@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.
- package/README.md +13 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +32 -0
- package/dist/api.test.js +22 -0
- package/dist/bb-utils.d.ts +1 -0
- package/dist/bb-utils.d.ts.map +1 -1
- package/dist/bb-utils.js +3 -0
- package/dist/cdk/apigateway-account.d.ts +33 -0
- package/dist/cdk/apigateway-account.d.ts.map +1 -0
- package/dist/cdk/apigateway-account.js +60 -0
- package/dist/cdk/blocks-backend.d.ts +43 -9
- package/dist/cdk/blocks-backend.d.ts.map +1 -1
- package/dist/cdk/blocks-backend.js +69 -73
- package/dist/cdk/blocks-backend.test.js +78 -69
- package/dist/cdk/blocks-defaults.d.ts +92 -2
- package/dist/cdk/blocks-defaults.d.ts.map +1 -1
- package/dist/cdk/blocks-defaults.js +22 -2
- package/dist/cdk/blocks-defaults.test.js +21 -2
- package/dist/cdk/blocks-stack.test.js +94 -37
- package/dist/cdk/compute/compute-registry.d.ts +19 -0
- package/dist/cdk/compute/compute-registry.d.ts.map +1 -0
- package/dist/cdk/compute/compute-registry.js +38 -0
- package/dist/cdk/compute/compute.d.ts +2 -0
- package/dist/cdk/compute/compute.d.ts.map +1 -1
- package/dist/cdk/compute/compute.js +8 -0
- package/dist/cdk/compute/default-compute-factory.d.ts +34 -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/config-registry.d.ts +34 -4
- package/dist/cdk/config-registry.d.ts.map +1 -1
- package/dist/cdk/config-registry.js +83 -25
- package/dist/cdk/config-registry.test.d.ts +2 -0
- package/dist/cdk/config-registry.test.d.ts.map +1 -0
- package/dist/cdk/config-registry.test.js +115 -0
- package/dist/cdk/index.d.ts +76 -7
- package/dist/cdk/index.d.ts.map +1 -1
- package/dist/cdk/index.js +99 -9
- package/dist/cdk/internal.d.ts +1 -0
- package/dist/cdk/internal.d.ts.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/common/config.d.ts +34 -0
- package/dist/common/config.d.ts.map +1 -1
- package/dist/common/config.js +45 -3
- package/dist/common/config.test.js +19 -0
- package/dist/common/index.d.ts +8 -0
- package/dist/common/index.d.ts.map +1 -1
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +20 -0
- 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 +97 -6
- package/dist/hosting.d.ts.map +1 -1
- package/dist/hosting.js +91 -23
- package/dist/hosting.test.js +182 -10
- package/dist/index.cdk.d.ts +13 -12
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +13 -9
- 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.d.ts +17 -0
- package/dist/lambda-handler.d.ts.map +1 -1
- package/dist/lambda-handler.js +52 -3
- package/dist/lambda-handler.test.js +196 -1
- 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 +34 -0
- package/dist/rpc.test.js +38 -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-empty-buckets.test.d.ts +2 -0
- package/dist/scripts/sandbox-empty-buckets.test.d.ts.map +1 -0
- package/dist/scripts/sandbox-empty-buckets.test.js +171 -0
- package/dist/scripts/sandbox.d.ts +106 -0
- package/dist/scripts/sandbox.d.ts.map +1 -1
- package/dist/scripts/sandbox.js +255 -40
- 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/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/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -1
- package/src/api.test.ts +25 -0
- package/src/api.ts +39 -0
- package/src/bb-utils.ts +3 -0
- package/src/cdk/apigateway-account.ts +66 -0
- package/src/cdk/blocks-backend.test.ts +291 -272
- package/src/cdk/blocks-backend.ts +88 -83
- package/src/cdk/blocks-defaults.test.ts +25 -2
- package/src/cdk/blocks-defaults.ts +94 -2
- package/src/cdk/blocks-stack.test.ts +186 -118
- package/src/cdk/compute/compute-registry.ts +45 -0
- package/src/cdk/compute/compute.ts +10 -0
- package/src/cdk/compute/default-compute-factory.ts +38 -0
- package/src/cdk/config-registry.test.ts +135 -0
- package/src/cdk/config-registry.ts +92 -34
- package/src/cdk/index.ts +129 -10
- package/src/cdk/internal.ts +1 -0
- package/src/client/index.ts +1 -1
- package/src/common/config.test.ts +21 -0
- package/src/common/config.ts +47 -3
- package/src/common/index.ts +8 -0
- package/src/errors.ts +21 -0
- package/src/hosting-secrets.test.ts +106 -0
- package/src/hosting-secrets.ts +106 -0
- package/src/hosting.test.ts +272 -63
- package/src/hosting.ts +200 -68
- package/src/index.cdk.ts +80 -38
- package/src/index.ts +35 -16
- package/src/lambda-handler.test.ts +215 -1
- package/src/lambda-handler.ts +54 -2
- package/src/pipeline/index.ts +64 -11
- package/src/pipeline.test.ts +55 -0
- package/src/rpc.test.ts +44 -1
- package/src/rpc.ts +41 -0
- 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-empty-buckets.test.ts +191 -0
- package/src/scripts/sandbox.ts +287 -39
- package/src/scripts/secret.test.ts +35 -0
- package/src/scripts/secret.ts +35 -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/version.ts +1 -1
|
@@ -1,17 +1,67 @@
|
|
|
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';
|
|
15
|
+
import { Compute } from './compute/compute.js';
|
|
16
|
+
import { getComputes } from './compute/compute-registry.js';
|
|
17
|
+
import type { DefaultComputeFactory } from './compute/default-compute-factory.js';
|
|
10
18
|
import { BlocksStack, BlocksPresets, Scope } from './index.js';
|
|
11
19
|
|
|
20
|
+
// A real app gets its default compute from @aws-blocks/bb-lambda-compute (via
|
|
21
|
+
// @aws-blocks/blocks), which core's own tests can't depend on. Use an
|
|
22
|
+
// equivalent inline stub: a Compute that owns a NodejsFunction + API Gateway,
|
|
23
|
+
// so create() can build the default and the handler/gateway/apiUrl accessors
|
|
24
|
+
// and synth-shape assertions have something real to resolve to. It is passed to
|
|
25
|
+
// each create() via the internal `defaultComputeFactory` option (see makeStack /
|
|
26
|
+
// makeBackend), exactly as @aws-blocks/blocks injects LambdaCompute.
|
|
27
|
+
class StubLambdaCompute extends Compute {
|
|
28
|
+
readonly fn: lambda.NodejsFunction;
|
|
29
|
+
readonly apiGateway: apigateway.RestApi;
|
|
30
|
+
readonly apiUrl: string;
|
|
31
|
+
readonly logGroup: cdk.aws_logs.LogGroup;
|
|
32
|
+
|
|
33
|
+
constructor(scope: ScopeParent, id: string) {
|
|
34
|
+
super(id, { parent: scope });
|
|
35
|
+
this.logGroup = new cdk.aws_logs.LogGroup(this, 'HandlerLogGroup', {
|
|
36
|
+
removalPolicy: cdk.RemovalPolicy.DESTROY,
|
|
37
|
+
});
|
|
38
|
+
this.fn = new lambda.NodejsFunction(this, 'Handler', {
|
|
39
|
+
entry: this.backendHandlerPath,
|
|
40
|
+
runtime: cdk.aws_lambda.Runtime.NODEJS_22_X,
|
|
41
|
+
handler: 'handler',
|
|
42
|
+
role: this.executionRole,
|
|
43
|
+
logGroup: this.logGroup,
|
|
44
|
+
environment: { BLOCKS_STACK_NAME: this.backendStackName },
|
|
45
|
+
bundling: { minify: true, esbuildArgs: { '--conditions': 'aws-runtime' } },
|
|
46
|
+
});
|
|
47
|
+
this.apiGateway = new apigateway.RestApi(this, 'API', { restApiName: 'Blocks API' });
|
|
48
|
+
this.apiGateway.root.addProxy({
|
|
49
|
+
defaultIntegration: new apigateway.LambdaIntegration(this.fn),
|
|
50
|
+
anyMethod: true,
|
|
51
|
+
});
|
|
52
|
+
this.apiUrl = `${this.apiGateway.url}${BLOCKS_RPC_PREFIX.slice(1)}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
setEnv(key: string, value: string): void {
|
|
56
|
+
this.fn.addEnvironment(key, value);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const stubComputeFactory: DefaultComputeFactory = (root) => new StubLambdaCompute(root as never, 'DefaultCompute');
|
|
61
|
+
|
|
12
62
|
// Simulate the CDK condition being active (tests import CDK files directly)
|
|
13
63
|
before(() => {
|
|
14
|
-
|
|
64
|
+
process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS ?? '') + ' --conditions=cdk';
|
|
15
65
|
});
|
|
16
66
|
|
|
17
67
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -19,135 +69,153 @@ const handlerPath = join(__dirname, '__fixtures__', 'handler.js');
|
|
|
19
69
|
const sideEffectBackendPath = join(__dirname, '__fixtures__', 'side-effect-backend.js');
|
|
20
70
|
const factoryBackendPath = join(__dirname, '__fixtures__', 'factory-backend.js');
|
|
21
71
|
|
|
72
|
+
// Wrap create(), injecting the stub default-compute factory the way
|
|
73
|
+
// @aws-blocks/blocks injects LambdaCompute — so tests don't repeat it.
|
|
74
|
+
const makeStack = (scope: Construct, id: string, backendCDKPath: string) =>
|
|
75
|
+
BlocksStack.create(scope, id, { backendHandlerPath: handlerPath, backendCDKPath, defaults: BlocksPresets.production, defaultComputeFactory: stubComputeFactory });
|
|
76
|
+
const makeBackend = (scope: Construct, id: string, backendCDKPath: string) =>
|
|
77
|
+
BlocksBackend.create(scope, id, { backendHandlerPath: handlerPath, backendCDKPath, defaults: BlocksPresets.production, defaultComputeFactory: stubComputeFactory });
|
|
78
|
+
|
|
22
79
|
describe('ESM cache-busting (multi-stage)', () => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const findMarker = (scope: any) => scope.node.tryFindChild('SideEffectMarker');
|
|
39
|
-
|
|
40
|
-
assert.ok(
|
|
41
|
-
findMarker(stack1),
|
|
42
|
-
'First stack should have SideEffectMarker from module side effect',
|
|
43
|
-
);
|
|
44
|
-
assert.ok(
|
|
45
|
-
findMarker(stack2),
|
|
46
|
-
'Second stack should have SideEffectMarker from re-executed module (cache busted)',
|
|
47
|
-
);
|
|
48
|
-
});
|
|
80
|
+
test('BlocksStack.create() with same backendCDKPath but different IDs produces constructs in each', async () => {
|
|
81
|
+
const app = new cdk.App();
|
|
82
|
+
|
|
83
|
+
const stack1 = await makeStack(app, 'PipelineStage1', sideEffectBackendPath);
|
|
84
|
+
|
|
85
|
+
const stack2 = await makeStack(app, 'PipelineStage2', sideEffectBackendPath);
|
|
86
|
+
|
|
87
|
+
const findMarker = (scope: any) => scope.node.tryFindChild('SideEffectMarker');
|
|
88
|
+
|
|
89
|
+
assert.ok(findMarker(stack1), 'First stack should have SideEffectMarker from module side effect');
|
|
90
|
+
assert.ok(
|
|
91
|
+
findMarker(stack2),
|
|
92
|
+
'Second stack should have SideEffectMarker from re-executed module (cache busted)',
|
|
93
|
+
);
|
|
94
|
+
});
|
|
49
95
|
});
|
|
50
96
|
|
|
51
97
|
describe('factory function support', () => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const marker = stack.node.tryFindChild('FactoryMarker');
|
|
62
|
-
assert.ok(marker, 'Factory function should have created FactoryMarker on the stack');
|
|
63
|
-
});
|
|
98
|
+
test('BlocksStack.create() calls default export function with the stack instance', async () => {
|
|
99
|
+
const app = new cdk.App();
|
|
100
|
+
|
|
101
|
+
const stack = await makeStack(app, 'FactoryBlocksStack', factoryBackendPath);
|
|
102
|
+
|
|
103
|
+
const marker = stack.node.tryFindChild('FactoryMarker');
|
|
104
|
+
assert.ok(marker, 'Factory function should have created FactoryMarker on the stack');
|
|
105
|
+
});
|
|
64
106
|
});
|
|
65
107
|
|
|
66
108
|
describe('legacy side-effect mode (no default export)', () => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const marker = backend.node.tryFindChild('SideEffectMarker');
|
|
78
|
-
assert.ok(
|
|
79
|
-
marker,
|
|
80
|
-
'Side-effect-only module should register construct via globalThis.CURRENT_BLOCKS_STACK',
|
|
81
|
-
);
|
|
82
|
-
});
|
|
109
|
+
test('module with only side effects still registers constructs via globalThis', async () => {
|
|
110
|
+
const app = new cdk.App();
|
|
111
|
+
const stack = new cdk.Stack(app, 'LegacyTestStack');
|
|
112
|
+
|
|
113
|
+
const backend = await makeBackend(stack, 'LegacyStage', sideEffectBackendPath);
|
|
114
|
+
|
|
115
|
+
const marker = backend.node.tryFindChild('SideEffectMarker');
|
|
116
|
+
assert.ok(marker, 'Side-effect-only module should register construct via globalThis.CURRENT_BLOCKS_STACK');
|
|
117
|
+
});
|
|
83
118
|
});
|
|
84
119
|
|
|
85
120
|
describe('shared execution role (BlocksStack)', () => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
assert.ok(stack.executionRole, 'BlocksStack should expose a populated .executionRole');
|
|
99
|
-
});
|
|
121
|
+
// The role synth shape and the Scope.executionRole tree-walk are shared code
|
|
122
|
+
// (setupBlocksInfra + the getter), covered in blocks-backend.test.ts. The only
|
|
123
|
+
// BlocksStack-specific behavior is that its own constructor wires
|
|
124
|
+
// executionRole — a separate code path from BlocksBackend's constructor.
|
|
125
|
+
test('BlocksStack wires executionRole via its constructor', async () => {
|
|
126
|
+
const app = new cdk.App();
|
|
127
|
+
const stack = await makeStack(app, 'StackRoleStack', sideEffectBackendPath);
|
|
128
|
+
|
|
129
|
+
assert.ok(stack.executionRole, 'BlocksStack should expose a populated .executionRole');
|
|
130
|
+
});
|
|
100
131
|
});
|
|
101
132
|
|
|
102
133
|
describe('executionRole globalThis fallback', () => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
134
|
+
test('resolves via globalThis.CURRENT_BLOCKS_STACK when no owner is in the tree', async () => {
|
|
135
|
+
const app = new cdk.App();
|
|
136
|
+
const stack = await makeStack(app, 'FallbackStack', sideEffectBackendPath);
|
|
137
|
+
|
|
138
|
+
// A Scope whose construct-tree ancestry has no BlocksStack/BlocksBackend
|
|
139
|
+
// (parented under a plain cdk.Stack) exhausts the tree-walk and falls back
|
|
140
|
+
// to globalThis.CURRENT_BLOCKS_STACK. The `as any` is test plumbing — a
|
|
141
|
+
// plain Stack isn't a ScopeParent, but it IS a valid Construct parent.
|
|
142
|
+
const plainStack = new cdk.Stack(app, 'PlainStack');
|
|
143
|
+
(globalThis as any).CURRENT_BLOCKS_STACK = stack;
|
|
144
|
+
const orphan = new Scope('orphan', { parent: plainStack as any });
|
|
145
|
+
|
|
146
|
+
assert.strictEqual(orphan.executionRole, stack.executionRole, 'fallback resolves to the ambient stack role');
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
describe('root is bound to the owning stack (multi-stack synth)', () => {
|
|
151
|
+
test('a block under each of two stacks resolves its OWN stack, not the last globalThis', async () => {
|
|
152
|
+
// `root` is resolved once at Scope construction (not re-walked per getter),
|
|
153
|
+
// and `create()` mutates globalThis.CURRENT_BLOCKS_STACK for each stack it
|
|
154
|
+
// builds. With two stacks in one synth, a block constructed under the first
|
|
155
|
+
// must stay bound to the first even after the second stack is created and
|
|
156
|
+
// overwrites globalThis — otherwise its root-derived accessors (handler,
|
|
157
|
+
// executionRole, backendStackName) would silently point at the wrong stack.
|
|
158
|
+
const app = new cdk.App();
|
|
159
|
+
|
|
160
|
+
const stackA = await makeStack(app, 'RootBindingA', sideEffectBackendPath);
|
|
161
|
+
// A block explicitly parented under stackA (construct-tree walk resolves
|
|
162
|
+
// stackA regardless of the ambient globalThis).
|
|
163
|
+
const blockA = new Scope('blockA', { parent: stackA });
|
|
164
|
+
|
|
165
|
+
// Building the second stack overwrites globalThis.CURRENT_BLOCKS_STACK.
|
|
166
|
+
const stackB = await makeStack(app, 'RootBindingB', sideEffectBackendPath);
|
|
167
|
+
const blockB = new Scope('blockB', { parent: stackB });
|
|
168
|
+
|
|
169
|
+
assert.strictEqual(blockA.executionRole, stackA.executionRole, 'blockA stays bound to stackA');
|
|
170
|
+
assert.strictEqual(blockB.executionRole, stackB.executionRole, 'blockB binds to stackB');
|
|
171
|
+
assert.notStrictEqual(stackA.executionRole, stackB.executionRole, 'the two stacks have distinct roles');
|
|
172
|
+
assert.strictEqual(blockA.backendStackName, 'RootBindingA', 'blockA derives its own stack name');
|
|
173
|
+
assert.strictEqual(blockB.backendStackName, 'RootBindingB', 'blockB derives its own stack name');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('each stack owns an isolated compute registry (no cross-stack bleed)', async () => {
|
|
177
|
+
// Computes self-register on their owning stack (keyed per stack, not a
|
|
178
|
+
// process-global list), so a multi-stack synth keeps each stack's computes
|
|
179
|
+
// separate — finalize steps for one stack never see another's compute.
|
|
180
|
+
const app = new cdk.App();
|
|
181
|
+
|
|
182
|
+
const stackA = await makeStack(app, 'ComputeRegistryA', sideEffectBackendPath);
|
|
183
|
+
const stackB = await makeStack(app, 'ComputeRegistryB', sideEffectBackendPath);
|
|
184
|
+
|
|
185
|
+
const computesA = getComputes(stackA);
|
|
186
|
+
const computesB = getComputes(stackB);
|
|
187
|
+
|
|
188
|
+
assert.strictEqual(computesA.length, 1, 'stackA registered exactly its default compute');
|
|
189
|
+
assert.strictEqual(computesB.length, 1, 'stackB registered exactly its default compute');
|
|
190
|
+
assert.strictEqual(computesA[0], stackA._defaultCompute, 'stackA lists its own default');
|
|
191
|
+
assert.strictEqual(computesB[0], stackB._defaultCompute, 'stackB lists its own default');
|
|
192
|
+
assert.notStrictEqual(computesA[0], computesB[0], 'the two stacks hold distinct computes');
|
|
193
|
+
});
|
|
125
194
|
});
|
|
126
195
|
|
|
127
196
|
describe('assertCdkConditionActive', () => {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
});
|
|
197
|
+
test('BlocksStack.create() throws when --conditions=cdk is missing', async () => {
|
|
198
|
+
const origNodeOptions = process.env.NODE_OPTIONS;
|
|
199
|
+
const origExecArgv = process.execArgv;
|
|
200
|
+
process.env.NODE_OPTIONS = '';
|
|
201
|
+
process.execArgv = [];
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const app = new cdk.App();
|
|
205
|
+
|
|
206
|
+
await assert.rejects(
|
|
207
|
+
makeStack(app, 'MissingConditionStack', sideEffectBackendPath),
|
|
208
|
+
(err: Error) => {
|
|
209
|
+
assert.ok(
|
|
210
|
+
err.message.includes('Missing --conditions=cdk'),
|
|
211
|
+
`Expected condition error, got: ${err.message}`,
|
|
212
|
+
);
|
|
213
|
+
return true;
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
} finally {
|
|
217
|
+
process.env.NODE_OPTIONS = origNodeOptions;
|
|
218
|
+
process.execArgv = origExecArgv;
|
|
219
|
+
}
|
|
220
|
+
});
|
|
153
221
|
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import * as cdk from 'aws-cdk-lib';
|
|
5
|
+
import type { Construct } from 'constructs';
|
|
6
|
+
import type { Compute } from './compute.js';
|
|
7
|
+
|
|
8
|
+
const REGISTRY_KEY = Symbol.for('BLOCKS_COMPUTE_REGISTRY');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Get or create the compute list for a given stack. The list is stored on the
|
|
12
|
+
* stack object (keyed by a Symbol), so each stack in a multi-stack synth gets
|
|
13
|
+
* its own — a compute never leaks into another stack's list. Mirrors the config
|
|
14
|
+
* registry (`config-registry.ts`), which scopes its state the same way.
|
|
15
|
+
*/
|
|
16
|
+
function getRegistry(stack: cdk.Stack): Compute[] {
|
|
17
|
+
let list = (stack as any)[REGISTRY_KEY] as Compute[] | undefined;
|
|
18
|
+
if (!list) {
|
|
19
|
+
list = [];
|
|
20
|
+
(stack as any)[REGISTRY_KEY] = list;
|
|
21
|
+
}
|
|
22
|
+
return list;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Register a compute on its owning stack. Called from the {@link Compute} base
|
|
27
|
+
* constructor, so every compute self-registers the moment it is constructed —
|
|
28
|
+
* the finalize steps then enumerate them without a separate discovery pass
|
|
29
|
+
* (mirrors how `registerConfig` accumulates config during the backend import).
|
|
30
|
+
*
|
|
31
|
+
* @param compute - The compute to register (used to locate its stack).
|
|
32
|
+
*/
|
|
33
|
+
export function registerCompute(compute: Compute): void {
|
|
34
|
+
getRegistry(cdk.Stack.of(compute)).push(compute);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The computes registered on the stack that owns `scope`, in construction
|
|
39
|
+
* order. Returns an empty array before any compute is constructed.
|
|
40
|
+
*
|
|
41
|
+
* @param scope - Any construct in the stack (used to locate the stack).
|
|
42
|
+
*/
|
|
43
|
+
export function getComputes(scope: Construct): readonly Compute[] {
|
|
44
|
+
return getRegistry(cdk.Stack.of(scope));
|
|
45
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
+
import type { ScopeOptions } from '../../common/index.js';
|
|
4
5
|
import { Scope } from '../index.js';
|
|
6
|
+
import { registerCompute } from './compute-registry.js';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Base class for a Blocks *compute* — a runtime that executes handler code
|
|
@@ -28,6 +30,14 @@ export abstract class Compute extends Scope {
|
|
|
28
30
|
*/
|
|
29
31
|
readonly namespaces: string[] = [];
|
|
30
32
|
|
|
33
|
+
constructor(id: string, options?: ScopeOptions) {
|
|
34
|
+
super(id, options);
|
|
35
|
+
// Self-register on the owning stack so finalize steps (config, routing,
|
|
36
|
+
// dashboards) can enumerate every compute without a separate discovery
|
|
37
|
+
// pass. Scoped per stack, so a multi-stack synth keeps lists isolated.
|
|
38
|
+
registerCompute(this);
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
/**
|
|
32
42
|
* Inject a runtime configuration value (an environment variable) into this
|
|
33
43
|
* compute. The framework calls this instead of `handler.addEnvironment()`
|
|
@@ -0,0 +1,38 @@
|
|
|
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 type { Compute } from './compute.js';
|
|
6
|
+
import type { BlocksStack } from '../index.js';
|
|
7
|
+
import type { BlocksBackend } from '../blocks-backend.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Lambda-shaped surface of the default compute that the legacy
|
|
11
|
+
* `handler` / `gateway` / `apiUrl` accessors on BlocksStack/BlocksBackend read.
|
|
12
|
+
* The default compute (`LambdaCompute` from `@aws-blocks/bb-lambda-compute`)
|
|
13
|
+
* satisfies this structurally, so core exposes those accessors without
|
|
14
|
+
* importing the concrete class. To be removed with those accessors once
|
|
15
|
+
* consumers move to the multi-compute model.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export interface LambdaShapedCompute extends Compute {
|
|
20
|
+
readonly fn: cdk.aws_lambda_nodejs.NodejsFunction;
|
|
21
|
+
readonly apiGateway: cdk.aws_apigateway.RestApi;
|
|
22
|
+
readonly apiUrl: string;
|
|
23
|
+
readonly logGroup: cdk.aws_logs.ILogGroup;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Builds the default {@link Compute} for a stack/backend. `create()` reads it
|
|
28
|
+
* from its props (`CoreBlocksStackProps.defaultComputeFactory`) and calls it to
|
|
29
|
+
* build the default without importing a concrete compute class — the factory is
|
|
30
|
+
* supplied by whoever owns both core and a concrete compute package (the
|
|
31
|
+
* umbrella `@aws-blocks/blocks`, which injects `LambdaCompute`). It lives on
|
|
32
|
+
* `CoreBlocksStackProps`/`CoreBlocksBackendProps`, which the umbrella derives
|
|
33
|
+
* from the customer-facing props, so it is absent from `BlocksStackProps` and
|
|
34
|
+
* customers cannot set it.
|
|
35
|
+
*
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export type DefaultComputeFactory = (root: BlocksStack | BlocksBackend) => Compute;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CDK tests for the config registry — specifically the getConfigLocation ↔ finalizeConfigRegistry
|
|
6
|
+
* interaction. `getConfigLocation()` creates the config bucket eagerly (so co-located compute can
|
|
7
|
+
* inject BLOCKS_CONFIG_BUCKET/KEY at construction); finalize must therefore still upload the config
|
|
8
|
+
* object + wire the computes whenever a bucket exists, even if zero entries were registered —
|
|
9
|
+
* otherwise that compute's loadConfigToProcessEnv() would 404 forever against a created-but-empty
|
|
10
|
+
* bucket.
|
|
11
|
+
*/
|
|
12
|
+
import assert from 'node:assert';
|
|
13
|
+
import { afterEach, test } from 'node:test';
|
|
14
|
+
import * as cdk from 'aws-cdk-lib';
|
|
15
|
+
import { Match, Template } from 'aws-cdk-lib/assertions';
|
|
16
|
+
import { Construct } from 'constructs';
|
|
17
|
+
import { Compute } from './compute/compute.js';
|
|
18
|
+
import { finalizeConfigRegistry, getConfigLocation, registerConfig } from './config-registry.js';
|
|
19
|
+
import { DEFAULT_NODE_RUNTIME } from './node-version.js';
|
|
20
|
+
|
|
21
|
+
// getConfigLocation reads globalThis.CURRENT_BLOCKS_STACK to place the bucket under the owning
|
|
22
|
+
// stack/backend; clear it between tests so one test's owner never leaks into another.
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
delete (globalThis as any).CURRENT_BLOCKS_STACK;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// A real app's compute comes from @aws-blocks/bb-lambda-compute, which core's own tests can't
|
|
28
|
+
// depend on. This is the same shape: a Compute that owns a real Lambda function and injects config
|
|
29
|
+
// via addEnvironment — enough for finalizeConfigRegistry to stamp BLOCKS_CONFIG_BUCKET/KEY on it.
|
|
30
|
+
class TestCompute extends Compute {
|
|
31
|
+
readonly fn: cdk.aws_lambda.Function;
|
|
32
|
+
|
|
33
|
+
constructor(scope: Construct, id: string) {
|
|
34
|
+
super(id, { parent: scope as never });
|
|
35
|
+
this.fn = new cdk.aws_lambda.Function(this, 'Handler', {
|
|
36
|
+
runtime: DEFAULT_NODE_RUNTIME,
|
|
37
|
+
handler: 'index.handler',
|
|
38
|
+
code: cdk.aws_lambda.Code.fromInline('exports.handler = async () => {};'),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setEnv(key: string, value: string): void {
|
|
43
|
+
this.fn.addEnvironment(key, value);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function stackWithCompute(id: string): {
|
|
48
|
+
stack: cdk.Stack;
|
|
49
|
+
role: cdk.aws_iam.Role;
|
|
50
|
+
computes: readonly Compute[];
|
|
51
|
+
} {
|
|
52
|
+
const app = new cdk.App();
|
|
53
|
+
const stack = new cdk.Stack(app, id);
|
|
54
|
+
const role = new cdk.aws_iam.Role(stack, 'BlocksRole', {
|
|
55
|
+
assumedBy: new cdk.aws_iam.ServicePrincipal('lambda.amazonaws.com'),
|
|
56
|
+
});
|
|
57
|
+
const compute = new TestCompute(stack, 'Compute');
|
|
58
|
+
return { stack, role, computes: [compute] };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
test('finalize uploads + wires the computes even with zero entries when a bucket was created', () => {
|
|
62
|
+
const { stack, role, computes } = stackWithCompute('EmptyWithBucket');
|
|
63
|
+
// Simulate a co-located BB that creates the bucket but registers no config of its own.
|
|
64
|
+
getConfigLocation(stack);
|
|
65
|
+
finalizeConfigRegistry(stack, role, computes);
|
|
66
|
+
|
|
67
|
+
const t = Template.fromStack(stack);
|
|
68
|
+
assert.strictEqual(Object.keys(t.findResources('AWS::S3::Bucket')).length, 1, 'one config bucket');
|
|
69
|
+
t.resourceCountIs('Custom::CDKBucketDeployment', 1); // the (empty) blocks-config.json is uploaded
|
|
70
|
+
t.hasResourceProperties('AWS::Lambda::Function', {
|
|
71
|
+
Environment: { Variables: Match.objectLike({ BLOCKS_CONFIG_KEY: 'blocks-config.json' }) },
|
|
72
|
+
});
|
|
73
|
+
// Read is granted once to the shared role (not per-function), so every compute that assumes it
|
|
74
|
+
// — including co-located compute that never went through finalize — can read the object.
|
|
75
|
+
t.hasResourceProperties('AWS::IAM::Policy', {
|
|
76
|
+
PolicyDocument: {
|
|
77
|
+
Statement: Match.arrayWith([
|
|
78
|
+
Match.objectLike({ Action: Match.arrayWith([Match.stringLikeRegexp('^s3:GetObject')]) }),
|
|
79
|
+
]),
|
|
80
|
+
},
|
|
81
|
+
Roles: Match.arrayWith([Match.objectLike({ Ref: Match.stringLikeRegexp('BlocksRole') })]),
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('finalize is a no-op with zero entries and no bucket', () => {
|
|
86
|
+
const { stack, role, computes } = stackWithCompute('EmptyNoBucket');
|
|
87
|
+
finalizeConfigRegistry(stack, role, computes);
|
|
88
|
+
|
|
89
|
+
const t = Template.fromStack(stack);
|
|
90
|
+
assert.strictEqual(Object.keys(t.findResources('AWS::S3::Bucket')).length, 0, 'no config bucket created');
|
|
91
|
+
t.resourceCountIs('Custom::CDKBucketDeployment', 0);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('finalize uploads + wires the computes when config was registered (bucket auto-created)', () => {
|
|
95
|
+
const { stack, role, computes } = stackWithCompute('WithEntries');
|
|
96
|
+
registerConfig(stack, 'BLOCKS_SOMETHING', 'value');
|
|
97
|
+
finalizeConfigRegistry(stack, role, computes);
|
|
98
|
+
|
|
99
|
+
const t = Template.fromStack(stack);
|
|
100
|
+
assert.strictEqual(Object.keys(t.findResources('AWS::S3::Bucket')).length, 1, 'one config bucket');
|
|
101
|
+
t.resourceCountIs('Custom::CDKBucketDeployment', 1);
|
|
102
|
+
t.hasResourceProperties('AWS::Lambda::Function', {
|
|
103
|
+
Environment: { Variables: Match.objectLike({ BLOCKS_CONFIG_KEY: 'blocks-config.json' }) },
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('the config bucket is created under the owning stack/backend, not the (deep) caller scope', () => {
|
|
108
|
+
// Mimic a BlocksBackend embedded in a customer stack: the owner is a nested construct, and the
|
|
109
|
+
// first caller of getConfigLocation is a *deep* construct (like the AgentCore Runtime).
|
|
110
|
+
const app = new cdk.App();
|
|
111
|
+
const stack = new cdk.Stack(app, 'CustomerStack');
|
|
112
|
+
const owner = new Construct(stack, 'Embedded'); // stands in for the BlocksBackend construct
|
|
113
|
+
(globalThis as any).CURRENT_BLOCKS_STACK = owner;
|
|
114
|
+
const deepScope = new Construct(new Construct(owner, 'agent'), 'runtime');
|
|
115
|
+
|
|
116
|
+
getConfigLocation(deepScope);
|
|
117
|
+
|
|
118
|
+
const t = Template.fromStack(stack);
|
|
119
|
+
const bucketIds = Object.keys(t.findResources('AWS::S3::Bucket'));
|
|
120
|
+
assert.strictEqual(bucketIds.length, 1, 'exactly one config bucket');
|
|
121
|
+
// Logical IDs encode the construct path — under the owner it's `EmbeddedBlocksConfigBucket…`,
|
|
122
|
+
// at the stack root it would be `BlocksConfigBucket…`. Pin that it follows the owner.
|
|
123
|
+
assert.ok(bucketIds[0].startsWith('Embedded'), `bucket should be nested under the owner, got ${bucketIds[0]}`);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('getConfigLocation creates exactly one bucket across repeated calls (idempotent)', () => {
|
|
127
|
+
const app = new cdk.App();
|
|
128
|
+
const stack = new cdk.Stack(app, 'Idempotent');
|
|
129
|
+
const a = getConfigLocation(stack);
|
|
130
|
+
const b = getConfigLocation(stack);
|
|
131
|
+
assert.strictEqual(a.key, b.key, 'same config key');
|
|
132
|
+
assert.strictEqual(a.bucketName, b.bucketName, 'same bucket');
|
|
133
|
+
const t = Template.fromStack(stack);
|
|
134
|
+
assert.strictEqual(Object.keys(t.findResources('AWS::S3::Bucket')).length, 1, 'exactly one bucket');
|
|
135
|
+
});
|