fix: merge Companion repository response validation
This commit is contained in:
@@ -1,9 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { invalidateAll } from '$app/navigation';
|
import { invalidateAll } from '$app/navigation';
|
||||||
import { workspaceApiPath, workspaceRoute } from '$lib/workspace/api/http';
|
import { workspaceApiPath, workspaceRoute } from '$lib/workspace/api/http';
|
||||||
|
import type { RepositorySourceKind } from '$lib/generated/workspace-api';
|
||||||
import type { PageProps } from './$types';
|
import type { PageProps } from './$types';
|
||||||
|
|
||||||
let { data }: PageProps = $props();
|
let { data }: PageProps = $props();
|
||||||
|
|
||||||
|
function sourceLabel(kind: RepositorySourceKind): string {
|
||||||
|
if (kind === 'local_path' || kind === 'file') return 'Local';
|
||||||
|
if (kind === 'invalid') return 'Invalid';
|
||||||
|
return 'Remote Git';
|
||||||
|
}
|
||||||
|
|
||||||
|
function supportsRepositoryAccess(kind: RepositorySourceKind): boolean {
|
||||||
|
return kind === 'ssh' || kind === 'http' || kind === 'https';
|
||||||
|
}
|
||||||
let showAddRepository = $state(false);
|
let showAddRepository = $state(false);
|
||||||
let repositoryId = $state('');
|
let repositoryId = $state('');
|
||||||
let displayName = $state('');
|
let displayName = $state('');
|
||||||
@@ -122,22 +133,22 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each data.repositories.items as repository (repository.repository_id)}
|
{#each data.repositories.items as repository (repository.id)}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a class="inline-link" href={workspaceRoute(data.workspaceId, `/repositories/${encodeURIComponent(repository.repository_id)}`)}>
|
<a class="inline-link" href={workspaceRoute(data.workspaceId, `/repositories/${encodeURIComponent(repository.id)}`)}>
|
||||||
<strong>{repository.display_name}</strong>
|
<strong>{repository.display_name}</strong>
|
||||||
</a>
|
</a>
|
||||||
<small><code>{repository.repository_id}</code></small>
|
<small><code>{repository.id}</code></small>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span>{repository.source.kind === 'local_path' ? 'Local' : 'Remote Git'}</span>
|
<span>{sourceLabel(repository.source.kind)}</span>
|
||||||
<small><code>{repository.source.uri}</code></small>
|
<small><code>{repository.source.uri}</code></small>
|
||||||
</td>
|
</td>
|
||||||
<td>{repository.default_selector ?? '—'}</td>
|
<td>{repository.default_selector ?? '—'}</td>
|
||||||
<td>{repository.observed.status}</td>
|
<td>{repository.observed_status}</td>
|
||||||
<td>
|
<td>
|
||||||
{#if repository.source.kind === 'remote_git'}
|
{#if supportsRepositoryAccess(repository.source.kind)}
|
||||||
<a class="inline-link" href={workspaceRoute(data.workspaceId, '/settings/repository-access')}>Configure access</a>
|
<a class="inline-link" href={workspaceRoute(data.workspaceId, '/settings/repository-access')}>Configure access</a>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="settings-muted-action">Not required</span>
|
<span class="settings-muted-action">Not required</span>
|
||||||
|
|||||||
@@ -1,34 +1,13 @@
|
|||||||
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
|
import { loadJson, workspaceApiPath } from "$lib/workspace/api/http";
|
||||||
|
import { parseRepositoryListResponse } from "$lib/workspace/api/workspace-model";
|
||||||
import type { PageLoad } from "./$types";
|
import type { PageLoad } from "./$types";
|
||||||
|
|
||||||
export type RepositorySource = {
|
|
||||||
kind: "local_path" | "remote_git";
|
|
||||||
uri: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type RepositorySummary = {
|
|
||||||
repository_id: string;
|
|
||||||
display_name: string;
|
|
||||||
kind: string;
|
|
||||||
provider: string;
|
|
||||||
source: RepositorySource;
|
|
||||||
default_selector?: string | null;
|
|
||||||
observed: {
|
|
||||||
status: string;
|
|
||||||
observed_at?: string | null;
|
|
||||||
};
|
|
||||||
diagnostics: Array<{ code: string; message: string }>;
|
|
||||||
};
|
|
||||||
|
|
||||||
type RepositoryListResponse = {
|
|
||||||
items: RepositorySummary[];
|
|
||||||
diagnostics: Array<{ code: string; message: string }>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const load: PageLoad = async ({ fetch, params }) => {
|
export const load: PageLoad = async ({ fetch, params }) => {
|
||||||
const repositories = await loadJson<RepositoryListResponse>(
|
const repositories = await loadJson(
|
||||||
fetch,
|
fetch,
|
||||||
workspaceApiPath(params.workspaceId, "/repositories"),
|
workspaceApiPath(params.workspaceId, "/repositories"),
|
||||||
|
undefined,
|
||||||
|
parseRepositoryListResponse,
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
workspaceId: params.workspaceId,
|
workspaceId: params.workspaceId,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
declare const Deno: {
|
declare const Deno: {
|
||||||
test(name: string, fn: () => void | Promise<void>): void;
|
test(name: string, fn: () => void | Promise<void>): void;
|
||||||
|
readTextFile(path: URL): Promise<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -108,3 +109,45 @@ Deno.test("workspace response requires the permission projection", () => {
|
|||||||
"permissions must be an object",
|
"permissions must be an object",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test("Repository settings consume the validated shared wire shape", async () => {
|
||||||
|
const [loadSource, pageSource] = await Promise.all([
|
||||||
|
Deno.readTextFile(
|
||||||
|
new URL(
|
||||||
|
"../src/routes/w/[workspaceId]/settings/repositories/+page.ts",
|
||||||
|
import.meta.url,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Deno.readTextFile(
|
||||||
|
new URL(
|
||||||
|
"../src/routes/w/[workspaceId]/settings/repositories/+page.svelte",
|
||||||
|
import.meta.url,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (
|
||||||
|
const token of [
|
||||||
|
"parseRepositoryListResponse",
|
||||||
|
"repository.id",
|
||||||
|
"repository.observed_status",
|
||||||
|
"sourceLabel(repository.source.kind)",
|
||||||
|
"supportsRepositoryAccess(repository.source.kind)",
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
if (!loadSource.includes(token) && !pageSource.includes(token)) {
|
||||||
|
throw new Error(`Repository settings should include ${token}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (
|
||||||
|
const staleToken of [
|
||||||
|
"repository.repository_id",
|
||||||
|
"repository.observed.status",
|
||||||
|
"repository.source.kind === 'remote_git'",
|
||||||
|
]
|
||||||
|
) {
|
||||||
|
if (loadSource.includes(staleToken) || pageSource.includes(staleToken)) {
|
||||||
|
throw new Error(`Repository settings must not use ${staleToken}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user