@go-to-k/cdkd 0.206.0 → 0.207.1
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 +700 -57
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ import { ACMClient, AddTagsToCertificateCommand, DeleteCertificateCommand, Descr
|
|
|
24
24
|
import * as fs from "node:fs";
|
|
25
25
|
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
26
26
|
import * as path from "node:path";
|
|
27
|
-
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
27
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
28
28
|
import { execFile, spawn } from "node:child_process";
|
|
29
29
|
import { tmpdir } from "node:os";
|
|
30
30
|
import { AssociateVPCWithHostedZoneCommand, ChangeResourceRecordSetsCommand, ChangeTagsForResourceCommand, CreateHostedZoneCommand, CreateQueryLoggingConfigCommand, DeleteHostedZoneCommand, DeleteQueryLoggingConfigCommand, DisassociateVPCFromHostedZoneCommand, GetHostedZoneCommand, ListHostedZonesByNameCommand, ListHostedZonesCommand, ListQueryLoggingConfigsCommand, ListResourceRecordSetsCommand, ListTagsForResourceCommand as ListTagsForResourceCommand$6, Route53Client, UpdateHostedZoneCommentCommand, UpdateHostedZoneFeaturesCommand } from "@aws-sdk/client-route-53";
|
|
@@ -62,7 +62,7 @@ import { CreateNamespaceCommand, CreateTableBucketCommand, CreateTableCommand as
|
|
|
62
62
|
import { AttachLoadBalancerTargetGroupsCommand, AttachLoadBalancersCommand, AttachTrafficSourcesCommand, AutoScalingClient, CreateAutoScalingGroupCommand, CreateOrUpdateTagsCommand, DeleteAutoScalingGroupCommand, DeleteLifecycleHookCommand, DeleteNotificationConfigurationCommand, DeleteTagsCommand as DeleteTagsCommand$1, DescribeAutoScalingGroupsCommand, DescribeLifecycleHooksCommand, DescribeNotificationConfigurationsCommand, DescribeTrafficSourcesCommand, DetachLoadBalancerTargetGroupsCommand, DetachLoadBalancersCommand, DetachTrafficSourcesCommand, DisableMetricsCollectionCommand, EnableMetricsCollectionCommand, PutLifecycleHookCommand, PutNotificationConfigurationCommand, UpdateAutoScalingGroupCommand } from "@aws-sdk/client-auto-scaling";
|
|
63
63
|
import { Document, Pair, Scalar, YAMLMap, YAMLSeq, parse as parse$1, stringify } from "yaml";
|
|
64
64
|
import { createLocalStateProvider, getEmbedConfig, isCfnFlagPresent, listTargets, rejectExplicitCfnStackWithMultipleStacks, resolveCfnFallbackRegion, setEmbedConfig, substituteAgainstState, substituteAgainstStateAsync, substituteEnvVarsFromState, substituteEnvVarsFromStateAsync } from "cdk-local";
|
|
65
|
-
import { A2A_CONTAINER_PORT, A2A_PATH, AGENTCORE_A2A_PROTOCOL, AGENTCORE_AGUI_PROTOCOL, AGENTCORE_MCP_PROTOCOL, ConnectionRegistry, EcsTaskResolutionError, HOST_GATEWAY_MIN_VERSION, LocalInvokeBuildError, MCP_CONTAINER_PORT, MCP_PATH, a2aInvokeOnce, addAlbSpecificOptions, addCommonEcsServiceOptions, addStartServiceSpecificOptions, albStrategy, architectureToPlatform, attachAuthorizers, attachStageContext, availableApiIdentifiers, bufferToBody, buildAgentCoreCodeImage, buildCognitoJwksUrl, buildConnectEvent, buildContainerImage, buildCorsConfigByApiId, buildCorsConfigFromCloudFrontChain, buildDisconnectEvent, buildJwksUrlFromIssuer, buildMessageEvent, buildMgmtEndpointEnvUrl, buildStageMap, createAuthorizerCache, createFileWatcher, createJwksCache, createWatchPredicates, defaultCredentialsLoader, derivePseudoParametersFromRegion, discoverRoutes, discoverWebSocketApis, downloadAndExtractS3Bundle, filterRoutesByApiIdentifier, groupRoutesByServer, handleConnectionsRequest, invokeAgentCore, invokeAgentCoreWs, materializeLayerFromArn, mcpInvokeOnce, parseConnectionsPath, parseSelectionExpressionPath, pickAgentCoreCandidateStack, probeHostGatewaySupport, readMtlsMaterialsFromDisk, resolveAgentCoreTarget, resolveEnvVars, resolveRuntimeCodeMountPath, resolveRuntimeFileExtension, resolveRuntimeImage, resolveSingleTarget, resolveWatchConfig, runEcsServiceEmulator, signAgentCoreInvocation, startApiServer, substituteImagePlaceholders, tryResolveImageFnJoin, verifyJwtViaDiscovery, waitForAgentCorePing } from "cdk-local/internal";
|
|
65
|
+
import { A2A_CONTAINER_PORT, A2A_PATH, AGENTCORE_A2A_PROTOCOL, AGENTCORE_AGUI_PROTOCOL, AGENTCORE_MCP_PROTOCOL, ConnectionRegistry, EcsTaskResolutionError, HOST_GATEWAY_MIN_VERSION, LocalInvokeBuildError, MCP_CONTAINER_PORT, MCP_PATH, a2aInvokeOnce, addAlbSpecificOptions, addCommonEcsServiceOptions, addStartServiceSpecificOptions, albStrategy, architectureToPlatform, attachAuthorizers, attachStageContext, availableApiIdentifiers, bufferToBody, buildAgentCoreCodeImage, buildCognitoJwksUrl, buildConnectEvent, buildContainerImage, buildCorsConfigByApiId, buildCorsConfigFromCloudFrontChain, buildDisconnectEvent, buildJwksUrlFromIssuer, buildMessageEvent, buildMgmtEndpointEnvUrl, buildStageMap, classifySourceChange, createAuthorizerCache, createFileWatcher, createFileWatcher as createFileWatcher$1, createJwksCache, createWatchPredicates, defaultCredentialsLoader, derivePseudoParametersFromRegion, discoverRoutes, discoverWebSocketApis, downloadAndExtractS3Bundle, filterRoutesByApiIdentifier, groupRoutesByServer, handleConnectionsRequest, invokeAgentCore, invokeAgentCoreWs, materializeLayerFromArn, mcpInvokeOnce, parseConnectionsPath, parseSelectionExpressionPath, pickAgentCoreCandidateStack, pickAgentCoreCandidateStack as pickAgentCoreCandidateStack$1, probeHostGatewaySupport, readMtlsMaterialsFromDisk, resolveAgentCoreTarget, resolveEnvVars, resolveRuntimeCodeMountPath, resolveRuntimeFileExtension, resolveRuntimeImage, resolveSingleTarget, resolveWatchConfig, runEcsServiceEmulator, signAgentCoreInvocation, startApiServer, substituteImagePlaceholders, tryResolveImageFnJoin, verifyJwtViaDiscovery, waitForAgentCorePing } from "cdk-local/internal";
|
|
66
66
|
import { createServer } from "node:net";
|
|
67
67
|
import { promisify } from "node:util";
|
|
68
68
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
@@ -1857,6 +1857,95 @@ var ExportIndexStore = class {
|
|
|
1857
1857
|
}
|
|
1858
1858
|
};
|
|
1859
1859
|
|
|
1860
|
+
//#endregion
|
|
1861
|
+
//#region src/analyzer/cross-stack-deps.ts
|
|
1862
|
+
/**
|
|
1863
|
+
* Infer cross-stack ordering edges that CDK's manifest dependency graph
|
|
1864
|
+
* (`stack.dependencyNames`, surfaced from `addDependency`) does NOT capture.
|
|
1865
|
+
*
|
|
1866
|
+
* Two stacks can be linked by a RAW cross-stack reference —
|
|
1867
|
+
* `cdk.Fn.importValue('<exportName>')` (→ `Fn::ImportValue`) or
|
|
1868
|
+
* `Fn::GetStackOutput` — WITHOUT an explicit `addDependency`. CDK only emits a
|
|
1869
|
+
* manifest dependency when the reference goes through its own
|
|
1870
|
+
* `exportValue` / strong-reference machinery; a hand-written
|
|
1871
|
+
* `Fn.importValue` of a literal export name produces no manifest edge. Under
|
|
1872
|
+
* concurrent `--all` deploys that lets the consumer race ahead of the
|
|
1873
|
+
* producer and fail (`export not found` / `stack not found`); on `--all`
|
|
1874
|
+
* destroy it lets the producer be deleted while a consumer still imports it.
|
|
1875
|
+
*
|
|
1876
|
+
* This helper closes that gap by scanning the synthesized templates directly
|
|
1877
|
+
* (NOT the runtime S3 export index, which is empty on a fresh multi-stack
|
|
1878
|
+
* deploy). It returns, per consumer stackName, the set of OTHER stackNames in
|
|
1879
|
+
* the SAME input set that it depends on (consumer → producer edges).
|
|
1880
|
+
*
|
|
1881
|
+
* Rules (mirroring deploy.ts's existing `if (stackMap.has(depName))` guard):
|
|
1882
|
+
* - Edges are only added between stacks BOTH present in the input set. A
|
|
1883
|
+
* producer outside the set = no edge — its export resolves at runtime from
|
|
1884
|
+
* already-deployed state, which is correct.
|
|
1885
|
+
* - `Fn::ImportValue` with a literal-string export name matches against the
|
|
1886
|
+
* `Export.Name` of every OTHER stack's outputs. An import whose export name
|
|
1887
|
+
* no stack in the set produces (external / pre-existing export) is ignored.
|
|
1888
|
+
* - `Fn::GetStackOutput`'s `StackName` argument names the producer stack
|
|
1889
|
+
* DIRECTLY (the intrinsic arg is an object `{ StackName, OutputName,
|
|
1890
|
+
* Region?, RoleArn? }` — see
|
|
1891
|
+
* `src/deployment/intrinsic-function-resolver.ts`). If that stack is in the
|
|
1892
|
+
* set, add the edge.
|
|
1893
|
+
* - Non-literal `Fn::ImportValue` / `Fn::GetStackOutput` args (intrinsic
|
|
1894
|
+
* nesting, etc.) are skipped without crashing — they cannot be statically
|
|
1895
|
+
* resolved to a producer stack name here.
|
|
1896
|
+
*
|
|
1897
|
+
* @param stacks the stacks being deployed / destroyed together (the `--all`
|
|
1898
|
+
* set, or the auto-include-dependency-expanded target set).
|
|
1899
|
+
* @returns Map<consumerStackName, Set<producerStackName>>. Consumers with no
|
|
1900
|
+
* inferred cross-stack producer get an empty set entry.
|
|
1901
|
+
*/
|
|
1902
|
+
function inferCrossStackStackDeps(stacks) {
|
|
1903
|
+
const exportOwner = /* @__PURE__ */ new Map();
|
|
1904
|
+
for (const stack of stacks) {
|
|
1905
|
+
const outputs = stack.template.Outputs;
|
|
1906
|
+
if (!outputs) continue;
|
|
1907
|
+
for (const output of Object.values(outputs)) {
|
|
1908
|
+
const name = output?.Export?.Name;
|
|
1909
|
+
if (typeof name === "string" && name !== "") {
|
|
1910
|
+
if (!exportOwner.has(name)) exportOwner.set(name, stack.stackName);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
const stackNames = new Set(stacks.map((s) => s.stackName));
|
|
1915
|
+
const result = /* @__PURE__ */ new Map();
|
|
1916
|
+
for (const stack of stacks) {
|
|
1917
|
+
const producers = /* @__PURE__ */ new Set();
|
|
1918
|
+
collectCrossStackRefs(stack.template, (kind, value) => {
|
|
1919
|
+
let producer;
|
|
1920
|
+
if (kind === "ImportValue") {
|
|
1921
|
+
if (typeof value === "string") producer = exportOwner.get(value);
|
|
1922
|
+
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1923
|
+
const sn = value["StackName"];
|
|
1924
|
+
if (typeof sn === "string") producer = sn;
|
|
1925
|
+
}
|
|
1926
|
+
if (producer && producer !== stack.stackName && stackNames.has(producer)) producers.add(producer);
|
|
1927
|
+
});
|
|
1928
|
+
result.set(stack.stackName, producers);
|
|
1929
|
+
}
|
|
1930
|
+
return result;
|
|
1931
|
+
}
|
|
1932
|
+
/**
|
|
1933
|
+
* Recursively walk a template node, invoking `onRef` for every
|
|
1934
|
+
* `Fn::ImportValue` / `Fn::GetStackOutput` intrinsic encountered. The
|
|
1935
|
+
* callback receives the discriminator and the raw (unresolved) argument.
|
|
1936
|
+
*/
|
|
1937
|
+
function collectCrossStackRefs(node, onRef) {
|
|
1938
|
+
if (!node || typeof node !== "object") return;
|
|
1939
|
+
if (Array.isArray(node)) {
|
|
1940
|
+
for (const item of node) collectCrossStackRefs(item, onRef);
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
const obj = node;
|
|
1944
|
+
if ("Fn::ImportValue" in obj) onRef("ImportValue", obj["Fn::ImportValue"]);
|
|
1945
|
+
if ("Fn::GetStackOutput" in obj) onRef("GetStackOutput", obj["Fn::GetStackOutput"]);
|
|
1946
|
+
for (const value of Object.values(obj)) collectCrossStackRefs(value, onRef);
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1860
1949
|
//#endregion
|
|
1861
1950
|
//#region src/provisioning/providers/iam-policy-provider.ts
|
|
1862
1951
|
/**
|
|
@@ -34961,13 +35050,19 @@ async function deployCommand(stacks, options) {
|
|
|
34961
35050
|
else if (allStacks.length === 1) targetStacks = allStacks;
|
|
34962
35051
|
else throw new Error(`Multiple stacks found: ${allStacks.map(describeStack).join(", ")}. Specify stack name(s) or use --all`);
|
|
34963
35052
|
if (targetStacks.length === 0) throw new Error(stackPatterns.length > 0 ? `No stacks matching ${stackPatterns.join(", ")} found in assembly. Available: ${allStacks.map(describeStack).join(", ")}` : "No stacks found in assembly");
|
|
35053
|
+
const inferredCrossStackDeps = inferCrossStackStackDeps(allStacks);
|
|
35054
|
+
const effectiveStackDeps = (stackName, deps) => {
|
|
35055
|
+
const union = new Set(deps);
|
|
35056
|
+
for (const producer of inferredCrossStackDeps.get(stackName) ?? []) union.add(producer);
|
|
35057
|
+
return union;
|
|
35058
|
+
};
|
|
34964
35059
|
if (!options.exclusively) {
|
|
34965
35060
|
const targetNames = new Set(targetStacks.map((s) => s.stackName));
|
|
34966
35061
|
const allStackMap = new Map(allStacks.map((s) => [s.stackName, s]));
|
|
34967
35062
|
const addDependencies = (stackName) => {
|
|
34968
35063
|
const stack = allStackMap.get(stackName);
|
|
34969
35064
|
if (!stack) return;
|
|
34970
|
-
for (const depName of stack.dependencyNames) if (!targetNames.has(depName)) {
|
|
35065
|
+
for (const depName of effectiveStackDeps(stackName, stack.dependencyNames)) if (!targetNames.has(depName)) {
|
|
34971
35066
|
const depStack = allStackMap.get(depName);
|
|
34972
35067
|
if (depStack) {
|
|
34973
35068
|
targetNames.add(depName);
|
|
@@ -35012,7 +35107,7 @@ async function deployCommand(stacks, options) {
|
|
|
35012
35107
|
} catch (error) {
|
|
35013
35108
|
if (error.code !== "ENOENT") throw error;
|
|
35014
35109
|
}
|
|
35015
|
-
for (const depName of stack.dependencyNames) if (stackMap.has(depName)) stackDeps.add(`stack:${depName}`);
|
|
35110
|
+
for (const depName of effectiveStackDeps(stack.stackName, stack.dependencyNames)) if (stackMap.has(depName)) stackDeps.add(`stack:${depName}`);
|
|
35016
35111
|
workGraph.addNode({
|
|
35017
35112
|
id: stackNodeId,
|
|
35018
35113
|
type: "stack",
|
|
@@ -37369,6 +37464,39 @@ function createDriftCommand() {
|
|
|
37369
37464
|
//#endregion
|
|
37370
37465
|
//#region src/cli/commands/destroy.ts
|
|
37371
37466
|
/**
|
|
37467
|
+
* Topologically order `stackNames` so that every consumer is destroyed BEFORE
|
|
37468
|
+
* its producers. `consumerToProducers` maps consumer → set of producer stack
|
|
37469
|
+
* names (the deploy-direction edges from `inferCrossStackStackDeps`); destroy
|
|
37470
|
+
* is the reverse, so a stack must appear before all of its producers.
|
|
37471
|
+
*
|
|
37472
|
+
* Kahn-style with a deterministic tie-break: among nodes currently free to be
|
|
37473
|
+
* emitted (no remaining unemitted consumer pointing at them), pick the one
|
|
37474
|
+
* earliest in the original `stackNames` order so the output is stable and the
|
|
37475
|
+
* original order is preserved whenever there are no cross-stack edges. A cycle
|
|
37476
|
+
* (should not happen for valid templates) degrades gracefully — any leftover
|
|
37477
|
+
* nodes are appended in original order rather than dropped.
|
|
37478
|
+
*/
|
|
37479
|
+
function orderConsumersBeforeProducers(stackNames, consumerToProducers) {
|
|
37480
|
+
const pendingConsumers = /* @__PURE__ */ new Map();
|
|
37481
|
+
for (const name of stackNames) pendingConsumers.set(name, /* @__PURE__ */ new Set());
|
|
37482
|
+
for (const [consumer, producers] of consumerToProducers) for (const producer of producers) pendingConsumers.get(producer)?.add(consumer);
|
|
37483
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
37484
|
+
const ordered = [];
|
|
37485
|
+
while (ordered.length < stackNames.length) {
|
|
37486
|
+
const next = stackNames.find((name) => !emitted.has(name) && [...pendingConsumers.get(name) ?? []].every((c) => emitted.has(c)));
|
|
37487
|
+
if (!next) {
|
|
37488
|
+
for (const name of stackNames) if (!emitted.has(name)) {
|
|
37489
|
+
emitted.add(name);
|
|
37490
|
+
ordered.push(name);
|
|
37491
|
+
}
|
|
37492
|
+
break;
|
|
37493
|
+
}
|
|
37494
|
+
emitted.add(next);
|
|
37495
|
+
ordered.push(next);
|
|
37496
|
+
}
|
|
37497
|
+
return ordered;
|
|
37498
|
+
}
|
|
37499
|
+
/**
|
|
37372
37500
|
* Destroy command implementation
|
|
37373
37501
|
*/
|
|
37374
37502
|
async function destroyCommand(stackArgs, options) {
|
|
@@ -37414,21 +37542,27 @@ async function destroyCommand(stackArgs, options) {
|
|
|
37414
37542
|
if (options.allowUnsupportedTypes?.length) providerRegistry.allowUnsupportedTypes(options.allowUnsupportedTypes);
|
|
37415
37543
|
const appCmd = options.app || resolveApp();
|
|
37416
37544
|
let appStacks = [];
|
|
37545
|
+
let synthScanStacks = [];
|
|
37417
37546
|
if (appCmd) try {
|
|
37418
37547
|
const synthesizer = new Synthesizer();
|
|
37419
37548
|
const context = parseContextOptions(options.context);
|
|
37420
|
-
|
|
37549
|
+
const result = await synthesizer.synthesize({
|
|
37421
37550
|
app: appCmd,
|
|
37422
37551
|
output: options.output || "cdk.out",
|
|
37423
37552
|
...Object.keys(context).length > 0 && { context },
|
|
37424
37553
|
stateBucket,
|
|
37425
37554
|
...options.profile && { macroExpandS3ClientOpts: { profile: options.profile } }
|
|
37426
|
-
})
|
|
37555
|
+
});
|
|
37556
|
+
appStacks = result.stacks.map((s) => ({
|
|
37427
37557
|
stackName: s.stackName,
|
|
37428
37558
|
displayName: s.displayName,
|
|
37429
37559
|
...s.region && { region: s.region },
|
|
37430
37560
|
...s.terminationProtection !== void 0 && { terminationProtection: s.terminationProtection }
|
|
37431
37561
|
}));
|
|
37562
|
+
synthScanStacks = result.stacks.map((s) => ({
|
|
37563
|
+
stackName: s.stackName,
|
|
37564
|
+
template: s.template
|
|
37565
|
+
}));
|
|
37432
37566
|
} catch {
|
|
37433
37567
|
logger.debug("Could not synthesize app, falling back to state-based stack list");
|
|
37434
37568
|
}
|
|
@@ -37475,6 +37609,11 @@ async function destroyCommand(stackArgs, options) {
|
|
|
37475
37609
|
logger.info("No matching stacks found in state");
|
|
37476
37610
|
return;
|
|
37477
37611
|
}
|
|
37612
|
+
if (synthScanStacks.length > 0 && stackNames.length > 1) {
|
|
37613
|
+
const inSet = new Set(stackNames);
|
|
37614
|
+
const inferred = inferCrossStackStackDeps(synthScanStacks.filter((s) => inSet.has(s.stackName)));
|
|
37615
|
+
stackNames = orderConsumersBeforeProducers(stackNames, inferred);
|
|
37616
|
+
}
|
|
37478
37617
|
logger.info(`Found ${stackNames.length} stack(s) to destroy: ${stackNames.join(", ")}`);
|
|
37479
37618
|
const accountId = "unknown";
|
|
37480
37619
|
const stateRefsByName = /* @__PURE__ */ new Map();
|
|
@@ -44891,7 +45030,7 @@ async function applyCrossStackResolverToTask(task, context) {
|
|
|
44891
45030
|
|
|
44892
45031
|
//#endregion
|
|
44893
45032
|
//#region src/local/docker-runner.ts
|
|
44894
|
-
const execFileAsync$
|
|
45033
|
+
const execFileAsync$4 = promisify(execFile);
|
|
44895
45034
|
/**
|
|
44896
45035
|
* Wraps `docker pull` / `docker run` / `docker rm` for `cdkd local invoke`.
|
|
44897
45036
|
*
|
|
@@ -44989,7 +45128,7 @@ async function runDetached(opts) {
|
|
|
44989
45128
|
args.push(opts.image, ...entryPointTail, ...opts.cmd);
|
|
44990
45129
|
getLogger().child("docker").debug(`${getDockerCmd()} ${redactAwsCredentialsInArgs(args).join(" ")}`);
|
|
44991
45130
|
try {
|
|
44992
|
-
const { stdout } = await execFileAsync$
|
|
45131
|
+
const { stdout } = await execFileAsync$4(getDockerCmd(), args, {
|
|
44993
45132
|
maxBuffer: 10 * 1024 * 1024,
|
|
44994
45133
|
env: {
|
|
44995
45134
|
...process.env,
|
|
@@ -45032,7 +45171,7 @@ async function removeContainer(containerId) {
|
|
|
45032
45171
|
if (!containerId) return;
|
|
45033
45172
|
const logger = getLogger().child("docker");
|
|
45034
45173
|
try {
|
|
45035
|
-
await execFileAsync$
|
|
45174
|
+
await execFileAsync$4(getDockerCmd(), [
|
|
45036
45175
|
"rm",
|
|
45037
45176
|
"-f",
|
|
45038
45177
|
containerId
|
|
@@ -45051,7 +45190,7 @@ async function removeContainer(containerId) {
|
|
|
45051
45190
|
async function ensureDockerAvailable() {
|
|
45052
45191
|
const cmd = getDockerCmd();
|
|
45053
45192
|
try {
|
|
45054
|
-
await execFileAsync$
|
|
45193
|
+
await execFileAsync$4(cmd, [
|
|
45055
45194
|
"version",
|
|
45056
45195
|
"--format",
|
|
45057
45196
|
"{{.Server.Version}}"
|
|
@@ -46952,7 +47091,7 @@ async function localStartApiCommand(target, options) {
|
|
|
46952
47091
|
output: options.output,
|
|
46953
47092
|
watchConfig: resolveWatchConfig()
|
|
46954
47093
|
});
|
|
46955
|
-
watcher = createFileWatcher({
|
|
47094
|
+
watcher = createFileWatcher$1({
|
|
46956
47095
|
paths: [watchRoot],
|
|
46957
47096
|
ignored,
|
|
46958
47097
|
shouldTrigger,
|
|
@@ -48122,7 +48261,7 @@ function createLocalStartApiCommand() {
|
|
|
48122
48261
|
|
|
48123
48262
|
//#endregion
|
|
48124
48263
|
//#region src/local/ecs-network.ts
|
|
48125
|
-
const execFileAsync$
|
|
48264
|
+
const execFileAsync$3 = promisify(execFile);
|
|
48126
48265
|
/**
|
|
48127
48266
|
* Docker network + AWS-published metadata-endpoints sidecar lifecycle for
|
|
48128
48267
|
* `cdkd local run-task`. The sidecar (a small Go binary maintained by
|
|
@@ -48175,7 +48314,7 @@ async function createNetworkAndSidecar(args) {
|
|
|
48175
48314
|
await pullImage(METADATA_ENDPOINT_IMAGE, skipPull);
|
|
48176
48315
|
logger.info(`Creating docker network ${networkName} (subnet ${cidr})...`);
|
|
48177
48316
|
try {
|
|
48178
|
-
await execFileAsync$
|
|
48317
|
+
await execFileAsync$3(getDockerCmd(), [
|
|
48179
48318
|
"network",
|
|
48180
48319
|
"create",
|
|
48181
48320
|
"--driver",
|
|
@@ -48210,7 +48349,7 @@ async function createNetworkAndSidecar(args) {
|
|
|
48210
48349
|
sidecarArgs.push(METADATA_ENDPOINT_IMAGE);
|
|
48211
48350
|
logger.info(`Starting ECS local-container-endpoints sidecar at ${sidecarIp}...`);
|
|
48212
48351
|
try {
|
|
48213
|
-
const { stdout } = await execFileAsync$
|
|
48352
|
+
const { stdout } = await execFileAsync$3(getDockerCmd(), sidecarArgs, { maxBuffer: 10 * 1024 * 1024 });
|
|
48214
48353
|
return stdout.trim();
|
|
48215
48354
|
} catch (err) {
|
|
48216
48355
|
await destroyNetworkOnly(networkName);
|
|
@@ -48278,7 +48417,7 @@ async function destroyNetworkOnly(networkName) {
|
|
|
48278
48417
|
if (!networkName) return;
|
|
48279
48418
|
const logger = getLogger().child("ecs-network");
|
|
48280
48419
|
try {
|
|
48281
|
-
await execFileAsync$
|
|
48420
|
+
await execFileAsync$3(getDockerCmd(), [
|
|
48282
48421
|
"network",
|
|
48283
48422
|
"rm",
|
|
48284
48423
|
networkName
|
|
@@ -48411,7 +48550,7 @@ async function resolveSsm(entry, shape, client) {
|
|
|
48411
48550
|
|
|
48412
48551
|
//#endregion
|
|
48413
48552
|
//#region src/local/ecs-task-runner.ts
|
|
48414
|
-
const execFileAsync$
|
|
48553
|
+
const execFileAsync$2 = promisify(execFile);
|
|
48415
48554
|
/**
|
|
48416
48555
|
* Top-level orchestrator for `cdkd local run-task`. Coordinates image
|
|
48417
48556
|
* preparation, secret resolution, docker-network bring-up, container
|
|
@@ -48471,7 +48610,7 @@ async function cleanupEcsRun(state, options) {
|
|
|
48471
48610
|
if (state.network && !state.network.ownedByCaller) await destroyTaskNetwork(state.network);
|
|
48472
48611
|
state.network = void 0;
|
|
48473
48612
|
for (const v of state.dockerVolumeNames) try {
|
|
48474
|
-
await execFileAsync$
|
|
48613
|
+
await execFileAsync$2(getDockerCmd(), [
|
|
48475
48614
|
"volume",
|
|
48476
48615
|
"rm",
|
|
48477
48616
|
v
|
|
@@ -48547,7 +48686,7 @@ async function runEcsTask(task, options, state) {
|
|
|
48547
48686
|
logger.info(`Starting container '${container.name}' (image=${imagePlan.get(container.name)})`);
|
|
48548
48687
|
let id;
|
|
48549
48688
|
try {
|
|
48550
|
-
const { stdout } = await execFileAsync$
|
|
48689
|
+
const { stdout } = await execFileAsync$2(getDockerCmd(), args, { maxBuffer: 10 * 1024 * 1024 });
|
|
48551
48690
|
id = stdout.trim();
|
|
48552
48691
|
} catch (err) {
|
|
48553
48692
|
const e = err;
|
|
@@ -48656,7 +48795,7 @@ async function waitForContainerHealthy(containerId, displayName) {
|
|
|
48656
48795
|
let lastStatus = "";
|
|
48657
48796
|
while (Date.now() < deadline) {
|
|
48658
48797
|
try {
|
|
48659
|
-
const { stdout } = await execFileAsync$
|
|
48798
|
+
const { stdout } = await execFileAsync$2(getDockerCmd(), [
|
|
48660
48799
|
"inspect",
|
|
48661
48800
|
"--format",
|
|
48662
48801
|
"{{.State.Health.Status}}",
|
|
@@ -48679,7 +48818,7 @@ async function waitForContainerHealthy(containerId, displayName) {
|
|
|
48679
48818
|
}
|
|
48680
48819
|
async function waitForContainerExit(containerId) {
|
|
48681
48820
|
try {
|
|
48682
|
-
const { stdout } = await execFileAsync$
|
|
48821
|
+
const { stdout } = await execFileAsync$2(getDockerCmd(), ["wait", containerId], { maxBuffer: 1024 * 1024 });
|
|
48683
48822
|
const code = Number.parseInt(stdout.trim(), 10);
|
|
48684
48823
|
return Number.isFinite(code) ? code : 1;
|
|
48685
48824
|
} catch (err) {
|
|
@@ -48689,7 +48828,7 @@ async function waitForContainerExit(containerId) {
|
|
|
48689
48828
|
}
|
|
48690
48829
|
async function stopContainer(containerId, graceSeconds) {
|
|
48691
48830
|
try {
|
|
48692
|
-
await execFileAsync$
|
|
48831
|
+
await execFileAsync$2(getDockerCmd(), [
|
|
48693
48832
|
"stop",
|
|
48694
48833
|
"-t",
|
|
48695
48834
|
String(graceSeconds),
|
|
@@ -48814,7 +48953,7 @@ async function realizeDockerVolumes(volumes, state) {
|
|
|
48814
48953
|
const dockerVolumeName = `cdkd-local-${v.name}-${randHex(4)}`;
|
|
48815
48954
|
args.push(dockerVolumeName);
|
|
48816
48955
|
try {
|
|
48817
|
-
await execFileAsync$
|
|
48956
|
+
await execFileAsync$2(getDockerCmd(), args);
|
|
48818
48957
|
state.dockerVolumeNames.push(dockerVolumeName);
|
|
48819
48958
|
logger.debug(`Created docker volume ${dockerVolumeName} for task volume '${v.name}'`);
|
|
48820
48959
|
} catch (err) {
|
|
@@ -49280,6 +49419,292 @@ function createLocalStartServiceCommand() {
|
|
|
49280
49419
|
return addCommonEcsServiceOptions(cmd);
|
|
49281
49420
|
}
|
|
49282
49421
|
|
|
49422
|
+
//#endregion
|
|
49423
|
+
//#region src/local/invoke-agentcore-watch-loop.ts
|
|
49424
|
+
/**
|
|
49425
|
+
* cdkd-owned `cdkd local invoke-agentcore --watch` reload loop.
|
|
49426
|
+
*
|
|
49427
|
+
* cdk-local PR #270's `runAgentCoreWatchLoop` hard-couples to cdk-local's OWN
|
|
49428
|
+
* `Synthesizer` / `LocalInvokeAgentCoreOptions` types, so it cannot be shimmed.
|
|
49429
|
+
* Instead this module re-implements the loop on top of cdk-local's already-
|
|
49430
|
+
* exported watch primitives (`createFileWatcher` / `createWatchPredicates` /
|
|
49431
|
+
* `resolveWatchConfig` / `classifySourceChange` + the `ReloadVerdict` /
|
|
49432
|
+
* `ReloadAssetContext` types) — the SAME pattern `cdkd local start-api --watch`
|
|
49433
|
+
* uses — and drives cdkd's OWN re-synth / image-build / docker pipeline through
|
|
49434
|
+
* the `rebuild` / `softReload` callbacks the command supplies.
|
|
49435
|
+
*
|
|
49436
|
+
* Behavior parity with cdk-local #270:
|
|
49437
|
+
* - re-synth + reload the agent container on CDK source edits (watch the
|
|
49438
|
+
* source tree, honoring `cdk.json` `watch.include` / `watch.exclude`);
|
|
49439
|
+
* - per-firing classifier: an interpreted-language source edit inside a
|
|
49440
|
+
* CodeConfiguration source tree takes a soft-reload FAST PATH (`docker cp`
|
|
49441
|
+
* + `docker restart`, no rebuild); Dockerfile / compiled-source /
|
|
49442
|
+
* asset-hash-changed / ambiguous edits force a full rebuild;
|
|
49443
|
+
* - reload-chain serialization (no two reloads in parallel);
|
|
49444
|
+
* - for the `--ws` session path: close the active socket cleanly on each
|
|
49445
|
+
* reload (via the abort signal) and reopen against the new container;
|
|
49446
|
+
* - for the default one-shot `/invocations` path: re-run the single shot
|
|
49447
|
+
* after each reload (cdkd-specific — cdk-local treats single-shot as a
|
|
49448
|
+
* no-op WARN).
|
|
49449
|
+
*/
|
|
49450
|
+
const execFileAsync$1 = promisify(execFile);
|
|
49451
|
+
/**
|
|
49452
|
+
* Run the `--watch` reload loop. Returns when the invocation closes naturally
|
|
49453
|
+
* with no pending reload (one-shot finished, or `--ws` agent closed), or when
|
|
49454
|
+
* a reload callback throws (the previous container may already be torn down,
|
|
49455
|
+
* so blocking on the next ping would hang). SIGINT runs the outer command's
|
|
49456
|
+
* cleanup + `process.exit`, so this loop is not the termination path on
|
|
49457
|
+
* user-initiated shutdown.
|
|
49458
|
+
*/
|
|
49459
|
+
async function runAgentCoreWatchLoop(args) {
|
|
49460
|
+
const logger = getLogger();
|
|
49461
|
+
const waitForPing = args.__waitForPing ?? waitForAgentCorePing;
|
|
49462
|
+
let currentHostPort = args.hostPort;
|
|
49463
|
+
let currentStacks = args.stacks;
|
|
49464
|
+
let reloadChain = Promise.resolve();
|
|
49465
|
+
let currentAbort;
|
|
49466
|
+
let pendingReload = false;
|
|
49467
|
+
let reloadFailed = false;
|
|
49468
|
+
const watcherFactory = args.__watcherFactory ?? ((onChange) => {
|
|
49469
|
+
const watchRoot = process.cwd();
|
|
49470
|
+
const { ignored, shouldTrigger, excludePatterns } = createWatchPredicates({
|
|
49471
|
+
watchRoot,
|
|
49472
|
+
output: args.options.output,
|
|
49473
|
+
watchConfig: resolveWatchConfig()
|
|
49474
|
+
});
|
|
49475
|
+
logger.info(`Watching ${watchRoot} for source changes (excluding ${excludePatterns.join(", ")}).`);
|
|
49476
|
+
return createFileWatcher({
|
|
49477
|
+
paths: [watchRoot],
|
|
49478
|
+
ignored,
|
|
49479
|
+
shouldTrigger,
|
|
49480
|
+
onChange
|
|
49481
|
+
});
|
|
49482
|
+
});
|
|
49483
|
+
const cdkOutDir = args.options.output;
|
|
49484
|
+
const assetLoader = new AssetManifestLoader();
|
|
49485
|
+
const watcher = watcherFactory((changedPaths) => {
|
|
49486
|
+
reloadChain = reloadChain.then(async () => {
|
|
49487
|
+
let verdict = {
|
|
49488
|
+
kind: "rebuild",
|
|
49489
|
+
reason: "classifier not consulted"
|
|
49490
|
+
};
|
|
49491
|
+
try {
|
|
49492
|
+
let assetCtx;
|
|
49493
|
+
if (args.__classifierContext) assetCtx = await args.__classifierContext(changedPaths);
|
|
49494
|
+
else {
|
|
49495
|
+
const { stacks: freshStacks } = await args.synthesizer.synthesize(args.synthOpts);
|
|
49496
|
+
const oldAssetHash = await deriveOldAssetHash({
|
|
49497
|
+
resolvedTarget: args.resolvedTarget,
|
|
49498
|
+
resolved: args.resolved,
|
|
49499
|
+
stacks: currentStacks,
|
|
49500
|
+
cdkOutDir,
|
|
49501
|
+
assetLoader
|
|
49502
|
+
});
|
|
49503
|
+
assetCtx = await loadAgentCoreAssetContext({
|
|
49504
|
+
resolvedTarget: args.resolvedTarget,
|
|
49505
|
+
resolved: args.resolved,
|
|
49506
|
+
stacks: freshStacks,
|
|
49507
|
+
cdkOutDir,
|
|
49508
|
+
assetLoader,
|
|
49509
|
+
...oldAssetHash !== void 0 && { oldAssetHash }
|
|
49510
|
+
});
|
|
49511
|
+
}
|
|
49512
|
+
verdict = classifySourceChange(changedPaths, assetCtx);
|
|
49513
|
+
logger.info(`Detected source change (${changedPaths.length} path(s)); verdict=${verdict.kind} (${verdict.reason}).`);
|
|
49514
|
+
} catch (err) {
|
|
49515
|
+
logger.warn(`Reload: classifier context unavailable (${err instanceof Error ? err.message : String(err)}); falling back to rebuild.`);
|
|
49516
|
+
verdict = {
|
|
49517
|
+
kind: "rebuild",
|
|
49518
|
+
reason: "classifier context unavailable; falling back to rebuild"
|
|
49519
|
+
};
|
|
49520
|
+
}
|
|
49521
|
+
pendingReload = true;
|
|
49522
|
+
logger.warn("cdkd local invoke-agentcore --watch: source change detected; closing the active invocation so the client can reconnect to the rebuilt container.");
|
|
49523
|
+
currentAbort?.abort();
|
|
49524
|
+
try {
|
|
49525
|
+
if (verdict.kind === "soft-reload") {
|
|
49526
|
+
const { stacks: newStacks } = await args.softReload(verdict.newAssetSourceDir);
|
|
49527
|
+
currentStacks = newStacks;
|
|
49528
|
+
logger.info("Reload: soft-reloaded the agent container (docker cp + docker restart; container ID + host port preserved).");
|
|
49529
|
+
} else {
|
|
49530
|
+
const { hostPort: newHostPort, stacks: newStacks } = await args.rebuild();
|
|
49531
|
+
currentHostPort = newHostPort;
|
|
49532
|
+
currentStacks = newStacks;
|
|
49533
|
+
logger.info(`Reload: rebuilt the agent container.`);
|
|
49534
|
+
}
|
|
49535
|
+
} catch (err) {
|
|
49536
|
+
logger.error(`Reload failed: ${err instanceof Error ? err.message : String(err)}. The previous container may already be torn down; exiting --watch loop. Re-run cdkd local invoke-agentcore --watch to recover.`);
|
|
49537
|
+
reloadFailed = true;
|
|
49538
|
+
currentAbort?.abort();
|
|
49539
|
+
}
|
|
49540
|
+
}).catch((err) => {
|
|
49541
|
+
logger.error(`Reload chain threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
49542
|
+
});
|
|
49543
|
+
});
|
|
49544
|
+
try {
|
|
49545
|
+
let firstIteration = true;
|
|
49546
|
+
while (true) {
|
|
49547
|
+
if (reloadFailed) break;
|
|
49548
|
+
await waitForPing(args.containerHost, currentHostPort);
|
|
49549
|
+
const abort = new AbortController();
|
|
49550
|
+
currentAbort = abort;
|
|
49551
|
+
pendingReload = false;
|
|
49552
|
+
try {
|
|
49553
|
+
const outcome = await args.invokeOnce({
|
|
49554
|
+
hostPort: currentHostPort,
|
|
49555
|
+
abortSignal: abort.signal,
|
|
49556
|
+
firstIteration
|
|
49557
|
+
});
|
|
49558
|
+
firstIteration = false;
|
|
49559
|
+
if (outcome.pendingReload) pendingReload = true;
|
|
49560
|
+
} finally {
|
|
49561
|
+
currentAbort = void 0;
|
|
49562
|
+
}
|
|
49563
|
+
if (!pendingReload) {
|
|
49564
|
+
await reloadChain.catch(() => void 0);
|
|
49565
|
+
if (!pendingReload) break;
|
|
49566
|
+
}
|
|
49567
|
+
await reloadChain.catch(() => void 0);
|
|
49568
|
+
}
|
|
49569
|
+
} finally {
|
|
49570
|
+
try {
|
|
49571
|
+
await watcher.close();
|
|
49572
|
+
} catch (err) {
|
|
49573
|
+
logger.warn(`Watcher close failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
49574
|
+
}
|
|
49575
|
+
}
|
|
49576
|
+
}
|
|
49577
|
+
/**
|
|
49578
|
+
* Soft-reload the agent container in place: `docker cp` the freshly-synthed
|
|
49579
|
+
* asset directory contents into the running container's WORKDIR + `docker
|
|
49580
|
+
* restart`.
|
|
49581
|
+
*
|
|
49582
|
+
* - WORKDIR is resolved from the live container's image config via
|
|
49583
|
+
* `docker inspect --format '{{.Config.WorkingDir}}'`. An empty WORKDIR
|
|
49584
|
+
* (Docker runtime default) maps to `/`. For CodeConfiguration runtimes the
|
|
49585
|
+
* generated Dockerfile sets `WORKDIR /app`, so the copy lands there.
|
|
49586
|
+
* - The trailing `/.` on the source ensures CONTENTS are copied (not the
|
|
49587
|
+
* directory itself); the trailing `/` on the dest forces docker cp to treat
|
|
49588
|
+
* it as a directory.
|
|
49589
|
+
* - `docker restart` cycles PID 1 — the new source is picked up by the
|
|
49590
|
+
* interpreted-language runtime on its next startup. The container ID + host
|
|
49591
|
+
* port + network are preserved across the restart.
|
|
49592
|
+
*
|
|
49593
|
+
* @internal — exported for unit tests of the docker-cp + docker-restart shape
|
|
49594
|
+
* without standing up a real container.
|
|
49595
|
+
*/
|
|
49596
|
+
async function softReloadAgentContainer(containerId, newAssetSourceDir) {
|
|
49597
|
+
const logger = getLogger();
|
|
49598
|
+
const dockerCmd = getDockerCmd();
|
|
49599
|
+
let workdir;
|
|
49600
|
+
try {
|
|
49601
|
+
const { stdout } = await execFileAsync$1(dockerCmd, [
|
|
49602
|
+
"inspect",
|
|
49603
|
+
"--format",
|
|
49604
|
+
"{{.Config.WorkingDir}}",
|
|
49605
|
+
containerId
|
|
49606
|
+
]);
|
|
49607
|
+
workdir = stdout.trim() || "/";
|
|
49608
|
+
} catch (err) {
|
|
49609
|
+
throw new CdkdError(`softReloadAgentContainer: docker inspect of container '${containerId}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_INSPECT_FAILED");
|
|
49610
|
+
}
|
|
49611
|
+
const workdirDest = workdir.endsWith("/") ? workdir : `${workdir}/`;
|
|
49612
|
+
logger.info(`Soft-reload: docker cp ${newAssetSourceDir} -> ${containerId}:${workdirDest}; restart.`);
|
|
49613
|
+
try {
|
|
49614
|
+
await execFileAsync$1(dockerCmd, [
|
|
49615
|
+
"cp",
|
|
49616
|
+
`${newAssetSourceDir}/.`,
|
|
49617
|
+
`${containerId}:${workdirDest}`
|
|
49618
|
+
], { maxBuffer: 64 * 1024 * 1024 });
|
|
49619
|
+
} catch (err) {
|
|
49620
|
+
throw new CdkdError(`softReloadAgentContainer: docker cp into '${containerId}:${workdir}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_CP_FAILED");
|
|
49621
|
+
}
|
|
49622
|
+
try {
|
|
49623
|
+
await execFileAsync$1(dockerCmd, ["restart", containerId]);
|
|
49624
|
+
} catch (err) {
|
|
49625
|
+
throw new CdkdError(`softReloadAgentContainer: docker restart of '${containerId}' failed: ${describeExecError(err)}.`, "LOCAL_INVOKE_AGENTCORE_WATCH_SOFT_RELOAD_RESTART_FAILED");
|
|
49626
|
+
}
|
|
49627
|
+
}
|
|
49628
|
+
function describeExecError(err) {
|
|
49629
|
+
if (!(err instanceof Error)) return String(err);
|
|
49630
|
+
const stderr = err.stderr;
|
|
49631
|
+
const stderrText = typeof stderr === "string" ? stderr.trim() : stderr instanceof Buffer ? stderr.toString("utf8").trim() : "";
|
|
49632
|
+
return stderrText ? `${err.message}\n${stderrText}` : err.message;
|
|
49633
|
+
}
|
|
49634
|
+
/**
|
|
49635
|
+
* Build the per-firing classifier context for the agent container. Mirrors the
|
|
49636
|
+
* ECS service emulator's `loadAssetContextForTarget` shape: returns `undefined`
|
|
49637
|
+
* (and the classifier defaults to `'rebuild'`) when the runtime's image is not
|
|
49638
|
+
* a CDK docker-image asset, or when the asset manifest lookup misses.
|
|
49639
|
+
*
|
|
49640
|
+
* For a CodeConfiguration (`fromCodeAsset`) runtime we treat the bundle's
|
|
49641
|
+
* `codeAssetHash` as the asset hash + the staged source directory as
|
|
49642
|
+
* `newAssetSourceDir`, and synthesize a `Dockerfile` basename that never
|
|
49643
|
+
* matches a real file (the generated Dockerfile lives in a build tmpdir, not
|
|
49644
|
+
* the source tree, so chokidar can't observe an edit to it). A `fromS3` bundle
|
|
49645
|
+
* has no local source tree, so it returns `undefined` and the classifier
|
|
49646
|
+
* defaults to rebuild.
|
|
49647
|
+
*
|
|
49648
|
+
* NOTE: `loadAgentCoreAssetContext` is NOT exported from `cdk-local/internal`
|
|
49649
|
+
* (verified against `node_modules/cdk-local/dist/internal.d.ts`), so this
|
|
49650
|
+
* helper is copied locally; `deriveOldAssetHash` is copied for the same reason.
|
|
49651
|
+
*
|
|
49652
|
+
* @internal — exported for the watch loop's classifier dispatch test.
|
|
49653
|
+
*/
|
|
49654
|
+
async function loadAgentCoreAssetContext(args) {
|
|
49655
|
+
const { resolvedTarget, resolved, stacks, cdkOutDir, assetLoader, oldAssetHash } = args;
|
|
49656
|
+
const newCandidate = pickAgentCoreCandidateStack(resolvedTarget, stacks);
|
|
49657
|
+
if (!newCandidate) return void 0;
|
|
49658
|
+
if (resolved.codeArtifact) {
|
|
49659
|
+
if (resolved.codeArtifact.s3Source) return void 0;
|
|
49660
|
+
const manifest = await assetLoader.loadManifest(cdkOutDir, newCandidate.stackName);
|
|
49661
|
+
if (!manifest) return void 0;
|
|
49662
|
+
const asset = assetLoader.getFileAssets(manifest).get(resolved.codeArtifact.codeAssetHash);
|
|
49663
|
+
if (!asset) return void 0;
|
|
49664
|
+
const sourceDir = assetLoader.getAssetSourcePath(cdkOutDir, asset);
|
|
49665
|
+
return {
|
|
49666
|
+
...oldAssetHash !== void 0 && { oldAssetHash },
|
|
49667
|
+
newAssetHash: resolved.codeArtifact.codeAssetHash,
|
|
49668
|
+
newAssetSourceDir: sourceDir,
|
|
49669
|
+
dockerFile: ".cdkd-agentcore-generated-Dockerfile"
|
|
49670
|
+
};
|
|
49671
|
+
}
|
|
49672
|
+
if (resolved.containerUri === void 0) return void 0;
|
|
49673
|
+
const manifest = await assetLoader.loadManifest(cdkOutDir, newCandidate.stackName);
|
|
49674
|
+
if (!manifest) return void 0;
|
|
49675
|
+
const dockerImageEntry = getDockerImageBySourceHash(manifest, resolved.containerUri);
|
|
49676
|
+
if (!dockerImageEntry) return void 0;
|
|
49677
|
+
const newDockerImage = dockerImageEntry.asset;
|
|
49678
|
+
if (!newDockerImage.source.directory) return void 0;
|
|
49679
|
+
const newAssetSourceDir = resolve(cdkOutDir, newDockerImage.source.directory);
|
|
49680
|
+
return {
|
|
49681
|
+
...oldAssetHash !== void 0 && { oldAssetHash },
|
|
49682
|
+
newAssetHash: dockerImageEntry.hash,
|
|
49683
|
+
newAssetSourceDir,
|
|
49684
|
+
dockerFile: basename(newDockerImage.source.dockerFile ?? "Dockerfile")
|
|
49685
|
+
};
|
|
49686
|
+
}
|
|
49687
|
+
/**
|
|
49688
|
+
* Derive the OLD (pre-reload) asset hash for the classifier's `oldAssetHash`
|
|
49689
|
+
* field. Code-artifact runtimes carry the hash on the boot-time
|
|
49690
|
+
* `resolved.codeArtifact.codeAssetHash`; container runtimes need a manifest
|
|
49691
|
+
* lookup against the previous-synth stacks to extract it. Returns `undefined`
|
|
49692
|
+
* when the OLD hash can't be derived — the classifier treats `undefined` as
|
|
49693
|
+
* "force rebuild", which is the conservative default.
|
|
49694
|
+
*
|
|
49695
|
+
* NOT exported from `cdk-local/internal`, so copied locally.
|
|
49696
|
+
*/
|
|
49697
|
+
async function deriveOldAssetHash(args) {
|
|
49698
|
+
const { resolvedTarget, resolved, stacks, cdkOutDir, assetLoader } = args;
|
|
49699
|
+
if (resolved.codeArtifact) return resolved.codeArtifact.codeAssetHash;
|
|
49700
|
+
if (resolved.containerUri === void 0) return void 0;
|
|
49701
|
+
const candidate = pickAgentCoreCandidateStack(resolvedTarget, stacks);
|
|
49702
|
+
if (!candidate) return void 0;
|
|
49703
|
+
const manifest = await assetLoader.loadManifest(cdkOutDir, candidate.stackName);
|
|
49704
|
+
if (!manifest) return void 0;
|
|
49705
|
+
return getDockerImageBySourceHash(manifest, resolved.containerUri)?.hash;
|
|
49706
|
+
}
|
|
49707
|
+
|
|
49283
49708
|
//#endregion
|
|
49284
49709
|
//#region src/cli/commands/local-invoke-agentcore.ts
|
|
49285
49710
|
/**
|
|
@@ -49361,7 +49786,7 @@ async function localInvokeAgentCoreCommand(target, options) {
|
|
|
49361
49786
|
noun: "AgentCore Runtimes",
|
|
49362
49787
|
onMissing: () => new CdkdError(`${getEmbedConfig().cliName} invoke-agentcore requires a <target> (an AgentCore Runtime display path or logical ID). Run \`${getEmbedConfig().cliName} list\` to see them, or run it in a TTY to pick interactively.`, "LOCAL_INVOKE_AGENTCORE_TARGET_REQUIRED")
|
|
49363
49788
|
});
|
|
49364
|
-
const candidate = pickAgentCoreCandidateStack(resolvedTarget, stacks);
|
|
49789
|
+
const candidate = pickAgentCoreCandidateStack$1(resolvedTarget, stacks);
|
|
49365
49790
|
stateProvider = createLocalStateProvider$1(options, candidate?.stackName ?? "", await resolveCfnFallbackRegion(options, candidate?.region));
|
|
49366
49791
|
const { context: imageContext, loaded: loadedState } = stateProvider && candidate ? await buildAgentCoreImageContext(candidate, stateProvider, options) : {
|
|
49367
49792
|
context: void 0,
|
|
@@ -49385,28 +49810,24 @@ async function localInvokeAgentCoreCommand(target, options) {
|
|
|
49385
49810
|
logger.info(`${resolved.protocol} runtime: invoking the local container's ${pathLabel} directly (vanilla ${resolved.protocol}). An inbound JWT / --bearer-token is an AgentCore managed-plane concern and is not applied locally.`);
|
|
49386
49811
|
}
|
|
49387
49812
|
} else authorization = await resolveInboundAuthorization(resolved, options);
|
|
49388
|
-
|
|
49389
|
-
const
|
|
49390
|
-
|
|
49391
|
-
const hostPort = await pickFreePort();
|
|
49813
|
+
const watchEligible = isAgentCoreWatchEligible(resolved.protocol);
|
|
49814
|
+
const watchActive = options.watch === true && watchEligible;
|
|
49815
|
+
if (options.watch === true && !watchEligible) logger.warn(`--watch is supported only on the HTTP / AGUI protocols; ignoring it for this ${resolved.protocol} runtime (its single shot runs once and exits).`);
|
|
49392
49816
|
const containerHost = options.containerHost;
|
|
49393
|
-
const
|
|
49394
|
-
|
|
49395
|
-
|
|
49396
|
-
|
|
49397
|
-
|
|
49398
|
-
|
|
49399
|
-
|
|
49400
|
-
|
|
49401
|
-
|
|
49402
|
-
|
|
49403
|
-
host: containerHost,
|
|
49404
|
-
platform: options.platform,
|
|
49405
|
-
name: containerName,
|
|
49406
|
-
...containerPort !== void 0 && { containerPort },
|
|
49407
|
-
...sensitiveEnvKeys.size > 0 && { sensitiveEnvKeys }
|
|
49817
|
+
const boot = await bootAgentCoreContainer({
|
|
49818
|
+
resolved,
|
|
49819
|
+
options,
|
|
49820
|
+
profileCredentials,
|
|
49821
|
+
profileCredsFile,
|
|
49822
|
+
stateProvider,
|
|
49823
|
+
loadedState,
|
|
49824
|
+
imageContext,
|
|
49825
|
+
isMcp,
|
|
49826
|
+
isA2a
|
|
49408
49827
|
});
|
|
49409
|
-
|
|
49828
|
+
containerId = boot.containerId;
|
|
49829
|
+
const hostPort = boot.hostPort;
|
|
49830
|
+
stopLogs = boot.stopLogs;
|
|
49410
49831
|
sigintHandler = () => {
|
|
49411
49832
|
cleanup().then(() => process.exit(130));
|
|
49412
49833
|
};
|
|
@@ -49423,19 +49844,145 @@ async function localInvokeAgentCoreCommand(target, options) {
|
|
|
49423
49844
|
emitA2aResult(a2a);
|
|
49424
49845
|
} else if (options.ws) {
|
|
49425
49846
|
const interactive = process.stdin.isTTY === true;
|
|
49426
|
-
await
|
|
49427
|
-
|
|
49428
|
-
|
|
49429
|
-
|
|
49430
|
-
|
|
49431
|
-
|
|
49432
|
-
|
|
49433
|
-
|
|
49434
|
-
|
|
49847
|
+
if (watchActive) await runAgentCoreWatchLoop({
|
|
49848
|
+
containerHost,
|
|
49849
|
+
hostPort,
|
|
49850
|
+
options,
|
|
49851
|
+
resolvedTarget,
|
|
49852
|
+
resolved,
|
|
49853
|
+
synthesizer,
|
|
49854
|
+
synthOpts,
|
|
49855
|
+
stacks,
|
|
49856
|
+
invokeOnce: async ({ hostPort: port, abortSignal, firstIteration }) => {
|
|
49857
|
+
const frameSource = interactive ? readStdinLines() : void 0;
|
|
49858
|
+
logger.info(firstIteration ? interactive ? "Opening the agent /ws WebSocket (interactive — stdin lines = follow-up frames; Ctrl-D to end)..." : "Opening the agent /ws WebSocket and streaming frames..." : interactive ? "Re-opening the agent /ws WebSocket (interactive) against the rebuilt container..." : "Re-opening the agent /ws WebSocket against the rebuilt container...");
|
|
49859
|
+
const wsResult = await invokeAgentCoreWs(containerHost, port, event, {
|
|
49860
|
+
sessionId,
|
|
49861
|
+
timeoutMs: options.timeout,
|
|
49862
|
+
onMessage: wrapWsOnMessage((text) => process.stdout.write(text), interactive),
|
|
49863
|
+
abortSignal,
|
|
49864
|
+
...authorization && { authorization },
|
|
49865
|
+
...frameSource && { frameSource }
|
|
49866
|
+
});
|
|
49867
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
49868
|
+
emitWsResult(wsResult);
|
|
49869
|
+
return { pendingReload: abortSignal.aborted };
|
|
49870
|
+
},
|
|
49871
|
+
rebuild: async () => {
|
|
49872
|
+
const result = await rebuildAgentCoreContainer({
|
|
49873
|
+
cleanupBefore: async () => {
|
|
49874
|
+
if (stopLogs) {
|
|
49875
|
+
try {
|
|
49876
|
+
stopLogs();
|
|
49877
|
+
} catch {}
|
|
49878
|
+
stopLogs = void 0;
|
|
49879
|
+
}
|
|
49880
|
+
if (containerId) {
|
|
49881
|
+
await removeContainer(containerId);
|
|
49882
|
+
containerId = void 0;
|
|
49883
|
+
}
|
|
49884
|
+
},
|
|
49885
|
+
resolvedTarget,
|
|
49886
|
+
options,
|
|
49887
|
+
synthesizer,
|
|
49888
|
+
synthOpts,
|
|
49889
|
+
profileCredentials,
|
|
49890
|
+
profileCredsFile,
|
|
49891
|
+
stateProvider,
|
|
49892
|
+
isMcp,
|
|
49893
|
+
isA2a
|
|
49894
|
+
});
|
|
49895
|
+
containerId = result.containerId;
|
|
49896
|
+
stopLogs = result.stopLogs;
|
|
49897
|
+
return {
|
|
49898
|
+
containerId: result.containerId,
|
|
49899
|
+
hostPort: result.hostPort,
|
|
49900
|
+
stacks: result.stacks
|
|
49901
|
+
};
|
|
49902
|
+
},
|
|
49903
|
+
softReload: async (newSourceDir) => {
|
|
49904
|
+
if (!containerId) throw new CdkdError("softReload: no live container to docker cp / docker restart into.", "LOCAL_INVOKE_AGENTCORE_WATCH_NO_CONTAINER");
|
|
49905
|
+
await softReloadAgentContainer(containerId, newSourceDir);
|
|
49906
|
+
const { stacks: newStacks } = await synthesizer.synthesize(synthOpts);
|
|
49907
|
+
return { stacks: newStacks };
|
|
49908
|
+
}
|
|
49435
49909
|
});
|
|
49436
|
-
|
|
49437
|
-
|
|
49438
|
-
|
|
49910
|
+
else {
|
|
49911
|
+
await waitForAgentCorePing(containerHost, hostPort);
|
|
49912
|
+
const frameSource = interactive ? readStdinLines() : void 0;
|
|
49913
|
+
logger.info(interactive ? "Opening the agent /ws WebSocket (interactive — stdin lines = follow-up frames; Ctrl-D to end)..." : "Opening the agent /ws WebSocket and streaming frames...");
|
|
49914
|
+
const wsResult = await invokeAgentCoreWs(containerHost, hostPort, event, {
|
|
49915
|
+
sessionId,
|
|
49916
|
+
timeoutMs: options.timeout,
|
|
49917
|
+
onMessage: wrapWsOnMessage((text) => process.stdout.write(text), interactive),
|
|
49918
|
+
...authorization && { authorization },
|
|
49919
|
+
...frameSource && { frameSource }
|
|
49920
|
+
});
|
|
49921
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
49922
|
+
emitWsResult(wsResult);
|
|
49923
|
+
}
|
|
49924
|
+
} else if (watchActive) await runAgentCoreWatchLoop({
|
|
49925
|
+
containerHost,
|
|
49926
|
+
hostPort,
|
|
49927
|
+
options,
|
|
49928
|
+
resolvedTarget,
|
|
49929
|
+
resolved,
|
|
49930
|
+
synthesizer,
|
|
49931
|
+
synthOpts,
|
|
49932
|
+
stacks,
|
|
49933
|
+
invokeOnce: async ({ hostPort: port, abortSignal }) => {
|
|
49934
|
+
const additionalHeaders = await buildSigV4HeadersIfRequested(options, resolved, loadedState, containerHost, port, event, sessionId);
|
|
49935
|
+
const result = await invokeAgentCore(containerHost, port, event, {
|
|
49936
|
+
sessionId,
|
|
49937
|
+
timeoutMs: options.timeout,
|
|
49938
|
+
onChunk: (text) => process.stdout.write(text),
|
|
49939
|
+
...authorization && { authorization },
|
|
49940
|
+
...additionalHeaders && { additionalHeaders }
|
|
49941
|
+
});
|
|
49942
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
49943
|
+
emitResult(result);
|
|
49944
|
+
return { pendingReload: abortSignal.aborted };
|
|
49945
|
+
},
|
|
49946
|
+
rebuild: async () => {
|
|
49947
|
+
const result = await rebuildAgentCoreContainer({
|
|
49948
|
+
cleanupBefore: async () => {
|
|
49949
|
+
if (stopLogs) {
|
|
49950
|
+
try {
|
|
49951
|
+
stopLogs();
|
|
49952
|
+
} catch {}
|
|
49953
|
+
stopLogs = void 0;
|
|
49954
|
+
}
|
|
49955
|
+
if (containerId) {
|
|
49956
|
+
await removeContainer(containerId);
|
|
49957
|
+
containerId = void 0;
|
|
49958
|
+
}
|
|
49959
|
+
},
|
|
49960
|
+
resolvedTarget,
|
|
49961
|
+
options,
|
|
49962
|
+
synthesizer,
|
|
49963
|
+
synthOpts,
|
|
49964
|
+
profileCredentials,
|
|
49965
|
+
profileCredsFile,
|
|
49966
|
+
stateProvider,
|
|
49967
|
+
isMcp,
|
|
49968
|
+
isA2a
|
|
49969
|
+
});
|
|
49970
|
+
containerId = result.containerId;
|
|
49971
|
+
stopLogs = result.stopLogs;
|
|
49972
|
+
return {
|
|
49973
|
+
containerId: result.containerId,
|
|
49974
|
+
hostPort: result.hostPort,
|
|
49975
|
+
stacks: result.stacks
|
|
49976
|
+
};
|
|
49977
|
+
},
|
|
49978
|
+
softReload: async (newSourceDir) => {
|
|
49979
|
+
if (!containerId) throw new CdkdError("softReload: no live container to docker cp / docker restart into.", "LOCAL_INVOKE_AGENTCORE_WATCH_NO_CONTAINER");
|
|
49980
|
+
await softReloadAgentContainer(containerId, newSourceDir);
|
|
49981
|
+
const { stacks: newStacks } = await synthesizer.synthesize(synthOpts);
|
|
49982
|
+
return { stacks: newStacks };
|
|
49983
|
+
}
|
|
49984
|
+
});
|
|
49985
|
+
else {
|
|
49439
49986
|
await waitForAgentCorePing(containerHost, hostPort);
|
|
49440
49987
|
const additionalHeaders = await buildSigV4HeadersIfRequested(options, resolved, loadedState, containerHost, hostPort, event, sessionId);
|
|
49441
49988
|
const result = await invokeAgentCore(containerHost, hostPort, event, {
|
|
@@ -49454,6 +50001,102 @@ async function localInvokeAgentCoreCommand(target, options) {
|
|
|
49454
50001
|
}
|
|
49455
50002
|
}
|
|
49456
50003
|
/**
|
|
50004
|
+
* `--watch` is supported only on the long-running HTTP / AGUI paths (the
|
|
50005
|
+
* `--ws` session AND the default one-shot `POST /invocations`, which the
|
|
50006
|
+
* reload re-runs). The MCP `POST /mcp` and A2A `POST /` paths run once and
|
|
50007
|
+
* exit with no reconnect surface, so `--watch` is a no-op WARN for them.
|
|
50008
|
+
*
|
|
50009
|
+
* Exported so a unit test can lock the eligibility contract without driving
|
|
50010
|
+
* the full synth + docker pipeline.
|
|
50011
|
+
*/
|
|
50012
|
+
function isAgentCoreWatchEligible(protocol) {
|
|
50013
|
+
return protocol !== AGENTCORE_MCP_PROTOCOL && protocol !== AGENTCORE_A2A_PROTOCOL;
|
|
50014
|
+
}
|
|
50015
|
+
/**
|
|
50016
|
+
* Cold-boot (and rebuild) the agent container from an already-resolved runtime
|
|
50017
|
+
* descriptor: resolve an intrinsic fromS3 bucket, resolve the image, build the
|
|
50018
|
+
* container env, pick a free port, `docker run`, and start the log stream.
|
|
50019
|
+
*
|
|
50020
|
+
* Extracted from the main command so the `--watch` rebuild callback can re-run
|
|
50021
|
+
* the identical sequence against a fresh synth. The non-watch one-shot / `--ws`
|
|
50022
|
+
* paths and the watch loop both go through this helper, so the container boot
|
|
50023
|
+
* sequence is single-sourced.
|
|
50024
|
+
*
|
|
50025
|
+
* Exported so a unit test can drive the boot shape without the full command.
|
|
50026
|
+
*/
|
|
50027
|
+
async function bootAgentCoreContainer(args) {
|
|
50028
|
+
const logger = getLogger();
|
|
50029
|
+
const { resolved, options, profileCredentials, profileCredsFile, stateProvider, loadedState, imageContext, isMcp, isA2a } = args;
|
|
50030
|
+
await resolveFromS3BucketIntrinsic(resolved, stateProvider, loadedState, imageContext);
|
|
50031
|
+
const image = await resolveAgentCoreImage(resolved, options, loadedState);
|
|
50032
|
+
const { env: dockerEnv, sensitiveEnvKeys } = await buildContainerEnv(resolved, options, profileCredentials, profileCredsFile, stateProvider, loadedState, imageContext);
|
|
50033
|
+
const hostPort = await pickFreePort();
|
|
50034
|
+
const containerHost = options.containerHost;
|
|
50035
|
+
const containerName = `${getEmbedConfig().resourceNamePrefix}-agentcore-${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
|
|
50036
|
+
const containerPort = isMcp ? MCP_CONTAINER_PORT : isA2a ? A2A_CONTAINER_PORT : void 0;
|
|
50037
|
+
const containerPortLabel = isMcp ? `${MCP_CONTAINER_PORT}${MCP_PATH}` : isA2a ? `${A2A_CONTAINER_PORT}${A2A_PATH}` : "8080";
|
|
50038
|
+
logger.info(`Starting agent container (image=${image}, port=${hostPort} -> ${containerPortLabel})...`);
|
|
50039
|
+
const containerId = await runDetached({
|
|
50040
|
+
image,
|
|
50041
|
+
mounts: [],
|
|
50042
|
+
env: dockerEnv,
|
|
50043
|
+
cmd: [],
|
|
50044
|
+
hostPort,
|
|
50045
|
+
host: containerHost,
|
|
50046
|
+
platform: options.platform,
|
|
50047
|
+
name: containerName,
|
|
50048
|
+
...containerPort !== void 0 && { containerPort },
|
|
50049
|
+
...sensitiveEnvKeys.size > 0 && { sensitiveEnvKeys }
|
|
50050
|
+
});
|
|
50051
|
+
let stopLogs;
|
|
50052
|
+
try {
|
|
50053
|
+
stopLogs = streamLogs(containerId);
|
|
50054
|
+
} catch (err) {
|
|
50055
|
+
await removeContainer(containerId).catch(() => {});
|
|
50056
|
+
throw err;
|
|
50057
|
+
}
|
|
50058
|
+
return {
|
|
50059
|
+
containerId,
|
|
50060
|
+
hostPort,
|
|
50061
|
+
stopLogs
|
|
50062
|
+
};
|
|
50063
|
+
}
|
|
50064
|
+
/**
|
|
50065
|
+
* Rebuild the agent container for the `--watch` loop: tear down the OLD
|
|
50066
|
+
* container (via the supplied `cleanupBefore`), re-synth the CDK app, re-pick
|
|
50067
|
+
* the candidate stack + re-resolve the runtime (so a new asset hash / env /
|
|
50068
|
+
* image is picked up), then re-run {@link bootAgentCoreContainer}. The inbound
|
|
50069
|
+
* auth (bearer token / OIDC discovery URL) is NOT re-resolved — it is stable
|
|
50070
|
+
* across a CDK source edit, so the caller threads its boot-time `authorization`
|
|
50071
|
+
* back into the next invocation.
|
|
50072
|
+
*
|
|
50073
|
+
* Exported so a unit test can drive the rebuild shape without the full command.
|
|
50074
|
+
*/
|
|
50075
|
+
async function rebuildAgentCoreContainer(args) {
|
|
50076
|
+
const { cleanupBefore, resolvedTarget, options, synthesizer, synthOpts, profileCredentials, profileCredsFile, stateProvider, isMcp, isA2a } = args;
|
|
50077
|
+
await cleanupBefore();
|
|
50078
|
+
const { stacks: newStacks } = await synthesizer.synthesize(synthOpts);
|
|
50079
|
+
const newCandidate = pickAgentCoreCandidateStack$1(resolvedTarget, newStacks);
|
|
50080
|
+
const { context: newImageContext, loaded: newLoaded } = stateProvider && newCandidate ? await buildAgentCoreImageContext(newCandidate, stateProvider, options) : {
|
|
50081
|
+
context: void 0,
|
|
50082
|
+
loaded: void 0
|
|
50083
|
+
};
|
|
50084
|
+
return {
|
|
50085
|
+
...await bootAgentCoreContainer({
|
|
50086
|
+
resolved: resolveAgentCoreTarget(resolvedTarget, newStacks, newImageContext),
|
|
50087
|
+
options,
|
|
50088
|
+
profileCredentials,
|
|
50089
|
+
profileCredsFile,
|
|
50090
|
+
stateProvider,
|
|
50091
|
+
loadedState: newLoaded,
|
|
50092
|
+
imageContext: newImageContext,
|
|
50093
|
+
isMcp,
|
|
50094
|
+
isA2a
|
|
50095
|
+
}),
|
|
50096
|
+
stacks: newStacks
|
|
50097
|
+
};
|
|
50098
|
+
}
|
|
50099
|
+
/**
|
|
49457
50100
|
* Enforce the runtime's inbound JWT authorizer (when declared) and return
|
|
49458
50101
|
* the `Authorization` header to forward to `/invocations`.
|
|
49459
50102
|
*
|
|
@@ -50107,7 +50750,7 @@ function readEnvOverridesFile$1(filePath) {
|
|
|
50107
50750
|
return parsed;
|
|
50108
50751
|
}
|
|
50109
50752
|
function createLocalInvokeAgentCoreCommand() {
|
|
50110
|
-
const cmd = new Command("invoke-agentcore").description("Run a Bedrock AgentCore Runtime container locally and invoke it once over its protocol contract: HTTP (POST /invocations + GET /ping on 8080) or MCP (POST /mcp Streamable HTTP on 8000). Resolves the AWS::BedrockAgentCore::Runtime, pulls/builds its container, injects env vars + AWS credentials, and prints the response. For an MCP runtime, runs the session handshake then sends one JSON-RPC request (tools/list by default, or the method/params from --event). Target accepts a CDK display path (MyStack/MyAgent) or stack-qualified logical ID (MyStack:MyAgentRuntime1234). Single-stack apps may omit the stack prefix. Omit <target> in an interactive terminal to pick from a list. Supports the container artifact and the CodeConfiguration managed-runtime artifact (fromCodeAsset, built from source) on the HTTP + MCP protocols; the agent calls real AWS for managed services.").argument("[target]", "CDK display path or stack-qualified logical ID of the AgentCore Runtime to invoke (omit to pick interactively in a TTY)").addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}})")).addOption(new Option("--session-id <id>", "AgentCore runtime session id header value (default: a random UUID)")).addOption(new Option("--ws", "Stream over the HTTP-protocol agent's bidirectional /ws WebSocket endpoint (on 8080) instead of POST /invocations: send --event as the first frame and print every received frame to stdout until the agent closes. When stdin is a TTY, auto-enters a REPL — each typed line is sent as a follow-up text frame until Ctrl-D / agent close; pipe from /dev/null to force one-shot in a TTY. Ignored for an MCP runtime.").default(false)).addOption(new Option("--bearer-token <jwt>", "Bearer JWT to present when the runtime declares a customJwtAuthorizer. Verified against the runtime OIDC discovery URL (signature / issuer / expiry / audience) before the container starts, then forwarded to /invocations as Authorization: Bearer <jwt>.")).addOption(new Option("--no-verify-auth", "Skip inbound JWT verification even when the runtime declares a customJwtAuthorizer (local-dev escape hatch). A --bearer-token, if given, is still forwarded.")).addOption(new Option("--sigv4", "Sign the /invocations POST with AWS SigV4 (service bedrock-agentcore) using the resolved credentials, matching the cloud default when the runtime declares no customJwtAuthorizer. Opt-in: default unsigned. Mutually exclusive with --bearer-token; ignored on a JWT-protected runtime.").default(false)).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64)").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for the local-build path")).addOption(new Option("--no-build", "Skip docker build on the local-asset path (use the previously-built tag). No-op for the ECR / registry pull paths.")).addOption(new Option("--container-host <host>", "Host to bind the agent port to").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Per-request timeout in milliseconds. Applied to POST /invocations, POST /mcp, and the /ws open-to-close window. Raise this for long-running agent calls that exceed the default.").default(12e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the runtime's execution role and forward STS-issued temp credentials to the container so the agent runs with the deployed role. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's RoleArn when it is a literal ARN; (3) `--no-assume-role` opts out. Off by default — the developer's shell credentials are forwarded unchanged.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-state", "Load cdkd's S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::ImportValue in env vars with the deployed physical IDs / cross-stack exports. Mutually exclusive with --from-cfn-stack.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. For CDK apps deployed via the upstream CDK CLI. Bare form uses the resolved stack name; pass an explicit value when the CFn stack name differs. Mutually exclusive with --from-state.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).action(withErrorHandling(async (target, options) => {
|
|
50753
|
+
const cmd = new Command("invoke-agentcore").description("Run a Bedrock AgentCore Runtime container locally and invoke it once over its protocol contract: HTTP (POST /invocations + GET /ping on 8080) or MCP (POST /mcp Streamable HTTP on 8000). Resolves the AWS::BedrockAgentCore::Runtime, pulls/builds its container, injects env vars + AWS credentials, and prints the response. For an MCP runtime, runs the session handshake then sends one JSON-RPC request (tools/list by default, or the method/params from --event). Target accepts a CDK display path (MyStack/MyAgent) or stack-qualified logical ID (MyStack:MyAgentRuntime1234). Single-stack apps may omit the stack prefix. Omit <target> in an interactive terminal to pick from a list. Supports the container artifact and the CodeConfiguration managed-runtime artifact (fromCodeAsset, built from source) on the HTTP + MCP protocols; the agent calls real AWS for managed services.").argument("[target]", "CDK display path or stack-qualified logical ID of the AgentCore Runtime to invoke (omit to pick interactively in a TTY)").addOption(new Option("-e, --event <file>", "JSON event payload file (default: {})")).addOption(new Option("--event-stdin", "Read event JSON from stdin").default(false)).addOption(new Option("--env-vars <file>", "JSON env-var overrides (SAM-compatible: {\"LogicalId\":{\"KEY\":\"VALUE\"}})")).addOption(new Option("--session-id <id>", "AgentCore runtime session id header value (default: a random UUID)")).addOption(new Option("--ws", "Stream over the HTTP-protocol agent's bidirectional /ws WebSocket endpoint (on 8080) instead of POST /invocations: send --event as the first frame and print every received frame to stdout until the agent closes. When stdin is a TTY, auto-enters a REPL — each typed line is sent as a follow-up text frame until Ctrl-D / agent close; pipe from /dev/null to force one-shot in a TTY. Ignored for an MCP runtime.").default(false)).addOption(new Option("--watch", "Re-synth and reload the agent container on CDK source changes (follows cdk-local #270). Supported on the HTTP / AGUI protocols, both the --ws session AND the default one-shot POST /invocations (the reload re-runs the single shot). An interpreted-language source edit inside a CodeConfiguration source tree takes a soft-reload fast path (docker cp + docker restart, no rebuild); Dockerfile / compiled-source / asset-hash-changed / ambiguous edits force a full rebuild. For MCP / A2A runtimes --watch is a no-op WARN and the single shot proceeds.").default(false)).addOption(new Option("--bearer-token <jwt>", "Bearer JWT to present when the runtime declares a customJwtAuthorizer. Verified against the runtime OIDC discovery URL (signature / issuer / expiry / audience) before the container starts, then forwarded to /invocations as Authorization: Bearer <jwt>.")).addOption(new Option("--no-verify-auth", "Skip inbound JWT verification even when the runtime declares a customJwtAuthorizer (local-dev escape hatch). A --bearer-token, if given, is still forwarded.")).addOption(new Option("--sigv4", "Sign the /invocations POST with AWS SigV4 (service bedrock-agentcore) using the resolved credentials, matching the cloud default when the runtime declares no customJwtAuthorizer. Opt-in: default unsigned. Mutually exclusive with --bearer-token; ignored on a JWT-protected runtime.").default(false)).addOption(new Option("--platform <platform>", "docker --platform for the agent container (linux/amd64 or linux/arm64)").choices(["linux/amd64", "linux/arm64"]).default("linux/arm64")).addOption(new Option("--no-pull", "Skip docker pull (use cached image) — no-op for the local-build path")).addOption(new Option("--no-build", "Skip docker build on the local-asset path (use the previously-built tag). No-op for the ECR / registry pull paths.")).addOption(new Option("--container-host <host>", "Host to bind the agent port to").default("127.0.0.1")).addOption(new Option("--timeout <ms>", "Per-request timeout in milliseconds. Applied to POST /invocations, POST /mcp, and the /ws open-to-close window. Raise this for long-running agent calls that exceed the default.").default(12e4).argParser(parseTimeoutMs)).addOption(new Option("--assume-role [arn]", "Assume the runtime's execution role and forward STS-issued temp credentials to the container so the agent runs with the deployed role. Three forms: (1) `--assume-role <arn>` assumes the explicit ARN; (2) `--assume-role` (bare) uses the runtime's RoleArn when it is a literal ARN; (3) `--no-assume-role` opts out. Off by default — the developer's shell credentials are forwarded unchanged.")).addOption(new Option("--ecr-role-arn <arn>", "Role ARN to assume before authenticating against ECR for cross-account / centralized registries. Same-account / same-region pulls do not need this flag.")).addOption(new Option("--from-state", "Load cdkd's S3 state for the target stack and substitute Ref / Fn::GetAtt / Fn::Sub / Fn::ImportValue in env vars with the deployed physical IDs / cross-stack exports. Mutually exclusive with --from-cfn-stack.").default(false)).addOption(new Option("--from-cfn-stack [cfn-stack-name]", "Read a deployed CloudFormation stack via DescribeStackResources and substitute Ref / Fn::ImportValue in env vars with the deployed physical IDs / exports. For CDK apps deployed via the upstream CDK CLI. Bare form uses the resolved stack name; pass an explicit value when the CFn stack name differs. Mutually exclusive with --from-state.")).addOption(new Option("--stack-region <region>", "Region of the state record to read. Used with --from-cfn-stack as the CFn client region.")).action(withErrorHandling(async (target, options) => {
|
|
50111
50754
|
await localInvokeAgentCoreCommand(target, options);
|
|
50112
50755
|
}));
|
|
50113
50756
|
[
|
|
@@ -52053,7 +52696,7 @@ function reorderArgs(argv) {
|
|
|
52053
52696
|
*/
|
|
52054
52697
|
async function main() {
|
|
52055
52698
|
const program = new Command();
|
|
52056
|
-
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.
|
|
52699
|
+
program.name("cdkd").description("CDK Direct - Deploy AWS CDK apps directly via SDK/Cloud Control API").version("0.207.1");
|
|
52057
52700
|
program.addCommand(createBootstrapCommand());
|
|
52058
52701
|
program.addCommand(createSynthCommand());
|
|
52059
52702
|
program.addCommand(createListCommand());
|