@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,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"}
|