60 lines
5.5 KiB
JavaScript
60 lines
5.5 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { createRequire } from 'node:module';
|
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
import { createHash } from 'node:crypto';
|
|
import { parse } from '@babel/parser';
|
|
import postcss from 'postcss';
|
|
const require = createRequire(import.meta.url);
|
|
const base = '0b2bc376aa8b6af9d2492b5620d8ae776391508d';
|
|
const output = '_local/quality-20261010';
|
|
const files = [...new Set(execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard', '-z'], { encoding: 'utf8' }).split('\0'))].filter(Boolean);
|
|
const original = f => execFileSync('git', ['show', `${base}:${f}`], { encoding: 'utf8', maxBuffer: 10e6 });
|
|
const read = f => fs.readFileSync(f, 'utf8');
|
|
const hash = v => createHash('sha256').update(v).digest('hex');
|
|
const protectedFiles = ['themes/default.json', 'packages/ui/src/theme/default.css', 'packages/ui/src/theme/primitives.css', 'packages/ui/src/theme/default-values.generated.ts', 'packages/ui/src/components/ui/tabs.tsx', 'packages/ui/src/theme/tabs.css', 'packages/ui/src/components/ui/carousel.tsx', 'packages/ui/src/theme/carousel.css', 'docs/guidelines/publisher-additional-review-2026-10-10.md'];
|
|
const preservation = protectedFiles.map(file => ({ file, unchanged: hash(original(file)) === hash(read(file)) }));
|
|
const cssImports = [];
|
|
for (const file of files.filter(f => f.endsWith('.css'))) postcss.parse(read(file)).walkAtRules('import', n => {
|
|
const spec = n.params.match(/^["']([^"']+)["']/)?.[1];
|
|
if (!spec) return;
|
|
let resolved;
|
|
try { resolved = spec.startsWith('.') ? path.resolve(path.dirname(file), spec) : require.resolve(spec); } catch {}
|
|
if (!resolved && !spec.startsWith('.')) {
|
|
try {
|
|
const pieces = spec.split('/');
|
|
const pkg = pieces.slice(0, spec.startsWith('@') ? 2 : 1).join('/');
|
|
const directory = path.resolve('node_modules', pkg);
|
|
const manifest = JSON.parse(read(path.join(directory, 'package.json')));
|
|
const entry = manifest.exports?.[spec === pkg ? '.' : `.${spec.slice(pkg.length)}`];
|
|
if (entry?.style) resolved = path.join(directory, entry.style);
|
|
} catch {}
|
|
}
|
|
cssImports.push({ file, spec, resolved, exists: Boolean(resolved && fs.existsSync(resolved)) });
|
|
});
|
|
const exports = [];
|
|
for (const pkg of ['ui', 'center-graphic']) {
|
|
const dir = `packages/${pkg}`;
|
|
const manifest = JSON.parse(read(`${dir}/package.json`));
|
|
const check = (v, key) => typeof v === 'string' ? exports.push({ package: manifest.name, key, file: `${dir}/${v}`, exists: fs.existsSync(path.join(dir, v)) }) : Object.entries(v).forEach(([name, value]) => check(value, `${key}/${name}`));
|
|
Object.entries(manifest.exports).forEach(([key, value]) => check(value, key));
|
|
}
|
|
const walk = (n, fn) => { if (!n || typeof n !== 'object') return; if (n.type) fn(n); for (const [k, v] of Object.entries(n)) if (!['loc', 'start', 'end', 'comments'].includes(k)) Array.isArray(v) ? v.forEach(x => walk(x, fn)) : walk(v, fn); };
|
|
const clean = (key, value) => ['start', 'end', 'loc', 'extra', 'comments', 'leadingComments', 'trailingComments', 'innerComments'].includes(key) ? undefined : value;
|
|
const tests = text => { const result = new Map(); walk(parse(text, { sourceType: 'module' }), n => { if (n.type === 'CallExpression' && n.callee.name === 'test' && n.arguments[0]?.type === 'StringLiteral') result.set(n.arguments[0].value, JSON.stringify(n, clean)); }); return result; };
|
|
const testPreservation = ['scripts/ai-generation-guard.test.mjs', 'scripts/publisher-components.test.mjs'].map(file => { const before = tests(original(file)), after = tests(read(file)); return { file, before: before.size, after: after.size, unchangedBodies: [...before].filter(([name, value]) => after.get(name) === value).length }; });
|
|
const snapshot = 'D:/디자인시스템/HMDesign-AI-Generation-Test/retest-20261010/reference/storybook-static/index.json';
|
|
const beforeIDs = Object.keys(JSON.parse(read(snapshot)).entries).sort();
|
|
const afterIDs = Object.keys(JSON.parse(read('storybook-static/index.json')).entries).sort();
|
|
const crossTokens = [];
|
|
for (const id of ['02', '06', '07']) {
|
|
const target = `D:/디자인시스템/HMDesign-AI-Generation-Test/retest-20261010/cross-model/${id}`;
|
|
const r = spawnSync(process.execPath, ['scripts/ai-generation-guard.mjs', '--target', target, '--scope', 'FULL_SCREEN', '--mode', id === '07' ? 'DASHBOARD_ONLY' : 'GNB_ONLY', '--result', 'index.html', '--json'], { encoding: 'utf8' });
|
|
fs.writeFileSync(`${output}/cross-existing-${id}.json`, r.stdout);
|
|
const report = JSON.parse(r.stdout); crossTokens.push({ id, guard: report.status, tokenFindings: report.issues.filter(i => i.code.startsWith('TOKEN')) });
|
|
}
|
|
const result = { base, preservation, testPreservation, cssImports: { count: cssImports.length, missing: cssImports.filter(i => !i.exists) }, exports: { count: exports.length, missing: exports.filter(i => !i.exists) }, storyIDs: { before: beforeIDs.length, after: afterIDs.length, deleted: beforeIDs.filter(id => !afterIDs.includes(id)), added: afterIDs.filter(id => !beforeIDs.includes(id)) }, existingLowReasoningCases: { note: 'Reinspection of preserved historical output, not a new cross-model generation', cases: crossTokens } };
|
|
fs.writeFileSync(`${output}/contract-audit.json`, JSON.stringify(result, null, 2));
|
|
console.log(JSON.stringify(result, null, 2));
|
|
if (preservation.some(i => !i.unchanged) || testPreservation.some(i => i.before !== i.unchangedBodies) || result.cssImports.missing.length || result.exports.missing.length || result.storyIDs.deleted.length) process.exitCode = 1;
|