@aws-blocks/core 0.1.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/LICENSE +174 -0
- package/dist/api.d.ts +147 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +10 -0
- package/dist/api.test.d.ts +2 -0
- package/dist/api.test.d.ts.map +1 -0
- package/dist/api.test.js +19 -0
- package/dist/bb-utils.d.ts +11 -0
- package/dist/bb-utils.d.ts.map +1 -0
- package/dist/bb-utils.js +12 -0
- package/dist/builtin-routes.d.ts +6 -0
- package/dist/builtin-routes.d.ts.map +1 -0
- package/dist/builtin-routes.js +57 -0
- package/dist/cdk/__fixtures__/factory-backend.d.ts +3 -0
- package/dist/cdk/__fixtures__/factory-backend.d.ts.map +1 -0
- package/dist/cdk/__fixtures__/factory-backend.js +6 -0
- package/dist/cdk/__fixtures__/fullid-construct-backend.d.ts +2 -0
- package/dist/cdk/__fixtures__/fullid-construct-backend.d.ts.map +1 -0
- package/dist/cdk/__fixtures__/fullid-construct-backend.js +37 -0
- package/dist/cdk/__fixtures__/handler.d.ts +5 -0
- package/dist/cdk/__fixtures__/handler.d.ts.map +1 -0
- package/dist/cdk/__fixtures__/handler.js +3 -0
- package/dist/cdk/__fixtures__/side-effect-backend.d.ts +2 -0
- package/dist/cdk/__fixtures__/side-effect-backend.d.ts.map +1 -0
- package/dist/cdk/__fixtures__/side-effect-backend.js +5 -0
- package/dist/cdk/blocks-backend.d.ts +68 -0
- package/dist/cdk/blocks-backend.d.ts.map +1 -0
- package/dist/cdk/blocks-backend.js +214 -0
- package/dist/cdk/blocks-backend.test.d.ts +2 -0
- package/dist/cdk/blocks-backend.test.d.ts.map +1 -0
- package/dist/cdk/blocks-backend.test.js +151 -0
- package/dist/cdk/blocks-stack.test.d.ts +2 -0
- package/dist/cdk/blocks-stack.test.d.ts.map +1 -0
- package/dist/cdk/blocks-stack.test.js +78 -0
- package/dist/cdk/config-registry.d.ts +26 -0
- package/dist/cdk/config-registry.d.ts.map +1 -0
- package/dist/cdk/config-registry.js +80 -0
- package/dist/cdk/index.d.ts +35 -0
- package/dist/cdk/index.d.ts.map +1 -0
- package/dist/cdk/index.js +90 -0
- package/dist/cdk/mixins.d.ts +28 -0
- package/dist/cdk/mixins.d.ts.map +1 -0
- package/dist/cdk/mixins.js +37 -0
- package/dist/cdk/mixins.test.d.ts +2 -0
- package/dist/cdk/mixins.test.d.ts.map +1 -0
- package/dist/cdk/mixins.test.js +108 -0
- package/dist/cdk/node-version.d.ts +14 -0
- package/dist/cdk/node-version.d.ts.map +1 -0
- package/dist/cdk/node-version.js +15 -0
- package/dist/cdk/stack-metadata.d.ts +12 -0
- package/dist/cdk/stack-metadata.d.ts.map +1 -0
- package/dist/cdk/stack-metadata.js +22 -0
- package/dist/cdk/stack-metadata.test.d.ts +2 -0
- package/dist/cdk/stack-metadata.test.d.ts.map +1 -0
- package/dist/cdk/stack-metadata.test.js +56 -0
- package/dist/cdk/synth-guard.d.ts +14 -0
- package/dist/cdk/synth-guard.d.ts.map +1 -0
- package/dist/cdk/synth-guard.js +20 -0
- package/dist/cdk/synth-guard.test.d.ts +2 -0
- package/dist/cdk/synth-guard.test.d.ts.map +1 -0
- package/dist/cdk/synth-guard.test.js +15 -0
- package/dist/client/index.d.ts +97 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +238 -0
- package/dist/client/index.test.d.ts +2 -0
- package/dist/client/index.test.d.ts.map +1 -0
- package/dist/client/index.test.js +113 -0
- package/dist/common/config.d.ts +45 -0
- package/dist/common/config.d.ts.map +1 -0
- package/dist/common/config.js +154 -0
- package/dist/common/config.test.d.ts +2 -0
- package/dist/common/config.test.d.ts.map +1 -0
- package/dist/common/config.test.js +173 -0
- package/dist/common/constants.d.ts +11 -0
- package/dist/common/constants.d.ts.map +1 -0
- package/dist/common/constants.js +12 -0
- package/dist/common/crypto.d.ts +6 -0
- package/dist/common/crypto.d.ts.map +1 -0
- package/dist/common/crypto.js +14 -0
- package/dist/common/index.d.ts +220 -0
- package/dist/common/index.d.ts.map +1 -0
- package/dist/common/index.js +180 -0
- package/dist/common/index.test.d.ts +2 -0
- package/dist/common/index.test.d.ts.map +1 -0
- package/dist/common/index.test.js +195 -0
- package/dist/common/mock-data.d.ts +40 -0
- package/dist/common/mock-data.d.ts.map +1 -0
- package/dist/common/mock-data.js +39 -0
- package/dist/common/official-bb-names.generated.d.ts +7 -0
- package/dist/common/official-bb-names.generated.d.ts.map +1 -0
- package/dist/common/official-bb-names.generated.js +28 -0
- package/dist/common/sdk-registry.d.ts +136 -0
- package/dist/common/sdk-registry.d.ts.map +1 -0
- package/dist/common/sdk-registry.js +125 -0
- package/dist/constants.d.ts +27 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +28 -0
- package/dist/cors.d.ts +39 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/cors.js +85 -0
- package/dist/cors.test.d.ts +2 -0
- package/dist/cors.test.d.ts.map +1 -0
- package/dist/cors.test.js +241 -0
- package/dist/db-naming.d.ts +21 -0
- package/dist/db-naming.d.ts.map +1 -0
- package/dist/db-naming.js +38 -0
- package/dist/db-naming.test.d.ts +2 -0
- package/dist/db-naming.test.d.ts.map +1 -0
- package/dist/db-naming.test.js +29 -0
- package/dist/errors.d.ts +67 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +69 -0
- package/dist/hosting.d.ts +258 -0
- package/dist/hosting.d.ts.map +1 -0
- package/dist/hosting.js +340 -0
- package/dist/hosting.test.d.ts +2 -0
- package/dist/hosting.test.d.ts.map +1 -0
- package/dist/hosting.test.js +1077 -0
- package/dist/index.cdk.d.ts +13 -0
- package/dist/index.cdk.d.ts.map +1 -0
- package/dist/index.cdk.js +13 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/lambda-handler.d.ts +131 -0
- package/dist/lambda-handler.d.ts.map +1 -0
- package/dist/lambda-handler.js +610 -0
- package/dist/lambda-handler.test.d.ts +2 -0
- package/dist/lambda-handler.test.d.ts.map +1 -0
- package/dist/lambda-handler.test.js +835 -0
- package/dist/pipeline/index.d.ts +4 -0
- package/dist/pipeline/index.d.ts.map +1 -0
- package/dist/pipeline/index.js +3 -0
- package/dist/pipeline/pipeline-construct.d.ts +130 -0
- package/dist/pipeline/pipeline-construct.d.ts.map +1 -0
- package/dist/pipeline/pipeline-construct.js +484 -0
- package/dist/pipeline/pipeline.test.d.ts +2 -0
- package/dist/pipeline/pipeline.test.d.ts.map +1 -0
- package/dist/pipeline/pipeline.test.js +1231 -0
- package/dist/pipeline/types.d.ts +351 -0
- package/dist/pipeline/types.d.ts.map +1 -0
- package/dist/pipeline/types.js +3 -0
- package/dist/raw-route.cdk.d.ts +47 -0
- package/dist/raw-route.cdk.d.ts.map +1 -0
- package/dist/raw-route.cdk.js +51 -0
- package/dist/raw-route.d.ts +119 -0
- package/dist/raw-route.d.ts.map +1 -0
- package/dist/raw-route.js +239 -0
- package/dist/raw-route.mock.d.ts +47 -0
- package/dist/raw-route.mock.d.ts.map +1 -0
- package/dist/raw-route.mock.js +51 -0
- package/dist/raw-route.test.d.ts +2 -0
- package/dist/raw-route.test.d.ts.map +1 -0
- package/dist/raw-route.test.js +597 -0
- package/dist/redact.d.ts +51 -0
- package/dist/redact.d.ts.map +1 -0
- package/dist/redact.js +134 -0
- package/dist/redact.test.d.ts +2 -0
- package/dist/redact.test.d.ts.map +1 -0
- package/dist/redact.test.js +199 -0
- package/dist/rpc.d.ts +54 -0
- package/dist/rpc.d.ts.map +1 -0
- package/dist/rpc.js +127 -0
- package/dist/rpc.test.d.ts +2 -0
- package/dist/rpc.test.d.ts.map +1 -0
- package/dist/rpc.test.js +60 -0
- package/dist/scripts/cdk-telemetry-env.d.ts +13 -0
- package/dist/scripts/cdk-telemetry-env.d.ts.map +1 -0
- package/dist/scripts/cdk-telemetry-env.js +21 -0
- package/dist/scripts/cdk-telemetry-env.test.d.ts +2 -0
- package/dist/scripts/cdk-telemetry-env.test.d.ts.map +1 -0
- package/dist/scripts/cdk-telemetry-env.test.js +56 -0
- package/dist/scripts/cleanup.d.ts +3 -0
- package/dist/scripts/cleanup.d.ts.map +1 -0
- package/dist/scripts/cleanup.js +35 -0
- package/dist/scripts/console.d.ts +6 -0
- package/dist/scripts/console.d.ts.map +1 -0
- package/dist/scripts/console.js +25 -0
- package/dist/scripts/deploy.d.ts +6 -0
- package/dist/scripts/deploy.d.ts.map +1 -0
- package/dist/scripts/deploy.js +82 -0
- package/dist/scripts/destroy.d.ts +6 -0
- package/dist/scripts/destroy.d.ts.map +1 -0
- package/dist/scripts/destroy.js +30 -0
- package/dist/scripts/dev-server-cors.test.d.ts +2 -0
- package/dist/scripts/dev-server-cors.test.d.ts.map +1 -0
- package/dist/scripts/dev-server-cors.test.js +23 -0
- package/dist/scripts/dev-server-telemetry.test.d.ts +2 -0
- package/dist/scripts/dev-server-telemetry.test.d.ts.map +1 -0
- package/dist/scripts/dev-server-telemetry.test.js +113 -0
- package/dist/scripts/dev-server.d.ts +21 -0
- package/dist/scripts/dev-server.d.ts.map +1 -0
- package/dist/scripts/dev-server.js +456 -0
- package/dist/scripts/ensure-secrets.d.ts +31 -0
- package/dist/scripts/ensure-secrets.d.ts.map +1 -0
- package/dist/scripts/ensure-secrets.js +100 -0
- package/dist/scripts/ensure-secrets.test.d.ts +2 -0
- package/dist/scripts/ensure-secrets.test.d.ts.map +1 -0
- package/dist/scripts/ensure-secrets.test.js +44 -0
- package/dist/scripts/external-migrations-step.d.ts +62 -0
- package/dist/scripts/external-migrations-step.d.ts.map +1 -0
- package/dist/scripts/external-migrations-step.js +243 -0
- package/dist/scripts/external-migrations-step.test.d.ts +2 -0
- package/dist/scripts/external-migrations-step.test.d.ts.map +1 -0
- package/dist/scripts/external-migrations-step.test.js +47 -0
- package/dist/scripts/extract-ts-types.d.ts +65 -0
- package/dist/scripts/extract-ts-types.d.ts.map +1 -0
- package/dist/scripts/extract-ts-types.js +664 -0
- package/dist/scripts/extract-ts-types.test.d.ts +2 -0
- package/dist/scripts/extract-ts-types.test.d.ts.map +1 -0
- package/dist/scripts/extract-ts-types.test.js +497 -0
- package/dist/scripts/generate-client-worker.d.ts +2 -0
- package/dist/scripts/generate-client-worker.d.ts.map +1 -0
- package/dist/scripts/generate-client-worker.js +13 -0
- package/dist/scripts/generate-client.d.ts +14 -0
- package/dist/scripts/generate-client.d.ts.map +1 -0
- package/dist/scripts/generate-client.js +73 -0
- package/dist/scripts/generate-client.test.d.ts +2 -0
- package/dist/scripts/generate-client.test.d.ts.map +1 -0
- package/dist/scripts/generate-client.test.js +48 -0
- package/dist/scripts/generate-spec-cli.d.ts +3 -0
- package/dist/scripts/generate-spec-cli.d.ts.map +1 -0
- package/dist/scripts/generate-spec-cli.js +77 -0
- package/dist/scripts/generate-spec-cli.test.d.ts +14 -0
- package/dist/scripts/generate-spec-cli.test.d.ts.map +1 -0
- package/dist/scripts/generate-spec-cli.test.js +90 -0
- package/dist/scripts/generate-spec.d.ts +61 -0
- package/dist/scripts/generate-spec.d.ts.map +1 -0
- package/dist/scripts/generate-spec.js +746 -0
- package/dist/scripts/generate-spec.test.d.ts +12 -0
- package/dist/scripts/generate-spec.test.d.ts.map +1 -0
- package/dist/scripts/generate-spec.test.js +88 -0
- package/dist/scripts/index.d.ts +12 -0
- package/dist/scripts/index.d.ts.map +1 -0
- package/dist/scripts/index.js +13 -0
- package/dist/scripts/sandbox.d.ts +11 -0
- package/dist/scripts/sandbox.d.ts.map +1 -0
- package/dist/scripts/sandbox.js +200 -0
- package/dist/scripts/telemetry-cli.d.ts +3 -0
- package/dist/scripts/telemetry-cli.d.ts.map +1 -0
- package/dist/scripts/telemetry-cli.js +8 -0
- package/dist/scripts/telemetry.d.ts +17 -0
- package/dist/scripts/telemetry.d.ts.map +1 -0
- package/dist/scripts/telemetry.js +150 -0
- package/dist/scripts/telemetry.test.d.ts +2 -0
- package/dist/scripts/telemetry.test.d.ts.map +1 -0
- package/dist/scripts/telemetry.test.js +203 -0
- package/dist/scripts/validate-spec.d.ts +22 -0
- package/dist/scripts/validate-spec.d.ts.map +1 -0
- package/dist/scripts/validate-spec.js +159 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3 -0
- package/dist/server/withAuth.d.ts +142 -0
- package/dist/server/withAuth.d.ts.map +1 -0
- package/dist/server/withAuth.js +261 -0
- package/dist/server/withAuth.test.d.ts +2 -0
- package/dist/server/withAuth.test.d.ts.map +1 -0
- package/dist/server/withAuth.test.js +278 -0
- package/dist/telemetry/client.d.ts +59 -0
- package/dist/telemetry/client.d.ts.map +1 -0
- package/dist/telemetry/client.js +215 -0
- package/dist/telemetry/config-writer.d.ts +16 -0
- package/dist/telemetry/config-writer.d.ts.map +1 -0
- package/dist/telemetry/config-writer.js +36 -0
- package/dist/telemetry/consent-commands.test.d.ts +2 -0
- package/dist/telemetry/consent-commands.test.d.ts.map +1 -0
- package/dist/telemetry/consent-commands.test.js +161 -0
- package/dist/telemetry/consent.d.ts +60 -0
- package/dist/telemetry/consent.d.ts.map +1 -0
- package/dist/telemetry/consent.js +122 -0
- package/dist/telemetry/environment.d.ts +33 -0
- package/dist/telemetry/environment.d.ts.map +1 -0
- package/dist/telemetry/environment.js +87 -0
- package/dist/telemetry/identifiers.d.ts +20 -0
- package/dist/telemetry/identifiers.d.ts.map +1 -0
- package/dist/telemetry/identifiers.js +86 -0
- package/dist/telemetry/index.d.ts +6 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/index.js +4 -0
- package/dist/telemetry/telemetry.test.d.ts +2 -0
- package/dist/telemetry/telemetry.test.d.ts.map +1 -0
- package/dist/telemetry/telemetry.test.js +1131 -0
- package/dist/telemetry/trackCommand.d.ts +32 -0
- package/dist/telemetry/trackCommand.d.ts.map +1 -0
- package/dist/telemetry/trackCommand.js +102 -0
- package/dist/telemetry/types.d.ts +92 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/telemetry/types.js +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/package.json +83 -0
- package/src/api.test.ts +25 -0
- package/src/api.ts +163 -0
- package/src/bb-utils.ts +13 -0
- package/src/builtin-routes.ts +62 -0
- package/src/cdk/__fixtures__/factory-backend.ts +8 -0
- package/src/cdk/__fixtures__/fullid-construct-backend.ts +42 -0
- package/src/cdk/__fixtures__/handler.ts +4 -0
- package/src/cdk/__fixtures__/side-effect-backend.ts +7 -0
- package/src/cdk/blocks-backend.test.ts +207 -0
- package/src/cdk/blocks-backend.ts +249 -0
- package/src/cdk/blocks-stack.test.ts +106 -0
- package/src/cdk/config-registry.ts +106 -0
- package/src/cdk/index.ts +115 -0
- package/src/cdk/mixins.test.ts +129 -0
- package/src/cdk/mixins.ts +41 -0
- package/src/cdk/node-version.ts +17 -0
- package/src/cdk/stack-metadata.test.ts +71 -0
- package/src/cdk/stack-metadata.ts +29 -0
- package/src/cdk/synth-guard.test.ts +21 -0
- package/src/cdk/synth-guard.ts +23 -0
- package/src/client/index.test.ts +118 -0
- package/src/client/index.ts +320 -0
- package/src/common/config.test.ts +236 -0
- package/src/common/config.ts +169 -0
- package/src/common/constants.ts +15 -0
- package/src/common/crypto.ts +15 -0
- package/src/common/index.test.ts +242 -0
- package/src/common/index.ts +335 -0
- package/src/common/mock-data.ts +52 -0
- package/src/common/official-bb-names.generated.ts +29 -0
- package/src/common/sdk-registry.ts +150 -0
- package/src/constants.ts +31 -0
- package/src/cors.test.ts +279 -0
- package/src/cors.ts +91 -0
- package/src/db-naming.test.ts +48 -0
- package/src/db-naming.ts +41 -0
- package/src/errors.ts +72 -0
- package/src/hosting.test.ts +1431 -0
- package/src/hosting.ts +626 -0
- package/src/index.cdk.ts +45 -0
- package/src/index.ts +22 -0
- package/src/lambda-handler.test.ts +1005 -0
- package/src/lambda-handler.ts +706 -0
- package/src/pipeline/index.ts +12 -0
- package/src/pipeline/pipeline-construct.ts +662 -0
- package/src/pipeline/pipeline.test.ts +1565 -0
- package/src/pipeline/types.ts +384 -0
- package/src/raw-route.cdk.ts +56 -0
- package/src/raw-route.mock.ts +56 -0
- package/src/raw-route.test.ts +819 -0
- package/src/raw-route.ts +316 -0
- package/src/redact.test.ts +222 -0
- package/src/redact.ts +143 -0
- package/src/rpc.test.ts +73 -0
- package/src/rpc.ts +174 -0
- package/src/scripts/cdk-telemetry-env.test.ts +75 -0
- package/src/scripts/cdk-telemetry-env.ts +23 -0
- package/src/scripts/cleanup.ts +39 -0
- package/src/scripts/console.ts +34 -0
- package/src/scripts/deploy.ts +106 -0
- package/src/scripts/destroy.ts +42 -0
- package/src/scripts/dev-server-cors.test.ts +28 -0
- package/src/scripts/dev-server-telemetry.test.ts +127 -0
- package/src/scripts/dev-server.ts +515 -0
- package/src/scripts/ensure-secrets.test.ts +50 -0
- package/src/scripts/ensure-secrets.ts +113 -0
- package/src/scripts/external-migrations-step.test.ts +64 -0
- package/src/scripts/external-migrations-step.ts +269 -0
- package/src/scripts/extract-ts-types.test.ts +516 -0
- package/src/scripts/extract-ts-types.ts +773 -0
- package/src/scripts/generate-client-worker.ts +15 -0
- package/src/scripts/generate-client.test.ts +69 -0
- package/src/scripts/generate-client.ts +86 -0
- package/src/scripts/generate-spec-cli.test.ts +106 -0
- package/src/scripts/generate-spec-cli.ts +92 -0
- package/src/scripts/generate-spec.test.ts +99 -0
- package/src/scripts/generate-spec.ts +833 -0
- package/src/scripts/index.ts +21 -0
- package/src/scripts/sandbox.ts +230 -0
- package/src/scripts/telemetry-cli.ts +10 -0
- package/src/scripts/telemetry.test.ts +239 -0
- package/src/scripts/telemetry.ts +173 -0
- package/src/scripts/validate-spec.ts +199 -0
- package/src/server/index.ts +5 -0
- package/src/server/withAuth.test.ts +348 -0
- package/src/server/withAuth.ts +323 -0
- package/src/telemetry/client.ts +226 -0
- package/src/telemetry/config-writer.ts +52 -0
- package/src/telemetry/consent-commands.test.ts +196 -0
- package/src/telemetry/consent.ts +154 -0
- package/src/telemetry/environment.ts +100 -0
- package/src/telemetry/identifiers.ts +94 -0
- package/src/telemetry/index.ts +11 -0
- package/src/telemetry/telemetry.test.ts +1343 -0
- package/src/telemetry/trackCommand.ts +110 -0
- package/src/telemetry/types.ts +92 -0
- package/src/types.ts +33 -0
- package/src/version.ts +2 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { createServer } from 'node:http';
|
|
4
|
+
import { pathToFileURL, URL } from 'node:url';
|
|
5
|
+
import { resolve, dirname, join } from 'node:path';
|
|
6
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { createConnection } from 'node:net';
|
|
9
|
+
import httpProxy from 'http-proxy';
|
|
10
|
+
import { writeClientCode } from './generate-client.js';
|
|
11
|
+
import { ApiError } from '../errors.js';
|
|
12
|
+
import { BLOCKS_RPC_PREFIX } from '../constants.js';
|
|
13
|
+
import { matchRoute, lockRouteRegistry } from '../raw-route.js';
|
|
14
|
+
import { registerBuiltinRoutes } from '../builtin-routes.js';
|
|
15
|
+
import { parseRpcRequest, successResponse, errorResponseFromCatch, methodNotFoundResponse, } from '../rpc.js';
|
|
16
|
+
import { redactToJson } from '../redact.js';
|
|
17
|
+
import { buildAndSendEvent } from '../telemetry/client.js';
|
|
18
|
+
import { applyDevMigrations } from './external-migrations-step.js';
|
|
19
|
+
function toBodyStream(text) {
|
|
20
|
+
if (!text)
|
|
21
|
+
return null;
|
|
22
|
+
return new ReadableStream({
|
|
23
|
+
start(controller) {
|
|
24
|
+
controller.enqueue(new TextEncoder().encode(text));
|
|
25
|
+
controller.close();
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export const LOCALHOST_PATTERN = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the CORS origin for the dev server.
|
|
32
|
+
* Reflects back origins matching localhost/127.0.0.1; otherwise returns the fallback.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveDevCorsOrigin(origin) {
|
|
35
|
+
return LOCALHOST_PATTERN.test(origin) ? origin : 'http://localhost:3000';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Initialize all building blocks that have an initialize() method.
|
|
39
|
+
* This is the "local deploy" phase - mirrors CDK deploy.
|
|
40
|
+
*/
|
|
41
|
+
async function deployLocal(backend) {
|
|
42
|
+
const initPromises = [];
|
|
43
|
+
for (const [name, value] of Object.entries(backend)) {
|
|
44
|
+
if (value && typeof value.initialize === 'function') {
|
|
45
|
+
console.log(` Initializing ${name}...`);
|
|
46
|
+
initPromises.push(value.initialize());
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
await Promise.all(initPromises);
|
|
50
|
+
}
|
|
51
|
+
/** Wait for a port to accept TCP connections. */
|
|
52
|
+
async function waitForPort(port, maxAttempts = 60) {
|
|
53
|
+
const { setTimeout: sleep } = await import('node:timers/promises');
|
|
54
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
55
|
+
const connected = await new Promise((resolve) => {
|
|
56
|
+
const socket = createConnection({ port, host: 'localhost' }, () => {
|
|
57
|
+
socket.destroy();
|
|
58
|
+
resolve(true);
|
|
59
|
+
});
|
|
60
|
+
socket.on('error', () => { socket.destroy(); resolve(false); });
|
|
61
|
+
socket.setTimeout(300, () => { socket.destroy(); resolve(false); });
|
|
62
|
+
});
|
|
63
|
+
if (connected)
|
|
64
|
+
return;
|
|
65
|
+
await sleep(500);
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`Frontend server on port ${port} did not start within ${maxAttempts * 500}ms`);
|
|
68
|
+
}
|
|
69
|
+
export async function startDevServer(options) {
|
|
70
|
+
const { port = 3000, backendPath, frontendCommand, frontendPort = 3100, } = options;
|
|
71
|
+
const devStartTime = Date.now();
|
|
72
|
+
// Load .env.local if present (connection strings, project refs, etc.)
|
|
73
|
+
try {
|
|
74
|
+
process.loadEnvFile('.env.local');
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
if (e.code !== 'ENOENT')
|
|
78
|
+
throw e;
|
|
79
|
+
}
|
|
80
|
+
// Apply pending external-database migrations to the dev database so the schema
|
|
81
|
+
// change is live locally and the generated types are refreshed from it.
|
|
82
|
+
// No-op for managed/PGlite apps; refuses if .env.local points at production.
|
|
83
|
+
await applyDevMigrations();
|
|
84
|
+
// Resolve path and convert to file URL for dynamic import
|
|
85
|
+
const resolvedPath = resolve(backendPath);
|
|
86
|
+
const backendUrl = pathToFileURL(resolvedPath).href;
|
|
87
|
+
// Detect sandbox: if BLOCKS_API_URL env var is set (by sandbox.ts), proxy to it.
|
|
88
|
+
const isSandbox = !!process.env.BLOCKS_API_URL;
|
|
89
|
+
const apiTarget = isSandbox ? process.env.BLOCKS_API_URL.replace(/\/aws-blocks\/api$/, '') : null;
|
|
90
|
+
// Write config for client-side JS — always reflects the current mode.
|
|
91
|
+
// In sandbox, point the browser at the localhost front door (not the raw
|
|
92
|
+
// execute-api URL) so the data-plane RPC is same-origin with the page and the
|
|
93
|
+
// session cookie — set host-scoped to localhost during the auth callback — is
|
|
94
|
+
// sent on every request. The dev server proxies `/aws-blocks/api` to
|
|
95
|
+
// BLOCKS_API_URL server-side, so the request still reaches the deployed Lambda.
|
|
96
|
+
// This makes sandbox single-origin, matching `npm run dev` and the prod
|
|
97
|
+
// CloudFront proxy; `crossDomain` stays unnecessary.
|
|
98
|
+
mkdirSync('.blocks-sandbox', { recursive: true });
|
|
99
|
+
writeFileSync('.blocks-sandbox/config.json', JSON.stringify({
|
|
100
|
+
apiUrl: `http://localhost:${port}${BLOCKS_RPC_PREFIX}`,
|
|
101
|
+
environment: isSandbox ? 'sandbox' : 'local',
|
|
102
|
+
}, null, 2));
|
|
103
|
+
// 1. Set up global collectors for plugin discovery
|
|
104
|
+
globalThis.__BLOCKS_CLIENT_MIDDLEWARE__ = [];
|
|
105
|
+
globalThis.__BLOCKS_DEV_ATTACHMENTS__ = [];
|
|
106
|
+
// 2. Import backend (sync construction phase — BBs register plugins via globals)
|
|
107
|
+
console.log('Loading backend...');
|
|
108
|
+
const backend = await import(backendUrl);
|
|
109
|
+
// 3. Read collected dev attachments and clean up
|
|
110
|
+
const devAttachments = globalThis.__BLOCKS_DEV_ATTACHMENTS__;
|
|
111
|
+
delete globalThis.__BLOCKS_DEV_ATTACHMENTS__;
|
|
112
|
+
// 4. Deploy local (async initialization phase) — skip in sandbox mode
|
|
113
|
+
if (!isSandbox) {
|
|
114
|
+
console.log('Deploying local resources...');
|
|
115
|
+
await deployLocal(backend);
|
|
116
|
+
}
|
|
117
|
+
// 5. Collect APIs for runtime
|
|
118
|
+
const apis = new Map();
|
|
119
|
+
for (const [exportName, exportValue] of Object.entries(backend)) {
|
|
120
|
+
if (typeof exportValue === 'function' || typeof exportValue === 'object') {
|
|
121
|
+
apis.set(exportName, exportValue);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
registerBuiltinRoutes();
|
|
125
|
+
lockRouteRegistry();
|
|
126
|
+
// ── Frontend proxy ─────────────────────────────────────────────────────
|
|
127
|
+
let frontendProcess = null;
|
|
128
|
+
const frontendProxy = frontendCommand
|
|
129
|
+
? httpProxy.createProxyServer({ target: `http://localhost:${frontendPort}`, ws: true })
|
|
130
|
+
: null;
|
|
131
|
+
frontendProxy?.on('error', (_err, _req, res) => {
|
|
132
|
+
if (!res || typeof res.writeHead !== 'function')
|
|
133
|
+
return;
|
|
134
|
+
if (res.headersSent)
|
|
135
|
+
return;
|
|
136
|
+
res.writeHead(502);
|
|
137
|
+
res.end('Frontend server unavailable');
|
|
138
|
+
});
|
|
139
|
+
// ── API Gateway proxy (sandbox mode) ───────────────────────────────────
|
|
140
|
+
// `changeOrigin: true` rewrites the outgoing `Host` to the execute-api target
|
|
141
|
+
// (required for API Gateway's TLS SNI / host-based routing). That would make
|
|
142
|
+
// the backend compute absolute URLs (OIDC redirect_uri, stub issuer URLs)
|
|
143
|
+
// against the execute-api host instead of this localhost front door, breaking
|
|
144
|
+
// redirect-based auth in sandbox. We forward the real front-door host via
|
|
145
|
+
// `X-Forwarded-Host`; the Lambda honors it only because it is loopback (see
|
|
146
|
+
// `isLoopbackForwardedHost` in lambda-handler.ts).
|
|
147
|
+
const apiProxy = apiTarget
|
|
148
|
+
? httpProxy.createProxyServer({
|
|
149
|
+
target: apiTarget,
|
|
150
|
+
changeOrigin: true,
|
|
151
|
+
headers: { 'X-Forwarded-Host': `localhost:${port}` },
|
|
152
|
+
})
|
|
153
|
+
: null;
|
|
154
|
+
apiProxy?.on('error', (err, _req, res) => {
|
|
155
|
+
if (res.headersSent)
|
|
156
|
+
return;
|
|
157
|
+
res.writeHead(502);
|
|
158
|
+
res.end(JSON.stringify({ error: 'API Gateway unavailable', details: err.message }));
|
|
159
|
+
});
|
|
160
|
+
// ── Request handler ────────────────────────────────────────────────────
|
|
161
|
+
function isApiRequest(method, pathname) {
|
|
162
|
+
if (pathname === BLOCKS_RPC_PREFIX || pathname.startsWith(BLOCKS_RPC_PREFIX + '/'))
|
|
163
|
+
return true;
|
|
164
|
+
if (matchRoute(method, pathname))
|
|
165
|
+
return true;
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
const server = createServer((req, res) => {
|
|
169
|
+
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
|
170
|
+
const method = req.method || 'GET';
|
|
171
|
+
// CORS headers
|
|
172
|
+
const requestOrigin = req.headers.origin || '';
|
|
173
|
+
const allowedOrigin = resolveDevCorsOrigin(requestOrigin);
|
|
174
|
+
res.setHeader('Access-Control-Allow-Origin', allowedOrigin);
|
|
175
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS');
|
|
176
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
177
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
178
|
+
res.setHeader('Access-Control-Max-Age', '86400');
|
|
179
|
+
if (method === 'OPTIONS') {
|
|
180
|
+
res.writeHead(200);
|
|
181
|
+
res.end();
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// ── API/RawRoute requests ──────────────────────────────────────────
|
|
185
|
+
if (isApiRequest(method, url.pathname)) {
|
|
186
|
+
if (isSandbox && apiProxy) {
|
|
187
|
+
apiProxy.web(req, res);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
handleApiRequest(req, res, url, method, apis);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
// ── Frontend requests ──────────────────────────────────────────────
|
|
194
|
+
if (frontendProxy) {
|
|
195
|
+
frontendProxy.web(req, res);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
res.writeHead(404);
|
|
199
|
+
res.end();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
// WebSocket upgrade — route to frontend (HMR) or dev attachments
|
|
203
|
+
server.on('upgrade', (req, socket, head) => {
|
|
204
|
+
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
|
205
|
+
if (url.pathname === '/realtime')
|
|
206
|
+
return; // handled by dev attachment (noServer mode)
|
|
207
|
+
if (frontendProxy) {
|
|
208
|
+
frontendProxy.ws(req, socket, head);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
socket.destroy();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
// ── Attach dev servers ─────────────────────────────────────────────────
|
|
215
|
+
for (const specifier of devAttachments) {
|
|
216
|
+
console.log(` 🔌 Attaching dev server (from ${specifier})`);
|
|
217
|
+
const mod = await import(specifier);
|
|
218
|
+
if (typeof mod.attach !== 'function') {
|
|
219
|
+
throw new Error(`Dev attachment '${specifier}' does not export an attach() function`);
|
|
220
|
+
}
|
|
221
|
+
await mod.attach(server);
|
|
222
|
+
}
|
|
223
|
+
globalThis.__BLOCKS_REALTIME_WS_URL__ = `ws://localhost:${port}/realtime`;
|
|
224
|
+
globalThis.__BLOCKS_DEV_SERVER_PORT__ = port;
|
|
225
|
+
// Config already written during startup (local mode overwrites, sandbox mode preserves).
|
|
226
|
+
// Generate client code — skip in sandbox mode because sandbox.ts already
|
|
227
|
+
// generated it with --conditions=aws-runtime (correct aws-middleware).
|
|
228
|
+
// Re-generating here without that condition would overwrite with mock-middleware.
|
|
229
|
+
if (!isSandbox) {
|
|
230
|
+
const awsBlocksDir = dirname(resolvedPath);
|
|
231
|
+
const clientPath = join(awsBlocksDir, 'client.js');
|
|
232
|
+
console.log('📝 Generating client code...');
|
|
233
|
+
await writeClientCode(resolvedPath, clientPath);
|
|
234
|
+
}
|
|
235
|
+
// ── Start listening ────────────────────────────────────────────────────
|
|
236
|
+
server.listen(port, '127.0.0.1', async () => {
|
|
237
|
+
console.log(`AWS Blocks local server running on http://localhost:${port}`);
|
|
238
|
+
buildAndSendEvent({ command: 'dev', state: 'SUCCESS', duration: Date.now() - devStartTime });
|
|
239
|
+
// Spawn frontend dev server after Blocks server is ready
|
|
240
|
+
if (frontendCommand) {
|
|
241
|
+
frontendProcess = spawn(frontendCommand, {
|
|
242
|
+
shell: true,
|
|
243
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
244
|
+
env: { ...process.env, NODE_OPTIONS: '' },
|
|
245
|
+
});
|
|
246
|
+
// Suppress frontend output — only show errors
|
|
247
|
+
frontendProcess.stderr?.on('data', (d) => {
|
|
248
|
+
const msg = d.toString();
|
|
249
|
+
if (!msg.includes('DeprecationWarning'))
|
|
250
|
+
process.stderr.write(msg);
|
|
251
|
+
});
|
|
252
|
+
frontendProcess.on('exit', (code) => {
|
|
253
|
+
if (code !== 0 && code !== null) {
|
|
254
|
+
console.error(`⚠️ Frontend process exited with code ${code}`);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
try {
|
|
258
|
+
await waitForPort(frontendPort);
|
|
259
|
+
console.log(`\n ➜ http://localhost:${port}/\n`);
|
|
260
|
+
}
|
|
261
|
+
catch (e) {
|
|
262
|
+
console.error(`⚠️ Frontend did not start: ${e.message}`);
|
|
263
|
+
console.log(`\n ➜ http://localhost:${port}/ (API only — frontend unavailable)\n`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
console.log(`\n ➜ http://localhost:${port}/\n`);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
server.on('error', (err) => {
|
|
271
|
+
const errorCode = err.code === 'EADDRINUSE' ? 'PORT_IN_USE' : 'UNKNOWN';
|
|
272
|
+
buildAndSendEvent({ command: 'dev', state: 'FAIL', duration: Date.now() - devStartTime, error: { code: errorCode, phase: 'startup' } });
|
|
273
|
+
});
|
|
274
|
+
// ── Cleanup ────────────────────────────────────────────────────────────
|
|
275
|
+
const cleanup = async () => {
|
|
276
|
+
console.log('\nShutting down...');
|
|
277
|
+
if (frontendProcess)
|
|
278
|
+
frontendProcess.kill('SIGTERM');
|
|
279
|
+
if (typeof backend.__cleanup === 'function') {
|
|
280
|
+
try {
|
|
281
|
+
await backend.__cleanup();
|
|
282
|
+
}
|
|
283
|
+
catch { }
|
|
284
|
+
}
|
|
285
|
+
frontendProxy?.close();
|
|
286
|
+
apiProxy?.close();
|
|
287
|
+
server.close(() => process.exit(0));
|
|
288
|
+
setTimeout(() => process.exit(0), 2000).unref();
|
|
289
|
+
};
|
|
290
|
+
process.on('SIGINT', cleanup);
|
|
291
|
+
process.on('SIGTERM', cleanup);
|
|
292
|
+
}
|
|
293
|
+
// ── Local API handler ────────────────────────────────────────────────────────
|
|
294
|
+
function handleApiRequest(req, res, url, method, apis) {
|
|
295
|
+
if (method === 'POST' && url.pathname === BLOCKS_RPC_PREFIX) {
|
|
296
|
+
let body = '';
|
|
297
|
+
req.on('data', chunk => body += chunk);
|
|
298
|
+
req.on('end', async () => {
|
|
299
|
+
const rpcHeaders = { 'Content-Type': 'application/json' };
|
|
300
|
+
// Verbose request/response logging — useful when debugging cross-stack
|
|
301
|
+
// wire format mismatches (e.g. native client codegen vs server). Set
|
|
302
|
+
// BLOCKS_DEV_QUIET=1 to suppress. Sensitive fields (passwords, session
|
|
303
|
+
// tokens, OTP codes) are redacted so they never reach the log stream.
|
|
304
|
+
if (!process.env.BLOCKS_DEV_QUIET) {
|
|
305
|
+
let inLog;
|
|
306
|
+
try {
|
|
307
|
+
inLog = redactToJson(JSON.parse(body || '{}'));
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
// Body isn't valid JSON (parse error surfaced below) — fall back to
|
|
311
|
+
// the raw text. A malformed body can't carry a structured secret,
|
|
312
|
+
// but truncate it like before to keep logs readable.
|
|
313
|
+
inLog = body;
|
|
314
|
+
}
|
|
315
|
+
console.log('[rpc-in]', inLog.length > 800 ? inLog.slice(0, 800) + '…' : inLog);
|
|
316
|
+
}
|
|
317
|
+
const parsed = parseRpcRequest(body);
|
|
318
|
+
if (!parsed.ok) {
|
|
319
|
+
if (!process.env.BLOCKS_DEV_QUIET)
|
|
320
|
+
console.log('[rpc-out parse-error]', parsed.response);
|
|
321
|
+
res.writeHead(200, rpcHeaders);
|
|
322
|
+
res.end(parsed.response);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const { apiNamespace, method: rpcMethod, args, id: rpcId } = parsed.request;
|
|
326
|
+
if (!process.env.BLOCKS_DEV_QUIET) {
|
|
327
|
+
// redactToJson handles circulars and serialization failures itself.
|
|
328
|
+
console.log('[rpc-call]', `${apiNamespace}.${rpcMethod}`, redactToJson(args));
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
const headers = new Headers();
|
|
332
|
+
Object.entries(req.headers).forEach(([k, v]) => {
|
|
333
|
+
headers.set(k, Array.isArray(v) ? v[0] : v || '');
|
|
334
|
+
});
|
|
335
|
+
let responseStatus = 200;
|
|
336
|
+
const responseHeaders = new Headers({ 'Content-Type': 'application/json' });
|
|
337
|
+
let responseBody;
|
|
338
|
+
const context = {
|
|
339
|
+
request: {
|
|
340
|
+
headers,
|
|
341
|
+
body: toBodyStream(body),
|
|
342
|
+
json: async () => JSON.parse(body),
|
|
343
|
+
text: async () => body,
|
|
344
|
+
url: new URL(req.url || BLOCKS_RPC_PREFIX, `http://${req.headers.host}`),
|
|
345
|
+
params: {},
|
|
346
|
+
},
|
|
347
|
+
response: {
|
|
348
|
+
headers: responseHeaders,
|
|
349
|
+
get status() { return responseStatus; },
|
|
350
|
+
set status(code) { responseStatus = code; },
|
|
351
|
+
send: (b) => { responseBody = b; },
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
const apiHandler = apis.get(apiNamespace);
|
|
355
|
+
if (!apiHandler) {
|
|
356
|
+
res.writeHead(200, rpcHeaders);
|
|
357
|
+
res.end(methodNotFoundResponse(`API '${apiNamespace}' not found. Available: ${Array.from(apis.keys()).join(', ')}`, rpcId));
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const apiMethods = typeof apiHandler === 'function' ? apiHandler(context) : apiHandler;
|
|
361
|
+
if (!apiMethods[rpcMethod]) {
|
|
362
|
+
res.writeHead(200, rpcHeaders);
|
|
363
|
+
res.end(methodNotFoundResponse(`'${rpcMethod}' on API '${apiNamespace}'`, rpcId));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
const result = await apiMethods[rpcMethod](...args);
|
|
367
|
+
const headerObj = {};
|
|
368
|
+
for (const [key, value] of responseHeaders.entries()) {
|
|
369
|
+
if (key === 'set-cookie')
|
|
370
|
+
continue;
|
|
371
|
+
headerObj[key] = value;
|
|
372
|
+
}
|
|
373
|
+
const setCookies = responseHeaders.getSetCookie?.() ?? [];
|
|
374
|
+
if (setCookies.length > 0)
|
|
375
|
+
headerObj['set-cookie'] = setCookies;
|
|
376
|
+
const successPayload = successResponse(responseBody ?? result, rpcId);
|
|
377
|
+
if (!process.env.BLOCKS_DEV_QUIET) {
|
|
378
|
+
// Log a redacted copy of the response value — never the raw
|
|
379
|
+
// payload, which can carry challenge `session` tokens, MFA shared
|
|
380
|
+
// secrets, etc. that the client legitimately round-trips.
|
|
381
|
+
const okLog = redactToJson(responseBody ?? result);
|
|
382
|
+
console.log('[rpc-ok]', `${apiNamespace}.${rpcMethod}`, okLog.length > 800 ? okLog.slice(0, 800) + '…' : okLog);
|
|
383
|
+
}
|
|
384
|
+
res.writeHead(responseStatus, headerObj);
|
|
385
|
+
res.end(successPayload);
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
const errPayload = errorResponseFromCatch(error, rpcId);
|
|
389
|
+
if (!process.env.BLOCKS_DEV_QUIET) {
|
|
390
|
+
console.log('[rpc-err]', `${apiNamespace}.${rpcMethod}`, error?.name ?? 'Error', '-', error?.message);
|
|
391
|
+
if (error?.stack)
|
|
392
|
+
console.log(error.stack);
|
|
393
|
+
}
|
|
394
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
395
|
+
res.end(errPayload);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
// RawRoute dispatch
|
|
401
|
+
const matched = matchRoute(method, url.pathname);
|
|
402
|
+
if (matched) {
|
|
403
|
+
let body = '';
|
|
404
|
+
req.on('data', (chunk) => body += chunk);
|
|
405
|
+
req.on('end', async () => {
|
|
406
|
+
try {
|
|
407
|
+
const headers = new Headers();
|
|
408
|
+
Object.entries(req.headers).forEach(([k, v]) => {
|
|
409
|
+
headers.set(k, Array.isArray(v) ? v[0] : v || '');
|
|
410
|
+
});
|
|
411
|
+
let responseStatus = 200;
|
|
412
|
+
const responseHeaders = new Headers({ 'Content-Type': 'application/json' });
|
|
413
|
+
let responseBody;
|
|
414
|
+
const context = {
|
|
415
|
+
request: {
|
|
416
|
+
headers,
|
|
417
|
+
body: toBodyStream(body),
|
|
418
|
+
json: async () => JSON.parse(body),
|
|
419
|
+
text: async () => body,
|
|
420
|
+
url,
|
|
421
|
+
params: matched.params,
|
|
422
|
+
},
|
|
423
|
+
response: {
|
|
424
|
+
headers: responseHeaders,
|
|
425
|
+
get status() { return responseStatus; },
|
|
426
|
+
set status(code) { responseStatus = code; },
|
|
427
|
+
send: (b) => { responseBody = b; },
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
await matched.route.handler(context);
|
|
431
|
+
const headerObj = {};
|
|
432
|
+
for (const [key, value] of responseHeaders.entries()) {
|
|
433
|
+
if (key === 'set-cookie')
|
|
434
|
+
continue;
|
|
435
|
+
headerObj[key] = value;
|
|
436
|
+
}
|
|
437
|
+
const setCookies = responseHeaders.getSetCookie?.() ?? [];
|
|
438
|
+
if (setCookies.length > 0)
|
|
439
|
+
headerObj['set-cookie'] = setCookies;
|
|
440
|
+
res.writeHead(responseStatus, headerObj);
|
|
441
|
+
res.end(responseBody !== undefined ? (typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody)) : '');
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
const status = error instanceof ApiError ? error.status : 500;
|
|
445
|
+
const errBody = { error: error.message };
|
|
446
|
+
if (error.name && error.name !== 'Error')
|
|
447
|
+
errBody.name = error.name;
|
|
448
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
449
|
+
res.end(JSON.stringify(errBody));
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
res.writeHead(404);
|
|
455
|
+
res.end();
|
|
456
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface EnsureSecretsResult {
|
|
2
|
+
created: string[];
|
|
3
|
+
updated: string[];
|
|
4
|
+
unchanged: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function findConnectionString(): {
|
|
7
|
+
name: string;
|
|
8
|
+
value: string;
|
|
9
|
+
} | null;
|
|
10
|
+
/**
|
|
11
|
+
* Ensure the connection string is stored in SSM for the current stage.
|
|
12
|
+
*/
|
|
13
|
+
export declare function ensureSecrets(stage?: 'sandbox' | 'production'): Promise<EnsureSecretsResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Load environment for production deployment.
|
|
16
|
+
*
|
|
17
|
+
* Loads `.env.production` into `process.env` when present, then returns.
|
|
18
|
+
* If the file is absent this is a no-op — a missing `.env.production` is
|
|
19
|
+
* valid for templates that need no production-only configuration (e.g. the
|
|
20
|
+
* default DynamoDB template, Next.js, auth-cognito).
|
|
21
|
+
*
|
|
22
|
+
* Note: this function intentionally does NOT require any specific connection
|
|
23
|
+
* string. A Building Block that connects to an external database (e.g. via
|
|
24
|
+
* `fromExisting()`) is responsible for asserting its own configuration during
|
|
25
|
+
* synth/deploy, where the requirement can be checked against the construct
|
|
26
|
+
* tree rather than guessed at by the generic deploy script.
|
|
27
|
+
*/
|
|
28
|
+
export declare function loadProductionEnv(): void;
|
|
29
|
+
/** Load a .env file into process.env. Uses Node 21.7+ API with fallback. */
|
|
30
|
+
export declare function loadEnvFile(filePath: string): void;
|
|
31
|
+
//# sourceMappingURL=ensure-secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ensure-secrets.d.ts","sourceRoot":"","sources":["../../src/scripts/ensure-secrets.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,wBAAgB,oBAAoB,IAAI;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAO7E;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,YAAY,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAsClG;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAIxC;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAelD"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Pre-deploy secret provisioning.
|
|
5
|
+
*
|
|
6
|
+
* Writes the connection string to an SSM SecureString parameter.
|
|
7
|
+
* Parameter name includes the stage to prevent sandbox/prod collision:
|
|
8
|
+
* /blocks/{stage}/db-connection-string
|
|
9
|
+
*
|
|
10
|
+
* On first deploy: creates the parameter.
|
|
11
|
+
* On subsequent deploys: updates if value changed, no-op otherwise.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
14
|
+
import { dbConnectionParameterName } from '../db-naming.js';
|
|
15
|
+
const CONNECTION_STRING_PATTERN = /_(DB_URL|CONNECTION_STRING)$/;
|
|
16
|
+
export function findConnectionString() {
|
|
17
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
18
|
+
if (CONNECTION_STRING_PATTERN.test(name) && value) {
|
|
19
|
+
return { name, value };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Ensure the connection string is stored in SSM for the current stage.
|
|
26
|
+
*/
|
|
27
|
+
export async function ensureSecrets(stage) {
|
|
28
|
+
const result = { created: [], updated: [], unchanged: [] };
|
|
29
|
+
const conn = findConnectionString();
|
|
30
|
+
if (!conn)
|
|
31
|
+
return result;
|
|
32
|
+
const resolvedStage = stage ?? process.env.BLOCKS_STAGE ?? 'sandbox';
|
|
33
|
+
const { SSMClient, GetParameterCommand, PutParameterCommand } = await import('@aws-sdk/client-ssm');
|
|
34
|
+
const client = new SSMClient();
|
|
35
|
+
const parameterName = dbConnectionParameterName(resolvedStage);
|
|
36
|
+
let isNew = false;
|
|
37
|
+
try {
|
|
38
|
+
const current = await client.send(new GetParameterCommand({
|
|
39
|
+
Name: parameterName,
|
|
40
|
+
WithDecryption: true,
|
|
41
|
+
}));
|
|
42
|
+
if (current.Parameter?.Value === conn.value) {
|
|
43
|
+
result.unchanged.push(parameterName);
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
if (e.name !== 'ParameterNotFound')
|
|
49
|
+
throw e;
|
|
50
|
+
isNew = true;
|
|
51
|
+
}
|
|
52
|
+
await client.send(new PutParameterCommand({
|
|
53
|
+
Name: parameterName,
|
|
54
|
+
Value: conn.value,
|
|
55
|
+
Type: 'SecureString',
|
|
56
|
+
Overwrite: true,
|
|
57
|
+
}));
|
|
58
|
+
(isNew ? result.created : result.updated).push(parameterName);
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Load environment for production deployment.
|
|
63
|
+
*
|
|
64
|
+
* Loads `.env.production` into `process.env` when present, then returns.
|
|
65
|
+
* If the file is absent this is a no-op — a missing `.env.production` is
|
|
66
|
+
* valid for templates that need no production-only configuration (e.g. the
|
|
67
|
+
* default DynamoDB template, Next.js, auth-cognito).
|
|
68
|
+
*
|
|
69
|
+
* Note: this function intentionally does NOT require any specific connection
|
|
70
|
+
* string. A Building Block that connects to an external database (e.g. via
|
|
71
|
+
* `fromExisting()`) is responsible for asserting its own configuration during
|
|
72
|
+
* synth/deploy, where the requirement can be checked against the construct
|
|
73
|
+
* tree rather than guessed at by the generic deploy script.
|
|
74
|
+
*/
|
|
75
|
+
export function loadProductionEnv() {
|
|
76
|
+
if (existsSync('.env.production')) {
|
|
77
|
+
loadEnvFile('.env.production');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Load a .env file into process.env. Uses Node 21.7+ API with fallback. */
|
|
81
|
+
export function loadEnvFile(filePath) {
|
|
82
|
+
if (typeof process.loadEnvFile === 'function') {
|
|
83
|
+
process.loadEnvFile(filePath);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
87
|
+
for (const line of content.split('\n')) {
|
|
88
|
+
const trimmed = line.trim();
|
|
89
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
90
|
+
continue;
|
|
91
|
+
const eqIdx = trimmed.indexOf('=');
|
|
92
|
+
if (eqIdx === -1)
|
|
93
|
+
continue;
|
|
94
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
95
|
+
const value = trimmed.slice(eqIdx + 1).trim();
|
|
96
|
+
if (!process.env[key])
|
|
97
|
+
process.env[key] = value;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ensure-secrets.test.d.ts","sourceRoot":"","sources":["../../src/scripts/ensure-secrets.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
4
|
+
import assert from 'node:assert';
|
|
5
|
+
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { tmpdir } from 'node:os';
|
|
8
|
+
import { loadProductionEnv } from './ensure-secrets.js';
|
|
9
|
+
describe('loadProductionEnv', () => {
|
|
10
|
+
let workDir;
|
|
11
|
+
let originalCwd;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
originalCwd = process.cwd();
|
|
14
|
+
workDir = mkdtempSync(join(tmpdir(), 'load-prod-env-'));
|
|
15
|
+
process.chdir(workDir);
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
process.chdir(originalCwd);
|
|
19
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
// Regression for bug bash item #2: deploy of a non-Supabase template
|
|
22
|
+
// (no .env.production, no SUPABASE_DB_URL) must not throw.
|
|
23
|
+
it('does not throw when .env.production is absent and no connection string is set', () => {
|
|
24
|
+
delete process.env.SUPABASE_DB_URL;
|
|
25
|
+
assert.doesNotThrow(() => loadProductionEnv());
|
|
26
|
+
});
|
|
27
|
+
it('does not throw when .env.production is absent even without any DB env var', () => {
|
|
28
|
+
delete process.env.SUPABASE_DB_URL;
|
|
29
|
+
delete process.env.DATABASE_URL;
|
|
30
|
+
assert.doesNotThrow(() => loadProductionEnv());
|
|
31
|
+
});
|
|
32
|
+
it('loads variables from .env.production when present', () => {
|
|
33
|
+
const key = 'LOAD_PROD_ENV_TEST_VAR';
|
|
34
|
+
delete process.env[key];
|
|
35
|
+
writeFileSync(join(workDir, '.env.production'), `${key}=hello-prod\n`);
|
|
36
|
+
try {
|
|
37
|
+
loadProductionEnv();
|
|
38
|
+
assert.strictEqual(process.env[key], 'hello-prod');
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
delete process.env[key];
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
});
|