@nekostack/cli 1.0.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/CHANGELOG.md +42 -0
- package/LICENSE +202 -0
- package/README.md +89 -0
- package/bin/neko +6 -0
- package/dist/.build.tsbuildinfo +1 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/cli.d.ts +60 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +355 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +11 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/schema/check.d.ts +55 -0
- package/dist/commands/schema/check.d.ts.map +1 -0
- package/dist/commands/schema/check.js +197 -0
- package/dist/commands/schema/check.js.map +1 -0
- package/dist/commands/schema/diff.d.ts +48 -0
- package/dist/commands/schema/diff.d.ts.map +1 -0
- package/dist/commands/schema/diff.js +245 -0
- package/dist/commands/schema/diff.js.map +1 -0
- package/dist/commands/schema/generate.d.ts +59 -0
- package/dist/commands/schema/generate.d.ts.map +1 -0
- package/dist/commands/schema/generate.js +135 -0
- package/dist/commands/schema/generate.js.map +1 -0
- package/dist/commands/schema/list.d.ts +50 -0
- package/dist/commands/schema/list.d.ts.map +1 -0
- package/dist/commands/schema/list.js +115 -0
- package/dist/commands/schema/list.js.map +1 -0
- package/dist/commands/schema/migrate/list.d.ts +65 -0
- package/dist/commands/schema/migrate/list.d.ts.map +1 -0
- package/dist/commands/schema/migrate/list.js +148 -0
- package/dist/commands/schema/migrate/list.js.map +1 -0
- package/dist/commands/schema/migrate/plan.d.ts +79 -0
- package/dist/commands/schema/migrate/plan.d.ts.map +1 -0
- package/dist/commands/schema/migrate/plan.js +255 -0
- package/dist/commands/schema/migrate/plan.js.map +1 -0
- package/dist/commands/schema/migrate/stub.d.ts +59 -0
- package/dist/commands/schema/migrate/stub.d.ts.map +1 -0
- package/dist/commands/schema/migrate/stub.js +195 -0
- package/dist/commands/schema/migrate/stub.js.map +1 -0
- package/dist/commands/schema/migrate/verify.d.ts +59 -0
- package/dist/commands/schema/migrate/verify.d.ts.map +1 -0
- package/dist/commands/schema/migrate/verify.js +268 -0
- package/dist/commands/schema/migrate/verify.js.map +1 -0
- package/dist/exit-codes.d.ts +47 -0
- package/dist/exit-codes.d.ts.map +1 -0
- package/dist/exit-codes.js +46 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/formatters/json.d.ts +25 -0
- package/dist/formatters/json.d.ts.map +1 -0
- package/dist/formatters/json.js +27 -0
- package/dist/formatters/json.js.map +1 -0
- package/dist/formatters/pretty.d.ts +131 -0
- package/dist/formatters/pretty.d.ts.map +1 -0
- package/dist/formatters/pretty.js +229 -0
- package/dist/formatters/pretty.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/loaders/read-artifacts.d.ts +55 -0
- package/dist/loaders/read-artifacts.d.ts.map +1 -0
- package/dist/loaders/read-artifacts.js +99 -0
- package/dist/loaders/read-artifacts.js.map +1 -0
- package/dist/loaders/read-migrations.d.ts +70 -0
- package/dist/loaders/read-migrations.d.ts.map +1 -0
- package/dist/loaders/read-migrations.js +206 -0
- package/dist/loaders/read-migrations.js.map +1 -0
- package/dist/loaders/tsx-loader.d.ts +116 -0
- package/dist/loaders/tsx-loader.d.ts.map +1 -0
- package/dist/loaders/tsx-loader.js +250 -0
- package/dist/loaders/tsx-loader.js.map +1 -0
- package/dist/loaders/walk-workspace.d.ts +62 -0
- package/dist/loaders/walk-workspace.d.ts.map +1 -0
- package/dist/loaders/walk-workspace.js +133 -0
- package/dist/loaders/walk-workspace.js.map +1 -0
- package/docs/SCOPE.md +42 -0
- package/package.json +39 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `neko schema migrate plan` command implementation (v0.8 Step 21).
|
|
3
|
+
*
|
|
4
|
+
* The diff-aware planner CLI surface. Mirrors `runMigrateList`'s
|
|
5
|
+
* pure / writer-injected discipline (Master plan Decision #1).
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
*
|
|
9
|
+
* 1. `walkWorkspace({ root })` — load every `*.schema.{ts,js}`.
|
|
10
|
+
* Any per-file load failure → IO_ERROR.
|
|
11
|
+
* 2. `buildRegistry(walk.entries)` — index the schemas.
|
|
12
|
+
* `duplicate_schema_id` → LOGICAL_FAILURE; any `integrity_error`
|
|
13
|
+
* → INTEGRITY_ERROR.
|
|
14
|
+
* 3. `readMigrations({ root })` — load every `*.migration.ts`.
|
|
15
|
+
* Any per-file load failure → IO_ERROR.
|
|
16
|
+
* 4. `buildMigrationRegistry(migrationWalk.entries)` — index the
|
|
17
|
+
* migrations and parse each one's provenance.
|
|
18
|
+
* `duplicate_migration` → LOGICAL_FAILURE; any `integrity_error`
|
|
19
|
+
* → INTEGRITY_ERROR.
|
|
20
|
+
* 5. `planMigrationHandler({ schemaRegistry, migrationRegistry,
|
|
21
|
+
* schemaId, fromVersion, toVersion })`. Failure-issue mapping:
|
|
22
|
+
* - `migration_missing_endpoint` → LOGICAL_FAILURE
|
|
23
|
+
* - `migration_not_found` → LOGICAL_FAILURE
|
|
24
|
+
* - `migration_chain_broken` → LOGICAL_FAILURE
|
|
25
|
+
* - `migration_ambiguous_chain` → LOGICAL_FAILURE
|
|
26
|
+
* 6. Format and write:
|
|
27
|
+
* - `--json` → one-line `{ plan: {...} }` to stdout
|
|
28
|
+
* - default → pretty paragraph distinguishing the four success
|
|
29
|
+
* shapes (no-change / over_specified /
|
|
30
|
+
* additive_no_migration / chain).
|
|
31
|
+
* 7. Return SUCCESS.
|
|
32
|
+
*
|
|
33
|
+
* **Pure.** No `process.exit`, no `console.*`, no direct
|
|
34
|
+
* `process.stdout` / `process.stderr` writes. Writers are injected.
|
|
35
|
+
*
|
|
36
|
+
* **`migration.transform` is NEVER invoked here.** The planner is
|
|
37
|
+
* structural — `diffNodes` + DFS chain enumeration. The v0.8
|
|
38
|
+
* INVARIANTS ("no apply, no transform execution") stay in force.
|
|
39
|
+
* Static-scan asserted by [`../../../../tests/commands/schema-migrate-plan.test.ts`](../../../../tests/commands/schema-migrate-plan.test.ts).
|
|
40
|
+
*
|
|
41
|
+
* **JSON output shape is locked.** The `MigrationEntry.migration`
|
|
42
|
+
* field — the live `AnyMigration` carrying the `transform` closure —
|
|
43
|
+
* is NEVER serialized (neither inside `chain` rows nor inside
|
|
44
|
+
* `notes[].migration` projections):
|
|
45
|
+
*
|
|
46
|
+
* {
|
|
47
|
+
* "plan": {
|
|
48
|
+
* "schemaId": "com.x.Tenant",
|
|
49
|
+
* "fromVersion": "1.0.0",
|
|
50
|
+
* "toVersion": "2.0.0",
|
|
51
|
+
* "worstSeverity": "breaking" | "additive" | "cosmetic" | null,
|
|
52
|
+
* "versionPath": ["1.0.0", "1.5.0", "2.0.0"],
|
|
53
|
+
* "notes": [
|
|
54
|
+
* { "kind": "over_specified", "migration": { schemaId, fromVersion, toVersion,
|
|
55
|
+
* sourcePath, fromIrHash, toIrHash, fromSourceHash, toSourceHash } },
|
|
56
|
+
* { "kind": "additive_no_migration", "worstSeverity": "additive" }
|
|
57
|
+
* ],
|
|
58
|
+
* "chain": [
|
|
59
|
+
* { "schemaId": "...", "fromVersion": "1.0.0", "toVersion": "1.5.0",
|
|
60
|
+
* "sourcePath": "...", "fromIrHash": "sha256:...", "toIrHash": "sha256:...",
|
|
61
|
+
* "fromSourceHash": "sha256:...", "toSourceHash": "sha256:..." },
|
|
62
|
+
* …
|
|
63
|
+
* ]
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
*/
|
|
67
|
+
import { buildMigrationRegistry, buildRegistry, planMigrationHandler, } from "@nekostack/schema/cli";
|
|
68
|
+
import { EXIT_CODES } from "../../../exit-codes.js";
|
|
69
|
+
import { formatJson } from "../../../formatters/json.js";
|
|
70
|
+
import { formatIssuesPretty, formatLoadFailuresPretty, } from "../../../formatters/pretty.js";
|
|
71
|
+
import { readMigrations } from "../../../loaders/read-migrations.js";
|
|
72
|
+
import { walkWorkspace } from "../../../loaders/walk-workspace.js";
|
|
73
|
+
function projectMigrationEntry(e) {
|
|
74
|
+
return {
|
|
75
|
+
schemaId: e.schemaId,
|
|
76
|
+
fromVersion: e.fromVersion,
|
|
77
|
+
toVersion: e.toVersion,
|
|
78
|
+
sourcePath: e.sourcePath,
|
|
79
|
+
fromIrHash: e.fromIrHash,
|
|
80
|
+
toIrHash: e.toIrHash,
|
|
81
|
+
fromSourceHash: e.fromSourceHash,
|
|
82
|
+
toSourceHash: e.toSourceHash,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function projectPlanNote(note) {
|
|
86
|
+
if (note.kind === "over_specified") {
|
|
87
|
+
return { kind: "over_specified", migration: projectMigrationEntry(note.migration) };
|
|
88
|
+
}
|
|
89
|
+
return { kind: "additive_no_migration", worstSeverity: note.worstSeverity };
|
|
90
|
+
}
|
|
91
|
+
function projectPlan(plan, opts) {
|
|
92
|
+
return {
|
|
93
|
+
schemaId: plan.schemaId,
|
|
94
|
+
fromVersion: opts.fromVersion,
|
|
95
|
+
toVersion: opts.toVersion,
|
|
96
|
+
worstSeverity: plan.worstSeverity,
|
|
97
|
+
versionPath: plan.versionPath,
|
|
98
|
+
notes: plan.notes.map(projectPlanNote),
|
|
99
|
+
chain: plan.chain.map(projectMigrationEntry),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// =============================================================================
|
|
103
|
+
// Load-failure projection (strip `cause`)
|
|
104
|
+
// =============================================================================
|
|
105
|
+
function toSerializableLoadFailure(f) {
|
|
106
|
+
return { path: f.path, reason: f.reason, message: f.message };
|
|
107
|
+
}
|
|
108
|
+
// =============================================================================
|
|
109
|
+
// Exit-code mapping for issue lists
|
|
110
|
+
// =============================================================================
|
|
111
|
+
/**
|
|
112
|
+
* `buildRegistry` / `buildMigrationRegistry` failure → exit code.
|
|
113
|
+
* Any `integrity_error` → INTEGRITY_ERROR; otherwise LOGICAL_FAILURE
|
|
114
|
+
* (covers `duplicate_schema_id` / `duplicate_migration`). Same rule
|
|
115
|
+
* as the v0.7 `runCheck` handler-failure mapping and Step 20's
|
|
116
|
+
* `runMigrateList`.
|
|
117
|
+
*/
|
|
118
|
+
function pickRegistryFailureExitCode(issues) {
|
|
119
|
+
for (const i of issues) {
|
|
120
|
+
if (i.code === "integrity_error")
|
|
121
|
+
return EXIT_CODES.INTEGRITY_ERROR;
|
|
122
|
+
}
|
|
123
|
+
return EXIT_CODES.LOGICAL_FAILURE;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* `planMigrationHandler` failure → exit code. All four locked
|
|
127
|
+
* failure codes (`migration_missing_endpoint` / `migration_not_found`
|
|
128
|
+
* / `migration_chain_broken` / `migration_ambiguous_chain`) map to
|
|
129
|
+
* `LOGICAL_FAILURE`. Defensive fallback also returns LOGICAL_FAILURE
|
|
130
|
+
* — the v0.8 planner does not emit `integrity_error`.
|
|
131
|
+
*/
|
|
132
|
+
function pickPlannerFailureExitCode(_issues) {
|
|
133
|
+
return EXIT_CODES.LOGICAL_FAILURE;
|
|
134
|
+
}
|
|
135
|
+
// =============================================================================
|
|
136
|
+
// Pretty formatter
|
|
137
|
+
// =============================================================================
|
|
138
|
+
function formatMigrationPlanPretty(plan, opts) {
|
|
139
|
+
const header = `Plan: ${plan.schemaId} ${opts.fromVersion} → ${opts.toVersion}` +
|
|
140
|
+
` (worstSeverity: ${plan.worstSeverity ?? "none"})`;
|
|
141
|
+
const lines = [header];
|
|
142
|
+
if (plan.chain.length === 0) {
|
|
143
|
+
if (plan.notes.length === 0) {
|
|
144
|
+
lines.push(" No migration needed — diff has no breaking or additive changes.");
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
for (const note of plan.notes) {
|
|
148
|
+
if (note.kind === "over_specified") {
|
|
149
|
+
lines.push(` No migration needed — diff is cosmetic/none, but a migration is registered at \`${note.migration.sourcePath}\`. (over_specified)`);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
lines.push(` No migration registered — diff is additive (\`${note.worstSeverity}\`); a migration is optional. (additive_no_migration)`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
lines.push(` Chain (${plan.chain.length} hop${plan.chain.length === 1 ? "" : "s"}):`);
|
|
159
|
+
for (let i = 0; i < plan.chain.length; i++) {
|
|
160
|
+
const hop = plan.chain[i];
|
|
161
|
+
lines.push(` ${i + 1}. ${hop.fromVersion} → ${hop.toVersion} ${hop.sourcePath}`);
|
|
162
|
+
}
|
|
163
|
+
lines.push(` Version path: ${plan.versionPath.join(" → ")}`);
|
|
164
|
+
}
|
|
165
|
+
return lines.join("\n") + "\n";
|
|
166
|
+
}
|
|
167
|
+
// =============================================================================
|
|
168
|
+
// Public entry
|
|
169
|
+
// =============================================================================
|
|
170
|
+
export async function runMigratePlan(opts) {
|
|
171
|
+
// 1. Schema walk + load failures → IO_ERROR.
|
|
172
|
+
const walk = await walkWorkspace({ root: opts.root });
|
|
173
|
+
if (walk.failures.length > 0) {
|
|
174
|
+
if (opts.json) {
|
|
175
|
+
opts.stdout(formatJson({
|
|
176
|
+
failures: walk.failures.map(toSerializableLoadFailure),
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
opts.stderr(formatLoadFailuresPretty(walk.failures));
|
|
181
|
+
}
|
|
182
|
+
return EXIT_CODES.IO_ERROR;
|
|
183
|
+
}
|
|
184
|
+
// 2. Build the schema registry.
|
|
185
|
+
const reg = buildRegistry(walk.entries);
|
|
186
|
+
if (!reg.success) {
|
|
187
|
+
if (opts.json) {
|
|
188
|
+
opts.stdout(formatJson({ issues: reg.issues }));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
opts.stderr(formatIssuesPretty(reg.issues));
|
|
192
|
+
}
|
|
193
|
+
return pickRegistryFailureExitCode(reg.issues);
|
|
194
|
+
}
|
|
195
|
+
// 3. Migration walk + load failures → IO_ERROR.
|
|
196
|
+
const migWalk = await readMigrations({ root: opts.root });
|
|
197
|
+
if (migWalk.failures.length > 0) {
|
|
198
|
+
if (opts.json) {
|
|
199
|
+
opts.stdout(formatJson({
|
|
200
|
+
failures: migWalk.failures.map(toSerializableLoadFailure),
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
opts.stderr(formatLoadFailuresPretty(migWalk.failures, {
|
|
205
|
+
noun: { singular: "migration file", plural: "migration files" },
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
return EXIT_CODES.IO_ERROR;
|
|
209
|
+
}
|
|
210
|
+
// 4. Build the migration registry.
|
|
211
|
+
const migReg = buildMigrationRegistry(migWalk.entries);
|
|
212
|
+
if (!migReg.success) {
|
|
213
|
+
if (opts.json) {
|
|
214
|
+
opts.stdout(formatJson({ issues: migReg.issues }));
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
opts.stderr(formatIssuesPretty(migReg.issues));
|
|
218
|
+
}
|
|
219
|
+
return pickRegistryFailureExitCode(migReg.issues);
|
|
220
|
+
}
|
|
221
|
+
// 5. Dispatch to the planner.
|
|
222
|
+
const result = planMigrationHandler({
|
|
223
|
+
schemaRegistry: reg.data,
|
|
224
|
+
migrationRegistry: migReg.data,
|
|
225
|
+
schemaId: opts.schemaId,
|
|
226
|
+
fromVersion: opts.fromVersion,
|
|
227
|
+
toVersion: opts.toVersion,
|
|
228
|
+
});
|
|
229
|
+
if (!result.success) {
|
|
230
|
+
if (opts.json) {
|
|
231
|
+
opts.stdout(formatJson({ issues: result.issues }));
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
opts.stderr(formatIssuesPretty(result.issues));
|
|
235
|
+
}
|
|
236
|
+
return pickPlannerFailureExitCode(result.issues);
|
|
237
|
+
}
|
|
238
|
+
// 6. Success.
|
|
239
|
+
if (opts.json) {
|
|
240
|
+
opts.stdout(formatJson({
|
|
241
|
+
plan: projectPlan(result.data, {
|
|
242
|
+
fromVersion: opts.fromVersion,
|
|
243
|
+
toVersion: opts.toVersion,
|
|
244
|
+
}),
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
opts.stdout(formatMigrationPlanPretty(result.data, {
|
|
249
|
+
fromVersion: opts.fromVersion,
|
|
250
|
+
toVersion: opts.toVersion,
|
|
251
|
+
}));
|
|
252
|
+
}
|
|
253
|
+
return EXIT_CODES.SUCCESS;
|
|
254
|
+
}
|
|
255
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../../../../src/commands/schema/migrate/plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `neko schema migrate stub` command implementation (v0.8 Step 23).
|
|
3
|
+
*
|
|
4
|
+
* The only v0.8 migrate verb that writes a file. Mirrors v0.7's
|
|
5
|
+
* `runGenerate` shape: the schema-side handler is pure and returns
|
|
6
|
+
* `{ suggestedPath, content }`; this command is responsible for the
|
|
7
|
+
* actual filesystem persistence. Master plan Decision #1 keeps the
|
|
8
|
+
* filesystem on the CLI side.
|
|
9
|
+
*
|
|
10
|
+
* Flow:
|
|
11
|
+
*
|
|
12
|
+
* 1. `walkWorkspace({ root })` — load every `*.schema.{ts,js}`.
|
|
13
|
+
* Any per-file load failure → IO_ERROR.
|
|
14
|
+
* 2. `buildRegistry(walk.entries)` — index the schemas.
|
|
15
|
+
* `duplicate_schema_id` → LOGICAL_FAILURE.
|
|
16
|
+
* 3. `stubMigrationHandler({ schemaRegistry, schemaId, fromVersion,
|
|
17
|
+
* toVersion })` — generate the stub payload. Failure modes
|
|
18
|
+
* surface as `migration_missing_endpoint` →
|
|
19
|
+
* LOGICAL_FAILURE.
|
|
20
|
+
* 4. **Refuse to overwrite.** Unlike `runGenerate` (which clobbers
|
|
21
|
+
* generated artifacts by default), `runMigrateStub` writes a
|
|
22
|
+
* file that contains hand-authored code. If the destination
|
|
23
|
+
* already exists, the command returns LOGICAL_FAILURE without
|
|
24
|
+
* modifying it. Decision #5 of the v0.8 phase plan locks this
|
|
25
|
+
* contract.
|
|
26
|
+
* 5. `mkdir -p` the destination's parent + `writeFile` the
|
|
27
|
+
* stub content. Any thrown error (EACCES, ENOSPC, etc.) →
|
|
28
|
+
* IO_ERROR.
|
|
29
|
+
* 6. Format and write:
|
|
30
|
+
* - `--json` (success) → `{ stub: { schemaId, fromVersion,
|
|
31
|
+
* toVersion, suggestedPath } }`. The `content` field is
|
|
32
|
+
* deliberately omitted — machine consumers don't need the
|
|
33
|
+
* full source body, and the file is already on disk.
|
|
34
|
+
* - pretty → `Wrote stub: <suggestedPath>`.
|
|
35
|
+
*
|
|
36
|
+
* **Pure helpers + bounded I/O.** `mkdir` and `writeFile` (and a
|
|
37
|
+
* `stat` for the overwrite check) are the only `node:fs` calls.
|
|
38
|
+
* `process.exit`, `console.*`, and direct stdout/stderr writes are
|
|
39
|
+
* forbidden — static-scan asserted by
|
|
40
|
+
* [`../../../../tests/commands/schema-migrate-stub.test.ts`](../../../../tests/commands/schema-migrate-stub.test.ts).
|
|
41
|
+
*
|
|
42
|
+
* **No `migration.transform`.** The stub command doesn't even own
|
|
43
|
+
* a registry of authored migrations — only the schema registry. The
|
|
44
|
+
* `.transform(` pattern still appears as a forbidden token in the
|
|
45
|
+
* static scan so the v0.8 boundary stays enforced everywhere.
|
|
46
|
+
*/
|
|
47
|
+
import { type ExitCode } from "../../../exit-codes.js";
|
|
48
|
+
export interface RunMigrateStubOptions {
|
|
49
|
+
readonly root: string;
|
|
50
|
+
readonly schemaId: string;
|
|
51
|
+
readonly fromVersion: string;
|
|
52
|
+
readonly toVersion: string;
|
|
53
|
+
readonly json: boolean;
|
|
54
|
+
readonly quiet: boolean;
|
|
55
|
+
readonly stdout: (s: string) => void;
|
|
56
|
+
readonly stderr: (s: string) => void;
|
|
57
|
+
}
|
|
58
|
+
export declare function runMigrateStub(opts: RunMigrateStubOptions): Promise<ExitCode>;
|
|
59
|
+
//# sourceMappingURL=stub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stub.d.ts","sourceRoot":"","sources":["../../../../src/commands/schema/migrate/stub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AASH,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAanE,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AA6BD,wBAAsB,cAAc,CAClC,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,QAAQ,CAAC,CA6EnB"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `neko schema migrate stub` command implementation (v0.8 Step 23).
|
|
3
|
+
*
|
|
4
|
+
* The only v0.8 migrate verb that writes a file. Mirrors v0.7's
|
|
5
|
+
* `runGenerate` shape: the schema-side handler is pure and returns
|
|
6
|
+
* `{ suggestedPath, content }`; this command is responsible for the
|
|
7
|
+
* actual filesystem persistence. Master plan Decision #1 keeps the
|
|
8
|
+
* filesystem on the CLI side.
|
|
9
|
+
*
|
|
10
|
+
* Flow:
|
|
11
|
+
*
|
|
12
|
+
* 1. `walkWorkspace({ root })` — load every `*.schema.{ts,js}`.
|
|
13
|
+
* Any per-file load failure → IO_ERROR.
|
|
14
|
+
* 2. `buildRegistry(walk.entries)` — index the schemas.
|
|
15
|
+
* `duplicate_schema_id` → LOGICAL_FAILURE.
|
|
16
|
+
* 3. `stubMigrationHandler({ schemaRegistry, schemaId, fromVersion,
|
|
17
|
+
* toVersion })` — generate the stub payload. Failure modes
|
|
18
|
+
* surface as `migration_missing_endpoint` →
|
|
19
|
+
* LOGICAL_FAILURE.
|
|
20
|
+
* 4. **Refuse to overwrite.** Unlike `runGenerate` (which clobbers
|
|
21
|
+
* generated artifacts by default), `runMigrateStub` writes a
|
|
22
|
+
* file that contains hand-authored code. If the destination
|
|
23
|
+
* already exists, the command returns LOGICAL_FAILURE without
|
|
24
|
+
* modifying it. Decision #5 of the v0.8 phase plan locks this
|
|
25
|
+
* contract.
|
|
26
|
+
* 5. `mkdir -p` the destination's parent + `writeFile` the
|
|
27
|
+
* stub content. Any thrown error (EACCES, ENOSPC, etc.) →
|
|
28
|
+
* IO_ERROR.
|
|
29
|
+
* 6. Format and write:
|
|
30
|
+
* - `--json` (success) → `{ stub: { schemaId, fromVersion,
|
|
31
|
+
* toVersion, suggestedPath } }`. The `content` field is
|
|
32
|
+
* deliberately omitted — machine consumers don't need the
|
|
33
|
+
* full source body, and the file is already on disk.
|
|
34
|
+
* - pretty → `Wrote stub: <suggestedPath>`.
|
|
35
|
+
*
|
|
36
|
+
* **Pure helpers + bounded I/O.** `mkdir` and `writeFile` (and a
|
|
37
|
+
* `stat` for the overwrite check) are the only `node:fs` calls.
|
|
38
|
+
* `process.exit`, `console.*`, and direct stdout/stderr writes are
|
|
39
|
+
* forbidden — static-scan asserted by
|
|
40
|
+
* [`../../../../tests/commands/schema-migrate-stub.test.ts`](../../../../tests/commands/schema-migrate-stub.test.ts).
|
|
41
|
+
*
|
|
42
|
+
* **No `migration.transform`.** The stub command doesn't even own
|
|
43
|
+
* a registry of authored migrations — only the schema registry. The
|
|
44
|
+
* `.transform(` pattern still appears as a forbidden token in the
|
|
45
|
+
* static scan so the v0.8 boundary stays enforced everywhere.
|
|
46
|
+
*/
|
|
47
|
+
import { dirname, isAbsolute, resolve, sep } from "node:path";
|
|
48
|
+
import { mkdir, stat, writeFile } from "node:fs/promises";
|
|
49
|
+
import { buildRegistry, stubMigrationHandler, } from "@nekostack/schema/cli";
|
|
50
|
+
import { EXIT_CODES } from "../../../exit-codes.js";
|
|
51
|
+
import { formatJson } from "../../../formatters/json.js";
|
|
52
|
+
import { formatIssuesPretty, formatLoadFailuresPretty, } from "../../../formatters/pretty.js";
|
|
53
|
+
import { walkWorkspace } from "../../../loaders/walk-workspace.js";
|
|
54
|
+
// =============================================================================
|
|
55
|
+
// Public entry
|
|
56
|
+
// =============================================================================
|
|
57
|
+
export async function runMigrateStub(opts) {
|
|
58
|
+
// 1. Schema walk + load failures → IO_ERROR.
|
|
59
|
+
const walk = await walkWorkspace({ root: opts.root });
|
|
60
|
+
if (walk.failures.length > 0) {
|
|
61
|
+
writeLoadFailures(opts, walk.failures);
|
|
62
|
+
return EXIT_CODES.IO_ERROR;
|
|
63
|
+
}
|
|
64
|
+
// 2. Build the schema registry.
|
|
65
|
+
const reg = buildRegistry(walk.entries);
|
|
66
|
+
if (!reg.success) {
|
|
67
|
+
writeIssues(opts, reg.issues);
|
|
68
|
+
return pickRegistryFailureExitCode(reg.issues);
|
|
69
|
+
}
|
|
70
|
+
// 3. Plan the stub. Failures (e.g., `migration_missing_endpoint`)
|
|
71
|
+
// surface as `LOGICAL_FAILURE`.
|
|
72
|
+
const result = stubMigrationHandler({
|
|
73
|
+
schemaRegistry: reg.data,
|
|
74
|
+
schemaId: opts.schemaId,
|
|
75
|
+
fromVersion: opts.fromVersion,
|
|
76
|
+
toVersion: opts.toVersion,
|
|
77
|
+
});
|
|
78
|
+
if (!result.success) {
|
|
79
|
+
writeIssues(opts, result.issues);
|
|
80
|
+
return EXIT_CODES.LOGICAL_FAILURE;
|
|
81
|
+
}
|
|
82
|
+
const stub = result.data;
|
|
83
|
+
const rootAbs = isAbsolute(opts.root) ? opts.root : resolve(opts.root);
|
|
84
|
+
const destAbs = resolve(rootAbs, stub.suggestedPath.split("/").join(sep));
|
|
85
|
+
// 4. Refuse to overwrite. The destination is hand-authored migration
|
|
86
|
+
// code; clobbering it would silently destroy the author's work.
|
|
87
|
+
if (await pathExists(destAbs)) {
|
|
88
|
+
const failure = {
|
|
89
|
+
path: stub.suggestedPath,
|
|
90
|
+
reason: "stub_path_exists",
|
|
91
|
+
message: `Refusing to overwrite existing file at \`${stub.suggestedPath}\`. ` +
|
|
92
|
+
`Move or delete it first, or pick a different (from, to) pair.`,
|
|
93
|
+
};
|
|
94
|
+
writeLocalFailures(opts, [failure]);
|
|
95
|
+
return EXIT_CODES.LOGICAL_FAILURE;
|
|
96
|
+
}
|
|
97
|
+
// 5. Persist. `mkdir -p` + `writeFile` may throw on I/O errors
|
|
98
|
+
// (EACCES, ENOSPC, etc.); surface those as IO_ERROR.
|
|
99
|
+
try {
|
|
100
|
+
await mkdir(dirname(destAbs), { recursive: true });
|
|
101
|
+
await writeFile(destAbs, stub.content, "utf8");
|
|
102
|
+
}
|
|
103
|
+
catch (cause) {
|
|
104
|
+
const failure = {
|
|
105
|
+
path: stub.suggestedPath,
|
|
106
|
+
reason: "stub_write_failed",
|
|
107
|
+
message: errorMessageOf(cause),
|
|
108
|
+
};
|
|
109
|
+
writeLocalFailures(opts, [failure]);
|
|
110
|
+
return EXIT_CODES.IO_ERROR;
|
|
111
|
+
}
|
|
112
|
+
// 6. Success — emit the locked machine-readable shape (no `content`).
|
|
113
|
+
if (opts.json) {
|
|
114
|
+
opts.stdout(formatJson({
|
|
115
|
+
stub: {
|
|
116
|
+
schemaId: stub.schemaId,
|
|
117
|
+
fromVersion: stub.fromVersion,
|
|
118
|
+
toVersion: stub.toVersion,
|
|
119
|
+
suggestedPath: stub.suggestedPath,
|
|
120
|
+
},
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
opts.stdout(`Wrote stub: ${stub.suggestedPath}\n`);
|
|
125
|
+
}
|
|
126
|
+
return EXIT_CODES.SUCCESS;
|
|
127
|
+
}
|
|
128
|
+
// =============================================================================
|
|
129
|
+
// Helpers
|
|
130
|
+
// =============================================================================
|
|
131
|
+
async function pathExists(abs) {
|
|
132
|
+
try {
|
|
133
|
+
await stat(abs);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function errorMessageOf(err) {
|
|
141
|
+
if (err instanceof Error)
|
|
142
|
+
return err.message;
|
|
143
|
+
if (typeof err === "string")
|
|
144
|
+
return err;
|
|
145
|
+
try {
|
|
146
|
+
return JSON.stringify(err);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return String(err);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Pick exit code for `buildRegistry` failure. Same mapping as
|
|
154
|
+
* `runMigratePlan` / `runMigrateVerify`.
|
|
155
|
+
*/
|
|
156
|
+
function pickRegistryFailureExitCode(issues) {
|
|
157
|
+
for (const i of issues) {
|
|
158
|
+
if (i.code === "integrity_error")
|
|
159
|
+
return EXIT_CODES.INTEGRITY_ERROR;
|
|
160
|
+
}
|
|
161
|
+
return EXIT_CODES.LOGICAL_FAILURE;
|
|
162
|
+
}
|
|
163
|
+
function writeLoadFailures(opts, failures) {
|
|
164
|
+
if (opts.json) {
|
|
165
|
+
opts.stdout(formatJson({
|
|
166
|
+
failures: failures.map((f) => ({
|
|
167
|
+
path: f.path,
|
|
168
|
+
reason: f.reason,
|
|
169
|
+
message: f.message,
|
|
170
|
+
})),
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
opts.stderr(formatLoadFailuresPretty(failures));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function writeIssues(opts, issues) {
|
|
178
|
+
if (opts.json) {
|
|
179
|
+
opts.stdout(formatJson({ issues }));
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
opts.stderr(formatIssuesPretty(issues));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function writeLocalFailures(opts, failures) {
|
|
186
|
+
if (opts.json) {
|
|
187
|
+
opts.stdout(formatJson({ failures }));
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const header = failures.length === 1 ? "Stub failed:" : "Stub failed:";
|
|
191
|
+
const lines = failures.map((f) => ` [${f.reason}] ${f.path} — ${f.message}`);
|
|
192
|
+
opts.stderr([header, ...lines].join("\n") + "\n");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=stub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stub.js","sourceRoot":"","sources":["../../../../src/commands/schema/migrate/stub.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `neko schema migrate verify` command implementation (v0.8 Step 22).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `runMigratePlan`'s pure / writer-injected discipline
|
|
5
|
+
* (Master plan Decision #1).
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
*
|
|
9
|
+
* 1. `walkWorkspace({ root })` — load every `*.schema.{ts,js}`.
|
|
10
|
+
* Any per-file load failure → IO_ERROR.
|
|
11
|
+
* 2. `buildRegistry(walk.entries)` — index the schemas.
|
|
12
|
+
* `duplicate_schema_id` → LOGICAL_FAILURE; `integrity_error` →
|
|
13
|
+
* INTEGRITY_ERROR.
|
|
14
|
+
* 3. `readMigrations({ root })` — load every `*.migration.ts`.
|
|
15
|
+
* Any per-file load failure → IO_ERROR.
|
|
16
|
+
* 4. `buildMigrationRegistry(migWalk.entries)` — index migrations
|
|
17
|
+
* and parse each one's provenance. `duplicate_migration` →
|
|
18
|
+
* LOGICAL_FAILURE; `integrity_error` → INTEGRITY_ERROR.
|
|
19
|
+
* 5. **CLI-side verdict derivation** — walk the migration registry
|
|
20
|
+
* with the same classification rule as
|
|
21
|
+
* `verifyMigrationProvenance` so the CLI can surface verdicts +
|
|
22
|
+
* summary on BOTH the success and failure JSON branches. (The
|
|
23
|
+
* schema-side handler discards verdicts on failure; the v0.8
|
|
24
|
+
* verify-CLI contract requires them on both branches.)
|
|
25
|
+
* 6. `verifyMigrationsHandler({ schemaRegistry, migrationRegistry })`
|
|
26
|
+
* — gets the official success/failure determination and any
|
|
27
|
+
* `migration_drift` / `migration_missing_endpoint` issues.
|
|
28
|
+
* 7. Format and write:
|
|
29
|
+
* - `--json` (success) → `{ verdicts, summary }`
|
|
30
|
+
* - `--json` (failure) → `{ verdicts, summary, issues }`
|
|
31
|
+
* - pretty → summary header + per-verdict rows
|
|
32
|
+
* 8. Exit-code mapping:
|
|
33
|
+
* - any drift / missing_endpoint → LOGICAL_FAILURE
|
|
34
|
+
* - only bound / cosmetic_drift (or empty registry) → SUCCESS
|
|
35
|
+
*
|
|
36
|
+
* **Pure.** No `process.exit`, no `console.*`, no direct
|
|
37
|
+
* `process.stdout` / `process.stderr` writes. Writers are injected.
|
|
38
|
+
*
|
|
39
|
+
* **`migration.transform` is NEVER invoked here.** Verification is
|
|
40
|
+
* provenance-only (the v0.8 INVARIANTS contract). Static-scan
|
|
41
|
+
* asserted by [`../../../../tests/commands/schema-migrate-verify.test.ts`](../../../../tests/commands/schema-migrate-verify.test.ts).
|
|
42
|
+
*
|
|
43
|
+
* **JSON output never serializes `MigrationEntry.migration` or its
|
|
44
|
+
* `transform` closure.** Verdicts are already shaped as
|
|
45
|
+
* `{ status, schemaId, fromVersion, toVersion, sourcePath }` per the
|
|
46
|
+
* `MigrationVerdict` type — no migration reference — so the projection
|
|
47
|
+
* is the identity. Issue records likewise carry only structured
|
|
48
|
+
* metadata.
|
|
49
|
+
*/
|
|
50
|
+
import { type ExitCode } from "../../../exit-codes.js";
|
|
51
|
+
export interface RunMigrateVerifyOptions {
|
|
52
|
+
readonly root: string;
|
|
53
|
+
readonly json: boolean;
|
|
54
|
+
readonly quiet: boolean;
|
|
55
|
+
readonly stdout: (s: string) => void;
|
|
56
|
+
readonly stderr: (s: string) => void;
|
|
57
|
+
}
|
|
58
|
+
export declare function runMigrateVerify(opts: RunMigrateVerifyOptions): Promise<ExitCode>;
|
|
59
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../../../src/commands/schema/migrate/verify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAeH,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAcnE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AA+KD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,QAAQ,CAAC,CA+FnB"}
|