@aws-blocks/core 0.2.0 → 0.4.0

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 (178) hide show
  1. package/README.md +13 -0
  2. package/dist/api.d.ts.map +1 -1
  3. package/dist/api.js +32 -0
  4. package/dist/api.test.js +22 -0
  5. package/dist/bb-utils.d.ts +1 -0
  6. package/dist/bb-utils.d.ts.map +1 -1
  7. package/dist/bb-utils.js +3 -0
  8. package/dist/cdk/apigateway-account.d.ts +33 -0
  9. package/dist/cdk/apigateway-account.d.ts.map +1 -0
  10. package/dist/cdk/apigateway-account.js +60 -0
  11. package/dist/cdk/blocks-backend.d.ts +43 -9
  12. package/dist/cdk/blocks-backend.d.ts.map +1 -1
  13. package/dist/cdk/blocks-backend.js +69 -73
  14. package/dist/cdk/blocks-backend.test.js +78 -69
  15. package/dist/cdk/blocks-defaults.d.ts +92 -2
  16. package/dist/cdk/blocks-defaults.d.ts.map +1 -1
  17. package/dist/cdk/blocks-defaults.js +22 -2
  18. package/dist/cdk/blocks-defaults.test.js +21 -2
  19. package/dist/cdk/blocks-stack.test.js +94 -37
  20. package/dist/cdk/compute/compute-registry.d.ts +19 -0
  21. package/dist/cdk/compute/compute-registry.d.ts.map +1 -0
  22. package/dist/cdk/compute/compute-registry.js +38 -0
  23. package/dist/cdk/compute/compute.d.ts +2 -0
  24. package/dist/cdk/compute/compute.d.ts.map +1 -1
  25. package/dist/cdk/compute/compute.js +8 -0
  26. package/dist/cdk/compute/default-compute-factory.d.ts +34 -0
  27. package/dist/cdk/compute/default-compute-factory.d.ts.map +1 -0
  28. package/dist/cdk/compute/default-compute-factory.js +3 -0
  29. package/dist/cdk/config-registry.d.ts +34 -4
  30. package/dist/cdk/config-registry.d.ts.map +1 -1
  31. package/dist/cdk/config-registry.js +83 -25
  32. package/dist/cdk/config-registry.test.d.ts +2 -0
  33. package/dist/cdk/config-registry.test.d.ts.map +1 -0
  34. package/dist/cdk/config-registry.test.js +115 -0
  35. package/dist/cdk/index.d.ts +76 -7
  36. package/dist/cdk/index.d.ts.map +1 -1
  37. package/dist/cdk/index.js +99 -9
  38. package/dist/cdk/internal.d.ts +1 -0
  39. package/dist/cdk/internal.d.ts.map +1 -1
  40. package/dist/client/index.d.ts +1 -1
  41. package/dist/client/index.d.ts.map +1 -1
  42. package/dist/client/index.js +1 -1
  43. package/dist/common/config.d.ts +34 -0
  44. package/dist/common/config.d.ts.map +1 -1
  45. package/dist/common/config.js +45 -3
  46. package/dist/common/config.test.js +19 -0
  47. package/dist/common/index.d.ts +8 -0
  48. package/dist/common/index.d.ts.map +1 -1
  49. package/dist/errors.d.ts +16 -0
  50. package/dist/errors.d.ts.map +1 -1
  51. package/dist/errors.js +20 -0
  52. package/dist/hosting-secrets.d.ts +48 -0
  53. package/dist/hosting-secrets.d.ts.map +1 -0
  54. package/dist/hosting-secrets.js +57 -0
  55. package/dist/hosting-secrets.test.d.ts +2 -0
  56. package/dist/hosting-secrets.test.d.ts.map +1 -0
  57. package/dist/hosting-secrets.test.js +81 -0
  58. package/dist/hosting.d.ts +97 -6
  59. package/dist/hosting.d.ts.map +1 -1
  60. package/dist/hosting.js +91 -23
  61. package/dist/hosting.test.js +182 -10
  62. package/dist/index.cdk.d.ts +13 -12
  63. package/dist/index.cdk.d.ts.map +1 -1
  64. package/dist/index.cdk.js +13 -9
  65. package/dist/index.d.ts +8 -7
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +9 -5
  68. package/dist/lambda-handler.d.ts +17 -0
  69. package/dist/lambda-handler.d.ts.map +1 -1
  70. package/dist/lambda-handler.js +52 -3
  71. package/dist/lambda-handler.test.js +196 -1
  72. package/dist/pipeline/index.d.ts +24 -2
  73. package/dist/pipeline/index.d.ts.map +1 -1
  74. package/dist/pipeline/index.js +41 -1
  75. package/dist/pipeline.test.d.ts +2 -0
  76. package/dist/pipeline.test.d.ts.map +1 -0
  77. package/dist/pipeline.test.js +47 -0
  78. package/dist/rpc.d.ts +21 -0
  79. package/dist/rpc.d.ts.map +1 -1
  80. package/dist/rpc.js +34 -0
  81. package/dist/rpc.test.js +38 -1
  82. package/dist/scripts/config.d.ts +15 -0
  83. package/dist/scripts/config.d.ts.map +1 -0
  84. package/dist/scripts/config.js +29 -0
  85. package/dist/scripts/deploy.d.ts.map +1 -1
  86. package/dist/scripts/deploy.js +4 -0
  87. package/dist/scripts/dev-server.d.ts +8 -0
  88. package/dist/scripts/dev-server.d.ts.map +1 -1
  89. package/dist/scripts/dev-server.js +31 -0
  90. package/dist/scripts/generate-client.d.ts.map +1 -1
  91. package/dist/scripts/generate-client.js +9 -4
  92. package/dist/scripts/generate-client.test.js +19 -4
  93. package/dist/scripts/index.d.ts +13 -10
  94. package/dist/scripts/index.d.ts.map +1 -1
  95. package/dist/scripts/index.js +11 -8
  96. package/dist/scripts/preflight-credentials.d.ts +30 -0
  97. package/dist/scripts/preflight-credentials.d.ts.map +1 -0
  98. package/dist/scripts/preflight-credentials.js +108 -0
  99. package/dist/scripts/preflight-credentials.test.d.ts +2 -0
  100. package/dist/scripts/preflight-credentials.test.d.ts.map +1 -0
  101. package/dist/scripts/preflight-credentials.test.js +77 -0
  102. package/dist/scripts/sandbox-args.test.d.ts +2 -0
  103. package/dist/scripts/sandbox-args.test.d.ts.map +1 -0
  104. package/dist/scripts/sandbox-args.test.js +77 -0
  105. package/dist/scripts/sandbox-empty-buckets.test.d.ts +2 -0
  106. package/dist/scripts/sandbox-empty-buckets.test.d.ts.map +1 -0
  107. package/dist/scripts/sandbox-empty-buckets.test.js +171 -0
  108. package/dist/scripts/sandbox.d.ts +106 -0
  109. package/dist/scripts/sandbox.d.ts.map +1 -1
  110. package/dist/scripts/sandbox.js +255 -40
  111. package/dist/scripts/secret.d.ts +15 -0
  112. package/dist/scripts/secret.d.ts.map +1 -0
  113. package/dist/scripts/secret.js +29 -0
  114. package/dist/scripts/secret.test.d.ts +2 -0
  115. package/dist/scripts/secret.test.d.ts.map +1 -0
  116. package/dist/scripts/secret.test.js +28 -0
  117. package/dist/scripts/typegen.d.ts +15 -0
  118. package/dist/scripts/typegen.d.ts.map +1 -0
  119. package/dist/scripts/typegen.js +16 -0
  120. package/dist/secret-naming.d.ts +58 -0
  121. package/dist/secret-naming.d.ts.map +1 -0
  122. package/dist/secret-naming.js +67 -0
  123. package/dist/secret-naming.test.d.ts +2 -0
  124. package/dist/secret-naming.test.d.ts.map +1 -0
  125. package/dist/secret-naming.test.js +40 -0
  126. package/dist/version.d.ts +1 -1
  127. package/dist/version.js +1 -1
  128. package/package.json +4 -1
  129. package/src/api.test.ts +25 -0
  130. package/src/api.ts +39 -0
  131. package/src/bb-utils.ts +3 -0
  132. package/src/cdk/apigateway-account.ts +66 -0
  133. package/src/cdk/blocks-backend.test.ts +291 -272
  134. package/src/cdk/blocks-backend.ts +88 -83
  135. package/src/cdk/blocks-defaults.test.ts +25 -2
  136. package/src/cdk/blocks-defaults.ts +94 -2
  137. package/src/cdk/blocks-stack.test.ts +186 -118
  138. package/src/cdk/compute/compute-registry.ts +45 -0
  139. package/src/cdk/compute/compute.ts +10 -0
  140. package/src/cdk/compute/default-compute-factory.ts +38 -0
  141. package/src/cdk/config-registry.test.ts +135 -0
  142. package/src/cdk/config-registry.ts +92 -34
  143. package/src/cdk/index.ts +129 -10
  144. package/src/cdk/internal.ts +1 -0
  145. package/src/client/index.ts +1 -1
  146. package/src/common/config.test.ts +21 -0
  147. package/src/common/config.ts +47 -3
  148. package/src/common/index.ts +8 -0
  149. package/src/errors.ts +21 -0
  150. package/src/hosting-secrets.test.ts +106 -0
  151. package/src/hosting-secrets.ts +106 -0
  152. package/src/hosting.test.ts +272 -63
  153. package/src/hosting.ts +200 -68
  154. package/src/index.cdk.ts +80 -38
  155. package/src/index.ts +35 -16
  156. package/src/lambda-handler.test.ts +215 -1
  157. package/src/lambda-handler.ts +54 -2
  158. package/src/pipeline/index.ts +64 -11
  159. package/src/pipeline.test.ts +55 -0
  160. package/src/rpc.test.ts +44 -1
  161. package/src/rpc.ts +41 -0
  162. package/src/scripts/config.ts +35 -0
  163. package/src/scripts/deploy.ts +5 -0
  164. package/src/scripts/dev-server.ts +40 -0
  165. package/src/scripts/generate-client.test.ts +43 -8
  166. package/src/scripts/generate-client.ts +70 -65
  167. package/src/scripts/index.ts +20 -17
  168. package/src/scripts/preflight-credentials.test.ts +110 -0
  169. package/src/scripts/preflight-credentials.ts +131 -0
  170. package/src/scripts/sandbox-args.test.ts +92 -0
  171. package/src/scripts/sandbox-empty-buckets.test.ts +191 -0
  172. package/src/scripts/sandbox.ts +287 -39
  173. package/src/scripts/secret.test.ts +35 -0
  174. package/src/scripts/secret.ts +35 -0
  175. package/src/scripts/typegen.ts +18 -0
  176. package/src/secret-naming.test.ts +59 -0
  177. package/src/secret-naming.ts +99 -0
  178. package/src/version.ts +1 -1
@@ -0,0 +1,29 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * `blocks secret` — manage a Blocks app's **secrets** (AWS Secrets Manager),
5
+ * pinned to the Blocks `/blocks/<stackId>/secrets` namespace (scoped to the app's
6
+ * stable `stackId` from `.blocks/config.json`, so two apps in one account never
7
+ * collide). Thin wrapper over the shared `@aws-blocks/hosting` CLI with the kind
8
+ * fixed to `secret`. The `config` counterpart is `./config.ts`.
9
+ *
10
+ * @module
11
+ */
12
+ import { listValues, removeValue, runValueCli, setValue } from '@aws-blocks/hosting/scripts';
13
+ import { blocksSecretPrefix } from '../secret-naming.js';
14
+ /** Set (create or overwrite) a Blocks secret. */
15
+ export function setSecret(key, value, opts = {}) {
16
+ return setValue('secret', key, value, { prefix: blocksSecretPrefix(), ...opts });
17
+ }
18
+ /** List Blocks secret keys (names only). */
19
+ export function listSecrets(opts = {}) {
20
+ return listValues('secret', { prefix: blocksSecretPrefix(), ...opts });
21
+ }
22
+ /** Remove a Blocks secret. */
23
+ export function removeSecret(key, opts = {}) {
24
+ return removeValue('secret', key, { prefix: blocksSecretPrefix(), ...opts });
25
+ }
26
+ /** CLI dispatcher for `blocks secret <set|list|remove> …`. */
27
+ export function runSecretCli(argv) {
28
+ return runValueCli(argv, { kind: 'secret', prefix: blocksSecretPrefix(), label: 'blocks secret' });
29
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=secret.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret.test.d.ts","sourceRoot":"","sources":["../../src/scripts/secret.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import assert from 'node:assert';
4
+ import { describe, it } from 'node:test';
5
+ import { runConfigCli } from './config.js';
6
+ import { runSecretCli } from './secret.js';
7
+ // argv parsing + validation only (no live SSM/Secrets Manager). The set/list/
8
+ // remove SDK calls are exercised end-to-end during deploy verification.
9
+ void describe('blocks secret / config CLI argv parsing', () => {
10
+ void it('rejects an unknown subcommand', async () => {
11
+ await assert.rejects(runSecretCli(['frobnicate']), /Unknown subcommand/);
12
+ await assert.rejects(runConfigCli(['frobnicate']), /Unknown subcommand/);
13
+ });
14
+ void it('requires a key for set (label reflects the kind)', async () => {
15
+ await assert.rejects(runSecretCli(['set']), /Usage: blocks secret set/);
16
+ await assert.rejects(runConfigCli(['set']), /Usage: blocks config set/);
17
+ });
18
+ void it('key-only set falls through to the hidden prompt (TTY error, not usage)', async () => {
19
+ await assert.rejects(runSecretCli(['set', 'ONLY_KEY']), /stdin is not a TTY/);
20
+ });
21
+ void it('requires a key for remove', async () => {
22
+ await assert.rejects(runSecretCli(['remove']), /Usage: blocks secret remove/);
23
+ await assert.rejects(runConfigCli(['remove']), /Usage: blocks config remove/);
24
+ });
25
+ void it('validates the key before any store call', async () => {
26
+ await assert.rejects(runSecretCli(['set', '1bad', 'value']), /Invalid key/);
27
+ });
28
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `blocks typegen` — generate the type-safe key augmentation for `getSecret` /
3
+ * `getConfig` in a Blocks app. Statically scans the app's `secret('...')` /
4
+ * `config('...')` calls (no execution, no AWS credentials) and writes a `.d.ts`
5
+ * that narrows the runtime getters to your declared keys (autocomplete + typo
6
+ * errors) with no call-site change.
7
+ *
8
+ * Thin pass-through to the `@aws-blocks/hosting` engine — the getters live on
9
+ * `@aws-blocks/hosting`, so the generated file augments that module by default;
10
+ * a Blocks app needs no extra configuration.
11
+ *
12
+ * @module
13
+ */
14
+ export { runTypegenCli } from '@aws-blocks/hosting/scripts';
15
+ //# sourceMappingURL=typegen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typegen.d.ts","sourceRoot":"","sources":["../../src/scripts/typegen.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,16 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * `blocks typegen` — generate the type-safe key augmentation for `getSecret` /
5
+ * `getConfig` in a Blocks app. Statically scans the app's `secret('...')` /
6
+ * `config('...')` calls (no execution, no AWS credentials) and writes a `.d.ts`
7
+ * that narrows the runtime getters to your declared keys (autocomplete + typo
8
+ * errors) with no call-site change.
9
+ *
10
+ * Thin pass-through to the `@aws-blocks/hosting` engine — the getters live on
11
+ * `@aws-blocks/hosting`, so the generated file augments that module by default;
12
+ * a Blocks app needs no extra configuration.
13
+ *
14
+ * @module
15
+ */
16
+ export { runTypegenCli } from '@aws-blocks/hosting/scripts';
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Blocks-owned namespaces for hosting values.
3
+ *
4
+ * The mechanism (markers, resolvers, path/env naming) lives in the
5
+ * framework-neutral `@aws-blocks/hosting` package, which defaults to neutral
6
+ * `/hosting/secrets` and `/hosting/config` prefixes so a non-Blocks consumer
7
+ * never inherits Blocks branding.
8
+ *
9
+ * This module is the ONE place Blocks pins its own namespaces. The prefixes are
10
+ * **scoped to the app's stable `stackId`** (from the committed `.blocks/config.json`)
11
+ * — `/blocks/<stackId>/secrets` (Secrets Manager) and `/blocks/<stackId>/config`
12
+ * (SSM Parameter Store) — so two Blocks apps in one account/region never collide.
13
+ *
14
+ * Both sides derive the scope from the SAME source: the out-of-band CLI
15
+ * (`npm run secret`/`config`) and the CDK synth both call {@link blocksStoreConfig},
16
+ * which reads `stackId` via {@link getStackId}. When `.blocks/config.json` is absent
17
+ * (e.g. a bare unit test) both sides fall back to the unscoped `/blocks/secrets` /
18
+ * `/blocks/config` identically — so the CLI write and the deploy read can never
19
+ * diverge, whether the id is present or not. `stackId` is stage-independent
20
+ * (prod and sandbox share it); use the opt-in `stage` segment for per-stage values.
21
+ *
22
+ * @module
23
+ */
24
+ import type { StoreConfig } from '@aws-blocks/hosting/constructs';
25
+ /** Base Blocks prefix for `secret()` values (Secrets Manager); scoped by `stackId`. */
26
+ export declare const BLOCKS_SECRET_PARAMETER_PREFIX = "/blocks/secrets";
27
+ /** Base Blocks prefix for `config()` values (SSM Parameter Store); scoped by `stackId`. */
28
+ export declare const BLOCKS_CONFIG_PARAMETER_PREFIX = "/blocks/config";
29
+ /** The Blocks `secret()` prefix, scoped to `stackId` when available. */
30
+ export declare function blocksSecretPrefix(stackId?: string): string;
31
+ /** The Blocks `config()` prefix, scoped to `stackId` when available. */
32
+ export declare function blocksConfigPrefix(stackId?: string): string;
33
+ /**
34
+ * The **actual store name** of a Blocks `secret()` key in AWS Secrets Manager —
35
+ * routed through {@link secretStoreLocator} so it matches exactly what the CLI
36
+ * writes, the IAM grant scopes to, and the runtime resolver reads. Secrets Manager
37
+ * names are slash-free at the root, so this is `blocks/<stackId>/secrets/<KEY>`
38
+ * (no leading slash), NOT an SSM-style path. Pass `stackId` to compute it without
39
+ * reading `.blocks/config.json`.
40
+ *
41
+ * @example 'blocks/myapp/secrets/STRIPE_KEY'
42
+ */
43
+ export declare function blocksSecretParameterName(key: string, stackId?: string): string;
44
+ /**
45
+ * The **actual store name** of a Blocks `config()` key in SSM Parameter Store —
46
+ * routed through {@link secretStoreLocator} for consistency with the CLI/grant/
47
+ * runtime path. SSM keeps the leading-slash path.
48
+ *
49
+ * @example '/blocks/myapp/config/FEATURE_FLAGS'
50
+ */
51
+ export declare function blocksConfigParameterName(key: string, stackId?: string): string;
52
+ /**
53
+ * The Blocks store config passed to the shared hosting engine — `stackId`-scoped
54
+ * prefixes (falling back to the unscoped base when `.blocks/config.json` is absent).
55
+ * Both the CLI and the CDK synth call this, so they resolve identical names.
56
+ */
57
+ export declare function blocksStoreConfig(stackId?: string): StoreConfig;
58
+ //# sourceMappingURL=secret-naming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-naming.d.ts","sourceRoot":"","sources":["../src/secret-naming.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAIlE,uFAAuF;AACvF,eAAO,MAAM,8BAA8B,oBAAoB,CAAC;AAEhE,2FAA2F;AAC3F,eAAO,MAAM,8BAA8B,mBAAmB,CAAC;AAgB/D,wEAAwE;AACxE,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAG3D;AAED,wEAAwE;AACxE,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAG3D;AAED;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAE/E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CAK/D"}
@@ -0,0 +1,67 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { secretStoreLocator } from '@aws-blocks/hosting';
4
+ import { getStackId } from './scripts/stack-id.js';
5
+ /** Base Blocks prefix for `secret()` values (Secrets Manager); scoped by `stackId`. */
6
+ export const BLOCKS_SECRET_PARAMETER_PREFIX = '/blocks/secrets';
7
+ /** Base Blocks prefix for `config()` values (SSM Parameter Store); scoped by `stackId`. */
8
+ export const BLOCKS_CONFIG_PARAMETER_PREFIX = '/blocks/config';
9
+ /**
10
+ * Resolve the app's `stackId` (stable, stage-independent, from `.blocks/config.json`),
11
+ * or `undefined` when it can't be read. Never throws — a missing id means both the
12
+ * CLI and synth fall back to the unscoped base prefix, so they still agree.
13
+ */
14
+ function resolveStackId(explicit) {
15
+ if (explicit)
16
+ return explicit;
17
+ try {
18
+ return getStackId();
19
+ }
20
+ catch {
21
+ return undefined;
22
+ }
23
+ }
24
+ /** The Blocks `secret()` prefix, scoped to `stackId` when available. */
25
+ export function blocksSecretPrefix(stackId) {
26
+ const id = resolveStackId(stackId);
27
+ return id ? `/blocks/${id}/secrets` : BLOCKS_SECRET_PARAMETER_PREFIX;
28
+ }
29
+ /** The Blocks `config()` prefix, scoped to `stackId` when available. */
30
+ export function blocksConfigPrefix(stackId) {
31
+ const id = resolveStackId(stackId);
32
+ return id ? `/blocks/${id}/config` : BLOCKS_CONFIG_PARAMETER_PREFIX;
33
+ }
34
+ /**
35
+ * The **actual store name** of a Blocks `secret()` key in AWS Secrets Manager —
36
+ * routed through {@link secretStoreLocator} so it matches exactly what the CLI
37
+ * writes, the IAM grant scopes to, and the runtime resolver reads. Secrets Manager
38
+ * names are slash-free at the root, so this is `blocks/<stackId>/secrets/<KEY>`
39
+ * (no leading slash), NOT an SSM-style path. Pass `stackId` to compute it without
40
+ * reading `.blocks/config.json`.
41
+ *
42
+ * @example 'blocks/myapp/secrets/STRIPE_KEY'
43
+ */
44
+ export function blocksSecretParameterName(key, stackId) {
45
+ return secretStoreLocator(key, { prefix: blocksSecretPrefix(stackId), store: 'secrets-manager' });
46
+ }
47
+ /**
48
+ * The **actual store name** of a Blocks `config()` key in SSM Parameter Store —
49
+ * routed through {@link secretStoreLocator} for consistency with the CLI/grant/
50
+ * runtime path. SSM keeps the leading-slash path.
51
+ *
52
+ * @example '/blocks/myapp/config/FEATURE_FLAGS'
53
+ */
54
+ export function blocksConfigParameterName(key, stackId) {
55
+ return secretStoreLocator(key, { prefix: blocksConfigPrefix(stackId), store: 'ssm' });
56
+ }
57
+ /**
58
+ * The Blocks store config passed to the shared hosting engine — `stackId`-scoped
59
+ * prefixes (falling back to the unscoped base when `.blocks/config.json` is absent).
60
+ * Both the CLI and the CDK synth call this, so they resolve identical names.
61
+ */
62
+ export function blocksStoreConfig(stackId) {
63
+ return {
64
+ secretStore: { prefix: blocksSecretPrefix(stackId) },
65
+ configStore: { prefix: blocksConfigPrefix(stackId) },
66
+ };
67
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=secret-naming.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-naming.test.d.ts","sourceRoot":"","sources":["../src/secret-naming.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,40 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import assert from 'node:assert';
4
+ import { describe, it } from 'node:test';
5
+ import { secretStoreLocator } from '@aws-blocks/hosting';
6
+ import { BLOCKS_CONFIG_PARAMETER_PREFIX, BLOCKS_SECRET_PARAMETER_PREFIX, blocksConfigParameterName, blocksConfigPrefix, blocksSecretParameterName, blocksSecretPrefix, blocksStoreConfig, } from './secret-naming.js';
7
+ // These run with no `.blocks/config.json` in cwd, so they exercise the UNSCOPED
8
+ // fallback. A real Blocks app has the file, so it gets the `stackId`-scoped path
9
+ // (covered by the explicit-stackId cases below).
10
+ void describe('Blocks value namespaces — fallback (no .blocks/config.json)', () => {
11
+ void it('falls back to the unscoped /blocks/secrets and /blocks/config bases', () => {
12
+ assert.strictEqual(BLOCKS_SECRET_PARAMETER_PREFIX, '/blocks/secrets');
13
+ assert.strictEqual(BLOCKS_CONFIG_PARAMETER_PREFIX, '/blocks/config');
14
+ // Secrets Manager names are slash-free at the root; SSM keeps the leading slash.
15
+ assert.strictEqual(blocksSecretParameterName('STRIPE_KEY'), 'blocks/secrets/STRIPE_KEY');
16
+ assert.strictEqual(blocksConfigParameterName('FEATURE_FLAGS'), '/blocks/config/FEATURE_FLAGS');
17
+ assert.deepStrictEqual(blocksStoreConfig(), {
18
+ secretStore: { prefix: '/blocks/secrets' },
19
+ configStore: { prefix: '/blocks/config' },
20
+ });
21
+ });
22
+ void it('matches the store locator the CLI/grant/runtime actually use (no divergent name)', () => {
23
+ assert.strictEqual(blocksSecretParameterName('K'), secretStoreLocator('K', { prefix: BLOCKS_SECRET_PARAMETER_PREFIX, store: 'secrets-manager' }));
24
+ assert.strictEqual(blocksConfigParameterName('K'), secretStoreLocator('K', { prefix: BLOCKS_CONFIG_PARAMETER_PREFIX, store: 'ssm' }));
25
+ });
26
+ });
27
+ void describe('Blocks value namespaces — stackId-scoped (B5)', () => {
28
+ void it('scopes the prefix by stackId so two apps in one account never collide', () => {
29
+ assert.strictEqual(blocksSecretPrefix('myapp'), '/blocks/myapp/secrets');
30
+ assert.strictEqual(blocksConfigPrefix('myapp'), '/blocks/myapp/config');
31
+ assert.deepStrictEqual(blocksStoreConfig('myapp'), {
32
+ secretStore: { prefix: '/blocks/myapp/secrets' },
33
+ configStore: { prefix: '/blocks/myapp/config' },
34
+ });
35
+ });
36
+ void it('scoped store names match secretStoreLocator (SM slash-free, SSM leading-slash)', () => {
37
+ assert.strictEqual(blocksSecretParameterName('STRIPE_KEY', 'myapp'), 'blocks/myapp/secrets/STRIPE_KEY');
38
+ assert.strictEqual(blocksConfigParameterName('FEATURE_FLAGS', 'myapp'), '/blocks/myapp/config/FEATURE_FLAGS');
39
+ });
40
+ });
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const CORE_VERSION = "0.2.0";
1
+ export declare const CORE_VERSION = "0.4.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/generate-version.mjs — do not edit manually
2
- export const CORE_VERSION = '0.2.0';
2
+ export const CORE_VERSION = '0.4.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-blocks/core",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/aws-devtools-labs/aws-blocks.git",
@@ -52,6 +52,7 @@
52
52
  "require": "./dist/server/index.js"
53
53
  },
54
54
  "./scripts": "./dist/scripts/index.js",
55
+ "./scripts/generate-client-worker": "./dist/scripts/generate-client-worker.js",
55
56
  "./db-naming": {
56
57
  "types": "./dist/db-naming.d.ts",
57
58
  "default": "./dist/db-naming.js"
@@ -78,8 +79,10 @@
78
79
  "dependencies": {
79
80
  "@aws-blocks/hosting": "*",
80
81
  "@aws-blocks/pipeline": "*",
82
+ "@aws-sdk/client-cloudformation": "^3.700.0",
81
83
  "@aws-sdk/client-s3": "^3.700.0",
82
84
  "@aws-sdk/client-ssm": "^3.700.0",
85
+ "@aws-sdk/client-sts": "^3.700.0",
83
86
  "cross-spawn": "^7.0.6",
84
87
  "http-proxy": "^1.18.1"
85
88
  },
package/src/api.test.ts CHANGED
@@ -23,3 +23,28 @@ test('Different ApiNamespace names should not collide', () => {
23
23
  assert.strictEqual((api1 as any)[API_NAMESPACE_MARKER], 'api1');
24
24
  assert.strictEqual((api2 as any)[API_NAMESPACE_MARKER], 'api2');
25
25
  });
26
+
27
+ // ApiNamespace records its name on the scope's resolved compute (CDK
28
+ // synth), while staying a silent no-op where no compute is resolvable.
29
+ test('ApiNamespace records its name on a resolvable compute', () => {
30
+ const compute = { namespaces: [] as string[] };
31
+ new ApiNamespace({ id: 'app', compute } as never, 'myapi', () => ({ ping: () => 'ok' }));
32
+ assert.deepStrictEqual(compute.namespaces, ['myapi']);
33
+ });
34
+
35
+ test('ApiNamespace is a no-op recorder when the scope has no compute', () => {
36
+ // The common Scope (mock/runtime) has no `compute`; recording must not throw
37
+ // and the handler is still tagged and returned unchanged.
38
+ const handler = new ApiNamespace(new Scope('no-compute'), 'plainapi', () => ({ x: () => 1 }));
39
+ assert.strictEqual((handler as any)[API_NAMESPACE_MARKER], 'plainapi');
40
+ });
41
+
42
+ test('ApiNamespace records a namespace at most once per compute', () => {
43
+ // `namespaces` is a set of names: recording the same name twice (e.g. a
44
+ // re-imported module during synth) must not append a duplicate.
45
+ const compute = { namespaces: [] as string[] };
46
+ const scopeLike = { id: 'app', compute } as never;
47
+ new ApiNamespace(scopeLike, 'dup', () => ({ ping: () => 'ok' }));
48
+ new ApiNamespace(scopeLike, 'dup', () => ({ ping: () => 'ok' }));
49
+ assert.deepStrictEqual(compute.namespaces, ['dup']);
50
+ });
package/src/api.ts CHANGED
@@ -59,6 +59,42 @@ export const API_NAMESPACE_MARKER = Symbol.for('blocks:ApiNamespace');
59
59
 
60
60
  import type { ScopeParent } from './common/index.js';
61
61
 
62
+ /**
63
+ * Structural view of a scope that resolves a compute carrying a recordable
64
+ * namespace list. Only the CDK `Scope` satisfies this at runtime (via its
65
+ * `compute` getter); in the mock/runtime bundles the property is absent.
66
+ */
67
+ type ComputeResolvingScope = { compute?: { namespaces?: string[] } };
68
+
69
+ /**
70
+ * Record this namespace on its resolved compute so request routing can later
71
+ * map a namespace to the compute that hosts it.
72
+ *
73
+ * CDK-synth concern only: in the CDK bundle `scope.compute` resolves to the
74
+ * namespace's compute (the stack default today), and we append the name to its
75
+ * `namespaces` list. In the mock/runtime bundles — or a scopeless test — the
76
+ * property is absent, so `scope.compute` is `undefined` and this is a silent
77
+ * no-op. The public `ApiNamespace` signature is unchanged; this is a purely
78
+ * internal side effect.
79
+ *
80
+ * No `try/catch`: `BlocksBackend.create()` initializes the default compute
81
+ * before it imports the backend module that constructs any `ApiNamespace`, so
82
+ * the `compute` getter always resolves at this call site. If it ever threw
83
+ * here it would signal a genuine lifecycle violation (an `ApiNamespace` built
84
+ * before `create()` resolved), which should surface rather than be swallowed.
85
+ */
86
+ function recordNamespaceOnCompute(scope: ScopeParent | null | undefined, name: string): void {
87
+ // An API created without a Scope stays unrecorded and routes to the
88
+ // default compute.
89
+ if (!scope || typeof scope !== 'object') return;
90
+ const compute = (scope as ComputeResolvingScope).compute;
91
+ // Guard against duplicates so `namespaces` stays a set of names: a namespace
92
+ // could otherwise be recorded twice (e.g. a re-imported module during synth).
93
+ if (compute && Array.isArray(compute.namespaces) && !compute.namespaces.includes(name)) {
94
+ compute.namespaces.push(name);
95
+ }
96
+ }
97
+
62
98
  /**
63
99
  * Define a type-safe API namespace that works seamlessly between frontend and backend.
64
100
  *
@@ -157,6 +193,9 @@ export interface ApiNamespaceConstructor {
157
193
  export const ApiNamespace: ApiNamespaceConstructor = class ApiNamespace {
158
194
  constructor(scope: ScopeParent, name: string, handler: any) {
159
195
  handler[API_NAMESPACE_MARKER] = name;
196
+ // Record the namespace → compute association for per-compute routing.
197
+ // No-op outside CDK synth. Signature and returned handler are unchanged.
198
+ recordNamespaceOnCompute(scope, name);
160
199
  return handler;
161
200
  }
162
201
  } as any;
package/src/bb-utils.ts CHANGED
@@ -11,3 +11,6 @@ export { getMockDataDir } from './common/mock-data.js';
11
11
  export { API_NAMESPACE_MARKER } from './api.js';
12
12
  export { EventSourceMapping } from './lambda-handler.js';
13
13
  export { constantTimeEquals } from './common/crypto.js';
14
+ // Defined in the config module (it owns the config-key contract), re-exported
15
+ // here so BB authors get it alongside the other BB utilities.
16
+ export { sanitizeConfigKey } from './common/config.js';
@@ -0,0 +1,66 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import type * as cdk from 'aws-cdk-lib';
5
+ import { CfnAccount } from 'aws-cdk-lib/aws-apigateway';
6
+ import { ManagedPolicy, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
7
+
8
+ /**
9
+ * Global registry key for the per-stack API Gateway CloudWatch Logs account
10
+ * resource. Deliberately a `Symbol.for(...)` string key so the SAME symbol is
11
+ * shared across packages (core, hosting) — each package can carry its own copy
12
+ * of {@link ensureApiGatewayAccount} yet still find and reuse the one account a
13
+ * sibling package already created on the stack, instead of emitting a second
14
+ * `AWS::ApiGateway::Account` (which is account/region-level and would clash).
15
+ */
16
+ const ACCOUNT_KEY = Symbol.for('BLOCKS_APIGATEWAY_ACCOUNT');
17
+
18
+ /**
19
+ * Ensure the account-level API Gateway CloudWatch Logs role exists on `stack`,
20
+ * creating it at most once per stack.
21
+ *
22
+ * API Gateway access logging — for both REST (v1) and WebSocket (v2) stages —
23
+ * requires an `AWS::ApiGateway::Account` whose `cloudWatchRoleArn` points at a
24
+ * role trusted by `apigateway.amazonaws.com` and carrying
25
+ * `AmazonAPIGatewayPushToCloudWatchLogs`. Nothing else in a Blocks stack
26
+ * provisions it, so a clean-account first deploy of any access-logging stage
27
+ * otherwise fails at CreateStage with "CloudWatch Logs role ARN must be set in
28
+ * account settings to enable logging".
29
+ *
30
+ * Callers should add their stage's dependency on the returned account so the
31
+ * account setting is applied before the stage is created.
32
+ *
33
+ * ⚠️ Single-Blocks-stack-per-region assumption. `AWS::ApiGateway::Account` is an
34
+ * account/region-level singleton (one effective `cloudWatchRoleArn` per region),
35
+ * but this provisions one per Blocks stack. If two Blocks stacks in the same
36
+ * account+region both enable access logging:
37
+ * - on deploy, the later stack's role wins (overwrites the account setting); and
38
+ * - on teardown of that later stack, its role is deleted and the account
39
+ * setting is left pointing at a now-deleted role — silently breaking access
40
+ * logging (or blocking `CreateStage`) for the surviving stack until it
41
+ * redeploys.
42
+ * Enabling `accessLogging` is therefore safe for one Blocks stack per region.
43
+ * Multi-stack support (a shared/imported account) is deferred; if you run
44
+ * multiple Blocks stacks per region, manage the account role out-of-band and
45
+ * leave `accessLogging` to a single owner.
46
+ */
47
+ export function ensureApiGatewayAccount(stack: cdk.Stack): CfnAccount {
48
+ const existing = (stack as unknown as Record<symbol, CfnAccount | undefined>)[ACCOUNT_KEY];
49
+ if (existing) return existing;
50
+
51
+ const role = new Role(stack, 'BlocksApiGatewayCloudWatchRole', {
52
+ assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
53
+ managedPolicies: [
54
+ ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonAPIGatewayPushToCloudWatchLogs'),
55
+ ],
56
+ });
57
+
58
+ // `cloudWatchRoleArn: role.roleArn` already gives CDK an implicit dependency
59
+ // on the role, so no explicit node.addDependency is needed.
60
+ const account = new CfnAccount(stack, 'BlocksApiGatewayAccount', {
61
+ cloudWatchRoleArn: role.roleArn,
62
+ });
63
+
64
+ (stack as unknown as Record<symbol, CfnAccount | undefined>)[ACCOUNT_KEY] = account;
65
+ return account;
66
+ }