mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* Add design system import manifest schema * Generate hybrid design system imports * Read design system usage and cached manifests * Add design system pull-file tool * Show design system package evidence * Wire design system import semantics * Add design system package quality guard --------- Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local>
158 lines
5 KiB
TypeScript
158 lines
5 KiB
TypeScript
import express from 'express';
|
|
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
import http from 'node:http';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
|
|
import { registerDesignSystemToolRoutes } from '../src/design-system-tool-routes.js';
|
|
|
|
type JsonFetchResult = { status: number; body: Record<string, any> };
|
|
|
|
let server: http.Server | undefined;
|
|
|
|
afterEach(async () => {
|
|
await new Promise<void>((resolve, reject) => {
|
|
if (!server) return resolve();
|
|
server.close((error?: Error) => (error ? reject(error) : resolve()));
|
|
});
|
|
server = undefined;
|
|
});
|
|
|
|
function fresh(): string {
|
|
return mkdtempSync(path.join(tmpdir(), 'od-design-system-tool-routes-'));
|
|
}
|
|
|
|
function writeHybridDesignSystem(root: string, id: string): string {
|
|
const dir = path.join(root, id);
|
|
mkdirSync(path.join(dir, 'preview'), { recursive: true });
|
|
writeFileSync(path.join(dir, 'DESIGN.md'), '# Test\n');
|
|
writeFileSync(path.join(dir, 'tokens.css'), ':root { --bg: #fff; }');
|
|
writeFileSync(path.join(dir, 'components.html'), '<button>ok</button>');
|
|
writeFileSync(path.join(dir, 'preview', 'colors.html'), '<h1>Colors</h1>');
|
|
writeFileSync(path.join(dir, 'preview', 'spacing.html'), '<h1>Spacing</h1>');
|
|
writeFileSync(path.join(dir, 'manifest.json'), `${JSON.stringify({
|
|
schemaVersion: 'od-design-system-project/v1',
|
|
id,
|
|
name: 'Test',
|
|
category: 'Imported',
|
|
source: { type: 'local', path: '/tmp/source' },
|
|
files: {
|
|
design: 'DESIGN.md',
|
|
tokens: 'tokens.css',
|
|
components: 'components.html',
|
|
},
|
|
preview: {
|
|
dir: 'preview',
|
|
pages: [{ path: 'preview/colors.html', role: 'colors', title: 'Colors' }],
|
|
},
|
|
}, null, 2)}\n`);
|
|
return dir;
|
|
}
|
|
|
|
async function startRouteServer(options: {
|
|
builtInRoot: string;
|
|
userRoot: string;
|
|
activeDesignSystemId: string | null;
|
|
}): Promise<string> {
|
|
const app = express();
|
|
app.use(express.json());
|
|
registerDesignSystemToolRoutes(app, {
|
|
auth: {
|
|
authorizeToolRequest: (_req, _res, operation) => {
|
|
expect(operation).toBe('design-systems:read');
|
|
return {
|
|
token: 'token',
|
|
runId: 'run-1',
|
|
projectId: 'project-1',
|
|
allowedEndpoints: ['/api/tools/design-systems/read'],
|
|
allowedOperations: ['design-systems:read'],
|
|
issuedAt: new Date(0).toISOString(),
|
|
expiresAt: new Date(60_000).toISOString(),
|
|
};
|
|
},
|
|
},
|
|
http: {
|
|
sendApiError: (res, status, code, message, extras = {}) => {
|
|
res.status(status).json({ error: { code, message, ...extras } });
|
|
},
|
|
},
|
|
paths: {
|
|
DESIGN_SYSTEMS_DIR: options.builtInRoot,
|
|
USER_DESIGN_SYSTEMS_DIR: options.userRoot,
|
|
},
|
|
projects: {
|
|
getProject: () => ({
|
|
id: 'project-1',
|
|
designSystemId: options.activeDesignSystemId,
|
|
}),
|
|
},
|
|
});
|
|
|
|
server = app.listen(0);
|
|
await new Promise<void>((resolve) => server?.once('listening', resolve));
|
|
const address = server.address();
|
|
if (!address || typeof address === 'string') throw new Error('unexpected listen address');
|
|
return `http://127.0.0.1:${address.port}`;
|
|
}
|
|
|
|
async function jsonFetch(url: string, body: Record<string, unknown>): Promise<JsonFetchResult> {
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: 'Bearer token',
|
|
},
|
|
body: JSON.stringify(body),
|
|
});
|
|
return { status: response.status, body: await response.json() as Record<string, any> };
|
|
}
|
|
|
|
describe('design-system pull tool route', () => {
|
|
it('reads manifest-allowed files from the active design system', async () => {
|
|
const builtInRoot = fresh();
|
|
const userRoot = fresh();
|
|
writeHybridDesignSystem(builtInRoot, 'pull-brand');
|
|
const baseUrl = await startRouteServer({
|
|
builtInRoot,
|
|
userRoot,
|
|
activeDesignSystemId: 'pull-brand',
|
|
});
|
|
|
|
const response = await jsonFetch(`${baseUrl}/api/tools/design-systems/read`, {
|
|
path: 'preview/colors.html',
|
|
});
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(response.body.file).toMatchObject({
|
|
path: 'preview/colors.html',
|
|
encoding: 'utf8',
|
|
content: '<h1>Colors</h1>',
|
|
});
|
|
});
|
|
|
|
it('rejects unlisted files and non-active design-system ids', async () => {
|
|
const builtInRoot = fresh();
|
|
const userRoot = fresh();
|
|
writeHybridDesignSystem(builtInRoot, 'pull-brand');
|
|
const baseUrl = await startRouteServer({
|
|
builtInRoot,
|
|
userRoot,
|
|
activeDesignSystemId: 'pull-brand',
|
|
});
|
|
|
|
const unlisted = await jsonFetch(`${baseUrl}/api/tools/design-systems/read`, {
|
|
path: 'preview/spacing.html',
|
|
});
|
|
expect(unlisted.status).toBe(404);
|
|
expect(unlisted.body.error.code).toBe('DESIGN_SYSTEM_FILE_NOT_FOUND');
|
|
|
|
const mismatch = await jsonFetch(`${baseUrl}/api/tools/design-systems/read`, {
|
|
designSystemId: 'other-brand',
|
|
path: 'preview/colors.html',
|
|
});
|
|
expect(mismatch.status).toBe(403);
|
|
expect(mismatch.body.error.code).toBe('DESIGN_SYSTEM_DENIED');
|
|
});
|
|
});
|