tina4-nodejs 3.13.133 → 3.13.134
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/CLAUDE.md +3 -3
- package/README.md +2 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +3181 -3051
- package/packages/cli/src/commands/generate.ts +33 -22
- package/packages/cli/src/commands/lint.ts +77 -111
- package/packages/core/dist/index.js +3090 -2952
- package/packages/core/src/.tina4-metrics.json +15004 -0
- package/packages/core/src/aiClient.ts +199 -161
- package/packages/core/src/dispatchPipeline.ts +65 -67
- package/packages/core/src/docs.ts +52 -544
- package/packages/core/src/docsParser.ts +270 -0
- package/packages/core/src/docsScanner.ts +121 -0
- package/packages/core/src/docsSignatures.ts +165 -0
- package/packages/core/src/index.ts +2 -0
- package/packages/core/src/logger.ts +68 -82
- package/packages/core/src/mcp.ts +32 -60
- package/packages/core/src/messenger.ts +136 -157
- package/packages/core/src/middleware.ts +56 -60
- package/packages/core/src/plan.ts +78 -70
- package/packages/core/src/projectIndex.ts +15 -288
- package/packages/core/src/projectIndexExtractors.ts +126 -0
- package/packages/core/src/projectIndexStorage.ts +122 -0
- package/packages/core/src/push.ts +281 -0
- package/packages/core/src/server.ts +182 -183
- package/packages/frond/dist/index.js +607 -770
- package/packages/frond/src/engine.ts +670 -818
- package/packages/orm/dist/index.js +3100 -2965
- package/packages/orm/src/adapters/mongodb.ts +99 -144
- package/packages/orm/src/baseModel.ts +429 -515
- package/packages/orm/src/fakeData.ts +73 -61
- package/packages/orm/src/migration.ts +96 -126
- package/packages/orm/src/seeder.ts +6 -238
- package/packages/orm/src/seederTable.ts +101 -0
- package/packages/orm/src/seederTypes.ts +14 -0
- package/packages/orm/src/validation.ts +97 -80
- package/types/core/src/aiClient.d.ts +5 -0
- package/types/core/src/docsParser.d.ts +28 -0
- package/types/core/src/docsScanner.d.ts +1 -0
- package/types/core/src/docsSignatures.d.ts +11 -0
- package/types/core/src/index.d.ts +2 -0
- package/types/core/src/messenger.d.ts +8 -0
- package/types/core/src/projectIndexExtractors.d.ts +3 -0
- package/types/core/src/projectIndexStorage.d.ts +13 -0
- package/types/core/src/push.d.ts +45 -0
- package/types/frond/src/engine.d.ts +25 -0
- package/types/orm/src/fakeData.d.ts +3 -0
- package/types/orm/src/seeder.d.ts +3 -89
- package/types/orm/src/seederTable.d.ts +9 -0
- package/types/orm/src/seederTypes.d.ts +16 -0
|
@@ -416,22 +416,7 @@ function captureEditHints(absPath: string, content: string): void {
|
|
|
416
416
|
}
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
-
|
|
420
|
-
* Emit the resolution — as JSON on STDOUT for `--json`, otherwise as a human
|
|
421
|
-
* block on STDERR (stderr so a caller piping stdout for other output isn't
|
|
422
|
-
* polluted). Called from `generate()` BEFORE the files are written on the
|
|
423
|
-
* human path so an operator sees WHY the tool made its choices before disk
|
|
424
|
-
* changes; the JSON path prints after collection so the envelope carries the
|
|
425
|
-
* completed `actions_taken`.
|
|
426
|
-
*/
|
|
427
|
-
function printResolution(): void {
|
|
428
|
-
if (__resolution.jsonMode) {
|
|
429
|
-
process.stdout.write(JSON.stringify(currentResolution(), null, 2) + "\n");
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
// Human block on STDERR, so `command | jq …` on stdout works cleanly.
|
|
433
|
-
const b = __resolution.body;
|
|
434
|
-
const lines: string[] = [];
|
|
419
|
+
function addResolutionSummary(lines: string[], b: ResolutionBody): void {
|
|
435
420
|
lines.push("");
|
|
436
421
|
lines.push(`Generated ${__resolution.target} ${__resolution.input.name}`);
|
|
437
422
|
if (b.class_name || b.file_path) {
|
|
@@ -449,13 +434,18 @@ function printResolution(): void {
|
|
|
449
434
|
if (b.migration_path) {
|
|
450
435
|
lines.push(` migration ${b.migration_path}`);
|
|
451
436
|
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function addReservedWordGuidance(lines: string[], b: ResolutionBody): void {
|
|
452
440
|
const reserved = b.transformations.find((t) => t.kind === "reserved_word_pluralize");
|
|
453
|
-
if (reserved
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
441
|
+
if (!reserved?.from) return;
|
|
442
|
+
lines.push("");
|
|
443
|
+
lines.push(` To set the table name yourself:`);
|
|
444
|
+
lines.push(` tina4nodejs generate ${__resolution.target} ${__resolution.input.name} --table-name <name>`);
|
|
445
|
+
lines.push(` Tina4 interpolates table names unquoted; if you force the reserved '${reserved.from}', you own the quoting in raw SQL.`);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function addResolutionLists(lines: string[], b: ResolutionBody): void {
|
|
459
449
|
// v1.1 (ADR-0063): surface the already-populated test_paths[], and the two
|
|
460
450
|
// new arrays (edit_hints, next) when either is non-empty. Sections stay
|
|
461
451
|
// absent when the corresponding array is empty — a listener/service
|
|
@@ -478,6 +468,27 @@ function printResolution(): void {
|
|
|
478
468
|
lines.push(" Next:");
|
|
479
469
|
for (const step of b.next) lines.push(` ${step}`);
|
|
480
470
|
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Emit the resolution — as JSON on STDOUT for `--json`, otherwise as a human
|
|
475
|
+
* block on STDERR (stderr so a caller piping stdout for other output isn't
|
|
476
|
+
* polluted). Called from `generate()` BEFORE the files are written on the
|
|
477
|
+
* human path so an operator sees WHY the tool made its choices before disk
|
|
478
|
+
* changes; the JSON path prints after collection so the envelope carries the
|
|
479
|
+
* completed `actions_taken`.
|
|
480
|
+
*/
|
|
481
|
+
function printResolution(): void {
|
|
482
|
+
if (__resolution.jsonMode) {
|
|
483
|
+
process.stdout.write(JSON.stringify(currentResolution(), null, 2) + "\n");
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
// Human block on STDERR, so `command | jq …` on stdout works cleanly.
|
|
487
|
+
const lines: string[] = [];
|
|
488
|
+
const b = __resolution.body;
|
|
489
|
+
addResolutionSummary(lines, b);
|
|
490
|
+
addReservedWordGuidance(lines, b);
|
|
491
|
+
addResolutionLists(lines, b);
|
|
481
492
|
lines.push("");
|
|
482
493
|
process.stderr.write(lines.join("\n"));
|
|
483
494
|
}
|
|
@@ -223,138 +223,104 @@ function resolveNpm(): string | null {
|
|
|
223
223
|
return null;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
export function runLint(args: string[]): void {
|
|
231
|
-
const fix = args.includes("--fix");
|
|
232
|
-
const noInstall = args.includes("--no-install");
|
|
233
|
-
const cwd = process.cwd();
|
|
226
|
+
interface EslintSetup {
|
|
227
|
+
bin: string | null;
|
|
228
|
+
config: string | null;
|
|
229
|
+
}
|
|
234
230
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
231
|
+
function bootstrapEslint(cwd: string, noInstall: boolean): EslintSetup {
|
|
232
|
+
let bin = resolveEslintBin(cwd);
|
|
233
|
+
let config = findEslintConfig(cwd);
|
|
234
|
+
if (noInstall || (bin && config)) return { bin, config };
|
|
235
|
+
const npm = resolveNpm();
|
|
236
|
+
if (!npm) {
|
|
237
|
+
console.log(" · npm not found — using the zero-dependency baseline.");
|
|
238
|
+
return { bin, config };
|
|
239
239
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
// Running `tina4 lint` is the consent to add eslint as a DEV dependency of the
|
|
246
|
-
// PROJECT and scaffold a minimal flat config. --no-install opts out (CI /
|
|
247
|
-
// offline) and falls through to the zero-dependency baseline. We only bootstrap
|
|
248
|
-
// what is missing, and only scaffold a config once eslint is actually available
|
|
249
|
-
// to use it (so a failed install never leaves a stray eslint.config.js).
|
|
250
|
-
if (!noInstall && (!eslintBin || !eslintConfig)) {
|
|
251
|
-
const npm = resolveNpm();
|
|
252
|
-
if (!npm) {
|
|
253
|
-
console.log(" · npm not found — using the zero-dependency baseline.");
|
|
254
|
-
} else {
|
|
255
|
-
if (!eslintBin) {
|
|
256
|
-
console.log(" · installing eslint (npm i -D eslint @eslint/js typescript-eslint)...");
|
|
257
|
-
const rc = spawnSync(npm, ["install", "-D", "eslint", "@eslint/js", "typescript-eslint"], {
|
|
258
|
-
cwd,
|
|
259
|
-
stdio: "inherit",
|
|
260
|
-
}).status;
|
|
261
|
-
if (rc === 0) {
|
|
262
|
-
eslintBin = resolveEslintBin(cwd);
|
|
263
|
-
} else {
|
|
264
|
-
console.log(" · could not install eslint — using the zero-dependency baseline.");
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
// Scaffold ONLY once all three the config imports are on disk (eslint bin +
|
|
268
|
-
// @eslint/js + typescript-eslint), so a partial/failed install never leaves
|
|
269
|
-
// an eslint.config.js that cannot load.
|
|
270
|
-
if (
|
|
271
|
-
eslintBin && !eslintConfig &&
|
|
272
|
-
hasPackage(cwd, "@eslint/js") && hasPackage(cwd, "typescript-eslint")
|
|
273
|
-
) {
|
|
274
|
-
const scaffold = join(cwd, ESLINT_SCAFFOLD_FILE);
|
|
275
|
-
try {
|
|
276
|
-
writeFileSync(scaffold, ESLINT_SCAFFOLD, "utf-8");
|
|
277
|
-
eslintConfig = scaffold;
|
|
278
|
-
console.log(` · scaffolded ${ESLINT_SCAFFOLD_FILE} (@eslint/js + typescript-eslint recommended).`);
|
|
279
|
-
} catch (err) {
|
|
280
|
-
console.log(
|
|
281
|
-
` · could not scaffold ${ESLINT_SCAFFOLD_FILE} (${err instanceof Error ? err.message : String(err)}).`,
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
240
|
+
if (!bin) {
|
|
241
|
+
console.log(" · installing eslint (npm i -D eslint @eslint/js typescript-eslint)...");
|
|
242
|
+
const rc = spawnSync(npm, ["install", "-D", "eslint", "@eslint/js", "typescript-eslint"], { cwd, stdio: "inherit" }).status;
|
|
243
|
+
if (rc === 0) bin = resolveEslintBin(cwd);
|
|
244
|
+
else console.log(" · could not install eslint — using the zero-dependency baseline.");
|
|
286
245
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (code !== 0) {
|
|
296
|
-
console.log(` ✗ lint failed — ${files.length} file(s) [${label}]`);
|
|
297
|
-
process.exit(1);
|
|
246
|
+
if (bin && !config && hasPackage(cwd, "@eslint/js") && hasPackage(cwd, "typescript-eslint")) {
|
|
247
|
+
const scaffold = join(cwd, ESLINT_SCAFFOLD_FILE);
|
|
248
|
+
try {
|
|
249
|
+
writeFileSync(scaffold, ESLINT_SCAFFOLD, "utf-8");
|
|
250
|
+
config = scaffold;
|
|
251
|
+
console.log(` · scaffolded ${ESLINT_SCAFFOLD_FILE} (@eslint/js + typescript-eslint recommended).`);
|
|
252
|
+
} catch (err) {
|
|
253
|
+
console.log(` · could not scaffold ${ESLINT_SCAFFOLD_FILE} (${err instanceof Error ? err.message : String(err)}).`);
|
|
298
254
|
}
|
|
299
|
-
console.log(` ✓ lint clean — ${files.length} file(s) [${label}]`);
|
|
300
|
-
process.exit(0);
|
|
301
255
|
}
|
|
256
|
+
return { bin, config };
|
|
257
|
+
}
|
|
302
258
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
259
|
+
function runEslint(cwd: string, files: string[], fix: boolean, bin: string): void {
|
|
260
|
+
const label = fix ? "eslint --fix" : "eslint";
|
|
261
|
+
const eslintArgs = [bin, ...files, ...(fix ? ["--fix"] : [])];
|
|
262
|
+
const code = spawnSync(process.execPath, eslintArgs, { cwd, stdio: "inherit" }).status ?? 1;
|
|
263
|
+
if (code !== 0) {
|
|
264
|
+
console.log(` ✗ lint failed — ${files.length} file(s) [${label}]`);
|
|
265
|
+
process.exit(1);
|
|
306
266
|
}
|
|
267
|
+
console.log(` ✓ lint clean — ${files.length} file(s) [${label}]`);
|
|
268
|
+
process.exit(0);
|
|
269
|
+
}
|
|
307
270
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (tscBin) {
|
|
316
|
-
const code = spawnSync(process.execPath, [tscBin, "--noEmit"], { cwd, stdio: "inherit" }).status ?? 1;
|
|
317
|
-
if (code !== 0) {
|
|
318
|
-
console.log(` ✗ lint failed — ${files.length} file(s) [tsc]`);
|
|
319
|
-
process.exit(1);
|
|
320
|
-
}
|
|
321
|
-
console.log(` ✓ lint clean — ${files.length} file(s) [tsc]`);
|
|
322
|
-
process.exit(0);
|
|
271
|
+
function runTypescript(cwd: string, files: string[]): boolean {
|
|
272
|
+
const tscBin = resolvePackageBin(cwd, "typescript", "bin/tsc");
|
|
273
|
+
if (!tscBin) return false;
|
|
274
|
+
const code = spawnSync(process.execPath, [tscBin, "--noEmit"], { cwd, stdio: "inherit" }).status ?? 1;
|
|
275
|
+
if (code !== 0) {
|
|
276
|
+
console.log(` ✗ lint failed — ${files.length} file(s) [tsc]`);
|
|
277
|
+
process.exit(1);
|
|
323
278
|
}
|
|
279
|
+
console.log(` ✓ lint clean — ${files.length} file(s) [tsc]`);
|
|
280
|
+
process.exit(0);
|
|
281
|
+
}
|
|
324
282
|
|
|
325
|
-
|
|
326
|
-
// Ships with node — zero dependency. A full syntax parse that never runs the
|
|
327
|
-
// code. TypeScript files are out of its reach (they belong to the tsc path).
|
|
283
|
+
function runJavaScript(cwd: string, files: string[]): void {
|
|
328
284
|
const jsFiles = files.filter((f) => JS_EXTENSIONS.some((ext) => f.endsWith(ext)));
|
|
329
285
|
if (jsFiles.length === 0) {
|
|
330
|
-
console.log(
|
|
331
|
-
" lint: no JavaScript files to check — add tsconfig.json + typescript to type-check .ts files.",
|
|
332
|
-
);
|
|
286
|
+
console.log(" lint: no JavaScript files to check — add tsconfig.json + typescript to type-check .ts files.");
|
|
333
287
|
process.exit(0);
|
|
334
288
|
}
|
|
335
|
-
|
|
336
289
|
let syntaxErrors = 0;
|
|
337
290
|
for (const file of jsFiles) {
|
|
338
291
|
const result = spawnSync(process.execPath, ["--check", file], { cwd, encoding: "utf-8" });
|
|
339
|
-
if ((result.status ?? 1)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
.split("\n")
|
|
345
|
-
.reverse()
|
|
346
|
-
.find((line) => line.includes("Error:")) || "syntax error";
|
|
347
|
-
console.log(` ✗ ${relative(cwd, file)}: ${detail.trim()}`);
|
|
348
|
-
syntaxErrors++;
|
|
349
|
-
}
|
|
292
|
+
if ((result.status ?? 1) === 0) continue;
|
|
293
|
+
const stderr = (result.stderr || "").trim();
|
|
294
|
+
const detail = stderr.split("\n").reverse().find((line) => line.includes("Error:")) || "syntax error";
|
|
295
|
+
console.log(` ✗ ${relative(cwd, file)}: ${detail.trim()}`);
|
|
296
|
+
syntaxErrors++;
|
|
350
297
|
}
|
|
351
|
-
|
|
352
298
|
if (syntaxErrors > 0) {
|
|
353
|
-
console.log(
|
|
354
|
-
` ✗ lint failed — ${syntaxErrors} syntax error(s) in ${jsFiles.length} file(s) [node --check]`,
|
|
355
|
-
);
|
|
299
|
+
console.log(` ✗ lint failed — ${syntaxErrors} syntax error(s) in ${jsFiles.length} file(s) [node --check]`);
|
|
356
300
|
process.exit(1);
|
|
357
301
|
}
|
|
358
302
|
console.log(` ✓ lint clean — ${jsFiles.length} file(s) [node --check]`);
|
|
359
303
|
process.exit(0);
|
|
360
304
|
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Lint the project's source. Exits 0 = clean, 1 = findings (parity with the
|
|
308
|
+
* Python master and with `tina4nodejs test`'s exit-code contract).
|
|
309
|
+
*/
|
|
310
|
+
export function runLint(args: string[]): void {
|
|
311
|
+
const fix = args.includes("--fix");
|
|
312
|
+
const noInstall = args.includes("--no-install");
|
|
313
|
+
const cwd = process.cwd();
|
|
314
|
+
const files = collectAppFiles(cwd);
|
|
315
|
+
if (files.length === 0) {
|
|
316
|
+
console.log(" lint: nothing to lint (no src/ or app.ts).");
|
|
317
|
+
process.exit(0);
|
|
318
|
+
}
|
|
319
|
+
const eslint = bootstrapEslint(cwd, noInstall);
|
|
320
|
+
if (eslint.bin && eslint.config) return runEslint(cwd, files, fix, eslint.bin);
|
|
321
|
+
if (fix) {
|
|
322
|
+
console.log(" · --fix needs eslint — the baseline check has no autofix.");
|
|
323
|
+
}
|
|
324
|
+
if (existsSync(join(cwd, "tsconfig.json")) && runTypescript(cwd, files)) return;
|
|
325
|
+
runJavaScript(cwd, files);
|
|
326
|
+
}
|