@geekmidas/cli 1.10.14 → 1.10.16
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/CHANGELOG.md +12 -0
- package/dist/{bundler-Di5Gz9Ou.mjs → bundler-B4AackW5.mjs} +2 -2
- package/dist/{bundler-Di5Gz9Ou.mjs.map → bundler-B4AackW5.mjs.map} +1 -1
- package/dist/{bundler-DVJkwNMQ.cjs → bundler-BhhfkI9T.cjs} +2 -2
- package/dist/{bundler-DVJkwNMQ.cjs.map → bundler-BhhfkI9T.cjs.map} +1 -1
- package/dist/config.d.cts +2 -2
- package/dist/config.d.mts +2 -2
- package/dist/{fullstack-secrets-D9rjTNyx.cjs → fullstack-secrets-DOHBU4Rp.cjs} +110 -4
- package/dist/fullstack-secrets-DOHBU4Rp.cjs.map +1 -0
- package/dist/{fullstack-secrets-BIFFv4UZ.mjs → fullstack-secrets-x2Kffx7-.mjs} +99 -5
- package/dist/fullstack-secrets-x2Kffx7-.mjs.map +1 -0
- package/dist/{index-UCsZ_Vkw.d.cts → index-BkibYzso.d.cts} +15 -4
- package/dist/index-BkibYzso.d.cts.map +1 -0
- package/dist/{index-gXAGDSGu.d.mts → index-CY-ieuRp.d.mts} +15 -4
- package/dist/index-CY-ieuRp.d.mts.map +1 -0
- package/dist/index.cjs +326 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +326 -46
- package/dist/index.mjs.map +1 -1
- package/dist/openapi-BYxAWwok.cjs.map +1 -1
- package/dist/openapi-DenF-okj.mjs.map +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/{reconcile-DxTEausy.mjs → reconcile-BLh6rswz.mjs} +2 -2
- package/dist/{reconcile-DxTEausy.mjs.map → reconcile-BLh6rswz.mjs.map} +1 -1
- package/dist/{reconcile-LaaJkFlO.cjs → reconcile-Ch7sIcf8.cjs} +2 -2
- package/dist/{reconcile-LaaJkFlO.cjs.map → reconcile-Ch7sIcf8.cjs.map} +1 -1
- package/dist/{storage-6GBoLCYF.cjs → storage-B1wvztiJ.cjs} +14 -2
- package/dist/{storage-DMf420PP.mjs.map → storage-B1wvztiJ.cjs.map} +1 -1
- package/dist/{storage-BFqrVsip.mjs → storage-Cs4WBsc4.mjs} +1 -1
- package/dist/{storage-DCqjCiDn.cjs → storage-DOEtT2Hr.cjs} +1 -1
- package/dist/{storage-DMf420PP.mjs → storage-dbb9RyBl.mjs} +14 -2
- package/dist/{storage-6GBoLCYF.cjs.map → storage-dbb9RyBl.mjs.map} +1 -1
- package/dist/{sync-DjD_TeNX.mjs → sync-COnAugP-.mjs} +1 -1
- package/dist/sync-D1Pa30oV.cjs +4 -0
- package/dist/{sync-DIGGOxCw.cjs → sync-DGXXSk2v.cjs} +2 -2
- package/dist/{sync-DIGGOxCw.cjs.map → sync-DGXXSk2v.cjs.map} +1 -1
- package/dist/{sync-Do9O7QZ8.mjs → sync-D_NowTkZ.mjs} +2 -2
- package/dist/{sync-Do9O7QZ8.mjs.map → sync-D_NowTkZ.mjs.map} +1 -1
- package/dist/{types-JvWj5Ckc.d.cts → types-DdHfUbxk.d.cts} +13 -3
- package/dist/types-DdHfUbxk.d.cts.map +1 -0
- package/dist/{types-DiV9Mbvc.d.mts → types-OszPdw9m.d.mts} +13 -3
- package/dist/types-OszPdw9m.d.mts.map +1 -0
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace-4SP3Gx4Y.cjs.map +1 -1
- package/dist/workspace-D4z4A4cq.mjs.map +1 -1
- package/package.json +4 -4
- package/src/dev/__tests__/entry.spec.ts +3 -5
- package/src/dev/__tests__/index.spec.ts +5 -5
- package/src/dev/index.ts +10 -10
- package/src/docker/compose.ts +134 -2
- package/src/init/__tests__/generators.spec.ts +84 -0
- package/src/init/generators/docker.ts +128 -16
- package/src/init/index.ts +26 -1
- package/src/init/templates/index.ts +28 -0
- package/src/secrets/__tests__/generator.spec.ts +183 -0
- package/src/secrets/generator.ts +116 -4
- package/src/secrets/storage.ts +14 -0
- package/src/secrets/types.ts +11 -1
- package/src/setup/__tests__/reconcile-secrets.spec.ts +86 -0
- package/src/setup/index.ts +64 -1
- package/src/test/__tests__/index.spec.ts +1 -4
- package/src/types.ts +13 -1
- package/src/workspace/types.ts +13 -2
- package/dist/fullstack-secrets-BIFFv4UZ.mjs.map +0 -1
- package/dist/fullstack-secrets-D9rjTNyx.cjs.map +0 -1
- package/dist/index-UCsZ_Vkw.d.cts.map +0 -1
- package/dist/index-gXAGDSGu.d.mts.map +0 -1
- package/dist/sync-BVNso6AA.cjs +0 -4
- package/dist/types-DiV9Mbvc.d.mts.map +0 -1
- package/dist/types-JvWj5Ckc.d.cts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @geekmidas/cli
|
|
2
2
|
|
|
3
|
+
## 1.10.16
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ✨ [`9607c5e`](https://github.com/geekmidas/toolbox/commit/9607c5e6045bf0a4df3bee81437df2b3d7a34513) Thanks [@geekmidas](https://github.com/geekmidas)! - Add events support on root config
|
|
8
|
+
|
|
9
|
+
## 1.10.15
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- ✨ [`619e4e6`](https://github.com/geekmidas/toolbox/commit/619e4e6e3de73c0266008e9747d7bd735e214216) Thanks [@geekmidas](https://github.com/geekmidas)! - Add default MAIL_FROM and SMTP_SECURE
|
|
14
|
+
|
|
3
15
|
## 1.10.14
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -58,7 +58,7 @@ async function bundleServer(options) {
|
|
|
58
58
|
for (const ext of external) args.push(`--external:${ext}`);
|
|
59
59
|
let masterKey;
|
|
60
60
|
if (stage) {
|
|
61
|
-
const { readStageSecrets, toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets, writeStageSecrets } = await import("./storage-
|
|
61
|
+
const { readStageSecrets, toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets, writeStageSecrets } = await import("./storage-Cs4WBsc4.mjs");
|
|
62
62
|
const { encryptSecrets, generateDefineOptions } = await import("./encryption-a9TNMWav.mjs");
|
|
63
63
|
let secrets = await readStageSecrets(stage);
|
|
64
64
|
if (!secrets) {
|
|
@@ -132,4 +132,4 @@ async function bundleServer(options) {
|
|
|
132
132
|
|
|
133
133
|
//#endregion
|
|
134
134
|
export { bundleServer };
|
|
135
|
-
//# sourceMappingURL=bundler-
|
|
135
|
+
//# sourceMappingURL=bundler-B4AackW5.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundler-Di5Gz9Ou.mjs","names":["constructs: Construct[]","DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>>","options: BundleOptions","masterKey: string | undefined"],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\n\n/**\n * Banner to inject into ESM bundle for CJS compatibility.\n * Creates a `require` function using Node's createRequire for packages\n * that internally use CommonJS require() for Node builtins.\n */\nconst ESM_CJS_COMPAT_BANNER =\n\t'import { createRequire } from \"module\"; const require = createRequire(import.meta.url);';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\n\t/** Docker compose services configured (for auto-populating env vars) */\n\tdockerServices?: {\n\t\tpostgres?: boolean;\n\t\tredis?: boolean;\n\t\trabbitmq?: boolean;\n\t};\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * Collect all required environment variables from constructs.\n * Uses the SnifferEnvironmentParser to detect which env vars each service needs.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated array of required environment variable names\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\tconst allEnvVars = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tconst envVars = await construct.getEnvironment();\n\t\tenvVars.forEach((v) => allEnvVars.add(v));\n\t}\n\n\treturn Array.from(allEnvVars).sort();\n}\n\n/**\n * Bundle the server application using esbuild.\n * Creates a fully standalone bundle with all dependencies included.\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\n\n/** Default env var values for docker compose services */\nconst DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>> = {\n\tpostgres: {\n\t\tDATABASE_URL: 'postgresql://postgres:postgres@postgres:5432/app',\n\t},\n\tredis: {\n\t\tREDIS_URL: 'redis://redis:6379',\n\t},\n\trabbitmq: {\n\t\tRABBITMQ_URL: 'amqp://rabbitmq:5672',\n\t},\n};\n\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t\tdockerServices,\n\t} = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\t// Build command-line arguments for esbuild\n\tconst args = [\n\t\t'npx',\n\t\t'esbuild',\n\t\tentryPoint,\n\t\t'--bundle',\n\t\t'--platform=node',\n\t\t'--target=node22',\n\t\t'--format=esm',\n\t\t`--outfile=${mjsOutput}`,\n\t\t'--packages=bundle', // Bundle all dependencies for standalone output\n\t\t`--banner:js=${ESM_CJS_COMPAT_BANNER}`, // CJS compatibility for packages like pino\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages (user-specified)\n\tfor (const ext of external) {\n\t\targs.push(`--external:${ext}`);\n\t}\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst {\n\t\t\treadStageSecrets,\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t\tinitStageSecrets,\n\t\t\twriteStageSecrets,\n\t\t} = await import('../secrets/storage');\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\tlet secrets = await readStageSecrets(stage);\n\n\t\tif (!secrets) {\n\t\t\t// Auto-initialize secrets for the stage\n\t\t\tconsole.log(` Initializing secrets for stage \"${stage}\"...`);\n\t\t\tsecrets = initStageSecrets(stage);\n\t\t\tawait writeStageSecrets(secrets);\n\t\t\tconsole.log(` ✓ Created .gkm/secrets/${stage}.json`);\n\t\t}\n\n\t\t// Auto-populate env vars from docker compose services\n\t\tif (dockerServices) {\n\t\t\tfor (const [service, enabled] of Object.entries(dockerServices)) {\n\t\t\t\tif (enabled && DOCKER_SERVICE_ENV_VARS[service]) {\n\t\t\t\t\tfor (const [envVar, defaultValue] of Object.entries(\n\t\t\t\t\t\tDOCKER_SERVICE_ENV_VARS[service],\n\t\t\t\t\t)) {\n\t\t\t\t\t\t// Check if not already in urls or custom\n\t\t\t\t\t\tconst urlKey = envVar as keyof typeof secrets.urls;\n\t\t\t\t\t\tif (!secrets.urls[urlKey] && !secrets.custom[envVar]) {\n\t\t\t\t\t\t\tsecrets.urls[urlKey] = defaultValue;\n\t\t\t\t\t\t\tconsole.log(` Auto-populated ${envVar} from docker compose`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to .gkm/secrets/${stage}.json using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection using esbuild's --define:KEY=VALUE format\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\targs.push(`--define:${key}=${JSON.stringify(value)}`);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t}\n\n\ttry {\n\t\t// Run esbuild with command-line arguments\n\t\tconst [cmd, ...cmdArgs] = args as [string, ...string[]];\n\t\tconst result = spawnSync(cmd, cmdArgs, {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t\tshell: process.platform === 'win32', // Only use shell on Windows for npx resolution\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t\tif (result.status !== 0) {\n\t\t\tthrow new Error(`esbuild exited with code ${result.status}`);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bundler-B4AackW5.mjs","names":["constructs: Construct[]","DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>>","options: BundleOptions","masterKey: string | undefined"],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\n\n/**\n * Banner to inject into ESM bundle for CJS compatibility.\n * Creates a `require` function using Node's createRequire for packages\n * that internally use CommonJS require() for Node builtins.\n */\nconst ESM_CJS_COMPAT_BANNER =\n\t'import { createRequire } from \"module\"; const require = createRequire(import.meta.url);';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\n\t/** Docker compose services configured (for auto-populating env vars) */\n\tdockerServices?: {\n\t\tpostgres?: boolean;\n\t\tredis?: boolean;\n\t\trabbitmq?: boolean;\n\t};\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * Collect all required environment variables from constructs.\n * Uses the SnifferEnvironmentParser to detect which env vars each service needs.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated array of required environment variable names\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\tconst allEnvVars = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tconst envVars = await construct.getEnvironment();\n\t\tenvVars.forEach((v) => allEnvVars.add(v));\n\t}\n\n\treturn Array.from(allEnvVars).sort();\n}\n\n/**\n * Bundle the server application using esbuild.\n * Creates a fully standalone bundle with all dependencies included.\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\n\n/** Default env var values for docker compose services */\nconst DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>> = {\n\tpostgres: {\n\t\tDATABASE_URL: 'postgresql://postgres:postgres@postgres:5432/app',\n\t},\n\tredis: {\n\t\tREDIS_URL: 'redis://redis:6379',\n\t},\n\trabbitmq: {\n\t\tRABBITMQ_URL: 'amqp://rabbitmq:5672',\n\t},\n};\n\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t\tdockerServices,\n\t} = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\t// Build command-line arguments for esbuild\n\tconst args = [\n\t\t'npx',\n\t\t'esbuild',\n\t\tentryPoint,\n\t\t'--bundle',\n\t\t'--platform=node',\n\t\t'--target=node22',\n\t\t'--format=esm',\n\t\t`--outfile=${mjsOutput}`,\n\t\t'--packages=bundle', // Bundle all dependencies for standalone output\n\t\t`--banner:js=${ESM_CJS_COMPAT_BANNER}`, // CJS compatibility for packages like pino\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages (user-specified)\n\tfor (const ext of external) {\n\t\targs.push(`--external:${ext}`);\n\t}\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst {\n\t\t\treadStageSecrets,\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t\tinitStageSecrets,\n\t\t\twriteStageSecrets,\n\t\t} = await import('../secrets/storage');\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\tlet secrets = await readStageSecrets(stage);\n\n\t\tif (!secrets) {\n\t\t\t// Auto-initialize secrets for the stage\n\t\t\tconsole.log(` Initializing secrets for stage \"${stage}\"...`);\n\t\t\tsecrets = initStageSecrets(stage);\n\t\t\tawait writeStageSecrets(secrets);\n\t\t\tconsole.log(` ✓ Created .gkm/secrets/${stage}.json`);\n\t\t}\n\n\t\t// Auto-populate env vars from docker compose services\n\t\tif (dockerServices) {\n\t\t\tfor (const [service, enabled] of Object.entries(dockerServices)) {\n\t\t\t\tif (enabled && DOCKER_SERVICE_ENV_VARS[service]) {\n\t\t\t\t\tfor (const [envVar, defaultValue] of Object.entries(\n\t\t\t\t\t\tDOCKER_SERVICE_ENV_VARS[service],\n\t\t\t\t\t)) {\n\t\t\t\t\t\t// Check if not already in urls or custom\n\t\t\t\t\t\tconst urlKey = envVar as keyof typeof secrets.urls;\n\t\t\t\t\t\tif (!secrets.urls[urlKey] && !secrets.custom[envVar]) {\n\t\t\t\t\t\t\tsecrets.urls[urlKey] = defaultValue;\n\t\t\t\t\t\t\tconsole.log(` Auto-populated ${envVar} from docker compose`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to .gkm/secrets/${stage}.json using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection using esbuild's --define:KEY=VALUE format\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\targs.push(`--define:${key}=${JSON.stringify(value)}`);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t}\n\n\ttry {\n\t\t// Run esbuild with command-line arguments\n\t\tconst [cmd, ...cmdArgs] = args as [string, ...string[]];\n\t\tconst result = spawnSync(cmd, cmdArgs, {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t\tshell: process.platform === 'win32', // Only use shell on Windows for npx resolution\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t\tif (result.status !== 0) {\n\t\t\tthrow new Error(`esbuild exited with code ${result.status}`);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -59,7 +59,7 @@ async function bundleServer(options) {
|
|
|
59
59
|
for (const ext of external) args.push(`--external:${ext}`);
|
|
60
60
|
let masterKey;
|
|
61
61
|
if (stage) {
|
|
62
|
-
const { readStageSecrets, toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets, writeStageSecrets } = await Promise.resolve().then(() => require("./storage-
|
|
62
|
+
const { readStageSecrets, toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets, writeStageSecrets } = await Promise.resolve().then(() => require("./storage-DOEtT2Hr.cjs"));
|
|
63
63
|
const { encryptSecrets, generateDefineOptions } = await Promise.resolve().then(() => require("./encryption-Cv3zips0.cjs"));
|
|
64
64
|
let secrets = await readStageSecrets(stage);
|
|
65
65
|
if (!secrets) {
|
|
@@ -133,4 +133,4 @@ async function bundleServer(options) {
|
|
|
133
133
|
|
|
134
134
|
//#endregion
|
|
135
135
|
exports.bundleServer = bundleServer;
|
|
136
|
-
//# sourceMappingURL=bundler-
|
|
136
|
+
//# sourceMappingURL=bundler-BhhfkI9T.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundler-DVJkwNMQ.cjs","names":["constructs: Construct[]","DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>>","options: BundleOptions","masterKey: string | undefined"],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\n\n/**\n * Banner to inject into ESM bundle for CJS compatibility.\n * Creates a `require` function using Node's createRequire for packages\n * that internally use CommonJS require() for Node builtins.\n */\nconst ESM_CJS_COMPAT_BANNER =\n\t'import { createRequire } from \"module\"; const require = createRequire(import.meta.url);';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\n\t/** Docker compose services configured (for auto-populating env vars) */\n\tdockerServices?: {\n\t\tpostgres?: boolean;\n\t\tredis?: boolean;\n\t\trabbitmq?: boolean;\n\t};\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * Collect all required environment variables from constructs.\n * Uses the SnifferEnvironmentParser to detect which env vars each service needs.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated array of required environment variable names\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\tconst allEnvVars = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tconst envVars = await construct.getEnvironment();\n\t\tenvVars.forEach((v) => allEnvVars.add(v));\n\t}\n\n\treturn Array.from(allEnvVars).sort();\n}\n\n/**\n * Bundle the server application using esbuild.\n * Creates a fully standalone bundle with all dependencies included.\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\n\n/** Default env var values for docker compose services */\nconst DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>> = {\n\tpostgres: {\n\t\tDATABASE_URL: 'postgresql://postgres:postgres@postgres:5432/app',\n\t},\n\tredis: {\n\t\tREDIS_URL: 'redis://redis:6379',\n\t},\n\trabbitmq: {\n\t\tRABBITMQ_URL: 'amqp://rabbitmq:5672',\n\t},\n};\n\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t\tdockerServices,\n\t} = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\t// Build command-line arguments for esbuild\n\tconst args = [\n\t\t'npx',\n\t\t'esbuild',\n\t\tentryPoint,\n\t\t'--bundle',\n\t\t'--platform=node',\n\t\t'--target=node22',\n\t\t'--format=esm',\n\t\t`--outfile=${mjsOutput}`,\n\t\t'--packages=bundle', // Bundle all dependencies for standalone output\n\t\t`--banner:js=${ESM_CJS_COMPAT_BANNER}`, // CJS compatibility for packages like pino\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages (user-specified)\n\tfor (const ext of external) {\n\t\targs.push(`--external:${ext}`);\n\t}\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst {\n\t\t\treadStageSecrets,\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t\tinitStageSecrets,\n\t\t\twriteStageSecrets,\n\t\t} = await import('../secrets/storage');\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\tlet secrets = await readStageSecrets(stage);\n\n\t\tif (!secrets) {\n\t\t\t// Auto-initialize secrets for the stage\n\t\t\tconsole.log(` Initializing secrets for stage \"${stage}\"...`);\n\t\t\tsecrets = initStageSecrets(stage);\n\t\t\tawait writeStageSecrets(secrets);\n\t\t\tconsole.log(` ✓ Created .gkm/secrets/${stage}.json`);\n\t\t}\n\n\t\t// Auto-populate env vars from docker compose services\n\t\tif (dockerServices) {\n\t\t\tfor (const [service, enabled] of Object.entries(dockerServices)) {\n\t\t\t\tif (enabled && DOCKER_SERVICE_ENV_VARS[service]) {\n\t\t\t\t\tfor (const [envVar, defaultValue] of Object.entries(\n\t\t\t\t\t\tDOCKER_SERVICE_ENV_VARS[service],\n\t\t\t\t\t)) {\n\t\t\t\t\t\t// Check if not already in urls or custom\n\t\t\t\t\t\tconst urlKey = envVar as keyof typeof secrets.urls;\n\t\t\t\t\t\tif (!secrets.urls[urlKey] && !secrets.custom[envVar]) {\n\t\t\t\t\t\t\tsecrets.urls[urlKey] = defaultValue;\n\t\t\t\t\t\t\tconsole.log(` Auto-populated ${envVar} from docker compose`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to .gkm/secrets/${stage}.json using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection using esbuild's --define:KEY=VALUE format\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\targs.push(`--define:${key}=${JSON.stringify(value)}`);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t}\n\n\ttry {\n\t\t// Run esbuild with command-line arguments\n\t\tconst [cmd, ...cmdArgs] = args as [string, ...string[]];\n\t\tconst result = spawnSync(cmd, cmdArgs, {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t\tshell: process.platform === 'win32', // Only use shell on Windows for npx resolution\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t\tif (result.status !== 0) {\n\t\t\tthrow new Error(`esbuild exited with code ${result.status}`);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bundler-BhhfkI9T.cjs","names":["constructs: Construct[]","DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>>","options: BundleOptions","masterKey: string | undefined"],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\n\n/**\n * Banner to inject into ESM bundle for CJS compatibility.\n * Creates a `require` function using Node's createRequire for packages\n * that internally use CommonJS require() for Node builtins.\n */\nconst ESM_CJS_COMPAT_BANNER =\n\t'import { createRequire } from \"module\"; const require = createRequire(import.meta.url);';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\n\t/** Docker compose services configured (for auto-populating env vars) */\n\tdockerServices?: {\n\t\tpostgres?: boolean;\n\t\tredis?: boolean;\n\t\trabbitmq?: boolean;\n\t};\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * Collect all required environment variables from constructs.\n * Uses the SnifferEnvironmentParser to detect which env vars each service needs.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated array of required environment variable names\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\tconst allEnvVars = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tconst envVars = await construct.getEnvironment();\n\t\tenvVars.forEach((v) => allEnvVars.add(v));\n\t}\n\n\treturn Array.from(allEnvVars).sort();\n}\n\n/**\n * Bundle the server application using esbuild.\n * Creates a fully standalone bundle with all dependencies included.\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\n\n/** Default env var values for docker compose services */\nconst DOCKER_SERVICE_ENV_VARS: Record<string, Record<string, string>> = {\n\tpostgres: {\n\t\tDATABASE_URL: 'postgresql://postgres:postgres@postgres:5432/app',\n\t},\n\tredis: {\n\t\tREDIS_URL: 'redis://redis:6379',\n\t},\n\trabbitmq: {\n\t\tRABBITMQ_URL: 'amqp://rabbitmq:5672',\n\t},\n};\n\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t\tdockerServices,\n\t} = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\t// Build command-line arguments for esbuild\n\tconst args = [\n\t\t'npx',\n\t\t'esbuild',\n\t\tentryPoint,\n\t\t'--bundle',\n\t\t'--platform=node',\n\t\t'--target=node22',\n\t\t'--format=esm',\n\t\t`--outfile=${mjsOutput}`,\n\t\t'--packages=bundle', // Bundle all dependencies for standalone output\n\t\t`--banner:js=${ESM_CJS_COMPAT_BANNER}`, // CJS compatibility for packages like pino\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages (user-specified)\n\tfor (const ext of external) {\n\t\targs.push(`--external:${ext}`);\n\t}\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst {\n\t\t\treadStageSecrets,\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t\tinitStageSecrets,\n\t\t\twriteStageSecrets,\n\t\t} = await import('../secrets/storage');\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\tlet secrets = await readStageSecrets(stage);\n\n\t\tif (!secrets) {\n\t\t\t// Auto-initialize secrets for the stage\n\t\t\tconsole.log(` Initializing secrets for stage \"${stage}\"...`);\n\t\t\tsecrets = initStageSecrets(stage);\n\t\t\tawait writeStageSecrets(secrets);\n\t\t\tconsole.log(` ✓ Created .gkm/secrets/${stage}.json`);\n\t\t}\n\n\t\t// Auto-populate env vars from docker compose services\n\t\tif (dockerServices) {\n\t\t\tfor (const [service, enabled] of Object.entries(dockerServices)) {\n\t\t\t\tif (enabled && DOCKER_SERVICE_ENV_VARS[service]) {\n\t\t\t\t\tfor (const [envVar, defaultValue] of Object.entries(\n\t\t\t\t\t\tDOCKER_SERVICE_ENV_VARS[service],\n\t\t\t\t\t)) {\n\t\t\t\t\t\t// Check if not already in urls or custom\n\t\t\t\t\t\tconst urlKey = envVar as keyof typeof secrets.urls;\n\t\t\t\t\t\tif (!secrets.urls[urlKey] && !secrets.custom[envVar]) {\n\t\t\t\t\t\t\tsecrets.urls[urlKey] = defaultValue;\n\t\t\t\t\t\t\tconsole.log(` Auto-populated ${envVar} from docker compose`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to .gkm/secrets/${stage}.json using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection using esbuild's --define:KEY=VALUE format\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\targs.push(`--define:${key}=${JSON.stringify(value)}`);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t}\n\n\ttry {\n\t\t// Run esbuild with command-line arguments\n\t\tconst [cmd, ...cmdArgs] = args as [string, ...string[]];\n\t\tconst result = spawnSync(cmd, cmdArgs, {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t\tshell: process.platform === 'win32', // Only use shell on Windows for npx resolution\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t\tif (result.status !== 0) {\n\t\t\tthrow new Error(`esbuild exited with code ${result.status}`);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":";;;;;;;;;;;AAUA,MAAM,wBACL;;;;;;;;AAuCD,eAAe,uBACdA,YACoB;CACpB,MAAM,6BAAa,IAAI;AAEvB,MAAK,MAAM,aAAa,YAAY;EACnC,MAAM,UAAU,MAAM,UAAU,gBAAgB;AAChD,UAAQ,QAAQ,CAAC,MAAM,WAAW,IAAI,EAAE,CAAC;CACzC;AAED,QAAO,MAAM,KAAK,WAAW,CAAC,MAAM;AACpC;;;;;;;;;AAWD,MAAMC,0BAAkE;CACvE,UAAU,EACT,cAAc,mDACd;CACD,OAAO,EACN,WAAW,qBACX;CACD,UAAU,EACT,cAAc,uBACd;AACD;AAED,eAAsB,aACrBC,SACwB;CACxB,MAAM,EACL,YACA,WACA,QACA,WACA,UACA,OACA,YACA,gBACA,GAAG;AAGJ,OAAM,4BAAM,WAAW,EAAE,WAAW,KAAM,EAAC;CAE3C,MAAM,YAAY,oBAAK,WAAW,aAAa;CAG/C,MAAM,OAAO;EACZ;EACA;EACA;EACA;EACA;EACA;EACA;GACC,YAAY,UAAU;EACvB;GACC,cAAc,sBAAsB;CACrC;AAED,KAAI,OACH,MAAK,KAAK,WAAW;AAGtB,KAAI,UACH,MAAK,KAAK,cAAc;AAIzB,MAAK,MAAM,OAAO,SACjB,MAAK,MAAM,aAAa,IAAI,EAAE;CAI/B,IAAIC;AAEJ,KAAI,OAAO;EACV,MAAM,EACL,kBACA,qBACA,8BACA,kBACA,mBACA,GAAG,2CAAM;EACV,MAAM,EAAE,gBAAgB,uBAAuB,GAAG,2CAAM;EAIxD,IAAI,UAAU,MAAM,iBAAiB,MAAM;AAE3C,OAAK,SAAS;AAEb,WAAQ,KAAK,oCAAoC,MAAM,MAAM;AAC7D,aAAU,iBAAiB,MAAM;AACjC,SAAM,kBAAkB,QAAQ;AAChC,WAAQ,KAAK,2BAA2B,MAAM,OAAO;EACrD;AAGD,MAAI,gBACH;QAAK,MAAM,CAAC,SAAS,QAAQ,IAAI,OAAO,QAAQ,eAAe,CAC9D,KAAI,WAAW,wBAAwB,SACtC,MAAK,MAAM,CAAC,QAAQ,aAAa,IAAI,OAAO,QAC3C,wBAAwB,SACxB,EAAE;IAEF,MAAM,SAAS;AACf,SAAK,QAAQ,KAAK,YAAY,QAAQ,OAAO,SAAS;AACrD,aAAQ,KAAK,UAAU;AACvB,aAAQ,KAAK,mBAAmB,OAAO,sBAAsB;IAC7D;GACD;EAEF;AAIF,MAAI,cAAc,WAAW,SAAS,GAAG;AACxC,WAAQ,IAAI,mDAAmD;GAC/D,MAAM,eAAe,MAAM,uBAAuB,WAAW;AAE7D,OAAI,aAAa,SAAS,GAAG;IAC5B,MAAM,aAAa,6BAA6B,cAAc,QAAQ;AAEtE,SAAK,WAAW,OAAO;KACtB,MAAM,eAAe;OACnB,2CAA2C,MAAM;MAClD;MACA,GAAG,WAAW,QAAQ,IAAI,CAAC,OAAO,MAAM,EAAE,EAAE;MAC5C;MACA;OACC,iDAAiD,MAAM;OACvD,6CAA6C,MAAM;MACpD;OACC;OACA,+CAA+C,MAAM;MACtD;MACA;MACA,GAAG,WAAW,SAAS,IAAI,CAAC,MAC3B,WAAW,QAAQ,SAAS,EAAE,IAAI,MAAM,EAAE,KAAK,MAAM,EAAE,EACvD;KACD,EAAC,KAAK,KAAK;AAEZ,WAAM,IAAI,MAAM;IAChB;AAED,YAAQ,KACN,UAAU,aAAa,OAAO,uCAC/B;GACD;EACD;EAGD,MAAM,aAAa,oBAAoB,QAAQ;EAC/C,MAAM,YAAY,eAAe,WAAW;AAC5C,cAAY,UAAU;EAGtB,MAAM,UAAU,sBAAsB,UAAU;AAChD,OAAK,MAAM,CAAC,KAAK,MAAM,IAAI,OAAO,QAAQ,QAAQ,CACjD,MAAK,MAAM,WAAW,IAAI,GAAG,KAAK,UAAU,MAAM,CAAC,EAAE;AAGtD,UAAQ,KAAK,iCAAiC,MAAM,GAAG;CACvD;AAED,KAAI;EAEH,MAAM,CAAC,KAAK,GAAG,QAAQ,GAAG;EAC1B,MAAM,SAAS,kCAAU,KAAK,SAAS;GACtC,KAAK,QAAQ,KAAK;GAClB,OAAO;GACP,OAAO,QAAQ,aAAa;EAC5B,EAAC;AAEF,MAAI,OAAO,MACV,OAAM,OAAO;AAEd,MAAI,OAAO,WAAW,EACrB,OAAM,IAAI,OAAO,2BAA2B,OAAO,OAAO;EAI3D,MAAM,EAAE,UAAU,GAAG,MAAM,OAAO;EAClC,MAAM,UAAU,MAAM,SAAS,WAAW,QAAQ;AAClD,OAAK,QAAQ,WAAW,KAAK,CAC5B,OAAM,gCAAU,YAAY,uBAAuB,QAAQ,EAAE;CAE9D,SAAQ,OAAO;AACf,QAAM,IAAI,OACR,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,gBAAgB;CAEtF;AAED,QAAO;EACN,YAAY;EACZ;CACA;AACD"}
|
package/dist/config.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { GkmConfig } from "./types-
|
|
2
|
-
import { LoadedConfig, NormalizedAppConfig, NormalizedWorkspace, WorkspaceConfig, defineWorkspace } from "./index-
|
|
1
|
+
import { GkmConfig } from "./types-DdHfUbxk.cjs";
|
|
2
|
+
import { LoadedConfig, NormalizedAppConfig, NormalizedWorkspace, WorkspaceConfig, defineWorkspace } from "./index-BkibYzso.cjs";
|
|
3
3
|
|
|
4
4
|
//#region src/config.d.ts
|
|
5
5
|
|
package/dist/config.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { GkmConfig } from "./types-
|
|
2
|
-
import { LoadedConfig, NormalizedAppConfig, NormalizedWorkspace, WorkspaceConfig, defineWorkspace } from "./index-
|
|
1
|
+
import { GkmConfig } from "./types-OszPdw9m.mjs";
|
|
2
|
+
import { LoadedConfig, NormalizedAppConfig, NormalizedWorkspace, WorkspaceConfig, defineWorkspace } from "./index-CY-ieuRp.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/config.d.ts
|
|
5
5
|
|
|
@@ -40,8 +40,21 @@ const SERVICE_DEFAULTS = {
|
|
|
40
40
|
host: "localhost",
|
|
41
41
|
port: 1025,
|
|
42
42
|
username: "app"
|
|
43
|
+
},
|
|
44
|
+
localstack: {
|
|
45
|
+
host: "localhost",
|
|
46
|
+
port: 4566,
|
|
47
|
+
username: "localstack",
|
|
48
|
+
region: "us-east-1"
|
|
43
49
|
}
|
|
44
50
|
};
|
|
51
|
+
/** Default credentials for pgboss (not a Docker service, reuses postgres) */
|
|
52
|
+
const PGBOSS_DEFAULTS = {
|
|
53
|
+
host: "localhost",
|
|
54
|
+
port: 5432,
|
|
55
|
+
username: "pgboss",
|
|
56
|
+
database: "app"
|
|
57
|
+
};
|
|
45
58
|
/**
|
|
46
59
|
* Generate credentials for a specific service.
|
|
47
60
|
*/
|
|
@@ -90,14 +103,73 @@ function generateMinioEndpoint(creds) {
|
|
|
90
103
|
return `http://${host}:${port}`;
|
|
91
104
|
}
|
|
92
105
|
/**
|
|
106
|
+
* Generate a LocalStack-compatible access key ID.
|
|
107
|
+
* Must start with 'LSIA' prefix and be at least 20 characters.
|
|
108
|
+
* @see https://docs.localstack.cloud/aws/capabilities/config/credentials/
|
|
109
|
+
*/
|
|
110
|
+
function generateLocalStackAccessKeyId() {
|
|
111
|
+
const suffix = (0, node_crypto.randomBytes)(12).toString("base64url").slice(0, 16);
|
|
112
|
+
return `LSIA${suffix}`;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Generate connection URL for pg-boss (uses PostgreSQL protocol).
|
|
116
|
+
* Format: pgboss://user:pass@host:port/db?schema=pgboss
|
|
117
|
+
*/
|
|
118
|
+
function generatePgBossUrl(creds) {
|
|
119
|
+
const { username, password, host, port, database } = creds;
|
|
120
|
+
return `pgboss://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}?schema=pgboss`;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Generate event connection strings based on the events backend.
|
|
124
|
+
*/
|
|
125
|
+
function generateEventConnectionStrings(eventsBackend, services) {
|
|
126
|
+
switch (eventsBackend) {
|
|
127
|
+
case "pgboss": {
|
|
128
|
+
const creds = services.pgboss;
|
|
129
|
+
if (!creds) throw new Error("pgboss credentials required for pgboss events");
|
|
130
|
+
const url = generatePgBossUrl(creds);
|
|
131
|
+
return {
|
|
132
|
+
publisher: url,
|
|
133
|
+
subscriber: url
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
case "sns": {
|
|
137
|
+
const creds = services.localstack;
|
|
138
|
+
if (!creds) throw new Error("localstack credentials required for sns events");
|
|
139
|
+
const endpoint = `http://${creds.host}:${creds.port}`;
|
|
140
|
+
const region = creds.region ?? "us-east-1";
|
|
141
|
+
const accessKeyId = creds.accessKeyId ?? creds.username;
|
|
142
|
+
const secretKey = encodeURIComponent(creds.password);
|
|
143
|
+
return {
|
|
144
|
+
publisher: `sns://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,
|
|
145
|
+
subscriber: `sqs://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
case "rabbitmq": {
|
|
149
|
+
const creds = services.rabbitmq;
|
|
150
|
+
if (!creds) throw new Error("rabbitmq credentials required for rabbitmq events");
|
|
151
|
+
const url = generateRabbitmqUrl(creds);
|
|
152
|
+
return {
|
|
153
|
+
publisher: url,
|
|
154
|
+
subscriber: url
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
93
160
|
* Generate connection URLs from service credentials.
|
|
94
161
|
*/
|
|
95
|
-
function generateConnectionUrls(services) {
|
|
162
|
+
function generateConnectionUrls(services, eventsBackend) {
|
|
96
163
|
const urls = {};
|
|
97
164
|
if (services.postgres) urls.DATABASE_URL = generatePostgresUrl(services.postgres);
|
|
98
165
|
if (services.redis) urls.REDIS_URL = generateRedisUrl(services.redis);
|
|
99
166
|
if (services.rabbitmq) urls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);
|
|
100
167
|
if (services.minio) urls.STORAGE_ENDPOINT = generateMinioEndpoint(services.minio);
|
|
168
|
+
if (eventsBackend) {
|
|
169
|
+
const eventUrls = generateEventConnectionStrings(eventsBackend, services);
|
|
170
|
+
urls.EVENT_PUBLISHER_CONNECTION_STRING = eventUrls.publisher;
|
|
171
|
+
urls.EVENT_SUBSCRIBER_CONNECTION_STRING = eventUrls.subscriber;
|
|
172
|
+
}
|
|
101
173
|
if (services.mailpit) {
|
|
102
174
|
urls.SMTP_HOST = services.mailpit.host;
|
|
103
175
|
urls.SMTP_PORT = String(services.mailpit.port);
|
|
@@ -105,11 +177,23 @@ function generateConnectionUrls(services) {
|
|
|
105
177
|
return urls;
|
|
106
178
|
}
|
|
107
179
|
/**
|
|
180
|
+
* Generate LocalStack service credentials with LSIA-prefixed access key.
|
|
181
|
+
*/
|
|
182
|
+
function generateLocalStackCredentials() {
|
|
183
|
+
const defaults = SERVICE_DEFAULTS.localstack;
|
|
184
|
+
return {
|
|
185
|
+
...defaults,
|
|
186
|
+
password: generateSecurePassword(),
|
|
187
|
+
accessKeyId: generateLocalStackAccessKeyId()
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
108
191
|
* Create a new StageSecrets object with generated credentials.
|
|
109
192
|
* @param stage - The deployment stage (e.g., 'development', 'production')
|
|
110
193
|
* @param services - List of services to generate credentials for
|
|
111
194
|
* @param options - Optional configuration
|
|
112
195
|
* @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')
|
|
196
|
+
* @param options.eventsBackend - Event backend type (pgboss, sns, rabbitmq)
|
|
113
197
|
*/
|
|
114
198
|
function createStageSecrets(stage, services, options) {
|
|
115
199
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -121,11 +205,21 @@ function createStageSecrets(stage, services, options) {
|
|
|
121
205
|
serviceCredentials.minio.username = options.projectName;
|
|
122
206
|
}
|
|
123
207
|
}
|
|
124
|
-
const
|
|
208
|
+
const eventsBackend = options?.eventsBackend;
|
|
209
|
+
if (eventsBackend === "pgboss" && serviceCredentials.postgres) serviceCredentials.pgboss = {
|
|
210
|
+
...PGBOSS_DEFAULTS,
|
|
211
|
+
password: generateSecurePassword(),
|
|
212
|
+
host: serviceCredentials.postgres.host,
|
|
213
|
+
port: serviceCredentials.postgres.port,
|
|
214
|
+
database: serviceCredentials.postgres.database
|
|
215
|
+
};
|
|
216
|
+
if (eventsBackend === "sns") serviceCredentials.localstack = generateLocalStackCredentials();
|
|
217
|
+
const urls = generateConnectionUrls(serviceCredentials, eventsBackend);
|
|
125
218
|
return {
|
|
126
219
|
stage,
|
|
127
220
|
createdAt: now,
|
|
128
221
|
updatedAt: now,
|
|
222
|
+
eventsBackend,
|
|
129
223
|
services: serviceCredentials,
|
|
130
224
|
urls,
|
|
131
225
|
custom: {}
|
|
@@ -149,7 +243,7 @@ function rotateServicePassword(secrets, service) {
|
|
|
149
243
|
...secrets,
|
|
150
244
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
151
245
|
services: newServices,
|
|
152
|
-
urls: generateConnectionUrls(newServices)
|
|
246
|
+
urls: generateConnectionUrls(newServices, secrets.eventsBackend)
|
|
153
247
|
};
|
|
154
248
|
}
|
|
155
249
|
|
|
@@ -263,6 +357,18 @@ Object.defineProperty(exports, 'generateFullstackCustomSecrets', {
|
|
|
263
357
|
return generateFullstackCustomSecrets;
|
|
264
358
|
}
|
|
265
359
|
});
|
|
360
|
+
Object.defineProperty(exports, 'generateLocalStackCredentials', {
|
|
361
|
+
enumerable: true,
|
|
362
|
+
get: function () {
|
|
363
|
+
return generateLocalStackCredentials;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
Object.defineProperty(exports, 'generateSecurePassword', {
|
|
367
|
+
enumerable: true,
|
|
368
|
+
get: function () {
|
|
369
|
+
return generateSecurePassword;
|
|
370
|
+
}
|
|
371
|
+
});
|
|
266
372
|
Object.defineProperty(exports, 'generateServiceCredentials', {
|
|
267
373
|
enumerable: true,
|
|
268
374
|
get: function () {
|
|
@@ -281,4 +387,4 @@ Object.defineProperty(exports, 'writeDockerEnvFromSecrets', {
|
|
|
281
387
|
return writeDockerEnvFromSecrets;
|
|
282
388
|
}
|
|
283
389
|
});
|
|
284
|
-
//# sourceMappingURL=fullstack-secrets-
|
|
390
|
+
//# sourceMappingURL=fullstack-secrets-DOHBU4Rp.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fullstack-secrets-DOHBU4Rp.cjs","names":["SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n>","PGBOSS_DEFAULTS: Omit<ServiceCredentials, 'password'>","service: ComposeServiceName","services: ComposeServiceName[]","result: StageSecrets['services']","creds: ServiceCredentials","eventsBackend: EventsBackend","services: StageSecrets['services']","eventsBackend?: EventsBackend","urls: StageSecrets['urls']","stage: string","options?: { projectName?: string; eventsBackend?: EventsBackend }","secrets: StageSecrets","newCreds: ServiceCredentials","appName: string","password: string","projectName: string","workspace: NormalizedWorkspace","customs: Record<string, string>","frontendPorts: number[]","upperName","secrets: StageSecrets","workspaceRoot: string"],"sources":["../src/secrets/generator.ts","../src/setup/fullstack-secrets.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport type { ComposeServiceName, EventsBackend } from '../types';\nimport type { ServiceCredentials, StageSecrets } from './types';\n\n/**\n * Generate a secure random password using URL-safe base64 characters.\n * @param length Password length (default: 32)\n */\nexport function generateSecurePassword(length = 32): string {\n\treturn randomBytes(Math.ceil((length * 3) / 4))\n\t\t.toString('base64url')\n\t\t.slice(0, length);\n}\n\n/** Default service configurations (localhost for local dev via Docker port mapping) */\nconst SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n> = {\n\tpostgres: {\n\t\thost: 'localhost',\n\t\tport: 5432,\n\t\tusername: 'app',\n\t\tdatabase: 'app',\n\t},\n\tredis: {\n\t\thost: 'localhost',\n\t\tport: 6379,\n\t\tusername: 'default',\n\t},\n\trabbitmq: {\n\t\thost: 'localhost',\n\t\tport: 5672,\n\t\tusername: 'app',\n\t\tvhost: '/',\n\t},\n\tminio: {\n\t\thost: 'localhost',\n\t\tport: 9000,\n\t\tusername: 'app',\n\t\tbucket: 'app',\n\t},\n\tmailpit: {\n\t\thost: 'localhost',\n\t\tport: 1025,\n\t\tusername: 'app',\n\t},\n\tlocalstack: {\n\t\thost: 'localhost',\n\t\tport: 4566,\n\t\tusername: 'localstack',\n\t\tregion: 'us-east-1',\n\t},\n};\n\n/** Default credentials for pgboss (not a Docker service, reuses postgres) */\nconst PGBOSS_DEFAULTS: Omit<ServiceCredentials, 'password'> = {\n\thost: 'localhost',\n\tport: 5432,\n\tusername: 'pgboss',\n\tdatabase: 'app',\n};\n\n/**\n * Generate credentials for a specific service.\n */\nexport function generateServiceCredentials(\n\tservice: ComposeServiceName,\n): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS[service];\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t};\n}\n\n/**\n * Generate credentials for multiple services.\n */\nexport function generateServicesCredentials(\n\tservices: ComposeServiceName[],\n): StageSecrets['services'] {\n\tconst result: StageSecrets['services'] = {};\n\n\tfor (const service of services) {\n\t\tresult[service] = generateServiceCredentials(service);\n\t}\n\n\treturn result;\n}\n\n/**\n * Generate connection URL for PostgreSQL.\n */\nexport function generatePostgresUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;\n}\n\n/**\n * Generate connection URL for Redis.\n */\nexport function generateRedisUrl(creds: ServiceCredentials): string {\n\tconst { password, host, port } = creds;\n\treturn `redis://:${encodeURIComponent(password)}@${host}:${port}`;\n}\n\n/**\n * Generate connection URL for RabbitMQ.\n */\nexport function generateRabbitmqUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, vhost } = creds;\n\tconst encodedVhost = encodeURIComponent(vhost ?? '/');\n\treturn `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;\n}\n\n/**\n * Generate endpoint URL for MinIO (S3-compatible).\n */\nexport function generateMinioEndpoint(creds: ServiceCredentials): string {\n\tconst { host, port } = creds;\n\treturn `http://${host}:${port}`;\n}\n\n/**\n * Generate a LocalStack-compatible access key ID.\n * Must start with 'LSIA' prefix and be at least 20 characters.\n * @see https://docs.localstack.cloud/aws/capabilities/config/credentials/\n */\nexport function generateLocalStackAccessKeyId(): string {\n\tconst suffix = randomBytes(12).toString('base64url').slice(0, 16);\n\treturn `LSIA${suffix}`;\n}\n\n/**\n * Generate connection URL for pg-boss (uses PostgreSQL protocol).\n * Format: pgboss://user:pass@host:port/db?schema=pgboss\n */\nexport function generatePgBossUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `pgboss://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}?schema=pgboss`;\n}\n\n/**\n * Generate event connection strings based on the events backend.\n */\nexport function generateEventConnectionStrings(\n\teventsBackend: EventsBackend,\n\tservices: StageSecrets['services'],\n): { publisher: string; subscriber: string } {\n\tswitch (eventsBackend) {\n\t\tcase 'pgboss': {\n\t\t\tconst creds = services.pgboss;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('pgboss credentials required for pgboss events');\n\t\t\t}\n\t\t\tconst url = generatePgBossUrl(creds);\n\t\t\treturn { publisher: url, subscriber: url };\n\t\t}\n\t\tcase 'sns': {\n\t\t\tconst creds = services.localstack;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('localstack credentials required for sns events');\n\t\t\t}\n\t\t\tconst endpoint = `http://${creds.host}:${creds.port}`;\n\t\t\tconst region = creds.region ?? 'us-east-1';\n\t\t\tconst accessKeyId = creds.accessKeyId ?? creds.username;\n\t\t\tconst secretKey = encodeURIComponent(creds.password);\n\t\t\treturn {\n\t\t\t\tpublisher: `sns://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,\n\t\t\t\tsubscriber: `sqs://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,\n\t\t\t};\n\t\t}\n\t\tcase 'rabbitmq': {\n\t\t\tconst creds = services.rabbitmq;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('rabbitmq credentials required for rabbitmq events');\n\t\t\t}\n\t\t\tconst url = generateRabbitmqUrl(creds);\n\t\t\treturn { publisher: url, subscriber: url };\n\t\t}\n\t}\n}\n\n/**\n * Generate connection URLs from service credentials.\n */\nexport function generateConnectionUrls(\n\tservices: StageSecrets['services'],\n\teventsBackend?: EventsBackend,\n): StageSecrets['urls'] {\n\tconst urls: StageSecrets['urls'] = {};\n\n\tif (services.postgres) {\n\t\turls.DATABASE_URL = generatePostgresUrl(services.postgres);\n\t}\n\n\tif (services.redis) {\n\t\turls.REDIS_URL = generateRedisUrl(services.redis);\n\t}\n\n\tif (services.rabbitmq) {\n\t\turls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);\n\t}\n\n\tif (services.minio) {\n\t\turls.STORAGE_ENDPOINT = generateMinioEndpoint(services.minio);\n\t}\n\n\tif (eventsBackend) {\n\t\tconst eventUrls = generateEventConnectionStrings(eventsBackend, services);\n\t\turls.EVENT_PUBLISHER_CONNECTION_STRING = eventUrls.publisher;\n\t\turls.EVENT_SUBSCRIBER_CONNECTION_STRING = eventUrls.subscriber;\n\t}\n\n\tif (services.mailpit) {\n\t\turls.SMTP_HOST = services.mailpit.host;\n\t\turls.SMTP_PORT = String(services.mailpit.port);\n\t}\n\n\treturn urls;\n}\n\n/**\n * Generate LocalStack service credentials with LSIA-prefixed access key.\n */\nexport function generateLocalStackCredentials(): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS.localstack;\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t\taccessKeyId: generateLocalStackAccessKeyId(),\n\t};\n}\n\n/**\n * Create a new StageSecrets object with generated credentials.\n * @param stage - The deployment stage (e.g., 'development', 'production')\n * @param services - List of services to generate credentials for\n * @param options - Optional configuration\n * @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')\n * @param options.eventsBackend - Event backend type (pgboss, sns, rabbitmq)\n */\nexport function createStageSecrets(\n\tstage: string,\n\tservices: ComposeServiceName[],\n\toptions?: { projectName?: string; eventsBackend?: EventsBackend },\n): StageSecrets {\n\tconst now = new Date().toISOString();\n\tconst serviceCredentials = generateServicesCredentials(services);\n\n\t// Override service defaults with project-derived names if provided\n\tif (options?.projectName) {\n\t\tif (serviceCredentials.postgres) {\n\t\t\tserviceCredentials.postgres.database = `${options.projectName.replace(/-/g, '_')}_dev`;\n\t\t}\n\t\tif (serviceCredentials.minio) {\n\t\t\tserviceCredentials.minio.bucket = options.projectName;\n\t\t\tserviceCredentials.minio.username = options.projectName;\n\t\t}\n\t}\n\n\t// Generate event-specific credentials\n\tconst eventsBackend = options?.eventsBackend;\n\tif (eventsBackend === 'pgboss' && serviceCredentials.postgres) {\n\t\t// pgboss reuses postgres host/port/database but with dedicated user\n\t\tserviceCredentials.pgboss = {\n\t\t\t...PGBOSS_DEFAULTS,\n\t\t\tpassword: generateSecurePassword(),\n\t\t\thost: serviceCredentials.postgres.host,\n\t\t\tport: serviceCredentials.postgres.port,\n\t\t\tdatabase: serviceCredentials.postgres.database,\n\t\t};\n\t}\n\tif (eventsBackend === 'sns') {\n\t\t// LocalStack credentials with LSIA-prefixed access key\n\t\tserviceCredentials.localstack = generateLocalStackCredentials();\n\t}\n\n\tconst urls = generateConnectionUrls(serviceCredentials, eventsBackend);\n\n\treturn {\n\t\tstage,\n\t\tcreatedAt: now,\n\t\tupdatedAt: now,\n\t\teventsBackend,\n\t\tservices: serviceCredentials,\n\t\turls,\n\t\tcustom: {},\n\t};\n}\n\n/**\n * Rotate password for a specific service.\n */\nexport function rotateServicePassword(\n\tsecrets: StageSecrets,\n\tservice: ComposeServiceName,\n): StageSecrets {\n\tconst currentCreds = secrets.services[service];\n\tif (!currentCreds) {\n\t\tthrow new Error(`Service \"${service}\" not configured in secrets`);\n\t}\n\n\tconst newCreds: ServiceCredentials = {\n\t\t...currentCreds,\n\t\tpassword: generateSecurePassword(),\n\t};\n\n\tconst newServices = {\n\t\t...secrets.services,\n\t\t[service]: newCreds,\n\t};\n\n\treturn {\n\t\t...secrets,\n\t\tupdatedAt: new Date().toISOString(),\n\t\tservices: newServices,\n\t\turls: generateConnectionUrls(newServices, secrets.eventsBackend),\n\t};\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { generateSecurePassword } from '../secrets/generator.js';\nimport type { StageSecrets } from '../secrets/types.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\n/**\n * Generate a secure random password for database users.\n * Uses a combination of timestamp and random bytes for uniqueness.\n */\nexport function generateDbPassword(): string {\n\treturn `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Generate database URL for an app.\n * All apps connect to the same database, but use different users/schemas.\n */\nexport function generateDbUrl(\n\tappName: string,\n\tpassword: string,\n\tprojectName: string,\n\thost = 'localhost',\n\tport = 5432,\n): string {\n\tconst userName = appName.replace(/-/g, '_');\n\tconst dbName = `${projectName.replace(/-/g, '_')}_dev`;\n\treturn `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;\n}\n\n/**\n * Generate fullstack-aware custom secrets for a workspace.\n *\n * Generates:\n * - Common secrets: NODE_ENV, PORT, LOG_LEVEL, JWT_SECRET\n * - Per-app database passwords and URLs for backend apps with db service\n * - Better-auth secrets for apps using the better-auth framework\n */\nexport function generateFullstackCustomSecrets(\n\tworkspace: NormalizedWorkspace,\n): Record<string, string> {\n\tconst hasDb = !!workspace.services.db;\n\tconst customs: Record<string, string> = {\n\t\tNODE_ENV: 'development',\n\t\tPORT: '3000',\n\t\tLOG_LEVEL: 'debug',\n\t\tJWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n\t};\n\n\tif (!hasDb) {\n\t\treturn customs;\n\t}\n\n\t// Collect all frontend ports for trusted origins\n\tconst frontendPorts: number[] = [];\n\n\tfor (const [appName, appConfig] of Object.entries(workspace.apps)) {\n\t\tif (appConfig.type === 'frontend') {\n\t\t\tfrontendPorts.push(appConfig.port);\n\t\t\tconst upperName = appName.toUpperCase();\n\t\t\tcustoms[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Backend apps with database: generate per-app DB passwords and URLs\n\t\tconst password = generateDbPassword();\n\t\tconst upperName = appName.toUpperCase();\n\n\t\tcustoms[`${upperName}_DATABASE_URL`] = generateDbUrl(\n\t\t\tappName,\n\t\t\tpassword,\n\t\t\tworkspace.name,\n\t\t);\n\t\tcustoms[`${upperName}_DB_PASSWORD`] = password;\n\n\t\t// Better-auth framework secrets\n\t\tif (appConfig.framework === 'better-auth') {\n\t\t\tcustoms.AUTH_PORT = String(appConfig.port);\n\t\t\tcustoms.AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t\tcustoms.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${generateSecurePassword(16)}`;\n\t\t\tcustoms.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t}\n\t}\n\n\t// Generate trusted origins for better-auth (all app ports)\n\tif (customs.BETTER_AUTH_SECRET) {\n\t\tconst allPorts = Object.values(workspace.apps).map((a) => a.port);\n\t\tcustoms.BETTER_AUTH_TRUSTED_ORIGINS = allPorts\n\t\t\t.map((p) => `http://localhost:${p}`)\n\t\t\t.join(',');\n\t}\n\n\treturn customs;\n}\n\n/**\n * Extract *_DB_PASSWORD keys from secrets and write docker/.env.\n *\n * The docker/.env file contains database passwords that the PostgreSQL\n * init script reads to create per-app database users.\n */\nexport async function writeDockerEnvFromSecrets(\n\tsecrets: StageSecrets,\n\tworkspaceRoot: string,\n): Promise<void> {\n\tconst dbPasswordEntries = Object.entries(secrets.custom).filter(([key]) =>\n\t\tkey.endsWith('_DB_PASSWORD'),\n\t);\n\n\tif (dbPasswordEntries.length === 0) {\n\t\treturn;\n\t}\n\n\tconst envContent = `# Auto-generated docker environment file\n# Contains database passwords for docker-compose postgres init\n# This file is gitignored - do not commit to version control\n${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join('\\n')}\n`;\n\n\tconst envPath = join(workspaceRoot, 'docker', '.env');\n\tawait mkdir(dirname(envPath), { recursive: true });\n\tawait writeFile(envPath, envContent);\n}\n"],"mappings":"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"}
|
|
@@ -39,8 +39,21 @@ const SERVICE_DEFAULTS = {
|
|
|
39
39
|
host: "localhost",
|
|
40
40
|
port: 1025,
|
|
41
41
|
username: "app"
|
|
42
|
+
},
|
|
43
|
+
localstack: {
|
|
44
|
+
host: "localhost",
|
|
45
|
+
port: 4566,
|
|
46
|
+
username: "localstack",
|
|
47
|
+
region: "us-east-1"
|
|
42
48
|
}
|
|
43
49
|
};
|
|
50
|
+
/** Default credentials for pgboss (not a Docker service, reuses postgres) */
|
|
51
|
+
const PGBOSS_DEFAULTS = {
|
|
52
|
+
host: "localhost",
|
|
53
|
+
port: 5432,
|
|
54
|
+
username: "pgboss",
|
|
55
|
+
database: "app"
|
|
56
|
+
};
|
|
44
57
|
/**
|
|
45
58
|
* Generate credentials for a specific service.
|
|
46
59
|
*/
|
|
@@ -89,14 +102,73 @@ function generateMinioEndpoint(creds) {
|
|
|
89
102
|
return `http://${host}:${port}`;
|
|
90
103
|
}
|
|
91
104
|
/**
|
|
105
|
+
* Generate a LocalStack-compatible access key ID.
|
|
106
|
+
* Must start with 'LSIA' prefix and be at least 20 characters.
|
|
107
|
+
* @see https://docs.localstack.cloud/aws/capabilities/config/credentials/
|
|
108
|
+
*/
|
|
109
|
+
function generateLocalStackAccessKeyId() {
|
|
110
|
+
const suffix = randomBytes(12).toString("base64url").slice(0, 16);
|
|
111
|
+
return `LSIA${suffix}`;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Generate connection URL for pg-boss (uses PostgreSQL protocol).
|
|
115
|
+
* Format: pgboss://user:pass@host:port/db?schema=pgboss
|
|
116
|
+
*/
|
|
117
|
+
function generatePgBossUrl(creds) {
|
|
118
|
+
const { username, password, host, port, database } = creds;
|
|
119
|
+
return `pgboss://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}?schema=pgboss`;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Generate event connection strings based on the events backend.
|
|
123
|
+
*/
|
|
124
|
+
function generateEventConnectionStrings(eventsBackend, services) {
|
|
125
|
+
switch (eventsBackend) {
|
|
126
|
+
case "pgboss": {
|
|
127
|
+
const creds = services.pgboss;
|
|
128
|
+
if (!creds) throw new Error("pgboss credentials required for pgboss events");
|
|
129
|
+
const url = generatePgBossUrl(creds);
|
|
130
|
+
return {
|
|
131
|
+
publisher: url,
|
|
132
|
+
subscriber: url
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
case "sns": {
|
|
136
|
+
const creds = services.localstack;
|
|
137
|
+
if (!creds) throw new Error("localstack credentials required for sns events");
|
|
138
|
+
const endpoint = `http://${creds.host}:${creds.port}`;
|
|
139
|
+
const region = creds.region ?? "us-east-1";
|
|
140
|
+
const accessKeyId = creds.accessKeyId ?? creds.username;
|
|
141
|
+
const secretKey = encodeURIComponent(creds.password);
|
|
142
|
+
return {
|
|
143
|
+
publisher: `sns://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,
|
|
144
|
+
subscriber: `sqs://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
case "rabbitmq": {
|
|
148
|
+
const creds = services.rabbitmq;
|
|
149
|
+
if (!creds) throw new Error("rabbitmq credentials required for rabbitmq events");
|
|
150
|
+
const url = generateRabbitmqUrl(creds);
|
|
151
|
+
return {
|
|
152
|
+
publisher: url,
|
|
153
|
+
subscriber: url
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
92
159
|
* Generate connection URLs from service credentials.
|
|
93
160
|
*/
|
|
94
|
-
function generateConnectionUrls(services) {
|
|
161
|
+
function generateConnectionUrls(services, eventsBackend) {
|
|
95
162
|
const urls = {};
|
|
96
163
|
if (services.postgres) urls.DATABASE_URL = generatePostgresUrl(services.postgres);
|
|
97
164
|
if (services.redis) urls.REDIS_URL = generateRedisUrl(services.redis);
|
|
98
165
|
if (services.rabbitmq) urls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);
|
|
99
166
|
if (services.minio) urls.STORAGE_ENDPOINT = generateMinioEndpoint(services.minio);
|
|
167
|
+
if (eventsBackend) {
|
|
168
|
+
const eventUrls = generateEventConnectionStrings(eventsBackend, services);
|
|
169
|
+
urls.EVENT_PUBLISHER_CONNECTION_STRING = eventUrls.publisher;
|
|
170
|
+
urls.EVENT_SUBSCRIBER_CONNECTION_STRING = eventUrls.subscriber;
|
|
171
|
+
}
|
|
100
172
|
if (services.mailpit) {
|
|
101
173
|
urls.SMTP_HOST = services.mailpit.host;
|
|
102
174
|
urls.SMTP_PORT = String(services.mailpit.port);
|
|
@@ -104,11 +176,23 @@ function generateConnectionUrls(services) {
|
|
|
104
176
|
return urls;
|
|
105
177
|
}
|
|
106
178
|
/**
|
|
179
|
+
* Generate LocalStack service credentials with LSIA-prefixed access key.
|
|
180
|
+
*/
|
|
181
|
+
function generateLocalStackCredentials() {
|
|
182
|
+
const defaults = SERVICE_DEFAULTS.localstack;
|
|
183
|
+
return {
|
|
184
|
+
...defaults,
|
|
185
|
+
password: generateSecurePassword(),
|
|
186
|
+
accessKeyId: generateLocalStackAccessKeyId()
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
107
190
|
* Create a new StageSecrets object with generated credentials.
|
|
108
191
|
* @param stage - The deployment stage (e.g., 'development', 'production')
|
|
109
192
|
* @param services - List of services to generate credentials for
|
|
110
193
|
* @param options - Optional configuration
|
|
111
194
|
* @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')
|
|
195
|
+
* @param options.eventsBackend - Event backend type (pgboss, sns, rabbitmq)
|
|
112
196
|
*/
|
|
113
197
|
function createStageSecrets(stage, services, options) {
|
|
114
198
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -120,11 +204,21 @@ function createStageSecrets(stage, services, options) {
|
|
|
120
204
|
serviceCredentials.minio.username = options.projectName;
|
|
121
205
|
}
|
|
122
206
|
}
|
|
123
|
-
const
|
|
207
|
+
const eventsBackend = options?.eventsBackend;
|
|
208
|
+
if (eventsBackend === "pgboss" && serviceCredentials.postgres) serviceCredentials.pgboss = {
|
|
209
|
+
...PGBOSS_DEFAULTS,
|
|
210
|
+
password: generateSecurePassword(),
|
|
211
|
+
host: serviceCredentials.postgres.host,
|
|
212
|
+
port: serviceCredentials.postgres.port,
|
|
213
|
+
database: serviceCredentials.postgres.database
|
|
214
|
+
};
|
|
215
|
+
if (eventsBackend === "sns") serviceCredentials.localstack = generateLocalStackCredentials();
|
|
216
|
+
const urls = generateConnectionUrls(serviceCredentials, eventsBackend);
|
|
124
217
|
return {
|
|
125
218
|
stage,
|
|
126
219
|
createdAt: now,
|
|
127
220
|
updatedAt: now,
|
|
221
|
+
eventsBackend,
|
|
128
222
|
services: serviceCredentials,
|
|
129
223
|
urls,
|
|
130
224
|
custom: {}
|
|
@@ -148,7 +242,7 @@ function rotateServicePassword(secrets, service) {
|
|
|
148
242
|
...secrets,
|
|
149
243
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
150
244
|
services: newServices,
|
|
151
|
-
urls: generateConnectionUrls(newServices)
|
|
245
|
+
urls: generateConnectionUrls(newServices, secrets.eventsBackend)
|
|
152
246
|
};
|
|
153
247
|
}
|
|
154
248
|
|
|
@@ -232,5 +326,5 @@ ${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join("\n")}
|
|
|
232
326
|
}
|
|
233
327
|
|
|
234
328
|
//#endregion
|
|
235
|
-
export { createStageSecrets, generateConnectionUrls, generateDbPassword, generateDbUrl, generateFullstackCustomSecrets, generateServiceCredentials, rotateServicePassword, writeDockerEnvFromSecrets };
|
|
236
|
-
//# sourceMappingURL=fullstack-secrets-
|
|
329
|
+
export { createStageSecrets, generateConnectionUrls, generateDbPassword, generateDbUrl, generateFullstackCustomSecrets, generateLocalStackCredentials, generateSecurePassword, generateServiceCredentials, rotateServicePassword, writeDockerEnvFromSecrets };
|
|
330
|
+
//# sourceMappingURL=fullstack-secrets-x2Kffx7-.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fullstack-secrets-x2Kffx7-.mjs","names":["SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n>","PGBOSS_DEFAULTS: Omit<ServiceCredentials, 'password'>","service: ComposeServiceName","services: ComposeServiceName[]","result: StageSecrets['services']","creds: ServiceCredentials","eventsBackend: EventsBackend","services: StageSecrets['services']","eventsBackend?: EventsBackend","urls: StageSecrets['urls']","stage: string","options?: { projectName?: string; eventsBackend?: EventsBackend }","secrets: StageSecrets","newCreds: ServiceCredentials","appName: string","password: string","projectName: string","workspace: NormalizedWorkspace","customs: Record<string, string>","frontendPorts: number[]","upperName","secrets: StageSecrets","workspaceRoot: string"],"sources":["../src/secrets/generator.ts","../src/setup/fullstack-secrets.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport type { ComposeServiceName, EventsBackend } from '../types';\nimport type { ServiceCredentials, StageSecrets } from './types';\n\n/**\n * Generate a secure random password using URL-safe base64 characters.\n * @param length Password length (default: 32)\n */\nexport function generateSecurePassword(length = 32): string {\n\treturn randomBytes(Math.ceil((length * 3) / 4))\n\t\t.toString('base64url')\n\t\t.slice(0, length);\n}\n\n/** Default service configurations (localhost for local dev via Docker port mapping) */\nconst SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n> = {\n\tpostgres: {\n\t\thost: 'localhost',\n\t\tport: 5432,\n\t\tusername: 'app',\n\t\tdatabase: 'app',\n\t},\n\tredis: {\n\t\thost: 'localhost',\n\t\tport: 6379,\n\t\tusername: 'default',\n\t},\n\trabbitmq: {\n\t\thost: 'localhost',\n\t\tport: 5672,\n\t\tusername: 'app',\n\t\tvhost: '/',\n\t},\n\tminio: {\n\t\thost: 'localhost',\n\t\tport: 9000,\n\t\tusername: 'app',\n\t\tbucket: 'app',\n\t},\n\tmailpit: {\n\t\thost: 'localhost',\n\t\tport: 1025,\n\t\tusername: 'app',\n\t},\n\tlocalstack: {\n\t\thost: 'localhost',\n\t\tport: 4566,\n\t\tusername: 'localstack',\n\t\tregion: 'us-east-1',\n\t},\n};\n\n/** Default credentials for pgboss (not a Docker service, reuses postgres) */\nconst PGBOSS_DEFAULTS: Omit<ServiceCredentials, 'password'> = {\n\thost: 'localhost',\n\tport: 5432,\n\tusername: 'pgboss',\n\tdatabase: 'app',\n};\n\n/**\n * Generate credentials for a specific service.\n */\nexport function generateServiceCredentials(\n\tservice: ComposeServiceName,\n): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS[service];\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t};\n}\n\n/**\n * Generate credentials for multiple services.\n */\nexport function generateServicesCredentials(\n\tservices: ComposeServiceName[],\n): StageSecrets['services'] {\n\tconst result: StageSecrets['services'] = {};\n\n\tfor (const service of services) {\n\t\tresult[service] = generateServiceCredentials(service);\n\t}\n\n\treturn result;\n}\n\n/**\n * Generate connection URL for PostgreSQL.\n */\nexport function generatePostgresUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;\n}\n\n/**\n * Generate connection URL for Redis.\n */\nexport function generateRedisUrl(creds: ServiceCredentials): string {\n\tconst { password, host, port } = creds;\n\treturn `redis://:${encodeURIComponent(password)}@${host}:${port}`;\n}\n\n/**\n * Generate connection URL for RabbitMQ.\n */\nexport function generateRabbitmqUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, vhost } = creds;\n\tconst encodedVhost = encodeURIComponent(vhost ?? '/');\n\treturn `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;\n}\n\n/**\n * Generate endpoint URL for MinIO (S3-compatible).\n */\nexport function generateMinioEndpoint(creds: ServiceCredentials): string {\n\tconst { host, port } = creds;\n\treturn `http://${host}:${port}`;\n}\n\n/**\n * Generate a LocalStack-compatible access key ID.\n * Must start with 'LSIA' prefix and be at least 20 characters.\n * @see https://docs.localstack.cloud/aws/capabilities/config/credentials/\n */\nexport function generateLocalStackAccessKeyId(): string {\n\tconst suffix = randomBytes(12).toString('base64url').slice(0, 16);\n\treturn `LSIA${suffix}`;\n}\n\n/**\n * Generate connection URL for pg-boss (uses PostgreSQL protocol).\n * Format: pgboss://user:pass@host:port/db?schema=pgboss\n */\nexport function generatePgBossUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `pgboss://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}?schema=pgboss`;\n}\n\n/**\n * Generate event connection strings based on the events backend.\n */\nexport function generateEventConnectionStrings(\n\teventsBackend: EventsBackend,\n\tservices: StageSecrets['services'],\n): { publisher: string; subscriber: string } {\n\tswitch (eventsBackend) {\n\t\tcase 'pgboss': {\n\t\t\tconst creds = services.pgboss;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('pgboss credentials required for pgboss events');\n\t\t\t}\n\t\t\tconst url = generatePgBossUrl(creds);\n\t\t\treturn { publisher: url, subscriber: url };\n\t\t}\n\t\tcase 'sns': {\n\t\t\tconst creds = services.localstack;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('localstack credentials required for sns events');\n\t\t\t}\n\t\t\tconst endpoint = `http://${creds.host}:${creds.port}`;\n\t\t\tconst region = creds.region ?? 'us-east-1';\n\t\t\tconst accessKeyId = creds.accessKeyId ?? creds.username;\n\t\t\tconst secretKey = encodeURIComponent(creds.password);\n\t\t\treturn {\n\t\t\t\tpublisher: `sns://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,\n\t\t\t\tsubscriber: `sqs://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,\n\t\t\t};\n\t\t}\n\t\tcase 'rabbitmq': {\n\t\t\tconst creds = services.rabbitmq;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('rabbitmq credentials required for rabbitmq events');\n\t\t\t}\n\t\t\tconst url = generateRabbitmqUrl(creds);\n\t\t\treturn { publisher: url, subscriber: url };\n\t\t}\n\t}\n}\n\n/**\n * Generate connection URLs from service credentials.\n */\nexport function generateConnectionUrls(\n\tservices: StageSecrets['services'],\n\teventsBackend?: EventsBackend,\n): StageSecrets['urls'] {\n\tconst urls: StageSecrets['urls'] = {};\n\n\tif (services.postgres) {\n\t\turls.DATABASE_URL = generatePostgresUrl(services.postgres);\n\t}\n\n\tif (services.redis) {\n\t\turls.REDIS_URL = generateRedisUrl(services.redis);\n\t}\n\n\tif (services.rabbitmq) {\n\t\turls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);\n\t}\n\n\tif (services.minio) {\n\t\turls.STORAGE_ENDPOINT = generateMinioEndpoint(services.minio);\n\t}\n\n\tif (eventsBackend) {\n\t\tconst eventUrls = generateEventConnectionStrings(eventsBackend, services);\n\t\turls.EVENT_PUBLISHER_CONNECTION_STRING = eventUrls.publisher;\n\t\turls.EVENT_SUBSCRIBER_CONNECTION_STRING = eventUrls.subscriber;\n\t}\n\n\tif (services.mailpit) {\n\t\turls.SMTP_HOST = services.mailpit.host;\n\t\turls.SMTP_PORT = String(services.mailpit.port);\n\t}\n\n\treturn urls;\n}\n\n/**\n * Generate LocalStack service credentials with LSIA-prefixed access key.\n */\nexport function generateLocalStackCredentials(): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS.localstack;\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t\taccessKeyId: generateLocalStackAccessKeyId(),\n\t};\n}\n\n/**\n * Create a new StageSecrets object with generated credentials.\n * @param stage - The deployment stage (e.g., 'development', 'production')\n * @param services - List of services to generate credentials for\n * @param options - Optional configuration\n * @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')\n * @param options.eventsBackend - Event backend type (pgboss, sns, rabbitmq)\n */\nexport function createStageSecrets(\n\tstage: string,\n\tservices: ComposeServiceName[],\n\toptions?: { projectName?: string; eventsBackend?: EventsBackend },\n): StageSecrets {\n\tconst now = new Date().toISOString();\n\tconst serviceCredentials = generateServicesCredentials(services);\n\n\t// Override service defaults with project-derived names if provided\n\tif (options?.projectName) {\n\t\tif (serviceCredentials.postgres) {\n\t\t\tserviceCredentials.postgres.database = `${options.projectName.replace(/-/g, '_')}_dev`;\n\t\t}\n\t\tif (serviceCredentials.minio) {\n\t\t\tserviceCredentials.minio.bucket = options.projectName;\n\t\t\tserviceCredentials.minio.username = options.projectName;\n\t\t}\n\t}\n\n\t// Generate event-specific credentials\n\tconst eventsBackend = options?.eventsBackend;\n\tif (eventsBackend === 'pgboss' && serviceCredentials.postgres) {\n\t\t// pgboss reuses postgres host/port/database but with dedicated user\n\t\tserviceCredentials.pgboss = {\n\t\t\t...PGBOSS_DEFAULTS,\n\t\t\tpassword: generateSecurePassword(),\n\t\t\thost: serviceCredentials.postgres.host,\n\t\t\tport: serviceCredentials.postgres.port,\n\t\t\tdatabase: serviceCredentials.postgres.database,\n\t\t};\n\t}\n\tif (eventsBackend === 'sns') {\n\t\t// LocalStack credentials with LSIA-prefixed access key\n\t\tserviceCredentials.localstack = generateLocalStackCredentials();\n\t}\n\n\tconst urls = generateConnectionUrls(serviceCredentials, eventsBackend);\n\n\treturn {\n\t\tstage,\n\t\tcreatedAt: now,\n\t\tupdatedAt: now,\n\t\teventsBackend,\n\t\tservices: serviceCredentials,\n\t\turls,\n\t\tcustom: {},\n\t};\n}\n\n/**\n * Rotate password for a specific service.\n */\nexport function rotateServicePassword(\n\tsecrets: StageSecrets,\n\tservice: ComposeServiceName,\n): StageSecrets {\n\tconst currentCreds = secrets.services[service];\n\tif (!currentCreds) {\n\t\tthrow new Error(`Service \"${service}\" not configured in secrets`);\n\t}\n\n\tconst newCreds: ServiceCredentials = {\n\t\t...currentCreds,\n\t\tpassword: generateSecurePassword(),\n\t};\n\n\tconst newServices = {\n\t\t...secrets.services,\n\t\t[service]: newCreds,\n\t};\n\n\treturn {\n\t\t...secrets,\n\t\tupdatedAt: new Date().toISOString(),\n\t\tservices: newServices,\n\t\turls: generateConnectionUrls(newServices, secrets.eventsBackend),\n\t};\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { generateSecurePassword } from '../secrets/generator.js';\nimport type { StageSecrets } from '../secrets/types.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\n/**\n * Generate a secure random password for database users.\n * Uses a combination of timestamp and random bytes for uniqueness.\n */\nexport function generateDbPassword(): string {\n\treturn `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Generate database URL for an app.\n * All apps connect to the same database, but use different users/schemas.\n */\nexport function generateDbUrl(\n\tappName: string,\n\tpassword: string,\n\tprojectName: string,\n\thost = 'localhost',\n\tport = 5432,\n): string {\n\tconst userName = appName.replace(/-/g, '_');\n\tconst dbName = `${projectName.replace(/-/g, '_')}_dev`;\n\treturn `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;\n}\n\n/**\n * Generate fullstack-aware custom secrets for a workspace.\n *\n * Generates:\n * - Common secrets: NODE_ENV, PORT, LOG_LEVEL, JWT_SECRET\n * - Per-app database passwords and URLs for backend apps with db service\n * - Better-auth secrets for apps using the better-auth framework\n */\nexport function generateFullstackCustomSecrets(\n\tworkspace: NormalizedWorkspace,\n): Record<string, string> {\n\tconst hasDb = !!workspace.services.db;\n\tconst customs: Record<string, string> = {\n\t\tNODE_ENV: 'development',\n\t\tPORT: '3000',\n\t\tLOG_LEVEL: 'debug',\n\t\tJWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n\t};\n\n\tif (!hasDb) {\n\t\treturn customs;\n\t}\n\n\t// Collect all frontend ports for trusted origins\n\tconst frontendPorts: number[] = [];\n\n\tfor (const [appName, appConfig] of Object.entries(workspace.apps)) {\n\t\tif (appConfig.type === 'frontend') {\n\t\t\tfrontendPorts.push(appConfig.port);\n\t\t\tconst upperName = appName.toUpperCase();\n\t\t\tcustoms[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Backend apps with database: generate per-app DB passwords and URLs\n\t\tconst password = generateDbPassword();\n\t\tconst upperName = appName.toUpperCase();\n\n\t\tcustoms[`${upperName}_DATABASE_URL`] = generateDbUrl(\n\t\t\tappName,\n\t\t\tpassword,\n\t\t\tworkspace.name,\n\t\t);\n\t\tcustoms[`${upperName}_DB_PASSWORD`] = password;\n\n\t\t// Better-auth framework secrets\n\t\tif (appConfig.framework === 'better-auth') {\n\t\t\tcustoms.AUTH_PORT = String(appConfig.port);\n\t\t\tcustoms.AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t\tcustoms.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${generateSecurePassword(16)}`;\n\t\t\tcustoms.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t}\n\t}\n\n\t// Generate trusted origins for better-auth (all app ports)\n\tif (customs.BETTER_AUTH_SECRET) {\n\t\tconst allPorts = Object.values(workspace.apps).map((a) => a.port);\n\t\tcustoms.BETTER_AUTH_TRUSTED_ORIGINS = allPorts\n\t\t\t.map((p) => `http://localhost:${p}`)\n\t\t\t.join(',');\n\t}\n\n\treturn customs;\n}\n\n/**\n * Extract *_DB_PASSWORD keys from secrets and write docker/.env.\n *\n * The docker/.env file contains database passwords that the PostgreSQL\n * init script reads to create per-app database users.\n */\nexport async function writeDockerEnvFromSecrets(\n\tsecrets: StageSecrets,\n\tworkspaceRoot: string,\n): Promise<void> {\n\tconst dbPasswordEntries = Object.entries(secrets.custom).filter(([key]) =>\n\t\tkey.endsWith('_DB_PASSWORD'),\n\t);\n\n\tif (dbPasswordEntries.length === 0) {\n\t\treturn;\n\t}\n\n\tconst envContent = `# Auto-generated docker environment file\n# Contains database passwords for docker-compose postgres init\n# This file is gitignored - do not commit to version control\n${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join('\\n')}\n`;\n\n\tconst envPath = join(workspaceRoot, 'docker', '.env');\n\tawait mkdir(dirname(envPath), { recursive: true });\n\tawait writeFile(envPath, envContent);\n}\n"],"mappings":"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"}
|