testMirrorNewNamespacesAndNearbyRenames
a mirror makes files for new namespaces, and renames reach files near it
import { okIn, tempDir } from "#codebase/tcmts/cli/test/e2e/harness"; import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; export const testMirrorNewNamespacesAndNearbyRenames = () => { const root = tempDir("mirror"); const repo = path.join(root, "near"); const cbDir = path.join(root, "near-cb"); fs.mkdirSync(path.join(repo, "src"), { recursive: true }); fs.mkdirSync(path.join(repo, "scripts")); fs.writeFileSync(path.join(repo, "package.json"), JSON.stringify({ type: "module" })); fs.writeFileSync(path.join(repo, "src", "math.ts"), `export function double(x: number): number {\n return x * 2;\n}\n`); // outside the mirror (src/), in the same repository const report = `import { double } from "../src/math.ts";\n\nconsole.log(double(21));\n`; fs.writeFileSync(path.join(repo, "scripts", "report.ts"), report); assert.equal(spawnSync("git", ["-C", repo, "init", "-q"]).status, 0); fs.mkdirSync(cbDir); const run = (...args: string[]) => okIn(cbDir, ...args); run("init"); run("import", path.join(repo, "src"), "--into", "app", "--mirror"); // a namespace with no file yet gets one, where the mirror reads it back as that namespace fs.writeFileSync(path.join(cbDir, "scratch.ts"), `// tcm: namespace app.extra\nimport { double } from "#codebase/app/math";\nexport const quad = (x: number): number => double(double(x));\n`); assert.match(run("add"), /mirror: wrote extra\.ts/); assert.equal(fs.readFileSync(path.join(repo, "src", "extra.ts"), "utf8"), `import { double } from "./math.ts";\n\nexport const quad = (x: number): number => double(double(x));\n`); // a rename reaches the script that uses it, outside the mirror assert.match(run("mv", "app.math.double", "app.math.twice"), /mv|moved/); assert.equal(fs.readFileSync(path.join(repo, "scripts", "report.ts"), "utf8"), report.replaceAll("double", "twice")); assert.match(fs.readFileSync(path.join(repo, "src", "extra.ts"), "utf8"), /twice\(twice\(x\)\)/); assert.match(run("mirror", "--check"), /the files hash to what the codebase holds/); // code edited in a scratch file that spells an import its own way is kept as the file spells it, so the next // write doesn't bring the scratch file's spelling in fs.writeFileSync(path.join(repo, "src", "paths.ts"), `import path from "node:path";\n\nexport const base = (p: string): string => path.basename(p);\n`); run("import", path.join(repo, "src"), "--into", "app", "--update"); fs.writeFileSync(path.join(cbDir, "scratch.ts"), `// tcm: namespace app.paths\nimport nodePath from "node:path";\nexport const base = (p: string): string => nodePath.basename(p).toUpperCase();\n`); run("update"); assert.match(fs.readFileSync(path.join(repo, "src", "paths.ts"), "utf8"), / path\.basename\(p\)\.toUpperCase\(\)/); assert.match(run("view", "app.paths.base"), / path\.basename\(p\)\.toUpperCase\(\)/); // in the codebase anything can be imported; in the files, what another file imports is exported where it is fs.appendFileSync(path.join(repo, "src", "paths.ts"), `\nconst SEP = "/";\n\nexport const join = (a: string, b: string): string => a + SEP + b;\n`); fs.writeFileSync(path.join(cbDir, "scratch.ts"), `// tcm: namespace app.extra\nimport { SEP } from "#codebase/app/paths";\nexport const under = (p: string): string => SEP + p;\n`); run("add"); assert.match(fs.readFileSync(path.join(repo, "src", "paths.ts"), "utf8"), /\nexport const SEP = "\/";\n/); assert.match(fs.readFileSync(path.join(repo, "src", "extra.ts"), "utf8"), /import \{ SEP \} from "\.\/paths\.ts";/); assert.match(run("mirror", "--check"), /the files hash to what the codebase holds/); // saved from a scratch file and then re-derived in the same update (it uses one that was): its new code is // written, not left as propagation-only fs.writeFileSync(path.join(repo, "src", "chain.ts"), `export const h = (): number => 1;\nexport const g = (): number => h() + 1;\nexport const f = (): number => g() * 10;\n`); fs.writeFileSync(path.join(cbDir, "scratch.ts"), `// tcm: namespace app.chain\nimport { g } from "#codebase/app/chain";\nexport const h = (): number => 2;\nexport const f = (): number => g() * 100;\n`); assert.match(run("update"), /mirror: wrote chain\.ts/); assert.equal(fs.readFileSync(path.join(repo, "src", "chain.ts"), "utf8"), `export const h = (): number => 2;\nexport const g = (): number => h() + 1;\nexport const f = (): number => g() * 100;\n`); assert.match(run("mirror", "--check"), /the files hash to what the codebase holds/); }