cdk-local 0.97.1 → 0.99.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal.d.ts +29 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -2
- package/dist/{local-studio-D2dRUwKK.d.ts → local-studio-BGHLCa1m.d.ts} +13 -2
- package/dist/{local-studio-D2dRUwKK.d.ts.map → local-studio-BGHLCa1m.d.ts.map} +1 -1
- package/dist/{local-studio-DFo7-QhO.js → local-studio-Cm3Qt0MG.js} +368 -4
- package/dist/local-studio-Cm3Qt0MG.js.map +1 -0
- package/package.json +1 -1
- package/dist/local-studio-DFo7-QhO.js.map +0 -1
|
@@ -28525,6 +28525,21 @@ const STUDIO_CSS = `
|
|
|
28525
28525
|
border: 0; border-radius: 3px; cursor: pointer; font: inherit; font-weight: 700;
|
|
28526
28526
|
}
|
|
28527
28527
|
.composer button:hover { background: #339152; }
|
|
28528
|
+
.req-composer .req-row { display: flex; gap: 6px; margin-bottom: 6px; }
|
|
28529
|
+
.req-composer .req-method { background: #111; color: #ddd; border: 1px solid #333; border-radius: 3px;
|
|
28530
|
+
padding: 4px 6px; font: 12px ui-monospace, Menlo, monospace; }
|
|
28531
|
+
.req-composer .req-path { flex: 1; min-width: 0; background: #111; color: #ddd; border: 1px solid #333;
|
|
28532
|
+
border-radius: 3px; padding: 4px 6px; font: 12px ui-monospace, Menlo, monospace; }
|
|
28533
|
+
.req-composer textarea { width: 100%; box-sizing: border-box; min-height: 48px; resize: vertical;
|
|
28534
|
+
background: #111; color: #ddd; border: 1px solid #333; border-radius: 3px; padding: 5px;
|
|
28535
|
+
font: 12px/1.5 ui-monospace, Menlo, monospace; margin-bottom: 6px; }
|
|
28536
|
+
.req-composer .req-body { min-height: 70px; }
|
|
28537
|
+
.req-composer select:focus, .req-composer input:focus, .req-composer textarea:focus {
|
|
28538
|
+
outline: none; border-color: #4ec97a; }
|
|
28539
|
+
.req-composer .req-send { display: flex; align-items: center; gap: 10px; }
|
|
28540
|
+
.req-composer .req-send button { margin-top: 0; padding: 4px 16px; }
|
|
28541
|
+
.req-composer .req-status { margin-top: 8px; font: 12px ui-monospace, Menlo, monospace; }
|
|
28542
|
+
.req-composer .req-result pre { background: #0e0e0e; }
|
|
28528
28543
|
.composer button:disabled { background: #333; color: #888; cursor: default; }
|
|
28529
28544
|
.composer .err { color: #e0707a; margin-top: 6px; min-height: 18px; }
|
|
28530
28545
|
.section { padding: 8px 12px; border-bottom: 1px solid #222; }
|
|
@@ -29143,6 +29158,12 @@ const STUDIO_SCRIPT = `
|
|
|
29143
29158
|
const link = href(url);
|
|
29144
29159
|
epSec.appendChild(link);
|
|
29145
29160
|
}
|
|
29161
|
+
} else if (running && isEcs && st.hostUrl) {
|
|
29162
|
+
// An ecs service published via --host-port IS reachable on the host
|
|
29163
|
+
// (issue #322); show its host URL. No proxy fronts it, so requests are
|
|
29164
|
+
// not captured on the timeline.
|
|
29165
|
+
epSec.appendChild(href(st.hostUrl));
|
|
29166
|
+
epSec.appendChild(el('div', 'opt-hint', '(direct host port — not captured on the timeline)'));
|
|
29146
29167
|
} else if (running && isEcs) {
|
|
29147
29168
|
// A pure-compute ECS service has no host endpoint — it just runs the
|
|
29148
29169
|
// replicas (reach them container-to-container via Cloud Map).
|
|
@@ -29152,6 +29173,18 @@ const STUDIO_SCRIPT = `
|
|
|
29152
29173
|
}
|
|
29153
29174
|
ws.appendChild(epSec);
|
|
29154
29175
|
|
|
29176
|
+
// In-workspace HTTP request composer for a running api / alb (or ecs with
|
|
29177
|
+
// --host-port) serve (issue #322): compose a request and Send it; studio
|
|
29178
|
+
// relays it server-side (same-origin) so it works cross-port and, for
|
|
29179
|
+
// api / alb, lands on the timeline via the capture proxy.
|
|
29180
|
+
const httpBase = running
|
|
29181
|
+
? (st.endpoints || []).find((u) => /^https?:/.test(u)) || (isEcs ? st.hostUrl : null)
|
|
29182
|
+
: null;
|
|
29183
|
+
if (httpBase) {
|
|
29184
|
+
const captured = (st.endpoints || []).indexOf(httpBase) >= 0;
|
|
29185
|
+
ws.appendChild(renderRequestComposer(id, httpBase, captured));
|
|
29186
|
+
}
|
|
29187
|
+
|
|
29155
29188
|
// A served WebSocket API exposes a ws:// endpoint — attach a WebSocket
|
|
29156
29189
|
// console so the browser can connect + exchange frames (issue #303).
|
|
29157
29190
|
const wsEndpoint = running ? (st.endpoints || []).find((u) => /^wss?:/.test(u)) : null;
|
|
@@ -29166,6 +29199,111 @@ const STUDIO_SCRIPT = `
|
|
|
29166
29199
|
ws.appendChild(logSec);
|
|
29167
29200
|
}
|
|
29168
29201
|
|
|
29202
|
+
// In-workspace HTTP request composer for a running serve (issue #322):
|
|
29203
|
+
// Method + Path + Headers + Body -> Send. The request is relayed by studio's
|
|
29204
|
+
// OWN server (POST /api/request) so it reaches the served port without a
|
|
29205
|
+
// cross-origin fetch; for an api / alb serve it flows through the capture
|
|
29206
|
+
// proxy and lands on the timeline.
|
|
29207
|
+
const REQ_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
|
|
29208
|
+
|
|
29209
|
+
function parseHeaderLines(text) {
|
|
29210
|
+
const out = {};
|
|
29211
|
+
String(text || '').split('\\n').forEach(function (line) {
|
|
29212
|
+
const i = line.indexOf(':');
|
|
29213
|
+
if (i <= 0) return;
|
|
29214
|
+
const k = line.slice(0, i).trim();
|
|
29215
|
+
if (k) out[k] = line.slice(i + 1).trim();
|
|
29216
|
+
});
|
|
29217
|
+
return out;
|
|
29218
|
+
}
|
|
29219
|
+
|
|
29220
|
+
function renderRequestComposer(id, baseUrl, captured) {
|
|
29221
|
+
const sec = el('div', 'section req-composer');
|
|
29222
|
+
sec.appendChild(el('h3', null, 'Request'));
|
|
29223
|
+
const row = el('div', 'req-row');
|
|
29224
|
+
const method = el('select', 'req-method');
|
|
29225
|
+
REQ_METHODS.forEach(function (m) {
|
|
29226
|
+
const o = el('option', null, m);
|
|
29227
|
+
o.value = m;
|
|
29228
|
+
method.appendChild(o);
|
|
29229
|
+
});
|
|
29230
|
+
row.appendChild(method);
|
|
29231
|
+
const path = el('input', 'req-path');
|
|
29232
|
+
path.value = '/';
|
|
29233
|
+
path.placeholder = '/path?query';
|
|
29234
|
+
row.appendChild(path);
|
|
29235
|
+
sec.appendChild(row);
|
|
29236
|
+
|
|
29237
|
+
sec.appendChild(el('div', 'opt-label', 'Headers (one per line: Name: value)'));
|
|
29238
|
+
const headers = el('textarea', 'req-headers');
|
|
29239
|
+
headers.placeholder = 'Authorization: Bearer demo';
|
|
29240
|
+
headers.spellcheck = false;
|
|
29241
|
+
sec.appendChild(headers);
|
|
29242
|
+
|
|
29243
|
+
sec.appendChild(el('div', 'opt-label', 'Body'));
|
|
29244
|
+
const bodyTa = el('textarea', 'req-body');
|
|
29245
|
+
bodyTa.placeholder = '{ }';
|
|
29246
|
+
bodyTa.spellcheck = false;
|
|
29247
|
+
sec.appendChild(bodyTa);
|
|
29248
|
+
|
|
29249
|
+
const sendRow = el('div', 'req-send');
|
|
29250
|
+
const btn = el('button', null, 'Send');
|
|
29251
|
+
sendRow.appendChild(btn);
|
|
29252
|
+
sendRow.appendChild(
|
|
29253
|
+
el('span', 'opt-hint', captured
|
|
29254
|
+
? 'Relayed via studio to ' + baseUrl + ' — captured on the timeline.'
|
|
29255
|
+
: 'Relayed direct to ' + baseUrl + ' (ecs host port) — not captured.')
|
|
29256
|
+
);
|
|
29257
|
+
sec.appendChild(sendRow);
|
|
29258
|
+
const msg = el('div', 'err');
|
|
29259
|
+
sec.appendChild(msg);
|
|
29260
|
+
const result = el('div', 'req-result');
|
|
29261
|
+
sec.appendChild(result);
|
|
29262
|
+
|
|
29263
|
+
btn.onclick = async function () {
|
|
29264
|
+
msg.textContent = '';
|
|
29265
|
+
btn.disabled = true;
|
|
29266
|
+
btn.textContent = 'Sending…';
|
|
29267
|
+
result.innerHTML = '';
|
|
29268
|
+
try {
|
|
29269
|
+
const payload = {
|
|
29270
|
+
targetId: id,
|
|
29271
|
+
method: method.value,
|
|
29272
|
+
path: path.value || '/',
|
|
29273
|
+
headers: parseHeaderLines(headers.value),
|
|
29274
|
+
};
|
|
29275
|
+
if (bodyTa.value !== '') payload.body = bodyTa.value;
|
|
29276
|
+
const res = await fetch('/api/request', {
|
|
29277
|
+
method: 'POST',
|
|
29278
|
+
headers: { 'content-type': 'application/json' },
|
|
29279
|
+
body: JSON.stringify(payload),
|
|
29280
|
+
});
|
|
29281
|
+
const data = await res.json();
|
|
29282
|
+
if (!res.ok) {
|
|
29283
|
+
msg.textContent = 'Request failed: ' + (data.error || ('HTTP ' + res.status));
|
|
29284
|
+
return;
|
|
29285
|
+
}
|
|
29286
|
+
const statusLine = el('div', 'req-status');
|
|
29287
|
+
const cls = data.status >= 200 && data.status < 300 ? 'ok' : 'bad';
|
|
29288
|
+
statusLine.appendChild(
|
|
29289
|
+
el('span', cls, data.status + (data.durationMs != null ? ' · ' + data.durationMs + 'ms' : ''))
|
|
29290
|
+
);
|
|
29291
|
+
result.appendChild(statusLine);
|
|
29292
|
+
const hdrs = Object.keys(data.headers || {})
|
|
29293
|
+
.map(function (k) { return k + ': ' + data.headers[k]; })
|
|
29294
|
+
.join('\\n');
|
|
29295
|
+
if (hdrs) result.appendChild(el('pre', 'req-resp-headers', hdrs));
|
|
29296
|
+
result.appendChild(el('pre', null, data.body != null ? data.body : ''));
|
|
29297
|
+
} catch (err) {
|
|
29298
|
+
msg.textContent = 'Request failed: ' + err;
|
|
29299
|
+
} finally {
|
|
29300
|
+
btn.disabled = false;
|
|
29301
|
+
btn.textContent = 'Send';
|
|
29302
|
+
}
|
|
29303
|
+
};
|
|
29304
|
+
return sec;
|
|
29305
|
+
}
|
|
29306
|
+
|
|
29169
29307
|
// Build an <a> that opens an http(s) endpoint in a new tab; ws:// URLs
|
|
29170
29308
|
// are shown as plain text (not navigable in a browser tab — the WebSocket
|
|
29171
29309
|
// console below connects to them instead).
|
|
@@ -29601,7 +29739,7 @@ const STUDIO_SCRIPT = `
|
|
|
29601
29739
|
if (ev.status === 'stopped' || ev.status === 'error') {
|
|
29602
29740
|
serveState.set(ev.target, { status: ev.status, endpoints: [] });
|
|
29603
29741
|
} else {
|
|
29604
|
-
serveState.set(ev.target, { status: ev.status, endpoints: ev.endpoints || [] });
|
|
29742
|
+
serveState.set(ev.target, { status: ev.status, endpoints: ev.endpoints || [], hostUrl: ev.hostUrl });
|
|
29605
29743
|
}
|
|
29606
29744
|
updateServeRow(ev.target);
|
|
29607
29745
|
}
|
|
@@ -29969,6 +30107,10 @@ function handleRequest(req, res, bus, html, targetsJson, options) {
|
|
|
29969
30107
|
handleDispatch(req, res, options.onRun);
|
|
29970
30108
|
return;
|
|
29971
30109
|
}
|
|
30110
|
+
if (req.method === "POST" && path === "/api/request") {
|
|
30111
|
+
handleDispatch(req, res, options.onServeRequest);
|
|
30112
|
+
return;
|
|
30113
|
+
}
|
|
29972
30114
|
if (req.method === "POST" && path === "/api/stop") {
|
|
29973
30115
|
handleDispatch(req, res, options.onStop);
|
|
29974
30116
|
return;
|
|
@@ -30111,6 +30253,88 @@ function listenWithBump(server, host, port, maxBump) {
|
|
|
30111
30253
|
});
|
|
30112
30254
|
}
|
|
30113
30255
|
|
|
30256
|
+
//#endregion
|
|
30257
|
+
//#region src/local/studio-custom-resource-filter.ts
|
|
30258
|
+
/**
|
|
30259
|
+
* Substrings / patterns that identify a Lambda as part of CDK's custom-resource
|
|
30260
|
+
* or provider-framework plumbing rather than the user's own application code.
|
|
30261
|
+
*
|
|
30262
|
+
* Matched case-INSENSITIVELY against a target's display path / qualified id.
|
|
30263
|
+
* Each entry corresponds to a well-known CDK-generated construct:
|
|
30264
|
+
*
|
|
30265
|
+
* - `framework-onEvent` / `framework-onTimeout` / `framework-isComplete` —
|
|
30266
|
+
* the provider-framework (`@aws-cdk/custom-resources` `Provider`) handlers.
|
|
30267
|
+
* - `/Provider/` — the `Provider` construct segment that wraps a custom
|
|
30268
|
+
* resource's lifecycle Lambdas.
|
|
30269
|
+
* - `LogRetention` — the singleton log-retention custom resource CDK injects
|
|
30270
|
+
* when a construct sets `logRetention`.
|
|
30271
|
+
* - `BucketNotificationsHandler` — the S3 bucket-notifications custom resource.
|
|
30272
|
+
* - `AwsCustomResource` — the `aws-custom-resource` SDK-call construct.
|
|
30273
|
+
* - `AWS679f53fac002430cb0da5b7982bd2287` — the singleton logical id CDK emits
|
|
30274
|
+
* for the `AwsCustomResource` provider Lambda.
|
|
30275
|
+
* - `CustomResourceProvider` — the low-level `CustomResourceProvider` framework
|
|
30276
|
+
* Lambda (used by many L2 constructs).
|
|
30277
|
+
* - `cdkbucketdeployment` — the `BucketDeployment` asset-copy Lambda (the
|
|
30278
|
+
* logical id is `CustomCDKBucketDeployment<hash>`, the construct path node is
|
|
30279
|
+
* `Custom::CDKBucketDeployment<hash>` — the shared substring covers both).
|
|
30280
|
+
* - `CDKMetadata` — the CDK metadata resource (not a Lambda, but cheap to
|
|
30281
|
+
* exclude defensively if it ever surfaces as one).
|
|
30282
|
+
*/
|
|
30283
|
+
const CUSTOM_RESOURCE_PATTERNS = [
|
|
30284
|
+
"framework-onevent",
|
|
30285
|
+
"framework-ontimeout",
|
|
30286
|
+
"framework-iscomplete",
|
|
30287
|
+
"/provider/",
|
|
30288
|
+
"logretention",
|
|
30289
|
+
"bucketnotificationshandler",
|
|
30290
|
+
"awscustomresource",
|
|
30291
|
+
"aws679f53fac002430cb0da5b7982bd2287",
|
|
30292
|
+
"customresourceprovider",
|
|
30293
|
+
"cdkbucketdeployment",
|
|
30294
|
+
"cdkmetadata"
|
|
30295
|
+
];
|
|
30296
|
+
/**
|
|
30297
|
+
* Classify a studio Lambda target as a CDK custom-resource / provider-framework
|
|
30298
|
+
* Lambda (vs the user's own application code). Matches the target's display
|
|
30299
|
+
* path / qualified id against {@link CUSTOM_RESOURCE_PATTERNS}
|
|
30300
|
+
* case-insensitively.
|
|
30301
|
+
*
|
|
30302
|
+
* Host-side use case: a host CLI building its own studio (or `cdkl list`-style
|
|
30303
|
+
* UI) reuses this to hide CDK-generated plumbing Lambdas — provider-framework
|
|
30304
|
+
* onEvent/onTimeout/isComplete handlers, log-retention, bucket-notifications,
|
|
30305
|
+
* AwsCustomResource, BucketDeployment — from the default target list, so the
|
|
30306
|
+
* user sees only their own functions. Pure / side-effect-free.
|
|
30307
|
+
*/
|
|
30308
|
+
function isCustomResourceLambdaTarget(entry) {
|
|
30309
|
+
const haystack = `${entry.id} ${entry.qualifiedId}`.toLowerCase();
|
|
30310
|
+
return CUSTOM_RESOURCE_PATTERNS.some((p) => haystack.includes(p));
|
|
30311
|
+
}
|
|
30312
|
+
/**
|
|
30313
|
+
* Drop CDK custom-resource / provider-framework Lambdas from the `lambda` group
|
|
30314
|
+
* of `groups` (issue #323). Only the `lambda` group is touched — every other
|
|
30315
|
+
* group (api / ecs / alb / agentcore) is returned unchanged — and within it
|
|
30316
|
+
* only entries {@link isCustomResourceLambdaTarget} matches are removed.
|
|
30317
|
+
*
|
|
30318
|
+
* Pass `{ include: true }` to keep them (the `--include-custom-resources`
|
|
30319
|
+
* opt-in), in which case `groups` is returned unchanged.
|
|
30320
|
+
*
|
|
30321
|
+
* Host-side use case: `cdkl studio` (and any host CLI embedding the studio
|
|
30322
|
+
* building blocks) applies this after the `--stack` display filter so the
|
|
30323
|
+
* default target list shows only the user's own Lambdas, not CDK's generated
|
|
30324
|
+
* plumbing. Returns a new array (the matching group is rebuilt); inputs are not
|
|
30325
|
+
* mutated.
|
|
30326
|
+
*/
|
|
30327
|
+
function filterStudioCustomResources(groups, opts = {}) {
|
|
30328
|
+
if (opts.include) return groups;
|
|
30329
|
+
return groups.map((g) => {
|
|
30330
|
+
if (g.kind !== "lambda") return g;
|
|
30331
|
+
return {
|
|
30332
|
+
...g,
|
|
30333
|
+
entries: g.entries.filter((e) => !isCustomResourceLambdaTarget(e))
|
|
30334
|
+
};
|
|
30335
|
+
});
|
|
30336
|
+
}
|
|
30337
|
+
|
|
30114
30338
|
//#endregion
|
|
30115
30339
|
//#region src/local/studio-child-args.ts
|
|
30116
30340
|
/**
|
|
@@ -30417,6 +30641,62 @@ function tryParseJson(raw) {
|
|
|
30417
30641
|
}
|
|
30418
30642
|
}
|
|
30419
30643
|
|
|
30644
|
+
//#endregion
|
|
30645
|
+
//#region src/local/studio-request-relay.ts
|
|
30646
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
30647
|
+
const DEFAULT_MAX_BODY = 512 * 1024;
|
|
30648
|
+
const BODY_METHODS = new Set([
|
|
30649
|
+
"POST",
|
|
30650
|
+
"PUT",
|
|
30651
|
+
"PATCH",
|
|
30652
|
+
"DELETE"
|
|
30653
|
+
]);
|
|
30654
|
+
/** Join a base URL and a path without doubling or dropping the `/`. */
|
|
30655
|
+
function joinUrl(baseUrl, path) {
|
|
30656
|
+
const base = baseUrl.replace(/\/+$/, "");
|
|
30657
|
+
if (!path || path === "") return base + "/";
|
|
30658
|
+
return base + (path.startsWith("/") ? path : "/" + path);
|
|
30659
|
+
}
|
|
30660
|
+
/**
|
|
30661
|
+
* Perform one HTTP request against a running serve and return a bounded
|
|
30662
|
+
* response. `fetchFn` is injectable for tests (defaults to the global `fetch`).
|
|
30663
|
+
* Throws on a network / timeout error (the caller maps it to a 502/500); an
|
|
30664
|
+
* HTTP error status is a NORMAL result (returned, not thrown).
|
|
30665
|
+
*/
|
|
30666
|
+
async function relayServeRequest(input, fetchFn = fetch, clock = Date.now) {
|
|
30667
|
+
const method = input.method.toUpperCase();
|
|
30668
|
+
const url = joinUrl(input.baseUrl, input.path);
|
|
30669
|
+
const maxBody = input.maxBodyChars ?? DEFAULT_MAX_BODY;
|
|
30670
|
+
const controller = new AbortController();
|
|
30671
|
+
const timer = setTimeout(() => controller.abort(), input.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
30672
|
+
const startedAt = clock();
|
|
30673
|
+
try {
|
|
30674
|
+
const res = await fetchFn(url, {
|
|
30675
|
+
method,
|
|
30676
|
+
headers: input.headers ?? {},
|
|
30677
|
+
...BODY_METHODS.has(method) && input.body !== void 0 ? { body: input.body } : {},
|
|
30678
|
+
signal: controller.signal,
|
|
30679
|
+
redirect: "manual"
|
|
30680
|
+
});
|
|
30681
|
+
const headers = {};
|
|
30682
|
+
res.headers.forEach((value, key) => {
|
|
30683
|
+
headers[key] = value;
|
|
30684
|
+
});
|
|
30685
|
+
const full = await res.text();
|
|
30686
|
+
const truncated = full.length > maxBody;
|
|
30687
|
+
const body = truncated ? full.slice(0, maxBody) + "\n…[truncated]" : full;
|
|
30688
|
+
return {
|
|
30689
|
+
status: res.status,
|
|
30690
|
+
headers,
|
|
30691
|
+
body,
|
|
30692
|
+
truncated,
|
|
30693
|
+
durationMs: clock() - startedAt
|
|
30694
|
+
};
|
|
30695
|
+
} finally {
|
|
30696
|
+
clearTimeout(timer);
|
|
30697
|
+
}
|
|
30698
|
+
}
|
|
30699
|
+
|
|
30420
30700
|
//#endregion
|
|
30421
30701
|
//#region src/local/studio-proxy.ts
|
|
30422
30702
|
let proxyIdCounter = 0;
|
|
@@ -30695,6 +30975,7 @@ function createStudioServeManager(config) {
|
|
|
30695
30975
|
startedAt: e.startedAt
|
|
30696
30976
|
};
|
|
30697
30977
|
if (e.pid !== void 0) s.pid = e.pid;
|
|
30978
|
+
if (e.hostUrl !== void 0) s.hostUrl = e.hostUrl;
|
|
30698
30979
|
return s;
|
|
30699
30980
|
}
|
|
30700
30981
|
function emitServe(e, message) {
|
|
@@ -30706,6 +30987,7 @@ function createStudioServeManager(config) {
|
|
|
30706
30987
|
endpoints: [...e.endpoints]
|
|
30707
30988
|
};
|
|
30708
30989
|
if (e.pid !== void 0) ev.pid = e.pid;
|
|
30990
|
+
if (e.hostUrl !== void 0) ev.hostUrl = e.hostUrl;
|
|
30709
30991
|
if (message !== void 0) ev.message = message;
|
|
30710
30992
|
config.bus.emit("serve", ev);
|
|
30711
30993
|
}
|
|
@@ -30744,6 +31026,13 @@ function createStudioServeManager(config) {
|
|
|
30744
31026
|
proxies: []
|
|
30745
31027
|
};
|
|
30746
31028
|
if (child.pid !== void 0) entry.pid = child.pid;
|
|
31029
|
+
if (req.kind === "ecs") {
|
|
31030
|
+
const hp = req.options?.["--host-port"];
|
|
31031
|
+
if (Array.isArray(hp)) {
|
|
31032
|
+
const first = hp.find((r) => r && typeof r === "object" && typeof r.right === "string" && r.right.trim() !== "");
|
|
31033
|
+
if (first) entry.hostUrl = "http://127.0.0.1:" + first.right.trim();
|
|
31034
|
+
}
|
|
31035
|
+
}
|
|
30747
31036
|
entries.set(req.targetId, entry);
|
|
30748
31037
|
emitServe(entry);
|
|
30749
31038
|
return new Promise((resolve, reject) => {
|
|
@@ -30981,6 +31270,60 @@ function coerceStopRequest(body) {
|
|
|
30981
31270
|
if (typeof targetId !== "string" || targetId.trim() === "") throw new Error("Request body must include a non-empty \"targetId\" string.");
|
|
30982
31271
|
return { targetId };
|
|
30983
31272
|
}
|
|
31273
|
+
const SERVE_REQUEST_METHODS = new Set([
|
|
31274
|
+
"GET",
|
|
31275
|
+
"POST",
|
|
31276
|
+
"PUT",
|
|
31277
|
+
"PATCH",
|
|
31278
|
+
"DELETE",
|
|
31279
|
+
"HEAD",
|
|
31280
|
+
"OPTIONS"
|
|
31281
|
+
]);
|
|
31282
|
+
/**
|
|
31283
|
+
* Pick the HTTP base URL the request composer relays to for a running serve
|
|
31284
|
+
* (issue #322): the first `http(s)://` endpoint (the api / alb capture-proxy
|
|
31285
|
+
* URL — a `ws://` WebSocket-API endpoint is NOT relayable, so it is skipped),
|
|
31286
|
+
* else the ecs `--host-port` host URL, else `undefined` (no reachable HTTP
|
|
31287
|
+
* endpoint). Exported so the relay base-URL choice is unit-testable.
|
|
31288
|
+
*/
|
|
31289
|
+
function resolveServeBaseUrl(state) {
|
|
31290
|
+
return (state.endpoints || []).find((u) => /^https?:/.test(u)) ?? state.hostUrl;
|
|
31291
|
+
}
|
|
31292
|
+
/**
|
|
31293
|
+
* Validate + narrow the untyped `POST /api/request` body (issue #322). Throws
|
|
31294
|
+
* on a malformed body; the studio server surfaces a thrown handler error as a
|
|
31295
|
+
* 500 (the same convention as {@link coerceRunRequest} / {@link
|
|
31296
|
+
* coerceStopRequest}) so a bad UI / curl payload fails loudly rather than
|
|
31297
|
+
* relaying a bogus request.
|
|
31298
|
+
*/
|
|
31299
|
+
function coerceServeRequest(body) {
|
|
31300
|
+
if (typeof body !== "object" || body === null) throw new Error("Request body must be a JSON object.");
|
|
31301
|
+
const { targetId, method, path, headers, body: reqBody } = body;
|
|
31302
|
+
if (typeof targetId !== "string" || targetId.trim() === "") throw new Error("Request body must include a non-empty \"targetId\" string.");
|
|
31303
|
+
if (typeof method !== "string" || !SERVE_REQUEST_METHODS.has(method.toUpperCase())) throw new Error(`Request body "method" must be one of: ${[...SERVE_REQUEST_METHODS].join(", ")}.`);
|
|
31304
|
+
const out = {
|
|
31305
|
+
targetId,
|
|
31306
|
+
method: method.toUpperCase()
|
|
31307
|
+
};
|
|
31308
|
+
if (path !== void 0) {
|
|
31309
|
+
if (typeof path !== "string") throw new Error("Request body \"path\" must be a string.");
|
|
31310
|
+
out.path = path;
|
|
31311
|
+
}
|
|
31312
|
+
if (headers !== void 0) {
|
|
31313
|
+
if (typeof headers !== "object" || headers === null || Array.isArray(headers)) throw new Error("Request body \"headers\" must be a JSON object of string values.");
|
|
31314
|
+
const h = {};
|
|
31315
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
31316
|
+
if (typeof v !== "string") throw new Error(`Request header "${k}" must be a string.`);
|
|
31317
|
+
if (k.trim() !== "") h[k] = v;
|
|
31318
|
+
}
|
|
31319
|
+
out.headers = h;
|
|
31320
|
+
}
|
|
31321
|
+
if (reqBody !== void 0) {
|
|
31322
|
+
if (typeof reqBody !== "string") throw new Error("Request body \"body\" must be a string.");
|
|
31323
|
+
out.body = reqBody;
|
|
31324
|
+
}
|
|
31325
|
+
return out;
|
|
31326
|
+
}
|
|
30984
31327
|
/**
|
|
30985
31328
|
* Validate a `PATCH /api/config` body and apply the editable run-time
|
|
30986
31329
|
* bindings (`fromCfnStack` / `assumeRole`) onto `target` in place. Only the
|
|
@@ -31078,7 +31421,13 @@ async function localStudioCommand(options) {
|
|
|
31078
31421
|
};
|
|
31079
31422
|
const { stacks } = await synthesizer.synthesize(synthOpts);
|
|
31080
31423
|
const assemblyDir = resolveBootAssemblyDir(appCmd, options.output);
|
|
31081
|
-
const
|
|
31424
|
+
const stackFiltered = filterStudioTargetGroups(toStudioTargetGroups(listTargets(stacks)), options.stack);
|
|
31425
|
+
const lambdasBefore = stackFiltered.find((g) => g.kind === "lambda")?.entries.length ?? 0;
|
|
31426
|
+
const targetGroups = filterStudioCustomResources(stackFiltered, { include: options.includeCustomResources === true });
|
|
31427
|
+
if (!options.includeCustomResources) {
|
|
31428
|
+
const hidden = lambdasBefore - (targetGroups.find((g) => g.kind === "lambda")?.entries.length ?? 0);
|
|
31429
|
+
if (hidden > 0) logger.info(`Hid ${hidden} CDK custom-resource / provider Lambda(s); pass --include-custom-resources to show them.`);
|
|
31430
|
+
}
|
|
31082
31431
|
if (options.stack && options.stack.length > 0) {
|
|
31083
31432
|
const shown = targetGroups.reduce((n, g) => n + g.entries.length, 0);
|
|
31084
31433
|
if (shown === 0) logger.warn(`--stack ${options.stack.join(" ")} matched no targets; the UI list is empty.`);
|
|
@@ -31140,6 +31489,20 @@ async function localStudioCommand(options) {
|
|
|
31140
31489
|
await serveManager.stop(req);
|
|
31141
31490
|
return { stopped: req.targetId };
|
|
31142
31491
|
},
|
|
31492
|
+
onServeRequest: async (body) => {
|
|
31493
|
+
const req = coerceServeRequest(body);
|
|
31494
|
+
const state = serveManager.list().find((s) => s.targetId === req.targetId);
|
|
31495
|
+
if (!state || state.status !== "running") throw new Error(`'${req.targetId}' is not a running serve target.`);
|
|
31496
|
+
const baseUrl = resolveServeBaseUrl(state);
|
|
31497
|
+
if (!baseUrl) throw new Error(`'${req.targetId}' has no reachable HTTP endpoint (an ecs service needs --host-port).`);
|
|
31498
|
+
return await relayServeRequest({
|
|
31499
|
+
baseUrl,
|
|
31500
|
+
method: req.method,
|
|
31501
|
+
...req.path !== void 0 ? { path: req.path } : {},
|
|
31502
|
+
...req.headers !== void 0 ? { headers: req.headers } : {},
|
|
31503
|
+
...req.body !== void 0 ? { body: req.body } : {}
|
|
31504
|
+
});
|
|
31505
|
+
},
|
|
31143
31506
|
getRunning: () => ({ running: serveManager.list() }),
|
|
31144
31507
|
getConfig: () => sessionConfigSnapshot(),
|
|
31145
31508
|
patchConfig: (body) => {
|
|
@@ -31217,9 +31580,10 @@ function addStudioSpecificOptions(cmd) {
|
|
|
31217
31580
|
cmd.addOption(new Option("--assume-role <arn>", "IAM role ARN to assume for every invoke / serve started from the UI (temp credentials forwarded into the containers). Forwarded to each child command."));
|
|
31218
31581
|
cmd.addOption(new Option("--stack <glob...>", "Filter the DISPLAYED targets by stack glob (e.g. \"dev/*\"); a target id is \"Stack/Construct\". Display-only — does NOT scope synth (the whole app is still synthesized; gate synth with the app's own -c context or a committed cdk.context.json). Space-separate multiple globs; a target matching ANY glob is shown."));
|
|
31219
31582
|
cmd.addOption(new Option("--watch", "Spawn serves started from the UI (start-api / start-alb / start-service) with --watch, so they re-synth + rolling-reload on CDK source changes. Toggleable from the Session bar. No effect on single-shot invokes (each invoke re-synths anyway); the target list is not re-synthed (restart studio to pick up newly-added resources)."));
|
|
31583
|
+
cmd.addOption(new Option("--include-custom-resources", "Show CDK custom-resource / provider-framework Lambdas in the target list (provider framework onEvent/onTimeout/isComplete handlers, log-retention, bucket-notifications, AwsCustomResource, BucketDeployment, etc.). Hidden by default so the list shows only your own functions."));
|
|
31220
31584
|
return cmd;
|
|
31221
31585
|
}
|
|
31222
31586
|
|
|
31223
31587
|
//#endregion
|
|
31224
|
-
export {
|
|
31225
|
-
//# sourceMappingURL=local-studio-
|
|
31588
|
+
export { describePinnedImageUri as $, CfnLocalStateProvider as $n, buildCognitoJwksUrl as $t, addStartServiceSpecificOptions as A, buildMgmtEndpointEnvUrl as An, toCmdArgv as At, ecsClusterOption as B, resolveRuntimeImage as Bn, attachStageContext as Bt, addAlbSpecificOptions as C, selectIntegrationResponse as Cn, derivePseudoParametersFromRegion as Cr, invokeAgentCore as Ct, resolveAlbTarget as D, probeHostGatewaySupport as Dn, LocalInvokeBuildError as Dr, buildAgentCoreCodeImage as Dt, parseLbPortOverrides as E, HOST_GATEWAY_MIN_VERSION as En, tryResolveImageFnJoin as Er, SUPPORTED_CODE_RUNTIMES as Et, MAX_TASKS_SUBNET_RANGE_CAP as F, buildMessageEvent as Fn, createLocalStartApiCommand as Ft, runEcsServiceEmulator as G, substituteEnvVarsFromStateAsync as Gn, filterRoutesByApiIdentifier as Gt, parseRestartPolicy as H, substituteAgainstState as Hn, materializeLayerFromArn as Ht, addCommonEcsServiceOptions as I, architectureToPlatform as In, createWatchPredicates as It, enforceImageOverrideOrphans as J, isCfnFlagPresent as Jn, readMtlsMaterialsFromDisk as Jt, ImageOverrideError as K, LocalStateSourceError as Kn, filterRoutesByApiIdentifiers as Kt, addEcsAssumeRoleOptions as L, buildContainerImage as Ln, resolveApiTargetSubset as Lt, serviceStrategy as M, parseConnectionsPath as Mn, addInvokeSpecificOptions as Mt, addRunTaskSpecificOptions as N, buildConnectEvent as Nn, createLocalInvokeCommand as Nt, isApplicationLoadBalancer as O, bufferToBody as On, buildStsClientConfig as Or, computeCodeImageTag as Ot, createLocalRunTaskCommand as P, buildDisconnectEvent as Pn, addStartApiSpecificOptions as Pt, runImageOverrideBuilds as Q, resolveCfnStackName as Qn, defaultCredentialsLoader as Qt, addImageOverrideOptions as R, resolveRuntimeCodeMountPath as Rn, createAuthorizerCache as Rt, formatTargetListing as S, pickResponseTemplate as Sn, resolveAgentCoreTarget as Sr, AGENTCORE_SESSION_ID_HEADER as St, createLocalStartAlbCommand as T, VtlEvaluationError as Tn, substituteImagePlaceholders as Tr, downloadAndExtractS3Bundle as Tt, resolveEcsAssumeRoleOption as U, substituteAgainstStateAsync as Un, resolveEnvVars$1 as Ut, parseMaxTasks as V, EcsTaskResolutionError as Vn, buildStageMap as Vt, resolveSharedSidecarCredentials as W, substituteEnvVarsFromState as Wn, availableApiIdentifiers as Wt, parseImageOverrideFlags as X, resolveCfnFallbackRegion as Xn, resolveSelectionExpression as Xt, mergeForService as Y, rejectExplicitCfnStackWithMultipleStacks as Yn, startApiServer as Yt, resolveImageOverrides as Z, resolveCfnRegion as Zn, resolveServiceIntegrationParameters as Zt, renderStudioHtml as _, translateLambdaResponse as _n, AGENTCORE_HTTP_PROTOCOL as _r, MCP_PROTOCOL_VERSION as _t, createLocalStudioCommand as a, buildMethodArn as an, listTargets as ar, SOFT_RELOAD_COMPLETION_LOG_SUFFIX as at, addListSpecificOptions as b, buildRestV1Event as bn, AgentCoreResolutionError as br, AGENTCORE_SIGV4_SERVICE as bt, startStudioProxy as c, invokeRequestAuthorizer as cn, discoverWebSocketApisOrThrow as cr, attachContainerLogStreamer as ct, filterStudioCustomResources as d, applyCorsResponseHeaders as dn, webSocketApiMatchesIdentifier as dr, invokeAgentCoreWs as dt, buildJwksUrlFromIssuer as en, collectSsmParameterRefs as er, isLocalCdkAssetImage as et, isCustomResourceLambdaTarget as f, buildCorsConfigByApiId as fn, discoverRoutes as fr, A2A_CONTAINER_PORT as ft, toStudioTargetGroups as g, matchRoute as gn, AGENTCORE_AGUI_PROTOCOL as gr, MCP_PATH as gt, startStudioServer as h, matchPreflight as hn, AGENTCORE_A2A_PROTOCOL as hr, MCP_CONTAINER_PORT as ht, coerceStopRequest as i, verifyJwtViaDiscovery as in, countTargets as ir, DEFAULT_SHADOW_READY_TIMEOUT_MS as it, createLocalStartServiceCommand as j, handleConnectionsRequest as jn, classifySourceChange as jt, resolveAlbFrontDoor as k, ConnectionRegistry as kn, resolveProfileCredentials as kr, renderCodeDockerfile as kt, relayServeRequest as l, invokeTokenAuthorizer as ln, filterWebSocketApisByIdentifiers as lr, addInvokeAgentCoreSpecificOptions as lt, filterStudioTargetGroups as m, isFunctionUrlOacFronted as mn, resolveLambdaArnIntrinsic as mr, a2aInvokeOnce as mt, coerceRunRequest as n, verifyCognitoJwt as nn, resolveWatchConfig as nr, buildCloudMapIndex as nt, resolveServeBaseUrl as o, computeRequestIdentityHash as on, availableWebSocketApiIdentifiers as or, setShadowReadyTimeoutMs as ot, annotatePinnedEcsTargets as p, buildCorsConfigFromCloudFrontChain as pn, pickRefLogicalId as pr, A2A_PATH as pt, buildImageOverrideTag as q, createLocalStateProvider as qn, groupRoutesByServer as qt, coerceServeRequest as r, verifyJwtAuthorizer as rn, resolveSingleTarget as rr, CloudMapRegistry as rt, createStudioServeManager as s, evaluateCachedLambdaPolicy as sn, discoverWebSocketApis as sr, getContainerNetworkIp as st, addStudioSpecificOptions as t, createJwksCache as tn, resolveSsmParameters as tr, listPinnedTargets as tt, createStudioDispatcher as u, attachAuthorizers as un, parseSelectionExpressionPath as ur, createLocalInvokeAgentCoreCommand as ut, createStudioStore as v, applyAuthorizerOverlay as vn, AGENTCORE_MCP_PROTOCOL as vr, mcpInvokeOnce as vt, albStrategy as w, tryParseStatus as wn, formatStateRemedy as wr, waitForAgentCorePing as wt, createLocalListCommand as x, evaluateResponseParameters as xn, pickAgentCoreCandidateStack as xr, signAgentCoreInvocation as xt, StudioEventBus as y, buildHttpApiV2Event as yn, AGENTCORE_RUNTIME_TYPE as yr, parseSseForJsonRpc as yt, buildEcsImageResolutionContext$1 as z, resolveRuntimeFileExtension as zn, createFileWatcher as zt };
|
|
31589
|
+
//# sourceMappingURL=local-studio-Cm3Qt0MG.js.map
|