@metaobjectsdev/cli 0.24.2 → 0.24.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 +1 @@
1
- {"version":3,"file":"codegen-drift.d.ts","sourceRoot":"","sources":["../../../src/lib/codegen-drift.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAMzD,MAAM,WAAW,kBAAkB;IACjC,oEAAoE;IACpE,KAAK,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,uDAAuD;IACvD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAkCD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,oBAAoB,EAC5B,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,MAAM,EACnB,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GAC/B,OAAO,CAAC,kBAAkB,CAAC,CA4G7B"}
1
+ {"version":3,"file":"codegen-drift.d.ts","sourceRoot":"","sources":["../../../src/lib/codegen-drift.ts"],"names":[],"mappings":"AA+CA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAMzD,MAAM,WAAW,kBAAkB;IACjC,oEAAoE;IACpE,KAAK,EAAE,OAAO,CAAC;IACf,8EAA8E;IAC9E,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,uDAAuD;IACvD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAkCD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,oBAAoB,EAC5B,QAAQ,EAAE,QAAQ,EAClB,WAAW,EAAE,MAAM,EACnB,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GAC/B,OAAO,CAAC,kBAAkB,CAAC,CAwI7B"}
@@ -2,10 +2,25 @@
2
2
  //
3
3
  // Regenerates the configured codegen into a throwaway temp directory and DIFFs
4
4
  // the freshly-generated file tree against the committed output (the config's
5
- // outDir / per-target outDirs). Any difference a file present in one tree but
6
- // not the other, or differing content is drift: either "metadata changed but
7
- // `meta gen` wasn't re-run" or "a generated file was hand-edited". Reuses the
8
- // exact same `runGen` pipeline `meta gen` uses, so the comparison is faithful.
5
+ // outDir / per-target outDirs). Reuses the exact same `runGen` pipeline `meta gen`
6
+ // uses, so the comparison is faithful.
7
+ //
8
+ // A DIFFERENCE IS NOT AUTOMATICALLY DRIFT. This gate used to treat "metadata
9
+ // changed but `meta gen` wasn't re-run" and "a generated file was hand-edited" as
10
+ // one verdict. Only the first is drift; the second is the documented workflow —
11
+ // `meta gen` three-way-merges hand edits and reports "merged", and the product
12
+ // says in as many words that anything inside a generated file is fair game to
13
+ // edit. Convicting both made the gate unusable for anyone who took that offer,
14
+ // and its printed remedy was a LOOP: running `meta gen` merges the edit back in,
15
+ // so the next run failed identically. Requirement-test stubs were the worst case,
16
+ // being worthless until hand-edited.
17
+ //
18
+ // The discriminator is `.gen-state/.hashes.json`, which records what the GENERATOR
19
+ // WROTE rather than what the file became, and is the committed half of `.gen-state`
20
+ // precisely so this is answerable on a machine that did not generate the output.
21
+ // When a fresh regen hashes to what we recorded writing, the generator's
22
+ // contribution is current and the on-disk difference is a preserved hand edit.
23
+ // Design: spec/design-docs/2026-08-27-codegen-drift-hand-edits-design.md
9
24
  //
10
25
  // Faithfulness note: generated content can embed import paths computed RELATIVE
11
26
  // to a target's outDir (and between targets). To keep the regen byte-identical
@@ -15,7 +30,7 @@
15
30
  import { mkdtempSync, rmSync, existsSync, readFileSync, readdirSync, statSync, cpSync, } from "node:fs";
16
31
  import { tmpdir } from "node:os";
17
32
  import { join, relative, resolve, isAbsolute } from "node:path";
18
- import { runGen } from "@metaobjectsdev/codegen-ts";
33
+ import { runGen, contentHash, readGeneratedHash, listGeneratedPaths, } from "@metaobjectsdev/codegen-ts";
19
34
  /** Collect the committed outDirs declared by the config (default + per-target),
20
35
  * resolved against `projectRoot` — a relative outDir (the common, portable,
21
36
  * `meta init`-scaffolded shape) must resolve against the project the CLI's
@@ -66,6 +81,10 @@ function listFiles(dir) {
66
81
  */
67
82
  export async function computeCodegenDrift(config, metadata, projectRoot, scope) {
68
83
  const root = isAbsolute(projectRoot) ? projectRoot : resolve(projectRoot);
84
+ // The PROJECT's snapshot manifest, never the temp one built below — that temp
85
+ // manifest only describes the regen that just ran, so it can say nothing about
86
+ // what `meta gen` last wrote here. Same default location runner.ts derives.
87
+ const projectGenStateDir = join(root, ".metaobjects", ".gen-state");
69
88
  const committedDirs = committedOutDirs(config, root);
70
89
  if (committedDirs.length === 0) {
71
90
  return {
@@ -134,6 +153,10 @@ export async function computeCodegenDrift(config, metadata, projectRoot, scope)
134
153
  // Diff each committed outDir against its temp mirror.
135
154
  const driftedFiles = new Set();
136
155
  const lines = [];
156
+ // Whether we have any record of what we wrote here. With records, the orphan
157
+ // branch below can scope itself to our own output; with none, it cannot tell
158
+ // our stale output from a stranger's file, so the old verdict stands.
159
+ const haveWriteRecords = listGeneratedPaths(projectGenStateDir).length > 0;
137
160
  for (const committed of committedDirs) {
138
161
  const fresh = tempFor(committed);
139
162
  const committedFiles = new Set(listFiles(committed));
@@ -144,6 +167,19 @@ export async function computeCodegenDrift(config, metadata, projectRoot, scope)
144
167
  const inCommitted = committedFiles.has(rel);
145
168
  const inFresh = freshFiles.has(rel);
146
169
  if (inCommitted && !inFresh) {
170
+ // JURISDICTION, not staleness. A regen not emitting a path means
171
+ // "stale generated output" only for a path we have ever WRITTEN; for
172
+ // anything else it means the file was never ours. outDir is a
173
+ // directory, not a namespace we own: the documented quickstart itself
174
+ // fills it with strangers — `npx tsc` under a stock tsconfig (no
175
+ // `outDir` of its own) drops .js/.d.ts/.map beside the sources — and
176
+ // convicting those failed a project with no drift of any kind.
177
+ // `meta gen`'s orphan sweep already scopes itself this way via
178
+ // `listGeneratedPaths` before it will delete anything; the gate asks
179
+ // the same question of the same evidence so the two doors agree.
180
+ if (haveWriteRecords && readGeneratedHash(projectGenStateDir, relKey) === undefined) {
181
+ continue;
182
+ }
147
183
  driftedFiles.add(relKey);
148
184
  lines.push(`- ${relKey} (committed but regen would not emit it)`);
149
185
  }
@@ -154,7 +190,12 @@ export async function computeCodegenDrift(config, metadata, projectRoot, scope)
154
190
  else {
155
191
  const a = readFileSync(join(committed, rel), "utf8");
156
192
  const b = readFileSync(join(fresh, rel), "utf8");
157
- if (a !== b) {
193
+ // Not `a !== b` alone: that convicts the hand edit `meta gen` preserved.
194
+ // The question this gate can honestly answer is "is the GENERATED
195
+ // contribution current?", and the recorded hash answers exactly it.
196
+ // FAILS CLOSED, matching `isPristineGenerated`: with no recorded hash
197
+ // nothing is proven, so the old byte verdict stands.
198
+ if (a !== b && readGeneratedHash(projectGenStateDir, relKey) !== contentHash(b)) {
158
199
  driftedFiles.add(relKey);
159
200
  lines.push(`~ ${relKey} (committed content differs from a fresh regen)`);
160
201
  }
@@ -1 +1 @@
1
- {"version":3,"file":"codegen-drift.js","sourceRoot":"","sources":["../../../src/lib/codegen-drift.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"codegen-drift.js","sourceRoot":"","sources":["../../../src/lib/codegen-drift.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/cli",
3
- "version": "0.24.2",
3
+ "version": "0.24.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",
@@ -49,15 +49,15 @@
49
49
  ],
50
50
  "dependencies": {
51
51
  "@libsql/kysely-libsql": "^0.4.0",
52
- "@metaobjectsdev/codegen-ts": "0.24.2",
53
- "@metaobjectsdev/codegen-ts-react": "0.24.2",
54
- "@metaobjectsdev/codegen-ts-tanstack": "0.24.2",
55
- "@metaobjectsdev/docs-site": "0.24.2",
56
- "@metaobjectsdev/metadata": "0.24.2",
57
- "@metaobjectsdev/migrate-ts": "0.24.2",
58
- "@metaobjectsdev/render": "0.24.2",
59
- "@metaobjectsdev/runtime-ts": "0.24.2",
60
- "@metaobjectsdev/sdk": "0.24.2",
52
+ "@metaobjectsdev/codegen-ts": "0.24.3",
53
+ "@metaobjectsdev/codegen-ts-react": "0.24.3",
54
+ "@metaobjectsdev/codegen-ts-tanstack": "0.24.3",
55
+ "@metaobjectsdev/docs-site": "0.24.3",
56
+ "@metaobjectsdev/metadata": "0.24.3",
57
+ "@metaobjectsdev/migrate-ts": "0.24.3",
58
+ "@metaobjectsdev/render": "0.24.3",
59
+ "@metaobjectsdev/runtime-ts": "0.24.3",
60
+ "@metaobjectsdev/sdk": "0.24.3",
61
61
  "@toon-format/toon": "^2.3.0",
62
62
  "jiti": "^2.4.0"
63
63
  },
@@ -2,10 +2,25 @@
2
2
  //
3
3
  // Regenerates the configured codegen into a throwaway temp directory and DIFFs
4
4
  // the freshly-generated file tree against the committed output (the config's
5
- // outDir / per-target outDirs). Any difference a file present in one tree but
6
- // not the other, or differing content is drift: either "metadata changed but
7
- // `meta gen` wasn't re-run" or "a generated file was hand-edited". Reuses the
8
- // exact same `runGen` pipeline `meta gen` uses, so the comparison is faithful.
5
+ // outDir / per-target outDirs). Reuses the exact same `runGen` pipeline `meta gen`
6
+ // uses, so the comparison is faithful.
7
+ //
8
+ // A DIFFERENCE IS NOT AUTOMATICALLY DRIFT. This gate used to treat "metadata
9
+ // changed but `meta gen` wasn't re-run" and "a generated file was hand-edited" as
10
+ // one verdict. Only the first is drift; the second is the documented workflow —
11
+ // `meta gen` three-way-merges hand edits and reports "merged", and the product
12
+ // says in as many words that anything inside a generated file is fair game to
13
+ // edit. Convicting both made the gate unusable for anyone who took that offer,
14
+ // and its printed remedy was a LOOP: running `meta gen` merges the edit back in,
15
+ // so the next run failed identically. Requirement-test stubs were the worst case,
16
+ // being worthless until hand-edited.
17
+ //
18
+ // The discriminator is `.gen-state/.hashes.json`, which records what the GENERATOR
19
+ // WROTE rather than what the file became, and is the committed half of `.gen-state`
20
+ // precisely so this is answerable on a machine that did not generate the output.
21
+ // When a fresh regen hashes to what we recorded writing, the generator's
22
+ // contribution is current and the on-disk difference is a preserved hand edit.
23
+ // Design: spec/design-docs/2026-08-27-codegen-drift-hand-edits-design.md
9
24
  //
10
25
  // Faithfulness note: generated content can embed import paths computed RELATIVE
11
26
  // to a target's outDir (and between targets). To keep the regen byte-identical
@@ -24,7 +39,12 @@ import {
24
39
  } from "node:fs";
25
40
  import { tmpdir } from "node:os";
26
41
  import { join, relative, resolve, isAbsolute } from "node:path";
27
- import { runGen } from "@metaobjectsdev/codegen-ts";
42
+ import {
43
+ runGen,
44
+ contentHash,
45
+ readGeneratedHash,
46
+ listGeneratedPaths,
47
+ } from "@metaobjectsdev/codegen-ts";
28
48
  import type { MetaobjectsGenConfig } from "@metaobjectsdev/codegen-ts";
29
49
  import type { MetaData } from "@metaobjectsdev/metadata";
30
50
 
@@ -97,6 +117,11 @@ export async function computeCodegenDrift(
97
117
  ): Promise<CodegenDriftResult> {
98
118
  const root = isAbsolute(projectRoot) ? projectRoot : resolve(projectRoot);
99
119
 
120
+ // The PROJECT's snapshot manifest, never the temp one built below — that temp
121
+ // manifest only describes the regen that just ran, so it can say nothing about
122
+ // what `meta gen` last wrote here. Same default location runner.ts derives.
123
+ const projectGenStateDir = join(root, ".metaobjects", ".gen-state");
124
+
100
125
  const committedDirs = committedOutDirs(config, root);
101
126
  if (committedDirs.length === 0) {
102
127
  return {
@@ -171,6 +196,11 @@ export async function computeCodegenDrift(
171
196
  // Diff each committed outDir against its temp mirror.
172
197
  const driftedFiles = new Set<string>();
173
198
  const lines: string[] = [];
199
+
200
+ // Whether we have any record of what we wrote here. With records, the orphan
201
+ // branch below can scope itself to our own output; with none, it cannot tell
202
+ // our stale output from a stranger's file, so the old verdict stands.
203
+ const haveWriteRecords = listGeneratedPaths(projectGenStateDir).length > 0;
174
204
  for (const committed of committedDirs) {
175
205
  const fresh = tempFor(committed);
176
206
  const committedFiles = new Set(listFiles(committed));
@@ -181,6 +211,19 @@ export async function computeCodegenDrift(
181
211
  const inCommitted = committedFiles.has(rel);
182
212
  const inFresh = freshFiles.has(rel);
183
213
  if (inCommitted && !inFresh) {
214
+ // JURISDICTION, not staleness. A regen not emitting a path means
215
+ // "stale generated output" only for a path we have ever WRITTEN; for
216
+ // anything else it means the file was never ours. outDir is a
217
+ // directory, not a namespace we own: the documented quickstart itself
218
+ // fills it with strangers — `npx tsc` under a stock tsconfig (no
219
+ // `outDir` of its own) drops .js/.d.ts/.map beside the sources — and
220
+ // convicting those failed a project with no drift of any kind.
221
+ // `meta gen`'s orphan sweep already scopes itself this way via
222
+ // `listGeneratedPaths` before it will delete anything; the gate asks
223
+ // the same question of the same evidence so the two doors agree.
224
+ if (haveWriteRecords && readGeneratedHash(projectGenStateDir, relKey) === undefined) {
225
+ continue;
226
+ }
184
227
  driftedFiles.add(relKey);
185
228
  lines.push(`- ${relKey} (committed but regen would not emit it)`);
186
229
  } else if (!inCommitted && inFresh) {
@@ -189,7 +232,12 @@ export async function computeCodegenDrift(
189
232
  } else {
190
233
  const a = readFileSync(join(committed, rel), "utf8");
191
234
  const b = readFileSync(join(fresh, rel), "utf8");
192
- if (a !== b) {
235
+ // Not `a !== b` alone: that convicts the hand edit `meta gen` preserved.
236
+ // The question this gate can honestly answer is "is the GENERATED
237
+ // contribution current?", and the recorded hash answers exactly it.
238
+ // FAILS CLOSED, matching `isPristineGenerated`: with no recorded hash
239
+ // nothing is proven, so the old byte verdict stands.
240
+ if (a !== b && readGeneratedHash(projectGenStateDir, relKey) !== contentHash(b)) {
193
241
  driftedFiles.add(relKey);
194
242
  lines.push(`~ ${relKey} (committed content differs from a fresh regen)`);
195
243
  }