diff --git a/.fallowrc.jsonc b/.fallowrc.jsonc index 480ef20940..df303ad2d1 100644 --- a/.fallowrc.jsonc +++ b/.fallowrc.jsonc @@ -26,8 +26,9 @@ // page.addScriptTag (see layout.ts / validate.ts) — referenced by file // path, never imported, so they have no import-graph referrer. "packages/cli/src/commands/layout-audit.browser.js", - "packages/cli/src/commands/contrast-audit.browser.js", "packages/cli/src/commands/motion-sample.browser.js", + // @hyperframes/validate contrast audit injects it via page.addScriptTag (raw string, no import referrer). + "packages/validate/src/contrast-audit.browser.js", // Worker entry points loaded dynamically by their *Pool.ts companions. "packages/producer/src/services/pngDecodeBlitWorker.ts", "packages/producer/src/services/shaderTransitionWorker.ts", @@ -305,6 +306,13 @@ // new package makes fallow see it as a fresh finding; the underlying clone // predates this refactor. "packages/parsers/src/hfIds.ts", + // @hyperframes/validate vendors these from cli/src/utils/* so it needn't import @hyperframes/cli (which depends on it); a shared low-level package is a follow-up. + "packages/validate/src/staticProjectServer.ts", + "packages/validate/src/compositionViewport.ts", + "packages/validate/src/dom.ts", + "packages/validate/src/errorMessage.ts", + // browserValidate.ts: raceMediaReady vs auditClipDurations' in-page closure share the race wiring; intentional — the closure is Puppeteer-serialized into a separate realm (see raceMediaReady doc). + "packages/validate/src/browserValidate.ts", // Parser test files: parallel arrange/act/assert test cases — pre-existing // duplication moved from packages/core/src/parsers/. "packages/parsers/src/gsapParser.test.ts", @@ -573,6 +581,8 @@ "packages/studio/src/player/components/TimelineCanvas.tsx", "packages/studio/src/player/components/TimelineClip.tsx", "packages/studio/src/player/components/TimelineClipDiamonds.tsx", + // WCAG contrast sampler ported wholesale into @hyperframes/validate; high cyclomatic is inherent to the pixel scan (no new logic). + "packages/validate/src/contrast-audit.browser.js", ], }, } diff --git a/bun.lock b/bun.lock index cac1f849d6..c95cdb9c9d 100644 --- a/bun.lock +++ b/bun.lock @@ -22,7 +22,7 @@ }, "packages/aws-lambda": { "name": "@hyperframes/aws-lambda", - "version": "0.7.22", + "version": "0.7.38", "dependencies": { "@aws-sdk/client-s3": "^3.700.0", "@aws-sdk/client-sfn": "^3.700.0", @@ -54,7 +54,7 @@ }, "packages/cli": { "name": "@hyperframes/cli", - "version": "0.7.22", + "version": "0.7.38", "bin": { "hyperframes": "./dist/cli.js", }, @@ -86,6 +86,7 @@ "@hyperframes/producer": "workspace:*", "@hyperframes/studio": "workspace:*", "@hyperframes/studio-server": "workspace:*", + "@hyperframes/validate": "workspace:*", "@types/adm-zip": "^0.5.7", "@types/fontkit": "^2.0.9", "@types/mime-types": "^3.0.1", @@ -103,7 +104,7 @@ }, "packages/core": { "name": "@hyperframes/core", - "version": "0.7.22", + "version": "0.7.38", "dependencies": { "@chenglou/pretext": "^0.0.5", "@hyperframes/lint": "workspace:*", @@ -128,7 +129,7 @@ }, "packages/engine": { "name": "@hyperframes/engine", - "version": "0.7.22", + "version": "0.7.38", "dependencies": { "@hono/node-server": "^1.13.0", "@hyperframes/core": "workspace:^", @@ -146,7 +147,7 @@ }, "packages/gcp-cloud-run": { "name": "@hyperframes/gcp-cloud-run", - "version": "0.7.22", + "version": "0.7.38", "dependencies": { "@google-cloud/storage": "^7.14.0", "@google-cloud/workflows": "^4.2.0", @@ -166,7 +167,7 @@ }, "packages/lint": { "name": "@hyperframes/lint", - "version": "0.7.22", + "version": "0.7.38", "dependencies": { "@hyperframes/parsers": "workspace:*", "linkedom": "^0.18.12", @@ -182,7 +183,7 @@ }, "packages/parsers": { "name": "@hyperframes/parsers", - "version": "0.7.22", + "version": "0.7.38", "dependencies": { "@babel/parser": "^7.27.0", "acorn": "^8.17.0", @@ -202,7 +203,7 @@ }, "packages/player": { "name": "@hyperframes/player", - "version": "0.7.22", + "version": "0.7.38", "dependencies": { "@hyperframes/core": "workspace:*", }, @@ -217,7 +218,7 @@ }, "packages/producer": { "name": "@hyperframes/producer", - "version": "0.7.22", + "version": "0.7.38", "dependencies": { "@fontsource/archivo-black": "^5.2.8", "@fontsource/eb-garamond": "^5.2.7", @@ -261,7 +262,7 @@ }, "packages/sdk": { "name": "@hyperframes/sdk", - "version": "0.7.22", + "version": "0.7.38", "dependencies": { "@hyperframes/core": "workspace:*", "@hyperframes/parsers": "workspace:*", @@ -288,7 +289,7 @@ }, "packages/shader-transitions": { "name": "@hyperframes/shader-transitions", - "version": "0.7.22", + "version": "0.7.38", "dependencies": { "html2canvas": "^1.4.1", }, @@ -300,7 +301,7 @@ }, "packages/studio": { "name": "@hyperframes/studio", - "version": "0.7.22", + "version": "0.7.38", "dependencies": { "@codemirror/autocomplete": "^6.20.1", "@codemirror/commands": "^6.10.3", @@ -348,7 +349,7 @@ }, "packages/studio-server": { "name": "@hyperframes/studio-server", - "version": "0.7.22", + "version": "0.7.38", "dependencies": { "@hyperframes/core": "workspace:*", "@hyperframes/parsers": "workspace:*", @@ -365,6 +366,25 @@ "vitest": "^3.2.4", }, }, + "packages/validate": { + "name": "@hyperframes/validate", + "version": "0.7.38", + "dependencies": { + "@hyperframes/core": "workspace:*", + "@hyperframes/engine": "workspace:*", + "@hyperframes/producer": "workspace:*", + "linkedom": "^0.18.12", + "puppeteer-core": "24.40.0", + }, + "devDependencies": { + "@types/node": "^25.0.10", + "happy-dom": "^20.9.0", + "tsup": "^8.0.0", + "tsx": "^4.21.0", + "typescript": "^5.0.0", + "vitest": "^3.2.4", + }, + }, }, "overrides": { "@types/react": "^19.0.0", @@ -741,6 +761,8 @@ "@hyperframes/studio-server": ["@hyperframes/studio-server@workspace:packages/studio-server"], + "@hyperframes/validate": ["@hyperframes/validate@workspace:packages/validate"], + "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], @@ -2177,6 +2199,8 @@ "@google-cloud/storage/google-auth-library": ["google-auth-library@9.15.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^6.1.1", "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", "jws": "^4.0.0" } }, "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng=="], + "@hyperframes/validate/puppeteer-core": ["puppeteer-core@24.40.0", "", { "dependencies": { "@puppeteer/browsers": "2.13.0", "chromium-bidi": "14.0.0", "debug": "^4.4.3", "devtools-protocol": "0.0.1581282", "typed-query-selector": "^2.12.1", "webdriver-bidi-protocol": "0.4.1", "ws": "^8.19.0" } }, "sha512-MWL3XbUCfVgGR0gRsidzT6oKJT2QydPLhMITU6HoVWiiv4gkb6gJi3pcdAa8q4HwjBTbqISOWVP4aJiiyUJvag=="], + "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "@isaacs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], @@ -2295,6 +2319,10 @@ "@google-cloud/storage/google-auth-library/gcp-metadata": ["gcp-metadata@6.1.1", "", { "dependencies": { "gaxios": "^6.1.1", "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" } }, "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A=="], + "@hyperframes/validate/puppeteer-core/@puppeteer/browsers": ["@puppeteer/browsers@2.13.0", "", { "dependencies": { "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", "semver": "^7.7.4", "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" } }, "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA=="], + + "@hyperframes/validate/puppeteer-core/devtools-protocol": ["devtools-protocol@0.0.1581282", "", {}, "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ=="], + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], diff --git a/package.json b/package.json index 33b4b16ce2..b998490327 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "type": "module", "scripts": { "dev": "bun run studio", - "build": "bun run --filter '@hyperframes/{parsers,lint,studio-server}' build && bun run --filter @hyperframes/core build && bun run --filter '@hyperframes/{core,engine,producer,player,studio,shader-transitions,aws-lambda,gcp-cloud-run,sdk}' build && bun run --filter @hyperframes/cli build", + "build": "bun run --filter '@hyperframes/{parsers,lint,studio-server}' build && bun run --filter @hyperframes/core build && bun run --filter '@hyperframes/{core,engine,producer,player,studio,shader-transitions,aws-lambda,gcp-cloud-run,sdk}' build && bun run --filter @hyperframes/validate build && bun run --filter @hyperframes/cli build", "build:producer": "bun run --filter @hyperframes/producer build", "studio": "bun run --filter @hyperframes/studio dev", "build:hyperframes-runtime": "bun run --filter @hyperframes/core build:hyperframes-runtime", diff --git a/packages/cli/package.json b/packages/cli/package.json index d907879201..7b2dc37457 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -54,6 +54,7 @@ "@hyperframes/producer": "workspace:*", "@hyperframes/studio": "workspace:*", "@hyperframes/studio-server": "workspace:*", + "@hyperframes/validate": "workspace:*", "@types/adm-zip": "^0.5.7", "@types/fontkit": "^2.0.9", "@types/mime-types": "^3.0.1", diff --git a/packages/cli/scripts/build-copy.mjs b/packages/cli/scripts/build-copy.mjs index 55f198e91a..11d5f7355e 100644 --- a/packages/cli/scripts/build-copy.mjs +++ b/packages/cli/scripts/build-copy.mjs @@ -99,7 +99,8 @@ async function main() { cpSync(layoutAuditScript, join(DIST, "commands", "layout-audit.browser.js")); } - const contrastAuditScript = join(CLI_ROOT, "src", "commands", "contrast-audit.browser.js"); + // Owned by @hyperframes/validate; bundled CLI loads it from dist/commands via page.addScriptTag. + const contrastAuditScript = join(CLI_ROOT, "..", "validate", "src", "contrast-audit.browser.js"); if (existsSync(contrastAuditScript)) { cpSync(contrastAuditScript, join(DIST, "commands", "contrast-audit.browser.js")); } diff --git a/packages/cli/src/commands/layout-audit.browser.test.ts b/packages/cli/src/commands/layout-audit.browser.test.ts index e084637f29..bea24800ab 100644 --- a/packages/cli/src/commands/layout-audit.browser.test.ts +++ b/packages/cli/src/commands/layout-audit.browser.test.ts @@ -6,7 +6,6 @@ import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const script = readFileSync(join(__dirname, "layout-audit.browser.js"), "utf-8"); -const contrastScript = readFileSync(join(__dirname, "contrast-audit.browser.js"), "utf-8"); interface RectInput { left: number; @@ -196,47 +195,6 @@ describe("layout-audit.browser content overlap", () => { }); }); -describe("contrast-audit.browser clip-path visibility", () => { - afterEach(() => { - vi.restoreAllMocks(); - vi.unstubAllGlobals(); - document.body.innerHTML = ""; - delete (document as unknown as { elementFromPoint?: unknown }).elementFromPoint; - delete (window as unknown as { __contrastAudit?: unknown }).__contrastAudit; - }); - - it("excludes text clipped to nothing by clip-path from contrast reports", async () => { - document.body.innerHTML = ` -