@geekmidas/cli 10.0.0-alpha.87 → 10.0.0-alpha.89
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/GoDaddyProvider--36rw8cP.mjs +387 -0
- package/dist/GoDaddyProvider--36rw8cP.mjs.map +1 -0
- package/dist/GoDaddyProvider-DnRor066.cjs +387 -0
- package/dist/GoDaddyProvider-DnRor066.cjs.map +1 -0
- package/dist/{HostingerProvider-CVc3rLPB.mjs → HostingerProvider-BI6J7oJr.mjs} +2 -2
- package/dist/{HostingerProvider-CVc3rLPB.mjs.map → HostingerProvider-BI6J7oJr.mjs.map} +1 -1
- package/dist/{HostingerProvider-BCB1QJj1.cjs → HostingerProvider-CyTpG_jy.cjs} +2 -2
- package/dist/{HostingerProvider-BCB1QJj1.cjs.map → HostingerProvider-CyTpG_jy.cjs.map} +1 -1
- package/dist/{LegacyStateStore-CWG_HVC0.cjs → LegacyStateStore-BaBoYl59.cjs} +2 -2
- package/dist/{LegacyStateStore-CWG_HVC0.cjs.map → LegacyStateStore-BaBoYl59.cjs.map} +1 -1
- package/dist/{LegacyStateStore-B0R4LlX_.mjs → LegacyStateStore-CO7bzJCU.mjs} +2 -2
- package/dist/{LegacyStateStore-B0R4LlX_.mjs.map → LegacyStateStore-CO7bzJCU.mjs.map} +1 -1
- package/dist/{LocalStateStore-CLwa2DpK.cjs → LocalStateStore-CUWh2BWU.cjs} +2 -2
- package/dist/{LocalStateStore-CLwa2DpK.cjs.map → LocalStateStore-CUWh2BWU.cjs.map} +1 -1
- package/dist/{LocalStateStore-Q4vXg2ua.mjs → LocalStateStore-C_hYDv6I.mjs} +2 -2
- package/dist/{LocalStateStore-Q4vXg2ua.mjs.map → LocalStateStore-C_hYDv6I.mjs.map} +1 -1
- package/dist/{S3StateStore-Cea9_5gF.cjs → S3StateStore-O8EYfjfG.cjs} +2 -2
- package/dist/{S3StateStore-Cea9_5gF.cjs.map → S3StateStore-O8EYfjfG.cjs.map} +1 -1
- package/dist/{S3StateStore-CksKYKRa.mjs → S3StateStore-hIm8Rrmu.mjs} +2 -2
- package/dist/{S3StateStore-CksKYKRa.mjs.map → S3StateStore-hIm8Rrmu.mjs.map} +1 -1
- package/dist/{SSMStateStore-aBOTF018.cjs → SSMStateStore-DIzbiqCz.cjs} +2 -2
- package/dist/{SSMStateStore-aBOTF018.cjs.map → SSMStateStore-DIzbiqCz.cjs.map} +1 -1
- package/dist/{SSMStateStore-BszSCAS2.mjs → SSMStateStore-IvcjTk8h.mjs} +2 -2
- package/dist/{SSMStateStore-BszSCAS2.mjs.map → SSMStateStore-IvcjTk8h.mjs.map} +1 -1
- package/dist/SubscriberGenerator-BPqQf8fB.mjs.map +1 -1
- package/dist/SubscriberGenerator-BVn7cQ-2.cjs.map +1 -1
- package/dist/{add-5MZAXybv.cjs → add-C9qX4Juc.cjs} +30 -7
- package/dist/add-C9qX4Juc.cjs.map +1 -0
- package/dist/{add-D_jNqnfZ.mjs → add-v3tCbrpE.mjs} +30 -7
- package/dist/add-v3tCbrpE.mjs.map +1 -0
- package/dist/{bundler-1REMhKps.cjs → bundler-C4NmpRTD.cjs} +5 -5
- package/dist/{bundler-1REMhKps.cjs.map → bundler-C4NmpRTD.cjs.map} +1 -1
- package/dist/{bundler-BMo-JP5s.mjs → bundler-C_fCgcVO.mjs} +5 -5
- package/dist/{bundler-BMo-JP5s.mjs.map → bundler-C_fCgcVO.mjs.map} +1 -1
- package/dist/{cli-BL8ZHsLQ.cjs → cli-C85Dut8K.cjs} +6 -5
- package/dist/cli-C85Dut8K.cjs.map +1 -0
- package/dist/{cli-7edNFwGk.mjs → cli-REdO9X5b.mjs} +7 -6
- package/dist/cli-REdO9X5b.mjs.map +1 -0
- package/dist/{compose-xw86kVYi.cjs → compose-BvrhDwJ6.cjs} +556 -30
- package/dist/compose-BvrhDwJ6.cjs.map +1 -0
- package/dist/{compose-DbJhRQlh.mjs → compose-Dn0oAgQ0.mjs} +509 -31
- package/dist/compose-Dn0oAgQ0.mjs.map +1 -0
- package/dist/{config-DrCAyoH5.d.mts → config-CdEcTrjG.d.mts} +3 -3
- package/dist/{config-CpjsVxru.d.cts.map → config-CdEcTrjG.d.mts.map} +1 -1
- package/dist/{config-CpjsVxru.d.cts → config-pST7E0CS.d.cts} +3 -3
- package/dist/{config-DrCAyoH5.d.mts.map → config-pST7E0CS.d.cts.map} +1 -1
- package/dist/config.cjs +1 -1
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +1 -1
- package/dist/{credentials-tr26-KML.cjs → credentials-BKfIugu2.cjs} +47 -2
- package/dist/credentials-BKfIugu2.cjs.map +1 -0
- package/dist/{credentials-BgJ5mS_d.mjs → credentials-CnVqoXMb.mjs} +30 -3
- package/dist/credentials-CnVqoXMb.mjs.map +1 -0
- package/dist/databases-BY-pEImp.cjs.map +1 -1
- package/dist/databases-CzszQV0N.mjs.map +1 -1
- package/dist/deploy/public.cjs +10 -10
- package/dist/deploy/public.d.cts +9 -3
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +9 -3
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +10 -10
- package/dist/{deploy-BTfmXd3g.mjs → deploy-BaERRh0Q.mjs} +10 -8
- package/dist/deploy-BaERRh0Q.mjs.map +1 -0
- package/dist/{deploy-CtbZj4zN.cjs → deploy-DbBGo3a6.cjs} +10 -8
- package/dist/deploy-DbBGo3a6.cjs.map +1 -0
- package/dist/{dokploy-r69yZaIt.mjs → dokploy-CMmD_ddX.mjs} +2 -2
- package/dist/{dokploy-r69yZaIt.mjs.map → dokploy-CMmD_ddX.mjs.map} +1 -1
- package/dist/{dokploy-TGk3oPwr.cjs → dokploy-DwAaKA9B.cjs} +2 -2
- package/dist/{dokploy-TGk3oPwr.cjs.map → dokploy-DwAaKA9B.cjs.map} +1 -1
- package/dist/{encryption-C-8Tt5Hh.mjs → encryption-BQmhq9_y.mjs} +3 -3
- package/dist/{encryption-C-8Tt5Hh.mjs.map → encryption-BQmhq9_y.mjs.map} +1 -1
- package/dist/{encryption-CDn7BH5O.cjs → encryption-BrN_9_CM.cjs} +3 -3
- package/dist/{encryption-CDn7BH5O.cjs.map → encryption-BrN_9_CM.cjs.map} +1 -1
- package/dist/{engine-jAINgiOB.mjs → engine-DILocOOH.mjs} +23 -404
- package/dist/engine-DILocOOH.mjs.map +1 -0
- package/dist/{engine-Bt5k_UMq.cjs → engine-DgHoFBMw.cjs} +25 -406
- package/dist/engine-DgHoFBMw.cjs.map +1 -0
- package/dist/{github-DFhuJQlp.cjs → github-Bf7pz6AV.cjs} +3 -3
- package/dist/{github-DFhuJQlp.cjs.map → github-Bf7pz6AV.cjs.map} +1 -1
- package/dist/{github-CZHAtwPT.mjs → github-xzg99wz8.mjs} +3 -3
- package/dist/{github-CZHAtwPT.mjs.map → github-xzg99wz8.mjs.map} +1 -1
- package/dist/{index-MnUJuUlj.d.mts → index-D-F_hTDz.d.cts} +21 -4
- package/dist/index-D-F_hTDz.d.cts.map +1 -0
- package/dist/{index-elsLJTUv.d.cts → index-YFNxR5Ns.d.mts} +21 -4
- package/dist/index-YFNxR5Ns.d.mts.map +1 -0
- package/dist/index.cjs +305 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +306 -71
- package/dist/index.mjs.map +1 -1
- package/dist/{local-Cq8qdgNX.cjs → local-BjxDfPvy.cjs} +2 -2
- package/dist/{local-Cq8qdgNX.cjs.map → local-BjxDfPvy.cjs.map} +1 -1
- package/dist/{local-Ciq-bTPr.mjs → local-IuoIF3CT.mjs} +2 -2
- package/dist/{local-Ciq-bTPr.mjs.map → local-IuoIF3CT.mjs.map} +1 -1
- package/dist/{migrate-B1rVlwj1.cjs → migrate-7xayVtzL.cjs} +3 -3
- package/dist/{migrate-B1rVlwj1.cjs.map → migrate-7xayVtzL.cjs.map} +1 -1
- package/dist/{migrate-ggkPVJRC.mjs → migrate-CKLJ73OO.mjs} +4 -4
- package/dist/{migrate-ggkPVJRC.mjs.map → migrate-CKLJ73OO.mjs.map} +1 -1
- package/dist/{migrate-Dn7nK6sh.mjs → migrate-CpmO5qnl.mjs} +3 -3
- package/dist/{migrate-Dn7nK6sh.mjs.map → migrate-CpmO5qnl.mjs.map} +1 -1
- package/dist/{migrate-B16qgYTn.cjs → migrate-DPF7ZF8M.cjs} +3 -3
- package/dist/{migrate-B16qgYTn.cjs.map → migrate-DPF7ZF8M.cjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/ready-BpNbORRB.cjs.map +1 -1
- package/dist/ready-b6vm0-W-.mjs.map +1 -1
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/{reconcile--I7V4Klj.mjs → reconcile-Dh1Jk7WE.mjs} +2 -2
- package/dist/{reconcile--I7V4Klj.mjs.map → reconcile-Dh1Jk7WE.mjs.map} +1 -1
- package/dist/{resolve-BSke45k6.mjs → resolve-Dv_weZoh.mjs} +4 -4
- package/dist/{resolve-BSke45k6.mjs.map → resolve-Dv_weZoh.mjs.map} +1 -1
- package/dist/{resolve-BXeXzpmU.cjs → resolve-dlKPFNgv.cjs} +4 -4
- package/dist/{resolve-BXeXzpmU.cjs.map → resolve-dlKPFNgv.cjs.map} +1 -1
- package/dist/{rollback-DFMiBWf0.cjs → rollback-65VVIdBM.cjs} +6 -6
- package/dist/{rollback-DFMiBWf0.cjs.map → rollback-65VVIdBM.cjs.map} +1 -1
- package/dist/{rollback-CEE6dse4.mjs → rollback-DpSU2DsD.mjs} +6 -6
- package/dist/{rollback-CEE6dse4.mjs.map → rollback-DpSU2DsD.mjs.map} +1 -1
- package/dist/{runtime-HabiNBtJ.d.mts → runtime-CN_kmqxo.d.cts} +2 -2
- package/dist/{runtime-D48ZKk8_.d.cts.map → runtime-CN_kmqxo.d.cts.map} +1 -1
- package/dist/{runtime-D48ZKk8_.d.cts → runtime-Cjvon3oj.d.mts} +2 -2
- package/dist/{runtime-HabiNBtJ.d.mts.map → runtime-Cjvon3oj.d.mts.map} +1 -1
- package/dist/{seeds-CsI0A_Ef.cjs → seeds-CN6tB-va.cjs} +646 -187
- package/dist/{seeds-CsI0A_Ef.cjs.map → seeds-CN6tB-va.cjs.map} +1 -1
- package/dist/{seeds-BYdrtZsC.mjs → seeds-perzdN81.mjs} +599 -164
- package/dist/seeds-perzdN81.mjs.map +1 -0
- package/dist/{sniffer-B0EU73bj.mjs → sniffer-BTtX3NUM.mjs} +3 -3
- package/dist/{sniffer-B0EU73bj.mjs.map → sniffer-BTtX3NUM.mjs.map} +1 -1
- package/dist/{sniffer-DJYupBeK.cjs → sniffer-IAQEpsQb.cjs} +3 -3
- package/dist/{sniffer-DJYupBeK.cjs.map → sniffer-IAQEpsQb.cjs.map} +1 -1
- package/dist/{sst-DAqUoZKG.cjs → sst-BgcOsaBi.cjs} +3 -3
- package/dist/{sst-DAqUoZKG.cjs.map → sst-BgcOsaBi.cjs.map} +1 -1
- package/dist/{sst-D1Qj4sIs.mjs → sst-CJzvyrEz.mjs} +3 -3
- package/dist/{sst-D1Qj4sIs.mjs.map → sst-CJzvyrEz.mjs.map} +1 -1
- package/dist/{stages-Brn3vcdy.cjs → stages-CUn6gvXL.cjs} +2 -2
- package/dist/{stages-Brn3vcdy.cjs.map → stages-CUn6gvXL.cjs.map} +1 -1
- package/dist/{stages-DoHHz6pS.mjs → stages-CnZRYmkI.mjs} +2 -2
- package/dist/{stages-DoHHz6pS.mjs.map → stages-CnZRYmkI.mjs.map} +1 -1
- package/dist/target/public.cjs +1 -1
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +1 -1
- package/dist/{terminal-BTbZlrFV.mjs → terminal-D8TPK7o9.mjs} +3 -3
- package/dist/{terminal-BTbZlrFV.mjs.map → terminal-D8TPK7o9.mjs.map} +1 -1
- package/dist/{terminal-COv23heb.cjs → terminal-DuapRxh6.cjs} +2 -2
- package/dist/{terminal-COv23heb.cjs.map → terminal-DuapRxh6.cjs.map} +1 -1
- package/dist/{types-0oAyFOfR.d.cts → types-NqX1Hz4P.d.cts} +210 -205
- package/dist/types-NqX1Hz4P.d.cts.map +1 -0
- package/dist/{types-BZ01tZs-.d.mts → types-ZHuZ89hv.d.mts} +210 -205
- package/dist/types-ZHuZ89hv.d.mts.map +1 -0
- package/dist/workspace/index.cjs +3 -1
- package/dist/workspace/index.d.cts +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +2 -2
- package/dist/{workspace-D2CYLMCW.mjs → workspace-CJ0lKFq-.mjs} +231 -10
- package/dist/workspace-CJ0lKFq-.mjs.map +1 -0
- package/dist/{workspace-CT0JM3Y8.cjs → workspace-CwW3Gkru.cjs} +266 -9
- package/dist/workspace-CwW3Gkru.cjs.map +1 -0
- package/package.json +15 -15
- package/dist/add-5MZAXybv.cjs.map +0 -1
- package/dist/add-D_jNqnfZ.mjs.map +0 -1
- package/dist/cli-7edNFwGk.mjs.map +0 -1
- package/dist/cli-BL8ZHsLQ.cjs.map +0 -1
- package/dist/compose-DbJhRQlh.mjs.map +0 -1
- package/dist/compose-xw86kVYi.cjs.map +0 -1
- package/dist/credentials-BgJ5mS_d.mjs.map +0 -1
- package/dist/credentials-tr26-KML.cjs.map +0 -1
- package/dist/deploy-BTfmXd3g.mjs.map +0 -1
- package/dist/deploy-CtbZj4zN.cjs.map +0 -1
- package/dist/engine-Bt5k_UMq.cjs.map +0 -1
- package/dist/engine-jAINgiOB.mjs.map +0 -1
- package/dist/index-MnUJuUlj.d.mts.map +0 -1
- package/dist/index-elsLJTUv.d.cts.map +0 -1
- package/dist/seeds-BYdrtZsC.mjs.map +0 -1
- package/dist/types-0oAyFOfR.d.cts.map +0 -1
- package/dist/types-BZ01tZs-.d.mts.map +0 -1
- package/dist/workspace-CT0JM3Y8.cjs.map +0 -1
- package/dist/workspace-D2CYLMCW.mjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_credentials = require("./credentials-
|
|
1
|
+
const require_credentials = require("./credentials-BKfIugu2.cjs");
|
|
2
2
|
const require_sandbox = require("./sandbox-CL--ZDui.cjs");
|
|
3
3
|
let node_path = require("node:path");
|
|
4
4
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -238,4 +238,4 @@ Object.defineProperty(exports, "allowlistedEnv", {
|
|
|
238
238
|
}
|
|
239
239
|
});
|
|
240
240
|
|
|
241
|
-
//# sourceMappingURL=local-
|
|
241
|
+
//# sourceMappingURL=local-BjxDfPvy.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-Cq8qdgNX.cjs","names":["resolve","confineCwd","SECRETS_DIR_ENV","rm","mkdtemp","join","tmpdir","writeFile","spawn","CommandTimedOut"],"sources":["../src/sandbox/env.ts","../src/sandbox/local.ts"],"sourcesContent":["/**\n * What of the host's environment a local sandbox passes on.\n *\n * An allowlist, not a denylist: a credential's variable has any name its\n * owner chose (`DOKPLOY_TOKEN`, `AWS_SECRET_ACCESS_KEY`, `MY_VAULT_TOKEN`), so\n * the only safe default is to name what a program needs to *run* and pass\n * nothing else.\n */\n\n/**\n * Variables a command needs to find programs, write temp files, print in the\n * user's locale and reach the network through the host's proxy — and nothing\n * that authenticates as anyone.\n *\n * Deliberately left out:\n * - `NODE_OPTIONS`: it can `--require` or `--import` code into every node\n * process, and CI often holds `--import tsx` there. A step that needs a\n * loader passes its own.\n * - `NODE_AUTH_TOKEN`, `NPM_TOKEN`, `npm_config_*`: registry tokens\n * (`actions/setup-node` writes `NODE_AUTH_TOKEN`), which is why there is no\n * `NODE_*` wildcard.\n * - `AWS_*`, `DOCKER_*`, `DOKPLOY_*`, `GITHUB_TOKEN`, `TURBO_TOKEN`, `GKM_*`:\n * credentials, or where the deploy keeps them.\n * - `SHELL`, `EDITOR` and the rest of a login session: nothing here starts a\n * shell.\n *\n * Proxies are passed: behind one, nothing installs or builds without it. A\n * proxy URL with a password in it reaches the command too — a host that keeps\n * one there and builds code it does not trust should pass a sandbox of its\n * own.\n */\nexport const SANDBOX_ENV_ALLOWLIST: readonly (string | RegExp)[] = [\n\t// Finding programs and a home to cache in.\n\t'PATH',\n\t'HOME',\n\t'USER',\n\t'LOGNAME',\n\t'TMPDIR',\n\t'TMP',\n\t'TEMP',\n\t// Locale and terminal: output a person reads should look the same.\n\t'LANG',\n\t'LANGUAGE',\n\t'TZ',\n\t/^LC_[A-Z]+$/,\n\t'TERM',\n\t'COLORTERM',\n\t'NO_COLOR',\n\t'FORCE_COLOR',\n\t'CI',\n\t// Node: the mode a build runs in, and the CAs a corporate network adds.\n\t'NODE_ENV',\n\t'NODE_EXTRA_CA_CERTS',\n\t// Package managers' own homes, so a build reuses the host's store.\n\t'COREPACK_HOME',\n\t'PNPM_HOME',\n\t'XDG_CACHE_HOME',\n\t'XDG_DATA_HOME',\n\t// The network, through the host's proxy.\n\t'HTTP_PROXY',\n\t'HTTPS_PROXY',\n\t'NO_PROXY',\n\t'http_proxy',\n\t'https_proxy',\n\t'no_proxy',\n\t// Windows: without these, nothing starts.\n\t'SystemRoot',\n\t'SYSTEMROOT',\n\t'windir',\n\t'WINDIR',\n\t'ComSpec',\n\t'COMSPEC',\n\t'PATHEXT',\n\t'USERPROFILE',\n\t'APPDATA',\n\t'LOCALAPPDATA',\n];\n\nfunction allowed(name: string): boolean {\n\treturn SANDBOX_ENV_ALLOWLIST.some((entry) =>\n\t\ttypeof entry === 'string' ? entry === name : entry.test(name),\n\t);\n}\n\n/**\n * The allowlisted part of `source`, plus each of `extra` the host chose to\n * pass on by name.\n */\nexport function allowlistedEnv(\n\tsource: NodeJS.ProcessEnv = process.env,\n\textra: readonly string[] = [],\n): Record<string, string> {\n\tconst env: Record<string, string> = {};\n\tfor (const [name, value] of Object.entries(source)) {\n\t\tif (value === undefined) continue;\n\t\tif (allowed(name) || extra.includes(name)) env[name] = value;\n\t}\n\treturn env;\n}\n","import { type SpawnOptions, spawn } from 'node:child_process';\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { CommandTimedOut } from '../run';\nimport { allowlistedEnv } from './env';\nimport {\n\tassertSecretNames,\n\tconfineCwd,\n\ttype Sandbox,\n\ttype SandboxExecOptions,\n\ttype SandboxOutput,\n\ttype SandboxResult,\n\tSECRETS_DIR_ENV,\n} from './sandbox';\n\n/** How long a timed-out command has to exit on SIGTERM before SIGKILL. */\nconst KILL_GRACE_MS = 5_000;\n\nexport interface LocalSandboxOptions {\n\t/** The project. Every command runs at or below it. */\n\troot: string;\n\t/**\n\t * `HOME` for every command. Defaults to the host's, because a build\n\t * reuses the caches kept there — the pnpm store, corepack's package\n\t * managers, turbo's cache — and a cold one re-downloads all of them.\n\t * Point it at an empty directory to keep `~/.aws`, `~/.npmrc` and\n\t * `~/.docker` out of reach of an SDK's default lookup — though, sharing\n\t * the host's filesystem, a command that goes looking still finds them.\n\t */\n\thome?: string;\n\t/**\n\t * Host variables to pass on beyond the allowlist, by name — a remote\n\t * cache's `TURBO_TOKEN`, for a build the host trusts with it.\n\t */\n\tpassEnv?: readonly string[];\n\t/** Where the allowlisted variables are read from. Defaults to `process.env`. */\n\thostEnv?: NodeJS.ProcessEnv;\n}\n\n/** Where `output` sends a child's streams. */\nconst STDIO: Record<SandboxOutput, SpawnOptions['stdio']> = {\n\tcapture: ['ignore', 'pipe', 'pipe'],\n\tinherit: 'inherit',\n\tstderr: ['ignore', 2, 2],\n\tignore: 'ignore',\n};\n\n/**\n * A child process on this machine, as this user, with an allowlisted\n * environment.\n *\n * What it withholds from the command: the host's environment beyond\n * {@link SANDBOX_ENV_ALLOWLIST}, a shell, a working directory outside the\n * project, and any time past the timeout. What it does not: the host's\n * filesystem, which the command can read and write as the user running the\n * deploy. So it is not `isolating`, and it is the default for projects the\n * host already trusts — the developer's own, a team's own CI. Code nobody\n * vetted belongs in a sandbox that is.\n *\n * A timed-out command is sent SIGTERM, then SIGKILL; processes it started\n * itself are its to stop.\n */\nexport class LocalSandbox implements Sandbox {\n\treadonly root: string;\n\treadonly isolating = false;\n\treadonly env: Readonly<Record<string, string>>;\n\n\tconstructor(options: LocalSandboxOptions) {\n\t\tthis.root = resolve(options.root);\n\t\tthis.env = {\n\t\t\t...allowlistedEnv(options.hostEnv ?? process.env, options.passEnv),\n\t\t\t...(options.home\n\t\t\t\t? { HOME: options.home, USERPROFILE: options.home }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\tasync exec(\n\t\tcommand: string,\n\t\targs: readonly string[],\n\t\toptions: SandboxExecOptions,\n\t): Promise<SandboxResult> {\n\t\tconst cwd = confineCwd(this.root, options.cwd);\n\t\tassertSecretNames(options.secrets);\n\t\toptions.signal?.throwIfAborted();\n\n\t\tconst secretsDir = await mountSecrets(options.secrets);\n\t\ttry {\n\t\t\treturn await spawnConfined(command, args, {\n\t\t\t\t...options,\n\t\t\t\tcwd,\n\t\t\t\tenv: secretsDir\n\t\t\t\t\t? { ...options.env, [SECRETS_DIR_ENV]: secretsDir }\n\t\t\t\t\t: options.env,\n\t\t\t});\n\t\t} finally {\n\t\t\tif (secretsDir) await rm(secretsDir, { recursive: true, force: true });\n\t\t}\n\t}\n}\n\n/**\n * Each secret as a 0600 file in a fresh 0700 directory outside the project —\n * where nothing the build writes, packs or commits will pick it up.\n */\nasync function mountSecrets(\n\tsecrets: Readonly<Record<string, string>> | undefined,\n): Promise<string | undefined> {\n\tif (!secrets || Object.keys(secrets).length === 0) return undefined;\n\n\tconst dir = await mkdtemp(join(tmpdir(), 'gkm-secrets-'));\n\tfor (const [name, value] of Object.entries(secrets)) {\n\t\tawait writeFile(join(dir, name), value, { mode: 0o600 });\n\t}\n\treturn dir;\n}\n\nfunction spawnConfined(\n\tcommand: string,\n\targs: readonly string[],\n\toptions: SandboxExecOptions,\n): Promise<SandboxResult> {\n\tconst { output = 'capture', signal, timeoutMs } = options;\n\n\treturn new Promise<SandboxResult>((resolvePromise, reject) => {\n\t\tconst child = spawn(command, [...args], {\n\t\t\tcwd: options.cwd,\n\t\t\t// The whole environment, as given. `spawn` reads `process.env` only\n\t\t\t// when this is left out.\n\t\t\tenv: { ...options.env },\n\t\t\tstdio: STDIO[output],\n\t\t\tshell: false,\n\t\t\t...(signal ? { signal } : {}),\n\t\t});\n\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\t\tchild.stdout?.on('data', (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t});\n\t\tchild.stderr?.on('data', (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\n\t\tlet timedOut = false;\n\t\tlet killer: NodeJS.Timeout | undefined;\n\t\tconst timer = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tchild.kill('SIGTERM');\n\t\t\t// A command that ignores SIGTERM would otherwise outlive its timeout.\n\t\t\tkiller = setTimeout(() => child.kill('SIGKILL'), KILL_GRACE_MS);\n\t\t\tkiller.unref?.();\n\t\t}, timeoutMs);\n\t\ttimer.unref?.();\n\n\t\tconst settle = () => {\n\t\t\tclearTimeout(timer);\n\t\t\tif (killer) clearTimeout(killer);\n\t\t};\n\n\t\tchild.on('error', (error) => {\n\t\t\tsettle();\n\t\t\t// Node reports an abort as its own `AbortError`; the caller asked\n\t\t\t// with a reason, and that is what it gets back.\n\t\t\treject(signal?.aborted ? signal.reason : error);\n\t\t});\n\n\t\tchild.on('close', (code: number | null, killedBy: string | null) => {\n\t\t\tsettle();\n\t\t\tif (signal?.aborted) reject(signal.reason);\n\t\t\telse if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));\n\t\t\telse\n\t\t\t\tresolvePromise({\n\t\t\t\t\texitCode: code,\n\t\t\t\t\tsignal: killedBy ?? null,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t});\n\t\t});\n\t});\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local-BjxDfPvy.cjs","names":["resolve","confineCwd","SECRETS_DIR_ENV","rm","mkdtemp","join","tmpdir","writeFile","spawn","CommandTimedOut"],"sources":["../src/sandbox/env.ts","../src/sandbox/local.ts"],"sourcesContent":["/**\n * What of the host's environment a local sandbox passes on.\n *\n * An allowlist, not a denylist: a credential's variable has any name its\n * owner chose (`DOKPLOY_TOKEN`, `AWS_SECRET_ACCESS_KEY`, `MY_VAULT_TOKEN`), so\n * the only safe default is to name what a program needs to *run* and pass\n * nothing else.\n */\n\n/**\n * Variables a command needs to find programs, write temp files, print in the\n * user's locale and reach the network through the host's proxy — and nothing\n * that authenticates as anyone.\n *\n * Deliberately left out:\n * - `NODE_OPTIONS`: it can `--require` or `--import` code into every node\n * process, and CI often holds `--import tsx` there. A step that needs a\n * loader passes its own.\n * - `NODE_AUTH_TOKEN`, `NPM_TOKEN`, `npm_config_*`: registry tokens\n * (`actions/setup-node` writes `NODE_AUTH_TOKEN`), which is why there is no\n * `NODE_*` wildcard.\n * - `AWS_*`, `DOCKER_*`, `DOKPLOY_*`, `GITHUB_TOKEN`, `TURBO_TOKEN`, `GKM_*`:\n * credentials, or where the deploy keeps them.\n * - `SHELL`, `EDITOR` and the rest of a login session: nothing here starts a\n * shell.\n *\n * Proxies are passed: behind one, nothing installs or builds without it. A\n * proxy URL with a password in it reaches the command too — a host that keeps\n * one there and builds code it does not trust should pass a sandbox of its\n * own.\n */\nexport const SANDBOX_ENV_ALLOWLIST: readonly (string | RegExp)[] = [\n\t// Finding programs and a home to cache in.\n\t'PATH',\n\t'HOME',\n\t'USER',\n\t'LOGNAME',\n\t'TMPDIR',\n\t'TMP',\n\t'TEMP',\n\t// Locale and terminal: output a person reads should look the same.\n\t'LANG',\n\t'LANGUAGE',\n\t'TZ',\n\t/^LC_[A-Z]+$/,\n\t'TERM',\n\t'COLORTERM',\n\t'NO_COLOR',\n\t'FORCE_COLOR',\n\t'CI',\n\t// Node: the mode a build runs in, and the CAs a corporate network adds.\n\t'NODE_ENV',\n\t'NODE_EXTRA_CA_CERTS',\n\t// Package managers' own homes, so a build reuses the host's store.\n\t'COREPACK_HOME',\n\t'PNPM_HOME',\n\t'XDG_CACHE_HOME',\n\t'XDG_DATA_HOME',\n\t// The network, through the host's proxy.\n\t'HTTP_PROXY',\n\t'HTTPS_PROXY',\n\t'NO_PROXY',\n\t'http_proxy',\n\t'https_proxy',\n\t'no_proxy',\n\t// Windows: without these, nothing starts.\n\t'SystemRoot',\n\t'SYSTEMROOT',\n\t'windir',\n\t'WINDIR',\n\t'ComSpec',\n\t'COMSPEC',\n\t'PATHEXT',\n\t'USERPROFILE',\n\t'APPDATA',\n\t'LOCALAPPDATA',\n];\n\nfunction allowed(name: string): boolean {\n\treturn SANDBOX_ENV_ALLOWLIST.some((entry) =>\n\t\ttypeof entry === 'string' ? entry === name : entry.test(name),\n\t);\n}\n\n/**\n * The allowlisted part of `source`, plus each of `extra` the host chose to\n * pass on by name.\n */\nexport function allowlistedEnv(\n\tsource: NodeJS.ProcessEnv = process.env,\n\textra: readonly string[] = [],\n): Record<string, string> {\n\tconst env: Record<string, string> = {};\n\tfor (const [name, value] of Object.entries(source)) {\n\t\tif (value === undefined) continue;\n\t\tif (allowed(name) || extra.includes(name)) env[name] = value;\n\t}\n\treturn env;\n}\n","import { type SpawnOptions, spawn } from 'node:child_process';\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { CommandTimedOut } from '../run';\nimport { allowlistedEnv } from './env';\nimport {\n\tassertSecretNames,\n\tconfineCwd,\n\ttype Sandbox,\n\ttype SandboxExecOptions,\n\ttype SandboxOutput,\n\ttype SandboxResult,\n\tSECRETS_DIR_ENV,\n} from './sandbox';\n\n/** How long a timed-out command has to exit on SIGTERM before SIGKILL. */\nconst KILL_GRACE_MS = 5_000;\n\nexport interface LocalSandboxOptions {\n\t/** The project. Every command runs at or below it. */\n\troot: string;\n\t/**\n\t * `HOME` for every command. Defaults to the host's, because a build\n\t * reuses the caches kept there — the pnpm store, corepack's package\n\t * managers, turbo's cache — and a cold one re-downloads all of them.\n\t * Point it at an empty directory to keep `~/.aws`, `~/.npmrc` and\n\t * `~/.docker` out of reach of an SDK's default lookup — though, sharing\n\t * the host's filesystem, a command that goes looking still finds them.\n\t */\n\thome?: string;\n\t/**\n\t * Host variables to pass on beyond the allowlist, by name — a remote\n\t * cache's `TURBO_TOKEN`, for a build the host trusts with it.\n\t */\n\tpassEnv?: readonly string[];\n\t/** Where the allowlisted variables are read from. Defaults to `process.env`. */\n\thostEnv?: NodeJS.ProcessEnv;\n}\n\n/** Where `output` sends a child's streams. */\nconst STDIO: Record<SandboxOutput, SpawnOptions['stdio']> = {\n\tcapture: ['ignore', 'pipe', 'pipe'],\n\tinherit: 'inherit',\n\tstderr: ['ignore', 2, 2],\n\tignore: 'ignore',\n};\n\n/**\n * A child process on this machine, as this user, with an allowlisted\n * environment.\n *\n * What it withholds from the command: the host's environment beyond\n * {@link SANDBOX_ENV_ALLOWLIST}, a shell, a working directory outside the\n * project, and any time past the timeout. What it does not: the host's\n * filesystem, which the command can read and write as the user running the\n * deploy. So it is not `isolating`, and it is the default for projects the\n * host already trusts — the developer's own, a team's own CI. Code nobody\n * vetted belongs in a sandbox that is.\n *\n * A timed-out command is sent SIGTERM, then SIGKILL; processes it started\n * itself are its to stop.\n */\nexport class LocalSandbox implements Sandbox {\n\treadonly root: string;\n\treadonly isolating = false;\n\treadonly env: Readonly<Record<string, string>>;\n\n\tconstructor(options: LocalSandboxOptions) {\n\t\tthis.root = resolve(options.root);\n\t\tthis.env = {\n\t\t\t...allowlistedEnv(options.hostEnv ?? process.env, options.passEnv),\n\t\t\t...(options.home\n\t\t\t\t? { HOME: options.home, USERPROFILE: options.home }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\tasync exec(\n\t\tcommand: string,\n\t\targs: readonly string[],\n\t\toptions: SandboxExecOptions,\n\t): Promise<SandboxResult> {\n\t\tconst cwd = confineCwd(this.root, options.cwd);\n\t\tassertSecretNames(options.secrets);\n\t\toptions.signal?.throwIfAborted();\n\n\t\tconst secretsDir = await mountSecrets(options.secrets);\n\t\ttry {\n\t\t\treturn await spawnConfined(command, args, {\n\t\t\t\t...options,\n\t\t\t\tcwd,\n\t\t\t\tenv: secretsDir\n\t\t\t\t\t? { ...options.env, [SECRETS_DIR_ENV]: secretsDir }\n\t\t\t\t\t: options.env,\n\t\t\t});\n\t\t} finally {\n\t\t\tif (secretsDir) await rm(secretsDir, { recursive: true, force: true });\n\t\t}\n\t}\n}\n\n/**\n * Each secret as a 0600 file in a fresh 0700 directory outside the project —\n * where nothing the build writes, packs or commits will pick it up.\n */\nasync function mountSecrets(\n\tsecrets: Readonly<Record<string, string>> | undefined,\n): Promise<string | undefined> {\n\tif (!secrets || Object.keys(secrets).length === 0) return undefined;\n\n\tconst dir = await mkdtemp(join(tmpdir(), 'gkm-secrets-'));\n\tfor (const [name, value] of Object.entries(secrets)) {\n\t\tawait writeFile(join(dir, name), value, { mode: 0o600 });\n\t}\n\treturn dir;\n}\n\nfunction spawnConfined(\n\tcommand: string,\n\targs: readonly string[],\n\toptions: SandboxExecOptions,\n): Promise<SandboxResult> {\n\tconst { output = 'capture', signal, timeoutMs } = options;\n\n\treturn new Promise<SandboxResult>((resolvePromise, reject) => {\n\t\tconst child = spawn(command, [...args], {\n\t\t\tcwd: options.cwd,\n\t\t\t// The whole environment, as given. `spawn` reads `process.env` only\n\t\t\t// when this is left out.\n\t\t\tenv: { ...options.env },\n\t\t\tstdio: STDIO[output],\n\t\t\tshell: false,\n\t\t\t...(signal ? { signal } : {}),\n\t\t});\n\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\t\tchild.stdout?.on('data', (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t});\n\t\tchild.stderr?.on('data', (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\n\t\tlet timedOut = false;\n\t\tlet killer: NodeJS.Timeout | undefined;\n\t\tconst timer = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tchild.kill('SIGTERM');\n\t\t\t// A command that ignores SIGTERM would otherwise outlive its timeout.\n\t\t\tkiller = setTimeout(() => child.kill('SIGKILL'), KILL_GRACE_MS);\n\t\t\tkiller.unref?.();\n\t\t}, timeoutMs);\n\t\ttimer.unref?.();\n\n\t\tconst settle = () => {\n\t\t\tclearTimeout(timer);\n\t\t\tif (killer) clearTimeout(killer);\n\t\t};\n\n\t\tchild.on('error', (error) => {\n\t\t\tsettle();\n\t\t\t// Node reports an abort as its own `AbortError`; the caller asked\n\t\t\t// with a reason, and that is what it gets back.\n\t\t\treject(signal?.aborted ? signal.reason : error);\n\t\t});\n\n\t\tchild.on('close', (code: number | null, killedBy: string | null) => {\n\t\t\tsettle();\n\t\t\tif (signal?.aborted) reject(signal.reason);\n\t\t\telse if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));\n\t\t\telse\n\t\t\t\tresolvePromise({\n\t\t\t\t\texitCode: code,\n\t\t\t\t\tsignal: killedBy ?? null,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t});\n\t\t});\n\t});\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as CommandTimedOut } from "./credentials-
|
|
1
|
+
import { a as CommandTimedOut } from "./credentials-CnVqoXMb.mjs";
|
|
2
2
|
import { a as assertSecretNames, o as confineCwd, t as SECRETS_DIR_ENV } from "./sandbox-Dm247NGx.mjs";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
@@ -221,4 +221,4 @@ function spawnConfined(command, args, options) {
|
|
|
221
221
|
//#endregion
|
|
222
222
|
export { SANDBOX_ENV_ALLOWLIST as n, allowlistedEnv as r, LocalSandbox as t };
|
|
223
223
|
|
|
224
|
-
//# sourceMappingURL=local-
|
|
224
|
+
//# sourceMappingURL=local-IuoIF3CT.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-Ciq-bTPr.mjs","names":[],"sources":["../src/sandbox/env.ts","../src/sandbox/local.ts"],"sourcesContent":["/**\n * What of the host's environment a local sandbox passes on.\n *\n * An allowlist, not a denylist: a credential's variable has any name its\n * owner chose (`DOKPLOY_TOKEN`, `AWS_SECRET_ACCESS_KEY`, `MY_VAULT_TOKEN`), so\n * the only safe default is to name what a program needs to *run* and pass\n * nothing else.\n */\n\n/**\n * Variables a command needs to find programs, write temp files, print in the\n * user's locale and reach the network through the host's proxy — and nothing\n * that authenticates as anyone.\n *\n * Deliberately left out:\n * - `NODE_OPTIONS`: it can `--require` or `--import` code into every node\n * process, and CI often holds `--import tsx` there. A step that needs a\n * loader passes its own.\n * - `NODE_AUTH_TOKEN`, `NPM_TOKEN`, `npm_config_*`: registry tokens\n * (`actions/setup-node` writes `NODE_AUTH_TOKEN`), which is why there is no\n * `NODE_*` wildcard.\n * - `AWS_*`, `DOCKER_*`, `DOKPLOY_*`, `GITHUB_TOKEN`, `TURBO_TOKEN`, `GKM_*`:\n * credentials, or where the deploy keeps them.\n * - `SHELL`, `EDITOR` and the rest of a login session: nothing here starts a\n * shell.\n *\n * Proxies are passed: behind one, nothing installs or builds without it. A\n * proxy URL with a password in it reaches the command too — a host that keeps\n * one there and builds code it does not trust should pass a sandbox of its\n * own.\n */\nexport const SANDBOX_ENV_ALLOWLIST: readonly (string | RegExp)[] = [\n\t// Finding programs and a home to cache in.\n\t'PATH',\n\t'HOME',\n\t'USER',\n\t'LOGNAME',\n\t'TMPDIR',\n\t'TMP',\n\t'TEMP',\n\t// Locale and terminal: output a person reads should look the same.\n\t'LANG',\n\t'LANGUAGE',\n\t'TZ',\n\t/^LC_[A-Z]+$/,\n\t'TERM',\n\t'COLORTERM',\n\t'NO_COLOR',\n\t'FORCE_COLOR',\n\t'CI',\n\t// Node: the mode a build runs in, and the CAs a corporate network adds.\n\t'NODE_ENV',\n\t'NODE_EXTRA_CA_CERTS',\n\t// Package managers' own homes, so a build reuses the host's store.\n\t'COREPACK_HOME',\n\t'PNPM_HOME',\n\t'XDG_CACHE_HOME',\n\t'XDG_DATA_HOME',\n\t// The network, through the host's proxy.\n\t'HTTP_PROXY',\n\t'HTTPS_PROXY',\n\t'NO_PROXY',\n\t'http_proxy',\n\t'https_proxy',\n\t'no_proxy',\n\t// Windows: without these, nothing starts.\n\t'SystemRoot',\n\t'SYSTEMROOT',\n\t'windir',\n\t'WINDIR',\n\t'ComSpec',\n\t'COMSPEC',\n\t'PATHEXT',\n\t'USERPROFILE',\n\t'APPDATA',\n\t'LOCALAPPDATA',\n];\n\nfunction allowed(name: string): boolean {\n\treturn SANDBOX_ENV_ALLOWLIST.some((entry) =>\n\t\ttypeof entry === 'string' ? entry === name : entry.test(name),\n\t);\n}\n\n/**\n * The allowlisted part of `source`, plus each of `extra` the host chose to\n * pass on by name.\n */\nexport function allowlistedEnv(\n\tsource: NodeJS.ProcessEnv = process.env,\n\textra: readonly string[] = [],\n): Record<string, string> {\n\tconst env: Record<string, string> = {};\n\tfor (const [name, value] of Object.entries(source)) {\n\t\tif (value === undefined) continue;\n\t\tif (allowed(name) || extra.includes(name)) env[name] = value;\n\t}\n\treturn env;\n}\n","import { type SpawnOptions, spawn } from 'node:child_process';\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { CommandTimedOut } from '../run';\nimport { allowlistedEnv } from './env';\nimport {\n\tassertSecretNames,\n\tconfineCwd,\n\ttype Sandbox,\n\ttype SandboxExecOptions,\n\ttype SandboxOutput,\n\ttype SandboxResult,\n\tSECRETS_DIR_ENV,\n} from './sandbox';\n\n/** How long a timed-out command has to exit on SIGTERM before SIGKILL. */\nconst KILL_GRACE_MS = 5_000;\n\nexport interface LocalSandboxOptions {\n\t/** The project. Every command runs at or below it. */\n\troot: string;\n\t/**\n\t * `HOME` for every command. Defaults to the host's, because a build\n\t * reuses the caches kept there — the pnpm store, corepack's package\n\t * managers, turbo's cache — and a cold one re-downloads all of them.\n\t * Point it at an empty directory to keep `~/.aws`, `~/.npmrc` and\n\t * `~/.docker` out of reach of an SDK's default lookup — though, sharing\n\t * the host's filesystem, a command that goes looking still finds them.\n\t */\n\thome?: string;\n\t/**\n\t * Host variables to pass on beyond the allowlist, by name — a remote\n\t * cache's `TURBO_TOKEN`, for a build the host trusts with it.\n\t */\n\tpassEnv?: readonly string[];\n\t/** Where the allowlisted variables are read from. Defaults to `process.env`. */\n\thostEnv?: NodeJS.ProcessEnv;\n}\n\n/** Where `output` sends a child's streams. */\nconst STDIO: Record<SandboxOutput, SpawnOptions['stdio']> = {\n\tcapture: ['ignore', 'pipe', 'pipe'],\n\tinherit: 'inherit',\n\tstderr: ['ignore', 2, 2],\n\tignore: 'ignore',\n};\n\n/**\n * A child process on this machine, as this user, with an allowlisted\n * environment.\n *\n * What it withholds from the command: the host's environment beyond\n * {@link SANDBOX_ENV_ALLOWLIST}, a shell, a working directory outside the\n * project, and any time past the timeout. What it does not: the host's\n * filesystem, which the command can read and write as the user running the\n * deploy. So it is not `isolating`, and it is the default for projects the\n * host already trusts — the developer's own, a team's own CI. Code nobody\n * vetted belongs in a sandbox that is.\n *\n * A timed-out command is sent SIGTERM, then SIGKILL; processes it started\n * itself are its to stop.\n */\nexport class LocalSandbox implements Sandbox {\n\treadonly root: string;\n\treadonly isolating = false;\n\treadonly env: Readonly<Record<string, string>>;\n\n\tconstructor(options: LocalSandboxOptions) {\n\t\tthis.root = resolve(options.root);\n\t\tthis.env = {\n\t\t\t...allowlistedEnv(options.hostEnv ?? process.env, options.passEnv),\n\t\t\t...(options.home\n\t\t\t\t? { HOME: options.home, USERPROFILE: options.home }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\tasync exec(\n\t\tcommand: string,\n\t\targs: readonly string[],\n\t\toptions: SandboxExecOptions,\n\t): Promise<SandboxResult> {\n\t\tconst cwd = confineCwd(this.root, options.cwd);\n\t\tassertSecretNames(options.secrets);\n\t\toptions.signal?.throwIfAborted();\n\n\t\tconst secretsDir = await mountSecrets(options.secrets);\n\t\ttry {\n\t\t\treturn await spawnConfined(command, args, {\n\t\t\t\t...options,\n\t\t\t\tcwd,\n\t\t\t\tenv: secretsDir\n\t\t\t\t\t? { ...options.env, [SECRETS_DIR_ENV]: secretsDir }\n\t\t\t\t\t: options.env,\n\t\t\t});\n\t\t} finally {\n\t\t\tif (secretsDir) await rm(secretsDir, { recursive: true, force: true });\n\t\t}\n\t}\n}\n\n/**\n * Each secret as a 0600 file in a fresh 0700 directory outside the project —\n * where nothing the build writes, packs or commits will pick it up.\n */\nasync function mountSecrets(\n\tsecrets: Readonly<Record<string, string>> | undefined,\n): Promise<string | undefined> {\n\tif (!secrets || Object.keys(secrets).length === 0) return undefined;\n\n\tconst dir = await mkdtemp(join(tmpdir(), 'gkm-secrets-'));\n\tfor (const [name, value] of Object.entries(secrets)) {\n\t\tawait writeFile(join(dir, name), value, { mode: 0o600 });\n\t}\n\treturn dir;\n}\n\nfunction spawnConfined(\n\tcommand: string,\n\targs: readonly string[],\n\toptions: SandboxExecOptions,\n): Promise<SandboxResult> {\n\tconst { output = 'capture', signal, timeoutMs } = options;\n\n\treturn new Promise<SandboxResult>((resolvePromise, reject) => {\n\t\tconst child = spawn(command, [...args], {\n\t\t\tcwd: options.cwd,\n\t\t\t// The whole environment, as given. `spawn` reads `process.env` only\n\t\t\t// when this is left out.\n\t\t\tenv: { ...options.env },\n\t\t\tstdio: STDIO[output],\n\t\t\tshell: false,\n\t\t\t...(signal ? { signal } : {}),\n\t\t});\n\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\t\tchild.stdout?.on('data', (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t});\n\t\tchild.stderr?.on('data', (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\n\t\tlet timedOut = false;\n\t\tlet killer: NodeJS.Timeout | undefined;\n\t\tconst timer = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tchild.kill('SIGTERM');\n\t\t\t// A command that ignores SIGTERM would otherwise outlive its timeout.\n\t\t\tkiller = setTimeout(() => child.kill('SIGKILL'), KILL_GRACE_MS);\n\t\t\tkiller.unref?.();\n\t\t}, timeoutMs);\n\t\ttimer.unref?.();\n\n\t\tconst settle = () => {\n\t\t\tclearTimeout(timer);\n\t\t\tif (killer) clearTimeout(killer);\n\t\t};\n\n\t\tchild.on('error', (error) => {\n\t\t\tsettle();\n\t\t\t// Node reports an abort as its own `AbortError`; the caller asked\n\t\t\t// with a reason, and that is what it gets back.\n\t\t\treject(signal?.aborted ? signal.reason : error);\n\t\t});\n\n\t\tchild.on('close', (code: number | null, killedBy: string | null) => {\n\t\t\tsettle();\n\t\t\tif (signal?.aborted) reject(signal.reason);\n\t\t\telse if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));\n\t\t\telse\n\t\t\t\tresolvePromise({\n\t\t\t\t\texitCode: code,\n\t\t\t\t\tsignal: killedBy ?? null,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t});\n\t\t});\n\t});\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local-IuoIF3CT.mjs","names":[],"sources":["../src/sandbox/env.ts","../src/sandbox/local.ts"],"sourcesContent":["/**\n * What of the host's environment a local sandbox passes on.\n *\n * An allowlist, not a denylist: a credential's variable has any name its\n * owner chose (`DOKPLOY_TOKEN`, `AWS_SECRET_ACCESS_KEY`, `MY_VAULT_TOKEN`), so\n * the only safe default is to name what a program needs to *run* and pass\n * nothing else.\n */\n\n/**\n * Variables a command needs to find programs, write temp files, print in the\n * user's locale and reach the network through the host's proxy — and nothing\n * that authenticates as anyone.\n *\n * Deliberately left out:\n * - `NODE_OPTIONS`: it can `--require` or `--import` code into every node\n * process, and CI often holds `--import tsx` there. A step that needs a\n * loader passes its own.\n * - `NODE_AUTH_TOKEN`, `NPM_TOKEN`, `npm_config_*`: registry tokens\n * (`actions/setup-node` writes `NODE_AUTH_TOKEN`), which is why there is no\n * `NODE_*` wildcard.\n * - `AWS_*`, `DOCKER_*`, `DOKPLOY_*`, `GITHUB_TOKEN`, `TURBO_TOKEN`, `GKM_*`:\n * credentials, or where the deploy keeps them.\n * - `SHELL`, `EDITOR` and the rest of a login session: nothing here starts a\n * shell.\n *\n * Proxies are passed: behind one, nothing installs or builds without it. A\n * proxy URL with a password in it reaches the command too — a host that keeps\n * one there and builds code it does not trust should pass a sandbox of its\n * own.\n */\nexport const SANDBOX_ENV_ALLOWLIST: readonly (string | RegExp)[] = [\n\t// Finding programs and a home to cache in.\n\t'PATH',\n\t'HOME',\n\t'USER',\n\t'LOGNAME',\n\t'TMPDIR',\n\t'TMP',\n\t'TEMP',\n\t// Locale and terminal: output a person reads should look the same.\n\t'LANG',\n\t'LANGUAGE',\n\t'TZ',\n\t/^LC_[A-Z]+$/,\n\t'TERM',\n\t'COLORTERM',\n\t'NO_COLOR',\n\t'FORCE_COLOR',\n\t'CI',\n\t// Node: the mode a build runs in, and the CAs a corporate network adds.\n\t'NODE_ENV',\n\t'NODE_EXTRA_CA_CERTS',\n\t// Package managers' own homes, so a build reuses the host's store.\n\t'COREPACK_HOME',\n\t'PNPM_HOME',\n\t'XDG_CACHE_HOME',\n\t'XDG_DATA_HOME',\n\t// The network, through the host's proxy.\n\t'HTTP_PROXY',\n\t'HTTPS_PROXY',\n\t'NO_PROXY',\n\t'http_proxy',\n\t'https_proxy',\n\t'no_proxy',\n\t// Windows: without these, nothing starts.\n\t'SystemRoot',\n\t'SYSTEMROOT',\n\t'windir',\n\t'WINDIR',\n\t'ComSpec',\n\t'COMSPEC',\n\t'PATHEXT',\n\t'USERPROFILE',\n\t'APPDATA',\n\t'LOCALAPPDATA',\n];\n\nfunction allowed(name: string): boolean {\n\treturn SANDBOX_ENV_ALLOWLIST.some((entry) =>\n\t\ttypeof entry === 'string' ? entry === name : entry.test(name),\n\t);\n}\n\n/**\n * The allowlisted part of `source`, plus each of `extra` the host chose to\n * pass on by name.\n */\nexport function allowlistedEnv(\n\tsource: NodeJS.ProcessEnv = process.env,\n\textra: readonly string[] = [],\n): Record<string, string> {\n\tconst env: Record<string, string> = {};\n\tfor (const [name, value] of Object.entries(source)) {\n\t\tif (value === undefined) continue;\n\t\tif (allowed(name) || extra.includes(name)) env[name] = value;\n\t}\n\treturn env;\n}\n","import { type SpawnOptions, spawn } from 'node:child_process';\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { CommandTimedOut } from '../run';\nimport { allowlistedEnv } from './env';\nimport {\n\tassertSecretNames,\n\tconfineCwd,\n\ttype Sandbox,\n\ttype SandboxExecOptions,\n\ttype SandboxOutput,\n\ttype SandboxResult,\n\tSECRETS_DIR_ENV,\n} from './sandbox';\n\n/** How long a timed-out command has to exit on SIGTERM before SIGKILL. */\nconst KILL_GRACE_MS = 5_000;\n\nexport interface LocalSandboxOptions {\n\t/** The project. Every command runs at or below it. */\n\troot: string;\n\t/**\n\t * `HOME` for every command. Defaults to the host's, because a build\n\t * reuses the caches kept there — the pnpm store, corepack's package\n\t * managers, turbo's cache — and a cold one re-downloads all of them.\n\t * Point it at an empty directory to keep `~/.aws`, `~/.npmrc` and\n\t * `~/.docker` out of reach of an SDK's default lookup — though, sharing\n\t * the host's filesystem, a command that goes looking still finds them.\n\t */\n\thome?: string;\n\t/**\n\t * Host variables to pass on beyond the allowlist, by name — a remote\n\t * cache's `TURBO_TOKEN`, for a build the host trusts with it.\n\t */\n\tpassEnv?: readonly string[];\n\t/** Where the allowlisted variables are read from. Defaults to `process.env`. */\n\thostEnv?: NodeJS.ProcessEnv;\n}\n\n/** Where `output` sends a child's streams. */\nconst STDIO: Record<SandboxOutput, SpawnOptions['stdio']> = {\n\tcapture: ['ignore', 'pipe', 'pipe'],\n\tinherit: 'inherit',\n\tstderr: ['ignore', 2, 2],\n\tignore: 'ignore',\n};\n\n/**\n * A child process on this machine, as this user, with an allowlisted\n * environment.\n *\n * What it withholds from the command: the host's environment beyond\n * {@link SANDBOX_ENV_ALLOWLIST}, a shell, a working directory outside the\n * project, and any time past the timeout. What it does not: the host's\n * filesystem, which the command can read and write as the user running the\n * deploy. So it is not `isolating`, and it is the default for projects the\n * host already trusts — the developer's own, a team's own CI. Code nobody\n * vetted belongs in a sandbox that is.\n *\n * A timed-out command is sent SIGTERM, then SIGKILL; processes it started\n * itself are its to stop.\n */\nexport class LocalSandbox implements Sandbox {\n\treadonly root: string;\n\treadonly isolating = false;\n\treadonly env: Readonly<Record<string, string>>;\n\n\tconstructor(options: LocalSandboxOptions) {\n\t\tthis.root = resolve(options.root);\n\t\tthis.env = {\n\t\t\t...allowlistedEnv(options.hostEnv ?? process.env, options.passEnv),\n\t\t\t...(options.home\n\t\t\t\t? { HOME: options.home, USERPROFILE: options.home }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\tasync exec(\n\t\tcommand: string,\n\t\targs: readonly string[],\n\t\toptions: SandboxExecOptions,\n\t): Promise<SandboxResult> {\n\t\tconst cwd = confineCwd(this.root, options.cwd);\n\t\tassertSecretNames(options.secrets);\n\t\toptions.signal?.throwIfAborted();\n\n\t\tconst secretsDir = await mountSecrets(options.secrets);\n\t\ttry {\n\t\t\treturn await spawnConfined(command, args, {\n\t\t\t\t...options,\n\t\t\t\tcwd,\n\t\t\t\tenv: secretsDir\n\t\t\t\t\t? { ...options.env, [SECRETS_DIR_ENV]: secretsDir }\n\t\t\t\t\t: options.env,\n\t\t\t});\n\t\t} finally {\n\t\t\tif (secretsDir) await rm(secretsDir, { recursive: true, force: true });\n\t\t}\n\t}\n}\n\n/**\n * Each secret as a 0600 file in a fresh 0700 directory outside the project —\n * where nothing the build writes, packs or commits will pick it up.\n */\nasync function mountSecrets(\n\tsecrets: Readonly<Record<string, string>> | undefined,\n): Promise<string | undefined> {\n\tif (!secrets || Object.keys(secrets).length === 0) return undefined;\n\n\tconst dir = await mkdtemp(join(tmpdir(), 'gkm-secrets-'));\n\tfor (const [name, value] of Object.entries(secrets)) {\n\t\tawait writeFile(join(dir, name), value, { mode: 0o600 });\n\t}\n\treturn dir;\n}\n\nfunction spawnConfined(\n\tcommand: string,\n\targs: readonly string[],\n\toptions: SandboxExecOptions,\n): Promise<SandboxResult> {\n\tconst { output = 'capture', signal, timeoutMs } = options;\n\n\treturn new Promise<SandboxResult>((resolvePromise, reject) => {\n\t\tconst child = spawn(command, [...args], {\n\t\t\tcwd: options.cwd,\n\t\t\t// The whole environment, as given. `spawn` reads `process.env` only\n\t\t\t// when this is left out.\n\t\t\tenv: { ...options.env },\n\t\t\tstdio: STDIO[output],\n\t\t\tshell: false,\n\t\t\t...(signal ? { signal } : {}),\n\t\t});\n\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\t\tchild.stdout?.on('data', (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t});\n\t\tchild.stderr?.on('data', (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\n\t\tlet timedOut = false;\n\t\tlet killer: NodeJS.Timeout | undefined;\n\t\tconst timer = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tchild.kill('SIGTERM');\n\t\t\t// A command that ignores SIGTERM would otherwise outlive its timeout.\n\t\t\tkiller = setTimeout(() => child.kill('SIGKILL'), KILL_GRACE_MS);\n\t\t\tkiller.unref?.();\n\t\t}, timeoutMs);\n\t\ttimer.unref?.();\n\n\t\tconst settle = () => {\n\t\t\tclearTimeout(timer);\n\t\t\tif (killer) clearTimeout(killer);\n\t\t};\n\n\t\tchild.on('error', (error) => {\n\t\t\tsettle();\n\t\t\t// Node reports an abort as its own `AbortError`; the caller asked\n\t\t\t// with a reason, and that is what it gets back.\n\t\t\treject(signal?.aborted ? signal.reason : error);\n\t\t});\n\n\t\tchild.on('close', (code: number | null, killedBy: string | null) => {\n\t\t\tsettle();\n\t\t\tif (signal?.aborted) reject(signal.reason);\n\t\t\telse if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));\n\t\t\telse\n\t\t\t\tresolvePromise({\n\t\t\t\t\texitCode: code,\n\t\t\t\t\tsignal: killedBy ?? null,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t});\n\t\t});\n\t});\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const require_discover = require("./discover-D1It5r_h.cjs");
|
|
2
|
-
const require_workspace = require("./workspace-
|
|
2
|
+
const require_workspace = require("./workspace-CwW3Gkru.cjs");
|
|
3
3
|
const require_config = require("./config.cjs");
|
|
4
|
-
const require_seeds = require("./seeds-
|
|
4
|
+
const require_seeds = require("./seeds-CN6tB-va.cjs");
|
|
5
5
|
const require_databases = require("./databases-BY-pEImp.cjs");
|
|
6
6
|
const require_index = require("./index.cjs");
|
|
7
7
|
let node_path = require("node:path");
|
|
@@ -267,4 +267,4 @@ exports.prepareDevDatabases = prepareDevDatabases;
|
|
|
267
267
|
exports.reportPendingMigrations = reportPendingMigrations;
|
|
268
268
|
exports.seedCommand = seedCommand;
|
|
269
269
|
|
|
270
|
-
//# sourceMappingURL=migrate-
|
|
270
|
+
//# sourceMappingURL=migrate-7xayVtzL.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-B1rVlwj1.cjs","names":["TEST_STAGE","loadWorkspaceConfig","discover","constructGlobs","reconcileWorkspace","migrateDatabases","migrateAndSeed","pendingMigrations","migrationTargets","canonicalId","NoSuchMigrationTarget","stamp","slug","loadSecretsForApp","secretsStoreFor","EnvironmentParser","serviceContext","join","mkdir","writeFile"],"sources":["../src/migrate/index.ts"],"sourcesContent":["/**\n * `gkm migrate`, `gkm seed` and `gkm migration` — applying a construct's\n * migrations, running its seeds, and writing its next migration.\n *\n * Both work on a stage this machine reconciles: its containers up, its roles\n * provisioned, its owner URLs resolved. A deployed stage is migrated by the\n * deploy that ships it, which is the one thing that can reach its database.\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { EnvironmentParser } from '@geekmidas/envkit';\nimport {\n\ttype ConstructManifest,\n\tcanonicalId,\n\ttype MigrationTarget,\n\tmigrationTargets,\n} from '@geekmidas/manifest';\nimport {\n\ttype ServiceRegisterOptions,\n\tserviceContext,\n} from '@geekmidas/services';\nimport { loadWorkspaceConfig } from '../config';\nimport { loadSecretsForApp } from '../credentials';\nimport { type ConstructSource, discover } from '../reconcile/discover';\nimport { constructGlobs, reconcileWorkspace } from '../reconcile/workspace.js';\nimport { secretsStoreFor } from '../secrets/store.js';\nimport { TEST_STAGE } from '../workspace/stages';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport {\n\ttype MigrationRun,\n\tmigrateAndSeed,\n\tmigrateDatabases,\n\tNoSuchMigrationTarget,\n\tpendingMigrations,\n\ttype SeedRun,\n} from './databases';\nimport { slug, stamp } from './names';\n\nexport {\n\tKyselyNotFound,\n\tMigrationFailed,\n\ttype MigrationRun,\n\tMigrationsOutsideFolder,\n\tmigrateAndSeed,\n\tmigrateDatabases,\n\tNoOwnerCredential,\n\tNoSuchMigrationTarget,\n\ttype PendingMigrations,\n\tpendingMigrations,\n\tSeedFailed,\n\ttype SeedOptions,\n\ttype SeedRun,\n\tseedDatabases,\n\tUnknownDatabaseFolder,\n\tUnknownMigrationFolder,\n} from './databases';\nexport { slug, stamp } from './names';\nexport {\n\tDuplicateMigration,\n\tMigrationHasNoUp,\n\tSeedHasNoSeed,\n} from './provider';\n\nconst logger = console;\n\n/** `gkm migrate --stage` named a stage only a deploy can reach. */\nexport class MigrateDeployedStage extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly local: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${stage}' is a deployed stage, and its database is migrated by ` +\n\t\t\t\t`\\`gkm deploy --stage ${stage}\\`, which is what can reach it. ` +\n\t\t\t\t`\\`gkm migrate\\` runs against '${local}' or '${TEST_STAGE}'.`,\n\t\t);\n\t\tthis.name = 'MigrateDeployedStage';\n\t}\n}\n\n/** `gkm migration` for a Kysely database, with no name for the file. */\nexport class MigrationNeedsName extends Error {\n\tconstructor(readonly construct: string) {\n\t\tsuper(\n\t\t\t`Name the migration: \\`gkm migration ${construct} add_workouts\\`. ` +\n\t\t\t\t`The name is what the file — and the history — calls it.`,\n\t\t);\n\t\tthis.name = 'MigrationNeedsName';\n\t}\n}\n\nexport interface MigrateOptions {\n\t/** The stage to migrate: the project's local one, or `test`. */\n\tstage?: string;\n\t/** One construct, by name. All of them when absent. */\n\tconstruct?: string;\n}\n\n/** A workspace's stage, reconciled and ready to migrate. */\nexport interface ReadyStage {\n\tworkspace: NormalizedWorkspace;\n\tmanifest: ConstructManifest;\n\tsources: Record<string, ConstructSource>;\n\t/** Every URL the stage resolved — the owner URLs among them. */\n\tenv: Record<string, string>;\n\t/** The stage it is: the local one, or `test`. */\n\tstage: string;\n}\n\n/**\n * Reconcile a stage so it can be migrated: containers up, databases, schemas,\n * roles and grants in place, owner URLs resolved.\n *\n * Migrating assumes the roles exist, and this is what makes that true rather\n * than something a developer has to have run first.\n */\nexport async function readyStage(\n\tcwd: string,\n\tstage?: string,\n): Promise<ReadyStage> {\n\tconst { workspace } = await loadWorkspaceConfig(cwd);\n\tconst local = workspace.stages.local;\n\tconst target = stage ?? local;\n\tif (target !== local && target !== TEST_STAGE) {\n\t\tthrow new MigrateDeployedStage(target, local);\n\t}\n\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\n\tconst reconciled = await reconcileWorkspace(workspace, {\n\t\tstage: target,\n\t\tmanifest,\n\t\tstart: true,\n\t});\n\n\treturn {\n\t\tworkspace,\n\t\tmanifest,\n\t\tsources,\n\t\tenv: { ...reconciled.env },\n\t\tstage: target,\n\t};\n}\n\n/** `gkm migrate [construct]`. */\nexport async function migrateCommand(\n\toptions: MigrateOptions = {},\n): Promise<void> {\n\tconst ready = await readyStage(process.cwd(), options.stage);\n\n\tconst runs = await migrateDatabases({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\t...(options.construct ? { only: options.construct } : {}),\n\t});\n\n\tif (runs.length === 0) {\n\t\tlogger.log('🗄️ No database constructs to migrate.');\n\t\treturn;\n\t}\n\n\tlogMigrations(runs);\n}\n\n/**\n * `gkm seed [construct]` — migrate, then run the seeds.\n *\n * Always migrates first: a seed is written against the schema its migrations\n * build, and against an older one it fails on a table that is not there yet.\n */\nexport async function seedCommand(options: MigrateOptions = {}): Promise<void> {\n\tconst ready = await readyStage(process.cwd(), options.stage);\n\n\tconst { migrations, seeds } = await migrateAndSeed({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\tstage: ready.stage,\n\t\t...(options.construct ? { only: options.construct } : {}),\n\t});\n\n\tif (migrations.length === 0) {\n\t\tlogger.log('🗄️ No database constructs to seed.');\n\t\treturn;\n\t}\n\n\tlogMigrations(migrations);\n\tlogSeeds(seeds);\n}\n\n/** One line per construct: up to date, or what was applied. */\nexport function logMigrations(\n\truns: readonly MigrationRun[],\n\tlog: (line: string) => void = logger.log,\n): void {\n\tfor (const { target, applied } of runs) {\n\t\tif (applied.length === 0) {\n\t\t\tlog(`🗄️ ${target.migrations}: up to date`);\n\t\t\tcontinue;\n\t\t}\n\t\tlog(`🗄️ ${target.migrations}: applied ${applied.length}`);\n\t\tfor (const name of applied) log(` ✓ ${name}`);\n\t}\n}\n\n/** One line per construct that has seeds, and each seed it ran. */\nexport function logSeeds(\n\truns: readonly SeedRun[],\n\tlog: (line: string) => void = logger.log,\n): void {\n\tfor (const { target, seeded } of runs) {\n\t\tif (seeded.length === 0) continue;\n\t\tlog(`🌱 ${target.seeds}: ran ${seeded.length}`);\n\t\tfor (const name of seeded) log(` ✓ ${name}`);\n\t}\n}\n\n/**\n * What `gkm dev` prints: one line per construct whose folder is ahead of its\n * history, and nothing when every folder is applied.\n *\n * Reported, never applied. A migration being written would otherwise run the\n * moment its file is saved, half-finished, against the database the developer\n * is working in.\n */\nexport async function reportPendingMigrations(\n\tworkspace: NormalizedWorkspace,\n\tenv: Readonly<Record<string, string>>,\n): Promise<void> {\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\n\tconst pending = await pendingMigrations({\n\t\troot: workspace.root,\n\t\tmanifest,\n\t\tsources,\n\t\tenv,\n\t});\n\n\tfor (const { target, pending: names } of pending) {\n\t\tlogger.log(\n\t\t\t`⚠️ ${target.migrations}: ${names.length} pending (${names.join(', ')}) — run gkm migrate`,\n\t\t);\n\t}\n}\n\n/**\n * What `gkm dev --migrate` and `gkm dev --seed` do before the apps start:\n * migrate, and with `seed`, run the seeds after — once, at startup, never on a\n * save, for the reason {@link reportPendingMigrations} gives.\n *\n * A failure here is thrown, unlike the report's: the developer asked for the\n * database to be brought up to date, and apps started against one in an\n * unknown state would fail somewhere less obvious.\n */\nexport async function prepareDevDatabases(\n\tworkspace: NormalizedWorkspace,\n\tenv: Readonly<Record<string, string>>,\n\t{ seed = false }: { seed?: boolean } = {},\n): Promise<void> {\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\tconst options = { root: workspace.root, manifest, sources, env };\n\n\tif (!seed) {\n\t\tlogMigrations(await migrateDatabases(options));\n\t\treturn;\n\t}\n\n\tconst { migrations, seeds } = await migrateAndSeed({\n\t\t...options,\n\t\tstage: workspace.stages.local,\n\t});\n\tlogMigrations(migrations);\n\tlogSeeds(seeds);\n}\n\nexport interface MigrationOptions {\n\t/** The construct the migration is for, by name. */\n\tconstruct: string;\n\t/** What the file is called, after its timestamp. */\n\tname?: string;\n\t/** The file's timestamp; the current time when absent. */\n\tnow?: Date;\n}\n\n/**\n * `gkm migration <construct> [name]` — write the construct's next migration.\n *\n * For a database, an empty `up`/`down` to fill in. For an auth server, what\n * its schema is missing, computed against its tenant once every committed\n * migration has been applied — so the file is only the change, and is\n * reviewed and committed like any other.\n */\nexport async function migrationCommand(\n\toptions: MigrationOptions,\n): Promise<string | undefined> {\n\tconst ready = await readyStage(process.cwd());\n\tconst targets = migrationTargets(ready.manifest);\n\tconst id = canonicalId(options.construct);\n\n\tconst auth = authConstruct(ready.sources, id);\n\tif (auth) {\n\t\tconst target = targets.find((t) => t.id === auth.databaseId);\n\t\tif (!target) {\n\t\t\tthrow new NoSuchMigrationTarget(auth.databaseId, ids(targets));\n\t\t}\n\t\treturn writeAuthMigration(ready, auth, target, options);\n\t}\n\n\tconst target = targets.find((t) => t.id === id);\n\tif (!target) {\n\t\tthrow new NoSuchMigrationTarget(options.construct, [\n\t\t\t...ids(targets),\n\t\t\t...authIds(ready.sources),\n\t\t]);\n\t}\n\tif (!options.name) throw new MigrationNeedsName(options.construct);\n\n\tconst file = await write(\n\t\tready.workspace.root,\n\t\ttarget,\n\t\t`${stamp(options.now)}_${slug(options.name)}.ts`,\n\t\tSTUB,\n\t);\n\tlogger.log(`📝 ${file}`);\n\treturn file;\n}\n\n/** What an auth construct offers `gkm migration`, duck-typed across copies. */\ninterface AuthConstruct {\n\tid: string;\n\tdatabaseId: string;\n\tpendingMigration(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<string | undefined>;\n}\n\nfunction authConstruct(\n\tsources: Readonly<Record<string, ConstructSource>>,\n\tid: string,\n): AuthConstruct | undefined {\n\tconst construct = sources[id]?.construct as\n\t\t| Partial<AuthConstruct>\n\t\t| undefined;\n\treturn construct &&\n\t\ttypeof construct.pendingMigration === 'function' &&\n\t\ttypeof construct.databaseId === 'string'\n\t\t? (construct as AuthConstruct)\n\t\t: undefined;\n}\n\nfunction authIds(sources: Readonly<Record<string, ConstructSource>>): string[] {\n\treturn Object.keys(sources).filter((id) => authConstruct(sources, id));\n}\n\nasync function writeAuthMigration(\n\tready: ReadyStage,\n\tauth: AuthConstruct,\n\ttarget: MigrationTarget,\n\toptions: MigrationOptions,\n): Promise<string | undefined> {\n\t// Against a tenant already at its committed migrations, so the comparison\n\t// finds only what the files do not yet say.\n\tawait migrateDatabases({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\tonly: target.id,\n\t});\n\n\t// Its secret and URL come from the stage's secrets; the tenant's owner URL\n\t// from the reconcile above, which wins.\n\tconst stage = ready.workspace.stages.local;\n\tconst secrets = await loadSecretsForApp(\n\t\tawait secretsStoreFor(ready.workspace, stage),\n\t\tstage,\n\t);\n\tconst pending = await auth.pendingMigration({\n\t\tenvParser: new EnvironmentParser({ ...secrets, ...ready.env }),\n\t\tcontext: serviceContext,\n\t});\n\n\tif (!pending) {\n\t\tlogger.log(`✓ ${target.migrations}: ${auth.id} is up to date`);\n\t\treturn undefined;\n\t}\n\n\tconst file = await write(\n\t\tready.workspace.root,\n\t\ttarget,\n\t\t`${stamp(options.now)}_${slug(options.name ?? 'better_auth')}.sql`,\n\t\t`${pending.trim()}\\n`,\n\t);\n\tlogger.log(`📝 ${file}`);\n\treturn file;\n}\n\nconst STUB = `import type { Kysely } from 'kysely';\n\nexport async function up(db: Kysely<unknown>): Promise<void> {\n // await db.schema.createTable('…').addColumn(…).execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n // await db.schema.dropTable('…').execute();\n}\n`;\n\nasync function write(\n\troot: string,\n\ttarget: MigrationTarget,\n\tfile: string,\n\tcontent: string,\n): Promise<string> {\n\tconst folder = join(root, target.migrations);\n\tawait mkdir(folder, { recursive: true });\n\tawait writeFile(join(folder, file), content, { flag: 'wx' });\n\treturn `${target.migrations}/${file}`;\n}\n\nfunction ids(targets: readonly MigrationTarget[]): string[] {\n\treturn targets.map((target) => target.id);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"migrate-7xayVtzL.cjs","names":["TEST_STAGE","loadWorkspaceConfig","discover","constructGlobs","reconcileWorkspace","migrateDatabases","migrateAndSeed","pendingMigrations","migrationTargets","canonicalId","NoSuchMigrationTarget","stamp","slug","loadSecretsForApp","secretsStoreFor","EnvironmentParser","serviceContext","join","mkdir","writeFile"],"sources":["../src/migrate/index.ts"],"sourcesContent":["/**\n * `gkm migrate`, `gkm seed` and `gkm migration` — applying a construct's\n * migrations, running its seeds, and writing its next migration.\n *\n * Both work on a stage this machine reconciles: its containers up, its roles\n * provisioned, its owner URLs resolved. A deployed stage is migrated by the\n * deploy that ships it, which is the one thing that can reach its database.\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { EnvironmentParser } from '@geekmidas/envkit';\nimport {\n\ttype ConstructManifest,\n\tcanonicalId,\n\ttype MigrationTarget,\n\tmigrationTargets,\n} from '@geekmidas/manifest';\nimport {\n\ttype ServiceRegisterOptions,\n\tserviceContext,\n} from '@geekmidas/services';\nimport { loadWorkspaceConfig } from '../config';\nimport { loadSecretsForApp } from '../credentials';\nimport { type ConstructSource, discover } from '../reconcile/discover';\nimport { constructGlobs, reconcileWorkspace } from '../reconcile/workspace.js';\nimport { secretsStoreFor } from '../secrets/store.js';\nimport { TEST_STAGE } from '../workspace/stages';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport {\n\ttype MigrationRun,\n\tmigrateAndSeed,\n\tmigrateDatabases,\n\tNoSuchMigrationTarget,\n\tpendingMigrations,\n\ttype SeedRun,\n} from './databases';\nimport { slug, stamp } from './names';\n\nexport {\n\tKyselyNotFound,\n\tMigrationFailed,\n\ttype MigrationRun,\n\tMigrationsOutsideFolder,\n\tmigrateAndSeed,\n\tmigrateDatabases,\n\tNoOwnerCredential,\n\tNoSuchMigrationTarget,\n\ttype PendingMigrations,\n\tpendingMigrations,\n\tSeedFailed,\n\ttype SeedOptions,\n\ttype SeedRun,\n\tseedDatabases,\n\tUnknownDatabaseFolder,\n\tUnknownMigrationFolder,\n} from './databases';\nexport { slug, stamp } from './names';\nexport {\n\tDuplicateMigration,\n\tMigrationHasNoUp,\n\tSeedHasNoSeed,\n} from './provider';\n\nconst logger = console;\n\n/** `gkm migrate --stage` named a stage only a deploy can reach. */\nexport class MigrateDeployedStage extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly local: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${stage}' is a deployed stage, and its database is migrated by ` +\n\t\t\t\t`\\`gkm deploy --stage ${stage}\\`, which is what can reach it. ` +\n\t\t\t\t`\\`gkm migrate\\` runs against '${local}' or '${TEST_STAGE}'.`,\n\t\t);\n\t\tthis.name = 'MigrateDeployedStage';\n\t}\n}\n\n/** `gkm migration` for a Kysely database, with no name for the file. */\nexport class MigrationNeedsName extends Error {\n\tconstructor(readonly construct: string) {\n\t\tsuper(\n\t\t\t`Name the migration: \\`gkm migration ${construct} add_workouts\\`. ` +\n\t\t\t\t`The name is what the file — and the history — calls it.`,\n\t\t);\n\t\tthis.name = 'MigrationNeedsName';\n\t}\n}\n\nexport interface MigrateOptions {\n\t/** The stage to migrate: the project's local one, or `test`. */\n\tstage?: string;\n\t/** One construct, by name. All of them when absent. */\n\tconstruct?: string;\n}\n\n/** A workspace's stage, reconciled and ready to migrate. */\nexport interface ReadyStage {\n\tworkspace: NormalizedWorkspace;\n\tmanifest: ConstructManifest;\n\tsources: Record<string, ConstructSource>;\n\t/** Every URL the stage resolved — the owner URLs among them. */\n\tenv: Record<string, string>;\n\t/** The stage it is: the local one, or `test`. */\n\tstage: string;\n}\n\n/**\n * Reconcile a stage so it can be migrated: containers up, databases, schemas,\n * roles and grants in place, owner URLs resolved.\n *\n * Migrating assumes the roles exist, and this is what makes that true rather\n * than something a developer has to have run first.\n */\nexport async function readyStage(\n\tcwd: string,\n\tstage?: string,\n): Promise<ReadyStage> {\n\tconst { workspace } = await loadWorkspaceConfig(cwd);\n\tconst local = workspace.stages.local;\n\tconst target = stage ?? local;\n\tif (target !== local && target !== TEST_STAGE) {\n\t\tthrow new MigrateDeployedStage(target, local);\n\t}\n\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\n\tconst reconciled = await reconcileWorkspace(workspace, {\n\t\tstage: target,\n\t\tmanifest,\n\t\tstart: true,\n\t});\n\n\treturn {\n\t\tworkspace,\n\t\tmanifest,\n\t\tsources,\n\t\tenv: { ...reconciled.env },\n\t\tstage: target,\n\t};\n}\n\n/** `gkm migrate [construct]`. */\nexport async function migrateCommand(\n\toptions: MigrateOptions = {},\n): Promise<void> {\n\tconst ready = await readyStage(process.cwd(), options.stage);\n\n\tconst runs = await migrateDatabases({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\t...(options.construct ? { only: options.construct } : {}),\n\t});\n\n\tif (runs.length === 0) {\n\t\tlogger.log('🗄️ No database constructs to migrate.');\n\t\treturn;\n\t}\n\n\tlogMigrations(runs);\n}\n\n/**\n * `gkm seed [construct]` — migrate, then run the seeds.\n *\n * Always migrates first: a seed is written against the schema its migrations\n * build, and against an older one it fails on a table that is not there yet.\n */\nexport async function seedCommand(options: MigrateOptions = {}): Promise<void> {\n\tconst ready = await readyStage(process.cwd(), options.stage);\n\n\tconst { migrations, seeds } = await migrateAndSeed({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\tstage: ready.stage,\n\t\t...(options.construct ? { only: options.construct } : {}),\n\t});\n\n\tif (migrations.length === 0) {\n\t\tlogger.log('🗄️ No database constructs to seed.');\n\t\treturn;\n\t}\n\n\tlogMigrations(migrations);\n\tlogSeeds(seeds);\n}\n\n/** One line per construct: up to date, or what was applied. */\nexport function logMigrations(\n\truns: readonly MigrationRun[],\n\tlog: (line: string) => void = logger.log,\n): void {\n\tfor (const { target, applied } of runs) {\n\t\tif (applied.length === 0) {\n\t\t\tlog(`🗄️ ${target.migrations}: up to date`);\n\t\t\tcontinue;\n\t\t}\n\t\tlog(`🗄️ ${target.migrations}: applied ${applied.length}`);\n\t\tfor (const name of applied) log(` ✓ ${name}`);\n\t}\n}\n\n/** One line per construct that has seeds, and each seed it ran. */\nexport function logSeeds(\n\truns: readonly SeedRun[],\n\tlog: (line: string) => void = logger.log,\n): void {\n\tfor (const { target, seeded } of runs) {\n\t\tif (seeded.length === 0) continue;\n\t\tlog(`🌱 ${target.seeds}: ran ${seeded.length}`);\n\t\tfor (const name of seeded) log(` ✓ ${name}`);\n\t}\n}\n\n/**\n * What `gkm dev` prints: one line per construct whose folder is ahead of its\n * history, and nothing when every folder is applied.\n *\n * Reported, never applied. A migration being written would otherwise run the\n * moment its file is saved, half-finished, against the database the developer\n * is working in.\n */\nexport async function reportPendingMigrations(\n\tworkspace: NormalizedWorkspace,\n\tenv: Readonly<Record<string, string>>,\n): Promise<void> {\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\n\tconst pending = await pendingMigrations({\n\t\troot: workspace.root,\n\t\tmanifest,\n\t\tsources,\n\t\tenv,\n\t});\n\n\tfor (const { target, pending: names } of pending) {\n\t\tlogger.log(\n\t\t\t`⚠️ ${target.migrations}: ${names.length} pending (${names.join(', ')}) — run gkm migrate`,\n\t\t);\n\t}\n}\n\n/**\n * What `gkm dev --migrate` and `gkm dev --seed` do before the apps start:\n * migrate, and with `seed`, run the seeds after — once, at startup, never on a\n * save, for the reason {@link reportPendingMigrations} gives.\n *\n * A failure here is thrown, unlike the report's: the developer asked for the\n * database to be brought up to date, and apps started against one in an\n * unknown state would fail somewhere less obvious.\n */\nexport async function prepareDevDatabases(\n\tworkspace: NormalizedWorkspace,\n\tenv: Readonly<Record<string, string>>,\n\t{ seed = false }: { seed?: boolean } = {},\n): Promise<void> {\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\tconst options = { root: workspace.root, manifest, sources, env };\n\n\tif (!seed) {\n\t\tlogMigrations(await migrateDatabases(options));\n\t\treturn;\n\t}\n\n\tconst { migrations, seeds } = await migrateAndSeed({\n\t\t...options,\n\t\tstage: workspace.stages.local,\n\t});\n\tlogMigrations(migrations);\n\tlogSeeds(seeds);\n}\n\nexport interface MigrationOptions {\n\t/** The construct the migration is for, by name. */\n\tconstruct: string;\n\t/** What the file is called, after its timestamp. */\n\tname?: string;\n\t/** The file's timestamp; the current time when absent. */\n\tnow?: Date;\n}\n\n/**\n * `gkm migration <construct> [name]` — write the construct's next migration.\n *\n * For a database, an empty `up`/`down` to fill in. For an auth server, what\n * its schema is missing, computed against its tenant once every committed\n * migration has been applied — so the file is only the change, and is\n * reviewed and committed like any other.\n */\nexport async function migrationCommand(\n\toptions: MigrationOptions,\n): Promise<string | undefined> {\n\tconst ready = await readyStage(process.cwd());\n\tconst targets = migrationTargets(ready.manifest);\n\tconst id = canonicalId(options.construct);\n\n\tconst auth = authConstruct(ready.sources, id);\n\tif (auth) {\n\t\tconst target = targets.find((t) => t.id === auth.databaseId);\n\t\tif (!target) {\n\t\t\tthrow new NoSuchMigrationTarget(auth.databaseId, ids(targets));\n\t\t}\n\t\treturn writeAuthMigration(ready, auth, target, options);\n\t}\n\n\tconst target = targets.find((t) => t.id === id);\n\tif (!target) {\n\t\tthrow new NoSuchMigrationTarget(options.construct, [\n\t\t\t...ids(targets),\n\t\t\t...authIds(ready.sources),\n\t\t]);\n\t}\n\tif (!options.name) throw new MigrationNeedsName(options.construct);\n\n\tconst file = await write(\n\t\tready.workspace.root,\n\t\ttarget,\n\t\t`${stamp(options.now)}_${slug(options.name)}.ts`,\n\t\tSTUB,\n\t);\n\tlogger.log(`📝 ${file}`);\n\treturn file;\n}\n\n/** What an auth construct offers `gkm migration`, duck-typed across copies. */\ninterface AuthConstruct {\n\tid: string;\n\tdatabaseId: string;\n\tpendingMigration(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<string | undefined>;\n}\n\nfunction authConstruct(\n\tsources: Readonly<Record<string, ConstructSource>>,\n\tid: string,\n): AuthConstruct | undefined {\n\tconst construct = sources[id]?.construct as\n\t\t| Partial<AuthConstruct>\n\t\t| undefined;\n\treturn construct &&\n\t\ttypeof construct.pendingMigration === 'function' &&\n\t\ttypeof construct.databaseId === 'string'\n\t\t? (construct as AuthConstruct)\n\t\t: undefined;\n}\n\nfunction authIds(sources: Readonly<Record<string, ConstructSource>>): string[] {\n\treturn Object.keys(sources).filter((id) => authConstruct(sources, id));\n}\n\nasync function writeAuthMigration(\n\tready: ReadyStage,\n\tauth: AuthConstruct,\n\ttarget: MigrationTarget,\n\toptions: MigrationOptions,\n): Promise<string | undefined> {\n\t// Against a tenant already at its committed migrations, so the comparison\n\t// finds only what the files do not yet say.\n\tawait migrateDatabases({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\tonly: target.id,\n\t});\n\n\t// Its secret and URL come from the stage's secrets; the tenant's owner URL\n\t// from the reconcile above, which wins.\n\tconst stage = ready.workspace.stages.local;\n\tconst secrets = await loadSecretsForApp(\n\t\tawait secretsStoreFor(ready.workspace, stage),\n\t\tstage,\n\t);\n\tconst pending = await auth.pendingMigration({\n\t\tenvParser: new EnvironmentParser({ ...secrets, ...ready.env }),\n\t\tcontext: serviceContext,\n\t});\n\n\tif (!pending) {\n\t\tlogger.log(`✓ ${target.migrations}: ${auth.id} is up to date`);\n\t\treturn undefined;\n\t}\n\n\tconst file = await write(\n\t\tready.workspace.root,\n\t\ttarget,\n\t\t`${stamp(options.now)}_${slug(options.name ?? 'better_auth')}.sql`,\n\t\t`${pending.trim()}\\n`,\n\t);\n\tlogger.log(`📝 ${file}`);\n\treturn file;\n}\n\nconst STUB = `import type { Kysely } from 'kysely';\n\nexport async function up(db: Kysely<unknown>): Promise<void> {\n // await db.schema.createTable('…').addColumn(…).execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n // await db.schema.dropTable('…').execute();\n}\n`;\n\nasync function write(\n\troot: string,\n\ttarget: MigrationTarget,\n\tfile: string,\n\tcontent: string,\n): Promise<string> {\n\tconst folder = join(root, target.migrations);\n\tawait mkdir(folder, { recursive: true });\n\tawait writeFile(join(folder, file), content, { flag: 'wx' });\n\treturn `${target.migrations}/${file}`;\n}\n\nfunction ids(targets: readonly MigrationTarget[]): string[] {\n\treturn targets.map((target) => target.id);\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { r as discover } from "./discover-BPelzeGW.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { j as TEST_STAGE } from "./workspace-CJ0lKFq-.mjs";
|
|
3
3
|
import { loadWorkspaceConfig } from "./config.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { Mn as secretsStoreFor, ft as constructGlobs, kn as loadSecretsForApp, mt as reconcileWorkspace } from "./seeds-perzdN81.mjs";
|
|
5
5
|
import { a as NoSuchMigrationTarget, d as pendingMigrations, l as migrateAndSeed, u as migrateDatabases } from "./databases-CzszQV0N.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { i as stamp, r as slug } from "./index.mjs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
9
9
|
import { canonicalId, migrationTargets } from "@geekmidas/manifest";
|
|
@@ -262,4 +262,4 @@ function ids(targets) {
|
|
|
262
262
|
//#endregion
|
|
263
263
|
export { migrateAndSeed, migrateCommand, migrationCommand, prepareDevDatabases, reportPendingMigrations, seedCommand };
|
|
264
264
|
|
|
265
|
-
//# sourceMappingURL=migrate-
|
|
265
|
+
//# sourceMappingURL=migrate-CKLJ73OO.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-ggkPVJRC.mjs","names":[],"sources":["../src/migrate/index.ts"],"sourcesContent":["/**\n * `gkm migrate`, `gkm seed` and `gkm migration` — applying a construct's\n * migrations, running its seeds, and writing its next migration.\n *\n * Both work on a stage this machine reconciles: its containers up, its roles\n * provisioned, its owner URLs resolved. A deployed stage is migrated by the\n * deploy that ships it, which is the one thing that can reach its database.\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { EnvironmentParser } from '@geekmidas/envkit';\nimport {\n\ttype ConstructManifest,\n\tcanonicalId,\n\ttype MigrationTarget,\n\tmigrationTargets,\n} from '@geekmidas/manifest';\nimport {\n\ttype ServiceRegisterOptions,\n\tserviceContext,\n} from '@geekmidas/services';\nimport { loadWorkspaceConfig } from '../config';\nimport { loadSecretsForApp } from '../credentials';\nimport { type ConstructSource, discover } from '../reconcile/discover';\nimport { constructGlobs, reconcileWorkspace } from '../reconcile/workspace.js';\nimport { secretsStoreFor } from '../secrets/store.js';\nimport { TEST_STAGE } from '../workspace/stages';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport {\n\ttype MigrationRun,\n\tmigrateAndSeed,\n\tmigrateDatabases,\n\tNoSuchMigrationTarget,\n\tpendingMigrations,\n\ttype SeedRun,\n} from './databases';\nimport { slug, stamp } from './names';\n\nexport {\n\tKyselyNotFound,\n\tMigrationFailed,\n\ttype MigrationRun,\n\tMigrationsOutsideFolder,\n\tmigrateAndSeed,\n\tmigrateDatabases,\n\tNoOwnerCredential,\n\tNoSuchMigrationTarget,\n\ttype PendingMigrations,\n\tpendingMigrations,\n\tSeedFailed,\n\ttype SeedOptions,\n\ttype SeedRun,\n\tseedDatabases,\n\tUnknownDatabaseFolder,\n\tUnknownMigrationFolder,\n} from './databases';\nexport { slug, stamp } from './names';\nexport {\n\tDuplicateMigration,\n\tMigrationHasNoUp,\n\tSeedHasNoSeed,\n} from './provider';\n\nconst logger = console;\n\n/** `gkm migrate --stage` named a stage only a deploy can reach. */\nexport class MigrateDeployedStage extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly local: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${stage}' is a deployed stage, and its database is migrated by ` +\n\t\t\t\t`\\`gkm deploy --stage ${stage}\\`, which is what can reach it. ` +\n\t\t\t\t`\\`gkm migrate\\` runs against '${local}' or '${TEST_STAGE}'.`,\n\t\t);\n\t\tthis.name = 'MigrateDeployedStage';\n\t}\n}\n\n/** `gkm migration` for a Kysely database, with no name for the file. */\nexport class MigrationNeedsName extends Error {\n\tconstructor(readonly construct: string) {\n\t\tsuper(\n\t\t\t`Name the migration: \\`gkm migration ${construct} add_workouts\\`. ` +\n\t\t\t\t`The name is what the file — and the history — calls it.`,\n\t\t);\n\t\tthis.name = 'MigrationNeedsName';\n\t}\n}\n\nexport interface MigrateOptions {\n\t/** The stage to migrate: the project's local one, or `test`. */\n\tstage?: string;\n\t/** One construct, by name. All of them when absent. */\n\tconstruct?: string;\n}\n\n/** A workspace's stage, reconciled and ready to migrate. */\nexport interface ReadyStage {\n\tworkspace: NormalizedWorkspace;\n\tmanifest: ConstructManifest;\n\tsources: Record<string, ConstructSource>;\n\t/** Every URL the stage resolved — the owner URLs among them. */\n\tenv: Record<string, string>;\n\t/** The stage it is: the local one, or `test`. */\n\tstage: string;\n}\n\n/**\n * Reconcile a stage so it can be migrated: containers up, databases, schemas,\n * roles and grants in place, owner URLs resolved.\n *\n * Migrating assumes the roles exist, and this is what makes that true rather\n * than something a developer has to have run first.\n */\nexport async function readyStage(\n\tcwd: string,\n\tstage?: string,\n): Promise<ReadyStage> {\n\tconst { workspace } = await loadWorkspaceConfig(cwd);\n\tconst local = workspace.stages.local;\n\tconst target = stage ?? local;\n\tif (target !== local && target !== TEST_STAGE) {\n\t\tthrow new MigrateDeployedStage(target, local);\n\t}\n\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\n\tconst reconciled = await reconcileWorkspace(workspace, {\n\t\tstage: target,\n\t\tmanifest,\n\t\tstart: true,\n\t});\n\n\treturn {\n\t\tworkspace,\n\t\tmanifest,\n\t\tsources,\n\t\tenv: { ...reconciled.env },\n\t\tstage: target,\n\t};\n}\n\n/** `gkm migrate [construct]`. */\nexport async function migrateCommand(\n\toptions: MigrateOptions = {},\n): Promise<void> {\n\tconst ready = await readyStage(process.cwd(), options.stage);\n\n\tconst runs = await migrateDatabases({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\t...(options.construct ? { only: options.construct } : {}),\n\t});\n\n\tif (runs.length === 0) {\n\t\tlogger.log('🗄️ No database constructs to migrate.');\n\t\treturn;\n\t}\n\n\tlogMigrations(runs);\n}\n\n/**\n * `gkm seed [construct]` — migrate, then run the seeds.\n *\n * Always migrates first: a seed is written against the schema its migrations\n * build, and against an older one it fails on a table that is not there yet.\n */\nexport async function seedCommand(options: MigrateOptions = {}): Promise<void> {\n\tconst ready = await readyStage(process.cwd(), options.stage);\n\n\tconst { migrations, seeds } = await migrateAndSeed({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\tstage: ready.stage,\n\t\t...(options.construct ? { only: options.construct } : {}),\n\t});\n\n\tif (migrations.length === 0) {\n\t\tlogger.log('🗄️ No database constructs to seed.');\n\t\treturn;\n\t}\n\n\tlogMigrations(migrations);\n\tlogSeeds(seeds);\n}\n\n/** One line per construct: up to date, or what was applied. */\nexport function logMigrations(\n\truns: readonly MigrationRun[],\n\tlog: (line: string) => void = logger.log,\n): void {\n\tfor (const { target, applied } of runs) {\n\t\tif (applied.length === 0) {\n\t\t\tlog(`🗄️ ${target.migrations}: up to date`);\n\t\t\tcontinue;\n\t\t}\n\t\tlog(`🗄️ ${target.migrations}: applied ${applied.length}`);\n\t\tfor (const name of applied) log(` ✓ ${name}`);\n\t}\n}\n\n/** One line per construct that has seeds, and each seed it ran. */\nexport function logSeeds(\n\truns: readonly SeedRun[],\n\tlog: (line: string) => void = logger.log,\n): void {\n\tfor (const { target, seeded } of runs) {\n\t\tif (seeded.length === 0) continue;\n\t\tlog(`🌱 ${target.seeds}: ran ${seeded.length}`);\n\t\tfor (const name of seeded) log(` ✓ ${name}`);\n\t}\n}\n\n/**\n * What `gkm dev` prints: one line per construct whose folder is ahead of its\n * history, and nothing when every folder is applied.\n *\n * Reported, never applied. A migration being written would otherwise run the\n * moment its file is saved, half-finished, against the database the developer\n * is working in.\n */\nexport async function reportPendingMigrations(\n\tworkspace: NormalizedWorkspace,\n\tenv: Readonly<Record<string, string>>,\n): Promise<void> {\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\n\tconst pending = await pendingMigrations({\n\t\troot: workspace.root,\n\t\tmanifest,\n\t\tsources,\n\t\tenv,\n\t});\n\n\tfor (const { target, pending: names } of pending) {\n\t\tlogger.log(\n\t\t\t`⚠️ ${target.migrations}: ${names.length} pending (${names.join(', ')}) — run gkm migrate`,\n\t\t);\n\t}\n}\n\n/**\n * What `gkm dev --migrate` and `gkm dev --seed` do before the apps start:\n * migrate, and with `seed`, run the seeds after — once, at startup, never on a\n * save, for the reason {@link reportPendingMigrations} gives.\n *\n * A failure here is thrown, unlike the report's: the developer asked for the\n * database to be brought up to date, and apps started against one in an\n * unknown state would fail somewhere less obvious.\n */\nexport async function prepareDevDatabases(\n\tworkspace: NormalizedWorkspace,\n\tenv: Readonly<Record<string, string>>,\n\t{ seed = false }: { seed?: boolean } = {},\n): Promise<void> {\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\tconst options = { root: workspace.root, manifest, sources, env };\n\n\tif (!seed) {\n\t\tlogMigrations(await migrateDatabases(options));\n\t\treturn;\n\t}\n\n\tconst { migrations, seeds } = await migrateAndSeed({\n\t\t...options,\n\t\tstage: workspace.stages.local,\n\t});\n\tlogMigrations(migrations);\n\tlogSeeds(seeds);\n}\n\nexport interface MigrationOptions {\n\t/** The construct the migration is for, by name. */\n\tconstruct: string;\n\t/** What the file is called, after its timestamp. */\n\tname?: string;\n\t/** The file's timestamp; the current time when absent. */\n\tnow?: Date;\n}\n\n/**\n * `gkm migration <construct> [name]` — write the construct's next migration.\n *\n * For a database, an empty `up`/`down` to fill in. For an auth server, what\n * its schema is missing, computed against its tenant once every committed\n * migration has been applied — so the file is only the change, and is\n * reviewed and committed like any other.\n */\nexport async function migrationCommand(\n\toptions: MigrationOptions,\n): Promise<string | undefined> {\n\tconst ready = await readyStage(process.cwd());\n\tconst targets = migrationTargets(ready.manifest);\n\tconst id = canonicalId(options.construct);\n\n\tconst auth = authConstruct(ready.sources, id);\n\tif (auth) {\n\t\tconst target = targets.find((t) => t.id === auth.databaseId);\n\t\tif (!target) {\n\t\t\tthrow new NoSuchMigrationTarget(auth.databaseId, ids(targets));\n\t\t}\n\t\treturn writeAuthMigration(ready, auth, target, options);\n\t}\n\n\tconst target = targets.find((t) => t.id === id);\n\tif (!target) {\n\t\tthrow new NoSuchMigrationTarget(options.construct, [\n\t\t\t...ids(targets),\n\t\t\t...authIds(ready.sources),\n\t\t]);\n\t}\n\tif (!options.name) throw new MigrationNeedsName(options.construct);\n\n\tconst file = await write(\n\t\tready.workspace.root,\n\t\ttarget,\n\t\t`${stamp(options.now)}_${slug(options.name)}.ts`,\n\t\tSTUB,\n\t);\n\tlogger.log(`📝 ${file}`);\n\treturn file;\n}\n\n/** What an auth construct offers `gkm migration`, duck-typed across copies. */\ninterface AuthConstruct {\n\tid: string;\n\tdatabaseId: string;\n\tpendingMigration(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<string | undefined>;\n}\n\nfunction authConstruct(\n\tsources: Readonly<Record<string, ConstructSource>>,\n\tid: string,\n): AuthConstruct | undefined {\n\tconst construct = sources[id]?.construct as\n\t\t| Partial<AuthConstruct>\n\t\t| undefined;\n\treturn construct &&\n\t\ttypeof construct.pendingMigration === 'function' &&\n\t\ttypeof construct.databaseId === 'string'\n\t\t? (construct as AuthConstruct)\n\t\t: undefined;\n}\n\nfunction authIds(sources: Readonly<Record<string, ConstructSource>>): string[] {\n\treturn Object.keys(sources).filter((id) => authConstruct(sources, id));\n}\n\nasync function writeAuthMigration(\n\tready: ReadyStage,\n\tauth: AuthConstruct,\n\ttarget: MigrationTarget,\n\toptions: MigrationOptions,\n): Promise<string | undefined> {\n\t// Against a tenant already at its committed migrations, so the comparison\n\t// finds only what the files do not yet say.\n\tawait migrateDatabases({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\tonly: target.id,\n\t});\n\n\t// Its secret and URL come from the stage's secrets; the tenant's owner URL\n\t// from the reconcile above, which wins.\n\tconst stage = ready.workspace.stages.local;\n\tconst secrets = await loadSecretsForApp(\n\t\tawait secretsStoreFor(ready.workspace, stage),\n\t\tstage,\n\t);\n\tconst pending = await auth.pendingMigration({\n\t\tenvParser: new EnvironmentParser({ ...secrets, ...ready.env }),\n\t\tcontext: serviceContext,\n\t});\n\n\tif (!pending) {\n\t\tlogger.log(`✓ ${target.migrations}: ${auth.id} is up to date`);\n\t\treturn undefined;\n\t}\n\n\tconst file = await write(\n\t\tready.workspace.root,\n\t\ttarget,\n\t\t`${stamp(options.now)}_${slug(options.name ?? 'better_auth')}.sql`,\n\t\t`${pending.trim()}\\n`,\n\t);\n\tlogger.log(`📝 ${file}`);\n\treturn file;\n}\n\nconst STUB = `import type { Kysely } from 'kysely';\n\nexport async function up(db: Kysely<unknown>): Promise<void> {\n // await db.schema.createTable('…').addColumn(…).execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n // await db.schema.dropTable('…').execute();\n}\n`;\n\nasync function write(\n\troot: string,\n\ttarget: MigrationTarget,\n\tfile: string,\n\tcontent: string,\n): Promise<string> {\n\tconst folder = join(root, target.migrations);\n\tawait mkdir(folder, { recursive: true });\n\tawait writeFile(join(folder, file), content, { flag: 'wx' });\n\treturn `${target.migrations}/${file}`;\n}\n\nfunction ids(targets: readonly MigrationTarget[]): string[] {\n\treturn targets.map((target) => target.id);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"migrate-CKLJ73OO.mjs","names":[],"sources":["../src/migrate/index.ts"],"sourcesContent":["/**\n * `gkm migrate`, `gkm seed` and `gkm migration` — applying a construct's\n * migrations, running its seeds, and writing its next migration.\n *\n * Both work on a stage this machine reconciles: its containers up, its roles\n * provisioned, its owner URLs resolved. A deployed stage is migrated by the\n * deploy that ships it, which is the one thing that can reach its database.\n */\n\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { EnvironmentParser } from '@geekmidas/envkit';\nimport {\n\ttype ConstructManifest,\n\tcanonicalId,\n\ttype MigrationTarget,\n\tmigrationTargets,\n} from '@geekmidas/manifest';\nimport {\n\ttype ServiceRegisterOptions,\n\tserviceContext,\n} from '@geekmidas/services';\nimport { loadWorkspaceConfig } from '../config';\nimport { loadSecretsForApp } from '../credentials';\nimport { type ConstructSource, discover } from '../reconcile/discover';\nimport { constructGlobs, reconcileWorkspace } from '../reconcile/workspace.js';\nimport { secretsStoreFor } from '../secrets/store.js';\nimport { TEST_STAGE } from '../workspace/stages';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport {\n\ttype MigrationRun,\n\tmigrateAndSeed,\n\tmigrateDatabases,\n\tNoSuchMigrationTarget,\n\tpendingMigrations,\n\ttype SeedRun,\n} from './databases';\nimport { slug, stamp } from './names';\n\nexport {\n\tKyselyNotFound,\n\tMigrationFailed,\n\ttype MigrationRun,\n\tMigrationsOutsideFolder,\n\tmigrateAndSeed,\n\tmigrateDatabases,\n\tNoOwnerCredential,\n\tNoSuchMigrationTarget,\n\ttype PendingMigrations,\n\tpendingMigrations,\n\tSeedFailed,\n\ttype SeedOptions,\n\ttype SeedRun,\n\tseedDatabases,\n\tUnknownDatabaseFolder,\n\tUnknownMigrationFolder,\n} from './databases';\nexport { slug, stamp } from './names';\nexport {\n\tDuplicateMigration,\n\tMigrationHasNoUp,\n\tSeedHasNoSeed,\n} from './provider';\n\nconst logger = console;\n\n/** `gkm migrate --stage` named a stage only a deploy can reach. */\nexport class MigrateDeployedStage extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly local: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${stage}' is a deployed stage, and its database is migrated by ` +\n\t\t\t\t`\\`gkm deploy --stage ${stage}\\`, which is what can reach it. ` +\n\t\t\t\t`\\`gkm migrate\\` runs against '${local}' or '${TEST_STAGE}'.`,\n\t\t);\n\t\tthis.name = 'MigrateDeployedStage';\n\t}\n}\n\n/** `gkm migration` for a Kysely database, with no name for the file. */\nexport class MigrationNeedsName extends Error {\n\tconstructor(readonly construct: string) {\n\t\tsuper(\n\t\t\t`Name the migration: \\`gkm migration ${construct} add_workouts\\`. ` +\n\t\t\t\t`The name is what the file — and the history — calls it.`,\n\t\t);\n\t\tthis.name = 'MigrationNeedsName';\n\t}\n}\n\nexport interface MigrateOptions {\n\t/** The stage to migrate: the project's local one, or `test`. */\n\tstage?: string;\n\t/** One construct, by name. All of them when absent. */\n\tconstruct?: string;\n}\n\n/** A workspace's stage, reconciled and ready to migrate. */\nexport interface ReadyStage {\n\tworkspace: NormalizedWorkspace;\n\tmanifest: ConstructManifest;\n\tsources: Record<string, ConstructSource>;\n\t/** Every URL the stage resolved — the owner URLs among them. */\n\tenv: Record<string, string>;\n\t/** The stage it is: the local one, or `test`. */\n\tstage: string;\n}\n\n/**\n * Reconcile a stage so it can be migrated: containers up, databases, schemas,\n * roles and grants in place, owner URLs resolved.\n *\n * Migrating assumes the roles exist, and this is what makes that true rather\n * than something a developer has to have run first.\n */\nexport async function readyStage(\n\tcwd: string,\n\tstage?: string,\n): Promise<ReadyStage> {\n\tconst { workspace } = await loadWorkspaceConfig(cwd);\n\tconst local = workspace.stages.local;\n\tconst target = stage ?? local;\n\tif (target !== local && target !== TEST_STAGE) {\n\t\tthrow new MigrateDeployedStage(target, local);\n\t}\n\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\n\tconst reconciled = await reconcileWorkspace(workspace, {\n\t\tstage: target,\n\t\tmanifest,\n\t\tstart: true,\n\t});\n\n\treturn {\n\t\tworkspace,\n\t\tmanifest,\n\t\tsources,\n\t\tenv: { ...reconciled.env },\n\t\tstage: target,\n\t};\n}\n\n/** `gkm migrate [construct]`. */\nexport async function migrateCommand(\n\toptions: MigrateOptions = {},\n): Promise<void> {\n\tconst ready = await readyStage(process.cwd(), options.stage);\n\n\tconst runs = await migrateDatabases({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\t...(options.construct ? { only: options.construct } : {}),\n\t});\n\n\tif (runs.length === 0) {\n\t\tlogger.log('🗄️ No database constructs to migrate.');\n\t\treturn;\n\t}\n\n\tlogMigrations(runs);\n}\n\n/**\n * `gkm seed [construct]` — migrate, then run the seeds.\n *\n * Always migrates first: a seed is written against the schema its migrations\n * build, and against an older one it fails on a table that is not there yet.\n */\nexport async function seedCommand(options: MigrateOptions = {}): Promise<void> {\n\tconst ready = await readyStage(process.cwd(), options.stage);\n\n\tconst { migrations, seeds } = await migrateAndSeed({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\tstage: ready.stage,\n\t\t...(options.construct ? { only: options.construct } : {}),\n\t});\n\n\tif (migrations.length === 0) {\n\t\tlogger.log('🗄️ No database constructs to seed.');\n\t\treturn;\n\t}\n\n\tlogMigrations(migrations);\n\tlogSeeds(seeds);\n}\n\n/** One line per construct: up to date, or what was applied. */\nexport function logMigrations(\n\truns: readonly MigrationRun[],\n\tlog: (line: string) => void = logger.log,\n): void {\n\tfor (const { target, applied } of runs) {\n\t\tif (applied.length === 0) {\n\t\t\tlog(`🗄️ ${target.migrations}: up to date`);\n\t\t\tcontinue;\n\t\t}\n\t\tlog(`🗄️ ${target.migrations}: applied ${applied.length}`);\n\t\tfor (const name of applied) log(` ✓ ${name}`);\n\t}\n}\n\n/** One line per construct that has seeds, and each seed it ran. */\nexport function logSeeds(\n\truns: readonly SeedRun[],\n\tlog: (line: string) => void = logger.log,\n): void {\n\tfor (const { target, seeded } of runs) {\n\t\tif (seeded.length === 0) continue;\n\t\tlog(`🌱 ${target.seeds}: ran ${seeded.length}`);\n\t\tfor (const name of seeded) log(` ✓ ${name}`);\n\t}\n}\n\n/**\n * What `gkm dev` prints: one line per construct whose folder is ahead of its\n * history, and nothing when every folder is applied.\n *\n * Reported, never applied. A migration being written would otherwise run the\n * moment its file is saved, half-finished, against the database the developer\n * is working in.\n */\nexport async function reportPendingMigrations(\n\tworkspace: NormalizedWorkspace,\n\tenv: Readonly<Record<string, string>>,\n): Promise<void> {\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\n\tconst pending = await pendingMigrations({\n\t\troot: workspace.root,\n\t\tmanifest,\n\t\tsources,\n\t\tenv,\n\t});\n\n\tfor (const { target, pending: names } of pending) {\n\t\tlogger.log(\n\t\t\t`⚠️ ${target.migrations}: ${names.length} pending (${names.join(', ')}) — run gkm migrate`,\n\t\t);\n\t}\n}\n\n/**\n * What `gkm dev --migrate` and `gkm dev --seed` do before the apps start:\n * migrate, and with `seed`, run the seeds after — once, at startup, never on a\n * save, for the reason {@link reportPendingMigrations} gives.\n *\n * A failure here is thrown, unlike the report's: the developer asked for the\n * database to be brought up to date, and apps started against one in an\n * unknown state would fail somewhere less obvious.\n */\nexport async function prepareDevDatabases(\n\tworkspace: NormalizedWorkspace,\n\tenv: Readonly<Record<string, string>>,\n\t{ seed = false }: { seed?: boolean } = {},\n): Promise<void> {\n\tconst sources: Record<string, ConstructSource> = {};\n\tconst manifest = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\tsources,\n\t});\n\tconst options = { root: workspace.root, manifest, sources, env };\n\n\tif (!seed) {\n\t\tlogMigrations(await migrateDatabases(options));\n\t\treturn;\n\t}\n\n\tconst { migrations, seeds } = await migrateAndSeed({\n\t\t...options,\n\t\tstage: workspace.stages.local,\n\t});\n\tlogMigrations(migrations);\n\tlogSeeds(seeds);\n}\n\nexport interface MigrationOptions {\n\t/** The construct the migration is for, by name. */\n\tconstruct: string;\n\t/** What the file is called, after its timestamp. */\n\tname?: string;\n\t/** The file's timestamp; the current time when absent. */\n\tnow?: Date;\n}\n\n/**\n * `gkm migration <construct> [name]` — write the construct's next migration.\n *\n * For a database, an empty `up`/`down` to fill in. For an auth server, what\n * its schema is missing, computed against its tenant once every committed\n * migration has been applied — so the file is only the change, and is\n * reviewed and committed like any other.\n */\nexport async function migrationCommand(\n\toptions: MigrationOptions,\n): Promise<string | undefined> {\n\tconst ready = await readyStage(process.cwd());\n\tconst targets = migrationTargets(ready.manifest);\n\tconst id = canonicalId(options.construct);\n\n\tconst auth = authConstruct(ready.sources, id);\n\tif (auth) {\n\t\tconst target = targets.find((t) => t.id === auth.databaseId);\n\t\tif (!target) {\n\t\t\tthrow new NoSuchMigrationTarget(auth.databaseId, ids(targets));\n\t\t}\n\t\treturn writeAuthMigration(ready, auth, target, options);\n\t}\n\n\tconst target = targets.find((t) => t.id === id);\n\tif (!target) {\n\t\tthrow new NoSuchMigrationTarget(options.construct, [\n\t\t\t...ids(targets),\n\t\t\t...authIds(ready.sources),\n\t\t]);\n\t}\n\tif (!options.name) throw new MigrationNeedsName(options.construct);\n\n\tconst file = await write(\n\t\tready.workspace.root,\n\t\ttarget,\n\t\t`${stamp(options.now)}_${slug(options.name)}.ts`,\n\t\tSTUB,\n\t);\n\tlogger.log(`📝 ${file}`);\n\treturn file;\n}\n\n/** What an auth construct offers `gkm migration`, duck-typed across copies. */\ninterface AuthConstruct {\n\tid: string;\n\tdatabaseId: string;\n\tpendingMigration(\n\t\toptions: ServiceRegisterOptions,\n\t): Promise<string | undefined>;\n}\n\nfunction authConstruct(\n\tsources: Readonly<Record<string, ConstructSource>>,\n\tid: string,\n): AuthConstruct | undefined {\n\tconst construct = sources[id]?.construct as\n\t\t| Partial<AuthConstruct>\n\t\t| undefined;\n\treturn construct &&\n\t\ttypeof construct.pendingMigration === 'function' &&\n\t\ttypeof construct.databaseId === 'string'\n\t\t? (construct as AuthConstruct)\n\t\t: undefined;\n}\n\nfunction authIds(sources: Readonly<Record<string, ConstructSource>>): string[] {\n\treturn Object.keys(sources).filter((id) => authConstruct(sources, id));\n}\n\nasync function writeAuthMigration(\n\tready: ReadyStage,\n\tauth: AuthConstruct,\n\ttarget: MigrationTarget,\n\toptions: MigrationOptions,\n): Promise<string | undefined> {\n\t// Against a tenant already at its committed migrations, so the comparison\n\t// finds only what the files do not yet say.\n\tawait migrateDatabases({\n\t\troot: ready.workspace.root,\n\t\tmanifest: ready.manifest,\n\t\tsources: ready.sources,\n\t\tenv: ready.env,\n\t\tonly: target.id,\n\t});\n\n\t// Its secret and URL come from the stage's secrets; the tenant's owner URL\n\t// from the reconcile above, which wins.\n\tconst stage = ready.workspace.stages.local;\n\tconst secrets = await loadSecretsForApp(\n\t\tawait secretsStoreFor(ready.workspace, stage),\n\t\tstage,\n\t);\n\tconst pending = await auth.pendingMigration({\n\t\tenvParser: new EnvironmentParser({ ...secrets, ...ready.env }),\n\t\tcontext: serviceContext,\n\t});\n\n\tif (!pending) {\n\t\tlogger.log(`✓ ${target.migrations}: ${auth.id} is up to date`);\n\t\treturn undefined;\n\t}\n\n\tconst file = await write(\n\t\tready.workspace.root,\n\t\ttarget,\n\t\t`${stamp(options.now)}_${slug(options.name ?? 'better_auth')}.sql`,\n\t\t`${pending.trim()}\\n`,\n\t);\n\tlogger.log(`📝 ${file}`);\n\treturn file;\n}\n\nconst STUB = `import type { Kysely } from 'kysely';\n\nexport async function up(db: Kysely<unknown>): Promise<void> {\n // await db.schema.createTable('…').addColumn(…).execute();\n}\n\nexport async function down(db: Kysely<unknown>): Promise<void> {\n // await db.schema.dropTable('…').execute();\n}\n`;\n\nasync function write(\n\troot: string,\n\ttarget: MigrationTarget,\n\tfile: string,\n\tcontent: string,\n): Promise<string> {\n\tconst folder = join(root, target.migrations);\n\tawait mkdir(folder, { recursive: true });\n\tawait writeFile(join(folder, file), content, { flag: 'wx' });\n\treturn `${target.migrations}/${file}`;\n}\n\nfunction ids(targets: readonly MigrationTarget[]): string[] {\n\treturn targets.map((target) => target.id);\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { N as assertDeployedStage, ct as SECRETS_STORE_PROVIDERS, lt as UnknownSecretsStoreProvider } from "./workspace-CJ0lKFq-.mjs";
|
|
2
2
|
import { loadWorkspaceSettings } from "./config.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { Mn as secretsStoreFor, Nn as storeFromConfig } from "./seeds-perzdN81.mjs";
|
|
4
4
|
//#region src/secrets/migrate.ts
|
|
5
5
|
/**
|
|
6
6
|
* `gkm secrets:migrate` — copy a deployed stage's secrets from the store
|
|
@@ -90,4 +90,4 @@ async function secretsMigrateCommand(options) {
|
|
|
90
90
|
//#endregion
|
|
91
91
|
export { secretsMigrateCommand };
|
|
92
92
|
|
|
93
|
-
//# sourceMappingURL=migrate-
|
|
93
|
+
//# sourceMappingURL=migrate-CpmO5qnl.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-
|
|
1
|
+
{"version":3,"file":"migrate-CpmO5qnl.mjs","names":[],"sources":["../src/secrets/migrate.ts"],"sourcesContent":["/**\n * `gkm secrets:migrate` — copy a deployed stage's secrets from the store\n * `secrets.store` names to another one, whole: service passwords, URLs and\n * custom keys alike, so nothing is regenerated and running services keep\n * their credentials.\n *\n * It copies and nothing more. The source is left as it was, and gkm.config.ts\n * is not edited: pointing `secrets.store` at the new store is the switch, and\n * the old copy is deleted by hand once deploys read from the new one.\n */\n\nimport { loadWorkspaceSettings } from '../config.js';\nimport { assertDeployedStage } from '../workspace/stages.js';\nimport {\n\tSECRETS_STORE_PROVIDERS,\n\ttype SecretsStoreConfig,\n\ttype SecretsStoreOptions,\n\ttype SecretsStoreProvider,\n\tsecretsStoreFor,\n\tstoreFromConfig,\n\tUnknownSecretsStoreProvider,\n} from './store.js';\n\nconst logger = console;\n\nexport interface SecretsMigrateOptions {\n\tstage: string;\n\t/** The store to copy to: `'file'`, `'ssm'` or `'secrets-manager'`. */\n\tto: string;\n\t/** The target's AWS region; defaults to the configured store's. */\n\tregion?: string;\n\t/** Overwrite a stage the target already holds. */\n\tforce?: boolean;\n\t/** The AWS profile for the stage's account, for both stores. */\n\tprofile?: string;\n}\n\n/** An AWS store to copy to, with no region given and none configured. */\nexport class MigrateTargetNeedsRegion extends Error {\n\tconstructor(readonly to: string) {\n\t\tsuper(\n\t\t\t`Copying to ${to} needs a region, and secrets.store has none to reuse. ` +\n\t\t\t\t`Pass it: gkm secrets:migrate --to ${to} --region <region> --stage <stage>.`,\n\t\t);\n\t\tthis.name = 'MigrateTargetNeedsRegion';\n\t}\n}\n\n/** The target is the store the stage is already read from. */\nexport class MigrateTargetIsSource extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly to: string,\n\t) {\n\t\tsuper(\n\t\t\t`Stage \"${stage}\" is already kept in ${to}, so there is nothing to copy. ` +\n\t\t\t\t'Name a different store with --to, or a different region with --region.',\n\t\t);\n\t\tthis.name = 'MigrateTargetIsSource';\n\t}\n}\n\n/** The configured store holds nothing for the stage. */\nexport class NoSecretsToMigrate extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly store: string,\n\t) {\n\t\tsuper(\n\t\t\t`The ${store} store holds no secrets for stage \"${stage}\", so there is nothing to copy. ` +\n\t\t\t\t`Check secrets.store in gkm.config.ts still names the store the stage is in, ` +\n\t\t\t\t`or create the stage there with gkm secrets:init --stage ${stage}.`,\n\t\t);\n\t\tthis.name = 'NoSecretsToMigrate';\n\t}\n}\n\n/** The target already holds the stage, and `--force` was not given. */\nexport class MigrateTargetHoldsStage extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly to: string,\n\t) {\n\t\tsuper(\n\t\t\t`The ${to} store already holds secrets for stage \"${stage}\". ` +\n\t\t\t\t'Pass --force to replace them with the ones being copied.',\n\t\t);\n\t\tthis.name = 'MigrateTargetHoldsStage';\n\t}\n}\n\n/** The store a `--to` name and region describe. */\nfunction targetConfig(\n\tto: string,\n\tregion: string | undefined,\n\tconfigured: SecretsStoreConfig,\n): SecretsStoreConfig {\n\tif (!(SECRETS_STORE_PROVIDERS as readonly string[]).includes(to)) {\n\t\tthrow new UnknownSecretsStoreProvider(to);\n\t}\n\tconst provider = to as SecretsStoreProvider;\n\tif (provider === 'file') return 'file';\n\n\tconst fallback =\n\t\ttypeof configured === 'object' && 'region' in configured\n\t\t\t? configured.region\n\t\t\t: undefined;\n\tconst resolved = region ?? fallback;\n\tif (!resolved) throw new MigrateTargetNeedsRegion(provider);\n\treturn { provider, region: resolved };\n}\n\nexport async function secretsMigrateCommand(\n\toptions: SecretsMigrateOptions,\n): Promise<void> {\n\tconst { stage, to } = options;\n\tconst workspace = await loadWorkspaceSettings();\n\tassertDeployedStage(workspace.stages, stage);\n\n\tconst configured = workspace.secrets.store ?? 'file';\n\tconst target = targetConfig(to, options.region, configured);\n\tconst sameStore =\n\t\ttypeof configured === 'string' || typeof target === 'string'\n\t\t\t? configured === target\n\t\t\t: configured.provider === target.provider &&\n\t\t\t\t'region' in configured &&\n\t\t\t\t'region' in target &&\n\t\t\t\tconfigured.region === target.region;\n\tif (sameStore) throw new MigrateTargetIsSource(stage, to);\n\n\tconst storeOptions: SecretsStoreOptions = options.profile\n\t\t? { profile: options.profile }\n\t\t: {};\n\tconst source = await secretsStoreFor(workspace, stage, storeOptions);\n\tconst destination = await storeFromConfig(workspace, target, storeOptions);\n\n\tconst secrets = await source.read(stage);\n\tif (!secrets) throw new NoSecretsToMigrate(stage, source.name);\n\n\tif (!options.force && (await destination.read(stage))) {\n\t\tthrow new MigrateTargetHoldsStage(stage, destination.name);\n\t}\n\n\tawait destination.write(stage, secrets);\n\n\tconst shape =\n\t\ttypeof target === 'object' && 'region' in target\n\t\t\t? `{ provider: '${target.provider}', region: '${target.region}' }`\n\t\t\t: `'file'`;\n\tlogger.log(\n\t\t`\\n✓ Copied the secrets for stage \"${stage}\" from ${source.name} to ${destination.name}`,\n\t);\n\tlogger.log(\n\t\t`\\n Next: set secrets.store to ${shape} in gkm.config.ts, so commands and deploys read from ${destination.name}.`,\n\t);\n\tlogger.log(\n\t\t` The copy in ${source.name} is left as it was; delete it once deploys read from ${destination.name}.`,\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;AAuBA,MAAM,SAAS;;AAef,IAAa,2BAAb,cAA8C,MAAM;CAC9B;CAArB,YAAY,IAAqB;EAChC,MACC,cAAc,GAAG,0FACqB,GAAG,oCAC1C;EAJoB,KAAA,KAAA;EAKpB,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,wBAAb,cAA2C,MAAM;CAEtC;CACA;CAFV,YACC,OACA,IACC;EACD,MACC,UAAU,MAAM,uBAAuB,GAAG,sGAE3C;EANS,KAAA,QAAA;EACA,KAAA,KAAA;EAMT,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,qBAAb,cAAwC,MAAM;CAEnC;CACA;CAFV,YACC,OACA,OACC;EACD,MACC,OAAO,MAAM,qCAAqC,MAAM,sKAEI,MAAM,EACnE;EAPS,KAAA,QAAA;EACA,KAAA,QAAA;EAOT,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,0BAAb,cAA6C,MAAM;CAExC;CACA;CAFV,YACC,OACA,IACC;EACD,MACC,OAAO,GAAG,0CAA0C,MAAM,4DAE3D;EANS,KAAA,QAAA;EACA,KAAA,KAAA;EAMT,KAAK,OAAO;CACb;AACD;;AAGA,SAAS,aACR,IACA,QACA,YACqB;CACrB,IAAI,CAAE,wBAA8C,SAAS,EAAE,GAC9D,MAAM,IAAI,4BAA4B,EAAE;CAEzC,MAAM,WAAW;CACjB,IAAI,aAAa,QAAQ,OAAO;CAEhC,MAAM,WACL,OAAO,eAAe,YAAY,YAAY,aAC3C,WAAW,SACX,KAAA;CACJ,MAAM,WAAW,UAAU;CAC3B,IAAI,CAAC,UAAU,MAAM,IAAI,yBAAyB,QAAQ;CAC1D,OAAO;EAAE;EAAU,QAAQ;CAAS;AACrC;AAEA,eAAsB,sBACrB,SACgB;CAChB,MAAM,EAAE,OAAO,OAAO;CACtB,MAAM,YAAY,MAAM,sBAAsB;CAC9C,oBAAoB,UAAU,QAAQ,KAAK;CAE3C,MAAM,aAAa,UAAU,QAAQ,SAAS;CAC9C,MAAM,SAAS,aAAa,IAAI,QAAQ,QAAQ,UAAU;CAQ1D,IANC,OAAO,eAAe,YAAY,OAAO,WAAW,WACjD,eAAe,SACf,WAAW,aAAa,OAAO,YAChC,YAAY,cACZ,YAAY,UACZ,WAAW,WAAW,OAAO,QACjB,MAAM,IAAI,sBAAsB,OAAO,EAAE;CAExD,MAAM,eAAoC,QAAQ,UAC/C,EAAE,SAAS,QAAQ,QAAQ,IAC3B,CAAC;CACJ,MAAM,SAAS,MAAM,gBAAgB,WAAW,OAAO,YAAY;CACnE,MAAM,cAAc,MAAM,gBAAgB,WAAW,QAAQ,YAAY;CAEzE,MAAM,UAAU,MAAM,OAAO,KAAK,KAAK;CACvC,IAAI,CAAC,SAAS,MAAM,IAAI,mBAAmB,OAAO,OAAO,IAAI;CAE7D,IAAI,CAAC,QAAQ,SAAU,MAAM,YAAY,KAAK,KAAK,GAClD,MAAM,IAAI,wBAAwB,OAAO,YAAY,IAAI;CAG1D,MAAM,YAAY,MAAM,OAAO,OAAO;CAEtC,MAAM,QACL,OAAO,WAAW,YAAY,YAAY,SACvC,gBAAgB,OAAO,SAAS,cAAc,OAAO,OAAO,OAC5D;CACJ,OAAO,IACN,qCAAqC,MAAM,SAAS,OAAO,KAAK,MAAM,YAAY,MACnF;CACA,OAAO,IACN,kCAAkC,MAAM,uDAAuD,YAAY,KAAK,EACjH;CACA,OAAO,IACN,iBAAiB,OAAO,KAAK,uDAAuD,YAAY,KAAK,EACtG;AACD"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const require_workspace = require("./workspace-
|
|
1
|
+
const require_workspace = require("./workspace-CwW3Gkru.cjs");
|
|
2
2
|
const require_config = require("./config.cjs");
|
|
3
|
-
const require_seeds = require("./seeds-
|
|
3
|
+
const require_seeds = require("./seeds-CN6tB-va.cjs");
|
|
4
4
|
//#region src/secrets/migrate.ts
|
|
5
5
|
/**
|
|
6
6
|
* `gkm secrets:migrate` — copy a deployed stage's secrets from the store
|
|
@@ -90,4 +90,4 @@ async function secretsMigrateCommand(options) {
|
|
|
90
90
|
//#endregion
|
|
91
91
|
exports.secretsMigrateCommand = secretsMigrateCommand;
|
|
92
92
|
|
|
93
|
-
//# sourceMappingURL=migrate-
|
|
93
|
+
//# sourceMappingURL=migrate-DPF7ZF8M.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-
|
|
1
|
+
{"version":3,"file":"migrate-DPF7ZF8M.cjs","names":["SECRETS_STORE_PROVIDERS","UnknownSecretsStoreProvider","loadWorkspaceSettings","secretsStoreFor","storeFromConfig"],"sources":["../src/secrets/migrate.ts"],"sourcesContent":["/**\n * `gkm secrets:migrate` — copy a deployed stage's secrets from the store\n * `secrets.store` names to another one, whole: service passwords, URLs and\n * custom keys alike, so nothing is regenerated and running services keep\n * their credentials.\n *\n * It copies and nothing more. The source is left as it was, and gkm.config.ts\n * is not edited: pointing `secrets.store` at the new store is the switch, and\n * the old copy is deleted by hand once deploys read from the new one.\n */\n\nimport { loadWorkspaceSettings } from '../config.js';\nimport { assertDeployedStage } from '../workspace/stages.js';\nimport {\n\tSECRETS_STORE_PROVIDERS,\n\ttype SecretsStoreConfig,\n\ttype SecretsStoreOptions,\n\ttype SecretsStoreProvider,\n\tsecretsStoreFor,\n\tstoreFromConfig,\n\tUnknownSecretsStoreProvider,\n} from './store.js';\n\nconst logger = console;\n\nexport interface SecretsMigrateOptions {\n\tstage: string;\n\t/** The store to copy to: `'file'`, `'ssm'` or `'secrets-manager'`. */\n\tto: string;\n\t/** The target's AWS region; defaults to the configured store's. */\n\tregion?: string;\n\t/** Overwrite a stage the target already holds. */\n\tforce?: boolean;\n\t/** The AWS profile for the stage's account, for both stores. */\n\tprofile?: string;\n}\n\n/** An AWS store to copy to, with no region given and none configured. */\nexport class MigrateTargetNeedsRegion extends Error {\n\tconstructor(readonly to: string) {\n\t\tsuper(\n\t\t\t`Copying to ${to} needs a region, and secrets.store has none to reuse. ` +\n\t\t\t\t`Pass it: gkm secrets:migrate --to ${to} --region <region> --stage <stage>.`,\n\t\t);\n\t\tthis.name = 'MigrateTargetNeedsRegion';\n\t}\n}\n\n/** The target is the store the stage is already read from. */\nexport class MigrateTargetIsSource extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly to: string,\n\t) {\n\t\tsuper(\n\t\t\t`Stage \"${stage}\" is already kept in ${to}, so there is nothing to copy. ` +\n\t\t\t\t'Name a different store with --to, or a different region with --region.',\n\t\t);\n\t\tthis.name = 'MigrateTargetIsSource';\n\t}\n}\n\n/** The configured store holds nothing for the stage. */\nexport class NoSecretsToMigrate extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly store: string,\n\t) {\n\t\tsuper(\n\t\t\t`The ${store} store holds no secrets for stage \"${stage}\", so there is nothing to copy. ` +\n\t\t\t\t`Check secrets.store in gkm.config.ts still names the store the stage is in, ` +\n\t\t\t\t`or create the stage there with gkm secrets:init --stage ${stage}.`,\n\t\t);\n\t\tthis.name = 'NoSecretsToMigrate';\n\t}\n}\n\n/** The target already holds the stage, and `--force` was not given. */\nexport class MigrateTargetHoldsStage extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly to: string,\n\t) {\n\t\tsuper(\n\t\t\t`The ${to} store already holds secrets for stage \"${stage}\". ` +\n\t\t\t\t'Pass --force to replace them with the ones being copied.',\n\t\t);\n\t\tthis.name = 'MigrateTargetHoldsStage';\n\t}\n}\n\n/** The store a `--to` name and region describe. */\nfunction targetConfig(\n\tto: string,\n\tregion: string | undefined,\n\tconfigured: SecretsStoreConfig,\n): SecretsStoreConfig {\n\tif (!(SECRETS_STORE_PROVIDERS as readonly string[]).includes(to)) {\n\t\tthrow new UnknownSecretsStoreProvider(to);\n\t}\n\tconst provider = to as SecretsStoreProvider;\n\tif (provider === 'file') return 'file';\n\n\tconst fallback =\n\t\ttypeof configured === 'object' && 'region' in configured\n\t\t\t? configured.region\n\t\t\t: undefined;\n\tconst resolved = region ?? fallback;\n\tif (!resolved) throw new MigrateTargetNeedsRegion(provider);\n\treturn { provider, region: resolved };\n}\n\nexport async function secretsMigrateCommand(\n\toptions: SecretsMigrateOptions,\n): Promise<void> {\n\tconst { stage, to } = options;\n\tconst workspace = await loadWorkspaceSettings();\n\tassertDeployedStage(workspace.stages, stage);\n\n\tconst configured = workspace.secrets.store ?? 'file';\n\tconst target = targetConfig(to, options.region, configured);\n\tconst sameStore =\n\t\ttypeof configured === 'string' || typeof target === 'string'\n\t\t\t? configured === target\n\t\t\t: configured.provider === target.provider &&\n\t\t\t\t'region' in configured &&\n\t\t\t\t'region' in target &&\n\t\t\t\tconfigured.region === target.region;\n\tif (sameStore) throw new MigrateTargetIsSource(stage, to);\n\n\tconst storeOptions: SecretsStoreOptions = options.profile\n\t\t? { profile: options.profile }\n\t\t: {};\n\tconst source = await secretsStoreFor(workspace, stage, storeOptions);\n\tconst destination = await storeFromConfig(workspace, target, storeOptions);\n\n\tconst secrets = await source.read(stage);\n\tif (!secrets) throw new NoSecretsToMigrate(stage, source.name);\n\n\tif (!options.force && (await destination.read(stage))) {\n\t\tthrow new MigrateTargetHoldsStage(stage, destination.name);\n\t}\n\n\tawait destination.write(stage, secrets);\n\n\tconst shape =\n\t\ttypeof target === 'object' && 'region' in target\n\t\t\t? `{ provider: '${target.provider}', region: '${target.region}' }`\n\t\t\t: `'file'`;\n\tlogger.log(\n\t\t`\\n✓ Copied the secrets for stage \"${stage}\" from ${source.name} to ${destination.name}`,\n\t);\n\tlogger.log(\n\t\t`\\n Next: set secrets.store to ${shape} in gkm.config.ts, so commands and deploys read from ${destination.name}.`,\n\t);\n\tlogger.log(\n\t\t` The copy in ${source.name} is left as it was; delete it once deploys read from ${destination.name}.`,\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;AAuBA,MAAM,SAAS;;AAef,IAAa,2BAAb,cAA8C,MAAM;CAC9B;CAArB,YAAY,IAAqB;EAChC,MACC,cAAc,GAAG,0FACqB,GAAG,oCAC1C;EAJoB,KAAA,KAAA;EAKpB,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,wBAAb,cAA2C,MAAM;CAEtC;CACA;CAFV,YACC,OACA,IACC;EACD,MACC,UAAU,MAAM,uBAAuB,GAAG,sGAE3C;EANS,KAAA,QAAA;EACA,KAAA,KAAA;EAMT,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,qBAAb,cAAwC,MAAM;CAEnC;CACA;CAFV,YACC,OACA,OACC;EACD,MACC,OAAO,MAAM,qCAAqC,MAAM,sKAEI,MAAM,EACnE;EAPS,KAAA,QAAA;EACA,KAAA,QAAA;EAOT,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,0BAAb,cAA6C,MAAM;CAExC;CACA;CAFV,YACC,OACA,IACC;EACD,MACC,OAAO,GAAG,0CAA0C,MAAM,4DAE3D;EANS,KAAA,QAAA;EACA,KAAA,KAAA;EAMT,KAAK,OAAO;CACb;AACD;;AAGA,SAAS,aACR,IACA,QACA,YACqB;CACrB,IAAI,CAAEA,kBAAAA,wBAA8C,SAAS,EAAE,GAC9D,MAAM,IAAIC,kBAAAA,4BAA4B,EAAE;CAEzC,MAAM,WAAW;CACjB,IAAI,aAAa,QAAQ,OAAO;CAEhC,MAAM,WACL,OAAO,eAAe,YAAY,YAAY,aAC3C,WAAW,SACX,KAAA;CACJ,MAAM,WAAW,UAAU;CAC3B,IAAI,CAAC,UAAU,MAAM,IAAI,yBAAyB,QAAQ;CAC1D,OAAO;EAAE;EAAU,QAAQ;CAAS;AACrC;AAEA,eAAsB,sBACrB,SACgB;CAChB,MAAM,EAAE,OAAO,OAAO;CACtB,MAAM,YAAY,MAAMC,eAAAA,sBAAsB;CAC9C,kBAAA,oBAAoB,UAAU,QAAQ,KAAK;CAE3C,MAAM,aAAa,UAAU,QAAQ,SAAS;CAC9C,MAAM,SAAS,aAAa,IAAI,QAAQ,QAAQ,UAAU;CAQ1D,IANC,OAAO,eAAe,YAAY,OAAO,WAAW,WACjD,eAAe,SACf,WAAW,aAAa,OAAO,YAChC,YAAY,cACZ,YAAY,UACZ,WAAW,WAAW,OAAO,QACjB,MAAM,IAAI,sBAAsB,OAAO,EAAE;CAExD,MAAM,eAAoC,QAAQ,UAC/C,EAAE,SAAS,QAAQ,QAAQ,IAC3B,CAAC;CACJ,MAAM,SAAS,MAAMC,cAAAA,gBAAgB,WAAW,OAAO,YAAY;CACnE,MAAM,cAAc,MAAMC,cAAAA,gBAAgB,WAAW,QAAQ,YAAY;CAEzE,MAAM,UAAU,MAAM,OAAO,KAAK,KAAK;CACvC,IAAI,CAAC,SAAS,MAAM,IAAI,mBAAmB,OAAO,OAAO,IAAI;CAE7D,IAAI,CAAC,QAAQ,SAAU,MAAM,YAAY,KAAK,KAAK,GAClD,MAAM,IAAI,wBAAwB,OAAO,YAAY,IAAI;CAG1D,MAAM,YAAY,MAAM,OAAO,OAAO;CAEtC,MAAM,QACL,OAAO,WAAW,YAAY,YAAY,SACvC,gBAAgB,OAAO,SAAS,cAAc,OAAO,OAAO,OAC5D;CACJ,OAAO,IACN,qCAAqC,MAAM,SAAS,OAAO,KAAK,MAAM,YAAY,MACnF;CACA,OAAO,IACN,kCAAkC,MAAM,uDAAuD,YAAY,KAAK,EACjH;CACA,OAAO,IACN,iBAAiB,OAAO,KAAK,uDAAuD,YAAY,KAAK,EACtG;AACD"}
|
package/dist/openapi.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const require_derive = require("./derive-DAbo49sm.cjs");
|
|
4
|
-
const require_workspace = require("./workspace-
|
|
4
|
+
const require_workspace = require("./workspace-CwW3Gkru.cjs");
|
|
5
5
|
const require_config = require("./config.cjs");
|
|
6
6
|
const require_EndpointGenerator = require("./EndpointGenerator-CwLjBCFE.cjs");
|
|
7
7
|
const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-B--HV-sW.cjs");
|
package/dist/openapi.d.cts
CHANGED
package/dist/openapi.d.mts
CHANGED
package/dist/openapi.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
import { t as appKey } from "./derive-B4vDpzgP.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { o as appConstructGlobs } from "./workspace-CJ0lKFq-.mjs";
|
|
4
4
|
import { loadWorkspaceConfig } from "./config.mjs";
|
|
5
5
|
import { a as telemetryArgs, r as parseTelemetryFlag, t as EndpointGenerator } from "./EndpointGenerator-CVc6Oi1Y.mjs";
|
|
6
6
|
import { t as OpenApiTsGenerator } from "./OpenApiTsGenerator-CkvsPdgg.mjs";
|