@aws-blocks/core 0.1.12 → 0.1.17
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 +180 -17
- package/dist/cors.d.ts +27 -1
- package/dist/cors.d.ts.map +1 -1
- package/dist/cors.js +55 -2
- package/dist/cors.test.js +81 -2
- package/dist/errors.test.js +26 -1
- package/dist/hosting.d.ts.map +1 -1
- package/dist/hosting.js +26 -1
- package/dist/hosting.test.js +73 -0
- package/dist/lambda-handler.d.ts.map +1 -1
- package/dist/lambda-handler.js +4 -17
- package/dist/lambda-handler.test.js +59 -2
- package/dist/redact.d.ts +3 -2
- package/dist/redact.d.ts.map +1 -1
- package/dist/redact.js +4 -3
- package/dist/redact.test.js +9 -0
- package/dist/rpc.test.js +77 -1
- package/dist/scripts/console.d.ts.map +1 -1
- package/dist/scripts/console.js +30 -2
- package/dist/scripts/deploy-stream.d.ts +181 -0
- package/dist/scripts/deploy-stream.d.ts.map +1 -0
- package/dist/scripts/deploy-stream.js +332 -0
- package/dist/scripts/deploy-stream.test.d.ts +2 -0
- package/dist/scripts/deploy-stream.test.d.ts.map +1 -0
- package/dist/scripts/deploy-stream.test.js +845 -0
- package/dist/scripts/deploy.d.ts.map +1 -1
- package/dist/scripts/deploy.js +16 -9
- package/dist/scripts/dev-server-cors.test.js +19 -1
- package/dist/scripts/dev-server-rpc.test.d.ts +2 -0
- package/dist/scripts/dev-server-rpc.test.d.ts.map +1 -0
- package/dist/scripts/dev-server-rpc.test.js +157 -0
- package/dist/scripts/dev-server.d.ts +8 -0
- package/dist/scripts/dev-server.d.ts.map +1 -1
- package/dist/scripts/dev-server.js +35 -8
- package/dist/scripts/sandbox.js +1 -1
- package/dist/telemetry/client.js +4 -4
- package/dist/telemetry/telemetry-send-worker.js +4 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +10 -1
- package/src/cors.test.ts +96 -2
- package/src/cors.ts +59 -2
- package/src/errors.test.ts +29 -1
- package/src/hosting.test.ts +107 -0
- package/src/hosting.ts +27 -1
- package/src/lambda-handler.test.ts +71 -2
- package/src/lambda-handler.ts +4 -20
- package/src/redact.test.ts +12 -0
- package/src/redact.ts +4 -3
- package/src/rpc.test.ts +96 -1
- package/src/scripts/console.ts +29 -2
- package/src/scripts/deploy-stream.test.ts +1035 -0
- package/src/scripts/deploy-stream.ts +475 -0
- package/src/scripts/deploy.ts +18 -11
- package/src/scripts/dev-server-cors.test.ts +26 -1
- package/src/scripts/dev-server-rpc.test.ts +169 -0
- package/src/scripts/dev-server.ts +38 -8
- package/src/scripts/sandbox.ts +1 -1
- package/src/telemetry/client.ts +4 -4
- package/src/telemetry/telemetry-send-worker.ts +5 -0
- package/src/version.ts +1 -1
package/dist/hosting.test.js
CHANGED
|
@@ -1134,4 +1134,77 @@ describe('Hosting', () => {
|
|
|
1134
1134
|
`found DependsOn=${JSON.stringify(dependsOn)}`);
|
|
1135
1135
|
});
|
|
1136
1136
|
});
|
|
1137
|
+
describe('config.json stale-placeholder guard (#173)', () => {
|
|
1138
|
+
// Helper: pull every BucketDeployment custom resource's Properties.
|
|
1139
|
+
const bucketDeployments = (stack) => {
|
|
1140
|
+
const crs = Template.fromStack(stack).findResources('Custom::CDKBucketDeployment');
|
|
1141
|
+
return Object.values(crs).map((cr) => cr.Properties);
|
|
1142
|
+
};
|
|
1143
|
+
it('uploads the placeholder config.json with a no-cache directive, never the 1-year mutable cache-control', () => {
|
|
1144
|
+
// The build-time placeholder (`{_placeholder:true}`) is written into the
|
|
1145
|
+
// static dir. If it inherits the mutable asset tier's
|
|
1146
|
+
// `s-maxage=31536000` and an edge caches it during the deploy window, the
|
|
1147
|
+
// edge serves the placeholder for up to a year — breaking every client
|
|
1148
|
+
// API call. It must instead be uploaded as a no-cache path so the edge
|
|
1149
|
+
// never caches it long-term.
|
|
1150
|
+
createSpaBuildOutput(tmpDir);
|
|
1151
|
+
const app = new App();
|
|
1152
|
+
const stack = new Stack(app, 'PlaceholderCacheStack');
|
|
1153
|
+
new Hosting(stack, 'Hosting', { root: tmpDir, api: MOCK_API });
|
|
1154
|
+
const deployments = bucketDeployments(stack);
|
|
1155
|
+
const cc = (p) => p.SystemMetadata?.['cache-control'];
|
|
1156
|
+
const includes = (p) => p.Include ?? [];
|
|
1157
|
+
const excludes = (p) => p.Exclude ?? [];
|
|
1158
|
+
// A deployment must upload `.blocks-sandbox/config.json` with the
|
|
1159
|
+
// no-cache directive (this is the placeholder upload).
|
|
1160
|
+
const noCacheDeploy = deployments.find((p) => includes(p).includes('.blocks-sandbox/config.json') &&
|
|
1161
|
+
cc(p) === 'no-cache, no-store, must-revalidate');
|
|
1162
|
+
assert.ok(noCacheDeploy, 'placeholder .blocks-sandbox/config.json must be uploaded with ' +
|
|
1163
|
+
'"no-cache, no-store, must-revalidate"');
|
|
1164
|
+
// No deployment may cover the placeholder with the 1-year mutable
|
|
1165
|
+
// cache-control: the mutable/other tier must EXCLUDE it.
|
|
1166
|
+
const leaks = deployments.filter((p) => {
|
|
1167
|
+
const directive = cc(p);
|
|
1168
|
+
return (typeof directive === 'string' &&
|
|
1169
|
+
directive.includes('s-maxage=31536000') &&
|
|
1170
|
+
!excludes(p).includes('.blocks-sandbox/config.json'));
|
|
1171
|
+
});
|
|
1172
|
+
assert.strictEqual(leaks.length, 0, 'no mutable-tier deployment may apply s-maxage=31536000 to ' +
|
|
1173
|
+
'.blocks-sandbox/config.json');
|
|
1174
|
+
});
|
|
1175
|
+
it('registers the placeholder as a no-cache path even for a static-only site (no api)', () => {
|
|
1176
|
+
// The placeholder is always written (step 5), so its no-cache
|
|
1177
|
+
// registration must not depend on `props.api`. This guards against a
|
|
1178
|
+
// regression that moves the registration inside an `if (props.api)`
|
|
1179
|
+
// block, which would reopen the stale-placeholder window for
|
|
1180
|
+
// static-only sites.
|
|
1181
|
+
createSpaBuildOutput(tmpDir);
|
|
1182
|
+
const app = new App();
|
|
1183
|
+
const stack = new Stack(app, 'StaticOnlyPlaceholderStack');
|
|
1184
|
+
new Hosting(stack, 'Hosting', { root: tmpDir });
|
|
1185
|
+
Template.fromStack(stack).hasResourceProperties('Custom::CDKBucketDeployment', Match.objectLike({
|
|
1186
|
+
Include: Match.arrayWith(['.blocks-sandbox/config.json']),
|
|
1187
|
+
SystemMetadata: Match.objectLike({
|
|
1188
|
+
'cache-control': 'no-cache, no-store, must-revalidate',
|
|
1189
|
+
}),
|
|
1190
|
+
}));
|
|
1191
|
+
});
|
|
1192
|
+
it('invalidates the post-rewrite cache key (/builds/<id>/.blocks-sandbox/*)', () => {
|
|
1193
|
+
// The viewer-request skew-protection function rewrites the URI to
|
|
1194
|
+
// `/builds/<buildId>/.blocks-sandbox/config.json` BEFORE the cache
|
|
1195
|
+
// lookup, so the real edge cache key lives under `/builds/<id>/`.
|
|
1196
|
+
// Invalidating only `/.blocks-sandbox/*` never matches it.
|
|
1197
|
+
createSpaBuildOutput(tmpDir);
|
|
1198
|
+
const app = new App();
|
|
1199
|
+
const stack = new Stack(app, 'InvalidationPathStack');
|
|
1200
|
+
new Hosting(stack, 'Hosting', { root: tmpDir, api: MOCK_API });
|
|
1201
|
+
// Assert via Template + Match so a CDK property rename fails loudly here
|
|
1202
|
+
// rather than silently skipping a structural-heuristic lookup.
|
|
1203
|
+
Template.fromStack(stack).hasResourceProperties('Custom::CDKBucketDeployment', Match.objectLike({
|
|
1204
|
+
DistributionPaths: Match.arrayWith([
|
|
1205
|
+
Match.stringLikeRegexp('^/builds/.+/\\.blocks-sandbox/\\*$'),
|
|
1206
|
+
]),
|
|
1207
|
+
}));
|
|
1208
|
+
});
|
|
1209
|
+
});
|
|
1137
1210
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lambda-handler.d.ts","sourceRoot":"","sources":["../src/lambda-handler.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAerD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,2BAAkC,CAAC;AAG9D;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;CAErB,CAAC;
|
|
1
|
+
{"version":3,"file":"lambda-handler.d.ts","sourceRoot":"","sources":["../src/lambda-handler.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAerD,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,2BAAkC,CAAC;AAG9D;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;CAErB,CAAC;AAqCX;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAWlF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,CAwD7C;AAiBD;;;;;;;GAOG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAErE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,UAAU,CAWpD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,GAAG,GAAG,OAAO,CAEzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CAIrE;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,wBAAwB,IAAI,MAAM,CAAC;CACpC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CAAC,cAAc,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,IA0BtD,OAAO,GAAG,EAAE,UAAU,aAAa,kBAiElD"}
|
package/dist/lambda-handler.js
CHANGED
|
@@ -8,7 +8,7 @@ import { matchRoute, lockRouteRegistry } from './raw-route.js';
|
|
|
8
8
|
import { registerBuiltinRoutes } from './builtin-routes.js';
|
|
9
9
|
import { loadConfigToProcessEnv } from './common/config.js';
|
|
10
10
|
import { parseRpcRequest, successResponse, errorResponse, errorResponseFromCatch, methodNotFoundResponse, } from './rpc.js';
|
|
11
|
-
import { getCorsPatterns, isOriginAllowed, corsRejection } from './cors.js';
|
|
11
|
+
import { getCorsPatterns, isOriginAllowed, corsRejection, buildCorsHeaders, CORS_MAX_AGE } from './cors.js';
|
|
12
12
|
export { parseCorsPatterns, _resetCorsPatterns } from './cors.js';
|
|
13
13
|
/**
|
|
14
14
|
* AsyncLocalStorage that carries the inbound HTTP request cookies through
|
|
@@ -28,18 +28,6 @@ globalThis.__BLOCKS_REQUEST_COOKIES_STORE__ = requestCookies;
|
|
|
28
28
|
export const EventSourceMapping = {
|
|
29
29
|
SQS: 'aws:sqs',
|
|
30
30
|
};
|
|
31
|
-
// ── CORS helpers (private to handler) ───────────────────────────────────────
|
|
32
|
-
function buildCorsHeaders(origin) {
|
|
33
|
-
const headers = {};
|
|
34
|
-
if (isOriginAllowed(origin)) {
|
|
35
|
-
headers['Access-Control-Allow-Origin'] = origin;
|
|
36
|
-
headers['Access-Control-Allow-Credentials'] = 'true';
|
|
37
|
-
}
|
|
38
|
-
else if (origin) {
|
|
39
|
-
console.warn(`[CORS] Origin "${origin}" is not allowed. Set the CORS_ALLOWED_ORIGINS environment variable to allow this origin. Example: CORS_ALLOWED_ORIGINS=https://myapp\\.com,^https?://(localhost|127\\.0\\.0\\.1)(:\\d+)?$`);
|
|
40
|
-
}
|
|
41
|
-
return headers;
|
|
42
|
-
}
|
|
43
31
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
44
32
|
/**
|
|
45
33
|
* Decode the Lambda event body and return both the raw string and a ReadableStream.
|
|
@@ -330,7 +318,7 @@ export function createLambdaHandler(backendFactory) {
|
|
|
330
318
|
// Timeout won the race — build a 504 response. Format depends on
|
|
331
319
|
// whether the request targeted an RPC endpoint (structured JSON-RPC
|
|
332
320
|
// error envelope) or a plain HTTP path (simple error JSON).
|
|
333
|
-
const origin = event.headers?.origin || event.headers?.Origin || '
|
|
321
|
+
const origin = event.headers?.origin || event.headers?.Origin || '';
|
|
334
322
|
const requestPath = getRequestPath(event);
|
|
335
323
|
const isRpcPath = requestPath === BLOCKS_RPC_PREFIX || requestPath.startsWith(BLOCKS_RPC_PREFIX + '/');
|
|
336
324
|
const body = isRpcPath
|
|
@@ -340,8 +328,7 @@ export function createLambdaHandler(backendFactory) {
|
|
|
340
328
|
statusCode: 504,
|
|
341
329
|
headers: {
|
|
342
330
|
'Content-Type': 'application/json',
|
|
343
|
-
|
|
344
|
-
'Access-Control-Allow-Credentials': 'true',
|
|
331
|
+
...buildCorsHeaders(origin),
|
|
345
332
|
},
|
|
346
333
|
body,
|
|
347
334
|
};
|
|
@@ -414,7 +401,7 @@ function createHandler(backend) {
|
|
|
414
401
|
...corsHeaders,
|
|
415
402
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS',
|
|
416
403
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
417
|
-
'Access-Control-Max-Age':
|
|
404
|
+
'Access-Control-Max-Age': CORS_MAX_AGE,
|
|
418
405
|
},
|
|
419
406
|
body: '',
|
|
420
407
|
};
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { describe, it, beforeEach } from 'node:test';
|
|
4
4
|
import assert from 'node:assert';
|
|
5
|
-
import { createLambdaHandler, requestCookies, isApiGatewayHttpEvent, computeHttpDeadlineMs, classifyEvent, buildEventUrl, isLoopbackForwardedHost } from './lambda-handler.js';
|
|
5
|
+
import { createLambdaHandler, _resetCorsPatterns, requestCookies, isApiGatewayHttpEvent, computeHttpDeadlineMs, classifyEvent, buildEventUrl, isLoopbackForwardedHost } from './lambda-handler.js';
|
|
6
6
|
import { registerRoute, clearRouteRegistry } from './raw-route.js';
|
|
7
7
|
import { decodeRpcResponse } from './rpc.js';
|
|
8
8
|
beforeEach(() => {
|
|
@@ -499,7 +499,7 @@ describe('createLambdaHandler — timeout guard for API Gateway events', () => {
|
|
|
499
499
|
assert.strictEqual(body.jsonrpc, '2.0');
|
|
500
500
|
assert.strictEqual(body.error.code, 504);
|
|
501
501
|
});
|
|
502
|
-
it('504 response includes CORS headers
|
|
502
|
+
it('504 response includes CORS headers for an allowed origin', async () => {
|
|
503
503
|
const backend = {
|
|
504
504
|
api: () => ({
|
|
505
505
|
async echo() {
|
|
@@ -508,6 +508,9 @@ describe('createLambdaHandler — timeout guard for API Gateway events', () => {
|
|
|
508
508
|
},
|
|
509
509
|
}),
|
|
510
510
|
};
|
|
511
|
+
process.env.CORS_ALLOWED_ORIGINS = 'https://myapp\\.example\\.com';
|
|
512
|
+
delete process.env.CORS_HOSTING_ORIGINS;
|
|
513
|
+
_resetCorsPatterns();
|
|
511
514
|
const event = makeEvent({
|
|
512
515
|
headers: {
|
|
513
516
|
'Content-Type': 'application/json',
|
|
@@ -519,6 +522,60 @@ describe('createLambdaHandler — timeout guard for API Gateway events', () => {
|
|
|
519
522
|
assert.strictEqual(result.statusCode, 504);
|
|
520
523
|
assert.strictEqual(result.headers['Access-Control-Allow-Origin'], 'https://myapp.example.com');
|
|
521
524
|
assert.strictEqual(result.headers['Access-Control-Allow-Credentials'], 'true');
|
|
525
|
+
delete process.env.CORS_ALLOWED_ORIGINS;
|
|
526
|
+
_resetCorsPatterns();
|
|
527
|
+
});
|
|
528
|
+
it('504 response omits CORS headers when no allowlist is configured', async () => {
|
|
529
|
+
const backend = {
|
|
530
|
+
api: () => ({
|
|
531
|
+
async echo() {
|
|
532
|
+
await new Promise(resolve => setTimeout(resolve, 5_000));
|
|
533
|
+
return {};
|
|
534
|
+
},
|
|
535
|
+
}),
|
|
536
|
+
};
|
|
537
|
+
delete process.env.CORS_ALLOWED_ORIGINS;
|
|
538
|
+
delete process.env.CORS_HOSTING_ORIGINS;
|
|
539
|
+
_resetCorsPatterns();
|
|
540
|
+
const event = makeEvent({
|
|
541
|
+
headers: {
|
|
542
|
+
'Content-Type': 'application/json',
|
|
543
|
+
origin: 'https://evil.example.com',
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
const ctx = makeLambdaContext(50);
|
|
547
|
+
const result = await invokeWithContext(backend, event, ctx);
|
|
548
|
+
assert.strictEqual(result.statusCode, 504);
|
|
549
|
+
assert.strictEqual(result.headers['Access-Control-Allow-Origin'], undefined);
|
|
550
|
+
assert.strictEqual(result.headers['Access-Control-Allow-Credentials'], undefined);
|
|
551
|
+
delete process.env.CORS_ALLOWED_ORIGINS;
|
|
552
|
+
_resetCorsPatterns();
|
|
553
|
+
});
|
|
554
|
+
it('disallowed origin with a configured allowlist is rejected with 403 before the timeout path', async () => {
|
|
555
|
+
const backend = {
|
|
556
|
+
api: () => ({
|
|
557
|
+
async echo() {
|
|
558
|
+
await new Promise(resolve => setTimeout(resolve, 5_000));
|
|
559
|
+
return {};
|
|
560
|
+
},
|
|
561
|
+
}),
|
|
562
|
+
};
|
|
563
|
+
process.env.CORS_ALLOWED_ORIGINS = 'https://myapp\\.example\\.com';
|
|
564
|
+
delete process.env.CORS_HOSTING_ORIGINS;
|
|
565
|
+
_resetCorsPatterns();
|
|
566
|
+
const event = makeEvent({
|
|
567
|
+
headers: {
|
|
568
|
+
'Content-Type': 'application/json',
|
|
569
|
+
origin: 'https://evil.example.com',
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
const ctx = makeLambdaContext(50);
|
|
573
|
+
const result = await invokeWithContext(backend, event, ctx);
|
|
574
|
+
assert.strictEqual(result.statusCode, 403);
|
|
575
|
+
assert.strictEqual(result.headers['Access-Control-Allow-Origin'], undefined);
|
|
576
|
+
assert.strictEqual(result.headers['Access-Control-Allow-Credentials'], undefined);
|
|
577
|
+
delete process.env.CORS_ALLOWED_ORIGINS;
|
|
578
|
+
_resetCorsPatterns();
|
|
522
579
|
});
|
|
523
580
|
});
|
|
524
581
|
describe('createLambdaHandler — async events bypass timeout guard', () => {
|
package/dist/redact.d.ts
CHANGED
|
@@ -44,8 +44,9 @@ export declare const REDACTED: "[REDACTED]";
|
|
|
44
44
|
export declare function redactForLogging(value: unknown, seen?: WeakSet<object>): unknown;
|
|
45
45
|
/**
|
|
46
46
|
* Convenience for log call sites: redact `value` and serialize it to a JSON
|
|
47
|
-
* string. Returns
|
|
48
|
-
*
|
|
47
|
+
* string. Returns `'undefined'` when JSON serialization produces no output,
|
|
48
|
+
* or a safe placeholder if serialization throws (e.g. a BigInt slips through),
|
|
49
|
+
* so logging can never crash a request.
|
|
49
50
|
*/
|
|
50
51
|
export declare function redactToJson(value: unknown): string;
|
|
51
52
|
//# sourceMappingURL=redact.d.ts.map
|
package/dist/redact.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,sDAAsD;AACtD,eAAO,MAAM,QAAQ,EAAG,YAAqB,CAAC;AAwC9C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,GAAE,OAAO,CAAC,MAAM,CAAiB,GAAG,OAAO,CA2C/F;AAED
|
|
1
|
+
{"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../src/redact.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,sDAAsD;AACtD,eAAO,MAAM,QAAQ,EAAG,YAAqB,CAAC;AAwC9C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,GAAE,OAAO,CAAC,MAAM,CAAiB,GAAG,OAAO,CA2C/F;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAMnD"}
|
package/dist/redact.js
CHANGED
|
@@ -121,12 +121,13 @@ export function redactForLogging(value, seen = new WeakSet()) {
|
|
|
121
121
|
}
|
|
122
122
|
/**
|
|
123
123
|
* Convenience for log call sites: redact `value` and serialize it to a JSON
|
|
124
|
-
* string. Returns
|
|
125
|
-
*
|
|
124
|
+
* string. Returns `'undefined'` when JSON serialization produces no output,
|
|
125
|
+
* or a safe placeholder if serialization throws (e.g. a BigInt slips through),
|
|
126
|
+
* so logging can never crash a request.
|
|
126
127
|
*/
|
|
127
128
|
export function redactToJson(value) {
|
|
128
129
|
try {
|
|
129
|
-
return JSON.stringify(redactForLogging(value));
|
|
130
|
+
return JSON.stringify(redactForLogging(value)) ?? 'undefined';
|
|
130
131
|
}
|
|
131
132
|
catch {
|
|
132
133
|
return '[unserializable]';
|
package/dist/redact.test.js
CHANGED
|
@@ -179,6 +179,15 @@ describe('redactToJson', () => {
|
|
|
179
179
|
// BigInt is not JSON-serializable and survives redaction (not a sensitive key).
|
|
180
180
|
assert.equal(redactToJson({ big: 10n }), '[unserializable]');
|
|
181
181
|
});
|
|
182
|
+
it('returns a string for undefined', () => {
|
|
183
|
+
assert.equal(redactToJson(undefined), 'undefined');
|
|
184
|
+
});
|
|
185
|
+
it('returns a string for function values', () => {
|
|
186
|
+
assert.equal(redactToJson(() => { }), 'undefined');
|
|
187
|
+
});
|
|
188
|
+
it('returns a string for symbol values', () => {
|
|
189
|
+
assert.equal(redactToJson(Symbol('x')), 'undefined');
|
|
190
|
+
});
|
|
182
191
|
it('does not leak secrets even when truncation would apply downstream', () => {
|
|
183
192
|
const json = redactToJson([{ action: 'signIn', username: 'u', password: 'p'.repeat(50) }]);
|
|
184
193
|
assert.ok(!json.includes('pppp'));
|
package/dist/rpc.test.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { describe, it } from 'node:test';
|
|
4
4
|
import assert from 'node:assert';
|
|
5
|
-
import { parseRpcRequest, RpcErrorCode } from './rpc.js';
|
|
5
|
+
import { decodeRpcResponse, errorResponseFromCatch, parseRpcRequest, RpcErrorCode } from './rpc.js';
|
|
6
|
+
import { ApiError, isBlocksError } from './errors.js';
|
|
6
7
|
describe('-32600 Invalid Request error shape', () => {
|
|
7
8
|
it('returns proper JSON-RPC 2.0 envelope with error code', () => {
|
|
8
9
|
const result = parseRpcRequest(JSON.stringify({ method: 'ns.method', id: 1 }));
|
|
@@ -58,3 +59,78 @@ describe('-32600 Invalid Request error shape', () => {
|
|
|
58
59
|
}
|
|
59
60
|
});
|
|
60
61
|
});
|
|
62
|
+
describe('params decoding', () => {
|
|
63
|
+
it('uses an array of params as positional args', () => {
|
|
64
|
+
const result = parseRpcRequest(JSON.stringify({ jsonrpc: '2.0', method: 'api.greet', params: ['World', 42], id: 1 }));
|
|
65
|
+
assert.strictEqual(result.ok, true);
|
|
66
|
+
if (result.ok) {
|
|
67
|
+
assert.deepStrictEqual(result.request.args, ['World', 42]);
|
|
68
|
+
assert.strictEqual(result.request.apiNamespace, 'api');
|
|
69
|
+
assert.strictEqual(result.request.method, 'greet');
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
it('flattens an object of named params in key order', () => {
|
|
73
|
+
const result = parseRpcRequest(JSON.stringify({ jsonrpc: '2.0', method: 'api.greet', params: { name: 'World', times: 42 }, id: 1 }));
|
|
74
|
+
assert.strictEqual(result.ok, true);
|
|
75
|
+
if (result.ok)
|
|
76
|
+
assert.deepStrictEqual(result.request.args, ['World', 42]);
|
|
77
|
+
});
|
|
78
|
+
it('yields no args when params is omitted', () => {
|
|
79
|
+
const result = parseRpcRequest(JSON.stringify({ jsonrpc: '2.0', method: 'api.ping', id: 7 }));
|
|
80
|
+
assert.strictEqual(result.ok, true);
|
|
81
|
+
if (result.ok)
|
|
82
|
+
assert.deepStrictEqual(result.request.args, []);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('batch requests (top-level JSON array body)', () => {
|
|
86
|
+
it('rejects an array body as Invalid Request with a null id', () => {
|
|
87
|
+
const result = parseRpcRequest(JSON.stringify([
|
|
88
|
+
{ jsonrpc: '2.0', method: 'api.greet', params: ['a'], id: 1 },
|
|
89
|
+
{ jsonrpc: '2.0', method: 'api.greet', params: ['b'], id: 2 },
|
|
90
|
+
]));
|
|
91
|
+
assert.strictEqual(result.ok, false);
|
|
92
|
+
if (!result.ok) {
|
|
93
|
+
const parsed = JSON.parse(result.response);
|
|
94
|
+
assert.strictEqual(parsed.error.code, RpcErrorCode.InvalidRequest);
|
|
95
|
+
assert.strictEqual(parsed.error.data.name, 'InvalidRequest');
|
|
96
|
+
assert.strictEqual(parsed.id, null);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
it('reports a parse error for a body that is not JSON at all', () => {
|
|
100
|
+
const result = parseRpcRequest('{oops');
|
|
101
|
+
assert.strictEqual(result.ok, false);
|
|
102
|
+
if (!result.ok) {
|
|
103
|
+
const parsed = JSON.parse(result.response);
|
|
104
|
+
assert.strictEqual(parsed.error.code, RpcErrorCode.ParseError);
|
|
105
|
+
assert.strictEqual(parsed.id, null);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe('ApiError status ↔ JSON-RPC error code', () => {
|
|
110
|
+
it('encodes the HTTP status as the error code, with name and retriable in data', () => {
|
|
111
|
+
const encoded = errorResponseFromCatch(new ApiError('Username already taken', 409, { name: 'ConditionalCheckFailedException', retriable: true }), 1);
|
|
112
|
+
const parsed = JSON.parse(encoded);
|
|
113
|
+
assert.strictEqual(parsed.error.code, 409);
|
|
114
|
+
assert.strictEqual(parsed.error.message, 'Username already taken');
|
|
115
|
+
assert.strictEqual(parsed.error.data.name, 'ConditionalCheckFailedException');
|
|
116
|
+
assert.strictEqual(parsed.error.data.retriable, true);
|
|
117
|
+
});
|
|
118
|
+
it('encodes a non-ApiError throw as code 500 with no data.name', () => {
|
|
119
|
+
const parsed = JSON.parse(errorResponseFromCatch(new Error('plain'), 2));
|
|
120
|
+
assert.strictEqual(parsed.error.code, 500);
|
|
121
|
+
assert.strictEqual(parsed.error.data, undefined);
|
|
122
|
+
});
|
|
123
|
+
it('round-trips status, name and retriable back into an ApiError on the client', () => {
|
|
124
|
+
const wire = JSON.parse(errorResponseFromCatch(new ApiError('Username already taken', 409, { name: 'ConditionalCheckFailedException', retriable: true }), 1));
|
|
125
|
+
assert.throws(() => decodeRpcResponse(wire), (e) => {
|
|
126
|
+
assert.ok(e instanceof ApiError);
|
|
127
|
+
assert.strictEqual(e.status, 409);
|
|
128
|
+
assert.strictEqual(e.retriable, true);
|
|
129
|
+
assert.ok(isBlocksError(e, 'ConditionalCheckFailedException'));
|
|
130
|
+
return true;
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
it('decodes reserved -32xxx codes as status 500', () => {
|
|
134
|
+
assert.throws(() => decodeRpcResponse({ jsonrpc: '2.0', error: { code: RpcErrorCode.InvalidRequest, message: 'Invalid Request' }, id: null }), (e) => e instanceof ApiError && e.status === 500);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../src/scripts/console.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;
|
|
1
|
+
{"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../src/scripts/console.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA6BD,wBAAsB,WAAW,CAAC,OAAO,EAAE,cAAc,iBAqBxD"}
|
package/dist/scripts/console.js
CHANGED
|
@@ -3,6 +3,34 @@
|
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
4
|
import { readFileSync } from 'node:fs';
|
|
5
5
|
import { trackCommand } from '../telemetry/trackCommand.js';
|
|
6
|
+
function resolveRegion() {
|
|
7
|
+
const fromEnv = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
|
|
8
|
+
if (fromEnv)
|
|
9
|
+
return fromEnv;
|
|
10
|
+
try {
|
|
11
|
+
const fromConfig = execFileSync('aws', ['configure', 'get', 'region'], { encoding: 'utf-8' }).trim();
|
|
12
|
+
if (fromConfig)
|
|
13
|
+
return fromConfig;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// aws CLI not configured — fall through to default.
|
|
17
|
+
}
|
|
18
|
+
return 'us-east-1';
|
|
19
|
+
}
|
|
20
|
+
/** Launch the URL in the default browser. Best-effort: no opener (headless/CI) is not a failure. */
|
|
21
|
+
function openInBrowser(url) {
|
|
22
|
+
const opener = process.platform === 'darwin' ? 'open' :
|
|
23
|
+
process.platform === 'win32' ? 'cmd' :
|
|
24
|
+
'xdg-open';
|
|
25
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
26
|
+
try {
|
|
27
|
+
execFileSync(opener, args, { stdio: 'ignore' });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// Headless environment (CI, remote shell) — the URL is already printed above.
|
|
31
|
+
console.log('(Could not launch a browser automatically — open the URL above manually.)');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
6
34
|
export async function openConsole(options) {
|
|
7
35
|
return trackCommand('console', async () => {
|
|
8
36
|
let stackName;
|
|
@@ -16,10 +44,10 @@ export async function openConsole(options) {
|
|
|
16
44
|
else {
|
|
17
45
|
throw new Error('Must provide either stackId or outputsFile');
|
|
18
46
|
}
|
|
19
|
-
const region =
|
|
47
|
+
const region = resolveRegion();
|
|
20
48
|
const stackUrl = `https://${region}.console.aws.amazon.com/cloudformation/home?region=${region}#/stacks?filteringText=${encodeURIComponent(stackName)}`;
|
|
21
49
|
console.log('Opening AWS Console...');
|
|
22
50
|
console.log(stackUrl);
|
|
23
|
-
|
|
51
|
+
openInBrowser(stackUrl);
|
|
24
52
|
});
|
|
25
53
|
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/** What to do with a signal that arrives while a deploy is in flight. */
|
|
2
|
+
export type DeploySignalAction = 'defer' | 'abort';
|
|
3
|
+
export interface DeploySignalResponse {
|
|
4
|
+
action: DeploySignalAction;
|
|
5
|
+
/** Operator-facing line explaining what happened and how to force an abort. */
|
|
6
|
+
message: string;
|
|
7
|
+
/**
|
|
8
|
+
* True when this signal is a duplicate delivery of one the operator already
|
|
9
|
+
* sent (see {@link SIGNAL_COALESCE_MS}), so the caller can skip logging it
|
|
10
|
+
* twice.
|
|
11
|
+
*/
|
|
12
|
+
coalesced?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Signals whose delivery we take over while a deploy is in flight. */
|
|
15
|
+
export declare const DEPLOY_SIGNALS: readonly NodeJS.Signals[];
|
|
16
|
+
/** Default gap (ms) of silence after which the runner prints a progress heartbeat. */
|
|
17
|
+
export declare const DEFAULT_HEARTBEAT_MS = 30000;
|
|
18
|
+
/** Grace (ms) given to the child tree to exit after an operator-requested abort. */
|
|
19
|
+
export declare const ABORT_GRACE_MS = 10000;
|
|
20
|
+
/**
|
|
21
|
+
* Grace (ms) we wait after the child exits for its stdout/stderr pipes to end,
|
|
22
|
+
* so the last CloudFormation lines are relayed before we resolve. Bounded
|
|
23
|
+
* because a lingering grandchild could hold the pipe open forever; losing a
|
|
24
|
+
* trailing line is strictly better than hanging a finished deploy.
|
|
25
|
+
*/
|
|
26
|
+
export declare const STREAM_FLUSH_GRACE_MS = 2000;
|
|
27
|
+
/**
|
|
28
|
+
* Window (ms) in which repeated SIGTERMs count as ONE operator request.
|
|
29
|
+
*
|
|
30
|
+
* A single external signal reaches this process more than once. `npm run deploy`
|
|
31
|
+
* runs `npm -> sh -> tsx -> node`, so a process-group SIGTERM is delivered to
|
|
32
|
+
* the node process directly AND relayed to it a second time by `tsx`, which
|
|
33
|
+
* forwards SIGTERM/SIGINT to its child. Measured on a real deploy: one
|
|
34
|
+
* `kill -TERM -<pgid>` produced two SIGTERMs about 50ms apart. Without this
|
|
35
|
+
* window the second delivery is read as "the operator insisted" and the deploy
|
|
36
|
+
* is abandoned, which is the exact failure this module exists to prevent.
|
|
37
|
+
*
|
|
38
|
+
* 2s is far longer than a delivery burst (tens of ms) and far shorter than a
|
|
39
|
+
* deliberate repeat (a human running `kill` twice, or a supervisor's
|
|
40
|
+
* SIGTERM-then-escalate cycle), so both intents stay distinguishable.
|
|
41
|
+
*/
|
|
42
|
+
export declare const SIGNAL_COALESCE_MS = 2000;
|
|
43
|
+
/**
|
|
44
|
+
* Decide how to answer a signal that arrives while CloudFormation is still
|
|
45
|
+
* converging. This is the whole "decouple the CLI lifecycle from the in-flight
|
|
46
|
+
* deploy" policy, kept pure so it can be asserted directly.
|
|
47
|
+
*
|
|
48
|
+
* - `SIGHUP` → always `defer`. A hangup means the terminal or parent shell went
|
|
49
|
+
* away (a backgrounded `npm run deploy &`, a closed SSH session). The deploy
|
|
50
|
+
* is server-side work that is already paid for; killing the CLI here is what
|
|
51
|
+
* produced the phantom failures, so we keep streaming instead. Duplicate
|
|
52
|
+
* deliveries inside {@link SIGNAL_COALESCE_MS} are coalesced so one hangup
|
|
53
|
+
* logs one line, not one per delivery.
|
|
54
|
+
* - `SIGTERM` → `defer` the first time. A lone SIGTERM is almost always
|
|
55
|
+
* process-group collateral (a harness reaping the parent shell, a supervisor
|
|
56
|
+
* tidying up) rather than a deliberate "stop the deploy", so it only warns.
|
|
57
|
+
* Repeats inside {@link SIGNAL_COALESCE_MS} are duplicate *deliveries* of that
|
|
58
|
+
* same signal (the group delivers it, then `tsx` relays it) and are coalesced
|
|
59
|
+
* into the first. A SIGTERM after that window is a deliberate repeat and
|
|
60
|
+
* aborts. A SIGKILL follow-up (`docker stop`, most CI cancels) is uncatchable
|
|
61
|
+
* and still ends the process immediately, so this cannot wedge a shutdown.
|
|
62
|
+
* - `SIGINT` → always `abort`. Ctrl-C is unambiguous, interactive intent, so it
|
|
63
|
+
* stays responsive on the first press.
|
|
64
|
+
*
|
|
65
|
+
* @param signal - the signal received.
|
|
66
|
+
* @param msSinceFirstDeferral - ms since the first deferral of *this* signal, or
|
|
67
|
+
* `null` when this signal has not been deferred yet. Each signal is tracked
|
|
68
|
+
* separately, so a deferred SIGHUP never consumes the SIGTERM abort budget
|
|
69
|
+
* (and a repeated SIGHUP is deduped the same way a repeated SIGTERM is).
|
|
70
|
+
* @param coalesceWindowMs - see {@link SIGNAL_COALESCE_MS}.
|
|
71
|
+
*/
|
|
72
|
+
export declare function decideSignalResponse(signal: NodeJS.Signals, msSinceFirstDeferral: number | null, coalesceWindowMs?: number): DeploySignalResponse;
|
|
73
|
+
/**
|
|
74
|
+
* Split a byte stream into whole lines across chunk boundaries.
|
|
75
|
+
*
|
|
76
|
+
* The child's output arrives in arbitrary chunks, so a naive
|
|
77
|
+
* `chunk.toString().split('\n')` emits torn lines (and drops the tail). This
|
|
78
|
+
* keeps the partial trailing line buffered until it completes; {@link flush}
|
|
79
|
+
* returns whatever is left when the stream ends (CDK's final line has no
|
|
80
|
+
* trailing newline).
|
|
81
|
+
*/
|
|
82
|
+
export declare function createLineAssembler(): {
|
|
83
|
+
push(chunk: string): string[];
|
|
84
|
+
flush(): string[];
|
|
85
|
+
};
|
|
86
|
+
/** Human-readable elapsed time (`45s`, `4m 05s`) for progress lines. */
|
|
87
|
+
export declare function formatElapsed(ms: number): string;
|
|
88
|
+
export interface CdkDeployArgsOptions {
|
|
89
|
+
/** Project root passed to synth as `--context projectRoot=…`. */
|
|
90
|
+
projectRoot: string;
|
|
91
|
+
/** Path (relative to the project root) CDK writes stack outputs to. */
|
|
92
|
+
outputsFile: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Build the `cdk deploy` argv used by `npm run deploy`.
|
|
96
|
+
*
|
|
97
|
+
* Two of these flags exist purely so the deploy is observable — losing either
|
|
98
|
+
* one brings back the 0-byte stdout:
|
|
99
|
+
*
|
|
100
|
+
* - `--ci`: the CDK CLI picks its log stream as `isCI ? stdout : stderr`, so
|
|
101
|
+
* without it every CloudFormation event goes to **stderr** and a caller
|
|
102
|
+
* capturing stdout (`npm run deploy > deploy.log`) sees nothing for the whole
|
|
103
|
+
* multi-minute deploy. With it, progress goes to stdout and only error-level
|
|
104
|
+
* messages stay on stderr.
|
|
105
|
+
* - `--progress events`: print one line per resource transition instead of the
|
|
106
|
+
* redrawing progress bar. The bar needs a TTY, which a piped/backgrounded
|
|
107
|
+
* deploy does not have, and a half-rendered bar is not a usable progress
|
|
108
|
+
* signal in a log file.
|
|
109
|
+
*/
|
|
110
|
+
export declare function buildCdkDeployArgs({ projectRoot, outputsFile }: CdkDeployArgsOptions): string[];
|
|
111
|
+
/** Minimal sink surface so tests can capture the relayed streams. */
|
|
112
|
+
export interface OutputSink {
|
|
113
|
+
write(chunk: string): unknown;
|
|
114
|
+
}
|
|
115
|
+
/** Minimal signal-registration surface ({@link process} satisfies it). */
|
|
116
|
+
export interface SignalRegistry {
|
|
117
|
+
on(signal: NodeJS.Signals, handler: () => void): unknown;
|
|
118
|
+
off(signal: NodeJS.Signals, handler: () => void): unknown;
|
|
119
|
+
}
|
|
120
|
+
export interface RunStreamingOptions {
|
|
121
|
+
cwd?: string;
|
|
122
|
+
env?: NodeJS.ProcessEnv;
|
|
123
|
+
/** Prefix used by the runner's own progress/status lines. Defaults to `deploy`. */
|
|
124
|
+
label?: string;
|
|
125
|
+
/** Idle gap before a heartbeat line is printed. `0` disables heartbeats. */
|
|
126
|
+
heartbeatMs?: number;
|
|
127
|
+
/** Where child stdout and runner progress lines go. Defaults to `process.stdout`. */
|
|
128
|
+
stdout?: OutputSink;
|
|
129
|
+
/** Where child stderr goes. Defaults to `process.stderr`. */
|
|
130
|
+
stderr?: OutputSink;
|
|
131
|
+
/** Injected for tests. */
|
|
132
|
+
now?: () => number;
|
|
133
|
+
/** Injected for tests: signal registration seam. */
|
|
134
|
+
signalTarget?: SignalRegistry;
|
|
135
|
+
}
|
|
136
|
+
/** Raised when the child exits non-zero, is killed, or the operator aborts. */
|
|
137
|
+
export declare class DeployProcessError extends Error {
|
|
138
|
+
readonly exitCode: number | null;
|
|
139
|
+
readonly signal: NodeJS.Signals | null;
|
|
140
|
+
readonly aborted: boolean;
|
|
141
|
+
constructor(message: string, details?: {
|
|
142
|
+
exitCode?: number | null;
|
|
143
|
+
signal?: NodeJS.Signals | null;
|
|
144
|
+
aborted?: boolean;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Run a long deployment command, relaying its output line by line as it happens
|
|
149
|
+
* and keeping it alive across a stray SIGTERM/SIGHUP.
|
|
150
|
+
*
|
|
151
|
+
* Behaviour that matters to callers:
|
|
152
|
+
* - **Streamed, non-empty stdout.** Child stdout is relayed to `stdout` the
|
|
153
|
+
* moment a line completes (never buffered until exit) and child stderr to
|
|
154
|
+
* `stderr`, so `npm run deploy | tee` shows CloudFormation progress live.
|
|
155
|
+
* - **Idle heartbeat.** While the child is silent for `heartbeatMs`, a
|
|
156
|
+
* `still deploying` line with elapsed time is written to `stdout`, so a
|
|
157
|
+
* ten-minute RDS resource never looks like a hung process.
|
|
158
|
+
* - **Own process group (POSIX only).** The child is spawned `detached` on
|
|
159
|
+
* POSIX, so a process-group signal aimed at the parent shell
|
|
160
|
+
* (`kill -TERM -pgid`, a harness reaping a backgrounded job) cannot kill the
|
|
161
|
+
* CDK CLI behind our back; this runner is the only thing that signals it.
|
|
162
|
+
* Windows has neither process groups nor OS-delivered SIGTERM/SIGHUP, so the
|
|
163
|
+
* signal resilience below does not apply there — a `taskkill` on the tree ends
|
|
164
|
+
* the deploy, and the abort path reaps by pid via `terminateProcessTree`.
|
|
165
|
+
* - **No stdin.** The child gets `ignore` for stdin so a backgrounded deploy
|
|
166
|
+
* can never be stopped by SIGTTIN trying to read a terminal it no longer
|
|
167
|
+
* owns; the caller must keep passing `--require-approval never`.
|
|
168
|
+
* - **Signal policy (POSIX).** See {@link decideSignalResponse}: a deferred
|
|
169
|
+
* signal only logs (which itself doubles as a progress signal on stdout),
|
|
170
|
+
* while an abort reaps the child tree and throws a {@link DeployProcessError}
|
|
171
|
+
* with `aborted: true`. Repeat deliveries of the same signal inside
|
|
172
|
+
* {@link SIGNAL_COALESCE_MS} log once, not once per delivery.
|
|
173
|
+
* - **Streams stay separated.** Child stdout and child stderr are relayed to
|
|
174
|
+
* their own sinks and never merged, so a deploy failure reason (which the CDK
|
|
175
|
+
* CLI keeps on stderr even under `--ci`) stays on stderr while progress is on
|
|
176
|
+
* stdout.
|
|
177
|
+
*
|
|
178
|
+
* Resolves when the child exits 0; otherwise throws {@link DeployProcessError}.
|
|
179
|
+
*/
|
|
180
|
+
export declare function runStreaming(command: string, args: string[], options?: RunStreamingOptions): Promise<void>;
|
|
181
|
+
//# sourceMappingURL=deploy-stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy-stream.d.ts","sourceRoot":"","sources":["../../src/scripts/deploy-stream.ts"],"names":[],"mappings":"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"}
|