@reventlessdev/reventless-aws 3.0.0-alpha.368 → 3.0.0-alpha.370
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 +22 -0
- package/package.json +9 -8
- package/run-resolve-environment.mjs +3 -0
- package/scripts/ProvisionAdmin.res +2 -2
- package/scripts/ProvisionAdmin.res.mjs +7 -6
- package/scripts/ResolveEnvironment.res +135 -0
- package/scripts/ResolveEnvironment.res.mjs +148 -0
- package/src/Platform.res +32 -3
- package/src/Platform.res.mjs +22 -5
- package/src/Platform_Stack.res +6 -3
- package/src/Platform_Stack.res.mjs +3 -3
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +1 -1
- package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +1 -1
- package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res +1 -1
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +1 -1
- package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +2 -1
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +1 -0
- package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +1 -0
- package/src/components/Api/AppSync_Adapter.res +4 -3
- package/src/components/Api/AppSync_SdlDecorate.res +7 -3
- package/src/components/Api/AppSync_SdlDecorate.res.mjs +4 -3
- package/src/util/Util_OwnerScopeEnv.res +9 -0
- package/src/util/Util_OwnerScopeEnv.res.mjs +14 -1
- package/src/util/Util_ShellConfig.res +4 -0
- package/src/util/Util_ShellConfig.res.mjs +5 -1
- package/tests/AppSync_AdapterTest.res +46 -23
- package/tests/AppSync_AdapterTest.res.mjs +35 -27
- package/tests/PgPipeline_IntegrationTest.res +2 -1
- package/tests/PgQueryDbUnion_IntegrationTest.res +2 -1
- package/tests/ResolveEnvironmentTest.res +91 -0
- package/tests/ResolveEnvironmentTest.res.mjs +128 -0
- package/tests/Util_OwnerScopeEnvTest.res +48 -2
- package/tests/Util_OwnerScopeEnvTest.res.mjs +49 -3
- package/tests/Util_ShellConfigTest.res +17 -0
- package/tests/Util_ShellConfigTest.res.mjs +18 -0
- package/tests/integration/AggTestAggregate.res +2 -1
- package/tests/integration/EpCompositeSlice.res +2 -1
- package/tests/integration/EpTestSlice.res +2 -1
- package/tests/integration/QueryDbUnion_DynamoDb_IntegrationTest.res +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# 3.0.0-alpha.370 (2026-10-01)
|
|
7
|
+
|
|
8
|
+
* feat!: roles a plugin declares, groups a platform provides ([43daace](https://github.com/ReventlessDev/reventless-core/commit/43daace6ca588afe22562d079af1e6f1da81942e))
|
|
9
|
+
|
|
10
|
+
### BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* AllowGroups and AdminGroup are removed — write AllowRoles over
|
|
13
|
+
the plugin's Roles.t, and use Role.admin / Platform.adminGroup(). Spec module
|
|
14
|
+
types declare `type role`, and their rules are Authorization.rule<role>; a spec
|
|
15
|
+
that writes its own rule declares `type role` (Reventless.Role.name for a rule
|
|
16
|
+
over Authorization.permission). Role.name is a private string: make one from a
|
|
17
|
+
string with Role.make.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# 3.0.0-alpha.369 (2026-10-01)
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* **spec, aws, core, local:** an app declares its environments, and every deployment records its commit ([c7cc925](https://github.com/ReventlessDev/reventless-core/commit/c7cc92573e97742b681a9000cea0bdfd6e9a33fc))
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
# 3.0.0-alpha.368 (2026-09-29)
|
|
7
29
|
|
|
8
30
|
**Note:** Version bump only for package @reventlessdev/reventless-aws
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reventlessdev/reventless-aws",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.370",
|
|
4
4
|
"description": "AWS adapters for Reventless",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"provision-admin": "./run-provision-admin.mjs",
|
|
9
9
|
"provision-accounts": "./run-provision-accounts.mjs",
|
|
10
10
|
"bake-manifest": "./run-bake-manifest.mjs",
|
|
11
|
-
"deploy-app": "./run-deploy-app.mjs"
|
|
11
|
+
"deploy-app": "./run-deploy-app.mjs",
|
|
12
|
+
"resolve-environment": "./run-resolve-environment.mjs"
|
|
12
13
|
},
|
|
13
14
|
"dependencies": {
|
|
14
15
|
"@aws-sdk/client-appsync": "3.970.0",
|
|
@@ -22,17 +23,17 @@
|
|
|
22
23
|
"yaml": "^2.8.3",
|
|
23
24
|
"@reventlessdev/rescript-aws-sdk": "3.0.0-alpha.27",
|
|
24
25
|
"@reventlessdev/rescript-effect": "0.1.0-alpha.34",
|
|
26
|
+
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
25
27
|
"@reventlessdev/rescript-node": "2.0.0-alpha.20",
|
|
26
28
|
"@reventlessdev/rescript-pulumi-aws": "3.0.0-alpha.22",
|
|
27
|
-
"@reventlessdev/rescript-jest": "1.0.0-alpha.11",
|
|
28
|
-
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.22",
|
|
29
29
|
"@reventlessdev/rescript-uuid": "2.0.0-alpha.1",
|
|
30
|
+
"@reventlessdev/rescript-pulumi-pulumi": "2.3.0-alpha.22",
|
|
31
|
+
"@reventlessdev/reventless-core": "3.0.0-alpha.286",
|
|
30
32
|
"@reventlessdev/rescript-web": "1.0.0-alpha.7",
|
|
31
33
|
"@reventlessdev/reventless-interop": "3.0.0-alpha.38",
|
|
32
|
-
"@reventlessdev/reventless-
|
|
33
|
-
"@reventlessdev/reventless-
|
|
34
|
-
"@reventlessdev/reventless-
|
|
35
|
-
"@reventlessdev/reventless-infra": "3.0.0-alpha.182"
|
|
34
|
+
"@reventlessdev/reventless-postgres": "3.0.0-alpha.150",
|
|
35
|
+
"@reventlessdev/reventless-spec": "3.0.0-alpha.156",
|
|
36
|
+
"@reventlessdev/reventless-infra": "3.0.0-alpha.184"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"rescript": "12.3.1",
|
|
@@ -76,7 +76,7 @@ Make the first administrator of a Reventless deployment.
|
|
|
76
76
|
one. The run always names the stack it used.
|
|
77
77
|
--email <address> The address they sign in with.
|
|
78
78
|
|
|
79
|
-
Creates the "${Reventless.
|
|
79
|
+
Creates the "${Reventless.Role.adminGroup()}" group if it is missing, the account if
|
|
80
80
|
it is missing, a working password, and the membership between them. Provisions
|
|
81
81
|
nothing that a platform stack owns. Region and credentials come from the
|
|
82
82
|
environment, as for any AWS SDK call.
|
|
@@ -222,7 +222,7 @@ let run = async ({email, providerId, stack}: request): result<unit, string> =>
|
|
|
222
222
|
switch await checkPoolAcceptsEmail(~providerId) {
|
|
223
223
|
| Error(_) as e => e
|
|
224
224
|
| Ok() =>
|
|
225
|
-
let group = Reventless.
|
|
225
|
+
let group = Reventless.Role.adminGroup()
|
|
226
226
|
let password = Reventless.Util_Password.generate()
|
|
227
227
|
await ensureGroup(~providerId, ~group)
|
|
228
228
|
await ensureUser(~providerId, ~email)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
2
|
|
|
3
3
|
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
4
|
+
import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
|
|
4
5
|
import * as CliArgs$Reventless from "@reventlessdev/reventless-spec/src/CliArgs.res.mjs";
|
|
5
6
|
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
|
|
6
|
-
import * as AdminGroup$Reventless from "@reventlessdev/reventless-spec/src/types/AdminGroup.res.mjs";
|
|
7
7
|
import * as Util_Password$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Password.res.mjs";
|
|
8
8
|
import * as Util_AwsError$ReventlessAws from "../src/util/Util_AwsError.res.mjs";
|
|
9
9
|
import * as ProvisionCognito$ReventlessAws from "./ProvisionCognito.res.mjs";
|
|
@@ -36,7 +36,7 @@ Make the first administrator of a Reventless deployment.
|
|
|
36
36
|
one. The run always names the stack it used.
|
|
37
37
|
--email <address> The address they sign in with.
|
|
38
38
|
|
|
39
|
-
Creates the "` +
|
|
39
|
+
Creates the "` + Role$Reventless.adminGroup() + `" group if it is missing, the account if
|
|
40
40
|
it is missing, a working password, and the membership between them. Provisions
|
|
41
41
|
nothing that a platform stack owns. Region and credentials come from the
|
|
42
42
|
environment, as for any AWS SDK call.
|
|
@@ -156,12 +156,13 @@ async function run(param) {
|
|
|
156
156
|
if (e$1.TAG !== "Ok") {
|
|
157
157
|
return e$1;
|
|
158
158
|
}
|
|
159
|
+
let group = Role$Reventless.adminGroup();
|
|
159
160
|
let password = Util_Password$Reventless.generate();
|
|
160
|
-
await ensureGroup(providerId,
|
|
161
|
+
await ensureGroup(providerId, group);
|
|
161
162
|
await ensureUser(providerId, email);
|
|
162
163
|
await setPassword(providerId, email, password);
|
|
163
|
-
await addToGroup(providerId, email,
|
|
164
|
-
console.log(signInDetails(providerId, email, password,
|
|
164
|
+
await addToGroup(providerId, email, group);
|
|
165
|
+
console.log(signInDetails(providerId, email, password, group));
|
|
165
166
|
return {
|
|
166
167
|
TAG: "Ok",
|
|
167
168
|
_0: undefined
|
|
@@ -212,4 +213,4 @@ export {
|
|
|
212
213
|
cli,
|
|
213
214
|
main,
|
|
214
215
|
}
|
|
215
|
-
/*
|
|
216
|
+
/* usage Not a pure module */
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/***
|
|
2
|
+
Which environment a push, a tag or a pull request deploys to, from the app's
|
|
3
|
+
`environments.yaml`.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
pnpm exec resolve-environment --ref refs/heads/test
|
|
7
|
+
pnpm exec resolve-environment --pull-request 7 --base dev
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Prints `key=value` lines, so the deploy workflow appends them to
|
|
11
|
+
`$GITHUB_OUTPUT` as they are:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
matched=true
|
|
15
|
+
environment=test
|
|
16
|
+
stack=test
|
|
17
|
+
purpose=test
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Nothing matched is an answer, not a failure: `matched=false` and exit 0, so a push
|
|
21
|
+
to a branch no environment names deploys nowhere and says so. The workflow asks
|
|
22
|
+
only when the file exists; without it the stack is the branch, as it always was.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
type request =
|
|
26
|
+
| Push(string)
|
|
27
|
+
| PullRequest({number: int, base: string, draft: bool})
|
|
28
|
+
|
|
29
|
+
type args = {
|
|
30
|
+
file: option<string>,
|
|
31
|
+
manifest: option<string>,
|
|
32
|
+
request: request,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let parseArgs = (argv: array<string>): result<args, string> =>
|
|
36
|
+
Reventless.CliArgs.parse(
|
|
37
|
+
~strings=["file", "manifest", "ref", "pull-request", "base"],
|
|
38
|
+
~bools=["draft"],
|
|
39
|
+
argv,
|
|
40
|
+
)
|
|
41
|
+
->Result.flatMap(Reventless.CliArgs.noPositionals)
|
|
42
|
+
->Result.flatMap(a => {
|
|
43
|
+
let string = name => a->Reventless.CliArgs.string(name)
|
|
44
|
+
let request = switch (string("ref"), string("pull-request"), string("base")) {
|
|
45
|
+
| (Some(ref), None, None) => Ok(Push(ref))
|
|
46
|
+
| (None, Some(number), Some(base)) =>
|
|
47
|
+
switch Int.fromString(number) {
|
|
48
|
+
| Some(number) => Ok(PullRequest({number, base, draft: a->Reventless.CliArgs.bool("draft")}))
|
|
49
|
+
| None => Error(`--pull-request takes a number, not "${number}"`)
|
|
50
|
+
}
|
|
51
|
+
| (None, Some(_), None) => Error("--pull-request needs --base, the branch it targets")
|
|
52
|
+
| (None, None, None) => Error("say what deploys: --ref, or --pull-request with --base")
|
|
53
|
+
| _ => Error("--ref and --pull-request are two different questions; ask one")
|
|
54
|
+
}
|
|
55
|
+
request->Result.map(request => {file: string("file"), manifest: string("manifest"), request})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
let usage = `
|
|
59
|
+
Usage: resolve-environment (--ref <ref> | --pull-request <n> --base <branch> [--draft])
|
|
60
|
+
[--file <path>] [--manifest <path>]
|
|
61
|
+
|
|
62
|
+
Which environment of the app's environments.yaml a deploy goes to.
|
|
63
|
+
|
|
64
|
+
--ref <ref> A push or a tag: refs/heads/<branch> or refs/tags/<tag>.
|
|
65
|
+
--pull-request <n> A pull request, by number ...
|
|
66
|
+
--base <branch> ... and the branch it targets.
|
|
67
|
+
--draft The pull request is a draft.
|
|
68
|
+
--file <path> The environments file. Defaults to ${Reventless.Environments.fileName}
|
|
69
|
+
beside the deploy manifest.
|
|
70
|
+
--manifest <path> The deploy manifest. Defaults to ${DeployManifest.defaultFile}
|
|
71
|
+
in the working directory.
|
|
72
|
+
|
|
73
|
+
Prints matched, environment, stack and purpose as key=value lines; a deploy that
|
|
74
|
+
matches nothing prints matched=false and exits 0.
|
|
75
|
+
`
|
|
76
|
+
|
|
77
|
+
/** The lines printed for a request, from the environments already read. */
|
|
78
|
+
let answer = (environments: Reventless.Environments.t, request: request): array<string> => {
|
|
79
|
+
let resolved = switch request {
|
|
80
|
+
| Push(ref) =>
|
|
81
|
+
ref
|
|
82
|
+
->Reventless.Environments.refOf
|
|
83
|
+
->Option.flatMap(ref => environments->Reventless.Environments.resolve(ref))
|
|
84
|
+
| PullRequest({number, base, draft}) =>
|
|
85
|
+
environments->Reventless.Environments.resolvePullRequest(~base, ~number, ~draft)
|
|
86
|
+
}
|
|
87
|
+
switch resolved {
|
|
88
|
+
| Some({name, stack, purpose}) => [
|
|
89
|
+
"matched=true",
|
|
90
|
+
`environment=${name}`,
|
|
91
|
+
`stack=${stack}`,
|
|
92
|
+
`purpose=${purpose->Reventless.Environments.purposeName}`,
|
|
93
|
+
]
|
|
94
|
+
| None => ["matched=false"]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let run = (args: args): result<array<string>, string> => {
|
|
99
|
+
let file = switch args.file {
|
|
100
|
+
| Some(file) => file
|
|
101
|
+
| None =>
|
|
102
|
+
Reventless.Environments.pathBeside(
|
|
103
|
+
~manifest=args.manifest->Option.getOr(
|
|
104
|
+
NodePath.join([NodeProcess.cwd(), DeployManifest.defaultFile]),
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
if !NodeFs.existsSync(file) {
|
|
109
|
+
Error(
|
|
110
|
+
`no environments file at ${file}. Without one every branch deploys to the stack of its own name, and there is nothing to resolve; --file names another path.`,
|
|
111
|
+
)
|
|
112
|
+
} else {
|
|
113
|
+
file
|
|
114
|
+
->Reventless.Environments.parseFile
|
|
115
|
+
->Result.map(environments => answer(environments, args.request))
|
|
116
|
+
->Result.mapError(message =>
|
|
117
|
+
`${file}: ${message}. The deploy reads any file of this name beside the deploy manifest as its environments; a file kept there for something else needs another name, or the workflow's environments input pointed elsewhere.`
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let cli: Reventless.CliArgs.cli<args> = {bin: "resolve-environment", usage, parse: parseArgs}
|
|
123
|
+
|
|
124
|
+
let main = () =>
|
|
125
|
+
Reventless.CliArgs.run(cli, async args =>
|
|
126
|
+
switch run(args) {
|
|
127
|
+
| Ok(lines) => lines->Array.forEach(line => Console.log(line))
|
|
128
|
+
| Error(message) =>
|
|
129
|
+
Console.error(`resolve-environment: ${message}`)
|
|
130
|
+
NodeProcess.exit(1)
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
// No top-level call: `../run-resolve-environment.mjs` invokes [main], so a test can
|
|
135
|
+
// import this module.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
import * as Nodefs from "node:fs";
|
|
4
|
+
import * as Nodepath from "node:path";
|
|
5
|
+
import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
|
|
6
|
+
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
7
|
+
import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
|
|
8
|
+
import * as CliArgs$Reventless from "@reventlessdev/reventless-spec/src/CliArgs.res.mjs";
|
|
9
|
+
import * as Environments$Reventless from "@reventlessdev/reventless-spec/src/types/Environments.res.mjs";
|
|
10
|
+
import * as DeployManifest$ReventlessAws from "./DeployManifest.res.mjs";
|
|
11
|
+
|
|
12
|
+
function parseArgs(argv) {
|
|
13
|
+
return Stdlib_Result.flatMap(Stdlib_Result.flatMap(CliArgs$Reventless.parse([
|
|
14
|
+
"file",
|
|
15
|
+
"manifest",
|
|
16
|
+
"ref",
|
|
17
|
+
"pull-request",
|
|
18
|
+
"base"
|
|
19
|
+
], ["draft"], undefined, undefined, argv), CliArgs$Reventless.noPositionals), a => {
|
|
20
|
+
let match = CliArgs$Reventless.string(a, "ref");
|
|
21
|
+
let match$1 = CliArgs$Reventless.string(a, "pull-request");
|
|
22
|
+
let match$2 = CliArgs$Reventless.string(a, "base");
|
|
23
|
+
let request;
|
|
24
|
+
if (match !== undefined) {
|
|
25
|
+
request = match$1 !== undefined || match$2 !== undefined ? ({
|
|
26
|
+
TAG: "Error",
|
|
27
|
+
_0: "--ref and --pull-request are two different questions; ask one"
|
|
28
|
+
}) : ({
|
|
29
|
+
TAG: "Ok",
|
|
30
|
+
_0: {
|
|
31
|
+
TAG: "Push",
|
|
32
|
+
_0: match
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
} else if (match$1 !== undefined) {
|
|
36
|
+
if (match$2 !== undefined) {
|
|
37
|
+
let number = Stdlib_Int.fromString(match$1, undefined);
|
|
38
|
+
request = number !== undefined ? ({
|
|
39
|
+
TAG: "Ok",
|
|
40
|
+
_0: {
|
|
41
|
+
TAG: "PullRequest",
|
|
42
|
+
number: number,
|
|
43
|
+
base: match$2,
|
|
44
|
+
draft: CliArgs$Reventless.bool(a, "draft")
|
|
45
|
+
}
|
|
46
|
+
}) : ({
|
|
47
|
+
TAG: "Error",
|
|
48
|
+
_0: `--pull-request takes a number, not "` + match$1 + `"`
|
|
49
|
+
});
|
|
50
|
+
} else {
|
|
51
|
+
request = {
|
|
52
|
+
TAG: "Error",
|
|
53
|
+
_0: "--pull-request needs --base, the branch it targets"
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
request = match$2 !== undefined ? ({
|
|
58
|
+
TAG: "Error",
|
|
59
|
+
_0: "--ref and --pull-request are two different questions; ask one"
|
|
60
|
+
}) : ({
|
|
61
|
+
TAG: "Error",
|
|
62
|
+
_0: "say what deploys: --ref, or --pull-request with --base"
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return Stdlib_Result.map(request, request => ({
|
|
66
|
+
file: CliArgs$Reventless.string(a, "file"),
|
|
67
|
+
manifest: CliArgs$Reventless.string(a, "manifest"),
|
|
68
|
+
request: request
|
|
69
|
+
}));
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let usage = `
|
|
74
|
+
Usage: resolve-environment (--ref <ref> | --pull-request <n> --base <branch> [--draft])
|
|
75
|
+
[--file <path>] [--manifest <path>]
|
|
76
|
+
|
|
77
|
+
Which environment of the app's environments.yaml a deploy goes to.
|
|
78
|
+
|
|
79
|
+
--ref <ref> A push or a tag: refs/heads/<branch> or refs/tags/<tag>.
|
|
80
|
+
--pull-request <n> A pull request, by number ...
|
|
81
|
+
--base <branch> ... and the branch it targets.
|
|
82
|
+
--draft The pull request is a draft.
|
|
83
|
+
--file <path> The environments file. Defaults to ` + Environments$Reventless.fileName + `
|
|
84
|
+
beside the deploy manifest.
|
|
85
|
+
--manifest <path> The deploy manifest. Defaults to ` + DeployManifest$ReventlessAws.defaultFile + `
|
|
86
|
+
in the working directory.
|
|
87
|
+
|
|
88
|
+
Prints matched, environment, stack and purpose as key=value lines; a deploy that
|
|
89
|
+
matches nothing prints matched=false and exits 0.
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
function answer(environments, request) {
|
|
93
|
+
let resolved;
|
|
94
|
+
resolved = request.TAG === "Push" ? Stdlib_Option.flatMap(Environments$Reventless.refOf(request._0), ref => Environments$Reventless.resolve(environments, ref)) : Environments$Reventless.resolvePullRequest(environments, request.base, request.number, request.draft);
|
|
95
|
+
if (resolved !== undefined) {
|
|
96
|
+
return [
|
|
97
|
+
"matched=true",
|
|
98
|
+
`environment=` + resolved.name,
|
|
99
|
+
`stack=` + resolved.stack,
|
|
100
|
+
`purpose=` + Environments$Reventless.purposeName(resolved.purpose)
|
|
101
|
+
];
|
|
102
|
+
} else {
|
|
103
|
+
return ["matched=false"];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function run(args) {
|
|
108
|
+
let file = args.file;
|
|
109
|
+
let file$1 = file !== undefined ? file : Environments$Reventless.pathBeside(Stdlib_Option.getOr(args.manifest, Nodepath.join(process.cwd(), DeployManifest$ReventlessAws.defaultFile)));
|
|
110
|
+
if (Nodefs.existsSync(file$1)) {
|
|
111
|
+
return Stdlib_Result.mapError(Stdlib_Result.map(Environments$Reventless.parseFile(file$1), environments => answer(environments, args.request)), message => file$1 + `: ` + message + `. The deploy reads any file of this name beside the deploy manifest as its environments; a file kept there for something else needs another name, or the workflow's environments input pointed elsewhere.`);
|
|
112
|
+
} else {
|
|
113
|
+
return {
|
|
114
|
+
TAG: "Error",
|
|
115
|
+
_0: `no environments file at ` + file$1 + `. Without one every branch deploys to the stack of its own name, and there is nothing to resolve; --file names another path.`
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let cli = {
|
|
121
|
+
bin: "resolve-environment",
|
|
122
|
+
usage: usage,
|
|
123
|
+
parse: parseArgs
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
function main() {
|
|
127
|
+
return CliArgs$Reventless.run(cli, undefined, undefined, async args => {
|
|
128
|
+
let lines = run(args);
|
|
129
|
+
if (lines.TAG === "Ok") {
|
|
130
|
+
lines._0.forEach(line => {
|
|
131
|
+
console.log(line);
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
console.error(`resolve-environment: ` + lines._0);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export {
|
|
141
|
+
parseArgs,
|
|
142
|
+
usage,
|
|
143
|
+
answer,
|
|
144
|
+
run,
|
|
145
|
+
cli,
|
|
146
|
+
main,
|
|
147
|
+
}
|
|
148
|
+
/* node:fs Not a pure module */
|
package/src/Platform.res
CHANGED
|
@@ -107,6 +107,24 @@ let messagingEmailSenderRef: ref<Pulumi.Output.t<string>> = ref(Pulumi.Output.ma
|
|
|
107
107
|
`""` means the platform named none, which the runtime reads as its default. */
|
|
108
108
|
let messagingEmailProviderRef: ref<Pulumi.Output.t<string>> = ref(Pulumi.Output.make(""))
|
|
109
109
|
|
|
110
|
+
/** Which group stands for which role, only where the two differ; every other
|
|
111
|
+
role maps to the group of its own name. Call before `Make()`: the platform
|
|
112
|
+
resolves roles while it is built, and a mapping stated later is refused. */
|
|
113
|
+
let roleGroups = Reventless.Role.setGroups
|
|
114
|
+
|
|
115
|
+
/** The groups of a user pool this deployment did not create, which no accounts
|
|
116
|
+
manifest names. They count as provided by the check that every role a plugin
|
|
117
|
+
needs has a group. */
|
|
118
|
+
let providedGroups = Reventless.Role.provideGroups
|
|
119
|
+
|
|
120
|
+
/** The group a role resolves to — the name this platform writes into its
|
|
121
|
+
directives, its shell configuration and the elevated groups. For code that
|
|
122
|
+
grants access by group, so it never spells one itself. */
|
|
123
|
+
let groupOf = Reventless.Role.groupOf
|
|
124
|
+
|
|
125
|
+
/** `groupOf(Role.admin)`. */
|
|
126
|
+
let adminGroup = Reventless.Role.adminGroup
|
|
127
|
+
|
|
110
128
|
module MakeWithConfig = (
|
|
111
129
|
Config: {
|
|
112
130
|
let splitApi: bool
|
|
@@ -144,7 +162,15 @@ module MakeWithConfig = (
|
|
|
144
162
|
// A default, never a forcing: an explicit `setElevatedGroups` in a platform
|
|
145
163
|
// root — including one naming nobody — means what it says and wins. See
|
|
146
164
|
// [Reventless.OwnerScope.defaultElevatedGroups].
|
|
147
|
-
Reventless.OwnerScope.
|
|
165
|
+
Reventless.OwnerScope.defaultElevatedRoles([Reventless.Role.admin])
|
|
166
|
+
|
|
167
|
+
// What this deployment provides for the roles its plugins need: the
|
|
168
|
+
// administrator group the stack declares, and the groups of the accounts
|
|
169
|
+
// provisioning creates. Read when each plugin is built, after the root has
|
|
170
|
+
// mapped its roles.
|
|
171
|
+
Reventless.Role.provideGroupsFrom(() =>
|
|
172
|
+
[Reventless.Role.adminGroup()]->Array.concat(Reventless.AccountsManifest.declaredGroups())
|
|
173
|
+
)
|
|
148
174
|
|
|
149
175
|
// Dispatch per-flavor commandHandlerConfig records to the four runtime
|
|
150
176
|
// builders. Every sub-record is optional; only branches the caller actually
|
|
@@ -308,13 +334,16 @@ module MakeWithConfig = (
|
|
|
308
334
|
// Admin base as a source-API document — auth-decorated (all fields Admin,
|
|
309
335
|
// Cognito-only; the deploy-time SigV4 system-caller fields died with the
|
|
310
336
|
// fragment registry) plus the canonical stamp.
|
|
311
|
-
let adminSourceSdl = (): string =>
|
|
337
|
+
let adminSourceSdl = (): string => {
|
|
338
|
+
// The directive below fixes the administrator's group for this deploy.
|
|
339
|
+
Reventless.Role.freeze()
|
|
312
340
|
assembleCanonicalSourceSdl(
|
|
313
341
|
~baseFragment=AppSync_Adapter.injectAwsAuthAll(
|
|
314
342
|
ReventlessCore.Platform_AdminApi.baseFragment(~cloner=Config.cloner),
|
|
315
|
-
~group=Reventless.
|
|
343
|
+
~group=Reventless.Role.adminGroup(),
|
|
316
344
|
),
|
|
317
345
|
)
|
|
346
|
+
}
|
|
318
347
|
|
|
319
348
|
// Split-mode Domain source document: relay base types + Platform_ping — the
|
|
320
349
|
// Domain merged API's canonical owner (plugin fields come from plugin sources).
|
package/src/Platform.res.mjs
CHANGED
|
@@ -13,10 +13,10 @@ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
|
13
13
|
import * as Pulumi from "@pulumi/pulumi";
|
|
14
14
|
import * as Belt_SetString from "@rescript/runtime/lib/es6/Belt_SetString.js";
|
|
15
15
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
16
|
+
import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
|
|
16
17
|
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
17
18
|
import * as Plugin$ReventlessAws from "./components/Plugin.res.mjs";
|
|
18
19
|
import * as Util_Sury$Reventless from "@reventlessdev/reventless-spec/src/util/Util_Sury.res.mjs";
|
|
19
|
-
import * as AdminGroup$Reventless from "@reventlessdev/reventless-spec/src/types/AdminGroup.res.mjs";
|
|
20
20
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
21
21
|
import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
|
|
22
22
|
import * as Plugin$ReventlessCore from "@reventlessdev/reventless-core/src/plugin/component/Plugin.res.mjs";
|
|
@@ -34,6 +34,7 @@ import * as Util_Bundle$ReventlessAws from "./util/Util_Bundle.res.mjs";
|
|
|
34
34
|
import * as Plugin_Stack$ReventlessAws from "./plugin/stack/Plugin_Stack.res.mjs";
|
|
35
35
|
import * as UiFragments$ReventlessCore from "@reventlessdev/reventless-core/src/admin/UiFragmentRegistry/StateView/UiFragments.res.mjs";
|
|
36
36
|
import * as Util_Pulumi$ReventlessCore from "@reventlessdev/reventless-core/src/util/Util_Pulumi.res.mjs";
|
|
37
|
+
import * as AccountsManifest$Reventless from "@reventlessdev/reventless-spec/src/types/AccountsManifest.res.mjs";
|
|
37
38
|
import * as Platform_Casts$ReventlessAws from "./Platform_Casts.res.mjs";
|
|
38
39
|
import * as Platform_Stack$ReventlessAws from "./Platform_Stack.res.mjs";
|
|
39
40
|
import * as QueryDbBackend$ReventlessAws from "./adapter/QueryDb/QueryDbBackend.res.mjs";
|
|
@@ -152,7 +153,8 @@ let messagingEmailProviderRef = {
|
|
|
152
153
|
};
|
|
153
154
|
|
|
154
155
|
function MakeWithConfig(Config) {
|
|
155
|
-
OwnerScope$Reventless.
|
|
156
|
+
OwnerScope$Reventless.defaultElevatedRoles([Role$Reventless.admin]);
|
|
157
|
+
Role$Reventless.provideGroupsFrom(() => [Role$Reventless.adminGroup()].concat(AccountsManifest$Reventless.declaredGroups()));
|
|
156
158
|
Stdlib_Option.forEach(Config.commandHandlerConfig.aggregates, param => {
|
|
157
159
|
Stdlib_Option.forEach(param.sync, AggregateRuntime_Builder_Single$ReventlessAws.setConfig);
|
|
158
160
|
Stdlib_Option.forEach(param.async, AggregateRuntime_Builder_Single_Async$ReventlessAws.setConfig);
|
|
@@ -214,7 +216,8 @@ function MakeWithConfig(Config) {
|
|
|
214
216
|
subscriptionSources: []
|
|
215
217
|
});
|
|
216
218
|
let adminSourceSdl = () => {
|
|
217
|
-
|
|
219
|
+
Role$Reventless.freeze();
|
|
220
|
+
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(Platform_AdminApi$ReventlessCore.baseFragment(Config.cloner), Role$Reventless.adminGroup(), undefined);
|
|
218
221
|
return AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(baseFragment));
|
|
219
222
|
};
|
|
220
223
|
let match;
|
|
@@ -1566,7 +1569,8 @@ function MakeWithConfig(Config) {
|
|
|
1566
1569
|
|
|
1567
1570
|
function Make($star) {
|
|
1568
1571
|
let commandHandlerConfig = {};
|
|
1569
|
-
OwnerScope$Reventless.
|
|
1572
|
+
OwnerScope$Reventless.defaultElevatedRoles([Role$Reventless.admin]);
|
|
1573
|
+
Role$Reventless.provideGroupsFrom(() => [Role$Reventless.adminGroup()].concat(AccountsManifest$Reventless.declaredGroups()));
|
|
1570
1574
|
Stdlib_Option.forEach(commandHandlerConfig.aggregates, param => {
|
|
1571
1575
|
Stdlib_Option.forEach(param.sync, AggregateRuntime_Builder_Single$ReventlessAws.setConfig);
|
|
1572
1576
|
Stdlib_Option.forEach(param.async, AggregateRuntime_Builder_Single_Async$ReventlessAws.setConfig);
|
|
@@ -1628,7 +1632,8 @@ function Make($star) {
|
|
|
1628
1632
|
subscriptionSources: []
|
|
1629
1633
|
});
|
|
1630
1634
|
let adminSourceSdl = () => {
|
|
1631
|
-
|
|
1635
|
+
Role$Reventless.freeze();
|
|
1636
|
+
let baseFragment = AppSync_Adapter$ReventlessAws.injectAwsAuthAll(Platform_AdminApi$ReventlessCore.baseFragment(false), Role$Reventless.adminGroup(), undefined);
|
|
1632
1637
|
return AppSync_SdlDecorate$ReventlessAws.stampCanonicalTypes(AppSync_Adapter$ReventlessAws.stitchStandaloneWithAwsDirectives(baseFragment));
|
|
1633
1638
|
};
|
|
1634
1639
|
let match;
|
|
@@ -2958,6 +2963,14 @@ function Make($star) {
|
|
|
2958
2963
|
};
|
|
2959
2964
|
}
|
|
2960
2965
|
|
|
2966
|
+
let roleGroups = Role$Reventless.setGroups;
|
|
2967
|
+
|
|
2968
|
+
let providedGroups = Role$Reventless.provideGroups;
|
|
2969
|
+
|
|
2970
|
+
let groupOf = Role$Reventless.groupOf;
|
|
2971
|
+
|
|
2972
|
+
let adminGroup = Role$Reventless.adminGroup;
|
|
2973
|
+
|
|
2961
2974
|
export {
|
|
2962
2975
|
log,
|
|
2963
2976
|
apiConfigRef,
|
|
@@ -2969,6 +2982,10 @@ export {
|
|
|
2969
2982
|
geocoderPlaceIndexRef,
|
|
2970
2983
|
messagingEmailSenderRef,
|
|
2971
2984
|
messagingEmailProviderRef,
|
|
2985
|
+
roleGroups,
|
|
2986
|
+
providedGroups,
|
|
2987
|
+
groupOf,
|
|
2988
|
+
adminGroup,
|
|
2972
2989
|
MakeWithConfig,
|
|
2973
2990
|
Make,
|
|
2974
2991
|
}
|
package/src/Platform_Stack.res
CHANGED
|
@@ -25,7 +25,7 @@ type cognitoUserPool = {
|
|
|
25
25
|
* with SPA-friendly defaults (email sign-in unless `platform:loginIdentifier`
|
|
26
26
|
* says otherwise, 12-char password policy, no MFA, and admin-only user
|
|
27
27
|
* creation unless `platform:signUpMode` says otherwise), plus the
|
|
28
|
-
* administrator group named by [Reventless.
|
|
28
|
+
* administrator group named by [Reventless.Role.adminGroup] and an elevated-groups
|
|
29
29
|
* default naming it. What it does **not** create is the first administrator:
|
|
30
30
|
* accounts are not stack resources, and a pool full of them is not something a
|
|
31
31
|
* `pulumi destroy` should be able to empty. `provision-admin` makes that one,
|
|
@@ -302,7 +302,7 @@ let _resolveUncached = (): cognitoUserPool => {
|
|
|
302
302
|
// An ordinary child resource: the pool is ours, so this needs no out-of-band
|
|
303
303
|
// call and Pulumi's state stays the authority for whether it exists.
|
|
304
304
|
let _adminGroup = Util_CognitoGroupUser.addUserGroup(
|
|
305
|
-
~name=Reventless.
|
|
305
|
+
~name=Reventless.Role.adminGroup(),
|
|
306
306
|
~userPoolId=pool.id,
|
|
307
307
|
)
|
|
308
308
|
|
|
@@ -400,7 +400,10 @@ let _resolveUncached = (): cognitoUserPool => {
|
|
|
400
400
|
// name is what a caller needs either way, and an output that appeared in one
|
|
401
401
|
// mode and not the other would read as "there is no administrator group here"
|
|
402
402
|
// rather than "this stack did not create it".
|
|
403
|
-
Pulumi.Pulumi.export(
|
|
403
|
+
Pulumi.Pulumi.export(
|
|
404
|
+
"identityProviderAdminGroup",
|
|
405
|
+
Pulumi.Output.make(Reventless.Role.adminGroup()),
|
|
406
|
+
)
|
|
404
407
|
|
|
405
408
|
// Exported so an operator can read back the store this provider resolved to —
|
|
406
409
|
// the name is derived, not configured, so this is a convenience for checking
|
|
@@ -5,7 +5,7 @@ import * as Pulumi$Pulumi from "@reventlessdev/rescript-pulumi-pulumi/src/Pulumi
|
|
|
5
5
|
import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
|
|
6
6
|
import * as Pulumi from "@pulumi/pulumi";
|
|
7
7
|
import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
|
|
8
|
-
import * as
|
|
8
|
+
import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
|
|
9
9
|
import * as Logger$ReventlessCore from "@reventlessdev/reventless-core/src/util/Logger.res.mjs";
|
|
10
10
|
import * as AWS_Tags$ReventlessAws from "./adapter/AWS_Tags.res.mjs";
|
|
11
11
|
import * as Auth_SignUpMode$ReventlessAws from "./adapter/Auth/Auth_SignUpMode.res.mjs";
|
|
@@ -133,7 +133,7 @@ function _resolveUncached() {
|
|
|
133
133
|
mfaConfiguration: "OFF",
|
|
134
134
|
tags: AWS_Tags$ReventlessAws.make("HostUiPool", "Platform", "Auth", "Platform", undefined, undefined, undefined, undefined)
|
|
135
135
|
});
|
|
136
|
-
Util_CognitoGroupUser$ReventlessAws.addUserGroup(
|
|
136
|
+
Util_CognitoGroupUser$ReventlessAws.addUserGroup(Role$Reventless.adminGroup(), pool.id);
|
|
137
137
|
let tokenUnits2_accessToken = "minutes";
|
|
138
138
|
let tokenUnits2_idToken = "minutes";
|
|
139
139
|
let tokenUnits2_refreshToken = "days";
|
|
@@ -181,7 +181,7 @@ function _resolveUncached() {
|
|
|
181
181
|
Pulumi$Pulumi.$$export("cognitoUserPoolArn", result.poolArn);
|
|
182
182
|
Pulumi$Pulumi.$$export("cognitoRegion", regionOutput);
|
|
183
183
|
Pulumi$Pulumi.$$export("cognitoUserPoolManaged", managedStr);
|
|
184
|
-
Pulumi$Pulumi.$$export("identityProviderAdminGroup", Pulumi.output(
|
|
184
|
+
Pulumi$Pulumi.$$export("identityProviderAdminGroup", Pulumi.output(Role$Reventless.adminGroup()));
|
|
185
185
|
Pulumi$Pulumi.$$export("activeRoleStore", result.activeRoleTable.name);
|
|
186
186
|
return result;
|
|
187
187
|
}
|
|
@@ -194,7 +194,7 @@ let make = (
|
|
|
194
194
|
let adminEntryJson =
|
|
195
195
|
ReventlessCore.Platform_ComponentDefinitionsApi.encodePluginStructureEntry(
|
|
196
196
|
~pluginId=ReventlessCore.Platform_Admin_Structure.pluginId,
|
|
197
|
-
ReventlessCore.Platform_Admin_Structure.structure,
|
|
197
|
+
ReventlessCore.Platform_Admin_Structure.structure(),
|
|
198
198
|
)->JSON.stringify
|
|
199
199
|
|
|
200
200
|
// Bundle reventless-aws (the compiled `_Ops` handler lives inside it) and
|
|
@@ -107,7 +107,7 @@ function make(api, pluginReadModelTableName, offloadBucketName, schemaReady, bak
|
|
|
107
107
|
encodeSelections(j.selections)
|
|
108
108
|
]
|
|
109
109
|
]))) : "";
|
|
110
|
-
let adminEntryJson = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry(Platform_Admin_Structure$ReventlessCore.pluginId, undefined, Platform_Admin_Structure$ReventlessCore.structure));
|
|
110
|
+
let adminEntryJson = JSON.stringify(Platform_ComponentDefinitionsApi$ReventlessCore.encodePluginStructureEntry(Platform_Admin_Structure$ReventlessCore.pluginId, undefined, Platform_Admin_Structure$ReventlessCore.structure()));
|
|
111
111
|
let packageDirs = Object.fromEntries([[
|
|
112
112
|
"@reventlessdev/reventless-aws",
|
|
113
113
|
Util_Bundle$ReventlessAws.resolvePackageRoot(undefined, "@reventlessdev/reventless-aws")
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// ([Auth_ActiveRoleTrigger.res] bundles it and attaches it to the user pool).
|
|
7
7
|
//
|
|
8
8
|
// The narrowing has to happen here rather than in our own enforcement code
|
|
9
|
-
// because `@authorize(
|
|
9
|
+
// because `@authorize(AllowRoles([...]))` compiles to a field directive AppSync
|
|
10
10
|
// evaluates against `cognito:groups` **before any of our code executes**. A
|
|
11
11
|
// request header we invent could scope reads correctly and still leave every
|
|
12
12
|
// group-gated mutation callable — right about the data, wrong about the writes.
|