@reventlessdev/reventless-aws 3.0.0-alpha.345 → 3.0.0-alpha.347

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 (112) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/package.json +19 -12
  3. package/rescript.json +2 -1
  4. package/run-bake-manifest.mjs +3 -0
  5. package/run-deploy-app.mjs +3 -0
  6. package/run-provision-accounts.mjs +3 -0
  7. package/run-provision-admin.mjs +3 -0
  8. package/scripts/BakeManifest.res +395 -0
  9. package/scripts/BakeManifest.res.mjs +487 -0
  10. package/scripts/DeployApp.res +708 -0
  11. package/scripts/DeployApp.res.mjs +1015 -0
  12. package/scripts/DeployManifest.res +84 -0
  13. package/scripts/DeployManifest.res.mjs +112 -0
  14. package/scripts/ProvisionAccounts.res +339 -0
  15. package/scripts/ProvisionAccounts.res.mjs +351 -0
  16. package/scripts/ProvisionAdmin.res +275 -0
  17. package/scripts/ProvisionAdmin.res.mjs +289 -0
  18. package/scripts/ProvisionCognito.res +149 -0
  19. package/scripts/ProvisionCognito.res.mjs +98 -0
  20. package/scripts/ProvisionIdentity.res +76 -11
  21. package/scripts/ProvisionIdentity.res.mjs +72 -7
  22. package/scripts/ProvisionProvider.res +104 -0
  23. package/scripts/ProvisionProvider.res.mjs +123 -0
  24. package/scripts/PulumiCli.res +43 -0
  25. package/scripts/PulumiCli.res.mjs +106 -0
  26. package/src/Platform.res +51 -4
  27. package/src/Platform.res.mjs +32 -14
  28. package/src/Platform_Stack.res +119 -9
  29. package/src/Platform_Stack.res.mjs +42 -4
  30. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res +1 -5
  31. package/src/adapter/Api/Platform_ComponentDefinitions_Lambda.res.mjs +1 -1
  32. package/src/adapter/Api/Platform_UIFragments_Lambda.res +1 -5
  33. package/src/adapter/Api/Platform_UIFragments_Lambda.res.mjs +1 -2
  34. package/src/adapter/Auth/Auth_ActiveRoleStore.res +1 -5
  35. package/src/adapter/Auth/Auth_ActiveRoleStore.res.mjs +1 -2
  36. package/src/adapter/Auth/Auth_ActiveRoleTrigger.res +1 -5
  37. package/src/adapter/Auth/Auth_ActiveRoleTrigger.res.mjs +1 -2
  38. package/src/adapter/Auth/Auth_LoginIdentifier.res +66 -0
  39. package/src/adapter/Auth/Auth_LoginIdentifier.res.mjs +70 -0
  40. package/src/adapter/Auth/Auth_SignUpMode.res +78 -0
  41. package/src/adapter/Auth/Auth_SignUpMode.res.mjs +56 -0
  42. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res +52 -31
  43. package/src/adapter/DcbEventLog/DcbEventLogStorage_DynamoDb_Runtime.res.mjs +75 -44
  44. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res +1 -5
  45. package/src/adapter/EventLogSubscription/EventLogSubscription_AppSync.res.mjs +1 -2
  46. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res +1 -5
  47. package/src/adapter/Geocoder/Geocoder_AwsLocation_Resolver.res.mjs +1 -2
  48. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res +5 -4
  49. package/src/adapter/Postgres/PgChangeFeedRelay_Runtime.res.mjs +1 -1
  50. package/src/adapter/Postgres/PgMigration_Builder.res +1 -1
  51. package/src/adapter/Postgres/PgMigration_Builder.res.mjs +1 -2
  52. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res +5 -0
  53. package/src/adapter/Runtime/AutomationSliceEntryPoint_Ops.res.mjs +5 -1
  54. package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res +2 -0
  55. package/src/adapter/Runtime/CommandGeneratorEntryPoint_Ops.res.mjs +2 -2
  56. package/src/adapter/Runtime/DcbCommandTopicEntryPoint.mjs +15 -6
  57. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res +51 -30
  58. package/src/adapter/Runtime/DcbCommandTopicEntryPoint_Ops.res.mjs +31 -14
  59. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res +1 -5
  60. package/src/adapter/Runtime/RuntimeEnvironment_Lambda.res.mjs +1 -1
  61. package/src/adapter/StateTopic/StateTopic_AppSync.res +1 -5
  62. package/src/adapter/StateTopic/StateTopic_AppSync.res.mjs +1 -1
  63. package/src/adapter/Task/TaskBucket_S3.res +1 -3
  64. package/src/adapter/Task/TaskBucket_S3.res.mjs +1 -1
  65. package/src/adapter/Upload/Upload_Claim_S3.res +1 -5
  66. package/src/adapter/Upload/Upload_Claim_S3.res.mjs +1 -1
  67. package/src/adapter/Upload/Upload_Presign_S3.res +1 -5
  68. package/src/adapter/Upload/Upload_Presign_S3.res.mjs +1 -2
  69. package/src/capability/Capability_Geocoding_AwsLocation.res +5 -1
  70. package/src/capability/Capability_Geocoding_AwsLocation.res.mjs +7 -1
  71. package/src/components/Api/AppSync_Adapter.res +56 -72
  72. package/src/components/Api/AppSync_Adapter.res.mjs +26 -60
  73. package/src/components/Api/AppSync_SdlDecorate.res +82 -1
  74. package/src/components/Api/AppSync_SdlDecorate.res.mjs +45 -0
  75. package/src/plugin/cloner/ClonerRunner_Fargate.res +1 -5
  76. package/src/plugin/cloner/ClonerRunner_Fargate.res.mjs +1 -1
  77. package/src/plugin/stack/Plugin_Stack.res +4 -0
  78. package/src/plugin/stack/Plugin_Stack.res.mjs +2 -0
  79. package/src/util/Util_DeadLetterQueue.res +1 -5
  80. package/src/util/Util_DeadLetterQueue.res.mjs +1 -2
  81. package/src/util/Util_StoreLayout.res +15 -2
  82. package/src/util/Util_StoreLayout.res.mjs +11 -3
  83. package/tests/AppSync_AdapterTest.res +83 -0
  84. package/tests/AppSync_AdapterTest.res.mjs +69 -0
  85. package/tests/Auth_LoginIdentifierTest.res +51 -0
  86. package/tests/Auth_LoginIdentifierTest.res.mjs +51 -0
  87. package/tests/Auth_SignUpModeTest.res +56 -0
  88. package/tests/Auth_SignUpModeTest.res.mjs +53 -0
  89. package/tests/BakeManifestTest.res +204 -0
  90. package/tests/BakeManifestTest.res.mjs +259 -0
  91. package/tests/DcbColdStartPartitionTest.res +32 -0
  92. package/tests/DcbColdStartPartitionTest.res.mjs +33 -0
  93. package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res +88 -0
  94. package/tests/DcbEventLogStorage_DynamoDb_RuntimeTest.res.mjs +112 -0
  95. package/tests/DcbUnresolvedPartitionSlice.res +28 -0
  96. package/tests/DcbUnresolvedPartitionSlice.res.mjs +38 -0
  97. package/tests/DeployAppTest.res +90 -0
  98. package/tests/DeployAppTest.res.mjs +133 -0
  99. package/tests/LambdaLayerLookupTest.res +104 -0
  100. package/tests/LambdaLayerLookupTest.res.mjs +117 -0
  101. package/tests/ProvisionAccountsTest.res +168 -0
  102. package/tests/ProvisionAccountsTest.res.mjs +184 -0
  103. package/tests/ProvisionAdminTest.res +55 -0
  104. package/tests/ProvisionAdminTest.res.mjs +66 -0
  105. package/tests/ProvisionIdentityTest.res +89 -3
  106. package/tests/ProvisionIdentityTest.res.mjs +66 -1
  107. package/tests/Util_OwnerScopeEnvTest.res +25 -0
  108. package/tests/Util_OwnerScopeEnvTest.res.mjs +13 -0
  109. package/tests/Util_StoreLayoutTest.res +6 -0
  110. package/tests/Util_StoreLayoutTest.res.mjs +7 -4
  111. package/tests/dcbColdStartPartition.mjs +66 -0
  112. package/tests/setup/layerArn.cjs +5 -0
@@ -1,10 +1,13 @@
1
1
  // Generated by ReScript, PLEASE EDIT WITH CARE
2
2
 
3
3
  import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+ import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
4
5
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
5
6
  import * as ClientDynamodb from "@aws-sdk/client-dynamodb";
6
7
  import * as DynamoDb_DynamoDb$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/DynamoDb_DynamoDb.res.mjs";
7
8
  import * as Util_AwsError$ReventlessAws from "../src/util/Util_AwsError.res.mjs";
9
+ import * as Auth_SignUpMode$ReventlessAws from "../src/adapter/Auth/Auth_SignUpMode.res.mjs";
10
+ import * as Auth_LoginIdentifier$ReventlessAws from "../src/adapter/Auth/Auth_LoginIdentifier.res.mjs";
8
11
  import * as CognitoIdentityServiceProvider$AwsSdk from "@reventlessdev/rescript-aws-sdk/src/CognitoIdentityServiceProvider.res.mjs";
9
12
  import * as ClientCognitoIdentityProvider from "@aws-sdk/client-cognito-identity-provider";
10
13
  import * as Auth_ActiveRoleStore_Schema$ReventlessAws from "../src/adapter/Auth/Auth_ActiveRoleStore_Schema.res.mjs";
@@ -17,6 +20,8 @@ function parseArgs(argv) {
17
20
  _0: {
18
21
  poolName: defaultPoolName,
19
22
  providerId: undefined,
23
+ loginIdentifier: Auth_LoginIdentifier$ReventlessAws.default,
24
+ signUpMode: Auth_SignUpMode$ReventlessAws.default,
20
25
  help: false
21
26
  }
22
27
  };
@@ -31,6 +36,20 @@ function parseArgs(argv) {
31
36
  let a = match._0;
32
37
  let exit$1 = 0;
33
38
  switch (flag) {
39
+ case "--login-identifier" :
40
+ if (value !== undefined) {
41
+ acc = Stdlib_Result.map(Auth_LoginIdentifier$ReventlessAws.parse(value), loginIdentifier => ({
42
+ poolName: a.poolName,
43
+ providerId: a.providerId,
44
+ loginIdentifier: loginIdentifier,
45
+ signUpMode: a.signUpMode,
46
+ help: a.help
47
+ }));
48
+ i = i + 2 | 0;
49
+ } else {
50
+ exit = 1;
51
+ }
52
+ break;
34
53
  case "--name" :
35
54
  if (value !== undefined) {
36
55
  acc = {
@@ -38,6 +57,8 @@ function parseArgs(argv) {
38
57
  _0: {
39
58
  poolName: value,
40
59
  providerId: a.providerId,
60
+ loginIdentifier: a.loginIdentifier,
61
+ signUpMode: a.signUpMode,
41
62
  help: a.help
42
63
  }
43
64
  };
@@ -53,6 +74,8 @@ function parseArgs(argv) {
53
74
  _0: {
54
75
  poolName: a.poolName,
55
76
  providerId: value,
77
+ loginIdentifier: a.loginIdentifier,
78
+ signUpMode: a.signUpMode,
56
79
  help: a.help
57
80
  }
58
81
  };
@@ -61,6 +84,20 @@ function parseArgs(argv) {
61
84
  exit = 1;
62
85
  }
63
86
  break;
87
+ case "--sign-up-mode" :
88
+ if (value !== undefined) {
89
+ acc = Stdlib_Result.map(Auth_SignUpMode$ReventlessAws.parse(value), signUpMode => ({
90
+ poolName: a.poolName,
91
+ providerId: a.providerId,
92
+ loginIdentifier: a.loginIdentifier,
93
+ signUpMode: signUpMode,
94
+ help: a.help
95
+ }));
96
+ i = i + 2 | 0;
97
+ } else {
98
+ exit = 1;
99
+ }
100
+ break;
64
101
  case "--help" :
65
102
  case "-h" :
66
103
  exit$1 = 2;
@@ -77,6 +114,8 @@ function parseArgs(argv) {
77
114
  _0: {
78
115
  poolName: a.poolName,
79
116
  providerId: a.providerId,
117
+ loginIdentifier: a.loginIdentifier,
118
+ signUpMode: a.signUpMode,
80
119
  help: true
81
120
  }
82
121
  };
@@ -95,21 +134,36 @@ function parseArgs(argv) {
95
134
  return acc;
96
135
  }
97
136
 
137
+ let _loginIdentifiers = Auth_LoginIdentifier$ReventlessAws.all.map(Auth_LoginIdentifier$ReventlessAws.toString).join(" | ");
138
+
139
+ let _defaultLoginIdentifier = Auth_LoginIdentifier$ReventlessAws.toString(Auth_LoginIdentifier$ReventlessAws.default);
140
+
141
+ let _signUpModes = Auth_SignUpMode$ReventlessAws.all.map(Auth_SignUpMode$ReventlessAws.toString).join(" | ");
142
+
143
+ let _defaultSignUpMode = Auth_SignUpMode$ReventlessAws.toString(Auth_SignUpMode$ReventlessAws.default);
144
+
98
145
  let usage = `
99
146
  Provision a Reventless identity provider and its active-role store.
100
147
 
101
148
  --name <name> Pool name to create or adopt (default: ` + defaultPoolName + `)
102
149
  --provider-id <id> Use this existing pool instead of creating one; only the
103
150
  store is provisioned
151
+ --login-identifier <a> Sign-in attribute for a pool this creates: ` + _loginIdentifiers + `
152
+ (default: ` + _defaultLoginIdentifier + `). Fixed at creation —
153
+ no later change is possible without replacing the pool
154
+ and losing every account in it.
155
+ --sign-up-mode <m> Whether a stranger may register themselves: ` + _signUpModes + `
156
+ (default: ` + _defaultSignUpMode + `). Correctable later, but on a
157
+ pool shared between stacks it applies to all of them.
104
158
 
105
159
  Creates nothing that a platform stack owns, and never attaches a trigger.
106
160
  Region and credentials come from the environment, as for any AWS SDK call.
107
161
  `;
108
162
 
109
- function poolSettings(poolName) {
163
+ function poolSettings(poolName, loginIdentifier, signUpMode) {
110
164
  return {
111
165
  PoolName: poolName,
112
- UsernameAttributes: ["email"],
166
+ UsernameAttributes: Auth_LoginIdentifier$ReventlessAws.usernameAttributes(loginIdentifier),
113
167
  MfaConfiguration: "OFF",
114
168
  Policies: {
115
169
  PasswordPolicy: {
@@ -120,7 +174,7 @@ function poolSettings(poolName) {
120
174
  }
121
175
  },
122
176
  AdminCreateUserConfig: {
123
- AllowAdminCreateUserOnly: true
177
+ AllowAdminCreateUserOnly: Auth_SignUpMode$ReventlessAws.allowAdminCreateUserOnly(signUpMode)
124
178
  }
125
179
  };
126
180
  }
@@ -194,10 +248,10 @@ async function resolvePool(args) {
194
248
  _0: existing
195
249
  };
196
250
  }
197
- let created = await CognitoIdentityServiceProvider$AwsSdk.CreateUserPoolCommand.send(new ClientCognitoIdentityProvider.CreateUserPoolCommand(poolSettings(args.poolName)));
251
+ let created = await CognitoIdentityServiceProvider$AwsSdk.CreateUserPoolCommand.send(new ClientCognitoIdentityProvider.CreateUserPoolCommand(poolSettings(args.poolName, args.loginIdentifier, args.signUpMode)));
198
252
  let id$1 = Stdlib_Option.flatMap(created.UserPool, p => p.Id);
199
253
  if (id$1 !== undefined) {
200
- console.log(`pool ` + id$1 + ` (created, named "` + args.poolName + `")`);
254
+ console.log(`pool ` + id$1 + ` (created, named "` + args.poolName + `", sign-in on ` + Auth_LoginIdentifier$ReventlessAws.toString(args.loginIdentifier) + `, sign-up ` + Auth_SignUpMode$ReventlessAws.toString(args.signUpMode) + `)`);
201
255
  return {
202
256
  TAG: "Ok",
203
257
  _0: id$1
@@ -305,7 +359,14 @@ or set REVENTLESS_IDENTITY_PROVIDER_ID in CI. Every stack on this provider
305
359
  derives the same store, so there is nothing else to configure.
306
360
 
307
361
  The pre-token-generation trigger is attached by the deploy, not by this script.
308
- Users and groups are yours to create.`;
362
+
363
+ Once a stack has deployed against this provider, make the first administrator:
364
+
365
+ pnpm exec provision-admin --provider-id ` + providerId + ` --email you@example.com
366
+
367
+ That is a separate bin because it provisions nothing a stack owns, and because it
368
+ is needed on a pool created here and on one a stack created for itself alike. See
369
+ [ProvisionAdmin].`;
309
370
  }
310
371
 
311
372
  async function run() {
@@ -361,6 +422,10 @@ export {
361
422
  Schema,
362
423
  defaultPoolName,
363
424
  parseArgs,
425
+ _loginIdentifiers,
426
+ _defaultLoginIdentifier,
427
+ _signUpModes,
428
+ _defaultSignUpMode,
364
429
  usage,
365
430
  poolSettings,
366
431
  findPoolByName,
@@ -373,4 +438,4 @@ export {
373
438
  run,
374
439
  main,
375
440
  }
376
- /* @aws-sdk/client-dynamodb Not a pure module */
441
+ /* _loginIdentifiers Not a pure module */
@@ -0,0 +1,104 @@
1
+ /***
2
+ Which identity provider a provisioning run is about.
3
+
4
+ Both bins ask the same question and neither should make the operator answer it
5
+ twice. The docs used to say `--provider-id $(pulumi stack output
6
+ identityProviderId)`, which is a command substitution a tool can perform.
7
+
8
+ 🚨 **Three sources, in this order, and the last is a fallback rather than the
9
+ path.** An explicit `--provider-id` wins, then `REVENTLESS_IDENTITY_PROVIDER_ID`
10
+ — the variable [Platform_Stack] reads for the same value, so a CI shell that
11
+ already exports it needs no second spelling — and only then the stack.
12
+
13
+ Reading the stack works in *both* modes, which is the fact that makes this worth
14
+ doing rather than a convenience for auto deployments only: [Platform_Stack]
15
+ exports `identityProviderId` whether it created the pool or was handed one, so a
16
+ BYO deployment re-exports the provider it was given.
17
+
18
+ 🚨 **It always reports which source answered, and which stack.** `pulumi stack
19
+ output` with no `--stack` reads whichever stack happens to be selected, so a run
20
+ against `alpha` while the operator believes they are on `beta` would succeed —
21
+ in the wrong pool, leaving working accounts somewhere nobody is looking. That is
22
+ worse than failing. So the stack is resolved by name, named in the output, and
23
+ overridable with `--stack`.
24
+ */
25
+
26
+ /** Where an id came from, so a run can say so rather than leave it inferred. */
27
+ type source =
28
+ | Flag
29
+ | Environment
30
+ | Stack(string)
31
+
32
+ let describe = (source: source): string =>
33
+ switch source {
34
+ | Flag => "--provider-id"
35
+ | Environment => "REVENTLESS_IDENTITY_PROVIDER_ID"
36
+ | Stack(stack) => `stack ${stack}`
37
+ }
38
+
39
+ /** The same variable [Platform_Stack] reads for the same value. */
40
+ let envKey = "REVENTLESS_IDENTITY_PROVIDER_ID"
41
+
42
+ /** `stdio` leaves stderr to the caller's terminal but keeps stdout, so a Pulumi
43
+ failure is legible where it happens instead of being swallowed and re-reported
44
+ as a vaguer message here. */
45
+ let pulumi = (args: array<string>): string =>
46
+ NodeChildProcess.execFileSync(
47
+ "pulumi",
48
+ args,
49
+ {encoding: "utf8", stdio: ["ignore", "pipe", "ignore"]},
50
+ )->String.trim
51
+
52
+ /** The stack the working directory currently has selected. `None` when there is
53
+ no Pulumi project here, no stack selected, or no `pulumi` on the PATH — all of
54
+ which are ordinary for someone running against a BYO pool, and none of which
55
+ should be an error until the id is actually needed. */
56
+ let selectedStack = (): option<string> =>
57
+ switch pulumi(["stack", "--show-name"]) {
58
+ | "" => None
59
+ | name => Some(name)
60
+ | exception _ => None
61
+ }
62
+
63
+ /**
64
+ The provider id a stack exports.
65
+
66
+ `--stack` is passed explicitly even when it names the already-selected stack, so
67
+ the value read and the name reported cannot drift apart between the two calls.
68
+ */
69
+ let fromStack = (~stack: string): result<string, string> =>
70
+ switch pulumi(["stack", "output", "identityProviderId", "--stack", stack]) {
71
+ | "" =>
72
+ Error(
73
+ `stack "${stack}" exports no identityProviderId — deploy the platform stack first, or pass --provider-id`,
74
+ )
75
+ | id => Ok(id)
76
+ | exception _ =>
77
+ Error(
78
+ `could not read identityProviderId from stack "${stack}" — is pulumi installed, logged in, and the stack deployed? Pass --provider-id to skip this lookup`,
79
+ )
80
+ }
81
+
82
+ /**
83
+ The provider this run is about, and where the answer came from.
84
+
85
+ The error names all three sources rather than only the one that was tried last:
86
+ someone who has no Pulumi project here needs to be told about the flag, and
87
+ someone who has one needs to be told it was consulted and came back empty.
88
+ */
89
+ let resolve = (~given: option<string>, ~stack: option<string>): result<(string, source), string> =>
90
+ switch given {
91
+ | Some(id) => Ok((id, Flag))
92
+ | None =>
93
+ switch NodeProcess.env->Dict.get(envKey) {
94
+ | Some(id) if id->String.trim != "" => Ok((id, Environment))
95
+ | _ =>
96
+ switch stack->Option.orElse(selectedStack()) {
97
+ | None =>
98
+ Error(
99
+ `--provider-id is required (or set ${envKey}). It is also read from a deployed stack, but no Pulumi stack is selected here — run this from the platform package, or pass --stack`,
100
+ )
101
+ | Some(stack) => fromStack(~stack)->Result.map(id => (id, Stack(stack)))
102
+ }
103
+ }
104
+ }
@@ -0,0 +1,123 @@
1
+ // Generated by ReScript, PLEASE EDIT WITH CARE
2
+
3
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
4
+ import * as Stdlib_Result from "@rescript/runtime/lib/es6/Stdlib_Result.js";
5
+ import * as Nodechild_process from "node:child_process";
6
+
7
+ function describe(source) {
8
+ if (typeof source !== "object") {
9
+ if (source === "Flag") {
10
+ return "--provider-id";
11
+ } else {
12
+ return "REVENTLESS_IDENTITY_PROVIDER_ID";
13
+ }
14
+ } else {
15
+ return `stack ` + source._0;
16
+ }
17
+ }
18
+
19
+ let envKey = "REVENTLESS_IDENTITY_PROVIDER_ID";
20
+
21
+ function pulumi(args) {
22
+ return Nodechild_process.execFileSync("pulumi", args, {
23
+ encoding: "utf8",
24
+ stdio: [
25
+ "ignore",
26
+ "pipe",
27
+ "ignore"
28
+ ]
29
+ }).trim();
30
+ }
31
+
32
+ function selectedStack() {
33
+ let name;
34
+ try {
35
+ name = pulumi([
36
+ "stack",
37
+ "--show-name"
38
+ ]);
39
+ } catch (exn) {
40
+ return;
41
+ }
42
+ if (name === "") {
43
+ return;
44
+ } else {
45
+ return name;
46
+ }
47
+ }
48
+
49
+ function fromStack(stack) {
50
+ let id;
51
+ try {
52
+ id = pulumi([
53
+ "stack",
54
+ "output",
55
+ "identityProviderId",
56
+ "--stack",
57
+ stack
58
+ ]);
59
+ } catch (exn) {
60
+ return {
61
+ TAG: "Error",
62
+ _0: `could not read identityProviderId from stack "` + stack + `" — is pulumi installed, logged in, and the stack deployed? Pass --provider-id to skip this lookup`
63
+ };
64
+ }
65
+ if (id === "") {
66
+ return {
67
+ TAG: "Error",
68
+ _0: `stack "` + stack + `" exports no identityProviderId — deploy the platform stack first, or pass --provider-id`
69
+ };
70
+ } else {
71
+ return {
72
+ TAG: "Ok",
73
+ _0: id
74
+ };
75
+ }
76
+ }
77
+
78
+ function resolve(given, stack) {
79
+ if (given !== undefined) {
80
+ return {
81
+ TAG: "Ok",
82
+ _0: [
83
+ given,
84
+ "Flag"
85
+ ]
86
+ };
87
+ }
88
+ let id = process.env[envKey];
89
+ if (id !== undefined && id.trim() !== "") {
90
+ return {
91
+ TAG: "Ok",
92
+ _0: [
93
+ id,
94
+ "Environment"
95
+ ]
96
+ };
97
+ }
98
+ let stack$1 = Stdlib_Option.orElse(stack, selectedStack());
99
+ if (stack$1 !== undefined) {
100
+ return Stdlib_Result.map(fromStack(stack$1), id => [
101
+ id,
102
+ {
103
+ TAG: "Stack",
104
+ _0: stack$1
105
+ }
106
+ ]);
107
+ } else {
108
+ return {
109
+ TAG: "Error",
110
+ _0: `--provider-id is required (or set ` + envKey + `). It is also read from a deployed stack, but no Pulumi stack is selected here — run this from the platform package, or pass --stack`
111
+ };
112
+ }
113
+ }
114
+
115
+ export {
116
+ describe,
117
+ envKey,
118
+ pulumi,
119
+ selectedStack,
120
+ fromStack,
121
+ resolve,
122
+ }
123
+ /* node:child_process Not a pure module */
@@ -0,0 +1,43 @@
1
+ /***
2
+ Reading deployed stacks through the `pulumi` CLI, so a command follows whatever
3
+ backend the CLI is logged into.
4
+
5
+ stderr goes to the caller's terminal: a Pulumi failure is legible where it
6
+ happens rather than re-reported here as a vaguer message.
7
+ */
8
+
9
+ let run = (~cwd: string, args: array<string>): string =>
10
+ NodeChildProcess.execFileSync(
11
+ "pulumi",
12
+ args->Array.concat(["--cwd", cwd, "--non-interactive"]),
13
+ {encoding: "utf8", stdio: ["ignore", "pipe", "inherit"], maxBuffer: 64 * 1024 * 1024},
14
+ )->String.trim
15
+
16
+ /** Whether the project in `dir` keeps settings for `stack` — how the deploy
17
+ workflow tells a stack that was never deployed from one that was. */
18
+ let hasStackFile = (~dir: string, ~stack: string): bool =>
19
+ NodeFs.existsSync(NodePath.join([dir, `Pulumi.${stack}.yaml`]))
20
+
21
+ let stackOutputs = (~dir: string, ~stack: string): result<dict<JSON.t>, string> =>
22
+ switch run(~cwd=dir, ["stack", "output", "--json", "--stack", stack]) {
23
+ | text =>
24
+ switch text->JSON.parseOrThrow->JSON.Decode.object {
25
+ | Some(outputs) => Ok(outputs)
26
+ | None => Error(`stack "${stack}" in ${dir} returned outputs that are not an object`)
27
+ | exception _ => Error(`stack "${stack}" in ${dir} returned outputs that are not JSON`)
28
+ }
29
+ | exception _ => Error(`could not read the outputs of stack "${stack}" in ${dir}`)
30
+ }
31
+
32
+ let selectedStack = (~dir: string): option<string> =>
33
+ switch run(~cwd=dir, ["stack", "--show-name"]) {
34
+ | "" => None
35
+ | name => Some(name)
36
+ | exception _ => None
37
+ }
38
+
39
+ let stringOutput = (outputs: dict<JSON.t>, name: string): option<string> =>
40
+ switch outputs->Dict.get(name)->Option.flatMap(JSON.Decode.string) {
41
+ | Some("") | None => None
42
+ | value => value
43
+ }
@@ -0,0 +1,106 @@
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_JSON from "@rescript/runtime/lib/es6/Stdlib_JSON.js";
6
+ import * as Stdlib_Option from "@rescript/runtime/lib/es6/Stdlib_Option.js";
7
+ import * as Nodechild_process from "node:child_process";
8
+
9
+ function run(cwd, args) {
10
+ return Nodechild_process.execFileSync("pulumi", args.concat([
11
+ "--cwd",
12
+ cwd,
13
+ "--non-interactive"
14
+ ]), {
15
+ encoding: "utf8",
16
+ stdio: [
17
+ "ignore",
18
+ "pipe",
19
+ "inherit"
20
+ ],
21
+ maxBuffer: 67108864
22
+ }).trim();
23
+ }
24
+
25
+ function hasStackFile(dir, stack) {
26
+ return Nodefs.existsSync(Nodepath.join(dir, `Pulumi.` + stack + `.yaml`));
27
+ }
28
+
29
+ function stackOutputs(dir, stack) {
30
+ let exit = 0;
31
+ let text;
32
+ try {
33
+ text = run(dir, [
34
+ "stack",
35
+ "output",
36
+ "--json",
37
+ "--stack",
38
+ stack
39
+ ]);
40
+ exit = 1;
41
+ } catch (exn) {
42
+ return {
43
+ TAG: "Error",
44
+ _0: `could not read the outputs of stack "` + stack + `" in ` + dir
45
+ };
46
+ }
47
+ if (exit === 1) {
48
+ let exit$1 = 0;
49
+ let outputs;
50
+ try {
51
+ outputs = Stdlib_JSON.Decode.object(JSON.parse(text));
52
+ exit$1 = 2;
53
+ } catch (exn$1) {
54
+ return {
55
+ TAG: "Error",
56
+ _0: `stack "` + stack + `" in ` + dir + ` returned outputs that are not JSON`
57
+ };
58
+ }
59
+ if (exit$1 === 2) {
60
+ if (outputs !== undefined) {
61
+ return {
62
+ TAG: "Ok",
63
+ _0: outputs
64
+ };
65
+ } else {
66
+ return {
67
+ TAG: "Error",
68
+ _0: `stack "` + stack + `" in ` + dir + ` returned outputs that are not an object`
69
+ };
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ function selectedStack(dir) {
76
+ let name;
77
+ try {
78
+ name = run(dir, [
79
+ "stack",
80
+ "--show-name"
81
+ ]);
82
+ } catch (exn) {
83
+ return;
84
+ }
85
+ if (name === "") {
86
+ return;
87
+ } else {
88
+ return name;
89
+ }
90
+ }
91
+
92
+ function stringOutput(outputs, name) {
93
+ let value = Stdlib_Option.flatMap(outputs[name], Stdlib_JSON.Decode.string);
94
+ if (value !== undefined && value !== "") {
95
+ return value;
96
+ }
97
+ }
98
+
99
+ export {
100
+ run,
101
+ hasStackFile,
102
+ stackOutputs,
103
+ selectedStack,
104
+ stringOutput,
105
+ }
106
+ /* node:fs Not a pure module */
package/src/Platform.res CHANGED
@@ -121,6 +121,31 @@ module MakeWithConfig = (
121
121
  ): (
122
122
  ReventlessInfra.Platform.T with type api = Types.AppSync.api and type role = Types.AppSync.role
123
123
  ) => {
124
+ // The administrator group reads across every owner, unless the deployment says
125
+ // otherwise.
126
+ //
127
+ // Authorization and owner scoping decide admin-ness independently and nothing
128
+ // checks that they agree: without this, a deployment gets an administrator who
129
+ // passes every authorization check and then reads *empty* owner-scoped views.
130
+ // `Util_OwnerScopeEnv.applyElevatedGroupsDefault` already carries whatever
131
+ // `OwnerScope.elevatedGroups()` holds into every Lambda's environment — the pipe
132
+ // was laid and nothing ever put a value in.
133
+ //
134
+ // 🚨 **Here, at the entry point, rather than beside the pool that declares the
135
+ // group.** The value has to be set before the runtime builder composes any
136
+ // Lambda's environment, and this functor's body runs when the functor is
137
+ // applied — necessarily before any plugin is built through it. Written into
138
+ // `Platform_Stack.resolveCognitoUserPool` it would instead depend on *which
139
+ // caller resolved the pool first*, which is precisely how a feature once
140
+ // vanished from a deploy with no error and no resource; see the note on
141
+ // `_resolveUncached`. Ordering that is structural beats ordering that happens
142
+ // to hold.
143
+ //
144
+ // A default, never a forcing: an explicit `setElevatedGroups` in a platform
145
+ // root — including one naming nobody — means what it says and wins. See
146
+ // [Reventless.OwnerScope.defaultElevatedGroups].
147
+ Reventless.OwnerScope.defaultElevatedGroups([Reventless.AdminGroup.name])
148
+
124
149
  // Dispatch per-flavor commandHandlerConfig records to the four runtime
125
150
  // builders. Every sub-record is optional; only branches the caller actually
126
151
  // supplied propagate, and within each sub-record every field is optional too
@@ -287,7 +312,7 @@ module MakeWithConfig = (
287
312
  assembleCanonicalSourceSdl(
288
313
  ~baseFragment=AppSync_Adapter.injectAwsAuthAll(
289
314
  ReventlessCore.Platform_AdminApi.baseFragment(~cloner=Config.cloner),
290
- ~group="Admin",
315
+ ~group=Reventless.AdminGroup.name,
291
316
  ),
292
317
  )
293
318
 
@@ -1467,7 +1492,7 @@ module MakeWithConfig = (
1467
1492
  // GETs from it to resolve an offloaded `structure`. Kept out of the declared
1468
1493
  // `@storageRef` stores and their pending-claim/expiry machinery: offload
1469
1494
  // objects are written durably up front and are never "pending".
1470
- let offloadProtection = Util_StoreLayout.protectionFor(~stack=Pulumi.Pulumi.getStackName())
1495
+ let offloadProtection = Util_StoreLayout.protectionOfDeployedStack()
1471
1496
  let offloadBucket = PulumiAws.S3.Bucket.make(
1472
1497
  ~name="reventless-offload",
1473
1498
  ~args={
@@ -1493,6 +1518,14 @@ module MakeWithConfig = (
1493
1518
  },
1494
1519
  )
1495
1520
  Pulumi.Pulumi.export("offloadBucket", offloadBucketName)
1521
+ // What a plugin stack's `platform:stack` names, fully qualified so it holds on
1522
+ // every backend. `deploy-app` reads it to point the plugin stacks it creates.
1523
+ Pulumi.Pulumi.export(
1524
+ "platformStack",
1525
+ Pulumi.Output.make(
1526
+ `${Pulumi.Pulumi.getOrganization()}/${Pulumi.Pulumi.getProjectName()}/${Pulumi.Pulumi.getStackName()}`,
1527
+ ),
1528
+ )
1496
1529
 
1497
1530
  // Mount the Platform_ComponentDefinitions Lambda resolver on the Platform API
1498
1531
  // (split mode) or Domain API (unified mode — platformApi == domainApi above).
@@ -1749,7 +1782,7 @@ module MakeWithConfig = (
1749
1782
  ~stack=stackName,
1750
1783
  ~prodStacks=Util_HostUiDomain.resolveProdStacks(),
1751
1784
  )
1752
- let storeProtection = Util_StoreLayout.protectionFor(~stack=stackName)
1785
+ let storeProtection = Util_StoreLayout.protectionOfDeployedStack()
1753
1786
 
1754
1787
  // Dedup by `(plugin, store)` — that pair is a store's identity, and many
1755
1788
  // fields legitimately name one store.
@@ -1759,7 +1792,8 @@ module MakeWithConfig = (
1759
1792
  switch c {
1760
1793
  | ObjectStore({plugin, store}) => Some((plugin, store))
1761
1794
  | Geocoding
1762
- | Messaging =>
1795
+ | Messaging
1796
+ | IdentityProvider =>
1763
1797
  None
1764
1798
  }
1765
1799
  )
@@ -2503,6 +2537,15 @@ module MakeWithConfig = (
2503
2537
  let startServers = () => ()
2504
2538
 
2505
2539
  let deployPlugin = (~plugin: module(PluginMaker), ~apiTarget=Domain) => {
2540
+ // Without the setting the plugin used to deploy as a platform of its own,
2541
+ // with no error, and never register with the real one.
2542
+ if platformStackRef->Option.isNone {
2543
+ JsError.throwWithMessage(
2544
+ `This plugin stack has no platform:stack setting, so it cannot find its platform. ` ++
2545
+ `Set it to the platform's stack: pulumi config set platform:stack ` ++
2546
+ `${Pulumi.Pulumi.getOrganization()}/<platform project>/${Pulumi.Pulumi.getStackName()}`,
2547
+ )
2548
+ }
2506
2549
  log.info(
2507
2550
  ~comp="Platform:deployPlugin",
2508
2551
  `target=${switch apiTarget {
@@ -2826,6 +2869,10 @@ module MakeWithConfig = (
2826
2869
  `as \`~messagingSender\`. The sender itself is configuration and has no ` ++
2827
2870
  `default: set \`platform:messagingEmailSender\` in Pulumi.<stack>.yaml (or ` ++
2828
2871
  `REVENTLESS_MESSAGING_EMAIL_SENDER), then verify the address with SES — or ` ++ `\`platform:messagingEmailProvider: log\` to log every message and send none.`
2872
+ | IdentityProvider =>
2873
+ `\n IdentityProvider has no backend on any platform yet — the seam and the ` ++
2874
+ `refusal exist, the Cognito and local implementations do not. A plugin ` ++
2875
+ `declaring this need cannot be deployed until one lands; nothing about this ` ++ `deployment's configuration will change that.`
2829
2876
  }
2830
2877
  )
2831
2878
  // One line per capability, not per declaring component: two slices