@geekmidas/cli 10.0.0-alpha.96 → 10.0.0-alpha.97
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{GoDaddyProvider-BsUKjGcg.mjs → GoDaddyProvider-AqNu60WQ.mjs} +2 -2
- package/dist/{GoDaddyProvider-BsUKjGcg.mjs.map → GoDaddyProvider-AqNu60WQ.mjs.map} +1 -1
- package/dist/{GoDaddyProvider-DyHXHDHe.cjs → GoDaddyProvider-B4R_QFl8.cjs} +2 -2
- package/dist/{GoDaddyProvider-DyHXHDHe.cjs.map → GoDaddyProvider-B4R_QFl8.cjs.map} +1 -1
- package/dist/{LegacyStateStore-B6SOqRFi.mjs → LegacyStateStore-8F05-BOo.mjs} +3 -3
- package/dist/{LegacyStateStore-B6SOqRFi.mjs.map → LegacyStateStore-8F05-BOo.mjs.map} +1 -1
- package/dist/{LegacyStateStore-BnMcONE0.cjs → LegacyStateStore-B-aRqRdD.cjs} +3 -3
- package/dist/{LegacyStateStore-BnMcONE0.cjs.map → LegacyStateStore-B-aRqRdD.cjs.map} +1 -1
- package/dist/{LocalStateStore-pd4bbdJJ.cjs → LocalStateStore-B_bRI8M7.cjs} +2 -2
- package/dist/{LocalStateStore-pd4bbdJJ.cjs.map → LocalStateStore-B_bRI8M7.cjs.map} +1 -1
- package/dist/{LocalStateStore-6CrsZzqI.mjs → LocalStateStore-B_mfgcO8.mjs} +2 -2
- package/dist/{LocalStateStore-6CrsZzqI.mjs.map → LocalStateStore-B_mfgcO8.mjs.map} +1 -1
- package/dist/{S3StateStore-DP_7Q4yb.mjs → S3StateStore-CMN0Uzdp.mjs} +69 -8
- package/dist/S3StateStore-CMN0Uzdp.mjs.map +1 -0
- package/dist/{S3StateStore-CoGk6ZKU.cjs → S3StateStore-zK9zcd9f.cjs} +69 -8
- package/dist/S3StateStore-zK9zcd9f.cjs.map +1 -0
- package/dist/{SSMStateStore-D9s4_Do3.cjs → SSMStateStore-C489DnR6.cjs} +58 -7
- package/dist/SSMStateStore-C489DnR6.cjs.map +1 -0
- package/dist/{SSMStateStore-Bo6_KDYY.mjs → SSMStateStore-Cv7VcMak.mjs} +58 -7
- package/dist/SSMStateStore-Cv7VcMak.mjs.map +1 -0
- package/dist/{add-BQS2QZHp.mjs → add-BA5d4pA0.mjs} +4 -4
- package/dist/{add-BQS2QZHp.mjs.map → add-BA5d4pA0.mjs.map} +1 -1
- package/dist/{add-CjURJ-vI.cjs → add-HXzTCy_I.cjs} +4 -4
- package/dist/{add-CjURJ-vI.cjs.map → add-HXzTCy_I.cjs.map} +1 -1
- package/dist/{bundler-BMM3UsGm.mjs → bundler-CBomFsnd.mjs} +5 -5
- package/dist/{bundler-BMM3UsGm.mjs.map → bundler-CBomFsnd.mjs.map} +1 -1
- package/dist/{bundler-DmLJQ7AY.cjs → bundler-SX3Uy8qT.cjs} +5 -5
- package/dist/{bundler-DmLJQ7AY.cjs.map → bundler-SX3Uy8qT.cjs.map} +1 -1
- package/dist/{cli-C2c-jOVK.cjs → cli-1l0L9LCA.cjs} +2 -2
- package/dist/{cli-C2c-jOVK.cjs.map → cli-1l0L9LCA.cjs.map} +1 -1
- package/dist/{cli-Ce6NgaQF.mjs → cli-D7z1lQtO.mjs} +2 -2
- package/dist/{cli-Ce6NgaQF.mjs.map → cli-D7z1lQtO.mjs.map} +1 -1
- package/dist/{compose-ETFKqkTu.cjs → compose-C39XupJu.cjs} +7 -7
- package/dist/{compose-ETFKqkTu.cjs.map → compose-C39XupJu.cjs.map} +1 -1
- package/dist/{compose-BJDPFBAN.mjs → compose-DgWrNd2k.mjs} +7 -7
- package/dist/{compose-BJDPFBAN.mjs.map → compose-DgWrNd2k.mjs.map} +1 -1
- package/dist/{config-C8NPCn1l.d.mts → config-D-k0gaAr.d.mts} +4 -4
- package/dist/{config-4yirvxS8.d.cts.map → config-D-k0gaAr.d.mts.map} +1 -1
- package/dist/{config-4yirvxS8.d.cts → config-xIWIaDhW.d.cts} +4 -4
- package/dist/{config-C8NPCn1l.d.mts.map → config-xIWIaDhW.d.cts.map} +1 -1
- package/dist/config.cjs +1 -1
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +1 -1
- package/dist/deploy/public.cjs +7 -7
- package/dist/deploy/public.d.cts +5 -5
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +5 -5
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +7 -7
- package/dist/{deploy-BdN0ym2i.cjs → deploy-Cu_HyWoJ.cjs} +19 -8
- package/dist/deploy-Cu_HyWoJ.cjs.map +1 -0
- package/dist/{deploy-DktQl7ti.mjs → deploy-LY-20Yeb.mjs} +19 -8
- package/dist/{deploy-DktQl7ti.mjs.map → deploy-LY-20Yeb.mjs.map} +1 -1
- package/dist/{discover-D-MdXZgm.d.mts → discover-CbJCQq4J.d.mts} +2 -2
- package/dist/{discover-BtFofyQB.d.cts.map → discover-CbJCQq4J.d.mts.map} +1 -1
- package/dist/{discover-BtFofyQB.d.cts → discover-RCW5mBXb.d.cts} +2 -2
- package/dist/{discover-D-MdXZgm.d.mts.map → discover-RCW5mBXb.d.cts.map} +1 -1
- package/dist/{dns-C-X6PLkV.cjs → dns-BFnoawgh.cjs} +3 -3
- package/dist/{dns-C-X6PLkV.cjs.map → dns-BFnoawgh.cjs.map} +1 -1
- package/dist/{dns-C2EeR6n2.mjs → dns-BbTOzmVp.mjs} +3 -3
- package/dist/{dns-C2EeR6n2.mjs.map → dns-BbTOzmVp.mjs.map} +1 -1
- package/dist/{dns-BjHECDO7.mjs → dns-Bmbhmz41.mjs} +5 -5
- package/dist/{dns-BjHECDO7.mjs.map → dns-Bmbhmz41.mjs.map} +1 -1
- package/dist/{dns-ClI7-d5g.cjs → dns-Dro_vbk9.cjs} +5 -5
- package/dist/{dns-ClI7-d5g.cjs.map → dns-Dro_vbk9.cjs.map} +1 -1
- package/dist/{dokploy-CD_Bc4EV.cjs → dokploy-CKgSww82.cjs} +2 -2
- package/dist/{dokploy-CD_Bc4EV.cjs.map → dokploy-CKgSww82.cjs.map} +1 -1
- package/dist/{dokploy-CxtkS5hl.mjs → dokploy-LR94sBVI.mjs} +2 -2
- package/dist/{dokploy-CxtkS5hl.mjs.map → dokploy-LR94sBVI.mjs.map} +1 -1
- package/dist/{encryption-BgQd4f2S.mjs → encryption-C0plOf4a.mjs} +3 -3
- package/dist/{encryption-BgQd4f2S.mjs.map → encryption-C0plOf4a.mjs.map} +1 -1
- package/dist/{encryption-CqVfz-m7.cjs → encryption-CUyw-8sc.cjs} +3 -3
- package/dist/{encryption-CqVfz-m7.cjs.map → encryption-CUyw-8sc.cjs.map} +1 -1
- package/dist/{engine-OVVKtPmW.cjs → engine-D8TeraDK.cjs} +4 -4
- package/dist/{engine-OVVKtPmW.cjs.map → engine-D8TeraDK.cjs.map} +1 -1
- package/dist/{engine-YlXzlTUl.mjs → engine-DqbqQhck.mjs} +4 -4
- package/dist/{engine-YlXzlTUl.mjs.map → engine-DqbqQhck.mjs.map} +1 -1
- package/dist/{github-CsMloeSL.cjs → github-DC4SIqV1.cjs} +35 -20
- package/dist/github-DC4SIqV1.cjs.map +1 -0
- package/dist/{github-DuuvqI3Z.mjs → github-zgaBOQvh.mjs} +35 -20
- package/dist/github-zgaBOQvh.mjs.map +1 -0
- package/dist/{index-5eL8rM6H.d.mts → index-ClqOsbBl.d.mts} +2 -2
- package/dist/{index-5eL8rM6H.d.mts.map → index-ClqOsbBl.d.mts.map} +1 -1
- package/dist/{index-Dz-gyjm_.d.cts → index-DgOBAEl5.d.cts} +2 -2
- package/dist/{index-Dz-gyjm_.d.cts.map → index-DgOBAEl5.d.cts.map} +1 -1
- package/dist/index.cjs +56 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +56 -52
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-CB8B6W9y.mjs → migrate-BJ8aLOWk.mjs} +3 -3
- package/dist/{migrate-CB8B6W9y.mjs.map → migrate-BJ8aLOWk.mjs.map} +1 -1
- package/dist/{migrate-CsPFHgTs.mjs → migrate-C230QeXU.mjs} +3 -3
- package/dist/{migrate-CsPFHgTs.mjs.map → migrate-C230QeXU.mjs.map} +1 -1
- package/dist/{migrate-BAmJeVq1.cjs → migrate-CWq8-s21.cjs} +3 -3
- package/dist/{migrate-BAmJeVq1.cjs.map → migrate-CWq8-s21.cjs.map} +1 -1
- package/dist/{migrate-BdkoNdIu.cjs → migrate-CsMrI7Dk.cjs} +3 -3
- package/dist/{migrate-BdkoNdIu.cjs.map → migrate-CsMrI7Dk.cjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/projectBucket-BFp-SFee.mjs +177 -0
- package/dist/projectBucket-BFp-SFee.mjs.map +1 -0
- package/dist/projectBucket-Cho9DpSh.cjs +206 -0
- package/dist/projectBucket-Cho9DpSh.cjs.map +1 -0
- package/dist/reconcile/public.d.cts +2 -2
- package/dist/reconcile/public.d.mts +2 -2
- package/dist/{resolve-CUne0qOm.mjs → resolve-6EYNqYdr.mjs} +3 -3
- package/dist/{resolve-CUne0qOm.mjs.map → resolve-6EYNqYdr.mjs.map} +1 -1
- package/dist/{resolve-BDatNOoG.cjs → resolve-C8VkR4oW.cjs} +3 -3
- package/dist/{resolve-BDatNOoG.cjs.map → resolve-C8VkR4oW.cjs.map} +1 -1
- package/dist/{rollback-CCdEEEcF.mjs → rollback-BsThTeSN.mjs} +7 -6
- package/dist/{rollback-CCdEEEcF.mjs.map → rollback-BsThTeSN.mjs.map} +1 -1
- package/dist/{rollback-CuG3DkTY.cjs → rollback-CvGfm4DX.cjs} +7 -6
- package/dist/{rollback-CuG3DkTY.cjs.map → rollback-CvGfm4DX.cjs.map} +1 -1
- package/dist/{runtime-Vr6mleo2.d.mts → runtime-CaW4QqF2.d.cts} +2 -2
- package/dist/{runtime-DUQdM-mW.d.cts.map → runtime-CaW4QqF2.d.cts.map} +1 -1
- package/dist/{runtime-DUQdM-mW.d.cts → runtime-K5uxp6HE.d.mts} +2 -2
- package/dist/{runtime-Vr6mleo2.d.mts.map → runtime-K5uxp6HE.d.mts.map} +1 -1
- package/dist/{sandbox-DGOsMPMf.d.mts → sandbox-B_BkfmTP.d.mts} +2 -2
- package/dist/{sandbox-BbJmh2rI.d.cts.map → sandbox-B_BkfmTP.d.mts.map} +1 -1
- package/dist/{sandbox-BbJmh2rI.d.cts → sandbox-D8cfZUAA.d.cts} +2 -2
- package/dist/{sandbox-DGOsMPMf.d.mts.map → sandbox-D8cfZUAA.d.cts.map} +1 -1
- package/dist/{secrets-C5qROlbp.cjs → secrets-BTY6JhTp.cjs} +3 -3
- package/dist/{secrets-C5qROlbp.cjs.map → secrets-BTY6JhTp.cjs.map} +1 -1
- package/dist/{secrets-ZikTIxEm.mjs → secrets-HzKNTWg9.mjs} +3 -3
- package/dist/{secrets-ZikTIxEm.mjs.map → secrets-HzKNTWg9.mjs.map} +1 -1
- package/dist/{seeds-BjXW-Krf.cjs → seeds-BQlj9Fsg.cjs} +4 -4
- package/dist/{seeds-BjXW-Krf.cjs.map → seeds-BQlj9Fsg.cjs.map} +1 -1
- package/dist/{seeds-CbJSne1A.mjs → seeds-BsJRUL1K.mjs} +4 -4
- package/dist/{seeds-CbJSne1A.mjs.map → seeds-BsJRUL1K.mjs.map} +1 -1
- package/dist/{sniffer-NPD1dyab.cjs → sniffer-CeUKUoHn.cjs} +100 -10
- package/dist/sniffer-CeUKUoHn.cjs.map +1 -0
- package/dist/{sniffer-DQGBYY8i.mjs → sniffer-DVsvP8s-.mjs} +77 -11
- package/dist/sniffer-DVsvP8s-.mjs.map +1 -0
- package/dist/{stack-BFq3K-_z.mjs → stack-BQOyqaVG.mjs} +3 -3
- package/dist/{stack-BFq3K-_z.mjs.map → stack-BQOyqaVG.mjs.map} +1 -1
- package/dist/{stack-DOVvEd1T.cjs → stack-Clzi40EE.cjs} +3 -3
- package/dist/{stack-DOVvEd1T.cjs.map → stack-Clzi40EE.cjs.map} +1 -1
- package/dist/{stages-Cw6urZyJ.cjs → stages-BpDXFPGg.cjs} +4 -4
- package/dist/{stages-Cw6urZyJ.cjs.map → stages-BpDXFPGg.cjs.map} +1 -1
- package/dist/{stages-BEn2ArJZ.mjs → stages-DEmn7qI8.mjs} +4 -4
- package/dist/{stages-BEn2ArJZ.mjs.map → stages-DEmn7qI8.mjs.map} +1 -1
- package/dist/target/public.cjs +1 -1
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +1 -1
- package/dist/{types-dipJRmKV.d.cts → types-4vPS_YFX.d.cts} +21 -4
- package/dist/types-4vPS_YFX.d.cts.map +1 -0
- package/dist/{types-bkqVscdN.d.mts → types-CFsEyFb1.d.mts} +21 -4
- package/dist/types-CFsEyFb1.d.mts.map +1 -0
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-CqFUjObk.mjs → workspace-Bv4xROeu.mjs} +8 -4
- package/dist/workspace-Bv4xROeu.mjs.map +1 -0
- package/dist/{workspace-DDkm4y6D.cjs → workspace-C1M-S4al.cjs} +8 -4
- package/dist/workspace-C1M-S4al.cjs.map +1 -0
- package/package.json +16 -15
- package/dist/S3StateStore-CoGk6ZKU.cjs.map +0 -1
- package/dist/S3StateStore-DP_7Q4yb.mjs.map +0 -1
- package/dist/SSMStateStore-Bo6_KDYY.mjs.map +0 -1
- package/dist/SSMStateStore-D9s4_Do3.cjs.map +0 -1
- package/dist/deploy-BdN0ym2i.cjs.map +0 -1
- package/dist/github-CsMloeSL.cjs.map +0 -1
- package/dist/github-DuuvqI3Z.mjs.map +0 -1
- package/dist/sniffer-DQGBYY8i.mjs.map +0 -1
- package/dist/sniffer-NPD1dyab.cjs.map +0 -1
- package/dist/types-bkqVscdN.d.mts.map +0 -1
- package/dist/types-dipJRmKV.d.cts.map +0 -1
- package/dist/workspace-CqFUjObk.mjs.map +0 -1
- package/dist/workspace-DDkm4y6D.cjs.map +0 -1
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
import { n as __require } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { Ln as deployIdentity } from "./seeds-BsJRUL1K.mjs";
|
|
3
|
+
import { f as StateLocked, g as StateVersionConflict, l as DocumentStateStore } from "./sniffer-DVsvP8s-.mjs";
|
|
4
|
+
import { a as projectBucketName, i as findProjectBucket, n as callerAccountId, r as ensureProjectBucket } from "./projectBucket-BFp-SFee.mjs";
|
|
3
5
|
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
|
6
|
+
import { STSClient } from "@aws-sdk/client-sts";
|
|
4
7
|
//#region src/deploy/S3StateStore.ts
|
|
5
8
|
/**
|
|
6
9
|
* Amazon S3 state store.
|
|
7
10
|
*
|
|
11
|
+
* In the bucket the config names, which must exist — or, with none named, in
|
|
12
|
+
* the project bucket (`providers/projectBucket.ts`): found by its name, which
|
|
13
|
+
* is worked out from the project and the account, and created by the first
|
|
14
|
+
* write (a deploy's lock, `state:push`). A read of a project bucket that does
|
|
15
|
+
* not exist yet is a stage with no state; it creates nothing.
|
|
16
|
+
*
|
|
8
17
|
* - `<prefix>/<workspace>/<stage>/state.json`: the state.
|
|
9
18
|
* - `<prefix>/<workspace>/<stage>/lock.json`: the lock.
|
|
10
19
|
* - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on
|
|
@@ -26,27 +35,73 @@ function notFound(error) {
|
|
|
26
35
|
}
|
|
27
36
|
var S3StateStore = class S3StateStore extends DocumentStateStore {
|
|
28
37
|
workspaceName;
|
|
29
|
-
|
|
38
|
+
source;
|
|
30
39
|
client;
|
|
31
40
|
prefix;
|
|
32
|
-
|
|
41
|
+
/** The bucket's name, once known. */
|
|
42
|
+
name;
|
|
43
|
+
/** Whether the bucket is known to exist. */
|
|
44
|
+
exists = false;
|
|
45
|
+
constructor(workspaceName, source, client, prefix = "gkm") {
|
|
33
46
|
super();
|
|
34
47
|
this.workspaceName = workspaceName;
|
|
35
|
-
this.
|
|
48
|
+
this.source = source;
|
|
36
49
|
this.client = client;
|
|
37
50
|
this.prefix = prefix.replace(/\/+$/, "");
|
|
51
|
+
if (typeof source === "string") {
|
|
52
|
+
this.name = source;
|
|
53
|
+
this.exists = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** The bucket's name; a project bucket's is known after the first call. */
|
|
57
|
+
get bucket() {
|
|
58
|
+
if (this.name) return this.name;
|
|
59
|
+
const { identity } = this.source;
|
|
60
|
+
return projectBucketName(identity.scope, "<account id>");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The bucket, or `null` for a project bucket that does not exist yet and
|
|
64
|
+
* is not to be created (`create: false`, a read).
|
|
65
|
+
*/
|
|
66
|
+
async use(create) {
|
|
67
|
+
if (this.exists) return this.name;
|
|
68
|
+
const source = this.source;
|
|
69
|
+
this.name ??= projectBucketName(source.identity.scope, await callerAccountId(source.sts));
|
|
70
|
+
if (create) await ensureProjectBucket(this.client, {
|
|
71
|
+
bucket: this.name,
|
|
72
|
+
region: source.region,
|
|
73
|
+
identity: source.identity
|
|
74
|
+
});
|
|
75
|
+
else if (await findProjectBucket(this.client, this.name) === "none") return null;
|
|
76
|
+
this.exists = true;
|
|
77
|
+
return this.name;
|
|
38
78
|
}
|
|
39
79
|
static create(options) {
|
|
80
|
+
const endpoint = options.endpoint ?? process.env.AWS_ENDPOINT_URL_S3 ?? process.env.AWS_ENDPOINT_URL;
|
|
40
81
|
const config = {
|
|
41
82
|
region: options.region,
|
|
42
|
-
endpoint
|
|
43
|
-
forcePathStyle:
|
|
83
|
+
endpoint,
|
|
84
|
+
forcePathStyle: endpoint ? true : void 0
|
|
44
85
|
};
|
|
45
86
|
if (options.profile) {
|
|
46
87
|
const { fromIni } = __require("@aws-sdk/credential-providers");
|
|
47
88
|
config.credentials = fromIni({ profile: options.profile });
|
|
48
89
|
} else if (options.credentials) config.credentials = options.credentials;
|
|
49
|
-
|
|
90
|
+
let source;
|
|
91
|
+
if (options.bucket) source = options.bucket;
|
|
92
|
+
else source = {
|
|
93
|
+
identity: deployIdentity({
|
|
94
|
+
name: options.workspaceName,
|
|
95
|
+
...options.namespace ? { deploy: { namespace: options.namespace } } : {}
|
|
96
|
+
}, ""),
|
|
97
|
+
region: options.region ?? process.env.AWS_REGION ?? "us-east-1",
|
|
98
|
+
sts: new STSClient({
|
|
99
|
+
region: config.region,
|
|
100
|
+
...options.endpoint ? { endpoint: options.endpoint } : {},
|
|
101
|
+
...config.credentials ? { credentials: config.credentials } : {}
|
|
102
|
+
})
|
|
103
|
+
};
|
|
104
|
+
return new S3StateStore(options.workspaceName, source, new S3Client(config), options.prefix);
|
|
50
105
|
}
|
|
51
106
|
key(stage, leaf) {
|
|
52
107
|
const path = `${this.workspaceName}/${stage}/${leaf}`;
|
|
@@ -56,6 +111,7 @@ var S3StateStore = class S3StateStore extends DocumentStateStore {
|
|
|
56
111
|
return `s3://${this.bucket}/${this.key(stage, "state.json")}`;
|
|
57
112
|
}
|
|
58
113
|
async readRaw(stage) {
|
|
114
|
+
if (!await this.use(false)) return null;
|
|
59
115
|
const object = await this.get(this.key(stage, "state.json"));
|
|
60
116
|
if (!object) return null;
|
|
61
117
|
return {
|
|
@@ -64,6 +120,7 @@ var S3StateStore = class S3StateStore extends DocumentStateStore {
|
|
|
64
120
|
};
|
|
65
121
|
}
|
|
66
122
|
async writeRaw(stage, body, expectedVersion) {
|
|
123
|
+
await this.use(true);
|
|
67
124
|
const Key = this.key(stage, "state.json");
|
|
68
125
|
try {
|
|
69
126
|
const { ETag } = await this.client.send(new PutObjectCommand({
|
|
@@ -84,6 +141,7 @@ var S3StateStore = class S3StateStore extends DocumentStateStore {
|
|
|
84
141
|
}
|
|
85
142
|
}
|
|
86
143
|
async writeV1Backup(stage, body) {
|
|
144
|
+
await this.use(true);
|
|
87
145
|
try {
|
|
88
146
|
await this.client.send(new PutObjectCommand({
|
|
89
147
|
Bucket: this.bucket,
|
|
@@ -97,6 +155,7 @@ var S3StateStore = class S3StateStore extends DocumentStateStore {
|
|
|
97
155
|
}
|
|
98
156
|
}
|
|
99
157
|
async createLock(stage, holder) {
|
|
158
|
+
await this.use(true);
|
|
100
159
|
const Key = this.key(stage, "lock.json");
|
|
101
160
|
try {
|
|
102
161
|
await this.client.send(new PutObjectCommand({
|
|
@@ -112,6 +171,7 @@ var S3StateStore = class S3StateStore extends DocumentStateStore {
|
|
|
112
171
|
}
|
|
113
172
|
}
|
|
114
173
|
async readLock(stage) {
|
|
174
|
+
if (!await this.use(false)) return null;
|
|
115
175
|
const object = await this.get(this.key(stage, "lock.json"));
|
|
116
176
|
if (!object) return null;
|
|
117
177
|
try {
|
|
@@ -121,6 +181,7 @@ var S3StateStore = class S3StateStore extends DocumentStateStore {
|
|
|
121
181
|
}
|
|
122
182
|
}
|
|
123
183
|
async removeLock(stage, holder) {
|
|
184
|
+
if (!await this.use(false)) return;
|
|
124
185
|
const Key = this.key(stage, "lock.json");
|
|
125
186
|
const object = await this.get(Key);
|
|
126
187
|
if (!object) return;
|
|
@@ -158,4 +219,4 @@ var S3StateStore = class S3StateStore extends DocumentStateStore {
|
|
|
158
219
|
//#endregion
|
|
159
220
|
export { S3StateStore };
|
|
160
221
|
|
|
161
|
-
//# sourceMappingURL=S3StateStore-
|
|
222
|
+
//# sourceMappingURL=S3StateStore-CMN0Uzdp.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"S3StateStore-CMN0Uzdp.mjs","names":[],"sources":["../src/deploy/S3StateStore.ts"],"sourcesContent":["/**\n * Amazon S3 state store.\n *\n * In the bucket the config names, which must exist — or, with none named, in\n * the project bucket (`providers/projectBucket.ts`): found by its name, which\n * is worked out from the project and the account, and created by the first\n * write (a deploy's lock, `state:push`). A read of a project bucket that does\n * not exist yet is a stage with no state; it creates nothing.\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 { STSClient } from '@aws-sdk/client-sts';\nimport {\n\tcallerAccountId,\n\tensureProjectBucket,\n\tfindProjectBucket,\n\tprojectBucketName,\n} from '../providers/projectBucket';\nimport { type DeployIdentity, deployIdentity } from './identity';\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\t/** Omitted, the project bucket — created by the first write. */\n\tbucket?: string;\n\t/** `deploy.namespace`, for the project bucket's name. */\n\tnamespace?: 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\n/** The project bucket, for a store that names no bucket of its own. */\nexport interface ProjectBucketSource {\n\tidentity: Pick<DeployIdentity, 'scope' | 'key'>;\n\tregion: string;\n\t/** What the account id — and so the bucket's name — is read with. */\n\tsts: STSClient;\n}\n\nexport class S3StateStore extends DocumentStateStore {\n\tprivate readonly prefix: string;\n\t/** The bucket's name, once known. */\n\tprivate name?: string;\n\t/** Whether the bucket is known to exist. */\n\tprivate exists = false;\n\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\tprivate readonly source: string | ProjectBucketSource,\n\t\tprivate readonly client: S3Client,\n\t\tprefix = 'gkm',\n\t) {\n\t\tsuper();\n\t\tthis.prefix = prefix.replace(/\\/+$/, '');\n\t\tif (typeof source === 'string') {\n\t\t\tthis.name = source;\n\t\t\tthis.exists = true;\n\t\t}\n\t}\n\n\t/** The bucket's name; a project bucket's is known after the first call. */\n\tget bucket(): string {\n\t\tif (this.name) return this.name;\n\t\tconst { identity } = this.source as ProjectBucketSource;\n\t\treturn projectBucketName(identity.scope, '<account id>');\n\t}\n\n\t/**\n\t * The bucket, or `null` for a project bucket that does not exist yet and\n\t * is not to be created (`create: false`, a read).\n\t */\n\tprivate async use(create: boolean): Promise<string | null> {\n\t\tif (this.exists) return this.name!;\n\t\tconst source = this.source as ProjectBucketSource;\n\t\tthis.name ??= projectBucketName(\n\t\t\tsource.identity.scope,\n\t\t\tawait callerAccountId(source.sts),\n\t\t);\n\t\tif (create) {\n\t\t\tawait ensureProjectBucket(this.client, {\n\t\t\t\tbucket: this.name,\n\t\t\t\tregion: source.region,\n\t\t\t\tidentity: source.identity,\n\t\t\t});\n\t\t} else if ((await findProjectBucket(this.client, this.name)) === 'none') {\n\t\t\treturn null;\n\t\t}\n\t\tthis.exists = true;\n\t\treturn this.name;\n\t}\n\n\tstatic create(options: S3StateStoreOptions): S3StateStore {\n\t\tconst endpoint =\n\t\t\toptions.endpoint ??\n\t\t\tprocess.env.AWS_ENDPOINT_URL_S3 ??\n\t\t\tprocess.env.AWS_ENDPOINT_URL;\n\t\tconst config: S3ClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint,\n\t\t\t// The emulator serves buckets by path, not by subdomain.\n\t\t\tforcePathStyle: 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\tlet source: string | ProjectBucketSource;\n\t\tif (options.bucket) {\n\t\t\tsource = options.bucket;\n\t\t} else {\n\t\t\tsource = {\n\t\t\t\tidentity: deployIdentity(\n\t\t\t\t\t{\n\t\t\t\t\t\tname: options.workspaceName,\n\t\t\t\t\t\t...(options.namespace\n\t\t\t\t\t\t\t? { deploy: { namespace: options.namespace } }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t},\n\t\t\t\t\t'',\n\t\t\t\t),\n\t\t\t\tregion: options.region ?? process.env.AWS_REGION ?? 'us-east-1',\n\t\t\t\tsts: new STSClient({\n\t\t\t\t\tregion: config.region,\n\t\t\t\t\t...(options.endpoint ? { endpoint: options.endpoint } : {}),\n\t\t\t\t\t...(config.credentials ? { credentials: config.credentials } : {}),\n\t\t\t\t}),\n\t\t\t};\n\t\t}\n\t\treturn new S3StateStore(\n\t\t\toptions.workspaceName,\n\t\t\tsource,\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\tif (!(await this.use(false))) return 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\tawait this.use(true);\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\tawait this.use(true);\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\tawait this.use(true);\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\tif (!(await this.use(false))) return 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\tif (!(await this.use(false))) return;\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,9 +1,18 @@
|
|
|
1
|
-
const
|
|
1
|
+
const require_seeds = require("./seeds-BQlj9Fsg.cjs");
|
|
2
|
+
const require_sniffer = require("./sniffer-CeUKUoHn.cjs");
|
|
3
|
+
const require_projectBucket = require("./projectBucket-Cho9DpSh.cjs");
|
|
2
4
|
let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
|
|
5
|
+
let _aws_sdk_client_sts = require("@aws-sdk/client-sts");
|
|
3
6
|
//#region src/deploy/S3StateStore.ts
|
|
4
7
|
/**
|
|
5
8
|
* Amazon S3 state store.
|
|
6
9
|
*
|
|
10
|
+
* In the bucket the config names, which must exist — or, with none named, in
|
|
11
|
+
* the project bucket (`providers/projectBucket.ts`): found by its name, which
|
|
12
|
+
* is worked out from the project and the account, and created by the first
|
|
13
|
+
* write (a deploy's lock, `state:push`). A read of a project bucket that does
|
|
14
|
+
* not exist yet is a stage with no state; it creates nothing.
|
|
15
|
+
*
|
|
7
16
|
* - `<prefix>/<workspace>/<stage>/state.json`: the state.
|
|
8
17
|
* - `<prefix>/<workspace>/<stage>/lock.json`: the lock.
|
|
9
18
|
* - `<prefix>/<workspace>/<stage>/state.v1.json`: the v1 state, kept on
|
|
@@ -25,27 +34,73 @@ function notFound(error) {
|
|
|
25
34
|
}
|
|
26
35
|
var S3StateStore = class S3StateStore extends require_sniffer.DocumentStateStore {
|
|
27
36
|
workspaceName;
|
|
28
|
-
|
|
37
|
+
source;
|
|
29
38
|
client;
|
|
30
39
|
prefix;
|
|
31
|
-
|
|
40
|
+
/** The bucket's name, once known. */
|
|
41
|
+
name;
|
|
42
|
+
/** Whether the bucket is known to exist. */
|
|
43
|
+
exists = false;
|
|
44
|
+
constructor(workspaceName, source, client, prefix = "gkm") {
|
|
32
45
|
super();
|
|
33
46
|
this.workspaceName = workspaceName;
|
|
34
|
-
this.
|
|
47
|
+
this.source = source;
|
|
35
48
|
this.client = client;
|
|
36
49
|
this.prefix = prefix.replace(/\/+$/, "");
|
|
50
|
+
if (typeof source === "string") {
|
|
51
|
+
this.name = source;
|
|
52
|
+
this.exists = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** The bucket's name; a project bucket's is known after the first call. */
|
|
56
|
+
get bucket() {
|
|
57
|
+
if (this.name) return this.name;
|
|
58
|
+
const { identity } = this.source;
|
|
59
|
+
return require_projectBucket.projectBucketName(identity.scope, "<account id>");
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The bucket, or `null` for a project bucket that does not exist yet and
|
|
63
|
+
* is not to be created (`create: false`, a read).
|
|
64
|
+
*/
|
|
65
|
+
async use(create) {
|
|
66
|
+
if (this.exists) return this.name;
|
|
67
|
+
const source = this.source;
|
|
68
|
+
this.name ??= require_projectBucket.projectBucketName(source.identity.scope, await require_projectBucket.callerAccountId(source.sts));
|
|
69
|
+
if (create) await require_projectBucket.ensureProjectBucket(this.client, {
|
|
70
|
+
bucket: this.name,
|
|
71
|
+
region: source.region,
|
|
72
|
+
identity: source.identity
|
|
73
|
+
});
|
|
74
|
+
else if (await require_projectBucket.findProjectBucket(this.client, this.name) === "none") return null;
|
|
75
|
+
this.exists = true;
|
|
76
|
+
return this.name;
|
|
37
77
|
}
|
|
38
78
|
static create(options) {
|
|
79
|
+
const endpoint = options.endpoint ?? process.env.AWS_ENDPOINT_URL_S3 ?? process.env.AWS_ENDPOINT_URL;
|
|
39
80
|
const config = {
|
|
40
81
|
region: options.region,
|
|
41
|
-
endpoint
|
|
42
|
-
forcePathStyle:
|
|
82
|
+
endpoint,
|
|
83
|
+
forcePathStyle: endpoint ? true : void 0
|
|
43
84
|
};
|
|
44
85
|
if (options.profile) {
|
|
45
86
|
const { fromIni } = require("@aws-sdk/credential-providers");
|
|
46
87
|
config.credentials = fromIni({ profile: options.profile });
|
|
47
88
|
} else if (options.credentials) config.credentials = options.credentials;
|
|
48
|
-
|
|
89
|
+
let source;
|
|
90
|
+
if (options.bucket) source = options.bucket;
|
|
91
|
+
else source = {
|
|
92
|
+
identity: require_seeds.deployIdentity({
|
|
93
|
+
name: options.workspaceName,
|
|
94
|
+
...options.namespace ? { deploy: { namespace: options.namespace } } : {}
|
|
95
|
+
}, ""),
|
|
96
|
+
region: options.region ?? process.env.AWS_REGION ?? "us-east-1",
|
|
97
|
+
sts: new _aws_sdk_client_sts.STSClient({
|
|
98
|
+
region: config.region,
|
|
99
|
+
...options.endpoint ? { endpoint: options.endpoint } : {},
|
|
100
|
+
...config.credentials ? { credentials: config.credentials } : {}
|
|
101
|
+
})
|
|
102
|
+
};
|
|
103
|
+
return new S3StateStore(options.workspaceName, source, new _aws_sdk_client_s3.S3Client(config), options.prefix);
|
|
49
104
|
}
|
|
50
105
|
key(stage, leaf) {
|
|
51
106
|
const path = `${this.workspaceName}/${stage}/${leaf}`;
|
|
@@ -55,6 +110,7 @@ var S3StateStore = class S3StateStore extends require_sniffer.DocumentStateStore
|
|
|
55
110
|
return `s3://${this.bucket}/${this.key(stage, "state.json")}`;
|
|
56
111
|
}
|
|
57
112
|
async readRaw(stage) {
|
|
113
|
+
if (!await this.use(false)) return null;
|
|
58
114
|
const object = await this.get(this.key(stage, "state.json"));
|
|
59
115
|
if (!object) return null;
|
|
60
116
|
return {
|
|
@@ -63,6 +119,7 @@ var S3StateStore = class S3StateStore extends require_sniffer.DocumentStateStore
|
|
|
63
119
|
};
|
|
64
120
|
}
|
|
65
121
|
async writeRaw(stage, body, expectedVersion) {
|
|
122
|
+
await this.use(true);
|
|
66
123
|
const Key = this.key(stage, "state.json");
|
|
67
124
|
try {
|
|
68
125
|
const { ETag } = await this.client.send(new _aws_sdk_client_s3.PutObjectCommand({
|
|
@@ -83,6 +140,7 @@ var S3StateStore = class S3StateStore extends require_sniffer.DocumentStateStore
|
|
|
83
140
|
}
|
|
84
141
|
}
|
|
85
142
|
async writeV1Backup(stage, body) {
|
|
143
|
+
await this.use(true);
|
|
86
144
|
try {
|
|
87
145
|
await this.client.send(new _aws_sdk_client_s3.PutObjectCommand({
|
|
88
146
|
Bucket: this.bucket,
|
|
@@ -96,6 +154,7 @@ var S3StateStore = class S3StateStore extends require_sniffer.DocumentStateStore
|
|
|
96
154
|
}
|
|
97
155
|
}
|
|
98
156
|
async createLock(stage, holder) {
|
|
157
|
+
await this.use(true);
|
|
99
158
|
const Key = this.key(stage, "lock.json");
|
|
100
159
|
try {
|
|
101
160
|
await this.client.send(new _aws_sdk_client_s3.PutObjectCommand({
|
|
@@ -111,6 +170,7 @@ var S3StateStore = class S3StateStore extends require_sniffer.DocumentStateStore
|
|
|
111
170
|
}
|
|
112
171
|
}
|
|
113
172
|
async readLock(stage) {
|
|
173
|
+
if (!await this.use(false)) return null;
|
|
114
174
|
const object = await this.get(this.key(stage, "lock.json"));
|
|
115
175
|
if (!object) return null;
|
|
116
176
|
try {
|
|
@@ -120,6 +180,7 @@ var S3StateStore = class S3StateStore extends require_sniffer.DocumentStateStore
|
|
|
120
180
|
}
|
|
121
181
|
}
|
|
122
182
|
async removeLock(stage, holder) {
|
|
183
|
+
if (!await this.use(false)) return;
|
|
123
184
|
const Key = this.key(stage, "lock.json");
|
|
124
185
|
const object = await this.get(Key);
|
|
125
186
|
if (!object) return;
|
|
@@ -157,4 +218,4 @@ var S3StateStore = class S3StateStore extends require_sniffer.DocumentStateStore
|
|
|
157
218
|
//#endregion
|
|
158
219
|
exports.S3StateStore = S3StateStore;
|
|
159
220
|
|
|
160
|
-
//# sourceMappingURL=S3StateStore-
|
|
221
|
+
//# sourceMappingURL=S3StateStore-zK9zcd9f.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"S3StateStore-zK9zcd9f.cjs","names":["DocumentStateStore","projectBucketName","callerAccountId","ensureProjectBucket","findProjectBucket","deployIdentity","STSClient","S3Client","PutObjectCommand","StateVersionConflict","StateLocked","DeleteObjectCommand","GetObjectCommand"],"sources":["../src/deploy/S3StateStore.ts"],"sourcesContent":["/**\n * Amazon S3 state store.\n *\n * In the bucket the config names, which must exist — or, with none named, in\n * the project bucket (`providers/projectBucket.ts`): found by its name, which\n * is worked out from the project and the account, and created by the first\n * write (a deploy's lock, `state:push`). A read of a project bucket that does\n * not exist yet is a stage with no state; it creates nothing.\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 { STSClient } from '@aws-sdk/client-sts';\nimport {\n\tcallerAccountId,\n\tensureProjectBucket,\n\tfindProjectBucket,\n\tprojectBucketName,\n} from '../providers/projectBucket';\nimport { type DeployIdentity, deployIdentity } from './identity';\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\t/** Omitted, the project bucket — created by the first write. */\n\tbucket?: string;\n\t/** `deploy.namespace`, for the project bucket's name. */\n\tnamespace?: 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\n/** The project bucket, for a store that names no bucket of its own. */\nexport interface ProjectBucketSource {\n\tidentity: Pick<DeployIdentity, 'scope' | 'key'>;\n\tregion: string;\n\t/** What the account id — and so the bucket's name — is read with. */\n\tsts: STSClient;\n}\n\nexport class S3StateStore extends DocumentStateStore {\n\tprivate readonly prefix: string;\n\t/** The bucket's name, once known. */\n\tprivate name?: string;\n\t/** Whether the bucket is known to exist. */\n\tprivate exists = false;\n\n\tconstructor(\n\t\treadonly workspaceName: string,\n\t\tprivate readonly source: string | ProjectBucketSource,\n\t\tprivate readonly client: S3Client,\n\t\tprefix = 'gkm',\n\t) {\n\t\tsuper();\n\t\tthis.prefix = prefix.replace(/\\/+$/, '');\n\t\tif (typeof source === 'string') {\n\t\t\tthis.name = source;\n\t\t\tthis.exists = true;\n\t\t}\n\t}\n\n\t/** The bucket's name; a project bucket's is known after the first call. */\n\tget bucket(): string {\n\t\tif (this.name) return this.name;\n\t\tconst { identity } = this.source as ProjectBucketSource;\n\t\treturn projectBucketName(identity.scope, '<account id>');\n\t}\n\n\t/**\n\t * The bucket, or `null` for a project bucket that does not exist yet and\n\t * is not to be created (`create: false`, a read).\n\t */\n\tprivate async use(create: boolean): Promise<string | null> {\n\t\tif (this.exists) return this.name!;\n\t\tconst source = this.source as ProjectBucketSource;\n\t\tthis.name ??= projectBucketName(\n\t\t\tsource.identity.scope,\n\t\t\tawait callerAccountId(source.sts),\n\t\t);\n\t\tif (create) {\n\t\t\tawait ensureProjectBucket(this.client, {\n\t\t\t\tbucket: this.name,\n\t\t\t\tregion: source.region,\n\t\t\t\tidentity: source.identity,\n\t\t\t});\n\t\t} else if ((await findProjectBucket(this.client, this.name)) === 'none') {\n\t\t\treturn null;\n\t\t}\n\t\tthis.exists = true;\n\t\treturn this.name;\n\t}\n\n\tstatic create(options: S3StateStoreOptions): S3StateStore {\n\t\tconst endpoint =\n\t\t\toptions.endpoint ??\n\t\t\tprocess.env.AWS_ENDPOINT_URL_S3 ??\n\t\t\tprocess.env.AWS_ENDPOINT_URL;\n\t\tconst config: S3ClientConfig = {\n\t\t\tregion: options.region,\n\t\t\tendpoint,\n\t\t\t// The emulator serves buckets by path, not by subdomain.\n\t\t\tforcePathStyle: 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\tlet source: string | ProjectBucketSource;\n\t\tif (options.bucket) {\n\t\t\tsource = options.bucket;\n\t\t} else {\n\t\t\tsource = {\n\t\t\t\tidentity: deployIdentity(\n\t\t\t\t\t{\n\t\t\t\t\t\tname: options.workspaceName,\n\t\t\t\t\t\t...(options.namespace\n\t\t\t\t\t\t\t? { deploy: { namespace: options.namespace } }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t},\n\t\t\t\t\t'',\n\t\t\t\t),\n\t\t\t\tregion: options.region ?? process.env.AWS_REGION ?? 'us-east-1',\n\t\t\t\tsts: new STSClient({\n\t\t\t\t\tregion: config.region,\n\t\t\t\t\t...(options.endpoint ? { endpoint: options.endpoint } : {}),\n\t\t\t\t\t...(config.credentials ? { credentials: config.credentials } : {}),\n\t\t\t\t}),\n\t\t\t};\n\t\t}\n\t\treturn new S3StateStore(\n\t\t\toptions.workspaceName,\n\t\t\tsource,\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\tif (!(await this.use(false))) return 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\tawait this.use(true);\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\tawait this.use(true);\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\tawait this.use(true);\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\tif (!(await this.use(false))) return 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\tif (!(await this.use(false))) return;\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":";;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,SAAS,SAAS,OAAyB;CAC1C,MAAM,EAAE,MAAM,cAAe,SAAS,CAAC;CACvC,OACC,SAAS,wBACT,SAAS,gCACT,WAAW,mBAAmB,OAC9B,WAAW,mBAAmB;AAEhC;AAEA,SAAS,SAAS,OAAyB;CAC1C,MAAM,EAAE,MAAM,cAAe,SAAS,CAAC;CACvC,OACC,SAAS,eACT,SAAS,cACT,WAAW,mBAAmB;AAEhC;AAUA,IAAa,eAAb,MAAa,qBAAqBA,gBAAAA,mBAAmB;CAQ1C;CACQ;CACA;CATlB;;CAEA;;CAEA,SAAiB;CAEjB,YACC,eACA,QACA,QACA,SAAS,OACR;EACD,MAAM;EALG,KAAA,gBAAA;EACQ,KAAA,SAAA;EACA,KAAA,SAAA;EAIjB,KAAK,SAAS,OAAO,QAAQ,QAAQ,EAAE;EACvC,IAAI,OAAO,WAAW,UAAU;GAC/B,KAAK,OAAO;GACZ,KAAK,SAAS;EACf;CACD;;CAGA,IAAI,SAAiB;EACpB,IAAI,KAAK,MAAM,OAAO,KAAK;EAC3B,MAAM,EAAE,aAAa,KAAK;EAC1B,OAAOC,sBAAAA,kBAAkB,SAAS,OAAO,cAAc;CACxD;;;;;CAMA,MAAc,IAAI,QAAyC;EAC1D,IAAI,KAAK,QAAQ,OAAO,KAAK;EAC7B,MAAM,SAAS,KAAK;EACpB,KAAK,SAASA,sBAAAA,kBACb,OAAO,SAAS,OAChB,MAAMC,sBAAAA,gBAAgB,OAAO,GAAG,CACjC;EACA,IAAI,QACH,MAAMC,sBAAAA,oBAAoB,KAAK,QAAQ;GACtC,QAAQ,KAAK;GACb,QAAQ,OAAO;GACf,UAAU,OAAO;EAClB,CAAC;OACK,IAAK,MAAMC,sBAAAA,kBAAkB,KAAK,QAAQ,KAAK,IAAI,MAAO,QAChE,OAAO;EAER,KAAK,SAAS;EACd,OAAO,KAAK;CACb;CAEA,OAAO,OAAO,SAA4C;EACzD,MAAM,WACL,QAAQ,YACR,QAAQ,IAAI,uBACZ,QAAQ,IAAI;EACb,MAAM,SAAyB;GAC9B,QAAQ,QAAQ;GAChB;GAEA,gBAAgB,WAAW,OAAO,KAAA;EACnC;EACA,IAAI,QAAQ,SAAS;GAEpB,MAAM,EAAE,YAAY,QAAQ,+BAA+B;GAC3D,OAAO,cAAc,QAAQ,EAAE,SAAS,QAAQ,QAAQ,CAAC;EAC1D,OAAO,IAAI,QAAQ,aAClB,OAAO,cAAc,QAAQ;EAE9B,IAAI;EACJ,IAAI,QAAQ,QACX,SAAS,QAAQ;OAEjB,SAAS;GACR,UAAUC,cAAAA,eACT;IACC,MAAM,QAAQ;IACd,GAAI,QAAQ,YACT,EAAE,QAAQ,EAAE,WAAW,QAAQ,UAAU,EAAE,IAC3C,CAAC;GACL,GACA,EACD;GACA,QAAQ,QAAQ,UAAU,QAAQ,IAAI,cAAc;GACpD,KAAK,IAAIC,oBAAAA,UAAU;IAClB,QAAQ,OAAO;IACf,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;IACzD,GAAI,OAAO,cAAc,EAAE,aAAa,OAAO,YAAY,IAAI,CAAC;GACjE,CAAC;EACF;EAED,OAAO,IAAI,aACV,QAAQ,eACR,QACA,IAAIC,mBAAAA,SAAS,MAAM,GACnB,QAAQ,MACT;CACD;CAEA,IAAY,OAAe,MAAsB;EAChD,MAAM,OAAO,GAAG,KAAK,cAAc,GAAG,MAAM,GAAG;EAC/C,OAAO,KAAK,SAAS,GAAG,KAAK,OAAO,GAAG,SAAS;CACjD;CAEA,SAAmB,OAAuB;EACzC,OAAO,QAAQ,KAAK,OAAO,GAAG,KAAK,IAAI,OAAO,YAAY;CAC3D;CAEA,MAAgB,QAAQ,OAAyC;EAChE,IAAI,CAAE,MAAM,KAAK,IAAI,KAAK,GAAI,OAAO;EACrC,MAAM,SAAS,MAAM,KAAK,IAAI,KAAK,IAAI,OAAO,YAAY,CAAC;EAC3D,IAAI,CAAC,QAAQ,OAAO;EACpB,OAAO;GAAE,MAAM,OAAO;GAAM,SAAS,OAAO;EAAK;CAClD;CAEA,MAAgB,SACf,OACA,MACA,iBACwB;EACxB,MAAM,KAAK,IAAI,IAAI;EACnB,MAAM,MAAM,KAAK,IAAI,OAAO,YAAY;EACxC,IAAI;GACH,MAAM,EAAE,SAAS,MAAM,KAAK,OAAO,KAClC,IAAIC,mBAAAA,iBAAiB;IACpB,QAAQ,KAAK;IACb;IACA,MAAM;IACN,aAAa;IACb,GAAI,oBAAoB,OACrB,EAAE,aAAa,IAAI,IACnB,EAAE,SAAS,gBAAgB;GAC/B,CAAC,CACF;GACA,IAAI,MAAM,OAAO;GAEjB,QAAQ,MAAM,KAAK,IAAI,GAAG,EAAA,EAAI,QAAQ;EACvC,SAAS,OAAO;GACf,IAAI,SAAS,KAAK,GAAG;IACpB,MAAM,UAAU,MAAM,KAAK,IAAI,GAAG;IAClC,MAAM,IAAIC,gBAAAA,qBACT,OACA,iBACA,SAAS,QAAQ,IAClB;GACD;GACA,MAAM;EACP;CACD;CAEA,MAAgB,cAAc,OAAe,MAA6B;EACzE,MAAM,KAAK,IAAI,IAAI;EACnB,IAAI;GACH,MAAM,KAAK,OAAO,KACjB,IAAID,mBAAAA,iBAAiB;IACpB,QAAQ,KAAK;IACb,KAAK,KAAK,IAAI,OAAO,eAAe;IACpC,MAAM;IACN,aAAa;IACb,aAAa;GACd,CAAC,CACF;EACD,SAAS,OAAO;GAEf,IAAI,CAAC,SAAS,KAAK,GAAG,MAAM;EAC7B;CACD;CAEA,MAAgB,WAAW,OAAe,QAAmC;EAC5E,MAAM,KAAK,IAAI,IAAI;EACnB,MAAM,MAAM,KAAK,IAAI,OAAO,WAAW;EACvC,IAAI;GACH,MAAM,KAAK,OAAO,KACjB,IAAIA,mBAAAA,iBAAiB;IACpB,QAAQ,KAAK;IACb;IACA,MAAM,KAAK,UAAU,MAAM;IAC3B,aAAa;IACb,aAAa;GACd,CAAC,CACF;EACD,SAAS,OAAO;GACf,IAAI,SAAS,KAAK,GACjB,MAAM,IAAIE,gBAAAA,YACT,OACA,MAAM,KAAK,SAAS,KAAK,GACzB,QAAQ,KAAK,OAAO,GAAG,KACxB;GAED,MAAM;EACP;CACD;CAEA,MAAgB,SAAS,OAA2C;EACnE,IAAI,CAAE,MAAM,KAAK,IAAI,KAAK,GAAI,OAAO;EACrC,MAAM,SAAS,MAAM,KAAK,IAAI,KAAK,IAAI,OAAO,WAAW,CAAC;EAC1D,IAAI,CAAC,QAAQ,OAAO;EACpB,IAAI;GACH,OAAO,KAAK,MAAM,OAAO,IAAI;EAC9B,QAAQ;GACP,OAAO;EACR;CACD;CAEA,MAAgB,WACf,OACA,QACgB;EAChB,IAAI,CAAE,MAAM,KAAK,IAAI,KAAK,GAAI;EAC9B,MAAM,MAAM,KAAK,IAAI,OAAO,WAAW;EACvC,MAAM,SAAS,MAAM,KAAK,IAAI,GAAG;EACjC,IAAI,CAAC,QAAQ;EACb,IAAI,QACH,IAAI;GACH,IAAK,KAAK,MAAM,OAAO,IAAI,CAAC,CAAgB,OAAO,OAAO,IAAI;EAC/D,QAAQ;GACP;EACD;EAED,IAAI;GAGH,MAAM,KAAK,OAAO,KACjB,IAAIC,mBAAAA,oBAAoB;IACvB,QAAQ,KAAK;IACb;IACA,SAAS,OAAO;GACjB,CAAC,CACF;EACD,SAAS,OAAO;GACf,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,KAAK,GAAG,MAAM;EACjD;CACD;CAEA,MAAc,IACb,KACiD;EACjD,IAAI;GACH,MAAM,WAAW,MAAM,KAAK,OAAO,KAClC,IAAIC,mBAAAA,iBAAiB;IAAE,QAAQ,KAAK;IAAQ;GAAI,CAAC,CAClD;GAEA,OAAO;IAAE,MADK,MAAM,SAAS,MAAM,kBAAkB,OAAO,KAAM;IACnD,MAAM,SAAS,QAAQ;GAAG;EAC1C,SAAS,OAAO;GACf,IAAI,SAAS,KAAK,GAAG,OAAO;GAC5B,MAAM;EACP;CACD;AACD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_sniffer = require("./sniffer-
|
|
1
|
+
const require_sniffer = require("./sniffer-CeUKUoHn.cjs");
|
|
2
2
|
let _aws_sdk_client_ssm = require("@aws-sdk/client-ssm");
|
|
3
3
|
//#region src/deploy/SSMStateStore.ts
|
|
4
4
|
/**
|
|
@@ -13,14 +13,42 @@ let _aws_sdk_client_ssm = require("@aws-sdk/client-ssm");
|
|
|
13
13
|
* did not land exactly one version above the one it was based on raced
|
|
14
14
|
* another writer, and raises `StateVersionConflict` rather than reporting
|
|
15
15
|
* success. Both writes stay in the parameter's history.
|
|
16
|
+
*
|
|
17
|
+
* Size: the state is written with the Intelligent-Tiering tier, so SSM picks
|
|
18
|
+
* the advanced tier (8 KB) once a value passes the standard tier's 4 KB, and a
|
|
19
|
+
* document over {@link SSM_COMPRESS_OVER} bytes is stored gzipped, as `gz:`
|
|
20
|
+
* and base64. Both forms are read, so a document written before either is
|
|
21
|
+
* read as it was. A deploy checks before it starts that the stage's document
|
|
22
|
+
* leaves room under {@link SSM_MAX_BYTES} (`assertStateRoom`); a write SSM
|
|
23
|
+
* refuses anyway raises `StateRejectedBySsm` rather than the SDK's error.
|
|
24
|
+
*/
|
|
25
|
+
/** The most an advanced-tier parameter holds — what the state must fit. */
|
|
26
|
+
const SSM_MAX_BYTES = 8192;
|
|
27
|
+
/**
|
|
28
|
+
* `body` as the state parameter stores it: past the threshold, without its
|
|
29
|
+
* indentation and compressed.
|
|
16
30
|
*/
|
|
31
|
+
function packSsmBody(body) {
|
|
32
|
+
if (Buffer.byteLength(body) <= 3072) return body;
|
|
33
|
+
let compact = body;
|
|
34
|
+
try {
|
|
35
|
+
compact = JSON.stringify(JSON.parse(body));
|
|
36
|
+
} catch {}
|
|
37
|
+
return require_sniffer.compressBody(compact);
|
|
38
|
+
}
|
|
39
|
+
/** A refusal of the value itself — its size, past the tier's limit. */
|
|
40
|
+
function refusedValue(error) {
|
|
41
|
+
return error?.name === "ValidationException";
|
|
42
|
+
}
|
|
17
43
|
var SSMStateStore = class SSMStateStore extends require_sniffer.DocumentStateStore {
|
|
18
44
|
workspaceName;
|
|
19
45
|
client;
|
|
20
|
-
|
|
46
|
+
region;
|
|
47
|
+
constructor(workspaceName, client, region = "<region>") {
|
|
21
48
|
super();
|
|
22
49
|
this.workspaceName = workspaceName;
|
|
23
50
|
this.client = client;
|
|
51
|
+
this.region = region;
|
|
24
52
|
}
|
|
25
53
|
static create(options) {
|
|
26
54
|
const config = {
|
|
@@ -31,7 +59,7 @@ var SSMStateStore = class SSMStateStore extends require_sniffer.DocumentStateSto
|
|
|
31
59
|
const { fromIni } = require("@aws-sdk/credential-providers");
|
|
32
60
|
config.credentials = fromIni({ profile: options.profile });
|
|
33
61
|
} else if (options.credentials) config.credentials = options.credentials;
|
|
34
|
-
return new SSMStateStore(options.workspaceName, new _aws_sdk_client_ssm.SSMClient(config));
|
|
62
|
+
return new SSMStateStore(options.workspaceName, new _aws_sdk_client_ssm.SSMClient(config), options.region);
|
|
35
63
|
}
|
|
36
64
|
name(stage, leaf) {
|
|
37
65
|
return `/gkm/${this.workspaceName}/${stage}/${leaf}`;
|
|
@@ -43,11 +71,31 @@ var SSMStateStore = class SSMStateStore extends require_sniffer.DocumentStateSto
|
|
|
43
71
|
const parameter = await this.get(this.name(stage, "state"));
|
|
44
72
|
if (!parameter) return null;
|
|
45
73
|
return {
|
|
46
|
-
body: parameter.value,
|
|
74
|
+
body: require_sniffer.decompressBody(parameter.value),
|
|
47
75
|
version: String(parameter.version)
|
|
48
76
|
};
|
|
49
77
|
}
|
|
50
|
-
|
|
78
|
+
/** The stage's document as the next write would store it. */
|
|
79
|
+
async capacity(stage) {
|
|
80
|
+
const raw = await this.readRaw(stage);
|
|
81
|
+
if (!raw) return null;
|
|
82
|
+
return {
|
|
83
|
+
bytes: Buffer.byteLength(packSsmBody(raw.body)),
|
|
84
|
+
limit: SSM_MAX_BYTES,
|
|
85
|
+
location: this.location(stage),
|
|
86
|
+
region: this.region
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async writeRaw(stage, document, expectedVersion) {
|
|
90
|
+
try {
|
|
91
|
+
return await this.put(stage, packSsmBody(document), expectedVersion);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (!refusedValue(error)) throw error;
|
|
94
|
+
throw new require_sniffer.StateRejectedBySsm(stage, Buffer.byteLength(packSsmBody(document)), this.location(stage), this.region, error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** The conditional put of `body`, stored as it is given. */
|
|
98
|
+
async put(stage, body, expectedVersion) {
|
|
51
99
|
const Name = this.name(stage, "state");
|
|
52
100
|
const Description = `GKM deployment state for ${this.workspaceName}/${stage}`;
|
|
53
101
|
if (expectedVersion === null) try {
|
|
@@ -55,6 +103,7 @@ var SSMStateStore = class SSMStateStore extends require_sniffer.DocumentStateSto
|
|
|
55
103
|
Name,
|
|
56
104
|
Value: body,
|
|
57
105
|
Type: "SecureString",
|
|
106
|
+
Tier: "Intelligent-Tiering",
|
|
58
107
|
Description
|
|
59
108
|
}));
|
|
60
109
|
return String(Version);
|
|
@@ -72,6 +121,7 @@ var SSMStateStore = class SSMStateStore extends require_sniffer.DocumentStateSto
|
|
|
72
121
|
Name,
|
|
73
122
|
Value: body,
|
|
74
123
|
Type: "SecureString",
|
|
124
|
+
Tier: "Intelligent-Tiering",
|
|
75
125
|
Overwrite: true,
|
|
76
126
|
Description
|
|
77
127
|
}));
|
|
@@ -84,8 +134,9 @@ var SSMStateStore = class SSMStateStore extends require_sniffer.DocumentStateSto
|
|
|
84
134
|
try {
|
|
85
135
|
await this.client.send(new _aws_sdk_client_ssm.PutParameterCommand({
|
|
86
136
|
Name: this.name(stage, "state.v1"),
|
|
87
|
-
Value: body,
|
|
137
|
+
Value: packSsmBody(body),
|
|
88
138
|
Type: "SecureString",
|
|
139
|
+
Tier: "Intelligent-Tiering",
|
|
89
140
|
Description: `GKM v1 deployment state for ${this.workspaceName}/${stage}, kept on migration`
|
|
90
141
|
}));
|
|
91
142
|
} catch (error) {
|
|
@@ -150,4 +201,4 @@ function parseHolder(value) {
|
|
|
150
201
|
//#endregion
|
|
151
202
|
exports.SSMStateStore = SSMStateStore;
|
|
152
203
|
|
|
153
|
-
//# sourceMappingURL=SSMStateStore-
|
|
204
|
+
//# sourceMappingURL=SSMStateStore-C489DnR6.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SSMStateStore-C489DnR6.cjs","names":["compressBody","DocumentStateStore","SSMClient","decompressBody","StateRejectedBySsm","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 * Size: the state is written with the Intelligent-Tiering tier, so SSM picks\n * the advanced tier (8 KB) once a value passes the standard tier's 4 KB, and a\n * document over {@link SSM_COMPRESS_OVER} bytes is stored gzipped, as `gz:`\n * and base64. Both forms are read, so a document written before either is\n * read as it was. A deploy checks before it starts that the stage's document\n * leaves room under {@link SSM_MAX_BYTES} (`assertStateRoom`); a write SSM\n * refuses anyway raises `StateRejectedBySsm` rather than the SDK's error.\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\tcompressBody,\n\tDocumentStateStore,\n\tdecompressBody,\n\ttype LockHolder,\n\ttype RawState,\n\ttype StateCapacity,\n\tStateLocked,\n\tStateRejectedBySsm,\n\ttype StateVersion,\n\tStateVersionConflict,\n} from './StateStore';\n\n/** The most an advanced-tier parameter holds — what the state must fit. */\nexport const SSM_MAX_BYTES = 8192;\n/** A body past this is stored compressed: well under the standard 4 KB. */\nexport const SSM_COMPRESS_OVER = 3072;\n\n/**\n * `body` as the state parameter stores it: past the threshold, without its\n * indentation and compressed.\n */\nexport function packSsmBody(body: string): string {\n\tif (Buffer.byteLength(body) <= SSM_COMPRESS_OVER) return body;\n\tlet compact = body;\n\ttry {\n\t\tcompact = JSON.stringify(JSON.parse(body));\n\t} catch {\n\t\t// Not JSON: stored as it was given, compressed.\n\t}\n\treturn compressBody(compact);\n}\n\n/** A refusal of the value itself — its size, past the tier's limit. */\nfunction refusedValue(error: unknown): boolean {\n\treturn (error as { name?: string })?.name === 'ValidationException';\n}\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\t/** For messages; the client's own region is used for every call. */\n\t\treadonly region = '<region>',\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(\n\t\t\toptions.workspaceName,\n\t\t\tnew SSMClient(config),\n\t\t\toptions.region,\n\t\t);\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 {\n\t\t\tbody: decompressBody(parameter.value),\n\t\t\tversion: String(parameter.version),\n\t\t};\n\t}\n\n\t/** The stage's document as the next write would store it. */\n\tasync capacity(stage: string): Promise<StateCapacity | null> {\n\t\tconst raw = await this.readRaw(stage);\n\t\tif (!raw) return null;\n\t\treturn {\n\t\t\tbytes: Buffer.byteLength(packSsmBody(raw.body)),\n\t\t\tlimit: SSM_MAX_BYTES,\n\t\t\tlocation: this.location(stage),\n\t\t\tregion: this.region,\n\t\t};\n\t}\n\n\tprotected async writeRaw(\n\t\tstage: string,\n\t\tdocument: string,\n\t\texpectedVersion: StateVersion | null,\n\t): Promise<StateVersion> {\n\t\ttry {\n\t\t\treturn await this.put(stage, packSsmBody(document), expectedVersion);\n\t\t} catch (error) {\n\t\t\tif (!refusedValue(error)) throw error;\n\t\t\tthrow new StateRejectedBySsm(\n\t\t\t\tstage,\n\t\t\t\tBuffer.byteLength(packSsmBody(document)),\n\t\t\t\tthis.location(stage),\n\t\t\t\tthis.region,\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t}\n\n\t/** The conditional put of `body`, stored as it is given. */\n\tprivate async put(\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\tTier: 'Intelligent-Tiering',\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\tTier: 'Intelligent-Tiering',\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\t// Kept as it was — the v1 state a person restores by hand —\n\t\t\t\t\t// unless it is too big to store that way.\n\t\t\t\t\tValue: packSsmBody(body),\n\t\t\t\t\tType: 'SecureString',\n\t\t\t\t\tTier: 'Intelligent-Tiering',\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\t// A few hundred bytes: the standard tier, which costs nothing.\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":";;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,MAAa,gBAAgB;;;;;AAQ7B,SAAgB,YAAY,MAAsB;CACjD,IAAI,OAAO,WAAW,IAAI,KAAA,MAAwB,OAAO;CACzD,IAAI,UAAU;CACd,IAAI;EACH,UAAU,KAAK,UAAU,KAAK,MAAM,IAAI,CAAC;CAC1C,QAAQ,CAER;CACA,OAAOA,gBAAAA,aAAa,OAAO;AAC5B;;AAGA,SAAS,aAAa,OAAyB;CAC9C,OAAQ,OAA6B,SAAS;AAC/C;AAYA,IAAa,gBAAb,MAAa,sBAAsBC,gBAAAA,mBAAmB;CAE3C;CACQ;CAER;CAJV,YACC,eACA,QAEA,SAAkB,YACjB;EACD,MAAM;EALG,KAAA,gBAAA;EACQ,KAAA,SAAA;EAER,KAAA,SAAA;CAGV;CAEA,OAAO,OAAO,SAA8C;EAC3D,MAAM,SAA0B;GAC/B,QAAQ,QAAQ;GAChB,UAAU,QAAQ;EACnB;EACA,IAAI,QAAQ,SAAS;GAEpB,MAAM,EAAE,YAAY,QAAQ,+BAA+B;GAC3D,OAAO,cAAc,QAAQ,EAAE,SAAS,QAAQ,QAAQ,CAAC;EAC1D,OAAO,IAAI,QAAQ,aAClB,OAAO,cAAc,QAAQ;EAE9B,OAAO,IAAI,cACV,QAAQ,eACR,IAAIC,oBAAAA,UAAU,MAAM,GACpB,QAAQ,MACT;CACD;CAEA,KAAa,OAAe,MAAsB;EACjD,OAAO,QAAQ,KAAK,cAAc,GAAG,MAAM,GAAG;CAC/C;CAEA,SAAmB,OAAuB;EACzC,OAAO,OAAO,KAAK,KAAK,OAAO,OAAO;CACvC;CAEA,MAAgB,QAAQ,OAAyC;EAChE,MAAM,YAAY,MAAM,KAAK,IAAI,KAAK,KAAK,OAAO,OAAO,CAAC;EAC1D,IAAI,CAAC,WAAW,OAAO;EACvB,OAAO;GACN,MAAMC,gBAAAA,eAAe,UAAU,KAAK;GACpC,SAAS,OAAO,UAAU,OAAO;EAClC;CACD;;CAGA,MAAM,SAAS,OAA8C;EAC5D,MAAM,MAAM,MAAM,KAAK,QAAQ,KAAK;EACpC,IAAI,CAAC,KAAK,OAAO;EACjB,OAAO;GACN,OAAO,OAAO,WAAW,YAAY,IAAI,IAAI,CAAC;GAC9C,OAAO;GACP,UAAU,KAAK,SAAS,KAAK;GAC7B,QAAQ,KAAK;EACd;CACD;CAEA,MAAgB,SACf,OACA,UACA,iBACwB;EACxB,IAAI;GACH,OAAO,MAAM,KAAK,IAAI,OAAO,YAAY,QAAQ,GAAG,eAAe;EACpE,SAAS,OAAO;GACf,IAAI,CAAC,aAAa,KAAK,GAAG,MAAM;GAChC,MAAM,IAAIC,gBAAAA,mBACT,OACA,OAAO,WAAW,YAAY,QAAQ,CAAC,GACvC,KAAK,SAAS,KAAK,GACnB,KAAK,QACL,KACD;EACD;CACD;;CAGA,MAAc,IACb,OACA,MACA,iBACwB;EACxB,MAAM,OAAO,KAAK,KAAK,OAAO,OAAO;EACrC,MAAM,cAAc,4BAA4B,KAAK,cAAc,GAAG;EAEtE,IAAI,oBAAoB,MACvB,IAAI;GACH,MAAM,EAAE,YAAY,MAAM,KAAK,OAAO,KACrC,IAAIC,oBAAAA,oBAAoB;IACvB;IACA,OAAO;IACP,MAAM;IACN,MAAM;IACN;GACD,CAAC,CACF;GACA,OAAO,OAAO,OAAO;EACtB,SAAS,OAAO;GACf,IAAI,iBAAiBC,oBAAAA,wBAAwB;IAC5C,MAAM,UAAU,MAAM,KAAK,IAAI,IAAI;IACnC,MAAM,IAAIC,gBAAAA,qBACT,OACA,MACA,UAAU,OAAO,QAAQ,OAAO,IAAI,IACrC;GACD;GACA,MAAM;EACP;EAGD,MAAM,UAAU,MAAM,KAAK,IAAI,IAAI;EACnC,MAAM,SAAS,UAAU,OAAO,QAAQ,OAAO,IAAI;EACnD,IAAI,WAAW,iBACd,MAAM,IAAIA,gBAAAA,qBAAqB,OAAO,iBAAiB,MAAM;EAG9D,MAAM,EAAE,YAAY,MAAM,KAAK,OAAO,KACrC,IAAIF,oBAAAA,oBAAoB;GACvB;GACA,OAAO;GACP,MAAM;GACN,MAAM;GACN,WAAW;GACX;EACD,CAAC,CACF;EACA,IAAI,YAAY,OAAO,eAAe,IAAI,GAKzC,MAAM,IAAIE,gBAAAA,qBACT,OACA,iBACA,QAAQ,WAAW,KAAK,CAAC,CAC1B;EAQD,MAAM,SAAS,MAAM,KAAK,IAAI,IAAI;EAClC,IAAI,UAAU,OAAO,YAAY,WAAW,OAAO,UAAU,MAC5D,MAAM,IAAIA,gBAAAA,qBAAqB,OAAO,iBAAiB,OAAO,OAAO,CAAC;EAEvE,OAAO,OAAO,OAAO;CACtB;CAEA,MAAgB,cAAc,OAAe,MAA6B;EACzE,IAAI;GACH,MAAM,KAAK,OAAO,KACjB,IAAIF,oBAAAA,oBAAoB;IACvB,MAAM,KAAK,KAAK,OAAO,UAAU;IAGjC,OAAO,YAAY,IAAI;IACvB,MAAM;IACN,MAAM;IACN,aAAa,+BAA+B,KAAK,cAAc,GAAG,MAAM;GACzE,CAAC,CACF;EACD,SAAS,OAAO;GAEf,IAAI,EAAE,iBAAiBC,oBAAAA,yBAAyB,MAAM;EACvD;CACD;CAEA,MAAgB,WAAW,OAAe,QAAmC;EAC5E,MAAM,OAAO,KAAK,KAAK,OAAO,MAAM;EACpC,IAAI;GACH,MAAM,KAAK,OAAO,KACjB,IAAID,oBAAAA,oBAAoB;IACvB;IACA,OAAO,KAAK,UAAU,MAAM;IAE5B,MAAM;IACN,aAAa,uBAAuB,KAAK,cAAc,GAAG;GAC3D,CAAC,CACF;EACD,SAAS,OAAO;GACf,IAAI,iBAAiBC,oBAAAA,wBACpB,MAAM,IAAIE,gBAAAA,YAAY,OAAO,MAAM,KAAK,SAAS,KAAK,GAAG,OAAO,MAAM;GAEvE,MAAM;EACP;EAOA,MAAM,UAAU,MAAM,KAAK,IAAI,IAAI;EACnC,MAAM,WAAW,UAAU,YAAY,QAAQ,KAAK,IAAI;EACxD,IAAI,SAAS,YAAY,KAAK,UAAU,OAAO,OAAO,IACrD,MAAM,IAAIA,gBAAAA,YAAY,OAAO,UAAU,OAAO,MAAM;CAEtD;CAEA,MAAgB,SAAS,OAA2C;EACnE,MAAM,YAAY,MAAM,KAAK,IAAI,KAAK,KAAK,OAAO,MAAM,CAAC;EACzD,OAAO,YAAY,YAAY,UAAU,KAAK,IAAI;CACnD;CAEA,MAAgB,WACf,OACA,QACgB;EAChB,IAAI,QAEC;QAAA,MADkB,KAAK,SAAS,KAAK,EAAA,EAC5B,OAAO,OAAO,IAAI;EAAA;EAEhC,IAAI;GACH,MAAM,KAAK,OAAO,KACjB,IAAIC,oBAAAA,uBAAuB,EAAE,MAAM,KAAK,KAAK,OAAO,MAAM,EAAE,CAAC,CAC9D;EACD,SAAS,OAAO;GACf,IAAI,EAAE,iBAAiBC,oBAAAA,oBAAoB,MAAM;EAClD;CACD;CAEA,MAAc,IACb,MACqD;EACrD,IAAI;GACH,MAAM,EAAE,cAAc,MAAM,KAAK,OAAO,KACvC,IAAIC,oBAAAA,oBAAoB;IAAE;IAAM,gBAAgB;GAAK,CAAC,CACvD;GACA,IAAI,WAAW,UAAU,KAAA,GAAW,OAAO;GAC3C,OAAO;IAAE,OAAO,UAAU;IAAO,SAAS,UAAU,WAAW;GAAE;EAClE,SAAS,OAAO;GACf,IAAI,iBAAiBD,oBAAAA,mBAAmB,OAAO;GAC/C,MAAM;EACP;CACD;AACD;AAEA,SAAS,YAAY,OAAkC;CACtD,IAAI;EACH,OAAO,KAAK,MAAM,KAAK;CACxB,QAAQ;EACP,OAAO;CACR;AACD"}
|