@geekmidas/cli 10.0.0-alpha.66 → 10.0.0-alpha.68
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -178
- package/dist/{HostingerProvider-CY_VpVud.mjs → HostingerProvider-D85ayzTi.mjs} +2 -2
- package/dist/{HostingerProvider-CY_VpVud.mjs.map → HostingerProvider-D85ayzTi.mjs.map} +1 -1
- package/dist/{HostingerProvider-BznqRC2S.cjs → HostingerProvider-dxkwynSs.cjs} +2 -2
- package/dist/{HostingerProvider-BznqRC2S.cjs.map → HostingerProvider-dxkwynSs.cjs.map} +1 -1
- package/dist/{LegacyStateStore-BFcTT2QL.cjs → LegacyStateStore-BZiO9-Lw.cjs} +2 -2
- package/dist/{LegacyStateStore-BFcTT2QL.cjs.map → LegacyStateStore-BZiO9-Lw.cjs.map} +1 -1
- package/dist/{LegacyStateStore-CFheFo62.mjs → LegacyStateStore-C39UKwmf.mjs} +2 -2
- package/dist/{LegacyStateStore-CFheFo62.mjs.map → LegacyStateStore-C39UKwmf.mjs.map} +1 -1
- package/dist/{LocalStateStore-BVUexSvu.cjs → LocalStateStore-DHVsZLyF.cjs} +2 -2
- package/dist/{LocalStateStore-BVUexSvu.cjs.map → LocalStateStore-DHVsZLyF.cjs.map} +1 -1
- package/dist/{LocalStateStore-Bm3x9oOg.mjs → LocalStateStore-Dk3Wbvzk.mjs} +2 -2
- package/dist/{LocalStateStore-Bm3x9oOg.mjs.map → LocalStateStore-Dk3Wbvzk.mjs.map} +1 -1
- package/dist/{S3StateStore-CoXBhILd.cjs → S3StateStore--4tYVMAL.cjs} +2 -2
- package/dist/{S3StateStore-CoXBhILd.cjs.map → S3StateStore--4tYVMAL.cjs.map} +1 -1
- package/dist/{S3StateStore-BFHuctWM.mjs → S3StateStore-B_ZF9eHt.mjs} +2 -2
- package/dist/{S3StateStore-BFHuctWM.mjs.map → S3StateStore-B_ZF9eHt.mjs.map} +1 -1
- package/dist/{SSMStateStore-BqCA9TP2.cjs → SSMStateStore-ACZ8hRvZ.cjs} +2 -2
- package/dist/{SSMStateStore-BqCA9TP2.cjs.map → SSMStateStore-ACZ8hRvZ.cjs.map} +1 -1
- package/dist/{SSMStateStore-C1kvHScY.mjs → SSMStateStore-DjPuKqrm.mjs} +2 -2
- package/dist/{SSMStateStore-C1kvHScY.mjs.map → SSMStateStore-DjPuKqrm.mjs.map} +1 -1
- package/dist/{bundler-Dvaiud0h.mjs → bundler-C-w7aGoZ.mjs} +20 -5
- package/dist/bundler-C-w7aGoZ.mjs.map +1 -0
- package/dist/{bundler-orRqUJNZ.cjs → bundler-C1pnNrR0.cjs} +20 -5
- package/dist/bundler-C1pnNrR0.cjs.map +1 -0
- package/dist/{cli-D1JkkOwg.cjs → cli-BLyD4c0O.cjs} +14 -5
- package/dist/cli-BLyD4c0O.cjs.map +1 -0
- package/dist/{cli-CfHm0AO7.mjs → cli-Ce4pLL5O.mjs} +14 -5
- package/dist/cli-Ce4pLL5O.mjs.map +1 -0
- package/dist/{compose-CNQL8BnO.mjs → compose-DFxuYhn6.mjs} +291 -152
- package/dist/compose-DFxuYhn6.mjs.map +1 -0
- package/dist/{compose-jrLy7KGO.cjs → compose-DvsgumY2.cjs} +298 -159
- package/dist/compose-DvsgumY2.cjs.map +1 -0
- package/dist/{config-4XFxX1al.d.cts → config-BnA0kPTY.d.cts} +3 -3
- package/dist/{config-4XFxX1al.d.cts.map → config-BnA0kPTY.d.cts.map} +1 -1
- package/dist/{config-UtKzhpFb.d.mts → config-CxEp_g7j.d.mts} +3 -3
- package/dist/{config-UtKzhpFb.d.mts.map → config-CxEp_g7j.d.mts.map} +1 -1
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/{credentials-Bzbg23n8.cjs → credentials-BVs7mrtw.cjs} +239 -3
- package/dist/credentials-BVs7mrtw.cjs.map +1 -0
- package/dist/{credentials-Dq1MnWQf.mjs → credentials-pAtB70k3.mjs} +223 -5
- package/dist/credentials-pAtB70k3.mjs.map +1 -0
- package/dist/deploy/public.cjs +21 -17
- package/dist/deploy/public.cjs.map +1 -1
- package/dist/deploy/public.d.cts +15 -4
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +15 -4
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +10 -10
- package/dist/{deploy-B7aJi_Ah.cjs → deploy-C08GONYW.cjs} +14 -10
- package/dist/deploy-C08GONYW.cjs.map +1 -0
- package/dist/{deploy-DWn7YfIc.mjs → deploy-D-EVOWvu.mjs} +11 -7
- package/dist/deploy-D-EVOWvu.mjs.map +1 -0
- package/dist/{dokploy-B4LcYG1E.cjs → dokploy-CZ6nfSV9.cjs} +2 -2
- package/dist/{dokploy-B4LcYG1E.cjs.map → dokploy-CZ6nfSV9.cjs.map} +1 -1
- package/dist/{dokploy-DalwLML5.mjs → dokploy-DU-XJsoT.mjs} +2 -2
- package/dist/{dokploy-DalwLML5.mjs.map → dokploy-DU-XJsoT.mjs.map} +1 -1
- package/dist/{encryption-3KlGS1sl.cjs → encryption-CCcBSLZs.cjs} +2 -2
- package/dist/{encryption-3KlGS1sl.cjs.map → encryption-CCcBSLZs.cjs.map} +1 -1
- package/dist/{encryption-CPfENwWO.mjs → encryption-DSiLau1-.mjs} +2 -2
- package/dist/{encryption-CPfENwWO.mjs.map → encryption-DSiLau1-.mjs.map} +1 -1
- package/dist/{engine-DR6KurWX.cjs → engine-BM9Dy_DT.cjs} +141 -209
- package/dist/engine-BM9Dy_DT.cjs.map +1 -0
- package/dist/{engine-CunvOG3s.mjs → engine-llWnMZH5.mjs} +137 -205
- package/dist/engine-llWnMZH5.mjs.map +1 -0
- package/dist/{github-BJ5gh9bw.cjs → github-ZoMam0OH.cjs} +2 -2
- package/dist/{github-BJ5gh9bw.cjs.map → github-ZoMam0OH.cjs.map} +1 -1
- package/dist/{github-MXA9v7Ge.mjs → github-yy6WpHIl.mjs} +2 -2
- package/dist/{github-MXA9v7Ge.mjs.map → github-yy6WpHIl.mjs.map} +1 -1
- package/dist/{index-DD1FfUHh.d.mts → index-CkX89m6S.d.mts} +2 -2
- package/dist/{index-CyApaHVe.d.cts.map → index-CkX89m6S.d.mts.map} +1 -1
- package/dist/{index-CyApaHVe.d.cts → index-EvZBkycu.d.cts} +2 -2
- package/dist/{index-DD1FfUHh.d.mts.map → index-EvZBkycu.d.cts.map} +1 -1
- package/dist/index.cjs +59 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +55 -66
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-BJBcm4sG.mjs → migrate-BWPuwiXk.mjs} +2 -2
- package/dist/{migrate-BJBcm4sG.mjs.map → migrate-BWPuwiXk.mjs.map} +1 -1
- package/dist/{migrate-CF4J4GQS.cjs → migrate-tOwxQBTI.cjs} +2 -2
- package/dist/{migrate-CF4J4GQS.cjs.map → migrate-tOwxQBTI.cjs.map} +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/{resolve-CVmch0wI.cjs → resolve-BymzGtuT.cjs} +4 -4
- package/dist/{resolve-CVmch0wI.cjs.map → resolve-BymzGtuT.cjs.map} +1 -1
- package/dist/{resolve-Db0tNlrW.mjs → resolve-pIvXlZ1h.mjs} +4 -4
- package/dist/{resolve-Db0tNlrW.mjs.map → resolve-pIvXlZ1h.mjs.map} +1 -1
- package/dist/{rollback-DvAo4OKS.mjs → rollback-ZlsbQOvj.mjs} +5 -6
- package/dist/{rollback-DvAo4OKS.mjs.map → rollback-ZlsbQOvj.mjs.map} +1 -1
- package/dist/{rollback-DxJK84hy.cjs → rollback-zdfi6Reg.cjs} +6 -7
- package/dist/{rollback-DxJK84hy.cjs.map → rollback-zdfi6Reg.cjs.map} +1 -1
- package/dist/run-BJLIerMM.mjs +133 -0
- package/dist/run-BJLIerMM.mjs.map +1 -0
- package/dist/run-fVZ-rLnc.cjs +174 -0
- package/dist/run-fVZ-rLnc.cjs.map +1 -0
- package/dist/{runtime-BRqFOvPM.d.cts → runtime-B3jNgeP-.d.mts} +2 -2
- package/dist/{runtime-BRqFOvPM.d.cts.map → runtime-B3jNgeP-.d.mts.map} +1 -1
- package/dist/{runtime-CEk5n4VH.d.mts → runtime-DWThEP-r.d.cts} +2 -2
- package/dist/{runtime-CEk5n4VH.d.mts.map → runtime-DWThEP-r.d.cts.map} +1 -1
- package/dist/{sniffer-BAQhLEsI.mjs → sniffer-BdFeLSmA.mjs} +8 -60
- package/dist/sniffer-BdFeLSmA.mjs.map +1 -0
- package/dist/{sniffer-5xmrkh2a.cjs → sniffer-D66HIvi9.cjs} +7 -77
- package/dist/sniffer-D66HIvi9.cjs.map +1 -0
- package/dist/{sst-B2HbrDR3.mjs → sst-DRi3H5TB.mjs} +3 -3
- package/dist/{sst-B2HbrDR3.mjs.map → sst-DRi3H5TB.mjs.map} +1 -1
- package/dist/{sst-DcfsVH6x.cjs → sst-hBrKouBy.cjs} +5 -5
- package/dist/{sst-DcfsVH6x.cjs.map → sst-hBrKouBy.cjs.map} +1 -1
- package/dist/{state-uuYqnRVO.mjs → state-Bn5JDMFT.mjs} +1717 -1334
- package/dist/state-Bn5JDMFT.mjs.map +1 -0
- package/dist/{state-Y6ATakMX.cjs → state-CHZqCDRC.cjs} +2863 -2354
- package/dist/state-CHZqCDRC.cjs.map +1 -0
- 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-DUhcZ1ND.mjs → terminal-C3cv-OQu.mjs} +2 -2
- package/dist/{terminal-DUhcZ1ND.mjs.map → terminal-C3cv-OQu.mjs.map} +1 -1
- package/dist/{terminal-CHy_MdjG.cjs → terminal-DjzgLu6m.cjs} +2 -2
- package/dist/{terminal-CHy_MdjG.cjs.map → terminal-DjzgLu6m.cjs.map} +1 -1
- package/dist/{types-CSw2w69a.d.mts → types-CZdCxUCK.d.cts} +64 -2
- package/dist/types-CZdCxUCK.d.cts.map +1 -0
- package/dist/{types-D5WIxRDP.d.cts → types-Da0E3Qv7.d.mts} +63 -3
- package/dist/types-Da0E3Qv7.d.mts.map +1 -0
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/package.json +13 -13
- package/dist/bundler-Dvaiud0h.mjs.map +0 -1
- package/dist/bundler-orRqUJNZ.cjs.map +0 -1
- package/dist/cli-CfHm0AO7.mjs.map +0 -1
- package/dist/cli-D1JkkOwg.cjs.map +0 -1
- package/dist/compose-CNQL8BnO.mjs.map +0 -1
- package/dist/compose-jrLy7KGO.cjs.map +0 -1
- package/dist/credentials-Bzbg23n8.cjs.map +0 -1
- package/dist/credentials-Dq1MnWQf.mjs.map +0 -1
- package/dist/deploy-B7aJi_Ah.cjs.map +0 -1
- package/dist/deploy-DWn7YfIc.mjs.map +0 -1
- package/dist/engine-CunvOG3s.mjs.map +0 -1
- package/dist/engine-DR6KurWX.cjs.map +0 -1
- package/dist/local-BdH4XN0E.cjs +0 -409
- package/dist/local-BdH4XN0E.cjs.map +0 -1
- package/dist/local-CWR4Y_kW.mjs +0 -350
- package/dist/local-CWR4Y_kW.mjs.map +0 -1
- package/dist/sniffer-5xmrkh2a.cjs.map +0 -1
- package/dist/sniffer-BAQhLEsI.mjs.map +0 -1
- package/dist/state-Y6ATakMX.cjs.map +0 -1
- package/dist/state-uuYqnRVO.mjs.map +0 -1
- package/dist/types-CSw2w69a.d.mts.map +0 -1
- package/dist/types-D5WIxRDP.d.cts.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LegacyStateStore-
|
|
1
|
+
{"version":3,"file":"LegacyStateStore-C39UKwmf.mjs","names":[],"sources":["../src/deploy/LegacyStateStore.ts"],"sourcesContent":["/**\n * A custom `StateProvider` as a `StateStore`.\n *\n * A provider only reads and writes whole states, so this adapter can check a\n * version before it writes but cannot make the check and the write one step,\n * and cannot lock. It warns once (`StateStoreWithoutLocking`) the first time\n * a caller relies on either, and otherwise behaves as a store. Resource\n * records travel inside the state the provider stores, under `resources`.\n */\n\nimport type { StateProvider } from './StateProvider';\nimport {\n\tcontentVersion,\n\ttype LockHolder,\n\ttype LockOptions,\n\tnewLockHolder,\n\ttype ResourceInput,\n\ttype ResourceRecord,\n\ttype ResourceWriteOptions,\n\tStageStateMissing,\n\ttype StateLock,\n\ttype StateStore,\n\tStateStoreWithoutLocking,\n\ttype StateVersion,\n\tStateVersionConflict,\n\ttype StoredStageState,\n\ttype WriteOptions,\n} from './StateStore';\nimport type { DokployStageState } from './state';\n\ntype ProviderState = DokployStageState & {\n\tresources?: Record<string, ResourceRecord>;\n};\n\nexport interface LegacyStateStoreOptions {\n\t/** Where the warning goes; `process.emitWarning` by default. */\n\twarn?: (warning: StateStoreWithoutLocking) => void;\n}\n\nexport class LegacyStateStore implements StateStore {\n\tprivate warned = false;\n\tprivate readonly warn: (warning: StateStoreWithoutLocking) => void;\n\n\tconstructor(\n\t\treadonly provider: StateProvider,\n\t\toptions: LegacyStateStoreOptions = {},\n\t) {\n\t\tthis.warn = options.warn ?? ((warning) => process.emitWarning(warning));\n\t}\n\n\tasync lock(stage: string, options?: LockOptions): Promise<StateLock> {\n\t\tthis.warnOnce();\n\t\treturn { stage, holder: newLockHolder(options), release: async () => {} };\n\t}\n\n\tasync forceUnlock(_stage: string): Promise<LockHolder | null> {\n\t\treturn null;\n\t}\n\n\tasync read(stage: string): Promise<StoredStageState | null> {\n\t\tconst stored = (await this.provider.read(stage)) as ProviderState | null;\n\t\tif (!stored) return null;\n\t\tconst { resources = {}, ...state } = stored;\n\t\treturn { state, resources, version: versionOf(stored) };\n\t}\n\n\tasync write(\n\t\tstage: string,\n\t\tstate: DokployStageState,\n\t\toptions: WriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.read(stage);\n\t\tthis.check(stage, options.expectedVersion, current);\n\t\treturn this.put(stage, { ...state, resources: current?.resources ?? {} });\n\t}\n\n\tasync putResource(\n\t\tstage: string,\n\t\trecord: ResourceInput,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.current(stage, options);\n\t\treturn this.put(stage, {\n\t\t\t...current.state,\n\t\t\tresources: {\n\t\t\t\t...current.resources,\n\t\t\t\t[record.key]: { ...record, updatedAt: new Date().toISOString() },\n\t\t\t},\n\t\t});\n\t}\n\n\tasync deleteResource(\n\t\tstage: string,\n\t\tkey: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StateVersion> {\n\t\tconst current = await this.current(stage, options);\n\t\tconst { [key]: _removed, ...resources } = current.resources;\n\t\treturn this.put(stage, { ...current.state, resources });\n\t}\n\n\tprivate async current(\n\t\tstage: string,\n\t\toptions?: ResourceWriteOptions,\n\t): Promise<StoredStageState> {\n\t\tconst current = await this.read(stage);\n\t\tif (options?.expectedVersion !== undefined) {\n\t\t\tthis.check(stage, options.expectedVersion, current);\n\t\t}\n\t\tif (!current) throw new StageStateMissing(stage);\n\t\treturn current;\n\t}\n\n\tprivate check(\n\t\tstage: string,\n\t\texpected: StateVersion | null,\n\t\tcurrent: StoredStageState | null,\n\t): void {\n\t\tconst actual = current?.version ?? null;\n\t\tif (actual !== expected) {\n\t\t\tthrow new StateVersionConflict(stage, expected, actual);\n\t\t}\n\t}\n\n\tprivate async put(\n\t\tstage: string,\n\t\tstate: ProviderState,\n\t): Promise<StateVersion> {\n\t\tthis.warnOnce();\n\t\t// Providers stamp `lastDeployedAt` on the object they are given, so the\n\t\t// version is taken after the write, from what was actually stored.\n\t\tawait this.provider.write(stage, state);\n\t\treturn versionOf(state);\n\t}\n\n\tprivate warnOnce(): void {\n\t\tif (this.warned) return;\n\t\tthis.warned = true;\n\t\tthis.warn(\n\t\t\tnew StateStoreWithoutLocking(\n\t\t\t\tthis.provider.constructor?.name || 'anonymous',\n\t\t\t),\n\t\t);\n\t}\n}\n\n/** Key order is the provider's, so this is stable for a given stored state. */\nfunction versionOf(state: ProviderState): StateVersion {\n\treturn contentVersion(JSON.stringify(state));\n}\n"],"mappings":";;AAuCA,IAAa,mBAAb,MAAoD;CAKzC;CAJV,SAAiB;CACjB;CAEA,YACC,UACA,UAAmC,CAAC,GACnC;EAFQ,KAAA,WAAA;EAGT,KAAK,OAAO,QAAQ,UAAU,YAAY,QAAQ,YAAY,OAAO;CACtE;CAEA,MAAM,KAAK,OAAe,SAA2C;EACpE,KAAK,SAAS;EACd,OAAO;GAAE;GAAO,QAAQ,cAAc,OAAO;GAAG,SAAS,YAAY,CAAC;EAAE;CACzE;CAEA,MAAM,YAAY,QAA4C;EAC7D,OAAO;CACR;CAEA,MAAM,KAAK,OAAiD;EAC3D,MAAM,SAAU,MAAM,KAAK,SAAS,KAAK,KAAK;EAC9C,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,EAAE,YAAY,CAAC,GAAG,GAAG,UAAU;EACrC,OAAO;GAAE;GAAO;GAAW,SAAS,UAAU,MAAM;EAAE;CACvD;CAEA,MAAM,MACL,OACA,OACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,KAAK,KAAK;EACrC,KAAK,MAAM,OAAO,QAAQ,iBAAiB,OAAO;EAClD,OAAO,KAAK,IAAI,OAAO;GAAE,GAAG;GAAO,WAAW,SAAS,aAAa,CAAC;EAAE,CAAC;CACzE;CAEA,MAAM,YACL,OACA,QACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,OAAO;EACjD,OAAO,KAAK,IAAI,OAAO;GACtB,GAAG,QAAQ;GACX,WAAW;IACV,GAAG,QAAQ;KACV,OAAO,MAAM;KAAE,GAAG;KAAQ,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;IAAE;GAChE;EACD,CAAC;CACF;CAEA,MAAM,eACL,OACA,KACA,SACwB;EACxB,MAAM,UAAU,MAAM,KAAK,QAAQ,OAAO,OAAO;EACjD,MAAM,GAAG,MAAM,UAAU,GAAG,cAAc,QAAQ;EAClD,OAAO,KAAK,IAAI,OAAO;GAAE,GAAG,QAAQ;GAAO;EAAU,CAAC;CACvD;CAEA,MAAc,QACb,OACA,SAC4B;EAC5B,MAAM,UAAU,MAAM,KAAK,KAAK,KAAK;EACrC,IAAI,SAAS,oBAAoB,KAAA,GAChC,KAAK,MAAM,OAAO,QAAQ,iBAAiB,OAAO;EAEnD,IAAI,CAAC,SAAS,MAAM,IAAI,kBAAkB,KAAK;EAC/C,OAAO;CACR;CAEA,MACC,OACA,UACA,SACO;EACP,MAAM,SAAS,SAAS,WAAW;EACnC,IAAI,WAAW,UACd,MAAM,IAAI,qBAAqB,OAAO,UAAU,MAAM;CAExD;CAEA,MAAc,IACb,OACA,OACwB;EACxB,KAAK,SAAS;EAGd,MAAM,KAAK,SAAS,MAAM,OAAO,KAAK;EACtC,OAAO,UAAU,KAAK;CACvB;CAEA,WAAyB;EACxB,IAAI,KAAK,QAAQ;EACjB,KAAK,SAAS;EACd,KAAK,KACJ,IAAI,yBACH,KAAK,SAAS,aAAa,QAAQ,WACpC,CACD;CACD;AACD;;AAGA,SAAS,UAAU,OAAoC;CACtD,OAAO,eAAe,KAAK,UAAU,KAAK,CAAC;AAC5C"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
|
|
2
|
-
const require_sniffer = require("./sniffer-
|
|
2
|
+
const require_sniffer = require("./sniffer-D66HIvi9.cjs");
|
|
3
3
|
let node_path = require("node:path");
|
|
4
4
|
let node_fs_promises = require("node:fs/promises");
|
|
5
5
|
let node_crypto = require("node:crypto");
|
|
@@ -173,4 +173,4 @@ Object.defineProperty(exports, "LocalStateStore_exports", {
|
|
|
173
173
|
}
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
-
//# sourceMappingURL=LocalStateStore-
|
|
176
|
+
//# sourceMappingURL=LocalStateStore-DHVsZLyF.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalStateStore-
|
|
1
|
+
{"version":3,"file":"LocalStateStore-DHVsZLyF.cjs","names":["DocumentStateStore","join","readFile","contentVersion","StateVersionConflict","randomUUID","open","rename","rm","writeFile","StateLocked","mkdir","unlink","StateStoreBusy","stat"],"sources":["../src/deploy/LocalStateStore.ts"],"sourcesContent":["/**\n * Local filesystem state store.\n *\n * `.gkm/deploy-<stage>.json` holds the state, `.gkm/deploy-<stage>.lock` the\n * lock. Every file is mode 0600: state holds database passwords and IAM keys.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport {\n\tmkdir,\n\topen,\n\treadFile,\n\trename,\n\trm,\n\tstat,\n\tunlink,\n\twriteFile,\n} from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n\tcontentVersion,\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\tStateStoreBusy,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nconst FILE_MODE = 0o600;\n\n/** How long a write waits for another process's write to finish. */\nconst MUTEX_TIMEOUT_MS = 5_000;\n/**\n * A write holds the mutex for milliseconds, so one this old was left by a\n * process that died mid-write and is safe to take over.\n */\nconst MUTEX_STALE_MS = 30_000;\n\nfunction isCode(error: unknown, code: string): boolean {\n\treturn (error as NodeJS.ErrnoException)?.code === code;\n}\n\nexport class LocalStateStore extends DocumentStateStore {\n\tconstructor(readonly workspaceRoot: string) {\n\t\tsuper();\n\t}\n\n\tprivate get dir(): string {\n\t\treturn join(this.workspaceRoot, '.gkm');\n\t}\n\n\tstatePath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.json`);\n\t}\n\n\tlockPath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.lock`);\n\t}\n\n\tbackupPath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.v1.json`);\n\t}\n\n\tprivate mutexPath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.json.mutex`);\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn this.statePath(stage);\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\ttry {\n\t\t\tconst body = await readFile(this.statePath(stage), 'utf-8');\n\t\t\treturn { body, version: contentVersion(body) };\n\t\t} catch (error) {\n\t\t\tif (isCode(error, 'ENOENT')) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tawait this.ensureDir();\n\t\treturn this.withMutex(stage, async () => {\n\t\t\tconst current = await this.readRaw(stage);\n\t\t\tconst actual = current?.version ?? null;\n\t\t\tif (actual !== expectedVersion) {\n\t\t\t\tthrow new StateVersionConflict(stage, expectedVersion, actual);\n\t\t\t}\n\n\t\t\t// Written beside the target and renamed over it: a reader sees the\n\t\t\t// old file or the new one, never half of either.\n\t\t\tconst target = this.statePath(stage);\n\t\t\tconst temp = `${target}.${randomUUID()}.tmp`;\n\t\t\ttry {\n\t\t\t\tconst handle = await open(temp, 'wx', FILE_MODE);\n\t\t\t\ttry {\n\t\t\t\t\tawait handle.writeFile(body);\n\t\t\t\t\tawait handle.sync();\n\t\t\t\t} finally {\n\t\t\t\t\tawait handle.close();\n\t\t\t\t}\n\t\t\t\tawait rename(temp, target);\n\t\t\t} catch (error) {\n\t\t\t\tawait rm(temp, { force: true });\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\treturn contentVersion(body);\n\t\t});\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait writeFile(this.backupPath(stage), body, {\n\t\t\t\tflag: 'wx',\n\t\t\t\tmode: FILE_MODE,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!isCode(error, 'EEXIST')) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tawait this.ensureDir();\n\t\tlet handle: Awaited<ReturnType<typeof open>>;\n\t\ttry {\n\t\t\thandle = await open(this.lockPath(stage), 'wx', FILE_MODE);\n\t\t} catch (error) {\n\t\t\tif (isCode(error, 'EEXIST')) {\n\t\t\t\tthrow new StateLocked(\n\t\t\t\t\tstage,\n\t\t\t\t\tawait this.readLock(stage),\n\t\t\t\t\tthis.lockPath(stage),\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\ttry {\n\t\t\tawait handle.writeFile(JSON.stringify(holder, null, 2));\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\ttry {\n\t\t\treturn JSON.parse(\n\t\t\t\tawait readFile(this.lockPath(stage), 'utf-8'),\n\t\t\t) as LockHolder;\n\t\t} catch {\n\t\t\t// Missing, or created by a process that died before writing to it.\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tif (holder) {\n\t\t\tconst current = await this.readLock(stage);\n\t\t\tif (current?.id !== holder.id) return;\n\t\t}\n\t\tawait rm(this.lockPath(stage), { force: true });\n\t}\n\n\tprivate async ensureDir(): Promise<void> {\n\t\tawait mkdir(this.dir, { recursive: true, mode: 0o700 });\n\t}\n\n\t/**\n\t * Serialises the check-then-rename of a conditional write across\n\t * processes. The stage lock is for whole runs; this is for the instant of\n\t * a single write, so even writers that skip the lock never interleave.\n\t */\n\tprivate async withMutex<T>(stage: string, fn: () => Promise<T>): Promise<T> {\n\t\tconst path = this.mutexPath(stage);\n\t\tconst deadline = Date.now() + MUTEX_TIMEOUT_MS;\n\n\t\tfor (;;) {\n\t\t\ttry {\n\t\t\t\tconst handle = await open(path, 'wx', FILE_MODE);\n\t\t\t\tawait handle.close();\n\t\t\t\tbreak;\n\t\t\t} catch (error) {\n\t\t\t\tif (!isCode(error, 'EEXIST')) throw error;\n\t\t\t\tif (await this.isStale(path)) {\n\t\t\t\t\tawait unlink(path).catch(() => {});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (Date.now() > deadline) throw new StateStoreBusy(stage, path);\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 10));\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} finally {\n\t\t\tawait rm(path, { force: true });\n\t\t}\n\t}\n\n\tprivate async isStale(path: string): Promise<boolean> {\n\t\ttry {\n\t\t\treturn Date.now() - (await stat(path)).mtimeMs > MUTEX_STALE_MS;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
2
|
-
import { d as StateLocked, f as StateStoreBusy, h as contentVersion, l as DocumentStateStore, m as StateVersionConflict } from "./sniffer-
|
|
2
|
+
import { d as StateLocked, f as StateStoreBusy, h as contentVersion, l as DocumentStateStore, m as StateVersionConflict } from "./sniffer-BdFeLSmA.mjs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { mkdir, open, readFile, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
5
5
|
import { randomUUID } from "node:crypto";
|
|
@@ -162,4 +162,4 @@ var LocalStateStore = class extends DocumentStateStore {
|
|
|
162
162
|
//#endregion
|
|
163
163
|
export { LocalStateStore_exports as n, LocalStateStore as t };
|
|
164
164
|
|
|
165
|
-
//# sourceMappingURL=LocalStateStore-
|
|
165
|
+
//# sourceMappingURL=LocalStateStore-Dk3Wbvzk.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LocalStateStore-
|
|
1
|
+
{"version":3,"file":"LocalStateStore-Dk3Wbvzk.mjs","names":[],"sources":["../src/deploy/LocalStateStore.ts"],"sourcesContent":["/**\n * Local filesystem state store.\n *\n * `.gkm/deploy-<stage>.json` holds the state, `.gkm/deploy-<stage>.lock` the\n * lock. Every file is mode 0600: state holds database passwords and IAM keys.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport {\n\tmkdir,\n\topen,\n\treadFile,\n\trename,\n\trm,\n\tstat,\n\tunlink,\n\twriteFile,\n} from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n\tcontentVersion,\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\tStateStoreBusy,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nconst FILE_MODE = 0o600;\n\n/** How long a write waits for another process's write to finish. */\nconst MUTEX_TIMEOUT_MS = 5_000;\n/**\n * A write holds the mutex for milliseconds, so one this old was left by a\n * process that died mid-write and is safe to take over.\n */\nconst MUTEX_STALE_MS = 30_000;\n\nfunction isCode(error: unknown, code: string): boolean {\n\treturn (error as NodeJS.ErrnoException)?.code === code;\n}\n\nexport class LocalStateStore extends DocumentStateStore {\n\tconstructor(readonly workspaceRoot: string) {\n\t\tsuper();\n\t}\n\n\tprivate get dir(): string {\n\t\treturn join(this.workspaceRoot, '.gkm');\n\t}\n\n\tstatePath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.json`);\n\t}\n\n\tlockPath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.lock`);\n\t}\n\n\tbackupPath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.v1.json`);\n\t}\n\n\tprivate mutexPath(stage: string): string {\n\t\treturn join(this.dir, `deploy-${stage}.json.mutex`);\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn this.statePath(stage);\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\ttry {\n\t\t\tconst body = await readFile(this.statePath(stage), 'utf-8');\n\t\t\treturn { body, version: contentVersion(body) };\n\t\t} catch (error) {\n\t\t\tif (isCode(error, 'ENOENT')) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tawait this.ensureDir();\n\t\treturn this.withMutex(stage, async () => {\n\t\t\tconst current = await this.readRaw(stage);\n\t\t\tconst actual = current?.version ?? null;\n\t\t\tif (actual !== expectedVersion) {\n\t\t\t\tthrow new StateVersionConflict(stage, expectedVersion, actual);\n\t\t\t}\n\n\t\t\t// Written beside the target and renamed over it: a reader sees the\n\t\t\t// old file or the new one, never half of either.\n\t\t\tconst target = this.statePath(stage);\n\t\t\tconst temp = `${target}.${randomUUID()}.tmp`;\n\t\t\ttry {\n\t\t\t\tconst handle = await open(temp, 'wx', FILE_MODE);\n\t\t\t\ttry {\n\t\t\t\t\tawait handle.writeFile(body);\n\t\t\t\t\tawait handle.sync();\n\t\t\t\t} finally {\n\t\t\t\t\tawait handle.close();\n\t\t\t\t}\n\t\t\t\tawait rename(temp, target);\n\t\t\t} catch (error) {\n\t\t\t\tawait rm(temp, { force: true });\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\treturn contentVersion(body);\n\t\t});\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait writeFile(this.backupPath(stage), body, {\n\t\t\t\tflag: 'wx',\n\t\t\t\tmode: FILE_MODE,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!isCode(error, 'EEXIST')) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tawait this.ensureDir();\n\t\tlet handle: Awaited<ReturnType<typeof open>>;\n\t\ttry {\n\t\t\thandle = await open(this.lockPath(stage), 'wx', FILE_MODE);\n\t\t} catch (error) {\n\t\t\tif (isCode(error, 'EEXIST')) {\n\t\t\t\tthrow new StateLocked(\n\t\t\t\t\tstage,\n\t\t\t\t\tawait this.readLock(stage),\n\t\t\t\t\tthis.lockPath(stage),\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t\ttry {\n\t\t\tawait handle.writeFile(JSON.stringify(holder, null, 2));\n\t\t} finally {\n\t\t\tawait handle.close();\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\ttry {\n\t\t\treturn JSON.parse(\n\t\t\t\tawait readFile(this.lockPath(stage), 'utf-8'),\n\t\t\t) as LockHolder;\n\t\t} catch {\n\t\t\t// Missing, or created by a process that died before writing to it.\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tif (holder) {\n\t\t\tconst current = await this.readLock(stage);\n\t\t\tif (current?.id !== holder.id) return;\n\t\t}\n\t\tawait rm(this.lockPath(stage), { force: true });\n\t}\n\n\tprivate async ensureDir(): Promise<void> {\n\t\tawait mkdir(this.dir, { recursive: true, mode: 0o700 });\n\t}\n\n\t/**\n\t * Serialises the check-then-rename of a conditional write across\n\t * processes. The stage lock is for whole runs; this is for the instant of\n\t * a single write, so even writers that skip the lock never interleave.\n\t */\n\tprivate async withMutex<T>(stage: string, fn: () => Promise<T>): Promise<T> {\n\t\tconst path = this.mutexPath(stage);\n\t\tconst deadline = Date.now() + MUTEX_TIMEOUT_MS;\n\n\t\tfor (;;) {\n\t\t\ttry {\n\t\t\t\tconst handle = await open(path, 'wx', FILE_MODE);\n\t\t\t\tawait handle.close();\n\t\t\t\tbreak;\n\t\t\t} catch (error) {\n\t\t\t\tif (!isCode(error, 'EEXIST')) throw error;\n\t\t\t\tif (await this.isStale(path)) {\n\t\t\t\t\tawait unlink(path).catch(() => {});\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (Date.now() > deadline) throw new StateStoreBusy(stage, path);\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, 10));\n\t\t\t}\n\t\t}\n\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} finally {\n\t\t\tawait rm(path, { force: true });\n\t\t}\n\t}\n\n\tprivate async isStale(path: string): Promise<boolean> {\n\t\ttry {\n\t\t\treturn Date.now() - (await stat(path)).mtimeMs > MUTEX_STALE_MS;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_sniffer = require("./sniffer-
|
|
1
|
+
const require_sniffer = require("./sniffer-D66HIvi9.cjs");
|
|
2
2
|
let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
|
|
3
3
|
//#region src/deploy/S3StateStore.ts
|
|
4
4
|
/**
|
|
@@ -157,4 +157,4 @@ var S3StateStore = class S3StateStore extends require_sniffer.DocumentStateStore
|
|
|
157
157
|
//#endregion
|
|
158
158
|
exports.S3StateStore = S3StateStore;
|
|
159
159
|
|
|
160
|
-
//# sourceMappingURL=S3StateStore
|
|
160
|
+
//# sourceMappingURL=S3StateStore--4tYVMAL.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"S3StateStore-CoXBhILd.cjs","names":["DocumentStateStore","S3Client","PutObjectCommand","StateVersionConflict","StateLocked","DeleteObjectCommand","GetObjectCommand"],"sources":["../src/deploy/S3StateStore.ts"],"sourcesContent":["/**\n * Amazon S3 state store.\n *\n * - `<prefix>/<workspace>/<stage>/state.json`: the state.\n * - `<prefix>/<workspace>/<stage>/lock.json`: the lock.\n * - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on\n * migration.\n *\n * Every write is conditional on the server: `If-None-Match: *` to create,\n * `If-Match: <etag>` to replace. S3 answers a lost race with 412 (or 409 when\n * two conditional writes are in flight at once), so there is no window\n * between a check and a write for another run to slip into.\n */\n\nimport {\n\tDeleteObjectCommand,\n\tGetObjectCommand,\n\tPutObjectCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { AwsRegion } from './StateProvider';\nimport {\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nexport interface S3StateStoreOptions {\n\tworkspaceName: string;\n\tbucket: string;\n\t/** Key prefix inside the bucket; `gkm` by default. */\n\tprefix?: string;\n\tregion?: AwsRegion;\n\t/** Uses the default credential chain when omitted. */\n\tprofile?: string;\n\tcredentials?: S3ClientConfig['credentials'];\n\t/** For the local AWS emulator. */\n\tendpoint?: string;\n}\n\ninterface S3Failure {\n\tname?: string;\n\t$metadata?: { httpStatusCode?: number };\n}\n\n/** A conditional write that lost: the object is not what the write assumed. */\nfunction lostRace(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'PreconditionFailed' ||\n\t\tname === 'ConditionalRequestConflict' ||\n\t\t$metadata?.httpStatusCode === 412 ||\n\t\t$metadata?.httpStatusCode === 409\n\t);\n}\n\nfunction notFound(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'NoSuchKey' ||\n\t\tname === 'NotFound' ||\n\t\t$metadata?.httpStatusCode === 404\n\t);\n}\n\nexport class S3StateStore extends DocumentStateStore {\n\tprivate readonly prefix: string;\n\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\treadonly bucket: string,\n\t\tprivate readonly client: S3Client,\n\t\tprefix = 'gkm',\n\t) {\n\t\tsuper();\n\t\tthis.prefix = prefix.replace(/\\/+$/, '');\n\t}\n\n\tstatic create(options: S3StateStoreOptions): S3StateStore {\n\t\tconst config: S3ClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t\t// The emulator serves buckets by path, not by subdomain.\n\t\t\tforcePathStyle: options.endpoint ? true : undefined,\n\t\t};\n\t\tif (options.profile) {\n\t\t\t// Required lazily: only a profile needs the credential providers.\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tconfig.credentials = options.credentials;\n\t\t}\n\t\treturn new S3StateStore(\n\t\t\toptions.workspaceName,\n\t\t\toptions.bucket,\n\t\t\tnew S3Client(config),\n\t\t\toptions.prefix,\n\t\t);\n\t}\n\n\tprivate key(stage: string, leaf: string): string {\n\t\tconst path = `${this.workspaceName}/${stage}/${leaf}`;\n\t\treturn this.prefix ? `${this.prefix}/${path}` : path;\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn `s3://${this.bucket}/${this.key(stage, 'state.json')}`;\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\tconst object = await this.get(this.key(stage, 'state.json'));\n\t\tif (!object) return null;\n\t\treturn { body: object.body, version: object.etag };\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tconst Key = this.key(stage, 'state.json');\n\t\ttry {\n\t\t\tconst { ETag } = await this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\t...(expectedVersion === null\n\t\t\t\t\t\t? { IfNoneMatch: '*' }\n\t\t\t\t\t\t: { IfMatch: expectedVersion }),\n\t\t\t\t}),\n\t\t\t);\n\t\t\tif (ETag) return ETag;\n\t\t\t// Some S3-compatible servers omit the ETag on a conditional put.\n\t\t\treturn (await this.get(Key))?.etag ?? '';\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tconst current = await this.get(Key);\n\t\t\t\tthrow new StateVersionConflict(\n\t\t\t\t\tstage,\n\t\t\t\t\texpectedVersion,\n\t\t\t\t\tcurrent?.etag ?? null,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: this.key(stage, 'state.v1.json'),\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!lostRace(error)) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: JSON.stringify(holder),\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tthrow new StateLocked(\n\t\t\t\t\tstage,\n\t\t\t\t\tawait this.readLock(stage),\n\t\t\t\t\t`s3://${this.bucket}/${Key}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\tconst object = await this.get(this.key(stage, 'lock.json'));\n\t\tif (!object) return null;\n\t\ttry {\n\t\t\treturn JSON.parse(object.body) as LockHolder;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\tconst object = await this.get(Key);\n\t\tif (!object) return;\n\t\tif (holder) {\n\t\t\ttry {\n\t\t\t\tif ((JSON.parse(object.body) as LockHolder).id !== holder.id) return;\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\t// Conditional on the lock read above, so a lock taken by another\n\t\t\t// run in the meantime survives.\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tIfMatch: object.etag,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!lostRace(error) && !notFound(error)) throw error;\n\t\t}\n\t}\n\n\tprivate async get(\n\t\tKey: string,\n\t): Promise<{ body: string; etag: string } | null> {\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetObjectCommand({ Bucket: this.bucket, Key }),\n\t\t\t);\n\t\t\tconst body = (await response.Body?.transformToString('utf-8')) ?? '';\n\t\t\treturn { body, etag: response.ETag ?? '' };\n\t\t} catch (error) {\n\t\t\tif (notFound(error)) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"S3StateStore--4tYVMAL.cjs","names":["DocumentStateStore","S3Client","PutObjectCommand","StateVersionConflict","StateLocked","DeleteObjectCommand","GetObjectCommand"],"sources":["../src/deploy/S3StateStore.ts"],"sourcesContent":["/**\n * Amazon S3 state store.\n *\n * - `<prefix>/<workspace>/<stage>/state.json`: the state.\n * - `<prefix>/<workspace>/<stage>/lock.json`: the lock.\n * - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on\n * migration.\n *\n * Every write is conditional on the server: `If-None-Match: *` to create,\n * `If-Match: <etag>` to replace. S3 answers a lost race with 412 (or 409 when\n * two conditional writes are in flight at once), so there is no window\n * between a check and a write for another run to slip into.\n */\n\nimport {\n\tDeleteObjectCommand,\n\tGetObjectCommand,\n\tPutObjectCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { AwsRegion } from './StateProvider';\nimport {\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nexport interface S3StateStoreOptions {\n\tworkspaceName: string;\n\tbucket: string;\n\t/** Key prefix inside the bucket; `gkm` by default. */\n\tprefix?: string;\n\tregion?: AwsRegion;\n\t/** Uses the default credential chain when omitted. */\n\tprofile?: string;\n\tcredentials?: S3ClientConfig['credentials'];\n\t/** For the local AWS emulator. */\n\tendpoint?: string;\n}\n\ninterface S3Failure {\n\tname?: string;\n\t$metadata?: { httpStatusCode?: number };\n}\n\n/** A conditional write that lost: the object is not what the write assumed. */\nfunction lostRace(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'PreconditionFailed' ||\n\t\tname === 'ConditionalRequestConflict' ||\n\t\t$metadata?.httpStatusCode === 412 ||\n\t\t$metadata?.httpStatusCode === 409\n\t);\n}\n\nfunction notFound(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'NoSuchKey' ||\n\t\tname === 'NotFound' ||\n\t\t$metadata?.httpStatusCode === 404\n\t);\n}\n\nexport class S3StateStore extends DocumentStateStore {\n\tprivate readonly prefix: string;\n\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\treadonly bucket: string,\n\t\tprivate readonly client: S3Client,\n\t\tprefix = 'gkm',\n\t) {\n\t\tsuper();\n\t\tthis.prefix = prefix.replace(/\\/+$/, '');\n\t}\n\n\tstatic create(options: S3StateStoreOptions): S3StateStore {\n\t\tconst config: S3ClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t\t// The emulator serves buckets by path, not by subdomain.\n\t\t\tforcePathStyle: options.endpoint ? true : undefined,\n\t\t};\n\t\tif (options.profile) {\n\t\t\t// Required lazily: only a profile needs the credential providers.\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tconfig.credentials = options.credentials;\n\t\t}\n\t\treturn new S3StateStore(\n\t\t\toptions.workspaceName,\n\t\t\toptions.bucket,\n\t\t\tnew S3Client(config),\n\t\t\toptions.prefix,\n\t\t);\n\t}\n\n\tprivate key(stage: string, leaf: string): string {\n\t\tconst path = `${this.workspaceName}/${stage}/${leaf}`;\n\t\treturn this.prefix ? `${this.prefix}/${path}` : path;\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn `s3://${this.bucket}/${this.key(stage, 'state.json')}`;\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\tconst object = await this.get(this.key(stage, 'state.json'));\n\t\tif (!object) return null;\n\t\treturn { body: object.body, version: object.etag };\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tconst Key = this.key(stage, 'state.json');\n\t\ttry {\n\t\t\tconst { ETag } = await this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\t...(expectedVersion === null\n\t\t\t\t\t\t? { IfNoneMatch: '*' }\n\t\t\t\t\t\t: { IfMatch: expectedVersion }),\n\t\t\t\t}),\n\t\t\t);\n\t\t\tif (ETag) return ETag;\n\t\t\t// Some S3-compatible servers omit the ETag on a conditional put.\n\t\t\treturn (await this.get(Key))?.etag ?? '';\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tconst current = await this.get(Key);\n\t\t\t\tthrow new StateVersionConflict(\n\t\t\t\t\tstage,\n\t\t\t\t\texpectedVersion,\n\t\t\t\t\tcurrent?.etag ?? null,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: this.key(stage, 'state.v1.json'),\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!lostRace(error)) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: JSON.stringify(holder),\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tthrow new StateLocked(\n\t\t\t\t\tstage,\n\t\t\t\t\tawait this.readLock(stage),\n\t\t\t\t\t`s3://${this.bucket}/${Key}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\tconst object = await this.get(this.key(stage, 'lock.json'));\n\t\tif (!object) return null;\n\t\ttry {\n\t\t\treturn JSON.parse(object.body) as LockHolder;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\tconst object = await this.get(Key);\n\t\tif (!object) return;\n\t\tif (holder) {\n\t\t\ttry {\n\t\t\t\tif ((JSON.parse(object.body) as LockHolder).id !== holder.id) return;\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\t// Conditional on the lock read above, so a lock taken by another\n\t\t\t// run in the meantime survives.\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tIfMatch: object.etag,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!lostRace(error) && !notFound(error)) throw error;\n\t\t}\n\t}\n\n\tprivate async get(\n\t\tKey: string,\n\t): Promise<{ body: string; etag: string } | null> {\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetObjectCommand({ Bucket: this.bucket, Key }),\n\t\t\t);\n\t\t\tconst body = (await response.Body?.transformToString('utf-8')) ?? '';\n\t\t\treturn { body, etag: response.ETag ?? '' };\n\t\t} catch (error) {\n\t\t\tif (notFound(error)) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as __require } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
2
|
-
import { d as StateLocked, l as DocumentStateStore, m as StateVersionConflict } from "./sniffer-
|
|
2
|
+
import { d as StateLocked, l as DocumentStateStore, m as StateVersionConflict } from "./sniffer-BdFeLSmA.mjs";
|
|
3
3
|
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
4
4
|
//#region src/deploy/S3StateStore.ts
|
|
5
5
|
/**
|
|
@@ -158,4 +158,4 @@ var S3StateStore = class S3StateStore extends DocumentStateStore {
|
|
|
158
158
|
//#endregion
|
|
159
159
|
export { S3StateStore };
|
|
160
160
|
|
|
161
|
-
//# sourceMappingURL=S3StateStore-
|
|
161
|
+
//# sourceMappingURL=S3StateStore-B_ZF9eHt.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"S3StateStore-BFHuctWM.mjs","names":[],"sources":["../src/deploy/S3StateStore.ts"],"sourcesContent":["/**\n * Amazon S3 state store.\n *\n * - `<prefix>/<workspace>/<stage>/state.json`: the state.\n * - `<prefix>/<workspace>/<stage>/lock.json`: the lock.\n * - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on\n * migration.\n *\n * Every write is conditional on the server: `If-None-Match: *` to create,\n * `If-Match: <etag>` to replace. S3 answers a lost race with 412 (or 409 when\n * two conditional writes are in flight at once), so there is no window\n * between a check and a write for another run to slip into.\n */\n\nimport {\n\tDeleteObjectCommand,\n\tGetObjectCommand,\n\tPutObjectCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { AwsRegion } from './StateProvider';\nimport {\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nexport interface S3StateStoreOptions {\n\tworkspaceName: string;\n\tbucket: string;\n\t/** Key prefix inside the bucket; `gkm` by default. */\n\tprefix?: string;\n\tregion?: AwsRegion;\n\t/** Uses the default credential chain when omitted. */\n\tprofile?: string;\n\tcredentials?: S3ClientConfig['credentials'];\n\t/** For the local AWS emulator. */\n\tendpoint?: string;\n}\n\ninterface S3Failure {\n\tname?: string;\n\t$metadata?: { httpStatusCode?: number };\n}\n\n/** A conditional write that lost: the object is not what the write assumed. */\nfunction lostRace(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'PreconditionFailed' ||\n\t\tname === 'ConditionalRequestConflict' ||\n\t\t$metadata?.httpStatusCode === 412 ||\n\t\t$metadata?.httpStatusCode === 409\n\t);\n}\n\nfunction notFound(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'NoSuchKey' ||\n\t\tname === 'NotFound' ||\n\t\t$metadata?.httpStatusCode === 404\n\t);\n}\n\nexport class S3StateStore extends DocumentStateStore {\n\tprivate readonly prefix: string;\n\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\treadonly bucket: string,\n\t\tprivate readonly client: S3Client,\n\t\tprefix = 'gkm',\n\t) {\n\t\tsuper();\n\t\tthis.prefix = prefix.replace(/\\/+$/, '');\n\t}\n\n\tstatic create(options: S3StateStoreOptions): S3StateStore {\n\t\tconst config: S3ClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t\t// The emulator serves buckets by path, not by subdomain.\n\t\t\tforcePathStyle: options.endpoint ? true : undefined,\n\t\t};\n\t\tif (options.profile) {\n\t\t\t// Required lazily: only a profile needs the credential providers.\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tconfig.credentials = options.credentials;\n\t\t}\n\t\treturn new S3StateStore(\n\t\t\toptions.workspaceName,\n\t\t\toptions.bucket,\n\t\t\tnew S3Client(config),\n\t\t\toptions.prefix,\n\t\t);\n\t}\n\n\tprivate key(stage: string, leaf: string): string {\n\t\tconst path = `${this.workspaceName}/${stage}/${leaf}`;\n\t\treturn this.prefix ? `${this.prefix}/${path}` : path;\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn `s3://${this.bucket}/${this.key(stage, 'state.json')}`;\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\tconst object = await this.get(this.key(stage, 'state.json'));\n\t\tif (!object) return null;\n\t\treturn { body: object.body, version: object.etag };\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tconst Key = this.key(stage, 'state.json');\n\t\ttry {\n\t\t\tconst { ETag } = await this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\t...(expectedVersion === null\n\t\t\t\t\t\t? { IfNoneMatch: '*' }\n\t\t\t\t\t\t: { IfMatch: expectedVersion }),\n\t\t\t\t}),\n\t\t\t);\n\t\t\tif (ETag) return ETag;\n\t\t\t// Some S3-compatible servers omit the ETag on a conditional put.\n\t\t\treturn (await this.get(Key))?.etag ?? '';\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tconst current = await this.get(Key);\n\t\t\t\tthrow new StateVersionConflict(\n\t\t\t\t\tstage,\n\t\t\t\t\texpectedVersion,\n\t\t\t\t\tcurrent?.etag ?? null,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: this.key(stage, 'state.v1.json'),\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!lostRace(error)) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: JSON.stringify(holder),\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tthrow new StateLocked(\n\t\t\t\t\tstage,\n\t\t\t\t\tawait this.readLock(stage),\n\t\t\t\t\t`s3://${this.bucket}/${Key}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\tconst object = await this.get(this.key(stage, 'lock.json'));\n\t\tif (!object) return null;\n\t\ttry {\n\t\t\treturn JSON.parse(object.body) as LockHolder;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\tconst object = await this.get(Key);\n\t\tif (!object) return;\n\t\tif (holder) {\n\t\t\ttry {\n\t\t\t\tif ((JSON.parse(object.body) as LockHolder).id !== holder.id) return;\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\t// Conditional on the lock read above, so a lock taken by another\n\t\t\t// run in the meantime survives.\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tIfMatch: object.etag,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!lostRace(error) && !notFound(error)) throw error;\n\t\t}\n\t}\n\n\tprivate async get(\n\t\tKey: string,\n\t): Promise<{ body: string; etag: string } | null> {\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetObjectCommand({ Bucket: this.bucket, Key }),\n\t\t\t);\n\t\t\tconst body = (await response.Body?.transformToString('utf-8')) ?? '';\n\t\t\treturn { body, etag: response.ETag ?? '' };\n\t\t} catch (error) {\n\t\t\tif (notFound(error)) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"S3StateStore-B_ZF9eHt.mjs","names":[],"sources":["../src/deploy/S3StateStore.ts"],"sourcesContent":["/**\n * Amazon S3 state store.\n *\n * - `<prefix>/<workspace>/<stage>/state.json`: the state.\n * - `<prefix>/<workspace>/<stage>/lock.json`: the lock.\n * - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on\n * migration.\n *\n * Every write is conditional on the server: `If-None-Match: *` to create,\n * `If-Match: <etag>` to replace. S3 answers a lost race with 412 (or 409 when\n * two conditional writes are in flight at once), so there is no window\n * between a check and a write for another run to slip into.\n */\n\nimport {\n\tDeleteObjectCommand,\n\tGetObjectCommand,\n\tPutObjectCommand,\n\tS3Client,\n\ttype S3ClientConfig,\n} from '@aws-sdk/client-s3';\nimport type { AwsRegion } from './StateProvider';\nimport {\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nexport interface S3StateStoreOptions {\n\tworkspaceName: string;\n\tbucket: string;\n\t/** Key prefix inside the bucket; `gkm` by default. */\n\tprefix?: string;\n\tregion?: AwsRegion;\n\t/** Uses the default credential chain when omitted. */\n\tprofile?: string;\n\tcredentials?: S3ClientConfig['credentials'];\n\t/** For the local AWS emulator. */\n\tendpoint?: string;\n}\n\ninterface S3Failure {\n\tname?: string;\n\t$metadata?: { httpStatusCode?: number };\n}\n\n/** A conditional write that lost: the object is not what the write assumed. */\nfunction lostRace(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'PreconditionFailed' ||\n\t\tname === 'ConditionalRequestConflict' ||\n\t\t$metadata?.httpStatusCode === 412 ||\n\t\t$metadata?.httpStatusCode === 409\n\t);\n}\n\nfunction notFound(error: unknown): boolean {\n\tconst { name, $metadata } = (error ?? {}) as S3Failure;\n\treturn (\n\t\tname === 'NoSuchKey' ||\n\t\tname === 'NotFound' ||\n\t\t$metadata?.httpStatusCode === 404\n\t);\n}\n\nexport class S3StateStore extends DocumentStateStore {\n\tprivate readonly prefix: string;\n\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\treadonly bucket: string,\n\t\tprivate readonly client: S3Client,\n\t\tprefix = 'gkm',\n\t) {\n\t\tsuper();\n\t\tthis.prefix = prefix.replace(/\\/+$/, '');\n\t}\n\n\tstatic create(options: S3StateStoreOptions): S3StateStore {\n\t\tconst config: S3ClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t\t// The emulator serves buckets by path, not by subdomain.\n\t\t\tforcePathStyle: options.endpoint ? true : undefined,\n\t\t};\n\t\tif (options.profile) {\n\t\t\t// Required lazily: only a profile needs the credential providers.\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tconfig.credentials = options.credentials;\n\t\t}\n\t\treturn new S3StateStore(\n\t\t\toptions.workspaceName,\n\t\t\toptions.bucket,\n\t\t\tnew S3Client(config),\n\t\t\toptions.prefix,\n\t\t);\n\t}\n\n\tprivate key(stage: string, leaf: string): string {\n\t\tconst path = `${this.workspaceName}/${stage}/${leaf}`;\n\t\treturn this.prefix ? `${this.prefix}/${path}` : path;\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn `s3://${this.bucket}/${this.key(stage, 'state.json')}`;\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\tconst object = await this.get(this.key(stage, 'state.json'));\n\t\tif (!object) return null;\n\t\treturn { body: object.body, version: object.etag };\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tconst Key = this.key(stage, 'state.json');\n\t\ttry {\n\t\t\tconst { ETag } = await this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\t...(expectedVersion === null\n\t\t\t\t\t\t? { IfNoneMatch: '*' }\n\t\t\t\t\t\t: { IfMatch: expectedVersion }),\n\t\t\t\t}),\n\t\t\t);\n\t\t\tif (ETag) return ETag;\n\t\t\t// Some S3-compatible servers omit the ETag on a conditional put.\n\t\t\treturn (await this.get(Key))?.etag ?? '';\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tconst current = await this.get(Key);\n\t\t\t\tthrow new StateVersionConflict(\n\t\t\t\t\tstage,\n\t\t\t\t\texpectedVersion,\n\t\t\t\t\tcurrent?.etag ?? null,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey: this.key(stage, 'state.v1.json'),\n\t\t\t\t\tBody: body,\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!lostRace(error)) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tBody: JSON.stringify(holder),\n\t\t\t\t\tContentType: 'application/json',\n\t\t\t\t\tIfNoneMatch: '*',\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (lostRace(error)) {\n\t\t\t\tthrow new StateLocked(\n\t\t\t\t\tstage,\n\t\t\t\t\tawait this.readLock(stage),\n\t\t\t\t\t`s3://${this.bucket}/${Key}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\tconst object = await this.get(this.key(stage, 'lock.json'));\n\t\tif (!object) return null;\n\t\ttry {\n\t\t\treturn JSON.parse(object.body) as LockHolder;\n\t\t} catch {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tconst Key = this.key(stage, 'lock.json');\n\t\tconst object = await this.get(Key);\n\t\tif (!object) return;\n\t\tif (holder) {\n\t\t\ttry {\n\t\t\t\tif ((JSON.parse(object.body) as LockHolder).id !== holder.id) return;\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\ttry {\n\t\t\t// Conditional on the lock read above, so a lock taken by another\n\t\t\t// run in the meantime survives.\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteObjectCommand({\n\t\t\t\t\tBucket: this.bucket,\n\t\t\t\t\tKey,\n\t\t\t\t\tIfMatch: object.etag,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!lostRace(error) && !notFound(error)) throw error;\n\t\t}\n\t}\n\n\tprivate async get(\n\t\tKey: string,\n\t): Promise<{ body: string; etag: string } | null> {\n\t\ttry {\n\t\t\tconst response = await this.client.send(\n\t\t\t\tnew GetObjectCommand({ Bucket: this.bucket, Key }),\n\t\t\t);\n\t\t\tconst body = (await response.Body?.transformToString('utf-8')) ?? '';\n\t\t\treturn { body, etag: response.ETag ?? '' };\n\t\t} catch (error) {\n\t\t\tif (notFound(error)) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_sniffer = require("./sniffer-
|
|
1
|
+
const require_sniffer = require("./sniffer-D66HIvi9.cjs");
|
|
2
2
|
let _aws_sdk_client_ssm = require("@aws-sdk/client-ssm");
|
|
3
3
|
//#region src/deploy/SSMStateStore.ts
|
|
4
4
|
/**
|
|
@@ -150,4 +150,4 @@ function parseHolder(value) {
|
|
|
150
150
|
//#endregion
|
|
151
151
|
exports.SSMStateStore = SSMStateStore;
|
|
152
152
|
|
|
153
|
-
//# sourceMappingURL=SSMStateStore-
|
|
153
|
+
//# sourceMappingURL=SSMStateStore-ACZ8hRvZ.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SSMStateStore-BqCA9TP2.cjs","names":["DocumentStateStore","SSMClient","PutParameterCommand","ParameterAlreadyExists","StateVersionConflict","StateLocked","DeleteParameterCommand","ParameterNotFound","GetParameterCommand"],"sources":["../src/deploy/SSMStateStore.ts"],"sourcesContent":["/**\n * AWS SSM Parameter Store state store.\n *\n * - `/gkm/<workspace>/<stage>/state`: the state, a SecureString.\n * - `/gkm/<workspace>/<stage>/lock`: the lock, created only if absent.\n * - `/gkm/<workspace>/<stage>/state.v1`: the v1 state, kept on migration.\n *\n * SSM has no conditional put, so a write checks the parameter's version\n * before it puts, and checks the version the put returns after: a put that\n * did not land exactly one version above the one it was based on raced\n * another writer, and raises `StateVersionConflict` rather than reporting\n * success. Both writes stay in the parameter's history.\n */\n\nimport {\n\tDeleteParameterCommand,\n\tGetParameterCommand,\n\tParameterAlreadyExists,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { AwsRegion } from './StateProvider';\nimport {\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nexport interface SSMStateStoreOptions {\n\tworkspaceName: string;\n\tregion?: AwsRegion;\n\t/** Uses the default credential chain when omitted. */\n\tprofile?: string;\n\tcredentials?: SSMClientConfig['credentials'];\n\t/** For the local AWS emulator. */\n\tendpoint?: string;\n}\n\nexport class SSMStateStore extends DocumentStateStore {\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\tprivate readonly client: SSMClient,\n\t) {\n\t\tsuper();\n\t}\n\n\tstatic create(options: SSMStateStoreOptions): SSMStateStore {\n\t\tconst config: SSMClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t};\n\t\tif (options.profile) {\n\t\t\t// Required lazily: only a profile needs the credential providers.\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tconfig.credentials = options.credentials;\n\t\t}\n\t\treturn new SSMStateStore(options.workspaceName, new SSMClient(config));\n\t}\n\n\tprivate name(stage: string, leaf: string): string {\n\t\treturn `/gkm/${this.workspaceName}/${stage}/${leaf}`;\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn `ssm:${this.name(stage, 'state')}`;\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\tconst parameter = await this.get(this.name(stage, 'state'));\n\t\tif (!parameter) return null;\n\t\treturn { body: parameter.value, version: String(parameter.version) };\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tconst Name = this.name(stage, 'state');\n\t\tconst Description = `GKM deployment state for ${this.workspaceName}/${stage}`;\n\n\t\tif (expectedVersion === null) {\n\t\t\ttry {\n\t\t\t\tconst { Version } = await this.client.send(\n\t\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\t\tName,\n\t\t\t\t\t\tValue: body,\n\t\t\t\t\t\tType: 'SecureString',\n\t\t\t\t\t\tDescription,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn String(Version);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof ParameterAlreadyExists) {\n\t\t\t\t\tconst current = await this.get(Name);\n\t\t\t\t\tthrow new StateVersionConflict(\n\t\t\t\t\t\tstage,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tcurrent ? String(current.version) : null,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tconst current = await this.get(Name);\n\t\tconst actual = current ? String(current.version) : null;\n\t\tif (actual !== expectedVersion) {\n\t\t\tthrow new StateVersionConflict(stage, expectedVersion, actual);\n\t\t}\n\n\t\tconst { Version } = await this.client.send(\n\t\t\tnew PutParameterCommand({\n\t\t\t\tName,\n\t\t\t\tValue: body,\n\t\t\t\tType: 'SecureString',\n\t\t\t\tOverwrite: true,\n\t\t\t\tDescription,\n\t\t\t}),\n\t\t);\n\t\tif (Version !== Number(expectedVersion) + 1) {\n\t\t\t// Another put landed between the check and this one. Ours is now\n\t\t\t// on top, but the caller that wrote the other believes its write\n\t\t\t// is current — its next write will conflict too, so neither is\n\t\t\t// lost silently.\n\t\t\tthrow new StateVersionConflict(\n\t\t\t\tstage,\n\t\t\t\texpectedVersion,\n\t\t\t\tString((Version ?? 1) - 1),\n\t\t\t);\n\t\t}\n\n\t\t// The version alone is not enough under an emulator, and need not be\n\t\t// on SSM either: two overwrites that raced can both be answered with\n\t\t// the same version, and both would report success. If the parameter is\n\t\t// still at the version this put returned, it has to hold this body —\n\t\t// otherwise the other put is the one that stuck.\n\t\tconst landed = await this.get(Name);\n\t\tif (landed && landed.version === Version && landed.value !== body) {\n\t\t\tthrow new StateVersionConflict(stage, expectedVersion, String(Version));\n\t\t}\n\t\treturn String(Version);\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\tName: this.name(stage, 'state.v1'),\n\t\t\t\t\tValue: body,\n\t\t\t\t\tType: 'SecureString',\n\t\t\t\t\tDescription: `GKM v1 deployment state for ${this.workspaceName}/${stage}, kept on migration`,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!(error instanceof ParameterAlreadyExists)) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tconst Name = this.name(stage, 'lock');\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\tName,\n\t\t\t\t\tValue: JSON.stringify(holder),\n\t\t\t\t\tType: 'String',\n\t\t\t\t\tDescription: `GKM deploy lock for ${this.workspaceName}/${stage}`,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (error instanceof ParameterAlreadyExists) {\n\t\t\t\tthrow new StateLocked(stage, await this.readLock(stage), `ssm:${Name}`);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\n\t\t// A create-only put is atomic on SSM, so the lock is ours. Read it back\n\t\t// anyway: on a store where two racing creates can both succeed (the\n\t\t// local emulator does, a few times in a hundred) the later one has\n\t\t// overwritten the earlier — version 2, someone else's holder — and\n\t\t// taking the lock regardless would give the stage two deploys.\n\t\tconst created = await this.get(Name);\n\t\tconst recorded = created ? parseHolder(created.value) : null;\n\t\tif (created?.version !== 1 || recorded?.id !== holder.id) {\n\t\t\tthrow new StateLocked(stage, recorded, `ssm:${Name}`);\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\tconst parameter = await this.get(this.name(stage, 'lock'));\n\t\treturn parameter ? parseHolder(parameter.value) : null;\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tif (holder) {\n\t\t\tconst current = await this.readLock(stage);\n\t\t\tif (current?.id !== holder.id) return;\n\t\t}\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteParameterCommand({ Name: this.name(stage, 'lock') }),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!(error instanceof ParameterNotFound)) throw error;\n\t\t}\n\t}\n\n\tprivate async get(\n\t\tName: string,\n\t): Promise<{ value: string; version: number } | null> {\n\t\ttry {\n\t\t\tconst { Parameter } = await this.client.send(\n\t\t\t\tnew GetParameterCommand({ Name, WithDecryption: true }),\n\t\t\t);\n\t\t\tif (Parameter?.Value === undefined) return null;\n\t\t\treturn { value: Parameter.Value, version: Parameter.Version ?? 1 };\n\t\t} catch (error) {\n\t\t\tif (error instanceof ParameterNotFound) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\nfunction parseHolder(value: string): LockHolder | null {\n\ttry {\n\t\treturn JSON.parse(value) as LockHolder;\n\t} catch {\n\t\treturn null;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SSMStateStore-ACZ8hRvZ.cjs","names":["DocumentStateStore","SSMClient","PutParameterCommand","ParameterAlreadyExists","StateVersionConflict","StateLocked","DeleteParameterCommand","ParameterNotFound","GetParameterCommand"],"sources":["../src/deploy/SSMStateStore.ts"],"sourcesContent":["/**\n * AWS SSM Parameter Store state store.\n *\n * - `/gkm/<workspace>/<stage>/state`: the state, a SecureString.\n * - `/gkm/<workspace>/<stage>/lock`: the lock, created only if absent.\n * - `/gkm/<workspace>/<stage>/state.v1`: the v1 state, kept on migration.\n *\n * SSM has no conditional put, so a write checks the parameter's version\n * before it puts, and checks the version the put returns after: a put that\n * did not land exactly one version above the one it was based on raced\n * another writer, and raises `StateVersionConflict` rather than reporting\n * success. Both writes stay in the parameter's history.\n */\n\nimport {\n\tDeleteParameterCommand,\n\tGetParameterCommand,\n\tParameterAlreadyExists,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { AwsRegion } from './StateProvider';\nimport {\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nexport interface SSMStateStoreOptions {\n\tworkspaceName: string;\n\tregion?: AwsRegion;\n\t/** Uses the default credential chain when omitted. */\n\tprofile?: string;\n\tcredentials?: SSMClientConfig['credentials'];\n\t/** For the local AWS emulator. */\n\tendpoint?: string;\n}\n\nexport class SSMStateStore extends DocumentStateStore {\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\tprivate readonly client: SSMClient,\n\t) {\n\t\tsuper();\n\t}\n\n\tstatic create(options: SSMStateStoreOptions): SSMStateStore {\n\t\tconst config: SSMClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t};\n\t\tif (options.profile) {\n\t\t\t// Required lazily: only a profile needs the credential providers.\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tconfig.credentials = options.credentials;\n\t\t}\n\t\treturn new SSMStateStore(options.workspaceName, new SSMClient(config));\n\t}\n\n\tprivate name(stage: string, leaf: string): string {\n\t\treturn `/gkm/${this.workspaceName}/${stage}/${leaf}`;\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn `ssm:${this.name(stage, 'state')}`;\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\tconst parameter = await this.get(this.name(stage, 'state'));\n\t\tif (!parameter) return null;\n\t\treturn { body: parameter.value, version: String(parameter.version) };\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tconst Name = this.name(stage, 'state');\n\t\tconst Description = `GKM deployment state for ${this.workspaceName}/${stage}`;\n\n\t\tif (expectedVersion === null) {\n\t\t\ttry {\n\t\t\t\tconst { Version } = await this.client.send(\n\t\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\t\tName,\n\t\t\t\t\t\tValue: body,\n\t\t\t\t\t\tType: 'SecureString',\n\t\t\t\t\t\tDescription,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn String(Version);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof ParameterAlreadyExists) {\n\t\t\t\t\tconst current = await this.get(Name);\n\t\t\t\t\tthrow new StateVersionConflict(\n\t\t\t\t\t\tstage,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tcurrent ? String(current.version) : null,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tconst current = await this.get(Name);\n\t\tconst actual = current ? String(current.version) : null;\n\t\tif (actual !== expectedVersion) {\n\t\t\tthrow new StateVersionConflict(stage, expectedVersion, actual);\n\t\t}\n\n\t\tconst { Version } = await this.client.send(\n\t\t\tnew PutParameterCommand({\n\t\t\t\tName,\n\t\t\t\tValue: body,\n\t\t\t\tType: 'SecureString',\n\t\t\t\tOverwrite: true,\n\t\t\t\tDescription,\n\t\t\t}),\n\t\t);\n\t\tif (Version !== Number(expectedVersion) + 1) {\n\t\t\t// Another put landed between the check and this one. Ours is now\n\t\t\t// on top, but the caller that wrote the other believes its write\n\t\t\t// is current — its next write will conflict too, so neither is\n\t\t\t// lost silently.\n\t\t\tthrow new StateVersionConflict(\n\t\t\t\tstage,\n\t\t\t\texpectedVersion,\n\t\t\t\tString((Version ?? 1) - 1),\n\t\t\t);\n\t\t}\n\n\t\t// The version alone is not enough under an emulator, and need not be\n\t\t// on SSM either: two overwrites that raced can both be answered with\n\t\t// the same version, and both would report success. If the parameter is\n\t\t// still at the version this put returned, it has to hold this body —\n\t\t// otherwise the other put is the one that stuck.\n\t\tconst landed = await this.get(Name);\n\t\tif (landed && landed.version === Version && landed.value !== body) {\n\t\t\tthrow new StateVersionConflict(stage, expectedVersion, String(Version));\n\t\t}\n\t\treturn String(Version);\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\tName: this.name(stage, 'state.v1'),\n\t\t\t\t\tValue: body,\n\t\t\t\t\tType: 'SecureString',\n\t\t\t\t\tDescription: `GKM v1 deployment state for ${this.workspaceName}/${stage}, kept on migration`,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!(error instanceof ParameterAlreadyExists)) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tconst Name = this.name(stage, 'lock');\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\tName,\n\t\t\t\t\tValue: JSON.stringify(holder),\n\t\t\t\t\tType: 'String',\n\t\t\t\t\tDescription: `GKM deploy lock for ${this.workspaceName}/${stage}`,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (error instanceof ParameterAlreadyExists) {\n\t\t\t\tthrow new StateLocked(stage, await this.readLock(stage), `ssm:${Name}`);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\n\t\t// A create-only put is atomic on SSM, so the lock is ours. Read it back\n\t\t// anyway: on a store where two racing creates can both succeed (the\n\t\t// local emulator does, a few times in a hundred) the later one has\n\t\t// overwritten the earlier — version 2, someone else's holder — and\n\t\t// taking the lock regardless would give the stage two deploys.\n\t\tconst created = await this.get(Name);\n\t\tconst recorded = created ? parseHolder(created.value) : null;\n\t\tif (created?.version !== 1 || recorded?.id !== holder.id) {\n\t\t\tthrow new StateLocked(stage, recorded, `ssm:${Name}`);\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\tconst parameter = await this.get(this.name(stage, 'lock'));\n\t\treturn parameter ? parseHolder(parameter.value) : null;\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tif (holder) {\n\t\t\tconst current = await this.readLock(stage);\n\t\t\tif (current?.id !== holder.id) return;\n\t\t}\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteParameterCommand({ Name: this.name(stage, 'lock') }),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!(error instanceof ParameterNotFound)) throw error;\n\t\t}\n\t}\n\n\tprivate async get(\n\t\tName: string,\n\t): Promise<{ value: string; version: number } | null> {\n\t\ttry {\n\t\t\tconst { Parameter } = await this.client.send(\n\t\t\t\tnew GetParameterCommand({ Name, WithDecryption: true }),\n\t\t\t);\n\t\t\tif (Parameter?.Value === undefined) return null;\n\t\t\treturn { value: Parameter.Value, version: Parameter.Version ?? 1 };\n\t\t} catch (error) {\n\t\t\tif (error instanceof ParameterNotFound) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\nfunction parseHolder(value: string): LockHolder | null {\n\ttry {\n\t\treturn JSON.parse(value) as LockHolder;\n\t} catch {\n\t\treturn null;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as __require } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
2
|
-
import { d as StateLocked, l as DocumentStateStore, m as StateVersionConflict } from "./sniffer-
|
|
2
|
+
import { d as StateLocked, l as DocumentStateStore, m as StateVersionConflict } from "./sniffer-BdFeLSmA.mjs";
|
|
3
3
|
import { DeleteParameterCommand, GetParameterCommand, ParameterAlreadyExists, ParameterNotFound, PutParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
|
|
4
4
|
//#region src/deploy/SSMStateStore.ts
|
|
5
5
|
/**
|
|
@@ -151,4 +151,4 @@ function parseHolder(value) {
|
|
|
151
151
|
//#endregion
|
|
152
152
|
export { SSMStateStore };
|
|
153
153
|
|
|
154
|
-
//# sourceMappingURL=SSMStateStore-
|
|
154
|
+
//# sourceMappingURL=SSMStateStore-DjPuKqrm.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SSMStateStore-C1kvHScY.mjs","names":[],"sources":["../src/deploy/SSMStateStore.ts"],"sourcesContent":["/**\n * AWS SSM Parameter Store state store.\n *\n * - `/gkm/<workspace>/<stage>/state`: the state, a SecureString.\n * - `/gkm/<workspace>/<stage>/lock`: the lock, created only if absent.\n * - `/gkm/<workspace>/<stage>/state.v1`: the v1 state, kept on migration.\n *\n * SSM has no conditional put, so a write checks the parameter's version\n * before it puts, and checks the version the put returns after: a put that\n * did not land exactly one version above the one it was based on raced\n * another writer, and raises `StateVersionConflict` rather than reporting\n * success. Both writes stay in the parameter's history.\n */\n\nimport {\n\tDeleteParameterCommand,\n\tGetParameterCommand,\n\tParameterAlreadyExists,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { AwsRegion } from './StateProvider';\nimport {\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nexport interface SSMStateStoreOptions {\n\tworkspaceName: string;\n\tregion?: AwsRegion;\n\t/** Uses the default credential chain when omitted. */\n\tprofile?: string;\n\tcredentials?: SSMClientConfig['credentials'];\n\t/** For the local AWS emulator. */\n\tendpoint?: string;\n}\n\nexport class SSMStateStore extends DocumentStateStore {\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\tprivate readonly client: SSMClient,\n\t) {\n\t\tsuper();\n\t}\n\n\tstatic create(options: SSMStateStoreOptions): SSMStateStore {\n\t\tconst config: SSMClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t};\n\t\tif (options.profile) {\n\t\t\t// Required lazily: only a profile needs the credential providers.\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tconfig.credentials = options.credentials;\n\t\t}\n\t\treturn new SSMStateStore(options.workspaceName, new SSMClient(config));\n\t}\n\n\tprivate name(stage: string, leaf: string): string {\n\t\treturn `/gkm/${this.workspaceName}/${stage}/${leaf}`;\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn `ssm:${this.name(stage, 'state')}`;\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\tconst parameter = await this.get(this.name(stage, 'state'));\n\t\tif (!parameter) return null;\n\t\treturn { body: parameter.value, version: String(parameter.version) };\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tconst Name = this.name(stage, 'state');\n\t\tconst Description = `GKM deployment state for ${this.workspaceName}/${stage}`;\n\n\t\tif (expectedVersion === null) {\n\t\t\ttry {\n\t\t\t\tconst { Version } = await this.client.send(\n\t\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\t\tName,\n\t\t\t\t\t\tValue: body,\n\t\t\t\t\t\tType: 'SecureString',\n\t\t\t\t\t\tDescription,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn String(Version);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof ParameterAlreadyExists) {\n\t\t\t\t\tconst current = await this.get(Name);\n\t\t\t\t\tthrow new StateVersionConflict(\n\t\t\t\t\t\tstage,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tcurrent ? String(current.version) : null,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tconst current = await this.get(Name);\n\t\tconst actual = current ? String(current.version) : null;\n\t\tif (actual !== expectedVersion) {\n\t\t\tthrow new StateVersionConflict(stage, expectedVersion, actual);\n\t\t}\n\n\t\tconst { Version } = await this.client.send(\n\t\t\tnew PutParameterCommand({\n\t\t\t\tName,\n\t\t\t\tValue: body,\n\t\t\t\tType: 'SecureString',\n\t\t\t\tOverwrite: true,\n\t\t\t\tDescription,\n\t\t\t}),\n\t\t);\n\t\tif (Version !== Number(expectedVersion) + 1) {\n\t\t\t// Another put landed between the check and this one. Ours is now\n\t\t\t// on top, but the caller that wrote the other believes its write\n\t\t\t// is current — its next write will conflict too, so neither is\n\t\t\t// lost silently.\n\t\t\tthrow new StateVersionConflict(\n\t\t\t\tstage,\n\t\t\t\texpectedVersion,\n\t\t\t\tString((Version ?? 1) - 1),\n\t\t\t);\n\t\t}\n\n\t\t// The version alone is not enough under an emulator, and need not be\n\t\t// on SSM either: two overwrites that raced can both be answered with\n\t\t// the same version, and both would report success. If the parameter is\n\t\t// still at the version this put returned, it has to hold this body —\n\t\t// otherwise the other put is the one that stuck.\n\t\tconst landed = await this.get(Name);\n\t\tif (landed && landed.version === Version && landed.value !== body) {\n\t\t\tthrow new StateVersionConflict(stage, expectedVersion, String(Version));\n\t\t}\n\t\treturn String(Version);\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\tName: this.name(stage, 'state.v1'),\n\t\t\t\t\tValue: body,\n\t\t\t\t\tType: 'SecureString',\n\t\t\t\t\tDescription: `GKM v1 deployment state for ${this.workspaceName}/${stage}, kept on migration`,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!(error instanceof ParameterAlreadyExists)) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tconst Name = this.name(stage, 'lock');\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\tName,\n\t\t\t\t\tValue: JSON.stringify(holder),\n\t\t\t\t\tType: 'String',\n\t\t\t\t\tDescription: `GKM deploy lock for ${this.workspaceName}/${stage}`,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (error instanceof ParameterAlreadyExists) {\n\t\t\t\tthrow new StateLocked(stage, await this.readLock(stage), `ssm:${Name}`);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\n\t\t// A create-only put is atomic on SSM, so the lock is ours. Read it back\n\t\t// anyway: on a store where two racing creates can both succeed (the\n\t\t// local emulator does, a few times in a hundred) the later one has\n\t\t// overwritten the earlier — version 2, someone else's holder — and\n\t\t// taking the lock regardless would give the stage two deploys.\n\t\tconst created = await this.get(Name);\n\t\tconst recorded = created ? parseHolder(created.value) : null;\n\t\tif (created?.version !== 1 || recorded?.id !== holder.id) {\n\t\t\tthrow new StateLocked(stage, recorded, `ssm:${Name}`);\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\tconst parameter = await this.get(this.name(stage, 'lock'));\n\t\treturn parameter ? parseHolder(parameter.value) : null;\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tif (holder) {\n\t\t\tconst current = await this.readLock(stage);\n\t\t\tif (current?.id !== holder.id) return;\n\t\t}\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteParameterCommand({ Name: this.name(stage, 'lock') }),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!(error instanceof ParameterNotFound)) throw error;\n\t\t}\n\t}\n\n\tprivate async get(\n\t\tName: string,\n\t): Promise<{ value: string; version: number } | null> {\n\t\ttry {\n\t\t\tconst { Parameter } = await this.client.send(\n\t\t\t\tnew GetParameterCommand({ Name, WithDecryption: true }),\n\t\t\t);\n\t\t\tif (Parameter?.Value === undefined) return null;\n\t\t\treturn { value: Parameter.Value, version: Parameter.Version ?? 1 };\n\t\t} catch (error) {\n\t\t\tif (error instanceof ParameterNotFound) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\nfunction parseHolder(value: string): LockHolder | null {\n\ttry {\n\t\treturn JSON.parse(value) as LockHolder;\n\t} catch {\n\t\treturn null;\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SSMStateStore-DjPuKqrm.mjs","names":[],"sources":["../src/deploy/SSMStateStore.ts"],"sourcesContent":["/**\n * AWS SSM Parameter Store state store.\n *\n * - `/gkm/<workspace>/<stage>/state`: the state, a SecureString.\n * - `/gkm/<workspace>/<stage>/lock`: the lock, created only if absent.\n * - `/gkm/<workspace>/<stage>/state.v1`: the v1 state, kept on migration.\n *\n * SSM has no conditional put, so a write checks the parameter's version\n * before it puts, and checks the version the put returns after: a put that\n * did not land exactly one version above the one it was based on raced\n * another writer, and raises `StateVersionConflict` rather than reporting\n * success. Both writes stay in the parameter's history.\n */\n\nimport {\n\tDeleteParameterCommand,\n\tGetParameterCommand,\n\tParameterAlreadyExists,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { AwsRegion } from './StateProvider';\nimport {\n\tDocumentStateStore,\n\ttype LockHolder,\n\ttype RawState,\n\tStateLocked,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\nexport interface SSMStateStoreOptions {\n\tworkspaceName: string;\n\tregion?: AwsRegion;\n\t/** Uses the default credential chain when omitted. */\n\tprofile?: string;\n\tcredentials?: SSMClientConfig['credentials'];\n\t/** For the local AWS emulator. */\n\tendpoint?: string;\n}\n\nexport class SSMStateStore extends DocumentStateStore {\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\tprivate readonly client: SSMClient,\n\t) {\n\t\tsuper();\n\t}\n\n\tstatic create(options: SSMStateStoreOptions): SSMStateStore {\n\t\tconst config: SSMClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint: options.endpoint,\n\t\t};\n\t\tif (options.profile) {\n\t\t\t// Required lazily: only a profile needs the credential providers.\n\t\t\tconst { fromIni } = require('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: options.profile });\n\t\t} else if (options.credentials) {\n\t\t\tconfig.credentials = options.credentials;\n\t\t}\n\t\treturn new SSMStateStore(options.workspaceName, new SSMClient(config));\n\t}\n\n\tprivate name(stage: string, leaf: string): string {\n\t\treturn `/gkm/${this.workspaceName}/${stage}/${leaf}`;\n\t}\n\n\tprotected location(stage: string): string {\n\t\treturn `ssm:${this.name(stage, 'state')}`;\n\t}\n\n\tprotected async readRaw(stage: string): Promise<RawState | null> {\n\t\tconst parameter = await this.get(this.name(stage, 'state'));\n\t\tif (!parameter) return null;\n\t\treturn { body: parameter.value, version: String(parameter.version) };\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tbody: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\tconst Name = this.name(stage, 'state');\n\t\tconst Description = `GKM deployment state for ${this.workspaceName}/${stage}`;\n\n\t\tif (expectedVersion === null) {\n\t\t\ttry {\n\t\t\t\tconst { Version } = await this.client.send(\n\t\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\t\tName,\n\t\t\t\t\t\tValue: body,\n\t\t\t\t\t\tType: 'SecureString',\n\t\t\t\t\t\tDescription,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t\treturn String(Version);\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof ParameterAlreadyExists) {\n\t\t\t\t\tconst current = await this.get(Name);\n\t\t\t\t\tthrow new StateVersionConflict(\n\t\t\t\t\t\tstage,\n\t\t\t\t\t\tnull,\n\t\t\t\t\t\tcurrent ? String(current.version) : null,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\n\t\tconst current = await this.get(Name);\n\t\tconst actual = current ? String(current.version) : null;\n\t\tif (actual !== expectedVersion) {\n\t\t\tthrow new StateVersionConflict(stage, expectedVersion, actual);\n\t\t}\n\n\t\tconst { Version } = await this.client.send(\n\t\t\tnew PutParameterCommand({\n\t\t\t\tName,\n\t\t\t\tValue: body,\n\t\t\t\tType: 'SecureString',\n\t\t\t\tOverwrite: true,\n\t\t\t\tDescription,\n\t\t\t}),\n\t\t);\n\t\tif (Version !== Number(expectedVersion) + 1) {\n\t\t\t// Another put landed between the check and this one. Ours is now\n\t\t\t// on top, but the caller that wrote the other believes its write\n\t\t\t// is current — its next write will conflict too, so neither is\n\t\t\t// lost silently.\n\t\t\tthrow new StateVersionConflict(\n\t\t\t\tstage,\n\t\t\t\texpectedVersion,\n\t\t\t\tString((Version ?? 1) - 1),\n\t\t\t);\n\t\t}\n\n\t\t// The version alone is not enough under an emulator, and need not be\n\t\t// on SSM either: two overwrites that raced can both be answered with\n\t\t// the same version, and both would report success. If the parameter is\n\t\t// still at the version this put returned, it has to hold this body —\n\t\t// otherwise the other put is the one that stuck.\n\t\tconst landed = await this.get(Name);\n\t\tif (landed && landed.version === Version && landed.value !== body) {\n\t\t\tthrow new StateVersionConflict(stage, expectedVersion, String(Version));\n\t\t}\n\t\treturn String(Version);\n\t}\n\n\tprotected async writeV1Backup(stage: string, body: string): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\tName: this.name(stage, 'state.v1'),\n\t\t\t\t\tValue: body,\n\t\t\t\t\tType: 'SecureString',\n\t\t\t\t\tDescription: `GKM v1 deployment state for ${this.workspaceName}/${stage}, kept on migration`,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\t// The first migration's backup is the one worth keeping.\n\t\t\tif (!(error instanceof ParameterAlreadyExists)) throw error;\n\t\t}\n\t}\n\n\tprotected async createLock(stage: string, holder: LockHolder): Promise<void> {\n\t\tconst Name = this.name(stage, 'lock');\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew PutParameterCommand({\n\t\t\t\t\tName,\n\t\t\t\t\tValue: JSON.stringify(holder),\n\t\t\t\t\tType: 'String',\n\t\t\t\t\tDescription: `GKM deploy lock for ${this.workspaceName}/${stage}`,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (error instanceof ParameterAlreadyExists) {\n\t\t\t\tthrow new StateLocked(stage, await this.readLock(stage), `ssm:${Name}`);\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\n\t\t// A create-only put is atomic on SSM, so the lock is ours. Read it back\n\t\t// anyway: on a store where two racing creates can both succeed (the\n\t\t// local emulator does, a few times in a hundred) the later one has\n\t\t// overwritten the earlier — version 2, someone else's holder — and\n\t\t// taking the lock regardless would give the stage two deploys.\n\t\tconst created = await this.get(Name);\n\t\tconst recorded = created ? parseHolder(created.value) : null;\n\t\tif (created?.version !== 1 || recorded?.id !== holder.id) {\n\t\t\tthrow new StateLocked(stage, recorded, `ssm:${Name}`);\n\t\t}\n\t}\n\n\tprotected async readLock(stage: string): Promise<LockHolder | null> {\n\t\tconst parameter = await this.get(this.name(stage, 'lock'));\n\t\treturn parameter ? parseHolder(parameter.value) : null;\n\t}\n\n\tprotected async removeLock(\n\t\tstage: string,\n\t\tholder: LockHolder | null,\n\t): Promise<void> {\n\t\tif (holder) {\n\t\t\tconst current = await this.readLock(stage);\n\t\t\tif (current?.id !== holder.id) return;\n\t\t}\n\t\ttry {\n\t\t\tawait this.client.send(\n\t\t\t\tnew DeleteParameterCommand({ Name: this.name(stage, 'lock') }),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!(error instanceof ParameterNotFound)) throw error;\n\t\t}\n\t}\n\n\tprivate async get(\n\t\tName: string,\n\t): Promise<{ value: string; version: number } | null> {\n\t\ttry {\n\t\t\tconst { Parameter } = await this.client.send(\n\t\t\t\tnew GetParameterCommand({ Name, WithDecryption: true }),\n\t\t\t);\n\t\t\tif (Parameter?.Value === undefined) return null;\n\t\t\treturn { value: Parameter.Value, version: Parameter.Version ?? 1 };\n\t\t} catch (error) {\n\t\t\tif (error instanceof ParameterNotFound) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n}\n\nfunction parseHolder(value: string): LockHolder | null {\n\ttry {\n\t\treturn JSON.parse(value) as LockHolder;\n\t} catch {\n\t\treturn null;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
4
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
5
|
import { spawnSync } from "node:child_process";
|
|
5
6
|
//#region src/build/bundler.ts
|
|
@@ -81,10 +82,10 @@ async function bundleServer(options) {
|
|
|
81
82
|
for (const ext of external) args.push(`--external:${ext}`);
|
|
82
83
|
let masterKey;
|
|
83
84
|
if (stage) {
|
|
84
|
-
const { toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets } = await import("./state-
|
|
85
|
-
const { FileSecretsStore, secretsStoreFor } = await import("./state-
|
|
85
|
+
const { toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets } = await import("./state-Bn5JDMFT.mjs").then((n) => n.Yt);
|
|
86
|
+
const { FileSecretsStore, secretsStoreFor } = await import("./state-Bn5JDMFT.mjs").then((n) => n.Nt);
|
|
86
87
|
const { ConfigNotFound, loadWorkspaceSettings } = await import("./config.mjs");
|
|
87
|
-
const { encryptSecrets, generateDefineOptions } = await import("./
|
|
88
|
+
const { encryptSecrets, generateDefineOptions } = await import("./state-Bn5JDMFT.mjs").then((n) => n.L);
|
|
88
89
|
const store = await loadWorkspaceSettings().then((workspace) => secretsStoreFor(workspace, stage), (error) => {
|
|
89
90
|
if (error instanceof ConfigNotFound) return new FileSecretsStore(process.cwd());
|
|
90
91
|
throw error;
|
|
@@ -125,8 +126,22 @@ async function bundleServer(options) {
|
|
|
125
126
|
const encrypted = encryptSecrets(toEmbeddableSecrets(secrets));
|
|
126
127
|
masterKey = encrypted.masterKey;
|
|
127
128
|
const defines = generateDefineOptions(encrypted);
|
|
128
|
-
for (const [key, value] of Object.entries(defines)) args.push(`--define:${key}=${
|
|
129
|
+
for (const [key, value] of Object.entries(defines)) args.push(`--define:${key}=${value}`);
|
|
129
130
|
console.log(` Secrets encrypted for stage "${stage}"`);
|
|
131
|
+
} else {
|
|
132
|
+
const gkmDir = join(outputDir, "..", "..");
|
|
133
|
+
const encryptedPath = join(gkmDir, "credentials.enc");
|
|
134
|
+
const ivPath = join(gkmDir, "credentials.iv");
|
|
135
|
+
if (existsSync(encryptedPath) && existsSync(ivPath)) {
|
|
136
|
+
const { generateDefineOptions } = await import("./state-Bn5JDMFT.mjs").then((n) => n.L);
|
|
137
|
+
const defines = generateDefineOptions({
|
|
138
|
+
encrypted: readFileSync(encryptedPath, "utf-8").trim(),
|
|
139
|
+
iv: readFileSync(ivPath, "utf-8").trim(),
|
|
140
|
+
masterKey: ""
|
|
141
|
+
});
|
|
142
|
+
for (const [key, value] of Object.entries(defines)) args.push(`--define:${key}=${value}`);
|
|
143
|
+
console.log(" Embedded the encrypted credentials the build was given");
|
|
144
|
+
}
|
|
130
145
|
}
|
|
131
146
|
try {
|
|
132
147
|
const [cmd, ...cmdArgs] = args;
|
|
@@ -151,4 +166,4 @@ async function bundleServer(options) {
|
|
|
151
166
|
//#endregion
|
|
152
167
|
export { bundleServer };
|
|
153
168
|
|
|
154
|
-
//# sourceMappingURL=bundler-
|
|
169
|
+
//# sourceMappingURL=bundler-C-w7aGoZ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundler-C-w7aGoZ.mjs","names":[],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { dirname, join } from 'node:path';\nimport type { Construct } from '@geekmidas/constructs';\n\n/**\n * Banner to inject into ESM bundle for CJS compatibility.\n * Creates a `require` function using Node's createRequire for packages\n * that internally use CommonJS require() for Node builtins.\n */\nconst ESM_CJS_COMPAT_BANNER =\n\t'import { createRequire } from \"module\"; const require = createRequire(import.meta.url);';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * The esbuild binary, resolved from this package rather than from PATH.\n *\n * It used to be `npx esbuild`, which is two assumptions: that esbuild is\n * installed somewhere npx can find, and that the *app's* directory is where to\n * look. Neither holds — the CLI did not depend on esbuild at all, so a deploy\n * run from an app that had not installed it failed at the bundle step with\n * `sh: esbuild: command not found`, after provisioning had already happened.\n *\n * The same fix `bin/gkm.mjs` makes for tsx: resolve from this package's own\n * `node_modules`, so the tool the CLI shells out to is the one it depends on.\n */\nfunction esbuildBinary(): string {\n\tconst require = createRequire(import.meta.url);\n\n\ttry {\n\t\t// The package root, then its bin — `require.resolve('esbuild')` gives the\n\t\t// JS entry point, which is a library rather than the CLI.\n\t\tconst pkg = require.resolve('esbuild/package.json');\n\t\treturn join(dirname(pkg), 'bin', 'esbuild');\n\t} catch {\n\t\t// Better a PATH lookup than a hard failure: a project that installed\n\t\t// esbuild itself still works.\n\t\treturn 'esbuild';\n\t}\n}\n\n/**\n * Collect the environment variables a build cannot proceed without.\n *\n * Uses the SnifferEnvironmentParser to detect which env vars each service\n * reads, and keeps only the ones it cannot do without. `markOptional` is what\n * separates them: the sniffer already knows which reads went through\n * `.optional()` or `.default()`, and asking for that distinction is the\n * difference between a build that stops and one that proceeds correctly.\n *\n * Without it, a key that is *absent by design* failed the build. A surface\n * publishes `AUTH_COOKIE_DOMAIN` only when there is a domain to widen a cookie\n * to — one host has nothing to share it with, so the derivation correctly\n * yields nothing, and Better Auth reads it as optional for exactly that\n * reason. Treating every sniffed read as required made the honest answer\n * indistinguishable from a missing secret.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated, sorted names of the required variables\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\t// A key read optionally by one construct and required by another stays\n\t// required: the strictest read is the one that fails at runtime, and taking\n\t// the union of the required reads is what says so.\n\tconst required = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tfor (const name of await construct.getEnvironment({ markOptional: true })) {\n\t\t\tif (!name.endsWith('?')) required.add(name);\n\t\t}\n\t}\n\n\treturn Array.from(required).sort();\n}\n\n/**\n * Bundle the server application using esbuild.\n * Creates a fully standalone bundle with all dependencies included.\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\n\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t} = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\t// Build command-line arguments for esbuild\n\tconst args = [\n\t\tesbuildBinary(),\n\t\tentryPoint,\n\t\t'--bundle',\n\t\t'--platform=node',\n\t\t'--target=node22',\n\t\t'--format=esm',\n\t\t`--outfile=${mjsOutput}`,\n\t\t'--packages=bundle', // Bundle all dependencies for standalone output\n\t\t`--banner:js=${ESM_CJS_COMPAT_BANNER}`, // CJS compatibility for packages like pino\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages (user-specified)\n\tfor (const ext of external) {\n\t\targs.push(`--external:${ext}`);\n\t}\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst {\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t\tinitStageSecrets,\n\t\t} = await import('../secrets/storage');\n\t\tconst { FileSecretsStore, secretsStoreFor } = await import(\n\t\t\t'../secrets/store'\n\t\t);\n\t\tconst { ConfigNotFound, loadWorkspaceSettings } = await import('../config');\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\t// The stage's own store — SSM for a deployed stage kept there. Outside\n\t\t// any workspace nothing names one, so it is the file.\n\t\tconst store = await loadWorkspaceSettings().then(\n\t\t\t(workspace) => secretsStoreFor(workspace, stage),\n\t\t\t(error) => {\n\t\t\t\tif (error instanceof ConfigNotFound) {\n\t\t\t\t\treturn new FileSecretsStore(process.cwd());\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t},\n\t\t);\n\t\tlet secrets = await store.read(stage);\n\n\t\tif (!secrets) {\n\t\t\t// Auto-initialize secrets for the stage\n\t\t\tconsole.log(` Initializing secrets for stage \"${stage}\"...`);\n\t\t\tsecrets = initStageSecrets(stage);\n\t\t\tawait store.write(stage, secrets);\n\t\t\tconsole.log(` ✓ Created the \"${stage}\" secrets (${store.name})`);\n\t\t}\n\n\t\t// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to the \"${stage}\" secrets using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection using esbuild's --define:KEY=VALUE format\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\t// `value` is already a JS string literal; quoting it again would\n\t\t\t// embed the quotes, and the IV would not parse as hex.\n\t\t\targs.push(`--define:${key}=${value}`);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t} else {\n\t\t// Credentials encrypted elsewhere — an image build hands them in as a\n\t\t// BuildKit secret and writes them beside the app's `.gkm/server` — are\n\t\t// embedded as they are; whoever encrypted them holds the key.\n\t\tconst gkmDir = join(outputDir, '..', '..');\n\t\tconst encryptedPath = join(gkmDir, 'credentials.enc');\n\t\tconst ivPath = join(gkmDir, 'credentials.iv');\n\t\tif (existsSync(encryptedPath) && existsSync(ivPath)) {\n\t\t\tconst { generateDefineOptions } = await import('../secrets/encryption');\n\t\t\tconst defines = generateDefineOptions({\n\t\t\t\tencrypted: readFileSync(encryptedPath, 'utf-8').trim(),\n\t\t\t\tiv: readFileSync(ivPath, 'utf-8').trim(),\n\t\t\t\tmasterKey: '',\n\t\t\t});\n\t\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\t\targs.push(`--define:${key}=${value}`);\n\t\t\t}\n\t\t\tconsole.log(' Embedded the encrypted credentials the build was given');\n\t\t}\n\t}\n\n\ttry {\n\t\t// Run esbuild with command-line arguments\n\t\tconst [cmd, ...cmdArgs] = args as [string, ...string[]];\n\t\tconst result = spawnSync(cmd, cmdArgs, {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t\tshell: process.platform === 'win32', // Only use shell on Windows for npx resolution\n\t\t});\n\n\t\tif (result.error) {\n\t\t\tthrow result.error;\n\t\t}\n\t\tif (result.status !== 0) {\n\t\t\tthrow new Error(`esbuild exited with code ${result.status}`);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
let node_path = require("node:path");
|
|
2
|
+
let node_fs = require("node:fs");
|
|
2
3
|
let node_fs_promises = require("node:fs/promises");
|
|
3
4
|
let node_module = require("node:module");
|
|
4
5
|
let node_child_process = require("node:child_process");
|
|
@@ -81,10 +82,10 @@ async function bundleServer(options) {
|
|
|
81
82
|
for (const ext of external) args.push(`--external:${ext}`);
|
|
82
83
|
let masterKey;
|
|
83
84
|
if (stage) {
|
|
84
|
-
const { toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets } = await Promise.resolve().then(() => require("./state-
|
|
85
|
-
const { FileSecretsStore, secretsStoreFor } = await Promise.resolve().then(() => require("./state-
|
|
85
|
+
const { toEmbeddableSecrets, validateEnvironmentVariables, initStageSecrets } = await Promise.resolve().then(() => require("./state-CHZqCDRC.cjs")).then((n) => n.storage_exports);
|
|
86
|
+
const { FileSecretsStore, secretsStoreFor } = await Promise.resolve().then(() => require("./state-CHZqCDRC.cjs")).then((n) => n.store_exports);
|
|
86
87
|
const { ConfigNotFound, loadWorkspaceSettings } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
87
|
-
const { encryptSecrets, generateDefineOptions } = await Promise.resolve().then(() => require("./
|
|
88
|
+
const { encryptSecrets, generateDefineOptions } = await Promise.resolve().then(() => require("./state-CHZqCDRC.cjs")).then((n) => n.encryption_exports);
|
|
88
89
|
const store = await loadWorkspaceSettings().then((workspace) => secretsStoreFor(workspace, stage), (error) => {
|
|
89
90
|
if (error instanceof ConfigNotFound) return new FileSecretsStore(process.cwd());
|
|
90
91
|
throw error;
|
|
@@ -125,8 +126,22 @@ async function bundleServer(options) {
|
|
|
125
126
|
const encrypted = encryptSecrets(toEmbeddableSecrets(secrets));
|
|
126
127
|
masterKey = encrypted.masterKey;
|
|
127
128
|
const defines = generateDefineOptions(encrypted);
|
|
128
|
-
for (const [key, value] of Object.entries(defines)) args.push(`--define:${key}=${
|
|
129
|
+
for (const [key, value] of Object.entries(defines)) args.push(`--define:${key}=${value}`);
|
|
129
130
|
console.log(` Secrets encrypted for stage "${stage}"`);
|
|
131
|
+
} else {
|
|
132
|
+
const gkmDir = (0, node_path.join)(outputDir, "..", "..");
|
|
133
|
+
const encryptedPath = (0, node_path.join)(gkmDir, "credentials.enc");
|
|
134
|
+
const ivPath = (0, node_path.join)(gkmDir, "credentials.iv");
|
|
135
|
+
if ((0, node_fs.existsSync)(encryptedPath) && (0, node_fs.existsSync)(ivPath)) {
|
|
136
|
+
const { generateDefineOptions } = await Promise.resolve().then(() => require("./state-CHZqCDRC.cjs")).then((n) => n.encryption_exports);
|
|
137
|
+
const defines = generateDefineOptions({
|
|
138
|
+
encrypted: (0, node_fs.readFileSync)(encryptedPath, "utf-8").trim(),
|
|
139
|
+
iv: (0, node_fs.readFileSync)(ivPath, "utf-8").trim(),
|
|
140
|
+
masterKey: ""
|
|
141
|
+
});
|
|
142
|
+
for (const [key, value] of Object.entries(defines)) args.push(`--define:${key}=${value}`);
|
|
143
|
+
console.log(" Embedded the encrypted credentials the build was given");
|
|
144
|
+
}
|
|
130
145
|
}
|
|
131
146
|
try {
|
|
132
147
|
const [cmd, ...cmdArgs] = args;
|
|
@@ -151,4 +166,4 @@ async function bundleServer(options) {
|
|
|
151
166
|
//#endregion
|
|
152
167
|
exports.bundleServer = bundleServer;
|
|
153
168
|
|
|
154
|
-
//# sourceMappingURL=bundler-
|
|
169
|
+
//# sourceMappingURL=bundler-C1pnNrR0.cjs.map
|