@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.
Files changed (80) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/LICENSE +202 -0
  3. package/README.md +89 -0
  4. package/bin/neko +6 -0
  5. package/dist/.build.tsbuildinfo +1 -0
  6. package/dist/.tsbuildinfo +1 -0
  7. package/dist/cli.d.ts +60 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +355 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/commands/init.d.ts +8 -0
  12. package/dist/commands/init.d.ts.map +1 -0
  13. package/dist/commands/init.js +11 -0
  14. package/dist/commands/init.js.map +1 -0
  15. package/dist/commands/schema/check.d.ts +55 -0
  16. package/dist/commands/schema/check.d.ts.map +1 -0
  17. package/dist/commands/schema/check.js +197 -0
  18. package/dist/commands/schema/check.js.map +1 -0
  19. package/dist/commands/schema/diff.d.ts +48 -0
  20. package/dist/commands/schema/diff.d.ts.map +1 -0
  21. package/dist/commands/schema/diff.js +245 -0
  22. package/dist/commands/schema/diff.js.map +1 -0
  23. package/dist/commands/schema/generate.d.ts +59 -0
  24. package/dist/commands/schema/generate.d.ts.map +1 -0
  25. package/dist/commands/schema/generate.js +135 -0
  26. package/dist/commands/schema/generate.js.map +1 -0
  27. package/dist/commands/schema/list.d.ts +50 -0
  28. package/dist/commands/schema/list.d.ts.map +1 -0
  29. package/dist/commands/schema/list.js +115 -0
  30. package/dist/commands/schema/list.js.map +1 -0
  31. package/dist/commands/schema/migrate/list.d.ts +65 -0
  32. package/dist/commands/schema/migrate/list.d.ts.map +1 -0
  33. package/dist/commands/schema/migrate/list.js +148 -0
  34. package/dist/commands/schema/migrate/list.js.map +1 -0
  35. package/dist/commands/schema/migrate/plan.d.ts +79 -0
  36. package/dist/commands/schema/migrate/plan.d.ts.map +1 -0
  37. package/dist/commands/schema/migrate/plan.js +255 -0
  38. package/dist/commands/schema/migrate/plan.js.map +1 -0
  39. package/dist/commands/schema/migrate/stub.d.ts +59 -0
  40. package/dist/commands/schema/migrate/stub.d.ts.map +1 -0
  41. package/dist/commands/schema/migrate/stub.js +195 -0
  42. package/dist/commands/schema/migrate/stub.js.map +1 -0
  43. package/dist/commands/schema/migrate/verify.d.ts +59 -0
  44. package/dist/commands/schema/migrate/verify.d.ts.map +1 -0
  45. package/dist/commands/schema/migrate/verify.js +268 -0
  46. package/dist/commands/schema/migrate/verify.js.map +1 -0
  47. package/dist/exit-codes.d.ts +47 -0
  48. package/dist/exit-codes.d.ts.map +1 -0
  49. package/dist/exit-codes.js +46 -0
  50. package/dist/exit-codes.js.map +1 -0
  51. package/dist/formatters/json.d.ts +25 -0
  52. package/dist/formatters/json.d.ts.map +1 -0
  53. package/dist/formatters/json.js +27 -0
  54. package/dist/formatters/json.js.map +1 -0
  55. package/dist/formatters/pretty.d.ts +131 -0
  56. package/dist/formatters/pretty.d.ts.map +1 -0
  57. package/dist/formatters/pretty.js +229 -0
  58. package/dist/formatters/pretty.js.map +1 -0
  59. package/dist/index.d.ts +2 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +2 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/loaders/read-artifacts.d.ts +55 -0
  64. package/dist/loaders/read-artifacts.d.ts.map +1 -0
  65. package/dist/loaders/read-artifacts.js +99 -0
  66. package/dist/loaders/read-artifacts.js.map +1 -0
  67. package/dist/loaders/read-migrations.d.ts +70 -0
  68. package/dist/loaders/read-migrations.d.ts.map +1 -0
  69. package/dist/loaders/read-migrations.js +206 -0
  70. package/dist/loaders/read-migrations.js.map +1 -0
  71. package/dist/loaders/tsx-loader.d.ts +116 -0
  72. package/dist/loaders/tsx-loader.d.ts.map +1 -0
  73. package/dist/loaders/tsx-loader.js +250 -0
  74. package/dist/loaders/tsx-loader.js.map +1 -0
  75. package/dist/loaders/walk-workspace.d.ts +62 -0
  76. package/dist/loaders/walk-workspace.d.ts.map +1 -0
  77. package/dist/loaders/walk-workspace.js +133 -0
  78. package/dist/loaders/walk-workspace.js.map +1 -0
  79. package/docs/SCOPE.md +42 -0
  80. package/package.json +39 -0
@@ -0,0 +1,206 @@
1
+ /**
2
+ * `readMigrations({ root, pattern? })` — CLI-side discovery + load of
3
+ * authored `*.migration.ts` files (v0.8 Step 19).
4
+ *
5
+ * The CLI is the only filesystem walker and the only place that
6
+ * dynamic-imports authored migration modules (Master plan Decision #1;
7
+ * v0.8 INVARIANTS — `no apply, no transform execution` stays in force).
8
+ * This function:
9
+ *
10
+ * 1. Glob-walks `pattern` (default `**​/*.migration.ts`) under
11
+ * `root` using Node's built-in `fs/promises.glob` — same approach
12
+ * as the v0.7 `walk-workspace.ts`.
13
+ * 2. Reads each match's UTF-8 source text verbatim.
14
+ * 3. Hands the absolute path to a dynamic `import()` routed through
15
+ * the tsx ESM hook registered once by the sibling `tsx-loader.ts`
16
+ * module (importing it for the `register()` side effect is
17
+ * sufficient — no per-call `tsImport()`).
18
+ * 4. Validates the module's default export structurally as an
19
+ * `AnyMigration` (`schemaId` / `from` / `to` strings + `transform`
20
+ * function). Anything else → `no_migration_export` failure.
21
+ * 5. Pairs the load result with its `sourceText` and workspace-
22
+ * relative, forward-slash-normalized `sourcePath` into one
23
+ * `MigrationSourceEntry`.
24
+ * 6. Collects every per-file failure into a separate `failures`
25
+ * array — never short-circuits.
26
+ *
27
+ * **Top-level evaluation nuance.** Authored migration files are
28
+ * TypeScript modules. tsx evaluates their top-level code at import
29
+ * time; this is the CLI side of the v0.8 boundary (the schema package
30
+ * never imports authored migration modules). A top-level throw
31
+ * classifies as `runtime_error` — the rest of the walk continues.
32
+ *
33
+ * **`transform` is NEVER invoked here.** This loader reads, imports,
34
+ * validates the export shape, and stops. A static-scan test asserts
35
+ * the source contains no `.transform(` call.
36
+ *
37
+ * No `console.*`, no `process.exit`, no stdout/stderr writes —
38
+ * static-scan asserted by [`../../tests/loaders/read-migrations.test.ts`](../../tests/loaders/read-migrations.test.ts).
39
+ *
40
+ * This module does NOT:
41
+ * - Build a `MigrationRegistry` (that's `buildMigrationRegistry` on
42
+ * the schema side).
43
+ * - Parse the JSDoc provenance header (that's
44
+ * `parseMigrationProvenanceFromText` on the schema side).
45
+ * - Plan, verify, or stub migrations (Steps 21 / 22 / 23 commands).
46
+ * - Format output (later steps).
47
+ * - Decide exit codes (later steps).
48
+ */
49
+ import { readFile } from "node:fs/promises";
50
+ import { glob } from "node:fs/promises";
51
+ import { isAbsolute, resolve, sep } from "node:path";
52
+ import { pathToFileURL } from "node:url";
53
+ import { stat } from "node:fs/promises";
54
+ // Import the sibling loader purely for its `register()` side effect.
55
+ // `tsx-loader.ts` registers tsx's ESM hook once at module load, after
56
+ // which a plain dynamic `import()` of a `.ts` specifier routes through
57
+ // the tsx transform pipeline. Sharing the registration avoids paying
58
+ // the per-call `tsImport()` cost that compounds badly on Windows.
59
+ import { classifyImportError, errorMessageOf, } from "./tsx-loader.js";
60
+ // =============================================================================
61
+ // Public types
62
+ // =============================================================================
63
+ export const DEFAULT_MIGRATION_PATTERN = "**/*.migration.ts";
64
+ // =============================================================================
65
+ // Loader
66
+ // =============================================================================
67
+ export async function readMigrations(opts) {
68
+ const pattern = opts.pattern ?? DEFAULT_MIGRATION_PATTERN;
69
+ const rootAbs = isAbsolute(opts.root) ? opts.root : resolve(opts.root);
70
+ let relativePaths;
71
+ try {
72
+ relativePaths = await collectGlob(pattern, rootAbs);
73
+ }
74
+ catch (cause) {
75
+ return {
76
+ entries: [],
77
+ failures: [
78
+ {
79
+ path: rootAbs,
80
+ reason: "io_error",
81
+ message: errorMessageOf(cause),
82
+ cause,
83
+ },
84
+ ],
85
+ };
86
+ }
87
+ // Deterministic ordering: alphabetical on forward-slash-normalized
88
+ // relative paths. Same convention as `walk-workspace.ts`.
89
+ const normalized = relativePaths
90
+ .map((p) => p.split(sep).join("/"))
91
+ .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
92
+ const entries = [];
93
+ const failures = [];
94
+ for (const rel of normalized) {
95
+ const abs = resolve(rootAbs, rel);
96
+ // IO check — fail fast on missing / non-regular files. Glob
97
+ // results pass under normal conditions, but a race (file deleted
98
+ // between discovery and read) lands here.
99
+ const ioFailure = await checkRegularFile(abs, rel);
100
+ if (ioFailure !== undefined) {
101
+ failures.push(ioFailure);
102
+ continue;
103
+ }
104
+ let sourceText;
105
+ try {
106
+ sourceText = await readFile(abs, "utf8");
107
+ }
108
+ catch (cause) {
109
+ failures.push({
110
+ path: rel,
111
+ reason: "io_error",
112
+ message: errorMessageOf(cause),
113
+ cause,
114
+ });
115
+ continue;
116
+ }
117
+ const specifier = pathToFileURL(abs).href;
118
+ let mod;
119
+ try {
120
+ mod = (await import(specifier));
121
+ }
122
+ catch (cause) {
123
+ failures.push({
124
+ path: rel,
125
+ reason: classifyImportError(cause),
126
+ message: errorMessageOf(cause),
127
+ cause,
128
+ });
129
+ continue;
130
+ }
131
+ const migration = extractMigration(mod);
132
+ if (migration === undefined) {
133
+ failures.push({
134
+ path: rel,
135
+ reason: "no_migration_export",
136
+ message: `Module \`${rel}\` evaluated successfully but exposed no valid ` +
137
+ `\`Migration\` default export (need \`{ schemaId: string, from: string, ` +
138
+ `to: string, transform: (input) => output }\`).`,
139
+ });
140
+ continue;
141
+ }
142
+ entries.push({
143
+ sourcePath: rel,
144
+ sourceText,
145
+ migration,
146
+ });
147
+ }
148
+ return { entries, failures };
149
+ }
150
+ // =============================================================================
151
+ // Helpers
152
+ // =============================================================================
153
+ async function collectGlob(pattern, cwd) {
154
+ const out = [];
155
+ for await (const p of glob(pattern, { cwd }))
156
+ out.push(p);
157
+ return out;
158
+ }
159
+ async function checkRegularFile(abs, rel) {
160
+ try {
161
+ const stats = await stat(abs);
162
+ if (!stats.isFile()) {
163
+ return {
164
+ path: rel,
165
+ reason: "io_error",
166
+ message: `Path \`${rel}\` is not a regular file.`,
167
+ };
168
+ }
169
+ return undefined;
170
+ }
171
+ catch (cause) {
172
+ return {
173
+ path: rel,
174
+ reason: "io_error",
175
+ message: errorMessageOf(cause),
176
+ cause,
177
+ };
178
+ }
179
+ }
180
+ /**
181
+ * Structural `AnyMigration` validator on the module's default export.
182
+ * Mirrors the duck-typing approach in `tsx-loader.ts`'s `isAnySchema`:
183
+ * `instanceof` is unreliable because tsx and the CLI resolve
184
+ * `@nekostack/schema` through different module-graph entries, so the
185
+ * type identity differs across the boundary. Structural checks are
186
+ * the robust alternative.
187
+ *
188
+ * Returns the migration on success, `undefined` if the default export
189
+ * is missing or shaped incorrectly.
190
+ */
191
+ function extractMigration(mod) {
192
+ const def = mod.default;
193
+ if (typeof def !== "object" || def === null)
194
+ return undefined;
195
+ const m = def;
196
+ if (typeof m.schemaId !== "string")
197
+ return undefined;
198
+ if (typeof m.from !== "string")
199
+ return undefined;
200
+ if (typeof m.to !== "string")
201
+ return undefined;
202
+ if (typeof m.transform !== "function")
203
+ return undefined;
204
+ return def;
205
+ }
206
+ //# sourceMappingURL=read-migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-migrations.js","sourceRoot":"","sources":["../../src/loaders/read-migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAExC,qEAAqE;AACrE,sEAAsE;AACtE,uEAAuE;AACvE,qEAAqE;AACrE,kEAAkE;AAClE,OAAO,EACL,mBAAmB,EACnB,cAAc,GAEf,MAAM,iBAAiB,CAAC;AAIzB,gFAAgF;AAChF,eAAe;AACf,gFAAgF;AAEhF,MAAM,CAAC,MAAM,yBAAyB,GAAG,mBAAmB,CAAC;AAoB7D,gFAAgF;AAChF,SAAS;AACT,gFAAgF;AAEhF,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAwB;IAExB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,yBAAyB,CAAC;IAC1D,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEvE,IAAI,aAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,aAAa,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,EAAE;YACX,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,OAAO;oBACb,MAAM,EAAE,UAAU;oBAClB,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC;oBAC9B,KAAK;iBACN;aACF;SACF,CAAC;IACJ,CAAC;IAED,mEAAmE;IACnE,0DAA0D;IAC1D,MAAM,UAAU,GAAG,aAAa;SAC7B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;SAClC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEhD,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,MAAM,QAAQ,GAAkB,EAAE,CAAC;IAEnC,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAElC,4DAA4D;QAC5D,iEAAiE;QACjE,0CAA0C;QAC1C,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACnD,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACzB,SAAS;QACX,CAAC;QAED,IAAI,UAAkB,CAAC;QACvB,IAAI,CAAC;YACH,UAAU,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,GAAG;gBACT,MAAM,EAAE,UAAU;gBAClB,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC;gBAC9B,KAAK;aACN,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,MAAM,SAAS,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;QAC1C,IAAI,GAA4B,CAAC;QACjC,IAAI,CAAC;YACH,GAAG,GAAG,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,CAA4B,CAAC;QAC7D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,GAAG;gBACT,MAAM,EAAE,mBAAmB,CAAC,KAAK,CAAC;gBAClC,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC;gBAC9B,KAAK;aACN,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,MAAM,SAAS,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC5B,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,GAAG;gBACT,MAAM,EAAE,qBAAqB;gBAC7B,OAAO,EACL,YAAY,GAAG,iDAAiD;oBAChE,yEAAyE;oBACzE,gDAAgD;aACnD,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,OAAO,CAAC,IAAI,CAAC;YACX,UAAU,EAAE,GAAG;YACf,UAAU;YACV,SAAS;SACV,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC/B,CAAC;AAED,gFAAgF;AAChF,UAAU;AACV,gFAAgF;AAEhF,KAAK,UAAU,WAAW,CAAC,OAAe,EAAE,GAAW;IACrD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,KAAK,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC;QAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1D,OAAO,GAAG,CAAC;AACb,CAAC;AAED,KAAK,UAAU,gBAAgB,CAC7B,GAAW,EACX,GAAW;IAEX,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACpB,OAAO;gBACL,IAAI,EAAE,GAAG;gBACT,MAAM,EAAE,UAAU;gBAClB,OAAO,EAAE,UAAU,GAAG,2BAA2B;aAClD,CAAC;QACJ,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,IAAI,EAAE,GAAG;YACT,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC;YAC9B,KAAK;SACN,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,gBAAgB,CAAC,GAA4B;IACpD,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC;IACxB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC9D,MAAM,CAAC,GAAG,GAKT,CAAC;IACF,IAAI,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACrD,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACjD,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC/C,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,UAAU;QAAE,OAAO,SAAS,CAAC;IACxD,OAAO,GAAmB,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,116 @@
1
+ /**
2
+ * `tsx`-backed schema-module loader (v0.7 Step 22).
3
+ *
4
+ * Loads a single `*.schema.{ts,js,mts,cts}` file in-process, extracts
5
+ * every exported NekoStack `Schema` instance, and returns a typed
6
+ * `LoadResult`. Pure data-in / data-out at this layer — no `console.*`,
7
+ * no `process.exit`, no `stderr.write`. The CLI dispatch layer (Steps
8
+ * 25+) maps `LoadFailure` payloads to `schema_load_failed` `Issue`s
9
+ * and to exit code 3.
10
+ *
11
+ * **Loading strategy.** `register()` from `tsx/esm/api` installs tsx's
12
+ * ESM hook once at module load (`register()` is called below, near
13
+ * the imports). Subsequent loads use plain dynamic `import()` — Node
14
+ * routes `.ts` and `.tsx` specifiers through tsx's transform
15
+ * pipeline, and Node's own module cache makes repeat loads near-free.
16
+ * Per-call `tsImport()` was tried first but pays a fresh-ESM-context
17
+ * cost on every invocation that compounds badly on Windows (multi-
18
+ * second per-load latency when walking even a small workspace).
19
+ *
20
+ * Loader-level failures classify per the locked Master plan Decision #1
21
+ * categories:
22
+ *
23
+ * - `io_error` — file missing / unreadable / not a regular file.
24
+ * - `compile_error` — TS/JS syntax error; tsx (via esbuild) refused
25
+ * to transform the source.
26
+ * - `runtime_error` — module loaded but threw during evaluation
27
+ * (top-level code, side-effect imports, etc.).
28
+ * - `no_schema_export` — module evaluated successfully but exposed
29
+ * zero `Schema` instances on its exports.
30
+ *
31
+ * This module does NOT:
32
+ * - Walk a workspace (Step 23, `walk-workspace.ts`).
33
+ * - Read committed artifacts (Step 24, `read-artifacts.ts`).
34
+ * - Build a registry (Step 29's `list.ts` calls `buildRegistry` from
35
+ * `@nekostack/schema/cli`).
36
+ * - Format anything for stdout / stderr (Step 27 / Step 28).
37
+ *
38
+ * Compile-error detection — esbuild surfaces transform failures
39
+ * through one of several shapes depending on how tsx wraps them and
40
+ * what runtime is in front (plain Node vs. vitest's worker pool).
41
+ * `classifyImportError` checks, in order:
42
+ * 1. `error.errors` is a populated array (esbuild's `BuildFailure`).
43
+ * 2. `error instanceof SyntaxError` (Node's ESM parser fallback).
44
+ * 3. `error.name === "TransformError"`.
45
+ * 4. Specific Node module-loading codes
46
+ * (`ERR_UNSUPPORTED_DIR_IMPORT`, `ERR_INVALID_MODULE_SPECIFIER`).
47
+ * 5. Message-pattern fallback `/^Transform failed/` — vitest's
48
+ * worker flattens esbuild errors to plain `new Error(...)` with
49
+ * no discriminating properties, but the message prefix stays
50
+ * stable across tsx and esbuild versions.
51
+ * Anything else thrown while attempting to import is treated as a
52
+ * runtime error.
53
+ */
54
+ import type { AnySchema } from "@nekostack/schema";
55
+ export type LoadFailureReason = "io_error" | "compile_error" | "runtime_error" | "no_schema_export" | "no_migration_export";
56
+ export interface LoadFailure {
57
+ readonly path: string;
58
+ readonly reason: LoadFailureReason;
59
+ readonly message: string;
60
+ /**
61
+ * The original error (if any) that triggered the failure. Carried so
62
+ * the CLI dispatch layer can put it in `Issue.metadata.cause` for
63
+ * diagnostic logging. Always `undefined` for `no_schema_export`.
64
+ */
65
+ readonly cause?: unknown;
66
+ }
67
+ export interface LoadedSchemaModule {
68
+ readonly path: string;
69
+ /**
70
+ * Every exported value structurally recognized as a `Schema`,
71
+ * sorted ascending by `schemaId` with anonymous schemas (no `.id()`)
72
+ * pushed to the end in their original-iteration order. Sorting is
73
+ * done here so callers see a deterministic shape regardless of how
74
+ * the underlying ESM runtime iterates module namespaces — plain
75
+ * Node + tsx's `register()` hook returns alphabetical-by-export-name
76
+ * order, vitest's worker returns declaration order, and a future
77
+ * Node release could change either. Downstream registry code
78
+ * already sorts by `schemaId` (see `listHandler`); doing it here
79
+ * means single-file callers also get the stable order without
80
+ * re-sorting.
81
+ */
82
+ readonly schemas: readonly AnySchema[];
83
+ }
84
+ export type LoadResult = {
85
+ readonly success: true;
86
+ readonly data: LoadedSchemaModule;
87
+ } | {
88
+ readonly success: false;
89
+ readonly failure: LoadFailure;
90
+ };
91
+ /**
92
+ * Load one schema-source file by absolute or workspace-relative path.
93
+ *
94
+ * `parentURL` is retained on the signature for API stability (some
95
+ * future loader strategy may need it again) but is unused in the
96
+ * current `register()`-based implementation — file URLs are absolute,
97
+ * so resolution doesn't need a parent.
98
+ */
99
+ export declare function loadSchemaModule(path: string, _parentURL?: string): Promise<LoadResult>;
100
+ /**
101
+ * Classify a thrown value from a dynamic `import()` into one of the
102
+ * structural failure categories. Exported so sibling loaders (e.g.
103
+ * `read-migrations.ts`) can use the same classification rules without
104
+ * duplicating the esbuild/tsx detection list. The returned reason is
105
+ * one of the import-time categories only (`compile_error` /
106
+ * `runtime_error`); `io_error` and `no_*_export` are decided by
107
+ * callers before / after the import.
108
+ */
109
+ export declare function classifyImportError(err: unknown): LoadFailureReason;
110
+ /**
111
+ * Stringify an unknown thrown value the way every loader in this
112
+ * package needs to. Exported for sibling loaders so they share one
113
+ * coercion rule.
114
+ */
115
+ export declare function errorMessageOf(err: unknown): string;
116
+ //# sourceMappingURL=tsx-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tsx-loader.d.ts","sourceRoot":"","sources":["../../src/loaders/tsx-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAkBnD,MAAM,MAAM,iBAAiB,GACzB,UAAU,GACV,eAAe,GACf,eAAe,GACf,kBAAkB,GAClB,qBAAqB,CAAC;AAE1B,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS,SAAS,EAAE,CAAC;CACxC;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAA;CAAE,GAC7D;IAAE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;CAAE,CAAC;AAM/D;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EAEZ,UAAU,GAAE,MAAwB,GACnC,OAAO,CAAC,UAAU,CAAC,CA2CrB;AA2BD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,CA4BnE;AAgDD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAQnD"}
@@ -0,0 +1,250 @@
1
+ /**
2
+ * `tsx`-backed schema-module loader (v0.7 Step 22).
3
+ *
4
+ * Loads a single `*.schema.{ts,js,mts,cts}` file in-process, extracts
5
+ * every exported NekoStack `Schema` instance, and returns a typed
6
+ * `LoadResult`. Pure data-in / data-out at this layer — no `console.*`,
7
+ * no `process.exit`, no `stderr.write`. The CLI dispatch layer (Steps
8
+ * 25+) maps `LoadFailure` payloads to `schema_load_failed` `Issue`s
9
+ * and to exit code 3.
10
+ *
11
+ * **Loading strategy.** `register()` from `tsx/esm/api` installs tsx's
12
+ * ESM hook once at module load (`register()` is called below, near
13
+ * the imports). Subsequent loads use plain dynamic `import()` — Node
14
+ * routes `.ts` and `.tsx` specifiers through tsx's transform
15
+ * pipeline, and Node's own module cache makes repeat loads near-free.
16
+ * Per-call `tsImport()` was tried first but pays a fresh-ESM-context
17
+ * cost on every invocation that compounds badly on Windows (multi-
18
+ * second per-load latency when walking even a small workspace).
19
+ *
20
+ * Loader-level failures classify per the locked Master plan Decision #1
21
+ * categories:
22
+ *
23
+ * - `io_error` — file missing / unreadable / not a regular file.
24
+ * - `compile_error` — TS/JS syntax error; tsx (via esbuild) refused
25
+ * to transform the source.
26
+ * - `runtime_error` — module loaded but threw during evaluation
27
+ * (top-level code, side-effect imports, etc.).
28
+ * - `no_schema_export` — module evaluated successfully but exposed
29
+ * zero `Schema` instances on its exports.
30
+ *
31
+ * This module does NOT:
32
+ * - Walk a workspace (Step 23, `walk-workspace.ts`).
33
+ * - Read committed artifacts (Step 24, `read-artifacts.ts`).
34
+ * - Build a registry (Step 29's `list.ts` calls `buildRegistry` from
35
+ * `@nekostack/schema/cli`).
36
+ * - Format anything for stdout / stderr (Step 27 / Step 28).
37
+ *
38
+ * Compile-error detection — esbuild surfaces transform failures
39
+ * through one of several shapes depending on how tsx wraps them and
40
+ * what runtime is in front (plain Node vs. vitest's worker pool).
41
+ * `classifyImportError` checks, in order:
42
+ * 1. `error.errors` is a populated array (esbuild's `BuildFailure`).
43
+ * 2. `error instanceof SyntaxError` (Node's ESM parser fallback).
44
+ * 3. `error.name === "TransformError"`.
45
+ * 4. Specific Node module-loading codes
46
+ * (`ERR_UNSUPPORTED_DIR_IMPORT`, `ERR_INVALID_MODULE_SPECIFIER`).
47
+ * 5. Message-pattern fallback `/^Transform failed/` — vitest's
48
+ * worker flattens esbuild errors to plain `new Error(...)` with
49
+ * no discriminating properties, but the message prefix stays
50
+ * stable across tsx and esbuild versions.
51
+ * Anything else thrown while attempting to import is treated as a
52
+ * runtime error.
53
+ */
54
+ import { stat } from "node:fs/promises";
55
+ import { pathToFileURL } from "node:url";
56
+ import { register } from "tsx/esm/api";
57
+ // Register tsx's ESM hook once at module load. Per-call `tsImport()`
58
+ // pays a fresh-context cost that compounds across many files (each
59
+ // call spins up new ESM resolution state), which on Windows pushes
60
+ // per-load latency above 1s and turns a normal `walkWorkspace` over
61
+ // a few schema files into a multi-minute hang. `register()` installs
62
+ // the loader globally for the lifetime of the process, after which a
63
+ // plain dynamic `import()` of a `.ts` file goes through tsx's
64
+ // transform pipeline and benefits from Node's module cache — the
65
+ // second load of any file (or any cross-test repeat) is effectively
66
+ // free.
67
+ register();
68
+ // =============================================================================
69
+ // Loader
70
+ // =============================================================================
71
+ /**
72
+ * Load one schema-source file by absolute or workspace-relative path.
73
+ *
74
+ * `parentURL` is retained on the signature for API stability (some
75
+ * future loader strategy may need it again) but is unused in the
76
+ * current `register()`-based implementation — file URLs are absolute,
77
+ * so resolution doesn't need a parent.
78
+ */
79
+ export async function loadSchemaModule(path,
80
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
81
+ _parentURL = import.meta.url) {
82
+ // 1. IO check — fail fast on missing / non-regular files. This catches
83
+ // typos and stale paths before tsx spins up an esbuild worker.
84
+ const ioFailure = await checkRegularFile(path);
85
+ if (ioFailure !== undefined)
86
+ return { success: false, failure: ioFailure };
87
+ // 2. Resolve to a file:// URL so plain dynamic import treats it as
88
+ // an absolute specifier regardless of platform path separator.
89
+ const specifier = pathToFileURL(path).href;
90
+ let mod;
91
+ try {
92
+ mod = (await import(specifier));
93
+ }
94
+ catch (cause) {
95
+ const reason = classifyImportError(cause);
96
+ return {
97
+ success: false,
98
+ failure: {
99
+ path,
100
+ reason,
101
+ message: errorMessageOf(cause),
102
+ cause,
103
+ },
104
+ };
105
+ }
106
+ // 3. Extract every Schema instance from the module namespace, then
107
+ // sort deterministically by schemaId so callers see a stable
108
+ // shape regardless of how the underlying ESM runtime iterates
109
+ // namespace exports.
110
+ const schemas = sortSchemas(extractSchemas(mod));
111
+ if (schemas.length === 0) {
112
+ return {
113
+ success: false,
114
+ failure: {
115
+ path,
116
+ reason: "no_schema_export",
117
+ message: `Module \`${path}\` evaluated successfully but exposed no \`Schema\` exports.`,
118
+ },
119
+ };
120
+ }
121
+ return { success: true, data: { path, schemas } };
122
+ }
123
+ // =============================================================================
124
+ // Helpers
125
+ // =============================================================================
126
+ async function checkRegularFile(path) {
127
+ try {
128
+ const stats = await stat(path);
129
+ if (!stats.isFile()) {
130
+ return {
131
+ path,
132
+ reason: "io_error",
133
+ message: `Path \`${path}\` is not a regular file.`,
134
+ };
135
+ }
136
+ return undefined;
137
+ }
138
+ catch (cause) {
139
+ return {
140
+ path,
141
+ reason: "io_error",
142
+ message: errorMessageOf(cause),
143
+ cause,
144
+ };
145
+ }
146
+ }
147
+ /**
148
+ * Classify a thrown value from a dynamic `import()` into one of the
149
+ * structural failure categories. Exported so sibling loaders (e.g.
150
+ * `read-migrations.ts`) can use the same classification rules without
151
+ * duplicating the esbuild/tsx detection list. The returned reason is
152
+ * one of the import-time categories only (`compile_error` /
153
+ * `runtime_error`); `io_error` and `no_*_export` are decided by
154
+ * callers before / after the import.
155
+ */
156
+ export function classifyImportError(err) {
157
+ if (err === null || typeof err !== "object")
158
+ return "runtime_error";
159
+ const e = err;
160
+ // 1. esbuild `BuildFailure` — has a populated `errors` array.
161
+ if (Array.isArray(e.errors) && e.errors.length > 0)
162
+ return "compile_error";
163
+ // 2. Native `SyntaxError` raised by Node's ESM parser, or a tsx wrap of one.
164
+ if (err instanceof SyntaxError)
165
+ return "compile_error";
166
+ // 3. tsx / esbuild sometimes surfaces a `TransformError` name.
167
+ if (e.name === "TransformError")
168
+ return "compile_error";
169
+ // 4. Node module-loading error codes that mean "could not parse / load":
170
+ if (e.code === "ERR_UNSUPPORTED_DIR_IMPORT" || e.code === "ERR_INVALID_MODULE_SPECIFIER") {
171
+ return "compile_error";
172
+ }
173
+ // 5. Message-pattern fallback for environments where tsx's error
174
+ // propagation flattens `name` / `errors` to a plain `Error`. In
175
+ // vitest's worker pool, for instance, esbuild transform failures
176
+ // arrive as `new Error("Transform failed with N errors: ...")`
177
+ // with no other discriminating properties. The message prefix
178
+ // is esbuild's own and is stable across tsx/esbuild versions.
179
+ if (typeof e.message === "string" && /^Transform failed/.test(e.message)) {
180
+ return "compile_error";
181
+ }
182
+ return "runtime_error";
183
+ }
184
+ function extractSchemas(mod) {
185
+ const out = [];
186
+ for (const value of Object.values(mod)) {
187
+ if (isAnySchema(value))
188
+ out.push(value);
189
+ }
190
+ return out;
191
+ }
192
+ /**
193
+ * Sort schemas ascending by `schemaId`. Anonymous schemas (id ===
194
+ * undefined) are stable-sorted to the end, in their original index
195
+ * order. Matches the convention `listHandler` uses on the registry
196
+ * side so single-file and registry-level orderings agree.
197
+ */
198
+ function sortSchemas(schemas) {
199
+ return schemas
200
+ .map((s, i) => ({ s, i, id: s.node.metadata?.id }))
201
+ .sort((a, b) => {
202
+ // Anonymous-to-end, then by index for stability.
203
+ if (a.id === undefined && b.id === undefined)
204
+ return a.i - b.i;
205
+ if (a.id === undefined)
206
+ return 1;
207
+ if (b.id === undefined)
208
+ return -1;
209
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : a.i - b.i;
210
+ })
211
+ .map((r) => r.s);
212
+ }
213
+ /**
214
+ * Structural Schema detector. `instanceof Schema` would be cleaner, but
215
+ * tsx loads schema fixtures by resolving `@nekostack/schema` through a
216
+ * different module-graph entry than the CLI's own static import — so
217
+ * the `Schema` constructor identity differs between the two, and
218
+ * `instanceof` always returns false. Duck-typing on the IR shape
219
+ * (`node.kind: string`) is the robust alternative; the SchemaNode
220
+ * contract guarantees every concrete builder produces an object with
221
+ * `.node.kind` and `.node` is deep-frozen at construction. False
222
+ * positives would require a non-schema value that mimics the IR
223
+ * shape, which the CLI's own input surface does not produce.
224
+ */
225
+ function isAnySchema(v) {
226
+ if (typeof v !== "object" || v === null)
227
+ return false;
228
+ const node = v.node;
229
+ if (typeof node !== "object" || node === null)
230
+ return false;
231
+ return typeof node.kind === "string";
232
+ }
233
+ /**
234
+ * Stringify an unknown thrown value the way every loader in this
235
+ * package needs to. Exported for sibling loaders so they share one
236
+ * coercion rule.
237
+ */
238
+ export function errorMessageOf(err) {
239
+ if (err instanceof Error)
240
+ return err.message;
241
+ if (typeof err === "string")
242
+ return err;
243
+ try {
244
+ return JSON.stringify(err);
245
+ }
246
+ catch {
247
+ return String(err);
248
+ }
249
+ }
250
+ //# sourceMappingURL=tsx-loader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tsx-loader.js","sourceRoot":"","sources":["../../src/loaders/tsx-loader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `walkWorkspace({ root, pattern? })` — discover `*.schema.{ts,js}`
3
+ * files under a workspace root and load each one into a
4
+ * `RegistrySourceEntry`. v0.7 Step 23.
5
+ *
6
+ * The CLI is the only filesystem walker (Master plan / CLI plan
7
+ * Decision #2). This function:
8
+ *
9
+ * 1. Glob-walks `pattern` (default `**​/*.schema.{ts,js}`) under
10
+ * `root` using Node's built-in `fs/promises.glob` (Node 22+).
11
+ * No external glob dep.
12
+ * 2. Reads each match's UTF-8 source text.
13
+ * 3. Hands the absolute path to `loadSchemaModule` from
14
+ * `tsx-loader.ts`.
15
+ * 4. Builds one `RegistrySourceEntry` per successfully loaded module.
16
+ * 5. Collects every per-file failure into a separate `failures`
17
+ * array — never short-circuits. The CLI dispatch layer (Steps
18
+ * 25+) decides whether to surface them and what exit code to
19
+ * pick; this loader is silent.
20
+ *
21
+ * No `console.*`, no `process.exit`, no stderr / stdout writes —
22
+ * static-scan asserted by [`../../tests/loaders/walk-workspace.test.ts`](../../tests/loaders/walk-workspace.test.ts).
23
+ *
24
+ * This module does NOT:
25
+ * - Build a `Registry` (that's `buildRegistry` on the schema side).
26
+ * - Call any schema-side handler (`listHandler` etc. — Steps 29+).
27
+ * - Format output (Steps 27 / 28).
28
+ * - Decide exit codes (Step 25 dispatch + Step 26 exit-codes enum).
29
+ *
30
+ * Pattern semantics: a caller-supplied `pattern` **replaces** the
31
+ * default — it does not extend it. The CLI surface (Master plan CLI
32
+ * companion §"Locked subcommand surface") spells this out for the
33
+ * `[pattern]` positional on `generate` / `check`.
34
+ *
35
+ * Deterministic ordering: discovered paths are sorted ascending by
36
+ * their workspace-relative form (forward-slash-normalized) before
37
+ * loading. Downstream sort by `schemaId` is still required for
38
+ * registry order (the listHandler does that); this sort is purely so
39
+ * walk-time errors and CLI output have a stable order across runs.
40
+ */
41
+ import type { RegistrySourceEntry } from "@nekostack/schema/cli";
42
+ import { type LoadFailure } from "./tsx-loader.js";
43
+ export type { RegistrySourceEntry };
44
+ export declare const DEFAULT_SCHEMA_PATTERN = "**/*.schema.{ts,js}";
45
+ export interface WalkOpts {
46
+ /** Workspace root. Must be passed explicitly — the walker does not
47
+ * fall back to `process.cwd()`. CLI dispatch layer resolves
48
+ * `--root` (or its default) and hands the absolute path in. */
49
+ readonly root: string;
50
+ /** Optional glob, **replaces** {@link DEFAULT_SCHEMA_PATTERN}. */
51
+ readonly pattern?: string;
52
+ }
53
+ export interface WalkResult {
54
+ /** One entry per successfully loaded schema file, sorted by
55
+ * `sourcePath`. */
56
+ readonly entries: readonly RegistrySourceEntry[];
57
+ /** Per-file failures collected across the walk; ordered the same
58
+ * way as discovery. Empty array when every file loaded cleanly. */
59
+ readonly failures: readonly LoadFailure[];
60
+ }
61
+ export declare function walkWorkspace(opts: WalkOpts): Promise<WalkResult>;
62
+ //# sourceMappingURL=walk-workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"walk-workspace.d.ts","sourceRoot":"","sources":["../../src/loaders/walk-workspace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAKH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAoB,KAAK,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAErE,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAMpC,eAAO,MAAM,sBAAsB,wBAAwB,CAAC;AAE5D,MAAM,WAAW,QAAQ;IACvB;;oEAEgE;IAChE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB;wBACoB;IACpB,QAAQ,CAAC,OAAO,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACjD;wEACoE;IACpE,QAAQ,CAAC,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;CAC3C;AAMD,wBAAsB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CAiEvE"}