@aws-blocks/core 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,229 @@
1
+ # @aws-blocks/core
2
+
3
+ Core primitives for building full-stack applications with the AWS Blocks.
4
+
5
+ ## Key Exports
6
+
7
+ ### Scope
8
+
9
+ Defines the boundary for your backend resources. The `Scope` class docstring serves as an index to all available Building Blocks.
10
+
11
+ ```typescript
12
+ import { Scope } from '@aws-blocks/core';
13
+
14
+ const scope = new Scope('my-app');
15
+ ```
16
+
17
+ ### ApiNamespace
18
+
19
+ Define type-safe APIs with automatic frontend/backend integration.
20
+
21
+ ```typescript
22
+ import { ApiNamespace } from '@aws-blocks/core';
23
+
24
+ export const api = new ApiNamespace(scope, 'api', (context) => ({
25
+ async greet(name: string) {
26
+ return { message: `Hello, ${name}!` };
27
+ }
28
+ }));
29
+ ```
30
+
31
+ Frontend usage (fully typed):
32
+
33
+ ```typescript
34
+ import { api } from 'aws-blocks';
35
+
36
+ const result = await api.greet('World');
37
+ ```
38
+
39
+ #### Authentication — every method is a public endpoint
40
+
41
+ Each method you define becomes a public, internet-reachable RPC endpoint. There is **no authentication by default** — a method is callable by anyone until you gate it. Auth is opt-in, per method, by calling an auth Building Block at the top of the handler:
42
+
43
+ ```typescript
44
+ export const api = new ApiNamespace(scope, 'api', (context) => ({
45
+ // PUBLIC — intentionally callable by anyone.
46
+ async listPublicPosts() {
47
+ return db.posts.findPublished();
48
+ },
49
+
50
+ // GATED — requireAuth throws a 401 before the body runs.
51
+ async createPost(input: NewPost) {
52
+ const user = await auth.requireAuth(context);
53
+ return db.posts.create({ ...input, authorId: user.userId });
54
+ },
55
+ }));
56
+ ```
57
+
58
+ The local mock applies no auth either, so an ungated method passes every local check and still ships callable by anyone. See your auth block's README (e.g. `@aws-blocks/bb-auth-cognito`) for `requireAuth` / `requireRole`.
59
+
60
+ #### Calling the API over HTTP (JSON-RPC 2.0)
61
+
62
+ The typed `import { api } from 'aws-blocks'` client is the normal path. The HTTP form below is for manual verification (curl/Postman) and non-JS clients.
63
+
64
+ `POST` to the RPC path `/aws-blocks/api`:
65
+
66
+ - Local dev: `http://localhost:3001/aws-blocks/api`
67
+ - Deployed: the API Gateway stage URL + `/aws-blocks/api`
68
+
69
+ The body is JSON-RPC 2.0:
70
+
71
+ ```json
72
+ { "jsonrpc": "2.0", "method": "<namespace>.<methodName>", "params": [...], "id": 1 }
73
+ ```
74
+
75
+ - `method` is `<namespace>.<methodName>`, where `<namespace>` is the **export variable name** from `aws-blocks/index.ts` (e.g., `export const api = ...` → `api`).
76
+ - `params` is a POSITIONAL array of the method's arguments. A named object also works (its values are used in order).
77
+ - Errors come back as HTTP `200` with an `error` object in the body (per JSON-RPC), not as a non-2xx status.
78
+
79
+ Working example:
80
+
81
+ ```bash
82
+ curl -X POST http://localhost:3001/aws-blocks/api \
83
+ -H 'Content-Type: application/json' \
84
+ -d '{"jsonrpc":"2.0","method":"api.greet","params":["World"],"id":1}'
85
+ # → {"jsonrpc":"2.0","result":{"message":"Hello, World!"},"id":1}
86
+ ```
87
+
88
+ ### ApiError / isBlocksError
89
+
90
+ Typed error handling across the wire.
91
+
92
+ ```typescript
93
+ import { ApiError, isBlocksError } from '@aws-blocks/core';
94
+
95
+ // Throw with HTTP status and error name
96
+ throw new ApiError('Not found', 404, { name: 'ItemNotFoundException' });
97
+
98
+ // Catch with type narrowing
99
+ catch (e) {
100
+ if (isBlocksError(e, 'ItemNotFoundException')) { ... }
101
+ }
102
+ ```
103
+
104
+ ### RawRoute
105
+
106
+ Path-based HTTP routing Building Block for endpoints that need full request/response control — webhooks, REST APIs, health checks, file downloads. Use `ApiNamespace` (RPC) for typed function calls; use `RawRoute` when you need raw HTTP semantics.
107
+
108
+ ```typescript
109
+ import { RawRoute } from '@aws-blocks/blocks';
110
+
111
+ // Explicit path
112
+ new RawRoute(scope, 'GetUser', {
113
+ method: 'GET',
114
+ path: '/users/{id}',
115
+ handler: async (context) => {
116
+ const userId = context.request.params.id;
117
+ context.response.send({ id: userId });
118
+ },
119
+ });
120
+
121
+ // Derived path — path omitted, becomes /health from scope chain
122
+ new RawRoute(scope, 'health', { method: 'GET', handler: async (ctx) => {
123
+ ctx.response.send({ status: 'ok' });
124
+ }});
125
+ ```
126
+
127
+ Supports exact paths (`/health`), named parameters (`/users/{id}`), and wildcards (`/files/*`). Path can be omitted — it's derived from scope-chain IDs.
128
+
129
+ 📖 **Full RawRoute documentation (see source repo)**
130
+
131
+ ### Pipeline
132
+
133
+ CDK Pipelines-based CI/CD construct for multi-branch, multi-stage deployments. Creates self-mutating CodePipeline V2 instances with GitHub source via CodeConnections (OAuth).
134
+
135
+ 📖 **Full Pipeline documentation (see source repo)**
136
+
137
+ ### Hosting
138
+
139
+ CDK construct (from the `/cdk` entry point) that deploys a frontend on CloudFront + S3, with a single-origin API proxy when a backend stack is provided.
140
+
141
+ ```typescript
142
+ import { Hosting } from '@aws-blocks/core/cdk';
143
+
144
+ new Hosting(stack, 'Web', {
145
+ root: join(__dirname, '..'),
146
+ buildCommand: 'npm run build',
147
+ api: blocksStack,
148
+ });
149
+ ```
150
+
151
+ The `framework` option selects the frontend type: `'spa' | 'static' | 'nextjs'`. When omitted, the framework is auto-detected by reading your app's OWN `package.json` (not `node_modules`): a `next` dependency → `nextjs`; otherwise `spa`; and `static` when there is no `package.json`. Set `framework: 'spa'` explicitly to override auto-detection — e.g. when a stray `next` dependency would otherwise trigger an unwanted Next.js/OpenNext build. Full reference lives in the source JSDoc.
152
+
153
+ ## Building Blocks
154
+
155
+ Import Building Blocks from their specific packages (or from the `@aws-blocks/blocks` umbrella):
156
+
157
+ - `@aws-blocks/bb-kv-store` — Key-value storage
158
+ - `@aws-blocks/bb-distributed-table` — Tables with Zod schemas and indexes
159
+ - `@aws-blocks/auth-common` — Auth interfaces and Authenticator component
160
+ - `@aws-blocks/bb-auth-basic` — Username/password authentication
161
+ - `@aws-blocks/bb-data` — SQL database
162
+ - `@aws-blocks/bb-realtime` — Real-time pub/sub
163
+
164
+ ### withAuth (SSR cookie forwarding)
165
+
166
+ Lives in the `@aws-blocks/core/server` entry point (also re-exported as `@aws-blocks/blocks/server`). During SSR (server components / loaders) the browser's cookies aren't automatically attached to AWS Blocks API calls — `withAuth` reads them and forwards them to every AWS Blocks API call made inside the callback.
167
+
168
+ ```typescript
169
+ import { withAuth } from '@aws-blocks/blocks/server';
170
+
171
+ // Auto-detects cookies (Next.js detection is built in)
172
+ const posts = await withAuth(() => api.listMyPosts());
173
+
174
+ // Other frameworks: pass cookies explicitly as the 2nd arg…
175
+ const posts = await withAuth(() => api.listMyPosts(), request.headers.get('cookie'));
176
+ // …or register a provider once via registerCookieProvider.
177
+ ```
178
+
179
+ **Note:** `withAuth` throws a `401` `ApiError` when no cookies are found. Full reference lives in the source JSDoc.
180
+
181
+ ## Local Development
182
+
183
+ In local dev mode, Building Blocks use mock implementations. No AWS resources needed.
184
+
185
+ ## CORS Configuration
186
+
187
+ By default, the Lambda handler does **not** set any `Access-Control-Allow-Origin` header. CORS behavior is controlled entirely by the `CORS_ALLOWED_ORIGINS` environment variable.
188
+
189
+ ### When using Hosting (recommended)
190
+
191
+ If you use the `Hosting` construct with your API, CORS is handled automatically:
192
+
193
+ - **Same-origin requests** (frontend fetches through the CloudFront proxy at `/aws-blocks/api`) work without CORS headers since the browser treats them as same-origin.
194
+ - **Cross-origin requests** (e.g. direct API Gateway calls) are also covered: when you pass a `BlocksStack` or `BlocksBackend` as the `api` prop, the Hosting construct automatically adds the CloudFront distribution's domain to `CORS_ALLOWED_ORIGINS` on the backend Lambda. You do **not** need to configure CORS manually.
195
+
196
+ In sandbox mode, the localhost pattern is also preserved so your local dev frontend still works.
197
+
198
+ ### Local development (`npm run dev`)
199
+
200
+ The dev server automatically allows `localhost` / `127.0.0.1` origins. No configuration needed.
201
+
202
+ ### Sandbox deployments
203
+
204
+ The sandbox CLI automatically sets `CORS_ALLOWED_ORIGINS=^https?://(localhost|127\.0\.0\.1)(:\d+)?$` so your local frontend can reach the deployed sandbox API.
205
+
206
+ ### Production (frontend hosted separately)
207
+
208
+ If your frontend is hosted on a different domain (e.g., Vercel, Netlify), set the `CORS_ALLOWED_ORIGINS` environment variable on your Lambda:
209
+
210
+ ```typescript
211
+ // aws-blocks/index.cdk.ts
212
+ blocksStack.handler.addEnvironment(
213
+ 'CORS_ALLOWED_ORIGINS',
214
+ 'https://myapp\\.com,https://staging\\.myapp\\.com'
215
+ );
216
+ ```
217
+
218
+ Each entry is treated as a **regex pattern** (anchored with `^` and `$`). Examples:
219
+
220
+ | Pattern | Matches |
221
+ |---------|---------|
222
+ | `https://myapp\\.com` | Exact match for `https://myapp.com` |
223
+ | `https://.*\\.myapp\\.com` | Any subdomain of `myapp.com` |
224
+ | `^https?://(localhost\|127\\.0\\.0\\.1)(:\\d+)?$` | Localhost/127.0.0.1, any port, http or https (sandbox) |
225
+ | `.*` | All origins (escape hatch — use with caution) |
226
+
227
+ Multiple patterns are comma-separated. If a pattern is invalid regex, it falls back to literal string match.
228
+
229
+ If an origin doesn't match any pattern, the handler omits the `Access-Control-Allow-Origin` header (browser blocks the response) and logs a `[CORS]` warning to CloudWatch.
@@ -1 +1 @@
1
- {"version":3,"file":"blocks-backend.d.ts","sourceRoot":"","sources":["../../src/cdk/blocks-backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AAEnC,OAAO,KAAK,UAAU,MAAM,4BAA4B,CAAC;AAEzD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAOvC;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAkB/C;AAED,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,CAAC,EAAE,MAAM;;;;EA0GxF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,aAAc,SAAQ,SAAS;IAC1C,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC;IAC5C,SAAgB,OAAO,EAAE,GAAG,CAAC,iBAAiB,CAAC,cAAc,CAAC;IAC9D,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAE3C;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,MAAM,IAAI,MAAM,CAUnB;IAED,OAAO;WAmBM,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB;CAmB5E"}
1
+ {"version":3,"file":"blocks-backend.d.ts","sourceRoot":"","sources":["../../src/cdk/blocks-backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AAEnC,OAAO,KAAK,UAAU,MAAM,4BAA4B,CAAC;AAEzD,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAQvC;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAkB/C;AAED,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,CAAC,EAAE,MAAM;;;;EA0GxF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,aAAc,SAAQ,SAAS;IAC1C,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC;IAC5C,SAAgB,OAAO,EAAE,GAAG,CAAC,iBAAiB,CAAC,cAAc,CAAC;IAC9D,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAE3C;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,MAAM,IAAI,MAAM,CAUnB;IAED,OAAO;WAmBM,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB;CAsB5E"}
@@ -5,6 +5,7 @@ import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
5
5
  import * as apigateway from 'aws-cdk-lib/aws-apigateway';
6
6
  import { CfnGroup } from 'aws-cdk-lib/aws-resourcegroups';
7
7
  import { Construct } from 'constructs';
8
+ import { pathToFileURL } from 'node:url';
8
9
  import { DEFAULT_NODE_RUNTIME } from './node-version.js';
9
10
  import { addBlocksStackMetadata } from './stack-metadata.js';
10
11
  import { finalizeConfigRegistry, registerConfig } from './config-registry.js';
@@ -196,8 +197,11 @@ export class BlocksBackend extends Construct {
196
197
  static async create(scope, id, props) {
197
198
  assertCdkConditionActive();
198
199
  const backend = new BlocksBackend(scope, id, props);
199
- // ESM caches modules by URL append a unique query string so each stage re-executes the module body
200
- const mod = await import(`${props.backendCDKPath}?stack=${id}`);
200
+ // file:// URL (not a raw path) so the cache-busting query works on Windows,
201
+ // where an absolute path like `D:\...` is rejected as URL scheme `d:`.
202
+ const backendUrl = pathToFileURL(props.backendCDKPath);
203
+ backendUrl.searchParams.set('stack', id);
204
+ const mod = await import(backendUrl.href);
201
205
  if (typeof mod.default === 'function') {
202
206
  try {
203
207
  await mod.default(backend);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cdk/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,WAAW,IAAI,eAAe,EACnC,KAAK,WAAW,EAChB,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,gCAAgC,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEvD,qBAAa,WAAY,SAAQ,GAAG,CAAC,KAAM,YAAW,eAAe;IACnE,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,OAAO,EAAE,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC;IACpD,SAAgB,OAAO,EAAE,GAAG,CAAC,iBAAiB,CAAC,cAAc,CAAC;IAC9D,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAE3C,OAAO;WAcM,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB;CA0B1E;AAED,qBAAa,KAAM,SAAQ,SAAS;IAClC,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,MAAM,EAAE,WAAW,CAAC;IAEpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;gBAEhB,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;IAO9C,IAAI,OAAO,yCAWV;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,SAAS,CAAC,mBAAmB,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;IAKnD,wBAAwB,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;IACzD,qBAAqB,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;IACtD,0BAA0B,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IACrH,IAAI,gBAAgB,IAAI,SAAS,MAAM,EAAE,CAAe;IACxD,IAAI,cAAc,IAAI,SAAS,MAAM,EAAE,CAAe;CACvD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cdk/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,WAAW,IAAI,eAAe,EACnC,KAAK,WAAW,EAChB,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,gCAAgC,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEvD,qBAAa,WAAY,SAAQ,GAAG,CAAC,KAAM,YAAW,eAAe;IACnE,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,OAAO,EAAE,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC;IACpD,SAAgB,OAAO,EAAE,GAAG,CAAC,iBAAiB,CAAC,cAAc,CAAC;IAC9D,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAE3C,OAAO;WAcM,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB;CA6B1E;AAED,qBAAa,KAAM,SAAQ,SAAS;IAClC,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,MAAM,EAAE,WAAW,CAAC;IAEpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;gBAEhB,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;IAO9C,IAAI,OAAO,yCAWV;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,SAAS,CAAC,mBAAmB,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;IAKnD,wBAAwB,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;IACzD,qBAAqB,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;IACtD,0BAA0B,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IACrH,IAAI,gBAAgB,IAAI,SAAS,MAAM,EAAE,CAAe;IACxD,IAAI,cAAc,IAAI,SAAS,MAAM,EAAE,CAAe;CACvD"}
package/dist/cdk/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
  import * as cdk from 'aws-cdk-lib';
4
4
  import { Construct } from 'constructs';
5
+ import { pathToFileURL } from 'node:url';
5
6
  import { computeScopeFullId, } from '../common/index.js';
6
7
  import { setupBlocksInfra, BlocksBackend, assertCdkConditionActive } from './blocks-backend.js';
7
8
  import { addBlocksStackMetadata } from './stack-metadata.js';
@@ -35,8 +36,11 @@ export class BlocksStack extends cdk.Stack {
35
36
  const pipelineScope = globalThis.__PIPELINE_STAGE_SCOPE__;
36
37
  const actualScope = pipelineScope || scope;
37
38
  const stack = new BlocksStack(actualScope, id, props);
38
- // ESM caches modules by URL append a unique query string so each stage re-executes the module body
39
- const mod = await import(`${props.backendCDKPath}?stack=${id}`);
39
+ // file:// URL (not a raw path) so the cache-busting query works on Windows,
40
+ // where an absolute path like `D:\...` is rejected as URL scheme `d:`.
41
+ const backendUrl = pathToFileURL(props.backendCDKPath);
42
+ backendUrl.searchParams.set('stack', id);
43
+ const mod = await import(backendUrl.href);
40
44
  if (typeof mod.default === 'function') {
41
45
  try {
42
46
  await mod.default(stack);
@@ -8,7 +8,7 @@ type AsyncAPI<T extends Record<string, (...args: any[]) => any>> = {
8
8
  * Middleware can inspect or modify any of these properties before the
9
9
  * request is dispatched to the server.
10
10
  */
11
- export interface KitRequest {
11
+ export interface BlocksRequest {
12
12
  /** The namespace name this call targets (e.g., 'api', 'auth'). Corresponds to the second argument of `new ApiNamespace(scope, name, handler)` in the backend. */
13
13
  apiNamespace: string;
14
14
  /** The method being called on the namespace (e.g., 'getUser', 'kvSet'). */
@@ -39,9 +39,9 @@ export interface KitRequest {
39
39
  * });
40
40
  * ```
41
41
  */
42
- export interface KitMiddleware {
42
+ export interface BlocksMiddleware {
43
43
  /** Transform the request before it's sent. Modify the request in place or return a new one. Can be async. */
44
- onRequest?: (request: KitRequest) => KitRequest | void | Promise<KitRequest | void>;
44
+ onRequest?: (request: BlocksRequest) => BlocksRequest | void | Promise<BlocksRequest | void>;
45
45
  /** Transform the response data after it's received. Used to hydrate __blocks descriptors. */
46
46
  onResponse?: (data: unknown) => unknown;
47
47
  }
@@ -64,7 +64,7 @@ export interface KitMiddleware {
64
64
  * an explicit `attach(server)` pattern because they need the HTTP server
65
65
  * instance passed to them — something unavailable at import time.
66
66
  */
67
- export declare function registerMiddleware(middleware: KitMiddleware): void;
67
+ export declare function registerMiddleware(middleware: BlocksMiddleware): void;
68
68
  /**
69
69
  * Options for `ApiNamespaceClient`.
70
70
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC;AAChC,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC;AAiBhF,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,IAAI;KAChE,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,IAAI,KAAK,MAAM,CAAC,GACzD,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GACtC,KAAK;CACV,CAAC;AAoHF;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,iKAAiK;IACjK,YAAY,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,wHAAwH;IACxH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,aAAa;IAC5B,6GAA6G;IAC7G,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,UAAU,GAAG,IAAI,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACpF,6FAA6F;IAC7F,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;CACzC;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,aAAa,GAAG,IAAI,CAElE;AAqBD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,EAClF,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,yBAAyB,GAClC,QAAQ,CAAC,CAAC,CAAC,CAyDb;AAED,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC;AAChC,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,IAAI,GAAG,CAAC;AAiBhF,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,IAAI;KAChE,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,IAAI,KAAK,MAAM,CAAC,GACzD,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GACtC,KAAK;CACV,CAAC;AAoHF;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,iKAAiK;IACjK,YAAY,EAAE,MAAM,CAAC;IACrB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,wHAAwH;IACxH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6GAA6G;IAC7G,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,aAAa,GAAG,IAAI,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC7F,6FAA6F;IAC7F,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;CACzC;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAErE;AAqBD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,8EAA8E;IAC9E,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,EAClF,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,yBAAyB,GAClC,QAAQ,CAAC,CAAC,CAAC,CAyDb;AAED,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline-construct.d.ts","sourceRoot":"","sources":["../../src/pipeline/pipeline-construct.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AAInC,OAAO,EAEL,YAAY,EAIb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,KAAK,EAEV,aAAa,EACb,mBAAmB,EACpB,MAAM,YAAY,CAAC;AA6CpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,qBAAa,QAAQ,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,SAAS;IACxE,iFAAiF;IACjF,SAAgB,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAEjE,yEAAyE;IACzE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAuB;IAE1D;;;;;OAKG;gBACS,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;KAAE;IA0C7I;;;;;;;;;;;;;;;;;;;;;;OAsBG;WACU,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnD,KAAK,EAAE,SAAS,EAChB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,GAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;CA6C9B;AA0FD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAmBjE;AA+QD;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,GAAG,CAAC,UAAU;IACzF,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;CACpD;AAED;;;;;GAKG;AACH,qBAAa,WAAW,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,GAAG,CAAC,KAAK;IAC3E,SAAgB,WAAW,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;gBAE9C,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC;CAI3E"}
1
+ {"version":3,"file":"pipeline-construct.d.ts","sourceRoot":"","sources":["../../src/pipeline/pipeline-construct.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AAInC,OAAO,EAEL,YAAY,EAIb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIvC,OAAO,KAAK,EAEV,aAAa,EACb,mBAAmB,EACpB,MAAM,YAAY,CAAC;AA6CpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,qBAAa,QAAQ,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,SAAS;IACxE,iFAAiF;IACjF,SAAgB,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAEjE,yEAAyE;IACzE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAuB;IAE1D;;;;;OAKG;gBACS,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;KAAE;IA0C7I;;;;;;;;;;;;;;;;;;;;;;OAsBG;WACU,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnD,KAAK,EAAE,SAAS,EAChB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,GAC5B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;CA6C9B;AA0FD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAmBjE;AAiRD;;GAEG;AACH,MAAM,WAAW,gBAAgB,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,GAAG,CAAC,UAAU;IACzF,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;CACpD;AAED;;;;;GAKG;AACH,qBAAa,WAAW,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,GAAG,CAAC,KAAK;IAC3E,SAAgB,WAAW,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;gBAE9C,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC;CAI3E"}
@@ -8,6 +8,7 @@ import { CodeBuildStep, CodePipeline, CodePipelineSource, ManualApprovalStep, Sh
8
8
  import { Construct } from 'constructs';
9
9
  import * as fs from 'fs';
10
10
  import * as path from 'path';
11
+ import { pathToFileURL } from 'node:url';
11
12
  /**
12
13
  * Resolve a relative file path against the calling file's directory.
13
14
  *
@@ -420,8 +421,10 @@ async function importAppFileForStage(stage, stageConfig, appFile) {
420
421
  // Capture current beforeExit listeners before import
421
422
  const listenersBefore = process.listeners('beforeExit').slice();
422
423
  try {
423
- // ESM caches modules by URL append a unique query string so each stage re-executes the module body
424
- await import(`${appFile}?stage=${encodeURIComponent(stageConfig.name)}`);
424
+ // file:// URL (not a raw path) so the cache-busting query works on Windows.
425
+ const appUrl = pathToFileURL(appFile);
426
+ appUrl.searchParams.set('stage', stageConfig.name);
427
+ await import(appUrl.href);
425
428
  }
426
429
  finally {
427
430
  // Remove any beforeExit listeners added during import.
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/scripts/deploy.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,iBAwFlD"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/scripts/deploy.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,iBAwFlD"}
@@ -8,6 +8,7 @@ import { ensureSecrets, loadProductionEnv } from './ensure-secrets.js';
8
8
  import { applyExternalMigrations } from './external-migrations-step.js';
9
9
  import { trackCommand } from '../telemetry/trackCommand.js';
10
10
  import { getCdkTelemetryEnv } from './cdk-telemetry-env.js';
11
+ import { runSync } from './run-command.js';
11
12
  export async function deploy(options) {
12
13
  return trackCommand('deploy', async () => {
13
14
  console.log('🏗️ Preparing deployment...');
@@ -42,7 +43,7 @@ export async function deploy(options) {
42
43
  console.log(' - Backend API (Lambda + API Gateway)');
43
44
  console.log(' - Frontend hosting (S3 + CloudFront)');
44
45
  try {
45
- execFileSync("npx", [
46
+ runSync("npx", [
46
47
  "cdk", "deploy",
47
48
  "--require-approval", "never",
48
49
  "--outputs-file", ".blocks-sandbox/outputs.json",
@@ -1,13 +1,13 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- import { execFileSync } from 'node:child_process';
4
3
  import { trackCommand } from '../telemetry/trackCommand.js';
5
4
  import { getCdkTelemetryEnv } from './cdk-telemetry-env.js';
5
+ import { runSync } from './run-command.js';
6
6
  export async function destroy(options) {
7
7
  return trackCommand('destroy', async () => {
8
8
  console.log('🗑️ Destroying production stack...');
9
9
  try {
10
- execFileSync("npx", [
10
+ runSync("npx", [
11
11
  "cdk", "destroy",
12
12
  "--force",
13
13
  "--context", `projectRoot=${options.projectRoot}`,
@@ -22,10 +22,10 @@
22
22
  * exists. Aurora-managed databases run migrations via their in-VPC Lambda, not
23
23
  * here.
24
24
  */
25
- import { execFileSync } from 'node:child_process';
26
25
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
27
26
  import { findConnectionString } from './ensure-secrets.js';
28
27
  import { extractDbRef, dbConnectionParameterName } from '../db-naming.js';
28
+ import { runSync } from './run-command.js';
29
29
  const DEFAULT_MIGRATIONS_DIR = './migrations';
30
30
  /** Default output dir for db-pull generated files (database.types.ts / database.meta.ts). */
31
31
  const DEFAULT_GENERATED_DIR = './aws-blocks';
@@ -50,7 +50,7 @@ export function buildMigrateArgs(stage, migrationsDir, regenerateTypesDir) {
50
50
  return args;
51
51
  }
52
52
  function runMigrateSubprocess(connValue, stage, migrationsDir, regenerateTypesDir) {
53
- execFileSync('npx', buildMigrateArgs(stage, migrationsDir, regenerateTypesDir), {
53
+ runSync('npx', buildMigrateArgs(stage, migrationsDir, regenerateTypesDir), {
54
54
  stdio: 'inherit',
55
55
  env: { ...process.env, BLOCKS_MIGRATE_URL: connValue },
56
56
  });
@@ -20,7 +20,7 @@ import { existsSync } from 'fs';
20
20
  * JSDoc parsers.
21
21
  */
22
22
  export const BLOCKS_SKIP_CODEGEN_TAG = 'blocksSkipCodegen';
23
- function hasKitSkipCodegenTag(node) {
23
+ function hasBlocksSkipCodegenTag(node) {
24
24
  for (const tag of ts.getJSDocTags(node)) {
25
25
  if (tag.tagName.text === BLOCKS_SKIP_CODEGEN_TAG)
26
26
  return true;
@@ -51,7 +51,7 @@ export function extractSkipCodegenMethods(sourcePath) {
51
51
  if (ts.isMethodDeclaration(prop) &&
52
52
  prop.name &&
53
53
  ts.isIdentifier(prop.name) &&
54
- hasKitSkipCodegenTag(prop)) {
54
+ hasBlocksSkipCodegenTag(prop)) {
55
55
  result.add(prop.name.text);
56
56
  }
57
57
  }
@@ -253,7 +253,7 @@ function extractMethodTypeInfo(method, checker, sourceFile) {
253
253
  transferable = detectTransferable(retType, checker, retTypeNode);
254
254
  returnType = tsTypeToJsonSchema(retType, checker);
255
255
  }
256
- const skipCodegen = hasKitSkipCodegenTag(method) || undefined;
256
+ const skipCodegen = hasBlocksSkipCodegenTag(method) || undefined;
257
257
  return { params, returnType, transferable, skipCodegen };
258
258
  }
259
259
  /**
@@ -343,7 +343,7 @@ function extractMethodsFromResolvedType(type, checker, result) {
343
343
  // the BB-helper file that minted the AsyncAPI<T> shape).
344
344
  let skipCodegen;
345
345
  const declaration = prop.valueDeclaration;
346
- if (declaration && hasKitSkipCodegenTag(declaration)) {
346
+ if (declaration && hasBlocksSkipCodegenTag(declaration)) {
347
347
  skipCodegen = true;
348
348
  }
349
349
  result.set(propName, { params, returnType, transferable, skipCodegen });
@@ -0,0 +1,9 @@
1
+ import type { ChildProcess, SpawnOptions, SpawnSyncOptions } from 'node:child_process';
2
+ /**
3
+ * Run a command to completion (stdio inherited) and throw on failure — a
4
+ * cross-platform drop-in for `execFileSync` where only success/failure matters.
5
+ */
6
+ export declare function runSync(command: string, args: string[], options?: SpawnSyncOptions): void;
7
+ /** Spawn a long-running command and return the `ChildProcess` (e.g. `cdk watch`). */
8
+ export declare function spawnCommand(command: string, args: string[], options: SpawnOptions): ChildProcess;
9
+ //# sourceMappingURL=run-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-command.d.ts","sourceRoot":"","sources":["../../src/scripts/run-command.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EACZ,gBAAgB,EACjB,MAAM,oBAAoB,CAAC;AAQ5B;;;GAGG;AACH,wBAAgB,OAAO,CACrB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,GAAE,gBAAqB,GAC7B,IAAI,CAYN;AAED,qFAAqF;AACrF,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE,YAAY,GACpB,YAAY,CAEd"}
@@ -0,0 +1,27 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import spawn from 'cross-spawn';
4
+ // `npm`/`npx`/`cdk`/`tsx` are `.cmd` shims on Windows, which Node's
5
+ // execFileSync/spawn can't resolve (spawnSync ENOENT) and won't run without a
6
+ // shell. cross-spawn resolves the shim and quotes args safely (array form, no
7
+ // shell injection), so these wrappers work on Windows too.
8
+ /**
9
+ * Run a command to completion (stdio inherited) and throw on failure — a
10
+ * cross-platform drop-in for `execFileSync` where only success/failure matters.
11
+ */
12
+ export function runSync(command, args, options = {}) {
13
+ const result = spawn.sync(command, args, { stdio: 'inherit', ...options });
14
+ if (result.error) {
15
+ throw result.error;
16
+ }
17
+ if (result.signal) {
18
+ throw new Error(`${command} was terminated by signal ${result.signal}`);
19
+ }
20
+ if (typeof result.status === 'number' && result.status !== 0) {
21
+ throw new Error(`${command} ${args.join(' ')} exited with code ${result.status}`);
22
+ }
23
+ }
24
+ /** Spawn a long-running command and return the `ChildProcess` (e.g. `cdk watch`). */
25
+ export function spawnCommand(command, args, options) {
26
+ return spawn(command, args, options);
27
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/scripts/sandbox.ts"],"names":[],"mappings":"AA6BA,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,+BAqJzD;AAED,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,iBAwCvD"}
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../src/scripts/sandbox.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,cAAc,+BAqJzD;AAED,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,iBAwCvD"}
@@ -1,6 +1,6 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
- import { execFileSync, spawn } from "node:child_process";
3
+ import { execFileSync } from "node:child_process";
4
4
  import { writeFileSync, mkdirSync, readFileSync } from "node:fs";
5
5
  import { join, resolve, dirname } from "node:path";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -9,6 +9,7 @@ import { applyExternalMigrations } from './external-migrations-step.js';
9
9
  import { trackCommand } from '../telemetry/trackCommand.js';
10
10
  import { buildAndSendEvent } from '../telemetry/client.js';
11
11
  import { getCdkTelemetryEnv } from './cdk-telemetry-env.js';
12
+ import { runSync, spawnCommand } from './run-command.js';
12
13
  /**
13
14
  * Import the backend definition to populate the Scope BB registry.
14
15
  *
@@ -55,7 +56,7 @@ export async function startSandbox(options) {
55
56
  console.log("🚀 Deploying to AWS...");
56
57
  console.log(" (This may take a few minutes on first deploy)");
57
58
  try {
58
- execFileSync("npm", [
59
+ runSync("npm", [
59
60
  "exec", "cdk", "--", "deploy",
60
61
  "--require-approval", "never",
61
62
  "--outputs-file", `${outDir}/outputs.json`,
@@ -112,7 +113,7 @@ export async function startSandbox(options) {
112
113
  console.log("\n👀 Starting CDK watch mode...");
113
114
  console.log("🌐 Starting local dev server (proxying to AWS)...");
114
115
  console.log(`\n Open http://localhost:${clientPort}\n`);
115
- const cdkWatch = spawn("npx", [
116
+ const cdkWatch = spawnCommand("npx", [
116
117
  "cdk", "watch", "--hotswap",
117
118
  `--outputs-file`, `${outDir}/outputs.json`,
118
119
  `--context`, `projectRoot=${process.cwd()}`,
@@ -133,7 +134,7 @@ export async function startSandbox(options) {
133
134
  });
134
135
  const devServerCmd = devCommand || `npx tsx watch aws-blocks/scripts/server.ts`;
135
136
  const [cmd, ...args] = devServerCmd.split(' ');
136
- const devServer = spawn(cmd, args, {
137
+ const devServer = spawnCommand(cmd, args, {
137
138
  stdio: "inherit",
138
139
  shell: true,
139
140
  env: {
@@ -180,7 +181,7 @@ export async function destroySandbox(backendPath) {
180
181
  const retryDelays = [60_000, 120_000]; // 1min, then 2min
181
182
  for (let attempt = 0;; attempt++) {
182
183
  try {
183
- execFileSync("npm", cdkArgs, { stdio: "inherit", env: cdkEnv });
184
+ runSync("npm", cdkArgs, { stdio: "inherit", env: cdkEnv });
184
185
  console.log(attempt === 0 ? "\n✅ Sandbox destroyed!" : "\n✅ Sandbox destroyed on retry!");
185
186
  return;
186
187
  }
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const CORE_VERSION = "0.1.0";
1
+ export declare const CORE_VERSION = "0.1.2";
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.0';
2
+ export const CORE_VERSION = '0.1.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws-blocks/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "author": "Amazon Web Services",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -55,6 +55,7 @@
55
55
  "blocks-telemetry": "./dist/scripts/telemetry-cli.js"
56
56
  },
57
57
  "devDependencies": {
58
+ "@types/cross-spawn": "^6.0.6",
58
59
  "@types/http-proxy": "^1.17.16",
59
60
  "@types/node": "^20.0.0",
60
61
  "typescript": "^5.3.0",
@@ -64,6 +65,7 @@
64
65
  "@aws-blocks/hosting": "*",
65
66
  "@aws-sdk/client-s3": "^3.700.0",
66
67
  "@aws-sdk/client-ssm": "^3.700.0",
68
+ "cross-spawn": "^7.0.6",
67
69
  "http-proxy": "^1.18.1"
68
70
  },
69
71
  "peerDependencies": {
@@ -6,6 +6,7 @@ import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
6
6
  import * as apigateway from 'aws-cdk-lib/aws-apigateway';
7
7
  import { CfnGroup } from 'aws-cdk-lib/aws-resourcegroups';
8
8
  import { Construct } from 'constructs';
9
+ import { pathToFileURL } from 'node:url';
9
10
  import { DEFAULT_NODE_RUNTIME } from './node-version.js';
10
11
  import { addBlocksStackMetadata } from './stack-metadata.js';
11
12
  import { finalizeConfigRegistry, registerConfig } from './config-registry.js';
@@ -230,8 +231,11 @@ export class BlocksBackend extends Construct {
230
231
  static async create(scope: Construct, id: string, props: BlocksBackendProps) {
231
232
  assertCdkConditionActive();
232
233
  const backend = new BlocksBackend(scope, id, props);
233
- // ESM caches modules by URL append a unique query string so each stage re-executes the module body
234
- const mod = await import(`${props.backendCDKPath}?stack=${id}`);
234
+ // file:// URL (not a raw path) so the cache-busting query works on Windows,
235
+ // where an absolute path like `D:\...` is rejected as URL scheme `d:`.
236
+ const backendUrl = pathToFileURL(props.backendCDKPath);
237
+ backendUrl.searchParams.set('stack', id);
238
+ const mod = await import(backendUrl.href);
235
239
  if (typeof mod.default === 'function') {
236
240
  try {
237
241
  await mod.default(backend);
package/src/cdk/index.ts CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  import * as cdk from 'aws-cdk-lib';
5
5
  import { Construct } from 'constructs';
6
+ import { pathToFileURL } from 'node:url';
6
7
  import {
7
8
  type BlocksStackProps,
8
9
  type BlocksStack as BaseBlocksStack,
@@ -51,8 +52,11 @@ export class BlocksStack extends cdk.Stack implements BaseBlocksStack {
51
52
  const actualScope = pipelineScope || scope;
52
53
 
53
54
  const stack = new BlocksStack(actualScope, id, props);
54
- // ESM caches modules by URL append a unique query string so each stage re-executes the module body
55
- const mod = await import(`${props.backendCDKPath}?stack=${id}`);
55
+ // file:// URL (not a raw path) so the cache-busting query works on Windows,
56
+ // where an absolute path like `D:\...` is rejected as URL scheme `d:`.
57
+ const backendUrl = pathToFileURL(props.backendCDKPath);
58
+ backendUrl.searchParams.set('stack', id);
59
+ const mod = await import(backendUrl.href);
56
60
  if (typeof mod.default === 'function') {
57
61
  try {
58
62
  await mod.default(stack);
@@ -144,7 +144,7 @@ async function resolveApiUrl(): Promise<string> {
144
144
  * Middleware can inspect or modify any of these properties before the
145
145
  * request is dispatched to the server.
146
146
  */
147
- export interface KitRequest {
147
+ export interface BlocksRequest {
148
148
  /** The namespace name this call targets (e.g., 'api', 'auth'). Corresponds to the second argument of `new ApiNamespace(scope, name, handler)` in the backend. */
149
149
  apiNamespace: string;
150
150
  /** The method being called on the namespace (e.g., 'getUser', 'kvSet'). */
@@ -176,14 +176,14 @@ export interface KitRequest {
176
176
  * });
177
177
  * ```
178
178
  */
179
- export interface KitMiddleware {
179
+ export interface BlocksMiddleware {
180
180
  /** Transform the request before it's sent. Modify the request in place or return a new one. Can be async. */
181
- onRequest?: (request: KitRequest) => KitRequest | void | Promise<KitRequest | void>;
181
+ onRequest?: (request: BlocksRequest) => BlocksRequest | void | Promise<BlocksRequest | void>;
182
182
  /** Transform the response data after it's received. Used to hydrate __blocks descriptors. */
183
183
  onResponse?: (data: unknown) => unknown;
184
184
  }
185
185
 
186
- const middlewares: KitMiddleware[] = [];
186
+ const middlewares: BlocksMiddleware[] = [];
187
187
 
188
188
  /**
189
189
  * Register client middleware for request/response processing.
@@ -204,11 +204,11 @@ const middlewares: KitMiddleware[] = [];
204
204
  * an explicit `attach(server)` pattern because they need the HTTP server
205
205
  * instance passed to them — something unavailable at import time.
206
206
  */
207
- export function registerMiddleware(middleware: KitMiddleware): void {
207
+ export function registerMiddleware(middleware: BlocksMiddleware): void {
208
208
  middlewares.push(middleware);
209
209
  }
210
210
 
211
- async function processRequest(request: KitRequest): Promise<KitRequest> {
211
+ async function processRequest(request: BlocksRequest): Promise<BlocksRequest> {
212
212
  for (const mw of middlewares) {
213
213
  if (mw.onRequest) {
214
214
  const result = await mw.onRequest(request);
@@ -265,7 +265,7 @@ export function ApiNamespaceClient<T extends Record<string, (...args: any[]) =>
265
265
  return async (...args: any[]) => {
266
266
  const apiUrl = urlOverride ?? await getApiUrl();
267
267
 
268
- let request: KitRequest = {
268
+ let request: BlocksRequest = {
269
269
  apiNamespace: name,
270
270
  method,
271
271
  args,
@@ -15,6 +15,7 @@ import {
15
15
  import { Construct } from 'constructs';
16
16
  import * as fs from 'fs';
17
17
  import * as path from 'path';
18
+ import { pathToFileURL } from 'node:url';
18
19
  import type {
19
20
  BranchConfig,
20
21
  PipelineProps,
@@ -579,8 +580,10 @@ async function importAppFileForStage<TConfig>(
579
580
  const listenersBefore = process.listeners('beforeExit').slice();
580
581
 
581
582
  try {
582
- // ESM caches modules by URL append a unique query string so each stage re-executes the module body
583
- await import(`${appFile}?stage=${encodeURIComponent(stageConfig.name)}`);
583
+ // file:// URL (not a raw path) so the cache-busting query works on Windows.
584
+ const appUrl = pathToFileURL(appFile);
585
+ appUrl.searchParams.set('stage', stageConfig.name);
586
+ await import(appUrl.href);
584
587
  } finally {
585
588
  // Remove any beforeExit listeners added during import.
586
589
  // The imported file's cdk.App() registers a synth() handler that would
@@ -9,6 +9,7 @@ import { ensureSecrets, loadProductionEnv } from './ensure-secrets.js';
9
9
  import { applyExternalMigrations } from './external-migrations-step.js';
10
10
  import { trackCommand } from '../telemetry/trackCommand.js';
11
11
  import { getCdkTelemetryEnv } from './cdk-telemetry-env.js';
12
+ import { runSync } from './run-command.js';
12
13
 
13
14
  export interface DeployOptions {
14
15
  cdkAppPath: string;
@@ -56,7 +57,7 @@ export async function deploy(options: DeployOptions) {
56
57
  console.log(' - Frontend hosting (S3 + CloudFront)');
57
58
 
58
59
  try {
59
- execFileSync(
60
+ runSync(
60
61
  "npx",
61
62
  [
62
63
  "cdk", "deploy",
@@ -1,9 +1,9 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import { execFileSync } from 'node:child_process';
5
4
  import { trackCommand } from '../telemetry/trackCommand.js';
6
5
  import { getCdkTelemetryEnv } from './cdk-telemetry-env.js';
6
+ import { runSync } from './run-command.js';
7
7
 
8
8
  export interface DestroyOptions {
9
9
  cdkAppPath: string;
@@ -15,7 +15,7 @@ export async function destroy(options: DestroyOptions) {
15
15
  console.log('🗑️ Destroying production stack...');
16
16
 
17
17
  try {
18
- execFileSync(
18
+ runSync(
19
19
  "npx",
20
20
  [
21
21
  "cdk", "destroy",
@@ -24,10 +24,10 @@
24
24
  * here.
25
25
  */
26
26
 
27
- import { execFileSync } from 'node:child_process';
28
27
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
29
28
  import { findConnectionString } from './ensure-secrets.js';
30
29
  import { extractDbRef, dbConnectionParameterName } from '../db-naming.js';
30
+ import { runSync } from './run-command.js';
31
31
 
32
32
  const DEFAULT_MIGRATIONS_DIR = './migrations';
33
33
  /** Default output dir for db-pull generated files (database.types.ts / database.meta.ts). */
@@ -59,7 +59,7 @@ function runMigrateSubprocess(
59
59
  migrationsDir: string,
60
60
  regenerateTypesDir?: string,
61
61
  ): void {
62
- execFileSync('npx', buildMigrateArgs(stage, migrationsDir, regenerateTypesDir), {
62
+ runSync('npx', buildMigrateArgs(stage, migrationsDir, regenerateTypesDir), {
63
63
  stdio: 'inherit',
64
64
  env: { ...process.env, BLOCKS_MIGRATE_URL: connValue },
65
65
  });
@@ -48,7 +48,7 @@ export interface MethodTypeInfo {
48
48
  */
49
49
  export const BLOCKS_SKIP_CODEGEN_TAG = 'blocksSkipCodegen';
50
50
 
51
- function hasKitSkipCodegenTag(node: ts.Node): boolean {
51
+ function hasBlocksSkipCodegenTag(node: ts.Node): boolean {
52
52
  for (const tag of ts.getJSDocTags(node)) {
53
53
  if (tag.tagName.text === BLOCKS_SKIP_CODEGEN_TAG) return true;
54
54
  }
@@ -86,7 +86,7 @@ export function extractSkipCodegenMethods(sourcePath: string): Set<string> {
86
86
  ts.isMethodDeclaration(prop) &&
87
87
  prop.name &&
88
88
  ts.isIdentifier(prop.name) &&
89
- hasKitSkipCodegenTag(prop)
89
+ hasBlocksSkipCodegenTag(prop)
90
90
  ) {
91
91
  result.add(prop.name.text);
92
92
  }
@@ -328,7 +328,7 @@ function extractMethodTypeInfo(
328
328
  returnType = tsTypeToJsonSchema(retType, checker);
329
329
  }
330
330
 
331
- const skipCodegen = hasKitSkipCodegenTag(method) || undefined;
331
+ const skipCodegen = hasBlocksSkipCodegenTag(method) || undefined;
332
332
  return { params, returnType, transferable, skipCodegen };
333
333
  }
334
334
 
@@ -429,7 +429,7 @@ function extractMethodsFromResolvedType(
429
429
  // the BB-helper file that minted the AsyncAPI<T> shape).
430
430
  let skipCodegen: true | undefined;
431
431
  const declaration = prop.valueDeclaration;
432
- if (declaration && hasKitSkipCodegenTag(declaration)) {
432
+ if (declaration && hasBlocksSkipCodegenTag(declaration)) {
433
433
  skipCodegen = true;
434
434
  }
435
435
 
@@ -0,0 +1,45 @@
1
+ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import type {
5
+ ChildProcess,
6
+ SpawnOptions,
7
+ SpawnSyncOptions,
8
+ } from 'node:child_process';
9
+ import spawn from 'cross-spawn';
10
+
11
+ // `npm`/`npx`/`cdk`/`tsx` are `.cmd` shims on Windows, which Node's
12
+ // execFileSync/spawn can't resolve (spawnSync ENOENT) and won't run without a
13
+ // shell. cross-spawn resolves the shim and quotes args safely (array form, no
14
+ // shell injection), so these wrappers work on Windows too.
15
+
16
+ /**
17
+ * Run a command to completion (stdio inherited) and throw on failure — a
18
+ * cross-platform drop-in for `execFileSync` where only success/failure matters.
19
+ */
20
+ export function runSync(
21
+ command: string,
22
+ args: string[],
23
+ options: SpawnSyncOptions = {},
24
+ ): void {
25
+ const result = spawn.sync(command, args, { stdio: 'inherit', ...options });
26
+
27
+ if (result.error) {
28
+ throw result.error;
29
+ }
30
+ if (result.signal) {
31
+ throw new Error(`${command} was terminated by signal ${result.signal}`);
32
+ }
33
+ if (typeof result.status === 'number' && result.status !== 0) {
34
+ throw new Error(`${command} ${args.join(' ')} exited with code ${result.status}`);
35
+ }
36
+ }
37
+
38
+ /** Spawn a long-running command and return the `ChildProcess` (e.g. `cdk watch`). */
39
+ export function spawnCommand(
40
+ command: string,
41
+ args: string[],
42
+ options: SpawnOptions,
43
+ ): ChildProcess {
44
+ return spawn(command, args, options);
45
+ }
@@ -1,7 +1,7 @@
1
1
  // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
- import { execFileSync, spawn } from "node:child_process";
4
+ import { execFileSync } from "node:child_process";
5
5
  import { writeFileSync, mkdirSync, readFileSync } from "node:fs";
6
6
  import { join, resolve, dirname } from "node:path";
7
7
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -10,6 +10,7 @@ import { applyExternalMigrations } from './external-migrations-step.js';
10
10
  import { trackCommand } from '../telemetry/trackCommand.js';
11
11
  import { buildAndSendEvent } from '../telemetry/client.js';
12
12
  import { getCdkTelemetryEnv } from './cdk-telemetry-env.js';
13
+ import { runSync, spawnCommand } from './run-command.js';
13
14
 
14
15
  /**
15
16
  * Import the backend definition to populate the Scope BB registry.
@@ -69,7 +70,7 @@ export async function startSandbox(options: SandboxOptions) {
69
70
  console.log(" (This may take a few minutes on first deploy)");
70
71
 
71
72
  try {
72
- execFileSync(
73
+ runSync(
73
74
  "npm",
74
75
  [
75
76
  "exec", "cdk", "--", "deploy",
@@ -138,7 +139,7 @@ export async function startSandbox(options: SandboxOptions) {
138
139
  console.log("🌐 Starting local dev server (proxying to AWS)...");
139
140
  console.log(`\n Open http://localhost:${clientPort}\n`);
140
141
 
141
- const cdkWatch = spawn("npx", [
142
+ const cdkWatch = spawnCommand("npx", [
142
143
  "cdk", "watch", "--hotswap",
143
144
  `--outputs-file`, `${outDir}/outputs.json`,
144
145
  `--context`, `projectRoot=${process.cwd()}`,
@@ -162,7 +163,7 @@ export async function startSandbox(options: SandboxOptions) {
162
163
  const devServerCmd = devCommand || `npx tsx watch aws-blocks/scripts/server.ts`;
163
164
  const [cmd, ...args] = devServerCmd.split(' ');
164
165
 
165
- const devServer = spawn(cmd, args, {
166
+ const devServer = spawnCommand(cmd, args, {
166
167
  stdio: "inherit",
167
168
  shell: true,
168
169
  env: {
@@ -212,7 +213,7 @@ export async function destroySandbox(backendPath: string) {
212
213
 
213
214
  for (let attempt = 0; ; attempt++) {
214
215
  try {
215
- execFileSync("npm", cdkArgs, { stdio: "inherit", env: cdkEnv });
216
+ runSync("npm", cdkArgs, { stdio: "inherit", env: cdkEnv });
216
217
  console.log(attempt === 0 ? "\n✅ Sandbox destroyed!" : "\n✅ Sandbox destroyed on retry!");
217
218
  return;
218
219
  } catch (error) {
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.0';
2
+ export const CORE_VERSION = '0.1.2';