@reventlessdev/reventless-seed-aws 1.0.0-alpha.3 → 1.0.0-alpha.30
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/CHANGELOG.md +207 -0
- package/lib/bs/.compiler.log +2 -2
- package/lib/bs/.sourcedirs.json +1 -0
- package/lib/bs/compiler-info.json +2 -2
- package/lib/bs/src/ReventlessSeedAws.ast +0 -0
- package/lib/bs/src/ReventlessSeedAws.cmi +0 -0
- package/lib/bs/src/ReventlessSeedAws.cmj +0 -0
- package/lib/bs/src/ReventlessSeedAws.cmt +0 -0
- package/lib/bs/src/ReventlessSeedAws.res +198 -134
- package/lib/bs/src/ReventlessSeedAws.res.mjs +57 -31
- package/lib/bs/src/ReventlessSeedAws_Quiesce.ast +0 -0
- package/lib/bs/src/ReventlessSeedAws_Quiesce.cmi +0 -0
- package/lib/bs/src/ReventlessSeedAws_Quiesce.cmj +0 -0
- package/lib/bs/src/ReventlessSeedAws_Quiesce.cmt +0 -0
- package/lib/bs/src/ReventlessSeedAws_Quiesce.res +307 -0
- package/lib/bs/src/ReventlessSeedAws_Quiesce.res.mjs +217 -0
- package/lib/bs/src/ReventlessSeedAws_Reconcile.ast +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reconcile.cmi +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reconcile.cmj +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reconcile.cmt +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reconcile.res +391 -0
- package/lib/bs/src/ReventlessSeedAws_Reconcile.res.mjs +318 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.ast +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.cmi +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.cmj +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.cmt +0 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.res +1103 -0
- package/lib/bs/src/ReventlessSeedAws_Reset.res.mjs +991 -0
- package/lib/bs/tests/EndpointResolutionTest.ast +0 -0
- package/lib/bs/tests/EndpointResolutionTest.cmi +0 -0
- package/lib/bs/tests/EndpointResolutionTest.cmj +0 -0
- package/lib/bs/tests/EndpointResolutionTest.cmt +0 -0
- package/lib/bs/tests/EndpointResolutionTest.res +83 -0
- package/lib/bs/tests/EndpointResolutionTest.res.mjs +115 -0
- package/lib/bs/tests/ObjectStoreReconcileTest.ast +0 -0
- package/lib/bs/tests/ObjectStoreReconcileTest.cmi +0 -0
- package/lib/bs/tests/ObjectStoreReconcileTest.cmj +0 -0
- package/lib/bs/tests/ObjectStoreReconcileTest.cmt +0 -0
- package/lib/bs/tests/ObjectStoreReconcileTest.res +83 -0
- package/lib/bs/tests/ObjectStoreReconcileTest.res.mjs +102 -0
- package/lib/bs/tests/ObjectStoreResetTest.ast +0 -0
- package/lib/bs/tests/ObjectStoreResetTest.cmi +0 -0
- package/lib/bs/tests/ObjectStoreResetTest.cmj +0 -0
- package/lib/bs/tests/ObjectStoreResetTest.cmt +0 -0
- package/lib/bs/tests/ObjectStoreResetTest.res +191 -0
- package/lib/bs/tests/ObjectStoreResetTest.res.mjs +211 -0
- package/lib/bs/tests/QuiesceResetTest.ast +0 -0
- package/lib/bs/tests/QuiesceResetTest.cmi +0 -0
- package/lib/bs/tests/QuiesceResetTest.cmj +0 -0
- package/lib/bs/tests/QuiesceResetTest.cmt +0 -0
- package/lib/bs/tests/QuiesceResetTest.res +169 -0
- package/lib/bs/tests/QuiesceResetTest.res.mjs +157 -0
- package/lib/ocaml/.compiler.log +2 -2
- package/lib/ocaml/EndpointResolutionTest.ast +0 -0
- package/lib/ocaml/EndpointResolutionTest.cmi +0 -0
- package/lib/ocaml/EndpointResolutionTest.cmj +0 -0
- package/lib/ocaml/EndpointResolutionTest.cmt +0 -0
- package/lib/ocaml/EndpointResolutionTest.res +83 -0
- package/lib/ocaml/ObjectStoreReconcileTest.ast +0 -0
- package/lib/ocaml/ObjectStoreReconcileTest.cmi +0 -0
- package/lib/ocaml/ObjectStoreReconcileTest.cmj +0 -0
- package/lib/ocaml/ObjectStoreReconcileTest.cmt +0 -0
- package/lib/ocaml/ObjectStoreReconcileTest.res +83 -0
- package/lib/ocaml/ObjectStoreResetTest.ast +0 -0
- package/lib/ocaml/ObjectStoreResetTest.cmi +0 -0
- package/lib/ocaml/ObjectStoreResetTest.cmj +0 -0
- package/lib/ocaml/ObjectStoreResetTest.cmt +0 -0
- package/lib/ocaml/ObjectStoreResetTest.res +191 -0
- package/lib/ocaml/QuiesceResetTest.ast +0 -0
- package/lib/ocaml/QuiesceResetTest.cmi +0 -0
- package/lib/ocaml/QuiesceResetTest.cmj +0 -0
- package/lib/ocaml/QuiesceResetTest.cmt +0 -0
- package/lib/ocaml/QuiesceResetTest.res +169 -0
- package/lib/ocaml/ReventlessSeedAws.ast +0 -0
- package/lib/ocaml/ReventlessSeedAws.cmi +0 -0
- package/lib/ocaml/ReventlessSeedAws.cmj +0 -0
- package/lib/ocaml/ReventlessSeedAws.cmt +0 -0
- package/lib/ocaml/ReventlessSeedAws.res +198 -134
- package/lib/ocaml/ReventlessSeedAws_Quiesce.ast +0 -0
- package/lib/ocaml/ReventlessSeedAws_Quiesce.cmi +0 -0
- package/lib/ocaml/ReventlessSeedAws_Quiesce.cmj +0 -0
- package/lib/ocaml/ReventlessSeedAws_Quiesce.cmt +0 -0
- package/lib/ocaml/ReventlessSeedAws_Quiesce.res +307 -0
- package/lib/ocaml/ReventlessSeedAws_Reconcile.ast +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reconcile.cmi +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reconcile.cmj +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reconcile.cmt +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reconcile.res +391 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.ast +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.cmi +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.cmj +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.cmt +0 -0
- package/lib/ocaml/ReventlessSeedAws_Reset.res +1103 -0
- package/package.json +7 -4
- package/rescript.json +9 -1
- package/src/ReventlessSeedAws.res +198 -134
- package/src/ReventlessSeedAws.res.mjs +57 -31
- package/src/ReventlessSeedAws_Quiesce.res +307 -0
- package/src/ReventlessSeedAws_Quiesce.res.mjs +217 -0
- package/src/ReventlessSeedAws_Reconcile.res +391 -0
- package/src/ReventlessSeedAws_Reconcile.res.mjs +318 -0
- package/src/ReventlessSeedAws_Reset.res +1103 -0
- package/src/ReventlessSeedAws_Reset.res.mjs +991 -0
- package/tests/EndpointResolutionTest.res +83 -0
- package/tests/EndpointResolutionTest.res.mjs +115 -0
- package/tests/ObjectStoreReconcileTest.res +83 -0
- package/tests/ObjectStoreReconcileTest.res.mjs +102 -0
- package/tests/ObjectStoreResetTest.res +191 -0
- package/tests/ObjectStoreResetTest.res.mjs +211 -0
- package/tests/QuiesceResetTest.res +169 -0
- package/tests/QuiesceResetTest.res.mjs +157 -0
package/package.json
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-seed-aws",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
4
|
-
"description": "AWS connect for the Reventless seed harness — stack discovery
|
|
3
|
+
"version": "1.0.0-alpha.30",
|
|
4
|
+
"description": "AWS connect + guarded reset for the Reventless seed harness — stack discovery, Cognito login, and a tag-scoped, allowlisted store wipe",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@reventlessdev/
|
|
7
|
+
"@reventlessdev/rescript-node": "2.0.0-alpha.11",
|
|
8
|
+
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.18",
|
|
9
|
+
"@reventlessdev/reventless-seed": "1.0.0-alpha.21"
|
|
8
10
|
},
|
|
9
11
|
"devDependencies": {
|
|
10
|
-
"rescript": "12.3.0"
|
|
12
|
+
"rescript": "12.3.0",
|
|
13
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.10"
|
|
11
14
|
},
|
|
12
15
|
"peerDependencies": {
|
|
13
16
|
"rescript": "12.3.0"
|
package/rescript.json
CHANGED
|
@@ -11,10 +11,18 @@
|
|
|
11
11
|
{
|
|
12
12
|
"dir": "src",
|
|
13
13
|
"subdirs": true
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"dir": "tests",
|
|
17
|
+
"subdirs": true,
|
|
18
|
+
"type": "dev"
|
|
14
19
|
}
|
|
15
20
|
],
|
|
16
21
|
"dependencies": [
|
|
17
|
-
"@reventlessdev/reventless-seed"
|
|
22
|
+
"@reventlessdev/reventless-seed",
|
|
23
|
+
"@reventlessdev/rescript-aws-sdk",
|
|
24
|
+
"@reventlessdev/rescript-node",
|
|
25
|
+
"@reventlessdev/rescript-jest"
|
|
18
26
|
],
|
|
19
27
|
"compiler-flags": [],
|
|
20
28
|
"suffix": ".res.mjs"
|
|
@@ -14,12 +14,6 @@ open ReventlessSeed
|
|
|
14
14
|
|
|
15
15
|
// ── Node bindings ─────────────────────────────────────────────────────────────
|
|
16
16
|
|
|
17
|
-
@val @scope("process") external processEnv: dict<string> = "env"
|
|
18
|
-
|
|
19
|
-
type execOptions = {cwd: string, encoding: string, env?: dict<string>}
|
|
20
|
-
@module("node:child_process")
|
|
21
|
-
external execFileSync: (string, array<string>, execOptions) => string = "execFileSync"
|
|
22
|
-
|
|
23
17
|
type fetchInit = {method: string, headers: dict<string>, body?: string}
|
|
24
18
|
type response
|
|
25
19
|
@val external fetch: (string, fetchInit) => promise<response> = "fetch"
|
|
@@ -53,13 +47,17 @@ let envForBackend = (backend: option<string>): option<dict<string>> =>
|
|
|
53
47
|
switch backend {
|
|
54
48
|
| None => None
|
|
55
49
|
| Some(url) =>
|
|
56
|
-
let copy =
|
|
50
|
+
let copy = NodeProcess.env->Dict.toArray->Dict.fromArray
|
|
57
51
|
copy->Dict.set("PULUMI_BACKEND_URL", url)
|
|
58
52
|
Some(copy)
|
|
59
53
|
}
|
|
60
54
|
|
|
61
55
|
let pulumi = (~projectDir: string, ~backend: option<string>, args: array<string>): string =>
|
|
62
|
-
execFileSync(
|
|
56
|
+
NodeChildProcess.execFileSync(
|
|
57
|
+
"pulumi",
|
|
58
|
+
args,
|
|
59
|
+
{cwd: projectDir, encoding: "utf8", env: ?envForBackend(backend)},
|
|
60
|
+
)
|
|
63
61
|
|
|
64
62
|
// Stacks that actually exist in the Pulumi backend for this project — the names
|
|
65
63
|
// `pulumi stack output --stack <name>` accepts. A `Pulumi.<stack>.yaml` config
|
|
@@ -78,12 +76,15 @@ let deployedStacks = (~projectDir: string, ~backend: option<string>): array<stri
|
|
|
78
76
|
}
|
|
79
77
|
|
|
80
78
|
let stackOutputs = (~projectDir: string, ~backend: option<string>, stack: string): JSON.t => {
|
|
81
|
-
let raw = try pulumi(
|
|
79
|
+
let raw = try pulumi(
|
|
80
|
+
~projectDir,
|
|
81
|
+
~backend,
|
|
82
|
+
["stack", "output", "--stack", stack, "--json"],
|
|
83
|
+
) catch {
|
|
82
84
|
| _ =>
|
|
83
85
|
throw(
|
|
84
86
|
Seed.Failed(
|
|
85
|
-
`pulumi stack output --stack ${stack} failed — is pulumi installed, logged in, ` ++
|
|
86
|
-
"and the stack deployed?",
|
|
87
|
+
`pulumi stack output --stack ${stack} failed — is pulumi installed, logged in, ` ++ "and the stack deployed?",
|
|
87
88
|
),
|
|
88
89
|
)
|
|
89
90
|
}
|
|
@@ -98,7 +99,11 @@ let backendNote = (~backend: option<string>): string =>
|
|
|
98
99
|
| None => ""
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
let resolveStack = async (
|
|
102
|
+
let resolveStack = async (
|
|
103
|
+
~projectDir: string,
|
|
104
|
+
~backend: option<string>,
|
|
105
|
+
~stack: option<string>,
|
|
106
|
+
): string =>
|
|
102
107
|
switch stack {
|
|
103
108
|
| Some(s) => s
|
|
104
109
|
| None =>
|
|
@@ -112,8 +117,7 @@ let resolveStack = async (~projectDir: string, ~backend: option<string>, ~stack:
|
|
|
112
117
|
`no deployed Pulumi stacks for this project (\`pulumi stack ls\` is empty in ${projectDir})${backendNote(
|
|
113
118
|
~backend,
|
|
114
119
|
)}. ` ++
|
|
115
|
-
"A Pulumi.<stack>.yaml config file alone is not a deployed stack — run `pulumi up` " ++
|
|
116
|
-
"first, log in to the backend that holds the stack, or set SEED_STACK to target one.",
|
|
120
|
+
"A Pulumi.<stack>.yaml config file alone is not a deployed stack — run `pulumi up` " ++ "first, log in to the backend that holds the stack, or set SEED_STACK to target one.",
|
|
117
121
|
),
|
|
118
122
|
)
|
|
119
123
|
}
|
|
@@ -124,13 +128,13 @@ let resolveStack = async (~projectDir: string, ~backend: option<string>, ~stack:
|
|
|
124
128
|
// ── Endpoints ─────────────────────────────────────────────────────────────────
|
|
125
129
|
|
|
126
130
|
let fetchConfig = async (hostShellUrl: string): JSON.t => {
|
|
127
|
-
let base = hostShellUrl->String.replaceRegExp(
|
|
131
|
+
let base = hostShellUrl->String.replaceRegExp(/\/+$/g, "")
|
|
128
132
|
let url = `${base}/config.json`
|
|
129
133
|
let res = try await fetch(url, {method: "GET", headers: Dict.make()}) catch {
|
|
130
134
|
| _ => throw(Seed.Failed(`cannot reach ${url}`))
|
|
131
135
|
}
|
|
132
136
|
if !(res->responseOk) {
|
|
133
|
-
throw(Seed.Failed(`GET ${url} → HTTP ${
|
|
137
|
+
throw(Seed.Failed(`GET ${url} → HTTP ${res->responseStatus->Int.toString}`))
|
|
134
138
|
}
|
|
135
139
|
await res->responseJson
|
|
136
140
|
}
|
|
@@ -147,132 +151,196 @@ let resolveField = (~envKey: string, ~fromSource: option<string>, ~human: string
|
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
153
|
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
154
|
+
// Which document publishes the deployment's endpoints. A stack that serves a
|
|
155
|
+
// host shell publishes them in the shell's `config.json` under the client's key
|
|
156
|
+
// names; one that does not publishes them as stack outputs under Pulumi's. The
|
|
157
|
+
// two arms name different keys for the same values, which is why this is a
|
|
158
|
+
// variant and not a merged lookup.
|
|
159
|
+
type source = HostShellConfig(JSON.t) | StackOutputs(JSON.t)
|
|
160
|
+
|
|
161
|
+
type endpoints = {
|
|
162
|
+
graphql: string,
|
|
163
|
+
cognitoRegion: string,
|
|
164
|
+
cognitoClientId: string,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The endpoints a source document publishes — pure, so the branch selection and
|
|
169
|
+
* every key it reads can be exercised with synthetic documents.
|
|
170
|
+
*
|
|
171
|
+
* Upload endpoints are no longer resolved here: under route B the seed mints through
|
|
172
|
+
* the domain API's `Upload_Presign` mutation on `graphql`, passing the store per asset,
|
|
173
|
+
* so there is no per-store URL to discover.
|
|
174
|
+
*/
|
|
175
|
+
let endpointsFrom = (~stack: string, source: source): endpoints =>
|
|
176
|
+
switch source {
|
|
177
|
+
| HostShellConfig(cfg) =>
|
|
163
178
|
let fromCfg = key => cfg->field(key)->Option.flatMap(asString)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
~human="cognitoClientId",
|
|
183
|
-
)
|
|
184
|
-
(endpoint, uploadEndpoint, region, clientId)
|
|
185
|
-
| None =>
|
|
179
|
+
{
|
|
180
|
+
graphql: resolveField(
|
|
181
|
+
~envKey="REVENTLESS_GRAPHQL_ENDPOINT",
|
|
182
|
+
~fromSource=fromCfg("apiEndpoint"),
|
|
183
|
+
~human="apiEndpoint",
|
|
184
|
+
),
|
|
185
|
+
cognitoRegion: resolveField(
|
|
186
|
+
~envKey="AWS_REGION",
|
|
187
|
+
~fromSource=fromCfg("region"),
|
|
188
|
+
~human="region",
|
|
189
|
+
),
|
|
190
|
+
cognitoClientId: resolveField(
|
|
191
|
+
~envKey="COGNITO_CLIENT_ID",
|
|
192
|
+
~fromSource=fromCfg("cognitoClientId"),
|
|
193
|
+
~human="cognitoClientId",
|
|
194
|
+
),
|
|
195
|
+
}
|
|
196
|
+
| StackOutputs(outputs) =>
|
|
186
197
|
let out = key => outputs->field(key)->Option.flatMap(asString)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
198
|
+
{
|
|
199
|
+
graphql: switch (
|
|
200
|
+
Seed.Prompt.envValue("REVENTLESS_GRAPHQL_ENDPOINT"),
|
|
201
|
+
out("domainMergedApiEndpoint"),
|
|
202
|
+
out("domainApiEndpoint"),
|
|
203
|
+
) {
|
|
204
|
+
| (Some(v), _, _) | (_, Some(v), _) | (_, _, Some(v)) => v
|
|
205
|
+
| _ =>
|
|
206
|
+
throw(
|
|
207
|
+
Seed.Failed(
|
|
208
|
+
`stack "${stack}" exports neither domainMergedApiEndpoint nor domainApiEndpoint`,
|
|
209
|
+
),
|
|
210
|
+
)
|
|
211
|
+
},
|
|
212
|
+
cognitoRegion: resolveField(
|
|
213
|
+
~envKey="AWS_REGION",
|
|
214
|
+
~fromSource=out("cognitoRegion"),
|
|
215
|
+
~human="cognitoRegion",
|
|
216
|
+
),
|
|
217
|
+
cognitoClientId: resolveField(
|
|
218
|
+
~envKey="COGNITO_CLIENT_ID",
|
|
219
|
+
~fromSource=out("cognitoUserPoolClientId"),
|
|
220
|
+
~human="cognitoUserPoolClientId",
|
|
221
|
+
),
|
|
199
222
|
}
|
|
200
|
-
let region = resolveField(
|
|
201
|
-
~envKey="AWS_REGION",
|
|
202
|
-
~fromSource=out("cognitoRegion"),
|
|
203
|
-
~human="cognitoRegion",
|
|
204
|
-
)
|
|
205
|
-
let clientId = resolveField(
|
|
206
|
-
~envKey="COGNITO_CLIENT_ID",
|
|
207
|
-
~fromSource=out("cognitoUserPoolClientId"),
|
|
208
|
-
~human="cognitoUserPoolClientId",
|
|
209
|
-
)
|
|
210
|
-
// Absent → "" → the data set skips its upload phase.
|
|
211
|
-
let uploadEndpoint = Seed.Prompt.envValue("REVENTLESS_UPLOAD_ENDPOINT")->Option.getOr("")
|
|
212
|
-
(endpoint, uploadEndpoint, region, clientId)
|
|
213
223
|
}
|
|
224
|
+
|
|
225
|
+
let resolveEndpoints = async (
|
|
226
|
+
~projectDir: string,
|
|
227
|
+
~backend: option<string>,
|
|
228
|
+
~stack: string,
|
|
229
|
+
): endpoints => {
|
|
230
|
+
let outputs = stackOutputs(~projectDir, ~backend, stack)
|
|
231
|
+
let source = switch outputs->field("hostShellUrl")->Option.flatMap(asString) {
|
|
232
|
+
| Some(hostShellUrl) => HostShellConfig(await fetchConfig(hostShellUrl))
|
|
233
|
+
| None => StackOutputs(outputs)
|
|
234
|
+
}
|
|
235
|
+
endpointsFrom(~stack, source)
|
|
214
236
|
}
|
|
215
237
|
|
|
216
238
|
// ── Cognito login ─────────────────────────────────────────────────────────────
|
|
217
239
|
|
|
218
|
-
let cognito = (~region: string, ~clientId: string) =>
|
|
219
|
-
~username: string,
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
]),
|
|
240
|
+
let cognito = (~region: string, ~clientId: string) =>
|
|
241
|
+
async (~username: string, ~password: string): string => {
|
|
242
|
+
let body = JSON.stringify(
|
|
243
|
+
JSON.Encode.object(
|
|
244
|
+
Dict.fromArray([
|
|
245
|
+
("AuthFlow", JSON.Encode.string("USER_PASSWORD_AUTH")),
|
|
246
|
+
("ClientId", JSON.Encode.string(clientId)),
|
|
247
|
+
(
|
|
248
|
+
"AuthParameters",
|
|
249
|
+
JSON.Encode.object(
|
|
250
|
+
Dict.fromArray([
|
|
251
|
+
("USERNAME", JSON.Encode.string(username)),
|
|
252
|
+
("PASSWORD", JSON.Encode.string(password)),
|
|
253
|
+
]),
|
|
254
|
+
),
|
|
234
255
|
),
|
|
235
|
-
),
|
|
236
|
-
]),
|
|
237
|
-
),
|
|
238
|
-
)
|
|
239
|
-
let headers = Dict.fromArray([
|
|
240
|
-
("content-type", "application/x-amz-json-1.1"),
|
|
241
|
-
("x-amz-target", "AWSCognitoIdentityProviderService.InitiateAuth"),
|
|
242
|
-
])
|
|
243
|
-
let res = try await fetch(`https://cognito-idp.${region}.amazonaws.com/`, {
|
|
244
|
-
method: "POST",
|
|
245
|
-
headers,
|
|
246
|
-
body,
|
|
247
|
-
}) catch {
|
|
248
|
-
| _ => throw(Seed.Failed(`cannot reach Cognito in ${region}`))
|
|
249
|
-
}
|
|
250
|
-
let json = await res->responseJson
|
|
251
|
-
if !(res->responseOk) {
|
|
252
|
-
let detail =
|
|
253
|
-
json->field("message")->Option.flatMap(asString)->Option.getOr(JSON.stringify(json))
|
|
254
|
-
throw(
|
|
255
|
-
Seed.Failed(
|
|
256
|
-
`Cognito InitiateAuth failed (HTTP ${(res->responseStatus)->Int.toString}): ${detail}`,
|
|
256
|
+
]),
|
|
257
257
|
),
|
|
258
258
|
)
|
|
259
|
+
let headers = Dict.fromArray([
|
|
260
|
+
("content-type", "application/x-amz-json-1.1"),
|
|
261
|
+
("x-amz-target", "AWSCognitoIdentityProviderService.InitiateAuth"),
|
|
262
|
+
])
|
|
263
|
+
let res = try await fetch(
|
|
264
|
+
`https://cognito-idp.${region}.amazonaws.com/`,
|
|
265
|
+
{
|
|
266
|
+
method: "POST",
|
|
267
|
+
headers,
|
|
268
|
+
body,
|
|
269
|
+
},
|
|
270
|
+
) catch {
|
|
271
|
+
| _ => throw(Seed.Failed(`cannot reach Cognito in ${region}`))
|
|
272
|
+
}
|
|
273
|
+
let json = await res->responseJson
|
|
274
|
+
if !(res->responseOk) {
|
|
275
|
+
let detail =
|
|
276
|
+
json->field("message")->Option.flatMap(asString)->Option.getOr(JSON.stringify(json))
|
|
277
|
+
throw(
|
|
278
|
+
Seed.Failed(
|
|
279
|
+
`Cognito InitiateAuth failed (HTTP ${res->responseStatus->Int.toString}): ${detail}`,
|
|
280
|
+
),
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
switch json->field("ChallengeName")->Option.flatMap(asString) {
|
|
284
|
+
| Some(challenge) =>
|
|
285
|
+
throw(
|
|
286
|
+
Seed.Failed(
|
|
287
|
+
`Cognito returned challenge ${challenge} — set a permanent password first ` ++ "(aws cognito-idp admin-set-user-password … --permanent).",
|
|
288
|
+
),
|
|
289
|
+
)
|
|
290
|
+
| None => ()
|
|
291
|
+
}
|
|
292
|
+
switch json
|
|
293
|
+
->field("AuthenticationResult")
|
|
294
|
+
->Option.flatMap(ar => ar->field("IdToken"))
|
|
295
|
+
->Option.flatMap(asString) {
|
|
296
|
+
| Some(token) => token
|
|
297
|
+
| None => throw(Seed.Failed(`Cognito response carried no IdToken: ${JSON.stringify(json)}`))
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ── Active role ───────────────────────────────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* The Cognito path's active-role door: `Platform_SetActiveRole` on the domain
|
|
305
|
+
* API, which is where the seed already sends everything else.
|
|
306
|
+
*
|
|
307
|
+
* It sits on the domain base document at `AllowAuthenticated`, deliberately not
|
|
308
|
+
* behind the Admin gate — the whole point is that a caller can drop into a
|
|
309
|
+
* narrower role — so a token already narrowed to `Shopper` can still call this
|
|
310
|
+
* to widen back out. There is no chicken-and-egg, and no `sub` argument: the
|
|
311
|
+
* mutation addresses the authorizer-verified caller's own row and nobody else's.
|
|
312
|
+
*
|
|
313
|
+
* Returns `None` always: the choice is a stored row, read by the pool's
|
|
314
|
+
* pre-token-generation trigger on the NEXT login, so the current bearer is
|
|
315
|
+
* unchanged by it and the caller must re-authenticate to see any effect.
|
|
316
|
+
*
|
|
317
|
+
* Split-API only. A unified-mode deployment's single API does not carry the
|
|
318
|
+
* field, so a refusal naming it is reported as "this deployment offers no role
|
|
319
|
+
* switch" rather than as a seeding failure.
|
|
320
|
+
*/
|
|
321
|
+
let setActiveRole: Seed.Connect.roleSwitch = async (~client, ~role) => {
|
|
322
|
+
let argument = switch role {
|
|
323
|
+
| None => "null"
|
|
324
|
+
| Some(r) => JSON.stringify(JSON.Encode.string(r))
|
|
259
325
|
}
|
|
260
|
-
|
|
261
|
-
|
|
326
|
+
try {
|
|
327
|
+
let _ = await Seed.Client.gql(
|
|
328
|
+
client,
|
|
329
|
+
~query=`mutation { Platform_SetActiveRole(activeRole: ${argument}) { activeRole availableRoles } }`,
|
|
330
|
+
~label="Platform_SetActiveRole",
|
|
331
|
+
)
|
|
332
|
+
None
|
|
333
|
+
} catch {
|
|
334
|
+
| Seed.Failed(message) =>
|
|
262
335
|
throw(
|
|
263
336
|
Seed.Failed(
|
|
264
|
-
|
|
265
|
-
|
|
337
|
+
message->String.includes("Platform_SetActiveRole")
|
|
338
|
+
? `this deployment offers no role switch — \`Platform_SetActiveRole\` is a split-API ` ++
|
|
339
|
+
`field, and a unified-mode API does not carry it. Change the role in the host shell ` ++
|
|
340
|
+
`instead.\n ${message}`
|
|
341
|
+
: message,
|
|
266
342
|
),
|
|
267
343
|
)
|
|
268
|
-
| None => ()
|
|
269
|
-
}
|
|
270
|
-
switch json
|
|
271
|
-
->field("AuthenticationResult")
|
|
272
|
-
->Option.flatMap(ar => ar->field("IdToken"))
|
|
273
|
-
->Option.flatMap(asString) {
|
|
274
|
-
| Some(token) => token
|
|
275
|
-
| None => throw(Seed.Failed(`Cognito response carried no IdToken: ${JSON.stringify(json)}`))
|
|
276
344
|
}
|
|
277
345
|
}
|
|
278
346
|
|
|
@@ -302,15 +370,11 @@ let connect = (~projectDir: string=".", ~stack=?, ~backend=?, ()): (
|
|
|
302
370
|
| None => backend
|
|
303
371
|
}
|
|
304
372
|
let stackName = await resolveStack(~projectDir, ~backend, ~stack)
|
|
305
|
-
let
|
|
306
|
-
~projectDir,
|
|
307
|
-
~backend,
|
|
308
|
-
~stack=stackName,
|
|
309
|
-
)
|
|
373
|
+
let eps = await resolveEndpoints(~projectDir, ~backend, ~stack=stackName)
|
|
310
374
|
await Seed.Connect.make(
|
|
311
375
|
~label=stackName,
|
|
312
|
-
~endpoint,
|
|
313
|
-
~
|
|
314
|
-
~
|
|
376
|
+
~endpoint=eps.graphql,
|
|
377
|
+
~login=cognito(~region=eps.cognitoRegion, ~clientId=eps.cognitoClientId),
|
|
378
|
+
~roleSwitch=setActiveRole,
|
|
315
379
|
)
|
|
316
380
|
}
|
|
@@ -4,6 +4,8 @@ import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
|
|
|
4
4
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
5
5
|
import * as Nodechild_process from "node:child_process";
|
|
6
6
|
import * as Seed$ReventlessSeed from "@reventlessdev/reventless-seed/src/Seed.res.mjs";
|
|
7
|
+
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
8
|
+
import * as Seed_Client$ReventlessSeed from "@reventlessdev/reventless-seed/src/Seed_Client.res.mjs";
|
|
7
9
|
import * as Seed_Prompt$ReventlessSeed from "@reventlessdev/reventless-seed/src/Seed_Prompt.res.mjs";
|
|
8
10
|
import * as Seed_Connect$ReventlessSeed from "@reventlessdev/reventless-seed/src/Seed_Connect.res.mjs";
|
|
9
11
|
|
|
@@ -108,7 +110,7 @@ async function resolveStack(projectDir, backend, stack) {
|
|
|
108
110
|
return await Seed_Prompt$ReventlessSeed.select("Stack:", stacks.map(s => [
|
|
109
111
|
s,
|
|
110
112
|
s
|
|
111
|
-
]), undefined);
|
|
113
|
+
]), undefined, undefined);
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
async function fetchConfig(hostShellUrl) {
|
|
@@ -152,34 +154,28 @@ function resolveField(envKey, fromSource, human) {
|
|
|
152
154
|
};
|
|
153
155
|
}
|
|
154
156
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (hostShellUrl !== undefined) {
|
|
159
|
-
let cfg = await fetchConfig(hostShellUrl);
|
|
157
|
+
function endpointsFrom(stack, source) {
|
|
158
|
+
if (source.TAG === "HostShellConfig") {
|
|
159
|
+
let cfg = source._0;
|
|
160
160
|
let fromCfg = key => Stdlib_Option.flatMap(field(cfg, key), asString);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
endpoint,
|
|
167
|
-
uploadEndpoint,
|
|
168
|
-
region,
|
|
169
|
-
clientId
|
|
170
|
-
];
|
|
161
|
+
return {
|
|
162
|
+
graphql: resolveField("REVENTLESS_GRAPHQL_ENDPOINT", fromCfg("apiEndpoint"), "apiEndpoint"),
|
|
163
|
+
cognitoRegion: resolveField("AWS_REGION", fromCfg("region"), "region"),
|
|
164
|
+
cognitoClientId: resolveField("COGNITO_CLIENT_ID", fromCfg("cognitoClientId"), "cognitoClientId")
|
|
165
|
+
};
|
|
171
166
|
}
|
|
167
|
+
let outputs = source._0;
|
|
172
168
|
let out = key => Stdlib_Option.flatMap(field(outputs, key), asString);
|
|
173
169
|
let match = Seed_Prompt$ReventlessSeed.envValue("REVENTLESS_GRAPHQL_ENDPOINT");
|
|
174
170
|
let match$1 = out("domainMergedApiEndpoint");
|
|
175
171
|
let match$2 = out("domainApiEndpoint");
|
|
176
|
-
let
|
|
172
|
+
let tmp;
|
|
177
173
|
if (match !== undefined) {
|
|
178
|
-
|
|
174
|
+
tmp = match;
|
|
179
175
|
} else if (match$1 !== undefined) {
|
|
180
|
-
|
|
176
|
+
tmp = match$1;
|
|
181
177
|
} else if (match$2 !== undefined) {
|
|
182
|
-
|
|
178
|
+
tmp = match$2;
|
|
183
179
|
} else {
|
|
184
180
|
throw {
|
|
185
181
|
RE_EXN_ID: Seed$ReventlessSeed.Failed,
|
|
@@ -187,15 +183,24 @@ async function resolveEndpoints(projectDir, backend, stack) {
|
|
|
187
183
|
Error: new Error()
|
|
188
184
|
};
|
|
189
185
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
186
|
+
return {
|
|
187
|
+
graphql: tmp,
|
|
188
|
+
cognitoRegion: resolveField("AWS_REGION", out("cognitoRegion"), "cognitoRegion"),
|
|
189
|
+
cognitoClientId: resolveField("COGNITO_CLIENT_ID", out("cognitoUserPoolClientId"), "cognitoUserPoolClientId")
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function resolveEndpoints(projectDir, backend, stack) {
|
|
194
|
+
let outputs = stackOutputs(projectDir, backend, stack);
|
|
195
|
+
let hostShellUrl = Stdlib_Option.flatMap(field(outputs, "hostShellUrl"), asString);
|
|
196
|
+
let source = hostShellUrl !== undefined ? ({
|
|
197
|
+
TAG: "HostShellConfig",
|
|
198
|
+
_0: await fetchConfig(hostShellUrl)
|
|
199
|
+
}) : ({
|
|
200
|
+
TAG: "StackOutputs",
|
|
201
|
+
_0: outputs
|
|
202
|
+
});
|
|
203
|
+
return endpointsFrom(stack, source);
|
|
199
204
|
}
|
|
200
205
|
|
|
201
206
|
function cognito(region, clientId) {
|
|
@@ -276,14 +281,33 @@ function cognito(region, clientId) {
|
|
|
276
281
|
};
|
|
277
282
|
}
|
|
278
283
|
|
|
284
|
+
async function setActiveRole(client, role) {
|
|
285
|
+
let argument = role !== undefined ? JSON.stringify(role) : "null";
|
|
286
|
+
try {
|
|
287
|
+
await Seed_Client$ReventlessSeed.gql(client, `mutation { Platform_SetActiveRole(activeRole: ` + argument + `) { activeRole availableRoles } }`, "Platform_SetActiveRole");
|
|
288
|
+
return;
|
|
289
|
+
} catch (raw_message) {
|
|
290
|
+
let message = Primitive_exceptions.internalToException(raw_message);
|
|
291
|
+
if (message.RE_EXN_ID === Seed$ReventlessSeed.Failed) {
|
|
292
|
+
let message$1 = message._1;
|
|
293
|
+
throw {
|
|
294
|
+
RE_EXN_ID: Seed$ReventlessSeed.Failed,
|
|
295
|
+
_1: message$1.includes("Platform_SetActiveRole") ? `this deployment offers no role switch — \`Platform_SetActiveRole\` is a split-API field, and a unified-mode API does not carry it. Change the role in the host shell instead.\n ` + message$1 : message$1,
|
|
296
|
+
Error: new Error()
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
throw message;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
279
303
|
function connect($staropt$star, stack, backend, param) {
|
|
280
304
|
return async () => {
|
|
281
305
|
let projectDir = $staropt$star !== undefined ? $staropt$star : ".";
|
|
282
306
|
let url = Seed_Prompt$ReventlessSeed.envValue("SEED_PULUMI_BACKEND");
|
|
283
307
|
let backend$1 = url !== undefined ? url : backend;
|
|
284
308
|
let stackName = await resolveStack(projectDir, backend$1, stack);
|
|
285
|
-
let
|
|
286
|
-
return await Seed_Connect$ReventlessSeed.make(stackName,
|
|
309
|
+
let eps = await resolveEndpoints(projectDir, backend$1, stackName);
|
|
310
|
+
return await Seed_Connect$ReventlessSeed.make(stackName, eps.graphql, cognito(eps.cognitoRegion, eps.cognitoClientId), setActiveRole, undefined);
|
|
287
311
|
};
|
|
288
312
|
}
|
|
289
313
|
|
|
@@ -298,8 +322,10 @@ export {
|
|
|
298
322
|
resolveStack,
|
|
299
323
|
fetchConfig,
|
|
300
324
|
resolveField,
|
|
325
|
+
endpointsFrom,
|
|
301
326
|
resolveEndpoints,
|
|
302
327
|
cognito,
|
|
328
|
+
setActiveRole,
|
|
303
329
|
connect,
|
|
304
330
|
}
|
|
305
331
|
/* node:child_process Not a pure module */
|