@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,127 @@
|
|
|
1
|
+
import { describe, it, afterEach } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { createServer, type Server } from 'node:http';
|
|
4
|
+
import { writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { dirname } from 'node:path';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
describe('dev-server telemetry integration', () => {
|
|
14
|
+
let telemetryServer: Server | null = null;
|
|
15
|
+
let devProcess: ChildProcess | null = null;
|
|
16
|
+
let tmpDir: string | null = null;
|
|
17
|
+
|
|
18
|
+
afterEach(async () => {
|
|
19
|
+
if (devProcess && !devProcess.killed) {
|
|
20
|
+
devProcess.kill('SIGTERM');
|
|
21
|
+
await new Promise(r => setTimeout(r, 500));
|
|
22
|
+
if (!devProcess.killed) devProcess.kill('SIGKILL');
|
|
23
|
+
}
|
|
24
|
+
if (telemetryServer) {
|
|
25
|
+
telemetryServer.close();
|
|
26
|
+
}
|
|
27
|
+
if (tmpDir) {
|
|
28
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('sends telemetry event when dev server starts successfully', async () => {
|
|
33
|
+
const received: string[] = [];
|
|
34
|
+
|
|
35
|
+
// 1. Start a mock telemetry endpoint
|
|
36
|
+
telemetryServer = await new Promise<Server>((resolve) => {
|
|
37
|
+
const s = createServer((req, res) => {
|
|
38
|
+
let body = '';
|
|
39
|
+
req.on('data', (chunk) => { body += chunk; });
|
|
40
|
+
req.on('end', () => {
|
|
41
|
+
received.push(body);
|
|
42
|
+
res.writeHead(200);
|
|
43
|
+
res.end();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
s.listen(0, '127.0.0.1', () => resolve(s));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const addr = telemetryServer.address() as { port: number };
|
|
50
|
+
|
|
51
|
+
// 2. Create a minimal backend file and preload script in a temp directory
|
|
52
|
+
tmpDir = join(tmpdir(), `dev-telemetry-test-${Date.now()}`);
|
|
53
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
54
|
+
writeFileSync(join(tmpDir, 'backend.ts'), `export const hello = { greet: () => 'world' };\n`);
|
|
55
|
+
|
|
56
|
+
// Polyfill process.loadEnvFile for Node <21
|
|
57
|
+
writeFileSync(join(tmpDir, 'preload.mjs'), `
|
|
58
|
+
if (!process.loadEnvFile) {
|
|
59
|
+
process.loadEnvFile = () => { throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }); };
|
|
60
|
+
}
|
|
61
|
+
`);
|
|
62
|
+
|
|
63
|
+
const scriptContent = `
|
|
64
|
+
import { startDevServer } from '${join(__dirname, 'dev-server.js').replace(/\\/g, '/')}';
|
|
65
|
+
startDevServer({ backendPath: '${join(tmpDir!, 'backend.ts').replace(/\\/g, '/')}', port: 19877 });
|
|
66
|
+
`;
|
|
67
|
+
const scriptPath = join(tmpDir, 'run-dev.ts');
|
|
68
|
+
writeFileSync(scriptPath, scriptContent);
|
|
69
|
+
|
|
70
|
+
// 3. Start the dev server as a child process with telemetry endpoint configured
|
|
71
|
+
const tsxBin = join(__dirname, '..', '..', '..', '..', 'node_modules', '.bin', 'tsx');
|
|
72
|
+
devProcess = spawn(tsxBin, ['--import', join(tmpDir, 'preload.mjs'), scriptPath], {
|
|
73
|
+
env: {
|
|
74
|
+
...process.env,
|
|
75
|
+
BLOCKS_TELEMETRY_ENDPOINT: `http://127.0.0.1:${addr.port}/collect`,
|
|
76
|
+
AWS_BLOCKS_DISABLE_TELEMETRY: '',
|
|
77
|
+
CI: '',
|
|
78
|
+
CONTINUOUS_INTEGRATION: '',
|
|
79
|
+
BUILD_NUMBER: '',
|
|
80
|
+
CODEBUILD_BUILD_ID: '',
|
|
81
|
+
GITHUB_ACTIONS: '',
|
|
82
|
+
GITLAB_CI: '',
|
|
83
|
+
CIRCLECI: '',
|
|
84
|
+
JENKINS_URL: '',
|
|
85
|
+
TF_BUILD: '',
|
|
86
|
+
BITBUCKET_BUILD_NUMBER: '',
|
|
87
|
+
BUILDKITE: '',
|
|
88
|
+
BLOCKS_DEV_QUIET: '1',
|
|
89
|
+
},
|
|
90
|
+
cwd: tmpDir,
|
|
91
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
let stdout = '';
|
|
95
|
+
let stderr = '';
|
|
96
|
+
devProcess.stdout?.on('data', (d) => { stdout += d.toString(); });
|
|
97
|
+
devProcess.stderr?.on('data', (d) => { stderr += d.toString(); });
|
|
98
|
+
|
|
99
|
+
// 4. Wait for telemetry event to arrive (up to 15 seconds)
|
|
100
|
+
const deadline = Date.now() + 15_000;
|
|
101
|
+
while (received.length === 0 && Date.now() < deadline) {
|
|
102
|
+
await new Promise(r => setTimeout(r, 200));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 5. Verify a telemetry event was received
|
|
106
|
+
assert.ok(received.length >= 1, `Expected telemetry event but received ${received.length}. stdout: ${stdout.slice(0, 500)}, stderr: ${stderr.slice(0, 500)}`);
|
|
107
|
+
|
|
108
|
+
const parsed = JSON.parse(received[0]);
|
|
109
|
+
assert.strictEqual(parsed.telemetryVersion, '1.0.0');
|
|
110
|
+
assert.strictEqual(parsed.event.command, 'dev');
|
|
111
|
+
assert.strictEqual(parsed.event.state, 'SUCCESS');
|
|
112
|
+
assert.ok(parsed.event.duration >= 0);
|
|
113
|
+
assert.ok(parsed.identifiers.installationId);
|
|
114
|
+
assert.ok(parsed.identifiers.projectId);
|
|
115
|
+
assert.ok(parsed.identifiers.eventId);
|
|
116
|
+
assert.ok(parsed.identifiers.timestamp);
|
|
117
|
+
assert.ok(parsed.environment.os);
|
|
118
|
+
assert.ok(parsed.environment.nodeVersion);
|
|
119
|
+
assert.strictEqual(typeof parsed.environment.ci, 'boolean');
|
|
120
|
+
|
|
121
|
+
// Verify no extra top-level fields (server uses additionalProperties: false)
|
|
122
|
+
const allowedTopLevel = ['telemetryVersion', 'identifiers', 'event', 'environment', 'product', 'counters'];
|
|
123
|
+
for (const key of Object.keys(parsed)) {
|
|
124
|
+
assert.ok(allowedTopLevel.includes(key), `Unexpected top-level field: ${key}`);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
|
5
|
+
import { pathToFileURL, URL } from 'node:url';
|
|
6
|
+
import { resolve, dirname, join } from 'node:path';
|
|
7
|
+
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
8
|
+
import { spawn, type ChildProcess } from 'node:child_process';
|
|
9
|
+
import { createConnection } from 'node:net';
|
|
10
|
+
import httpProxy from 'http-proxy';
|
|
11
|
+
import { writeClientCode } from './generate-client.js';
|
|
12
|
+
import { ApiError } from '../errors.js';
|
|
13
|
+
import { BLOCKS_RPC_PREFIX } from '../constants.js';
|
|
14
|
+
import { matchRoute, lockRouteRegistry } from '../raw-route.js';
|
|
15
|
+
import { registerBuiltinRoutes } from '../builtin-routes.js';
|
|
16
|
+
import {
|
|
17
|
+
parseRpcRequest,
|
|
18
|
+
successResponse,
|
|
19
|
+
errorResponseFromCatch,
|
|
20
|
+
methodNotFoundResponse,
|
|
21
|
+
} from '../rpc.js';
|
|
22
|
+
import { redactToJson } from '../redact.js';
|
|
23
|
+
import { buildAndSendEvent } from '../telemetry/client.js';
|
|
24
|
+
import { applyDevMigrations } from './external-migrations-step.js';
|
|
25
|
+
|
|
26
|
+
function toBodyStream(text: string): ReadableStream<Uint8Array> | null {
|
|
27
|
+
if (!text) return null;
|
|
28
|
+
return new ReadableStream<Uint8Array>({
|
|
29
|
+
start(controller) {
|
|
30
|
+
controller.enqueue(new TextEncoder().encode(text));
|
|
31
|
+
controller.close();
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const LOCALHOST_PATTERN = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the CORS origin for the dev server.
|
|
40
|
+
* Reflects back origins matching localhost/127.0.0.1; otherwise returns the fallback.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveDevCorsOrigin(origin: string): string {
|
|
43
|
+
return LOCALHOST_PATTERN.test(origin) ? origin : 'http://localhost:3000';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface DevServerOptions {
|
|
47
|
+
/** Customer-facing port. Default: 3000. */
|
|
48
|
+
port?: number;
|
|
49
|
+
/** Path to the backend index.ts. */
|
|
50
|
+
backendPath: string;
|
|
51
|
+
/**
|
|
52
|
+
* Command to start the frontend dev server (e.g., 'npx vite --port 3100 --strictPort').
|
|
53
|
+
* Omit to run backend-only (no frontend proxy).
|
|
54
|
+
*/
|
|
55
|
+
frontendCommand?: string;
|
|
56
|
+
/** Port the frontend dev server listens on. Default: 3100. */
|
|
57
|
+
frontendPort?: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Initialize all building blocks that have an initialize() method.
|
|
62
|
+
* This is the "local deploy" phase - mirrors CDK deploy.
|
|
63
|
+
*/
|
|
64
|
+
async function deployLocal(backend: Record<string, any>): Promise<void> {
|
|
65
|
+
const initPromises: Promise<void>[] = [];
|
|
66
|
+
for (const [name, value] of Object.entries(backend)) {
|
|
67
|
+
if (value && typeof value.initialize === 'function') {
|
|
68
|
+
console.log(` Initializing ${name}...`);
|
|
69
|
+
initPromises.push(value.initialize());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
await Promise.all(initPromises);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Wait for a port to accept TCP connections. */
|
|
76
|
+
async function waitForPort(port: number, maxAttempts = 60): Promise<void> {
|
|
77
|
+
const { setTimeout: sleep } = await import('node:timers/promises');
|
|
78
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
79
|
+
const connected = await new Promise<boolean>((resolve) => {
|
|
80
|
+
const socket = createConnection({ port, host: 'localhost' }, () => {
|
|
81
|
+
socket.destroy();
|
|
82
|
+
resolve(true);
|
|
83
|
+
});
|
|
84
|
+
socket.on('error', () => { socket.destroy(); resolve(false); });
|
|
85
|
+
socket.setTimeout(300, () => { socket.destroy(); resolve(false); });
|
|
86
|
+
});
|
|
87
|
+
if (connected) return;
|
|
88
|
+
await sleep(500);
|
|
89
|
+
}
|
|
90
|
+
throw new Error(`Frontend server on port ${port} did not start within ${maxAttempts * 500}ms`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function startDevServer(options: DevServerOptions) {
|
|
94
|
+
const {
|
|
95
|
+
port = 3000,
|
|
96
|
+
backendPath,
|
|
97
|
+
frontendCommand,
|
|
98
|
+
frontendPort = 3100,
|
|
99
|
+
} = options;
|
|
100
|
+
const devStartTime = Date.now();
|
|
101
|
+
|
|
102
|
+
// Load .env.local if present (connection strings, project refs, etc.)
|
|
103
|
+
try { process.loadEnvFile('.env.local'); } catch (e: any) {
|
|
104
|
+
if (e.code !== 'ENOENT') throw e;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Apply pending external-database migrations to the dev database so the schema
|
|
108
|
+
// change is live locally and the generated types are refreshed from it.
|
|
109
|
+
// No-op for managed/PGlite apps; refuses if .env.local points at production.
|
|
110
|
+
await applyDevMigrations();
|
|
111
|
+
|
|
112
|
+
// Resolve path and convert to file URL for dynamic import
|
|
113
|
+
const resolvedPath = resolve(backendPath);
|
|
114
|
+
const backendUrl = pathToFileURL(resolvedPath).href;
|
|
115
|
+
|
|
116
|
+
// Detect sandbox: if BLOCKS_API_URL env var is set (by sandbox.ts), proxy to it.
|
|
117
|
+
const isSandbox = !!process.env.BLOCKS_API_URL;
|
|
118
|
+
const apiTarget = isSandbox ? process.env.BLOCKS_API_URL!.replace(/\/aws-blocks\/api$/, '') : null;
|
|
119
|
+
|
|
120
|
+
// Write config for client-side JS — always reflects the current mode.
|
|
121
|
+
// In sandbox, point the browser at the localhost front door (not the raw
|
|
122
|
+
// execute-api URL) so the data-plane RPC is same-origin with the page and the
|
|
123
|
+
// session cookie — set host-scoped to localhost during the auth callback — is
|
|
124
|
+
// sent on every request. The dev server proxies `/aws-blocks/api` to
|
|
125
|
+
// BLOCKS_API_URL server-side, so the request still reaches the deployed Lambda.
|
|
126
|
+
// This makes sandbox single-origin, matching `npm run dev` and the prod
|
|
127
|
+
// CloudFront proxy; `crossDomain` stays unnecessary.
|
|
128
|
+
mkdirSync('.blocks-sandbox', { recursive: true });
|
|
129
|
+
writeFileSync('.blocks-sandbox/config.json', JSON.stringify({
|
|
130
|
+
apiUrl: `http://localhost:${port}${BLOCKS_RPC_PREFIX}`,
|
|
131
|
+
environment: isSandbox ? 'sandbox' : 'local',
|
|
132
|
+
}, null, 2));
|
|
133
|
+
|
|
134
|
+
// 1. Set up global collectors for plugin discovery
|
|
135
|
+
(globalThis as any).__BLOCKS_CLIENT_MIDDLEWARE__ = [];
|
|
136
|
+
(globalThis as any).__BLOCKS_DEV_ATTACHMENTS__ = [];
|
|
137
|
+
|
|
138
|
+
// 2. Import backend (sync construction phase — BBs register plugins via globals)
|
|
139
|
+
console.log('Loading backend...');
|
|
140
|
+
const backend = await import(backendUrl);
|
|
141
|
+
|
|
142
|
+
// 3. Read collected dev attachments and clean up
|
|
143
|
+
const devAttachments: string[] = (globalThis as any).__BLOCKS_DEV_ATTACHMENTS__;
|
|
144
|
+
delete (globalThis as any).__BLOCKS_DEV_ATTACHMENTS__;
|
|
145
|
+
|
|
146
|
+
// 4. Deploy local (async initialization phase) — skip in sandbox mode
|
|
147
|
+
if (!isSandbox) {
|
|
148
|
+
console.log('Deploying local resources...');
|
|
149
|
+
await deployLocal(backend);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 5. Collect APIs for runtime
|
|
153
|
+
const apis = new Map<string, any>();
|
|
154
|
+
for (const [exportName, exportValue] of Object.entries(backend)) {
|
|
155
|
+
if (typeof exportValue === 'function' || typeof exportValue === 'object') {
|
|
156
|
+
apis.set(exportName, exportValue);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
registerBuiltinRoutes();
|
|
161
|
+
lockRouteRegistry();
|
|
162
|
+
|
|
163
|
+
// ── Frontend proxy ─────────────────────────────────────────────────────
|
|
164
|
+
let frontendProcess: ChildProcess | null = null;
|
|
165
|
+
const frontendProxy = frontendCommand
|
|
166
|
+
? httpProxy.createProxyServer({ target: `http://localhost:${frontendPort}`, ws: true })
|
|
167
|
+
: null;
|
|
168
|
+
|
|
169
|
+
frontendProxy?.on('error', (_err, _req, res) => {
|
|
170
|
+
if (!res || typeof (res as any).writeHead !== 'function') return;
|
|
171
|
+
if ((res as ServerResponse).headersSent) return;
|
|
172
|
+
(res as ServerResponse).writeHead(502);
|
|
173
|
+
(res as ServerResponse).end('Frontend server unavailable');
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// ── API Gateway proxy (sandbox mode) ───────────────────────────────────
|
|
177
|
+
// `changeOrigin: true` rewrites the outgoing `Host` to the execute-api target
|
|
178
|
+
// (required for API Gateway's TLS SNI / host-based routing). That would make
|
|
179
|
+
// the backend compute absolute URLs (OIDC redirect_uri, stub issuer URLs)
|
|
180
|
+
// against the execute-api host instead of this localhost front door, breaking
|
|
181
|
+
// redirect-based auth in sandbox. We forward the real front-door host via
|
|
182
|
+
// `X-Forwarded-Host`; the Lambda honors it only because it is loopback (see
|
|
183
|
+
// `isLoopbackForwardedHost` in lambda-handler.ts).
|
|
184
|
+
const apiProxy = apiTarget
|
|
185
|
+
? httpProxy.createProxyServer({
|
|
186
|
+
target: apiTarget,
|
|
187
|
+
changeOrigin: true,
|
|
188
|
+
headers: { 'X-Forwarded-Host': `localhost:${port}` },
|
|
189
|
+
})
|
|
190
|
+
: null;
|
|
191
|
+
|
|
192
|
+
apiProxy?.on('error', (err, _req, res) => {
|
|
193
|
+
if ((res as ServerResponse).headersSent) return;
|
|
194
|
+
(res as ServerResponse).writeHead(502);
|
|
195
|
+
(res as ServerResponse).end(JSON.stringify({ error: 'API Gateway unavailable', details: err.message }));
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// ── Request handler ────────────────────────────────────────────────────
|
|
199
|
+
function isApiRequest(method: string, pathname: string): boolean {
|
|
200
|
+
if (pathname === BLOCKS_RPC_PREFIX || pathname.startsWith(BLOCKS_RPC_PREFIX + '/')) return true;
|
|
201
|
+
if (matchRoute(method, pathname)) return true;
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const server = createServer((req: IncomingMessage, res: ServerResponse) => {
|
|
206
|
+
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
|
207
|
+
const method = req.method || 'GET';
|
|
208
|
+
|
|
209
|
+
// CORS headers
|
|
210
|
+
const requestOrigin = req.headers.origin || '';
|
|
211
|
+
const allowedOrigin = resolveDevCorsOrigin(requestOrigin);
|
|
212
|
+
res.setHeader('Access-Control-Allow-Origin', allowedOrigin);
|
|
213
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS');
|
|
214
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
215
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true');
|
|
216
|
+
res.setHeader('Access-Control-Max-Age', '86400');
|
|
217
|
+
|
|
218
|
+
if (method === 'OPTIONS') {
|
|
219
|
+
res.writeHead(200);
|
|
220
|
+
res.end();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── API/RawRoute requests ──────────────────────────────────────────
|
|
225
|
+
if (isApiRequest(method, url.pathname)) {
|
|
226
|
+
if (isSandbox && apiProxy) {
|
|
227
|
+
apiProxy.web(req, res);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
handleApiRequest(req, res, url, method, apis);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ── Frontend requests ──────────────────────────────────────────────
|
|
235
|
+
if (frontendProxy) {
|
|
236
|
+
frontendProxy.web(req, res);
|
|
237
|
+
} else {
|
|
238
|
+
res.writeHead(404);
|
|
239
|
+
res.end();
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// WebSocket upgrade — route to frontend (HMR) or dev attachments
|
|
244
|
+
server.on('upgrade', (req, socket, head) => {
|
|
245
|
+
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
|
246
|
+
if (url.pathname === '/realtime') return; // handled by dev attachment (noServer mode)
|
|
247
|
+
if (frontendProxy) {
|
|
248
|
+
frontendProxy.ws(req, socket, head);
|
|
249
|
+
} else {
|
|
250
|
+
socket.destroy();
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// ── Attach dev servers ─────────────────────────────────────────────────
|
|
255
|
+
for (const specifier of devAttachments) {
|
|
256
|
+
console.log(` 🔌 Attaching dev server (from ${specifier})`);
|
|
257
|
+
const mod = await import(specifier);
|
|
258
|
+
if (typeof mod.attach !== 'function') {
|
|
259
|
+
throw new Error(`Dev attachment '${specifier}' does not export an attach() function`);
|
|
260
|
+
}
|
|
261
|
+
await mod.attach(server);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
(globalThis as any).__BLOCKS_REALTIME_WS_URL__ = `ws://localhost:${port}/realtime`;
|
|
265
|
+
(globalThis as any).__BLOCKS_DEV_SERVER_PORT__ = port;
|
|
266
|
+
|
|
267
|
+
// Config already written during startup (local mode overwrites, sandbox mode preserves).
|
|
268
|
+
|
|
269
|
+
// Generate client code — skip in sandbox mode because sandbox.ts already
|
|
270
|
+
// generated it with --conditions=aws-runtime (correct aws-middleware).
|
|
271
|
+
// Re-generating here without that condition would overwrite with mock-middleware.
|
|
272
|
+
if (!isSandbox) {
|
|
273
|
+
const awsBlocksDir = dirname(resolvedPath);
|
|
274
|
+
const clientPath = join(awsBlocksDir, 'client.js');
|
|
275
|
+
console.log('📝 Generating client code...');
|
|
276
|
+
await writeClientCode(resolvedPath, clientPath);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ── Start listening ────────────────────────────────────────────────────
|
|
280
|
+
server.listen(port, '127.0.0.1', async () => {
|
|
281
|
+
console.log(`AWS Blocks local server running on http://localhost:${port}`);
|
|
282
|
+
buildAndSendEvent({ command: 'dev', state: 'SUCCESS', duration: Date.now() - devStartTime });
|
|
283
|
+
|
|
284
|
+
// Spawn frontend dev server after Blocks server is ready
|
|
285
|
+
if (frontendCommand) {
|
|
286
|
+
frontendProcess = spawn(frontendCommand, {
|
|
287
|
+
shell: true,
|
|
288
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
289
|
+
env: { ...process.env, NODE_OPTIONS: '' },
|
|
290
|
+
});
|
|
291
|
+
// Suppress frontend output — only show errors
|
|
292
|
+
frontendProcess.stderr?.on('data', (d: Buffer) => {
|
|
293
|
+
const msg = d.toString();
|
|
294
|
+
if (!msg.includes('DeprecationWarning')) process.stderr.write(msg);
|
|
295
|
+
});
|
|
296
|
+
frontendProcess.on('exit', (code) => {
|
|
297
|
+
if (code !== 0 && code !== null) {
|
|
298
|
+
console.error(`⚠️ Frontend process exited with code ${code}`);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
try {
|
|
303
|
+
await waitForPort(frontendPort);
|
|
304
|
+
console.log(`\n ➜ http://localhost:${port}/\n`);
|
|
305
|
+
} catch (e) {
|
|
306
|
+
console.error(`⚠️ Frontend did not start: ${(e as Error).message}`);
|
|
307
|
+
console.log(`\n ➜ http://localhost:${port}/ (API only — frontend unavailable)\n`);
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
console.log(`\n ➜ http://localhost:${port}/\n`);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
server.on('error', (err: NodeJS.ErrnoException) => {
|
|
315
|
+
const errorCode = err.code === 'EADDRINUSE' ? 'PORT_IN_USE' : 'UNKNOWN';
|
|
316
|
+
buildAndSendEvent({ command: 'dev', state: 'FAIL', duration: Date.now() - devStartTime, error: { code: errorCode, phase: 'startup' } });
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// ── Cleanup ────────────────────────────────────────────────────────────
|
|
320
|
+
const cleanup = async () => {
|
|
321
|
+
console.log('\nShutting down...');
|
|
322
|
+
if (frontendProcess) frontendProcess.kill('SIGTERM');
|
|
323
|
+
if (typeof backend.__cleanup === 'function') {
|
|
324
|
+
try { await backend.__cleanup(); } catch {}
|
|
325
|
+
}
|
|
326
|
+
frontendProxy?.close();
|
|
327
|
+
apiProxy?.close();
|
|
328
|
+
server.close(() => process.exit(0));
|
|
329
|
+
setTimeout(() => process.exit(0), 2000).unref();
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
process.on('SIGINT', cleanup);
|
|
333
|
+
process.on('SIGTERM', cleanup);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ── Local API handler ────────────────────────────────────────────────────────
|
|
337
|
+
|
|
338
|
+
function handleApiRequest(
|
|
339
|
+
req: IncomingMessage,
|
|
340
|
+
res: ServerResponse,
|
|
341
|
+
url: URL,
|
|
342
|
+
method: string,
|
|
343
|
+
apis: Map<string, any>,
|
|
344
|
+
): void {
|
|
345
|
+
if (method === 'POST' && url.pathname === BLOCKS_RPC_PREFIX) {
|
|
346
|
+
let body = '';
|
|
347
|
+
req.on('data', chunk => body += chunk);
|
|
348
|
+
req.on('end', async () => {
|
|
349
|
+
const rpcHeaders: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
350
|
+
// Verbose request/response logging — useful when debugging cross-stack
|
|
351
|
+
// wire format mismatches (e.g. native client codegen vs server). Set
|
|
352
|
+
// BLOCKS_DEV_QUIET=1 to suppress. Sensitive fields (passwords, session
|
|
353
|
+
// tokens, OTP codes) are redacted so they never reach the log stream.
|
|
354
|
+
if (!process.env.BLOCKS_DEV_QUIET) {
|
|
355
|
+
let inLog: string;
|
|
356
|
+
try {
|
|
357
|
+
inLog = redactToJson(JSON.parse(body || '{}'));
|
|
358
|
+
} catch {
|
|
359
|
+
// Body isn't valid JSON (parse error surfaced below) — fall back to
|
|
360
|
+
// the raw text. A malformed body can't carry a structured secret,
|
|
361
|
+
// but truncate it like before to keep logs readable.
|
|
362
|
+
inLog = body;
|
|
363
|
+
}
|
|
364
|
+
console.log('[rpc-in]', inLog.length > 800 ? inLog.slice(0, 800) + '…' : inLog);
|
|
365
|
+
}
|
|
366
|
+
const parsed = parseRpcRequest(body);
|
|
367
|
+
|
|
368
|
+
if (!parsed.ok) {
|
|
369
|
+
if (!process.env.BLOCKS_DEV_QUIET) console.log('[rpc-out parse-error]', parsed.response);
|
|
370
|
+
res.writeHead(200, rpcHeaders);
|
|
371
|
+
res.end(parsed.response);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const { apiNamespace, method: rpcMethod, args, id: rpcId } = parsed.request;
|
|
376
|
+
if (!process.env.BLOCKS_DEV_QUIET) {
|
|
377
|
+
// redactToJson handles circulars and serialization failures itself.
|
|
378
|
+
console.log('[rpc-call]', `${apiNamespace}.${rpcMethod}`, redactToJson(args));
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
const headers = new Headers();
|
|
383
|
+
Object.entries(req.headers).forEach(([k, v]) => {
|
|
384
|
+
headers.set(k, Array.isArray(v) ? v[0] : v || '');
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
let responseStatus = 200;
|
|
388
|
+
const responseHeaders = new Headers({ 'Content-Type': 'application/json' });
|
|
389
|
+
let responseBody: any;
|
|
390
|
+
|
|
391
|
+
const context = {
|
|
392
|
+
request: {
|
|
393
|
+
headers,
|
|
394
|
+
body: toBodyStream(body),
|
|
395
|
+
json: async () => JSON.parse(body),
|
|
396
|
+
text: async () => body,
|
|
397
|
+
url: new URL(req.url || BLOCKS_RPC_PREFIX, `http://${req.headers.host}`),
|
|
398
|
+
params: {},
|
|
399
|
+
},
|
|
400
|
+
response: {
|
|
401
|
+
headers: responseHeaders,
|
|
402
|
+
get status() { return responseStatus; },
|
|
403
|
+
set status(code: number) { responseStatus = code; },
|
|
404
|
+
send: (b: any) => { responseBody = b; },
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const apiHandler = apis.get(apiNamespace);
|
|
409
|
+
if (!apiHandler) {
|
|
410
|
+
res.writeHead(200, rpcHeaders);
|
|
411
|
+
res.end(methodNotFoundResponse(`API '${apiNamespace}' not found. Available: ${Array.from(apis.keys()).join(', ')}`, rpcId));
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const apiMethods = typeof apiHandler === 'function' ? apiHandler(context) : apiHandler;
|
|
416
|
+
|
|
417
|
+
if (!apiMethods[rpcMethod]) {
|
|
418
|
+
res.writeHead(200, rpcHeaders);
|
|
419
|
+
res.end(methodNotFoundResponse(`'${rpcMethod}' on API '${apiNamespace}'`, rpcId));
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const result = await apiMethods[rpcMethod](...args);
|
|
424
|
+
|
|
425
|
+
const headerObj: Record<string, string | string[]> = {};
|
|
426
|
+
for (const [key, value] of responseHeaders.entries()) {
|
|
427
|
+
if (key === 'set-cookie') continue;
|
|
428
|
+
headerObj[key] = value;
|
|
429
|
+
}
|
|
430
|
+
const setCookies = responseHeaders.getSetCookie?.() ?? [];
|
|
431
|
+
if (setCookies.length > 0) headerObj['set-cookie'] = setCookies;
|
|
432
|
+
|
|
433
|
+
const successPayload = successResponse(responseBody ?? result, rpcId);
|
|
434
|
+
if (!process.env.BLOCKS_DEV_QUIET) {
|
|
435
|
+
// Log a redacted copy of the response value — never the raw
|
|
436
|
+
// payload, which can carry challenge `session` tokens, MFA shared
|
|
437
|
+
// secrets, etc. that the client legitimately round-trips.
|
|
438
|
+
const okLog = redactToJson(responseBody ?? result);
|
|
439
|
+
console.log('[rpc-ok]', `${apiNamespace}.${rpcMethod}`,
|
|
440
|
+
okLog.length > 800 ? okLog.slice(0, 800) + '…' : okLog);
|
|
441
|
+
}
|
|
442
|
+
res.writeHead(responseStatus, headerObj);
|
|
443
|
+
res.end(successPayload);
|
|
444
|
+
} catch (error: any) {
|
|
445
|
+
const errPayload = errorResponseFromCatch(error, rpcId);
|
|
446
|
+
if (!process.env.BLOCKS_DEV_QUIET) {
|
|
447
|
+
console.log('[rpc-err]', `${apiNamespace}.${rpcMethod}`, error?.name ?? 'Error', '-', error?.message);
|
|
448
|
+
if (error?.stack) console.log(error.stack);
|
|
449
|
+
}
|
|
450
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
451
|
+
res.end(errPayload);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// RawRoute dispatch
|
|
458
|
+
const matched = matchRoute(method, url.pathname);
|
|
459
|
+
if (matched) {
|
|
460
|
+
let body = '';
|
|
461
|
+
req.on('data', (chunk: string) => body += chunk);
|
|
462
|
+
req.on('end', async () => {
|
|
463
|
+
try {
|
|
464
|
+
const headers = new Headers();
|
|
465
|
+
Object.entries(req.headers).forEach(([k, v]) => {
|
|
466
|
+
headers.set(k, Array.isArray(v) ? v[0] : v || '');
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
let responseStatus = 200;
|
|
470
|
+
const responseHeaders = new Headers({ 'Content-Type': 'application/json' });
|
|
471
|
+
let responseBody: any;
|
|
472
|
+
|
|
473
|
+
const context = {
|
|
474
|
+
request: {
|
|
475
|
+
headers,
|
|
476
|
+
body: toBodyStream(body),
|
|
477
|
+
json: async () => JSON.parse(body),
|
|
478
|
+
text: async () => body,
|
|
479
|
+
url,
|
|
480
|
+
params: matched.params,
|
|
481
|
+
},
|
|
482
|
+
response: {
|
|
483
|
+
headers: responseHeaders,
|
|
484
|
+
get status() { return responseStatus; },
|
|
485
|
+
set status(code: number) { responseStatus = code; },
|
|
486
|
+
send: (b: any) => { responseBody = b; },
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
await matched.route.handler(context);
|
|
491
|
+
|
|
492
|
+
const headerObj: Record<string, string | string[]> = {};
|
|
493
|
+
for (const [key, value] of responseHeaders.entries()) {
|
|
494
|
+
if (key === 'set-cookie') continue;
|
|
495
|
+
headerObj[key] = value;
|
|
496
|
+
}
|
|
497
|
+
const setCookies = responseHeaders.getSetCookie?.() ?? [];
|
|
498
|
+
if (setCookies.length > 0) headerObj['set-cookie'] = setCookies;
|
|
499
|
+
|
|
500
|
+
res.writeHead(responseStatus, headerObj);
|
|
501
|
+
res.end(responseBody !== undefined ? (typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody)) : '');
|
|
502
|
+
} catch (error: any) {
|
|
503
|
+
const status = error instanceof ApiError ? error.status : 500;
|
|
504
|
+
const errBody: Record<string, any> = { error: error.message };
|
|
505
|
+
if (error.name && error.name !== 'Error') errBody.name = error.name;
|
|
506
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
507
|
+
res.end(JSON.stringify(errBody));
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
res.writeHead(404);
|
|
514
|
+
res.end();
|
|
515
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { describe, it, beforeEach, afterEach } from 'node:test';
|
|
5
|
+
import assert from 'node:assert';
|
|
6
|
+
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import { loadProductionEnv } from './ensure-secrets.js';
|
|
10
|
+
|
|
11
|
+
describe('loadProductionEnv', () => {
|
|
12
|
+
let workDir: string;
|
|
13
|
+
let originalCwd: string;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
originalCwd = process.cwd();
|
|
17
|
+
workDir = mkdtempSync(join(tmpdir(), 'load-prod-env-'));
|
|
18
|
+
process.chdir(workDir);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
process.chdir(originalCwd);
|
|
23
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Regression for bug bash item #2: deploy of a non-Supabase template
|
|
27
|
+
// (no .env.production, no SUPABASE_DB_URL) must not throw.
|
|
28
|
+
it('does not throw when .env.production is absent and no connection string is set', () => {
|
|
29
|
+
delete process.env.SUPABASE_DB_URL;
|
|
30
|
+
assert.doesNotThrow(() => loadProductionEnv());
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('does not throw when .env.production is absent even without any DB env var', () => {
|
|
34
|
+
delete process.env.SUPABASE_DB_URL;
|
|
35
|
+
delete process.env.DATABASE_URL;
|
|
36
|
+
assert.doesNotThrow(() => loadProductionEnv());
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('loads variables from .env.production when present', () => {
|
|
40
|
+
const key = 'LOAD_PROD_ENV_TEST_VAR';
|
|
41
|
+
delete process.env[key];
|
|
42
|
+
writeFileSync(join(workDir, '.env.production'), `${key}=hello-prod\n`);
|
|
43
|
+
try {
|
|
44
|
+
loadProductionEnv();
|
|
45
|
+
assert.strictEqual(process.env[key], 'hello-prod');
|
|
46
|
+
} finally {
|
|
47
|
+
delete process.env[key];
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|