@deployfoundation/foundation-deploy 0.2.40 → 0.2.42

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/bin/app.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  FoundationPages,
9
9
  FoundationPipeline,
10
10
  NewsletterStack
11
- } from "../chunk-fwqmac5n.js";
11
+ } from "../chunk-sgh3agpa.js";
12
12
  import {
13
13
  adminsFor,
14
14
  instanceNames,
@@ -18,9 +18,43 @@ import {
18
18
  import"../chunk-dz91avrb.js";
19
19
 
20
20
  // bin/app.ts
21
+ import * as cdk from "aws-cdk-lib";
22
+
23
+ // src/deploy/repo-path.ts
21
24
  import { execFileSync } from "node:child_process";
22
25
  import { basename, dirname, relative, resolve, sep } from "node:path";
23
- import * as cdk from "aws-cdk-lib";
26
+ function inside(root, absolute) {
27
+ const rel = relative(root, absolute);
28
+ return rel !== "" && !rel.startsWith("..") ? rel.split(sep).join("/") : undefined;
29
+ }
30
+ function gitTopLevel(directory) {
31
+ try {
32
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
33
+ cwd: directory,
34
+ stdio: ["ignore", "pipe", "ignore"]
35
+ }).toString().trim();
36
+ } catch {
37
+ return;
38
+ }
39
+ }
40
+ function repoRelativePath(filePath, env = process.env, topLevel = gitTopLevel) {
41
+ const absolute = resolve(filePath);
42
+ const codebuildRoot = env.CODEBUILD_SRC_DIR;
43
+ if (codebuildRoot !== undefined && codebuildRoot !== "") {
44
+ const rel = inside(resolve(codebuildRoot), absolute);
45
+ if (rel !== undefined)
46
+ return rel;
47
+ }
48
+ const top = topLevel(dirname(absolute));
49
+ if (top !== undefined) {
50
+ const rel = inside(top, absolute);
51
+ if (rel !== undefined)
52
+ return rel;
53
+ }
54
+ return basename(absolute);
55
+ }
56
+
57
+ // bin/app.ts
24
58
  var app = new cdk.App;
25
59
  var instanceFilePath = app.node.tryGetContext("instanceFile");
26
60
  if (instanceFilePath === undefined || instanceFilePath === "")
@@ -109,19 +143,6 @@ if (instance.newsletter.enabled)
109
143
  });
110
144
  cdk.Tags.of(app).add("project", instance.naming.secretPrefix);
111
145
  app.synth();
112
- function repoRelativePath(filePath) {
113
- const absolute = resolve(filePath);
114
- try {
115
- const top = execFileSync("git", ["rev-parse", "--show-toplevel"], {
116
- cwd: dirname(absolute),
117
- stdio: ["ignore", "pipe", "ignore"]
118
- }).toString().trim();
119
- const rel = relative(top, absolute);
120
- if (rel !== "" && !rel.startsWith(".."))
121
- return rel.split(sep).join("/");
122
- } catch {}
123
- return basename(absolute);
124
- }
125
146
  export {
126
147
  env
127
148
  };
@@ -1043,6 +1043,21 @@ var SMOKE_EXPECT = ['"authenticated":true', '"config_source":"s3"'];
1043
1043
  function smokeSessionId(prefix) {
1044
1044
  return `${prefix}-${Math.floor(Date.now() / 1000)}`.padEnd(40, "0");
1045
1045
  }
1046
+ async function stopSmokeSession(runner, opts, log) {
1047
+ try {
1048
+ await runner.capture([
1049
+ "bedrock-agentcore",
1050
+ "stop-runtime-session",
1051
+ "--agent-runtime-arn",
1052
+ opts.arn,
1053
+ ...opts.qualifier === undefined ? [] : ["--qualifier", opts.qualifier],
1054
+ "--runtime-session-id",
1055
+ opts.sessionId
1056
+ ]);
1057
+ } catch {
1058
+ log(" (could not stop the smoke session; it ends at the idle timeout)");
1059
+ }
1060
+ }
1046
1061
  async function smokeInvoke(runner, opts) {
1047
1062
  const log = opts.log ?? ((line) => console.log(line));
1048
1063
  const payload = opts.payload ?? '{"_smoke_test":true}';
@@ -1070,6 +1085,7 @@ async function smokeInvoke(runner, opts) {
1070
1085
  } finally {
1071
1086
  await rm(dir, { recursive: true, force: true });
1072
1087
  }
1088
+ await stopSmokeSession(runner, opts, log);
1073
1089
  log(` ${out.replaceAll(`
1074
1090
  `, `
1075
1091
  `)}`);
@@ -1104,6 +1104,14 @@ class FoundationApi extends cdk2.Stack {
1104
1104
  `arn:${this.partition}:bedrock-agentcore:${this.region}:${this.account}:runtime/${names.runtimeName}*/runtime-endpoint/*`
1105
1105
  ]
1106
1106
  }));
1107
+ invokerRole.addToPolicy(new iam2.PolicyStatement({
1108
+ sid: "StopSingleUseRuntimeSessions",
1109
+ actions: ["bedrock-agentcore:StopRuntimeSession"],
1110
+ resources: [
1111
+ `arn:${this.partition}:bedrock-agentcore:${this.region}:${this.account}:runtime/${names.runtimeName}*`,
1112
+ `arn:${this.partition}:bedrock-agentcore:${this.region}:${this.account}:runtime/${names.runtimeName}*/runtime-endpoint/*`
1113
+ ]
1114
+ }));
1107
1115
  invokerRole.addToPolicy(new iam2.PolicyStatement({
1108
1116
  sid: "ReadRuntimeEndpointVersion",
1109
1117
  actions: ["bedrock-agentcore:GetAgentRuntimeEndpoint"],
@@ -1937,6 +1945,7 @@ function deployStatements(stack, names, targets, instance) {
1937
1945
  sid: "SmokeTestRuntime",
1938
1946
  actions: [
1939
1947
  "bedrock-agentcore:InvokeAgentRuntime",
1948
+ "bedrock-agentcore:StopRuntimeSession",
1940
1949
  "bedrock-agentcore:GetAgentRuntime",
1941
1950
  "bedrock-agentcore:ListAgentRuntimeVersions",
1942
1951
  "bedrock-agentcore:GetAgentRuntimeEndpoint",
package/dist/src/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  FoundationPipeline,
11
11
  NewsletterStack,
12
12
  deployStatements
13
- } from "../chunk-fwqmac5n.js";
13
+ } from "../chunk-sgh3agpa.js";
14
14
  import {
15
15
  BUN_VERSION,
16
16
  LAMBDA_ENTRY_POINTS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deployfoundation/foundation-deploy",
3
- "version": "0.2.40",
3
+ "version": "0.2.42",
4
4
  "description": "Deploys one Foundation instance from a published, signed release: the CDK app and the deploy tool.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -214,6 +214,32 @@ export interface SmokeOptions {
214
214
  log?: (line: string) => void;
215
215
  }
216
216
 
217
+ /**
218
+ * End a smoke session once it has answered. Its id is minted per invoke and
219
+ * never used again, so otherwise the microVM idles out its whole idle timeout
220
+ * billed for its memory (~2 GB-hours each; a deploy runs three). Best-effort:
221
+ * a deployer without the permission still deploys.
222
+ */
223
+ async function stopSmokeSession(
224
+ runner: EndpointRunner,
225
+ opts: SmokeOptions,
226
+ log: (line: string) => void,
227
+ ): Promise<void> {
228
+ try {
229
+ await runner.capture([
230
+ "bedrock-agentcore",
231
+ "stop-runtime-session",
232
+ "--agent-runtime-arn",
233
+ opts.arn,
234
+ ...(opts.qualifier === undefined ? [] : ["--qualifier", opts.qualifier]),
235
+ "--runtime-session-id",
236
+ opts.sessionId,
237
+ ]);
238
+ } catch {
239
+ log(" (could not stop the smoke session; it ends at the idle timeout)");
240
+ }
241
+ }
242
+
217
243
  /** Invoke the runtime and assert the response says what a healthy runtime says. */
218
244
  export async function smokeInvoke(runner: EndpointRunner, opts: SmokeOptions): Promise<void> {
219
245
  const log = opts.log ?? ((line: string) => console.log(line));
@@ -250,6 +276,7 @@ export async function smokeInvoke(runner: EndpointRunner, opts: SmokeOptions): P
250
276
  } finally {
251
277
  await rm(dir, { recursive: true, force: true });
252
278
  }
279
+ await stopSmokeSession(runner, opts, log);
253
280
  log(` ${out.replaceAll("\n", "\n ")}`);
254
281
  for (const needle of opts.expect ?? SMOKE_EXPECT)
255
282
  if (!out.includes(needle)) throw new Error(`smoke test: response does not contain ${needle}`);
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The instance file's path INSIDE the instance repository, which is what the
3
+ * pipeline's CodeBuild project is told to deploy from.
4
+ *
5
+ * Two places run a deploy, and they find the repository root differently:
6
+ *
7
+ * - A person's checkout is a git work tree, so `git rev-parse` names the root.
8
+ * - CodeBuild unpacks CodePipeline's source artifact into `CODEBUILD_SRC_DIR`.
9
+ * That is a zip of the repository, not a git work tree, so `git rev-parse`
10
+ * fails there. Falling back to the bare file name made every pipeline deploy
11
+ * rewrite its own `FOUNDATION_INSTANCE_FILE` to `instance.yaml`, and the
12
+ * next run failed with "no instance file" (Sky and Olivia, 2026-10-11).
13
+ */
14
+ import { execFileSync } from "node:child_process";
15
+ import { basename, dirname, relative, resolve, sep } from "node:path";
16
+
17
+ /** `rel` when it names a file under the root, else undefined. */
18
+ function inside(root: string, absolute: string): string | undefined {
19
+ const rel = relative(root, absolute);
20
+ return rel !== "" && !rel.startsWith("..") ? rel.split(sep).join("/") : undefined;
21
+ }
22
+
23
+ function gitTopLevel(directory: string): string | undefined {
24
+ try {
25
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
26
+ cwd: directory,
27
+ stdio: ["ignore", "pipe", "ignore"],
28
+ })
29
+ .toString()
30
+ .trim();
31
+ } catch {
32
+ // Not inside a git work tree.
33
+ return undefined;
34
+ }
35
+ }
36
+
37
+ export function repoRelativePath(
38
+ filePath: string,
39
+ env: Record<string, string | undefined> = process.env,
40
+ topLevel: (directory: string) => string | undefined = gitTopLevel,
41
+ ): string {
42
+ const absolute = resolve(filePath);
43
+ const codebuildRoot = env.CODEBUILD_SRC_DIR;
44
+ if (codebuildRoot !== undefined && codebuildRoot !== "") {
45
+ const rel = inside(resolve(codebuildRoot), absolute);
46
+ if (rel !== undefined) return rel;
47
+ }
48
+ const top = topLevel(dirname(absolute));
49
+ if (top !== undefined) {
50
+ const rel = inside(top, absolute);
51
+ if (rel !== undefined) return rel;
52
+ }
53
+ return basename(absolute);
54
+ }
@@ -175,11 +175,13 @@ export function deployStatements(
175
175
  // `foundation-deploy post-deploy` (and `deploy`'s inline promotion): the
176
176
  // smoke test (`_smoke_test`, `_fs_probe`) against DEFAULT, then promotion
177
177
  // of the new version onto `live`, which CREATES `live` when an instance
178
- // brought up by `deploy` alone has none yet (`promoteEndpoint`).
178
+ // brought up by `deploy` alone has none yet (`promoteEndpoint`). Each
179
+ // smoke session is stopped once it answers: nothing invokes it again.
179
180
  new iam.PolicyStatement({
180
181
  sid: "SmokeTestRuntime",
181
182
  actions: [
182
183
  "bedrock-agentcore:InvokeAgentRuntime",
184
+ "bedrock-agentcore:StopRuntimeSession",
183
185
  "bedrock-agentcore:GetAgentRuntime",
184
186
  "bedrock-agentcore:ListAgentRuntimeVersions",
185
187
  "bedrock-agentcore:GetAgentRuntimeEndpoint",
@@ -529,6 +529,19 @@ export class FoundationApi extends cdk.Stack {
529
529
  ],
530
530
  }),
531
531
  );
532
+ // A routine fire's session is single-use: the invoker ends it as soon as
533
+ // the fire answers instead of paying for its memory through the idle
534
+ // timeout (`isSingleUseSession` in gateway/src/invoker.ts).
535
+ invokerRole.addToPolicy(
536
+ new iam.PolicyStatement({
537
+ sid: "StopSingleUseRuntimeSessions",
538
+ actions: ["bedrock-agentcore:StopRuntimeSession"],
539
+ resources: [
540
+ `arn:${this.partition}:bedrock-agentcore:${this.region}:${this.account}:runtime/${names.runtimeName}*`,
541
+ `arn:${this.partition}:bedrock-agentcore:${this.region}:${this.account}:runtime/${names.runtimeName}*/runtime-endpoint/*`,
542
+ ],
543
+ }),
544
+ );
532
545
  // Which version the endpoint serves: the invoker names it in the session
533
546
  // id so a thread moves off a pre-deploy microVM on its next turn.
534
547
  invokerRole.addToPolicy(