@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.
Files changed (39) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/package.json +9 -8
  3. package/run-resolve-environment.mjs +3 -0
  4. package/scripts/ProvisionAdmin.res +2 -2
  5. package/scripts/ProvisionAdmin.res.mjs +7 -6
  6. package/scripts/ResolveEnvironment.res +135 -0
  7. package/scripts/ResolveEnvironment.res.mjs +148 -0
  8. package/src/Platform.res +32 -3
  9. package/src/Platform.res.mjs +22 -5
  10. package/src/Platform_Stack.res +6 -3
  11. package/src/Platform_Stack.res.mjs +3 -3
  12. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +1 -1
  13. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +1 -1
  14. package/src/adapter/Auth/Auth_ActiveRoleTrigger_Ops.res +1 -1
  15. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res +1 -1
  16. package/src/adapter/Runtime/PgQueryResolverEntryPoint_Ops.res.mjs +2 -1
  17. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +1 -0
  18. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +1 -0
  19. package/src/components/Api/AppSync_Adapter.res +4 -3
  20. package/src/components/Api/AppSync_SdlDecorate.res +7 -3
  21. package/src/components/Api/AppSync_SdlDecorate.res.mjs +4 -3
  22. package/src/util/Util_OwnerScopeEnv.res +9 -0
  23. package/src/util/Util_OwnerScopeEnv.res.mjs +14 -1
  24. package/src/util/Util_ShellConfig.res +4 -0
  25. package/src/util/Util_ShellConfig.res.mjs +5 -1
  26. package/tests/AppSync_AdapterTest.res +46 -23
  27. package/tests/AppSync_AdapterTest.res.mjs +35 -27
  28. package/tests/PgPipeline_IntegrationTest.res +2 -1
  29. package/tests/PgQueryDbUnion_IntegrationTest.res +2 -1
  30. package/tests/ResolveEnvironmentTest.res +91 -0
  31. package/tests/ResolveEnvironmentTest.res.mjs +128 -0
  32. package/tests/Util_OwnerScopeEnvTest.res +48 -2
  33. package/tests/Util_OwnerScopeEnvTest.res.mjs +49 -3
  34. package/tests/Util_ShellConfigTest.res +17 -0
  35. package/tests/Util_ShellConfigTest.res.mjs +18 -0
  36. package/tests/integration/AggTestAggregate.res +2 -1
  37. package/tests/integration/EpCompositeSlice.res +2 -1
  38. package/tests/integration/EpTestSlice.res +2 -1
  39. package/tests/integration/QueryDbUnion_DynamoDb_IntegrationTest.res +2 -1
@@ -0,0 +1,128 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
4
+ import * as Stdlib_JsError from "@rescript/runtime/lib/es6/Stdlib_JsError.js";
5
+ import * as Environments$Reventless from "@reventlessdev/reventless-spec/src/types/Environments.res.mjs";
6
+ import * as ResolveEnvironment$ReventlessAws from "../scripts/ResolveEnvironment.res.mjs";
7
+
8
+ let environments = Environments$Reventless.parseString(`environments:
9
+ - { name: review, purpose: review, pullRequestsInto: dev }
10
+ - { name: dev, purpose: dev, branch: dev }
11
+ - { name: production, purpose: production, branch: main, tags: "v*", stack: prod }
12
+ `);
13
+
14
+ let environments$1;
15
+
16
+ environments$1 = environments.TAG === "Ok" ? environments._0 : Stdlib_JsError.throwWithMessage(environments._0);
17
+
18
+ globalThis.describe("ResolveEnvironment.parseArgs", () => {
19
+ globalThis.test("a ref asks about a push", () => {
20
+ globalThis.expect(Stdlib_Result.map(ResolveEnvironment$ReventlessAws.parseArgs([
21
+ "--ref",
22
+ "refs/heads/dev"
23
+ ]), a => a.request)).toEqual({
24
+ TAG: "Ok",
25
+ _0: {
26
+ TAG: "Push",
27
+ _0: "refs/heads/dev"
28
+ }
29
+ });
30
+ });
31
+ globalThis.test("a pull request needs the branch it targets", () => {
32
+ globalThis.expect(Stdlib_Result.isError(ResolveEnvironment$ReventlessAws.parseArgs([
33
+ "--pull-request",
34
+ "7"
35
+ ]))).toBe(true);
36
+ });
37
+ globalThis.test("a pull request with its base and draft state", () => {
38
+ globalThis.expect(Stdlib_Result.map(ResolveEnvironment$ReventlessAws.parseArgs([
39
+ "--pull-request",
40
+ "7",
41
+ "--base",
42
+ "dev",
43
+ "--draft"
44
+ ]), a => a.request)).toEqual({
45
+ TAG: "Ok",
46
+ _0: {
47
+ TAG: "PullRequest",
48
+ number: 7,
49
+ base: "dev",
50
+ draft: true
51
+ }
52
+ });
53
+ });
54
+ globalThis.test("a pull request number that is not a number is refused", () => {
55
+ globalThis.expect(Stdlib_Result.isError(ResolveEnvironment$ReventlessAws.parseArgs([
56
+ "--pull-request",
57
+ "seven",
58
+ "--base",
59
+ "dev"
60
+ ]))).toBe(true);
61
+ });
62
+ globalThis.test("both questions at once are refused", () => {
63
+ globalThis.expect(Stdlib_Result.isError(ResolveEnvironment$ReventlessAws.parseArgs([
64
+ "--ref",
65
+ "refs/heads/dev",
66
+ "--pull-request",
67
+ "7",
68
+ "--base",
69
+ "dev"
70
+ ]))).toBe(true);
71
+ });
72
+ globalThis.test("no question is refused", () => {
73
+ globalThis.expect(Stdlib_Result.isError(ResolveEnvironment$ReventlessAws.parseArgs([]))).toBe(true);
74
+ });
75
+ });
76
+
77
+ globalThis.describe("ResolveEnvironment.answer", () => {
78
+ globalThis.test("a push to a branch prints its environment, stack and purpose", () => {
79
+ globalThis.expect(ResolveEnvironment$ReventlessAws.answer(environments$1, {
80
+ TAG: "Push",
81
+ _0: "refs/heads/main"
82
+ })).toEqual([
83
+ "matched=true",
84
+ "environment=production",
85
+ "stack=prod",
86
+ "purpose=production"
87
+ ]);
88
+ });
89
+ globalThis.test("a tag deploys where its pattern says", () => {
90
+ globalThis.expect(ResolveEnvironment$ReventlessAws.answer(environments$1, {
91
+ TAG: "Push",
92
+ _0: "refs/tags/v1.2.0"
93
+ })[2]).toEqual("stack=prod");
94
+ });
95
+ globalThis.test("a pull request into dev gets its review stack", () => {
96
+ globalThis.expect(ResolveEnvironment$ReventlessAws.answer(environments$1, {
97
+ TAG: "PullRequest",
98
+ number: 7,
99
+ base: "dev",
100
+ draft: false
101
+ })).toEqual([
102
+ "matched=true",
103
+ "environment=review",
104
+ "stack=review-pr-7",
105
+ "purpose=review"
106
+ ]);
107
+ });
108
+ globalThis.test("nothing matched is an answer, not an error", () => {
109
+ globalThis.expect(ResolveEnvironment$ReventlessAws.answer(environments$1, {
110
+ TAG: "Push",
111
+ _0: "refs/heads/feature/x"
112
+ })).toEqual(["matched=false"]);
113
+ });
114
+ globalThis.test("a ref that is neither a branch nor a tag matches nothing", () => {
115
+ globalThis.expect(ResolveEnvironment$ReventlessAws.answer(environments$1, {
116
+ TAG: "Push",
117
+ _0: "refs/pull/7/merge"
118
+ })).toEqual(["matched=false"]);
119
+ });
120
+ });
121
+
122
+ let Resolve;
123
+
124
+ export {
125
+ Resolve,
126
+ environments$1 as environments,
127
+ }
128
+ /* environments Not a pure module */
@@ -68,7 +68,7 @@ describe("Util_OwnerScopeEnv — the elevated-groups carrier", () => {
68
68
  testSync("the platform's administrator default reaches a composed environment", () => {
69
69
  Reventless.OwnerScope.clearElevatedGroups()
70
70
  NodeProcess.env->Dict.delete(Util_OwnerScopeEnv.key)
71
- Reventless.OwnerScope.defaultElevatedGroups([Reventless.AdminGroup.name])
71
+ Reventless.OwnerScope.defaultElevatedRoles([Reventless.Role.admin])
72
72
 
73
73
  let variables = Dict.make()
74
74
  Util_OwnerScopeEnv.applyElevatedGroupsDefault(variables)
@@ -81,7 +81,53 @@ describe("Util_OwnerScopeEnv — the elevated-groups carrier", () => {
81
81
  // Round-tripped, like the first case above: the runtime reads this back with
82
82
  // `parseElevatedGroups`, so the encoding is asserted rather than the spelling.
83
83
  expect(Reventless.OwnerScope.parseElevatedGroups(encoded))->toEqual([
84
- Reventless.AdminGroup.name,
84
+ Reventless.Role.adminGroup(),
85
85
  ])
86
86
  })
87
+
88
+ // Runtimes read groups, never roles, so an elevated role travels as the group
89
+ // the deploy program mapped it to.
90
+ testSync("an elevated role travels as the group it is mapped to", () => {
91
+ Reventless.OwnerScope.clearElevatedGroups()
92
+ Reventless.Role.clearGroups()
93
+ NodeProcess.env->Dict.delete(Util_OwnerScopeEnv.key)
94
+ Reventless.Role.setGroups([(Reventless.Role.make("Fulfilment"), "shop-ops")])
95
+ Reventless.OwnerScope.setElevatedRoles([Reventless.Role.make("Fulfilment")])
96
+ Reventless.OwnerScope.setElevatedGroups(["Admin"])
97
+
98
+ let variables = Dict.make()
99
+ Util_OwnerScopeEnv.applyElevatedGroupsDefault(variables)
100
+ let encoded = switch variables->Dict.get(Util_OwnerScopeEnv.key) {
101
+ | Some(value) => value->Obj.magic
102
+ | None => ""
103
+ }
104
+ Reventless.OwnerScope.clearElevatedGroups()
105
+ Reventless.Role.clearGroups()
106
+
107
+ expect(Reventless.OwnerScope.parseElevatedGroups(encoded))->toEqual(["Admin", "shop-ops"])
108
+ })
109
+ })
110
+
111
+ describe("Util_OwnerScopeEnv.applyRoleGroupsDefault", () => {
112
+ testSync("carries the renamed roles, and only those", () => {
113
+ Reventless.Role.clearGroups()
114
+ Reventless.Role.setGroups([(Reventless.Role.make("Merchandiser"), "shop-merch-team")])
115
+ let variables = Dict.make()
116
+ Util_OwnerScopeEnv.applyRoleGroupsDefault(variables)
117
+ Reventless.Role.clearGroups()
118
+ let encoded = switch variables->Dict.get(Reventless.Role.envKey) {
119
+ | Some(value) => value->Obj.magic
120
+ | None => ""
121
+ }
122
+ expect(Reventless.Role.parseGroups(encoded))->toEqual([
123
+ (Reventless.Role.make("Merchandiser"), "shop-merch-team"),
124
+ ])
125
+ })
126
+
127
+ testSync("adds nothing when no role is renamed", () => {
128
+ Reventless.Role.clearGroups()
129
+ let variables = Dict.make()
130
+ Util_OwnerScopeEnv.applyRoleGroupsDefault(variables)
131
+ expect(variables->Dict.get(Reventless.Role.envKey)->Option.isNone)->toBe(true)
132
+ })
87
133
  })
@@ -2,7 +2,7 @@
2
2
 
3
3
  import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
4
4
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
5
- import * as AdminGroup$Reventless from "@reventlessdev/reventless-spec/src/types/AdminGroup.res.mjs";
5
+ import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
6
6
  import * as OwnerScope$Reventless from "@reventlessdev/reventless-spec/src/types/OwnerScope.res.mjs";
7
7
  import * as Util_OwnerScopeEnv$ReventlessAws from "../src/util/Util_OwnerScopeEnv.res.mjs";
8
8
 
@@ -59,13 +59,59 @@ globalThis.describe("Util_OwnerScopeEnv — the elevated-groups carrier", () =>
59
59
  globalThis.test("the platform's administrator default reaches a composed environment", () => {
60
60
  OwnerScope$Reventless.clearElevatedGroups();
61
61
  Stdlib_Dict.$$delete(process.env, Util_OwnerScopeEnv$ReventlessAws.key);
62
- OwnerScope$Reventless.defaultElevatedGroups([AdminGroup$Reventless.name]);
62
+ OwnerScope$Reventless.defaultElevatedRoles([Role$Reventless.admin]);
63
63
  let variables = {};
64
64
  Util_OwnerScopeEnv$ReventlessAws.applyElevatedGroupsDefault(variables);
65
65
  let value = variables[Util_OwnerScopeEnv$ReventlessAws.key];
66
66
  let encoded = value !== undefined ? value : "";
67
67
  OwnerScope$Reventless.clearElevatedGroups();
68
- globalThis.expect(OwnerScope$Reventless.parseElevatedGroups(encoded)).toEqual([AdminGroup$Reventless.name]);
68
+ globalThis.expect(OwnerScope$Reventless.parseElevatedGroups(encoded)).toEqual([Role$Reventless.adminGroup()]);
69
+ });
70
+ globalThis.test("an elevated role travels as the group it is mapped to", () => {
71
+ OwnerScope$Reventless.clearElevatedGroups();
72
+ Role$Reventless.clearGroups();
73
+ Stdlib_Dict.$$delete(process.env, Util_OwnerScopeEnv$ReventlessAws.key);
74
+ Role$Reventless.setGroups([[
75
+ "Fulfilment",
76
+ "shop-ops"
77
+ ]]);
78
+ OwnerScope$Reventless.setElevatedRoles(["Fulfilment"]);
79
+ OwnerScope$Reventless.setElevatedGroups(["Admin"]);
80
+ let variables = {};
81
+ Util_OwnerScopeEnv$ReventlessAws.applyElevatedGroupsDefault(variables);
82
+ let value = variables[Util_OwnerScopeEnv$ReventlessAws.key];
83
+ let encoded = value !== undefined ? value : "";
84
+ OwnerScope$Reventless.clearElevatedGroups();
85
+ Role$Reventless.clearGroups();
86
+ globalThis.expect(OwnerScope$Reventless.parseElevatedGroups(encoded)).toEqual([
87
+ "Admin",
88
+ "shop-ops"
89
+ ]);
90
+ });
91
+ });
92
+
93
+ globalThis.describe("Util_OwnerScopeEnv.applyRoleGroupsDefault", () => {
94
+ globalThis.test("carries the renamed roles, and only those", () => {
95
+ Role$Reventless.clearGroups();
96
+ Role$Reventless.setGroups([[
97
+ "Merchandiser",
98
+ "shop-merch-team"
99
+ ]]);
100
+ let variables = {};
101
+ Util_OwnerScopeEnv$ReventlessAws.applyRoleGroupsDefault(variables);
102
+ Role$Reventless.clearGroups();
103
+ let value = variables[Role$Reventless.envKey];
104
+ let encoded = value !== undefined ? value : "";
105
+ globalThis.expect(Role$Reventless.parseGroups(encoded)).toEqual([[
106
+ "Merchandiser",
107
+ "shop-merch-team"
108
+ ]]);
109
+ });
110
+ globalThis.test("adds nothing when no role is renamed", () => {
111
+ Role$Reventless.clearGroups();
112
+ let variables = {};
113
+ Util_OwnerScopeEnv$ReventlessAws.applyRoleGroupsDefault(variables);
114
+ globalThis.expect(Stdlib_Option.isNone(variables[Role$Reventless.envKey])).toBe(true);
69
115
  });
70
116
  });
71
117
 
@@ -328,3 +328,20 @@ describe("the subscription endpoint", () => {
328
328
  )
329
329
  )
330
330
  })
331
+
332
+ // The shell gates its admin discovery on this group; a mirror of the server's
333
+ // own resolution, so a renamed administrator role reaches the browser too.
334
+ describe("Util_ShellConfig.fields — adminGroup", () => {
335
+ testSync("is absent while the administrator role keeps its own name", () => {
336
+ Reventless.Role.clearGroups()
337
+ expect(Util_ShellConfig.fields(~computed)->Dict.get("adminGroup")->Option.isNone)->toBe(true)
338
+ })
339
+
340
+ testSync("carries the group a renamed administrator role maps to", () => {
341
+ Reventless.Role.clearGroups()
342
+ Reventless.Role.setGroups([(Reventless.Role.admin, "shop-admins")])
343
+ let out = Util_ShellConfig.fields(~computed)
344
+ Reventless.Role.clearGroups()
345
+ expect(out->get("adminGroup"))->toEqual(JSON.Encode.string("shop-admins"))
346
+ })
347
+ })
@@ -4,6 +4,7 @@ import * as JestGlobals from "@reventlessdev/rescript-jest/src/JestGlobals.res.m
4
4
  import * as Stdlib_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
5
5
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
6
6
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
+ import * as Role$Reventless from "@reventlessdev/reventless-spec/src/types/Role.res.mjs";
7
8
  import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
8
9
  import * as Primitive_string from "@rescript/runtime/lib/es6/Primitive_string.js";
9
10
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
@@ -341,6 +342,23 @@ globalThis.describe("the subscription endpoint", () => {
341
342
  });
342
343
  });
343
344
 
345
+ globalThis.describe("Util_ShellConfig.fields — adminGroup", () => {
346
+ globalThis.test("is absent while the administrator role keeps its own name", () => {
347
+ Role$Reventless.clearGroups();
348
+ globalThis.expect(Stdlib_Option.isNone(Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, undefined, undefined)["adminGroup"])).toBe(true);
349
+ });
350
+ globalThis.test("carries the group a renamed administrator role maps to", () => {
351
+ Role$Reventless.clearGroups();
352
+ Role$Reventless.setGroups([[
353
+ Role$Reventless.admin,
354
+ "shop-admins"
355
+ ]]);
356
+ let out = Util_ShellConfig$ReventlessAws.fields(computed, undefined, undefined, undefined, undefined);
357
+ Role$Reventless.clearGroups();
358
+ globalThis.expect(get(out, "adminGroup")).toEqual("shop-admins");
359
+ });
360
+ });
361
+
344
362
  export {
345
363
  computed,
346
364
  get,
@@ -18,6 +18,7 @@ type error = AlreadyExists
18
18
 
19
19
  let name = "AggTestAggregate"
20
20
  let moduleUrl = "agg-test://AggTestAggregate"
21
- let commandAuthorization = (_: command): Reventless.Authorization.permission => AllowAnonymous
21
+ type role = Reventless.Role.name
22
+ let commandAuthorization = (_: command): Reventless.Authorization.rule<role> => AllowAnonymous
22
23
  type lifecycleState = unit
23
24
  let commandTransition = (_: command): Reventless.Transition.t<lifecycleState> => Unrestricted
@@ -63,7 +63,8 @@ type event =
63
63
 
64
64
  let name = "EpCompositeSlice"
65
65
  let moduleUrl = "ep-test://EpCompositeSlice"
66
- let commandAuthorization = (_: command): Reventless.Authorization.permission => AllowAnonymous
66
+ type role = Reventless.Role.name
67
+ let commandAuthorization = (_: command): Reventless.Authorization.rule<role> => AllowAnonymous
67
68
  type lifecycleState = unit
68
69
  let commandTransition = (_: command): Reventless.Transition.t<lifecycleState> => Unrestricted
69
70
  let readConsistency = Reventless.ReadConsistency.EscalateOnRetry
@@ -21,7 +21,8 @@ type event = WidgetAdded({widgetId: @s.matches(Reventless.DcbTag.partition) stri
21
21
 
22
22
  let name = "EpTestSlice"
23
23
  let moduleUrl = "ep-test://EpTestSlice"
24
- let commandAuthorization = (_: command): Reventless.Authorization.permission => AllowAnonymous
24
+ type role = Reventless.Role.name
25
+ let commandAuthorization = (_: command): Reventless.Authorization.rule<role> => AllowAnonymous
25
26
  type lifecycleState = unit
26
27
  let commandTransition = (_: command): Reventless.Transition.t<lifecycleState> => Unrestricted
27
28
  let readConsistency = Reventless.ReadConsistency.EscalateOnRetry
@@ -32,7 +32,8 @@ module CustomersSpec = {
32
32
 
33
33
  let config = Reventless.ReadModel.config()
34
34
  let subIdConfig = None
35
- let authorization: Reventless.Authorization.permission = AllowAuthenticated
35
+ type role = Reventless.Role.name
36
+ let authorization: Reventless.Authorization.rule<role> = AllowAuthenticated
36
37
  let visibility: Reventless.Visibility.t = Public
37
38
  }
38
39