mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
fix(daemon): honor artifact manifest entry refs
This commit is contained in:
parent
393bfd0c6e
commit
646fa370d2
3 changed files with 58 additions and 9 deletions
|
|
@ -201,10 +201,15 @@ export function validateArtifactManifestInput(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const safeEntry = typeof entry === 'string' ? entry : '';
|
const manifestEntry =
|
||||||
if (!safeEntry || safeEntry.length > MAX_ENTRY_LENGTH) {
|
typeof manifest.entry === 'string' && manifest.entry.trim()
|
||||||
return { ok: false, error: `artifact entry exceeds max length (${MAX_ENTRY_LENGTH})` };
|
? manifest.entry.trim()
|
||||||
|
: entry;
|
||||||
|
const entryErr = validateSupportingPath(manifestEntry);
|
||||||
|
if (entryErr) {
|
||||||
|
return { ok: false, error: `artifactManifest.entry ${entryErr}` };
|
||||||
}
|
}
|
||||||
|
const safeEntry = (manifestEntry as string).replace(/\\/g, '/');
|
||||||
|
|
||||||
return { ok: true, value: sanitizeManifest(manifest, safeEntry, options) };
|
return { ok: true, value: sanitizeManifest(manifest, safeEntry, options) };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -731,7 +731,7 @@ function buildProjectExportManifestResponse({
|
||||||
artifactSupporting.add(normalized);
|
artifactSupporting.add(normalized);
|
||||||
note(normalized, reason);
|
note(normalized, reason);
|
||||||
};
|
};
|
||||||
addManifestRef(manifest.entry, 'artifact-entry');
|
addManifestRef(manifest.entry, 'artifact-entry', { preferProjectRoot: true });
|
||||||
if (typeof manifest.primary === 'string') {
|
if (typeof manifest.primary === 'string') {
|
||||||
addManifestRef(manifest.primary, 'artifact-primary', { preferProjectRoot: true });
|
addManifestRef(manifest.primary, 'artifact-primary', { preferProjectRoot: true });
|
||||||
}
|
}
|
||||||
|
|
@ -798,12 +798,18 @@ function chooseExportManifestEntryFile(
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const manifest = file.artifactManifest;
|
const manifest = file.artifactManifest;
|
||||||
if (!manifest || typeof manifest !== 'object') continue;
|
if (!manifest || typeof manifest !== 'object') continue;
|
||||||
|
if (isInferredArtifactManifest(manifest)) continue;
|
||||||
if (manifest.primary === true) return file.name;
|
if (manifest.primary === true) return file.name;
|
||||||
if (typeof manifest.primary !== 'string') continue;
|
if (typeof manifest.primary === 'string') {
|
||||||
const rootPrimary = normalizeManifestProjectRootRef(manifest.primary);
|
const rootPrimary = normalizeManifestProjectRootRef(manifest.primary);
|
||||||
if (rootPrimary && filesByName.has(rootPrimary)) return rootPrimary;
|
if (rootPrimary && filesByName.has(rootPrimary)) return rootPrimary;
|
||||||
const ownerRelativePrimary = normalizeManifestProjectRef(manifest.primary, file.name);
|
const ownerRelativePrimary = normalizeManifestProjectRef(manifest.primary, file.name);
|
||||||
if (ownerRelativePrimary && filesByName.has(ownerRelativePrimary)) return ownerRelativePrimary;
|
if (ownerRelativePrimary && filesByName.has(ownerRelativePrimary)) return ownerRelativePrimary;
|
||||||
|
}
|
||||||
|
const rootEntry = normalizeManifestProjectRootRef(manifest.entry);
|
||||||
|
if (rootEntry && filesByName.has(rootEntry)) return rootEntry;
|
||||||
|
const ownerRelativeEntry = normalizeManifestProjectRef(manifest.entry, file.name);
|
||||||
|
if (ownerRelativeEntry && filesByName.has(ownerRelativeEntry)) return ownerRelativeEntry;
|
||||||
}
|
}
|
||||||
return files.find((file) => /(^|\/)index\.html?$/i.test(file.name))?.name
|
return files.find((file) => /(^|\/)index\.html?$/i.test(file.name))?.name
|
||||||
?? files.find((file) => file.kind === 'html')?.name
|
?? files.find((file) => file.kind === 'html')?.name
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,44 @@ describe('project export manifest route', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('uses artifact entry strings as project-relative entry refs without primary hints', async () => {
|
||||||
|
const projectId = await createProject({ kind: 'prototype' });
|
||||||
|
await writeFile(projectId, {
|
||||||
|
name: 'index.html',
|
||||||
|
content: '<!doctype html><main>fallback</main>',
|
||||||
|
});
|
||||||
|
await writeFile(projectId, {
|
||||||
|
name: 'reviewed.html',
|
||||||
|
content: '<!doctype html><main>reviewed</main>',
|
||||||
|
});
|
||||||
|
await writeFile(projectId, {
|
||||||
|
name: 'preview/wrapper.html',
|
||||||
|
content: '<!doctype html><iframe src="../reviewed.html"></iframe>',
|
||||||
|
artifactManifest: {
|
||||||
|
version: 1,
|
||||||
|
kind: 'html',
|
||||||
|
title: 'Review wrapper',
|
||||||
|
entry: 'reviewed.html',
|
||||||
|
renderer: 'html',
|
||||||
|
status: 'complete',
|
||||||
|
exports: ['html'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(`${baseUrl}/api/projects/${projectId}/export/manifest`);
|
||||||
|
expect(response.ok).toBe(true);
|
||||||
|
const body = await response.json() as {
|
||||||
|
entryFile: string;
|
||||||
|
files: Array<{ name: string; role: string; reasons: string[] }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(body.entryFile).toBe('reviewed.html');
|
||||||
|
expect(body.files.find((file) => file.name === 'reviewed.html')).toMatchObject({
|
||||||
|
role: 'entry',
|
||||||
|
reasons: expect.arrayContaining(['artifact-entry', 'project-entry-file']),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('rejects invalid project ids before listing files', async () => {
|
it('rejects invalid project ids before listing files', async () => {
|
||||||
const response = await fetch(`${baseUrl}/api/projects/bad:id/export/manifest`);
|
const response = await fetch(`${baseUrl}/api/projects/bad:id/export/manifest`);
|
||||||
expect(response.status).toBe(400);
|
expect(response.status).toBe(400);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue