test: prove TechLog UI migration parity
This commit is contained in:
@@ -2,6 +2,7 @@ import { mkdir, readFile, readdir } from "node:fs/promises";
|
||||
|
||||
import { designSystemReportArtifactSchema } from "./contracts/release-artifacts.ts";
|
||||
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
|
||||
import { containsRawPaletteValue } from "./lib/design-system-source.ts";
|
||||
import path from "node:path";
|
||||
|
||||
import { REQUIRED_COMPONENT_TOKENS, REQUIRED_PRIMITIVE_TOKENS, REQUIRED_SEMANTIC_TOKENS } from "../src/presentation/design-system/tokens/token-contract.ts";
|
||||
@@ -109,7 +110,7 @@ for (const file of sources) {
|
||||
}
|
||||
if (
|
||||
!file.includes("src/presentation/design-system/tokens/") &&
|
||||
/(?:#[0-9a-f]{3,8}\b|oklch\(|rgba?\()/i.test(source)
|
||||
containsRawPaletteValue(source)
|
||||
) {
|
||||
failures.push(`raw palette value in ${file}`);
|
||||
}
|
||||
|
||||
@@ -21,13 +21,12 @@ import {
|
||||
} from "./lib/build-environment.ts";
|
||||
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
|
||||
import { CANONICAL_VITE_MANIFEST_PATH } from "./lib/build-manifest-outputs.ts";
|
||||
import {
|
||||
findViteDynamicRouteChunk,
|
||||
type ViteManifestRouteEntry,
|
||||
} from "./lib/vite-route-chunks.ts";
|
||||
|
||||
assertCiBuildEnvironment(process.env);
|
||||
type ViteManifestEntry = Readonly<{
|
||||
file: string;
|
||||
name?: string;
|
||||
isDynamicEntry?: boolean;
|
||||
}>;
|
||||
|
||||
const packageJson = parsePackageMetadata(
|
||||
JSON.parse(await readFile("package.json", "utf8")),
|
||||
@@ -63,9 +62,9 @@ const runtimeContracts: Readonly<Record<string, { moduleId: string }>> =
|
||||
ROUTE_RUNTIME_CONTRACT;
|
||||
for (const definition of Object.values(ROUTE_REGISTRY)) {
|
||||
const runtime = runtimeContracts[definition.routeId];
|
||||
const asset = Object.values(viteManifestObject).find(
|
||||
(entry) => entry.name === runtime?.moduleId && entry.isDynamicEntry,
|
||||
);
|
||||
const asset = runtime
|
||||
? findViteDynamicRouteChunk(viteManifestObject, runtime.moduleId)
|
||||
: undefined;
|
||||
if (!runtime || !asset?.file) {
|
||||
throw new Error(`Missing built route chunk: ${definition.routeId}`);
|
||||
}
|
||||
@@ -163,9 +162,9 @@ function parsePackageMetadata(value: unknown): Readonly<{
|
||||
|
||||
function parseViteManifest(
|
||||
value: unknown,
|
||||
): Readonly<Record<string, ViteManifestEntry>> {
|
||||
): Readonly<Record<string, ViteManifestRouteEntry>> {
|
||||
if (!isRecord(value)) throw new TypeError("Vite manifest must be an object");
|
||||
const entries: Record<string, ViteManifestEntry> = {};
|
||||
const entries: Record<string, ViteManifestRouteEntry> = {};
|
||||
for (const [key, candidate] of Object.entries(value)) {
|
||||
if (!isRecord(candidate) || typeof candidate.file !== "string") {
|
||||
throw new TypeError(`Invalid Vite manifest entry: ${key}`);
|
||||
@@ -176,6 +175,10 @@ function parseViteManifest(
|
||||
...(typeof candidate.isDynamicEntry === "boolean"
|
||||
? { isDynamicEntry: candidate.isDynamicEntry }
|
||||
: {}),
|
||||
...(Array.isArray(candidate.dynamicImports) &&
|
||||
candidate.dynamicImports.every((item) => typeof item === "string")
|
||||
? { dynamicImports: candidate.dynamicImports as string[] }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
return entries;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
const RAW_PALETTE_VALUE_PATTERN =
|
||||
/(?<![a-z0-9/_-])(?:#[0-9a-f]{3,8}\b|oklch\(|rgba?\()/i;
|
||||
|
||||
export function containsRawPaletteValue(source: string): boolean {
|
||||
return RAW_PALETTE_VALUE_PATTERN.test(source);
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
type ReleaseCandidateManifest,
|
||||
} from "./release-candidate.ts";
|
||||
import { verifyReleaseRuntimeCoherence } from "./release-runtime-coherence.ts";
|
||||
import { findViteDynamicRouteChunk } from "./vite-route-chunks.ts";
|
||||
import { digestReleaseInputFiles } from "./release-input-evidence.ts";
|
||||
import {
|
||||
compareStoredDependencyEvidence,
|
||||
@@ -1256,12 +1257,27 @@ async function verifyReleaseOutputs(
|
||||
ROUTE_RUNTIME_CONTRACT;
|
||||
for (const definition of Object.values(ROUTE_REGISTRY)) {
|
||||
const runtime = runtimeContracts[definition.routeId];
|
||||
const viteEntry = Object.values(viteManifest).find(
|
||||
(entry) =>
|
||||
isRecord(entry) &&
|
||||
entry.name === runtime?.moduleId &&
|
||||
entry.isDynamicEntry === true,
|
||||
);
|
||||
const viteEntry = runtime
|
||||
? findViteDynamicRouteChunk(
|
||||
Object.fromEntries(
|
||||
Object.entries(viteManifest).flatMap(([key, entry]) => {
|
||||
if (!isRecord(entry) || typeof entry.file !== "string") return [];
|
||||
return [[key, {
|
||||
file: entry.file,
|
||||
...(typeof entry.name === "string" ? { name: entry.name } : {}),
|
||||
...(typeof entry.isDynamicEntry === "boolean"
|
||||
? { isDynamicEntry: entry.isDynamicEntry }
|
||||
: {}),
|
||||
...(Array.isArray(entry.dynamicImports) &&
|
||||
entry.dynamicImports.every((item) => typeof item === "string")
|
||||
? { dynamicImports: entry.dynamicImports as string[] }
|
||||
: {}),
|
||||
}]];
|
||||
}),
|
||||
),
|
||||
runtime.moduleId,
|
||||
)
|
||||
: undefined;
|
||||
const file = isRecord(viteEntry) ? viteEntry.file : null;
|
||||
if (
|
||||
typeof file !== "string" ||
|
||||
|
||||
@@ -52,6 +52,7 @@ export const LOCAL_EVIDENCE_VERIFIER_SOURCE_PATHS = Object.freeze([
|
||||
"scripts/lib/secret-scan.ts",
|
||||
"scripts/lib/supply-chain.ts",
|
||||
"scripts/lib/validated-json-artifact.ts",
|
||||
"scripts/lib/vite-route-chunks.ts",
|
||||
"src/contracts/release-artifacts.ts",
|
||||
"src/features/installed-contract-contributions.ts",
|
||||
"src/features/installed-feature-contracts.ts",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
export type ViteManifestRouteEntry = Readonly<{
|
||||
file: string;
|
||||
name?: string;
|
||||
isDynamicEntry?: boolean;
|
||||
dynamicImports?: readonly string[];
|
||||
}>;
|
||||
|
||||
export function findViteDynamicRouteChunk(
|
||||
manifest: Readonly<Record<string, ViteManifestRouteEntry>>,
|
||||
moduleId: string,
|
||||
): ViteManifestRouteEntry | undefined {
|
||||
const dynamicallyImportedKeys = new Set(
|
||||
Object.values(manifest).flatMap((entry) => entry.dynamicImports ?? []),
|
||||
);
|
||||
return Object.entries(manifest).find(
|
||||
([key, entry]) =>
|
||||
entry.name === moduleId &&
|
||||
(entry.isDynamicEntry === true || dynamicallyImportedKeys.has(key)),
|
||||
)?.[1];
|
||||
}
|
||||
+13
-10
@@ -28,6 +28,10 @@ import {
|
||||
} from "./lib/build-manifest-outputs.ts";
|
||||
import { verifyReleaseRuntimeCoherence } from "./lib/release-runtime-coherence.ts";
|
||||
import { writeValidatedJsonArtifact } from "./lib/validated-json-artifact.ts";
|
||||
import {
|
||||
findViteDynamicRouteChunk,
|
||||
type ViteManifestRouteEntry,
|
||||
} from "./lib/vite-route-chunks.ts";
|
||||
import { releaseVerificationArtifactSchema } from "./contracts/release-artifacts.ts";
|
||||
|
||||
type CoherenceFixture = Readonly<{
|
||||
@@ -38,11 +42,6 @@ type CoherenceFixture = Readonly<{
|
||||
}>;
|
||||
type RuntimeConfigDocument = RuntimeConfigArtifact &
|
||||
Readonly<{ BUILD_ID: string; RELEASE_ID: string }>;
|
||||
type ViteManifestEntry = Readonly<{
|
||||
file: string;
|
||||
name?: string;
|
||||
isDynamicEntry?: boolean;
|
||||
}>;
|
||||
|
||||
export type ReleaseArtifactReader = (path: string) => Promise<unknown>;
|
||||
|
||||
@@ -170,9 +169,9 @@ const runtimeContracts: Readonly<Record<string, { moduleId: string }>> =
|
||||
ROUTE_RUNTIME_CONTRACT;
|
||||
for (const definition of Object.values(ROUTE_REGISTRY)) {
|
||||
const runtime = runtimeContracts[definition.routeId];
|
||||
const viteEntry = Object.values(viteManifestObject).find(
|
||||
(entry) => entry.name === runtime?.moduleId && entry.isDynamicEntry,
|
||||
);
|
||||
const viteEntry = runtime
|
||||
? findViteDynamicRouteChunk(viteManifestObject, runtime.moduleId)
|
||||
: undefined;
|
||||
const routeAsset = release.routeChunks[definition.chunkId];
|
||||
if (!runtime || !viteEntry || routeAsset !== viteEntry.file) {
|
||||
artifactMismatches.push(`routeChunk:mismatch:${definition.chunkId}`);
|
||||
@@ -315,9 +314,9 @@ function parseCompatibilityTuple(value: unknown, label: string): CompatibilityTu
|
||||
|
||||
function parseViteManifest(
|
||||
value: unknown,
|
||||
): Readonly<Record<string, ViteManifestEntry>> {
|
||||
): Readonly<Record<string, ViteManifestRouteEntry>> {
|
||||
const document = requireRecord(value, "Vite manifest");
|
||||
const entries: Record<string, ViteManifestEntry> = {};
|
||||
const entries: Record<string, ViteManifestRouteEntry> = {};
|
||||
for (const [key, candidate] of Object.entries(document)) {
|
||||
const entry = requireRecord(candidate, `Vite manifest entry ${key}`);
|
||||
entries[key] = {
|
||||
@@ -326,6 +325,10 @@ function parseViteManifest(
|
||||
...(typeof entry.isDynamicEntry === "boolean"
|
||||
? { isDynamicEntry: entry.isDynamicEntry }
|
||||
: {}),
|
||||
...(Array.isArray(entry.dynamicImports) &&
|
||||
entry.dynamicImports.every((item) => typeof item === "string")
|
||||
? { dynamicImports: entry.dynamicImports as string[] }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
return entries;
|
||||
|
||||
Reference in New Issue
Block a user