@aws-blocks/core 0.1.3 → 0.1.4

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.
@@ -24,4 +24,16 @@ export declare const BLOCKS_RPC_PREFIX = "/aws-blocks/api";
24
24
  * prefix; CloudFront forwards the subtree and the Lambda dispatches by path.
25
25
  */
26
26
  export declare const BLOCKS_AUTH_PREFIX = "/aws-blocks/auth";
27
+ /**
28
+ * Reserved path for the client runtime config (`config.json`).
29
+ *
30
+ * In production CloudFront serves `${BLOCKS_SANDBOX_PREFIX}/*` from S3 as
31
+ * static assets (see hosting.ts) so the browser client can resolve its API
32
+ * URL. The local dev server mirrors this by serving the config from the front
33
+ * door itself, instead of proxying the request to the framework dev server —
34
+ * which only serves its own static dir (Next.js `public/`, etc.) and would 404
35
+ * on a project-root file. Keeping this symmetric with production is what makes
36
+ * the browser client work the same in `dev`, `sandbox`, and deployed.
37
+ */
38
+ export declare const BLOCKS_SANDBOX_PREFIX = "/.blocks-sandbox";
27
39
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAE9C;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAEnD;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,qBAAqB,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,eAAO,MAAM,gBAAgB,gBAAgB,CAAC;AAE9C;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,oBAAoB,CAAC;AAEnD;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,qBAAqB,CAAC;AAErD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,qBAAqB,CAAC"}
package/dist/constants.js CHANGED
@@ -26,3 +26,15 @@ export const BLOCKS_RPC_PREFIX = '/aws-blocks/api';
26
26
  * prefix; CloudFront forwards the subtree and the Lambda dispatches by path.
27
27
  */
28
28
  export const BLOCKS_AUTH_PREFIX = '/aws-blocks/auth';
29
+ /**
30
+ * Reserved path for the client runtime config (`config.json`).
31
+ *
32
+ * In production CloudFront serves `${BLOCKS_SANDBOX_PREFIX}/*` from S3 as
33
+ * static assets (see hosting.ts) so the browser client can resolve its API
34
+ * URL. The local dev server mirrors this by serving the config from the front
35
+ * door itself, instead of proxying the request to the framework dev server —
36
+ * which only serves its own static dir (Next.js `public/`, etc.) and would 404
37
+ * on a project-root file. Keeping this symmetric with production is what makes
38
+ * the browser client work the same in `dev`, `sandbox`, and deployed.
39
+ */
40
+ export const BLOCKS_SANDBOX_PREFIX = '/.blocks-sandbox';
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=dev-server-config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-server-config.test.d.ts","sourceRoot":"","sources":["../../src/scripts/dev-server-config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,37 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { describe, it } from 'node:test';
4
+ import assert from 'node:assert';
5
+ import { buildBlocksConfig, isBlocksConfigRequest } from './dev-server.js';
6
+ describe('buildBlocksConfig — client runtime config', () => {
7
+ it('points the browser at the localhost front door RPC endpoint', () => {
8
+ assert.deepStrictEqual(buildBlocksConfig(3000, false), {
9
+ apiUrl: 'http://localhost:3000/aws-blocks/api',
10
+ environment: 'local',
11
+ });
12
+ });
13
+ it('honors a custom port', () => {
14
+ assert.strictEqual(buildBlocksConfig(4000, false).apiUrl, 'http://localhost:4000/aws-blocks/api');
15
+ });
16
+ it('marks the environment as sandbox but keeps the localhost front door', () => {
17
+ assert.deepStrictEqual(buildBlocksConfig(3000, true), {
18
+ apiUrl: 'http://localhost:3000/aws-blocks/api',
19
+ environment: 'sandbox',
20
+ });
21
+ });
22
+ });
23
+ describe('isBlocksConfigRequest — reserved runtime-config path', () => {
24
+ it('matches GET /.blocks-sandbox/config.json', () => {
25
+ assert.strictEqual(isBlocksConfigRequest('GET', '/.blocks-sandbox/config.json'), true);
26
+ });
27
+ it('ignores non-GET methods (only the static-style read is reserved)', () => {
28
+ assert.strictEqual(isBlocksConfigRequest('POST', '/.blocks-sandbox/config.json'), false);
29
+ assert.strictEqual(isBlocksConfigRequest('HEAD', '/.blocks-sandbox/config.json'), false);
30
+ });
31
+ it('does not match other paths (frontend/app routes pass through to the proxy)', () => {
32
+ assert.strictEqual(isBlocksConfigRequest('GET', '/'), false);
33
+ assert.strictEqual(isBlocksConfigRequest('GET', '/api/chat'), false);
34
+ assert.strictEqual(isBlocksConfigRequest('GET', '/.blocks-sandbox/other.json'), false);
35
+ assert.strictEqual(isBlocksConfigRequest('GET', '/aws-blocks/api'), false);
36
+ });
37
+ });
@@ -4,6 +4,25 @@ export declare const LOCALHOST_PATTERN: RegExp;
4
4
  * Reflects back origins matching localhost/127.0.0.1; otherwise returns the fallback.
5
5
  */
6
6
  export declare function resolveDevCorsOrigin(origin: string): string;
7
+ /** Shape of the client runtime config the browser fetches to discover the API URL. */
8
+ export interface BlocksRuntimeConfig {
9
+ apiUrl: string;
10
+ environment: 'local' | 'sandbox';
11
+ }
12
+ /**
13
+ * Build the runtime config the browser fetches at `${BLOCKS_SANDBOX_PREFIX}/config.json`.
14
+ * In sandbox mode the browser still targets the localhost front door (the dev
15
+ * server proxies `/aws-blocks/api` to the deployed API), so the shape is the
16
+ * same in both modes — only `environment` differs.
17
+ */
18
+ export declare function buildBlocksConfig(port: number, isSandbox: boolean): BlocksRuntimeConfig;
19
+ /**
20
+ * True for the reserved runtime-config request the dev server answers itself
21
+ * (mirroring production, where CloudFront serves `${BLOCKS_SANDBOX_PREFIX}/*`
22
+ * statically) instead of proxying it to the framework dev server — which only
23
+ * serves its own static dir (Next.js `public/`, etc.) and would 404.
24
+ */
25
+ export declare function isBlocksConfigRequest(method: string, pathname: string): boolean;
7
26
  export interface DevServerOptions {
8
27
  /** Customer-facing port. Default: 3000. */
9
28
  port?: number;
@@ -1 +1 @@
1
- {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../../src/scripts/dev-server.ts"],"names":[],"mappings":"AAmCA,eAAO,MAAM,iBAAiB,QAAiD,CAAC;AAEhF;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAmCD,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,iBAiP7D"}
1
+ {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../../src/scripts/dev-server.ts"],"names":[],"mappings":"AAmCA,eAAO,MAAM,iBAAiB,QAAiD,CAAC;AAEhF;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,sFAAsF;AACtF,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,OAAO,GAAG,SAAS,CAAC;CAClC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,mBAAmB,CAKvF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE/E;AAED,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAmCD,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,iBA8P7D"}
@@ -9,7 +9,7 @@ import { createConnection } from 'node:net';
9
9
  import httpProxy from 'http-proxy';
10
10
  import { writeClientCode } from './generate-client.js';
11
11
  import { ApiError } from '../errors.js';
12
- import { BLOCKS_RPC_PREFIX } from '../constants.js';
12
+ import { BLOCKS_RPC_PREFIX, BLOCKS_SANDBOX_PREFIX } from '../constants.js';
13
13
  import { matchRoute, lockRouteRegistry } from '../raw-route.js';
14
14
  import { registerBuiltinRoutes } from '../builtin-routes.js';
15
15
  import { parseRpcRequest, successResponse, errorResponseFromCatch, methodNotFoundResponse, } from '../rpc.js';
@@ -34,6 +34,27 @@ export const LOCALHOST_PATTERN = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/;
34
34
  export function resolveDevCorsOrigin(origin) {
35
35
  return LOCALHOST_PATTERN.test(origin) ? origin : 'http://localhost:3000';
36
36
  }
37
+ /**
38
+ * Build the runtime config the browser fetches at `${BLOCKS_SANDBOX_PREFIX}/config.json`.
39
+ * In sandbox mode the browser still targets the localhost front door (the dev
40
+ * server proxies `/aws-blocks/api` to the deployed API), so the shape is the
41
+ * same in both modes — only `environment` differs.
42
+ */
43
+ export function buildBlocksConfig(port, isSandbox) {
44
+ return {
45
+ apiUrl: `http://localhost:${port}${BLOCKS_RPC_PREFIX}`,
46
+ environment: isSandbox ? 'sandbox' : 'local',
47
+ };
48
+ }
49
+ /**
50
+ * True for the reserved runtime-config request the dev server answers itself
51
+ * (mirroring production, where CloudFront serves `${BLOCKS_SANDBOX_PREFIX}/*`
52
+ * statically) instead of proxying it to the framework dev server — which only
53
+ * serves its own static dir (Next.js `public/`, etc.) and would 404.
54
+ */
55
+ export function isBlocksConfigRequest(method, pathname) {
56
+ return method === 'GET' && pathname === `${BLOCKS_SANDBOX_PREFIX}/config.json`;
57
+ }
37
58
  /**
38
59
  * Initialize all building blocks that have an initialize() method.
39
60
  * This is the "local deploy" phase - mirrors CDK deploy.
@@ -95,11 +116,9 @@ export async function startDevServer(options) {
95
116
  // BLOCKS_API_URL server-side, so the request still reaches the deployed Lambda.
96
117
  // This makes sandbox single-origin, matching `npm run dev` and the prod
97
118
  // CloudFront proxy; `crossDomain` stays unnecessary.
119
+ const blocksConfig = buildBlocksConfig(port, isSandbox);
98
120
  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));
121
+ writeFileSync('.blocks-sandbox/config.json', JSON.stringify(blocksConfig, null, 2));
103
122
  // 1. Set up global collectors for plugin discovery
104
123
  globalThis.__BLOCKS_CLIENT_MIDDLEWARE__ = [];
105
124
  globalThis.__BLOCKS_DEV_ATTACHMENTS__ = [];
@@ -181,6 +200,20 @@ export async function startDevServer(options) {
181
200
  res.end();
182
201
  return;
183
202
  }
203
+ // ── Blocks runtime config ──────────────────────────────────────────
204
+ // Reserved path: serve it from the front door so it works for every
205
+ // framework (Next/Nuxt/Astro/SPA all proxy through this :3000 server),
206
+ // mirroring production where CloudFront serves `${BLOCKS_SANDBOX_PREFIX}/*`
207
+ // statically. Otherwise the request is proxied to the framework dev
208
+ // server, which can't serve this project-root file and 404s.
209
+ if (isBlocksConfigRequest(method, url.pathname)) {
210
+ res.writeHead(200, {
211
+ 'Content-Type': 'application/json',
212
+ 'Cache-Control': 'no-store',
213
+ });
214
+ res.end(JSON.stringify(blocksConfig));
215
+ return;
216
+ }
184
217
  // ── API/RawRoute requests ──────────────────────────────────────────
185
218
  if (isApiRequest(method, url.pathname)) {
186
219
  if (isSandbox && apiProxy) {
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const CORE_VERSION = "0.1.3";
1
+ export declare const CORE_VERSION = "0.1.4";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/generate-version.mjs — do not edit manually
2
- export const CORE_VERSION = '0.1.3';
2
+ export const CORE_VERSION = '0.1.4';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-blocks/core",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "author": "Amazon Web Services",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/constants.ts CHANGED
@@ -29,3 +29,16 @@ export const BLOCKS_RPC_PREFIX = '/aws-blocks/api';
29
29
  * prefix; CloudFront forwards the subtree and the Lambda dispatches by path.
30
30
  */
31
31
  export const BLOCKS_AUTH_PREFIX = '/aws-blocks/auth';
32
+
33
+ /**
34
+ * Reserved path for the client runtime config (`config.json`).
35
+ *
36
+ * In production CloudFront serves `${BLOCKS_SANDBOX_PREFIX}/*` from S3 as
37
+ * static assets (see hosting.ts) so the browser client can resolve its API
38
+ * URL. The local dev server mirrors this by serving the config from the front
39
+ * door itself, instead of proxying the request to the framework dev server —
40
+ * which only serves its own static dir (Next.js `public/`, etc.) and would 404
41
+ * on a project-root file. Keeping this symmetric with production is what makes
42
+ * the browser client work the same in `dev`, `sandbox`, and deployed.
43
+ */
44
+ export const BLOCKS_SANDBOX_PREFIX = '/.blocks-sandbox';
@@ -0,0 +1,43 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { describe, it } from 'node:test';
4
+ import assert from 'node:assert';
5
+ import { buildBlocksConfig, isBlocksConfigRequest } from './dev-server.js';
6
+
7
+ describe('buildBlocksConfig — client runtime config', () => {
8
+ it('points the browser at the localhost front door RPC endpoint', () => {
9
+ assert.deepStrictEqual(buildBlocksConfig(3000, false), {
10
+ apiUrl: 'http://localhost:3000/aws-blocks/api',
11
+ environment: 'local',
12
+ });
13
+ });
14
+
15
+ it('honors a custom port', () => {
16
+ assert.strictEqual(buildBlocksConfig(4000, false).apiUrl, 'http://localhost:4000/aws-blocks/api');
17
+ });
18
+
19
+ it('marks the environment as sandbox but keeps the localhost front door', () => {
20
+ assert.deepStrictEqual(buildBlocksConfig(3000, true), {
21
+ apiUrl: 'http://localhost:3000/aws-blocks/api',
22
+ environment: 'sandbox',
23
+ });
24
+ });
25
+ });
26
+
27
+ describe('isBlocksConfigRequest — reserved runtime-config path', () => {
28
+ it('matches GET /.blocks-sandbox/config.json', () => {
29
+ assert.strictEqual(isBlocksConfigRequest('GET', '/.blocks-sandbox/config.json'), true);
30
+ });
31
+
32
+ it('ignores non-GET methods (only the static-style read is reserved)', () => {
33
+ assert.strictEqual(isBlocksConfigRequest('POST', '/.blocks-sandbox/config.json'), false);
34
+ assert.strictEqual(isBlocksConfigRequest('HEAD', '/.blocks-sandbox/config.json'), false);
35
+ });
36
+
37
+ it('does not match other paths (frontend/app routes pass through to the proxy)', () => {
38
+ assert.strictEqual(isBlocksConfigRequest('GET', '/'), false);
39
+ assert.strictEqual(isBlocksConfigRequest('GET', '/api/chat'), false);
40
+ assert.strictEqual(isBlocksConfigRequest('GET', '/.blocks-sandbox/other.json'), false);
41
+ assert.strictEqual(isBlocksConfigRequest('GET', '/aws-blocks/api'), false);
42
+ });
43
+ });
@@ -4,13 +4,13 @@
4
4
  import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
5
5
  import { pathToFileURL, URL } from 'node:url';
6
6
  import { resolve, dirname, join } from 'node:path';
7
- import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
7
+ import { writeFileSync, mkdirSync } from 'node:fs';
8
8
  import { spawn, type ChildProcess } from 'node:child_process';
9
9
  import { createConnection } from 'node:net';
10
10
  import httpProxy from 'http-proxy';
11
11
  import { writeClientCode } from './generate-client.js';
12
12
  import { ApiError } from '../errors.js';
13
- import { BLOCKS_RPC_PREFIX } from '../constants.js';
13
+ import { BLOCKS_RPC_PREFIX, BLOCKS_SANDBOX_PREFIX } from '../constants.js';
14
14
  import { matchRoute, lockRouteRegistry } from '../raw-route.js';
15
15
  import { registerBuiltinRoutes } from '../builtin-routes.js';
16
16
  import {
@@ -43,6 +43,35 @@ export function resolveDevCorsOrigin(origin: string): string {
43
43
  return LOCALHOST_PATTERN.test(origin) ? origin : 'http://localhost:3000';
44
44
  }
45
45
 
46
+ /** Shape of the client runtime config the browser fetches to discover the API URL. */
47
+ export interface BlocksRuntimeConfig {
48
+ apiUrl: string;
49
+ environment: 'local' | 'sandbox';
50
+ }
51
+
52
+ /**
53
+ * Build the runtime config the browser fetches at `${BLOCKS_SANDBOX_PREFIX}/config.json`.
54
+ * In sandbox mode the browser still targets the localhost front door (the dev
55
+ * server proxies `/aws-blocks/api` to the deployed API), so the shape is the
56
+ * same in both modes — only `environment` differs.
57
+ */
58
+ export function buildBlocksConfig(port: number, isSandbox: boolean): BlocksRuntimeConfig {
59
+ return {
60
+ apiUrl: `http://localhost:${port}${BLOCKS_RPC_PREFIX}`,
61
+ environment: isSandbox ? 'sandbox' : 'local',
62
+ };
63
+ }
64
+
65
+ /**
66
+ * True for the reserved runtime-config request the dev server answers itself
67
+ * (mirroring production, where CloudFront serves `${BLOCKS_SANDBOX_PREFIX}/*`
68
+ * statically) instead of proxying it to the framework dev server — which only
69
+ * serves its own static dir (Next.js `public/`, etc.) and would 404.
70
+ */
71
+ export function isBlocksConfigRequest(method: string, pathname: string): boolean {
72
+ return method === 'GET' && pathname === `${BLOCKS_SANDBOX_PREFIX}/config.json`;
73
+ }
74
+
46
75
  export interface DevServerOptions {
47
76
  /** Customer-facing port. Default: 3000. */
48
77
  port?: number;
@@ -125,11 +154,9 @@ export async function startDevServer(options: DevServerOptions) {
125
154
  // BLOCKS_API_URL server-side, so the request still reaches the deployed Lambda.
126
155
  // This makes sandbox single-origin, matching `npm run dev` and the prod
127
156
  // CloudFront proxy; `crossDomain` stays unnecessary.
157
+ const blocksConfig = buildBlocksConfig(port, isSandbox);
128
158
  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));
159
+ writeFileSync('.blocks-sandbox/config.json', JSON.stringify(blocksConfig, null, 2));
133
160
 
134
161
  // 1. Set up global collectors for plugin discovery
135
162
  (globalThis as any).__BLOCKS_CLIENT_MIDDLEWARE__ = [];
@@ -221,6 +248,21 @@ export async function startDevServer(options: DevServerOptions) {
221
248
  return;
222
249
  }
223
250
 
251
+ // ── Blocks runtime config ──────────────────────────────────────────
252
+ // Reserved path: serve it from the front door so it works for every
253
+ // framework (Next/Nuxt/Astro/SPA all proxy through this :3000 server),
254
+ // mirroring production where CloudFront serves `${BLOCKS_SANDBOX_PREFIX}/*`
255
+ // statically. Otherwise the request is proxied to the framework dev
256
+ // server, which can't serve this project-root file and 404s.
257
+ if (isBlocksConfigRequest(method, url.pathname)) {
258
+ res.writeHead(200, {
259
+ 'Content-Type': 'application/json',
260
+ 'Cache-Control': 'no-store',
261
+ });
262
+ res.end(JSON.stringify(blocksConfig));
263
+ return;
264
+ }
265
+
224
266
  // ── API/RawRoute requests ──────────────────────────────────────────
225
267
  if (isApiRequest(method, url.pathname)) {
226
268
  if (isSandbox && apiProxy) {
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/generate-version.mjs — do not edit manually
2
- export const CORE_VERSION = '0.1.3';
2
+ export const CORE_VERSION = '0.1.4';