treelay 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2,12 +2,16 @@
2
2
  import {
3
3
  checkDrift,
4
4
  compile,
5
+ eject,
5
6
  explain,
6
7
  explainDest,
7
8
  extract,
8
9
  formatDrift,
10
+ formatEject,
9
11
  formatExplanation,
10
12
  formatStatus,
13
+ formatValidation,
14
+ formatWatchEvent,
11
15
  hasDrift,
12
16
  hasState,
13
17
  lockfilePath,
@@ -19,14 +23,69 @@ import {
19
23
  sourceOf,
20
24
  status,
21
25
  update,
26
+ validate,
27
+ watch,
22
28
  writeLock
23
- } from "./chunk-QKQUPZVD.js";
29
+ } from "./chunk-CP7GIVGA.js";
24
30
 
25
31
  // src/cli.ts
26
32
  import { readFileSync } from "fs";
27
33
  import { createRequire } from "module";
28
34
  import { Command } from "commander";
29
35
  import { parse as parseYaml } from "yaml";
36
+
37
+ // src/lock-command.ts
38
+ function shortRev(rev) {
39
+ return /^[0-9a-f]{40}$/i.test(rev) ? rev.slice(0, 12) : rev;
40
+ }
41
+ function lockCommand(dir, options = {}) {
42
+ const { check, update: update2, drift, ...resolveOptions } = options;
43
+ const graph = resolve(dir, {
44
+ ...resolveOptions,
45
+ // `--check` resolves in the normal mode and then refuses to write, rather
46
+ // than resolving frozen: it is reporting on the lock, not relying on it.
47
+ ...update2 ? { updateRefs: true } : {}
48
+ });
49
+ const entries = Object.entries(graph.lock?.refs ?? {}).sort(
50
+ ([a], [b]) => a < b ? -1 : a > b ? 1 : 0
51
+ );
52
+ const result = {
53
+ output: [],
54
+ errors: [],
55
+ exitCode: 0,
56
+ wrote: false,
57
+ refs: entries.map(([ref]) => ref)
58
+ };
59
+ if (check) {
60
+ if (graph.lockDirty) {
61
+ result.errors.push(
62
+ `${lockfilePath(dir)} is out of date.
63
+ Run \`treelay lock ${dir}\` and commit the result.`
64
+ );
65
+ return { ...result, exitCode: 1 };
66
+ }
67
+ result.output.push(`treelay.lock is up to date (${entries.length} pinned ref(s)).`);
68
+ } else if (graph.lock && graph.lockDir) {
69
+ result.wrote = writeLock(graph.lockDir, graph.lock);
70
+ result.output.push(
71
+ result.wrote ? `Wrote ${lockfilePath(dir)} \u2014 ${entries.length} pinned ref(s).` : `treelay.lock already current (${entries.length} pinned ref(s)).`
72
+ );
73
+ }
74
+ for (const [ref, entry] of entries) {
75
+ result.output.push(
76
+ ` ${ref}
77
+ \u2192 ${shortRev(entry.resolved)} ${entry.integrity.slice(0, 21)}\u2026`
78
+ );
79
+ }
80
+ if (drift) {
81
+ const reports = checkDrift(graph);
82
+ result.output.push(formatDrift(reports) || "\nAll pinned refs match their upstream.");
83
+ if (hasDrift(reports)) result.exitCode = 1;
84
+ }
85
+ return result;
86
+ }
87
+
88
+ // src/cli.ts
30
89
  var { version } = createRequire(import.meta.url)("../package.json");
31
90
  var program = new Command();
32
91
  program.name("treelay").description(
@@ -44,7 +103,7 @@ program.command("plan").argument("[dir]", "leaf overlay directory", ".").option(
44
103
  graph.layers.forEach((l, i) => {
45
104
  const marks = [
46
105
  l.mountPath ? `mounted at ${l.mountPath}/` : void 0,
47
- l.origin?.revision ? `pinned ${shortRev(l.origin.revision)}` : void 0
106
+ l.origin?.revision ? `pinned ${shortRev2(l.origin.revision)}` : void 0
48
107
  ].filter(Boolean);
49
108
  const suffix = marks.length ? ` [${marks.join(", ")}]` : "";
50
109
  console.log(` ${i + 1}. ${l.manifest.name ?? l.id}${suffix}`);
@@ -58,41 +117,14 @@ treelay.lock is out of date \u2014 run \`treelay lock ${dir}\` to record the rev
58
117
  );
59
118
  }
60
119
  });
61
- function shortRev(rev) {
120
+ function shortRev2(rev) {
62
121
  return /^[0-9a-f]{40}$/i.test(rev) ? rev.slice(0, 12) : rev;
63
122
  }
64
123
  program.command("lock").argument("[dir]", "leaf overlay directory", ".").option("--check", "verify the lockfile is complete and current; write nothing").option("--update", "re-resolve moving refs to their current upstream revision").option("--drift", "report refs whose upstream has moved (network)").description("resolve every layer ref and pin it in treelay.lock").action((dir, opts) => {
65
- const graph = resolve(dir, {
66
- // `--check` must not be able to *fix* what it is checking, so it resolves
67
- // in the normal (non-frozen) mode and then refuses to write.
68
- ...opts.update ? { updateRefs: true } : {}
69
- });
70
- const refs = Object.entries(graph.lock?.refs ?? {});
71
- if (opts.check) {
72
- if (graph.lockDirty) {
73
- console.error(
74
- `${lockfilePath(dir)} is out of date.
75
- Run \`treelay lock ${dir}\` and commit the result.`
76
- );
77
- process.exit(1);
78
- }
79
- console.log(`treelay.lock is up to date (${refs.length} pinned ref(s)).`);
80
- } else if (graph.lock && graph.lockDir) {
81
- const wrote = writeLock(graph.lockDir, graph.lock);
82
- console.log(
83
- wrote ? `Wrote ${lockfilePath(dir)} \u2014 ${refs.length} pinned ref(s).` : `treelay.lock already current (${refs.length} pinned ref(s)).`
84
- );
85
- }
86
- for (const [ref, entry] of refs.sort()) {
87
- console.log(` ${ref}
88
- \u2192 ${shortRev(entry.resolved)} ${entry.integrity.slice(0, 21)}\u2026`);
89
- }
90
- if (opts.drift) {
91
- const reports = checkDrift(graph);
92
- const text = formatDrift(reports);
93
- console.log(text || "\nAll pinned refs match their upstream.");
94
- if (hasDrift(reports)) process.exit(1);
95
- }
124
+ const result = lockCommand(dir, opts);
125
+ for (const line of result.output) console.log(line);
126
+ for (const line of result.errors) console.error(line);
127
+ if (result.exitCode !== 0) process.exit(result.exitCode);
96
128
  });
97
129
  function loadAnswers(file) {
98
130
  const text = readFileSync(file, "utf8");
@@ -279,13 +311,62 @@ function pick(files, path) {
279
311
  const hit = files[path];
280
312
  return hit ? { [path]: hit } : {};
281
313
  }
282
- program.command("validate").argument("[dir]", "leaf overlay directory", ".").description("check for cycles, failing patches, conflicts, lock drift").action(() => notImplemented("validate"));
283
- program.command("watch").argument("<src>", "leaf overlay directory").argument("<dest>", "destination directory").description("recompile on change").action(() => notImplemented("watch"));
284
- program.command("eject").argument("<dest>", "destination directory").description("flatten and drop .treelay state (sever the template link)").action(() => notImplemented("eject"));
285
- function notImplemented(cmd) {
286
- console.error(`treelay ${cmd}: not implemented yet (see SPEC.md \xA712 build order)`);
287
- process.exit(2);
288
- }
314
+ program.command("validate").argument("[dir]", "leaf overlay directory", ".").option("--set <k=v>", "value used to render the composition", collectSet).option("--answers <file>", "answers file to seed values").option("--drift", "also probe upstreams for movement (network)").option("--frozen-lockfile", "fail on any ref treelay.lock does not pin").option("--json", "emit machine-readable JSON").description("check for cycles, failing patches, conflicts, lock drift").action(
315
+ async (dir, opts) => {
316
+ const values = {
317
+ ...opts.answers ? loadAnswers(opts.answers) : {},
318
+ ...opts.set ?? {}
319
+ };
320
+ const report = await validate(dir, {
321
+ ...Object.keys(values).length ? { values } : {},
322
+ ...opts.drift ? { drift: true } : {},
323
+ ...opts.frozenLockfile ? { frozen: true } : {}
324
+ });
325
+ if (opts.json) {
326
+ console.log(JSON.stringify(report, null, 2));
327
+ } else {
328
+ console.log(formatValidation(report, dir));
329
+ }
330
+ if (!report.ok) process.exit(1);
331
+ }
332
+ );
333
+ program.command("watch").argument("<src>", "leaf overlay directory").argument("<dest>", "destination directory").option("--set <k=v>", "set a variable", collectSet).option("--answers <file>", "answers file to seed values").option("--debounce <ms>", "settle time before recompiling", "120").option("--poll", "poll instead of using native events (network/Docker mounts)").option("--frozen-lockfile", "fail on any ref treelay.lock does not pin").description("recompile on change").action(
334
+ async (src, dest, opts) => {
335
+ const debounceMs = Number(opts.debounce);
336
+ if (!Number.isFinite(debounceMs) || debounceMs < 0) {
337
+ console.error(`treelay watch: --debounce expects milliseconds, got "${opts.debounce}"`);
338
+ process.exit(2);
339
+ }
340
+ const values = {
341
+ ...opts.answers ? loadAnswers(opts.answers) : {},
342
+ ...opts.set ?? {}
343
+ };
344
+ const handle = await watch(src, dest, {
345
+ debounceMs,
346
+ ...opts.poll ? { usePolling: true } : {},
347
+ ...Object.keys(values).length ? { values } : {},
348
+ ...opts.frozenLockfile ? { frozen: true } : {},
349
+ onEvent: (event) => {
350
+ const line = formatWatchEvent(event);
351
+ if (event.kind === "failed") console.error(line);
352
+ else console.log(line);
353
+ }
354
+ });
355
+ const stop = () => {
356
+ void handle.close().then(() => {
357
+ console.log("\nStopped watching.");
358
+ process.exit(0);
359
+ });
360
+ };
361
+ process.on("SIGINT", stop);
362
+ process.on("SIGTERM", stop);
363
+ }
364
+ );
365
+ program.command("eject").argument("<dest>", "destination directory").option("--dry-run", "show what would be severed; remove nothing").description("flatten and drop .treelay state (sever the template link)").action((dest, opts) => {
366
+ requireState(dest, "eject");
367
+ const result = eject(dest, opts.dryRun ? { dryRun: true } : {});
368
+ console.log(formatEject(result, dest));
369
+ });
289
370
  program.parseAsync().catch((err) => {
290
371
  console.error(err instanceof Error ? err.message : err);
291
372
  process.exit(1);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * treelay CLI — SPEC §9.\n *\n * Thin shell over the library. Commands are wired to their API entry points;\n * unimplemented ones surface a clear \"not implemented yet\" until built (§12).\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { parse as parseYaml } from \"yaml\";\nimport { resolve } from \"./resolve.js\";\nimport { resolveValues } from \"./variables.js\";\nimport { compile } from \"./compile.js\";\nimport { update, planUpdate } from \"./update.js\";\nimport { explain, explainDest, formatExplanation } from \"./explain.js\";\nimport { status, promote, extract, formatStatus } from \"./reflux.js\";\nimport { hasState, readState, sourceOf } from \"./state.js\";\nimport { lockfilePath, writeLock } from \"./lockfile.js\";\nimport { checkDrift, formatDrift, hasDrift } from \"./drift.js\";\nimport type { Values } from \"./types.js\";\n\n// The published version has exactly one home: package.json. `../package.json`\n// resolves to the package root from both `src/cli.ts` and the bundled\n// `dist/cli.js`, so dev and install agree without a build-time substitution.\nconst { version } = createRequire(import.meta.url)(\"../package.json\") as {\n version: string;\n};\n\nconst program = new Command();\n\nprogram\n .name(\"treelay\")\n .description(\n \"Compose directory trees from parents and mixins, materialize them, \" +\n \"and keep the output linked for two-way updates.\",\n )\n .version(version);\n\n/** Parse repeated `--set k=v` flags into an object. */\nfunction collectSet(value: string, previous: Record<string, string> = {}) {\n const eq = value.indexOf(\"=\");\n if (eq === -1) throw new Error(`--set expects k=v, got \"${value}\"`);\n previous[value.slice(0, eq)] = value.slice(eq + 1);\n return previous;\n}\n\nprogram\n .command(\"plan\")\n .argument(\"[dir]\", \"leaf overlay directory\", \".\")\n .option(\"--frozen-lockfile\", \"fail on any ref treelay.lock does not pin\")\n .description(\"print the linearized layer order; write nothing\")\n .action((dir: string, opts: { frozenLockfile?: boolean }) => {\n const graph = resolve(dir, opts.frozenLockfile ? { frozen: true } : {});\n console.log(\"Layers (lowest → highest precedence):\");\n graph.layers.forEach((l, i) => {\n const marks = [\n l.mountPath ? `mounted at ${l.mountPath}/` : undefined,\n l.origin?.revision ? `pinned ${shortRev(l.origin.revision)}` : undefined,\n ].filter(Boolean);\n const suffix = marks.length ? ` [${marks.join(\", \")}]` : \"\";\n console.log(` ${i + 1}. ${l.manifest.name ?? l.id}${suffix}`);\n });\n const vars = Object.keys(graph.variables);\n if (vars.length) console.log(`Variables: ${vars.join(\", \")}`);\n if (graph.lockDirty) {\n console.error(\n `\\ntreelay.lock is out of date — run \\`treelay lock ${dir}\\` to record ` +\n `the revisions this plan resolved.`,\n );\n }\n });\n\n/** Abbreviate a commit SHA for display; package versions pass through. */\nfunction shortRev(rev: string): string {\n return /^[0-9a-f]{40}$/i.test(rev) ? rev.slice(0, 12) : rev;\n}\n\nprogram\n .command(\"lock\")\n .argument(\"[dir]\", \"leaf overlay directory\", \".\")\n .option(\"--check\", \"verify the lockfile is complete and current; write nothing\")\n .option(\"--update\", \"re-resolve moving refs to their current upstream revision\")\n .option(\"--drift\", \"report refs whose upstream has moved (network)\")\n .description(\"resolve every layer ref and pin it in treelay.lock\")\n .action((dir: string, opts: { check?: boolean; update?: boolean; drift?: boolean }) => {\n const graph = resolve(dir, {\n // `--check` must not be able to *fix* what it is checking, so it resolves\n // in the normal (non-frozen) mode and then refuses to write.\n ...(opts.update ? { updateRefs: true } : {}),\n });\n\n const refs = Object.entries(graph.lock?.refs ?? {});\n if (opts.check) {\n if (graph.lockDirty) {\n console.error(\n `${lockfilePath(dir)} is out of date.\\n` +\n `Run \\`treelay lock ${dir}\\` and commit the result.`,\n );\n process.exit(1);\n }\n console.log(`treelay.lock is up to date (${refs.length} pinned ref(s)).`);\n } else if (graph.lock && graph.lockDir) {\n const wrote = writeLock(graph.lockDir, graph.lock);\n console.log(\n wrote\n ? `Wrote ${lockfilePath(dir)} — ${refs.length} pinned ref(s).`\n : `treelay.lock already current (${refs.length} pinned ref(s)).`,\n );\n }\n\n for (const [ref, entry] of refs.sort()) {\n console.log(` ${ref}\\n → ${shortRev(entry.resolved)} ${entry.integrity.slice(0, 21)}…`);\n }\n\n if (opts.drift) {\n const reports = checkDrift(graph);\n const text = formatDrift(reports);\n console.log(text || \"\\nAll pinned refs match their upstream.\");\n if (hasDrift(reports)) process.exit(1);\n }\n });\n\n/** Load an answers file (JSON or YAML) into a values object. */\nfunction loadAnswers(file: string): Values {\n const text = readFileSync(file, \"utf8\");\n return (file.endsWith(\".json\") ? JSON.parse(text) : parseYaml(text)) as Values;\n}\n\nprogram\n .command(\"compile\")\n .argument(\"<src>\", \"leaf overlay directory\")\n .argument(\"<dest>\", \"destination directory\")\n .option(\"--set <k=v>\", \"set a variable\", collectSet)\n .option(\"--answers <file>\", \"answers file to seed values\")\n .option(\"--no-prompt\", \"do not prompt for missing variables\")\n .option(\"--frozen-lockfile\", \"fail on any ref treelay.lock does not pin\")\n .description(\"materialize template → destination (first run = instantiate)\")\n .action(\n async (\n src: string,\n dest: string,\n opts: {\n set?: Values;\n answers?: string;\n prompt?: boolean;\n frozenLockfile?: boolean;\n },\n ) => {\n if (hasState(dest)) {\n console.error(\n `treelay compile: ${dest} already has .treelay state — use ` +\n `\\`treelay update ${dest}\\` to pull template changes in.`,\n );\n process.exit(2);\n }\n const graph = resolve(src, opts.frozenLockfile ? { frozen: true } : {});\n const values = await resolveValues(graph, {\n ...(opts.answers ? { answers: loadAnswers(opts.answers) } : {}),\n ...(opts.set ? { set: opts.set } : {}),\n prompt: opts.prompt !== false,\n });\n const gainedPins = graph.lockDirty;\n const result = await compile(graph, { destDir: dest, values });\n const n = Object.keys(result.files).length;\n console.log(`Compiled ${n} file${n === 1 ? \"\" : \"s\"} → ${dest}`);\n // Writing the source lockfile is a side effect on a tree the user may not\n // have expected this command to touch, so it is always announced.\n if (gainedPins) {\n console.log(`Recorded new pins in ${lockfilePath(src)} — commit it.`);\n }\n },\n );\n\nprogram\n .command(\"update\")\n .argument(\"<dest>\", \"destination directory\")\n .option(\"--set <k=v>\", \"override a saved answer\", collectSet)\n .option(\n \"--on-conflict <mode>\",\n \"how to surface conflicts: markers | rej\",\n \"markers\",\n )\n .option(\"--no-prompt\", \"do not prompt for newly-introduced variables\")\n .option(\"--dry-run\", \"show what would change; write nothing\")\n .option(\"--frozen-lockfile\", \"fail on any ref treelay.lock does not pin\")\n .description(\"re-render with saved answers and 3-way merge into the project\")\n .action(\n async (\n dest: string,\n opts: {\n set?: Values;\n onConflict?: string;\n prompt?: boolean;\n dryRun?: boolean;\n frozenLockfile?: boolean;\n },\n ) => {\n requireState(dest, \"update\");\n if (opts.onConflict !== \"markers\" && opts.onConflict !== \"rej\") {\n console.error(\n `treelay update: --on-conflict expects \"markers\" or \"rej\", got \"${opts.onConflict}\"`,\n );\n process.exit(2);\n }\n\n const options = {\n onConflict: opts.onConflict,\n ...(opts.set ? { set: opts.set } : {}),\n ...(opts.frozenLockfile ? { frozen: true } : {}),\n prompt: opts.prompt !== false,\n } as const;\n\n const plan = opts.dryRun\n ? await planUpdate(dest, options)\n : await update(dest, options);\n\n if (plan.newVariables.length) {\n console.log(`New variables: ${plan.newVariables.join(\", \")}`);\n }\n\n // Drift is advisory and goes to stderr: this update composed from the\n // pinned revisions, and saying so must not be mistaken for a file change.\n const drift = formatDrift(plan.drift);\n if (drift) console.error(drift + \"\\n\");\n\n // Only report files the working tree actually gained, lost, or had\n // rewritten. `keep-ours`/`unchanged` write nothing, and listing them every\n // run buries the handful that moved — and makes a no-op look like work.\n const mark: Record<string, string> = {\n \"take-theirs\": \"U\",\n merged: \"M\",\n conflict: \"C\",\n delete: \"D\",\n };\n const changed = Object.entries(plan.files).filter(([, r]) => r in mark);\n const kept = Object.values(plan.files).filter((r) => r === \"keep-ours\").length;\n\n if (!changed.length) {\n console.log(\"Already up to date.\");\n if (kept) console.log(`(${kept} file(s) with local edits left as-is.)`);\n return;\n }\n\n for (const [path, r] of changed.sort()) {\n console.log(` ${mark[r]} ${path}${r === \"conflict\" ? \" ← conflict\" : \"\"}`);\n }\n if (kept) console.log(` … ${kept} file(s) with local edits left as-is.`);\n\n if (plan.conflicts.length) {\n console.error(\n `\\n${plan.conflicts.length} conflict(s). ` +\n (opts.dryRun\n ? \"Re-run without --dry-run to write them.\"\n : options.onConflict === \"rej\"\n ? \"Your files are unchanged; incoming versions are in *.rej.\"\n : \"Resolve the markers, then run `treelay update` again.\"),\n );\n process.exit(1);\n }\n },\n );\n\n/** Load a destination's changes, narrowed to `files` when any were named. */\nasync function changesFor(dest: string, files: string[]) {\n requireState(dest, \"status\");\n const all = await status(dest);\n if (!files.length) return all;\n\n const selected = all.filter((c) => files.includes(c.path));\n const missing = files.filter((f) => !all.some((c) => c.path === f));\n if (missing.length) {\n console.error(\n `No change recorded for: ${missing.join(\", \")}\\n` +\n `Run \\`treelay status ${dest}\\` to see what diverged from the baseline.`,\n );\n process.exit(2);\n }\n return selected;\n}\n\n/** Exit with guidance when a directory carries no treelay state. */\nfunction requireState(dest: string, cmd: string): void {\n if (hasState(dest)) return;\n console.error(\n `treelay ${cmd}: ${dest} has no .treelay state — it was not created by ` +\n `treelay. Use \\`treelay compile <src> ${dest}\\` first.`,\n );\n process.exit(2);\n}\n\nprogram\n .command(\"status\")\n .argument(\"<dest>\", \"destination directory\")\n .option(\"--json\", \"emit machine-readable JSON\")\n .description(\"list changes vs baseline, annotated with producing layer\")\n .action(async (dest: string, opts: { json?: boolean }) => {\n requireState(dest, \"status\");\n const changes = await status(dest);\n if (opts.json) {\n console.log(JSON.stringify(changes, null, 2));\n return;\n }\n const state = readState(dest);\n const src = sourceOf(state);\n console.log(formatStatus(changes, resolve(src!)));\n });\n\nprogram\n .command(\"promote\")\n .argument(\"<dest>\", \"destination directory\")\n .argument(\"[files...]\", \"files to promote\")\n .option(\"--to <layer>\", \"target layer (auto-suggested if omitted)\")\n .option(\"--no-verify\", \"skip the round-trip recompile check (§8 guard 2)\")\n .option(\"--dry-run\", \"show what each target would gain; write nothing\")\n .description(\"push instance edits up into a layer\")\n .action(\n async (\n dest: string,\n files: string[],\n opts: { to?: string; verify?: boolean; dryRun?: boolean },\n ) => {\n const changes = await changesFor(dest, files);\n if (!changes.length) {\n console.log(\"Nothing to promote — no changes vs baseline.\");\n return;\n }\n\n if (opts.dryRun) {\n console.log(\n `Would promote ${changes.length} change(s)${opts.to ? ` into ${opts.to}` : \"\"}:`,\n );\n for (const c of changes) console.log(` ${c.kind.padEnd(8)} ${c.path}`);\n return;\n }\n\n const result = await promote(dest, changes, {\n ...(opts.to ? { to: opts.to } : {}),\n verify: opts.verify !== false,\n });\n\n console.log(`Promoted into ${result.targetName}:`);\n for (const l of result.landed) {\n console.log(` ${l.mode.padEnd(9)} ${l.path} → ${l.wrote}`);\n }\n console.log(\n result.verified\n ? \"Round-trip verified: the destination reproduces from the template.\"\n : \"Round-trip verification skipped (--no-verify).\",\n );\n // Guard 3 is advisory, but it is the one with consequences for other\n // people, so it goes to stderr where it will not be piped away silently.\n console.error(`\\n${result.blastRadiusWarning}`);\n },\n );\n\nprogram\n .command(\"extract\")\n .argument(\"<dest>\", \"destination directory\")\n .argument(\"[files...]\", \"files to extract\")\n .requiredOption(\"--as <path>\", \"path for the new overlay layer\")\n .option(\"--mixin\", \"wire the new layer in as a mixin\")\n .option(\"--name <name>\", \"name for the new layer's manifest\")\n .description(\"capture instance edits as a new overlay layer\")\n .action(\n async (\n dest: string,\n files: string[],\n opts: { as: string; mixin?: boolean; name?: string },\n ) => {\n const changes = await changesFor(dest, files);\n if (!changes.length) {\n console.log(\"Nothing to extract — no changes vs baseline.\");\n return;\n }\n\n const result = await extract(dest, changes, {\n as: opts.as,\n ...(opts.mixin ? { asMixin: true } : {}),\n ...(opts.name ? { name: opts.name } : {}),\n });\n\n console.log(`Extracted ${result.files.length} file(s) → ${result.layer}`);\n for (const f of result.files) console.log(` ${f}`);\n if (result.wired) {\n console.log(`Wired in as a mixin of the leaf; round-trip verified.`);\n } else {\n console.error(\n `\\nThe new layer is not wired into the graph, so these edits are still ` +\n `local. Add \"${opts.as}\" to the leaf's parents or mixins (or re-run ` +\n `with --mixin) to make them inherited.`,\n );\n }\n },\n );\n\nprogram\n .command(\"explain\")\n .argument(\"<dir>\", \"leaf overlay directory, or a compiled destination\")\n .argument(\"[file]\", \"a single output path to trace (default: every file)\")\n .option(\"--set <k=v>\", \"value used to render templated paths\", collectSet)\n .option(\"--answers <file>\", \"answers file to seed values\")\n .option(\"--json\", \"emit machine-readable JSON\")\n .description(\"trace which layers touched a file, in order\")\n .action(\n async (\n dir: string,\n file: string | undefined,\n opts: { set?: Values; answers?: string; json?: boolean },\n ) => {\n // A compiled destination explains itself from its own state (lineage +\n // saved answers); a source directory is resolved and explained directly.\n const result = hasState(dir)\n ? await explainDest(dir)\n : await explain(resolve(dir), {\n values: {\n ...(opts.answers ? loadAnswers(opts.answers) : {}),\n ...(opts.set ?? {}),\n },\n });\n\n if (opts.json) {\n const payload = file\n ? { layers: result.layers, files: pick(result.files, file) }\n : result;\n console.log(JSON.stringify(payload, null, 2));\n return;\n }\n\n console.log(formatExplanation(result, file));\n if (file && !result.files[file]) process.exit(1);\n },\n );\n\n/** Narrow an explanation's file map to a single path (empty when absent). */\nfunction pick<T>(files: Record<string, T>, path: string): Record<string, T> {\n const hit = files[path];\n return hit ? { [path]: hit } : {};\n}\n\nprogram\n .command(\"validate\")\n .argument(\"[dir]\", \"leaf overlay directory\", \".\")\n .description(\"check for cycles, failing patches, conflicts, lock drift\")\n .action(() => notImplemented(\"validate\"));\n\nprogram\n .command(\"watch\")\n .argument(\"<src>\", \"leaf overlay directory\")\n .argument(\"<dest>\", \"destination directory\")\n .description(\"recompile on change\")\n .action(() => notImplemented(\"watch\"));\n\nprogram\n .command(\"eject\")\n .argument(\"<dest>\", \"destination directory\")\n .description(\"flatten and drop .treelay state (sever the template link)\")\n .action(() => notImplemented(\"eject\"));\n\nfunction notImplemented(cmd: string): never {\n console.error(`treelay ${cmd}: not implemented yet (see SPEC.md §12 build order)`);\n process.exit(2);\n}\n\nprogram.parseAsync().catch((err: unknown) => {\n console.error(err instanceof Error ? err.message : err);\n process.exit(1);\n});\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/lock-command.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * treelay CLI — SPEC §9.\n *\n * Thin shell over the library: every command is a thin wrapper over an API\n * entry point, so anything worth testing lives in the module behind it.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { Command } from \"commander\";\nimport { parse as parseYaml } from \"yaml\";\nimport { resolve } from \"./resolve.js\";\nimport { resolveValues } from \"./variables.js\";\nimport { compile } from \"./compile.js\";\nimport { update, planUpdate } from \"./update.js\";\nimport { explain, explainDest, formatExplanation } from \"./explain.js\";\nimport { status, promote, extract, formatStatus } from \"./reflux.js\";\nimport { eject, formatEject } from \"./eject.js\";\nimport { validate, formatValidation } from \"./validate.js\";\nimport { watch, formatWatchEvent } from \"./watch.js\";\nimport { lockCommand } from \"./lock-command.js\";\nimport { hasState, readState, sourceOf } from \"./state.js\";\nimport { lockfilePath } from \"./lockfile.js\";\nimport { formatDrift } from \"./drift.js\";\nimport type { Values } from \"./types.js\";\n\n// The published version has exactly one home: package.json. `../package.json`\n// resolves to the package root from both `src/cli.ts` and the bundled\n// `dist/cli.js`, so dev and install agree without a build-time substitution.\nconst { version } = createRequire(import.meta.url)(\"../package.json\") as {\n version: string;\n};\n\nconst program = new Command();\n\nprogram\n .name(\"treelay\")\n .description(\n \"Compose directory trees from parents and mixins, materialize them, \" +\n \"and keep the output linked for two-way updates.\",\n )\n .version(version);\n\n/** Parse repeated `--set k=v` flags into an object. */\nfunction collectSet(value: string, previous: Record<string, string> = {}) {\n const eq = value.indexOf(\"=\");\n if (eq === -1) throw new Error(`--set expects k=v, got \"${value}\"`);\n previous[value.slice(0, eq)] = value.slice(eq + 1);\n return previous;\n}\n\nprogram\n .command(\"plan\")\n .argument(\"[dir]\", \"leaf overlay directory\", \".\")\n .option(\"--frozen-lockfile\", \"fail on any ref treelay.lock does not pin\")\n .description(\"print the linearized layer order; write nothing\")\n .action((dir: string, opts: { frozenLockfile?: boolean }) => {\n const graph = resolve(dir, opts.frozenLockfile ? { frozen: true } : {});\n console.log(\"Layers (lowest → highest precedence):\");\n graph.layers.forEach((l, i) => {\n const marks = [\n l.mountPath ? `mounted at ${l.mountPath}/` : undefined,\n l.origin?.revision ? `pinned ${shortRev(l.origin.revision)}` : undefined,\n ].filter(Boolean);\n const suffix = marks.length ? ` [${marks.join(\", \")}]` : \"\";\n console.log(` ${i + 1}. ${l.manifest.name ?? l.id}${suffix}`);\n });\n const vars = Object.keys(graph.variables);\n if (vars.length) console.log(`Variables: ${vars.join(\", \")}`);\n if (graph.lockDirty) {\n console.error(\n `\\ntreelay.lock is out of date — run \\`treelay lock ${dir}\\` to record ` +\n `the revisions this plan resolved.`,\n );\n }\n });\n\n/** Abbreviate a commit SHA for display; package versions pass through. */\nfunction shortRev(rev: string): string {\n return /^[0-9a-f]{40}$/i.test(rev) ? rev.slice(0, 12) : rev;\n}\n\nprogram\n .command(\"lock\")\n .argument(\"[dir]\", \"leaf overlay directory\", \".\")\n .option(\"--check\", \"verify the lockfile is complete and current; write nothing\")\n .option(\"--update\", \"re-resolve moving refs to their current upstream revision\")\n .option(\"--drift\", \"report refs whose upstream has moved (network)\")\n .description(\"resolve every layer ref and pin it in treelay.lock\")\n .action((dir: string, opts: { check?: boolean; update?: boolean; drift?: boolean }) => {\n const result = lockCommand(dir, opts);\n for (const line of result.output) console.log(line);\n for (const line of result.errors) console.error(line);\n if (result.exitCode !== 0) process.exit(result.exitCode);\n });\n\n/** Load an answers file (JSON or YAML) into a values object. */\nfunction loadAnswers(file: string): Values {\n const text = readFileSync(file, \"utf8\");\n return (file.endsWith(\".json\") ? JSON.parse(text) : parseYaml(text)) as Values;\n}\n\nprogram\n .command(\"compile\")\n .argument(\"<src>\", \"leaf overlay directory\")\n .argument(\"<dest>\", \"destination directory\")\n .option(\"--set <k=v>\", \"set a variable\", collectSet)\n .option(\"--answers <file>\", \"answers file to seed values\")\n .option(\"--no-prompt\", \"do not prompt for missing variables\")\n .option(\"--frozen-lockfile\", \"fail on any ref treelay.lock does not pin\")\n .description(\"materialize template → destination (first run = instantiate)\")\n .action(\n async (\n src: string,\n dest: string,\n opts: {\n set?: Values;\n answers?: string;\n prompt?: boolean;\n frozenLockfile?: boolean;\n },\n ) => {\n if (hasState(dest)) {\n console.error(\n `treelay compile: ${dest} already has .treelay state — use ` +\n `\\`treelay update ${dest}\\` to pull template changes in.`,\n );\n process.exit(2);\n }\n const graph = resolve(src, opts.frozenLockfile ? { frozen: true } : {});\n const values = await resolveValues(graph, {\n ...(opts.answers ? { answers: loadAnswers(opts.answers) } : {}),\n ...(opts.set ? { set: opts.set } : {}),\n prompt: opts.prompt !== false,\n });\n const gainedPins = graph.lockDirty;\n const result = await compile(graph, { destDir: dest, values });\n const n = Object.keys(result.files).length;\n console.log(`Compiled ${n} file${n === 1 ? \"\" : \"s\"} → ${dest}`);\n // Writing the source lockfile is a side effect on a tree the user may not\n // have expected this command to touch, so it is always announced.\n if (gainedPins) {\n console.log(`Recorded new pins in ${lockfilePath(src)} — commit it.`);\n }\n },\n );\n\nprogram\n .command(\"update\")\n .argument(\"<dest>\", \"destination directory\")\n .option(\"--set <k=v>\", \"override a saved answer\", collectSet)\n .option(\n \"--on-conflict <mode>\",\n \"how to surface conflicts: markers | rej\",\n \"markers\",\n )\n .option(\"--no-prompt\", \"do not prompt for newly-introduced variables\")\n .option(\"--dry-run\", \"show what would change; write nothing\")\n .option(\"--frozen-lockfile\", \"fail on any ref treelay.lock does not pin\")\n .description(\"re-render with saved answers and 3-way merge into the project\")\n .action(\n async (\n dest: string,\n opts: {\n set?: Values;\n onConflict?: string;\n prompt?: boolean;\n dryRun?: boolean;\n frozenLockfile?: boolean;\n },\n ) => {\n requireState(dest, \"update\");\n if (opts.onConflict !== \"markers\" && opts.onConflict !== \"rej\") {\n console.error(\n `treelay update: --on-conflict expects \"markers\" or \"rej\", got \"${opts.onConflict}\"`,\n );\n process.exit(2);\n }\n\n const options = {\n onConflict: opts.onConflict,\n ...(opts.set ? { set: opts.set } : {}),\n ...(opts.frozenLockfile ? { frozen: true } : {}),\n prompt: opts.prompt !== false,\n } as const;\n\n const plan = opts.dryRun\n ? await planUpdate(dest, options)\n : await update(dest, options);\n\n if (plan.newVariables.length) {\n console.log(`New variables: ${plan.newVariables.join(\", \")}`);\n }\n\n // Drift is advisory and goes to stderr: this update composed from the\n // pinned revisions, and saying so must not be mistaken for a file change.\n const drift = formatDrift(plan.drift);\n if (drift) console.error(drift + \"\\n\");\n\n // Only report files the working tree actually gained, lost, or had\n // rewritten. `keep-ours`/`unchanged` write nothing, and listing them every\n // run buries the handful that moved — and makes a no-op look like work.\n const mark: Record<string, string> = {\n \"take-theirs\": \"U\",\n merged: \"M\",\n conflict: \"C\",\n delete: \"D\",\n };\n const changed = Object.entries(plan.files).filter(([, r]) => r in mark);\n const kept = Object.values(plan.files).filter((r) => r === \"keep-ours\").length;\n\n if (!changed.length) {\n console.log(\"Already up to date.\");\n if (kept) console.log(`(${kept} file(s) with local edits left as-is.)`);\n return;\n }\n\n for (const [path, r] of changed.sort()) {\n console.log(` ${mark[r]} ${path}${r === \"conflict\" ? \" ← conflict\" : \"\"}`);\n }\n if (kept) console.log(` … ${kept} file(s) with local edits left as-is.`);\n\n if (plan.conflicts.length) {\n console.error(\n `\\n${plan.conflicts.length} conflict(s). ` +\n (opts.dryRun\n ? \"Re-run without --dry-run to write them.\"\n : options.onConflict === \"rej\"\n ? \"Your files are unchanged; incoming versions are in *.rej.\"\n : \"Resolve the markers, then run `treelay update` again.\"),\n );\n process.exit(1);\n }\n },\n );\n\n/** Load a destination's changes, narrowed to `files` when any were named. */\nasync function changesFor(dest: string, files: string[]) {\n requireState(dest, \"status\");\n const all = await status(dest);\n if (!files.length) return all;\n\n const selected = all.filter((c) => files.includes(c.path));\n const missing = files.filter((f) => !all.some((c) => c.path === f));\n if (missing.length) {\n console.error(\n `No change recorded for: ${missing.join(\", \")}\\n` +\n `Run \\`treelay status ${dest}\\` to see what diverged from the baseline.`,\n );\n process.exit(2);\n }\n return selected;\n}\n\n/** Exit with guidance when a directory carries no treelay state. */\nfunction requireState(dest: string, cmd: string): void {\n if (hasState(dest)) return;\n console.error(\n `treelay ${cmd}: ${dest} has no .treelay state — it was not created by ` +\n `treelay. Use \\`treelay compile <src> ${dest}\\` first.`,\n );\n process.exit(2);\n}\n\nprogram\n .command(\"status\")\n .argument(\"<dest>\", \"destination directory\")\n .option(\"--json\", \"emit machine-readable JSON\")\n .description(\"list changes vs baseline, annotated with producing layer\")\n .action(async (dest: string, opts: { json?: boolean }) => {\n requireState(dest, \"status\");\n const changes = await status(dest);\n if (opts.json) {\n console.log(JSON.stringify(changes, null, 2));\n return;\n }\n const state = readState(dest);\n const src = sourceOf(state);\n console.log(formatStatus(changes, resolve(src!)));\n });\n\nprogram\n .command(\"promote\")\n .argument(\"<dest>\", \"destination directory\")\n .argument(\"[files...]\", \"files to promote\")\n .option(\"--to <layer>\", \"target layer (auto-suggested if omitted)\")\n .option(\"--no-verify\", \"skip the round-trip recompile check (§8 guard 2)\")\n .option(\"--dry-run\", \"show what each target would gain; write nothing\")\n .description(\"push instance edits up into a layer\")\n .action(\n async (\n dest: string,\n files: string[],\n opts: { to?: string; verify?: boolean; dryRun?: boolean },\n ) => {\n const changes = await changesFor(dest, files);\n if (!changes.length) {\n console.log(\"Nothing to promote — no changes vs baseline.\");\n return;\n }\n\n if (opts.dryRun) {\n console.log(\n `Would promote ${changes.length} change(s)${opts.to ? ` into ${opts.to}` : \"\"}:`,\n );\n for (const c of changes) console.log(` ${c.kind.padEnd(8)} ${c.path}`);\n return;\n }\n\n const result = await promote(dest, changes, {\n ...(opts.to ? { to: opts.to } : {}),\n verify: opts.verify !== false,\n });\n\n console.log(`Promoted into ${result.targetName}:`);\n for (const l of result.landed) {\n console.log(` ${l.mode.padEnd(9)} ${l.path} → ${l.wrote}`);\n }\n console.log(\n result.verified\n ? \"Round-trip verified: the destination reproduces from the template.\"\n : \"Round-trip verification skipped (--no-verify).\",\n );\n // Guard 3 is advisory, but it is the one with consequences for other\n // people, so it goes to stderr where it will not be piped away silently.\n console.error(`\\n${result.blastRadiusWarning}`);\n },\n );\n\nprogram\n .command(\"extract\")\n .argument(\"<dest>\", \"destination directory\")\n .argument(\"[files...]\", \"files to extract\")\n .requiredOption(\"--as <path>\", \"path for the new overlay layer\")\n .option(\"--mixin\", \"wire the new layer in as a mixin\")\n .option(\"--name <name>\", \"name for the new layer's manifest\")\n .description(\"capture instance edits as a new overlay layer\")\n .action(\n async (\n dest: string,\n files: string[],\n opts: { as: string; mixin?: boolean; name?: string },\n ) => {\n const changes = await changesFor(dest, files);\n if (!changes.length) {\n console.log(\"Nothing to extract — no changes vs baseline.\");\n return;\n }\n\n const result = await extract(dest, changes, {\n as: opts.as,\n ...(opts.mixin ? { asMixin: true } : {}),\n ...(opts.name ? { name: opts.name } : {}),\n });\n\n console.log(`Extracted ${result.files.length} file(s) → ${result.layer}`);\n for (const f of result.files) console.log(` ${f}`);\n if (result.wired) {\n console.log(`Wired in as a mixin of the leaf; round-trip verified.`);\n } else {\n console.error(\n `\\nThe new layer is not wired into the graph, so these edits are still ` +\n `local. Add \"${opts.as}\" to the leaf's parents or mixins (or re-run ` +\n `with --mixin) to make them inherited.`,\n );\n }\n },\n );\n\nprogram\n .command(\"explain\")\n .argument(\"<dir>\", \"leaf overlay directory, or a compiled destination\")\n .argument(\"[file]\", \"a single output path to trace (default: every file)\")\n .option(\"--set <k=v>\", \"value used to render templated paths\", collectSet)\n .option(\"--answers <file>\", \"answers file to seed values\")\n .option(\"--json\", \"emit machine-readable JSON\")\n .description(\"trace which layers touched a file, in order\")\n .action(\n async (\n dir: string,\n file: string | undefined,\n opts: { set?: Values; answers?: string; json?: boolean },\n ) => {\n // A compiled destination explains itself from its own state (lineage +\n // saved answers); a source directory is resolved and explained directly.\n const result = hasState(dir)\n ? await explainDest(dir)\n : await explain(resolve(dir), {\n values: {\n ...(opts.answers ? loadAnswers(opts.answers) : {}),\n ...(opts.set ?? {}),\n },\n });\n\n if (opts.json) {\n const payload = file\n ? { layers: result.layers, files: pick(result.files, file) }\n : result;\n console.log(JSON.stringify(payload, null, 2));\n return;\n }\n\n console.log(formatExplanation(result, file));\n if (file && !result.files[file]) process.exit(1);\n },\n );\n\n/** Narrow an explanation's file map to a single path (empty when absent). */\nfunction pick<T>(files: Record<string, T>, path: string): Record<string, T> {\n const hit = files[path];\n return hit ? { [path]: hit } : {};\n}\n\nprogram\n .command(\"validate\")\n .argument(\"[dir]\", \"leaf overlay directory\", \".\")\n .option(\"--set <k=v>\", \"value used to render the composition\", collectSet)\n .option(\"--answers <file>\", \"answers file to seed values\")\n .option(\"--drift\", \"also probe upstreams for movement (network)\")\n .option(\"--frozen-lockfile\", \"fail on any ref treelay.lock does not pin\")\n .option(\"--json\", \"emit machine-readable JSON\")\n .description(\"check for cycles, failing patches, conflicts, lock drift\")\n .action(\n async (\n dir: string,\n opts: {\n set?: Values;\n answers?: string;\n drift?: boolean;\n frozenLockfile?: boolean;\n json?: boolean;\n },\n ) => {\n const values = {\n ...(opts.answers ? loadAnswers(opts.answers) : {}),\n ...(opts.set ?? {}),\n };\n const report = await validate(dir, {\n ...(Object.keys(values).length ? { values } : {}),\n ...(opts.drift ? { drift: true } : {}),\n ...(opts.frozenLockfile ? { frozen: true } : {}),\n });\n\n if (opts.json) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n console.log(formatValidation(report, dir));\n }\n // Warnings are reportable but not fatal, so a tree with stale pins still\n // exits 0 — otherwise `validate` could not be a merge gate.\n if (!report.ok) process.exit(1);\n },\n );\n\nprogram\n .command(\"watch\")\n .argument(\"<src>\", \"leaf overlay directory\")\n .argument(\"<dest>\", \"destination directory\")\n .option(\"--set <k=v>\", \"set a variable\", collectSet)\n .option(\"--answers <file>\", \"answers file to seed values\")\n .option(\"--debounce <ms>\", \"settle time before recompiling\", \"120\")\n .option(\"--poll\", \"poll instead of using native events (network/Docker mounts)\")\n .option(\"--frozen-lockfile\", \"fail on any ref treelay.lock does not pin\")\n .description(\"recompile on change\")\n .action(\n async (\n src: string,\n dest: string,\n opts: {\n set?: Values;\n answers?: string;\n debounce: string;\n poll?: boolean;\n frozenLockfile?: boolean;\n },\n ) => {\n const debounceMs = Number(opts.debounce);\n if (!Number.isFinite(debounceMs) || debounceMs < 0) {\n console.error(`treelay watch: --debounce expects milliseconds, got \"${opts.debounce}\"`);\n process.exit(2);\n }\n const values = {\n ...(opts.answers ? loadAnswers(opts.answers) : {}),\n ...(opts.set ?? {}),\n };\n\n const handle = await watch(src, dest, {\n debounceMs,\n ...(opts.poll ? { usePolling: true } : {}),\n ...(Object.keys(values).length ? { values } : {}),\n ...(opts.frozenLockfile ? { frozen: true } : {}),\n onEvent: (event) => {\n // Failures go to stderr so piping the build log keeps them separable.\n const line = formatWatchEvent(event);\n if (event.kind === \"failed\") console.error(line);\n else console.log(line);\n },\n });\n\n // Watch is the one command that does not end on its own.\n const stop = () => {\n void handle.close().then(() => {\n console.log(\"\\nStopped watching.\");\n process.exit(0);\n });\n };\n process.on(\"SIGINT\", stop);\n process.on(\"SIGTERM\", stop);\n },\n );\n\nprogram\n .command(\"eject\")\n .argument(\"<dest>\", \"destination directory\")\n .option(\"--dry-run\", \"show what would be severed; remove nothing\")\n .description(\"flatten and drop .treelay state (sever the template link)\")\n .action((dest: string, opts: { dryRun?: boolean }) => {\n requireState(dest, \"eject\");\n const result = eject(dest, opts.dryRun ? { dryRun: true } : {});\n console.log(formatEject(result, dest));\n });\n\nprogram.parseAsync().catch((err: unknown) => {\n console.error(err instanceof Error ? err.message : err);\n process.exit(1);\n});\n","/**\n * The `treelay lock` command, as a function (SPEC §3, §9).\n *\n * Lives here rather than inline in `cli.ts` because the interesting part is not\n * the printing — it is *which mode writes*, and *what exits non-zero*. `--check`\n * must never be able to fix the thing it is checking, and `--drift` must fail a\n * CI job. Both are behaviours worth a test, and neither is testable from inside\n * an action handler that calls `process.exit`.\n *\n * The command returns its output instead of printing it, so `cli.ts` stays the\n * thin shell it claims to be.\n */\n\nimport { resolve, type ResolveOptions } from \"./resolve.js\";\nimport { lockfilePath, writeLock } from \"./lockfile.js\";\nimport { checkDrift, formatDrift, hasDrift } from \"./drift.js\";\n\nexport interface LockCommandOptions extends ResolveOptions {\n /** Verify the lock is complete and current; write nothing. */\n check?: boolean;\n /** Re-resolve moving refs to their current upstream revision. */\n update?: boolean;\n /** Report refs whose upstream has moved. Needs the network. */\n drift?: boolean;\n}\n\nexport interface LockCommandResult {\n /** Lines destined for stdout. */\n output: string[];\n /** Lines destined for stderr. */\n errors: string[];\n /** What the process should exit with: 0 unless a check failed. */\n exitCode: number;\n /** Whether the lockfile on disk was actually rewritten. */\n wrote: boolean;\n /** Canonical refs pinned, sorted. */\n refs: string[];\n}\n\n/** Abbreviate a commit SHA for display; package versions pass through. */\nexport function shortRev(rev: string): string {\n return /^[0-9a-f]{40}$/i.test(rev) ? rev.slice(0, 12) : rev;\n}\n\nexport function lockCommand(\n dir: string,\n options: LockCommandOptions = {},\n): LockCommandResult {\n const { check, update, drift, ...resolveOptions } = options;\n\n const graph = resolve(dir, {\n ...resolveOptions,\n // `--check` resolves in the normal mode and then refuses to write, rather\n // than resolving frozen: it is reporting on the lock, not relying on it.\n ...(update ? { updateRefs: true } : {}),\n });\n\n const entries = Object.entries(graph.lock?.refs ?? {}).sort(([a], [b]) =>\n a < b ? -1 : a > b ? 1 : 0,\n );\n const result: LockCommandResult = {\n output: [],\n errors: [],\n exitCode: 0,\n wrote: false,\n refs: entries.map(([ref]) => ref),\n };\n\n if (check) {\n if (graph.lockDirty) {\n result.errors.push(\n `${lockfilePath(dir)} is out of date.\\n` +\n `Run \\`treelay lock ${dir}\\` and commit the result.`,\n );\n // Returns early: listing the pins underneath a failure reads as though\n // the command succeeded.\n return { ...result, exitCode: 1 };\n }\n result.output.push(`treelay.lock is up to date (${entries.length} pinned ref(s)).`);\n } else if (graph.lock && graph.lockDir) {\n result.wrote = writeLock(graph.lockDir, graph.lock);\n result.output.push(\n result.wrote\n ? `Wrote ${lockfilePath(dir)} — ${entries.length} pinned ref(s).`\n : `treelay.lock already current (${entries.length} pinned ref(s)).`,\n );\n }\n\n for (const [ref, entry] of entries) {\n result.output.push(\n ` ${ref}\\n → ${shortRev(entry.resolved)} ${entry.integrity.slice(0, 21)}…`,\n );\n }\n\n if (drift) {\n const reports = checkDrift(graph);\n result.output.push(formatDrift(reports) || \"\\nAll pinned refs match their upstream.\");\n // Non-zero so a scheduled job can notice an upstream has moved.\n if (hasDrift(reports)) result.exitCode = 1;\n }\n\n return result;\n}\n"],"mappings":"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","names":["update","shortRev"]}
package/dist/index.d.ts CHANGED
@@ -681,7 +681,12 @@ interface TreelayState {
681
681
  answers: Values;
682
682
  /** relative path → content hash of what the template last produced. */
683
683
  baseline: Record<string, string>;
684
- /** relative path → whether the template owns it (vs user-created). */
684
+ /**
685
+ * relative path → provenance. `owned` is true for a file *the user* created
686
+ * in the destination, false for one the template produced — the same polarity
687
+ * as {@link FileProvenance.owned}, and the opposite of what the name suggests
688
+ * on first reading.
689
+ */
685
690
  manifest: Record<string, {
686
691
  owned: boolean;
687
692
  fromLayer: string;
@@ -847,7 +852,7 @@ declare class SelfCompileError extends Error {
847
852
  * Extra ignore globs so a destination nested *inside* a layer never feeds its
848
853
  * own prior output back into the composition (§7).
849
854
  *
850
- * The autom-lake shape — compiling into a gitignored `build/` inside the source
855
+ * The klamath shape — compiling into a gitignored `build/` inside the source
851
856
  * repo — is explicitly supported: a destination that is a strict descendant of
852
857
  * a layer is pruned from that layer's walk. A destination that *is* the layer
853
858
  * root is rejected via {@link SelfCompileError}.
@@ -1256,6 +1261,172 @@ declare function roundTripVerify(destDir: string, graph: ResolvedGraph, values:
1256
1261
  /** Render mismatches into a message suitable for a thrown error. */
1257
1262
  declare function describeMismatches(mismatches: VerifyMismatch[]): string;
1258
1263
 
1264
+ /**
1265
+ * `eject` — sever a destination's link to its template (SPEC §7, §9).
1266
+ *
1267
+ * The composed files are already plain files on disk; the only thing binding a
1268
+ * destination to its template is `<dest>/.treelay/`. Ejecting removes that
1269
+ * directory and nothing else, so the output is untouched and the project simply
1270
+ * stops being a treelay instance.
1271
+ *
1272
+ * This is deliberately one-way. The baseline it deletes is the merge base that
1273
+ * makes `update` a three-way merge rather than a guess, and it cannot be
1274
+ * reconstructed from the output afterwards — the composed tree records what the
1275
+ * files *are*, not what the template last produced. Hence {@link EjectResult}
1276
+ * reporting what is about to be lost, and a dry run to see it first.
1277
+ */
1278
+ interface EjectOptions {
1279
+ /** Report what would be severed; remove nothing. */
1280
+ dryRun?: boolean;
1281
+ }
1282
+ interface EjectResult {
1283
+ /** The leaf template the destination was compiled from, when recorded. */
1284
+ source?: string;
1285
+ /** Layer ids lowest → highest precedence at the last compile. */
1286
+ lineage: string[];
1287
+ /** Output paths the template was tracking, and would no longer update. */
1288
+ tracked: string[];
1289
+ /** Paths present in the output that the template never owned. */
1290
+ userOwned: string[];
1291
+ /** False for a dry run; true once the state directory is gone. */
1292
+ removed: boolean;
1293
+ }
1294
+ /** Raised when a directory carries no treelay state to eject. */
1295
+ declare class NotEjectableError extends Error {
1296
+ constructor(destDir: string);
1297
+ }
1298
+ /**
1299
+ * Remove a destination's template link, leaving every composed file in place.
1300
+ *
1301
+ * Fails loud on a directory that was never compiled rather than silently
1302
+ * succeeding: "nothing to do" and "you pointed this at the wrong directory"
1303
+ * look identical to the caller otherwise.
1304
+ */
1305
+ declare function eject(destDir: string, options?: EjectOptions): EjectResult;
1306
+ /** Human-readable summary of an eject (or of what one would do). */
1307
+ declare function formatEject(result: EjectResult, destDir: string): string;
1308
+
1309
+ /**
1310
+ * `watch` — recompile a template into a destination as its sources change
1311
+ * (SPEC §9, §12 step 10).
1312
+ *
1313
+ * The loop is deliberately a full re-`resolve` plus re-`compile`, not an
1314
+ * incremental update. Editing a manifest can change the *graph* — add a parent,
1315
+ * reorder mixins, move a mount — so anything cached from the previous pass may
1316
+ * already be wrong. Composition is cheap relative to how often a human saves a
1317
+ * file, and a watch loop that is subtly stale is worse than one that is simply
1318
+ * slower.
1319
+ *
1320
+ * Only **local** layers are watched. Fetched git and npm layers are pinned to a
1321
+ * revision and cannot change underneath a build without the lock changing too,
1322
+ * so watching the cache would be watching something that is immutable by
1323
+ * construction.
1324
+ */
1325
+
1326
+ /** Something the watch loop wants the caller to know about. */
1327
+ type WatchEvent = {
1328
+ kind: "ready";
1329
+ roots: string[];
1330
+ } | {
1331
+ kind: "compiled";
1332
+ files: number;
1333
+ ms: number;
1334
+ trigger?: string;
1335
+ } | {
1336
+ kind: "failed";
1337
+ error: Error;
1338
+ trigger?: string;
1339
+ };
1340
+ interface WatchOptions extends ResolveOptions {
1341
+ /** Values to compose with, as `compile` would receive them. */
1342
+ values?: Values;
1343
+ /**
1344
+ * How long to wait for the filesystem to settle before recompiling.
1345
+ * Editors write, rename and touch in bursts; without this a single save
1346
+ * triggers several builds.
1347
+ */
1348
+ debounceMs?: number;
1349
+ /**
1350
+ * Poll for changes instead of using native filesystem events.
1351
+ *
1352
+ * Native events do not cross some boundaries — network shares, Docker bind
1353
+ * mounts, a few virtualised filesystems — where a watcher silently never
1354
+ * fires. Polling is slower and busier, but it works everywhere.
1355
+ */
1356
+ usePolling?: boolean;
1357
+ /** How often to poll, when polling. */
1358
+ pollIntervalMs?: number;
1359
+ /** Progress callback; the CLI prints these. */
1360
+ onEvent?: (event: WatchEvent) => void;
1361
+ }
1362
+ interface WatchHandle {
1363
+ /** Local layer directories being watched. */
1364
+ roots: string[];
1365
+ /** Stop watching. Safe to call more than once. */
1366
+ close: () => Promise<void>;
1367
+ /** Recompile now, as a change would. Exposed for tests and manual triggers. */
1368
+ trigger: () => Promise<void>;
1369
+ }
1370
+ /** Raised when the destination belongs to a different template. */
1371
+ declare class WatchTargetError extends Error {
1372
+ constructor(destDir: string, existing: string, incoming: string);
1373
+ }
1374
+ /**
1375
+ * Start watching `srcDir`'s local layers and recompiling into `destDir`.
1376
+ *
1377
+ * Resolves once up front so a broken graph fails immediately rather than on the
1378
+ * first save, and returns a handle the caller closes to stop.
1379
+ */
1380
+ declare function watch(srcDir: string, destDir: string, options?: WatchOptions): Promise<WatchHandle>;
1381
+ /** One-line rendering of a watch event, for the CLI. */
1382
+ declare function formatWatchEvent(event: WatchEvent): string;
1383
+
1384
+ /**
1385
+ * `validate` — one command that answers "will this compile?" (SPEC §9, §12).
1386
+ *
1387
+ * The four questions from §9: does the graph linearize, do the patches apply,
1388
+ * are there unresolved conflicts, and does the lock still describe reality.
1389
+ *
1390
+ * Every other command fails at the *first* problem, because it is trying to
1391
+ * produce something and cannot continue past a broken layer. Validate is trying
1392
+ * to produce a *report*, so it collects instead: an issue never stops the run,
1393
+ * it only stops the checks that genuinely cannot proceed without it. Finding
1394
+ * out about a cycle, then a bad patch, then stale pins across three runs is a
1395
+ * worse experience than seeing all three at once.
1396
+ */
1397
+
1398
+ /** What a finding means for the build. */
1399
+ type IssueSeverity = "error" | "warning";
1400
+ type IssueCode = "cycle" | "hierarchy" | "resolve-failed" | "lock-stale" | "drift" | "variables-unresolved" | "merge-conflict" | "compose-failed";
1401
+ interface ValidationIssue {
1402
+ severity: IssueSeverity;
1403
+ code: IssueCode;
1404
+ message: string;
1405
+ /** What to do about it, when there is a specific next step. */
1406
+ remedy?: string;
1407
+ }
1408
+ interface ValidationReport {
1409
+ /** True when nothing at `error` severity was found. Warnings do not fail. */
1410
+ ok: boolean;
1411
+ issues: ValidationIssue[];
1412
+ /** Layers resolved, once resolution succeeded. */
1413
+ layerCount?: number;
1414
+ /** Files the composition would produce, once it got that far. */
1415
+ fileCount?: number;
1416
+ /** Checks that could not run, and why — so a clean report is not misread. */
1417
+ skipped: string[];
1418
+ }
1419
+ interface ValidateOptions extends ResolveOptions {
1420
+ /** Values to compose with, as `compile` would receive them. */
1421
+ values?: Values;
1422
+ /** Probe upstreams for movement. Off by default: it needs the network. */
1423
+ drift?: boolean;
1424
+ }
1425
+ /** Run every check that can run, and report what could not. */
1426
+ declare function validate(srcDir: string, options?: ValidateOptions): Promise<ValidationReport>;
1427
+ /** Human-readable report; empty issue list produces the all-clear line. */
1428
+ declare function formatValidation(report: ValidationReport, srcDir: string): string;
1429
+
1259
1430
  /**
1260
1431
  * Git-backed layers (SPEC §3).
1261
1432
  *
@@ -1396,4 +1567,4 @@ declare function verifyIntegrity(dir: string, expected: string): boolean;
1396
1567
  */
1397
1568
  declare function liveRevision(ref: RemoteRef, fromDir: string): string | undefined;
1398
1569
 
1399
- export { ALWAYS_IGNORE, type ArrayPolicy, type BlastRadius, type BlastRadiusOptions, type Change, type ChangeKind, type CompileResult, CycleError, type DriftReport, type DriftStatus, type ExtractOptions, type ExtractResult, type FetchOptions, type FetchedLayer, type FileEntry, type FileProvenance, GitFetchError, type GitRef, InconsistentHierarchyError, IntegrityError, InvalidRefError, LOCKFILE_NAME, LOCKFILE_VERSION, type LandedChange, type LandingMode, type Layer, type LayerOrigin, type LayerRef, type LocalRef, type LockEntry, type LockFile, LockMissingError, LockfileError, MERGE_LABELS, type Manifest, MergeConflictError, type MergeStrategy, type MismatchKind, MountError, NotImplementedError, type NpmRef, NpmResolveError, type ParsedRef, type Patch3WayArgs, type PromoteOptions, type PromoteResult, type RefKind, type RemoteRef, type RemoteRefKind, type RenderMode, type Resolution, type ResolveOptions, type ResolvedGraph, STATE_DIR, SelfCompileError, type SidecarOp, type SidecarOpKind, type TextMerge3Args, type TextMerge3Result, type TreelayLock, type TreelayState, type UpdateOptions, type UpdatePlan, type Values, type VariableDecl, type VariableType, type VerifyMismatch, type VerifyResult, applyJsonPatch, applyMergePatch, applyPatch3Way, blastRadius, c3Linearize, cacheRoot, canonicalRef, checkDrift, classifyEntry, commonAncestor, compile, composeFiles, composeToMemory, createEngine, deepMerge, defaultStrategy, describeBlastRadius, describeMismatches, destExclusions, desugarSuffix, emptyLock, enumerateLayer, explain, explainDest, explainFile, extract, fetchLayer, formatDrift, formatExplanation, formatStatus, hasDrift, hasState, hashContent, hashTree, isCommitSha, isLocalRef, isSidecar, listLayerFiles, liveRevision, loadManifest, locationKey, lockfilePath, locksEqual, matchesHash, mergeStructured3, mergeText3, mergeVariableDecls, mountTarget, parseRef, parseSidecar, pinnedRef, planUpdate, promote, readBaselineFile, readLock, readState, renderString, resolve, resolveRef, resolveValues, roundTripVerify, serializeLock, sidecarTarget, sourceOf, statePaths, status, strategyFor, summarize, summarizeLayers, templateTarget, update, verifyIntegrity, writeLock, writeState };
1570
+ export { ALWAYS_IGNORE, type ArrayPolicy, type BlastRadius, type BlastRadiusOptions, type Change, type ChangeKind, type CompileResult, CycleError, type DriftReport, type DriftStatus, type EjectOptions, type EjectResult, type ExtractOptions, type ExtractResult, type FetchOptions, type FetchedLayer, type FileEntry, type FileProvenance, GitFetchError, type GitRef, InconsistentHierarchyError, IntegrityError, InvalidRefError, type IssueCode, type IssueSeverity, LOCKFILE_NAME, LOCKFILE_VERSION, type LandedChange, type LandingMode, type Layer, type LayerOrigin, type LayerRef, type LocalRef, type LockEntry, type LockFile, LockMissingError, LockfileError, MERGE_LABELS, type Manifest, MergeConflictError, type MergeStrategy, type MismatchKind, MountError, NotEjectableError, NotImplementedError, type NpmRef, NpmResolveError, type ParsedRef, type Patch3WayArgs, type PromoteOptions, type PromoteResult, type RefKind, type RemoteRef, type RemoteRefKind, type RenderMode, type Resolution, type ResolveOptions, type ResolvedGraph, STATE_DIR, SelfCompileError, type SidecarOp, type SidecarOpKind, type TextMerge3Args, type TextMerge3Result, type TreelayLock, type TreelayState, type UpdateOptions, type UpdatePlan, type ValidateOptions, type ValidationIssue, type ValidationReport, type Values, type VariableDecl, type VariableType, type VerifyMismatch, type VerifyResult, type WatchEvent, type WatchHandle, type WatchOptions, WatchTargetError, applyJsonPatch, applyMergePatch, applyPatch3Way, blastRadius, c3Linearize, cacheRoot, canonicalRef, checkDrift, classifyEntry, commonAncestor, compile, composeFiles, composeToMemory, createEngine, deepMerge, defaultStrategy, describeBlastRadius, describeMismatches, destExclusions, desugarSuffix, eject, emptyLock, enumerateLayer, explain, explainDest, explainFile, extract, fetchLayer, formatDrift, formatEject, formatExplanation, formatStatus, formatValidation, formatWatchEvent, hasDrift, hasState, hashContent, hashTree, isCommitSha, isLocalRef, isSidecar, listLayerFiles, liveRevision, loadManifest, locationKey, lockfilePath, locksEqual, matchesHash, mergeStructured3, mergeText3, mergeVariableDecls, mountTarget, parseRef, parseSidecar, pinnedRef, planUpdate, promote, readBaselineFile, readLock, readState, renderString, resolve, resolveRef, resolveValues, roundTripVerify, serializeLock, sidecarTarget, sourceOf, statePaths, status, strategyFor, summarize, summarizeLayers, templateTarget, update, validate, verifyIntegrity, watch, writeLock, writeState };