@aws-blocks/core 0.1.18 → 0.3.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.
- package/dist/cdk/__fixtures__/import-meta-handler.d.ts +6 -0
- package/dist/cdk/__fixtures__/import-meta-handler.d.ts.map +1 -0
- package/dist/cdk/__fixtures__/import-meta-handler.js +13 -0
- package/dist/cdk/blocks-backend.d.ts +46 -9
- package/dist/cdk/blocks-backend.d.ts.map +1 -1
- package/dist/cdk/blocks-backend.js +66 -67
- package/dist/cdk/blocks-backend.test.js +114 -56
- package/dist/cdk/blocks-defaults.d.ts +70 -0
- package/dist/cdk/blocks-defaults.d.ts.map +1 -0
- package/dist/cdk/blocks-defaults.js +27 -0
- package/dist/cdk/blocks-defaults.test.d.ts +2 -0
- package/dist/cdk/blocks-defaults.test.d.ts.map +1 -0
- package/dist/cdk/blocks-defaults.test.js +24 -0
- package/dist/cdk/blocks-stack.test.js +74 -31
- package/dist/cdk/bundling.d.ts +42 -0
- package/dist/cdk/bundling.d.ts.map +1 -0
- package/dist/cdk/bundling.js +72 -0
- package/dist/cdk/bundling.test.d.ts +2 -0
- package/dist/cdk/bundling.test.d.ts.map +1 -0
- package/dist/cdk/bundling.test.js +80 -0
- package/dist/cdk/compute/compute.d.ts +33 -0
- package/dist/cdk/compute/compute.d.ts.map +1 -0
- package/dist/cdk/compute/compute.js +28 -0
- package/dist/cdk/compute/default-compute-factory.d.ts +33 -0
- package/dist/cdk/compute/default-compute-factory.d.ts.map +1 -0
- package/dist/cdk/compute/default-compute-factory.js +3 -0
- package/dist/cdk/index.d.ts +92 -12
- package/dist/cdk/index.d.ts.map +1 -1
- package/dist/cdk/index.js +141 -25
- package/dist/cdk/internal.d.ts +25 -0
- package/dist/cdk/internal.d.ts.map +1 -0
- package/dist/cdk/internal.js +27 -0
- package/dist/cdk/mixins.d.ts +18 -4
- package/dist/cdk/mixins.d.ts.map +1 -1
- package/dist/cdk/mixins.js +31 -7
- package/dist/cdk/mixins.test.js +45 -0
- package/dist/common/index.d.ts +9 -0
- package/dist/common/index.d.ts.map +1 -1
- package/dist/hosting-secrets.d.ts +48 -0
- package/dist/hosting-secrets.d.ts.map +1 -0
- package/dist/hosting-secrets.js +57 -0
- package/dist/hosting-secrets.test.d.ts +2 -0
- package/dist/hosting-secrets.test.d.ts.map +1 -0
- package/dist/hosting-secrets.test.js +81 -0
- package/dist/hosting.d.ts +88 -6
- package/dist/hosting.d.ts.map +1 -1
- package/dist/hosting.js +83 -24
- package/dist/hosting.test.js +158 -10
- package/dist/index.cdk.d.ts +13 -11
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +13 -8
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- package/dist/lambda-handler.test.js +66 -0
- package/dist/pipeline/index.d.ts +24 -2
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/index.js +41 -1
- package/dist/pipeline.test.d.ts +2 -0
- package/dist/pipeline.test.d.ts.map +1 -0
- package/dist/pipeline.test.js +47 -0
- package/dist/rpc.d.ts +21 -0
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +46 -3
- package/dist/rpc.test.js +58 -1
- package/dist/scripts/config.d.ts +15 -0
- package/dist/scripts/config.d.ts.map +1 -0
- package/dist/scripts/config.js +29 -0
- package/dist/scripts/deploy.d.ts.map +1 -1
- package/dist/scripts/deploy.js +4 -0
- package/dist/scripts/dev-server.d.ts +8 -0
- package/dist/scripts/dev-server.d.ts.map +1 -1
- package/dist/scripts/dev-server.js +31 -0
- package/dist/scripts/generate-client.d.ts.map +1 -1
- package/dist/scripts/generate-client.js +9 -4
- package/dist/scripts/generate-client.test.js +19 -4
- package/dist/scripts/index.d.ts +13 -10
- package/dist/scripts/index.d.ts.map +1 -1
- package/dist/scripts/index.js +11 -8
- package/dist/scripts/preflight-credentials.d.ts +30 -0
- package/dist/scripts/preflight-credentials.d.ts.map +1 -0
- package/dist/scripts/preflight-credentials.js +108 -0
- package/dist/scripts/preflight-credentials.test.d.ts +2 -0
- package/dist/scripts/preflight-credentials.test.d.ts.map +1 -0
- package/dist/scripts/preflight-credentials.test.js +77 -0
- package/dist/scripts/sandbox-args.test.d.ts +2 -0
- package/dist/scripts/sandbox-args.test.d.ts.map +1 -0
- package/dist/scripts/sandbox-args.test.js +77 -0
- package/dist/scripts/sandbox.d.ts +52 -0
- package/dist/scripts/sandbox.d.ts.map +1 -1
- package/dist/scripts/sandbox.js +92 -15
- package/dist/scripts/secret.d.ts +15 -0
- package/dist/scripts/secret.d.ts.map +1 -0
- package/dist/scripts/secret.js +29 -0
- package/dist/scripts/secret.test.d.ts +2 -0
- package/dist/scripts/secret.test.d.ts.map +1 -0
- package/dist/scripts/secret.test.js +28 -0
- package/dist/scripts/telemetry.test.js +2 -0
- package/dist/scripts/typegen.d.ts +15 -0
- package/dist/scripts/typegen.d.ts.map +1 -0
- package/dist/scripts/typegen.js +16 -0
- package/dist/secret-naming.d.ts +58 -0
- package/dist/secret-naming.d.ts.map +1 -0
- package/dist/secret-naming.js +67 -0
- package/dist/secret-naming.test.d.ts +2 -0
- package/dist/secret-naming.test.d.ts.map +1 -0
- package/dist/secret-naming.test.js +40 -0
- package/dist/telemetry/environment.d.ts.map +1 -1
- package/dist/telemetry/environment.js +3 -1
- package/dist/telemetry/telemetry.test.js +10 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -1
- package/src/cdk/__fixtures__/import-meta-handler.ts +16 -0
- package/src/cdk/blocks-backend.test.ts +308 -237
- package/src/cdk/blocks-backend.ts +98 -77
- package/src/cdk/blocks-defaults.test.ts +27 -0
- package/src/cdk/blocks-defaults.ts +75 -0
- package/src/cdk/blocks-stack.test.ts +160 -110
- package/src/cdk/bundling.test.ts +90 -0
- package/src/cdk/bundling.ts +76 -0
- package/src/cdk/compute/compute.ts +37 -0
- package/src/cdk/compute/default-compute-factory.ts +37 -0
- package/src/cdk/index.ts +178 -27
- package/src/cdk/internal.ts +30 -0
- package/src/cdk/mixins.test.ts +56 -1
- package/src/cdk/mixins.ts +32 -7
- package/src/common/index.ts +9 -0
- package/src/hosting-secrets.test.ts +106 -0
- package/src/hosting-secrets.ts +106 -0
- package/src/hosting.test.ts +244 -63
- package/src/hosting.ts +180 -68
- package/src/index.cdk.ts +79 -36
- package/src/index.ts +35 -16
- package/src/lambda-handler.test.ts +74 -0
- package/src/pipeline/index.ts +64 -11
- package/src/pipeline.test.ts +55 -0
- package/src/rpc.test.ts +66 -1
- package/src/rpc.ts +60 -3
- package/src/scripts/config.ts +35 -0
- package/src/scripts/deploy.ts +5 -0
- package/src/scripts/dev-server.ts +40 -0
- package/src/scripts/generate-client.test.ts +43 -8
- package/src/scripts/generate-client.ts +70 -65
- package/src/scripts/index.ts +20 -17
- package/src/scripts/preflight-credentials.test.ts +110 -0
- package/src/scripts/preflight-credentials.ts +131 -0
- package/src/scripts/sandbox-args.test.ts +92 -0
- package/src/scripts/sandbox.ts +102 -15
- package/src/scripts/secret.test.ts +35 -0
- package/src/scripts/secret.ts +35 -0
- package/src/scripts/telemetry.test.ts +2 -0
- package/src/scripts/typegen.ts +18 -0
- package/src/secret-naming.test.ts +59 -0
- package/src/secret-naming.ts +99 -0
- package/src/telemetry/environment.ts +3 -1
- package/src/telemetry/telemetry.test.ts +12 -0
- package/src/version.ts +1 -1
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
4
|
import * as cdk from 'aws-cdk-lib';
|
|
5
|
-
import * as
|
|
6
|
-
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
|
|
5
|
+
import type * as apigateway from 'aws-cdk-lib/aws-apigateway';
|
|
7
6
|
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
8
7
|
import { CfnGroup } from 'aws-cdk-lib/aws-resourcegroups';
|
|
9
8
|
import { Construct } from 'constructs';
|
|
10
9
|
import { pathToFileURL } from 'node:url';
|
|
11
|
-
import { DEFAULT_NODE_RUNTIME } from './node-version.js';
|
|
12
10
|
import { addBlocksStackMetadata } from './stack-metadata.js';
|
|
13
11
|
import { finalizeConfigRegistry, registerConfig } from './config-registry.js';
|
|
14
|
-
import {
|
|
12
|
+
import type { BlocksDefaults } from './blocks-defaults.js';
|
|
15
13
|
import { registerBuiltinRoutes } from '../builtin-routes.js';
|
|
14
|
+
import type { Compute } from './compute/compute.js';
|
|
15
|
+
import type { DefaultComputeFactory, LambdaShapedCompute } from './compute/default-compute-factory.js';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Validate that the Node.js process was started with `--conditions=cdk`.
|
|
@@ -42,21 +42,68 @@ export function assertCdkConditionActive(): void {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Timeout of the shared Blocks handler Lambda, and therefore the ceiling on a
|
|
47
|
+
* single invocation. Exported because resources that feed the handler have to
|
|
48
|
+
* size their own timeouts against it (e.g. an SQS queue's visibility timeout).
|
|
49
|
+
*/
|
|
50
|
+
export const SHARED_HANDLER_TIMEOUT_SECONDS = 60 * 15;
|
|
51
|
+
|
|
45
52
|
export interface BlocksBackendProps {
|
|
46
53
|
backendHandlerPath: string;
|
|
47
54
|
backendCDKPath: string;
|
|
55
|
+
/**
|
|
56
|
+
* Stack-wide infrastructure defaults applied to every Building Block (removal
|
|
57
|
+
* policy, deletion protection, …). See {@link BlocksDefaults}. Start from
|
|
58
|
+
* `BlocksPresets.sandbox` or `BlocksPresets.production` and override
|
|
59
|
+
* individual fields as needed. A per-block option always wins over the
|
|
60
|
+
* corresponding stack default.
|
|
61
|
+
*/
|
|
62
|
+
defaults: BlocksDefaults;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Core's `create()` props: the public {@link BlocksBackendProps} plus the
|
|
67
|
+
* required `defaultComputeFactory`, spread on by the umbrella (`@aws-blocks/blocks`).
|
|
68
|
+
* Customers use {@link BlocksBackendProps} and never set the factory.
|
|
69
|
+
*
|
|
70
|
+
* @internal
|
|
71
|
+
*/
|
|
72
|
+
export interface CoreBlocksBackendProps extends BlocksBackendProps {
|
|
73
|
+
/** Builds the backend's default compute. Injected by `@aws-blocks/blocks`. */
|
|
74
|
+
defaultComputeFactory: DefaultComputeFactory;
|
|
48
75
|
}
|
|
49
76
|
|
|
50
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Shared infra setup — provisions the stack-level resources that are NOT owned
|
|
79
|
+
* by a compute: the shared execution role, resource groups, and console-redirect
|
|
80
|
+
* routes.
|
|
81
|
+
*/
|
|
51
82
|
export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id?: string) {
|
|
52
|
-
//
|
|
83
|
+
// Fail fast with an actionable message at the create() call site if `defaults`
|
|
84
|
+
// is missing (e.g. a plain-JS caller, `as any`, or a dynamically-built props
|
|
85
|
+
// object) — otherwise the first Building Block to read `scope.defaults` throws
|
|
86
|
+
// a cryptic `Cannot read properties of undefined (reading 'removalPolicy')`.
|
|
87
|
+
if (!props.defaults) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
'BlocksStack/BlocksBackend requires a `defaults` field. Pass a posture from ' +
|
|
90
|
+
'`@aws-blocks/core/cdk` — typically `defaults: sandboxMode ? BlocksPresets.sandbox : BlocksPresets.production`.',
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ── Shared execution role ───────────────────────────────────────────────
|
|
53
95
|
// A single IAM role that every Building Block grants to. Provisioned here so
|
|
54
96
|
// it exists before the backend module is imported (Building Blocks reach it
|
|
55
97
|
// via `scope.executionRole`). Block grants sit on the role's default (inline)
|
|
56
|
-
// policy
|
|
98
|
+
// policy. AWSLambdaBasicExecutionRole is attached so compute functions retain
|
|
99
|
+
// CloudWatch Logs permissions.
|
|
57
100
|
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
101
|
+
// INVARIANT: this must be a mutable, framework-owned `iam.Role` — never an
|
|
102
|
+
// imported role (`Role.fromRoleArn`/`fromRoleName`), which is immutable by
|
|
103
|
+
// default. On an immutable role, every Building Block's `grant*()` /
|
|
104
|
+
// `addToPrincipalPolicy()` silently becomes a no-op (returns false, no error),
|
|
105
|
+
// so permissions would quietly vanish. If a bring-your-own-role option is ever
|
|
106
|
+
// added, it must resolve to a mutable role (`{ mutable: true }`).
|
|
60
107
|
const executionRole = new iam.Role(scope, 'BlocksRole', {
|
|
61
108
|
// CompositePrincipal (rather than a bare ServicePrincipal) so additional
|
|
62
109
|
// compute types can assume this same shared role as they are introduced
|
|
@@ -67,60 +114,7 @@ export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id
|
|
|
67
114
|
],
|
|
68
115
|
});
|
|
69
116
|
|
|
70
|
-
|
|
71
|
-
entry: props.backendHandlerPath,
|
|
72
|
-
runtime: DEFAULT_NODE_RUNTIME,
|
|
73
|
-
handler: 'handler',
|
|
74
|
-
role: executionRole,
|
|
75
|
-
memorySize: 2048,
|
|
76
|
-
timeout: cdk.Duration.seconds(60 * 15),
|
|
77
|
-
environment: {
|
|
78
|
-
NODE_ENV: 'production',
|
|
79
|
-
/**
|
|
80
|
-
* BLOCKS_STACK_NAME is used at runtime to derive physical resource names
|
|
81
|
-
* (DynamoDB table names, env var prefixes). It must match the CDK-time
|
|
82
|
-
* fullId of the BlocksStack/BlocksBackend so resource lookups work correctly.
|
|
83
|
-
*
|
|
84
|
-
* For BlocksStack: this equals the stack name (id).
|
|
85
|
-
* For BlocksBackend: the caller overrides this after construction to include
|
|
86
|
-
* the parent stack name for deployment uniqueness.
|
|
87
|
-
*/
|
|
88
|
-
BLOCKS_STACK_NAME: id ?? cdk.Stack.of(scope).stackName,
|
|
89
|
-
},
|
|
90
|
-
bundling: {
|
|
91
|
-
minify: true,
|
|
92
|
-
esbuildArgs: { '--conditions': 'aws-runtime' },
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// In sandbox mode, allow localhost origins so the local dev frontend can
|
|
97
|
-
// reach the deployed Lambda API via CORS.
|
|
98
|
-
const isSandbox =
|
|
99
|
-
scope.node.tryGetContext('sandboxMode') === 'true' ||
|
|
100
|
-
scope.node.tryGetContext('sandboxMode') === true;
|
|
101
|
-
if (isSandbox) {
|
|
102
|
-
handler.addEnvironment('CORS_ALLOWED_ORIGINS', '^https?://(localhost|127\\.0\\.0\\.1)(:\\d+)?$');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const api = new apigateway.RestApi(scope, 'API', {
|
|
106
|
-
restApiName: 'Blocks API',
|
|
107
|
-
deployOptions: { cachingEnabled: false },
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const integration = new apigateway.LambdaIntegration(handler);
|
|
111
|
-
|
|
112
|
-
// Build the nested resource tree for /aws-blocks/api.
|
|
113
|
-
// Intermediate resource gets a proxy so sub-paths (RawRoutes) still reach Lambda.
|
|
114
|
-
const awsBlocksResource = api.root.addResource(BLOCKS_NAMESPACE.slice(1));
|
|
115
|
-
awsBlocksResource.addProxy({ defaultIntegration: integration, anyMethod: true });
|
|
116
|
-
|
|
117
|
-
const apiResource = awsBlocksResource.addResource('api');
|
|
118
|
-
apiResource.addMethod('POST', integration);
|
|
119
|
-
apiResource.addMethod('OPTIONS', integration);
|
|
120
|
-
|
|
121
|
-
api.root.addProxy({ defaultIntegration: integration, anyMethod: true });
|
|
122
|
-
|
|
123
|
-
// ── Resource Groups ────────────────────────────────────────────────────
|
|
117
|
+
// ── Resource Groups ───────────────────────────────────────────────────
|
|
124
118
|
let rootStack = cdk.Stack.of(scope);
|
|
125
119
|
while (rootStack.nestedStackParent) rootStack = rootStack.nestedStackParent;
|
|
126
120
|
const groupPrefix = (id && id !== rootStack.stackName) ? `${rootStack.stackName}-${id}` : rootStack.stackName;
|
|
@@ -156,7 +150,7 @@ export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id
|
|
|
156
150
|
},
|
|
157
151
|
});
|
|
158
152
|
|
|
159
|
-
// ── Console redirect routes
|
|
153
|
+
// ── Console redirect routes ───────────────────────────────────────────
|
|
160
154
|
const region = cdk.Fn.ref('AWS::Region');
|
|
161
155
|
const resourcesUrl = cdk.Fn.join('', [
|
|
162
156
|
'https://', region, '.console.aws.amazon.com/resource-groups/group/',
|
|
@@ -172,7 +166,7 @@ export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id
|
|
|
172
166
|
|
|
173
167
|
registerBuiltinRoutes();
|
|
174
168
|
|
|
175
|
-
return {
|
|
169
|
+
return { executionRole };
|
|
176
170
|
}
|
|
177
171
|
|
|
178
172
|
/**
|
|
@@ -193,12 +187,33 @@ export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id
|
|
|
193
187
|
* ```
|
|
194
188
|
*/
|
|
195
189
|
export class BlocksBackend extends Construct {
|
|
196
|
-
public readonly apiUrl: string;
|
|
197
|
-
public readonly gateway: apigateway.RestApi;
|
|
198
|
-
public readonly handler: cdk.aws_lambda_nodejs.NodejsFunction;
|
|
199
190
|
public readonly backendHandlerPath: string;
|
|
200
191
|
/** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
|
|
201
192
|
public readonly executionRole: iam.IRole;
|
|
193
|
+
/** Infrastructure defaults for Building Blocks created under this backend. */
|
|
194
|
+
public readonly defaults: BlocksDefaults;
|
|
195
|
+
/** The default compute (owns the Lambda function + API Gateway); set in `create()`. @internal */
|
|
196
|
+
_defaultCompute?: Compute;
|
|
197
|
+
|
|
198
|
+
/** The default compute's Lambda function. To be removed once consumers move to the multi-compute model. */
|
|
199
|
+
get handler(): cdk.aws_lambda_nodejs.NodejsFunction {
|
|
200
|
+
return this.requireDefaultCompute().fn;
|
|
201
|
+
}
|
|
202
|
+
/** The default compute's API Gateway REST API. To be removed once consumers move to the multi-compute model. */
|
|
203
|
+
get gateway(): apigateway.RestApi {
|
|
204
|
+
return this.requireDefaultCompute().apiGateway;
|
|
205
|
+
}
|
|
206
|
+
/** The default compute's RPC endpoint URL. To be removed once consumers move to the multi-compute model. */
|
|
207
|
+
get apiUrl(): string {
|
|
208
|
+
return this.requireDefaultCompute().apiUrl;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private requireDefaultCompute(): LambdaShapedCompute {
|
|
212
|
+
if (!this._defaultCompute) {
|
|
213
|
+
throw new Error('Blocks backend not fully initialized — access .handler/.gateway/.apiUrl after BlocksBackend.create() resolves.');
|
|
214
|
+
}
|
|
215
|
+
return this._defaultCompute as LambdaShapedCompute;
|
|
216
|
+
}
|
|
202
217
|
|
|
203
218
|
/**
|
|
204
219
|
* The fullId used by child Scopes to compute their env var names,
|
|
@@ -239,21 +254,27 @@ export class BlocksBackend extends Construct {
|
|
|
239
254
|
// Expose self to Building Blocks at CDK time
|
|
240
255
|
(globalThis as any).CURRENT_BLOCKS_STACK = this;
|
|
241
256
|
|
|
257
|
+
// Store defaults on the backend (not the stack) so several BlocksBackends
|
|
258
|
+
// in one stack each keep their own posture; Building Blocks resolve them by
|
|
259
|
+
// walking up to their owning backend (see Scope.defaults).
|
|
260
|
+
this.defaults = props.defaults;
|
|
261
|
+
|
|
242
262
|
const infra = setupBlocksInfra(this, props, id);
|
|
243
|
-
this.handler = infra.handler;
|
|
244
|
-
this.gateway = infra.gateway;
|
|
245
|
-
this.apiUrl = infra.apiUrl;
|
|
246
263
|
this.executionRole = infra.executionRole;
|
|
247
|
-
|
|
248
|
-
//
|
|
249
|
-
// resource lookups (DynamoDB table names) match the CDK-time fullId
|
|
250
|
-
// and are unique per deployment.
|
|
251
|
-
this.handler.addEnvironment('BLOCKS_STACK_NAME', this.fullId);
|
|
264
|
+
// The default compute (and thus handler/gateway) is created in create(),
|
|
265
|
+
// after construction — it derives BLOCKS_STACK_NAME from this.fullId.
|
|
252
266
|
}
|
|
253
267
|
|
|
254
|
-
static async create(scope: Construct, id: string, props:
|
|
268
|
+
static async create(scope: Construct, id: string, props: CoreBlocksBackendProps) {
|
|
255
269
|
assertCdkConditionActive();
|
|
256
270
|
const backend = new BlocksBackend(scope, id, props);
|
|
271
|
+
// Create the default compute before importing the backend: it OWNS the
|
|
272
|
+
// Lambda function + API Gateway (which back .handler/.gateway/.apiUrl), and
|
|
273
|
+
// a block reading `this.compute` in its constructor (during that import)
|
|
274
|
+
// must resolve to it. The factory is supplied by the umbrella
|
|
275
|
+
// @aws-blocks/blocks (which injects LambdaCompute) via props, so core never
|
|
276
|
+
// imports the concrete compute class.
|
|
277
|
+
backend._defaultCompute = props.defaultComputeFactory(backend);
|
|
257
278
|
// file:// URL (not a raw path) so the cache-busting query works on Windows,
|
|
258
279
|
// where an absolute path like `D:\...` is rejected as URL scheme `d:`.
|
|
259
280
|
const backendUrl = pathToFileURL(props.backendCDKPath);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* BlocksPresets carry the expected posture. Resolution/anchoring (a block
|
|
6
|
+
* reading its owning backend's defaults, and two backends in one stack keeping
|
|
7
|
+
* separate postures) is covered in blocks-backend.test.ts, which has the
|
|
8
|
+
* fixtures to construct real backends.
|
|
9
|
+
*/
|
|
10
|
+
import { test, describe } from 'node:test';
|
|
11
|
+
import assert from 'node:assert';
|
|
12
|
+
import { RemovalPolicy } from 'aws-cdk-lib';
|
|
13
|
+
import { BlocksPresets } from './blocks-defaults.js';
|
|
14
|
+
|
|
15
|
+
describe('BlocksPresets', () => {
|
|
16
|
+
test('sandbox is disposable: DESTROY + deletion protection off + no PITR', () => {
|
|
17
|
+
assert.strictEqual(BlocksPresets.sandbox.removalPolicy, RemovalPolicy.DESTROY);
|
|
18
|
+
assert.strictEqual(BlocksPresets.sandbox.deletionProtection, false);
|
|
19
|
+
assert.strictEqual(BlocksPresets.sandbox.pointInTimeRecovery, false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('production is durable: RETAIN + deletion protection on + PITR on', () => {
|
|
23
|
+
assert.strictEqual(BlocksPresets.production.removalPolicy, RemovalPolicy.RETAIN);
|
|
24
|
+
assert.strictEqual(BlocksPresets.production.deletionProtection, true);
|
|
25
|
+
assert.strictEqual(BlocksPresets.production.pointInTimeRecovery, true);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { RemovalPolicy } from 'aws-cdk-lib';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Default values for every Amazon-authored Building Block created within a
|
|
8
|
+
* `BlocksStack` or `BlocksBackend`. A per-block option always overrides the
|
|
9
|
+
* corresponding default (`option ?? scope.defaults.field`).
|
|
10
|
+
*
|
|
11
|
+
* Start from {@link BlocksPresets} and override individual fields as needed.
|
|
12
|
+
*/
|
|
13
|
+
export interface BlocksDefaults {
|
|
14
|
+
/**
|
|
15
|
+
* What happens to a stateful resource (table, bucket, user pool) when the
|
|
16
|
+
* stack is deleted, via CDK/CloudFormation: `RETAIN` keeps the resource (and
|
|
17
|
+
* its data) behind; `DESTROY` tears it down. Applies only to CDK/CloudFormation
|
|
18
|
+
* deletions.
|
|
19
|
+
*/
|
|
20
|
+
removalPolicy: RemovalPolicy;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether to block deletion of a stateful resource. Unlike `removalPolicy`,
|
|
24
|
+
* this guards against deletion through **any** path — CDK/CloudFormation as
|
|
25
|
+
* well as the CLI, API, and AWS console — until it is turned off.
|
|
26
|
+
*/
|
|
27
|
+
deletionProtection: boolean;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* CORS origins the compute's API accepts, as regular-expression patterns
|
|
31
|
+
* matched against the request `Origin` header. Empty means no cross-origin
|
|
32
|
+
* requests are allowed. The `sandbox` preset allows localhost so a local dev
|
|
33
|
+
* frontend can reach a deployed API; `production` allows none by default.
|
|
34
|
+
*/
|
|
35
|
+
allowedOrigins: string[];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Whether stateful resources that support continuous backups keep them on by
|
|
39
|
+
* default — e.g. DynamoDB Point-in-Time Recovery, letting you restore to any
|
|
40
|
+
* second in the retention window. On in `production`, off in `sandbox` (where
|
|
41
|
+
* throwaway data isn't worth the backup-storage cost). Blocks whose service
|
|
42
|
+
* has no equivalent simply ignore it.
|
|
43
|
+
*
|
|
44
|
+
* `true` enables backups with the service's default window; `false` disables
|
|
45
|
+
* them; `{ retentionDays: n }` enables them and pins the window (a block
|
|
46
|
+
* clamps/validates `n` to its service's supported range — DynamoDB PITR is
|
|
47
|
+
* 1–35 days). Backups only have a window when on, so the two are one field.
|
|
48
|
+
*/
|
|
49
|
+
pointInTimeRecovery: boolean | { retentionDays: number };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Prepared starting points for {@link BlocksDefaults}. Pick one and override
|
|
54
|
+
* individual fields with a spread:
|
|
55
|
+
*
|
|
56
|
+
* ```ts
|
|
57
|
+
* defaults: { ...BlocksPresets.production, deletionProtection: false }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export const BlocksPresets = {
|
|
61
|
+
/** Disposable development stacks: tear down cleanly, no delete guard, no backups. */
|
|
62
|
+
sandbox: {
|
|
63
|
+
removalPolicy: RemovalPolicy.DESTROY,
|
|
64
|
+
deletionProtection: false,
|
|
65
|
+
allowedOrigins: ['^https?://(localhost|127\\.0\\.0\\.1)(:\\d+)?$'],
|
|
66
|
+
pointInTimeRecovery: false,
|
|
67
|
+
},
|
|
68
|
+
/** Durable, protected posture for permanent deployments. */
|
|
69
|
+
production: {
|
|
70
|
+
removalPolicy: RemovalPolicy.RETAIN,
|
|
71
|
+
deletionProtection: true,
|
|
72
|
+
allowedOrigins: [],
|
|
73
|
+
pointInTimeRecovery: true,
|
|
74
|
+
},
|
|
75
|
+
} satisfies Record<string, BlocksDefaults>;
|
|
@@ -1,17 +1,61 @@
|
|
|
1
1
|
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import { test, describe, before } from 'node:test';
|
|
5
4
|
import assert from 'node:assert';
|
|
6
|
-
import { fileURLToPath } from 'node:url';
|
|
7
5
|
import { dirname, join } from 'node:path';
|
|
6
|
+
import { before, describe, test } from 'node:test';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import * as cdk from 'aws-cdk-lib';
|
|
9
|
+
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
|
|
10
|
+
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
11
|
+
import type { Construct } from 'constructs';
|
|
12
|
+
import type { ScopeParent } from '../common/index.js';
|
|
13
|
+
import { BLOCKS_RPC_PREFIX } from '../constants.js';
|
|
9
14
|
import { BlocksBackend } from './blocks-backend.js';
|
|
10
|
-
import {
|
|
15
|
+
import { Compute } from './compute/compute.js';
|
|
16
|
+
import type { DefaultComputeFactory } from './compute/default-compute-factory.js';
|
|
17
|
+
import { BlocksStack, BlocksPresets, Scope } from './index.js';
|
|
18
|
+
|
|
19
|
+
// A real app gets its default compute from @aws-blocks/bb-lambda-compute (via
|
|
20
|
+
// @aws-blocks/blocks), which core's own tests can't depend on. Use an
|
|
21
|
+
// equivalent inline stub: a Compute that owns a NodejsFunction + API Gateway,
|
|
22
|
+
// so create() can build the default and the handler/gateway/apiUrl accessors
|
|
23
|
+
// and synth-shape assertions have something real to resolve to. It is passed to
|
|
24
|
+
// each create() via the internal `defaultComputeFactory` option (see makeStack /
|
|
25
|
+
// makeBackend), exactly as @aws-blocks/blocks injects LambdaCompute.
|
|
26
|
+
class StubLambdaCompute extends Compute {
|
|
27
|
+
readonly fn: lambda.NodejsFunction;
|
|
28
|
+
readonly apiGateway: apigateway.RestApi;
|
|
29
|
+
readonly apiUrl: string;
|
|
30
|
+
|
|
31
|
+
constructor(scope: ScopeParent, id: string) {
|
|
32
|
+
super(id, { parent: scope });
|
|
33
|
+
this.fn = new lambda.NodejsFunction(this, 'Handler', {
|
|
34
|
+
entry: this.backendHandlerPath,
|
|
35
|
+
runtime: cdk.aws_lambda.Runtime.NODEJS_22_X,
|
|
36
|
+
handler: 'handler',
|
|
37
|
+
role: this.executionRole,
|
|
38
|
+
environment: { BLOCKS_STACK_NAME: this.backendStackName },
|
|
39
|
+
bundling: { minify: true, esbuildArgs: { '--conditions': 'aws-runtime' } },
|
|
40
|
+
});
|
|
41
|
+
this.apiGateway = new apigateway.RestApi(this, 'API', { restApiName: 'Blocks API' });
|
|
42
|
+
this.apiGateway.root.addProxy({
|
|
43
|
+
defaultIntegration: new apigateway.LambdaIntegration(this.fn),
|
|
44
|
+
anyMethod: true,
|
|
45
|
+
});
|
|
46
|
+
this.apiUrl = `${this.apiGateway.url}${BLOCKS_RPC_PREFIX.slice(1)}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
setEnv(key: string, value: string): void {
|
|
50
|
+
this.fn.addEnvironment(key, value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const stubComputeFactory: DefaultComputeFactory = (root) => new StubLambdaCompute(root as never, 'DefaultCompute');
|
|
11
55
|
|
|
12
56
|
// Simulate the CDK condition being active (tests import CDK files directly)
|
|
13
57
|
before(() => {
|
|
14
|
-
|
|
58
|
+
process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS ?? '') + ' --conditions=cdk';
|
|
15
59
|
});
|
|
16
60
|
|
|
17
61
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -19,128 +63,134 @@ const handlerPath = join(__dirname, '__fixtures__', 'handler.js');
|
|
|
19
63
|
const sideEffectBackendPath = join(__dirname, '__fixtures__', 'side-effect-backend.js');
|
|
20
64
|
const factoryBackendPath = join(__dirname, '__fixtures__', 'factory-backend.js');
|
|
21
65
|
|
|
66
|
+
// Wrap create(), injecting the stub default-compute factory the way
|
|
67
|
+
// @aws-blocks/blocks injects LambdaCompute — so tests don't repeat it.
|
|
68
|
+
const makeStack = (scope: Construct, id: string, backendCDKPath: string) =>
|
|
69
|
+
BlocksStack.create(scope, id, { backendHandlerPath: handlerPath, backendCDKPath, defaults: BlocksPresets.production, defaultComputeFactory: stubComputeFactory });
|
|
70
|
+
const makeBackend = (scope: Construct, id: string, backendCDKPath: string) =>
|
|
71
|
+
BlocksBackend.create(scope, id, { backendHandlerPath: handlerPath, backendCDKPath, defaults: BlocksPresets.production, defaultComputeFactory: stubComputeFactory });
|
|
72
|
+
|
|
22
73
|
describe('ESM cache-busting (multi-stage)', () => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
assert.ok(
|
|
39
|
-
findMarker(stack1),
|
|
40
|
-
'First stack should have SideEffectMarker from module side effect',
|
|
41
|
-
);
|
|
42
|
-
assert.ok(
|
|
43
|
-
findMarker(stack2),
|
|
44
|
-
'Second stack should have SideEffectMarker from re-executed module (cache busted)',
|
|
45
|
-
);
|
|
46
|
-
});
|
|
74
|
+
test('BlocksStack.create() with same backendCDKPath but different IDs produces constructs in each', async () => {
|
|
75
|
+
const app = new cdk.App();
|
|
76
|
+
|
|
77
|
+
const stack1 = await makeStack(app, 'PipelineStage1', sideEffectBackendPath);
|
|
78
|
+
|
|
79
|
+
const stack2 = await makeStack(app, 'PipelineStage2', sideEffectBackendPath);
|
|
80
|
+
|
|
81
|
+
const findMarker = (scope: any) => scope.node.tryFindChild('SideEffectMarker');
|
|
82
|
+
|
|
83
|
+
assert.ok(findMarker(stack1), 'First stack should have SideEffectMarker from module side effect');
|
|
84
|
+
assert.ok(
|
|
85
|
+
findMarker(stack2),
|
|
86
|
+
'Second stack should have SideEffectMarker from re-executed module (cache busted)',
|
|
87
|
+
);
|
|
88
|
+
});
|
|
47
89
|
});
|
|
48
90
|
|
|
49
91
|
describe('factory function support', () => {
|
|
50
|
-
|
|
51
|
-
|
|
92
|
+
test('BlocksStack.create() calls default export function with the stack instance', async () => {
|
|
93
|
+
const app = new cdk.App();
|
|
52
94
|
|
|
53
|
-
|
|
54
|
-
backendHandlerPath: handlerPath,
|
|
55
|
-
backendCDKPath: factoryBackendPath,
|
|
56
|
-
});
|
|
95
|
+
const stack = await makeStack(app, 'FactoryBlocksStack', factoryBackendPath);
|
|
57
96
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
97
|
+
const marker = stack.node.tryFindChild('FactoryMarker');
|
|
98
|
+
assert.ok(marker, 'Factory function should have created FactoryMarker on the stack');
|
|
99
|
+
});
|
|
61
100
|
});
|
|
62
101
|
|
|
63
102
|
describe('legacy side-effect mode (no default export)', () => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const marker = backend.node.tryFindChild('SideEffectMarker');
|
|
74
|
-
assert.ok(
|
|
75
|
-
marker,
|
|
76
|
-
'Side-effect-only module should register construct via globalThis.CURRENT_BLOCKS_STACK',
|
|
77
|
-
);
|
|
78
|
-
});
|
|
103
|
+
test('module with only side effects still registers constructs via globalThis', async () => {
|
|
104
|
+
const app = new cdk.App();
|
|
105
|
+
const stack = new cdk.Stack(app, 'LegacyTestStack');
|
|
106
|
+
|
|
107
|
+
const backend = await makeBackend(stack, 'LegacyStage', sideEffectBackendPath);
|
|
108
|
+
|
|
109
|
+
const marker = backend.node.tryFindChild('SideEffectMarker');
|
|
110
|
+
assert.ok(marker, 'Side-effect-only module should register construct via globalThis.CURRENT_BLOCKS_STACK');
|
|
111
|
+
});
|
|
79
112
|
});
|
|
80
113
|
|
|
81
114
|
describe('shared execution role (BlocksStack)', () => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
assert.ok(stack.executionRole, 'BlocksStack should expose a populated .executionRole');
|
|
94
|
-
});
|
|
115
|
+
// The role synth shape and the Scope.executionRole tree-walk are shared code
|
|
116
|
+
// (setupBlocksInfra + the getter), covered in blocks-backend.test.ts. The only
|
|
117
|
+
// BlocksStack-specific behavior is that its own constructor wires
|
|
118
|
+
// executionRole — a separate code path from BlocksBackend's constructor.
|
|
119
|
+
test('BlocksStack wires executionRole via its constructor', async () => {
|
|
120
|
+
const app = new cdk.App();
|
|
121
|
+
const stack = await makeStack(app, 'StackRoleStack', sideEffectBackendPath);
|
|
122
|
+
|
|
123
|
+
assert.ok(stack.executionRole, 'BlocksStack should expose a populated .executionRole');
|
|
124
|
+
});
|
|
95
125
|
});
|
|
96
126
|
|
|
97
127
|
describe('executionRole globalThis fallback', () => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
128
|
+
test('resolves via globalThis.CURRENT_BLOCKS_STACK when no owner is in the tree', async () => {
|
|
129
|
+
const app = new cdk.App();
|
|
130
|
+
const stack = await makeStack(app, 'FallbackStack', sideEffectBackendPath);
|
|
131
|
+
|
|
132
|
+
// A Scope whose construct-tree ancestry has no BlocksStack/BlocksBackend
|
|
133
|
+
// (parented under a plain cdk.Stack) exhausts the tree-walk and falls back
|
|
134
|
+
// to globalThis.CURRENT_BLOCKS_STACK. The `as any` is test plumbing — a
|
|
135
|
+
// plain Stack isn't a ScopeParent, but it IS a valid Construct parent.
|
|
136
|
+
const plainStack = new cdk.Stack(app, 'PlainStack');
|
|
137
|
+
(globalThis as any).CURRENT_BLOCKS_STACK = stack;
|
|
138
|
+
const orphan = new Scope('orphan', { parent: plainStack as any });
|
|
139
|
+
|
|
140
|
+
assert.strictEqual(orphan.executionRole, stack.executionRole, 'fallback resolves to the ambient stack role');
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('root is bound to the owning stack (multi-stack synth)', () => {
|
|
145
|
+
test('a block under each of two stacks resolves its OWN stack, not the last globalThis', async () => {
|
|
146
|
+
// `root` is resolved once at Scope construction (not re-walked per getter),
|
|
147
|
+
// and `create()` mutates globalThis.CURRENT_BLOCKS_STACK for each stack it
|
|
148
|
+
// builds. With two stacks in one synth, a block constructed under the first
|
|
149
|
+
// must stay bound to the first even after the second stack is created and
|
|
150
|
+
// overwrites globalThis — otherwise its root-derived accessors (handler,
|
|
151
|
+
// executionRole, backendStackName) would silently point at the wrong stack.
|
|
152
|
+
const app = new cdk.App();
|
|
153
|
+
|
|
154
|
+
const stackA = await makeStack(app, 'RootBindingA', sideEffectBackendPath);
|
|
155
|
+
// A block explicitly parented under stackA (construct-tree walk resolves
|
|
156
|
+
// stackA regardless of the ambient globalThis).
|
|
157
|
+
const blockA = new Scope('blockA', { parent: stackA });
|
|
158
|
+
|
|
159
|
+
// Building the second stack overwrites globalThis.CURRENT_BLOCKS_STACK.
|
|
160
|
+
const stackB = await makeStack(app, 'RootBindingB', sideEffectBackendPath);
|
|
161
|
+
const blockB = new Scope('blockB', { parent: stackB });
|
|
162
|
+
|
|
163
|
+
assert.strictEqual(blockA.executionRole, stackA.executionRole, 'blockA stays bound to stackA');
|
|
164
|
+
assert.strictEqual(blockB.executionRole, stackB.executionRole, 'blockB binds to stackB');
|
|
165
|
+
assert.notStrictEqual(stackA.executionRole, stackB.executionRole, 'the two stacks have distinct roles');
|
|
166
|
+
assert.strictEqual(blockA.backendStackName, 'RootBindingA', 'blockA derives its own stack name');
|
|
167
|
+
assert.strictEqual(blockB.backendStackName, 'RootBindingB', 'blockB derives its own stack name');
|
|
168
|
+
});
|
|
119
169
|
});
|
|
120
170
|
|
|
121
171
|
describe('assertCdkConditionActive', () => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
172
|
+
test('BlocksStack.create() throws when --conditions=cdk is missing', async () => {
|
|
173
|
+
const origNodeOptions = process.env.NODE_OPTIONS;
|
|
174
|
+
const origExecArgv = process.execArgv;
|
|
175
|
+
process.env.NODE_OPTIONS = '';
|
|
176
|
+
process.execArgv = [];
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const app = new cdk.App();
|
|
180
|
+
|
|
181
|
+
await assert.rejects(
|
|
182
|
+
makeStack(app, 'MissingConditionStack', sideEffectBackendPath),
|
|
183
|
+
(err: Error) => {
|
|
184
|
+
assert.ok(
|
|
185
|
+
err.message.includes('Missing --conditions=cdk'),
|
|
186
|
+
`Expected condition error, got: ${err.message}`,
|
|
187
|
+
);
|
|
188
|
+
return true;
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
} finally {
|
|
192
|
+
process.env.NODE_OPTIONS = origNodeOptions;
|
|
193
|
+
process.execArgv = origExecArgv;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
146
196
|
});
|