@geekmidas/cli 10.0.0-alpha.2 → 10.0.0-alpha.21
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/README.md +25 -48
- package/bin/adjacent-tsconfig.mjs +59 -0
- package/bin/gkm.mjs +7 -1
- package/dist/{HostingerProvider-Cft7lEYr.mjs → HostingerProvider-BY26bWwB.mjs} +20 -21
- package/dist/HostingerProvider-BY26bWwB.mjs.map +1 -0
- package/dist/{HostingerProvider-h3EbEp-Z.cjs → HostingerProvider-BZFGYYVA.cjs} +21 -22
- package/dist/HostingerProvider-BZFGYYVA.cjs.map +1 -0
- package/dist/{LocalStateProvider-Db9SEjyH.cjs → LocalStateProvider-7HR6SS2t.cjs} +11 -6
- package/dist/LocalStateProvider-7HR6SS2t.cjs.map +1 -0
- package/dist/{LocalStateProvider-Bn6tS-oL.mjs → LocalStateProvider-D7QyJoLM.mjs} +10 -4
- package/dist/LocalStateProvider-D7QyJoLM.mjs.map +1 -0
- package/dist/{Route53Provider-RdpUDJQk.mjs → Route53Provider-Cvj40A8I.mjs} +12 -12
- package/dist/Route53Provider-Cvj40A8I.mjs.map +1 -0
- package/dist/{Route53Provider-di2fMM5N.cjs → Route53Provider-DVILBsvV.cjs} +20 -21
- package/dist/Route53Provider-DVILBsvV.cjs.map +1 -0
- package/dist/{SSMStateProvider-BRt2py-A.mjs → SSMStateProvider-B0Hu1MKZ.mjs} +13 -4
- package/dist/SSMStateProvider-B0Hu1MKZ.mjs.map +1 -0
- package/dist/{SSMStateProvider-DyX8s9HI.cjs → SSMStateProvider-VrQ-yeqd.cjs} +17 -9
- package/dist/SSMStateProvider-VrQ-yeqd.cjs.map +1 -0
- package/dist/{backup-provisioner-BrsHwYG0.cjs → backup-provisioner-BmhIO_Yr.cjs} +21 -19
- package/dist/backup-provisioner-BmhIO_Yr.cjs.map +1 -0
- package/dist/{backup-provisioner-Bgj5Vi_L.mjs → backup-provisioner-DIVCbfee.mjs} +10 -7
- package/dist/backup-provisioner-DIVCbfee.mjs.map +1 -0
- package/dist/{bundler-BY1OmV1e.cjs → bundler-mjqGAvu1.cjs} +10 -28
- package/dist/bundler-mjqGAvu1.cjs.map +1 -0
- package/dist/{bundler-Be8Zg4Wd.mjs → bundler-mwH7WOb1.mjs} +9 -26
- package/dist/bundler-mwH7WOb1.mjs.map +1 -0
- package/dist/{config-BholeD-0.cjs → config-DSXI480r.cjs} +80 -148
- package/dist/config-DSXI480r.cjs.map +1 -0
- package/dist/{config-CzWnfcXM.mjs → config-DdKkoUE1.mjs} +33 -88
- package/dist/config-DdKkoUE1.mjs.map +1 -0
- package/dist/config.cjs +4 -5
- package/dist/config.d.cts +14 -17
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +14 -17
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -5
- package/dist/{credentials-DQmbRnr1.cjs → credentials-BfmmB4WD.cjs} +1508 -1243
- package/dist/credentials-BfmmB4WD.cjs.map +1 -0
- package/dist/{credentials-BZcp3CGx.mjs → credentials-CD8ftUS6.mjs} +1374 -1114
- package/dist/credentials-CD8ftUS6.mjs.map +1 -0
- package/dist/deploy/sniffer-envkit-patch.cjs +18 -10
- package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.cts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.cts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.mts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.mts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.mjs +10 -2
- package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
- package/dist/deploy/sniffer-hooks.cjs +13 -7
- package/dist/deploy/sniffer-hooks.cjs.map +1 -1
- package/dist/deploy/sniffer-hooks.d.cts +1 -2
- package/dist/deploy/sniffer-hooks.d.cts.map +1 -1
- package/dist/deploy/sniffer-hooks.d.mts +1 -2
- package/dist/deploy/sniffer-hooks.d.mts.map +1 -1
- package/dist/deploy/sniffer-hooks.mjs +9 -2
- package/dist/deploy/sniffer-hooks.mjs.map +1 -1
- package/dist/deploy/sniffer-loader.cjs +16 -7
- package/dist/deploy/sniffer-loader.cjs.map +1 -1
- package/dist/deploy/sniffer-loader.d.cts +1 -1
- package/dist/deploy/sniffer-loader.d.mts +1 -1
- package/dist/deploy/sniffer-loader.mjs +13 -2
- package/dist/deploy/sniffer-loader.mjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.cjs +11 -11
- package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.d.cts +1 -1
- package/dist/deploy/sniffer-routes-worker.d.mts +1 -1
- package/dist/deploy/sniffer-routes-worker.mjs +2 -2
- package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
- package/dist/deploy/sniffer-worker.cjs +15 -6
- package/dist/deploy/sniffer-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-worker.d.cts +1 -3
- package/dist/deploy/sniffer-worker.d.cts.map +1 -1
- package/dist/deploy/sniffer-worker.d.mts +1 -3
- package/dist/deploy/sniffer-worker.d.mts.map +1 -1
- package/dist/deploy/sniffer-worker.mjs +15 -4
- package/dist/deploy/sniffer-worker.mjs.map +1 -1
- package/dist/{discover-DTmFeloL.cjs → discover-BERGFg8v.cjs} +81 -64
- package/dist/discover-BERGFg8v.cjs.map +1 -0
- package/dist/{discover-CAlFgYec.mjs → discover-CK-n3w6b.mjs} +35 -20
- package/dist/discover-CK-n3w6b.mjs.map +1 -0
- package/dist/{emit-CbcRolsa.cjs → emit-BdoPFjZf.cjs} +53 -36
- package/dist/emit-BdoPFjZf.cjs.map +1 -0
- package/dist/{emit-CiZS_5Ub.mjs → emit-Bzq7JsMP.mjs} +24 -6
- package/dist/emit-Bzq7JsMP.mjs.map +1 -0
- package/dist/{generator-VE2JrFlY.mjs → generator-LfZU7NlU.mjs} +8 -14
- package/dist/generator-LfZU7NlU.mjs.map +1 -0
- package/dist/{generator-CNzBw7g5.cjs → generator-Wdfp0jCS.cjs} +38 -45
- package/dist/generator-Wdfp0jCS.cjs.map +1 -0
- package/dist/github-Bho-C6Zg.cjs +216 -0
- package/dist/github-Bho-C6Zg.cjs.map +1 -0
- package/dist/github-De-yMwVt.mjs +216 -0
- package/dist/github-De-yMwVt.mjs.map +1 -0
- package/dist/{index-CHB4tSzX.d.cts → index-B4H4XF7A.d.mts} +4 -13
- package/dist/index-B4H4XF7A.d.mts.map +1 -0
- package/dist/{index-BThUzEIp.d.mts → index-RCUizGxL.d.cts} +4 -13
- package/dist/index-RCUizGxL.d.cts.map +1 -0
- package/dist/index.cjs +5108 -3381
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +5025 -3315
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-c9bpieI5.cjs → openapi-B8bu_OXN.cjs} +228 -188
- package/dist/openapi-B8bu_OXN.cjs.map +1 -0
- package/dist/{openapi-CY9xFkTH.mjs → openapi-DfjbhrsM.mjs} +172 -143
- package/dist/openapi-DfjbhrsM.mjs.map +1 -0
- package/dist/openapi.cjs +5 -6
- package/dist/openapi.d.cts +47 -14
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +47 -14
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +2 -6
- package/dist/reconcile/public.cjs +4 -4
- package/dist/reconcile/public.d.cts +8 -26
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +8 -26
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +3 -4
- package/dist/{fullstack-secrets-D1o9-G5l.mjs → reconcile-gb1lz1Dk.mjs} +43 -17
- package/dist/reconcile-gb1lz1Dk.mjs.map +1 -0
- package/dist/{fullstack-secrets-C-GGohke.cjs → reconcile-hx_vBvux.cjs} +63 -38
- package/dist/reconcile-hx_vBvux.cjs.map +1 -0
- package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
- package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
- package/dist/ssm-BKN1Cg4z.mjs +60 -0
- package/dist/ssm-BKN1Cg4z.mjs.map +1 -0
- package/dist/ssm-FxeSAt35.cjs +60 -0
- package/dist/ssm-FxeSAt35.cjs.map +1 -0
- package/dist/{storage-BJmzYxt8.cjs → storage-DgypfSD6.cjs} +65 -77
- package/dist/storage-DgypfSD6.cjs.map +1 -0
- package/dist/{storage-Bi24WfoW.mjs → storage-TP0x118M.mjs} +22 -15
- package/dist/storage-TP0x118M.mjs.map +1 -0
- package/dist/transfer-CXoyQZbB.cjs +151 -0
- package/dist/transfer-CXoyQZbB.cjs.map +1 -0
- package/dist/transfer-Gsxfy6m-.mjs +122 -0
- package/dist/transfer-Gsxfy6m-.mjs.map +1 -0
- package/dist/types-7R81hu0i.d.mts +6302 -0
- package/dist/types-7R81hu0i.d.mts.map +1 -0
- package/dist/types-N7c2O2m7.d.cts +6303 -0
- package/dist/types-N7c2O2m7.d.cts.map +1 -0
- package/dist/workspace/index.cjs +3 -3
- package/dist/workspace/index.d.cts +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +2 -3
- package/dist/workspace-Bu3xNCej.mjs +6563 -0
- package/dist/workspace-Bu3xNCej.mjs.map +1 -0
- package/dist/workspace-CRD9YinV.cjs +6741 -0
- package/dist/workspace-CRD9YinV.cjs.map +1 -0
- package/package.json +33 -43
- package/dist/CachedStateProvider-BLo7zv7S.cjs +0 -3
- package/dist/CachedStateProvider-BxoAm-B2.mjs +0 -3
- package/dist/CachedStateProvider-CqPdW7CB.cjs +0 -60
- package/dist/CachedStateProvider-CqPdW7CB.cjs.map +0 -1
- package/dist/CachedStateProvider-DTniehMP.mjs +0 -54
- package/dist/CachedStateProvider-DTniehMP.mjs.map +0 -1
- package/dist/HostingerProvider-Cft7lEYr.mjs.map +0 -1
- package/dist/HostingerProvider-h3EbEp-Z.cjs.map +0 -1
- package/dist/LocalStateProvider-Bn6tS-oL.mjs.map +0 -1
- package/dist/LocalStateProvider-Db9SEjyH.cjs.map +0 -1
- package/dist/Route53Provider-RdpUDJQk.mjs.map +0 -1
- package/dist/Route53Provider-di2fMM5N.cjs.map +0 -1
- package/dist/SSMStateProvider-BRt2py-A.mjs.map +0 -1
- package/dist/SSMStateProvider-DyX8s9HI.cjs.map +0 -1
- package/dist/backup-provisioner-Bgj5Vi_L.mjs.map +0 -1
- package/dist/backup-provisioner-BrsHwYG0.cjs.map +0 -1
- package/dist/bundler-BY1OmV1e.cjs.map +0 -1
- package/dist/bundler-Be8Zg4Wd.mjs.map +0 -1
- package/dist/chunk-Duj1WY3L.mjs +0 -7
- package/dist/config-BholeD-0.cjs.map +0 -1
- package/dist/config-CzWnfcXM.mjs.map +0 -1
- package/dist/credentials-B5zRHdAl.mjs +0 -141
- package/dist/credentials-B5zRHdAl.mjs.map +0 -1
- package/dist/credentials-BZcp3CGx.mjs.map +0 -1
- package/dist/credentials-BapzjyV5.cjs +0 -195
- package/dist/credentials-BapzjyV5.cjs.map +0 -1
- package/dist/credentials-DQmbRnr1.cjs.map +0 -1
- package/dist/discover-CAlFgYec.mjs.map +0 -1
- package/dist/discover-DTmFeloL.cjs.map +0 -1
- package/dist/dokploy-api-BS2xnJLZ.mjs +0 -3
- package/dist/dokploy-api-BsVH0y_s.cjs +0 -718
- package/dist/dokploy-api-BsVH0y_s.cjs.map +0 -1
- package/dist/dokploy-api-BuxDRo3l.cjs +0 -3
- package/dist/dokploy-api-aTFPz7_4.mjs +0 -706
- package/dist/dokploy-api-aTFPz7_4.mjs.map +0 -1
- package/dist/emit-CbcRolsa.cjs.map +0 -1
- package/dist/emit-CiZS_5Ub.mjs.map +0 -1
- package/dist/encryption-C6jaU0hE.cjs +0 -54
- package/dist/encryption-C6jaU0hE.cjs.map +0 -1
- package/dist/encryption-CHzzeR8a.cjs +0 -4
- package/dist/encryption-CQUncu7v.mjs +0 -3
- package/dist/encryption-D-abyhZx.mjs +0 -42
- package/dist/encryption-D-abyhZx.mjs.map +0 -1
- package/dist/fullstack-secrets-C-GGohke.cjs.map +0 -1
- package/dist/fullstack-secrets-D1o9-G5l.mjs.map +0 -1
- package/dist/generator-CNzBw7g5.cjs.map +0 -1
- package/dist/generator-VE2JrFlY.mjs.map +0 -1
- package/dist/index-BThUzEIp.d.mts.map +0 -1
- package/dist/index-CHB4tSzX.d.cts.map +0 -1
- package/dist/openapi-CY9xFkTH.mjs.map +0 -1
- package/dist/openapi-c9bpieI5.cjs.map +0 -1
- package/dist/openapi-react-query-Byltvm_7.mjs +0 -167
- package/dist/openapi-react-query-Byltvm_7.mjs.map +0 -1
- package/dist/openapi-react-query-CmJ1xDbn.cjs +0 -173
- package/dist/openapi-react-query-CmJ1xDbn.cjs.map +0 -1
- package/dist/openapi-react-query.cjs +0 -4
- package/dist/openapi-react-query.d.cts +0 -11
- package/dist/openapi-react-query.d.cts.map +0 -1
- package/dist/openapi-react-query.d.mts +0 -11
- package/dist/openapi-react-query.d.mts.map +0 -1
- package/dist/openapi-react-query.mjs +0 -4
- package/dist/reconcile-CFs8C_1a.mjs +0 -37
- package/dist/reconcile-CFs8C_1a.mjs.map +0 -1
- package/dist/reconcile-D0pimNiy.cjs +0 -37
- package/dist/reconcile-D0pimNiy.cjs.map +0 -1
- package/dist/setup-Bc73t0ve.mjs +0 -443
- package/dist/setup-Bc73t0ve.mjs.map +0 -1
- package/dist/setup-C48C9hto.cjs +0 -473
- package/dist/setup-C48C9hto.cjs.map +0 -1
- package/dist/setup-DCuudnCl.mjs +0 -11
- package/dist/setup-fG_ivuxO.cjs +0 -11
- package/dist/storage-BJmzYxt8.cjs.map +0 -1
- package/dist/storage-BM9hGpgv.cjs +0 -7
- package/dist/storage-Bi24WfoW.mjs.map +0 -1
- package/dist/storage-CYlGwWTj.mjs +0 -3
- package/dist/sync-CmEuNZsB.mjs +0 -76
- package/dist/sync-CmEuNZsB.mjs.map +0 -1
- package/dist/sync-D306yOWu.cjs +0 -4
- package/dist/sync-SizaChmC.cjs +0 -93
- package/dist/sync-SizaChmC.cjs.map +0 -1
- package/dist/sync-hOqAWHR6.mjs +0 -3
- package/dist/types-C-arnQ1r.d.mts +0 -2184
- package/dist/types-C-arnQ1r.d.mts.map +0 -1
- package/dist/types-abJQuk7m.d.cts +0 -2184
- package/dist/types-abJQuk7m.d.cts.map +0 -1
- package/dist/workspace-7FqprxuJ.mjs +0 -4323
- package/dist/workspace-7FqprxuJ.mjs.map +0 -1
- package/dist/workspace-BO_KWohI.cjs +0 -9
- package/dist/workspace-DK4WWJIn.mjs +0 -8
- package/dist/workspace-XvqO9jIv.cjs +0 -4472
- package/dist/workspace-XvqO9jIv.cjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit-BdoPFjZf.cjs","names":["provideKey","canonicalId","join","mkdir","dirname","writeFile"],"sources":["../src/reconcile/emit.ts"],"sourcesContent":["/**\n * The manifest, written out as a module.\n *\n * Discovery imports the application's own code — that is how it finds\n * constructs — so anything calling it evaluates the whole runtime graph: React\n * email templates, database clients, modules that read env at import time. That\n * is fine inside `gkm dev`, which is already running the app. It is not fine\n * inside a deploy config, where it fails on the first thing the deploy toolchain\n * does not expect and would keep failing on the next.\n *\n * So discovery happens once, in the build, and everything downstream reads what\n * it wrote. The same split `RestApi` makes by naming a routes glob rather than\n * importing route modules.\n *\n * A module rather than JSON, because a manifest is not just data to this\n * codebase — `as const satisfies ConstructManifest` is what keeps every id,\n * kind and provided key a *literal*, so `IdsOf`, `ProvidedKeys` and the rest\n * can select out of it. `JSON.parse` returns `any` and gives all of that up.\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type {\n\tConstructManifest,\n\tCronInfo,\n\tDeclaration,\n\tDependency,\n\tFunctionInfo,\n\tQueueInfo,\n\tRestApiEndpoint,\n\tRouteInfo,\n\tSubscriberInfo,\n} from '@geekmidas/manifest';\nimport { canonicalId, provideKey } from '@geekmidas/manifest';\nimport type { CacheBackend, EmailBackend } from '../types.js';\n\n/** Where the build writes it, relative to the app root. */\nexport const MANIFEST_PATH = '.gkm/manifest.ts';\n\n/**\n * Fold the build's generated routes into the surface that serves them.\n *\n * The routes already exist: `gkm build` generates one handler per endpoint and\n * records its path, method and environment. What was missing is that the\n * *construct* manifest never learned about them, so a `rest-api` node said\n * `endpoints: []` while five handlers sat in `.gkm/`. Two documents describing\n * the same routes, one of them wrong.\n *\n * Merging here rather than making the construct name a glob or import its\n * endpoints: the build is the thing that already walked the filesystem and\n * already knows where it wrote each handler. Asking the declaration to restate\n * any of that is how the two come to disagree.\n *\n * Attribution is by exclusion — routes go to the surface that declared none of\n * its own. A surface with static endpoints (an auth server's single wildcard)\n * keeps them. That is exact while an app has one API of its own, and needs a\n * real answer the day it has two; the alternative today would be inventing one\n * before anything needs it.\n *\n * **Only from a provider that generates one handler per route.** The `server`\n * provider generates a single catch-all — `ALL *` pointing at the Hono app that\n * routes in-process — which is a true description of that build and a false\n * description of the application. Merging it wrote `endpoints: [{ method:\n * 'ALL', path: '*' }]` into a manifest for an app with five routes, and made the\n * document's contents depend on which `--provider` ran last. A manifest must not\n * do that.\n */\nexport function withRoutes(\n\tmanifest: ConstructManifest,\n\troutes: readonly RouteInfo[],\n\toptions: { perRoute: boolean },\n): ConstructManifest {\n\tif (routes.length === 0 || !options.perRoute) return manifest;\n\n\tconst target = Object.entries(manifest).find(\n\t\t([, declaration]) =>\n\t\t\tdeclaration.kind === 'rest-api' && declaration.endpoints.length === 0,\n\t);\n\n\tif (!target) return manifest;\n\n\tconst [id, declaration] = target;\n\tif (declaration.kind !== 'rest-api') return manifest;\n\n\treturn {\n\t\t...manifest,\n\t\t[id]: {\n\t\t\t...declaration,\n\t\t\tendpoints: routes.map((route) => asEndpoint(manifest, id, route)),\n\t\t},\n\t};\n}\n\n/**\n * Fold in the compute the build generated: functions, crons, queue workers and\n * topic subscribers.\n *\n * All four are named by their *export*, the same way an endpoint's handler is —\n * `CronGenerator` uses the export key as the name — so none of them can declare\n * themselves and all of them are the build's to add. That is why a `Function`\n * has no `declare()` and never will.\n *\n * A worker and a subscriber **nest** inside the queue or topic that triggers\n * them, because position carries the trigger. A function and a cron are\n * top-level, because nothing triggers them but a caller or a clock.\n */\nexport function withCompute(\n\tmanifest: ConstructManifest,\n\tcompute: {\n\t\tfunctions?: readonly FunctionInfo[];\n\t\tcrons?: readonly CronInfo[];\n\t\tqueues?: readonly QueueInfo[];\n\t\tsubscribers?: readonly SubscriberInfo[];\n\t},\n): ConstructManifest {\n\tconst next: Record<string, Declaration> = { ...manifest };\n\n\tfor (const fn of compute.functions ?? []) {\n\t\tnext[fn.name] = {\n\t\t\tkind: 'function',\n\t\t\tid: fn.name,\n\t\t\thandler: fn.handler,\n\t\t\tdependencies: edgesTo(manifest, fn.dependencies),\n\t\t\tprovides: [provideKey(fn.name, 'url')],\n\t\t};\n\t}\n\n\tfor (const cron of compute.crons ?? []) {\n\t\tnext[cron.name] = {\n\t\t\tkind: 'cron',\n\t\t\tid: cron.name,\n\t\t\thandler: cron.handler,\n\t\t\tschedule: cron.schedule,\n\t\t\tdependencies: edgesTo(manifest, cron.dependencies),\n\t\t};\n\t}\n\n\t// A worker drains exactly one queue, so it is matched by the queue it names\n\t// rather than by anything about the handler.\n\tfor (const worker of compute.queues ?? []) {\n\t\tconst queue = next[canonicalId(worker.name)];\n\t\tif (queue?.kind !== 'queue') continue;\n\n\t\tnext[queue.id] = {\n\t\t\t...queue,\n\t\t\tworker: {\n\t\t\t\tid: `${queue.id}Worker`,\n\t\t\t\thandler: worker.handler,\n\t\t\t\tdependencies: edgesTo(manifest, worker.dependencies),\n\t\t\t},\n\t\t};\n\t}\n\n\tfor (const subscriber of compute.subscribers ?? []) {\n\t\tconst topicId = subscriber.topic && canonicalId(subscriber.topic);\n\t\tconst topic = topicId ? next[topicId] : undefined;\n\t\tif (topic?.kind !== 'topic') continue;\n\n\t\tnext[topic.id] = {\n\t\t\t...topic,\n\t\t\tsubscribers: [\n\t\t\t\t...topic.subscribers,\n\t\t\t\t{\n\t\t\t\t\tid: subscriber.name,\n\t\t\t\t\thandler: subscriber.handler,\n\t\t\t\t\tevents: subscriber.subscribedEvents,\n\t\t\t\t\tdependencies: edgesTo(manifest, subscriber.dependencies),\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}\n\n\treturn next;\n}\n\n/**\n * Construct ids as manifest edges.\n *\n * A `Dependency` carries the target's kind as well as its id, and the kind is\n * read off the manifest rather than recorded beside the edge — the same rule\n * `edgeTo` follows, for the same reason: a hand-written kind is one fact stated\n * twice, and the copy is what goes stale.\n *\n * An id that names nothing is dropped rather than thrown on. A handler can\n * depend on a construct that declares no node of its own — a lifted `Service`\n * is exactly that — and a build is the wrong place to refuse it; what a target\n * needs from this list is the subset it can actually grant.\n */\nfunction edgesTo(\n\tmanifest: ConstructManifest,\n\tids: readonly string[] = [],\n): Dependency[] {\n\treturn ids.flatMap((target) => {\n\t\tconst declaration = manifest[target];\n\n\t\treturn declaration ? [{ target, kind: declaration.kind }] : [];\n\t});\n}\n\n/** One generated route as the manifest's shape. */\nfunction asEndpoint(\n\tmanifest: ConstructManifest,\n\tsurface: string,\n\troute: RouteInfo,\n): RestApiEndpoint {\n\treturn {\n\t\t// The handler path is already unique per route and is what a target needs\n\t\t// to point a function at, so it is also the natural id.\n\t\tid: `${surface}${route.method}${route.path}`,\n\t\thandler: route.handler,\n\t\tmethod: route.method,\n\t\tpath: route.path,\n\t\t// The edges the handler declared, which is what a target reads to grant\n\t\t// this one function access to exactly those constructs. `environment` is\n\t\t// the same edges' shadow — the keys read — and cannot be turned back into\n\t\t// them, which is why the ids are carried rather than inferred.\n\t\tdependencies: edgesTo(manifest, route.dependencies),\n\t\t...(route.authorizer ? { authorizer: route.authorizer } : {}),\n\t};\n}\n\n/**\n * The manifest as TypeScript source.\n *\n * Pure, so what gets written can be asserted without touching a filesystem.\n */\nexport function manifestModule(\n\tmanifest: ConstructManifest,\n\tbackends: Backends = {},\n): string {\n\treturn `${HEADER}\nexport const manifest = ${serialise(manifest)} as const satisfies ConstructManifest;\n\n/**\n * Where the backends that are *config* rather than declaration resolved to.\n *\n * Recorded because they were being answered twice: once by the build, which\n * registers exactly one cache driver, and once by a deploy config, which told\n * the provisioner something else. The result was a deployed URL the running code\n * had no driver for — two documents disagreeing about one fact, which is what\n * this manifest exists to stop.\n */\nexport const backends = ${serialise(backends)} as const;\n\n// Derived types\nexport type Ids = IdsOf<typeof manifest>;\nexport type Construct<Id extends Ids> = DeclarationOf<typeof manifest, Id>;\nexport type Kind = Construct<Ids>['kind'];\n\n// Useful union types\nexport type ProvidedKeys = AllProvidedKeys<typeof manifest>;\nexport type Surfaces = IdsOfKind<typeof manifest, 'rest-api'>;\nexport type CacheBackend = (typeof backends)['cache'];\nexport type EmailBackend = (typeof backends)['email'];\n`;\n}\n\n/** Write it where the deploy config expects to import it from. */\nexport async function writeManifestModule(\n\tmanifest: ConstructManifest,\n\tappRoot: string,\n\tbackends: Backends = {},\n): Promise<string> {\n\tconst path = join(appRoot, MANIFEST_PATH);\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, manifestModule(manifest, backends), 'utf8');\n\n\treturn path;\n}\n\n/** The choices that are deployment config rather than declaration. */\nexport interface Backends {\n\tcache?: CacheBackend;\n\temail?: EmailBackend;\n}\n\nconst HEADER = `// Generated by \\`gkm build\\`. Do not edit.\n//\n// Discovery imports application code, so it runs once here rather than in every\n// consumer — a deploy config that discovered for itself would evaluate the whole\n// runtime graph inside its own toolchain. This is the result, as literals.\n\nimport type {\n\tAllProvidedKeys,\n\tConstructManifest,\n\tDeclarationOf,\n\tIdsOf,\n\tIdsOfKind,\n} from '@geekmidas/manifest';\n`;\n\n/**\n * JSON, re-indented with tabs and with keys left unquoted where they can be.\n *\n * Cosmetic, and worth it: this file is read by people trying to work out what\n * their app declares, and a wall of quoted keys reads as output rather than as\n * something anyone is expected to understand.\n */\nfunction serialise(value: object): string {\n\treturn JSON.stringify(value, null, '\\t')\n\t\t.replace(/^(\\t*)\"([A-Za-z_$][\\w$]*)\":/gm, '$1$2:')\n\t\t.replace(/\"/g, \"'\");\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,26 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
1
2
|
import { dirname, join } from "node:path";
|
|
2
3
|
import { canonicalId, provideKey } from "@geekmidas/manifest";
|
|
3
|
-
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
-
|
|
5
4
|
//#region src/reconcile/emit.ts
|
|
5
|
+
/**
|
|
6
|
+
* The manifest, written out as a module.
|
|
7
|
+
*
|
|
8
|
+
* Discovery imports the application's own code — that is how it finds
|
|
9
|
+
* constructs — so anything calling it evaluates the whole runtime graph: React
|
|
10
|
+
* email templates, database clients, modules that read env at import time. That
|
|
11
|
+
* is fine inside `gkm dev`, which is already running the app. It is not fine
|
|
12
|
+
* inside a deploy config, where it fails on the first thing the deploy toolchain
|
|
13
|
+
* does not expect and would keep failing on the next.
|
|
14
|
+
*
|
|
15
|
+
* So discovery happens once, in the build, and everything downstream reads what
|
|
16
|
+
* it wrote. The same split `RestApi` makes by naming a routes glob rather than
|
|
17
|
+
* importing route modules.
|
|
18
|
+
*
|
|
19
|
+
* A module rather than JSON, because a manifest is not just data to this
|
|
20
|
+
* codebase — `as const satisfies ConstructManifest` is what keeps every id,
|
|
21
|
+
* kind and provided key a *literal*, so `IdsOf`, `ProvidedKeys` and the rest
|
|
22
|
+
* can select out of it. `JSON.parse` returns `any` and gives all of that up.
|
|
23
|
+
*/
|
|
6
24
|
/** Where the build writes it, relative to the app root. */
|
|
7
25
|
const MANIFEST_PATH = ".gkm/manifest.ts";
|
|
8
26
|
/**
|
|
@@ -35,7 +53,7 @@ const MANIFEST_PATH = ".gkm/manifest.ts";
|
|
|
35
53
|
*/
|
|
36
54
|
function withRoutes(manifest, routes, options) {
|
|
37
55
|
if (routes.length === 0 || !options.perRoute) return manifest;
|
|
38
|
-
const target = Object.entries(manifest).find(([, declaration
|
|
56
|
+
const target = Object.entries(manifest).find(([, declaration]) => declaration.kind === "rest-api" && declaration.endpoints.length === 0);
|
|
39
57
|
if (!target) return manifest;
|
|
40
58
|
const [id, declaration] = target;
|
|
41
59
|
if (declaration.kind !== "rest-api") return manifest;
|
|
@@ -200,7 +218,7 @@ import type {
|
|
|
200
218
|
function serialise(value) {
|
|
201
219
|
return JSON.stringify(value, null, " ").replace(/^(\t*)"([A-Za-z_$][\w$]*)":/gm, "$1$2:").replace(/"/g, "'");
|
|
202
220
|
}
|
|
203
|
-
|
|
204
221
|
//#endregion
|
|
205
|
-
export {
|
|
206
|
-
|
|
222
|
+
export { writeManifestModule as a, withRoutes as i, manifestModule as n, withCompute as r, MANIFEST_PATH as t };
|
|
223
|
+
|
|
224
|
+
//# sourceMappingURL=emit-Bzq7JsMP.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emit-Bzq7JsMP.mjs","names":[],"sources":["../src/reconcile/emit.ts"],"sourcesContent":["/**\n * The manifest, written out as a module.\n *\n * Discovery imports the application's own code — that is how it finds\n * constructs — so anything calling it evaluates the whole runtime graph: React\n * email templates, database clients, modules that read env at import time. That\n * is fine inside `gkm dev`, which is already running the app. It is not fine\n * inside a deploy config, where it fails on the first thing the deploy toolchain\n * does not expect and would keep failing on the next.\n *\n * So discovery happens once, in the build, and everything downstream reads what\n * it wrote. The same split `RestApi` makes by naming a routes glob rather than\n * importing route modules.\n *\n * A module rather than JSON, because a manifest is not just data to this\n * codebase — `as const satisfies ConstructManifest` is what keeps every id,\n * kind and provided key a *literal*, so `IdsOf`, `ProvidedKeys` and the rest\n * can select out of it. `JSON.parse` returns `any` and gives all of that up.\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type {\n\tConstructManifest,\n\tCronInfo,\n\tDeclaration,\n\tDependency,\n\tFunctionInfo,\n\tQueueInfo,\n\tRestApiEndpoint,\n\tRouteInfo,\n\tSubscriberInfo,\n} from '@geekmidas/manifest';\nimport { canonicalId, provideKey } from '@geekmidas/manifest';\nimport type { CacheBackend, EmailBackend } from '../types.js';\n\n/** Where the build writes it, relative to the app root. */\nexport const MANIFEST_PATH = '.gkm/manifest.ts';\n\n/**\n * Fold the build's generated routes into the surface that serves them.\n *\n * The routes already exist: `gkm build` generates one handler per endpoint and\n * records its path, method and environment. What was missing is that the\n * *construct* manifest never learned about them, so a `rest-api` node said\n * `endpoints: []` while five handlers sat in `.gkm/`. Two documents describing\n * the same routes, one of them wrong.\n *\n * Merging here rather than making the construct name a glob or import its\n * endpoints: the build is the thing that already walked the filesystem and\n * already knows where it wrote each handler. Asking the declaration to restate\n * any of that is how the two come to disagree.\n *\n * Attribution is by exclusion — routes go to the surface that declared none of\n * its own. A surface with static endpoints (an auth server's single wildcard)\n * keeps them. That is exact while an app has one API of its own, and needs a\n * real answer the day it has two; the alternative today would be inventing one\n * before anything needs it.\n *\n * **Only from a provider that generates one handler per route.** The `server`\n * provider generates a single catch-all — `ALL *` pointing at the Hono app that\n * routes in-process — which is a true description of that build and a false\n * description of the application. Merging it wrote `endpoints: [{ method:\n * 'ALL', path: '*' }]` into a manifest for an app with five routes, and made the\n * document's contents depend on which `--provider` ran last. A manifest must not\n * do that.\n */\nexport function withRoutes(\n\tmanifest: ConstructManifest,\n\troutes: readonly RouteInfo[],\n\toptions: { perRoute: boolean },\n): ConstructManifest {\n\tif (routes.length === 0 || !options.perRoute) return manifest;\n\n\tconst target = Object.entries(manifest).find(\n\t\t([, declaration]) =>\n\t\t\tdeclaration.kind === 'rest-api' && declaration.endpoints.length === 0,\n\t);\n\n\tif (!target) return manifest;\n\n\tconst [id, declaration] = target;\n\tif (declaration.kind !== 'rest-api') return manifest;\n\n\treturn {\n\t\t...manifest,\n\t\t[id]: {\n\t\t\t...declaration,\n\t\t\tendpoints: routes.map((route) => asEndpoint(manifest, id, route)),\n\t\t},\n\t};\n}\n\n/**\n * Fold in the compute the build generated: functions, crons, queue workers and\n * topic subscribers.\n *\n * All four are named by their *export*, the same way an endpoint's handler is —\n * `CronGenerator` uses the export key as the name — so none of them can declare\n * themselves and all of them are the build's to add. That is why a `Function`\n * has no `declare()` and never will.\n *\n * A worker and a subscriber **nest** inside the queue or topic that triggers\n * them, because position carries the trigger. A function and a cron are\n * top-level, because nothing triggers them but a caller or a clock.\n */\nexport function withCompute(\n\tmanifest: ConstructManifest,\n\tcompute: {\n\t\tfunctions?: readonly FunctionInfo[];\n\t\tcrons?: readonly CronInfo[];\n\t\tqueues?: readonly QueueInfo[];\n\t\tsubscribers?: readonly SubscriberInfo[];\n\t},\n): ConstructManifest {\n\tconst next: Record<string, Declaration> = { ...manifest };\n\n\tfor (const fn of compute.functions ?? []) {\n\t\tnext[fn.name] = {\n\t\t\tkind: 'function',\n\t\t\tid: fn.name,\n\t\t\thandler: fn.handler,\n\t\t\tdependencies: edgesTo(manifest, fn.dependencies),\n\t\t\tprovides: [provideKey(fn.name, 'url')],\n\t\t};\n\t}\n\n\tfor (const cron of compute.crons ?? []) {\n\t\tnext[cron.name] = {\n\t\t\tkind: 'cron',\n\t\t\tid: cron.name,\n\t\t\thandler: cron.handler,\n\t\t\tschedule: cron.schedule,\n\t\t\tdependencies: edgesTo(manifest, cron.dependencies),\n\t\t};\n\t}\n\n\t// A worker drains exactly one queue, so it is matched by the queue it names\n\t// rather than by anything about the handler.\n\tfor (const worker of compute.queues ?? []) {\n\t\tconst queue = next[canonicalId(worker.name)];\n\t\tif (queue?.kind !== 'queue') continue;\n\n\t\tnext[queue.id] = {\n\t\t\t...queue,\n\t\t\tworker: {\n\t\t\t\tid: `${queue.id}Worker`,\n\t\t\t\thandler: worker.handler,\n\t\t\t\tdependencies: edgesTo(manifest, worker.dependencies),\n\t\t\t},\n\t\t};\n\t}\n\n\tfor (const subscriber of compute.subscribers ?? []) {\n\t\tconst topicId = subscriber.topic && canonicalId(subscriber.topic);\n\t\tconst topic = topicId ? next[topicId] : undefined;\n\t\tif (topic?.kind !== 'topic') continue;\n\n\t\tnext[topic.id] = {\n\t\t\t...topic,\n\t\t\tsubscribers: [\n\t\t\t\t...topic.subscribers,\n\t\t\t\t{\n\t\t\t\t\tid: subscriber.name,\n\t\t\t\t\thandler: subscriber.handler,\n\t\t\t\t\tevents: subscriber.subscribedEvents,\n\t\t\t\t\tdependencies: edgesTo(manifest, subscriber.dependencies),\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}\n\n\treturn next;\n}\n\n/**\n * Construct ids as manifest edges.\n *\n * A `Dependency` carries the target's kind as well as its id, and the kind is\n * read off the manifest rather than recorded beside the edge — the same rule\n * `edgeTo` follows, for the same reason: a hand-written kind is one fact stated\n * twice, and the copy is what goes stale.\n *\n * An id that names nothing is dropped rather than thrown on. A handler can\n * depend on a construct that declares no node of its own — a lifted `Service`\n * is exactly that — and a build is the wrong place to refuse it; what a target\n * needs from this list is the subset it can actually grant.\n */\nfunction edgesTo(\n\tmanifest: ConstructManifest,\n\tids: readonly string[] = [],\n): Dependency[] {\n\treturn ids.flatMap((target) => {\n\t\tconst declaration = manifest[target];\n\n\t\treturn declaration ? [{ target, kind: declaration.kind }] : [];\n\t});\n}\n\n/** One generated route as the manifest's shape. */\nfunction asEndpoint(\n\tmanifest: ConstructManifest,\n\tsurface: string,\n\troute: RouteInfo,\n): RestApiEndpoint {\n\treturn {\n\t\t// The handler path is already unique per route and is what a target needs\n\t\t// to point a function at, so it is also the natural id.\n\t\tid: `${surface}${route.method}${route.path}`,\n\t\thandler: route.handler,\n\t\tmethod: route.method,\n\t\tpath: route.path,\n\t\t// The edges the handler declared, which is what a target reads to grant\n\t\t// this one function access to exactly those constructs. `environment` is\n\t\t// the same edges' shadow — the keys read — and cannot be turned back into\n\t\t// them, which is why the ids are carried rather than inferred.\n\t\tdependencies: edgesTo(manifest, route.dependencies),\n\t\t...(route.authorizer ? { authorizer: route.authorizer } : {}),\n\t};\n}\n\n/**\n * The manifest as TypeScript source.\n *\n * Pure, so what gets written can be asserted without touching a filesystem.\n */\nexport function manifestModule(\n\tmanifest: ConstructManifest,\n\tbackends: Backends = {},\n): string {\n\treturn `${HEADER}\nexport const manifest = ${serialise(manifest)} as const satisfies ConstructManifest;\n\n/**\n * Where the backends that are *config* rather than declaration resolved to.\n *\n * Recorded because they were being answered twice: once by the build, which\n * registers exactly one cache driver, and once by a deploy config, which told\n * the provisioner something else. The result was a deployed URL the running code\n * had no driver for — two documents disagreeing about one fact, which is what\n * this manifest exists to stop.\n */\nexport const backends = ${serialise(backends)} as const;\n\n// Derived types\nexport type Ids = IdsOf<typeof manifest>;\nexport type Construct<Id extends Ids> = DeclarationOf<typeof manifest, Id>;\nexport type Kind = Construct<Ids>['kind'];\n\n// Useful union types\nexport type ProvidedKeys = AllProvidedKeys<typeof manifest>;\nexport type Surfaces = IdsOfKind<typeof manifest, 'rest-api'>;\nexport type CacheBackend = (typeof backends)['cache'];\nexport type EmailBackend = (typeof backends)['email'];\n`;\n}\n\n/** Write it where the deploy config expects to import it from. */\nexport async function writeManifestModule(\n\tmanifest: ConstructManifest,\n\tappRoot: string,\n\tbackends: Backends = {},\n): Promise<string> {\n\tconst path = join(appRoot, MANIFEST_PATH);\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, manifestModule(manifest, backends), 'utf8');\n\n\treturn path;\n}\n\n/** The choices that are deployment config rather than declaration. */\nexport interface Backends {\n\tcache?: CacheBackend;\n\temail?: EmailBackend;\n}\n\nconst HEADER = `// Generated by \\`gkm build\\`. Do not edit.\n//\n// Discovery imports application code, so it runs once here rather than in every\n// consumer — a deploy config that discovered for itself would evaluate the whole\n// runtime graph inside its own toolchain. This is the result, as literals.\n\nimport type {\n\tAllProvidedKeys,\n\tConstructManifest,\n\tDeclarationOf,\n\tIdsOf,\n\tIdsOfKind,\n} from '@geekmidas/manifest';\n`;\n\n/**\n * JSON, re-indented with tabs and with keys left unquoted where they can be.\n *\n * Cosmetic, and worth it: this file is read by people trying to work out what\n * their app declares, and a wall of quoted keys reads as output rather than as\n * something anyone is expected to understand.\n */\nfunction serialise(value: object): string {\n\treturn JSON.stringify(value, null, '\\t')\n\t\t.replace(/^(\\t*)\"([A-Za-z_$][\\w$]*)\":/gm, '$1$2:')\n\t\t.replace(/\"/g, \"'\");\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
|
|
3
2
|
//#region src/secrets/generator.ts
|
|
4
3
|
/**
|
|
5
4
|
* Generate a secure random password using URL-safe base64 characters.
|
|
@@ -56,9 +55,8 @@ const PGBOSS_DEFAULTS = {
|
|
|
56
55
|
* Generate credentials for a specific service.
|
|
57
56
|
*/
|
|
58
57
|
function generateServiceCredentials(service) {
|
|
59
|
-
const defaults = SERVICE_DEFAULTS[service];
|
|
60
58
|
return {
|
|
61
|
-
...
|
|
59
|
+
...SERVICE_DEFAULTS[service],
|
|
62
60
|
password: generateSecurePassword()
|
|
63
61
|
};
|
|
64
62
|
}
|
|
@@ -89,8 +87,7 @@ function generateRedisUrl(creds) {
|
|
|
89
87
|
*/
|
|
90
88
|
function generateRabbitmqUrl(creds) {
|
|
91
89
|
const { username, password, host, port, vhost } = creds;
|
|
92
|
-
|
|
93
|
-
return `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;
|
|
90
|
+
return `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodeURIComponent(vhost ?? "/")}`;
|
|
94
91
|
}
|
|
95
92
|
/**
|
|
96
93
|
* Generate endpoint URL for MinIO (S3-compatible).
|
|
@@ -105,8 +102,7 @@ function generateMinioEndpoint(creds) {
|
|
|
105
102
|
* @see https://docs.localstack.cloud/aws/capabilities/config/credentials/
|
|
106
103
|
*/
|
|
107
104
|
function generateLocalStackAccessKeyId() {
|
|
108
|
-
|
|
109
|
-
return `LSIA${suffix}`;
|
|
105
|
+
return `LSIA${randomBytes(12).toString("base64url").slice(0, 16)}`;
|
|
110
106
|
}
|
|
111
107
|
/**
|
|
112
108
|
* Generate connection URL for pg-boss (uses PostgreSQL protocol).
|
|
@@ -181,9 +177,8 @@ function generateConnectionUrls(services, eventsBackend) {
|
|
|
181
177
|
* Generate LocalStack service credentials with LSIA-prefixed access key.
|
|
182
178
|
*/
|
|
183
179
|
function generateLocalStackCredentials() {
|
|
184
|
-
const defaults = SERVICE_DEFAULTS.localstack;
|
|
185
180
|
return {
|
|
186
|
-
...
|
|
181
|
+
...SERVICE_DEFAULTS.localstack,
|
|
187
182
|
password: generateSecurePassword(),
|
|
188
183
|
accessKeyId: generateLocalStackAccessKeyId()
|
|
189
184
|
};
|
|
@@ -215,14 +210,13 @@ function createStageSecrets(stage, services, options) {
|
|
|
215
210
|
};
|
|
216
211
|
const eventsBackend = options?.eventsBackend;
|
|
217
212
|
if (eventsBackend === "sns") serviceCredentials.localstack = generateLocalStackCredentials();
|
|
218
|
-
const urls = generateConnectionUrls(serviceCredentials, eventsBackend);
|
|
219
213
|
return {
|
|
220
214
|
stage,
|
|
221
215
|
createdAt: now,
|
|
222
216
|
updatedAt: now,
|
|
223
217
|
eventsBackend,
|
|
224
218
|
services: serviceCredentials,
|
|
225
|
-
urls,
|
|
219
|
+
urls: generateConnectionUrls(serviceCredentials, eventsBackend),
|
|
226
220
|
custom: {}
|
|
227
221
|
};
|
|
228
222
|
}
|
|
@@ -247,7 +241,7 @@ function rotateServicePassword(secrets, service) {
|
|
|
247
241
|
urls: generateConnectionUrls(newServices, secrets.eventsBackend)
|
|
248
242
|
};
|
|
249
243
|
}
|
|
250
|
-
|
|
251
244
|
//#endregion
|
|
252
|
-
export {
|
|
253
|
-
|
|
245
|
+
export { generateServiceCredentials as a, generateSecurePassword as i, generateConnectionUrls as n, rotateServicePassword as o, generatePgBossUrl as r, createStageSecrets as t };
|
|
246
|
+
|
|
247
|
+
//# sourceMappingURL=generator-LfZU7NlU.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator-LfZU7NlU.mjs","names":[],"sources":["../src/secrets/generator.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport type { ComposeServiceName, EventsBackend } from '../types';\nimport type { ServiceCredentials, StageSecrets } from './types';\n\n/**\n * Generate a secure random password using URL-safe base64 characters.\n * @param length Password length (default: 32)\n */\nexport function generateSecurePassword(length = 32): string {\n\treturn randomBytes(Math.ceil((length * 3) / 4))\n\t\t.toString('base64url')\n\t\t.slice(0, length);\n}\n\n/** Default service configurations (localhost for local dev via Docker port mapping) */\nconst SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n> = {\n\tpostgres: {\n\t\thost: 'localhost',\n\t\tport: 5432,\n\t\tusername: 'app',\n\t\tdatabase: 'app',\n\t},\n\tredis: {\n\t\thost: 'localhost',\n\t\tport: 6379,\n\t\tusername: 'default',\n\t},\n\trabbitmq: {\n\t\thost: 'localhost',\n\t\tport: 5672,\n\t\tusername: 'app',\n\t\tvhost: '/',\n\t},\n\tminio: {\n\t\thost: 'localhost',\n\t\tport: 9000,\n\t\tusername: 'app',\n\t\tbucket: 'app',\n\t},\n\tmailpit: {\n\t\thost: 'localhost',\n\t\tport: 1025,\n\t\tusername: 'app',\n\t},\n\tlocalstack: {\n\t\thost: 'localhost',\n\t\tport: 4566,\n\t\tusername: 'localstack',\n\t\tregion: 'us-east-1',\n\t},\n};\n\n/** Default credentials for pgboss (not a Docker service, reuses postgres) */\nconst PGBOSS_DEFAULTS: Omit<ServiceCredentials, 'password'> = {\n\thost: 'localhost',\n\tport: 5432,\n\tusername: 'pgboss',\n\tdatabase: 'app',\n};\n\n/**\n * Generate credentials for a specific service.\n */\nexport function generateServiceCredentials(\n\tservice: ComposeServiceName,\n): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS[service];\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t};\n}\n\n/**\n * Generate credentials for multiple services.\n */\nexport function generateServicesCredentials(\n\tservices: ComposeServiceName[],\n): StageSecrets['services'] {\n\tconst result: StageSecrets['services'] = {};\n\n\tfor (const service of services) {\n\t\tresult[service] = generateServiceCredentials(service);\n\t}\n\n\treturn result;\n}\n\n/**\n * Generate connection URL for PostgreSQL.\n */\nexport function generatePostgresUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;\n}\n\n/**\n * Generate connection URL for Redis.\n */\nexport function generateRedisUrl(creds: ServiceCredentials): string {\n\tconst { password, host, port } = creds;\n\treturn `redis://:${encodeURIComponent(password)}@${host}:${port}`;\n}\n\n/**\n * Generate connection URL for RabbitMQ.\n */\nexport function generateRabbitmqUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, vhost } = creds;\n\tconst encodedVhost = encodeURIComponent(vhost ?? '/');\n\treturn `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;\n}\n\n/**\n * Generate endpoint URL for MinIO (S3-compatible).\n */\nexport function generateMinioEndpoint(creds: ServiceCredentials): string {\n\tconst { host, port } = creds;\n\treturn `http://${host}:${port}`;\n}\n\n/**\n * Generate a LocalStack-compatible access key ID.\n * Must start with 'LSIA' prefix and be at least 20 characters.\n * @see https://docs.localstack.cloud/aws/capabilities/config/credentials/\n */\nexport function generateLocalStackAccessKeyId(): string {\n\tconst suffix = randomBytes(12).toString('base64url').slice(0, 16);\n\treturn `LSIA${suffix}`;\n}\n\n/**\n * Generate connection URL for pg-boss (uses PostgreSQL protocol).\n * Format: pgboss://user:pass@host:port/db?schema=pgboss\n */\nexport function generatePgBossUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `pgboss://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}?schema=pgboss`;\n}\n\n/**\n * Generate event connection strings based on the events backend.\n */\nexport function generateEventConnectionStrings(\n\teventsBackend: EventsBackend,\n\tservices: StageSecrets['services'],\n): { publisher: string; subscriber: string } {\n\tswitch (eventsBackend) {\n\t\tcase 'pgboss': {\n\t\t\tconst creds = services.pgboss;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('pgboss credentials required for pgboss events');\n\t\t\t}\n\t\t\tconst url = generatePgBossUrl(creds);\n\t\t\treturn { publisher: url, subscriber: url };\n\t\t}\n\t\tcase 'sns': {\n\t\t\tconst creds = services.localstack;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('localstack credentials required for sns events');\n\t\t\t}\n\t\t\tconst endpoint = `http://${creds.host}:${creds.port}`;\n\t\t\tconst region = creds.region ?? 'us-east-1';\n\t\t\tconst accessKeyId = creds.accessKeyId ?? creds.username;\n\t\t\tconst secretKey = encodeURIComponent(creds.password);\n\t\t\treturn {\n\t\t\t\tpublisher: `sns://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,\n\t\t\t\tsubscriber: `sqs://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,\n\t\t\t};\n\t\t}\n\t\tcase 'rabbitmq': {\n\t\t\tconst creds = services.rabbitmq;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('rabbitmq credentials required for rabbitmq events');\n\t\t\t}\n\t\t\tconst url = generateRabbitmqUrl(creds);\n\t\t\treturn { publisher: url, subscriber: url };\n\t\t}\n\t}\n}\n\n/**\n * Generate connection URLs from service credentials.\n */\nexport function generateConnectionUrls(\n\tservices: StageSecrets['services'],\n\teventsBackend?: EventsBackend,\n): StageSecrets['urls'] {\n\tconst urls: StageSecrets['urls'] = {};\n\n\tif (services.postgres) {\n\t\turls.DATABASE_URL = generatePostgresUrl(services.postgres);\n\t}\n\n\tif (services.redis) {\n\t\turls.REDIS_URL = generateRedisUrl(services.redis);\n\t}\n\n\tif (services.rabbitmq) {\n\t\turls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);\n\t}\n\n\tif (services.minio) {\n\t\turls.STORAGE_ENDPOINT = generateMinioEndpoint(services.minio);\n\t}\n\n\tif (eventsBackend) {\n\t\tconst eventUrls = generateEventConnectionStrings(eventsBackend, services);\n\t\turls.EVENT_PUBLISHER_CONNECTION_STRING = eventUrls.publisher;\n\t\turls.EVENT_SUBSCRIBER_CONNECTION_STRING = eventUrls.subscriber;\n\t} else if (services.pgboss) {\n\t\t// Default to pgboss when credentials exist but no explicit events backend\n\t\tconst pgbossUrl = generatePgBossUrl(services.pgboss);\n\t\turls.EVENT_PUBLISHER_CONNECTION_STRING = pgbossUrl;\n\t\turls.EVENT_SUBSCRIBER_CONNECTION_STRING = pgbossUrl;\n\t}\n\n\tif (services.mailpit) {\n\t\turls.SMTP_HOST = services.mailpit.host;\n\t\turls.SMTP_PORT = String(services.mailpit.port);\n\t}\n\n\treturn urls;\n}\n\n/**\n * Generate LocalStack service credentials with LSIA-prefixed access key.\n */\nexport function generateLocalStackCredentials(): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS.localstack;\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t\taccessKeyId: generateLocalStackAccessKeyId(),\n\t};\n}\n\n/**\n * Create a new StageSecrets object with generated credentials.\n * @param stage - The deployment stage (e.g., 'development', 'production')\n * @param services - List of services to generate credentials for\n * @param options - Optional configuration\n * @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')\n * @param options.eventsBackend - Event backend type (pgboss, sns, rabbitmq)\n */\nexport function createStageSecrets(\n\tstage: string,\n\tservices: ComposeServiceName[],\n\toptions?: { projectName?: string; eventsBackend?: EventsBackend },\n): StageSecrets {\n\tconst now = new Date().toISOString();\n\tconst serviceCredentials = generateServicesCredentials(services);\n\n\t// Override service defaults with project-derived names if provided\n\tif (options?.projectName) {\n\t\tif (serviceCredentials.postgres) {\n\t\t\tserviceCredentials.postgres.database = `${options.projectName.replace(/-/g, '_')}_dev`;\n\t\t}\n\t\tif (serviceCredentials.minio) {\n\t\t\tserviceCredentials.minio.bucket = options.projectName;\n\t\t\tserviceCredentials.minio.username = options.projectName;\n\t\t}\n\t}\n\n\t// Always create pgboss credentials when postgres is available.\n\t// pgboss reuses postgres host/port/database but with a dedicated user,\n\t// so subscribers work out of the box without explicit events config.\n\tif (serviceCredentials.postgres && !serviceCredentials.pgboss) {\n\t\tserviceCredentials.pgboss = {\n\t\t\t...PGBOSS_DEFAULTS,\n\t\t\tpassword: generateSecurePassword(),\n\t\t\thost: serviceCredentials.postgres.host,\n\t\t\tport: serviceCredentials.postgres.port,\n\t\t\tdatabase: serviceCredentials.postgres.database,\n\t\t};\n\t}\n\n\t// Generate event-specific credentials\n\tconst eventsBackend = options?.eventsBackend;\n\tif (eventsBackend === 'sns') {\n\t\t// LocalStack credentials with LSIA-prefixed access key\n\t\tserviceCredentials.localstack = generateLocalStackCredentials();\n\t}\n\n\tconst urls = generateConnectionUrls(serviceCredentials, eventsBackend);\n\n\treturn {\n\t\tstage,\n\t\tcreatedAt: now,\n\t\tupdatedAt: now,\n\t\teventsBackend,\n\t\tservices: serviceCredentials,\n\t\turls,\n\t\tcustom: {},\n\t};\n}\n\n/**\n * Rotate password for a specific service.\n */\nexport function rotateServicePassword(\n\tsecrets: StageSecrets,\n\tservice: ComposeServiceName,\n): StageSecrets {\n\tconst currentCreds = secrets.services[service];\n\tif (!currentCreds) {\n\t\tthrow new Error(`Service \"${service}\" not configured in secrets`);\n\t}\n\n\tconst newCreds: ServiceCredentials = {\n\t\t...currentCreds,\n\t\tpassword: generateSecurePassword(),\n\t};\n\n\tconst newServices = {\n\t\t...secrets.services,\n\t\t[service]: newCreds,\n\t};\n\n\treturn {\n\t\t...secrets,\n\t\tupdatedAt: new Date().toISOString(),\n\t\tservices: newServices,\n\t\turls: generateConnectionUrls(newServices, secrets.eventsBackend),\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
const node_crypto = require_chunk.__toESM(require("node:crypto"));
|
|
3
|
-
|
|
1
|
+
let node_crypto = require("node:crypto");
|
|
4
2
|
//#region src/secrets/generator.ts
|
|
5
3
|
/**
|
|
6
4
|
* Generate a secure random password using URL-safe base64 characters.
|
|
@@ -57,9 +55,8 @@ const PGBOSS_DEFAULTS = {
|
|
|
57
55
|
* Generate credentials for a specific service.
|
|
58
56
|
*/
|
|
59
57
|
function generateServiceCredentials(service) {
|
|
60
|
-
const defaults = SERVICE_DEFAULTS[service];
|
|
61
58
|
return {
|
|
62
|
-
...
|
|
59
|
+
...SERVICE_DEFAULTS[service],
|
|
63
60
|
password: generateSecurePassword()
|
|
64
61
|
};
|
|
65
62
|
}
|
|
@@ -90,8 +87,7 @@ function generateRedisUrl(creds) {
|
|
|
90
87
|
*/
|
|
91
88
|
function generateRabbitmqUrl(creds) {
|
|
92
89
|
const { username, password, host, port, vhost } = creds;
|
|
93
|
-
|
|
94
|
-
return `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;
|
|
90
|
+
return `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodeURIComponent(vhost ?? "/")}`;
|
|
95
91
|
}
|
|
96
92
|
/**
|
|
97
93
|
* Generate endpoint URL for MinIO (S3-compatible).
|
|
@@ -106,8 +102,7 @@ function generateMinioEndpoint(creds) {
|
|
|
106
102
|
* @see https://docs.localstack.cloud/aws/capabilities/config/credentials/
|
|
107
103
|
*/
|
|
108
104
|
function generateLocalStackAccessKeyId() {
|
|
109
|
-
|
|
110
|
-
return `LSIA${suffix}`;
|
|
105
|
+
return `LSIA${(0, node_crypto.randomBytes)(12).toString("base64url").slice(0, 16)}`;
|
|
111
106
|
}
|
|
112
107
|
/**
|
|
113
108
|
* Generate connection URL for pg-boss (uses PostgreSQL protocol).
|
|
@@ -182,9 +177,8 @@ function generateConnectionUrls(services, eventsBackend) {
|
|
|
182
177
|
* Generate LocalStack service credentials with LSIA-prefixed access key.
|
|
183
178
|
*/
|
|
184
179
|
function generateLocalStackCredentials() {
|
|
185
|
-
const defaults = SERVICE_DEFAULTS.localstack;
|
|
186
180
|
return {
|
|
187
|
-
...
|
|
181
|
+
...SERVICE_DEFAULTS.localstack,
|
|
188
182
|
password: generateSecurePassword(),
|
|
189
183
|
accessKeyId: generateLocalStackAccessKeyId()
|
|
190
184
|
};
|
|
@@ -216,14 +210,13 @@ function createStageSecrets(stage, services, options) {
|
|
|
216
210
|
};
|
|
217
211
|
const eventsBackend = options?.eventsBackend;
|
|
218
212
|
if (eventsBackend === "sns") serviceCredentials.localstack = generateLocalStackCredentials();
|
|
219
|
-
const urls = generateConnectionUrls(serviceCredentials, eventsBackend);
|
|
220
213
|
return {
|
|
221
214
|
stage,
|
|
222
215
|
createdAt: now,
|
|
223
216
|
updatedAt: now,
|
|
224
217
|
eventsBackend,
|
|
225
218
|
services: serviceCredentials,
|
|
226
|
-
urls,
|
|
219
|
+
urls: generateConnectionUrls(serviceCredentials, eventsBackend),
|
|
227
220
|
custom: {}
|
|
228
221
|
};
|
|
229
222
|
}
|
|
@@ -248,42 +241,42 @@ function rotateServicePassword(secrets, service) {
|
|
|
248
241
|
urls: generateConnectionUrls(newServices, secrets.eventsBackend)
|
|
249
242
|
};
|
|
250
243
|
}
|
|
251
|
-
|
|
252
244
|
//#endregion
|
|
253
|
-
Object.defineProperty(exports,
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
245
|
+
Object.defineProperty(exports, "createStageSecrets", {
|
|
246
|
+
enumerable: true,
|
|
247
|
+
get: function() {
|
|
248
|
+
return createStageSecrets;
|
|
249
|
+
}
|
|
258
250
|
});
|
|
259
|
-
Object.defineProperty(exports,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
251
|
+
Object.defineProperty(exports, "generateConnectionUrls", {
|
|
252
|
+
enumerable: true,
|
|
253
|
+
get: function() {
|
|
254
|
+
return generateConnectionUrls;
|
|
255
|
+
}
|
|
264
256
|
});
|
|
265
|
-
Object.defineProperty(exports,
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
257
|
+
Object.defineProperty(exports, "generatePgBossUrl", {
|
|
258
|
+
enumerable: true,
|
|
259
|
+
get: function() {
|
|
260
|
+
return generatePgBossUrl;
|
|
261
|
+
}
|
|
270
262
|
});
|
|
271
|
-
Object.defineProperty(exports,
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
263
|
+
Object.defineProperty(exports, "generateSecurePassword", {
|
|
264
|
+
enumerable: true,
|
|
265
|
+
get: function() {
|
|
266
|
+
return generateSecurePassword;
|
|
267
|
+
}
|
|
276
268
|
});
|
|
277
|
-
Object.defineProperty(exports,
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
269
|
+
Object.defineProperty(exports, "generateServiceCredentials", {
|
|
270
|
+
enumerable: true,
|
|
271
|
+
get: function() {
|
|
272
|
+
return generateServiceCredentials;
|
|
273
|
+
}
|
|
282
274
|
});
|
|
283
|
-
Object.defineProperty(exports,
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
275
|
+
Object.defineProperty(exports, "rotateServicePassword", {
|
|
276
|
+
enumerable: true,
|
|
277
|
+
get: function() {
|
|
278
|
+
return rotateServicePassword;
|
|
279
|
+
}
|
|
288
280
|
});
|
|
289
|
-
|
|
281
|
+
|
|
282
|
+
//# sourceMappingURL=generator-Wdfp0jCS.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator-Wdfp0jCS.cjs","names":["randomBytes"],"sources":["../src/secrets/generator.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport type { ComposeServiceName, EventsBackend } from '../types';\nimport type { ServiceCredentials, StageSecrets } from './types';\n\n/**\n * Generate a secure random password using URL-safe base64 characters.\n * @param length Password length (default: 32)\n */\nexport function generateSecurePassword(length = 32): string {\n\treturn randomBytes(Math.ceil((length * 3) / 4))\n\t\t.toString('base64url')\n\t\t.slice(0, length);\n}\n\n/** Default service configurations (localhost for local dev via Docker port mapping) */\nconst SERVICE_DEFAULTS: Record<\n\tComposeServiceName,\n\tOmit<ServiceCredentials, 'password'>\n> = {\n\tpostgres: {\n\t\thost: 'localhost',\n\t\tport: 5432,\n\t\tusername: 'app',\n\t\tdatabase: 'app',\n\t},\n\tredis: {\n\t\thost: 'localhost',\n\t\tport: 6379,\n\t\tusername: 'default',\n\t},\n\trabbitmq: {\n\t\thost: 'localhost',\n\t\tport: 5672,\n\t\tusername: 'app',\n\t\tvhost: '/',\n\t},\n\tminio: {\n\t\thost: 'localhost',\n\t\tport: 9000,\n\t\tusername: 'app',\n\t\tbucket: 'app',\n\t},\n\tmailpit: {\n\t\thost: 'localhost',\n\t\tport: 1025,\n\t\tusername: 'app',\n\t},\n\tlocalstack: {\n\t\thost: 'localhost',\n\t\tport: 4566,\n\t\tusername: 'localstack',\n\t\tregion: 'us-east-1',\n\t},\n};\n\n/** Default credentials for pgboss (not a Docker service, reuses postgres) */\nconst PGBOSS_DEFAULTS: Omit<ServiceCredentials, 'password'> = {\n\thost: 'localhost',\n\tport: 5432,\n\tusername: 'pgboss',\n\tdatabase: 'app',\n};\n\n/**\n * Generate credentials for a specific service.\n */\nexport function generateServiceCredentials(\n\tservice: ComposeServiceName,\n): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS[service];\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t};\n}\n\n/**\n * Generate credentials for multiple services.\n */\nexport function generateServicesCredentials(\n\tservices: ComposeServiceName[],\n): StageSecrets['services'] {\n\tconst result: StageSecrets['services'] = {};\n\n\tfor (const service of services) {\n\t\tresult[service] = generateServiceCredentials(service);\n\t}\n\n\treturn result;\n}\n\n/**\n * Generate connection URL for PostgreSQL.\n */\nexport function generatePostgresUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;\n}\n\n/**\n * Generate connection URL for Redis.\n */\nexport function generateRedisUrl(creds: ServiceCredentials): string {\n\tconst { password, host, port } = creds;\n\treturn `redis://:${encodeURIComponent(password)}@${host}:${port}`;\n}\n\n/**\n * Generate connection URL for RabbitMQ.\n */\nexport function generateRabbitmqUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, vhost } = creds;\n\tconst encodedVhost = encodeURIComponent(vhost ?? '/');\n\treturn `amqp://${username}:${encodeURIComponent(password)}@${host}:${port}/${encodedVhost}`;\n}\n\n/**\n * Generate endpoint URL for MinIO (S3-compatible).\n */\nexport function generateMinioEndpoint(creds: ServiceCredentials): string {\n\tconst { host, port } = creds;\n\treturn `http://${host}:${port}`;\n}\n\n/**\n * Generate a LocalStack-compatible access key ID.\n * Must start with 'LSIA' prefix and be at least 20 characters.\n * @see https://docs.localstack.cloud/aws/capabilities/config/credentials/\n */\nexport function generateLocalStackAccessKeyId(): string {\n\tconst suffix = randomBytes(12).toString('base64url').slice(0, 16);\n\treturn `LSIA${suffix}`;\n}\n\n/**\n * Generate connection URL for pg-boss (uses PostgreSQL protocol).\n * Format: pgboss://user:pass@host:port/db?schema=pgboss\n */\nexport function generatePgBossUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `pgboss://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}?schema=pgboss`;\n}\n\n/**\n * Generate event connection strings based on the events backend.\n */\nexport function generateEventConnectionStrings(\n\teventsBackend: EventsBackend,\n\tservices: StageSecrets['services'],\n): { publisher: string; subscriber: string } {\n\tswitch (eventsBackend) {\n\t\tcase 'pgboss': {\n\t\t\tconst creds = services.pgboss;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('pgboss credentials required for pgboss events');\n\t\t\t}\n\t\t\tconst url = generatePgBossUrl(creds);\n\t\t\treturn { publisher: url, subscriber: url };\n\t\t}\n\t\tcase 'sns': {\n\t\t\tconst creds = services.localstack;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('localstack credentials required for sns events');\n\t\t\t}\n\t\t\tconst endpoint = `http://${creds.host}:${creds.port}`;\n\t\t\tconst region = creds.region ?? 'us-east-1';\n\t\t\tconst accessKeyId = creds.accessKeyId ?? creds.username;\n\t\t\tconst secretKey = encodeURIComponent(creds.password);\n\t\t\treturn {\n\t\t\t\tpublisher: `sns://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,\n\t\t\t\tsubscriber: `sqs://${accessKeyId}:${secretKey}@${creds.host}:${creds.port}?region=${region}&endpoint=${encodeURIComponent(endpoint)}`,\n\t\t\t};\n\t\t}\n\t\tcase 'rabbitmq': {\n\t\t\tconst creds = services.rabbitmq;\n\t\t\tif (!creds) {\n\t\t\t\tthrow new Error('rabbitmq credentials required for rabbitmq events');\n\t\t\t}\n\t\t\tconst url = generateRabbitmqUrl(creds);\n\t\t\treturn { publisher: url, subscriber: url };\n\t\t}\n\t}\n}\n\n/**\n * Generate connection URLs from service credentials.\n */\nexport function generateConnectionUrls(\n\tservices: StageSecrets['services'],\n\teventsBackend?: EventsBackend,\n): StageSecrets['urls'] {\n\tconst urls: StageSecrets['urls'] = {};\n\n\tif (services.postgres) {\n\t\turls.DATABASE_URL = generatePostgresUrl(services.postgres);\n\t}\n\n\tif (services.redis) {\n\t\turls.REDIS_URL = generateRedisUrl(services.redis);\n\t}\n\n\tif (services.rabbitmq) {\n\t\turls.RABBITMQ_URL = generateRabbitmqUrl(services.rabbitmq);\n\t}\n\n\tif (services.minio) {\n\t\turls.STORAGE_ENDPOINT = generateMinioEndpoint(services.minio);\n\t}\n\n\tif (eventsBackend) {\n\t\tconst eventUrls = generateEventConnectionStrings(eventsBackend, services);\n\t\turls.EVENT_PUBLISHER_CONNECTION_STRING = eventUrls.publisher;\n\t\turls.EVENT_SUBSCRIBER_CONNECTION_STRING = eventUrls.subscriber;\n\t} else if (services.pgboss) {\n\t\t// Default to pgboss when credentials exist but no explicit events backend\n\t\tconst pgbossUrl = generatePgBossUrl(services.pgboss);\n\t\turls.EVENT_PUBLISHER_CONNECTION_STRING = pgbossUrl;\n\t\turls.EVENT_SUBSCRIBER_CONNECTION_STRING = pgbossUrl;\n\t}\n\n\tif (services.mailpit) {\n\t\turls.SMTP_HOST = services.mailpit.host;\n\t\turls.SMTP_PORT = String(services.mailpit.port);\n\t}\n\n\treturn urls;\n}\n\n/**\n * Generate LocalStack service credentials with LSIA-prefixed access key.\n */\nexport function generateLocalStackCredentials(): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS.localstack;\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t\taccessKeyId: generateLocalStackAccessKeyId(),\n\t};\n}\n\n/**\n * Create a new StageSecrets object with generated credentials.\n * @param stage - The deployment stage (e.g., 'development', 'production')\n * @param services - List of services to generate credentials for\n * @param options - Optional configuration\n * @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')\n * @param options.eventsBackend - Event backend type (pgboss, sns, rabbitmq)\n */\nexport function createStageSecrets(\n\tstage: string,\n\tservices: ComposeServiceName[],\n\toptions?: { projectName?: string; eventsBackend?: EventsBackend },\n): StageSecrets {\n\tconst now = new Date().toISOString();\n\tconst serviceCredentials = generateServicesCredentials(services);\n\n\t// Override service defaults with project-derived names if provided\n\tif (options?.projectName) {\n\t\tif (serviceCredentials.postgres) {\n\t\t\tserviceCredentials.postgres.database = `${options.projectName.replace(/-/g, '_')}_dev`;\n\t\t}\n\t\tif (serviceCredentials.minio) {\n\t\t\tserviceCredentials.minio.bucket = options.projectName;\n\t\t\tserviceCredentials.minio.username = options.projectName;\n\t\t}\n\t}\n\n\t// Always create pgboss credentials when postgres is available.\n\t// pgboss reuses postgres host/port/database but with a dedicated user,\n\t// so subscribers work out of the box without explicit events config.\n\tif (serviceCredentials.postgres && !serviceCredentials.pgboss) {\n\t\tserviceCredentials.pgboss = {\n\t\t\t...PGBOSS_DEFAULTS,\n\t\t\tpassword: generateSecurePassword(),\n\t\t\thost: serviceCredentials.postgres.host,\n\t\t\tport: serviceCredentials.postgres.port,\n\t\t\tdatabase: serviceCredentials.postgres.database,\n\t\t};\n\t}\n\n\t// Generate event-specific credentials\n\tconst eventsBackend = options?.eventsBackend;\n\tif (eventsBackend === 'sns') {\n\t\t// LocalStack credentials with LSIA-prefixed access key\n\t\tserviceCredentials.localstack = generateLocalStackCredentials();\n\t}\n\n\tconst urls = generateConnectionUrls(serviceCredentials, eventsBackend);\n\n\treturn {\n\t\tstage,\n\t\tcreatedAt: now,\n\t\tupdatedAt: now,\n\t\teventsBackend,\n\t\tservices: serviceCredentials,\n\t\turls,\n\t\tcustom: {},\n\t};\n}\n\n/**\n * Rotate password for a specific service.\n */\nexport function rotateServicePassword(\n\tsecrets: StageSecrets,\n\tservice: ComposeServiceName,\n): StageSecrets {\n\tconst currentCreds = secrets.services[service];\n\tif (!currentCreds) {\n\t\tthrow new Error(`Service \"${service}\" not configured in secrets`);\n\t}\n\n\tconst newCreds: ServiceCredentials = {\n\t\t...currentCreds,\n\t\tpassword: generateSecurePassword(),\n\t};\n\n\tconst newServices = {\n\t\t...secrets.services,\n\t\t[service]: newCreds,\n\t};\n\n\treturn {\n\t\t...secrets,\n\t\tupdatedAt: new Date().toISOString(),\n\t\tservices: newServices,\n\t\turls: generateConnectionUrls(newServices, secrets.eventsBackend),\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
const require_config = require("./config-DSXI480r.cjs");
|
|
2
|
+
const require_workspace = require("./workspace-CRD9YinV.cjs");
|
|
3
|
+
const require_storage = require("./storage-DgypfSD6.cjs");
|
|
4
|
+
const require_transfer = require("./transfer-CXoyQZbB.cjs");
|
|
5
|
+
let node_fs = require("node:fs");
|
|
6
|
+
let node_child_process = require("node:child_process");
|
|
7
|
+
let _aws_sdk_client_iam = require("@aws-sdk/client-iam");
|
|
8
|
+
//#region src/deploy/github.ts
|
|
9
|
+
/**
|
|
10
|
+
* `gkm deploy:github` — let GitHub Actions deploy one stage, with no keys.
|
|
11
|
+
*
|
|
12
|
+
* A stage usually lives in its own AWS account — staging in one, production in
|
|
13
|
+
* another — so this runs once per stage, with that account's profile:
|
|
14
|
+
*
|
|
15
|
+
* gkm deploy:github --stage staging --profile acme-dev
|
|
16
|
+
* gkm deploy:github --stage prod --profile acme-prod
|
|
17
|
+
*
|
|
18
|
+
* In the stage's account it makes sure GitHub's OIDC provider exists, and a
|
|
19
|
+
* role that only this repository's `<stage>` environment can assume. On
|
|
20
|
+
* GitHub it creates that environment and gives it what the generated deploy
|
|
21
|
+
* workflow reads: `AWS_ROLE_ARN`, and — when the stage's secrets are kept in
|
|
22
|
+
* the local file — its key as `GKM_SECRETS_KEY`. With `secrets.store` set to
|
|
23
|
+
* SSM the deploy job pulls them with the role instead, so no key is handed to
|
|
24
|
+
* GitHub, and this pushes the stage's local secrets to SSM in the same account,
|
|
25
|
+
* with the same profile. Re-running it converges rather than duplicating.
|
|
26
|
+
*/
|
|
27
|
+
const logger = console;
|
|
28
|
+
const GITHUB_OIDC_HOST = "token.actions.githubusercontent.com";
|
|
29
|
+
const AUDIENCE = "sts.amazonaws.com";
|
|
30
|
+
const defaultGh = (args, input) => (0, node_child_process.execFileSync)("gh", args, {
|
|
31
|
+
encoding: "utf-8",
|
|
32
|
+
...input === void 0 ? {} : {
|
|
33
|
+
input,
|
|
34
|
+
stdio: [
|
|
35
|
+
"pipe",
|
|
36
|
+
"pipe",
|
|
37
|
+
"pipe"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}).trim();
|
|
41
|
+
/** The role a stage deploys as: `<project>-github-<stage>`, within IAM's 64. */
|
|
42
|
+
function roleName(project, stage) {
|
|
43
|
+
return `${project}-github-${stage}`.toLowerCase().replace(/[^a-z0-9+=,.@_-]/g, "-").slice(0, 64);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Who may assume the role: a workflow job running in this repository's
|
|
47
|
+
* `<stage>` environment, and nothing else — not another branch's job, not
|
|
48
|
+
* another environment's. That is what keeps a staging deploy from using the
|
|
49
|
+
* production role.
|
|
50
|
+
*/
|
|
51
|
+
function trustPolicy(providerArn, repo, stage) {
|
|
52
|
+
return JSON.stringify({
|
|
53
|
+
Version: "2012-10-17",
|
|
54
|
+
Statement: [{
|
|
55
|
+
Effect: "Allow",
|
|
56
|
+
Principal: { Federated: providerArn },
|
|
57
|
+
Action: "sts:AssumeRoleWithWebIdentity",
|
|
58
|
+
Condition: { StringEquals: {
|
|
59
|
+
[`${GITHUB_OIDC_HOST}:aud`]: AUDIENCE,
|
|
60
|
+
[`${GITHUB_OIDC_HOST}:sub`]: `repo:${repo}:environment:${stage}`
|
|
61
|
+
} }
|
|
62
|
+
}]
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** GitHub's OIDC provider in this account, created if it is not there. */
|
|
66
|
+
async function ensureOidcProvider(iam) {
|
|
67
|
+
const { OpenIDConnectProviderList = [] } = await iam.send(new _aws_sdk_client_iam.ListOpenIDConnectProvidersCommand({}));
|
|
68
|
+
const existing = OpenIDConnectProviderList.find((p) => p.Arn?.endsWith(`oidc-provider/${GITHUB_OIDC_HOST}`))?.Arn;
|
|
69
|
+
if (existing) return existing;
|
|
70
|
+
return (await iam.send(new _aws_sdk_client_iam.CreateOpenIDConnectProviderCommand({
|
|
71
|
+
Url: `https://${GITHUB_OIDC_HOST}`,
|
|
72
|
+
ClientIDList: [AUDIENCE]
|
|
73
|
+
}))).OpenIDConnectProviderArn;
|
|
74
|
+
}
|
|
75
|
+
/** The deploy role, created or brought back to this trust and policy. */
|
|
76
|
+
async function ensureRole(iam, name, trust, policyArn) {
|
|
77
|
+
let arn;
|
|
78
|
+
try {
|
|
79
|
+
const { Role } = await iam.send(new _aws_sdk_client_iam.GetRoleCommand({ RoleName: name }));
|
|
80
|
+
await iam.send(new _aws_sdk_client_iam.UpdateAssumeRolePolicyCommand({
|
|
81
|
+
RoleName: name,
|
|
82
|
+
PolicyDocument: trust
|
|
83
|
+
}));
|
|
84
|
+
arn = Role.Arn;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error.name !== "NoSuchEntityException") throw error;
|
|
87
|
+
const { Role } = await iam.send(new _aws_sdk_client_iam.CreateRoleCommand({
|
|
88
|
+
RoleName: name,
|
|
89
|
+
AssumeRolePolicyDocument: trust,
|
|
90
|
+
Description: "Deploys from GitHub Actions (gkm deploy:github)",
|
|
91
|
+
MaxSessionDuration: 7200
|
|
92
|
+
}));
|
|
93
|
+
arn = Role.Arn;
|
|
94
|
+
}
|
|
95
|
+
await iam.send(new _aws_sdk_client_iam.AttachRolePolicyCommand({
|
|
96
|
+
RoleName: name,
|
|
97
|
+
PolicyArn: policyArn
|
|
98
|
+
}));
|
|
99
|
+
return arn;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* An IAM client for the stage's account.
|
|
103
|
+
*
|
|
104
|
+
* `fromIni` for a named profile, deliberately: it resolves exactly that
|
|
105
|
+
* profile — keys, assume-role, `credential_process` or SSO, whichever
|
|
106
|
+
* ~/.aws/config says — and never falls back to AWS_* in the environment. The
|
|
107
|
+
* provider chain checks the environment first, so exported staging keys would
|
|
108
|
+
* quietly win over `--profile prod` and the production role would land in the
|
|
109
|
+
* staging account.
|
|
110
|
+
*/
|
|
111
|
+
async function iamFor(profile, endpoint) {
|
|
112
|
+
const config = { region: "us-east-1" };
|
|
113
|
+
if (profile) {
|
|
114
|
+
const { fromIni } = await import("@aws-sdk/credential-providers");
|
|
115
|
+
config.credentials = fromIni({ profile });
|
|
116
|
+
}
|
|
117
|
+
if (endpoint) {
|
|
118
|
+
config.endpoint = endpoint;
|
|
119
|
+
config.credentials = {
|
|
120
|
+
accessKeyId: "test",
|
|
121
|
+
secretAccessKey: "test"
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return new _aws_sdk_client_iam.IAMClient(config);
|
|
125
|
+
}
|
|
126
|
+
async function deployGithubCommand(options, deps = {}) {
|
|
127
|
+
const gh = deps.gh ?? defaultGh;
|
|
128
|
+
const { workspace } = await require_config.loadWorkspaceConfig(deps.cwd);
|
|
129
|
+
require_workspace.assertDeployedStage(workspace.stages, options.stage);
|
|
130
|
+
const repo = options.repo ?? gh([
|
|
131
|
+
"repo",
|
|
132
|
+
"view",
|
|
133
|
+
"--json",
|
|
134
|
+
"nameWithOwner",
|
|
135
|
+
"-q",
|
|
136
|
+
".nameWithOwner"
|
|
137
|
+
]);
|
|
138
|
+
const role = roleName(workspace.name, options.stage);
|
|
139
|
+
const policyArn = options.policyArn ?? "arn:aws:iam::aws:policy/AdministratorAccess";
|
|
140
|
+
const keyPath = require_storage.getKeyPath(options.stage, workspace.name);
|
|
141
|
+
const hasKey = (0, node_fs.existsSync)(keyPath);
|
|
142
|
+
const remote = require_transfer.isRemoteStore(workspace, options.stage);
|
|
143
|
+
const hasSecrets = require_storage.secretsExist(options.stage, workspace.root);
|
|
144
|
+
const via = options.profile ? `profile "${options.profile}"` : "the default credentials";
|
|
145
|
+
logger.log(`\n🔐 GitHub → AWS for stage "${options.stage}"\n`);
|
|
146
|
+
logger.log(` Repository: ${repo}`);
|
|
147
|
+
logger.log(` AWS profile: ${options.profile ?? "(default credentials)"}`);
|
|
148
|
+
logger.log(` Role: ${role}`);
|
|
149
|
+
logger.log(` Policy: ${policyArn}${options.policyArn ? "" : " (default; --policy-arn to narrow)"}`);
|
|
150
|
+
logger.log(` Trusted by: repo:${repo}:environment:${options.stage} only`);
|
|
151
|
+
if (remote) logger.log(` Secrets: ${hasSecrets ? `pushed to the store with ${via}` : `none on this machine — gkm secrets:push --stage ${options.stage} later`}`);
|
|
152
|
+
else logger.log(` Secrets key: ${hasKey ? keyPath : `none at ${keyPath} — run gkm secrets:init --stage ${options.stage} first`}`);
|
|
153
|
+
if (options.dryRun) {
|
|
154
|
+
logger.log("\n --dry-run: nothing changed.\n");
|
|
155
|
+
return { repo };
|
|
156
|
+
}
|
|
157
|
+
const iam = deps.iam ?? await iamFor(options.profile);
|
|
158
|
+
const roleArn = await ensureRole(iam, role, trustPolicy(await ensureOidcProvider(iam).catch((error) => {
|
|
159
|
+
if (/SSO session/i.test(String(error?.message)) && options.profile) throw new SsoSessionExpired(options.profile);
|
|
160
|
+
throw error;
|
|
161
|
+
}), repo, options.stage), policyArn);
|
|
162
|
+
logger.log(`\n ✓ AWS: ${roleArn}`);
|
|
163
|
+
gh([
|
|
164
|
+
"api",
|
|
165
|
+
"--method",
|
|
166
|
+
"PUT",
|
|
167
|
+
`repos/${repo}/environments/${options.stage}`
|
|
168
|
+
]);
|
|
169
|
+
gh([
|
|
170
|
+
"variable",
|
|
171
|
+
"set",
|
|
172
|
+
"AWS_ROLE_ARN",
|
|
173
|
+
"--env",
|
|
174
|
+
options.stage,
|
|
175
|
+
"--repo",
|
|
176
|
+
repo,
|
|
177
|
+
"--body",
|
|
178
|
+
roleArn
|
|
179
|
+
]);
|
|
180
|
+
logger.log(` ✓ GitHub: environment "${options.stage}", AWS_ROLE_ARN`);
|
|
181
|
+
if (remote) {
|
|
182
|
+
if (hasSecrets) {
|
|
183
|
+
await require_transfer.pushStageSecrets(workspace, options.stage, { ...options.profile ? { profile: options.profile } : {} });
|
|
184
|
+
logger.log(` ✓ Secrets: "${options.stage}" pushed to the store with ${via}`);
|
|
185
|
+
} else logger.log(` ⚠ No secrets for "${options.stage}" on this machine; the deploy job pulls them from the store, so push them: gkm secrets:push --stage ${options.stage}${options.profile ? ` --profile ${options.profile}` : ""}.`);
|
|
186
|
+
} else if (hasKey) {
|
|
187
|
+
gh([
|
|
188
|
+
"secret",
|
|
189
|
+
"set",
|
|
190
|
+
"GKM_SECRETS_KEY",
|
|
191
|
+
"--env",
|
|
192
|
+
options.stage,
|
|
193
|
+
"--repo",
|
|
194
|
+
repo
|
|
195
|
+
], (0, node_fs.readFileSync)(keyPath, "utf-8").trim());
|
|
196
|
+
logger.log(" ✓ GitHub: GKM_SECRETS_KEY");
|
|
197
|
+
} else logger.log(` ⚠ No secrets key for "${options.stage}"; run gkm secrets:init --stage ${options.stage}, then this again.`);
|
|
198
|
+
logger.log(`\n Protect it: add required reviewers to the "${options.stage}" environment in the repository settings.\n`);
|
|
199
|
+
return {
|
|
200
|
+
roleArn,
|
|
201
|
+
repo
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** The profile's SSO login has lapsed; the fix is one command away. */
|
|
205
|
+
var SsoSessionExpired = class extends Error {
|
|
206
|
+
profile;
|
|
207
|
+
constructor(profile) {
|
|
208
|
+
super(`The SSO session for profile "${profile}" has expired or is invalid. Run: aws sso login --profile ${profile}`);
|
|
209
|
+
this.profile = profile;
|
|
210
|
+
this.name = "SsoSessionExpired";
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
//#endregion
|
|
214
|
+
exports.deployGithubCommand = deployGithubCommand;
|
|
215
|
+
|
|
216
|
+
//# sourceMappingURL=github-Bho-C6Zg.cjs.map
|