128 lines
5.8 KiB
TypeScript
128 lines
5.8 KiB
TypeScript
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
collectRemovedModuleSourceFiles,
|
|
findRemovedModuleMatches,
|
|
renderRemovedModuleScanResult,
|
|
scanRemovedModules,
|
|
} from "./scan-removed-modules.ts";
|
|
|
|
const tempRoots: string[] = [];
|
|
|
|
async function createFixtureRoot() {
|
|
const root = await mkdtemp(path.join(os.tmpdir(), "removed-modules-"));
|
|
tempRoots.push(root);
|
|
return root;
|
|
}
|
|
|
|
async function writeFixtureFile(root: string, relativePath: string, content: string) {
|
|
const absolutePath = path.join(root, relativePath);
|
|
await mkdir(path.dirname(absolutePath), { recursive: true });
|
|
await writeFile(absolutePath, content, "utf8");
|
|
return absolutePath;
|
|
}
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
});
|
|
|
|
describe("removed-module scanner", () => {
|
|
it("passes for clean runtime roots and reports observability counters", async () => {
|
|
const root = await createFixtureRoot();
|
|
await writeFixtureFile(root, "apps/web/app/root.tsx", "export const Root = () => null;\n");
|
|
await writeFixtureFile(root, "apps/worker/index.mjs", "export const worker = true;\n");
|
|
await writeFixtureFile(root, "packages/core/manifest.json", "{\"name\":\"core\"}\n");
|
|
|
|
const result = await scanRemovedModules(root);
|
|
const output = renderRemovedModuleScanResult(result);
|
|
|
|
expect(result.roots).toEqual(["apps", "packages"]);
|
|
expect(result.missingRoots).toEqual([]);
|
|
expect(result.files).toEqual(["apps/web/app/root.tsx", "apps/worker/index.mjs", "packages/core/manifest.json"]);
|
|
expect(result.diagnostics).toEqual([]);
|
|
expect(output).toContain("PASS removed-module scan");
|
|
expect(output).toContain("files scanned: 3");
|
|
expect(output).toContain("matches found: 0");
|
|
});
|
|
|
|
it("detects each forbidden runtime group with path, line, and excerpt", async () => {
|
|
const root = await createFixtureRoot();
|
|
await writeFixtureFile(
|
|
root,
|
|
"app/runtime.ts",
|
|
[
|
|
"const mailer = 'postmark';",
|
|
"const referral = 'Rewardful';",
|
|
"const block = 'PageBlocks';",
|
|
"const demo = 'my-site-demo';",
|
|
].join("\n"),
|
|
);
|
|
|
|
const result = await scanRemovedModules(root, ["app"]);
|
|
|
|
expect(result.diagnostics).toEqual([
|
|
{ id: "postmark", file: "app/runtime.ts", line: 1, excerpt: "const mailer = 'postmark';" },
|
|
{ id: "rewardful", file: "app/runtime.ts", line: 2, excerpt: "const referral = 'Rewardful';" },
|
|
{ id: "pageblocks", file: "app/runtime.ts", line: 3, excerpt: "const block = 'PageBlocks';" },
|
|
{ id: "site-demo", file: "app/runtime.ts", line: 4, excerpt: "const demo = 'my-site-demo';" },
|
|
]);
|
|
expect(renderRemovedModuleScanResult(result)).toContain("app/runtime.ts:4: [site-demo] const demo = 'my-site-demo';");
|
|
});
|
|
|
|
it("detects supported site-demo spellings and singular PageBlock", () => {
|
|
const diagnostics = findRemovedModuleMatches("Site-Demo\nsite demo\ndemo site\nSiteDemo\nPageBlock\n", "app/demo.ts");
|
|
|
|
expect(diagnostics.map((diagnostic) => diagnostic.id)).toEqual(["site-demo", "site-demo", "site-demo", "site-demo", "pageblocks"]);
|
|
});
|
|
|
|
it("ignores dependency, build, generated, cache, TC, optional plugin, test, fixture, story, and mock paths", async () => {
|
|
const root = await createFixtureRoot();
|
|
await writeFixtureFile(root, "app/entry.ts", "export const entry = true;\n");
|
|
await writeFixtureFile(root, "app/node_modules/pkg/index.ts", "postmark\n");
|
|
await writeFixtureFile(root, "app/build/server.js", "rewardful\n");
|
|
await writeFixtureFile(root, "app/.cache/cache.ts", "PageBlocks\n");
|
|
await writeFixtureFile(root, "app/.tc/plan.ts", "SiteDemo\n");
|
|
await writeFixtureFile(root, "app/generated/client.ts", "postmark\n");
|
|
await writeFixtureFile(root, "app/component.test.ts", "rewardful\n");
|
|
await writeFixtureFile(root, "app/component.stories.tsx", "PageBlocks\n");
|
|
await writeFixtureFile(root, "app/fixtures/data.ts", "demo site\n");
|
|
await writeFixtureFile(root, "app/__mocks__/mail.ts", "postmark\n");
|
|
await writeFixtureFile(root, "packages/plugins/index.ts", "rewardful\n");
|
|
await writeFixtureFile(root, "packages/core/types.d.ts", "declare const SiteDemo: string;\n");
|
|
await writeFixtureFile(root, "packages/core/bundle.min.js", "postmark\n");
|
|
await writeFixtureFile(root, "packages/core/source.js.map", "rewardful\n");
|
|
|
|
const result = await scanRemovedModules(root, ["app", "packages"]);
|
|
|
|
expect(result.files).toEqual(["app/entry.ts"]);
|
|
expect(result.diagnostics).toEqual([]);
|
|
});
|
|
|
|
it("records absent roots without failing collection", async () => {
|
|
const root = await createFixtureRoot();
|
|
await writeFixtureFile(root, "packages/core/index.ts", "export const core = true;\n");
|
|
|
|
const result = await collectRemovedModuleSourceFiles(root, ["app", "apps", "packages"]);
|
|
|
|
expect(result.roots).toEqual(["packages"]);
|
|
expect(result.missingRoots).toEqual(["app", "apps"]);
|
|
expect(result.files.map((file) => path.relative(root, file).split(path.sep).join("/"))).toEqual(["packages/core/index.ts"]);
|
|
});
|
|
|
|
it("scans markdown and mdx files inside runtime roots", async () => {
|
|
const root = await createFixtureRoot();
|
|
await writeFixtureFile(root, "packages/docs/runtime.md", "Legacy PageBlocks reference\n");
|
|
await writeFixtureFile(root, "packages/docs/runtime.mdx", "Legacy site demo reference\n");
|
|
|
|
const result = await scanRemovedModules(root, ["packages"]);
|
|
|
|
expect(result.diagnostics).toEqual([
|
|
{ id: "pageblocks", file: "packages/docs/runtime.md", line: 1, excerpt: "Legacy PageBlocks reference" },
|
|
{ id: "site-demo", file: "packages/docs/runtime.mdx", line: 1, excerpt: "Legacy site demo reference" },
|
|
]);
|
|
});
|
|
});
|