writeShim
uses node:path
Regenerate .tcm/codebase/** so scratch files can import { x } from "#codebase/ns": a file per namespace.
With only, just those namespaces' files (the ones whose names changed), and only the modules they import that
aren't there yet: the rest of the shim, and every module already written, are as they were.
import { exportId } from "#codebase/tcmts/core/hash"; import { isConflicted } from "#codebase/tcmts/core/types"; import { augmentationRoot, isAugmentation, lastSegment, materialize, moduleFile, writeIfChanged, writePairModule, } from "#codebase/tcmts/loader"; import type { DefSource, Layout, ShimName } from "#codebase/tcmts/loader"; import fs from "node:fs"; import path_1 from "node:path"; export function writeShim( names: ShimName[], src: DefSource, lay: Layout, only?: Set<string>, ): void { const nsOf = (p: string) => (p.includes(".") ? p.slice(0, p.lastIndexOf(".")) : ""); const fileOf = (ns: string) => path_1.join(lay.shimDir, ns ? `${ns.replaceAll(".", "/")}.ts` : "index.ts"); const byPath = new Map<string, string[]>(); for (const n of names) if (!only || only.has(nsOf(n.path))) byPath.set(n.path, [...(byPath.get(n.path) ?? []), n.hash]); const files = new Map<string, string[]>(); const hashes: string[] = []; // an augmentation has no name to export: a type package each instead, which every check lists (`augmentationRoot`) const augmentations = names.filter( (n) => ["global", "augments"].includes(lastSegment(n.path)) && isAugmentation(src.get(n.hash)!), ); const augmented = new Set(augmentations.map((n) => n.path)); const augRoot = augmentationRoot(lay); for (const n of augmentations) { const file = path_1.join(augRoot, n.path, "index.d.ts"); const module = path_1.join(lay.cacheDir, moduleFile(src.get(n.hash)!.componentHash, src)); files.set(file, [ `import "${path_1.relative(path_1.dirname(file), module).split(path_1.sep).join("/")}";`, ]); hashes.push(n.hash); } for (const [name, hs] of byPath) { if (augmented.has(name)) continue; // a file whose own name isn't an identifier (`getting-started`) is exported by its name in quotes const leaf = lastSegment(name); const exported = /^[A-Za-z_$][\w$]*$/.test(leaf) ? leaf : JSON.stringify(leaf); const file = fileOf(nsOf(name)); const lines = files.get(file) ?? []; files.set(file, lines); const relTo = (f: string) => { const rel = path_1 .relative(path_1.dirname(file), path_1.join(lay.cacheDir, f)) .split(path_1.sep) .join("/"); return rel.startsWith(".") ? rel : "./" + rel; }; const recs = hs.map((h) => src.get(h)!); if (isConflicted(recs.map((r) => r.kind))) { lines.push( `// ${lastSegment(name)}: conflicted (${hs.length} definitions) — see \`tcm todo\``, ); continue; } hashes.push(...hs); if (recs.length === 2 && exported === leaf) { lines.push( `export { ${lastSegment(name)} } from "${relTo(writePairModule(lastSegment(name), recs, src, lay))}";`, ); continue; } const rec = recs[0]!; lines.push( `export ${rec.kind === "type" ? "type " : ""}{ ${exportId(rec.hash)} as ${exported} } from "${relTo(moduleFile(rec.componentHash, src))}";`, ); } materialize(hashes, src, lay, only ? { trustExisting: true } : {}); // (the root's file is always there: it's what `#codebase` itself imports) const index = fileOf(""); if (!only || only.has("")) files.set(index, files.get(index) ?? []); for (const [file, lines] of files) writeIfChanged( file, `// generated by tcm — do not edit\n${lines.sort().join("\n")}\nexport {};\n`, ); for (const d of fs.existsSync(augRoot) ? fs.readdirSync(augRoot) : []) if (!augmented.has(d)) fs.rmSync(path_1.join(augRoot, d), { recursive: true, force: true }); // a namespace with no names left has no file if (only) { for (const ns of only) if (ns && !files.has(fileOf(ns))) fs.rmSync(fileOf(ns), { force: true }); return; } const walk = (dir: string): string[] => fs.existsSync(dir) ? fs .readdirSync(dir, { withFileTypes: true }) .flatMap((e) => e.isDirectory() ? walk(path_1.join(dir, e.name)) : [path_1.join(dir, e.name)], ) : []; for (const f of walk(lay.shimDir)) if (!files.has(f)) fs.rmSync(f); }
uses
- tcmts.core.hash.exportId
- tcmts.core.types.isConflicted
- tcmts.loader.DefSource
- tcmts.loader.Layout
- tcmts.loader.ShimName
- tcmts.loader.augmentationRoot
- tcmts.loader.isAugmentation
- tcmts.loader.lastSegment
- tcmts.loader.materialize
- tcmts.loader.moduleFile
- tcmts.loader.writeIfChanged
- tcmts.loader.writePairModule