@metaobjectsdev/cli 1.0.0-rc.1 → 1.0.0-rc.3

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.
@@ -1,3 +1,4 @@
1
+ import { type OwnedComparison } from "../lib/owned-copy.js";
1
2
  /** Every ejectable name, in registry order (stable — matches `meta eject --list`). */
2
3
  export declare function ejectableNames(): string[];
3
4
  export interface EjectOptions {
@@ -16,7 +17,20 @@ export interface EjectResult {
16
17
  packageName: string;
17
18
  /** Advisory lines about packages the ejected file imports but the project lacks. */
18
19
  dependencyNotes: string[];
19
- status: "created" | "preserved";
20
+ status: "created" | "preserved" | "replaced";
21
+ /**
22
+ * How the file ALREADY on disk compares to the reference template this CLI ships.
23
+ * `undefined` when there was no file.
24
+ *
25
+ * An owned copy is the one artifact ADR-0034 hands an adopter and then never speaks
26
+ * about again. It can sit any number of releases behind the engine it runs against —
27
+ * on the public reference app three of them were ~five minor lines stale, carrying a
28
+ * bare `ts-poet` import (the 0.21.6 split-tree defect), the pre-#248 subtype
29
+ * persistability check and a missing `isWriteThrough` branch — and every gate was
30
+ * green, because nothing compares an owned copy to anything. Reporting this is the
31
+ * cheapest thing that makes that condition observable.
32
+ */
33
+ comparison?: OwnedComparison | undefined;
20
34
  }
21
35
  /**
22
36
  * An ejected file is ordinary source in the adopter's repo, so its imports must be
@@ -1 +1 @@
1
- {"version":3,"file":"eject.d.ts","sourceRoot":"","sources":["../../../src/commands/eject.ts"],"names":[],"mappings":"AA4DA,sFAAsF;AACtF,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AA2CD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb;sFACkF;IAClF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,MAAM,EAAE,SAAS,GAAG,WAAW,CAAC;CACjC;AAoBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAkBvG;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CA8B7E;AAeD,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA0D/E"}
1
+ {"version":3,"file":"eject.d.ts","sourceRoot":"","sources":["../../../src/commands/eject.ts"],"names":[],"mappings":"AAcA,OAAO,EAAoB,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AA+C9E,sFAAsF;AACtF,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAEzC;AA2CD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb;sFACkF;IAClF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;IAC7C;;;;;;;;;;;OAWG;IACH,UAAU,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;CAC1C;AAoBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAkBvG;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAsC7E;AAmED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA4G/E"}
@@ -12,6 +12,7 @@ import * as tanstackTpl from "@metaobjectsdev/codegen-ts-tanstack";
12
12
  import { parseEjectArgs } from "../lib/args.js";
13
13
  import { log } from "../lib/log.js";
14
14
  import { declaredDependencyNames, readPackageManifest } from "../lib/package-manifest.js";
15
+ import { compareOwnedCopy } from "../lib/owned-copy.js";
15
16
  // Mirrors `OWNED_GENERATORS_DIR` in init.ts's `writeOwnedGenerators` — same directory,
16
17
  // same never-clobber-without-consent contract. Kept as its own local constant rather
17
18
  // than shared: eject is a standalone operation on ANY name, not a byproduct of init,
@@ -157,20 +158,71 @@ export async function ejectGenerator(opts) {
157
158
  packageName: source.packageName,
158
159
  dependencyNotes: notes,
159
160
  };
160
- if (!opts.force && (await fileExists(abs))) {
161
- return { ...common, status: "preserved" };
161
+ const existing = (await fileExists(abs)) ? await readFile(abs, "utf8") : undefined;
162
+ const comparison = existing === undefined ? undefined : await compareOwnedCopy(existing, templateSource);
163
+ if (!opts.force && existing !== undefined) {
164
+ return { ...common, status: "preserved", comparison };
162
165
  }
163
166
  await mkdir(join(opts.cwd, OWNED_GENERATORS_DIR), { recursive: true });
164
167
  await writeFile(abs, templateSource, "utf8");
165
- return { ...common, status: "created" };
168
+ return {
169
+ ...common,
170
+ status: existing === undefined ? "created" : "replaced",
171
+ comparison,
172
+ };
166
173
  }
167
- function listOutput() {
174
+ /**
175
+ * `--list` doubles as the staleness report for what this project ALREADY owns.
176
+ *
177
+ * A listing of names you could eject is the less useful half. The half nobody had is
178
+ * "which of the copies I own have fallen behind" — the condition that let a reference
179
+ * app run generators ~five minor lines old, carrying three retired patterns, with every
180
+ * gate green. Marking each owned copy `identical` / `differs` makes it a one-command
181
+ * answer instead of a diff nobody thinks to run.
182
+ */
183
+ async function listOutput(cwd) {
168
184
  const lines = [];
169
185
  lines.push("Ejectable generators (copy any of these into codegen/generators/ and own it):");
170
186
  lines.push("");
187
+ let anyStale = false;
171
188
  for (const source of SOURCES) {
172
189
  lines.push(`${source.packageName}:`);
173
- lines.push(` ${source.names.join(", ")}`);
190
+ for (const name of source.names) {
191
+ const abs = join(cwd, OWNED_GENERATORS_DIR, `${name}.ts`);
192
+ if (!(await fileExists(abs))) {
193
+ lines.push(` ${name}`);
194
+ continue;
195
+ }
196
+ let ref;
197
+ try {
198
+ ref = await readFile(join(source.root(), `${name}.ts`), "utf8");
199
+ }
200
+ catch {
201
+ lines.push(` ${name} [owned]`);
202
+ continue;
203
+ }
204
+ const owned = await readFile(abs, "utf8");
205
+ const cmp = await compareOwnedCopy(owned, ref);
206
+ if (cmp.verdict === "identical") {
207
+ lines.push(` ${name} [owned — identical to the reference]`);
208
+ }
209
+ else if (cmp.verdict === "reformatted") {
210
+ lines.push(` ${name} [owned — same content as the reference, your formatting]`);
211
+ }
212
+ else {
213
+ anyStale = true;
214
+ lines.push(` ${name} [owned — DIFFERS: ${cmp.referenceOnly} line(s) behind, ` +
215
+ `${cmp.localOnly} line(s) of your own]`);
216
+ }
217
+ }
218
+ lines.push("");
219
+ }
220
+ if (anyStale) {
221
+ lines.push("Both counts ignore formatting: each file is re-formatted and its lines sorted " +
222
+ "before comparing, so re-wrapping and import order never show up here.");
223
+ lines.push(" \"behind\" — lines the reference has that your copy does not. Upstream moved.");
224
+ lines.push(" \"of your own\" — lines your copy has that the reference does not. Your customization.");
225
+ lines.push("`meta eject <name>` prints the diff command that shows which lines they are.");
174
226
  lines.push("");
175
227
  }
176
228
  lines.push("Run: meta eject <name>");
@@ -186,7 +238,7 @@ export async function ejectCommand(args, cwd) {
186
238
  return 2;
187
239
  }
188
240
  if (flags.list) {
189
- log.info(listOutput());
241
+ log.info(await listOutput(cwd));
190
242
  return 0;
191
243
  }
192
244
  if (flags.name === undefined) {
@@ -196,7 +248,46 @@ export async function ejectCommand(args, cwd) {
196
248
  try {
197
249
  const result = await ejectGenerator({ cwd, name: flags.name, force: flags.force });
198
250
  if (result.status === "preserved") {
199
- log.info(`${result.path} already exists — left untouched (pass --force to overwrite).`);
251
+ // "already exists — left untouched" was the whole message, and it answered the
252
+ // question nobody has. What an owner needs to know is whether their copy still
253
+ // matches what this CLI ships — the only way an owned generator's staleness is
254
+ // ever observable, since no gate compares the two.
255
+ if (result.comparison?.verdict === "identical") {
256
+ log.info(`${result.path} already exists and is IDENTICAL to the ${result.packageName} ` +
257
+ "reference template — nothing to do.");
258
+ }
259
+ else if (result.comparison?.verdict === "reformatted") {
260
+ // Worth its own branch: this is the state a project that formats what it owns
261
+ // is in permanently, and calling it DIFFERS taught every one of them to ignore
262
+ // the line that is supposed to warn them.
263
+ log.info(`${result.path} already exists and has the SAME CONTENT as the ` +
264
+ `${result.packageName} reference template, in your own formatting — ` +
265
+ "nothing to do.");
266
+ }
267
+ else {
268
+ log.info(`${result.path} already exists and DIFFERS from the ${result.packageName} ` +
269
+ `reference template (${result.comparison?.referenceOnly ?? 0} line(s) behind it, ` +
270
+ `${result.comparison?.localOnly ?? 0} line(s) of your own) — left untouched.`);
271
+ log.info(" Formatting is not counted: both files are re-formatted and their lines " +
272
+ "sorted before comparing, so re-wrapping and import order never show up.");
273
+ log.info(" That difference is either YOUR customization or upstream having moved on. " +
274
+ "See which, before deciding:");
275
+ log.info(` diff -u node_modules/${result.packageName}/src/reference/${flags.name}.ts ${result.path}`);
276
+ log.info(" To take upstream changes AND keep your customization, three-way merge them — " +
277
+ "`git merge-file --diff3 <your copy> <the reference you ejected from> <the reference above>`. " +
278
+ "`--force` does NOT merge: it replaces the file and your customization with it.");
279
+ }
280
+ }
281
+ else if (result.status === "replaced") {
282
+ // Never let a --force over a modified file be silent: this is the step that
283
+ // destroys an adopter's customization, and the file's own header is often the
284
+ // only record that the customization was deliberate.
285
+ log.info(`Ejected "${flags.name}" -> ${result.path}, REPLACING the file that was there` +
286
+ (result.comparison?.verdict === "differs"
287
+ ? ` and DISCARDING ${result.comparison.localOnly} line(s) it had that the reference does not.`
288
+ : result.comparison?.verdict === "reformatted"
289
+ ? " (its content was already the reference's — only your formatting is gone)."
290
+ : " (it was already identical to the reference)."));
200
291
  }
201
292
  else {
202
293
  log.info(`Ejected "${flags.name}" -> ${result.path}. You own it now (ADR-0034 scaffold-and-own).`);
@@ -1 +1 @@
1
- {"version":3,"file":"eject.js","sourceRoot":"","sources":["../../../src/commands/eject.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"eject.js","sourceRoot":"","sources":["../../../src/commands/eject.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ export type OwnedVerdict =
2
+ /** Byte-for-byte the reference. */
3
+ "identical"
4
+ /** Same content, different layout — a formatter ran over it, nothing else. */
5
+ | "reformatted"
6
+ /** Genuine content difference: your edits, upstream's, or both. */
7
+ | "differs";
8
+ export interface OwnedComparison {
9
+ verdict: OwnedVerdict;
10
+ /** Canonical lines the COPY has that the reference does not — your edits, and what
11
+ * `--force` would destroy. */
12
+ localOnly: number;
13
+ /** Canonical lines the REFERENCE has that the copy does not — how far behind it is. */
14
+ referenceOnly: number;
15
+ }
16
+ /** The canonical line multiset of one file, sorted. Exported for the tests that pin
17
+ * each formatter-insensitivity claim above against real reformatted input. */
18
+ export declare function canonicalLines(src: string): Promise<string[]>;
19
+ export declare function compareOwnedCopy(owned: string, reference: string): Promise<OwnedComparison>;
20
+ //# sourceMappingURL=owned-copy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owned-copy.d.ts","sourceRoot":"","sources":["../../../src/lib/owned-copy.ts"],"names":[],"mappings":"AAqCA,MAAM,MAAM,YAAY;AACtB,mCAAmC;AACjC,WAAW;AACb,8EAA8E;GAC5E,aAAa;AACf,mEAAmE;GACjE,SAAS,CAAC;AAEd,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,YAAY,CAAC;IACtB;mCAC+B;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,uFAAuF;IACvF,aAAa,EAAE,MAAM,CAAC;CACvB;AAuBD;+EAC+E;AAC/E,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAmBnE;AAeD,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAUjG"}
@@ -0,0 +1,107 @@
1
+ // How an OWNED generator copy compares to the reference template this CLI ships.
2
+ //
3
+ // ADR-0034 hands an adopter a file and then never speaks about it again, so `meta
4
+ // eject` and `meta eject --list` are the only places that condition is ever observable.
5
+ // The first version of this compared BYTES and counted lines the copy had that the
6
+ // reference did not. On a project that runs its own formatter over the code it owns —
7
+ // which is most of them, and it is the whole point that these are ordinary files in
8
+ // their repo — that answer is useless: every copy reads DIFFERS forever, and the count
9
+ // is dominated by re-wrapping and import sorting rather than by anything anyone wrote.
10
+ // One adopter estate had all of its owned copies reporting double-digit differences
11
+ // while being, in content, exactly the reference.
12
+ //
13
+ // So the comparison is made on a CANONICAL form instead, and the verdict grew a third
14
+ // value — `reformatted`, meaning "same content, your layout". The canonical form is
15
+ // produced by re-formatting both sides through the SAME formatter, deliberately at
16
+ // `lineWidth: 1` so every construct explodes onto its own line, then sorting those
17
+ // lines. That composition is what makes it survive each way a formatter rewrites a
18
+ // file without changing it:
19
+ //
20
+ // indentation, wrapping, quote style, semicolons → the formatter normalizes them
21
+ // trailing commas → `trailingCommas: "none"` + the
22
+ // per-line strip below (reordering
23
+ // a list moves which member is last)
24
+ // import statement AND specifier order → exploded one per line, then sorted
25
+ //
26
+ // The cost is deliberate and stated: sorting lines also makes the comparison blind to
27
+ // a pure REORDERING of real code. That is the right trade for a report line whose job
28
+ // is "have you fallen behind upstream" — a reorder with no content change is not
29
+ // staleness — and every message that carries this verdict also prints the `diff`
30
+ // command, which is not blind to anything.
31
+ //
32
+ // Fail-safe: if the formatter is unavailable or rejects the input (an adopter's copy is
33
+ // theirs and may not even parse), canonicalization degrades to trimmed raw lines. The
34
+ // verdict then reads `differs` where it might have read `reformatted` — the pre-existing
35
+ // behaviour, never a false `reformatted`.
36
+ import { Biome, Distribution } from "@biomejs/js-api";
37
+ // One instance, created lazily and shared. Cache the PROMISE so concurrent first calls
38
+ // (--list compares every owned copy) share one in-flight create rather than racing.
39
+ let _biome;
40
+ function getBiome() {
41
+ if (!_biome) {
42
+ _biome = Biome.create({ distribution: Distribution.NODE })
43
+ .then((biome) => {
44
+ biome.applyConfiguration({
45
+ formatter: { enabled: true, indentStyle: "space", indentWidth: 2, lineWidth: 1 },
46
+ javascript: { formatter: { quoteStyle: "double", semicolons: "always", trailingCommas: "none" } },
47
+ });
48
+ return biome;
49
+ })
50
+ // A comparison is a REPORT LINE. If the formatter cannot start, say less — never
51
+ // fail the command the adopter actually ran.
52
+ .catch(() => undefined);
53
+ }
54
+ return _biome;
55
+ }
56
+ /** The canonical line multiset of one file, sorted. Exported for the tests that pin
57
+ * each formatter-insensitivity claim above against real reformatted input. */
58
+ export async function canonicalLines(src) {
59
+ let text = src;
60
+ const biome = await getBiome();
61
+ if (biome !== undefined) {
62
+ try {
63
+ const result = biome.formatContent(src, { filePath: "in-memory.ts" });
64
+ if (result.diagnostics.length === 0)
65
+ text = result.content;
66
+ }
67
+ catch {
68
+ /* keep the raw text — see the fail-safe note above */
69
+ }
70
+ }
71
+ return text
72
+ .split("\n")
73
+ // The trailing-comma strip is per LINE and only meaningful because every construct
74
+ // is already on its own line: reordering an import list changes which specifier is
75
+ // last, and only the last one lacks a comma.
76
+ .map((l) => l.trim().replace(/,$/, ""))
77
+ .filter((l) => l !== "")
78
+ .sort();
79
+ }
80
+ /** How many members of `a` are not in `b`, counting duplicates. */
81
+ function countMissing(a, b) {
82
+ const remaining = new Map();
83
+ for (const l of b)
84
+ remaining.set(l, (remaining.get(l) ?? 0) + 1);
85
+ let missing = 0;
86
+ for (const l of a) {
87
+ const n = remaining.get(l) ?? 0;
88
+ if (n > 0)
89
+ remaining.set(l, n - 1);
90
+ else
91
+ missing++;
92
+ }
93
+ return missing;
94
+ }
95
+ export async function compareOwnedCopy(owned, reference) {
96
+ if (owned === reference)
97
+ return { verdict: "identical", localOnly: 0, referenceOnly: 0 };
98
+ const [a, b] = await Promise.all([canonicalLines(owned), canonicalLines(reference)]);
99
+ const localOnly = countMissing(a, b);
100
+ const referenceOnly = countMissing(b, a);
101
+ return {
102
+ verdict: localOnly === 0 && referenceOnly === 0 ? "reformatted" : "differs",
103
+ localOnly,
104
+ referenceOnly,
105
+ };
106
+ }
107
+ //# sourceMappingURL=owned-copy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owned-copy.js","sourceRoot":"","sources":["../../../src/lib/owned-copy.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/cli",
3
- "version": "1.0.0-rc.1",
3
+ "version": "1.0.0-rc.3",
4
4
  "description": "CLI for MetaObjects: scaffold, codegen, migrate, and drift-detection commands.",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -48,16 +48,18 @@
48
48
  "drift-detection"
49
49
  ],
50
50
  "dependencies": {
51
+ "@biomejs/js-api": "^0.7.0",
52
+ "@biomejs/wasm-nodejs": "^1.9.4",
51
53
  "@libsql/kysely-libsql": "^0.4.0",
52
- "@metaobjectsdev/codegen-ts": "1.0.0-rc.1",
53
- "@metaobjectsdev/codegen-ts-react": "1.0.0-rc.1",
54
- "@metaobjectsdev/codegen-ts-tanstack": "1.0.0-rc.1",
55
- "@metaobjectsdev/docs-site": "1.0.0-rc.1",
56
- "@metaobjectsdev/metadata": "1.0.0-rc.1",
57
- "@metaobjectsdev/migrate-ts": "1.0.0-rc.1",
58
- "@metaobjectsdev/render": "1.0.0-rc.1",
59
- "@metaobjectsdev/runtime-ts": "1.0.0-rc.1",
60
- "@metaobjectsdev/sdk": "1.0.0-rc.1",
54
+ "@metaobjectsdev/codegen-ts": "1.0.0-rc.3",
55
+ "@metaobjectsdev/codegen-ts-react": "1.0.0-rc.3",
56
+ "@metaobjectsdev/codegen-ts-tanstack": "1.0.0-rc.3",
57
+ "@metaobjectsdev/docs-site": "1.0.0-rc.3",
58
+ "@metaobjectsdev/metadata": "1.0.0-rc.3",
59
+ "@metaobjectsdev/migrate-ts": "1.0.0-rc.3",
60
+ "@metaobjectsdev/render": "1.0.0-rc.3",
61
+ "@metaobjectsdev/runtime-ts": "1.0.0-rc.3",
62
+ "@metaobjectsdev/sdk": "1.0.0-rc.3",
61
63
  "@toon-format/toon": "^2.3.0",
62
64
  "jiti": "^2.4.0"
63
65
  },
@@ -12,6 +12,7 @@ import * as tanstackTpl from "@metaobjectsdev/codegen-ts-tanstack";
12
12
  import { parseEjectArgs } from "../lib/args.js";
13
13
  import { log } from "../lib/log.js";
14
14
  import { declaredDependencyNames, readPackageManifest } from "../lib/package-manifest.js";
15
+ import { compareOwnedCopy, type OwnedComparison } from "../lib/owned-copy.js";
15
16
 
16
17
  // Mirrors `OWNED_GENERATORS_DIR` in init.ts's `writeOwnedGenerators` — same directory,
17
18
  // same never-clobber-without-consent contract. Kept as its own local constant rather
@@ -121,7 +122,20 @@ export interface EjectResult {
121
122
  packageName: string;
122
123
  /** Advisory lines about packages the ejected file imports but the project lacks. */
123
124
  dependencyNotes: string[];
124
- status: "created" | "preserved";
125
+ status: "created" | "preserved" | "replaced";
126
+ /**
127
+ * How the file ALREADY on disk compares to the reference template this CLI ships.
128
+ * `undefined` when there was no file.
129
+ *
130
+ * An owned copy is the one artifact ADR-0034 hands an adopter and then never speaks
131
+ * about again. It can sit any number of releases behind the engine it runs against —
132
+ * on the public reference app three of them were ~five minor lines stale, carrying a
133
+ * bare `ts-poet` import (the 0.21.6 split-tree defect), the pre-#248 subtype
134
+ * persistability check and a missing `isWriteThrough` branch — and every gate was
135
+ * green, because nothing compares an owned copy to anything. Reporting this is the
136
+ * cheapest thing that makes that condition observable.
137
+ */
138
+ comparison?: OwnedComparison | undefined;
125
139
  }
126
140
 
127
141
  /** The `@metaobjectsdev/*` packages an ejected template imports, read from the file
@@ -199,22 +213,82 @@ export async function ejectGenerator(opts: EjectOptions): Promise<EjectResult> {
199
213
  dependencyNotes: notes,
200
214
  };
201
215
 
202
- if (!opts.force && (await fileExists(abs))) {
203
- return { ...common, status: "preserved" };
216
+ const existing = (await fileExists(abs)) ? await readFile(abs, "utf8") : undefined;
217
+ const comparison =
218
+ existing === undefined ? undefined : await compareOwnedCopy(existing, templateSource);
219
+
220
+ if (!opts.force && existing !== undefined) {
221
+ return { ...common, status: "preserved", comparison };
204
222
  }
205
223
 
206
224
  await mkdir(join(opts.cwd, OWNED_GENERATORS_DIR), { recursive: true });
207
225
  await writeFile(abs, templateSource, "utf8");
208
- return { ...common, status: "created" };
226
+ return {
227
+ ...common,
228
+ status: existing === undefined ? "created" : "replaced",
229
+ comparison,
230
+ };
209
231
  }
210
232
 
211
- function listOutput(): string {
233
+ /**
234
+ * `--list` doubles as the staleness report for what this project ALREADY owns.
235
+ *
236
+ * A listing of names you could eject is the less useful half. The half nobody had is
237
+ * "which of the copies I own have fallen behind" — the condition that let a reference
238
+ * app run generators ~five minor lines old, carrying three retired patterns, with every
239
+ * gate green. Marking each owned copy `identical` / `differs` makes it a one-command
240
+ * answer instead of a diff nobody thinks to run.
241
+ */
242
+ async function listOutput(cwd: string): Promise<string> {
212
243
  const lines: string[] = [];
213
244
  lines.push("Ejectable generators (copy any of these into codegen/generators/ and own it):");
214
245
  lines.push("");
246
+ let anyStale = false;
215
247
  for (const source of SOURCES) {
216
248
  lines.push(`${source.packageName}:`);
217
- lines.push(` ${source.names.join(", ")}`);
249
+ for (const name of source.names) {
250
+ const abs = join(cwd, OWNED_GENERATORS_DIR, `${name}.ts`);
251
+ if (!(await fileExists(abs))) {
252
+ lines.push(` ${name}`);
253
+ continue;
254
+ }
255
+ let ref: string;
256
+ try {
257
+ ref = await readFile(join(source.root(), `${name}.ts`), "utf8");
258
+ } catch {
259
+ lines.push(` ${name} [owned]`);
260
+ continue;
261
+ }
262
+ const owned = await readFile(abs, "utf8");
263
+ const cmp = await compareOwnedCopy(owned, ref);
264
+ if (cmp.verdict === "identical") {
265
+ lines.push(` ${name} [owned — identical to the reference]`);
266
+ } else if (cmp.verdict === "reformatted") {
267
+ lines.push(` ${name} [owned — same content as the reference, your formatting]`);
268
+ } else {
269
+ anyStale = true;
270
+ lines.push(
271
+ ` ${name} [owned — DIFFERS: ${cmp.referenceOnly} line(s) behind, ` +
272
+ `${cmp.localOnly} line(s) of your own]`,
273
+ );
274
+ }
275
+ }
276
+ lines.push("");
277
+ }
278
+ if (anyStale) {
279
+ lines.push(
280
+ "Both counts ignore formatting: each file is re-formatted and its lines sorted " +
281
+ "before comparing, so re-wrapping and import order never show up here.",
282
+ );
283
+ lines.push(
284
+ " \"behind\" — lines the reference has that your copy does not. Upstream moved.",
285
+ );
286
+ lines.push(
287
+ " \"of your own\" — lines your copy has that the reference does not. Your customization.",
288
+ );
289
+ lines.push(
290
+ "`meta eject <name>` prints the diff command that shows which lines they are.",
291
+ );
218
292
  lines.push("");
219
293
  }
220
294
  lines.push("Run: meta eject <name>");
@@ -231,7 +305,7 @@ export async function ejectCommand(args: string[], cwd: string): Promise<number>
231
305
  }
232
306
 
233
307
  if (flags.list) {
234
- log.info(listOutput());
308
+ log.info(await listOutput(cwd));
235
309
  return 0;
236
310
  }
237
311
 
@@ -243,7 +317,57 @@ export async function ejectCommand(args: string[], cwd: string): Promise<number>
243
317
  try {
244
318
  const result = await ejectGenerator({ cwd, name: flags.name, force: flags.force });
245
319
  if (result.status === "preserved") {
246
- log.info(`${result.path} already exists — left untouched (pass --force to overwrite).`);
320
+ // "already exists — left untouched" was the whole message, and it answered the
321
+ // question nobody has. What an owner needs to know is whether their copy still
322
+ // matches what this CLI ships — the only way an owned generator's staleness is
323
+ // ever observable, since no gate compares the two.
324
+ if (result.comparison?.verdict === "identical") {
325
+ log.info(
326
+ `${result.path} already exists and is IDENTICAL to the ${result.packageName} ` +
327
+ "reference template — nothing to do.",
328
+ );
329
+ } else if (result.comparison?.verdict === "reformatted") {
330
+ // Worth its own branch: this is the state a project that formats what it owns
331
+ // is in permanently, and calling it DIFFERS taught every one of them to ignore
332
+ // the line that is supposed to warn them.
333
+ log.info(
334
+ `${result.path} already exists and has the SAME CONTENT as the ` +
335
+ `${result.packageName} reference template, in your own formatting — ` +
336
+ "nothing to do.",
337
+ );
338
+ } else {
339
+ log.info(
340
+ `${result.path} already exists and DIFFERS from the ${result.packageName} ` +
341
+ `reference template (${result.comparison?.referenceOnly ?? 0} line(s) behind it, ` +
342
+ `${result.comparison?.localOnly ?? 0} line(s) of your own) — left untouched.`,
343
+ );
344
+ log.info(
345
+ " Formatting is not counted: both files are re-formatted and their lines " +
346
+ "sorted before comparing, so re-wrapping and import order never show up.",
347
+ );
348
+ log.info(
349
+ " That difference is either YOUR customization or upstream having moved on. " +
350
+ "See which, before deciding:",
351
+ );
352
+ log.info(` diff -u node_modules/${result.packageName}/src/reference/${flags.name}.ts ${result.path}`);
353
+ log.info(
354
+ " To take upstream changes AND keep your customization, three-way merge them — " +
355
+ "`git merge-file --diff3 <your copy> <the reference you ejected from> <the reference above>`. " +
356
+ "`--force` does NOT merge: it replaces the file and your customization with it.",
357
+ );
358
+ }
359
+ } else if (result.status === "replaced") {
360
+ // Never let a --force over a modified file be silent: this is the step that
361
+ // destroys an adopter's customization, and the file's own header is often the
362
+ // only record that the customization was deliberate.
363
+ log.info(
364
+ `Ejected "${flags.name}" -> ${result.path}, REPLACING the file that was there` +
365
+ (result.comparison?.verdict === "differs"
366
+ ? ` and DISCARDING ${result.comparison.localOnly} line(s) it had that the reference does not.`
367
+ : result.comparison?.verdict === "reformatted"
368
+ ? " (its content was already the reference's — only your formatting is gone)."
369
+ : " (it was already identical to the reference)."),
370
+ );
247
371
  } else {
248
372
  log.info(`Ejected "${flags.name}" -> ${result.path}. You own it now (ADR-0034 scaffold-and-own).`);
249
373
  }
@@ -0,0 +1,122 @@
1
+ // How an OWNED generator copy compares to the reference template this CLI ships.
2
+ //
3
+ // ADR-0034 hands an adopter a file and then never speaks about it again, so `meta
4
+ // eject` and `meta eject --list` are the only places that condition is ever observable.
5
+ // The first version of this compared BYTES and counted lines the copy had that the
6
+ // reference did not. On a project that runs its own formatter over the code it owns —
7
+ // which is most of them, and it is the whole point that these are ordinary files in
8
+ // their repo — that answer is useless: every copy reads DIFFERS forever, and the count
9
+ // is dominated by re-wrapping and import sorting rather than by anything anyone wrote.
10
+ // One adopter estate had all of its owned copies reporting double-digit differences
11
+ // while being, in content, exactly the reference.
12
+ //
13
+ // So the comparison is made on a CANONICAL form instead, and the verdict grew a third
14
+ // value — `reformatted`, meaning "same content, your layout". The canonical form is
15
+ // produced by re-formatting both sides through the SAME formatter, deliberately at
16
+ // `lineWidth: 1` so every construct explodes onto its own line, then sorting those
17
+ // lines. That composition is what makes it survive each way a formatter rewrites a
18
+ // file without changing it:
19
+ //
20
+ // indentation, wrapping, quote style, semicolons → the formatter normalizes them
21
+ // trailing commas → `trailingCommas: "none"` + the
22
+ // per-line strip below (reordering
23
+ // a list moves which member is last)
24
+ // import statement AND specifier order → exploded one per line, then sorted
25
+ //
26
+ // The cost is deliberate and stated: sorting lines also makes the comparison blind to
27
+ // a pure REORDERING of real code. That is the right trade for a report line whose job
28
+ // is "have you fallen behind upstream" — a reorder with no content change is not
29
+ // staleness — and every message that carries this verdict also prints the `diff`
30
+ // command, which is not blind to anything.
31
+ //
32
+ // Fail-safe: if the formatter is unavailable or rejects the input (an adopter's copy is
33
+ // theirs and may not even parse), canonicalization degrades to trimmed raw lines. The
34
+ // verdict then reads `differs` where it might have read `reformatted` — the pre-existing
35
+ // behaviour, never a false `reformatted`.
36
+ import { Biome, Distribution } from "@biomejs/js-api";
37
+
38
+ export type OwnedVerdict =
39
+ /** Byte-for-byte the reference. */
40
+ | "identical"
41
+ /** Same content, different layout — a formatter ran over it, nothing else. */
42
+ | "reformatted"
43
+ /** Genuine content difference: your edits, upstream's, or both. */
44
+ | "differs";
45
+
46
+ export interface OwnedComparison {
47
+ verdict: OwnedVerdict;
48
+ /** Canonical lines the COPY has that the reference does not — your edits, and what
49
+ * `--force` would destroy. */
50
+ localOnly: number;
51
+ /** Canonical lines the REFERENCE has that the copy does not — how far behind it is. */
52
+ referenceOnly: number;
53
+ }
54
+
55
+ // One instance, created lazily and shared. Cache the PROMISE so concurrent first calls
56
+ // (--list compares every owned copy) share one in-flight create rather than racing.
57
+ let _biome: Promise<Biome | undefined> | undefined;
58
+
59
+ function getBiome(): Promise<Biome | undefined> {
60
+ if (!_biome) {
61
+ _biome = Biome.create({ distribution: Distribution.NODE })
62
+ .then((biome) => {
63
+ biome.applyConfiguration({
64
+ formatter: { enabled: true, indentStyle: "space", indentWidth: 2, lineWidth: 1 },
65
+ javascript: { formatter: { quoteStyle: "double", semicolons: "always", trailingCommas: "none" } },
66
+ });
67
+ return biome;
68
+ })
69
+ // A comparison is a REPORT LINE. If the formatter cannot start, say less — never
70
+ // fail the command the adopter actually ran.
71
+ .catch(() => undefined);
72
+ }
73
+ return _biome;
74
+ }
75
+
76
+ /** The canonical line multiset of one file, sorted. Exported for the tests that pin
77
+ * each formatter-insensitivity claim above against real reformatted input. */
78
+ export async function canonicalLines(src: string): Promise<string[]> {
79
+ let text = src;
80
+ const biome = await getBiome();
81
+ if (biome !== undefined) {
82
+ try {
83
+ const result = biome.formatContent(src, { filePath: "in-memory.ts" });
84
+ if (result.diagnostics.length === 0) text = result.content;
85
+ } catch {
86
+ /* keep the raw text — see the fail-safe note above */
87
+ }
88
+ }
89
+ return text
90
+ .split("\n")
91
+ // The trailing-comma strip is per LINE and only meaningful because every construct
92
+ // is already on its own line: reordering an import list changes which specifier is
93
+ // last, and only the last one lacks a comma.
94
+ .map((l) => l.trim().replace(/,$/, ""))
95
+ .filter((l) => l !== "")
96
+ .sort();
97
+ }
98
+
99
+ /** How many members of `a` are not in `b`, counting duplicates. */
100
+ function countMissing(a: string[], b: string[]): number {
101
+ const remaining = new Map<string, number>();
102
+ for (const l of b) remaining.set(l, (remaining.get(l) ?? 0) + 1);
103
+ let missing = 0;
104
+ for (const l of a) {
105
+ const n = remaining.get(l) ?? 0;
106
+ if (n > 0) remaining.set(l, n - 1);
107
+ else missing++;
108
+ }
109
+ return missing;
110
+ }
111
+
112
+ export async function compareOwnedCopy(owned: string, reference: string): Promise<OwnedComparison> {
113
+ if (owned === reference) return { verdict: "identical", localOnly: 0, referenceOnly: 0 };
114
+ const [a, b] = await Promise.all([canonicalLines(owned), canonicalLines(reference)]);
115
+ const localOnly = countMissing(a, b);
116
+ const referenceOnly = countMissing(b, a);
117
+ return {
118
+ verdict: localOnly === 0 && referenceOnly === 0 ? "reformatted" : "differs",
119
+ localOnly,
120
+ referenceOnly,
121
+ };
122
+ }