From 4b7979678797a43a9cfe900d45c82e2f7b6f6b13 Mon Sep 17 00:00:00 2001 From: Dan Lynch Date: Tue, 4 Aug 2026 01:05:44 +0000 Subject: [PATCH] feat(codegen): cli.stashName so generated CLIs can share one signed-in state Also bumps genomic to ^5.6.4 so the workspace resolves a single inquirerer (4.9.3). --- .../__tests__/codegen/cli-generator.test.ts | 43 +++++++++++++++++++ .../core/codegen/cli/executor-generator.ts | 27 +++++++++--- .../src/core/codegen/cli/helpers-generator.ts | 15 +++++-- graphql/codegen/src/core/codegen/cli/index.ts | 13 ++++-- graphql/codegen/src/core/generate.ts | 1 + graphql/codegen/src/types/config.ts | 9 ++++ pgpm/cli/package.json | 2 +- pgpm/core/package.json | 2 +- pnpm-lock.yaml | 28 ++++-------- 9 files changed, 104 insertions(+), 36 deletions(-) diff --git a/graphql/codegen/src/__tests__/codegen/cli-generator.test.ts b/graphql/codegen/src/__tests__/codegen/cli-generator.test.ts index 16e6e82622..5fd6e84ffa 100644 --- a/graphql/codegen/src/__tests__/codegen/cli-generator.test.ts +++ b/graphql/codegen/src/__tests__/codegen/cli-generator.test.ts @@ -719,3 +719,46 @@ describe('multi-target cli with custom builtinNames', () => { expect(fileNames).not.toContain('commands/context.ts'); }); }); + +describe('cli stashName', () => { + it('omits stashName from the store call when not configured', () => { + const single = generateCli({ + tables: [carTable], + customOperations: { queries: [], mutations: [] }, + config: { cli: { toolName: 'myapp' } }, + }); + const executor = single.files.find((f) => f.fileName === 'executor.ts'); + expect(executor!.content).toContain('createConfigStore("myapp")'); + }); + + it('passes stashName to the store while keeping toolName', () => { + const single = generateCli({ + tables: [carTable], + customOperations: { queries: [], mutations: [] }, + config: { cli: { toolName: 'myapp', stashName: 'shared' } }, + }); + const executor = single.files.find((f) => f.fileName === 'executor.ts'); + expect(executor!.content).toContain('createConfigStore("myapp", {'); + expect(executor!.content).toContain('stashName: "shared"'); + }); + + it('passes stashName to multi-target executor and helpers', () => { + const multi = generateMultiTargetCli({ + toolName: 'myapp', + stashName: 'shared', + targets: [ + { + name: 'app', + endpoint: 'http://app.localhost/graphql', + ormImportPath: '../../generated/app/orm', + tables: [carTable], + customOperations: { queries: [], mutations: [] }, + }, + ], + }); + for (const fileName of ['executor.ts', 'helpers.ts']) { + const file = multi.files.find((f) => f.fileName === fileName); + expect(file!.content).toContain('stashName: "shared"'); + } + }); +}); diff --git a/graphql/codegen/src/core/codegen/cli/executor-generator.ts b/graphql/codegen/src/core/codegen/cli/executor-generator.ts index a4cc2e0ede..15b18bcefe 100644 --- a/graphql/codegen/src/core/codegen/cli/executor-generator.ts +++ b/graphql/codegen/src/core/codegen/cli/executor-generator.ts @@ -30,7 +30,23 @@ function createImportDeclaration( return decl; } -export function generateExecutorFile(toolName: string): GeneratedFile { +/** + * `createConfigStore(toolName)`, or `createConfigStore(toolName, { stashName })` + * when the CLI shares its signed-in state with other tools of the same product. + */ +function createStoreCall(toolName: string, stashName?: string): t.CallExpression { + const args: t.Expression[] = [t.stringLiteral(toolName)]; + if (stashName) { + args.push( + t.objectExpression([ + t.objectProperty(t.identifier('stashName'), t.stringLiteral(stashName)), + ]), + ); + } + return t.callExpression(t.identifier('createConfigStore'), args); +} + +export function generateExecutorFile(toolName: string, stashName?: string): GeneratedFile { const statements: t.Statement[] = []; statements.push( @@ -44,9 +60,7 @@ export function generateExecutorFile(toolName: string): GeneratedFile { t.variableDeclaration('const', [ t.variableDeclarator( t.identifier('store'), - t.callExpression(t.identifier('createConfigStore'), [ - t.stringLiteral(toolName), - ]), + createStoreCall(toolName, stashName), ), ]), ); @@ -238,6 +252,7 @@ export function generateExecutorFile(toolName: string): GeneratedFile { export function generateMultiTargetExecutorFile( toolName: string, targets: MultiTargetExecutorInput[], + stashName?: string, ): GeneratedFile { const statements: t.Statement[] = []; @@ -260,9 +275,7 @@ export function generateMultiTargetExecutorFile( t.variableDeclaration('const', [ t.variableDeclarator( t.identifier('store'), - t.callExpression(t.identifier('createConfigStore'), [ - t.stringLiteral(toolName), - ]), + createStoreCall(toolName, stashName), ), ]), ); diff --git a/graphql/codegen/src/core/codegen/cli/helpers-generator.ts b/graphql/codegen/src/core/codegen/cli/helpers-generator.ts index 1c04bab38b..9d2b40c519 100644 --- a/graphql/codegen/src/core/codegen/cli/helpers-generator.ts +++ b/graphql/codegen/src/core/codegen/cli/helpers-generator.ts @@ -37,6 +37,7 @@ export interface HelpersGeneratorInput { export function generateHelpersFile( toolName: string, targets: HelpersGeneratorInput[], + stashName?: string, ): GeneratedFile { const statements: t.Statement[] = []; @@ -61,14 +62,20 @@ export function generateHelpersFile( ); } - // const store = createConfigStore('toolName'); + // const store = createConfigStore('toolName', { stashName: 'product' }); + const storeArgs: t.Expression[] = [t.stringLiteral(toolName)]; + if (stashName) { + storeArgs.push( + t.objectExpression([ + t.objectProperty(t.identifier('stashName'), t.stringLiteral(stashName)), + ]), + ); + } statements.push( t.variableDeclaration('const', [ t.variableDeclarator( t.identifier('store'), - t.callExpression(t.identifier('createConfigStore'), [ - t.stringLiteral(toolName), - ]), + t.callExpression(t.identifier('createConfigStore'), storeArgs), ), ]), ); diff --git a/graphql/codegen/src/core/codegen/cli/index.ts b/graphql/codegen/src/core/codegen/cli/index.ts index 9bf444c25d..d6e84f5e8c 100644 --- a/graphql/codegen/src/core/codegen/cli/index.ts +++ b/graphql/codegen/src/core/codegen/cli/index.ts @@ -48,7 +48,10 @@ export function generateCli(options: GenerateCliOptions): GenerateCliResult { ? cliConfig.toolName : 'app'; - const executorFile = generateExecutorFile(toolName); + const stashName = + typeof cliConfig === 'object' ? cliConfig.stashName : undefined; + + const executorFile = generateExecutorFile(toolName, stashName); files.push(executorFile); const utilsFile = generateUtilsFile(); @@ -127,6 +130,8 @@ export interface MultiTargetCliTarget { export interface GenerateMultiTargetCliOptions { toolName: string; + /** Directory identity to share signed-in state with sibling tools. */ + stashName?: string; builtinNames?: BuiltinNames; targets: MultiTargetCliTarget[]; /** Generate a runnable index.ts entry point */ @@ -157,7 +162,7 @@ export function resolveBuiltinNames( export function generateMultiTargetCli( options: GenerateMultiTargetCliOptions, ): GenerateCliResult { - const { toolName, targets } = options; + const { toolName, stashName, targets } = options; const files: GeneratedFile[] = []; const targetNames = targets.map((t) => t.name); @@ -168,7 +173,7 @@ export function generateMultiTargetCli( endpoint: t.endpoint, ormImportPath: t.ormImportPath, })); - const executorFile = generateMultiTargetExecutorFile(toolName, executorInputs); + const executorFile = generateMultiTargetExecutorFile(toolName, executorInputs, stashName); files.push(executorFile); const utilsFile = generateUtilsFile(); @@ -201,7 +206,7 @@ export function generateMultiTargetCli( name: t.name, ormImportPath: t.ormImportPath, })); - const helpersFile = generateHelpersFile(toolName, helpersInputs); + const helpersFile = generateHelpersFile(toolName, helpersInputs, stashName); files.push(helpersFile); let totalTables = 0; diff --git a/graphql/codegen/src/core/generate.ts b/graphql/codegen/src/core/generate.ts index 2a589dcd87..a3ed3a11f5 100644 --- a/graphql/codegen/src/core/generate.ts +++ b/graphql/codegen/src/core/generate.ts @@ -747,6 +747,7 @@ export async function generateMulti( const firstTargetConfig = configs[names[0]]; const { files } = generateMultiTargetCli({ toolName, + stashName: cliConfig.stashName, builtinNames: cliConfig.builtinNames, targets: cliTargets, entryPoint: cliConfig.entryPoint, diff --git a/graphql/codegen/src/types/config.ts b/graphql/codegen/src/types/config.ts index 14261fb1bb..e2aaf3b214 100644 --- a/graphql/codegen/src/types/config.ts +++ b/graphql/codegen/src/types/config.ts @@ -212,6 +212,15 @@ export interface CliConfig { */ toolName?: string; + /** + * Directory identity for the stored contexts and credentials, when several + * tools are one product and should share a single signed-in state (e.g. a + * generated CLI, an agent CLI and a desktop app all using `constructive`). + * `toolName` still drives env-var prefixes and help text. + * @default toolName + */ + stashName?: string; + /** * Override infra command names (for collision handling) * Defaults: auth -> 'auth' (renamed to 'credentials' on collision), diff --git a/pgpm/cli/package.json b/pgpm/cli/package.json index 747647dfdf..b4fe521e33 100644 --- a/pgpm/cli/package.json +++ b/pgpm/cli/package.json @@ -58,7 +58,7 @@ "@pgsql/quotes": "^18.2.1", "appstash": "^0.7.0", "find-and-require-package-json": "^0.9.1", - "genomic": "^5.6.2", + "genomic": "^5.6.4", "inquirerer": "^4.9.3", "js-yaml": "^4.1.0", "pg-cache": "workspace:^", diff --git a/pgpm/core/package.json b/pgpm/core/package.json index 654369311b..f177ea335f 100644 --- a/pgpm/core/package.json +++ b/pgpm/core/package.json @@ -58,7 +58,7 @@ "@pgpmjs/transform": "workspace:^", "@pgpmjs/types": "workspace:^", "csv-to-pg": "workspace:^", - "genomic": "^5.6.2", + "genomic": "^5.6.4", "git-changed": "^0.3.0", "glob": "^13.0.6", "parse-package-name": "^1.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d8cea89353..d2738656e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2856,8 +2856,8 @@ importers: specifier: ^0.9.1 version: 0.9.1 genomic: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.4 + version: 5.6.4 inquirerer: specifier: ^4.9.3 version: 4.9.3 @@ -2948,8 +2948,8 @@ importers: specifier: workspace:^ version: link:../../packages/csv-to-pg/dist genomic: - specifier: ^5.6.2 - version: 5.6.2 + specifier: ^5.6.4 + version: 5.6.4 git-changed: specifier: ^0.3.0 version: 0.3.0 @@ -7940,8 +7940,8 @@ packages: resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} engines: {node: '>=18'} - genomic@5.6.2: - resolution: {integrity: sha512-y2LK1KQjeZZ4WT0DEQhjTxMNs+hsoZTclIqdnU5Xo3Ie8phDB6ynw8Sk2NLtELL7H3Q26tvZBJkckIkaSa0Lag==} + genomic@5.6.4: + resolution: {integrity: sha512-k4wUPBCMn5k7UNuYfYQ0e05yYp99hgo6gG7YToS/62Kr+2Ax4bKnXs2ZWfAB4HyyjTQ36d9Kaj4gfJquuUDcbQ==} gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} @@ -8393,9 +8393,6 @@ packages: resolution: {integrity: sha512-UjOaSel/iddGZJ5xP/Eixh6dY1XghiBw4XK13rCCIJcJfyhhoul/7KhLLUGtebEj6GDYM6Vnx/mVsjx2L/mFIA==} engines: {node: '>=12.0.0'} - inquirerer@4.9.1: - resolution: {integrity: sha512-RXgbivwNs9luseSHnjIcJ05A8VRGcdCQuLjyWWBmI1zwyorkSzCw4KyK8634a5KrbbXehSJHBE3rIrtgJJiBEQ==} - inquirerer@4.9.3: resolution: {integrity: sha512-f3iJubKDBE5Cp9NnJ0cRGt9zngbZ78b62QvrbRjwS13VX+s+fcJvcMaEv/3Es46dIyjDtFfhvGZa9/krNJnSbw==} @@ -15602,10 +15599,10 @@ snapshots: transitivePeerDependencies: - supports-color - genomic@5.6.2: + genomic@5.6.4: dependencies: - appstash: 0.7.0 - inquirerer: 4.9.1 + appstash: 0.7.1 + inquirerer: 4.9.3 gensync@1.0.0-beta.2: {} @@ -16290,13 +16287,6 @@ snapshots: transitivePeerDependencies: - '@types/node' - inquirerer@4.9.1: - dependencies: - deepmerge: 4.3.1 - find-and-require-package-json: 0.9.1 - minimist: 1.2.8 - yanse: 0.2.1 - inquirerer@4.9.3: dependencies: deepmerge: 4.3.1