@constructive-io/graphql-codegen 4.7.3 → 4.8.1

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 (37) hide show
  1. package/core/codegen/cli/command-map-generator.js +36 -11
  2. package/core/codegen/cli/custom-command-generator.js +59 -13
  3. package/core/codegen/cli/docs-generator.d.ts +1 -1
  4. package/core/codegen/cli/docs-generator.js +137 -54
  5. package/core/codegen/cli/executor-generator.js +20 -6
  6. package/core/codegen/cli/infra-generator.js +17 -14
  7. package/core/codegen/cli/table-command-generator.js +209 -53
  8. package/core/codegen/docs-utils.d.ts +12 -1
  9. package/core/codegen/docs-utils.js +49 -1
  10. package/core/codegen/hooks-docs-generator.d.ts +1 -1
  11. package/core/codegen/hooks-docs-generator.js +47 -7
  12. package/core/codegen/orm/docs-generator.d.ts +1 -1
  13. package/core/codegen/orm/docs-generator.js +57 -20
  14. package/core/generate.d.ts +5 -0
  15. package/core/generate.js +48 -12
  16. package/core/workspace.d.ts +13 -0
  17. package/core/workspace.js +92 -0
  18. package/esm/core/codegen/cli/command-map-generator.js +36 -11
  19. package/esm/core/codegen/cli/custom-command-generator.js +60 -14
  20. package/esm/core/codegen/cli/docs-generator.d.ts +1 -1
  21. package/esm/core/codegen/cli/docs-generator.js +138 -55
  22. package/esm/core/codegen/cli/executor-generator.js +20 -6
  23. package/esm/core/codegen/cli/infra-generator.js +17 -14
  24. package/esm/core/codegen/cli/table-command-generator.js +210 -54
  25. package/esm/core/codegen/docs-utils.d.ts +12 -1
  26. package/esm/core/codegen/docs-utils.js +48 -1
  27. package/esm/core/codegen/hooks-docs-generator.d.ts +1 -1
  28. package/esm/core/codegen/hooks-docs-generator.js +48 -8
  29. package/esm/core/codegen/orm/docs-generator.d.ts +1 -1
  30. package/esm/core/codegen/orm/docs-generator.js +58 -21
  31. package/esm/core/generate.d.ts +5 -0
  32. package/esm/core/generate.js +48 -12
  33. package/esm/core/workspace.d.ts +13 -0
  34. package/esm/core/workspace.js +89 -0
  35. package/esm/types/config.d.ts +12 -4
  36. package/package.json +22 -22
  37. package/types/config.d.ts +12 -4
@@ -4,6 +4,7 @@ exports.generateHooksReadme = generateHooksReadme;
4
4
  exports.generateHooksAgentsDocs = generateHooksAgentsDocs;
5
5
  exports.getHooksMcpTools = getHooksMcpTools;
6
6
  exports.generateHooksSkills = generateHooksSkills;
7
+ const komoji_1 = require("komoji");
7
8
  const docs_utils_1 = require("./docs-utils");
8
9
  const utils_1 = require("./utils");
9
10
  function getCustomHookName(op) {
@@ -377,8 +378,11 @@ function getHooksMcpTools(tables, customOperations) {
377
378
  }
378
379
  return tools;
379
380
  }
380
- function generateHooksSkills(tables, customOperations) {
381
+ function generateHooksSkills(tables, customOperations, targetName) {
381
382
  const files = [];
383
+ const skillName = `hooks-${targetName}`;
384
+ const referenceNames = [];
385
+ // Generate reference files for each table
382
386
  for (const table of tables) {
383
387
  const { singularName, pluralName } = (0, utils_1.getTableNames)(table);
384
388
  const pk = (0, utils_1.getPrimaryKeyInfo)(table)[0];
@@ -386,10 +390,12 @@ function generateHooksSkills(tables, customOperations) {
386
390
  const selectFields = scalarFields
387
391
  .map((f) => `${f.name}: true`)
388
392
  .join(', ');
393
+ const refName = (0, komoji_1.toKebabCase)(singularName);
394
+ referenceNames.push(refName);
389
395
  files.push({
390
- fileName: `skills/${(0, utils_1.lcFirst)(singularName)}.md`,
391
- content: (0, docs_utils_1.buildSkillFile)({
392
- name: `hooks-${(0, utils_1.lcFirst)(singularName)}`,
396
+ fileName: `${skillName}/references/${refName}.md`,
397
+ content: (0, docs_utils_1.buildSkillReference)({
398
+ title: singularName,
393
399
  description: table.description || `React Query hooks for ${table.name} data operations`,
394
400
  language: 'typescript',
395
401
  usage: [
@@ -429,15 +435,18 @@ function generateHooksSkills(tables, customOperations) {
429
435
  }),
430
436
  });
431
437
  }
438
+ // Generate reference files for custom operations
432
439
  for (const op of customOperations) {
433
440
  const hookName = getCustomHookName(op);
434
441
  const callArgs = op.args.length > 0
435
442
  ? `{ ${op.args.map((a) => `${a.name}: '<value>'`).join(', ')} }`
436
443
  : '';
444
+ const refName = (0, komoji_1.toKebabCase)(op.name);
445
+ referenceNames.push(refName);
437
446
  files.push({
438
- fileName: `skills/${op.name}.md`,
439
- content: (0, docs_utils_1.buildSkillFile)({
440
- name: `hooks-${op.name}`,
447
+ fileName: `${skillName}/references/${refName}.md`,
448
+ content: (0, docs_utils_1.buildSkillReference)({
449
+ title: op.name,
441
450
  description: op.description ||
442
451
  `React Query ${op.kind} hook for ${op.name}`,
443
452
  language: 'typescript',
@@ -464,5 +473,36 @@ function generateHooksSkills(tables, customOperations) {
464
473
  }),
465
474
  });
466
475
  }
476
+ // Generate the overview SKILL.md
477
+ const hookExamples = tables.slice(0, 3).map((t) => (0, utils_1.getListQueryHookName)(t));
478
+ files.push({
479
+ fileName: `${skillName}/SKILL.md`,
480
+ content: (0, docs_utils_1.buildSkillFile)({
481
+ name: skillName,
482
+ description: `React Query hooks for the ${targetName} API — provides typed query and mutation hooks for ${tables.length} tables and ${customOperations.length} custom operations`,
483
+ language: 'typescript',
484
+ usage: [
485
+ `// Import hooks`,
486
+ `import { ${hookExamples[0] || 'useModelQuery'} } from './hooks';`,
487
+ '',
488
+ `// Query hooks: use<Model>Query, use<Model>sQuery`,
489
+ `// Mutation hooks: useCreate<Model>Mutation, useUpdate<Model>Mutation, useDelete<Model>Mutation`,
490
+ '',
491
+ `const { data, isLoading } = ${hookExamples[0] || 'useModelQuery'}({`,
492
+ ` selection: { fields: { id: true } },`,
493
+ `});`,
494
+ ],
495
+ examples: [
496
+ {
497
+ description: 'Query records',
498
+ code: [
499
+ `const { data, isLoading } = ${hookExamples[0] || 'useModelQuery'}({`,
500
+ ' selection: { fields: { id: true } },',
501
+ '});',
502
+ ],
503
+ },
504
+ ],
505
+ }, referenceNames),
506
+ });
467
507
  return files;
468
508
  }
@@ -3,4 +3,4 @@ import type { GeneratedDocFile, McpTool } from '../docs-utils';
3
3
  export declare function generateOrmReadme(tables: CleanTable[], customOperations: CleanOperation[]): GeneratedDocFile;
4
4
  export declare function generateOrmAgentsDocs(tables: CleanTable[], customOperations: CleanOperation[]): GeneratedDocFile;
5
5
  export declare function getOrmMcpTools(tables: CleanTable[], customOperations: CleanOperation[]): McpTool[];
6
- export declare function generateOrmSkills(tables: CleanTable[], customOperations: CleanOperation[]): GeneratedDocFile[];
6
+ export declare function generateOrmSkills(tables: CleanTable[], customOperations: CleanOperation[], targetName: string): GeneratedDocFile[];
@@ -4,6 +4,7 @@ exports.generateOrmReadme = generateOrmReadme;
4
4
  exports.generateOrmAgentsDocs = generateOrmAgentsDocs;
5
5
  exports.getOrmMcpTools = getOrmMcpTools;
6
6
  exports.generateOrmSkills = generateOrmSkills;
7
+ const komoji_1 = require("komoji");
7
8
  const docs_utils_1 = require("../docs-utils");
8
9
  const utils_1 = require("../utils");
9
10
  function generateOrmReadme(tables, customOperations) {
@@ -346,30 +347,36 @@ function getOrmMcpTools(tables, customOperations) {
346
347
  }
347
348
  return tools;
348
349
  }
349
- function generateOrmSkills(tables, customOperations) {
350
+ function generateOrmSkills(tables, customOperations, targetName) {
350
351
  const files = [];
352
+ const skillName = `orm-${targetName}`;
353
+ const referenceNames = [];
354
+ // Generate reference files for each table
351
355
  for (const table of tables) {
352
356
  const { singularName } = (0, utils_1.getTableNames)(table);
353
357
  const pk = (0, utils_1.getPrimaryKeyInfo)(table)[0];
354
358
  const editableFields = (0, docs_utils_1.getEditableFields)(table);
359
+ const modelName = (0, utils_1.lcFirst)(singularName);
360
+ const refName = (0, komoji_1.toKebabCase)(singularName);
361
+ referenceNames.push(refName);
355
362
  files.push({
356
- fileName: `skills/${(0, utils_1.lcFirst)(singularName)}.md`,
357
- content: (0, docs_utils_1.buildSkillFile)({
358
- name: `orm-${(0, utils_1.lcFirst)(singularName)}`,
363
+ fileName: `${skillName}/references/${refName}.md`,
364
+ content: (0, docs_utils_1.buildSkillReference)({
365
+ title: singularName,
359
366
  description: table.description || `ORM operations for ${table.name} records`,
360
367
  language: 'typescript',
361
368
  usage: [
362
- `db.${(0, utils_1.lcFirst)(singularName)}.findMany({ select: { id: true } }).execute()`,
363
- `db.${(0, utils_1.lcFirst)(singularName)}.findOne({ ${pk.name}: '<value>', select: { id: true } }).execute()`,
364
- `db.${(0, utils_1.lcFirst)(singularName)}.create({ data: { ${editableFields.map((f) => `${f.name}: '<value>'`).join(', ')} }, select: { id: true } }).execute()`,
365
- `db.${(0, utils_1.lcFirst)(singularName)}.update({ where: { ${pk.name}: '<value>' }, data: { ${editableFields[0]?.name || 'field'}: '<new>' }, select: { id: true } }).execute()`,
366
- `db.${(0, utils_1.lcFirst)(singularName)}.delete({ where: { ${pk.name}: '<value>' } }).execute()`,
369
+ `db.${modelName}.findMany({ select: { id: true } }).execute()`,
370
+ `db.${modelName}.findOne({ ${pk.name}: '<value>', select: { id: true } }).execute()`,
371
+ `db.${modelName}.create({ data: { ${editableFields.map((f) => `${f.name}: '<value>'`).join(', ')} }, select: { id: true } }).execute()`,
372
+ `db.${modelName}.update({ where: { ${pk.name}: '<value>' }, data: { ${editableFields[0]?.name || 'field'}: '<new>' }, select: { id: true } }).execute()`,
373
+ `db.${modelName}.delete({ where: { ${pk.name}: '<value>' } }).execute()`,
367
374
  ],
368
375
  examples: [
369
376
  {
370
377
  description: `List all ${singularName} records`,
371
378
  code: [
372
- `const items = await db.${(0, utils_1.lcFirst)(singularName)}.findMany({`,
379
+ `const items = await db.${modelName}.findMany({`,
373
380
  ` select: { ${pk.name}: true, ${editableFields[0]?.name || 'name'}: true }`,
374
381
  '}).execute();',
375
382
  ],
@@ -377,7 +384,7 @@ function generateOrmSkills(tables, customOperations) {
377
384
  {
378
385
  description: `Create a ${singularName}`,
379
386
  code: [
380
- `const item = await db.${(0, utils_1.lcFirst)(singularName)}.create({`,
387
+ `const item = await db.${modelName}.create({`,
381
388
  ` data: { ${editableFields.map((f) => `${f.name}: 'value'`).join(', ')} },`,
382
389
  ` select: { ${pk.name}: true }`,
383
390
  '}).execute();',
@@ -387,30 +394,60 @@ function generateOrmSkills(tables, customOperations) {
387
394
  }),
388
395
  });
389
396
  }
397
+ // Generate reference files for custom operations
390
398
  for (const op of customOperations) {
391
399
  const accessor = op.kind === 'query' ? 'query' : 'mutation';
392
400
  const callArgs = op.args.length > 0
393
401
  ? `{ ${op.args.map((a) => `${a.name}: '<value>'`).join(', ')} }`
394
402
  : '';
403
+ const refName = (0, komoji_1.toKebabCase)(op.name);
404
+ referenceNames.push(refName);
395
405
  files.push({
396
- fileName: `skills/${op.name}.md`,
397
- content: (0, docs_utils_1.buildSkillFile)({
398
- name: `orm-${op.name}`,
406
+ fileName: `${skillName}/references/${refName}.md`,
407
+ content: (0, docs_utils_1.buildSkillReference)({
408
+ title: op.name,
399
409
  description: op.description || `Execute the ${op.name} ${op.kind}`,
400
410
  language: 'typescript',
401
- usage: [
402
- `db.${accessor}.${op.name}(${callArgs}).execute()`,
403
- ],
411
+ usage: [`db.${accessor}.${op.name}(${callArgs}).execute()`],
404
412
  examples: [
405
413
  {
406
414
  description: `Run ${op.name}`,
407
- code: [
408
- `const result = await db.${accessor}.${op.name}(${callArgs}).execute();`,
409
- ],
415
+ code: [`const result = await db.${accessor}.${op.name}(${callArgs}).execute();`],
410
416
  },
411
417
  ],
412
418
  }),
413
419
  });
414
420
  }
421
+ // Generate the overview SKILL.md
422
+ const tableNames = tables.map((t) => (0, utils_1.lcFirst)((0, utils_1.getTableNames)(t).singularName));
423
+ files.push({
424
+ fileName: `${skillName}/SKILL.md`,
425
+ content: (0, docs_utils_1.buildSkillFile)({
426
+ name: skillName,
427
+ description: `ORM client for the ${targetName} API — provides typed CRUD operations for ${tables.length} tables and ${customOperations.length} custom operations`,
428
+ language: 'typescript',
429
+ usage: [
430
+ `// Import the ORM client`,
431
+ `import { db } from './orm';`,
432
+ '',
433
+ `// Available models: ${tableNames.slice(0, 8).join(', ')}${tableNames.length > 8 ? ', ...' : ''}`,
434
+ `db.<model>.findMany({ select: { id: true } }).execute()`,
435
+ `db.<model>.findOne({ id: '<value>', select: { id: true } }).execute()`,
436
+ `db.<model>.create({ data: { ... }, select: { id: true } }).execute()`,
437
+ `db.<model>.update({ where: { id: '<value>' }, data: { ... }, select: { id: true } }).execute()`,
438
+ `db.<model>.delete({ where: { id: '<value>' } }).execute()`,
439
+ ],
440
+ examples: [
441
+ {
442
+ description: 'Query records',
443
+ code: [
444
+ `const items = await db.${tableNames[0] || 'model'}.findMany({`,
445
+ ' select: { id: true }',
446
+ '}).execute();',
447
+ ],
448
+ },
449
+ ],
450
+ }, referenceNames),
451
+ });
415
452
  return files;
416
453
  }
@@ -33,6 +33,11 @@ export interface GenerateResult {
33
33
  */
34
34
  export interface GenerateInternalOptions {
35
35
  skipCli?: boolean;
36
+ /**
37
+ * Internal-only name for the target when generating skills.
38
+ * Used by generateMulti() so skill names are stable and composable.
39
+ */
40
+ targetName?: string;
36
41
  }
37
42
  export declare function generate(options?: GenerateOptions, internalOptions?: GenerateInternalOptions): Promise<GenerateResult>;
38
43
  export declare function expandApiNamesToMultiTarget(config: GraphQLSDKConfigTarget): Record<string, GraphQLSDKConfigTarget> | null;
package/core/generate.js CHANGED
@@ -66,6 +66,18 @@ const target_docs_generator_1 = require("./codegen/target-docs-generator");
66
66
  const introspect_1 = require("./introspect");
67
67
  const output_1 = require("./output");
68
68
  const pipeline_1 = require("./pipeline");
69
+ const workspace_1 = require("./workspace");
70
+ function resolveSkillsOutputDir(config, outputRoot) {
71
+ const workspaceRoot = (0, workspace_1.findWorkspaceRoot)(node_path_1.default.resolve(outputRoot)) ??
72
+ (0, workspace_1.findWorkspaceRoot)(process.cwd()) ??
73
+ process.cwd();
74
+ if (config.skillsPath) {
75
+ return node_path_1.default.isAbsolute(config.skillsPath)
76
+ ? config.skillsPath
77
+ : node_path_1.default.resolve(workspaceRoot, config.skillsPath);
78
+ }
79
+ return node_path_1.default.resolve(workspaceRoot, 'skills');
80
+ }
69
81
  async function generate(options = {}, internalOptions) {
70
82
  // Apply defaults to get resolved config
71
83
  const config = (0, config_1.getConfigOptions)(options);
@@ -261,6 +273,8 @@ async function generate(options = {}, internalOptions) {
261
273
  ...(customOperations.mutations ?? []),
262
274
  ];
263
275
  const allMcpTools = [];
276
+ const targetName = internalOptions?.targetName ?? 'default';
277
+ const skillsToWrite = [];
264
278
  if (runOrm) {
265
279
  if (docsConfig.readme) {
266
280
  const readme = (0, docs_generator_2.generateOrmReadme)(tables, allCustomOps);
@@ -274,8 +288,8 @@ async function generate(options = {}, internalOptions) {
274
288
  allMcpTools.push(...(0, docs_generator_2.getOrmMcpTools)(tables, allCustomOps));
275
289
  }
276
290
  if (docsConfig.skills) {
277
- for (const skill of (0, docs_generator_2.generateOrmSkills)(tables, allCustomOps)) {
278
- filesToWrite.push({ path: node_path_1.default.posix.join('orm', skill.fileName), content: skill.content });
291
+ for (const skill of (0, docs_generator_2.generateOrmSkills)(tables, allCustomOps, targetName)) {
292
+ skillsToWrite.push({ path: skill.fileName, content: skill.content });
279
293
  }
280
294
  }
281
295
  }
@@ -292,8 +306,8 @@ async function generate(options = {}, internalOptions) {
292
306
  allMcpTools.push(...(0, hooks_docs_generator_1.getHooksMcpTools)(tables, allCustomOps));
293
307
  }
294
308
  if (docsConfig.skills) {
295
- for (const skill of (0, hooks_docs_generator_1.generateHooksSkills)(tables, allCustomOps)) {
296
- filesToWrite.push({ path: node_path_1.default.posix.join('hooks', skill.fileName), content: skill.content });
309
+ for (const skill of (0, hooks_docs_generator_1.generateHooksSkills)(tables, allCustomOps, targetName)) {
310
+ skillsToWrite.push({ path: skill.fileName, content: skill.content });
297
311
  }
298
312
  }
299
313
  }
@@ -313,8 +327,8 @@ async function generate(options = {}, internalOptions) {
313
327
  allMcpTools.push(...(0, docs_generator_1.getCliMcpTools)(tables, allCustomOps, toolName));
314
328
  }
315
329
  if (docsConfig.skills) {
316
- for (const skill of (0, docs_generator_1.generateSkills)(tables, allCustomOps, toolName)) {
317
- filesToWrite.push({ path: node_path_1.default.posix.join('cli', skill.fileName), content: skill.content });
330
+ for (const skill of (0, docs_generator_1.generateSkills)(tables, allCustomOps, toolName, targetName)) {
331
+ skillsToWrite.push({ path: skill.fileName, content: skill.content });
318
332
  }
319
333
  }
320
334
  }
@@ -352,6 +366,21 @@ async function generate(options = {}, internalOptions) {
352
366
  };
353
367
  }
354
368
  allFilesWritten.push(...(writeResult.filesWritten ?? []));
369
+ if (skillsToWrite.length > 0) {
370
+ const skillsOutputDir = resolveSkillsOutputDir(config, outputRoot);
371
+ const skillsWriteResult = await (0, output_1.writeGeneratedFiles)(skillsToWrite, skillsOutputDir, [], {
372
+ pruneStaleFiles: false,
373
+ });
374
+ if (!skillsWriteResult.success) {
375
+ return {
376
+ success: false,
377
+ message: `Failed to write generated skill files: ${skillsWriteResult.errors?.join(', ')}`,
378
+ output: skillsOutputDir,
379
+ errors: skillsWriteResult.errors,
380
+ };
381
+ }
382
+ allFilesWritten.push(...(skillsWriteResult.filesWritten ?? []));
383
+ }
355
384
  }
356
385
  const generators = [
357
386
  runReactQuery && 'React Query',
@@ -530,7 +559,7 @@ async function generateMulti(options) {
530
559
  dryRun,
531
560
  schemaOnly,
532
561
  schemaOnlyFilename: schemaOnly ? `${name}.graphql` : undefined,
533
- }, useUnifiedCli ? { skipCli: true } : undefined);
562
+ }, useUnifiedCli ? { skipCli: true, targetName: name } : { targetName: name });
534
563
  results.push({ name, result });
535
564
  if (!result.success) {
536
565
  hasError = true;
@@ -613,17 +642,24 @@ async function generateMulti(options) {
613
642
  if (docsConfig.mcp) {
614
643
  allMcpTools.push(...(0, docs_generator_1.getMultiTargetCliMcpTools)(docsInput));
615
644
  }
616
- if (docsConfig.skills) {
617
- for (const skill of (0, docs_generator_1.generateMultiTargetSkills)(docsInput)) {
618
- cliFilesToWrite.push({ path: node_path_1.default.posix.join('cli', skill.fileName), content: skill.content });
619
- }
620
- }
621
645
  if (docsConfig.mcp && allMcpTools.length > 0) {
622
646
  const mcpFile = (0, target_docs_generator_1.generateCombinedMcpConfig)(allMcpTools, toolName);
623
647
  cliFilesToWrite.push({ path: node_path_1.default.posix.join('cli', mcpFile.fileName), content: mcpFile.content });
624
648
  }
625
649
  const { writeGeneratedFiles: writeFiles } = await Promise.resolve().then(() => __importStar(require('./output')));
626
650
  await writeFiles(cliFilesToWrite, '.', [], { pruneStaleFiles: false });
651
+ if (docsConfig.skills) {
652
+ const cliSkillsToWrite = (0, docs_generator_1.generateMultiTargetSkills)(docsInput).map((skill) => ({
653
+ path: skill.fileName,
654
+ content: skill.content,
655
+ }));
656
+ const firstTargetResolved = (0, config_1.getConfigOptions)({
657
+ ...(firstTargetConfig ?? {}),
658
+ ...(cliOverrides ?? {}),
659
+ });
660
+ const skillsOutputDir = resolveSkillsOutputDir(firstTargetResolved, firstTargetResolved.output);
661
+ await writeFiles(cliSkillsToWrite, skillsOutputDir, [], { pruneStaleFiles: false });
662
+ }
627
663
  }
628
664
  // Generate root-root README if multi-target
629
665
  if (names.length > 1 && targetInfos.length > 0 && !dryRun) {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Find the workspace root directory.
3
+ *
4
+ * Search order:
5
+ * 1. pnpm-workspace.yaml (pnpm workspaces)
6
+ * 2. lerna.json (lerna workspaces)
7
+ * 3. package.json with "workspaces" field (npm/yarn workspaces)
8
+ * 4. Nearest package.json (fallback for non-workspace projects)
9
+ *
10
+ * @param cwd - Starting directory to search from (defaults to process.cwd())
11
+ * @returns The workspace root path, or null if nothing found
12
+ */
13
+ export declare function findWorkspaceRoot(cwd?: string): string | null;
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findWorkspaceRoot = findWorkspaceRoot;
4
+ /**
5
+ * Workspace detection utilities
6
+ *
7
+ * Finds the root of a workspace by walking up directories looking for
8
+ * workspace markers (pnpm-workspace.yaml, lerna.json, package.json with workspaces).
9
+ * Falls back to the nearest package.json directory.
10
+ *
11
+ * Inspired by @pgpmjs/env walkUp / resolvePnpmWorkspace patterns.
12
+ */
13
+ const node_fs_1 = require("node:fs");
14
+ const node_path_1 = require("node:path");
15
+ /**
16
+ * Walk up directories from startDir looking for a file.
17
+ * Returns the directory containing the file, or null if not found.
18
+ */
19
+ function walkUp(startDir, filename) {
20
+ let currentDir = (0, node_path_1.resolve)(startDir);
21
+ while (currentDir) {
22
+ const targetPath = (0, node_path_1.resolve)(currentDir, filename);
23
+ if ((0, node_fs_1.existsSync)(targetPath)) {
24
+ return currentDir;
25
+ }
26
+ const parentDir = (0, node_path_1.dirname)(currentDir);
27
+ if (parentDir === currentDir) {
28
+ break;
29
+ }
30
+ currentDir = parentDir;
31
+ }
32
+ return null;
33
+ }
34
+ /**
35
+ * Find the first pnpm workspace root by looking for pnpm-workspace.yaml
36
+ */
37
+ function findPnpmWorkspace(cwd) {
38
+ return walkUp(cwd, 'pnpm-workspace.yaml');
39
+ }
40
+ /**
41
+ * Find the first lerna workspace root by looking for lerna.json
42
+ */
43
+ function findLernaWorkspace(cwd) {
44
+ return walkUp(cwd, 'lerna.json');
45
+ }
46
+ /**
47
+ * Find the first npm/yarn workspace root by looking for package.json with workspaces field
48
+ */
49
+ function findNpmWorkspace(cwd) {
50
+ let currentDir = (0, node_path_1.resolve)(cwd);
51
+ const root = (0, node_path_1.parse)(currentDir).root;
52
+ while (currentDir !== root) {
53
+ const packageJsonPath = (0, node_path_1.resolve)(currentDir, 'package.json');
54
+ if ((0, node_fs_1.existsSync)(packageJsonPath)) {
55
+ try {
56
+ const packageJson = JSON.parse((0, node_fs_1.readFileSync)(packageJsonPath, 'utf8'));
57
+ if (packageJson.workspaces) {
58
+ return currentDir;
59
+ }
60
+ }
61
+ catch {
62
+ // Ignore JSON parse errors
63
+ }
64
+ }
65
+ currentDir = (0, node_path_1.dirname)(currentDir);
66
+ }
67
+ return null;
68
+ }
69
+ /**
70
+ * Find the nearest package.json directory (fallback)
71
+ */
72
+ function findPackageRoot(cwd) {
73
+ return walkUp(cwd, 'package.json');
74
+ }
75
+ /**
76
+ * Find the workspace root directory.
77
+ *
78
+ * Search order:
79
+ * 1. pnpm-workspace.yaml (pnpm workspaces)
80
+ * 2. lerna.json (lerna workspaces)
81
+ * 3. package.json with "workspaces" field (npm/yarn workspaces)
82
+ * 4. Nearest package.json (fallback for non-workspace projects)
83
+ *
84
+ * @param cwd - Starting directory to search from (defaults to process.cwd())
85
+ * @returns The workspace root path, or null if nothing found
86
+ */
87
+ function findWorkspaceRoot(cwd = process.cwd()) {
88
+ return (findPnpmWorkspace(cwd) ??
89
+ findLernaWorkspace(cwd) ??
90
+ findNpmWorkspace(cwd) ??
91
+ findPackageRoot(cwd));
92
+ }
@@ -11,6 +11,25 @@ function createNamedImportDeclaration(moduleSpecifier, namedImports, typeOnly =
11
11
  decl.importKind = typeOnly ? 'type' : 'value';
12
12
  return decl;
13
13
  }
14
+ /**
15
+ * Build the command handler function type:
16
+ * (argv: Partial<Record<string, unknown>>, prompter: Inquirerer, options: CLIOptions) => Promise<void>
17
+ * This matches the actual exported handler signatures from table/custom command files.
18
+ */
19
+ function buildCommandHandlerType() {
20
+ const argvParam = t.identifier('argv');
21
+ argvParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Partial'), t.tsTypeParameterInstantiation([
22
+ t.tsTypeReference(t.identifier('Record'), t.tsTypeParameterInstantiation([
23
+ t.tsStringKeyword(),
24
+ t.tsUnknownKeyword(),
25
+ ])),
26
+ ])));
27
+ const prompterParam = t.identifier('prompter');
28
+ prompterParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Inquirerer')));
29
+ const optionsParam = t.identifier('options');
30
+ optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('CLIOptions')));
31
+ return t.tsFunctionType(null, [argvParam, prompterParam, optionsParam], t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Promise'), t.tsTypeParameterInstantiation([t.tsVoidKeyword()]))));
32
+ }
14
33
  export function generateCommandMap(tables, customOperations, toolName) {
15
34
  const statements = [];
16
35
  statements.push(createNamedImportDeclaration('inquirerer', [
@@ -37,18 +56,17 @@ export function generateCommandMap(tables, customOperations, toolName) {
37
56
  statements.push(createImportDeclaration(`./commands/${kebab}`, importName));
38
57
  }
39
58
  const mapProperties = commandEntries.map((entry) => t.objectProperty(t.stringLiteral(entry.kebab), t.identifier(entry.importName)));
40
- const createCommandMapFunc = t.variableDeclaration('const', [
41
- t.variableDeclarator(t.identifier('createCommandMap'), t.arrowFunctionExpression([], t.objectExpression(mapProperties))),
42
- ]);
43
- const createCommandMapAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Record'), t.tsTypeParameterInstantiation([
44
- t.tsStringKeyword(),
45
- t.tsFunctionType(null, [], t.tsTypeAnnotation(t.tsAnyKeyword())),
46
- ])));
59
+ // Build command handler type matching actual handler signature:
60
+ // (argv: Partial<Record<string, unknown>>, prompter: Inquirerer, options: CLIOptions) => Promise<void>
61
+ const commandHandlerType = buildCommandHandlerType();
47
62
  const createCommandMapId = t.identifier('createCommandMap');
48
63
  createCommandMapId.typeAnnotation = t.tsTypeAnnotation(t.tsParenthesizedType(t.tsFunctionType(null, [], t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Record'), t.tsTypeParameterInstantiation([
49
64
  t.tsStringKeyword(),
50
- t.tsFunctionType(null, [], t.tsTypeAnnotation(t.tsUnknownKeyword())),
65
+ commandHandlerType,
51
66
  ]))))));
67
+ const createCommandMapFunc = t.variableDeclaration('const', [
68
+ t.variableDeclarator(createCommandMapId, t.arrowFunctionExpression([], t.objectExpression(mapProperties))),
69
+ ]);
52
70
  statements.push(createCommandMapFunc);
53
71
  const usageLines = [
54
72
  '',
@@ -115,7 +133,7 @@ export function generateCommandMap(tables, customOperations, toolName) {
115
133
  ]),
116
134
  ]))),
117
135
  ]),
118
- t.expressionStatement(t.assignmentExpression('=', t.identifier('command'), t.memberExpression(t.identifier('answer'), t.identifier('command')))),
136
+ t.expressionStatement(t.assignmentExpression('=', t.identifier('command'), t.tsAsExpression(t.memberExpression(t.identifier('answer'), t.identifier('command')), t.tsStringKeyword()))),
119
137
  ])),
120
138
  t.variableDeclaration('const', [
121
139
  t.variableDeclarator(t.identifier('commandFn'), t.memberExpression(t.identifier('commandMap'), t.identifier('command'), true)),
@@ -186,8 +204,15 @@ export function generateMultiTargetCommandMap(input) {
186
204
  }
187
205
  }
188
206
  const mapProperties = commandEntries.map((entry) => t.objectProperty(t.stringLiteral(entry.kebab), t.identifier(entry.importName)));
207
+ // Build command handler type matching actual handler signature
208
+ const multiTargetCommandHandlerType = buildCommandHandlerType();
209
+ const multiTargetCreateCommandMapId = t.identifier('createCommandMap');
210
+ multiTargetCreateCommandMapId.typeAnnotation = t.tsTypeAnnotation(t.tsParenthesizedType(t.tsFunctionType(null, [], t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Record'), t.tsTypeParameterInstantiation([
211
+ t.tsStringKeyword(),
212
+ multiTargetCommandHandlerType,
213
+ ]))))));
189
214
  const createCommandMapFunc = t.variableDeclaration('const', [
190
- t.variableDeclarator(t.identifier('createCommandMap'), t.arrowFunctionExpression([], t.objectExpression(mapProperties))),
215
+ t.variableDeclarator(multiTargetCreateCommandMapId, t.arrowFunctionExpression([], t.objectExpression(mapProperties))),
191
216
  ]);
192
217
  statements.push(createCommandMapFunc);
193
218
  const usageLines = [
@@ -261,7 +286,7 @@ export function generateMultiTargetCommandMap(input) {
261
286
  ]),
262
287
  ]))),
263
288
  ]),
264
- t.expressionStatement(t.assignmentExpression('=', t.identifier('command'), t.memberExpression(t.identifier('answer'), t.identifier('command')))),
289
+ t.expressionStatement(t.assignmentExpression('=', t.identifier('command'), t.tsAsExpression(t.memberExpression(t.identifier('answer'), t.identifier('command')), t.tsStringKeyword()))),
265
290
  ])),
266
291
  t.variableDeclaration('const', [
267
292
  t.variableDeclarator(t.identifier('commandFn'), t.memberExpression(t.identifier('commandMap'), t.identifier('command'), true)),