@geekmidas/cli 1.10.40 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +14 -0
  3. package/dist/{CachedStateProvider-37pG1HBx.cjs → CachedStateProvider-BdX7klRc.cjs} +1 -1
  4. package/dist/{CachedStateProvider-C8A-cCm5.mjs → CachedStateProvider-CzyFct42.mjs} +1 -1
  5. package/dist/{CachedStateProvider-C8A-cCm5.mjs.map → CachedStateProvider-CzyFct42.mjs.map} +1 -1
  6. package/dist/CachedStateProvider-GcGziXh1.mjs +3 -0
  7. package/dist/{CachedStateProvider-CxsXOKkg.cjs → CachedStateProvider-klLWZXWF.cjs} +1 -1
  8. package/dist/{CachedStateProvider-CxsXOKkg.cjs.map → CachedStateProvider-klLWZXWF.cjs.map} +1 -1
  9. package/dist/{HostingerProvider-ANWchdiK.mjs → HostingerProvider-B2ZYz7Yc.mjs} +1 -1
  10. package/dist/{HostingerProvider-ANWchdiK.mjs.map → HostingerProvider-B2ZYz7Yc.mjs.map} +1 -1
  11. package/dist/{HostingerProvider-5KYmwoK2.cjs → HostingerProvider-CrHfsgz9.cjs} +1 -1
  12. package/dist/{HostingerProvider-5KYmwoK2.cjs.map → HostingerProvider-CrHfsgz9.cjs.map} +1 -1
  13. package/dist/{LocalStateProvider-Dp0KkRcw.mjs → LocalStateProvider-CP49rjzt.mjs} +1 -1
  14. package/dist/{LocalStateProvider-Dp0KkRcw.mjs.map → LocalStateProvider-CP49rjzt.mjs.map} +1 -1
  15. package/dist/{LocalStateProvider-CLifRC0Y.cjs → LocalStateProvider-CalzNfNA.cjs} +1 -1
  16. package/dist/{LocalStateProvider-CLifRC0Y.cjs.map → LocalStateProvider-CalzNfNA.cjs.map} +1 -1
  17. package/dist/{Route53Provider-owQQ4pn6.cjs → Route53Provider-DwgXiLxt.cjs} +1 -1
  18. package/dist/{Route53Provider-owQQ4pn6.cjs.map → Route53Provider-DwgXiLxt.cjs.map} +1 -1
  19. package/dist/{Route53Provider-QoPgcXxn.mjs → Route53Provider-tlgBzrRQ.mjs} +1 -1
  20. package/dist/{Route53Provider-QoPgcXxn.mjs.map → Route53Provider-tlgBzrRQ.mjs.map} +1 -1
  21. package/dist/{SSMStateProvider-CksOTB8M.mjs → SSMStateProvider-BlrTiV5m.mjs} +1 -1
  22. package/dist/{SSMStateProvider-CksOTB8M.mjs.map → SSMStateProvider-BlrTiV5m.mjs.map} +1 -1
  23. package/dist/{SSMStateProvider-CT8tjl9o.cjs → SSMStateProvider-Bt6r8CTL.cjs} +1 -1
  24. package/dist/{SSMStateProvider-CT8tjl9o.cjs.map → SSMStateProvider-Bt6r8CTL.cjs.map} +1 -1
  25. package/dist/{backup-provisioner-C4noe75O.cjs → backup-provisioner-B3HBN-2k.cjs} +1 -1
  26. package/dist/{backup-provisioner-C4noe75O.cjs.map → backup-provisioner-B3HBN-2k.cjs.map} +1 -1
  27. package/dist/{backup-provisioner-BEXoHTuC.mjs → backup-provisioner-Xunq-p_8.mjs} +1 -1
  28. package/dist/{backup-provisioner-BEXoHTuC.mjs.map → backup-provisioner-Xunq-p_8.mjs.map} +1 -1
  29. package/dist/{bundler-c6QvGUBM.mjs → bundler-DkA4qzUD.mjs} +2 -2
  30. package/dist/{bundler-c6QvGUBM.mjs.map → bundler-DkA4qzUD.mjs.map} +1 -1
  31. package/dist/{bundler-BG6P249p.cjs → bundler-Dufq3uvI.cjs} +2 -2
  32. package/dist/{bundler-BG6P249p.cjs.map → bundler-Dufq3uvI.cjs.map} +1 -1
  33. package/dist/config.d.mts +2 -2
  34. package/dist/dokploy-api-BPYkG-ZZ.cjs +3 -0
  35. package/dist/dokploy-api-BiSzWM4v.mjs +3 -0
  36. package/dist/{dokploy-api-Dvyq-LsM.cjs → dokploy-api-CqOIKHme.cjs} +1 -1
  37. package/dist/{dokploy-api-Dvyq-LsM.cjs.map → dokploy-api-CqOIKHme.cjs.map} +1 -1
  38. package/dist/{dokploy-api-BIT0683o.mjs → dokploy-api-sfZFlWuE.mjs} +1 -1
  39. package/dist/{dokploy-api-BIT0683o.mjs.map → dokploy-api-sfZFlWuE.mjs.map} +1 -1
  40. package/dist/{encryption-CipsS0KW.cjs → encryption-BVS7Vmzs.cjs} +1 -1
  41. package/dist/encryption-Bd_m4q3g.mjs +3 -0
  42. package/dist/{encryption-DSkIlEZ6.mjs → encryption-CO5Q4Bhf.mjs} +1 -1
  43. package/dist/{encryption-DSkIlEZ6.mjs.map → encryption-CO5Q4Bhf.mjs.map} +1 -1
  44. package/dist/{encryption-DgKS-Dv9.cjs → encryption-MfXAiFTm.cjs} +1 -1
  45. package/dist/{encryption-DgKS-Dv9.cjs.map → encryption-MfXAiFTm.cjs.map} +1 -1
  46. package/dist/{index-BBvGMG_A.d.mts → index-Dx9Kk5bR.d.mts} +2 -2
  47. package/dist/{index-BBvGMG_A.d.mts.map → index-Dx9Kk5bR.d.mts.map} +1 -1
  48. package/dist/index.cjs +596 -1314
  49. package/dist/index.cjs.map +1 -1
  50. package/dist/index.mjs +725 -1443
  51. package/dist/index.mjs.map +1 -1
  52. package/dist/{openapi-DHp49UJV.cjs → openapi-CG1LBk7s.cjs} +1 -1
  53. package/dist/{openapi-DHp49UJV.cjs.map → openapi-CG1LBk7s.cjs.map} +1 -1
  54. package/dist/{openapi-DF9GyhyZ.mjs → openapi-CtRVU6RD.mjs} +1 -1
  55. package/dist/{openapi-DF9GyhyZ.mjs.map → openapi-CtRVU6RD.mjs.map} +1 -1
  56. package/dist/{openapi-react-query-C4UdILaI.mjs → openapi-react-query-C4iIvpPf.mjs} +1 -1
  57. package/dist/{openapi-react-query-C4UdILaI.mjs.map → openapi-react-query-C4iIvpPf.mjs.map} +1 -1
  58. package/dist/{openapi-react-query-DYbBq-WJ.cjs → openapi-react-query-CYWik72n.cjs} +1 -1
  59. package/dist/{openapi-react-query-DYbBq-WJ.cjs.map → openapi-react-query-CYWik72n.cjs.map} +1 -1
  60. package/dist/openapi-react-query.cjs +1 -1
  61. package/dist/openapi-react-query.mjs +1 -1
  62. package/dist/openapi.cjs +1 -1
  63. package/dist/openapi.d.mts +1 -1
  64. package/dist/openapi.mjs +1 -1
  65. package/dist/{reconcile-CYGmqXvq.mjs → reconcile-B2FoJE0a.mjs} +1 -1
  66. package/dist/{reconcile-CYGmqXvq.mjs.map → reconcile-B2FoJE0a.mjs.map} +1 -1
  67. package/dist/{reconcile-D93YG7G5.cjs → reconcile-BzqKJ2Rt.cjs} +1 -1
  68. package/dist/{reconcile-D93YG7G5.cjs.map → reconcile-BzqKJ2Rt.cjs.map} +1 -1
  69. package/dist/setup-DDXUXzzl.mjs +765 -0
  70. package/dist/setup-DDXUXzzl.mjs.map +1 -0
  71. package/dist/setup-DLwhfyt-.cjs +8 -0
  72. package/dist/setup-Dh-B7YTX.mjs +8 -0
  73. package/dist/setup-WZ-xqAQO.cjs +849 -0
  74. package/dist/setup-WZ-xqAQO.cjs.map +1 -0
  75. package/dist/{sync-COnAugP-.mjs → sync-B5UHXwRM.mjs} +1 -1
  76. package/dist/sync-BLA94rgC.cjs +4 -0
  77. package/dist/{sync-D_NowTkZ.mjs → sync-CSooqRQ8.mjs} +1 -1
  78. package/dist/{sync-D_NowTkZ.mjs.map → sync-CSooqRQ8.mjs.map} +1 -1
  79. package/dist/{sync-DGXXSk2v.cjs → sync-o_CtEbvR.cjs} +1 -1
  80. package/dist/{sync-DGXXSk2v.cjs.map → sync-o_CtEbvR.cjs.map} +1 -1
  81. package/dist/{types-OszPdw9m.d.mts → types-CqfhdmRi.d.mts} +1 -1
  82. package/dist/{types-OszPdw9m.d.mts.map → types-CqfhdmRi.d.mts.map} +1 -1
  83. package/dist/workspace/index.d.mts +2 -2
  84. package/package.json +5 -5
  85. package/src/index.ts +4 -0
  86. package/src/init/versions.ts +5 -5
  87. package/src/setup/__tests__/auto-setup.spec.ts +147 -0
  88. package/src/setup/index.ts +46 -6
  89. package/src/test/index.ts +17 -0
  90. package/dist/CachedStateProvider-CC0DyO09.mjs +0 -3
  91. package/dist/dokploy-api-BOx71j6F.cjs +0 -3
  92. package/dist/dokploy-api-BZGk-lkD.mjs +0 -3
  93. package/dist/encryption-Db2zvGqM.mjs +0 -3
  94. package/dist/sync-D1Pa30oV.cjs +0 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-WZ-xqAQO.cjs","names":["logger","envConfig: string | string[] | undefined","cwd: string","loaded: string[]","missing: string[]","port: number","resolve","err: NodeJS.ErrnoException","preferredPort: number","composePath: string","results: ComposePortMapping[]","workspaceRoot: string","ports: PortState","service: string","containerPort: number","dockerEnv: Record<string, string>","url: string","oldPort: number","newPort: number","secrets: Record<string, string>","resolvedPorts: ResolvedServicePorts","portReplacements: { defaultPort: number; resolvedPort: number }[]","secretsEnv?: Record<string, string>","portEnv?: Record<string, string>","workspace: { root: string; apps: Record<string, unknown> }","secretsRoot: string","appName?: string","stages: string[]","startDir: string","secretsJsonPath: string","preloadPath: string","wrapperPath: string","entryPath: string","secretsJsonPath?: string","options: {\n\texplicitPort?: number;\n\tcwd?: string;\n\tresolveDockerPorts?: 'full' | 'readonly';\n\t/** Secret stages to try, in order. Default: ['dev', 'development'] */\n\tstages?: string[];\n\t/** Start Docker Compose services after port resolution. Default: false */\n\tstartDocker?: boolean;\n\t/** Custom secrets JSON filename. Default: 'dev-secrets-{appName}.json' or 'dev-secrets.json' */\n\tsecretsFileName?: string;\n}","workspaceAppPort: number | undefined","appName: string | undefined","appInfo: WorkspaceAppInfo | undefined","options: SetupOptions","loadedConfig: LoadedConfig","stage: string","workspace: NormalizedWorkspace","secrets: StageSecrets","serviceMap: {\n\t\tkey: keyof typeof workspace.services;\n\t\tname: ComposeServiceName;\n\t}[]","missing: Record<string, string>","serviceNames: ComposeServiceName[]","cwd: string"],"sources":["../src/credentials/index.ts","../src/setup/index.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { createServer } from 'node:net';\nimport { dirname, join, resolve } from 'node:path';\nimport { config as dotenvConfig } from 'dotenv';\nimport { parse as parseYaml } from 'yaml';\nimport {\n\tgetAppNameFromCwd,\n\tloadWorkspaceAppInfo,\n\ttype WorkspaceAppInfo,\n} from '../config';\nimport { generatePgBossUrl } from '../secrets/generator.js';\nimport {\n\treadStageSecrets,\n\tsecretsExist,\n\ttoEmbeddableSecrets,\n} from '../secrets/storage.js';\nimport { getDependencyEnvVars } from '../workspace/index.js';\n\nconst logger = console;\n\n// ---------------------------------------------------------------------------\n// Environment files\n// ---------------------------------------------------------------------------\n\n/**\n * Load environment files\n * @internal Exported for testing\n */\nexport function loadEnvFiles(\n\tenvConfig: string | string[] | undefined,\n\tcwd: string = process.cwd(),\n): { loaded: string[]; missing: string[] } {\n\tconst loaded: string[] = [];\n\tconst missing: string[] = [];\n\n\t// Normalize to array\n\tconst envFiles = envConfig\n\t\t? Array.isArray(envConfig)\n\t\t\t? envConfig\n\t\t\t: [envConfig]\n\t\t: ['.env'];\n\n\t// Load each env file in order (later files override earlier)\n\tfor (const envFile of envFiles) {\n\t\tconst envPath = resolve(cwd, envFile);\n\t\tif (existsSync(envPath)) {\n\t\t\tdotenvConfig({ path: envPath, override: true, quiet: true });\n\t\t\tloaded.push(envFile);\n\t\t} else if (envConfig) {\n\t\t\t// Only report as missing if explicitly configured\n\t\t\tmissing.push(envFile);\n\t\t}\n\t}\n\n\treturn { loaded, missing };\n}\n\n// ---------------------------------------------------------------------------\n// Port utilities\n// ---------------------------------------------------------------------------\n\n/**\n * Check if a port is available\n * @internal Exported for testing\n */\nexport async function isPortAvailable(port: number): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst server = createServer();\n\n\t\tserver.once('error', (err: NodeJS.ErrnoException) => {\n\t\t\tif (err.code === 'EADDRINUSE') {\n\t\t\t\tresolve(false);\n\t\t\t} else {\n\t\t\t\tresolve(false);\n\t\t\t}\n\t\t});\n\n\t\tserver.once('listening', () => {\n\t\t\tserver.close();\n\t\t\tresolve(true);\n\t\t});\n\n\t\tserver.listen(port);\n\t});\n}\n\n/**\n * Find an available port starting from the preferred port\n * @internal Exported for testing\n */\nexport async function findAvailablePort(\n\tpreferredPort: number,\n\tmaxAttempts = 10,\n): Promise<number> {\n\tfor (let i = 0; i < maxAttempts; i++) {\n\t\tconst port = preferredPort + i;\n\t\tif (await isPortAvailable(port)) {\n\t\t\treturn port;\n\t\t}\n\t\tlogger.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);\n\t}\n\n\tthrow new Error(\n\t\t`Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`,\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Docker Compose port mapping\n// ---------------------------------------------------------------------------\n\n/**\n * A port mapping extracted from docker-compose.yml.\n * Only entries using env var interpolation (e.g., `${VAR:-default}:container`) are captured.\n */\nexport interface ComposePortMapping {\n\tservice: string;\n\tenvVar: string;\n\tdefaultPort: number;\n\tcontainerPort: number;\n}\n\n/** Port state persisted to .gkm/ports.json, keyed by env var name. */\nexport type PortState = Record<string, number>;\n\nexport interface ResolvedServicePorts {\n\tdockerEnv: Record<string, string>;\n\tports: PortState;\n\tmappings: ComposePortMapping[];\n}\n\nconst PORT_STATE_PATH = '.gkm/ports.json';\n\n/**\n * Parse docker-compose.yml and extract all port mappings that use env var interpolation.\n * Entries like `'${POSTGRES_HOST_PORT:-5432}:5432'` are captured.\n * Fixed port mappings like `'5050:80'` are skipped.\n * @internal Exported for testing\n */\nexport function parseComposePortMappings(\n\tcomposePath: string,\n): ComposePortMapping[] {\n\tif (!existsSync(composePath)) {\n\t\treturn [];\n\t}\n\n\tconst content = readFileSync(composePath, 'utf-8');\n\tconst compose = parseYaml(content) as {\n\t\tservices?: Record<string, { ports?: string[] }>;\n\t};\n\n\tif (!compose?.services) {\n\t\treturn [];\n\t}\n\n\tconst results: ComposePortMapping[] = [];\n\n\tfor (const [serviceName, serviceConfig] of Object.entries(compose.services)) {\n\t\tfor (const portMapping of serviceConfig?.ports ?? []) {\n\t\t\tconst match = String(portMapping).match(/\\$\\{(\\w+):-(\\d+)\\}:(\\d+)/);\n\t\t\tif (match?.[1] && match[2] && match[3]) {\n\t\t\t\tresults.push({\n\t\t\t\t\tservice: serviceName,\n\t\t\t\t\tenvVar: match[1],\n\t\t\t\t\tdefaultPort: Number(match[2]),\n\t\t\t\t\tcontainerPort: Number(match[3]),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\treturn results;\n}\n\n/**\n * Load saved port state from .gkm/ports.json.\n * @internal Exported for testing\n */\nexport async function loadPortState(workspaceRoot: string): Promise<PortState> {\n\ttry {\n\t\tconst raw = await readFile(join(workspaceRoot, PORT_STATE_PATH), 'utf-8');\n\t\treturn JSON.parse(raw) as PortState;\n\t} catch {\n\t\treturn {};\n\t}\n}\n\n/**\n * Save port state to .gkm/ports.json.\n * @internal Exported for testing\n */\nexport async function savePortState(\n\tworkspaceRoot: string,\n\tports: PortState,\n): Promise<void> {\n\tconst dir = join(workspaceRoot, '.gkm');\n\tawait mkdir(dir, { recursive: true });\n\tawait writeFile(\n\t\tjoin(workspaceRoot, PORT_STATE_PATH),\n\t\t`${JSON.stringify(ports, null, 2)}\\n`,\n\t);\n}\n\n/**\n * Check if a project's own Docker container is running and return its host port.\n * Uses `docker compose port` scoped to the project's compose file.\n * @internal Exported for testing\n */\nexport function getContainerHostPort(\n\tworkspaceRoot: string,\n\tservice: string,\n\tcontainerPort: number,\n): number | null {\n\ttry {\n\t\tconst result = execSync(`docker compose port ${service} ${containerPort}`, {\n\t\t\tcwd: workspaceRoot,\n\t\t\tstdio: 'pipe',\n\t\t})\n\t\t\t.toString()\n\t\t\t.trim();\n\t\tconst match = result.match(/:(\\d+)$/);\n\t\treturn match ? Number(match[1]) : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Resolve host ports for Docker services by parsing docker-compose.yml.\n * Priority: running container → saved state → find available port.\n * Persists resolved ports to .gkm/ports.json.\n * @internal Exported for testing\n */\nexport async function resolveServicePorts(\n\tworkspaceRoot: string,\n): Promise<ResolvedServicePorts> {\n\tconst composePath = join(workspaceRoot, 'docker-compose.yml');\n\tconst mappings = parseComposePortMappings(composePath);\n\n\tif (mappings.length === 0) {\n\t\treturn { dockerEnv: {}, ports: {}, mappings: [] };\n\t}\n\n\tconst savedState = await loadPortState(workspaceRoot);\n\tconst dockerEnv: Record<string, string> = {};\n\tconst ports: PortState = {};\n\t// Track ports assigned in this cycle to avoid duplicates\n\tconst assignedPorts = new Set<number>();\n\n\tlogger.log('\\n🔌 Resolving service ports...');\n\n\tfor (const mapping of mappings) {\n\t\t// 1. Check if own container is already running\n\t\tconst containerPort = getContainerHostPort(\n\t\t\tworkspaceRoot,\n\t\t\tmapping.service,\n\t\t\tmapping.containerPort,\n\t\t);\n\t\tif (containerPort !== null) {\n\t\t\tports[mapping.envVar] = containerPort;\n\t\t\tdockerEnv[mapping.envVar] = String(containerPort);\n\t\t\tassignedPorts.add(containerPort);\n\t\t\tlogger.log(\n\t\t\t\t` 🔄 ${mapping.service}:${mapping.containerPort}: reusing existing container on port ${containerPort}`,\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// 2. Check saved port state\n\t\tconst savedPort = savedState[mapping.envVar];\n\t\tif (\n\t\t\tsavedPort &&\n\t\t\t!assignedPorts.has(savedPort) &&\n\t\t\t(await isPortAvailable(savedPort))\n\t\t) {\n\t\t\tports[mapping.envVar] = savedPort;\n\t\t\tdockerEnv[mapping.envVar] = String(savedPort);\n\t\t\tassignedPorts.add(savedPort);\n\t\t\tlogger.log(\n\t\t\t\t` 💾 ${mapping.service}:${mapping.containerPort}: using saved port ${savedPort}`,\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\n\t\t// 3. Find available port (skipping ports already assigned this cycle)\n\t\tlet resolvedPort = await findAvailablePort(mapping.defaultPort);\n\t\twhile (assignedPorts.has(resolvedPort)) {\n\t\t\tresolvedPort = await findAvailablePort(resolvedPort + 1);\n\t\t}\n\t\tports[mapping.envVar] = resolvedPort;\n\t\tdockerEnv[mapping.envVar] = String(resolvedPort);\n\t\tassignedPorts.add(resolvedPort);\n\n\t\tif (resolvedPort !== mapping.defaultPort) {\n\t\t\tlogger.log(\n\t\t\t\t` ⚡ ${mapping.service}:${mapping.containerPort}: port ${mapping.defaultPort} occupied, using port ${resolvedPort}`,\n\t\t\t);\n\t\t} else {\n\t\t\tlogger.log(\n\t\t\t\t` ✅ ${mapping.service}:${mapping.containerPort}: using default port ${resolvedPort}`,\n\t\t\t);\n\t\t}\n\t}\n\n\tawait savePortState(workspaceRoot, ports);\n\n\treturn { dockerEnv, ports, mappings };\n}\n\n// ---------------------------------------------------------------------------\n// URL rewriting\n// ---------------------------------------------------------------------------\n\n/**\n * Replace a port in a URL string.\n * Handles both `hostname:port` and `localhost:port` patterns.\n * @internal Exported for testing\n */\nexport function replacePortInUrl(\n\turl: string,\n\toldPort: number,\n\tnewPort: number,\n): string {\n\tif (oldPort === newPort) return url;\n\t// Replace literal :port (in authority section)\n\tlet result = url.replace(\n\t\tnew RegExp(`:${oldPort}(?=[/?#]|$)`, 'g'),\n\t\t`:${newPort}`,\n\t);\n\t// Replace URL-encoded :port (e.g., in query params like endpoint=http%3A%2F%2Flocalhost%3A4566)\n\tresult = result.replace(\n\t\tnew RegExp(`%3A${oldPort}(?=[%/?#&]|$)`, 'gi'),\n\t\t`%3A${newPort}`,\n\t);\n\treturn result;\n}\n\n/**\n * Rewrite connection URLs and port vars in secrets with resolved ports.\n * Uses the parsed compose mappings to determine which default ports to replace.\n * Pure transform — does not modify secrets on disk.\n * @internal Exported for testing\n */\nexport function rewriteUrlsWithPorts(\n\tsecrets: Record<string, string>,\n\tresolvedPorts: ResolvedServicePorts,\n): Record<string, string> {\n\tconst { ports, mappings } = resolvedPorts;\n\tconst result = { ...secrets };\n\n\t// Build a map of defaultPort → resolvedPort for all changed ports\n\tconst portReplacements: { defaultPort: number; resolvedPort: number }[] = [];\n\t// Collect Docker service names for hostname rewriting\n\tconst serviceNames = new Set<string>();\n\tfor (const mapping of mappings) {\n\t\tserviceNames.add(mapping.service);\n\t\tconst resolved = ports[mapping.envVar];\n\t\tif (resolved !== undefined) {\n\t\t\tportReplacements.push({\n\t\t\t\tdefaultPort: mapping.defaultPort,\n\t\t\t\tresolvedPort: resolved,\n\t\t\t});\n\t\t}\n\t}\n\n\t// Rewrite _HOST env vars that use Docker service names\n\tfor (const [key, value] of Object.entries(result)) {\n\t\tif (!key.endsWith('_HOST')) continue;\n\t\tif (serviceNames.has(value)) {\n\t\t\tresult[key] = 'localhost';\n\t\t}\n\t}\n\n\t// Rewrite _PORT env vars whose values match a default port\n\tfor (const [key, value] of Object.entries(result)) {\n\t\tif (!key.endsWith('_PORT')) continue;\n\t\tfor (const { defaultPort, resolvedPort } of portReplacements) {\n\t\t\tif (value === String(defaultPort)) {\n\t\t\t\tresult[key] = String(resolvedPort);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Rewrite URLs: replace Docker service hostnames with localhost and fix ports\n\tfor (const [key, value] of Object.entries(result)) {\n\t\tif (\n\t\t\t!key.endsWith('_URL') &&\n\t\t\t!key.endsWith('_ENDPOINT') &&\n\t\t\t!key.endsWith('_CONNECTION_STRING') &&\n\t\t\tkey !== 'DATABASE_URL'\n\t\t)\n\t\t\tcontinue;\n\n\t\tlet rewritten = value;\n\t\tfor (const name of serviceNames) {\n\t\t\trewritten = rewritten.replace(\n\t\t\t\tnew RegExp(`@${name}:`, 'g'),\n\t\t\t\t'@localhost:',\n\t\t\t);\n\t\t}\n\t\tfor (const { defaultPort, resolvedPort } of portReplacements) {\n\t\t\trewritten = replacePortInUrl(rewritten, defaultPort, resolvedPort);\n\t\t}\n\t\tresult[key] = rewritten;\n\t}\n\n\treturn result;\n}\n\n// ---------------------------------------------------------------------------\n// Docker Compose services\n// ---------------------------------------------------------------------------\n\n/**\n * Build the environment variables to pass to `docker compose up`.\n * Merges process.env, secrets, and port mappings so that Docker Compose\n * can interpolate variables like ${POSTGRES_USER} correctly.\n * @internal Exported for testing\n */\nexport function buildDockerComposeEnv(\n\tsecretsEnv?: Record<string, string>,\n\tportEnv?: Record<string, string>,\n): Record<string, string | undefined> {\n\treturn { ...process.env, ...secretsEnv, ...portEnv };\n}\n\n/**\n * Parse all service names from a docker-compose.yml file.\n * @internal Exported for testing\n */\nexport function parseComposeServiceNames(composePath: string): string[] {\n\tif (!existsSync(composePath)) {\n\t\treturn [];\n\t}\n\n\tconst content = readFileSync(composePath, 'utf-8');\n\tconst compose = parseYaml(content) as {\n\t\tservices?: Record<string, unknown>;\n\t};\n\n\treturn Object.keys(compose?.services ?? {});\n}\n\n/**\n * Start docker-compose services for a single-app project (no workspace config).\n * Starts all services defined in docker-compose.yml.\n */\nexport async function startComposeServices(\n\tcwd: string,\n\tportEnv?: Record<string, string>,\n\tsecretsEnv?: Record<string, string>,\n): Promise<void> {\n\tconst composeFile = join(cwd, 'docker-compose.yml');\n\tif (!existsSync(composeFile)) {\n\t\treturn;\n\t}\n\n\tconst servicesToStart = parseComposeServiceNames(composeFile);\n\tif (servicesToStart.length === 0) {\n\t\treturn;\n\t}\n\n\tlogger.log(`🐳 Starting services: ${servicesToStart.join(', ')}`);\n\n\ttry {\n\t\texecSync(`docker compose up -d ${servicesToStart.join(' ')}`, {\n\t\t\tcwd,\n\t\t\tstdio: 'inherit',\n\t\t\tenv: buildDockerComposeEnv(secretsEnv, portEnv),\n\t\t});\n\n\t\tlogger.log('✅ Services started');\n\t} catch (error) {\n\t\tlogger.error('❌ Failed to start services:', (error as Error).message);\n\t\tthrow error;\n\t}\n}\n\n/**\n * Start docker-compose services for a workspace.\n * Discovers all services from docker-compose.yml and starts everything\n * except app services (which are managed by turbo).\n * @internal Exported for testing\n */\nexport async function startWorkspaceServices(\n\tworkspace: { root: string; apps: Record<string, unknown> },\n\tportEnv?: Record<string, string>,\n\tsecretsEnv?: Record<string, string>,\n): Promise<void> {\n\tconst composeFile = join(workspace.root, 'docker-compose.yml');\n\tif (!existsSync(composeFile)) {\n\t\treturn;\n\t}\n\n\t// Discover all services from docker-compose.yml\n\tconst allServices = parseComposeServiceNames(composeFile);\n\n\t// Exclude app services (managed by turbo, not docker)\n\tconst appNames = new Set(Object.keys(workspace.apps));\n\tconst servicesToStart = allServices.filter((name) => !appNames.has(name));\n\n\tif (servicesToStart.length === 0) {\n\t\treturn;\n\t}\n\n\tlogger.log(`🐳 Starting services: ${servicesToStart.join(', ')}`);\n\n\ttry {\n\t\t// Start services with docker-compose, passing secrets so that\n\t\t// POSTGRES_USER, POSTGRES_PASSWORD, etc. are interpolated correctly\n\t\texecSync(`docker compose up -d ${servicesToStart.join(' ')}`, {\n\t\t\tcwd: workspace.root,\n\t\t\tstdio: 'inherit',\n\t\t\tenv: buildDockerComposeEnv(secretsEnv, portEnv),\n\t\t});\n\n\t\tlogger.log('✅ Services started');\n\t} catch (error) {\n\t\tlogger.error('❌ Failed to start services:', (error as Error).message);\n\t\tthrow error;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Secrets loading\n// ---------------------------------------------------------------------------\n\n/**\n * Load and flatten secrets for an app from encrypted storage.\n * For workspace app: maps {APP}_DATABASE_URL → DATABASE_URL.\n * @internal Exported for testing\n */\nexport async function loadSecretsForApp(\n\tsecretsRoot: string,\n\tappName?: string,\n\tstages: string[] = ['dev', 'development'],\n): Promise<Record<string, string>> {\n\tlet secrets: Record<string, string> = {};\n\n\tfor (const stage of stages) {\n\t\tif (secretsExist(stage, secretsRoot)) {\n\t\t\tconst stageSecrets = await readStageSecrets(stage, secretsRoot);\n\t\t\tif (stageSecrets) {\n\t\t\t\tlogger.log(`🔐 Loading secrets from stage: ${stage}`);\n\t\t\t\tsecrets = toEmbeddableSecrets(stageSecrets);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (Object.keys(secrets).length === 0) {\n\t\treturn {};\n\t}\n\n\t// Single app mode - no mapping needed\n\tif (!appName) {\n\t\treturn secrets;\n\t}\n\n\t// Workspace app mode - map {APP}_* to generic names\n\tconst prefix = appName.toUpperCase();\n\tconst mapped = { ...secrets };\n\n\t// Map {APP}_DATABASE_URL → DATABASE_URL\n\tconst appDbUrl = secrets[`${prefix}_DATABASE_URL`];\n\tif (appDbUrl) {\n\t\tmapped.DATABASE_URL = appDbUrl;\n\t}\n\n\treturn mapped;\n}\n\n/**\n * Walk up the directory tree to find the root containing .gkm/secrets/.\n * @internal Exported for testing\n */\nexport function findSecretsRoot(startDir: string): string {\n\tlet dir = startDir;\n\twhile (dir !== '/') {\n\t\tif (existsSync(join(dir, '.gkm', 'secrets'))) {\n\t\t\treturn dir;\n\t\t}\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) break;\n\t\tdir = parent;\n\t}\n\treturn startDir;\n}\n\n// ---------------------------------------------------------------------------\n// Credentials preload / injection\n// ---------------------------------------------------------------------------\n\n/**\n * Generate the credentials injection code snippet.\n * This is the common logic used by both entry wrapper and exec preload.\n * @internal\n */\nfunction generateCredentialsInjection(secretsJsonPath: string): string {\n\treturn `import { existsSync, readFileSync } from 'node:fs';\n\n// Inject dev secrets via globalThis and process.env\n// Using globalThis.__gkm_credentials__ avoids CJS/ESM interop issues where\n// Object.assign on the Credentials export only mutates one module copy.\nconst secretsPath = '${secretsJsonPath}';\nif (existsSync(secretsPath)) {\n const secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));\n globalThis.__gkm_credentials__ = secrets;\n Object.assign(process.env, secrets);\n}\n`;\n}\n\n/**\n * Create a preload script that injects secrets into Credentials.\n * Used by `gkm exec` to inject secrets before running any command.\n * @internal Exported for testing\n */\nexport async function createCredentialsPreload(\n\tpreloadPath: string,\n\tsecretsJsonPath: string,\n): Promise<void> {\n\tconst content = `/**\n * Credentials preload generated by 'gkm exec'\n * This file is loaded via NODE_OPTIONS=\"--import <path>\"\n */\n${generateCredentialsInjection(secretsJsonPath)}`;\n\n\tawait writeFile(preloadPath, content);\n}\n\n/**\n * Create a wrapper script that injects secrets before importing the entry file.\n * @internal Exported for testing\n */\nexport async function createEntryWrapper(\n\twrapperPath: string,\n\tentryPath: string,\n\tsecretsJsonPath?: string,\n): Promise<void> {\n\tconst credentialsInjection = secretsJsonPath\n\t\t? `${generateCredentialsInjection(secretsJsonPath)}\n`\n\t\t: '';\n\n\t// Use dynamic import() to ensure secrets are assigned before the entry file loads\n\t// Static imports are hoisted, so Object.assign would run after the entry file is loaded\n\tconst content = `#!/usr/bin/env node\n/**\n * Entry wrapper generated by 'gkm dev --entry'\n */\n${credentialsInjection}// Import and run the user's entry file (dynamic import ensures secrets load first)\nawait import('${entryPath}');\n`;\n\n\tawait writeFile(wrapperPath, content);\n}\n\n// ---------------------------------------------------------------------------\n// Prepare credentials (shared by dev, exec, test)\n// ---------------------------------------------------------------------------\n\n/**\n * Result of preparing credentials.\n */\nexport interface EntryCredentialsResult {\n\t/** Credentials to inject (secrets + PORT) */\n\tcredentials: Record<string, string>;\n\t/** Resolved port (from --port, workspace config, or default 3000) */\n\tresolvedPort: number;\n\t/** Path where credentials JSON was written */\n\tsecretsJsonPath: string;\n\t/** Resolved app name (if in workspace) */\n\tappName: string | undefined;\n\t/** Secrets root directory */\n\tsecretsRoot: string;\n\t/** Workspace app info (if in a workspace) */\n\tappInfo?: WorkspaceAppInfo;\n}\n\n/**\n * Prepare credentials for dev/exec/test modes.\n * Loads workspace config, secrets, resolves Docker ports, rewrites URLs,\n * injects PORT, dependency URLs, and writes credentials JSON.\n *\n * @param options.resolveDockerPorts - How to resolve Docker ports:\n * - `'full'` (default): probe running containers, saved state, then find available ports. Used by dev/test.\n * - `'readonly'`: check running containers and saved state only, never probe for new ports. Used by exec.\n * @param options.stages - Secret stages to try, in order. Default: ['dev', 'development'].\n * @param options.startDocker - Start Docker Compose services after port resolution. Default: false.\n * @param options.secretsFileName - Custom secrets JSON filename. Default: 'dev-secrets-{appName}.json' or 'dev-secrets.json'.\n * @internal Exported for testing\n */\nexport async function prepareEntryCredentials(options: {\n\texplicitPort?: number;\n\tcwd?: string;\n\tresolveDockerPorts?: 'full' | 'readonly';\n\t/** Secret stages to try, in order. Default: ['dev', 'development'] */\n\tstages?: string[];\n\t/** Start Docker Compose services after port resolution. Default: false */\n\tstartDocker?: boolean;\n\t/** Custom secrets JSON filename. Default: 'dev-secrets-{appName}.json' or 'dev-secrets.json' */\n\tsecretsFileName?: string;\n}): Promise<EntryCredentialsResult> {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst portMode = options.resolveDockerPorts ?? 'full';\n\n\t// Try to get workspace app config for port and secrets\n\tlet workspaceAppPort: number | undefined;\n\tlet secretsRoot: string = cwd;\n\tlet appName: string | undefined;\n\tlet appInfo: WorkspaceAppInfo | undefined;\n\n\ttry {\n\t\tappInfo = await loadWorkspaceAppInfo(cwd);\n\t\tworkspaceAppPort = appInfo.app.port;\n\t\tsecretsRoot = appInfo.workspaceRoot;\n\t\tappName = appInfo.appName;\n\t} catch {\n\t\t// Not in a workspace - use defaults (expected for non-gkm apps using gkm exec)\n\t\tsecretsRoot = findSecretsRoot(cwd);\n\t\tappName = getAppNameFromCwd(cwd) ?? undefined;\n\t}\n\n\t// Determine port: explicit --port > workspace config > default 3000\n\tconst resolvedPort = options.explicitPort ?? workspaceAppPort ?? 3000;\n\n\t// Load secrets and inject PORT\n\tconst credentials = await loadSecretsForApp(\n\t\tsecretsRoot,\n\t\tappName,\n\t\toptions.stages,\n\t);\n\n\t// Always inject PORT into credentials so apps can read it\n\tcredentials.PORT = String(resolvedPort);\n\n\t// Resolve Docker ports and rewrite connection URLs\n\tconst composePath = join(secretsRoot, 'docker-compose.yml');\n\tconst mappings = parseComposePortMappings(composePath);\n\tif (mappings.length > 0) {\n\t\tlet resolvedPorts: ResolvedServicePorts;\n\n\t\tif (portMode === 'full') {\n\t\t\t// Full resolution: probe containers, saved state, find available ports\n\t\t\tresolvedPorts = await resolveServicePorts(secretsRoot);\n\t\t} else {\n\t\t\t// Readonly: check running containers and saved state only\n\t\t\tconst savedPorts = await loadPortState(secretsRoot);\n\t\t\tconst ports: PortState = {};\n\n\t\t\tfor (const mapping of mappings) {\n\t\t\t\tconst containerPort = getContainerHostPort(\n\t\t\t\t\tsecretsRoot,\n\t\t\t\t\tmapping.service,\n\t\t\t\t\tmapping.containerPort,\n\t\t\t\t);\n\t\t\t\tif (containerPort !== null) {\n\t\t\t\t\tports[mapping.envVar] = containerPort;\n\t\t\t\t} else {\n\t\t\t\t\tconst saved = savedPorts[mapping.envVar];\n\t\t\t\t\tif (saved !== undefined) {\n\t\t\t\t\t\tports[mapping.envVar] = saved;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresolvedPorts = { dockerEnv: {}, ports, mappings };\n\t\t}\n\n\t\t// Start Docker services if requested (between port resolution and URL rewriting)\n\t\t// Docker needs raw secrets (POSTGRES_USER, etc.) + resolved port env for compose interpolation\n\t\tif (options.startDocker) {\n\t\t\tif (appInfo) {\n\t\t\t\tawait startWorkspaceServices(\n\t\t\t\t\tappInfo.workspace,\n\t\t\t\t\tresolvedPorts.dockerEnv,\n\t\t\t\t\tcredentials,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tawait startComposeServices(\n\t\t\t\t\tsecretsRoot,\n\t\t\t\t\tresolvedPorts.dockerEnv,\n\t\t\t\t\tcredentials,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(resolvedPorts.ports).length > 0) {\n\t\t\tconst rewritten = rewriteUrlsWithPorts(credentials, resolvedPorts);\n\t\t\tObject.assign(credentials, rewritten);\n\t\t\tlogger.log(\n\t\t\t\t`🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`,\n\t\t\t);\n\t\t}\n\t}\n\n\t// Inject dependency URLs (works for both frontend and backend apps)\n\tif (appInfo?.appName) {\n\t\tconst depEnv = getDependencyEnvVars(appInfo.workspace, appInfo.appName);\n\t\tObject.assign(credentials, depEnv);\n\t}\n\n\t// Default event connection strings to pgboss when postgres is available.\n\t// Normally these come from secrets (gkm setup always creates pgboss credentials),\n\t// but this handles old secrets files that predate the pgboss default.\n\tif (\n\t\t!credentials.EVENT_PUBLISHER_CONNECTION_STRING &&\n\t\t!credentials.EVENT_SUBSCRIBER_CONNECTION_STRING &&\n\t\tcredentials.PGBOSS_DB_USER\n\t) {\n\t\tconst pgbossUrl = generatePgBossUrl({\n\t\t\tusername: credentials.PGBOSS_DB_USER,\n\t\t\tpassword: credentials.PGBOSS_DB_PASSWORD ?? '',\n\t\t\thost: credentials.POSTGRES_HOST ?? 'localhost',\n\t\t\tport: Number(credentials.POSTGRES_PORT ?? '5432'),\n\t\t\tdatabase: credentials.POSTGRES_DB ?? 'app',\n\t\t});\n\t\tcredentials.EVENT_PUBLISHER_CONNECTION_STRING = pgbossUrl;\n\t\tcredentials.EVENT_SUBSCRIBER_CONNECTION_STRING = pgbossUrl;\n\t}\n\n\t// Write secrets to temp JSON file (always write since we have PORT)\n\t// Use app-specific filename to avoid race conditions when running multiple apps via turbo\n\tconst secretsDir = join(secretsRoot, '.gkm');\n\tawait mkdir(secretsDir, { recursive: true });\n\tconst secretsFileName =\n\t\toptions.secretsFileName ??\n\t\t(appName ? `dev-secrets-${appName}.json` : 'dev-secrets.json');\n\tconst secretsJsonPath = join(secretsDir, secretsFileName);\n\tawait writeFile(secretsJsonPath, JSON.stringify(credentials, null, 2));\n\n\treturn {\n\t\tcredentials,\n\t\tresolvedPort,\n\t\tsecretsJsonPath,\n\t\tappName,\n\t\tsecretsRoot,\n\t\tappInfo,\n\t};\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport prompts from 'prompts';\nimport { loadWorkspaceConfig } from '../config.js';\nimport {\n\tresolveServicePorts,\n\tstartWorkspaceServices,\n} from '../credentials/index.js';\nimport {\n\tcreateStageSecrets,\n\tgenerateConnectionUrls,\n\tgenerateLocalStackCredentials,\n\tgenerateSecurePassword,\n\tgenerateServiceCredentials,\n} from '../secrets/generator.js';\nimport {\n\treadStageSecrets,\n\tsecretsExist,\n\twriteStageSecrets,\n} from '../secrets/storage.js';\nimport { isSSMConfigured, pullSecrets, pushSecrets } from '../secrets/sync.js';\nimport type { StageSecrets } from '../secrets/types.js';\nimport type { ComposeServiceName } from '../types.js';\nimport type { LoadedConfig, NormalizedWorkspace } from '../workspace/types.js';\nimport {\n\tgenerateFullstackCustomSecrets,\n\twriteDockerEnvFromSecrets,\n} from './fullstack-secrets.js';\n\nconst logger = console;\n\nexport interface SetupOptions {\n\tstage?: string;\n\tforce?: boolean;\n\tskipDocker?: boolean;\n\tyes?: boolean;\n}\n\n/**\n * Setup development environment.\n *\n * Orchestrates:\n * 1. Load workspace config\n * 2. Resolve secrets (local → SSM → generate fresh)\n * 3. Write docker/.env from secrets\n * 4. Start Docker services\n */\nexport async function setupCommand(options: SetupOptions = {}): Promise<void> {\n\tconst stage = options.stage ?? 'development';\n\n\tlogger.log('\\n🔧 Setting up development environment...\\n');\n\n\t// 1. Load workspace config\n\tlet loadedConfig: LoadedConfig;\n\ttry {\n\t\tloadedConfig = await loadWorkspaceConfig();\n\t} catch {\n\t\tlogger.error(\n\t\t\t'❌ No gkm.config.ts found. Run this command from a workspace root.',\n\t\t);\n\t\tprocess.exit(1);\n\t}\n\n\tconst { workspace } = loadedConfig;\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\n\tlogger.log(`📦 Workspace: ${workspace.name}`);\n\tlogger.log(`📱 Apps: ${Object.keys(workspace.apps).join(', ')}`);\n\tlogger.log(`🔑 Stage: ${stage}\\n`);\n\n\t// 2. Resolve secrets\n\tconst secrets = await resolveSecrets(stage, workspace, options);\n\n\tif (!secrets) {\n\t\tlogger.error('❌ Failed to resolve secrets. Exiting.');\n\t\tprocess.exit(1);\n\t}\n\n\t// 3. Write docker/.env from secrets (always regenerated as derived file)\n\tif (isMultiApp && workspace.services.db) {\n\t\tawait writeDockerEnvFromSecrets(secrets, workspace.root);\n\t\tlogger.log('📄 Generated docker/.env with database passwords');\n\t}\n\n\t// 4. Start Docker services with resolved ports\n\tif (!options.skipDocker) {\n\t\tconst composeFile = join(workspace.root, 'docker-compose.yml');\n\t\tif (existsSync(composeFile)) {\n\t\t\tlogger.log('');\n\t\t\tconst resolvedPorts = await resolveServicePorts(workspace.root);\n\t\t\tawait startWorkspaceServices(workspace, resolvedPorts.dockerEnv);\n\t\t} else {\n\t\t\tlogger.log('⚠️ No docker-compose.yml found. Skipping Docker services.');\n\t\t}\n\t}\n\n\t// Print summary\n\tprintSummary(workspace, stage);\n}\n\n/**\n * Resolve secrets with priority:\n * 1. Local secrets exist → use them (preserves manual additions)\n * 2. SSM configured and has secrets → pull and use\n * 3. Neither → generate fresh secrets\n *\n * --force skips checks 1 and 2 and always regenerates.\n */\nasync function resolveSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n\toptions: SetupOptions,\n) {\n\t// Force regeneration\n\tif (options.force) {\n\t\tlogger.log('🔐 Generating fresh secrets (--force)...');\n\t\treturn generateFreshSecrets(stage, workspace, options);\n\t}\n\n\t// Check local secrets first\n\tif (secretsExist(stage, workspace.root)) {\n\t\tlogger.log('🔐 Using existing local secrets');\n\t\tconst secrets = await readStageSecrets(stage, workspace.root);\n\t\tif (secrets) {\n\t\t\t// Reconcile: add any missing workspace-derived keys without overwriting\n\t\t\tconst reconciled = reconcileSecrets(secrets, workspace);\n\t\t\tif (reconciled) {\n\t\t\t\tawait writeStageSecrets(reconciled, workspace.root);\n\t\t\t}\n\t\t\treturn reconciled ?? secrets;\n\t\t}\n\t}\n\n\t// Try SSM pull if configured\n\tif (isSSMConfigured(workspace)) {\n\t\tlogger.log('☁️ Checking for remote secrets in SSM...');\n\t\ttry {\n\t\t\tconst remoteSecrets = await pullSecrets(stage, workspace);\n\t\t\tif (remoteSecrets) {\n\t\t\t\tlogger.log('✅ Pulled secrets from SSM');\n\t\t\t\tawait writeStageSecrets(remoteSecrets, workspace.root);\n\t\t\t\treturn remoteSecrets;\n\t\t\t}\n\t\t\tlogger.log(' No remote secrets found');\n\t\t} catch (error) {\n\t\t\tlogger.warn(`⚠️ Failed to pull from SSM: ${(error as Error).message}`);\n\t\t}\n\t}\n\n\t// Generate fresh secrets\n\tlogger.log('🔐 Generating fresh development secrets...');\n\treturn generateFreshSecrets(stage, workspace, options);\n}\n\n/**\n * Reconcile existing secrets with expected workspace-derived keys.\n * Adds missing keys (e.g. BETTER_AUTH_*) without overwriting existing values.\n * Returns the updated secrets if changes were made, or null if no changes needed.\n * @internal Exported for testing\n */\nexport function reconcileSecrets(\n\tsecrets: StageSecrets,\n\tworkspace: NormalizedWorkspace,\n): StageSecrets | null {\n\tlet changed = false;\n\tlet result = { ...secrets };\n\n\t// Reconcile service credentials: add missing services\n\tconst serviceMap: {\n\t\tkey: keyof typeof workspace.services;\n\t\tname: ComposeServiceName;\n\t}[] = [\n\t\t{ key: 'db', name: 'postgres' },\n\t\t{ key: 'cache', name: 'redis' },\n\t\t{ key: 'storage', name: 'minio' },\n\t\t{ key: 'mail', name: 'mailpit' },\n\t];\n\n\tfor (const { key, name } of serviceMap) {\n\t\tif (workspace.services[key] && !result.services[name]) {\n\t\t\tconst creds = generateServiceCredentials(name);\n\t\t\t// Override defaults with project-derived names\n\t\t\tif (name === 'minio') {\n\t\t\t\tcreds.bucket = workspace.name;\n\t\t\t\tcreds.username = workspace.name;\n\t\t\t}\n\t\t\tresult = {\n\t\t\t\t...result,\n\t\t\t\tservices: { ...result.services, [name]: creds },\n\t\t\t};\n\t\t\tresult.urls = generateConnectionUrls(\n\t\t\t\tresult.services,\n\t\t\t\tresult.eventsBackend,\n\t\t\t);\n\t\t\tlogger.log(` 🔄 Adding missing service credentials: ${name}`);\n\t\t\tchanged = true;\n\t\t}\n\t}\n\n\t// Always add pgboss credentials when postgres is available\n\tif (result.services.postgres && !result.services.pgboss) {\n\t\tresult = {\n\t\t\t...result,\n\t\t\tservices: {\n\t\t\t\t...result.services,\n\t\t\t\tpgboss: {\n\t\t\t\t\thost: result.services.postgres.host,\n\t\t\t\t\tport: result.services.postgres.port,\n\t\t\t\t\tusername: 'pgboss',\n\t\t\t\t\tpassword: generateSecurePassword(),\n\t\t\t\t\tdatabase: result.services.postgres.database ?? 'app',\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t\tresult.urls = generateConnectionUrls(result.services, result.eventsBackend);\n\t\tlogger.log(' 🔄 Adding missing service credentials: pgboss');\n\t\tchanged = true;\n\t}\n\n\t// Reconcile events backend\n\tconst eventsBackend = workspace.services.events;\n\tif (eventsBackend && result.eventsBackend !== eventsBackend) {\n\t\tresult.eventsBackend = eventsBackend;\n\n\t\t// Add localstack credentials if needed\n\t\tif (eventsBackend === 'sns' && !result.services.localstack) {\n\t\t\tresult = {\n\t\t\t\t...result,\n\t\t\t\tservices: {\n\t\t\t\t\t...result.services,\n\t\t\t\t\tlocalstack: generateLocalStackCredentials(),\n\t\t\t\t},\n\t\t\t};\n\t\t\tlogger.log(' 🔄 Adding missing service credentials: localstack');\n\t\t\tchanged = true;\n\t\t}\n\n\t\t// Add rabbitmq credentials if needed (for rabbitmq events)\n\t\tif (eventsBackend === 'rabbitmq' && !result.services.rabbitmq) {\n\t\t\tresult = {\n\t\t\t\t...result,\n\t\t\t\tservices: {\n\t\t\t\t\t...result.services,\n\t\t\t\t\trabbitmq: generateServiceCredentials('rabbitmq'),\n\t\t\t\t},\n\t\t\t};\n\t\t\tlogger.log(' 🔄 Adding missing service credentials: rabbitmq');\n\t\t\tchanged = true;\n\t\t}\n\n\t\t// Regenerate URLs with new events backend\n\t\tresult.urls = generateConnectionUrls(result.services, eventsBackend);\n\t\tchanged = true;\n\t}\n\n\t// Reconcile custom secrets for multi-app workspaces\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\tif (isMultiApp) {\n\t\tconst expected = generateFullstackCustomSecrets(workspace);\n\t\tconst missing: Record<string, string> = {};\n\n\t\tfor (const [key, value] of Object.entries(expected)) {\n\t\t\tif (!(key in result.custom)) {\n\t\t\t\tmissing[key] = value;\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(missing).length > 0) {\n\t\t\tlogger.log(\n\t\t\t\t` 🔄 Adding missing secrets: ${Object.keys(missing).join(', ')}`,\n\t\t\t);\n\t\t\tresult = {\n\t\t\t\t...result,\n\t\t\t\tcustom: { ...result.custom, ...missing },\n\t\t\t};\n\t\t\tchanged = true;\n\t\t}\n\t}\n\n\tif (!changed) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\t...result,\n\t\tupdatedAt: new Date().toISOString(),\n\t};\n}\n\n/**\n * Build a fresh StageSecrets object for a workspace: service credentials,\n * connection URLs, and custom secrets. Pure — does not write to disk or touch\n * SSM. Service passwords are freshly randomized, so the result must be the same\n * one used to start the matching Docker containers.\n * @internal Exported for reuse by `gkm test` auto-setup.\n */\nexport function createFreshWorkspaceSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n): StageSecrets {\n\t// Determine services from workspace config\n\tconst serviceNames: ComposeServiceName[] = [];\n\tif (workspace.services.db) serviceNames.push('postgres');\n\tif (workspace.services.cache) serviceNames.push('redis');\n\tif (workspace.services.storage) serviceNames.push('minio');\n\tif (workspace.services.mail) serviceNames.push('mailpit');\n\tif (workspace.services.events === 'sns') serviceNames.push('localstack');\n\tif (workspace.services.events === 'rabbitmq') serviceNames.push('rabbitmq');\n\n\t// Create base secrets with service credentials\n\tconst secrets = createStageSecrets(stage, serviceNames, {\n\t\tprojectName: workspace.name,\n\t\teventsBackend: workspace.services.events,\n\t});\n\n\t// Generate fullstack-aware custom secrets\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\tif (isMultiApp) {\n\t\tsecrets.custom = generateFullstackCustomSecrets(workspace);\n\t} else {\n\t\tsecrets.custom = {\n\t\t\tNODE_ENV: 'development',\n\t\t\tPORT: '3000',\n\t\t\tLOG_LEVEL: 'debug',\n\t\t\tJWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n\t\t};\n\t}\n\n\treturn secrets;\n}\n\n/**\n * Ensure a usable secret set exists for a stage, generating a fresh one from the\n * workspace config when none is present. No-op if secrets already exist.\n *\n * Powers `gkm test --auto-setup` / `GKM_AUTO_SETUP`: CI can run without a\n * committed secrets file or a shared encryption key — the stage is regenerated\n * from the committed `gkm.config.ts`, and `writeStageSecrets` mints a local key.\n *\n * @returns true if fresh secrets were generated, false if existing ones were kept\n */\nexport async function ensureStageSecrets(\n\tstage: string,\n\tcwd: string = process.cwd(),\n): Promise<boolean> {\n\tconst { workspace } = await loadWorkspaceConfig(cwd);\n\n\tif (secretsExist(stage, workspace.root)) {\n\t\treturn false;\n\t}\n\n\tconst secrets = createFreshWorkspaceSecrets(stage, workspace);\n\tawait writeStageSecrets(secrets, workspace.root);\n\treturn true;\n}\n\n/**\n * Generate fresh secrets for the workspace.\n */\nasync function generateFreshSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n\toptions: SetupOptions,\n) {\n\tconst secrets = createFreshWorkspaceSecrets(stage, workspace);\n\n\t// Write secrets\n\tawait writeStageSecrets(secrets, workspace.root);\n\tlogger.log(` Secrets written to .gkm/secrets/${stage}.json`);\n\n\t// Offer to push to SSM if configured\n\tif (isSSMConfigured(workspace) && !options.yes) {\n\t\tconst { shouldPush } = await prompts({\n\t\t\ttype: 'confirm',\n\t\t\tname: 'shouldPush',\n\t\t\tmessage: 'Push secrets to SSM for team sharing?',\n\t\t\tinitial: true,\n\t\t});\n\n\t\tif (shouldPush) {\n\t\t\ttry {\n\t\t\t\tawait pushSecrets(stage, workspace);\n\t\t\t\tlogger.log('☁️ Secrets pushed to SSM');\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(`⚠️ Failed to push to SSM: ${(error as Error).message}`);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn secrets;\n}\n\n/**\n * Print setup summary with next steps.\n */\nfunction printSummary(workspace: NormalizedWorkspace, stage: string): void {\n\tlogger.log(`\\n${'─'.repeat(50)}`);\n\tlogger.log('\\n✅ Development environment ready!\\n');\n\n\tlogger.log('📋 Apps:');\n\tfor (const [name, app] of Object.entries(workspace.apps)) {\n\t\tconst icon =\n\t\t\tapp.type === 'web' ? '🌐' : app.type === 'mobile' ? '📱' : '🔧';\n\t\tlogger.log(` ${icon} ${name} → http://localhost:${app.port}`);\n\t}\n\n\tlogger.log('\\n🚀 Next steps:');\n\tlogger.log(' gkm dev # Start all apps');\n\tlogger.log(` gkm secrets:show --stage ${stage} # View secrets`);\n\tlogger.log('');\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { isSSMConfigured, pullSecrets, pushSecrets } from "./sync-D_NowTkZ.mjs";
1
+ import { isSSMConfigured, pullSecrets, pushSecrets } from "./sync-CSooqRQ8.mjs";
2
2
 
3
3
  export { pullSecrets, pushSecrets };
@@ -0,0 +1,4 @@
1
+ const require_sync = require('./sync-o_CtEbvR.cjs');
2
+
3
+ exports.pullSecrets = require_sync.pullSecrets;
4
+ exports.pushSecrets = require_sync.pushSecrets;
@@ -73,4 +73,4 @@ function isSSMConfigured(workspace) {
73
73
 
74
74
  //#endregion
75
75
  export { isSSMConfigured, pullSecrets, pushSecrets };
76
- //# sourceMappingURL=sync-D_NowTkZ.mjs.map
76
+ //# sourceMappingURL=sync-CSooqRQ8.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"sync-D_NowTkZ.mjs","names":["workspaceName: string","stage: string","config: SSMStateConfig","clientConfig: SSMClientConfig","workspace: NormalizedWorkspace"],"sources":["../src/secrets/sync.ts"],"sourcesContent":["/**\n * Secrets Sync via AWS SSM Parameter Store\n *\n * Stores and retrieves encrypted StageSecrets as SecureString parameters.\n * Reuses the SSM infrastructure from the state provider.\n *\n * Parameter naming: /gkm/{workspaceName}/{stage}/secrets\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { SSMStateConfig } from '../deploy/StateProvider.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { StageSecrets } from './types.js';\n\n/**\n * Get the SSM parameter name for secrets.\n */\nfunction getSecretsParameterName(workspaceName: string, stage: string): string {\n\treturn `/gkm/${workspaceName}/${stage}/secrets`;\n}\n\n/**\n * Create an SSM client from workspace state config.\n */\nfunction createSSMClient(config: SSMStateConfig): SSMClient {\n\tconst clientConfig: SSMClientConfig = {\n\t\tregion: config.region,\n\t};\n\n\tif (config.profile) {\n\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\tclientConfig.credentials = fromIni({ profile: config.profile });\n\t}\n\n\treturn new SSMClient(clientConfig);\n}\n\n/**\n * Push secrets to SSM Parameter Store.\n *\n * Stores the full StageSecrets object as a SecureString parameter.\n */\nexport async function pushSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n): Promise<void> {\n\tconst config = workspace.state;\n\tif (!config || config.provider !== 'ssm') {\n\t\tthrow new Error(\n\t\t\t'SSM state provider not configured. Add state: { provider: \"ssm\", region: \"...\" } to gkm.config.ts.',\n\t\t);\n\t}\n\n\tif (!workspace.name) {\n\t\tthrow new Error(\n\t\t\t'Workspace name is required for SSM secrets sync. Set \"name\" in gkm.config.ts.',\n\t\t);\n\t}\n\n\tconst client = createSSMClient(config as SSMStateConfig);\n\tconst parameterName = getSecretsParameterName(workspace.name, stage);\n\n\tconst { readStageSecrets } = await import('./storage.js');\n\tconst secrets = await readStageSecrets(stage, workspace.root);\n\n\tif (!secrets) {\n\t\tthrow new Error(\n\t\t\t`No secrets found for stage \"${stage}\". Run \"gkm secrets:init --stage ${stage}\" first.`,\n\t\t);\n\t}\n\n\tawait client.send(\n\t\tnew PutParameterCommand({\n\t\t\tName: parameterName,\n\t\t\tValue: JSON.stringify(secrets),\n\t\t\tType: 'SecureString',\n\t\t\tOverwrite: true,\n\t\t\tDescription: `GKM secrets for ${workspace.name}/${stage}`,\n\t\t}),\n\t);\n}\n\n/**\n * Pull secrets from SSM Parameter Store.\n *\n * @returns StageSecrets or null if no secrets are stored remotely\n */\nexport async function pullSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n): Promise<StageSecrets | null> {\n\tconst config = workspace.state;\n\tif (!config || config.provider !== 'ssm') {\n\t\treturn null;\n\t}\n\n\tif (!workspace.name) {\n\t\treturn null;\n\t}\n\n\tconst client = createSSMClient(config as SSMStateConfig);\n\tconst parameterName = getSecretsParameterName(workspace.name, stage);\n\n\ttry {\n\t\tconst response = await client.send(\n\t\t\tnew GetParameterCommand({\n\t\t\t\tName: parameterName,\n\t\t\t\tWithDecryption: true,\n\t\t\t}),\n\t\t);\n\n\t\tif (!response.Parameter?.Value) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn JSON.parse(response.Parameter.Value) as StageSecrets;\n\t} catch (error) {\n\t\tif (error instanceof ParameterNotFound) {\n\t\t\treturn null;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if SSM is configured for the workspace.\n */\nexport function isSSMConfigured(workspace: NormalizedWorkspace): boolean {\n\treturn !!workspace.state && workspace.state.provider === 'ssm';\n}\n"],"mappings":";;;;;;;AAuBA,SAAS,wBAAwBA,eAAuBC,OAAuB;AAC9E,SAAQ,OAAO,cAAc,GAAG,MAAM;AACtC;;;;AAKD,SAAS,gBAAgBC,QAAmC;CAC3D,MAAMC,eAAgC,EACrC,QAAQ,OAAO,OACf;AAED,KAAI,OAAO,SAAS;EACnB,MAAM,EAAE,SAAS,GAAG,UAAQ,gCAAgC;AAC5D,eAAa,cAAc,QAAQ,EAAE,SAAS,OAAO,QAAS,EAAC;CAC/D;AAED,QAAO,IAAI,UAAU;AACrB;;;;;;AAOD,eAAsB,YACrBF,OACAG,WACgB;CAChB,MAAM,SAAS,UAAU;AACzB,MAAK,UAAU,OAAO,aAAa,MAClC,OAAM,IAAI,MACT;AAIF,MAAK,UAAU,KACd,OAAM,IAAI,MACT;CAIF,MAAM,SAAS,gBAAgB,OAAyB;CACxD,MAAM,gBAAgB,wBAAwB,UAAU,MAAM,MAAM;CAEpE,MAAM,EAAE,kBAAkB,GAAG,MAAM,OAAO;CAC1C,MAAM,UAAU,MAAM,iBAAiB,OAAO,UAAU,KAAK;AAE7D,MAAK,QACJ,OAAM,IAAI,OACR,8BAA8B,MAAM,mCAAmC,MAAM;AAIhF,OAAM,OAAO,KACZ,IAAI,oBAAoB;EACvB,MAAM;EACN,OAAO,KAAK,UAAU,QAAQ;EAC9B,MAAM;EACN,WAAW;EACX,cAAc,kBAAkB,UAAU,KAAK,GAAG,MAAM;CACxD,GACD;AACD;;;;;;AAOD,eAAsB,YACrBH,OACAG,WAC+B;CAC/B,MAAM,SAAS,UAAU;AACzB,MAAK,UAAU,OAAO,aAAa,MAClC,QAAO;AAGR,MAAK,UAAU,KACd,QAAO;CAGR,MAAM,SAAS,gBAAgB,OAAyB;CACxD,MAAM,gBAAgB,wBAAwB,UAAU,MAAM,MAAM;AAEpE,KAAI;EACH,MAAM,WAAW,MAAM,OAAO,KAC7B,IAAI,oBAAoB;GACvB,MAAM;GACN,gBAAgB;EAChB,GACD;AAED,OAAK,SAAS,WAAW,MACxB,QAAO;AAGR,SAAO,KAAK,MAAM,SAAS,UAAU,MAAM;CAC3C,SAAQ,OAAO;AACf,MAAI,iBAAiB,kBACpB,QAAO;AAER,QAAM;CACN;AACD;;;;AAKD,SAAgB,gBAAgBA,WAAyC;AACxE,UAAS,UAAU,SAAS,UAAU,MAAM,aAAa;AACzD"}
1
+ {"version":3,"file":"sync-CSooqRQ8.mjs","names":["workspaceName: string","stage: string","config: SSMStateConfig","clientConfig: SSMClientConfig","workspace: NormalizedWorkspace"],"sources":["../src/secrets/sync.ts"],"sourcesContent":["/**\n * Secrets Sync via AWS SSM Parameter Store\n *\n * Stores and retrieves encrypted StageSecrets as SecureString parameters.\n * Reuses the SSM infrastructure from the state provider.\n *\n * Parameter naming: /gkm/{workspaceName}/{stage}/secrets\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { SSMStateConfig } from '../deploy/StateProvider.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { StageSecrets } from './types.js';\n\n/**\n * Get the SSM parameter name for secrets.\n */\nfunction getSecretsParameterName(workspaceName: string, stage: string): string {\n\treturn `/gkm/${workspaceName}/${stage}/secrets`;\n}\n\n/**\n * Create an SSM client from workspace state config.\n */\nfunction createSSMClient(config: SSMStateConfig): SSMClient {\n\tconst clientConfig: SSMClientConfig = {\n\t\tregion: config.region,\n\t};\n\n\tif (config.profile) {\n\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\tclientConfig.credentials = fromIni({ profile: config.profile });\n\t}\n\n\treturn new SSMClient(clientConfig);\n}\n\n/**\n * Push secrets to SSM Parameter Store.\n *\n * Stores the full StageSecrets object as a SecureString parameter.\n */\nexport async function pushSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n): Promise<void> {\n\tconst config = workspace.state;\n\tif (!config || config.provider !== 'ssm') {\n\t\tthrow new Error(\n\t\t\t'SSM state provider not configured. Add state: { provider: \"ssm\", region: \"...\" } to gkm.config.ts.',\n\t\t);\n\t}\n\n\tif (!workspace.name) {\n\t\tthrow new Error(\n\t\t\t'Workspace name is required for SSM secrets sync. Set \"name\" in gkm.config.ts.',\n\t\t);\n\t}\n\n\tconst client = createSSMClient(config as SSMStateConfig);\n\tconst parameterName = getSecretsParameterName(workspace.name, stage);\n\n\tconst { readStageSecrets } = await import('./storage.js');\n\tconst secrets = await readStageSecrets(stage, workspace.root);\n\n\tif (!secrets) {\n\t\tthrow new Error(\n\t\t\t`No secrets found for stage \"${stage}\". Run \"gkm secrets:init --stage ${stage}\" first.`,\n\t\t);\n\t}\n\n\tawait client.send(\n\t\tnew PutParameterCommand({\n\t\t\tName: parameterName,\n\t\t\tValue: JSON.stringify(secrets),\n\t\t\tType: 'SecureString',\n\t\t\tOverwrite: true,\n\t\t\tDescription: `GKM secrets for ${workspace.name}/${stage}`,\n\t\t}),\n\t);\n}\n\n/**\n * Pull secrets from SSM Parameter Store.\n *\n * @returns StageSecrets or null if no secrets are stored remotely\n */\nexport async function pullSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n): Promise<StageSecrets | null> {\n\tconst config = workspace.state;\n\tif (!config || config.provider !== 'ssm') {\n\t\treturn null;\n\t}\n\n\tif (!workspace.name) {\n\t\treturn null;\n\t}\n\n\tconst client = createSSMClient(config as SSMStateConfig);\n\tconst parameterName = getSecretsParameterName(workspace.name, stage);\n\n\ttry {\n\t\tconst response = await client.send(\n\t\t\tnew GetParameterCommand({\n\t\t\t\tName: parameterName,\n\t\t\t\tWithDecryption: true,\n\t\t\t}),\n\t\t);\n\n\t\tif (!response.Parameter?.Value) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn JSON.parse(response.Parameter.Value) as StageSecrets;\n\t} catch (error) {\n\t\tif (error instanceof ParameterNotFound) {\n\t\t\treturn null;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if SSM is configured for the workspace.\n */\nexport function isSSMConfigured(workspace: NormalizedWorkspace): boolean {\n\treturn !!workspace.state && workspace.state.provider === 'ssm';\n}\n"],"mappings":";;;;;;;AAuBA,SAAS,wBAAwBA,eAAuBC,OAAuB;AAC9E,SAAQ,OAAO,cAAc,GAAG,MAAM;AACtC;;;;AAKD,SAAS,gBAAgBC,QAAmC;CAC3D,MAAMC,eAAgC,EACrC,QAAQ,OAAO,OACf;AAED,KAAI,OAAO,SAAS;EACnB,MAAM,EAAE,SAAS,GAAG,UAAQ,gCAAgC;AAC5D,eAAa,cAAc,QAAQ,EAAE,SAAS,OAAO,QAAS,EAAC;CAC/D;AAED,QAAO,IAAI,UAAU;AACrB;;;;;;AAOD,eAAsB,YACrBF,OACAG,WACgB;CAChB,MAAM,SAAS,UAAU;AACzB,MAAK,UAAU,OAAO,aAAa,MAClC,OAAM,IAAI,MACT;AAIF,MAAK,UAAU,KACd,OAAM,IAAI,MACT;CAIF,MAAM,SAAS,gBAAgB,OAAyB;CACxD,MAAM,gBAAgB,wBAAwB,UAAU,MAAM,MAAM;CAEpE,MAAM,EAAE,kBAAkB,GAAG,MAAM,OAAO;CAC1C,MAAM,UAAU,MAAM,iBAAiB,OAAO,UAAU,KAAK;AAE7D,MAAK,QACJ,OAAM,IAAI,OACR,8BAA8B,MAAM,mCAAmC,MAAM;AAIhF,OAAM,OAAO,KACZ,IAAI,oBAAoB;EACvB,MAAM;EACN,OAAO,KAAK,UAAU,QAAQ;EAC9B,MAAM;EACN,WAAW;EACX,cAAc,kBAAkB,UAAU,KAAK,GAAG,MAAM;CACxD,GACD;AACD;;;;;;AAOD,eAAsB,YACrBH,OACAG,WAC+B;CAC/B,MAAM,SAAS,UAAU;AACzB,MAAK,UAAU,OAAO,aAAa,MAClC,QAAO;AAGR,MAAK,UAAU,KACd,QAAO;CAGR,MAAM,SAAS,gBAAgB,OAAyB;CACxD,MAAM,gBAAgB,wBAAwB,UAAU,MAAM,MAAM;AAEpE,KAAI;EACH,MAAM,WAAW,MAAM,OAAO,KAC7B,IAAI,oBAAoB;GACvB,MAAM;GACN,gBAAgB;EAChB,GACD;AAED,OAAK,SAAS,WAAW,MACxB,QAAO;AAGR,SAAO,KAAK,MAAM,SAAS,UAAU,MAAM;CAC3C,SAAQ,OAAO;AACf,MAAI,iBAAiB,kBACpB,QAAO;AAER,QAAM;CACN;AACD;;;;AAKD,SAAgB,gBAAgBA,WAAyC;AACxE,UAAS,UAAU,SAAS,UAAU,MAAM,aAAa;AACzD"}
@@ -90,4 +90,4 @@ Object.defineProperty(exports, 'pushSecrets', {
90
90
  return pushSecrets;
91
91
  }
92
92
  });
93
- //# sourceMappingURL=sync-DGXXSk2v.cjs.map
93
+ //# sourceMappingURL=sync-o_CtEbvR.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"sync-DGXXSk2v.cjs","names":["workspaceName: string","stage: string","config: SSMStateConfig","clientConfig: SSMClientConfig","SSMClient","workspace: NormalizedWorkspace","PutParameterCommand","GetParameterCommand","ParameterNotFound"],"sources":["../src/secrets/sync.ts"],"sourcesContent":["/**\n * Secrets Sync via AWS SSM Parameter Store\n *\n * Stores and retrieves encrypted StageSecrets as SecureString parameters.\n * Reuses the SSM infrastructure from the state provider.\n *\n * Parameter naming: /gkm/{workspaceName}/{stage}/secrets\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { SSMStateConfig } from '../deploy/StateProvider.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { StageSecrets } from './types.js';\n\n/**\n * Get the SSM parameter name for secrets.\n */\nfunction getSecretsParameterName(workspaceName: string, stage: string): string {\n\treturn `/gkm/${workspaceName}/${stage}/secrets`;\n}\n\n/**\n * Create an SSM client from workspace state config.\n */\nfunction createSSMClient(config: SSMStateConfig): SSMClient {\n\tconst clientConfig: SSMClientConfig = {\n\t\tregion: config.region,\n\t};\n\n\tif (config.profile) {\n\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\tclientConfig.credentials = fromIni({ profile: config.profile });\n\t}\n\n\treturn new SSMClient(clientConfig);\n}\n\n/**\n * Push secrets to SSM Parameter Store.\n *\n * Stores the full StageSecrets object as a SecureString parameter.\n */\nexport async function pushSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n): Promise<void> {\n\tconst config = workspace.state;\n\tif (!config || config.provider !== 'ssm') {\n\t\tthrow new Error(\n\t\t\t'SSM state provider not configured. Add state: { provider: \"ssm\", region: \"...\" } to gkm.config.ts.',\n\t\t);\n\t}\n\n\tif (!workspace.name) {\n\t\tthrow new Error(\n\t\t\t'Workspace name is required for SSM secrets sync. Set \"name\" in gkm.config.ts.',\n\t\t);\n\t}\n\n\tconst client = createSSMClient(config as SSMStateConfig);\n\tconst parameterName = getSecretsParameterName(workspace.name, stage);\n\n\tconst { readStageSecrets } = await import('./storage.js');\n\tconst secrets = await readStageSecrets(stage, workspace.root);\n\n\tif (!secrets) {\n\t\tthrow new Error(\n\t\t\t`No secrets found for stage \"${stage}\". Run \"gkm secrets:init --stage ${stage}\" first.`,\n\t\t);\n\t}\n\n\tawait client.send(\n\t\tnew PutParameterCommand({\n\t\t\tName: parameterName,\n\t\t\tValue: JSON.stringify(secrets),\n\t\t\tType: 'SecureString',\n\t\t\tOverwrite: true,\n\t\t\tDescription: `GKM secrets for ${workspace.name}/${stage}`,\n\t\t}),\n\t);\n}\n\n/**\n * Pull secrets from SSM Parameter Store.\n *\n * @returns StageSecrets or null if no secrets are stored remotely\n */\nexport async function pullSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n): Promise<StageSecrets | null> {\n\tconst config = workspace.state;\n\tif (!config || config.provider !== 'ssm') {\n\t\treturn null;\n\t}\n\n\tif (!workspace.name) {\n\t\treturn null;\n\t}\n\n\tconst client = createSSMClient(config as SSMStateConfig);\n\tconst parameterName = getSecretsParameterName(workspace.name, stage);\n\n\ttry {\n\t\tconst response = await client.send(\n\t\t\tnew GetParameterCommand({\n\t\t\t\tName: parameterName,\n\t\t\t\tWithDecryption: true,\n\t\t\t}),\n\t\t);\n\n\t\tif (!response.Parameter?.Value) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn JSON.parse(response.Parameter.Value) as StageSecrets;\n\t} catch (error) {\n\t\tif (error instanceof ParameterNotFound) {\n\t\t\treturn null;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if SSM is configured for the workspace.\n */\nexport function isSSMConfigured(workspace: NormalizedWorkspace): boolean {\n\treturn !!workspace.state && workspace.state.provider === 'ssm';\n}\n"],"mappings":";;;;;;;AAuBA,SAAS,wBAAwBA,eAAuBC,OAAuB;AAC9E,SAAQ,OAAO,cAAc,GAAG,MAAM;AACtC;;;;AAKD,SAAS,gBAAgBC,QAAmC;CAC3D,MAAMC,eAAgC,EACrC,QAAQ,OAAO,OACf;AAED,KAAI,OAAO,SAAS;EACnB,MAAM,EAAE,SAAS,GAAG,QAAQ,gCAAgC;AAC5D,eAAa,cAAc,QAAQ,EAAE,SAAS,OAAO,QAAS,EAAC;CAC/D;AAED,QAAO,IAAIC,+BAAU;AACrB;;;;;;AAOD,eAAsB,YACrBH,OACAI,WACgB;CAChB,MAAM,SAAS,UAAU;AACzB,MAAK,UAAU,OAAO,aAAa,MAClC,OAAM,IAAI,MACT;AAIF,MAAK,UAAU,KACd,OAAM,IAAI,MACT;CAIF,MAAM,SAAS,gBAAgB,OAAyB;CACxD,MAAM,gBAAgB,wBAAwB,UAAU,MAAM,MAAM;CAEpE,MAAM,EAAE,kBAAkB,GAAG,2CAAM;CACnC,MAAM,UAAU,MAAM,iBAAiB,OAAO,UAAU,KAAK;AAE7D,MAAK,QACJ,OAAM,IAAI,OACR,8BAA8B,MAAM,mCAAmC,MAAM;AAIhF,OAAM,OAAO,KACZ,IAAIC,yCAAoB;EACvB,MAAM;EACN,OAAO,KAAK,UAAU,QAAQ;EAC9B,MAAM;EACN,WAAW;EACX,cAAc,kBAAkB,UAAU,KAAK,GAAG,MAAM;CACxD,GACD;AACD;;;;;;AAOD,eAAsB,YACrBL,OACAI,WAC+B;CAC/B,MAAM,SAAS,UAAU;AACzB,MAAK,UAAU,OAAO,aAAa,MAClC,QAAO;AAGR,MAAK,UAAU,KACd,QAAO;CAGR,MAAM,SAAS,gBAAgB,OAAyB;CACxD,MAAM,gBAAgB,wBAAwB,UAAU,MAAM,MAAM;AAEpE,KAAI;EACH,MAAM,WAAW,MAAM,OAAO,KAC7B,IAAIE,yCAAoB;GACvB,MAAM;GACN,gBAAgB;EAChB,GACD;AAED,OAAK,SAAS,WAAW,MACxB,QAAO;AAGR,SAAO,KAAK,MAAM,SAAS,UAAU,MAAM;CAC3C,SAAQ,OAAO;AACf,MAAI,iBAAiBC,uCACpB,QAAO;AAER,QAAM;CACN;AACD;;;;AAKD,SAAgB,gBAAgBH,WAAyC;AACxE,UAAS,UAAU,SAAS,UAAU,MAAM,aAAa;AACzD"}
1
+ {"version":3,"file":"sync-o_CtEbvR.cjs","names":["workspaceName: string","stage: string","config: SSMStateConfig","clientConfig: SSMClientConfig","SSMClient","workspace: NormalizedWorkspace","PutParameterCommand","GetParameterCommand","ParameterNotFound"],"sources":["../src/secrets/sync.ts"],"sourcesContent":["/**\n * Secrets Sync via AWS SSM Parameter Store\n *\n * Stores and retrieves encrypted StageSecrets as SecureString parameters.\n * Reuses the SSM infrastructure from the state provider.\n *\n * Parameter naming: /gkm/{workspaceName}/{stage}/secrets\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { SSMStateConfig } from '../deploy/StateProvider.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { StageSecrets } from './types.js';\n\n/**\n * Get the SSM parameter name for secrets.\n */\nfunction getSecretsParameterName(workspaceName: string, stage: string): string {\n\treturn `/gkm/${workspaceName}/${stage}/secrets`;\n}\n\n/**\n * Create an SSM client from workspace state config.\n */\nfunction createSSMClient(config: SSMStateConfig): SSMClient {\n\tconst clientConfig: SSMClientConfig = {\n\t\tregion: config.region,\n\t};\n\n\tif (config.profile) {\n\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\tclientConfig.credentials = fromIni({ profile: config.profile });\n\t}\n\n\treturn new SSMClient(clientConfig);\n}\n\n/**\n * Push secrets to SSM Parameter Store.\n *\n * Stores the full StageSecrets object as a SecureString parameter.\n */\nexport async function pushSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n): Promise<void> {\n\tconst config = workspace.state;\n\tif (!config || config.provider !== 'ssm') {\n\t\tthrow new Error(\n\t\t\t'SSM state provider not configured. Add state: { provider: \"ssm\", region: \"...\" } to gkm.config.ts.',\n\t\t);\n\t}\n\n\tif (!workspace.name) {\n\t\tthrow new Error(\n\t\t\t'Workspace name is required for SSM secrets sync. Set \"name\" in gkm.config.ts.',\n\t\t);\n\t}\n\n\tconst client = createSSMClient(config as SSMStateConfig);\n\tconst parameterName = getSecretsParameterName(workspace.name, stage);\n\n\tconst { readStageSecrets } = await import('./storage.js');\n\tconst secrets = await readStageSecrets(stage, workspace.root);\n\n\tif (!secrets) {\n\t\tthrow new Error(\n\t\t\t`No secrets found for stage \"${stage}\". Run \"gkm secrets:init --stage ${stage}\" first.`,\n\t\t);\n\t}\n\n\tawait client.send(\n\t\tnew PutParameterCommand({\n\t\t\tName: parameterName,\n\t\t\tValue: JSON.stringify(secrets),\n\t\t\tType: 'SecureString',\n\t\t\tOverwrite: true,\n\t\t\tDescription: `GKM secrets for ${workspace.name}/${stage}`,\n\t\t}),\n\t);\n}\n\n/**\n * Pull secrets from SSM Parameter Store.\n *\n * @returns StageSecrets or null if no secrets are stored remotely\n */\nexport async function pullSecrets(\n\tstage: string,\n\tworkspace: NormalizedWorkspace,\n): Promise<StageSecrets | null> {\n\tconst config = workspace.state;\n\tif (!config || config.provider !== 'ssm') {\n\t\treturn null;\n\t}\n\n\tif (!workspace.name) {\n\t\treturn null;\n\t}\n\n\tconst client = createSSMClient(config as SSMStateConfig);\n\tconst parameterName = getSecretsParameterName(workspace.name, stage);\n\n\ttry {\n\t\tconst response = await client.send(\n\t\t\tnew GetParameterCommand({\n\t\t\t\tName: parameterName,\n\t\t\t\tWithDecryption: true,\n\t\t\t}),\n\t\t);\n\n\t\tif (!response.Parameter?.Value) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn JSON.parse(response.Parameter.Value) as StageSecrets;\n\t} catch (error) {\n\t\tif (error instanceof ParameterNotFound) {\n\t\t\treturn null;\n\t\t}\n\t\tthrow error;\n\t}\n}\n\n/**\n * Check if SSM is configured for the workspace.\n */\nexport function isSSMConfigured(workspace: NormalizedWorkspace): boolean {\n\treturn !!workspace.state && workspace.state.provider === 'ssm';\n}\n"],"mappings":";;;;;;;AAuBA,SAAS,wBAAwBA,eAAuBC,OAAuB;AAC9E,SAAQ,OAAO,cAAc,GAAG,MAAM;AACtC;;;;AAKD,SAAS,gBAAgBC,QAAmC;CAC3D,MAAMC,eAAgC,EACrC,QAAQ,OAAO,OACf;AAED,KAAI,OAAO,SAAS;EACnB,MAAM,EAAE,SAAS,GAAG,QAAQ,gCAAgC;AAC5D,eAAa,cAAc,QAAQ,EAAE,SAAS,OAAO,QAAS,EAAC;CAC/D;AAED,QAAO,IAAIC,+BAAU;AACrB;;;;;;AAOD,eAAsB,YACrBH,OACAI,WACgB;CAChB,MAAM,SAAS,UAAU;AACzB,MAAK,UAAU,OAAO,aAAa,MAClC,OAAM,IAAI,MACT;AAIF,MAAK,UAAU,KACd,OAAM,IAAI,MACT;CAIF,MAAM,SAAS,gBAAgB,OAAyB;CACxD,MAAM,gBAAgB,wBAAwB,UAAU,MAAM,MAAM;CAEpE,MAAM,EAAE,kBAAkB,GAAG,2CAAM;CACnC,MAAM,UAAU,MAAM,iBAAiB,OAAO,UAAU,KAAK;AAE7D,MAAK,QACJ,OAAM,IAAI,OACR,8BAA8B,MAAM,mCAAmC,MAAM;AAIhF,OAAM,OAAO,KACZ,IAAIC,yCAAoB;EACvB,MAAM;EACN,OAAO,KAAK,UAAU,QAAQ;EAC9B,MAAM;EACN,WAAW;EACX,cAAc,kBAAkB,UAAU,KAAK,GAAG,MAAM;CACxD,GACD;AACD;;;;;;AAOD,eAAsB,YACrBL,OACAI,WAC+B;CAC/B,MAAM,SAAS,UAAU;AACzB,MAAK,UAAU,OAAO,aAAa,MAClC,QAAO;AAGR,MAAK,UAAU,KACd,QAAO;CAGR,MAAM,SAAS,gBAAgB,OAAyB;CACxD,MAAM,gBAAgB,wBAAwB,UAAU,MAAM,MAAM;AAEpE,KAAI;EACH,MAAM,WAAW,MAAM,OAAO,KAC7B,IAAIE,yCAAoB;GACvB,MAAM;GACN,gBAAgB;EAChB,GACD;AAED,OAAK,SAAS,WAAW,MACxB,QAAO;AAGR,SAAO,KAAK,MAAM,SAAS,UAAU,MAAM;CAC3C,SAAQ,OAAO;AACf,MAAI,iBAAiBC,uCACpB,QAAO;AAER,QAAM;CACN;AACD;;;;AAKD,SAAgB,gBAAgBH,WAAyC;AACxE,UAAS,UAAU,SAAS,UAAU,MAAM,aAAa;AACzD"}
@@ -275,4 +275,4 @@ interface GkmConfig {
275
275
  }
276
276
  //#endregion
277
277
  export { EventsBackend, GkmConfig, HooksConfig, OpenApiConfig, ProvidersConfig, Routes, Runtime, StudioConfig, TelescopeConfig };
278
- //# sourceMappingURL=types-OszPdw9m.d.mts.map
278
+ //# sourceMappingURL=types-CqfhdmRi.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types-OszPdw9m.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;AAYY,UALK,iBAAA,CAKwB;EAaxB,KAAA,EAAA,MAAA,GAAA,MAAc,EAAA;EAKd,SAAA,EAAA,CAAA,QAAA,EAAoB,MAAA,EAAA,GAAQ,MAAA;AAI7C;AAIiB,KA1BL,MAAA,GA0BK,MAAgB,GAAA,MAAA,EAAA,GA1BQ,iBA0BR;AAkDrB,UA/DK,cAAA,CA+DQ;EAGb,OAAA,CAAA,EAAA,OAAA;EASA,SAAA,CAAA,EAAA,MAAA;;AACL,UAvEU,mBAAA,SAA4B,cAuEtC,CAAA;AAA6C,UAnEnC,eAAA,SAAwB,cAmEW,CAAA,CAGpD;AAA6B,UAlEZ,gBAAA,CAkEY;EAAA;EAwBK,OAAG,CAAA,EAAA,OAAA;EAAkB;EAItC,MAAA,CAAA,EAAA,OAAa;EAAA;EAAA,MAIhB,CAAA,EAAA,OAAA;EAAgB;EAJsB,WAAA,CAAA,EAAA,MAAA;EAOxC;EAEK,gBAAA,CAAA,EAAe,OAAA;EAiBf;EASA,QAAA,CAAA,EAAA,MAAa,EAAA;EAWb;EA6BA,WAAA,CAAA,EAAA,SAAA,GAAqB,SAAA;EAarB;EAAe,OAAA,CAAA,EAAA,OAAA;EAAA;;;;;;AAaU;EAGzB,iBAAS,CAAA,EAAA,OAAA;;;AAEb,UA5KI,aAAA,CA4KJ;EAAM;;;;EAeC,KAQY,CAAA,EAAA,MAAA;EAAe;;;;EA2DzB,OAAA,CAAA,EAAA,MAAA;;;;;;;;;;;KAxOV,aAAA;;KAGA,kBAAA;;KASA,qBAAA,WACL,gCAAgC;UAGtB,YAAA;;;;;;;;;;;;;;;;;;;;;;;;eAwBJ,wBAAwB;;;UAIpB,YAAA,SAAqB;;;;eAIxB;;KAGF,OAAA;UAEK,eAAA;;;;;;;;;;;;;;;;UAiBA,YAAA;;;;;;;;UASA,aAAA;;;;;;;;;;UAWA,WAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA6BA,qBAAA;;;;;;;;;;;;UAaA,eAAA;;;qBAGC;qBACA;;;4BAGO;wBACJ;;;qBAGD;;sBAEC;;UAGJ,SAAA;UACR;cACI;UACJ;gBACM;;;cAGF;;;;;;;;;;UAUJ;;;;;;;;iCAQuB;;;;;;;;;;8BAUH;;;;;;;;;;;;;;;;;;;sBAmBR;;YAEV;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA4BD"}
1
+ {"version":3,"file":"types-CqfhdmRi.d.mts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";;AAYY,UALK,iBAAA,CAKwB;EAaxB,KAAA,EAAA,MAAA,GAAA,MAAc,EAAA;EAKd,SAAA,EAAA,CAAA,QAAA,EAAoB,MAAA,EAAA,GAAQ,MAAA;AAI7C;AAIiB,KA1BL,MAAA,GA0BK,MAAgB,GAAA,MAAA,EAAA,GA1BQ,iBA0BR;AAkDrB,UA/DK,cAAA,CA+DQ;EAGb,OAAA,CAAA,EAAA,OAAA;EASA,SAAA,CAAA,EAAA,MAAA;;AACL,UAvEU,mBAAA,SAA4B,cAuEtC,CAAA;AAA6C,UAnEnC,eAAA,SAAwB,cAmEW,CAAA,CAGpD;AAA6B,UAlEZ,gBAAA,CAkEY;EAAA;EAwBK,OAAG,CAAA,EAAA,OAAA;EAAkB;EAItC,MAAA,CAAA,EAAA,OAAa;EAAA;EAAA,MAIhB,CAAA,EAAA,OAAA;EAAgB;EAJsB,WAAA,CAAA,EAAA,MAAA;EAOxC;EAEK,gBAAA,CAAA,EAAe,OAAA;EAiBf;EASA,QAAA,CAAA,EAAA,MAAa,EAAA;EAWb;EA6BA,WAAA,CAAA,EAAA,SAAA,GAAqB,SAAA;EAarB;EAAe,OAAA,CAAA,EAAA,OAAA;EAAA;;;;;;AAaU;EAGzB,iBAAS,CAAA,EAAA,OAAA;;;AAEb,UA5KI,aAAA,CA4KJ;EAAM;;;;EAeC,KAQY,CAAA,EAAA,MAAA;EAAe;;;;EA2DzB,OAAA,CAAA,EAAA,MAAA;;;;;;;;;;;KAxOV,aAAA;;KAGA,kBAAA;;KASA,qBAAA,WACL,gCAAgC;UAGtB,YAAA;;;;;;;;;;;;;;;;;;;;;;;;eAwBJ,wBAAwB;;;UAIpB,YAAA,SAAqB;;;;eAIxB;;KAGF,OAAA;UAEK,eAAA;;;;;;;;;;;;;;;;UAiBA,YAAA;;;;;;;;UASA,aAAA;;;;;;;;;;UAWA,WAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;UA6BA,qBAAA;;;;;;;;;;;;UAaA,eAAA;;;qBAGC;qBACA;;;4BAGO;wBACJ;;;qBAGD;;sBAEC;;UAGJ,SAAA;UACR;cACI;UACJ;gBACM;;;cAGF;;;;;;;;;;UAUJ;;;;;;;;iCAQuB;;;;;;;;;;8BAUH;;;;;;;;;;;;;;;;;;;sBAmBR;;YAEV;;;;;;;;;;;;;;;;;;;;;;;;;;;;WA4BD"}
@@ -1,3 +1,3 @@
1
- import "../types-OszPdw9m.mjs";
2
- import { AppConfig, AppConfigInput, AppInput, AppsRecord, BackendFramework, ConstrainedApps, DeployConfig, DeployTarget, DokployWorkspaceConfig, FrontendFramework, InferAppNames, InferredWorkspaceConfig, LoadedConfig, MailServiceConfig, ModelsConfig, NormalizedAppConfig, NormalizedWorkspace, PHASE_2_DEPLOY_TARGETS, PUBLIC_ENV_PREFIXES, SUPPORTED_DEPLOY_TARGETS, SecretsConfig, ServiceImageConfig, ServicesConfig, SharedConfig, WorkspaceConfig, WorkspaceConfigSchema, WorkspaceInput, defineWorkspace, formatValidationErrors, getAppBuildOrder, getAppGkmConfig, getDependencyEnvVars, getDeployTargetError, getEndpointForStage, getPublicEnvPrefix, isDeployTargetSupported, isPhase2DeployTarget, isWorkspaceConfig, normalizeWorkspace, processConfig, safeValidateWorkspaceConfig, stripPublicPrefix, validateWorkspaceConfig, wrapSingleAppAsWorkspace } from "../index-BBvGMG_A.mjs";
1
+ import "../types-CqfhdmRi.mjs";
2
+ import { AppConfig, AppConfigInput, AppInput, AppsRecord, BackendFramework, ConstrainedApps, DeployConfig, DeployTarget, DokployWorkspaceConfig, FrontendFramework, InferAppNames, InferredWorkspaceConfig, LoadedConfig, MailServiceConfig, ModelsConfig, NormalizedAppConfig, NormalizedWorkspace, PHASE_2_DEPLOY_TARGETS, PUBLIC_ENV_PREFIXES, SUPPORTED_DEPLOY_TARGETS, SecretsConfig, ServiceImageConfig, ServicesConfig, SharedConfig, WorkspaceConfig, WorkspaceConfigSchema, WorkspaceInput, defineWorkspace, formatValidationErrors, getAppBuildOrder, getAppGkmConfig, getDependencyEnvVars, getDeployTargetError, getEndpointForStage, getPublicEnvPrefix, isDeployTargetSupported, isPhase2DeployTarget, isWorkspaceConfig, normalizeWorkspace, processConfig, safeValidateWorkspaceConfig, stripPublicPrefix, validateWorkspaceConfig, wrapSingleAppAsWorkspace } from "../index-Dx9Kk5bR.mjs";
3
3
  export { AppConfig, AppConfigInput, AppInput, AppsRecord, BackendFramework, ConstrainedApps, DeployConfig, DeployTarget, DokployWorkspaceConfig, FrontendFramework, InferAppNames, InferredWorkspaceConfig, LoadedConfig, MailServiceConfig, ModelsConfig, NormalizedAppConfig, NormalizedWorkspace, PHASE_2_DEPLOY_TARGETS, PUBLIC_ENV_PREFIXES, SUPPORTED_DEPLOY_TARGETS, SecretsConfig, ServiceImageConfig, ServicesConfig, SharedConfig, WorkspaceConfig, WorkspaceConfigSchema, WorkspaceInput, defineWorkspace, formatValidationErrors, getAppBuildOrder, getAppGkmConfig, getDependencyEnvVars, getDeployTargetError, getEndpointForStage, getPublicEnvPrefix, isDeployTargetSupported, isPhase2DeployTarget, isWorkspaceConfig, normalizeWorkspace, processConfig, safeValidateWorkspaceConfig, stripPublicPrefix, validateWorkspaceConfig, wrapSingleAppAsWorkspace };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geekmidas/cli",
3
- "version": "1.10.40",
3
+ "version": "1.11.0",
4
4
  "description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
5
5
  "private": false,
6
6
  "type": "module",
@@ -81,11 +81,11 @@
81
81
  "prompts": "~2.4.2",
82
82
  "tsx": "~4.20.3",
83
83
  "yaml": "~2.8.2",
84
+ "@geekmidas/constructs": "~4.0.0",
85
+ "@geekmidas/envkit": "~1.0.7",
84
86
  "@geekmidas/errors": "~1.0.1",
85
- "@geekmidas/logger": "~1.0.2",
86
- "@geekmidas/constructs": "~3.1.0",
87
87
  "@geekmidas/schema": "~1.0.2",
88
- "@geekmidas/envkit": "~1.0.7"
88
+ "@geekmidas/logger": "~1.0.2"
89
89
  },
90
90
  "devDependencies": {
91
91
  "@types/lodash.kebabcase": "^4.1.9",
@@ -95,7 +95,7 @@
95
95
  "typescript": "^5.8.2",
96
96
  "vitest": "^3.2.4",
97
97
  "zod": "~4.1.13",
98
- "@geekmidas/testkit": "1.0.9"
98
+ "@geekmidas/testkit": "2.0.0"
99
99
  },
100
100
  "peerDependencies": {
101
101
  "@geekmidas/telescope": "~1.0.1"
package/src/index.ts CHANGED
@@ -236,6 +236,10 @@ program
236
236
  .option('--watch', 'Enable watch mode')
237
237
  .option('--coverage', 'Generate coverage report')
238
238
  .option('--ui', 'Open Vitest UI')
239
+ .option(
240
+ '--auto-setup',
241
+ 'Generate a fresh stage (secrets + key) from the workspace config when none exists (for CI; also via GKM_AUTO_SETUP)',
242
+ )
239
243
  .argument('[pattern]', 'Pattern to filter tests')
240
244
  .action(async (pattern: string | undefined, options: TestOptions) => {
241
245
  try {
@@ -30,22 +30,22 @@ export const GEEKMIDAS_VERSIONS = {
30
30
  '@geekmidas/audit': '~2.0.1',
31
31
  '@geekmidas/auth': '~2.0.1',
32
32
  '@geekmidas/cache': '~1.1.1',
33
- '@geekmidas/client': '~4.0.5',
33
+ '@geekmidas/client': '~6.0.0',
34
34
  '@geekmidas/cloud': '~1.0.1',
35
- '@geekmidas/constructs': '~3.0.14',
35
+ '@geekmidas/constructs': '~4.0.0',
36
36
  '@geekmidas/db': '~1.0.2',
37
37
  '@geekmidas/emailkit': '~1.0.1',
38
38
  '@geekmidas/envkit': '~1.0.7',
39
39
  '@geekmidas/errors': '~1.0.1',
40
40
  '@geekmidas/events': '~1.1.4',
41
41
  '@geekmidas/logger': '~1.0.2',
42
- '@geekmidas/rate-limit': '~2.0.1',
42
+ '@geekmidas/rate-limit': '~3.0.0',
43
43
  '@geekmidas/schema': '~1.0.2',
44
- '@geekmidas/services': '~1.0.4',
44
+ '@geekmidas/services': '~1.1.1',
45
45
  '@geekmidas/storage': '~2.0.3',
46
46
  '@geekmidas/studio': '~1.0.1',
47
47
  '@geekmidas/telescope': '~1.0.1',
48
- '@geekmidas/testkit': '~1.0.9',
48
+ '@geekmidas/testkit': '~2.0.0',
49
49
  '@geekmidas/cli': CLI_VERSION,
50
50
  } as const;
51
51
 
@@ -0,0 +1,147 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { rm } from 'node:fs/promises';
3
+ import { homedir, tmpdir } from 'node:os';
4
+ import { basename, join } from 'node:path';
5
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
6
+ import { readStageSecrets, secretsExist } from '../../secrets/storage.js';
7
+ import type { NormalizedWorkspace } from '../../workspace/types.js';
8
+ import { createFreshWorkspaceSecrets, ensureStageSecrets } from '../index.js';
9
+
10
+ function createWorkspace(
11
+ overrides: Partial<NormalizedWorkspace> = {},
12
+ ): NormalizedWorkspace {
13
+ return {
14
+ name: 'test-project',
15
+ root: '/tmp/test-project',
16
+ apps: {
17
+ api: {
18
+ type: 'backend',
19
+ port: 3000,
20
+ root: '/tmp/test-project/apps/api',
21
+ packageName: '@test/api',
22
+ routes: './src/endpoints/**/*.ts',
23
+ dependencies: [],
24
+ },
25
+ },
26
+ services: { db: true },
27
+ deploy: {},
28
+ shared: {},
29
+ secrets: {},
30
+ ...overrides,
31
+ } as NormalizedWorkspace;
32
+ }
33
+
34
+ describe('createFreshWorkspaceSecrets', () => {
35
+ it('generates postgres credentials when db service is enabled', () => {
36
+ const secrets = createFreshWorkspaceSecrets(
37
+ 'development',
38
+ createWorkspace(),
39
+ );
40
+
41
+ expect(secrets.stage).toBe('development');
42
+ expect(secrets.services.postgres).toBeDefined();
43
+ expect(secrets.services.postgres?.password).toBeTruthy();
44
+ expect(secrets.urls.DATABASE_URL).toContain('postgresql://');
45
+ });
46
+
47
+ it('generates a randomized password each call', () => {
48
+ const a = createFreshWorkspaceSecrets('development', createWorkspace());
49
+ const b = createFreshWorkspaceSecrets('development', createWorkspace());
50
+
51
+ expect(a.services.postgres?.password).not.toBe(
52
+ b.services.postgres?.password,
53
+ );
54
+ });
55
+
56
+ it('adds single-app custom secrets', () => {
57
+ const secrets = createFreshWorkspaceSecrets(
58
+ 'development',
59
+ createWorkspace(),
60
+ );
61
+
62
+ expect(secrets.custom.NODE_ENV).toBe('development');
63
+ expect(secrets.custom.JWT_SECRET).toBeTruthy();
64
+ });
65
+ });
66
+
67
+ describe('ensureStageSecrets', () => {
68
+ let testDir: string;
69
+ const originalGkmConfigPath = process.env.GKM_CONFIG_PATH;
70
+
71
+ beforeEach(() => {
72
+ testDir = join(
73
+ tmpdir(),
74
+ `gkm-auto-setup-${Date.now()}-${Math.random().toString(36).slice(2)}`,
75
+ );
76
+ mkdirSync(testDir, { recursive: true });
77
+
78
+ // Minimal single-app workspace config with a db service.
79
+ writeFileSync(
80
+ join(testDir, 'gkm.config.ts'),
81
+ `import { defineWorkspace } from '@geekmidas/cli/config';
82
+
83
+ export default defineWorkspace({
84
+ name: 'test-workspace',
85
+ apps: {
86
+ api: {
87
+ type: 'backend',
88
+ path: 'apps/api',
89
+ port: 3001,
90
+ routes: './src/endpoints/**/*.ts',
91
+ envParser: './src/config/env#envParser',
92
+ logger: './src/config/logger#logger',
93
+ },
94
+ },
95
+ services: { db: true },
96
+ });
97
+ `,
98
+ );
99
+ const apiDir = join(testDir, 'apps', 'api');
100
+ mkdirSync(apiDir, { recursive: true });
101
+ writeFileSync(
102
+ join(apiDir, 'package.json'),
103
+ JSON.stringify({ name: '@test/api', version: '0.0.1' }),
104
+ );
105
+
106
+ process.env.GKM_CONFIG_PATH = join(testDir, 'gkm.config.ts');
107
+ });
108
+
109
+ afterEach(async () => {
110
+ if (originalGkmConfigPath === undefined) {
111
+ delete process.env.GKM_CONFIG_PATH;
112
+ } else {
113
+ process.env.GKM_CONFIG_PATH = originalGkmConfigPath;
114
+ }
115
+
116
+ await rm(testDir, { recursive: true, force: true });
117
+ // writeStageSecrets mints a key at ~/.gkm/{basename(root)}
118
+ await rm(join(homedir(), '.gkm', basename(testDir)), {
119
+ recursive: true,
120
+ force: true,
121
+ });
122
+ });
123
+
124
+ it('generates a decryptable stage when none exists', async () => {
125
+ expect(secretsExist('development', testDir)).toBe(false);
126
+
127
+ const generated = await ensureStageSecrets('development', testDir);
128
+
129
+ expect(generated).toBe(true);
130
+ expect(secretsExist('development', testDir)).toBe(true);
131
+
132
+ // Round-trips through the keystore the same way `gkm test` reads it.
133
+ const read = await readStageSecrets('development', testDir);
134
+ expect(read?.services.postgres).toBeDefined();
135
+ });
136
+
137
+ it('is a no-op when secrets already exist', async () => {
138
+ await ensureStageSecrets('development', testDir);
139
+ const before = await readStageSecrets('development', testDir);
140
+
141
+ const generated = await ensureStageSecrets('development', testDir);
142
+
143
+ expect(generated).toBe(false);
144
+ const after = await readStageSecrets('development', testDir);
145
+ expect(after).toEqual(before);
146
+ });
147
+ });
@@ -288,13 +288,16 @@ export function reconcileSecrets(
288
288
  }
289
289
 
290
290
  /**
291
- * Generate fresh secrets for the workspace.
291
+ * Build a fresh StageSecrets object for a workspace: service credentials,
292
+ * connection URLs, and custom secrets. Pure — does not write to disk or touch
293
+ * SSM. Service passwords are freshly randomized, so the result must be the same
294
+ * one used to start the matching Docker containers.
295
+ * @internal Exported for reuse by `gkm test` auto-setup.
292
296
  */
293
- async function generateFreshSecrets(
297
+ export function createFreshWorkspaceSecrets(
294
298
  stage: string,
295
299
  workspace: NormalizedWorkspace,
296
- options: SetupOptions,
297
- ) {
300
+ ): StageSecrets {
298
301
  // Determine services from workspace config
299
302
  const serviceNames: ComposeServiceName[] = [];
300
303
  if (workspace.services.db) serviceNames.push('postgres');
@@ -313,8 +316,7 @@ async function generateFreshSecrets(
313
316
  // Generate fullstack-aware custom secrets
314
317
  const isMultiApp = Object.keys(workspace.apps).length > 1;
315
318
  if (isMultiApp) {
316
- const customSecrets = generateFullstackCustomSecrets(workspace);
317
- secrets.custom = customSecrets;
319
+ secrets.custom = generateFullstackCustomSecrets(workspace);
318
320
  } else {
319
321
  secrets.custom = {
320
322
  NODE_ENV: 'development',
@@ -324,6 +326,44 @@ async function generateFreshSecrets(
324
326
  };
325
327
  }
326
328
 
329
+ return secrets;
330
+ }
331
+
332
+ /**
333
+ * Ensure a usable secret set exists for a stage, generating a fresh one from the
334
+ * workspace config when none is present. No-op if secrets already exist.
335
+ *
336
+ * Powers `gkm test --auto-setup` / `GKM_AUTO_SETUP`: CI can run without a
337
+ * committed secrets file or a shared encryption key — the stage is regenerated
338
+ * from the committed `gkm.config.ts`, and `writeStageSecrets` mints a local key.
339
+ *
340
+ * @returns true if fresh secrets were generated, false if existing ones were kept
341
+ */
342
+ export async function ensureStageSecrets(
343
+ stage: string,
344
+ cwd: string = process.cwd(),
345
+ ): Promise<boolean> {
346
+ const { workspace } = await loadWorkspaceConfig(cwd);
347
+
348
+ if (secretsExist(stage, workspace.root)) {
349
+ return false;
350
+ }
351
+
352
+ const secrets = createFreshWorkspaceSecrets(stage, workspace);
353
+ await writeStageSecrets(secrets, workspace.root);
354
+ return true;
355
+ }
356
+
357
+ /**
358
+ * Generate fresh secrets for the workspace.
359
+ */
360
+ async function generateFreshSecrets(
361
+ stage: string,
362
+ workspace: NormalizedWorkspace,
363
+ options: SetupOptions,
364
+ ) {
365
+ const secrets = createFreshWorkspaceSecrets(stage, workspace);
366
+
327
367
  // Write secrets
328
368
  await writeStageSecrets(secrets, workspace.root);
329
369
  logger.log(` Secrets written to .gkm/secrets/${stage}.json`);
package/src/test/index.ts CHANGED
@@ -21,6 +21,12 @@ export interface TestOptions {
21
21
  ui?: boolean;
22
22
  /** Pattern to filter tests */
23
23
  pattern?: string;
24
+ /**
25
+ * Generate a fresh stage (secrets + encryption key) from the workspace config
26
+ * when none exists, instead of requiring committed secrets / a shared key.
27
+ * Intended for CI. Also enabled by the GKM_AUTO_SETUP env var.
28
+ */
29
+ autoSetup?: boolean;
24
30
  }
25
31
 
26
32
  /**
@@ -33,6 +39,17 @@ export async function testCommand(options: TestOptions = {}): Promise<void> {
33
39
 
34
40
  console.log(`\n🧪 Running tests with ${stage} environment...\n`);
35
41
 
42
+ // 0. Auto-setup (CI): regenerate a fresh stage from the workspace config when
43
+ // none exists, so tests run without committed secrets or a shared key.
44
+ const autoSetup = options.autoSetup || Boolean(process.env.GKM_AUTO_SETUP);
45
+ if (autoSetup) {
46
+ const { ensureStageSecrets } = await import('../setup/index.js');
47
+ const generated = await ensureStageSecrets(stage, cwd);
48
+ if (generated) {
49
+ console.log(` 🔐 Generated fresh ${stage} secrets (auto-setup)`);
50
+ }
51
+ }
52
+
36
53
  // 1. Load .env files
37
54
  const defaultEnv = loadEnvFiles('.env');
38
55
  if (defaultEnv.loaded.length > 0) {
@@ -1,3 +0,0 @@
1
- import { CachedStateProvider } from "./CachedStateProvider-C8A-cCm5.mjs";
2
-
3
- export { CachedStateProvider };
@@ -1,3 +0,0 @@
1
- const require_dokploy_api = require('./dokploy-api-Dvyq-LsM.cjs');
2
-
3
- exports.DokployApi = require_dokploy_api.DokployApi;
@@ -1,3 +0,0 @@
1
- import { DokployApi, DokployApiError } from "./dokploy-api-BIT0683o.mjs";
2
-
3
- export { DokployApi };
@@ -1,3 +0,0 @@
1
- import { encryptSecrets, generateDefineOptions } from "./encryption-DSkIlEZ6.mjs";
2
-
3
- export { encryptSecrets, generateDefineOptions };
@@ -1,4 +0,0 @@
1
- const require_sync = require('./sync-DGXXSk2v.cjs');
2
-
3
- exports.pullSecrets = require_sync.pullSecrets;
4
- exports.pushSecrets = require_sync.pushSecrets;