first-commit
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
import { readdir } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
const repoRoot = path.resolve(import.meta.dirname, "..");
|
||||
const residualExtensions = new Set([".js", ".mjs", ".cjs"]);
|
||||
|
||||
const skippedDirectories = new Set([
|
||||
".agents",
|
||||
".astro",
|
||||
".claude",
|
||||
".claude-sessions",
|
||||
".codex",
|
||||
".cursor",
|
||||
".git",
|
||||
".od",
|
||||
".od-e2e",
|
||||
".opencode",
|
||||
".task",
|
||||
".tmp",
|
||||
".vite",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"out",
|
||||
]);
|
||||
|
||||
const allowedExactPaths = new Set([
|
||||
"packages/platform/esbuild.config.mjs",
|
||||
"packages/sidecar/esbuild.config.mjs",
|
||||
"packages/sidecar-proto/esbuild.config.mjs",
|
||||
// Maintainer utility scripts ported from the media branch. They are
|
||||
// executed directly by Node and are not loaded by the app runtime.
|
||||
"scripts/import-prompt-templates.mjs",
|
||||
"scripts/postinstall.mjs",
|
||||
"apps/packaged/esbuild.config.mjs",
|
||||
// Browser service workers must be served as JavaScript files.
|
||||
"apps/web/public/od-notifications-sw.js",
|
||||
"scripts/bake-html-ppt-examples.mjs",
|
||||
"scripts/scaffold-html-ppt-skills.mjs",
|
||||
"scripts/sync-hyperframes-skill.mjs",
|
||||
"scripts/verify-media-models.mjs",
|
||||
"tools/dev/bin/tools-dev.mjs",
|
||||
"tools/dev/esbuild.config.mjs",
|
||||
"tools/pack/bin/tools-pack.mjs",
|
||||
"tools/pack/esbuild.config.mjs",
|
||||
"tools/pack/resources/mac/notarize.cjs",
|
||||
]);
|
||||
|
||||
const allowedPathPrefixes = [
|
||||
"apps/daemon/dist/",
|
||||
"apps/web/.next/",
|
||||
"apps/web/out/",
|
||||
"generated/",
|
||||
"e2e/playwright-report/",
|
||||
"e2e/reports/html/",
|
||||
"e2e/reports/playwright-html-report/",
|
||||
"e2e/reports/test-results/",
|
||||
// Vendored upstream HyperFrames skill helper scripts.
|
||||
"skills/hyperframes/scripts/",
|
||||
// Vendored upstream html-ppt skill runtime assets (lewislulu/html-ppt-skill).
|
||||
"skills/html-ppt/assets/",
|
||||
"test-results/",
|
||||
"vendor/",
|
||||
];
|
||||
|
||||
function toRepositoryPath(filePath: string): string {
|
||||
return path.relative(repoRoot, filePath).split(path.sep).join("/");
|
||||
}
|
||||
|
||||
function isAllowedOutputPath(repositoryPath: string): boolean {
|
||||
if (allowedExactPaths.has(repositoryPath)) return true;
|
||||
return allowedPathPrefixes.some((prefix) => repositoryPath.startsWith(prefix));
|
||||
}
|
||||
|
||||
function isSkippedDirectoryName(directoryName: string): boolean {
|
||||
return skippedDirectories.has(directoryName) || directoryName === ".next" || directoryName.startsWith(".next-");
|
||||
}
|
||||
|
||||
async function collectResidualJavaScript(directory: string): Promise<string[]> {
|
||||
const entries = await readdir(directory, { withFileTypes: true });
|
||||
const residualFiles: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(directory, entry.name);
|
||||
const repositoryPath = toRepositoryPath(fullPath);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
if (isSkippedDirectoryName(entry.name) || isAllowedOutputPath(`${repositoryPath}/`)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
residualFiles.push(...(await collectResidualJavaScript(fullPath)));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entry.isFile() || !residualExtensions.has(path.extname(entry.name))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isAllowedOutputPath(repositoryPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
residualFiles.push(repositoryPath);
|
||||
}
|
||||
|
||||
return residualFiles;
|
||||
}
|
||||
|
||||
const residualFiles = await collectResidualJavaScript(repoRoot);
|
||||
|
||||
if (residualFiles.length > 0) {
|
||||
console.error("Residual project-owned JavaScript files found:");
|
||||
for (const filePath of residualFiles) {
|
||||
console.error(`- ${filePath}`);
|
||||
}
|
||||
console.error("Convert these files to TypeScript or add a documented generated/vendor/output allowlist entry.");
|
||||
process.exitCode = 1;
|
||||
} else {
|
||||
console.log("Residual JavaScript check passed: project-owned code is TypeScript-only.");
|
||||
}
|
||||
Reference in New Issue
Block a user