@geekmidas/cli 1.10.28 → 1.10.29

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.
@@ -1,4 +1,4 @@
1
- const require_fullstack_secrets = require('./fullstack-secrets-CsxU84eN.cjs');
1
+ const require_fullstack_secrets = require('./fullstack-secrets-Bchl2MDd.cjs');
2
2
 
3
3
  //#region src/secrets/reconcile.ts
4
4
  /**
@@ -33,4 +33,4 @@ function reconcileMissingSecrets(secrets, workspace) {
33
33
 
34
34
  //#endregion
35
35
  exports.reconcileMissingSecrets = reconcileMissingSecrets;
36
- //# sourceMappingURL=reconcile-DF8t4HC7.cjs.map
36
+ //# sourceMappingURL=reconcile-CZtzdrnc.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile-DF8t4HC7.cjs","names":["secrets: StageSecrets","workspace: NormalizedWorkspace","addedKeys: string[]"],"sources":["../src/secrets/reconcile.ts"],"sourcesContent":["import { generateFullstackCustomSecrets } from '../setup/fullstack-secrets.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface ReconcileResult {\n\t/** The updated secrets with missing keys backfilled */\n\tsecrets: StageSecrets;\n\t/** Keys that were added */\n\taddedKeys: string[];\n}\n\n/**\n * Reconcile missing custom secrets for a workspace.\n *\n * Compares current secrets against what generateFullstackCustomSecrets()\n * would produce and backfills any missing keys without overwriting\n * existing values.\n *\n * @returns ReconcileResult if keys were added, null if secrets are up-to-date\n */\nexport function reconcileMissingSecrets(\n\tsecrets: StageSecrets,\n\tworkspace: NormalizedWorkspace,\n): ReconcileResult | null {\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\tif (!isMultiApp) {\n\t\treturn null;\n\t}\n\n\tconst expectedCustom = generateFullstackCustomSecrets(workspace);\n\tconst addedKeys: string[] = [];\n\tconst mergedCustom = { ...secrets.custom };\n\n\tfor (const [key, value] of Object.entries(expectedCustom)) {\n\t\tif (!(key in mergedCustom)) {\n\t\t\tmergedCustom[key] = value;\n\t\t\taddedKeys.push(key);\n\t\t}\n\t}\n\n\tif (addedKeys.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tsecrets: {\n\t\t\t...secrets,\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\tcustom: mergedCustom,\n\t\t},\n\t\taddedKeys,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,SAAgB,wBACfA,SACAC,WACyB;CACzB,MAAM,aAAa,OAAO,KAAK,UAAU,KAAK,CAAC,SAAS;AACxD,MAAK,WACJ,QAAO;CAGR,MAAM,iBAAiB,yDAA+B,UAAU;CAChE,MAAMC,YAAsB,CAAE;CAC9B,MAAM,eAAe,EAAE,GAAG,QAAQ,OAAQ;AAE1C,MAAK,MAAM,CAAC,KAAK,MAAM,IAAI,OAAO,QAAQ,eAAe,CACxD,OAAM,OAAO,eAAe;AAC3B,eAAa,OAAO;AACpB,YAAU,KAAK,IAAI;CACnB;AAGF,KAAI,UAAU,WAAW,EACxB,QAAO;AAGR,QAAO;EACN,SAAS;GACR,GAAG;GACH,WAAW,qBAAI,QAAO,aAAa;GACnC,QAAQ;EACR;EACD;CACA;AACD"}
1
+ {"version":3,"file":"reconcile-CZtzdrnc.cjs","names":["secrets: StageSecrets","workspace: NormalizedWorkspace","addedKeys: string[]"],"sources":["../src/secrets/reconcile.ts"],"sourcesContent":["import { generateFullstackCustomSecrets } from '../setup/fullstack-secrets.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface ReconcileResult {\n\t/** The updated secrets with missing keys backfilled */\n\tsecrets: StageSecrets;\n\t/** Keys that were added */\n\taddedKeys: string[];\n}\n\n/**\n * Reconcile missing custom secrets for a workspace.\n *\n * Compares current secrets against what generateFullstackCustomSecrets()\n * would produce and backfills any missing keys without overwriting\n * existing values.\n *\n * @returns ReconcileResult if keys were added, null if secrets are up-to-date\n */\nexport function reconcileMissingSecrets(\n\tsecrets: StageSecrets,\n\tworkspace: NormalizedWorkspace,\n): ReconcileResult | null {\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\tif (!isMultiApp) {\n\t\treturn null;\n\t}\n\n\tconst expectedCustom = generateFullstackCustomSecrets(workspace);\n\tconst addedKeys: string[] = [];\n\tconst mergedCustom = { ...secrets.custom };\n\n\tfor (const [key, value] of Object.entries(expectedCustom)) {\n\t\tif (!(key in mergedCustom)) {\n\t\t\tmergedCustom[key] = value;\n\t\t\taddedKeys.push(key);\n\t\t}\n\t}\n\n\tif (addedKeys.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tsecrets: {\n\t\t\t...secrets,\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\tcustom: mergedCustom,\n\t\t},\n\t\taddedKeys,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,SAAgB,wBACfA,SACAC,WACyB;CACzB,MAAM,aAAa,OAAO,KAAK,UAAU,KAAK,CAAC,SAAS;AACxD,MAAK,WACJ,QAAO;CAGR,MAAM,iBAAiB,yDAA+B,UAAU;CAChE,MAAMC,YAAsB,CAAE;CAC9B,MAAM,eAAe,EAAE,GAAG,QAAQ,OAAQ;AAE1C,MAAK,MAAM,CAAC,KAAK,MAAM,IAAI,OAAO,QAAQ,eAAe,CACxD,OAAM,OAAO,eAAe;AAC3B,eAAa,OAAO;AACpB,YAAU,KAAK,IAAI;CACnB;AAGF,KAAI,UAAU,WAAW,EACxB,QAAO;AAGR,QAAO;EACN,SAAS;GACR,GAAG;GACH,WAAW,qBAAI,QAAO,aAAa;GACnC,QAAQ;EACR;EACD;CACA;AACD"}
@@ -1,4 +1,4 @@
1
- import { generateFullstackCustomSecrets } from "./fullstack-secrets-BDE7Sk93.mjs";
1
+ import { generateFullstackCustomSecrets } from "./fullstack-secrets-P84v8zWn.mjs";
2
2
 
3
3
  //#region src/secrets/reconcile.ts
4
4
  /**
@@ -33,4 +33,4 @@ function reconcileMissingSecrets(secrets, workspace) {
33
33
 
34
34
  //#endregion
35
35
  export { reconcileMissingSecrets };
36
- //# sourceMappingURL=reconcile-BjWhjfSp.mjs.map
36
+ //# sourceMappingURL=reconcile-DAAKa0YR.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"reconcile-BjWhjfSp.mjs","names":["secrets: StageSecrets","workspace: NormalizedWorkspace","addedKeys: string[]"],"sources":["../src/secrets/reconcile.ts"],"sourcesContent":["import { generateFullstackCustomSecrets } from '../setup/fullstack-secrets.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface ReconcileResult {\n\t/** The updated secrets with missing keys backfilled */\n\tsecrets: StageSecrets;\n\t/** Keys that were added */\n\taddedKeys: string[];\n}\n\n/**\n * Reconcile missing custom secrets for a workspace.\n *\n * Compares current secrets against what generateFullstackCustomSecrets()\n * would produce and backfills any missing keys without overwriting\n * existing values.\n *\n * @returns ReconcileResult if keys were added, null if secrets are up-to-date\n */\nexport function reconcileMissingSecrets(\n\tsecrets: StageSecrets,\n\tworkspace: NormalizedWorkspace,\n): ReconcileResult | null {\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\tif (!isMultiApp) {\n\t\treturn null;\n\t}\n\n\tconst expectedCustom = generateFullstackCustomSecrets(workspace);\n\tconst addedKeys: string[] = [];\n\tconst mergedCustom = { ...secrets.custom };\n\n\tfor (const [key, value] of Object.entries(expectedCustom)) {\n\t\tif (!(key in mergedCustom)) {\n\t\t\tmergedCustom[key] = value;\n\t\t\taddedKeys.push(key);\n\t\t}\n\t}\n\n\tif (addedKeys.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tsecrets: {\n\t\t\t...secrets,\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\tcustom: mergedCustom,\n\t\t},\n\t\taddedKeys,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,SAAgB,wBACfA,SACAC,WACyB;CACzB,MAAM,aAAa,OAAO,KAAK,UAAU,KAAK,CAAC,SAAS;AACxD,MAAK,WACJ,QAAO;CAGR,MAAM,iBAAiB,+BAA+B,UAAU;CAChE,MAAMC,YAAsB,CAAE;CAC9B,MAAM,eAAe,EAAE,GAAG,QAAQ,OAAQ;AAE1C,MAAK,MAAM,CAAC,KAAK,MAAM,IAAI,OAAO,QAAQ,eAAe,CACxD,OAAM,OAAO,eAAe;AAC3B,eAAa,OAAO;AACpB,YAAU,KAAK,IAAI;CACnB;AAGF,KAAI,UAAU,WAAW,EACxB,QAAO;AAGR,QAAO;EACN,SAAS;GACR,GAAG;GACH,WAAW,qBAAI,QAAO,aAAa;GACnC,QAAQ;EACR;EACD;CACA;AACD"}
1
+ {"version":3,"file":"reconcile-DAAKa0YR.mjs","names":["secrets: StageSecrets","workspace: NormalizedWorkspace","addedKeys: string[]"],"sources":["../src/secrets/reconcile.ts"],"sourcesContent":["import { generateFullstackCustomSecrets } from '../setup/fullstack-secrets.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface ReconcileResult {\n\t/** The updated secrets with missing keys backfilled */\n\tsecrets: StageSecrets;\n\t/** Keys that were added */\n\taddedKeys: string[];\n}\n\n/**\n * Reconcile missing custom secrets for a workspace.\n *\n * Compares current secrets against what generateFullstackCustomSecrets()\n * would produce and backfills any missing keys without overwriting\n * existing values.\n *\n * @returns ReconcileResult if keys were added, null if secrets are up-to-date\n */\nexport function reconcileMissingSecrets(\n\tsecrets: StageSecrets,\n\tworkspace: NormalizedWorkspace,\n): ReconcileResult | null {\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\tif (!isMultiApp) {\n\t\treturn null;\n\t}\n\n\tconst expectedCustom = generateFullstackCustomSecrets(workspace);\n\tconst addedKeys: string[] = [];\n\tconst mergedCustom = { ...secrets.custom };\n\n\tfor (const [key, value] of Object.entries(expectedCustom)) {\n\t\tif (!(key in mergedCustom)) {\n\t\t\tmergedCustom[key] = value;\n\t\t\taddedKeys.push(key);\n\t\t}\n\t}\n\n\tif (addedKeys.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tsecrets: {\n\t\t\t...secrets,\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\tcustom: mergedCustom,\n\t\t},\n\t\taddedKeys,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;AAoBA,SAAgB,wBACfA,SACAC,WACyB;CACzB,MAAM,aAAa,OAAO,KAAK,UAAU,KAAK,CAAC,SAAS;AACxD,MAAK,WACJ,QAAO;CAGR,MAAM,iBAAiB,+BAA+B,UAAU;CAChE,MAAMC,YAAsB,CAAE;CAC9B,MAAM,eAAe,EAAE,GAAG,QAAQ,OAAQ;AAE1C,MAAK,MAAM,CAAC,KAAK,MAAM,IAAI,OAAO,QAAQ,eAAe,CACxD,OAAM,OAAO,eAAe;AAC3B,eAAa,OAAO;AACpB,YAAU,KAAK,IAAI;CACnB;AAGF,KAAI,UAAU,WAAW,EACxB,QAAO;AAGR,QAAO;EACN,SAAS;GACR,GAAG;GACH,WAAW,qBAAI,QAAO,aAAa;GACnC,QAAQ;EACR;EACD;CACA;AACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/cli",
3
- "version": "1.10.28",
3
+ "version": "1.10.29",
4
4
  "description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
5
5
  "private": false,
6
6
  "type": "module",
@@ -56,11 +56,11 @@
56
56
  "prompts": "~2.4.2",
57
57
  "tsx": "~4.20.3",
58
58
  "yaml": "~2.8.2",
59
- "@geekmidas/constructs": "~3.0.5",
60
- "@geekmidas/errors": "~1.0.0",
59
+ "@geekmidas/constructs": "~3.0.6",
61
60
  "@geekmidas/envkit": "~1.0.4",
62
- "@geekmidas/schema": "~1.0.0",
63
- "@geekmidas/logger": "~1.0.0"
61
+ "@geekmidas/errors": "~1.0.0",
62
+ "@geekmidas/logger": "~1.0.0",
63
+ "@geekmidas/schema": "~1.0.0"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/lodash.kebabcase": "^4.1.9",
@@ -814,13 +814,15 @@ describe('generateTestFiles', () => {
814
814
  expect(configFile!.content).toContain('~/services/database.ts');
815
815
  });
816
816
 
817
- it('should use PostgresKyselyMigrator in globalSetup', () => {
817
+ it('should use PostgresKyselyMigrator with runInitScript in globalSetup', () => {
818
818
  const files = generateTestFiles(baseOptions, minimalTemplate);
819
819
  const setupFile = files.find((f) => f.path === 'test/globalSetup.ts');
820
820
  expect(setupFile).toBeDefined();
821
821
  expect(setupFile!.content).toContain('PostgresKyselyMigrator');
822
- expect(setupFile!.content).toContain('_test');
823
- expect(setupFile!.content).toContain('migrateToLatest');
822
+ expect(setupFile!.content).toContain('runInitScript');
823
+ expect(setupFile!.content).toContain('afterCreate');
824
+ expect(setupFile!.content).toContain('Credentials');
825
+ expect(setupFile!.content).toContain('PGBOSS_DB_PASSWORD');
824
826
  });
825
827
 
826
828
  it('should use KyselyFactory in factory files', () => {
@@ -38,6 +38,7 @@ export function generatePackageJson(
38
38
  devDependencies['@geekmidas/testkit'] =
39
39
  GEEKMIDAS_VERSIONS['@geekmidas/testkit'];
40
40
  devDependencies['@faker-js/faker'] = '~9.8.0';
41
+ devDependencies['kysely-ctl'] = '~0.10.0';
41
42
  devDependencies['vite-tsconfig-paths'] = '~5.1.0';
42
43
  }
43
44
 
@@ -18,6 +18,31 @@ export function generateTestFiles(
18
18
  }
19
19
 
20
20
  return [
21
+ // kysely.config.ts - Kysely CLI configuration for migrations
22
+ {
23
+ path: 'kysely.config.ts',
24
+ content: `import { Credentials } from '@geekmidas/envkit/credentials';
25
+ import { PostgresDialect } from 'kysely';
26
+ import { defineConfig } from 'kysely-ctl';
27
+ import pg from 'pg';
28
+
29
+ export default defineConfig({
30
+ dialect: new PostgresDialect({
31
+ pool: new pg.Pool({
32
+ password: Credentials.POSTGRES_PASSWORD,
33
+ user: Credentials.POSTGRES_USER,
34
+ database: Credentials.POSTGRES_DB,
35
+ port: Number(Credentials.POSTGRES_PORT),
36
+ host: Credentials.POSTGRES_HOST,
37
+ }),
38
+ }),
39
+ migrations: {
40
+ migrationFolder: './src/db/migrations',
41
+ },
42
+ });
43
+ `,
44
+ },
45
+
21
46
  // test/config.ts - Wraps vitest `it` with transaction auto-rollback
22
47
  {
23
48
  path: 'test/config.ts',
@@ -39,36 +64,55 @@ export const it = wrapVitestKyselyTransaction<Database>(itVitest, {
39
64
  `,
40
65
  },
41
66
 
42
- // test/globalSetup.ts - Runs migrations on the test database
43
- // Note: gkm test automatically rewrites DATABASE_URL to use a _test
44
- // suffixed database and creates it if needed. This setup only runs
45
- // migrations.
67
+ // test/globalSetup.ts - Creates test database, provisions users, runs migrations
46
68
  {
47
69
  path: 'test/globalSetup.ts',
48
- content: `import { Kysely, PostgresDialect } from 'kysely';
49
- import pg from 'pg';
70
+ content: `import fs from 'node:fs/promises';
71
+ import path from 'node:path';
72
+ import { Credentials } from '@geekmidas/envkit/credentials';
50
73
  import { PostgresKyselyMigrator } from '@geekmidas/testkit/kysely';
51
- import type { Database } from '~/services/database.ts';
74
+ import { runInitScript } from '@geekmidas/testkit/postgres';
75
+ import { FileMigrationProvider, Kysely, PostgresDialect } from 'kysely';
76
+ import pg from 'pg';
52
77
 
53
- export async function setup() {
54
- const testUrl = process.env.DATABASE_URL;
55
- if (!testUrl) throw new Error('DATABASE_URL is required for tests');
78
+ export default async function globalSetup() {
79
+ const databaseUrl = Credentials.DATABASE_URL!;
80
+ const migrationFolder = path.resolve(import.meta.dirname, '../db/migrations');
56
81
 
57
- // Run migrations on the test database
58
- // (gkm test already rewrites DATABASE_URL to point to the _test database)
59
- const db = new Kysely<Database>({
82
+ const db = new Kysely({
60
83
  dialect: new PostgresDialect({
61
- pool: new pg.Pool({ connectionString: testUrl }),
84
+ pool: new pg.Pool({ connectionString: databaseUrl }),
62
85
  }),
63
86
  });
64
87
 
65
88
  const migrator = new PostgresKyselyMigrator({
89
+ uri: databaseUrl,
66
90
  db,
67
- migrationsPath: './src/migrations',
91
+ afterCreate: async (uri) => {
92
+ const initScriptPath = path.resolve(process.cwd(), 'docker/postgres/init.sh');
93
+ const dbUrl = new URL(Credentials.DATABASE_URL!);
94
+ const apiUrl = new URL(Credentials.API_DATABASE_URL!);
95
+ const authUrl = new URL(Credentials.AUTH_DATABASE_URL!);
96
+
97
+ const env = {
98
+ POSTGRES_USER: dbUrl.username,
99
+ POSTGRES_DB: dbUrl.pathname.slice(1),
100
+ API_DB_PASSWORD: decodeURIComponent(apiUrl.password),
101
+ AUTH_DB_PASSWORD: decodeURIComponent(authUrl.password),
102
+ PGBOSS_DB_PASSWORD: Credentials.PGBOSS_DB_PASSWORD ?? 'pgboss-dev-password',
103
+ };
104
+
105
+ await runInitScript(initScriptPath, uri, env);
106
+ },
107
+ provider: new FileMigrationProvider({
108
+ fs,
109
+ path,
110
+ migrationFolder,
111
+ }),
68
112
  });
69
113
 
70
- await migrator.migrateToLatest();
71
- await db.destroy();
114
+ const teardown = await migrator.start();
115
+ return teardown;
72
116
  }
73
117
  `,
74
118
  },
@@ -107,6 +107,17 @@ export async function writeDockerEnvFromSecrets(
107
107
  key.endsWith('_DB_PASSWORD'),
108
108
  );
109
109
 
110
+ // Always include pgboss password when pgboss credentials exist
111
+ if (
112
+ secrets.services.pgboss &&
113
+ !dbPasswordEntries.some(([key]) => key === 'PGBOSS_DB_PASSWORD')
114
+ ) {
115
+ dbPasswordEntries.push([
116
+ 'PGBOSS_DB_PASSWORD',
117
+ secrets.services.pgboss.password,
118
+ ]);
119
+ }
120
+
110
121
  if (dbPasswordEntries.length === 0) {
111
122
  return;
112
123
  }