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.
Files changed (50) hide show
  1. package/CLAUDE.md +3 -3
  2. package/README.md +2 -2
  3. package/package.json +1 -1
  4. package/packages/cli/dist/bin.js +3181 -3051
  5. package/packages/cli/src/commands/generate.ts +33 -22
  6. package/packages/cli/src/commands/lint.ts +77 -111
  7. package/packages/core/dist/index.js +3090 -2952
  8. package/packages/core/src/.tina4-metrics.json +15004 -0
  9. package/packages/core/src/aiClient.ts +199 -161
  10. package/packages/core/src/dispatchPipeline.ts +65 -67
  11. package/packages/core/src/docs.ts +52 -544
  12. package/packages/core/src/docsParser.ts +270 -0
  13. package/packages/core/src/docsScanner.ts +121 -0
  14. package/packages/core/src/docsSignatures.ts +165 -0
  15. package/packages/core/src/index.ts +2 -0
  16. package/packages/core/src/logger.ts +68 -82
  17. package/packages/core/src/mcp.ts +32 -60
  18. package/packages/core/src/messenger.ts +136 -157
  19. package/packages/core/src/middleware.ts +56 -60
  20. package/packages/core/src/plan.ts +78 -70
  21. package/packages/core/src/projectIndex.ts +15 -288
  22. package/packages/core/src/projectIndexExtractors.ts +126 -0
  23. package/packages/core/src/projectIndexStorage.ts +122 -0
  24. package/packages/core/src/push.ts +281 -0
  25. package/packages/core/src/server.ts +182 -183
  26. package/packages/frond/dist/index.js +607 -770
  27. package/packages/frond/src/engine.ts +670 -818
  28. package/packages/orm/dist/index.js +3100 -2965
  29. package/packages/orm/src/adapters/mongodb.ts +99 -144
  30. package/packages/orm/src/baseModel.ts +429 -515
  31. package/packages/orm/src/fakeData.ts +73 -61
  32. package/packages/orm/src/migration.ts +96 -126
  33. package/packages/orm/src/seeder.ts +6 -238
  34. package/packages/orm/src/seederTable.ts +101 -0
  35. package/packages/orm/src/seederTypes.ts +14 -0
  36. package/packages/orm/src/validation.ts +97 -80
  37. package/types/core/src/aiClient.d.ts +5 -0
  38. package/types/core/src/docsParser.d.ts +28 -0
  39. package/types/core/src/docsScanner.d.ts +1 -0
  40. package/types/core/src/docsSignatures.d.ts +11 -0
  41. package/types/core/src/index.d.ts +2 -0
  42. package/types/core/src/messenger.d.ts +8 -0
  43. package/types/core/src/projectIndexExtractors.d.ts +3 -0
  44. package/types/core/src/projectIndexStorage.d.ts +13 -0
  45. package/types/core/src/push.d.ts +45 -0
  46. package/types/frond/src/engine.d.ts +25 -0
  47. package/types/orm/src/fakeData.d.ts +3 -0
  48. package/types/orm/src/seeder.d.ts +3 -89
  49. package/types/orm/src/seederTable.d.ts +9 -0
  50. 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 && reserved.from) {
454
- lines.push("");
455
- lines.push(` To set the table name yourself:`);
456
- lines.push(` tina4nodejs generate ${__resolution.target} ${__resolution.input.name} --table-name <name>`);
457
- lines.push(` Tina4 interpolates table names unquoted; if you force the reserved '${reserved.from}', you own the quoting in raw SQL.`);
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
- * Lint the project's source. Exits 0 = clean, 1 = findings (parity with the
228
- * Python master and with `tina4nodejs test`'s exit-code contract).
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
- const files = collectAppFiles(cwd);
236
- if (files.length === 0) {
237
- console.log(" lint: nothing to lint (no src/ or app.ts).");
238
- process.exit(0);
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
- let eslintBin = resolveEslintBin(cwd);
242
- let eslintConfig = findEslintConfig(cwd);
243
-
244
- // ── Silent on-demand bootstrap ──────────────────────────────────────
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
- // ── eslint: the project's own linter (installed dev-only on demand) ──
289
- // Only when a config exists AND eslint resolves from the project. eslint reports
290
- // syntax errors too, so when present it is the entire pass.
291
- if (eslintBin && eslintConfig) {
292
- const label = fix ? "eslint --fix" : "eslint";
293
- const eslintArgs = [eslintBin, ...files, ...(fix ? ["--fix"] : [])];
294
- const code = spawnSync(process.execPath, eslintArgs, { cwd, stdio: "inherit" }).status ?? 1;
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
- // From here on nothing can autofix say so once when --fix was asked for.
304
- if (fix) {
305
- console.log(" · --fix needs eslint the baseline check has no autofix.");
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
- // ── Baseline (TypeScript): the project's own `tsc --noEmit` ──────────
309
- // Every tina4-nodejs project ships tsconfig.json + typescript. `--noEmit` forces
310
- // a type+syntax check that writes NOTHING (the project's tsconfig may set outDir,
311
- // so this must never emit). tsc reports syntax errors too. Run from cwd so tsc
312
- // reads THIS tsconfig.json; diagnostics stream straight through.
313
- const hasTsconfig = existsSync(join(cwd, "tsconfig.json"));
314
- const tscBin = hasTsconfig ? resolvePackageBin(cwd, "typescript", "bin/tsc") : null;
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
- // ── Baseline (plain JS): stdlib `node --check` over .js/.mjs/.cjs ────
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) !== 0) {
340
- const stderr = (result.stderr || "").trim();
341
- // node --check ends its stderr with a `SyntaxError: ...` line — surface it.
342
- const detail =
343
- stderr
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
+ }