@constructive-io/graphql-codegen 4.0.1 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/cli/handler.d.ts +13 -0
  2. package/cli/handler.js +74 -0
  3. package/cli/index.js +11 -60
  4. package/cli/shared.d.ts +1 -1
  5. package/cli/shared.js +2 -5
  6. package/core/codegen/barrel.d.ts +1 -0
  7. package/core/codegen/barrel.js +5 -2
  8. package/core/codegen/cli/arg-mapper.d.ts +4 -0
  9. package/core/codegen/cli/arg-mapper.js +117 -0
  10. package/core/codegen/cli/command-map-generator.d.ts +16 -0
  11. package/core/codegen/cli/command-map-generator.js +338 -0
  12. package/core/codegen/cli/custom-command-generator.d.ts +8 -0
  13. package/core/codegen/cli/custom-command-generator.js +155 -0
  14. package/core/codegen/cli/docs-generator.d.ts +26 -0
  15. package/core/codegen/cli/docs-generator.js +1399 -0
  16. package/core/codegen/cli/executor-generator.d.ts +11 -0
  17. package/core/codegen/cli/executor-generator.js +217 -0
  18. package/core/codegen/cli/index.d.ts +53 -0
  19. package/core/codegen/cli/index.js +153 -0
  20. package/core/codegen/cli/infra-generator.d.ts +9 -0
  21. package/core/codegen/cli/infra-generator.js +1195 -0
  22. package/core/codegen/cli/table-command-generator.d.ts +7 -0
  23. package/core/codegen/cli/table-command-generator.js +323 -0
  24. package/core/codegen/docs-utils.d.ts +30 -0
  25. package/core/codegen/docs-utils.js +122 -0
  26. package/core/codegen/hooks-docs-generator.d.ts +6 -0
  27. package/core/codegen/hooks-docs-generator.js +468 -0
  28. package/core/codegen/orm/docs-generator.d.ts +6 -0
  29. package/core/codegen/orm/docs-generator.js +416 -0
  30. package/core/codegen/target-docs-generator.d.ts +20 -0
  31. package/core/codegen/target-docs-generator.js +110 -0
  32. package/core/database/index.d.ts +0 -12
  33. package/core/database/index.js +2 -19
  34. package/core/generate.d.ts +34 -2
  35. package/core/generate.js +453 -12
  36. package/core/index.d.ts +0 -2
  37. package/core/index.js +0 -2
  38. package/core/introspect/source/database.js +2 -2
  39. package/core/introspect/source/pgpm-module.js +2 -2
  40. package/core/output/index.d.ts +1 -1
  41. package/core/output/index.js +1 -2
  42. package/core/output/writer.d.ts +0 -10
  43. package/core/output/writer.js +0 -31
  44. package/esm/cli/handler.d.ts +13 -0
  45. package/esm/cli/handler.js +71 -0
  46. package/esm/cli/index.js +11 -60
  47. package/esm/cli/shared.d.ts +1 -1
  48. package/esm/cli/shared.js +2 -5
  49. package/esm/core/codegen/barrel.d.ts +1 -0
  50. package/esm/core/codegen/barrel.js +5 -2
  51. package/esm/core/codegen/cli/arg-mapper.d.ts +4 -0
  52. package/esm/core/codegen/cli/arg-mapper.js +80 -0
  53. package/esm/core/codegen/cli/command-map-generator.d.ts +16 -0
  54. package/esm/core/codegen/cli/command-map-generator.js +301 -0
  55. package/esm/core/codegen/cli/custom-command-generator.d.ts +8 -0
  56. package/esm/core/codegen/cli/custom-command-generator.js +119 -0
  57. package/esm/core/codegen/cli/docs-generator.d.ts +26 -0
  58. package/esm/core/codegen/cli/docs-generator.js +1387 -0
  59. package/esm/core/codegen/cli/executor-generator.d.ts +11 -0
  60. package/esm/core/codegen/cli/executor-generator.js +180 -0
  61. package/esm/core/codegen/cli/index.d.ts +53 -0
  62. package/esm/core/codegen/cli/index.js +128 -0
  63. package/esm/core/codegen/cli/infra-generator.d.ts +9 -0
  64. package/esm/core/codegen/cli/infra-generator.js +1156 -0
  65. package/esm/core/codegen/cli/table-command-generator.d.ts +7 -0
  66. package/esm/core/codegen/cli/table-command-generator.js +287 -0
  67. package/esm/core/codegen/docs-utils.d.ts +30 -0
  68. package/esm/core/codegen/docs-utils.js +112 -0
  69. package/esm/core/codegen/hooks-docs-generator.d.ts +6 -0
  70. package/esm/core/codegen/hooks-docs-generator.js +462 -0
  71. package/esm/core/codegen/orm/docs-generator.d.ts +6 -0
  72. package/esm/core/codegen/orm/docs-generator.js +410 -0
  73. package/esm/core/codegen/target-docs-generator.d.ts +20 -0
  74. package/esm/core/codegen/target-docs-generator.js +105 -0
  75. package/esm/core/database/index.d.ts +0 -12
  76. package/esm/core/database/index.js +1 -17
  77. package/esm/core/generate.d.ts +34 -2
  78. package/esm/core/generate.js +417 -12
  79. package/esm/core/index.d.ts +0 -2
  80. package/esm/core/index.js +0 -2
  81. package/esm/core/introspect/source/database.js +2 -2
  82. package/esm/core/introspect/source/pgpm-module.js +2 -2
  83. package/esm/core/output/index.d.ts +1 -1
  84. package/esm/core/output/index.js +1 -1
  85. package/esm/core/output/writer.d.ts +0 -10
  86. package/esm/core/output/writer.js +0 -30
  87. package/esm/generators/index.d.ts +0 -3
  88. package/esm/generators/index.js +0 -3
  89. package/esm/index.d.ts +4 -3
  90. package/esm/index.js +4 -2
  91. package/esm/types/config.d.ts +78 -0
  92. package/generators/index.d.ts +0 -3
  93. package/generators/index.js +0 -3
  94. package/index.d.ts +4 -3
  95. package/index.js +7 -2
  96. package/package.json +8 -7
  97. package/types/config.d.ts +78 -0
@@ -0,0 +1,71 @@
1
+ import { findConfigFile, loadConfigFile } from '../core/config';
2
+ import { expandApiNamesToMultiTarget, expandSchemaDirToMultiTarget, generate, generateMulti } from '../core/generate';
3
+ import { buildDbConfig, buildGenerateOptions, camelizeArgv, codegenQuestions, hasResolvedCodegenSource, normalizeCodegenListOptions, printResult, seedArgvFromConfig, } from './shared';
4
+ export async function runCodegenHandler(argv, prompter) {
5
+ const args = camelizeArgv(argv);
6
+ const schemaOnly = Boolean(args.schemaOnly);
7
+ const hasSourceFlags = Boolean(args.endpoint || args.schemaFile || args.schemaDir || args.schemas || args.apiNames);
8
+ const configPath = args.config ||
9
+ (!hasSourceFlags ? findConfigFile() : undefined);
10
+ const targetName = args.target;
11
+ let fileConfig = {};
12
+ if (configPath) {
13
+ const loaded = await loadConfigFile(configPath);
14
+ if (!loaded.success) {
15
+ console.error('x', loaded.error);
16
+ process.exit(1);
17
+ }
18
+ const config = loaded.config;
19
+ const isMulti = !('endpoint' in config ||
20
+ 'schemaFile' in config ||
21
+ 'schemaDir' in config ||
22
+ 'db' in config);
23
+ if (isMulti) {
24
+ const targets = config;
25
+ if (targetName && !targets[targetName]) {
26
+ console.error('x', `Target "${targetName}" not found. Available: ${Object.keys(targets).join(', ')}`);
27
+ process.exit(1);
28
+ }
29
+ const cliOptions = buildDbConfig(normalizeCodegenListOptions(args));
30
+ const selectedTargets = targetName
31
+ ? { [targetName]: targets[targetName] }
32
+ : targets;
33
+ const { results, hasError } = await generateMulti({
34
+ configs: selectedTargets,
35
+ cliOverrides: cliOptions,
36
+ schemaOnly,
37
+ });
38
+ for (const { name, result } of results) {
39
+ console.log(`\n[${name}]`);
40
+ printResult(result);
41
+ }
42
+ if (hasError)
43
+ process.exit(1);
44
+ return;
45
+ }
46
+ fileConfig = config;
47
+ }
48
+ const seeded = seedArgvFromConfig(args, fileConfig);
49
+ const answers = hasResolvedCodegenSource(seeded)
50
+ ? seeded
51
+ : await prompter.prompt(seeded, codegenQuestions);
52
+ const options = buildGenerateOptions(answers, fileConfig);
53
+ const expandedApi = expandApiNamesToMultiTarget(options);
54
+ const expandedDir = expandSchemaDirToMultiTarget(options);
55
+ const expanded = expandedApi || expandedDir;
56
+ if (expanded) {
57
+ const { results, hasError } = await generateMulti({
58
+ configs: expanded,
59
+ schemaOnly,
60
+ });
61
+ for (const { name, result } of results) {
62
+ console.log(`\n[${name}]`);
63
+ printResult(result);
64
+ }
65
+ if (hasError)
66
+ process.exit(1);
67
+ return;
68
+ }
69
+ const result = await generate({ ...options, schemaOnly });
70
+ printResult(result);
71
+ }
package/esm/cli/index.js CHANGED
@@ -6,9 +6,7 @@
6
6
  * All business logic is in the core modules.
7
7
  */
8
8
  import { CLI, getPackageJson } from 'inquirerer';
9
- import { findConfigFile, loadConfigFile } from '../core/config';
10
- import { generate } from '../core/generate';
11
- import { buildDbConfig, buildGenerateOptions, camelizeArgv, codegenQuestions, hasResolvedCodegenSource, normalizeCodegenListOptions, printResult, seedArgvFromConfig, } from './shared';
9
+ import { runCodegenHandler } from './handler';
12
10
  const usage = `
13
11
  graphql-codegen - GraphQL SDK generator for Constructive databases
14
12
 
@@ -19,10 +17,12 @@ Source Options (choose one):
19
17
  -c, --config <path> Path to config file
20
18
  -e, --endpoint <url> GraphQL endpoint URL
21
19
  -s, --schema-file <path> Path to GraphQL schema file
20
+ --schema-dir <dir> Directory of .graphql files (auto-expands to multi-target)
22
21
 
23
22
  Database Options:
24
23
  --schemas <list> Comma-separated PostgreSQL schemas
25
24
  --api-names <list> Comma-separated API names (mutually exclusive with --schemas)
25
+ Multiple apiNames auto-expand to multi-target (one schema per API).
26
26
 
27
27
  Generator Options:
28
28
  --react-query Generate React Query hooks
@@ -33,6 +33,11 @@ Generator Options:
33
33
  --dry-run Preview without writing files
34
34
  -v, --verbose Show detailed output
35
35
 
36
+ Schema Export:
37
+ --schema-only Export GraphQL SDL instead of running full codegen.
38
+ Works with any source (endpoint, file, database, PGPM).
39
+ With multiple apiNames, writes one .graphql per API.
40
+
36
41
  -h, --help Show this help message
37
42
  --version Show version number
38
43
  `;
@@ -46,60 +51,7 @@ export const commands = async (argv, prompter, _options) => {
46
51
  console.log(usage);
47
52
  process.exit(0);
48
53
  }
49
- const hasSourceFlags = Boolean(argv.endpoint ||
50
- argv.e ||
51
- argv['schema-file'] ||
52
- argv.s ||
53
- argv.schemas ||
54
- argv['api-names']);
55
- const explicitConfigPath = (argv.config || argv.c);
56
- const configPath = explicitConfigPath || (!hasSourceFlags ? findConfigFile() : undefined);
57
- const targetName = (argv.target || argv.t);
58
- let fileConfig = {};
59
- if (configPath) {
60
- const loaded = await loadConfigFile(configPath);
61
- if (!loaded.success) {
62
- console.error('x', loaded.error);
63
- process.exit(1);
64
- }
65
- const config = loaded.config;
66
- const isMulti = !('endpoint' in config ||
67
- 'schemaFile' in config ||
68
- 'db' in config);
69
- if (isMulti) {
70
- const targets = config;
71
- const names = targetName ? [targetName] : Object.keys(targets);
72
- if (targetName && !targets[targetName]) {
73
- console.error('x', `Target "${targetName}" not found. Available: ${Object.keys(targets).join(', ')}`);
74
- process.exit(1);
75
- }
76
- const cliOptions = buildDbConfig(normalizeCodegenListOptions(camelizeArgv(argv)));
77
- let hasError = false;
78
- for (const name of names) {
79
- console.log(`\n[${name}]`);
80
- const targetConfig = { ...targets[name], ...cliOptions };
81
- if (targets[name].db && targetConfig.db) {
82
- targetConfig.db = { ...targets[name].db, ...targetConfig.db };
83
- }
84
- const result = await generate(targetConfig);
85
- printResult(result);
86
- if (!result.success)
87
- hasError = true;
88
- }
89
- prompter.close();
90
- if (hasError)
91
- process.exit(1);
92
- return argv;
93
- }
94
- fileConfig = config;
95
- }
96
- const seeded = seedArgvFromConfig(argv, fileConfig);
97
- const answers = hasResolvedCodegenSource(seeded)
98
- ? seeded
99
- : await prompter.prompt(seeded, codegenQuestions);
100
- const options = buildGenerateOptions(answers, fileConfig);
101
- const result = await generate(options);
102
- printResult(result);
54
+ await runCodegenHandler(argv, prompter);
103
55
  prompter.close();
104
56
  return argv;
105
57
  };
@@ -115,16 +67,15 @@ export const options = {
115
67
  a: 'authorization',
116
68
  v: 'verbose',
117
69
  },
70
+ boolean: ['schema-only'],
118
71
  string: [
119
72
  'config',
120
73
  'endpoint',
121
74
  'schema-file',
75
+ 'schema-dir',
122
76
  'output',
123
77
  'target',
124
78
  'authorization',
125
- 'pgpm-module-path',
126
- 'pgpm-workspace-path',
127
- 'pgpm-module-name',
128
79
  'schemas',
129
80
  'api-names',
130
81
  ],
@@ -1,6 +1,6 @@
1
1
  import type { Question } from 'inquirerer';
2
2
  import type { GenerateResult } from '../core/generate';
3
- import type { GraphQLSDKConfigTarget } from '../types/config';
3
+ import { type GraphQLSDKConfigTarget } from '../types/config';
4
4
  export declare const splitCommas: (input: string | undefined) => string[] | undefined;
5
5
  export interface CodegenAnswers {
6
6
  endpoint?: string;
package/esm/cli/shared.js CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { camelize } from 'inflekt';
8
8
  import { inflektTree } from 'inflekt/transform-keys';
9
+ import { mergeConfig } from '../types/config';
9
10
  export const splitCommas = (input) => {
10
11
  if (!input)
11
12
  return undefined;
@@ -191,9 +192,5 @@ export function buildGenerateOptions(answers, fileConfig = {}) {
191
192
  const camelized = camelizeArgv(answers);
192
193
  const normalized = normalizeCodegenListOptions(camelized);
193
194
  const withDb = buildDbConfig(normalized);
194
- const merged = { ...fileConfig, ...withDb };
195
- if (fileConfig.db && merged.db) {
196
- merged.db = { ...fileConfig.db, ...merged.db };
197
- }
198
- return merged;
195
+ return mergeConfig(fileConfig, withDb);
199
196
  }
@@ -27,6 +27,7 @@ export interface RootBarrelOptions {
27
27
  hasTypes?: boolean;
28
28
  hasHooks?: boolean;
29
29
  hasOrm?: boolean;
30
+ hasCli?: boolean;
30
31
  }
31
32
  /**
32
33
  * Generate the root index.ts barrel file for the output directory.
@@ -135,7 +135,7 @@ export function generateMainBarrel(tables, options = {}) {
135
135
  * Re-exports from subdirectories based on which generators are enabled.
136
136
  */
137
137
  export function generateRootBarrel(options = {}) {
138
- const { hasTypes = false, hasHooks = false, hasOrm = false } = options;
138
+ const { hasTypes = false, hasHooks = false, hasOrm = false, hasCli = false } = options;
139
139
  const statements = [];
140
140
  if (hasTypes) {
141
141
  statements.push(exportAllFrom('./types'));
@@ -146,7 +146,10 @@ export function generateRootBarrel(options = {}) {
146
146
  if (hasOrm) {
147
147
  statements.push(exportAllFrom('./orm'));
148
148
  }
149
- // Add file header as leading comment on first statement
149
+ if (hasCli) {
150
+ statements.push(exportAllFrom('./cli'));
151
+ }
152
+ // Add file headeras leading comment on first statement
150
153
  if (statements.length > 0) {
151
154
  addJSDocComment(statements[0], [
152
155
  'Generated SDK - auto-generated, do not edit',
@@ -0,0 +1,4 @@
1
+ import * as t from '@babel/types';
2
+ import type { CleanArgument } from '../../../types/schema';
3
+ export declare function buildQuestionObject(arg: CleanArgument): t.ObjectExpression;
4
+ export declare function buildQuestionsArray(args: CleanArgument[]): t.ArrayExpression;
@@ -0,0 +1,80 @@
1
+ import * as t from '@babel/types';
2
+ function unwrapNonNull(typeRef) {
3
+ if (typeRef.kind === 'NON_NULL' && typeRef.ofType) {
4
+ return { inner: typeRef.ofType, required: true };
5
+ }
6
+ return { inner: typeRef, required: false };
7
+ }
8
+ function resolveBaseType(typeRef) {
9
+ if ((typeRef.kind === 'NON_NULL' || typeRef.kind === 'LIST') && typeRef.ofType) {
10
+ return resolveBaseType(typeRef.ofType);
11
+ }
12
+ return typeRef;
13
+ }
14
+ export function buildQuestionObject(arg) {
15
+ const { inner, required } = unwrapNonNull(arg.type);
16
+ const base = resolveBaseType(arg.type);
17
+ const props = [];
18
+ if (base.kind === 'ENUM' && base.enumValues && base.enumValues.length > 0) {
19
+ props.push(t.objectProperty(t.identifier('type'), t.stringLiteral('autocomplete')));
20
+ props.push(t.objectProperty(t.identifier('name'), t.stringLiteral(arg.name)));
21
+ props.push(t.objectProperty(t.identifier('message'), t.stringLiteral(arg.description || arg.name)));
22
+ props.push(t.objectProperty(t.identifier('options'), t.arrayExpression(base.enumValues.map((v) => t.stringLiteral(v)))));
23
+ }
24
+ else if (base.kind === 'SCALAR' && base.name === 'Boolean') {
25
+ props.push(t.objectProperty(t.identifier('type'), t.stringLiteral('confirm')));
26
+ props.push(t.objectProperty(t.identifier('name'), t.stringLiteral(arg.name)));
27
+ props.push(t.objectProperty(t.identifier('message'), t.stringLiteral(arg.description || arg.name)));
28
+ props.push(t.objectProperty(t.identifier('default'), t.booleanLiteral(false)));
29
+ }
30
+ else if (base.kind === 'SCALAR' &&
31
+ (base.name === 'Int' || base.name === 'Float')) {
32
+ props.push(t.objectProperty(t.identifier('type'), t.stringLiteral('text')));
33
+ props.push(t.objectProperty(t.identifier('name'), t.stringLiteral(arg.name)));
34
+ props.push(t.objectProperty(t.identifier('message'), t.stringLiteral(arg.description || `${arg.name} (number)`)));
35
+ }
36
+ else if (inner.kind === 'INPUT_OBJECT' && inner.inputFields) {
37
+ return buildInputObjectQuestion(arg.name, inner, required);
38
+ }
39
+ else {
40
+ props.push(t.objectProperty(t.identifier('type'), t.stringLiteral('text')));
41
+ props.push(t.objectProperty(t.identifier('name'), t.stringLiteral(arg.name)));
42
+ props.push(t.objectProperty(t.identifier('message'), t.stringLiteral(arg.description || arg.name)));
43
+ }
44
+ if (required) {
45
+ props.push(t.objectProperty(t.identifier('required'), t.booleanLiteral(true)));
46
+ }
47
+ return t.objectExpression(props);
48
+ }
49
+ function buildInputObjectQuestion(_name, typeRef, _required) {
50
+ if (typeRef.inputFields && typeRef.inputFields.length > 0) {
51
+ const firstField = typeRef.inputFields[0];
52
+ return buildQuestionObject(firstField);
53
+ }
54
+ return t.objectExpression([
55
+ t.objectProperty(t.identifier('type'), t.stringLiteral('text')),
56
+ t.objectProperty(t.identifier('name'), t.stringLiteral(_name)),
57
+ t.objectProperty(t.identifier('message'), t.stringLiteral(_name)),
58
+ ]);
59
+ }
60
+ export function buildQuestionsArray(args) {
61
+ const questions = [];
62
+ for (const arg of args) {
63
+ const base = resolveBaseType(arg.type);
64
+ const { inner } = unwrapNonNull(arg.type);
65
+ if (inner.kind === 'INPUT_OBJECT' && inner.inputFields) {
66
+ for (const field of inner.inputFields) {
67
+ questions.push(buildQuestionObject(field));
68
+ }
69
+ }
70
+ else if (base.kind === 'INPUT_OBJECT' && base.inputFields) {
71
+ for (const field of base.inputFields) {
72
+ questions.push(buildQuestionObject(field));
73
+ }
74
+ }
75
+ else {
76
+ questions.push(buildQuestionObject(arg));
77
+ }
78
+ }
79
+ return t.arrayExpression(questions);
80
+ }
@@ -0,0 +1,16 @@
1
+ import type { CleanTable, CleanOperation } from '../../../types/schema';
2
+ import type { GeneratedFile } from './executor-generator';
3
+ export declare function generateCommandMap(tables: CleanTable[], customOperations: CleanOperation[], toolName: string): GeneratedFile;
4
+ export interface MultiTargetCommandMapInput {
5
+ toolName: string;
6
+ builtinNames: {
7
+ auth: string;
8
+ context: string;
9
+ };
10
+ targets: Array<{
11
+ name: string;
12
+ tables: CleanTable[];
13
+ customOperations: CleanOperation[];
14
+ }>;
15
+ }
16
+ export declare function generateMultiTargetCommandMap(input: MultiTargetCommandMapInput): GeneratedFile;
@@ -0,0 +1,301 @@
1
+ import * as t from '@babel/types';
2
+ import { toKebabCase } from 'komoji';
3
+ import { generateCode } from '../babel-ast';
4
+ import { getGeneratedFileHeader, getTableNames } from '../utils';
5
+ function createImportDeclaration(moduleSpecifier, defaultImportName) {
6
+ return t.importDeclaration([t.importDefaultSpecifier(t.identifier(defaultImportName))], t.stringLiteral(moduleSpecifier));
7
+ }
8
+ function createNamedImportDeclaration(moduleSpecifier, namedImports, typeOnly = false) {
9
+ const specifiers = namedImports.map((name) => t.importSpecifier(t.identifier(name), t.identifier(name)));
10
+ const decl = t.importDeclaration(specifiers, t.stringLiteral(moduleSpecifier));
11
+ decl.importKind = typeOnly ? 'type' : 'value';
12
+ return decl;
13
+ }
14
+ export function generateCommandMap(tables, customOperations, toolName) {
15
+ const statements = [];
16
+ statements.push(createNamedImportDeclaration('inquirerer', [
17
+ 'CLIOptions',
18
+ 'Inquirerer',
19
+ 'extractFirst',
20
+ ]));
21
+ const commandEntries = [];
22
+ commandEntries.push({ kebab: 'context', importName: 'contextCmd' });
23
+ statements.push(createImportDeclaration('./commands/context', 'contextCmd'));
24
+ commandEntries.push({ kebab: 'auth', importName: 'authCmd' });
25
+ statements.push(createImportDeclaration('./commands/auth', 'authCmd'));
26
+ for (const table of tables) {
27
+ const { singularName } = getTableNames(table);
28
+ const kebab = toKebabCase(singularName);
29
+ const importName = `${singularName}Cmd`;
30
+ commandEntries.push({ kebab, importName });
31
+ statements.push(createImportDeclaration(`./commands/${kebab}`, importName));
32
+ }
33
+ for (const op of customOperations) {
34
+ const kebab = toKebabCase(op.name);
35
+ const importName = `${op.name}Cmd`;
36
+ commandEntries.push({ kebab, importName });
37
+ statements.push(createImportDeclaration(`./commands/${kebab}`, importName));
38
+ }
39
+ 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
+ ])));
47
+ const createCommandMapId = t.identifier('createCommandMap');
48
+ createCommandMapId.typeAnnotation = t.tsTypeAnnotation(t.tsParenthesizedType(t.tsFunctionType(null, [], t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Record'), t.tsTypeParameterInstantiation([
49
+ t.tsStringKeyword(),
50
+ t.tsFunctionType(null, [], t.tsTypeAnnotation(t.tsUnknownKeyword())),
51
+ ]))))));
52
+ statements.push(createCommandMapFunc);
53
+ const usageLines = [
54
+ '',
55
+ `${toolName} <command>`,
56
+ '',
57
+ 'Commands:',
58
+ ' context Manage API contexts',
59
+ ' auth Manage authentication',
60
+ ];
61
+ for (const table of tables) {
62
+ const { singularName } = getTableNames(table);
63
+ const kebab = toKebabCase(singularName);
64
+ usageLines.push(` ${kebab.padEnd(20)} ${singularName} CRUD operations`);
65
+ }
66
+ for (const op of customOperations) {
67
+ const kebab = toKebabCase(op.name);
68
+ usageLines.push(` ${kebab.padEnd(20)} ${op.description || op.name}`);
69
+ }
70
+ usageLines.push('');
71
+ usageLines.push(' --help, -h Show this help message');
72
+ usageLines.push(' --version, -v Show version');
73
+ usageLines.push('');
74
+ statements.push(t.variableDeclaration('const', [
75
+ t.variableDeclarator(t.identifier('usage'), t.stringLiteral(usageLines.join('\n'))),
76
+ ]));
77
+ const argvParam = t.identifier('argv');
78
+ argvParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Partial'), t.tsTypeParameterInstantiation([
79
+ t.tsTypeReference(t.identifier('Record'), t.tsTypeParameterInstantiation([
80
+ t.tsStringKeyword(),
81
+ t.tsUnknownKeyword(),
82
+ ])),
83
+ ])));
84
+ const prompterParam = t.identifier('prompter');
85
+ prompterParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Inquirerer')));
86
+ const optionsParam = t.identifier('options');
87
+ optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('CLIOptions')));
88
+ const commandsBody = [
89
+ t.ifStatement(t.logicalExpression('||', t.memberExpression(t.identifier('argv'), t.identifier('help')), t.memberExpression(t.identifier('argv'), t.identifier('h'))), t.blockStatement([
90
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('log')), [t.identifier('usage')])),
91
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(0)])),
92
+ ])),
93
+ t.variableDeclaration('let', [
94
+ t.variableDeclarator(t.objectPattern([
95
+ t.objectProperty(t.identifier('first'), t.identifier('command')),
96
+ t.objectProperty(t.identifier('newArgv'), t.identifier('newArgv'), false, true),
97
+ ]), t.callExpression(t.identifier('extractFirst'), [
98
+ t.identifier('argv'),
99
+ ])),
100
+ ]),
101
+ t.variableDeclaration('const', [
102
+ t.variableDeclarator(t.identifier('commandMap'), t.callExpression(t.identifier('createCommandMap'), [])),
103
+ ]),
104
+ t.ifStatement(t.unaryExpression('!', t.identifier('command')), t.blockStatement([
105
+ t.variableDeclaration('const', [
106
+ t.variableDeclarator(t.identifier('answer'), t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('prompter'), t.identifier('prompt')), [
107
+ t.identifier('argv'),
108
+ t.arrayExpression([
109
+ t.objectExpression([
110
+ t.objectProperty(t.identifier('type'), t.stringLiteral('autocomplete')),
111
+ t.objectProperty(t.identifier('name'), t.stringLiteral('command')),
112
+ t.objectProperty(t.identifier('message'), t.stringLiteral('What do you want to do?')),
113
+ t.objectProperty(t.identifier('options'), t.callExpression(t.memberExpression(t.identifier('Object'), t.identifier('keys')), [t.identifier('commandMap')])),
114
+ ]),
115
+ ]),
116
+ ]))),
117
+ ]),
118
+ t.expressionStatement(t.assignmentExpression('=', t.identifier('command'), t.memberExpression(t.identifier('answer'), t.identifier('command')))),
119
+ ])),
120
+ t.variableDeclaration('const', [
121
+ t.variableDeclarator(t.identifier('commandFn'), t.memberExpression(t.identifier('commandMap'), t.identifier('command'), true)),
122
+ ]),
123
+ t.ifStatement(t.unaryExpression('!', t.identifier('commandFn')), t.blockStatement([
124
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('log')), [t.identifier('usage')])),
125
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('error')), [
126
+ t.templateLiteral([
127
+ t.templateElement({
128
+ raw: 'Unknown command: ',
129
+ cooked: 'Unknown command: ',
130
+ }),
131
+ t.templateElement({ raw: '', cooked: '' }, true),
132
+ ], [t.identifier('command')]),
133
+ ])),
134
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
135
+ ])),
136
+ t.expressionStatement(t.awaitExpression(t.callExpression(t.identifier('commandFn'), [
137
+ t.identifier('newArgv'),
138
+ t.identifier('prompter'),
139
+ t.identifier('options'),
140
+ ]))),
141
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('prompter'), t.identifier('close')), [])),
142
+ t.returnStatement(t.identifier('argv')),
143
+ ];
144
+ const commandsFunc = t.arrowFunctionExpression([argvParam, prompterParam, optionsParam], t.blockStatement(commandsBody), true);
145
+ const commandsDecl = t.variableDeclaration('const', [
146
+ t.variableDeclarator(t.identifier('commands'), commandsFunc),
147
+ ]);
148
+ statements.push(t.exportNamedDeclaration(commandsDecl));
149
+ const header = getGeneratedFileHeader('CLI command map and entry point');
150
+ const code = generateCode(statements);
151
+ return {
152
+ fileName: 'commands.ts',
153
+ content: header + '\n' + code,
154
+ };
155
+ }
156
+ export function generateMultiTargetCommandMap(input) {
157
+ const { toolName, builtinNames, targets } = input;
158
+ const statements = [];
159
+ statements.push(createNamedImportDeclaration('inquirerer', [
160
+ 'CLIOptions',
161
+ 'Inquirerer',
162
+ 'extractFirst',
163
+ ]));
164
+ const commandEntries = [];
165
+ const contextImportName = `${builtinNames.context}Cmd`;
166
+ commandEntries.push({ kebab: builtinNames.context, importName: contextImportName });
167
+ statements.push(createImportDeclaration(`./commands/${builtinNames.context}`, contextImportName));
168
+ const authImportName = `${builtinNames.auth}Cmd`;
169
+ commandEntries.push({ kebab: builtinNames.auth, importName: authImportName });
170
+ statements.push(createImportDeclaration(`./commands/${builtinNames.auth}`, authImportName));
171
+ for (const target of targets) {
172
+ for (const table of target.tables) {
173
+ const { singularName } = getTableNames(table);
174
+ const kebab = toKebabCase(singularName);
175
+ const prefixedKebab = `${target.name}:${kebab}`;
176
+ const importName = `${target.name}${singularName[0].toUpperCase()}${singularName.slice(1)}Cmd`;
177
+ commandEntries.push({ kebab: prefixedKebab, importName });
178
+ statements.push(createImportDeclaration(`./commands/${target.name}/${kebab}`, importName));
179
+ }
180
+ for (const op of target.customOperations) {
181
+ const kebab = toKebabCase(op.name);
182
+ const prefixedKebab = `${target.name}:${kebab}`;
183
+ const importName = `${target.name}${op.name[0].toUpperCase()}${op.name.slice(1)}Cmd`;
184
+ commandEntries.push({ kebab: prefixedKebab, importName });
185
+ statements.push(createImportDeclaration(`./commands/${target.name}/${kebab}`, importName));
186
+ }
187
+ }
188
+ const mapProperties = commandEntries.map((entry) => t.objectProperty(t.stringLiteral(entry.kebab), t.identifier(entry.importName)));
189
+ const createCommandMapFunc = t.variableDeclaration('const', [
190
+ t.variableDeclarator(t.identifier('createCommandMap'), t.arrowFunctionExpression([], t.objectExpression(mapProperties))),
191
+ ]);
192
+ statements.push(createCommandMapFunc);
193
+ const usageLines = [
194
+ '',
195
+ `${toolName} <command>`,
196
+ '',
197
+ 'Commands:',
198
+ ` ${builtinNames.context.padEnd(20)} Manage API contexts`,
199
+ ` ${builtinNames.auth.padEnd(20)} Manage authentication`,
200
+ ];
201
+ for (const target of targets) {
202
+ usageLines.push('');
203
+ usageLines.push(` ${target.name}:`);
204
+ for (const table of target.tables) {
205
+ const { singularName } = getTableNames(table);
206
+ const kebab = toKebabCase(singularName);
207
+ const cmd = `${target.name}:${kebab}`;
208
+ usageLines.push(` ${cmd.padEnd(22)} ${singularName} CRUD operations`);
209
+ }
210
+ for (const op of target.customOperations) {
211
+ const kebab = toKebabCase(op.name);
212
+ const cmd = `${target.name}:${kebab}`;
213
+ usageLines.push(` ${cmd.padEnd(22)} ${op.description || op.name}`);
214
+ }
215
+ }
216
+ usageLines.push('');
217
+ usageLines.push(' --help, -h Show this help message');
218
+ usageLines.push(' --version, -v Show version');
219
+ usageLines.push('');
220
+ statements.push(t.variableDeclaration('const', [
221
+ t.variableDeclarator(t.identifier('usage'), t.stringLiteral(usageLines.join('\n'))),
222
+ ]));
223
+ const argvParam = t.identifier('argv');
224
+ argvParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Partial'), t.tsTypeParameterInstantiation([
225
+ t.tsTypeReference(t.identifier('Record'), t.tsTypeParameterInstantiation([
226
+ t.tsStringKeyword(),
227
+ t.tsUnknownKeyword(),
228
+ ])),
229
+ ])));
230
+ const prompterParam = t.identifier('prompter');
231
+ prompterParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('Inquirerer')));
232
+ const optionsParam = t.identifier('options');
233
+ optionsParam.typeAnnotation = t.tsTypeAnnotation(t.tsTypeReference(t.identifier('CLIOptions')));
234
+ const commandsBody = [
235
+ t.ifStatement(t.logicalExpression('||', t.memberExpression(t.identifier('argv'), t.identifier('help')), t.memberExpression(t.identifier('argv'), t.identifier('h'))), t.blockStatement([
236
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('log')), [t.identifier('usage')])),
237
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(0)])),
238
+ ])),
239
+ t.variableDeclaration('let', [
240
+ t.variableDeclarator(t.objectPattern([
241
+ t.objectProperty(t.identifier('first'), t.identifier('command')),
242
+ t.objectProperty(t.identifier('newArgv'), t.identifier('newArgv'), false, true),
243
+ ]), t.callExpression(t.identifier('extractFirst'), [
244
+ t.identifier('argv'),
245
+ ])),
246
+ ]),
247
+ t.variableDeclaration('const', [
248
+ t.variableDeclarator(t.identifier('commandMap'), t.callExpression(t.identifier('createCommandMap'), [])),
249
+ ]),
250
+ t.ifStatement(t.unaryExpression('!', t.identifier('command')), t.blockStatement([
251
+ t.variableDeclaration('const', [
252
+ t.variableDeclarator(t.identifier('answer'), t.awaitExpression(t.callExpression(t.memberExpression(t.identifier('prompter'), t.identifier('prompt')), [
253
+ t.identifier('argv'),
254
+ t.arrayExpression([
255
+ t.objectExpression([
256
+ t.objectProperty(t.identifier('type'), t.stringLiteral('autocomplete')),
257
+ t.objectProperty(t.identifier('name'), t.stringLiteral('command')),
258
+ t.objectProperty(t.identifier('message'), t.stringLiteral('What do you want to do?')),
259
+ t.objectProperty(t.identifier('options'), t.callExpression(t.memberExpression(t.identifier('Object'), t.identifier('keys')), [t.identifier('commandMap')])),
260
+ ]),
261
+ ]),
262
+ ]))),
263
+ ]),
264
+ t.expressionStatement(t.assignmentExpression('=', t.identifier('command'), t.memberExpression(t.identifier('answer'), t.identifier('command')))),
265
+ ])),
266
+ t.variableDeclaration('const', [
267
+ t.variableDeclarator(t.identifier('commandFn'), t.memberExpression(t.identifier('commandMap'), t.identifier('command'), true)),
268
+ ]),
269
+ t.ifStatement(t.unaryExpression('!', t.identifier('commandFn')), t.blockStatement([
270
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('log')), [t.identifier('usage')])),
271
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('console'), t.identifier('error')), [
272
+ t.templateLiteral([
273
+ t.templateElement({
274
+ raw: 'Unknown command: ',
275
+ cooked: 'Unknown command: ',
276
+ }),
277
+ t.templateElement({ raw: '', cooked: '' }, true),
278
+ ], [t.identifier('command')]),
279
+ ])),
280
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('process'), t.identifier('exit')), [t.numericLiteral(1)])),
281
+ ])),
282
+ t.expressionStatement(t.awaitExpression(t.callExpression(t.identifier('commandFn'), [
283
+ t.identifier('newArgv'),
284
+ t.identifier('prompter'),
285
+ t.identifier('options'),
286
+ ]))),
287
+ t.expressionStatement(t.callExpression(t.memberExpression(t.identifier('prompter'), t.identifier('close')), [])),
288
+ t.returnStatement(t.identifier('argv')),
289
+ ];
290
+ const commandsFunc = t.arrowFunctionExpression([argvParam, prompterParam, optionsParam], t.blockStatement(commandsBody), true);
291
+ const commandsDecl = t.variableDeclaration('const', [
292
+ t.variableDeclarator(t.identifier('commands'), commandsFunc),
293
+ ]);
294
+ statements.push(t.exportNamedDeclaration(commandsDecl));
295
+ const header = getGeneratedFileHeader('Multi-target CLI command map and entry point');
296
+ const code = generateCode(statements);
297
+ return {
298
+ fileName: 'commands.ts',
299
+ content: header + '\n' + code,
300
+ };
301
+ }
@@ -0,0 +1,8 @@
1
+ import type { CleanOperation } from '../../../types/schema';
2
+ import type { GeneratedFile } from './executor-generator';
3
+ export interface CustomCommandOptions {
4
+ targetName?: string;
5
+ executorImportPath?: string;
6
+ saveToken?: boolean;
7
+ }
8
+ export declare function generateCustomCommand(op: CleanOperation, options?: CustomCommandOptions): GeneratedFile;