@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,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `neko schema list` command implementation (v0.7 Step 29).
|
|
3
|
+
*
|
|
4
|
+
* First fully-wired schema verb. Replaces the Step 25 placeholder.
|
|
5
|
+
* Flow:
|
|
6
|
+
*
|
|
7
|
+
* 1. `walkWorkspace({ root })` discovers and loads every
|
|
8
|
+
* `*.schema.{ts,js}` file under `root`.
|
|
9
|
+
* 2. If any file failed to load → render failures, exit IO_ERROR.
|
|
10
|
+
* 3. `buildRegistry(entries)` indexes the loaded schemas.
|
|
11
|
+
* 4. If the build returned `duplicate_schema_id` Issues → render
|
|
12
|
+
* issues, exit LOGICAL_FAILURE.
|
|
13
|
+
* 5. `listHandler({ registry })` enumerates entries in
|
|
14
|
+
* schemaId-ascending order (unversioned last).
|
|
15
|
+
* 6. Format and write:
|
|
16
|
+
* - `--json` → one-line JSON `{ schemas: [...] }` to stdout
|
|
17
|
+
* - default → padded table to stdout via `formatListPretty`
|
|
18
|
+
* 7. Return SUCCESS.
|
|
19
|
+
*
|
|
20
|
+
* Pure: no `process.exit`, no `console.*`, no direct `process.stdout`
|
|
21
|
+
* / `process.stderr` writes. The caller (the commander action wired
|
|
22
|
+
* up in `cli.ts`) injects writers; tests pass collector functions.
|
|
23
|
+
*
|
|
24
|
+
* JSON output shape is locked here so machine consumers have a
|
|
25
|
+
* stable contract. The `schema` field of `RegistryEntry` (the live
|
|
26
|
+
* `Schema` instance) is deliberately NOT included — it's not
|
|
27
|
+
* JSON-serializable and not a useful identifier downstream.
|
|
28
|
+
*
|
|
29
|
+
* {
|
|
30
|
+
* "schemas": [
|
|
31
|
+
* { "schemaId": "com.x.User", "schemaVersion": "1.0.0",
|
|
32
|
+
* "sourcePath": "schemas/user.schema.ts",
|
|
33
|
+
* "irHash": "sha256:...", "sourceHash": "sha256:..." },
|
|
34
|
+
* …
|
|
35
|
+
* ]
|
|
36
|
+
* }
|
|
37
|
+
*
|
|
38
|
+
* Unversioned entries surface as `"schemaVersion": null` so the
|
|
39
|
+
* JSON shape stays uniform.
|
|
40
|
+
*/
|
|
41
|
+
import { buildRegistry, listHandler } from "@nekostack/schema/cli";
|
|
42
|
+
import { EXIT_CODES } from "../../exit-codes.js";
|
|
43
|
+
import { formatJson } from "../../formatters/json.js";
|
|
44
|
+
import { formatIssuesPretty, formatListPretty, formatLoadFailuresPretty, } from "../../formatters/pretty.js";
|
|
45
|
+
import { walkWorkspace } from "../../loaders/walk-workspace.js";
|
|
46
|
+
/**
|
|
47
|
+
* Project a `LoadFailure` to a stable, JSON-safe shape. The
|
|
48
|
+
* underlying `LoadFailure.cause` is an `unknown` thrown by tsx /
|
|
49
|
+
* esbuild and may not serialize cleanly (or at all — circular
|
|
50
|
+
* references, function values, etc.). The CLI's `--json` contract
|
|
51
|
+
* for load failures is `{ path, reason, message }` only.
|
|
52
|
+
*/
|
|
53
|
+
function toSerializableLoadFailure(f) {
|
|
54
|
+
return { path: f.path, reason: f.reason, message: f.message };
|
|
55
|
+
}
|
|
56
|
+
export async function runList(opts) {
|
|
57
|
+
// 1. Walk the workspace and load every matching schema file.
|
|
58
|
+
const walk = await walkWorkspace({ root: opts.root });
|
|
59
|
+
// 2. Per-file load failures → IO_ERROR. Render before returning.
|
|
60
|
+
// Failures are essential diagnostics; `--quiet` does not
|
|
61
|
+
// suppress them per the audit guidance for Step 29.
|
|
62
|
+
if (walk.failures.length > 0) {
|
|
63
|
+
if (opts.json) {
|
|
64
|
+
opts.stdout(formatJson({
|
|
65
|
+
failures: walk.failures.map(toSerializableLoadFailure),
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
opts.stderr(formatLoadFailuresPretty(walk.failures));
|
|
70
|
+
}
|
|
71
|
+
return EXIT_CODES.IO_ERROR;
|
|
72
|
+
}
|
|
73
|
+
// 3. Build the in-memory registry. `buildRegistry` returns a
|
|
74
|
+
// `Result` so duplicate-id collisions surface as `Issue[]`
|
|
75
|
+
// rather than throws.
|
|
76
|
+
const reg = buildRegistry(walk.entries);
|
|
77
|
+
if (!reg.success) {
|
|
78
|
+
if (opts.json) {
|
|
79
|
+
opts.stdout(formatJson({ issues: reg.issues }));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
opts.stderr(formatIssuesPretty(reg.issues));
|
|
83
|
+
}
|
|
84
|
+
return EXIT_CODES.LOGICAL_FAILURE;
|
|
85
|
+
}
|
|
86
|
+
// 4. Enumerate. `listHandler` has no failure mode, but treat any
|
|
87
|
+
// future failure defensively as LOGICAL_FAILURE.
|
|
88
|
+
const result = listHandler({ registry: reg.data });
|
|
89
|
+
if (!result.success) {
|
|
90
|
+
if (opts.json) {
|
|
91
|
+
opts.stdout(formatJson({ issues: result.issues }));
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
opts.stderr(formatIssuesPretty(result.issues));
|
|
95
|
+
}
|
|
96
|
+
return EXIT_CODES.LOGICAL_FAILURE;
|
|
97
|
+
}
|
|
98
|
+
// 5. Success — write the entries in the chosen format.
|
|
99
|
+
if (opts.json) {
|
|
100
|
+
opts.stdout(formatJson({
|
|
101
|
+
schemas: result.data.entries.map((e) => ({
|
|
102
|
+
schemaId: e.schemaId,
|
|
103
|
+
schemaVersion: e.schemaVersion ?? null,
|
|
104
|
+
sourcePath: e.sourcePath,
|
|
105
|
+
irHash: e.irHash,
|
|
106
|
+
sourceHash: e.sourceHash,
|
|
107
|
+
})),
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
opts.stdout(formatListPretty(result.data.entries));
|
|
112
|
+
}
|
|
113
|
+
return EXIT_CODES.SUCCESS;
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/commands/schema/list.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `neko schema migrate list` command implementation (v0.8 Step 20).
|
|
3
|
+
*
|
|
4
|
+
* First fully-wired v0.8 verb. Mirrors the v0.7 `runList` shape exactly
|
|
5
|
+
* — same pure / writer-injected discipline (Master plan Decision #1).
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
*
|
|
9
|
+
* 1. `readMigrations({ root })` discovers and loads every
|
|
10
|
+
* `*.migration.ts` file under `root`.
|
|
11
|
+
* 2. If any file failed to load → render failures, exit IO_ERROR.
|
|
12
|
+
* 3. `buildMigrationRegistry(entries)` indexes the loaded migrations
|
|
13
|
+
* and parses each one's JSDoc provenance header.
|
|
14
|
+
* 4. If the build returned issues:
|
|
15
|
+
* - any `integrity_error` → INTEGRITY_ERROR (4)
|
|
16
|
+
* - otherwise (e.g., `duplicate_migration`) → LOGICAL_FAILURE (1)
|
|
17
|
+
* 5. `listMigrationsHandler({ migrationRegistry })` enumerates
|
|
18
|
+
* entries in `(schemaId, fromVersion, toVersion)` ascending order.
|
|
19
|
+
* 6. Format and write:
|
|
20
|
+
* - `--json` → one-line JSON `{ migrations: [...] }` to stdout
|
|
21
|
+
* - default → padded table to stdout via `formatMigrationListPretty`
|
|
22
|
+
* 7. Return SUCCESS.
|
|
23
|
+
*
|
|
24
|
+
* **Pure.** No `process.exit`, no `console.*`, no direct
|
|
25
|
+
* `process.stdout` / `process.stderr` writes. The caller (the commander
|
|
26
|
+
* action wired up in `cli.ts` — landing in a later step) injects
|
|
27
|
+
* writers; tests pass collector functions.
|
|
28
|
+
*
|
|
29
|
+
* **`migration.transform` is NEVER invoked here.** The v0.8 boundary
|
|
30
|
+
* (INVARIANTS — "no apply, no transform execution") stays in force.
|
|
31
|
+
* Static-scan asserted by [`../../../../tests/commands/schema-migrate-list.test.ts`](../../../../tests/commands/schema-migrate-list.test.ts).
|
|
32
|
+
*
|
|
33
|
+
* JSON output shape is locked here so machine consumers have a stable
|
|
34
|
+
* contract. The `migration` field on `MigrationEntry` — the live
|
|
35
|
+
* `AnyMigration` carrying the `transform` closure — is deliberately
|
|
36
|
+
* NOT included; it's not JSON-serializable, not a useful downstream
|
|
37
|
+
* identifier, and touching it would tempt callers across the v0.8
|
|
38
|
+
* boundary.
|
|
39
|
+
*
|
|
40
|
+
* {
|
|
41
|
+
* "migrations": [
|
|
42
|
+
* {
|
|
43
|
+
* "schemaId": "com.x.Tenant",
|
|
44
|
+
* "fromVersion": "1.0.0",
|
|
45
|
+
* "toVersion": "2.0.0",
|
|
46
|
+
* "sourcePath": "schemas/tenant.1-0-0-to-2-0-0.migration.ts",
|
|
47
|
+
* "fromIrHash": "sha256:...",
|
|
48
|
+
* "toIrHash": "sha256:...",
|
|
49
|
+
* "fromSourceHash": "sha256:...",
|
|
50
|
+
* "toSourceHash": "sha256:..."
|
|
51
|
+
* },
|
|
52
|
+
* …
|
|
53
|
+
* ]
|
|
54
|
+
* }
|
|
55
|
+
*/
|
|
56
|
+
import { type ExitCode } from "../../../exit-codes.js";
|
|
57
|
+
export interface RunMigrateListOptions {
|
|
58
|
+
readonly root: string;
|
|
59
|
+
readonly json: boolean;
|
|
60
|
+
readonly quiet: boolean;
|
|
61
|
+
readonly stdout: (s: string) => void;
|
|
62
|
+
readonly stderr: (s: string) => void;
|
|
63
|
+
}
|
|
64
|
+
export declare function runMigrateList(opts: RunMigrateListOptions): Promise<ExitCode>;
|
|
65
|
+
//# sourceMappingURL=list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../../src/commands/schema/migrate/list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAIH,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAoCnE,MAAM,WAAW,qBAAqB;IACpC,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;AAED,wBAAsB,cAAc,CAClC,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,QAAQ,CAAC,CAqEnB"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `neko schema migrate list` command implementation (v0.8 Step 20).
|
|
3
|
+
*
|
|
4
|
+
* First fully-wired v0.8 verb. Mirrors the v0.7 `runList` shape exactly
|
|
5
|
+
* — same pure / writer-injected discipline (Master plan Decision #1).
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
*
|
|
9
|
+
* 1. `readMigrations({ root })` discovers and loads every
|
|
10
|
+
* `*.migration.ts` file under `root`.
|
|
11
|
+
* 2. If any file failed to load → render failures, exit IO_ERROR.
|
|
12
|
+
* 3. `buildMigrationRegistry(entries)` indexes the loaded migrations
|
|
13
|
+
* and parses each one's JSDoc provenance header.
|
|
14
|
+
* 4. If the build returned issues:
|
|
15
|
+
* - any `integrity_error` → INTEGRITY_ERROR (4)
|
|
16
|
+
* - otherwise (e.g., `duplicate_migration`) → LOGICAL_FAILURE (1)
|
|
17
|
+
* 5. `listMigrationsHandler({ migrationRegistry })` enumerates
|
|
18
|
+
* entries in `(schemaId, fromVersion, toVersion)` ascending order.
|
|
19
|
+
* 6. Format and write:
|
|
20
|
+
* - `--json` → one-line JSON `{ migrations: [...] }` to stdout
|
|
21
|
+
* - default → padded table to stdout via `formatMigrationListPretty`
|
|
22
|
+
* 7. Return SUCCESS.
|
|
23
|
+
*
|
|
24
|
+
* **Pure.** No `process.exit`, no `console.*`, no direct
|
|
25
|
+
* `process.stdout` / `process.stderr` writes. The caller (the commander
|
|
26
|
+
* action wired up in `cli.ts` — landing in a later step) injects
|
|
27
|
+
* writers; tests pass collector functions.
|
|
28
|
+
*
|
|
29
|
+
* **`migration.transform` is NEVER invoked here.** The v0.8 boundary
|
|
30
|
+
* (INVARIANTS — "no apply, no transform execution") stays in force.
|
|
31
|
+
* Static-scan asserted by [`../../../../tests/commands/schema-migrate-list.test.ts`](../../../../tests/commands/schema-migrate-list.test.ts).
|
|
32
|
+
*
|
|
33
|
+
* JSON output shape is locked here so machine consumers have a stable
|
|
34
|
+
* contract. The `migration` field on `MigrationEntry` — the live
|
|
35
|
+
* `AnyMigration` carrying the `transform` closure — is deliberately
|
|
36
|
+
* NOT included; it's not JSON-serializable, not a useful downstream
|
|
37
|
+
* identifier, and touching it would tempt callers across the v0.8
|
|
38
|
+
* boundary.
|
|
39
|
+
*
|
|
40
|
+
* {
|
|
41
|
+
* "migrations": [
|
|
42
|
+
* {
|
|
43
|
+
* "schemaId": "com.x.Tenant",
|
|
44
|
+
* "fromVersion": "1.0.0",
|
|
45
|
+
* "toVersion": "2.0.0",
|
|
46
|
+
* "sourcePath": "schemas/tenant.1-0-0-to-2-0-0.migration.ts",
|
|
47
|
+
* "fromIrHash": "sha256:...",
|
|
48
|
+
* "toIrHash": "sha256:...",
|
|
49
|
+
* "fromSourceHash": "sha256:...",
|
|
50
|
+
* "toSourceHash": "sha256:..."
|
|
51
|
+
* },
|
|
52
|
+
* …
|
|
53
|
+
* ]
|
|
54
|
+
* }
|
|
55
|
+
*/
|
|
56
|
+
import { buildMigrationRegistry, listMigrationsHandler } from "@nekostack/schema/cli";
|
|
57
|
+
import { EXIT_CODES } from "../../../exit-codes.js";
|
|
58
|
+
import { formatJson } from "../../../formatters/json.js";
|
|
59
|
+
import { formatIssuesPretty, formatLoadFailuresPretty, formatMigrationListPretty, } from "../../../formatters/pretty.js";
|
|
60
|
+
import { readMigrations } from "../../../loaders/read-migrations.js";
|
|
61
|
+
/**
|
|
62
|
+
* Project a `LoadFailure` to a JSON-safe shape. `LoadFailure.cause` is
|
|
63
|
+
* an `unknown` thrown by tsx/esbuild and may not serialize cleanly
|
|
64
|
+
* (circular references, function values, etc.). The CLI's `--json`
|
|
65
|
+
* contract for load failures is `{ path, reason, message }` only —
|
|
66
|
+
* same projection rule as the v0.7 `runList`.
|
|
67
|
+
*/
|
|
68
|
+
function toSerializableLoadFailure(f) {
|
|
69
|
+
return { path: f.path, reason: f.reason, message: f.message };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Pick the exit code for a `buildMigrationRegistry` failure:
|
|
73
|
+
* - any `integrity_error` → INTEGRITY_ERROR
|
|
74
|
+
* - otherwise (e.g., `duplicate_migration`) → LOGICAL_FAILURE
|
|
75
|
+
* Same dispatch rule as v0.7 `runCheck`'s handler-failure mapping.
|
|
76
|
+
*/
|
|
77
|
+
function pickRegistryFailureExitCode(issues) {
|
|
78
|
+
for (const i of issues) {
|
|
79
|
+
if (i.code === "integrity_error")
|
|
80
|
+
return EXIT_CODES.INTEGRITY_ERROR;
|
|
81
|
+
}
|
|
82
|
+
return EXIT_CODES.LOGICAL_FAILURE;
|
|
83
|
+
}
|
|
84
|
+
export async function runMigrateList(opts) {
|
|
85
|
+
// 1. Walk the workspace and load every `*.migration.ts` file.
|
|
86
|
+
const walk = await readMigrations({ root: opts.root });
|
|
87
|
+
// 2. Per-file load failures → IO_ERROR. Render before returning.
|
|
88
|
+
// Failures are essential diagnostics; `--quiet` does not suppress
|
|
89
|
+
// them, matching v0.7 `runList`.
|
|
90
|
+
if (walk.failures.length > 0) {
|
|
91
|
+
if (opts.json) {
|
|
92
|
+
opts.stdout(formatJson({
|
|
93
|
+
failures: walk.failures.map(toSerializableLoadFailure),
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
opts.stderr(formatLoadFailuresPretty(walk.failures, {
|
|
98
|
+
noun: { singular: "migration file", plural: "migration files" },
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
return EXIT_CODES.IO_ERROR;
|
|
102
|
+
}
|
|
103
|
+
// 3. Build the in-memory migration registry. `buildMigrationRegistry`
|
|
104
|
+
// returns a `Result` so `duplicate_migration` and provenance
|
|
105
|
+
// parse failures surface as `Issue[]` rather than throws.
|
|
106
|
+
const reg = buildMigrationRegistry(walk.entries);
|
|
107
|
+
if (!reg.success) {
|
|
108
|
+
if (opts.json) {
|
|
109
|
+
opts.stdout(formatJson({ issues: reg.issues }));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
opts.stderr(formatIssuesPretty(reg.issues));
|
|
113
|
+
}
|
|
114
|
+
return pickRegistryFailureExitCode(reg.issues);
|
|
115
|
+
}
|
|
116
|
+
// 4. Enumerate. `listMigrationsHandler` has no failure mode today
|
|
117
|
+
// but treat any future failure defensively as LOGICAL_FAILURE.
|
|
118
|
+
const result = listMigrationsHandler({ migrationRegistry: reg.data });
|
|
119
|
+
if (!result.success) {
|
|
120
|
+
if (opts.json) {
|
|
121
|
+
opts.stdout(formatJson({ issues: result.issues }));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
opts.stderr(formatIssuesPretty(result.issues));
|
|
125
|
+
}
|
|
126
|
+
return EXIT_CODES.LOGICAL_FAILURE;
|
|
127
|
+
}
|
|
128
|
+
// 5. Success — write the entries in the chosen format.
|
|
129
|
+
if (opts.json) {
|
|
130
|
+
opts.stdout(formatJson({
|
|
131
|
+
migrations: result.data.entries.map((e) => ({
|
|
132
|
+
schemaId: e.schemaId,
|
|
133
|
+
fromVersion: e.fromVersion,
|
|
134
|
+
toVersion: e.toVersion,
|
|
135
|
+
sourcePath: e.sourcePath,
|
|
136
|
+
fromIrHash: e.fromIrHash,
|
|
137
|
+
toIrHash: e.toIrHash,
|
|
138
|
+
fromSourceHash: e.fromSourceHash,
|
|
139
|
+
toSourceHash: e.toSourceHash,
|
|
140
|
+
})),
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
opts.stdout(formatMigrationListPretty(result.data.entries));
|
|
145
|
+
}
|
|
146
|
+
return EXIT_CODES.SUCCESS;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../../../src/commands/schema/migrate/list.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
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 { type ExitCode } from "../../../exit-codes.js";
|
|
68
|
+
export interface RunMigratePlanOptions {
|
|
69
|
+
readonly root: string;
|
|
70
|
+
readonly schemaId: string;
|
|
71
|
+
readonly fromVersion: string;
|
|
72
|
+
readonly toVersion: string;
|
|
73
|
+
readonly json: boolean;
|
|
74
|
+
readonly quiet: boolean;
|
|
75
|
+
readonly stdout: (s: string) => void;
|
|
76
|
+
readonly stderr: (s: string) => void;
|
|
77
|
+
}
|
|
78
|
+
export declare function runMigratePlan(opts: RunMigratePlanOptions): Promise<ExitCode>;
|
|
79
|
+
//# sourceMappingURL=plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../../src/commands/schema/migrate/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEG;AAaH,OAAO,EAAc,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAcnE,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;AAwJD,wBAAsB,cAAc,CAClC,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,QAAQ,CAAC,CA6FnB"}
|