@geekmidas/cli 10.0.0-alpha.1 → 10.0.0-alpha.3
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/dist/{config-DbPE1KXF.cjs → config-BholeD-0.cjs} +5 -5
- package/dist/config-BholeD-0.cjs.map +1 -0
- package/dist/{config-D8o6I_7Z.mjs → config-CzWnfcXM.mjs} +5 -5
- package/dist/config-CzWnfcXM.mjs.map +1 -0
- package/dist/config.cjs +1 -1
- package/dist/config.mjs +1 -1
- package/dist/{credentials-Dh0Q6HzY.mjs → credentials-BZcp3CGx.mjs} +3 -3
- package/dist/{credentials-Dh0Q6HzY.mjs.map → credentials-BZcp3CGx.mjs.map} +1 -1
- package/dist/{credentials-K961ThG3.cjs → credentials-DQmbRnr1.cjs} +3 -3
- package/dist/{credentials-K961ThG3.cjs.map → credentials-DQmbRnr1.cjs.map} +1 -1
- package/dist/index.cjs +469 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +469 -132
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-CgpMWMmM.mjs → openapi-CY9xFkTH.mjs} +2 -2
- package/dist/{openapi-CgpMWMmM.mjs.map → openapi-CY9xFkTH.mjs.map} +1 -1
- package/dist/{openapi-CljONd_M.cjs → openapi-c9bpieI5.cjs} +2 -2
- package/dist/{openapi-CljONd_M.cjs.map → openapi-c9bpieI5.cjs.map} +1 -1
- package/dist/openapi.cjs +2 -2
- package/dist/openapi.mjs +2 -2
- package/dist/{setup-DOsYueeQ.mjs → setup-Bc73t0ve.mjs} +3 -3
- package/dist/{setup-DOsYueeQ.mjs.map → setup-Bc73t0ve.mjs.map} +1 -1
- package/dist/{setup-CJiQM2pX.cjs → setup-C48C9hto.cjs} +3 -3
- package/dist/{setup-CJiQM2pX.cjs.map → setup-C48C9hto.cjs.map} +1 -1
- package/dist/{setup-DSutDAlt.mjs → setup-DCuudnCl.mjs} +3 -3
- package/dist/{setup-DMtU3p41.cjs → setup-fG_ivuxO.cjs} +3 -3
- package/dist/{workspace-8acupeaI.cjs → workspace-BO_KWohI.cjs} +2 -2
- package/dist/{workspace-yP5AHnrn.mjs → workspace-DK4WWJIn.mjs} +2 -2
- package/package.json +11 -11
- package/dist/config-D8o6I_7Z.mjs.map +0 -1
- package/dist/config-DbPE1KXF.cjs.map +0 -1
package/dist/openapi.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
require('./discover-DTmFeloL.cjs');
|
|
3
|
-
require('./config-
|
|
3
|
+
require('./config-BholeD-0.cjs');
|
|
4
4
|
require('./workspace-XvqO9jIv.cjs');
|
|
5
|
-
const require_openapi = require('./openapi-
|
|
5
|
+
const require_openapi = require('./openapi-c9bpieI5.cjs');
|
|
6
6
|
|
|
7
7
|
exports.OPENAPI_OUTPUT_PATH = require_openapi.OPENAPI_OUTPUT_PATH;
|
|
8
8
|
exports.generateOpenApi = require_openapi.generateOpenApi;
|
package/dist/openapi.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
import "./discover-CAlFgYec.mjs";
|
|
3
|
-
import "./config-
|
|
3
|
+
import "./config-CzWnfcXM.mjs";
|
|
4
4
|
import "./workspace-7FqprxuJ.mjs";
|
|
5
|
-
import { OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-
|
|
5
|
+
import { OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig } from "./openapi-CY9xFkTH.mjs";
|
|
6
6
|
|
|
7
7
|
export { OPENAPI_OUTPUT_PATH, generateOpenApi, openapiCommand, resolveOpenApiConfig };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { loadWorkspaceConfig } from "./config-
|
|
2
|
-
import { LOCAL_CA_PATH, derivedContainers, reconcileWorkspace } from "./credentials-
|
|
1
|
+
import { loadWorkspaceConfig } from "./config-CzWnfcXM.mjs";
|
|
2
|
+
import { LOCAL_CA_PATH, derivedContainers, reconcileWorkspace } from "./credentials-BZcp3CGx.mjs";
|
|
3
3
|
import { createStageSecrets, generateConnectionUrls, generateSecurePassword, generateServiceCredentials } from "./generator-VE2JrFlY.mjs";
|
|
4
4
|
import { readStageSecrets, secretsExist, writeStageSecrets } from "./storage-Bi24WfoW.mjs";
|
|
5
5
|
import { generateFullstackCustomSecrets, writeDockerEnvFromSecrets } from "./fullstack-secrets-D1o9-G5l.mjs";
|
|
@@ -440,4 +440,4 @@ function printSummary(workspace, stage) {
|
|
|
440
440
|
|
|
441
441
|
//#endregion
|
|
442
442
|
export { createFreshWorkspaceSecrets, ensureStageSecrets, reconcileSecrets, setupCommand, trustCommand };
|
|
443
|
-
//# sourceMappingURL=setup-
|
|
443
|
+
//# sourceMappingURL=setup-Bc73t0ve.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-DOsYueeQ.mjs","names":["logger","platform: NodeJS.Platform","certificate: string","options: TrustOptions","root: string","url: string","options: { configured?: boolean; assumeYes?: boolean }","path: string","platform: string","options: SetupOptions","loadedConfig: LoadedConfig","workspace: NormalizedWorkspace","stage: string","containers: readonly string[]","byContainer: Readonly<Record<string, ComposeServiceName>>","secrets: StageSecrets","missing: Record<string, string>","cwd: string"],"sources":["../src/trust/index.ts","../src/setup/index.ts"],"sourcesContent":["/**\n * Installing the local edge's certificate authority into the OS trust store.\n *\n * `NODE_EXTRA_CA_CERTS` covers everything gkm starts — the dev server, the test\n * suite, anything under `gkm exec` — because reconcile injects it. A browser\n * reads none of that: it asks the operating system, so until the root is in the\n * system store a local `https://` address is \"not secure\", which is true and\n * unhelpful.\n *\n * A separate command rather than part of `gkm setup`, for one reason: this\n * needs `sudo`, and a setup that silently escalates on every run is a worse\n * trade than one that tells you what to type. Caddy's own `caddy trust` draws\n * the line in the same place.\n */\n\nimport { execFile } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport prompts from 'prompts';\nimport { loadWorkspaceConfig } from '../config.js';\nimport { LOCAL_CA_PATH } from '../reconcile/index.js';\n\nconst logger = console;\n\nconst run = promisify(execFile);\n\nexport interface TrustOptions {\n\t/** Print the commands rather than running them. */\n\tdryRun?: boolean;\n}\n\n/**\n * How each platform is told about a new authority.\n *\n * Separate rather than shelled through one script because the failure modes\n * differ and the message should name the real one: macOS refuses without an\n * admin password, Debian needs the file in a specific directory before its\n * updater will look at it.\n */\nfunction installation(\n\tplatform: NodeJS.Platform,\n\tcertificate: string,\n): { describe: string; steps: [string, string[]][] } {\n\tif (platform === 'darwin') {\n\t\treturn {\n\t\t\tdescribe: 'the System keychain',\n\t\t\tsteps: [\n\t\t\t\t[\n\t\t\t\t\t'sudo',\n\t\t\t\t\t[\n\t\t\t\t\t\t'security',\n\t\t\t\t\t\t'add-trusted-cert',\n\t\t\t\t\t\t'-d',\n\t\t\t\t\t\t'-r',\n\t\t\t\t\t\t'trustRoot',\n\t\t\t\t\t\t'-k',\n\t\t\t\t\t\t'/Library/Keychains/System.keychain',\n\t\t\t\t\t\tcertificate,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t],\n\t\t};\n\t}\n\n\tif (platform === 'linux') {\n\t\t// Debian and Fedora keep anchors in different places and run different\n\t\t// updaters. Whichever directory exists is the one this system uses.\n\t\tconst debian = existsSync('/usr/local/share/ca-certificates');\n\n\t\treturn {\n\t\t\tdescribe: debian ? 'the system CA bundle' : 'the system trust anchors',\n\t\t\tsteps: debian\n\t\t\t\t? [\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t'sudo',\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'cp',\n\t\t\t\t\t\t\t\tcertificate,\n\t\t\t\t\t\t\t\t'/usr/local/share/ca-certificates/gkm-local-ca.crt',\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t],\n\t\t\t\t\t\t['sudo', ['update-ca-certificates']],\n\t\t\t\t\t]\n\t\t\t\t: [\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t'sudo',\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'cp',\n\t\t\t\t\t\t\t\tcertificate,\n\t\t\t\t\t\t\t\t'/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt',\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t],\n\t\t\t\t\t\t['sudo', ['update-ca-trust']],\n\t\t\t\t\t],\n\t\t};\n\t}\n\n\tthrow new UnsupportedPlatform(platform, certificate);\n}\n\n/**\n * Trust the certificate authority this project's edge issues from.\n *\n * Idempotent in effect: installing a root that is already trusted replaces the\n * same entry, so running it twice is not an error.\n */\nexport async function trustCommand(options: TrustOptions = {}): Promise<void> {\n\tconst { workspace } = await loadWorkspaceConfig();\n\tconst certificate = join(workspace.root, LOCAL_CA_PATH);\n\n\tif (!existsSync(certificate)) {\n\t\tthrow new NoLocalAuthority(certificate);\n\t}\n\n\tconst { describe, steps } = installation(process.platform, certificate);\n\n\tlogger.log(`\\n🔐 Trusting the local authority in ${describe}`);\n\tlogger.log(` ${certificate}\\n`);\n\n\tif (options.dryRun) {\n\t\tfor (const [command, args] of steps) {\n\t\t\tlogger.log(` ${command} ${args.join(' ')}`);\n\t\t}\n\t\treturn;\n\t}\n\n\tlogger.log(' This needs an admin password.\\n');\n\n\tfor (const [command, args] of steps) {\n\t\t// Inherited stdio, because sudo prompts on the terminal and a captured\n\t\t// prompt is a command that appears to hang.\n\t\tawait run(command, args, { stdio: 'inherit' } as never);\n\t}\n\n\tlogger.log('\\n✅ Local https addresses are trusted. Restart your browser.\\n');\n}\n\n/**\n * Trust the authority if this project wants that and this machine lacks it.\n *\n * The decision, in order: what the project configured, then what a person\n * previously answered, then a prompt. Never a silent `sudo` — that is the one\n * thing a setup command should not do on somebody's behalf.\n *\n * Silent and free when the machine already trusts it, which is what makes it\n * acceptable to call on every `gkm setup`.\n */\nexport async function ensureTrusted(\n\troot: string,\n\turl: string,\n\toptions: { configured?: boolean; assumeYes?: boolean } = {},\n): Promise<void> {\n\tif (options.configured === false) return;\n\tif (await isTrusted(url)) return;\n\n\tif (options.configured !== true && !options.assumeYes) {\n\t\t// Nothing to answer a prompt in CI, a hook, or a piped shell — and a\n\t\t// setup that stops there waiting is worse than one that leaves the\n\t\t// authority untrusted. Not recorded as declined, because nobody\n\t\t// declined: the next interactive run should still ask.\n\t\tif (!process.stdin.isTTY) {\n\t\t\tlogger.log(\n\t\t\t\t' Local https addresses are not trusted here. Run \"gkm trust\", ' +\n\t\t\t\t\t'or set services.trustLocalCa.',\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// A recorded \"no\" is why this asks once rather than every start. There is\n\t\t// no recorded \"yes\": trusting is checked directly above, so a yes that\n\t\t// worked answers itself and a yes that did not should ask again.\n\t\tif (await declined(root)) return;\n\n\t\tconst { install } = await prompts({\n\t\t\ttype: 'confirm',\n\t\t\tname: 'install',\n\t\t\tmessage:\n\t\t\t\t\"Trust this project's local certificate authority, so a browser \" +\n\t\t\t\t'accepts its https addresses? (needs sudo)',\n\t\t\tinitial: false,\n\t\t});\n\n\t\tif (!install) {\n\t\t\tawait recordDeclined(root);\n\t\t\tlogger.log(\n\t\t\t\t' Skipped. Run \"gkm trust\" later, or set services.trustLocalCa.',\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tawait trustCommand();\n}\n\n/** Where a declined answer is remembered, so setup asks once and not again. */\nconst TRUST_STATE = '.gkm/trust.json';\n\nasync function declined(root: string): Promise<boolean> {\n\ttry {\n\t\tconst raw = await readFile(join(root, TRUST_STATE), 'utf-8');\n\n\t\treturn (JSON.parse(raw) as { declined?: boolean }).declined === true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function recordDeclined(root: string): Promise<void> {\n\tconst path = join(root, TRUST_STATE);\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, `${JSON.stringify({ declined: true }, null, 2)}\\n`);\n}\n\n/**\n * Whether this machine already trusts the local authority.\n *\n * Asked by connecting rather than by reading a keychain: what matters is\n * whether a client with no special configuration verifies the edge, and that is\n * one platform-neutral question instead of three platform-specific ones. It is\n * also the exact thing a browser is about to do.\n *\n * A child process, because `NODE_EXTRA_CA_CERTS` is read once at startup and\n * this process almost certainly has it set — asking in-process would answer\n * \"yes\" for everyone.\n */\nexport async function isTrusted(url: string): Promise<boolean> {\n\tconst { NODE_EXTRA_CA_CERTS: _ignored, ...env } = process.env;\n\n\ttry {\n\t\tawait run(\n\t\t\tprocess.execPath,\n\t\t\t['-e', `fetch(${JSON.stringify(url)}).then(()=>0,()=>process.exit(1))`],\n\t\t\t{ env, timeout: 15_000 },\n\t\t);\n\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Copy the root somewhere a tool that wants a file can read it.\n *\n * For anything that takes a CA bundle rather than reading the system store —\n * another language's HTTP client, a container, a CI job.\n */\nexport async function exportLocalAuthority(to: string): Promise<void> {\n\tconst { workspace } = await loadWorkspaceConfig();\n\tconst certificate = join(workspace.root, LOCAL_CA_PATH);\n\n\tif (!existsSync(certificate)) throw new NoLocalAuthority(certificate);\n\n\tawait copyFile(certificate, to);\n}\n\n/** The edge has not run, so there is no authority to trust yet. */\nexport class NoLocalAuthority extends Error {\n\tconstructor(readonly path: string) {\n\t\tsuper(\n\t\t\t`No local certificate authority at ${path}. It is generated the first ` +\n\t\t\t\t`time the edge starts, so run \"gkm setup\" (or \"gkm dev\") once — and ` +\n\t\t\t\t`if this project declares no file server, nothing needs one.`,\n\t\t);\n\t\tthis.name = 'NoLocalAuthority';\n\t}\n}\n\n/** A platform with no trust store this knows how to write to. */\nexport class UnsupportedPlatform extends Error {\n\tconstructor(\n\t\treadonly platform: string,\n\t\treadonly certificate: string,\n\t) {\n\t\tsuper(\n\t\t\t`gkm does not know how to install a certificate authority on ` +\n\t\t\t\t`${platform}. The root is at ${certificate} — add it to the trust ` +\n\t\t\t\t`store by hand, or point a client at it with NODE_EXTRA_CA_CERTS.`,\n\t\t);\n\t\tthis.name = 'UnsupportedPlatform';\n\t}\n}\n","import prompts from 'prompts';\nimport { loadWorkspaceConfig } from '../config.js';\nimport {\n\tderivedContainers,\n\treconcileWorkspace,\n} from '../reconcile/workspace.js';\nimport {\n\tcreateStageSecrets,\n\tgenerateConnectionUrls,\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 { ensureTrusted } from '../trust/index.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\tconst containers = await derivedContainers(workspace, stage);\n\n\tif (isMultiApp && containers.includes('postgres')) {\n\t\tawait writeDockerEnvFromSecrets(secrets, workspace.root);\n\t\tlogger.log('📄 Generated docker/.env with database passwords');\n\t}\n\n\t// 4. Reconcile the local target: derive containers, allocate ports, start.\n\tif (!options.skipDocker) {\n\t\tlogger.log('');\n\n\t\tawait reconcileLocal(workspace, stage, options);\n\t}\n\n\t// Print summary\n\tprintSummary(workspace, stage);\n}\n\n/**\n * Converge this machine on what the manifest declares.\n *\n * Says what it did in one line and stays silent when it did nothing, because\n * reconciling on every start is only tolerable if the converged case is both\n * free and quiet.\n */\nasync function reconcileLocal(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n\toptions: SetupOptions,\n): Promise<void> {\n\tconst result = await reconcileWorkspace(workspace, { stage });\n\n\tif (result.plan.containers.length === 0) {\n\t\tlogger.log('🐳 No containers declared');\n\t\treturn;\n\t}\n\n\tif (!result.changed) {\n\t\tlogger.log('🐳 Services already up to date');\n\t\treturn;\n\t}\n\n\tlogger.log(`🐳 Services: ${result.plan.containers.join(', ')}`);\n\tfor (const [container, address] of Object.entries(result.addresses)) {\n\t\tlogger.log(` ${container}: ${address}`);\n\t}\n\n\t// Only where something actually answers on https. A project with no edge has\n\t// no authority to trust and should never be asked about one.\n\tconst served = result.plan.resources.find((r) => r.kind === 'file-server');\n\tconst url = served ? result.env[served.envKey] : undefined;\n\n\tif (url) {\n\t\tawait ensureTrusted(workspace.root, url, {\n\t\t\t...(workspace.services.trustLocalCa === undefined\n\t\t\t\t? {}\n\t\t\t\t: { configured: workspace.services.trustLocalCa }),\n\t\t\t...(options.yes ? { assumeYes: true } : {}),\n\t\t});\n\t}\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(\n\t\t\t\tsecrets,\n\t\t\t\tworkspace,\n\t\t\t\tawait derivedContainers(workspace, stage),\n\t\t\t);\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 */\n/**\n * Which containers hold a credential worth generating.\n *\n * Reconcile's container names, mapped to the service a credential is stored\n * under. `redis-http` is the Upstash-protocol proxy in front of Redis and holds\n * no credential of its own — its token is derived — so it maps to nothing, and\n * `caddy` is an edge with nothing to log into.\n */\nfunction credentialedServices(\n\tcontainers: readonly string[],\n): ComposeServiceName[] {\n\tconst byContainer: Readonly<Record<string, ComposeServiceName>> = {\n\t\tpostgres: 'postgres',\n\t\tredis: 'redis',\n\t\tminio: 'minio',\n\t\tmailpit: 'mailpit',\n\t\tlocalstack: 'localstack',\n\t\trabbitmq: 'rabbitmq',\n\t};\n\n\treturn [\n\t\t...new Set(\n\t\t\tcontainers\n\t\t\t\t.map((container) => byContainer[container])\n\t\t\t\t.filter((name): name is ComposeServiceName => Boolean(name)),\n\t\t),\n\t];\n}\n\nexport function reconcileSecrets(\n\tsecrets: StageSecrets,\n\tworkspace: NormalizedWorkspace,\n\tcontainers: readonly string[] = [],\n): StageSecrets | null {\n\tlet changed = false;\n\tlet result = { ...secrets };\n\n\t// Reconcile service credentials: add missing services\n\tfor (const name of credentialedServices(containers)) {\n\t\tif (!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 custom secrets for multi-app workspaces\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\tif (isMultiApp) {\n\t\tconst expected = generateFullstackCustomSecrets(workspace, containers);\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\tcontainers: readonly string[] = [],\n): StageSecrets {\n\t// Which services need a credential is which containers exist, and that is\n\t// the manifest's answer. It used to be a boolean per service read from\n\t// config, which generated a Postgres password for a project with no\n\t// database and none for a project that declared one and set no flag.\n\tconst serviceNames = credentialedServices(containers);\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, containers);\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(\n\t\tstage,\n\t\tworkspace,\n\t\tawait derivedContainers(workspace, stage),\n\t);\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(\n\t\tstage,\n\t\tworkspace,\n\t\tawait derivedContainers(workspace, stage),\n\t);\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
|
+
{"version":3,"file":"setup-Bc73t0ve.mjs","names":["logger","platform: NodeJS.Platform","certificate: string","options: TrustOptions","root: string","url: string","options: { configured?: boolean; assumeYes?: boolean }","path: string","platform: string","options: SetupOptions","loadedConfig: LoadedConfig","workspace: NormalizedWorkspace","stage: string","containers: readonly string[]","byContainer: Readonly<Record<string, ComposeServiceName>>","secrets: StageSecrets","missing: Record<string, string>","cwd: string"],"sources":["../src/trust/index.ts","../src/setup/index.ts"],"sourcesContent":["/**\n * Installing the local edge's certificate authority into the OS trust store.\n *\n * `NODE_EXTRA_CA_CERTS` covers everything gkm starts — the dev server, the test\n * suite, anything under `gkm exec` — because reconcile injects it. A browser\n * reads none of that: it asks the operating system, so until the root is in the\n * system store a local `https://` address is \"not secure\", which is true and\n * unhelpful.\n *\n * A separate command rather than part of `gkm setup`, for one reason: this\n * needs `sudo`, and a setup that silently escalates on every run is a worse\n * trade than one that tells you what to type. Caddy's own `caddy trust` draws\n * the line in the same place.\n */\n\nimport { execFile } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport prompts from 'prompts';\nimport { loadWorkspaceConfig } from '../config.js';\nimport { LOCAL_CA_PATH } from '../reconcile/index.js';\n\nconst logger = console;\n\nconst run = promisify(execFile);\n\nexport interface TrustOptions {\n\t/** Print the commands rather than running them. */\n\tdryRun?: boolean;\n}\n\n/**\n * How each platform is told about a new authority.\n *\n * Separate rather than shelled through one script because the failure modes\n * differ and the message should name the real one: macOS refuses without an\n * admin password, Debian needs the file in a specific directory before its\n * updater will look at it.\n */\nfunction installation(\n\tplatform: NodeJS.Platform,\n\tcertificate: string,\n): { describe: string; steps: [string, string[]][] } {\n\tif (platform === 'darwin') {\n\t\treturn {\n\t\t\tdescribe: 'the System keychain',\n\t\t\tsteps: [\n\t\t\t\t[\n\t\t\t\t\t'sudo',\n\t\t\t\t\t[\n\t\t\t\t\t\t'security',\n\t\t\t\t\t\t'add-trusted-cert',\n\t\t\t\t\t\t'-d',\n\t\t\t\t\t\t'-r',\n\t\t\t\t\t\t'trustRoot',\n\t\t\t\t\t\t'-k',\n\t\t\t\t\t\t'/Library/Keychains/System.keychain',\n\t\t\t\t\t\tcertificate,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t],\n\t\t};\n\t}\n\n\tif (platform === 'linux') {\n\t\t// Debian and Fedora keep anchors in different places and run different\n\t\t// updaters. Whichever directory exists is the one this system uses.\n\t\tconst debian = existsSync('/usr/local/share/ca-certificates');\n\n\t\treturn {\n\t\t\tdescribe: debian ? 'the system CA bundle' : 'the system trust anchors',\n\t\t\tsteps: debian\n\t\t\t\t? [\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t'sudo',\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'cp',\n\t\t\t\t\t\t\t\tcertificate,\n\t\t\t\t\t\t\t\t'/usr/local/share/ca-certificates/gkm-local-ca.crt',\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t],\n\t\t\t\t\t\t['sudo', ['update-ca-certificates']],\n\t\t\t\t\t]\n\t\t\t\t: [\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t'sudo',\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'cp',\n\t\t\t\t\t\t\t\tcertificate,\n\t\t\t\t\t\t\t\t'/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt',\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t],\n\t\t\t\t\t\t['sudo', ['update-ca-trust']],\n\t\t\t\t\t],\n\t\t};\n\t}\n\n\tthrow new UnsupportedPlatform(platform, certificate);\n}\n\n/**\n * Trust the certificate authority this project's edge issues from.\n *\n * Idempotent in effect: installing a root that is already trusted replaces the\n * same entry, so running it twice is not an error.\n */\nexport async function trustCommand(options: TrustOptions = {}): Promise<void> {\n\tconst { workspace } = await loadWorkspaceConfig();\n\tconst certificate = join(workspace.root, LOCAL_CA_PATH);\n\n\tif (!existsSync(certificate)) {\n\t\tthrow new NoLocalAuthority(certificate);\n\t}\n\n\tconst { describe, steps } = installation(process.platform, certificate);\n\n\tlogger.log(`\\n🔐 Trusting the local authority in ${describe}`);\n\tlogger.log(` ${certificate}\\n`);\n\n\tif (options.dryRun) {\n\t\tfor (const [command, args] of steps) {\n\t\t\tlogger.log(` ${command} ${args.join(' ')}`);\n\t\t}\n\t\treturn;\n\t}\n\n\tlogger.log(' This needs an admin password.\\n');\n\n\tfor (const [command, args] of steps) {\n\t\t// Inherited stdio, because sudo prompts on the terminal and a captured\n\t\t// prompt is a command that appears to hang.\n\t\tawait run(command, args, { stdio: 'inherit' } as never);\n\t}\n\n\tlogger.log('\\n✅ Local https addresses are trusted. Restart your browser.\\n');\n}\n\n/**\n * Trust the authority if this project wants that and this machine lacks it.\n *\n * The decision, in order: what the project configured, then what a person\n * previously answered, then a prompt. Never a silent `sudo` — that is the one\n * thing a setup command should not do on somebody's behalf.\n *\n * Silent and free when the machine already trusts it, which is what makes it\n * acceptable to call on every `gkm setup`.\n */\nexport async function ensureTrusted(\n\troot: string,\n\turl: string,\n\toptions: { configured?: boolean; assumeYes?: boolean } = {},\n): Promise<void> {\n\tif (options.configured === false) return;\n\tif (await isTrusted(url)) return;\n\n\tif (options.configured !== true && !options.assumeYes) {\n\t\t// Nothing to answer a prompt in CI, a hook, or a piped shell — and a\n\t\t// setup that stops there waiting is worse than one that leaves the\n\t\t// authority untrusted. Not recorded as declined, because nobody\n\t\t// declined: the next interactive run should still ask.\n\t\tif (!process.stdin.isTTY) {\n\t\t\tlogger.log(\n\t\t\t\t' Local https addresses are not trusted here. Run \"gkm trust\", ' +\n\t\t\t\t\t'or set services.trustLocalCa.',\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// A recorded \"no\" is why this asks once rather than every start. There is\n\t\t// no recorded \"yes\": trusting is checked directly above, so a yes that\n\t\t// worked answers itself and a yes that did not should ask again.\n\t\tif (await declined(root)) return;\n\n\t\tconst { install } = await prompts({\n\t\t\ttype: 'confirm',\n\t\t\tname: 'install',\n\t\t\tmessage:\n\t\t\t\t\"Trust this project's local certificate authority, so a browser \" +\n\t\t\t\t'accepts its https addresses? (needs sudo)',\n\t\t\tinitial: false,\n\t\t});\n\n\t\tif (!install) {\n\t\t\tawait recordDeclined(root);\n\t\t\tlogger.log(\n\t\t\t\t' Skipped. Run \"gkm trust\" later, or set services.trustLocalCa.',\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tawait trustCommand();\n}\n\n/** Where a declined answer is remembered, so setup asks once and not again. */\nconst TRUST_STATE = '.gkm/trust.json';\n\nasync function declined(root: string): Promise<boolean> {\n\ttry {\n\t\tconst raw = await readFile(join(root, TRUST_STATE), 'utf-8');\n\n\t\treturn (JSON.parse(raw) as { declined?: boolean }).declined === true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function recordDeclined(root: string): Promise<void> {\n\tconst path = join(root, TRUST_STATE);\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, `${JSON.stringify({ declined: true }, null, 2)}\\n`);\n}\n\n/**\n * Whether this machine already trusts the local authority.\n *\n * Asked by connecting rather than by reading a keychain: what matters is\n * whether a client with no special configuration verifies the edge, and that is\n * one platform-neutral question instead of three platform-specific ones. It is\n * also the exact thing a browser is about to do.\n *\n * A child process, because `NODE_EXTRA_CA_CERTS` is read once at startup and\n * this process almost certainly has it set — asking in-process would answer\n * \"yes\" for everyone.\n */\nexport async function isTrusted(url: string): Promise<boolean> {\n\tconst { NODE_EXTRA_CA_CERTS: _ignored, ...env } = process.env;\n\n\ttry {\n\t\tawait run(\n\t\t\tprocess.execPath,\n\t\t\t['-e', `fetch(${JSON.stringify(url)}).then(()=>0,()=>process.exit(1))`],\n\t\t\t{ env, timeout: 15_000 },\n\t\t);\n\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Copy the root somewhere a tool that wants a file can read it.\n *\n * For anything that takes a CA bundle rather than reading the system store —\n * another language's HTTP client, a container, a CI job.\n */\nexport async function exportLocalAuthority(to: string): Promise<void> {\n\tconst { workspace } = await loadWorkspaceConfig();\n\tconst certificate = join(workspace.root, LOCAL_CA_PATH);\n\n\tif (!existsSync(certificate)) throw new NoLocalAuthority(certificate);\n\n\tawait copyFile(certificate, to);\n}\n\n/** The edge has not run, so there is no authority to trust yet. */\nexport class NoLocalAuthority extends Error {\n\tconstructor(readonly path: string) {\n\t\tsuper(\n\t\t\t`No local certificate authority at ${path}. It is generated the first ` +\n\t\t\t\t`time the edge starts, so run \"gkm setup\" (or \"gkm dev\") once — and ` +\n\t\t\t\t`if this project declares no file server, nothing needs one.`,\n\t\t);\n\t\tthis.name = 'NoLocalAuthority';\n\t}\n}\n\n/** A platform with no trust store this knows how to write to. */\nexport class UnsupportedPlatform extends Error {\n\tconstructor(\n\t\treadonly platform: string,\n\t\treadonly certificate: string,\n\t) {\n\t\tsuper(\n\t\t\t`gkm does not know how to install a certificate authority on ` +\n\t\t\t\t`${platform}. The root is at ${certificate} — add it to the trust ` +\n\t\t\t\t`store by hand, or point a client at it with NODE_EXTRA_CA_CERTS.`,\n\t\t);\n\t\tthis.name = 'UnsupportedPlatform';\n\t}\n}\n","import prompts from 'prompts';\nimport { loadWorkspaceConfig } from '../config.js';\nimport {\n\tderivedContainers,\n\treconcileWorkspace,\n} from '../reconcile/workspace.js';\nimport {\n\tcreateStageSecrets,\n\tgenerateConnectionUrls,\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 { ensureTrusted } from '../trust/index.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\tconst containers = await derivedContainers(workspace, stage);\n\n\tif (isMultiApp && containers.includes('postgres')) {\n\t\tawait writeDockerEnvFromSecrets(secrets, workspace.root);\n\t\tlogger.log('📄 Generated docker/.env with database passwords');\n\t}\n\n\t// 4. Reconcile the local target: derive containers, allocate ports, start.\n\tif (!options.skipDocker) {\n\t\tlogger.log('');\n\n\t\tawait reconcileLocal(workspace, stage, options);\n\t}\n\n\t// Print summary\n\tprintSummary(workspace, stage);\n}\n\n/**\n * Converge this machine on what the manifest declares.\n *\n * Says what it did in one line and stays silent when it did nothing, because\n * reconciling on every start is only tolerable if the converged case is both\n * free and quiet.\n */\nasync function reconcileLocal(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n\toptions: SetupOptions,\n): Promise<void> {\n\tconst result = await reconcileWorkspace(workspace, { stage });\n\n\tif (result.plan.containers.length === 0) {\n\t\tlogger.log('🐳 No containers declared');\n\t\treturn;\n\t}\n\n\tif (!result.changed) {\n\t\tlogger.log('🐳 Services already up to date');\n\t\treturn;\n\t}\n\n\tlogger.log(`🐳 Services: ${result.plan.containers.join(', ')}`);\n\tfor (const [container, address] of Object.entries(result.addresses)) {\n\t\tlogger.log(` ${container}: ${address}`);\n\t}\n\n\t// Only where something actually answers on https. A project with no edge has\n\t// no authority to trust and should never be asked about one.\n\tconst served = result.plan.resources.find((r) => r.kind === 'file-server');\n\tconst url = served ? result.env[served.envKey] : undefined;\n\n\tif (url) {\n\t\tawait ensureTrusted(workspace.root, url, {\n\t\t\t...(workspace.services.trustLocalCa === undefined\n\t\t\t\t? {}\n\t\t\t\t: { configured: workspace.services.trustLocalCa }),\n\t\t\t...(options.yes ? { assumeYes: true } : {}),\n\t\t});\n\t}\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(\n\t\t\t\tsecrets,\n\t\t\t\tworkspace,\n\t\t\t\tawait derivedContainers(workspace, stage),\n\t\t\t);\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 */\n/**\n * Which containers hold a credential worth generating.\n *\n * Reconcile's container names, mapped to the service a credential is stored\n * under. `redis-http` is the Upstash-protocol proxy in front of Redis and holds\n * no credential of its own — its token is derived — so it maps to nothing, and\n * `caddy` is an edge with nothing to log into.\n */\nfunction credentialedServices(\n\tcontainers: readonly string[],\n): ComposeServiceName[] {\n\tconst byContainer: Readonly<Record<string, ComposeServiceName>> = {\n\t\tpostgres: 'postgres',\n\t\tredis: 'redis',\n\t\tminio: 'minio',\n\t\tmailpit: 'mailpit',\n\t\tlocalstack: 'localstack',\n\t\trabbitmq: 'rabbitmq',\n\t};\n\n\treturn [\n\t\t...new Set(\n\t\t\tcontainers\n\t\t\t\t.map((container) => byContainer[container])\n\t\t\t\t.filter((name): name is ComposeServiceName => Boolean(name)),\n\t\t),\n\t];\n}\n\nexport function reconcileSecrets(\n\tsecrets: StageSecrets,\n\tworkspace: NormalizedWorkspace,\n\tcontainers: readonly string[] = [],\n): StageSecrets | null {\n\tlet changed = false;\n\tlet result = { ...secrets };\n\n\t// Reconcile service credentials: add missing services\n\tfor (const name of credentialedServices(containers)) {\n\t\tif (!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 custom secrets for multi-app workspaces\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\tif (isMultiApp) {\n\t\tconst expected = generateFullstackCustomSecrets(workspace, containers);\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\tcontainers: readonly string[] = [],\n): StageSecrets {\n\t// Which services need a credential is which containers exist, and that is\n\t// the manifest's answer. It used to be a boolean per service read from\n\t// config, which generated a Postgres password for a project with no\n\t// database and none for a project that declared one and set no flag.\n\tconst serviceNames = credentialedServices(containers);\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, containers);\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(\n\t\tstage,\n\t\tworkspace,\n\t\tawait derivedContainers(workspace, stage),\n\t);\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(\n\t\tstage,\n\t\tworkspace,\n\t\tawait derivedContainers(workspace, stage),\n\t);\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,6 +1,6 @@
|
|
|
1
1
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
-
const require_config = require('./config-
|
|
3
|
-
const require_credentials = require('./credentials-
|
|
2
|
+
const require_config = require('./config-BholeD-0.cjs');
|
|
3
|
+
const require_credentials = require('./credentials-DQmbRnr1.cjs');
|
|
4
4
|
const require_generator = require('./generator-CNzBw7g5.cjs');
|
|
5
5
|
const require_storage = require('./storage-BJmzYxt8.cjs');
|
|
6
6
|
const require_fullstack_secrets = require('./fullstack-secrets-C-GGohke.cjs');
|
|
@@ -470,4 +470,4 @@ Object.defineProperty(exports, 'trustCommand', {
|
|
|
470
470
|
return trustCommand;
|
|
471
471
|
}
|
|
472
472
|
});
|
|
473
|
-
//# sourceMappingURL=setup-
|
|
473
|
+
//# sourceMappingURL=setup-C48C9hto.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-CJiQM2pX.cjs","names":["logger","execFile","platform: NodeJS.Platform","certificate: string","options: TrustOptions","LOCAL_CA_PATH","root: string","url: string","options: { configured?: boolean; assumeYes?: boolean }","path: string","platform: string","options: SetupOptions","loadedConfig: LoadedConfig","workspace: NormalizedWorkspace","stage: string","containers: readonly string[]","byContainer: Readonly<Record<string, ComposeServiceName>>","secrets: StageSecrets","missing: Record<string, string>","cwd: string"],"sources":["../src/trust/index.ts","../src/setup/index.ts"],"sourcesContent":["/**\n * Installing the local edge's certificate authority into the OS trust store.\n *\n * `NODE_EXTRA_CA_CERTS` covers everything gkm starts — the dev server, the test\n * suite, anything under `gkm exec` — because reconcile injects it. A browser\n * reads none of that: it asks the operating system, so until the root is in the\n * system store a local `https://` address is \"not secure\", which is true and\n * unhelpful.\n *\n * A separate command rather than part of `gkm setup`, for one reason: this\n * needs `sudo`, and a setup that silently escalates on every run is a worse\n * trade than one that tells you what to type. Caddy's own `caddy trust` draws\n * the line in the same place.\n */\n\nimport { execFile } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport prompts from 'prompts';\nimport { loadWorkspaceConfig } from '../config.js';\nimport { LOCAL_CA_PATH } from '../reconcile/index.js';\n\nconst logger = console;\n\nconst run = promisify(execFile);\n\nexport interface TrustOptions {\n\t/** Print the commands rather than running them. */\n\tdryRun?: boolean;\n}\n\n/**\n * How each platform is told about a new authority.\n *\n * Separate rather than shelled through one script because the failure modes\n * differ and the message should name the real one: macOS refuses without an\n * admin password, Debian needs the file in a specific directory before its\n * updater will look at it.\n */\nfunction installation(\n\tplatform: NodeJS.Platform,\n\tcertificate: string,\n): { describe: string; steps: [string, string[]][] } {\n\tif (platform === 'darwin') {\n\t\treturn {\n\t\t\tdescribe: 'the System keychain',\n\t\t\tsteps: [\n\t\t\t\t[\n\t\t\t\t\t'sudo',\n\t\t\t\t\t[\n\t\t\t\t\t\t'security',\n\t\t\t\t\t\t'add-trusted-cert',\n\t\t\t\t\t\t'-d',\n\t\t\t\t\t\t'-r',\n\t\t\t\t\t\t'trustRoot',\n\t\t\t\t\t\t'-k',\n\t\t\t\t\t\t'/Library/Keychains/System.keychain',\n\t\t\t\t\t\tcertificate,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t],\n\t\t};\n\t}\n\n\tif (platform === 'linux') {\n\t\t// Debian and Fedora keep anchors in different places and run different\n\t\t// updaters. Whichever directory exists is the one this system uses.\n\t\tconst debian = existsSync('/usr/local/share/ca-certificates');\n\n\t\treturn {\n\t\t\tdescribe: debian ? 'the system CA bundle' : 'the system trust anchors',\n\t\t\tsteps: debian\n\t\t\t\t? [\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t'sudo',\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'cp',\n\t\t\t\t\t\t\t\tcertificate,\n\t\t\t\t\t\t\t\t'/usr/local/share/ca-certificates/gkm-local-ca.crt',\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t],\n\t\t\t\t\t\t['sudo', ['update-ca-certificates']],\n\t\t\t\t\t]\n\t\t\t\t: [\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t'sudo',\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'cp',\n\t\t\t\t\t\t\t\tcertificate,\n\t\t\t\t\t\t\t\t'/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt',\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t],\n\t\t\t\t\t\t['sudo', ['update-ca-trust']],\n\t\t\t\t\t],\n\t\t};\n\t}\n\n\tthrow new UnsupportedPlatform(platform, certificate);\n}\n\n/**\n * Trust the certificate authority this project's edge issues from.\n *\n * Idempotent in effect: installing a root that is already trusted replaces the\n * same entry, so running it twice is not an error.\n */\nexport async function trustCommand(options: TrustOptions = {}): Promise<void> {\n\tconst { workspace } = await loadWorkspaceConfig();\n\tconst certificate = join(workspace.root, LOCAL_CA_PATH);\n\n\tif (!existsSync(certificate)) {\n\t\tthrow new NoLocalAuthority(certificate);\n\t}\n\n\tconst { describe, steps } = installation(process.platform, certificate);\n\n\tlogger.log(`\\n🔐 Trusting the local authority in ${describe}`);\n\tlogger.log(` ${certificate}\\n`);\n\n\tif (options.dryRun) {\n\t\tfor (const [command, args] of steps) {\n\t\t\tlogger.log(` ${command} ${args.join(' ')}`);\n\t\t}\n\t\treturn;\n\t}\n\n\tlogger.log(' This needs an admin password.\\n');\n\n\tfor (const [command, args] of steps) {\n\t\t// Inherited stdio, because sudo prompts on the terminal and a captured\n\t\t// prompt is a command that appears to hang.\n\t\tawait run(command, args, { stdio: 'inherit' } as never);\n\t}\n\n\tlogger.log('\\n✅ Local https addresses are trusted. Restart your browser.\\n');\n}\n\n/**\n * Trust the authority if this project wants that and this machine lacks it.\n *\n * The decision, in order: what the project configured, then what a person\n * previously answered, then a prompt. Never a silent `sudo` — that is the one\n * thing a setup command should not do on somebody's behalf.\n *\n * Silent and free when the machine already trusts it, which is what makes it\n * acceptable to call on every `gkm setup`.\n */\nexport async function ensureTrusted(\n\troot: string,\n\turl: string,\n\toptions: { configured?: boolean; assumeYes?: boolean } = {},\n): Promise<void> {\n\tif (options.configured === false) return;\n\tif (await isTrusted(url)) return;\n\n\tif (options.configured !== true && !options.assumeYes) {\n\t\t// Nothing to answer a prompt in CI, a hook, or a piped shell — and a\n\t\t// setup that stops there waiting is worse than one that leaves the\n\t\t// authority untrusted. Not recorded as declined, because nobody\n\t\t// declined: the next interactive run should still ask.\n\t\tif (!process.stdin.isTTY) {\n\t\t\tlogger.log(\n\t\t\t\t' Local https addresses are not trusted here. Run \"gkm trust\", ' +\n\t\t\t\t\t'or set services.trustLocalCa.',\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// A recorded \"no\" is why this asks once rather than every start. There is\n\t\t// no recorded \"yes\": trusting is checked directly above, so a yes that\n\t\t// worked answers itself and a yes that did not should ask again.\n\t\tif (await declined(root)) return;\n\n\t\tconst { install } = await prompts({\n\t\t\ttype: 'confirm',\n\t\t\tname: 'install',\n\t\t\tmessage:\n\t\t\t\t\"Trust this project's local certificate authority, so a browser \" +\n\t\t\t\t'accepts its https addresses? (needs sudo)',\n\t\t\tinitial: false,\n\t\t});\n\n\t\tif (!install) {\n\t\t\tawait recordDeclined(root);\n\t\t\tlogger.log(\n\t\t\t\t' Skipped. Run \"gkm trust\" later, or set services.trustLocalCa.',\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tawait trustCommand();\n}\n\n/** Where a declined answer is remembered, so setup asks once and not again. */\nconst TRUST_STATE = '.gkm/trust.json';\n\nasync function declined(root: string): Promise<boolean> {\n\ttry {\n\t\tconst raw = await readFile(join(root, TRUST_STATE), 'utf-8');\n\n\t\treturn (JSON.parse(raw) as { declined?: boolean }).declined === true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function recordDeclined(root: string): Promise<void> {\n\tconst path = join(root, TRUST_STATE);\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, `${JSON.stringify({ declined: true }, null, 2)}\\n`);\n}\n\n/**\n * Whether this machine already trusts the local authority.\n *\n * Asked by connecting rather than by reading a keychain: what matters is\n * whether a client with no special configuration verifies the edge, and that is\n * one platform-neutral question instead of three platform-specific ones. It is\n * also the exact thing a browser is about to do.\n *\n * A child process, because `NODE_EXTRA_CA_CERTS` is read once at startup and\n * this process almost certainly has it set — asking in-process would answer\n * \"yes\" for everyone.\n */\nexport async function isTrusted(url: string): Promise<boolean> {\n\tconst { NODE_EXTRA_CA_CERTS: _ignored, ...env } = process.env;\n\n\ttry {\n\t\tawait run(\n\t\t\tprocess.execPath,\n\t\t\t['-e', `fetch(${JSON.stringify(url)}).then(()=>0,()=>process.exit(1))`],\n\t\t\t{ env, timeout: 15_000 },\n\t\t);\n\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Copy the root somewhere a tool that wants a file can read it.\n *\n * For anything that takes a CA bundle rather than reading the system store —\n * another language's HTTP client, a container, a CI job.\n */\nexport async function exportLocalAuthority(to: string): Promise<void> {\n\tconst { workspace } = await loadWorkspaceConfig();\n\tconst certificate = join(workspace.root, LOCAL_CA_PATH);\n\n\tif (!existsSync(certificate)) throw new NoLocalAuthority(certificate);\n\n\tawait copyFile(certificate, to);\n}\n\n/** The edge has not run, so there is no authority to trust yet. */\nexport class NoLocalAuthority extends Error {\n\tconstructor(readonly path: string) {\n\t\tsuper(\n\t\t\t`No local certificate authority at ${path}. It is generated the first ` +\n\t\t\t\t`time the edge starts, so run \"gkm setup\" (or \"gkm dev\") once — and ` +\n\t\t\t\t`if this project declares no file server, nothing needs one.`,\n\t\t);\n\t\tthis.name = 'NoLocalAuthority';\n\t}\n}\n\n/** A platform with no trust store this knows how to write to. */\nexport class UnsupportedPlatform extends Error {\n\tconstructor(\n\t\treadonly platform: string,\n\t\treadonly certificate: string,\n\t) {\n\t\tsuper(\n\t\t\t`gkm does not know how to install a certificate authority on ` +\n\t\t\t\t`${platform}. The root is at ${certificate} — add it to the trust ` +\n\t\t\t\t`store by hand, or point a client at it with NODE_EXTRA_CA_CERTS.`,\n\t\t);\n\t\tthis.name = 'UnsupportedPlatform';\n\t}\n}\n","import prompts from 'prompts';\nimport { loadWorkspaceConfig } from '../config.js';\nimport {\n\tderivedContainers,\n\treconcileWorkspace,\n} from '../reconcile/workspace.js';\nimport {\n\tcreateStageSecrets,\n\tgenerateConnectionUrls,\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 { ensureTrusted } from '../trust/index.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\tconst containers = await derivedContainers(workspace, stage);\n\n\tif (isMultiApp && containers.includes('postgres')) {\n\t\tawait writeDockerEnvFromSecrets(secrets, workspace.root);\n\t\tlogger.log('📄 Generated docker/.env with database passwords');\n\t}\n\n\t// 4. Reconcile the local target: derive containers, allocate ports, start.\n\tif (!options.skipDocker) {\n\t\tlogger.log('');\n\n\t\tawait reconcileLocal(workspace, stage, options);\n\t}\n\n\t// Print summary\n\tprintSummary(workspace, stage);\n}\n\n/**\n * Converge this machine on what the manifest declares.\n *\n * Says what it did in one line and stays silent when it did nothing, because\n * reconciling on every start is only tolerable if the converged case is both\n * free and quiet.\n */\nasync function reconcileLocal(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n\toptions: SetupOptions,\n): Promise<void> {\n\tconst result = await reconcileWorkspace(workspace, { stage });\n\n\tif (result.plan.containers.length === 0) {\n\t\tlogger.log('🐳 No containers declared');\n\t\treturn;\n\t}\n\n\tif (!result.changed) {\n\t\tlogger.log('🐳 Services already up to date');\n\t\treturn;\n\t}\n\n\tlogger.log(`🐳 Services: ${result.plan.containers.join(', ')}`);\n\tfor (const [container, address] of Object.entries(result.addresses)) {\n\t\tlogger.log(` ${container}: ${address}`);\n\t}\n\n\t// Only where something actually answers on https. A project with no edge has\n\t// no authority to trust and should never be asked about one.\n\tconst served = result.plan.resources.find((r) => r.kind === 'file-server');\n\tconst url = served ? result.env[served.envKey] : undefined;\n\n\tif (url) {\n\t\tawait ensureTrusted(workspace.root, url, {\n\t\t\t...(workspace.services.trustLocalCa === undefined\n\t\t\t\t? {}\n\t\t\t\t: { configured: workspace.services.trustLocalCa }),\n\t\t\t...(options.yes ? { assumeYes: true } : {}),\n\t\t});\n\t}\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(\n\t\t\t\tsecrets,\n\t\t\t\tworkspace,\n\t\t\t\tawait derivedContainers(workspace, stage),\n\t\t\t);\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 */\n/**\n * Which containers hold a credential worth generating.\n *\n * Reconcile's container names, mapped to the service a credential is stored\n * under. `redis-http` is the Upstash-protocol proxy in front of Redis and holds\n * no credential of its own — its token is derived — so it maps to nothing, and\n * `caddy` is an edge with nothing to log into.\n */\nfunction credentialedServices(\n\tcontainers: readonly string[],\n): ComposeServiceName[] {\n\tconst byContainer: Readonly<Record<string, ComposeServiceName>> = {\n\t\tpostgres: 'postgres',\n\t\tredis: 'redis',\n\t\tminio: 'minio',\n\t\tmailpit: 'mailpit',\n\t\tlocalstack: 'localstack',\n\t\trabbitmq: 'rabbitmq',\n\t};\n\n\treturn [\n\t\t...new Set(\n\t\t\tcontainers\n\t\t\t\t.map((container) => byContainer[container])\n\t\t\t\t.filter((name): name is ComposeServiceName => Boolean(name)),\n\t\t),\n\t];\n}\n\nexport function reconcileSecrets(\n\tsecrets: StageSecrets,\n\tworkspace: NormalizedWorkspace,\n\tcontainers: readonly string[] = [],\n): StageSecrets | null {\n\tlet changed = false;\n\tlet result = { ...secrets };\n\n\t// Reconcile service credentials: add missing services\n\tfor (const name of credentialedServices(containers)) {\n\t\tif (!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 custom secrets for multi-app workspaces\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\tif (isMultiApp) {\n\t\tconst expected = generateFullstackCustomSecrets(workspace, containers);\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\tcontainers: readonly string[] = [],\n): StageSecrets {\n\t// Which services need a credential is which containers exist, and that is\n\t// the manifest's answer. It used to be a boolean per service read from\n\t// config, which generated a Postgres password for a project with no\n\t// database and none for a project that declared one and set no flag.\n\tconst serviceNames = credentialedServices(containers);\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, containers);\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(\n\t\tstage,\n\t\tworkspace,\n\t\tawait derivedContainers(workspace, stage),\n\t);\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(\n\t\tstage,\n\t\tworkspace,\n\t\tawait derivedContainers(workspace, stage),\n\t);\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
|
+
{"version":3,"file":"setup-C48C9hto.cjs","names":["logger","execFile","platform: NodeJS.Platform","certificate: string","options: TrustOptions","LOCAL_CA_PATH","root: string","url: string","options: { configured?: boolean; assumeYes?: boolean }","path: string","platform: string","options: SetupOptions","loadedConfig: LoadedConfig","workspace: NormalizedWorkspace","stage: string","containers: readonly string[]","byContainer: Readonly<Record<string, ComposeServiceName>>","secrets: StageSecrets","missing: Record<string, string>","cwd: string"],"sources":["../src/trust/index.ts","../src/setup/index.ts"],"sourcesContent":["/**\n * Installing the local edge's certificate authority into the OS trust store.\n *\n * `NODE_EXTRA_CA_CERTS` covers everything gkm starts — the dev server, the test\n * suite, anything under `gkm exec` — because reconcile injects it. A browser\n * reads none of that: it asks the operating system, so until the root is in the\n * system store a local `https://` address is \"not secure\", which is true and\n * unhelpful.\n *\n * A separate command rather than part of `gkm setup`, for one reason: this\n * needs `sudo`, and a setup that silently escalates on every run is a worse\n * trade than one that tells you what to type. Caddy's own `caddy trust` draws\n * the line in the same place.\n */\n\nimport { execFile } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport prompts from 'prompts';\nimport { loadWorkspaceConfig } from '../config.js';\nimport { LOCAL_CA_PATH } from '../reconcile/index.js';\n\nconst logger = console;\n\nconst run = promisify(execFile);\n\nexport interface TrustOptions {\n\t/** Print the commands rather than running them. */\n\tdryRun?: boolean;\n}\n\n/**\n * How each platform is told about a new authority.\n *\n * Separate rather than shelled through one script because the failure modes\n * differ and the message should name the real one: macOS refuses without an\n * admin password, Debian needs the file in a specific directory before its\n * updater will look at it.\n */\nfunction installation(\n\tplatform: NodeJS.Platform,\n\tcertificate: string,\n): { describe: string; steps: [string, string[]][] } {\n\tif (platform === 'darwin') {\n\t\treturn {\n\t\t\tdescribe: 'the System keychain',\n\t\t\tsteps: [\n\t\t\t\t[\n\t\t\t\t\t'sudo',\n\t\t\t\t\t[\n\t\t\t\t\t\t'security',\n\t\t\t\t\t\t'add-trusted-cert',\n\t\t\t\t\t\t'-d',\n\t\t\t\t\t\t'-r',\n\t\t\t\t\t\t'trustRoot',\n\t\t\t\t\t\t'-k',\n\t\t\t\t\t\t'/Library/Keychains/System.keychain',\n\t\t\t\t\t\tcertificate,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t],\n\t\t};\n\t}\n\n\tif (platform === 'linux') {\n\t\t// Debian and Fedora keep anchors in different places and run different\n\t\t// updaters. Whichever directory exists is the one this system uses.\n\t\tconst debian = existsSync('/usr/local/share/ca-certificates');\n\n\t\treturn {\n\t\t\tdescribe: debian ? 'the system CA bundle' : 'the system trust anchors',\n\t\t\tsteps: debian\n\t\t\t\t? [\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t'sudo',\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'cp',\n\t\t\t\t\t\t\t\tcertificate,\n\t\t\t\t\t\t\t\t'/usr/local/share/ca-certificates/gkm-local-ca.crt',\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t],\n\t\t\t\t\t\t['sudo', ['update-ca-certificates']],\n\t\t\t\t\t]\n\t\t\t\t: [\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t'sudo',\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\t'cp',\n\t\t\t\t\t\t\t\tcertificate,\n\t\t\t\t\t\t\t\t'/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt',\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t],\n\t\t\t\t\t\t['sudo', ['update-ca-trust']],\n\t\t\t\t\t],\n\t\t};\n\t}\n\n\tthrow new UnsupportedPlatform(platform, certificate);\n}\n\n/**\n * Trust the certificate authority this project's edge issues from.\n *\n * Idempotent in effect: installing a root that is already trusted replaces the\n * same entry, so running it twice is not an error.\n */\nexport async function trustCommand(options: TrustOptions = {}): Promise<void> {\n\tconst { workspace } = await loadWorkspaceConfig();\n\tconst certificate = join(workspace.root, LOCAL_CA_PATH);\n\n\tif (!existsSync(certificate)) {\n\t\tthrow new NoLocalAuthority(certificate);\n\t}\n\n\tconst { describe, steps } = installation(process.platform, certificate);\n\n\tlogger.log(`\\n🔐 Trusting the local authority in ${describe}`);\n\tlogger.log(` ${certificate}\\n`);\n\n\tif (options.dryRun) {\n\t\tfor (const [command, args] of steps) {\n\t\t\tlogger.log(` ${command} ${args.join(' ')}`);\n\t\t}\n\t\treturn;\n\t}\n\n\tlogger.log(' This needs an admin password.\\n');\n\n\tfor (const [command, args] of steps) {\n\t\t// Inherited stdio, because sudo prompts on the terminal and a captured\n\t\t// prompt is a command that appears to hang.\n\t\tawait run(command, args, { stdio: 'inherit' } as never);\n\t}\n\n\tlogger.log('\\n✅ Local https addresses are trusted. Restart your browser.\\n');\n}\n\n/**\n * Trust the authority if this project wants that and this machine lacks it.\n *\n * The decision, in order: what the project configured, then what a person\n * previously answered, then a prompt. Never a silent `sudo` — that is the one\n * thing a setup command should not do on somebody's behalf.\n *\n * Silent and free when the machine already trusts it, which is what makes it\n * acceptable to call on every `gkm setup`.\n */\nexport async function ensureTrusted(\n\troot: string,\n\turl: string,\n\toptions: { configured?: boolean; assumeYes?: boolean } = {},\n): Promise<void> {\n\tif (options.configured === false) return;\n\tif (await isTrusted(url)) return;\n\n\tif (options.configured !== true && !options.assumeYes) {\n\t\t// Nothing to answer a prompt in CI, a hook, or a piped shell — and a\n\t\t// setup that stops there waiting is worse than one that leaves the\n\t\t// authority untrusted. Not recorded as declined, because nobody\n\t\t// declined: the next interactive run should still ask.\n\t\tif (!process.stdin.isTTY) {\n\t\t\tlogger.log(\n\t\t\t\t' Local https addresses are not trusted here. Run \"gkm trust\", ' +\n\t\t\t\t\t'or set services.trustLocalCa.',\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// A recorded \"no\" is why this asks once rather than every start. There is\n\t\t// no recorded \"yes\": trusting is checked directly above, so a yes that\n\t\t// worked answers itself and a yes that did not should ask again.\n\t\tif (await declined(root)) return;\n\n\t\tconst { install } = await prompts({\n\t\t\ttype: 'confirm',\n\t\t\tname: 'install',\n\t\t\tmessage:\n\t\t\t\t\"Trust this project's local certificate authority, so a browser \" +\n\t\t\t\t'accepts its https addresses? (needs sudo)',\n\t\t\tinitial: false,\n\t\t});\n\n\t\tif (!install) {\n\t\t\tawait recordDeclined(root);\n\t\t\tlogger.log(\n\t\t\t\t' Skipped. Run \"gkm trust\" later, or set services.trustLocalCa.',\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tawait trustCommand();\n}\n\n/** Where a declined answer is remembered, so setup asks once and not again. */\nconst TRUST_STATE = '.gkm/trust.json';\n\nasync function declined(root: string): Promise<boolean> {\n\ttry {\n\t\tconst raw = await readFile(join(root, TRUST_STATE), 'utf-8');\n\n\t\treturn (JSON.parse(raw) as { declined?: boolean }).declined === true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nasync function recordDeclined(root: string): Promise<void> {\n\tconst path = join(root, TRUST_STATE);\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, `${JSON.stringify({ declined: true }, null, 2)}\\n`);\n}\n\n/**\n * Whether this machine already trusts the local authority.\n *\n * Asked by connecting rather than by reading a keychain: what matters is\n * whether a client with no special configuration verifies the edge, and that is\n * one platform-neutral question instead of three platform-specific ones. It is\n * also the exact thing a browser is about to do.\n *\n * A child process, because `NODE_EXTRA_CA_CERTS` is read once at startup and\n * this process almost certainly has it set — asking in-process would answer\n * \"yes\" for everyone.\n */\nexport async function isTrusted(url: string): Promise<boolean> {\n\tconst { NODE_EXTRA_CA_CERTS: _ignored, ...env } = process.env;\n\n\ttry {\n\t\tawait run(\n\t\t\tprocess.execPath,\n\t\t\t['-e', `fetch(${JSON.stringify(url)}).then(()=>0,()=>process.exit(1))`],\n\t\t\t{ env, timeout: 15_000 },\n\t\t);\n\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Copy the root somewhere a tool that wants a file can read it.\n *\n * For anything that takes a CA bundle rather than reading the system store —\n * another language's HTTP client, a container, a CI job.\n */\nexport async function exportLocalAuthority(to: string): Promise<void> {\n\tconst { workspace } = await loadWorkspaceConfig();\n\tconst certificate = join(workspace.root, LOCAL_CA_PATH);\n\n\tif (!existsSync(certificate)) throw new NoLocalAuthority(certificate);\n\n\tawait copyFile(certificate, to);\n}\n\n/** The edge has not run, so there is no authority to trust yet. */\nexport class NoLocalAuthority extends Error {\n\tconstructor(readonly path: string) {\n\t\tsuper(\n\t\t\t`No local certificate authority at ${path}. It is generated the first ` +\n\t\t\t\t`time the edge starts, so run \"gkm setup\" (or \"gkm dev\") once — and ` +\n\t\t\t\t`if this project declares no file server, nothing needs one.`,\n\t\t);\n\t\tthis.name = 'NoLocalAuthority';\n\t}\n}\n\n/** A platform with no trust store this knows how to write to. */\nexport class UnsupportedPlatform extends Error {\n\tconstructor(\n\t\treadonly platform: string,\n\t\treadonly certificate: string,\n\t) {\n\t\tsuper(\n\t\t\t`gkm does not know how to install a certificate authority on ` +\n\t\t\t\t`${platform}. The root is at ${certificate} — add it to the trust ` +\n\t\t\t\t`store by hand, or point a client at it with NODE_EXTRA_CA_CERTS.`,\n\t\t);\n\t\tthis.name = 'UnsupportedPlatform';\n\t}\n}\n","import prompts from 'prompts';\nimport { loadWorkspaceConfig } from '../config.js';\nimport {\n\tderivedContainers,\n\treconcileWorkspace,\n} from '../reconcile/workspace.js';\nimport {\n\tcreateStageSecrets,\n\tgenerateConnectionUrls,\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 { ensureTrusted } from '../trust/index.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\tconst containers = await derivedContainers(workspace, stage);\n\n\tif (isMultiApp && containers.includes('postgres')) {\n\t\tawait writeDockerEnvFromSecrets(secrets, workspace.root);\n\t\tlogger.log('📄 Generated docker/.env with database passwords');\n\t}\n\n\t// 4. Reconcile the local target: derive containers, allocate ports, start.\n\tif (!options.skipDocker) {\n\t\tlogger.log('');\n\n\t\tawait reconcileLocal(workspace, stage, options);\n\t}\n\n\t// Print summary\n\tprintSummary(workspace, stage);\n}\n\n/**\n * Converge this machine on what the manifest declares.\n *\n * Says what it did in one line and stays silent when it did nothing, because\n * reconciling on every start is only tolerable if the converged case is both\n * free and quiet.\n */\nasync function reconcileLocal(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n\toptions: SetupOptions,\n): Promise<void> {\n\tconst result = await reconcileWorkspace(workspace, { stage });\n\n\tif (result.plan.containers.length === 0) {\n\t\tlogger.log('🐳 No containers declared');\n\t\treturn;\n\t}\n\n\tif (!result.changed) {\n\t\tlogger.log('🐳 Services already up to date');\n\t\treturn;\n\t}\n\n\tlogger.log(`🐳 Services: ${result.plan.containers.join(', ')}`);\n\tfor (const [container, address] of Object.entries(result.addresses)) {\n\t\tlogger.log(` ${container}: ${address}`);\n\t}\n\n\t// Only where something actually answers on https. A project with no edge has\n\t// no authority to trust and should never be asked about one.\n\tconst served = result.plan.resources.find((r) => r.kind === 'file-server');\n\tconst url = served ? result.env[served.envKey] : undefined;\n\n\tif (url) {\n\t\tawait ensureTrusted(workspace.root, url, {\n\t\t\t...(workspace.services.trustLocalCa === undefined\n\t\t\t\t? {}\n\t\t\t\t: { configured: workspace.services.trustLocalCa }),\n\t\t\t...(options.yes ? { assumeYes: true } : {}),\n\t\t});\n\t}\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(\n\t\t\t\tsecrets,\n\t\t\t\tworkspace,\n\t\t\t\tawait derivedContainers(workspace, stage),\n\t\t\t);\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 */\n/**\n * Which containers hold a credential worth generating.\n *\n * Reconcile's container names, mapped to the service a credential is stored\n * under. `redis-http` is the Upstash-protocol proxy in front of Redis and holds\n * no credential of its own — its token is derived — so it maps to nothing, and\n * `caddy` is an edge with nothing to log into.\n */\nfunction credentialedServices(\n\tcontainers: readonly string[],\n): ComposeServiceName[] {\n\tconst byContainer: Readonly<Record<string, ComposeServiceName>> = {\n\t\tpostgres: 'postgres',\n\t\tredis: 'redis',\n\t\tminio: 'minio',\n\t\tmailpit: 'mailpit',\n\t\tlocalstack: 'localstack',\n\t\trabbitmq: 'rabbitmq',\n\t};\n\n\treturn [\n\t\t...new Set(\n\t\t\tcontainers\n\t\t\t\t.map((container) => byContainer[container])\n\t\t\t\t.filter((name): name is ComposeServiceName => Boolean(name)),\n\t\t),\n\t];\n}\n\nexport function reconcileSecrets(\n\tsecrets: StageSecrets,\n\tworkspace: NormalizedWorkspace,\n\tcontainers: readonly string[] = [],\n): StageSecrets | null {\n\tlet changed = false;\n\tlet result = { ...secrets };\n\n\t// Reconcile service credentials: add missing services\n\tfor (const name of credentialedServices(containers)) {\n\t\tif (!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 custom secrets for multi-app workspaces\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\tif (isMultiApp) {\n\t\tconst expected = generateFullstackCustomSecrets(workspace, containers);\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\tcontainers: readonly string[] = [],\n): StageSecrets {\n\t// Which services need a credential is which containers exist, and that is\n\t// the manifest's answer. It used to be a boolean per service read from\n\t// config, which generated a Postgres password for a project with no\n\t// database and none for a project that declared one and set no flag.\n\tconst serviceNames = credentialedServices(containers);\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, containers);\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(\n\t\tstage,\n\t\tworkspace,\n\t\tawait derivedContainers(workspace, stage),\n\t);\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(\n\t\tstage,\n\t\tworkspace,\n\t\tawait derivedContainers(workspace, stage),\n\t);\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,11 +1,11 @@
|
|
|
1
1
|
import "./discover-CAlFgYec.mjs";
|
|
2
|
-
import "./config-
|
|
2
|
+
import "./config-CzWnfcXM.mjs";
|
|
3
3
|
import "./workspace-7FqprxuJ.mjs";
|
|
4
|
-
import "./credentials-
|
|
4
|
+
import "./credentials-BZcp3CGx.mjs";
|
|
5
5
|
import "./generator-VE2JrFlY.mjs";
|
|
6
6
|
import "./storage-Bi24WfoW.mjs";
|
|
7
7
|
import "./fullstack-secrets-D1o9-G5l.mjs";
|
|
8
8
|
import "./sync-CmEuNZsB.mjs";
|
|
9
|
-
import { createFreshWorkspaceSecrets, ensureStageSecrets, reconcileSecrets, setupCommand } from "./setup-
|
|
9
|
+
import { createFreshWorkspaceSecrets, ensureStageSecrets, reconcileSecrets, setupCommand } from "./setup-Bc73t0ve.mjs";
|
|
10
10
|
|
|
11
11
|
export { ensureStageSecrets };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
require('./discover-DTmFeloL.cjs');
|
|
2
|
-
require('./config-
|
|
2
|
+
require('./config-BholeD-0.cjs');
|
|
3
3
|
require('./workspace-XvqO9jIv.cjs');
|
|
4
|
-
require('./credentials-
|
|
4
|
+
require('./credentials-DQmbRnr1.cjs');
|
|
5
5
|
require('./generator-CNzBw7g5.cjs');
|
|
6
6
|
require('./storage-BJmzYxt8.cjs');
|
|
7
7
|
require('./fullstack-secrets-C-GGohke.cjs');
|
|
8
8
|
require('./sync-SizaChmC.cjs');
|
|
9
|
-
const require_setup = require('./setup-
|
|
9
|
+
const require_setup = require('./setup-C48C9hto.cjs');
|
|
10
10
|
|
|
11
11
|
exports.ensureStageSecrets = require_setup.ensureStageSecrets;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require('./discover-DTmFeloL.cjs');
|
|
2
|
-
require('./config-
|
|
2
|
+
require('./config-BholeD-0.cjs');
|
|
3
3
|
require('./workspace-XvqO9jIv.cjs');
|
|
4
|
-
const require_credentials = require('./credentials-
|
|
4
|
+
const require_credentials = require('./credentials-DQmbRnr1.cjs');
|
|
5
5
|
require('./generator-CNzBw7g5.cjs');
|
|
6
6
|
require('./storage-BJmzYxt8.cjs');
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "./discover-CAlFgYec.mjs";
|
|
2
|
-
import "./config-
|
|
2
|
+
import "./config-CzWnfcXM.mjs";
|
|
3
3
|
import "./workspace-7FqprxuJ.mjs";
|
|
4
|
-
import { constructGlobs, derivedContainers, imagePins, reconcileWorkspace, usesConstructs } from "./credentials-
|
|
4
|
+
import { constructGlobs, derivedContainers, imagePins, reconcileWorkspace, usesConstructs } from "./credentials-BZcp3CGx.mjs";
|
|
5
5
|
import "./generator-VE2JrFlY.mjs";
|
|
6
6
|
import "./storage-Bi24WfoW.mjs";
|
|
7
7
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cli",
|
|
3
|
-
"version": "10.0.0-alpha.
|
|
3
|
+
"version": "10.0.0-alpha.3",
|
|
4
4
|
"description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -96,14 +96,14 @@
|
|
|
96
96
|
"prompts": "~2.4.2",
|
|
97
97
|
"tsx": "~4.20.3",
|
|
98
98
|
"yaml": "~2.8.2",
|
|
99
|
-
"@geekmidas/cache": "10.0.0-alpha.
|
|
100
|
-
"@geekmidas/
|
|
101
|
-
"@geekmidas/
|
|
102
|
-
"@geekmidas/
|
|
103
|
-
"@geekmidas/
|
|
104
|
-
"@geekmidas/
|
|
105
|
-
"@geekmidas/
|
|
106
|
-
"@geekmidas/
|
|
99
|
+
"@geekmidas/cache": "10.0.0-alpha.3",
|
|
100
|
+
"@geekmidas/constructs": "~10.0.0-alpha.3",
|
|
101
|
+
"@geekmidas/db": "10.0.0-alpha.3",
|
|
102
|
+
"@geekmidas/envkit": "~10.0.0-alpha.3",
|
|
103
|
+
"@geekmidas/errors": "~10.0.0-alpha.3",
|
|
104
|
+
"@geekmidas/logger": "~10.0.0-alpha.3",
|
|
105
|
+
"@geekmidas/manifest": "10.0.0-alpha.3",
|
|
106
|
+
"@geekmidas/schema": "~10.0.0-alpha.3"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
109
|
"@types/lodash.kebabcase": "^4.1.9",
|
|
@@ -113,10 +113,10 @@
|
|
|
113
113
|
"typescript": "^5.8.2",
|
|
114
114
|
"vitest": "^3.2.4",
|
|
115
115
|
"zod": "~4.1.13",
|
|
116
|
-
"@geekmidas/testkit": "10.0.0-alpha.
|
|
116
|
+
"@geekmidas/testkit": "10.0.0-alpha.3"
|
|
117
117
|
},
|
|
118
118
|
"peerDependencies": {
|
|
119
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
119
|
+
"@geekmidas/telescope": "~10.0.0-alpha.3"
|
|
120
120
|
},
|
|
121
121
|
"peerDependenciesMeta": {
|
|
122
122
|
"@geekmidas/telescope": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config-D8o6I_7Z.mjs","names":["FRAMEWORKS: Record<string, Framework>","id: string","manifest: ConstructManifest","apps: Record<string, NormalizedAppConfig>","name: string","app: NormalizedAppConfig","workspaceRoot: string","construct: string","expected: string","spec: AppSpec","kind: 'site' | 'rest-api'","workspace: NormalizedWorkspace","defaultTarget: DeployTarget","config: GkmConfig","configString: string","defaultAlias: string","cwd: string","loaded: LoadedConfig","loadedConfig: LoadedConfig","appName: string"],"sources":["../src/workspace/derive.ts","../src/config.ts"],"sourcesContent":["/**\n * The apps a manifest implies.\n *\n * `gkm.config.ts` used to carry an `apps` block naming every app: its type, its\n * path, its port, its framework, which other apps it depended on. Every one of\n * those facts was already declared. A `StaticSite('Web', { path: 'apps/web' })`\n * and an `apps.web = { type: 'web', path: 'apps/web', framework: 'vite',\n * dependencies: ['api'] }` are the same statement written twice, and only one of\n * them was checked against anything — so the copy in config was free to drift,\n * and a surface config had no entry for simply never deployed.\n *\n * So the list comes from the graph. A `site` is an app. A `rest-api` that named\n * an `app` is an app. A `rest-api` that did not is served by the surface that\n * named it as its authenticator, which is the same collapsing rule the deploy\n * already used, now stated once and read by everybody.\n *\n * What config still supplies is what no graph can answer: the backends a cache\n * and a mailer resolve to, the deploy endpoint, the stage's domain.\n */\n\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport {\n\ttype AppSpec,\n\ttype ConstructManifest,\n\tDEFAULT_APP_CODE,\n\tkebabCase,\n} from '@geekmidas/manifest';\nimport type {\n\tAppDomainConfig,\n\tDeployTarget,\n\tFramework,\n\tNormalizedAppConfig,\n\tNormalizedWorkspace,\n} from './types.js';\n\n/** Which frontend framework builds each site variant. */\nconst FRAMEWORKS: Record<string, Framework> = {\n\tstatic: 'vite',\n\tnext: 'nextjs',\n\ttanstack: 'tanstack-start',\n};\n\n/**\n * The app key a construct id becomes.\n *\n * The same kebab form every physical name is built from, so `Api` is `api` in\n * the config, `production-kitchen-sink-api` on Dokploy, and `API_URL` in an\n * environment — one id, three renderings, no second list.\n */\nexport function appKey(id: string): string {\n\treturn kebabCase(id);\n}\n\n/**\n * The app serving a surface: its own, always.\n *\n * Every surface gets a container. There is no arrangement in which one runs\n * inside another — two surfaces in a process share a filesystem, an\n * environment, and every credential either was granted, so an auth server\n * beside an API is one bug in the API away from being read by it.\n */\nexport function hostOf(\n\tmanifest: ConstructManifest,\n\tid: string,\n): string | undefined {\n\tconst declaration = manifest[id];\n\tif (!declaration || declaration.kind !== 'rest-api') return undefined;\n\n\treturn id;\n}\n\n/**\n * Local ports, assigned so that adding a site does not renumber the others.\n *\n * Backends first, then the site holding the base domain, then the rest\n * alphabetically. The ordering is arbitrary but it is *fixed*, which is the\n * property that matters: a port that moves when an unrelated app is added is a\n * port nobody can put in a bookmark. A construct that names its own port keeps\n * it, and is skipped here.\n */\nfunction assignPorts(apps: Record<string, NormalizedAppConfig>): void {\n\tconst rank = (\n\t\tname: string,\n\t\tapp: NormalizedAppConfig,\n\t): [number, number, string] => [\n\t\tapp.type === 'backend' ? 0 : app.type === 'web' ? 1 : 2,\n\t\tapp.root ? 0 : 1,\n\t\tname,\n\t];\n\n\tconst ordered = Object.entries(apps).sort(([aName, a], [bName, b]) => {\n\t\tconst [at, ar, an] = rank(aName, a);\n\t\tconst [bt, br, bn] = rank(bName, b);\n\t\treturn at - bt || ar - br || an.localeCompare(bn);\n\t});\n\n\tconst taken = new Set(\n\t\tordered.map(([, app]) => app.port).filter((p): p is number => !!p),\n\t);\n\n\tlet next = 3000;\n\tfor (const [, app] of ordered) {\n\t\tif (app.port) continue;\n\t\twhile (taken.has(next)) next += 1;\n\t\tapp.port = next;\n\t\ttaken.add(next);\n\t}\n}\n\n/**\n * Whichever site holds the base domain.\n *\n * The convention first — a site named `web` — because it is one people already\n * rely on. Then an explicit `root: true`. A project with one site needs neither.\n */\nfunction markRoot(apps: Record<string, NormalizedAppConfig>): void {\n\tconst sites = Object.entries(apps).filter(([, a]) => a.type === 'web');\n\tif (sites.length === 0) return;\n\tif (sites.some(([, a]) => a.root)) return;\n\n\tconst chosen =\n\t\tsites.find(([name]) => name === 'web') ??\n\t\t(sites.length === 1 ? sites[0] : undefined);\n\tif (chosen) chosen[1].root = true;\n}\n\n/**\n * What `app: true` means, and what the object form leaves out.\n *\n * Both fields follow from the construct's own id, so neither was worth making\n * someone write down:\n *\n * - `path` is `apps/<kebab-id>` where that directory exists, and the workspace\n * root otherwise. `Api` means `apps/api` in a monorepo and `.` in a\n * single-app project, and which of the two you are in is answerable by\n * looking.\n * - `code` is the conventional directories under `path`. A glob is worth\n * writing only when the code is somewhere else, which is the case the field\n * still exists for.\n *\n * A site takes no `code`: its build is its framework's, not ours.\n */\n/**\n * Where an app lives when it did not say.\n *\n * `apps/<kebab-id>`, which is what the id already said — or the project root\n * when there is no `apps/` at all, because then there is one app and it is the\n * project.\n *\n * In between, it refuses. A workspace *with* an `apps/` directory and no\n * `apps/<key>` in it is a construct naming a directory nobody wrote, and\n * quietly answering `.` there makes the app the whole repository: the build\n * filters turbo on the root `package.json` and builds the wrong thing, or\n * itself. A missing directory is a typo or an unconventional layout, and both\n * want saying out loud.\n */\nfunction conventionalPath(id: string, workspaceRoot: string): string {\n\tconst conventional = join('apps', appKey(id));\n\tif (existsSync(join(workspaceRoot, conventional))) return conventional;\n\n\t// No `apps/` at all: one app, and it is the project.\n\tif (!existsSync(join(workspaceRoot, 'apps'))) return '.';\n\n\tthrow new MisplacedApp(id, conventional);\n}\n\n/** A construct whose directory is not where its id says it is. */\nexport class MisplacedApp extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly expected: string,\n\t) {\n\t\tsuper(\n\t\t\t`\"${construct}\" has no directory at ${expected}.\\n` +\n\t\t\t\t`Create it, or say where it lives:\\n` +\n\t\t\t\t` new StaticSite('${construct}', { path: 'sites/${appKey(construct)}' })\\n` +\n\t\t\t\t` new RestApi('${construct}', { …, app: { path: 'services/${appKey(construct)}' } })`,\n\t\t);\n\t\tthis.name = 'MisplacedApp';\n\t}\n}\n\nexport function resolveAppSpec(\n\tid: string,\n\tspec: AppSpec,\n\tworkspaceRoot: string,\n\tkind: 'site' | 'rest-api',\n): AppSpec {\n\tconst path = spec.path ?? conventionalPath(id, workspaceRoot);\n\n\tconst givenAGlob =\n\t\tspec.code !== undefined ||\n\t\tspec.routes !== undefined ||\n\t\tspec.functions !== undefined ||\n\t\tspec.crons !== undefined ||\n\t\tspec.queues !== undefined ||\n\t\tspec.topics !== undefined ||\n\t\tspec.subscribers !== undefined;\n\n\treturn {\n\t\t...spec,\n\t\tpath,\n\t\t...(kind === 'rest-api' && !givenAGlob ? { code: DEFAULT_APP_CODE } : {}),\n\t};\n}\n\nexport function derivedApps(\n\tmanifest: ConstructManifest,\n\tworkspace: NormalizedWorkspace,\n): Record<string, NormalizedAppConfig> {\n\tconst apps: Record<string, NormalizedAppConfig> = {};\n\tconst defaultTarget: DeployTarget = workspace.deploy?.default ?? 'dokploy';\n\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (declaration.kind !== 'site' && declaration.kind !== 'rest-api')\n\t\t\tcontinue;\n\n\t\t// No opt-in to be had. A site is an app and so is a surface; `app` is an\n\t\t// override for a layout that differs, and having none is the ordinary\n\t\t// case rather than a surface with nowhere to run.\n\t\tconst spec = resolveAppSpec(\n\t\t\tid,\n\t\t\tdeclaration.app ?? {},\n\t\t\tworkspace.root,\n\t\t\tdeclaration.kind,\n\t\t);\n\n\t\tconst name = appKey(id);\n\t\t// A config entry of the same name still wins, so a workspace can override\n\t\t// one field without restating the app. Nothing in this repo needs to.\n\t\tconst configured = workspace.apps[name];\n\n\t\tapps[name] = {\n\t\t\t...configured,\n\t\t\ttype: declaration.kind === 'site' ? 'web' : 'backend',\n\t\t\tpath: spec.path,\n\t\t\tport: spec.port ?? configured?.port ?? 0,\n\t\t\tdependencies: [],\n\t\t\tresolvedDeployTarget: configured?.resolvedDeployTarget ?? defaultTarget,\n\t\t\t...(declaration.kind === 'site'\n\t\t\t\t? {\n\t\t\t\t\t\tframework: configured?.framework ?? FRAMEWORKS[declaration.variant],\n\t\t\t\t\t\t...(declaration.root ? { root: true } : {}),\n\t\t\t\t\t\t...(spec.config ? { config: spec.config } : {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t// One glob fans out to the six the build reads, because each generator\n\t\t\t// already inspects every export and keeps what it recognises. A\n\t\t\t// per-kind field still wins where one was given.\n\t\t\t...(spec.code !== undefined\n\t\t\t\t? {\n\t\t\t\t\t\troutes: spec.code,\n\t\t\t\t\t\tfunctions: spec.code,\n\t\t\t\t\t\tcrons: spec.code,\n\t\t\t\t\t\tqueues: spec.code,\n\t\t\t\t\t\ttopics: spec.code,\n\t\t\t\t\t\tsubscribers: spec.code,\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(spec.routes !== undefined ? { routes: spec.routes } : {}),\n\t\t\t...(spec.functions !== undefined ? { functions: spec.functions } : {}),\n\t\t\t...(spec.crons !== undefined ? { crons: spec.crons } : {}),\n\t\t\t...(spec.queues !== undefined ? { queues: spec.queues } : {}),\n\t\t\t...(spec.topics !== undefined ? { topics: spec.topics } : {}),\n\t\t\t...(spec.subscribers !== undefined\n\t\t\t\t? { subscribers: spec.subscribers }\n\t\t\t\t: {}),\n\t\t\t...(spec.envParser !== undefined ? { envParser: spec.envParser } : {}),\n\t\t\t...(spec.logger !== undefined ? { logger: spec.logger } : {}),\n\t\t\t...(spec.telescope !== undefined ? { telescope: spec.telescope } : {}),\n\t\t\t...(spec.studio !== undefined ? { studio: spec.studio } : {}),\n\t\t\t...(spec.openapi !== undefined ? { openapi: spec.openapi } : {}),\n\t\t\t...(spec.runtime !== undefined ? { runtime: spec.runtime } : {}),\n\t\t\t...(spec.env !== undefined ? { env: spec.env } : {}),\n\t\t\t...(spec.entry !== undefined ? { entry: spec.entry } : {}),\n\t\t\t...(configured?.domain\n\t\t\t\t? { domain: configured.domain as AppDomainConfig }\n\t\t\t\t: {}),\n\t\t} as NormalizedAppConfig;\n\t}\n\n\t// Build order, from the edges rather than from a hand-kept list. A site that\n\t// depends on a surface depends on the app that serves it — which for a\n\t// mounted auth server is its host, not a container that does not exist.\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (declaration.kind !== 'site' && declaration.kind !== 'rest-api')\n\t\t\tcontinue;\n\n\t\tconst app = apps[appKey(id)];\n\t\tif (!app) continue;\n\n\t\tconst edges =\n\t\t\tdeclaration.kind === 'site'\n\t\t\t\t? declaration.dependencies\n\t\t\t\t: (declaration.calls ?? []);\n\n\t\tconst names = new Set<string>();\n\t\tfor (const edge of edges) {\n\t\t\tif (edge.kind !== 'rest-api') continue;\n\t\t\tconst host = hostOf(manifest, edge.target);\n\t\t\tif (!host) continue;\n\t\t\tconst key = appKey(host);\n\t\t\tif (key !== appKey(id) && apps[key]) names.add(key);\n\t\t}\n\n\t\tapp.dependencies = [...names].sort();\n\t}\n\n\tmarkRoot(apps);\n\tassignPorts(apps);\n\n\t// Apps config declared that the graph knows nothing about — a `type: 'mobile'`\n\t// entry, or a hand-run process. Kept rather than dropped: config is still the\n\t// escape hatch for what nothing declares.\n\tfor (const [name, app] of Object.entries(workspace.apps)) {\n\t\tif (!apps[name]) apps[name] = app;\n\t}\n\n\treturn apps;\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join, parse } from 'node:path';\nimport { discover } from './reconcile/discover.js';\nimport type { GkmConfig } from './types.js';\nimport { derivedApps } from './workspace/derive.js';\nimport {\n\tallConstructGlobs,\n\tgetAppGkmConfig,\n\tisWorkspaceConfig,\n\ttype LoadedConfig,\n\ttype NormalizedAppConfig,\n\ttype NormalizedWorkspace,\n\tprocessConfig,\n\ttype WorkspaceConfig,\n} from './workspace/index.js';\n\nexport type { GkmConfig } from './types.js';\nexport type { LoadedConfig, WorkspaceConfig } from './workspace/index.js';\nexport { defineWorkspace } from './workspace/index.js';\n/**\n * Define GKM configuration with full TypeScript support.\n * This is an identity function that provides type safety and autocomplete.\n *\n * @example\n * ```ts\n * // gkm.config.ts\n * import { defineConfig } from '@geekmidas/cli/config';\n *\n * export default defineConfig({\n * routes: './src/endpoints/**\\/*.ts',\n * envParser: './src/config/env',\n * logger: './src/config/logger',\n * telescope: true,\n * });\n * ```\n */\nexport function defineConfig(config: GkmConfig): GkmConfig {\n\treturn config;\n}\n\nexport interface ParsedModuleConfig {\n\tpath: string;\n\timportPattern: string;\n}\n\n/**\n * Parse a module config string into path and import pattern.\n *\n * @param configString - Config string in format \"./path/to/module\" or \"./path/to/module#exportName\"\n * @param defaultAlias - The default alias name to use if no export name specified\n * @returns Object with path and import pattern\n *\n * @example\n * parseModuleConfig('./src/config/env', 'envParser')\n * // { path: './src/config/env', importPattern: 'envParser' }\n *\n * parseModuleConfig('./src/config/env#envParser', 'envParser')\n * // { path: './src/config/env', importPattern: '{ envParser }' }\n *\n * parseModuleConfig('./src/config/env#myEnv', 'envParser')\n * // { path: './src/config/env', importPattern: '{ myEnv as envParser }' }\n */\nexport function parseModuleConfig(\n\tconfigString: string,\n\tdefaultAlias: string,\n): ParsedModuleConfig {\n\tconst parts = configString.split('#');\n\tconst path = parts[0] ?? configString;\n\tconst exportName = parts[1];\n\tconst importPattern = !exportName\n\t\t? defaultAlias\n\t\t: exportName === defaultAlias\n\t\t\t? `{ ${defaultAlias} }`\n\t\t\t: `{ ${exportName} as ${defaultAlias} }`;\n\n\treturn { path, importPattern };\n}\n\nexport interface ConfigDiscoveryResult {\n\tconfigPath: string;\n\tworkspaceRoot: string;\n}\n\n/**\n * Find and return the path to the config file.\n *\n * Resolution order:\n * 1. GKM_CONFIG_PATH env var (set by workspace dev command)\n * 2. Walk up directory tree from cwd\n */\nfunction findConfigPath(cwd: string): ConfigDiscoveryResult {\n\tconst files = ['gkm.config.json', 'gkm.config.ts', 'gkm.config.js'];\n\n\t// Check GKM_CONFIG_PATH env var first (set by workspace dev command)\n\tconst envConfigPath = process.env.GKM_CONFIG_PATH;\n\tif (envConfigPath && existsSync(envConfigPath)) {\n\t\treturn {\n\t\t\tconfigPath: envConfigPath,\n\t\t\tworkspaceRoot: dirname(envConfigPath),\n\t\t};\n\t}\n\n\t// Walk up directory tree to find config\n\tlet currentDir = cwd;\n\tconst { root } = parse(currentDir);\n\n\twhile (currentDir !== root) {\n\t\tfor (const file of files) {\n\t\t\tconst configPath = join(currentDir, file);\n\t\t\tif (existsSync(configPath)) {\n\t\t\t\treturn {\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tworkspaceRoot: currentDir,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tcurrentDir = dirname(currentDir);\n\t}\n\n\tthrow new Error(\n\t\t'Configuration file not found. Please create gkm.config.json, gkm.config.ts, or gkm.config.js in the project root.',\n\t);\n}\n\n/**\n * Get app name from package.json in the given directory.\n * Handles scoped packages by extracting the name after the scope.\n *\n * @example\n * getAppNameFromCwd('/path/to/apps/api')\n * // package.json: { \"name\": \"@myorg/api\" }\n * // Returns: 'api'\n */\nexport function getAppNameFromCwd(cwd: string = process.cwd()): string | null {\n\tconst packageJsonPath = join(cwd, 'package.json');\n\n\tif (!existsSync(packageJsonPath)) {\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));\n\t\tconst name = packageJson.name as string | undefined;\n\n\t\tif (!name) {\n\t\t\treturn null;\n\t\t}\n\n\t\t// Handle scoped packages: @scope/name -> name\n\t\tif (name.startsWith('@') && name.includes('/')) {\n\t\t\treturn name.split('/')[1] ?? null;\n\t\t}\n\n\t\treturn name;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\ninterface RawConfigResult {\n\tconfig: GkmConfig | WorkspaceConfig;\n\tworkspaceRoot: string;\n}\n\n/**\n * Load raw configuration from file.\n */\nasync function loadRawConfig(cwd: string): Promise<RawConfigResult> {\n\tconst { configPath, workspaceRoot } = findConfigPath(cwd);\n\n\ttry {\n\t\tconst config = await import(configPath);\n\t\treturn {\n\t\t\tconfig: config.default,\n\t\t\tworkspaceRoot,\n\t\t};\n\t} catch (error) {\n\t\tthrow new Error(`Failed to load config: ${(error as Error).message}`);\n\t}\n}\n\n/**\n * Load configuration file (single-app format).\n * For backwards compatibility with existing code.\n *\n * @deprecated Use loadWorkspaceConfig for new code\n */\nexport async function loadConfig(\n\tcwd: string = process.cwd(),\n): Promise<GkmConfig> {\n\tconst { config } = await loadRawConfig(cwd);\n\n\t// If it's a workspace config, throw an error\n\tif (isWorkspaceConfig(config)) {\n\t\tthrow new Error(\n\t\t\t'Workspace configuration detected. Use loadWorkspaceConfig() instead.',\n\t\t);\n\t}\n\n\treturn config;\n}\n\n/**\n * Load configuration file and process it as a workspace.\n * Works with both single-app and workspace configurations.\n *\n * Single-app configs are automatically wrapped as a workspace with one app.\n *\n * @example\n * ```ts\n * const { type, workspace } = await loadWorkspaceConfig();\n *\n * if (type === 'workspace') {\n * console.log('Multi-app workspace:', workspace.apps);\n * } else {\n * console.log('Single app wrapped as workspace');\n * }\n * ```\n */\nexport async function loadWorkspaceConfig(\n\tcwd: string = process.cwd(),\n): Promise<LoadedConfig> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\treturn withDerivedApps(processConfig(config, workspaceRoot));\n}\n\n/**\n * The loaded config, with its apps read off the graph.\n *\n * Here rather than at each call site because every command needs the same\n * answer, and the ones that forgot to ask were exactly the bugs: a site that\n * never deployed, a surface that could not be its own process. `gkm dev`,\n * `gkm build`, `gkm docker` and `gkm deploy` now all see the same apps without\n * any of them knowing a derivation happened.\n *\n * Discovery imports the construct modules — declarations only, not endpoints —\n * which is the same import the reconciler and the build already do. A workspace\n * with no `constructs` glob discovers nothing and keeps whatever its config\n * said, so nothing that worked before changes.\n *\n * A construct file that throws is reported and skipped rather than taking the\n * command down: `gkm dev` failing to start because a half-written declaration\n * cannot be imported is a worse failure than starting with the app it does not\n * describe yet.\n */\nasync function withDerivedApps(loaded: LoadedConfig): Promise<LoadedConfig> {\n\tconst globs = allConstructGlobs(loaded.workspace);\n\tif (globs.length === 0) return loaded;\n\n\ttry {\n\t\tconst manifest = await discover({\n\t\t\tpatterns: globs,\n\t\t\tcwd: loaded.workspace.root,\n\t\t});\n\n\t\treturn {\n\t\t\t...loaded,\n\t\t\tmanifest,\n\t\t\tworkspace: {\n\t\t\t\t...loaded.workspace,\n\t\t\t\tapps: derivedApps(manifest, loaded.workspace),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tconsole.warn(\n\t\t\t`⚠️ Could not read constructs, so apps come from config alone: ${message}`,\n\t\t);\n\t\treturn loaded;\n\t}\n}\n\nexport interface AppConfigResult {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tgkmConfig: GkmConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n\tappRoot: string;\n}\n\n/**\n * The app a package directory refers to, and what the workspace calls it.\n *\n * A single-app config is wrapped as a one-app workspace keyed `api`, which is\n * almost never the package name. Matching on the key alone means an app like\n * `@geekmidas/example` resolves to nothing and silently loses everything that\n * comes from the workspace — including the URLs its constructs declare.\n */\nfunction resolveWorkspaceApp(\n\tloadedConfig: LoadedConfig,\n\tappName: string,\n): { key: string; app: NormalizedAppConfig } | undefined {\n\tconst apps = loadedConfig.workspace.apps;\n\n\tconst named = apps[appName];\n\tif (named) return { key: appName, app: named };\n\n\tconst names = Object.keys(apps);\n\tconst only = names[0];\n\tif (loadedConfig.type !== 'single' || names.length !== 1 || !only) {\n\t\treturn undefined;\n\t}\n\n\tconst app = apps[only];\n\n\treturn app ? { key: only, app } : undefined;\n}\n\n/**\n * Load app-specific configuration from workspace.\n * Uses the app name from package.json to find the correct app config.\n *\n * @example\n * ```ts\n * // From apps/api directory with package.json: { \"name\": \"@myorg/api\" }\n * const { app, workspace, workspaceRoot } = await loadAppConfig();\n * console.log(app.routes); // './src/endpoints/**\\/*.ts'\n * ```\n */\nexport async function loadAppConfig(\n\tcwd: string = process.cwd(),\n): Promise<AppConfigResult> {\n\tconst appName = getAppNameFromCwd(cwd);\n\n\tif (!appName) {\n\t\tthrow new Error(\n\t\t\t'Could not determine app name. Ensure package.json exists with a \"name\" field.',\n\t\t);\n\t}\n\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = await withDerivedApps(\n\t\tprocessConfig(config, workspaceRoot),\n\t);\n\n\tconst resolved = resolveWorkspaceApp(loadedConfig, appName);\n\n\tif (!resolved) {\n\t\tthrow new Error(\n\t\t\t`App \"${appName}\" not found in workspace config. Available apps: ${Object.keys(\n\t\t\t\tloadedConfig.workspace.apps,\n\t\t\t).join(', ')}. ` +\n\t\t\t\t`Ensure the package.json name matches the app key in gkm.config.ts.`,\n\t\t);\n\t}\n\n\tconst { key, app } = resolved;\n\n\t// Keyed by what the workspace calls the app, not by the package name — for a\n\t// single-app config those differ.\n\tconst gkmConfig = getAppGkmConfig(loadedConfig.workspace, key);\n\n\tif (!gkmConfig) {\n\t\tthrow new Error(\n\t\t\t`App \"${appName}\" is not a backend app and cannot be run with gkm dev.`,\n\t\t);\n\t}\n\n\treturn {\n\t\tappName,\n\t\tapp,\n\t\tgkmConfig,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t\tappRoot: join(workspaceRoot, app.path),\n\t};\n}\n\nexport interface WorkspaceAppInfo {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n}\n\n/**\n * Load workspace info for any app (frontend or backend).\n * Unlike loadAppConfig, this does NOT require the app to have a gkm config\n * (routes, entry, etc.), making it suitable for gkm exec/test from frontend apps.\n */\nexport async function loadWorkspaceAppInfo(\n\tcwd: string = process.cwd(),\n): Promise<WorkspaceAppInfo> {\n\tconst appName = getAppNameFromCwd(cwd);\n\n\tif (!appName) {\n\t\tthrow new Error(\n\t\t\t'Could not determine app name. Ensure package.json exists with a \"name\" field.',\n\t\t);\n\t}\n\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = await withDerivedApps(\n\t\tprocessConfig(config, workspaceRoot),\n\t);\n\n\tconst resolved = resolveWorkspaceApp(loadedConfig, appName);\n\n\tif (!resolved) {\n\t\tconst availableApps = Object.keys(loadedConfig.workspace.apps).join(', ');\n\t\tthrow new Error(\n\t\t\t`App \"${appName}\" not found in workspace config. Available apps: ${availableApps}. ` +\n\t\t\t\t`Ensure the package.json name matches the app key in gkm.config.ts.`,\n\t\t);\n\t}\n\n\treturn {\n\t\tappName,\n\t\tapp: resolved.app,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t};\n}\n"],"mappings":"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"}
|