@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":"
|
|
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).
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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
|
-
|
|
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":"AAAA,kEAAkE;AAClE,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,gFAAgF;AAChF,+EAA+E;AAC/E,8EAA8E;AAC9E,+EAA+E;AAC/E,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,6EAA6E;AAC7E,8EAA8E;AAC9E,mEAAmE;AAEnE,OAAO,EACL,WAAW,EACX,MAAM,EACN,UAAU,EACV,YAAY,EACZ,WAAW,EACX,QAAQ,EACR,MAAM,GACP,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,
|
|
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.
|
|
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.
|
|
53
|
-
"@metaobjectsdev/codegen-ts-react": "0.24.
|
|
54
|
-
"@metaobjectsdev/codegen-ts-tanstack": "0.24.
|
|
55
|
-
"@metaobjectsdev/docs-site": "0.24.
|
|
56
|
-
"@metaobjectsdev/metadata": "0.24.
|
|
57
|
-
"@metaobjectsdev/migrate-ts": "0.24.
|
|
58
|
-
"@metaobjectsdev/render": "0.24.
|
|
59
|
-
"@metaobjectsdev/runtime-ts": "0.24.
|
|
60
|
-
"@metaobjectsdev/sdk": "0.24.
|
|
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
|
},
|
package/src/lib/codegen-drift.ts
CHANGED
|
@@ -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).
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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 {
|
|
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
|
-
|
|
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
|
}
|