@constructive-io/graphql-codegen 4.0.2 → 4.1.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.
- package/cli/handler.d.ts +13 -0
- package/cli/handler.js +74 -0
- package/cli/index.js +11 -57
- package/core/codegen/barrel.d.ts +1 -0
- package/core/codegen/barrel.js +5 -2
- package/core/codegen/cli/arg-mapper.d.ts +4 -0
- package/core/codegen/cli/arg-mapper.js +117 -0
- package/core/codegen/cli/command-map-generator.d.ts +16 -0
- package/core/codegen/cli/command-map-generator.js +338 -0
- package/core/codegen/cli/custom-command-generator.d.ts +8 -0
- package/core/codegen/cli/custom-command-generator.js +155 -0
- package/core/codegen/cli/docs-generator.d.ts +26 -0
- package/core/codegen/cli/docs-generator.js +1399 -0
- package/core/codegen/cli/executor-generator.d.ts +11 -0
- package/core/codegen/cli/executor-generator.js +217 -0
- package/core/codegen/cli/index.d.ts +53 -0
- package/core/codegen/cli/index.js +153 -0
- package/core/codegen/cli/infra-generator.d.ts +9 -0
- package/core/codegen/cli/infra-generator.js +1195 -0
- package/core/codegen/cli/table-command-generator.d.ts +7 -0
- package/core/codegen/cli/table-command-generator.js +323 -0
- package/core/codegen/docs-utils.d.ts +30 -0
- package/core/codegen/docs-utils.js +122 -0
- package/core/codegen/hooks-docs-generator.d.ts +6 -0
- package/core/codegen/hooks-docs-generator.js +468 -0
- package/core/codegen/orm/docs-generator.d.ts +6 -0
- package/core/codegen/orm/docs-generator.js +416 -0
- package/core/codegen/target-docs-generator.d.ts +20 -0
- package/core/codegen/target-docs-generator.js +110 -0
- package/core/database/index.d.ts +0 -12
- package/core/database/index.js +2 -19
- package/core/generate.d.ts +34 -2
- package/core/generate.js +453 -12
- package/core/index.d.ts +0 -2
- package/core/index.js +0 -2
- package/core/introspect/source/database.js +2 -2
- package/core/introspect/source/pgpm-module.js +2 -2
- package/core/output/index.d.ts +1 -1
- package/core/output/index.js +1 -2
- package/core/output/writer.d.ts +0 -10
- package/core/output/writer.js +0 -31
- package/esm/cli/handler.d.ts +13 -0
- package/esm/cli/handler.js +71 -0
- package/esm/cli/index.js +11 -57
- package/esm/core/codegen/barrel.d.ts +1 -0
- package/esm/core/codegen/barrel.js +5 -2
- package/esm/core/codegen/cli/arg-mapper.d.ts +4 -0
- package/esm/core/codegen/cli/arg-mapper.js +80 -0
- package/esm/core/codegen/cli/command-map-generator.d.ts +16 -0
- package/esm/core/codegen/cli/command-map-generator.js +301 -0
- package/esm/core/codegen/cli/custom-command-generator.d.ts +8 -0
- package/esm/core/codegen/cli/custom-command-generator.js +119 -0
- package/esm/core/codegen/cli/docs-generator.d.ts +26 -0
- package/esm/core/codegen/cli/docs-generator.js +1387 -0
- package/esm/core/codegen/cli/executor-generator.d.ts +11 -0
- package/esm/core/codegen/cli/executor-generator.js +180 -0
- package/esm/core/codegen/cli/index.d.ts +53 -0
- package/esm/core/codegen/cli/index.js +128 -0
- package/esm/core/codegen/cli/infra-generator.d.ts +9 -0
- package/esm/core/codegen/cli/infra-generator.js +1156 -0
- package/esm/core/codegen/cli/table-command-generator.d.ts +7 -0
- package/esm/core/codegen/cli/table-command-generator.js +287 -0
- package/esm/core/codegen/docs-utils.d.ts +30 -0
- package/esm/core/codegen/docs-utils.js +112 -0
- package/esm/core/codegen/hooks-docs-generator.d.ts +6 -0
- package/esm/core/codegen/hooks-docs-generator.js +462 -0
- package/esm/core/codegen/orm/docs-generator.d.ts +6 -0
- package/esm/core/codegen/orm/docs-generator.js +410 -0
- package/esm/core/codegen/target-docs-generator.d.ts +20 -0
- package/esm/core/codegen/target-docs-generator.js +105 -0
- package/esm/core/database/index.d.ts +0 -12
- package/esm/core/database/index.js +1 -17
- package/esm/core/generate.d.ts +34 -2
- package/esm/core/generate.js +417 -12
- package/esm/core/index.d.ts +0 -2
- package/esm/core/index.js +0 -2
- package/esm/core/introspect/source/database.js +2 -2
- package/esm/core/introspect/source/pgpm-module.js +2 -2
- package/esm/core/output/index.d.ts +1 -1
- package/esm/core/output/index.js +1 -1
- package/esm/core/output/writer.d.ts +0 -10
- package/esm/core/output/writer.js +0 -30
- package/esm/generators/index.d.ts +0 -3
- package/esm/generators/index.js +0 -3
- package/esm/index.d.ts +4 -3
- package/esm/index.js +4 -2
- package/esm/types/config.d.ts +78 -0
- package/generators/index.d.ts +0 -3
- package/generators/index.js +0 -3
- package/index.d.ts +4 -3
- package/index.js +7 -2
- package/package.json +8 -7
- package/types/config.d.ts +78 -0
package/esm/core/generate.js
CHANGED
|
@@ -4,22 +4,27 @@
|
|
|
4
4
|
* This is the primary entry point for programmatic usage.
|
|
5
5
|
* The CLI is a thin wrapper around this function.
|
|
6
6
|
*/
|
|
7
|
+
import * as fs from 'node:fs';
|
|
7
8
|
import path from 'node:path';
|
|
9
|
+
import { buildClientSchema, printSchema } from 'graphql';
|
|
10
|
+
import { PgpmPackage } from '@pgpmjs/core';
|
|
11
|
+
import { createEphemeralDb } from 'pgsql-client';
|
|
12
|
+
import { deployPgpm } from 'pgsql-seed';
|
|
8
13
|
import { getConfigOptions } from '../types/config';
|
|
9
14
|
import { generate as generateReactQueryFiles } from './codegen';
|
|
10
15
|
import { generateRootBarrel } from './codegen/barrel';
|
|
16
|
+
import { generateCli as generateCliFiles, generateMultiTargetCli } from './codegen/cli';
|
|
17
|
+
import { generateReadme as generateCliReadme, generateAgentsDocs as generateCliAgentsDocs, getCliMcpTools, generateSkills as generateCliSkills, generateMultiTargetReadme, generateMultiTargetAgentsDocs, getMultiTargetCliMcpTools, generateMultiTargetSkills, } from './codegen/cli/docs-generator';
|
|
18
|
+
import { resolveDocsConfig } from './codegen/docs-utils';
|
|
19
|
+
import { generateHooksReadme, generateHooksAgentsDocs, getHooksMcpTools, generateHooksSkills, } from './codegen/hooks-docs-generator';
|
|
11
20
|
import { generateOrm as generateOrmFiles } from './codegen/orm';
|
|
21
|
+
import { generateOrmReadme, generateOrmAgentsDocs, getOrmMcpTools, generateOrmSkills, } from './codegen/orm/docs-generator';
|
|
12
22
|
import { generateSharedTypes } from './codegen/shared';
|
|
23
|
+
import { generateTargetReadme, generateCombinedMcpConfig, generateRootRootReadme, } from './codegen/target-docs-generator';
|
|
13
24
|
import { createSchemaSource, validateSourceOptions } from './introspect';
|
|
14
25
|
import { writeGeneratedFiles } from './output';
|
|
15
26
|
import { runCodegenPipeline, validateTablesFound } from './pipeline';
|
|
16
|
-
|
|
17
|
-
* Main generate function - takes a single config and generates code
|
|
18
|
-
*
|
|
19
|
-
* This is the primary entry point for programmatic usage.
|
|
20
|
-
* For multiple configs, call this function in a loop.
|
|
21
|
-
*/
|
|
22
|
-
export async function generate(options = {}) {
|
|
27
|
+
export async function generate(options = {}, internalOptions) {
|
|
23
28
|
// Apply defaults to get resolved config
|
|
24
29
|
const config = getConfigOptions(options);
|
|
25
30
|
const outputRoot = config.output;
|
|
@@ -27,11 +32,12 @@ export async function generate(options = {}) {
|
|
|
27
32
|
// ORM is always required when React Query is enabled (hooks delegate to ORM)
|
|
28
33
|
// This handles minimist setting orm=false when --orm flag is absent
|
|
29
34
|
const runReactQuery = config.reactQuery ?? false;
|
|
30
|
-
const
|
|
31
|
-
|
|
35
|
+
const runCli = internalOptions?.skipCli ? false : !!config.cli;
|
|
36
|
+
const runOrm = runReactQuery || !!config.cli || (options.orm !== undefined ? !!options.orm : false);
|
|
37
|
+
if (!options.schemaOnly && !runReactQuery && !runOrm && !runCli) {
|
|
32
38
|
return {
|
|
33
39
|
success: false,
|
|
34
|
-
message: 'No generators enabled. Use reactQuery: true or
|
|
40
|
+
message: 'No generators enabled. Use reactQuery: true, orm: true, or cli: true in your config.',
|
|
35
41
|
output: outputRoot,
|
|
36
42
|
};
|
|
37
43
|
}
|
|
@@ -55,6 +61,39 @@ export async function generate(options = {}) {
|
|
|
55
61
|
authorization: options.authorization || config.headers?.Authorization,
|
|
56
62
|
headers: config.headers,
|
|
57
63
|
});
|
|
64
|
+
if (options.schemaOnly) {
|
|
65
|
+
try {
|
|
66
|
+
console.log(`Fetching schema from ${source.describe()}...`);
|
|
67
|
+
const { introspection } = await source.fetch();
|
|
68
|
+
const schema = buildClientSchema(introspection);
|
|
69
|
+
const sdl = printSchema(schema);
|
|
70
|
+
if (!sdl.trim()) {
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
message: 'Schema introspection returned empty SDL.',
|
|
74
|
+
output: outputRoot,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const outDir = path.resolve(options.schemaOnlyOutput || outputRoot || '.');
|
|
78
|
+
await fs.promises.mkdir(outDir, { recursive: true });
|
|
79
|
+
const filename = options.schemaOnlyFilename || 'schema.graphql';
|
|
80
|
+
const filePath = path.join(outDir, filename);
|
|
81
|
+
await fs.promises.writeFile(filePath, sdl, 'utf-8');
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
message: `Schema exported to ${filePath}`,
|
|
85
|
+
output: outDir,
|
|
86
|
+
filesWritten: [filePath],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
return {
|
|
91
|
+
success: false,
|
|
92
|
+
message: `Failed to export schema: ${err instanceof Error ? err.message : 'Unknown error'}`,
|
|
93
|
+
output: outputRoot,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
58
97
|
// Run pipeline
|
|
59
98
|
let pipelineResult;
|
|
60
99
|
try {
|
|
@@ -136,14 +175,115 @@ export async function generate(options = {}) {
|
|
|
136
175
|
path: path.posix.join('orm', file.path),
|
|
137
176
|
})));
|
|
138
177
|
}
|
|
178
|
+
// Generate CLI commands
|
|
179
|
+
if (runCli) {
|
|
180
|
+
console.log('Generating CLI commands...');
|
|
181
|
+
const { files } = generateCliFiles({
|
|
182
|
+
tables,
|
|
183
|
+
customOperations: {
|
|
184
|
+
queries: customOperations.queries,
|
|
185
|
+
mutations: customOperations.mutations,
|
|
186
|
+
},
|
|
187
|
+
config,
|
|
188
|
+
});
|
|
189
|
+
filesToWrite.push(...files.map((file) => ({
|
|
190
|
+
path: path.posix.join('cli', file.fileName),
|
|
191
|
+
content: file.content,
|
|
192
|
+
})));
|
|
193
|
+
}
|
|
139
194
|
// Generate barrel file at output root
|
|
140
|
-
// This re-exports from the appropriate subdirectories based on which generators are enabled
|
|
141
195
|
const barrelContent = generateRootBarrel({
|
|
142
196
|
hasTypes: bothEnabled,
|
|
143
197
|
hasHooks: runReactQuery,
|
|
144
198
|
hasOrm: runOrm,
|
|
199
|
+
hasCli: runCli,
|
|
145
200
|
});
|
|
146
201
|
filesToWrite.push({ path: 'index.ts', content: barrelContent });
|
|
202
|
+
// Generate docs for each enabled generator
|
|
203
|
+
const docsConfig = resolveDocsConfig(config.docs);
|
|
204
|
+
const allCustomOps = [
|
|
205
|
+
...(customOperations.queries ?? []),
|
|
206
|
+
...(customOperations.mutations ?? []),
|
|
207
|
+
];
|
|
208
|
+
const allMcpTools = [];
|
|
209
|
+
if (runOrm) {
|
|
210
|
+
if (docsConfig.readme) {
|
|
211
|
+
const readme = generateOrmReadme(tables, allCustomOps);
|
|
212
|
+
filesToWrite.push({ path: path.posix.join('orm', readme.fileName), content: readme.content });
|
|
213
|
+
}
|
|
214
|
+
if (docsConfig.agents) {
|
|
215
|
+
const agents = generateOrmAgentsDocs(tables, allCustomOps);
|
|
216
|
+
filesToWrite.push({ path: path.posix.join('orm', agents.fileName), content: agents.content });
|
|
217
|
+
}
|
|
218
|
+
if (docsConfig.mcp) {
|
|
219
|
+
allMcpTools.push(...getOrmMcpTools(tables, allCustomOps));
|
|
220
|
+
}
|
|
221
|
+
if (docsConfig.skills) {
|
|
222
|
+
for (const skill of generateOrmSkills(tables, allCustomOps)) {
|
|
223
|
+
filesToWrite.push({ path: path.posix.join('orm', skill.fileName), content: skill.content });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (runReactQuery) {
|
|
228
|
+
if (docsConfig.readme) {
|
|
229
|
+
const readme = generateHooksReadme(tables, allCustomOps);
|
|
230
|
+
filesToWrite.push({ path: path.posix.join('hooks', readme.fileName), content: readme.content });
|
|
231
|
+
}
|
|
232
|
+
if (docsConfig.agents) {
|
|
233
|
+
const agents = generateHooksAgentsDocs(tables, allCustomOps);
|
|
234
|
+
filesToWrite.push({ path: path.posix.join('hooks', agents.fileName), content: agents.content });
|
|
235
|
+
}
|
|
236
|
+
if (docsConfig.mcp) {
|
|
237
|
+
allMcpTools.push(...getHooksMcpTools(tables, allCustomOps));
|
|
238
|
+
}
|
|
239
|
+
if (docsConfig.skills) {
|
|
240
|
+
for (const skill of generateHooksSkills(tables, allCustomOps)) {
|
|
241
|
+
filesToWrite.push({ path: path.posix.join('hooks', skill.fileName), content: skill.content });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (runCli) {
|
|
246
|
+
const toolName = typeof config.cli === 'object' && config.cli?.toolName
|
|
247
|
+
? config.cli.toolName
|
|
248
|
+
: 'app';
|
|
249
|
+
if (docsConfig.readme) {
|
|
250
|
+
const readme = generateCliReadme(tables, allCustomOps, toolName);
|
|
251
|
+
filesToWrite.push({ path: path.posix.join('cli', readme.fileName), content: readme.content });
|
|
252
|
+
}
|
|
253
|
+
if (docsConfig.agents) {
|
|
254
|
+
const agents = generateCliAgentsDocs(tables, allCustomOps, toolName);
|
|
255
|
+
filesToWrite.push({ path: path.posix.join('cli', agents.fileName), content: agents.content });
|
|
256
|
+
}
|
|
257
|
+
if (docsConfig.mcp) {
|
|
258
|
+
allMcpTools.push(...getCliMcpTools(tables, allCustomOps, toolName));
|
|
259
|
+
}
|
|
260
|
+
if (docsConfig.skills) {
|
|
261
|
+
for (const skill of generateCliSkills(tables, allCustomOps, toolName)) {
|
|
262
|
+
filesToWrite.push({ path: path.posix.join('cli', skill.fileName), content: skill.content });
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Generate combined mcp.json at output root
|
|
267
|
+
if (docsConfig.mcp && allMcpTools.length > 0) {
|
|
268
|
+
const mcpName = typeof config.cli === 'object' && config.cli?.toolName
|
|
269
|
+
? config.cli.toolName
|
|
270
|
+
: 'graphql-sdk';
|
|
271
|
+
const mcpFile = generateCombinedMcpConfig(allMcpTools, mcpName);
|
|
272
|
+
filesToWrite.push({ path: mcpFile.fileName, content: mcpFile.content });
|
|
273
|
+
}
|
|
274
|
+
// Generate per-target README at output root
|
|
275
|
+
if (docsConfig.readme) {
|
|
276
|
+
const targetReadme = generateTargetReadme({
|
|
277
|
+
hasOrm: runOrm,
|
|
278
|
+
hasHooks: runReactQuery,
|
|
279
|
+
hasCli: runCli,
|
|
280
|
+
tableCount: tables.length,
|
|
281
|
+
customQueryCount: customOperations.queries.length,
|
|
282
|
+
customMutationCount: customOperations.mutations.length,
|
|
283
|
+
config,
|
|
284
|
+
});
|
|
285
|
+
filesToWrite.push({ path: targetReadme.fileName, content: targetReadme.content });
|
|
286
|
+
}
|
|
147
287
|
if (!options.dryRun) {
|
|
148
288
|
const writeResult = await writeGeneratedFiles(filesToWrite, outputRoot, [], {
|
|
149
289
|
pruneStaleFiles: true,
|
|
@@ -158,7 +298,11 @@ export async function generate(options = {}) {
|
|
|
158
298
|
}
|
|
159
299
|
allFilesWritten.push(...(writeResult.filesWritten ?? []));
|
|
160
300
|
}
|
|
161
|
-
const generators = [
|
|
301
|
+
const generators = [
|
|
302
|
+
runReactQuery && 'React Query',
|
|
303
|
+
runOrm && 'ORM',
|
|
304
|
+
runCli && 'CLI',
|
|
305
|
+
]
|
|
162
306
|
.filter(Boolean)
|
|
163
307
|
.join(' and ');
|
|
164
308
|
return {
|
|
@@ -169,5 +313,266 @@ export async function generate(options = {}) {
|
|
|
169
313
|
output: outputRoot,
|
|
170
314
|
tables: tables.map((t) => t.name),
|
|
171
315
|
filesWritten: allFilesWritten,
|
|
316
|
+
pipelineData: {
|
|
317
|
+
tables,
|
|
318
|
+
customOperations: {
|
|
319
|
+
queries: customOperations.queries,
|
|
320
|
+
mutations: customOperations.mutations,
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
export function expandApiNamesToMultiTarget(config) {
|
|
326
|
+
const apiNames = config.db?.apiNames;
|
|
327
|
+
if (!apiNames || apiNames.length <= 1)
|
|
328
|
+
return null;
|
|
329
|
+
const targets = {};
|
|
330
|
+
for (const apiName of apiNames) {
|
|
331
|
+
targets[apiName] = {
|
|
332
|
+
...config,
|
|
333
|
+
db: {
|
|
334
|
+
...config.db,
|
|
335
|
+
apiNames: [apiName],
|
|
336
|
+
},
|
|
337
|
+
output: config.output
|
|
338
|
+
? `${config.output}/${apiName}`
|
|
339
|
+
: `./generated/graphql/${apiName}`,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
return targets;
|
|
343
|
+
}
|
|
344
|
+
export function expandSchemaDirToMultiTarget(config) {
|
|
345
|
+
const schemaDir = config.schemaDir;
|
|
346
|
+
if (!schemaDir)
|
|
347
|
+
return null;
|
|
348
|
+
const resolvedDir = path.resolve(schemaDir);
|
|
349
|
+
if (!fs.existsSync(resolvedDir) || !fs.statSync(resolvedDir).isDirectory()) {
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
const graphqlFiles = fs.readdirSync(resolvedDir)
|
|
353
|
+
.filter((f) => f.endsWith('.graphql'))
|
|
354
|
+
.sort();
|
|
355
|
+
if (graphqlFiles.length === 0)
|
|
356
|
+
return null;
|
|
357
|
+
const targets = {};
|
|
358
|
+
for (const file of graphqlFiles) {
|
|
359
|
+
const name = path.basename(file, '.graphql');
|
|
360
|
+
targets[name] = {
|
|
361
|
+
...config,
|
|
362
|
+
schemaDir: undefined,
|
|
363
|
+
schemaFile: path.join(resolvedDir, file),
|
|
364
|
+
output: config.output
|
|
365
|
+
? `${config.output}/${name}`
|
|
366
|
+
: `./generated/graphql/${name}`,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
return targets;
|
|
370
|
+
}
|
|
371
|
+
function getPgpmSourceKey(pgpm) {
|
|
372
|
+
if (pgpm.modulePath)
|
|
373
|
+
return `module:${path.resolve(pgpm.modulePath)}`;
|
|
374
|
+
if (pgpm.workspacePath && pgpm.moduleName)
|
|
375
|
+
return `workspace:${path.resolve(pgpm.workspacePath)}:${pgpm.moduleName}`;
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
function getModulePathFromPgpm(pgpm) {
|
|
379
|
+
if (pgpm.modulePath)
|
|
380
|
+
return pgpm.modulePath;
|
|
381
|
+
if (pgpm.workspacePath && pgpm.moduleName) {
|
|
382
|
+
const workspace = new PgpmPackage(pgpm.workspacePath);
|
|
383
|
+
const moduleProject = workspace.getModuleProject(pgpm.moduleName);
|
|
384
|
+
const modulePath = moduleProject.getModulePath();
|
|
385
|
+
if (!modulePath) {
|
|
386
|
+
throw new Error(`Module "${pgpm.moduleName}" not found in workspace`);
|
|
387
|
+
}
|
|
388
|
+
return modulePath;
|
|
389
|
+
}
|
|
390
|
+
throw new Error('Invalid PGPM config: requires modulePath or workspacePath+moduleName');
|
|
391
|
+
}
|
|
392
|
+
async function prepareSharedPgpmSources(configs, cliOverrides) {
|
|
393
|
+
const sharedSources = new Map();
|
|
394
|
+
const pgpmTargetCount = new Map();
|
|
395
|
+
for (const name of Object.keys(configs)) {
|
|
396
|
+
const merged = { ...configs[name], ...(cliOverrides ?? {}) };
|
|
397
|
+
const pgpm = merged.db?.pgpm;
|
|
398
|
+
if (!pgpm)
|
|
399
|
+
continue;
|
|
400
|
+
const key = getPgpmSourceKey(pgpm);
|
|
401
|
+
if (!key)
|
|
402
|
+
continue;
|
|
403
|
+
pgpmTargetCount.set(key, (pgpmTargetCount.get(key) ?? 0) + 1);
|
|
404
|
+
}
|
|
405
|
+
for (const [key, count] of pgpmTargetCount) {
|
|
406
|
+
if (count < 2)
|
|
407
|
+
continue;
|
|
408
|
+
let pgpmConfig;
|
|
409
|
+
for (const name of Object.keys(configs)) {
|
|
410
|
+
const merged = { ...configs[name], ...(cliOverrides ?? {}) };
|
|
411
|
+
const pgpm = merged.db?.pgpm;
|
|
412
|
+
if (pgpm && getPgpmSourceKey(pgpm) === key) {
|
|
413
|
+
pgpmConfig = pgpm;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if (!pgpmConfig)
|
|
418
|
+
continue;
|
|
419
|
+
const ephemeralDb = createEphemeralDb({
|
|
420
|
+
prefix: 'codegen_pgpm_shared_',
|
|
421
|
+
verbose: false,
|
|
422
|
+
});
|
|
423
|
+
const modulePath = getModulePathFromPgpm(pgpmConfig);
|
|
424
|
+
await deployPgpm(ephemeralDb.config, modulePath, false);
|
|
425
|
+
sharedSources.set(key, {
|
|
426
|
+
key,
|
|
427
|
+
ephemeralDb,
|
|
428
|
+
deployed: true,
|
|
429
|
+
});
|
|
430
|
+
console.log(`[multi-target] Shared PGPM source deployed once for ${count} targets: ${key}`);
|
|
431
|
+
}
|
|
432
|
+
return sharedSources;
|
|
433
|
+
}
|
|
434
|
+
function applySharedPgpmDb(config, sharedSources) {
|
|
435
|
+
const pgpm = config.db?.pgpm;
|
|
436
|
+
if (!pgpm)
|
|
437
|
+
return config;
|
|
438
|
+
const key = getPgpmSourceKey(pgpm);
|
|
439
|
+
if (!key)
|
|
440
|
+
return config;
|
|
441
|
+
const shared = sharedSources.get(key);
|
|
442
|
+
if (!shared)
|
|
443
|
+
return config;
|
|
444
|
+
const sharedDbConfig = {
|
|
445
|
+
...config.db,
|
|
446
|
+
pgpm: undefined,
|
|
447
|
+
config: shared.ephemeralDb.config,
|
|
448
|
+
keepDb: true,
|
|
449
|
+
};
|
|
450
|
+
return {
|
|
451
|
+
...config,
|
|
452
|
+
db: sharedDbConfig,
|
|
172
453
|
};
|
|
173
454
|
}
|
|
455
|
+
export async function generateMulti(options) {
|
|
456
|
+
const { configs, cliOverrides, verbose, dryRun, schemaOnly, unifiedCli } = options;
|
|
457
|
+
const names = Object.keys(configs);
|
|
458
|
+
const results = [];
|
|
459
|
+
let hasError = false;
|
|
460
|
+
const targetInfos = [];
|
|
461
|
+
const useUnifiedCli = !schemaOnly && !!unifiedCli && names.length > 1;
|
|
462
|
+
const cliTargets = [];
|
|
463
|
+
const sharedSources = await prepareSharedPgpmSources(configs, cliOverrides);
|
|
464
|
+
try {
|
|
465
|
+
for (const name of names) {
|
|
466
|
+
const baseConfig = {
|
|
467
|
+
...configs[name],
|
|
468
|
+
...(cliOverrides ?? {}),
|
|
469
|
+
};
|
|
470
|
+
const targetConfig = applySharedPgpmDb(baseConfig, sharedSources);
|
|
471
|
+
const result = await generate({
|
|
472
|
+
...targetConfig,
|
|
473
|
+
verbose,
|
|
474
|
+
dryRun,
|
|
475
|
+
schemaOnly,
|
|
476
|
+
schemaOnlyFilename: schemaOnly ? `${name}.graphql` : undefined,
|
|
477
|
+
}, useUnifiedCli ? { skipCli: true } : undefined);
|
|
478
|
+
results.push({ name, result });
|
|
479
|
+
if (!result.success) {
|
|
480
|
+
hasError = true;
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
const resolvedConfig = getConfigOptions(targetConfig);
|
|
484
|
+
const gens = [];
|
|
485
|
+
if (resolvedConfig.reactQuery)
|
|
486
|
+
gens.push('React Query');
|
|
487
|
+
if (resolvedConfig.orm || resolvedConfig.reactQuery || !!resolvedConfig.cli)
|
|
488
|
+
gens.push('ORM');
|
|
489
|
+
if (resolvedConfig.cli)
|
|
490
|
+
gens.push('CLI');
|
|
491
|
+
targetInfos.push({
|
|
492
|
+
name,
|
|
493
|
+
output: resolvedConfig.output,
|
|
494
|
+
endpoint: resolvedConfig.endpoint || undefined,
|
|
495
|
+
generators: gens,
|
|
496
|
+
});
|
|
497
|
+
if (useUnifiedCli && result.pipelineData) {
|
|
498
|
+
const isAuthTarget = name === 'auth';
|
|
499
|
+
cliTargets.push({
|
|
500
|
+
name,
|
|
501
|
+
endpoint: resolvedConfig.endpoint || '',
|
|
502
|
+
ormImportPath: `../${resolvedConfig.output.replace(/^\.\//, '')}/orm`,
|
|
503
|
+
tables: result.pipelineData.tables,
|
|
504
|
+
customOperations: result.pipelineData.customOperations,
|
|
505
|
+
isAuthTarget,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (useUnifiedCli && cliTargets.length > 0 && !dryRun) {
|
|
511
|
+
const cliConfig = typeof unifiedCli === 'object' ? unifiedCli : {};
|
|
512
|
+
const toolName = cliConfig.toolName ?? 'app';
|
|
513
|
+
const { files } = generateMultiTargetCli({
|
|
514
|
+
toolName,
|
|
515
|
+
builtinNames: cliConfig.builtinNames,
|
|
516
|
+
targets: cliTargets,
|
|
517
|
+
});
|
|
518
|
+
const cliFilesToWrite = files.map((file) => ({
|
|
519
|
+
path: path.posix.join('cli', file.fileName),
|
|
520
|
+
content: file.content,
|
|
521
|
+
}));
|
|
522
|
+
const firstTargetDocsConfig = names.length > 0 && configs[names[0]]?.docs;
|
|
523
|
+
const docsConfig = resolveDocsConfig(firstTargetDocsConfig);
|
|
524
|
+
const { resolveBuiltinNames } = await import('./codegen/cli');
|
|
525
|
+
const builtinNames = resolveBuiltinNames(cliTargets.map((t) => t.name), cliConfig.builtinNames);
|
|
526
|
+
const docsInput = {
|
|
527
|
+
toolName,
|
|
528
|
+
builtinNames,
|
|
529
|
+
targets: cliTargets.map((t) => ({
|
|
530
|
+
name: t.name,
|
|
531
|
+
endpoint: t.endpoint,
|
|
532
|
+
tables: t.tables,
|
|
533
|
+
customOperations: [
|
|
534
|
+
...(t.customOperations?.queries ?? []),
|
|
535
|
+
...(t.customOperations?.mutations ?? []),
|
|
536
|
+
],
|
|
537
|
+
isAuthTarget: t.isAuthTarget,
|
|
538
|
+
})),
|
|
539
|
+
};
|
|
540
|
+
const allMcpTools = [];
|
|
541
|
+
if (docsConfig.readme) {
|
|
542
|
+
const readme = generateMultiTargetReadme(docsInput);
|
|
543
|
+
cliFilesToWrite.push({ path: path.posix.join('cli', readme.fileName), content: readme.content });
|
|
544
|
+
}
|
|
545
|
+
if (docsConfig.agents) {
|
|
546
|
+
const agents = generateMultiTargetAgentsDocs(docsInput);
|
|
547
|
+
cliFilesToWrite.push({ path: path.posix.join('cli', agents.fileName), content: agents.content });
|
|
548
|
+
}
|
|
549
|
+
if (docsConfig.mcp) {
|
|
550
|
+
allMcpTools.push(...getMultiTargetCliMcpTools(docsInput));
|
|
551
|
+
}
|
|
552
|
+
if (docsConfig.skills) {
|
|
553
|
+
for (const skill of generateMultiTargetSkills(docsInput)) {
|
|
554
|
+
cliFilesToWrite.push({ path: path.posix.join('cli', skill.fileName), content: skill.content });
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (docsConfig.mcp && allMcpTools.length > 0) {
|
|
558
|
+
const mcpFile = generateCombinedMcpConfig(allMcpTools, toolName);
|
|
559
|
+
cliFilesToWrite.push({ path: path.posix.join('cli', mcpFile.fileName), content: mcpFile.content });
|
|
560
|
+
}
|
|
561
|
+
const { writeGeneratedFiles: writeFiles } = await import('./output');
|
|
562
|
+
await writeFiles(cliFilesToWrite, '.', [], { pruneStaleFiles: false });
|
|
563
|
+
}
|
|
564
|
+
// Generate root-root README if multi-target
|
|
565
|
+
if (names.length > 1 && targetInfos.length > 0 && !dryRun) {
|
|
566
|
+
const rootReadme = generateRootRootReadme(targetInfos);
|
|
567
|
+
const { writeGeneratedFiles: writeFiles } = await import('./output');
|
|
568
|
+
await writeFiles([{ path: rootReadme.fileName, content: rootReadme.content }], '.', [], { pruneStaleFiles: false });
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
finally {
|
|
572
|
+
for (const shared of sharedSources.values()) {
|
|
573
|
+
const keepDb = Object.values(configs).some((c) => c.db?.keepDb);
|
|
574
|
+
shared.ephemeralDb.teardown({ keepDb });
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return { results, hasError };
|
|
578
|
+
}
|
package/esm/core/index.d.ts
CHANGED
|
@@ -9,9 +9,7 @@ export { generate } from './generate';
|
|
|
9
9
|
export * from './types';
|
|
10
10
|
export * from './ast';
|
|
11
11
|
export * from './custom-ast';
|
|
12
|
-
/** @deprecated Legacy v4 query builder — use v5 ORM codegen instead */
|
|
13
12
|
export { MetaObject, QueryBuilder } from './query-builder';
|
|
14
|
-
/** @deprecated Legacy v4 meta-object utilities — v5 uses standard introspection */
|
|
15
13
|
export { convertFromMetaSchema, validateMetaObject } from './meta-object';
|
|
16
14
|
export * from './config';
|
|
17
15
|
export * from './codegen';
|
package/esm/core/index.js
CHANGED
|
@@ -11,10 +11,8 @@ export * from './types';
|
|
|
11
11
|
export * from './ast';
|
|
12
12
|
export * from './custom-ast';
|
|
13
13
|
// Query builder
|
|
14
|
-
/** @deprecated Legacy v4 query builder — use v5 ORM codegen instead */
|
|
15
14
|
export { MetaObject, QueryBuilder } from './query-builder';
|
|
16
15
|
// Meta object utilities
|
|
17
|
-
/** @deprecated Legacy v4 meta-object utilities — v5 uses standard introspection */
|
|
18
16
|
export { convertFromMetaSchema, validateMetaObject } from './meta-object';
|
|
19
17
|
// Configuration loading and resolution
|
|
20
18
|
export * from './config';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* introspection and converts it to introspection format.
|
|
6
6
|
*/
|
|
7
7
|
import { buildSchema, introspectionFromSchema } from 'graphql';
|
|
8
|
-
import {
|
|
8
|
+
import { buildSchemaSDL } from 'graphile-schema';
|
|
9
9
|
import { createDatabasePool, resolveApiSchemas, validateServicesSchemas, } from './api-schemas';
|
|
10
10
|
import { SchemaSourceError } from './types';
|
|
11
11
|
/**
|
|
@@ -45,7 +45,7 @@ export class DatabaseSchemaSource {
|
|
|
45
45
|
// Build SDL from database
|
|
46
46
|
let sdl;
|
|
47
47
|
try {
|
|
48
|
-
sdl = await
|
|
48
|
+
sdl = await buildSchemaSDL({
|
|
49
49
|
database,
|
|
50
50
|
schemas,
|
|
51
51
|
});
|
|
@@ -12,7 +12,7 @@ import { buildSchema, introspectionFromSchema } from 'graphql';
|
|
|
12
12
|
import { getPgPool } from 'pg-cache';
|
|
13
13
|
import { createEphemeralDb } from 'pgsql-client';
|
|
14
14
|
import { deployPgpm } from 'pgsql-seed';
|
|
15
|
-
import {
|
|
15
|
+
import { buildSchemaSDL } from 'graphile-schema';
|
|
16
16
|
import { resolveApiSchemas, validateServicesSchemas } from './api-schemas';
|
|
17
17
|
import { SchemaSourceError } from './types';
|
|
18
18
|
/**
|
|
@@ -98,7 +98,7 @@ export class PgpmModuleSchemaSource {
|
|
|
98
98
|
// Build SDL from the deployed database
|
|
99
99
|
let sdl;
|
|
100
100
|
try {
|
|
101
|
-
sdl = await
|
|
101
|
+
sdl = await buildSchemaSDL({
|
|
102
102
|
database: dbConfig.database,
|
|
103
103
|
schemas,
|
|
104
104
|
});
|
package/esm/core/output/index.js
CHANGED
|
@@ -29,13 +29,3 @@ export interface WriteOptions {
|
|
|
29
29
|
* @param options - Write options
|
|
30
30
|
*/
|
|
31
31
|
export declare function writeGeneratedFiles(files: GeneratedFile[], outputDir: string, subdirs: string[], options?: WriteOptions): Promise<WriteResult>;
|
|
32
|
-
/**
|
|
33
|
-
* Format generated files using oxfmt programmatically
|
|
34
|
-
*
|
|
35
|
-
* @deprecated Use writeGeneratedFiles with formatFiles option instead.
|
|
36
|
-
* This function is kept for backwards compatibility.
|
|
37
|
-
*/
|
|
38
|
-
export declare function formatOutput(outputDir: string): Promise<{
|
|
39
|
-
success: boolean;
|
|
40
|
-
error?: string;
|
|
41
|
-
}>;
|
|
@@ -168,33 +168,3 @@ function findTsFiles(dir) {
|
|
|
168
168
|
}
|
|
169
169
|
return files;
|
|
170
170
|
}
|
|
171
|
-
/**
|
|
172
|
-
* Format generated files using oxfmt programmatically
|
|
173
|
-
*
|
|
174
|
-
* @deprecated Use writeGeneratedFiles with formatFiles option instead.
|
|
175
|
-
* This function is kept for backwards compatibility.
|
|
176
|
-
*/
|
|
177
|
-
export async function formatOutput(outputDir) {
|
|
178
|
-
const formatFn = await getOxfmtFormat();
|
|
179
|
-
if (!formatFn) {
|
|
180
|
-
return {
|
|
181
|
-
success: false,
|
|
182
|
-
error: 'oxfmt not available. Install it with: npm install oxfmt',
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
const absoluteOutputDir = path.resolve(outputDir);
|
|
186
|
-
try {
|
|
187
|
-
// Find all .ts files in the output directory
|
|
188
|
-
const tsFiles = findTsFiles(absoluteOutputDir);
|
|
189
|
-
for (const filePath of tsFiles) {
|
|
190
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
191
|
-
const formatted = await formatFileContent(path.basename(filePath), content, formatFn);
|
|
192
|
-
fs.writeFileSync(filePath, formatted, 'utf-8');
|
|
193
|
-
}
|
|
194
|
-
return { success: true };
|
|
195
|
-
}
|
|
196
|
-
catch (err) {
|
|
197
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
198
|
-
return { success: false, error: message };
|
|
199
|
-
}
|
|
200
|
-
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Query and mutation generator exports
|
|
3
|
-
*
|
|
4
|
-
* @deprecated Legacy v4 generators — use v5 ORM codegen pipeline instead.
|
|
5
|
-
* These are retained for backward compatibility with existing v4 consumers.
|
|
6
3
|
*/
|
|
7
4
|
export { convertToSelectionOptions, getAvailableRelations, isRelationalField, validateFieldSelection, } from './field-selector';
|
|
8
5
|
export { buildCount, buildFindOne, buildSelect, cleanTableToMetaObject, createASTQueryBuilder, generateIntrospectionSchema, toCamelCasePlural, toOrderByTypeName, } from './select';
|
package/esm/generators/index.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Query and mutation generator exports
|
|
3
|
-
*
|
|
4
|
-
* @deprecated Legacy v4 generators — use v5 ORM codegen pipeline instead.
|
|
5
|
-
* These are retained for backward compatibility with existing v4 consumers.
|
|
6
3
|
*/
|
|
7
4
|
// Field selector utilities
|
|
8
5
|
export { convertToSelectionOptions, getAvailableRelations, isRelationalField, validateFieldSelection, } from './field-selector';
|
package/esm/index.d.ts
CHANGED
|
@@ -10,10 +10,11 @@ export * from './core';
|
|
|
10
10
|
export * from './generators';
|
|
11
11
|
export * from './client';
|
|
12
12
|
export { defineConfig } from './types/config';
|
|
13
|
-
export type { GenerateOptions, GenerateResult } from './core/generate';
|
|
14
|
-
export { generate } from './core/generate';
|
|
13
|
+
export type { GenerateOptions, GenerateResult, GenerateMultiOptions, GenerateMultiResult } from './core/generate';
|
|
14
|
+
export { generate, generateMulti, expandApiNamesToMultiTarget, expandSchemaDirToMultiTarget } from './core/generate';
|
|
15
15
|
export { findConfigFile, loadConfigFile } from './core/config';
|
|
16
|
+
export { runCodegenHandler } from './cli/handler';
|
|
16
17
|
export type { CodegenAnswers } from './cli/shared';
|
|
17
18
|
export { buildDbConfig, buildGenerateOptions, camelizeArgv, codegenQuestions, filterDefined, flattenDbFields, hasResolvedCodegenSource, hyphenateKeys, normalizeCodegenListOptions, printResult, seedArgvFromConfig, splitCommas, } from './cli/shared';
|
|
18
19
|
export type { BuildSchemaFromDatabaseOptions, BuildSchemaFromDatabaseResult, } from './core/database';
|
|
19
|
-
export { buildSchemaFromDatabase
|
|
20
|
+
export { buildSchemaFromDatabase } from './core/database';
|
package/esm/index.js
CHANGED
|
@@ -15,8 +15,10 @@ export * from './generators';
|
|
|
15
15
|
export * from './client';
|
|
16
16
|
// Config definition helper
|
|
17
17
|
export { defineConfig } from './types/config';
|
|
18
|
-
export { generate } from './core/generate';
|
|
18
|
+
export { generate, generateMulti, expandApiNamesToMultiTarget, expandSchemaDirToMultiTarget } from './core/generate';
|
|
19
19
|
// Config utilities
|
|
20
20
|
export { findConfigFile, loadConfigFile } from './core/config';
|
|
21
|
+
// CLI shared utilities (for packages/cli to import)
|
|
22
|
+
export { runCodegenHandler } from './cli/handler';
|
|
21
23
|
export { buildDbConfig, buildGenerateOptions, camelizeArgv, codegenQuestions, filterDefined, flattenDbFields, hasResolvedCodegenSource, hyphenateKeys, normalizeCodegenListOptions, printResult, seedArgvFromConfig, splitCommas, } from './cli/shared';
|
|
22
|
-
export { buildSchemaFromDatabase
|
|
24
|
+
export { buildSchemaFromDatabase } from './core/database';
|