@deployfoundation/foundation-deploy 0.2.38 → 0.2.39

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.
@@ -1,61 +1,464 @@
1
1
  #!/usr/bin/env node
2
2
  // @bun
3
- import {
4
- AGENT_DOCKERFILE,
5
- FOUNDATION_ROOT,
6
- INFRA_ROOT,
7
- PACKAGE_ASSETS,
8
- argv,
9
- aws,
10
- awsContext,
11
- awsMutate,
12
- callerAccountId,
13
- cdkEnv,
14
- createSecretString,
15
- foundationVersion,
16
- hasFoundationWorkspace,
17
- instanceBanner,
18
- loadInstanceContext,
19
- putSecretJson,
20
- putSecretString,
21
- readSecretJson,
22
- readSecretString,
23
- releaseContext,
24
- releaseRequest,
25
- resolveInstanceFilePath,
26
- resolveRelease,
27
- run,
28
- runCapture,
29
- secretExists,
30
- stackExists,
31
- stackOutput,
32
- toolVersion
33
- } from "../chunk-8e2z4rhf.js";
34
3
  import {
35
4
  BASE_SLACK_EVENTS,
36
5
  CAPABILITY_IDS,
37
6
  CUSTOMIZATION_ARTIFACT_NAME,
7
+ RELEASE_BUCKET_ENV,
8
+ RELEASE_VERSION_RE,
38
9
  instanceNames,
39
10
  isScheduledJob,
40
11
  isWebhookJob,
41
12
  jobWebhookKey,
13
+ loadInstanceFile,
14
+ manifestKey,
42
15
  parseInstanceConfig,
43
16
  parseSchedule,
17
+ releaseCacheDir,
44
18
  requiredGithubPermissions,
45
19
  requiredSlackCommands,
46
20
  requiredSlackScopes,
47
21
  skillsKey,
48
22
  slackCommandPrefix,
23
+ verifyManifest,
49
24
  versionedConfigKey
50
- } from "../chunk-2ra18dqx.js";
25
+ } from "../chunk-7rd8c1th.js";
26
+ import {
27
+ DEFAULT_RELEASE_BUCKET,
28
+ expectedKeyArn
29
+ } from "../chunk-dz91avrb.js";
51
30
 
52
- // src/deploy/config-sync.ts
53
- import { existsSync, mkdirSync, mkdtempSync, readdirSync } from "node:fs";
31
+ // src/deploy/aws.ts
32
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
54
33
  import { tmpdir } from "node:os";
55
- import { join, resolve } from "node:path";
34
+ import { join } from "node:path";
35
+
36
+ // src/deploy/instance.ts
37
+ import { dirname } from "node:path";
38
+ var INSTANCE_FILE_ENV = "FOUNDATION_INSTANCE_FILE";
39
+ function resolveInstanceFilePath(argv = [], env = {}) {
40
+ for (let i = 0;i < argv.length; i++) {
41
+ const arg = argv[i] ?? "";
42
+ if (arg === "--instance") {
43
+ const next = argv[i + 1];
44
+ if (next === undefined || next.startsWith("-"))
45
+ throw new Error("--instance needs a path, e.g. --instance .foundation/instance.yaml");
46
+ return next;
47
+ }
48
+ if (arg.startsWith("--instance=")) {
49
+ const value = arg.slice("--instance=".length);
50
+ if (value === "")
51
+ throw new Error("--instance needs a path");
52
+ return value;
53
+ }
54
+ }
55
+ const fromEnv = env[INSTANCE_FILE_ENV];
56
+ if (fromEnv !== undefined && fromEnv !== "")
57
+ return fromEnv;
58
+ throw new Error(`--instance <path> is required (or set ${INSTANCE_FILE_ENV}); it is the path to the deployment's instance YAML`);
59
+ }
60
+ function loadInstanceContext(path, cwd = process.cwd()) {
61
+ const file = loadInstanceFile(path, cwd);
62
+ return { ...file, root: dirname(file.path) };
63
+ }
64
+ function loadInstanceFromArgs(argv = process.argv.slice(2), env = process.env) {
65
+ return loadInstanceContext(resolveInstanceFilePath(argv, env));
66
+ }
67
+ function instanceBanner(context) {
68
+ const { instance } = context;
69
+ return `instance: ${instance.name} (account ${instance.aws.account}, region ${instance.aws.region})
70
+ file: ${context.path}
71
+ config: ${context.configPath}`;
72
+ }
73
+
74
+ // src/deploy/sh.ts
75
+ import { spawn as nodeSpawn } from "node:child_process";
76
+ function formatCommand(cmd, env) {
77
+ const prefix = Object.entries(env ?? {}).map(([k, v]) => `${k}=${quote(v)}`).join(" ");
78
+ const body = cmd.map(quote).join(" ");
79
+ return prefix === "" ? body : `${prefix} ${body}`;
80
+ }
81
+ function quote(value) {
82
+ return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
83
+ }
84
+ function start(cmd, opts, stdout) {
85
+ const [bin, ...rest] = cmd;
86
+ if (bin === undefined)
87
+ throw new Error("run: empty command");
88
+ const child = nodeSpawn(bin, rest, {
89
+ cwd: opts.cwd,
90
+ env: { ...process.env, ...opts.env },
91
+ stdio: [
92
+ opts.stdin === undefined ? "ignore" : "pipe",
93
+ stdout,
94
+ stdout === "inherit" ? "inherit" : "pipe"
95
+ ]
96
+ });
97
+ if (opts.stdin !== undefined && child.stdin !== null) {
98
+ child.stdin.end(opts.stdin);
99
+ }
100
+ return child;
101
+ }
102
+ function exited(child) {
103
+ return new Promise((resolve, reject) => {
104
+ child.once("error", reject);
105
+ child.once("close", (code, signal) => resolve(code ?? (signal === null ? 1 : 128)));
106
+ });
107
+ }
108
+ function collect(stream) {
109
+ if (stream === null)
110
+ return Promise.resolve("");
111
+ return new Promise((resolve, reject) => {
112
+ const chunks = [];
113
+ stream.on("data", (c) => chunks.push(c));
114
+ stream.once("error", reject);
115
+ stream.once("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
116
+ });
117
+ }
118
+ async function run(cmd, opts = {}) {
119
+ if (opts.dryRun === true) {
120
+ console.log(` $ ${formatCommand(cmd, opts.env)}`);
121
+ return;
122
+ }
123
+ const code = await exited(start(cmd, opts, "inherit"));
124
+ if (code !== 0)
125
+ throw new Error(`${formatCommand(cmd)} exited ${code}`);
126
+ }
127
+ async function runCapture(cmd, opts = {}) {
128
+ const child = start(cmd, opts, "pipe");
129
+ const [out, err, code] = await Promise.all([
130
+ collect(child.stdout),
131
+ collect(child.stderr),
132
+ exited(child)
133
+ ]);
134
+ if (code !== 0)
135
+ throw new Error(`${formatCommand(cmd)} exited ${code}: ${err.trim()}`);
136
+ return out.trim();
137
+ }
138
+
139
+ // src/deploy/aws.ts
140
+ function resolveProfile(fallback, explicit, env = process.env) {
141
+ if (env.FOUNDATION_NO_PROFILE === "1")
142
+ return "";
143
+ if (explicit !== undefined)
144
+ return explicit;
145
+ if (env.AWS_PROFILE !== undefined && env.AWS_PROFILE !== "")
146
+ return env.AWS_PROFILE;
147
+ if (env.GITHUB_ACTIONS === "true")
148
+ return "";
149
+ return fallback;
150
+ }
151
+ function awsContext(overrides = {}) {
152
+ const env = overrides.env ?? process.env;
153
+ const paths = overrides.paths ?? loadInstanceFromArgs();
154
+ const instance = paths.instance;
155
+ return {
156
+ profile: resolveProfile(instance.aws.profile, overrides.profile, env),
157
+ region: overrides.region ?? env.AWS_REGION ?? instance.aws.region,
158
+ dryRun: overrides.dryRun ?? false,
159
+ instance,
160
+ paths,
161
+ names: instanceNames(instance)
162
+ };
163
+ }
164
+ function argv(ctx, args) {
165
+ const profile = ctx.profile === "" ? [] : ["--profile", ctx.profile];
166
+ return ["aws", ...args, ...profile, "--region", ctx.region];
167
+ }
168
+ function cdkEnv(ctx) {
169
+ return {
170
+ ...ctx.profile === "" ? {} : { AWS_PROFILE: ctx.profile },
171
+ AWS_REGION: ctx.region,
172
+ CDK_DEFAULT_REGION: ctx.region
173
+ };
174
+ }
175
+ async function aws(ctx, args, stdin) {
176
+ return runCapture(argv(ctx, args), { stdin });
177
+ }
178
+ async function awsMutate(ctx, args, stdin) {
179
+ if (ctx.dryRun === true) {
180
+ console.log(` $ ${argv(ctx, args).join(" ")}${stdin === undefined ? "" : " < (stdin)"}`);
181
+ return;
182
+ }
183
+ await run(argv(ctx, args), { stdin });
184
+ }
185
+ async function stackOutput(ctx, stack, key) {
186
+ const value = await aws(ctx, [
187
+ "cloudformation",
188
+ "describe-stacks",
189
+ "--stack-name",
190
+ stack,
191
+ "--query",
192
+ `Stacks[0].Outputs[?OutputKey=='${key}'].OutputValue`,
193
+ "--output",
194
+ "text"
195
+ ]);
196
+ if (value === "" || value === "None")
197
+ throw new Error(`stack ${stack} has no output ${key} — has it been deployed?`);
198
+ return value;
199
+ }
200
+ async function stackExists(ctx, stack) {
201
+ try {
202
+ await aws(ctx, [
203
+ "cloudformation",
204
+ "describe-stacks",
205
+ "--stack-name",
206
+ stack,
207
+ "--output",
208
+ "text"
209
+ ]);
210
+ return true;
211
+ } catch {
212
+ return false;
213
+ }
214
+ }
215
+ async function secretExists(ctx, secretId) {
216
+ try {
217
+ await aws(ctx, [
218
+ "secretsmanager",
219
+ "describe-secret",
220
+ "--secret-id",
221
+ secretId,
222
+ "--output",
223
+ "text"
224
+ ]);
225
+ return true;
226
+ } catch {
227
+ return false;
228
+ }
229
+ }
230
+ async function readSecretString(ctx, secretId) {
231
+ return aws(ctx, [
232
+ "secretsmanager",
233
+ "get-secret-value",
234
+ "--secret-id",
235
+ secretId,
236
+ "--query",
237
+ "SecretString",
238
+ "--output",
239
+ "text"
240
+ ]);
241
+ }
242
+ async function readSecretJson(ctx, secretId) {
243
+ return JSON.parse(await readSecretString(ctx, secretId));
244
+ }
245
+ async function putSecretString(ctx, secretId, value) {
246
+ await throughSecretFile(value, (file) => awsMutate(ctx, [
247
+ "secretsmanager",
248
+ "put-secret-value",
249
+ "--secret-id",
250
+ secretId,
251
+ "--secret-string",
252
+ `file://${file}`
253
+ ]));
254
+ }
255
+ async function createSecretString(ctx, secretId, value, description) {
256
+ await throughSecretFile(value, (file) => awsMutate(ctx, [
257
+ "secretsmanager",
258
+ "create-secret",
259
+ "--name",
260
+ secretId,
261
+ "--description",
262
+ description,
263
+ "--secret-string",
264
+ `file://${file}`
265
+ ]));
266
+ }
267
+ async function throughSecretFile(value, body) {
268
+ const dir = mkdtempSync(join(tmpdir(), "foundation-secret-"));
269
+ const file = join(dir, "value.json");
270
+ try {
271
+ writeFileSync(file, value, { mode: 384 });
272
+ await body(file);
273
+ } finally {
274
+ rmSync(dir, { recursive: true, force: true });
275
+ }
276
+ }
277
+ async function putSecretJson(ctx, secretId, value) {
278
+ await putSecretString(ctx, secretId, JSON.stringify(value));
279
+ }
280
+ async function callerAccountId(ctx) {
281
+ return aws(ctx, ["sts", "get-caller-identity", "--query", "Account", "--output", "text"]);
282
+ }
283
+
284
+ // src/deploy/config-sync.ts
285
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, readdirSync } from "node:fs";
286
+ import { tmpdir as tmpdir2 } from "node:os";
287
+ import { join as join3, resolve as resolve3 } from "node:path";
288
+
289
+ // src/deploy/paths.ts
290
+ import { existsSync, readFileSync } from "node:fs";
291
+ import { dirname as dirname2, resolve } from "node:path";
292
+ import { fileURLToPath } from "node:url";
293
+ var PACKAGE_NAME = "@deployfoundation/foundation-deploy";
294
+ function packageRoot(from) {
295
+ let dir = from;
296
+ for (;; ) {
297
+ const manifest = resolve(dir, "package.json");
298
+ if (existsSync(manifest)) {
299
+ try {
300
+ const { name } = JSON.parse(readFileSync(manifest, "utf8"));
301
+ if (name === PACKAGE_NAME)
302
+ return dir;
303
+ } catch {}
304
+ }
305
+ const parent = dirname2(dir);
306
+ if (parent === dir)
307
+ throw new Error(`cannot find the ${PACKAGE_NAME} package root above ${from}`);
308
+ dir = parent;
309
+ }
310
+ }
311
+ var INFRA_ROOT = packageRoot(dirname2(fileURLToPath(import.meta.url)));
312
+ var FOUNDATION_ROOT = resolve(INFRA_ROOT, "..", "..");
313
+ var AGENT_DOCKERFILE = "packages/infra/agent-image/Dockerfile";
314
+ var PACKAGE_ASSETS = resolve(INFRA_ROOT, "src", "deploy", "assets");
315
+
316
+ // src/deploy/release.ts
317
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2 } from "node:fs";
318
+ import { dirname as dirname3, isAbsolute, join as join2, resolve as resolve2 } from "node:path";
319
+
320
+ // src/release/kms.ts
321
+ import { KMSClient, SignCommand, VerifyCommand } from "@aws-sdk/client-kms";
322
+ function regionOfKeyArn(keyArn) {
323
+ const region = keyArn.split(":")[3];
324
+ if (region === undefined || region === "")
325
+ throw new Error(`not a KMS key ARN: ${keyArn} (expected arn:aws:kms:<region>:<account>:key/…)`);
326
+ return region;
327
+ }
328
+ function client(keyArn, options = {}) {
329
+ const profile = options.profile ?? "";
330
+ return new KMSClient({
331
+ region: regionOfKeyArn(keyArn),
332
+ ...profile === "" ? {} : { profile }
333
+ });
334
+ }
335
+ function kmsVerifier(keyArn, options = {}) {
336
+ const kms = client(keyArn, options);
337
+ return {
338
+ async verify(input) {
339
+ const result = await kms.send(new VerifyCommand(input));
340
+ return {
341
+ ...result.SignatureValid === undefined ? {} : { SignatureValid: result.SignatureValid }
342
+ };
343
+ }
344
+ };
345
+ }
346
+
347
+ // src/deploy/release.ts
348
+ function flag(args, name) {
349
+ const index = args.indexOf(name);
350
+ const value = index === -1 ? undefined : args[index + 1];
351
+ if (value?.startsWith("-") === true)
352
+ throw new Error(`${name} needs a value, e.g. ${name} ${name === "--release" ? "v0.1.0" : "…"}`);
353
+ const inline = args.find((arg) => arg.startsWith(`${name}=`));
354
+ return value ?? (inline === undefined ? undefined : inline.slice(name.length + 1));
355
+ }
356
+ function toolVersion(infraRoot = INFRA_ROOT) {
357
+ const { version } = JSON.parse(readFileSync2(join2(infraRoot, "package.json"), "utf8"));
358
+ return version;
359
+ }
360
+ function foundationVersion(release) {
361
+ return release?.version ?? `v${toolVersion()}`;
362
+ }
363
+ function hasFoundationWorkspace(foundationRoot = FOUNDATION_ROOT) {
364
+ return existsSync2(join2(foundationRoot, "packages", "gateway", "package.json"));
365
+ }
366
+ function releaseRequest(args, options = {}) {
367
+ const env = options.env ?? process.env;
368
+ const bucket = flag(args, "--release-bucket") ?? env[RELEASE_BUCKET_ENV] ?? DEFAULT_RELEASE_BUCKET;
369
+ const manifestRef = flag(args, "--manifest");
370
+ const explicit = flag(args, "--release");
371
+ if (explicit !== undefined) {
372
+ if (!RELEASE_VERSION_RE.test(explicit))
373
+ throw new Error(`--release wants a version like v0.1.0, not "${explicit}"`);
374
+ return { version: explicit, bucket, ...manifestRef === undefined ? {} : { manifestRef } };
375
+ }
376
+ if (manifestRef !== undefined)
377
+ return { manifestRef, bucket };
378
+ const workspace = options.workspace ?? hasFoundationWorkspace();
379
+ if (workspace)
380
+ return;
381
+ const version = `v${options.version ?? toolVersion()}`;
382
+ if (!RELEASE_VERSION_RE.test(version))
383
+ throw new Error(`this copy of foundation-deploy is version ${version.slice(1)}, which is not a release; pass --release <version>`);
384
+ return { version, bucket };
385
+ }
386
+ async function s3Download(ctx, bucket, key, dest) {
387
+ mkdirSync(dirname3(dest), { recursive: true });
388
+ await aws(ctx, ["s3", "cp", `s3://${bucket}/${key}`, dest]);
389
+ }
390
+ async function verifyRelease(ctx, request, deps = {}) {
391
+ const env = deps.env ?? process.env;
392
+ const kmsKeyArn = deps.kmsKeyArn ?? expectedKeyArn(env);
393
+ const download = deps.download ?? s3Download;
394
+ const local = localManifestRef(request);
395
+ const version = request.version;
396
+ const cacheDir = deps.cacheDir ?? releaseCacheDir(version ?? "unversioned", env);
397
+ const manifestPath = local ?? join2(cacheDir, "manifest.json");
398
+ if (local === undefined) {
399
+ const source = s3ManifestRef(request);
400
+ await download(ctx, source.bucket, source.key, manifestPath);
401
+ }
402
+ const manifest = await verifyManifest(JSON.parse(readFileSync2(manifestPath, "utf8")), {
403
+ kmsKeyArn,
404
+ kms: deps.kms ?? kmsVerifier(kmsKeyArn, ctx.profile === "" ? {} : { profile: ctx.profile }),
405
+ ...version === undefined ? {} : { expectVersion: version },
406
+ ...deps.verifyArtifacts === false ? {} : {
407
+ readArtifact: async (key) => {
408
+ const dest = join2(cacheDir, "artifacts", key.split("/").slice(-1)[0] ?? "artifact");
409
+ await download(ctx, request.bucket, key, dest);
410
+ return readFileSync2(dest);
411
+ }
412
+ }
413
+ });
414
+ return { version: manifest.version, bucket: request.bucket, manifestPath, manifest };
415
+ }
416
+ async function resolveRelease(ctx, request, deps = {}) {
417
+ return ctx.dryRun === true ? plannedRelease(request) : verifyRelease(ctx, request, deps);
418
+ }
419
+ function plannedRelease(request) {
420
+ const version = request.version ?? "<from the manifest>";
421
+ const local = localManifestRef(request);
422
+ return {
423
+ version,
424
+ bucket: request.bucket,
425
+ manifestPath: local ?? join2(releaseCacheDir(version), "manifest.json")
426
+ };
427
+ }
428
+ function localManifestRef(request) {
429
+ const ref = request.manifestRef;
430
+ if (ref === undefined || ref.startsWith("s3://"))
431
+ return;
432
+ return isAbsolute(ref) ? ref : resolve2(process.cwd(), ref);
433
+ }
434
+ function s3ManifestRef(request) {
435
+ const ref = request.manifestRef;
436
+ if (ref?.startsWith("s3://") === true) {
437
+ const [bucket, ...rest] = ref.slice("s3://".length).split("/");
438
+ const key = rest.join("/");
439
+ if (bucket === undefined || bucket === "" || key === "")
440
+ throw new Error(`--manifest ${ref} is not an s3://bucket/key URI`);
441
+ return { bucket, key };
442
+ }
443
+ if (request.version === undefined)
444
+ throw new Error("a release needs a version or a manifest to fetch");
445
+ return { bucket: request.bucket, key: manifestKey(request.version) };
446
+ }
447
+ function releaseContext(release) {
448
+ return [
449
+ "-c",
450
+ `release=${release.version}`,
451
+ "-c",
452
+ `releaseManifest=${release.manifestPath}`,
453
+ "-c",
454
+ `releaseBucket=${release.bucket}`
455
+ ];
456
+ }
457
+
458
+ // src/deploy/config-sync.ts
56
459
  function productSkillsDir(foundationRoot = FOUNDATION_ROOT) {
57
- const dir = resolve(foundationRoot, "skills");
58
- if (!existsSync(dir))
460
+ const dir = resolve3(foundationRoot, "skills");
461
+ if (!existsSync3(dir))
59
462
  return;
60
463
  const entries = readdirSync(dir).filter((name) => name !== "README.md");
61
464
  return entries.length > 0 ? dir : undefined;
@@ -113,26 +516,26 @@ async function configSync(ctx, opts = {}) {
113
516
  }
114
517
  async function unpackReleaseSkills(ctx, release) {
115
518
  const key = release.manifest?.skills.key ?? skillsKey(release.version);
116
- const dir = mkdtempSync(join(tmpdir(), "foundation-skills-"));
117
- const tarball = join(dir, "skills.tar.gz");
519
+ const dir = mkdtempSync2(join3(tmpdir2(), "foundation-skills-"));
520
+ const tarball = join3(dir, "skills.tar.gz");
118
521
  await awsMutate(ctx, ["s3", "cp", `s3://${release.bucket}/${key}`, tarball]);
119
522
  return await extractSkillsTarball(tarball, dir, { dryRun: ctx.dryRun });
120
523
  }
121
524
  async function extractSkillsTarball(tarball, dir, opts = {}) {
122
- const contents = join(dir, "skills");
525
+ const contents = join3(dir, "skills");
123
526
  await run(["tar", "-xzf", tarball, "-C", dir], { dryRun: opts.dryRun });
124
- if (opts.dryRun !== true && !existsSync(contents))
527
+ if (opts.dryRun !== true && !existsSync3(contents))
125
528
  throw new Error(`${tarball} has no top-level skills/ directory`);
126
529
  if (opts.dryRun === true)
127
- mkdirSync(contents, { recursive: true });
530
+ mkdirSync2(contents, { recursive: true });
128
531
  return contents;
129
532
  }
130
533
 
131
534
  // src/deploy/deploy.ts
132
535
  import { createHash } from "node:crypto";
133
- import { existsSync as existsSync2 } from "node:fs";
536
+ import { existsSync as existsSync4 } from "node:fs";
134
537
  import { createRequire } from "node:module";
135
- import { join as join3 } from "node:path";
538
+ import { join as join5 } from "node:path";
136
539
 
137
540
  // ../connectors/src/github.ts
138
541
  var GITHUB_TOKEN_RESPONSE_MAX_BYTES = 128 * 1024;
@@ -505,7 +908,7 @@ var UPWORK_GRAPHQL_DOCUMENTS = Object.freeze({
505
908
  var ETRADE_RESPONSE_MAX_BYTES = 1024 * 1024;
506
909
  var MAX_SECRET_JSON_LENGTH = 16 * 1024;
507
910
  // src/deploy/config.ts
508
- import { readFileSync } from "node:fs";
911
+ import { readFileSync as readFileSync3 } from "node:fs";
509
912
  import { parse } from "yaml";
510
913
  function adminsCsv(yamlText, source = "runtime config") {
511
914
  const doc = parse(yamlText);
@@ -519,18 +922,18 @@ function adminsCsv(yamlText, source = "runtime config") {
519
922
  }).join(",");
520
923
  }
521
924
  function adminsCsvFromFile(path) {
522
- return adminsCsv(readFileSync(path, "utf8"), path);
925
+ return adminsCsv(readFileSync3(path, "utf8"), path);
523
926
  }
524
927
 
525
928
  // src/deploy/endpoint.ts
526
929
  import { mkdtemp, readFile, rm } from "node:fs/promises";
527
- import { tmpdir as tmpdir2 } from "node:os";
528
- import { join as join2 } from "node:path";
930
+ import { tmpdir as tmpdir3 } from "node:os";
931
+ import { join as join4 } from "node:path";
529
932
  function cliRunner(ctx) {
530
933
  return {
531
934
  capture: (args) => aws(ctx, args),
532
935
  mutate: (args) => awsMutate(ctx, args),
533
- sleep: (ms) => new Promise((resolve2) => setTimeout(resolve2, ms))
936
+ sleep: (ms) => new Promise((resolve4) => setTimeout(resolve4, ms))
534
937
  };
535
938
  }
536
939
  function runtimeIdFromArn(arn) {
@@ -658,8 +1061,8 @@ async function smokeInvoke(runner, opts) {
658
1061
  await runner.mutate([...args, "/dev/stdout"]);
659
1062
  return;
660
1063
  }
661
- const dir = await mkdtemp(join2(tmpdir2(), "foundation-smoke-"));
662
- const outFile = join2(dir, "response.json");
1064
+ const dir = await mkdtemp(join4(tmpdir3(), "foundation-smoke-"));
1065
+ const outFile = join4(dir, "response.json");
663
1066
  let out;
664
1067
  try {
665
1068
  const printed = await runner.capture([...args, outFile]);
@@ -996,14 +1399,14 @@ function composeRuntimeSecret(inputs) {
996
1399
  // src/deploy/stage-customization.ts
997
1400
  import {
998
1401
  lstatSync,
999
- mkdirSync as mkdirSync2,
1000
- readFileSync as readFileSync2,
1402
+ mkdirSync as mkdirSync3,
1403
+ readFileSync as readFileSync4,
1001
1404
  realpathSync,
1002
- rmSync,
1405
+ rmSync as rmSync2,
1003
1406
  statSync,
1004
- writeFileSync
1407
+ writeFileSync as writeFileSync2
1005
1408
  } from "node:fs";
1006
- import { dirname, isAbsolute, posix, relative, resolve as resolve2, sep } from "node:path";
1409
+ import { dirname as dirname4, isAbsolute as isAbsolute2, posix, relative, resolve as resolve4, sep } from "node:path";
1007
1410
  import { isDeepStrictEqual } from "node:util";
1008
1411
  function sourceRootFor(sourceDir) {
1009
1412
  if (sourceDir === undefined || sourceDir === "")
@@ -1029,11 +1432,11 @@ function assertCustomizationPath(path, label = "Customization path") {
1029
1432
  }
1030
1433
  function isWithin(root, candidate) {
1031
1434
  const pathFromRoot = relative(root, candidate);
1032
- return pathFromRoot !== "" && pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep}`) && !isAbsolute(pathFromRoot);
1435
+ return pathFromRoot !== "" && pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep}`) && !isAbsolute2(pathFromRoot);
1033
1436
  }
1034
1437
  function customizationFile(sourceRoot, configuredPath) {
1035
1438
  assertCustomizationPath(configuredPath);
1036
- const candidate = resolve2(sourceRoot, ...configuredPath.split("/"));
1439
+ const candidate = resolve4(sourceRoot, ...configuredPath.split("/"));
1037
1440
  if (!isWithin(sourceRoot, candidate))
1038
1441
  throw new Error("Customization path escapes its source directory.");
1039
1442
  return candidate;
@@ -1043,7 +1446,7 @@ function resolvedCustomizationFile(sourceRoot, configuredPath) {
1043
1446
  const parts = configuredPath.split("/");
1044
1447
  let current = sourceRoot;
1045
1448
  for (const [index, part] of parts.entries()) {
1046
- const entry = resolve2(current, part);
1449
+ const entry = resolve4(current, part);
1047
1450
  let entryStats;
1048
1451
  try {
1049
1452
  entryStats = lstatSync(entry);
@@ -1065,7 +1468,7 @@ function resolvedCustomizationFile(sourceRoot, configuredPath) {
1065
1468
  return current;
1066
1469
  }
1067
1470
  function stagedCustomizationPath(paths) {
1068
- return resolve2(paths.root, STAGING_DIRECTORY, `${paths.instance.name}.yaml`);
1471
+ return resolve4(paths.root, STAGING_DIRECTORY, `${paths.instance.name}.yaml`);
1069
1472
  }
1070
1473
  var STAGING_DIRECTORY = ".foundation-staging";
1071
1474
  function assertUnchanged(path, platform, tenant) {
@@ -1107,7 +1510,7 @@ function stageCustomization(options) {
1107
1510
  const platformConfigPath = options.paths.configPath;
1108
1511
  const stagedPath = stagedCustomizationPath(options.paths);
1109
1512
  if (options.dryRun !== true)
1110
- rmSync(stagedPath, { force: true });
1513
+ rmSync2(stagedPath, { force: true });
1111
1514
  const customization = instance.customization;
1112
1515
  if (customization === undefined)
1113
1516
  return { status: "not-configured", runtimeConfigPath: platformConfigPath };
@@ -1117,16 +1520,16 @@ function stageCustomization(options) {
1117
1520
  const sourceFile = resolvedCustomizationFile(sourceRoot, customization.path);
1118
1521
  if (sourceFile === undefined)
1119
1522
  return migrationFallback(customization.required, log, platformConfigPath);
1120
- const yaml = readFileSync2(sourceFile);
1523
+ const yaml = readFileSync4(sourceFile);
1121
1524
  const tenantConfig = parseInstanceConfig(yaml.toString("utf8"));
1122
- const platformConfig = parseInstanceConfig(readFileSync2(platformConfigPath, "utf8"));
1525
+ const platformConfig = parseInstanceConfig(readFileSync4(platformConfigPath, "utf8"));
1123
1526
  if (tenantConfig.name !== instance.displayName)
1124
1527
  throw new Error("Customization config name must match the selected instance.");
1125
1528
  assertValidTenantJobs(tenantConfig);
1126
1529
  assertProtectedPolicy(platformConfig, tenantConfig);
1127
1530
  if (options.dryRun !== true) {
1128
- mkdirSync2(dirname(stagedPath), { recursive: true });
1129
- writeFileSync(stagedPath, yaml);
1531
+ mkdirSync3(dirname4(stagedPath), { recursive: true });
1532
+ writeFileSync2(stagedPath, yaml);
1130
1533
  }
1131
1534
  return {
1132
1535
  status: "staged",
@@ -1203,10 +1606,10 @@ function cdkCommand() {
1203
1606
  }
1204
1607
  }
1205
1608
  function cdkAppCommand(infraRoot = INFRA_ROOT) {
1206
- const source = join3(infraRoot, "bin", "app.ts");
1207
- if (existsSync2(source))
1609
+ const source = join5(infraRoot, "bin", "app.ts");
1610
+ if (existsSync4(source))
1208
1611
  return `bun run ${source}`;
1209
- return `${process.execPath} ${join3(infraRoot, "dist", "bin", "app.js")}`;
1612
+ return `${process.execPath} ${join5(infraRoot, "dist", "bin", "app.js")}`;
1210
1613
  }
1211
1614
  async function cdkDeploy(ctx, opts) {
1212
1615
  const context = [
@@ -1285,13 +1688,13 @@ async function syncKnockOAuthClient(ctx, options = {}) {
1285
1688
  if (existing.redirect_uri === redirectUri)
1286
1689
  return "unchanged";
1287
1690
  } catch {}
1288
- const client = await registerKnockPublicClient({
1691
+ const client2 = await registerKnockPublicClient({
1289
1692
  redirectUri,
1290
1693
  ...options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }
1291
1694
  });
1292
1695
  await putSecretJson(ctx, ctx.names.secretKnockOauthClient, {
1293
- client_id: client.client_id,
1294
- redirect_uri: client.redirect_uri
1696
+ client_id: client2.client_id,
1697
+ redirect_uri: client2.redirect_uri
1295
1698
  });
1296
1699
  return "registered";
1297
1700
  }
@@ -1476,10 +1879,10 @@ async function deploy(ctx, opts) {
1476
1879
  import { createServer } from "node:http";
1477
1880
 
1478
1881
  // src/deploy/github-app-manifest.ts
1479
- import { readFileSync as readFileSync3 } from "node:fs";
1480
- import { join as join4 } from "node:path";
1882
+ import { readFileSync as readFileSync5 } from "node:fs";
1883
+ import { join as join6 } from "node:path";
1481
1884
  import { parse as parseYaml } from "yaml";
1482
- var GITHUB_APP_MANIFEST_PATH = join4(PACKAGE_ASSETS, "github-app-manifest.yml");
1885
+ var GITHUB_APP_MANIFEST_PATH = join6(PACKAGE_ASSETS, "github-app-manifest.yml");
1483
1886
  function manifestUrl(instance) {
1484
1887
  const repo = instance.github.defaultRepo ?? instanceNames(instance).defaultRepo;
1485
1888
  return `https://github.com/${repo === "" ? instance.github.org : repo}`;
@@ -1497,7 +1900,7 @@ function renderAppManifest(template, instance) {
1497
1900
  });
1498
1901
  }
1499
1902
  function loadAppManifest(instance, template) {
1500
- const text = template ?? readFileSync3(GITHUB_APP_MANIFEST_PATH, "utf8");
1903
+ const text = template ?? readFileSync5(GITHUB_APP_MANIFEST_PATH, "utf8");
1501
1904
  const manifest = parseYaml(renderAppManifest(text, instance));
1502
1905
  if ("default_permissions" in manifest)
1503
1906
  throw new Error("app-manifest.yml: default_permissions are generated from the capability registry; remove them from the template");
@@ -1615,7 +2018,7 @@ async function githubAppCreate(options) {
1615
2018
  }
1616
2019
 
1617
2020
  // src/deploy/job-key.ts
1618
- import { readFileSync as readFileSync4 } from "node:fs";
2021
+ import { readFileSync as readFileSync6 } from "node:fs";
1619
2022
  function jobWebhookOutputKey(jobId) {
1620
2023
  return `JobWebhookUrl${jobId.replace(/[^A-Za-z0-9]/g, "")}`;
1621
2024
  }
@@ -1623,7 +2026,7 @@ function jobsWebhookSecretName(secretPrefix) {
1623
2026
  return `${secretPrefix}/jobs/webhook`;
1624
2027
  }
1625
2028
  async function jobKey(ctx, jobId, options = {}) {
1626
- const config = parseInstanceConfig(readFileSync4(ctx.paths.configPath, "utf8"));
2029
+ const config = parseInstanceConfig(readFileSync6(ctx.paths.configPath, "utf8"));
1627
2030
  const job = config.jobs.find((entry) => entry.id === jobId);
1628
2031
  if (job === undefined)
1629
2032
  throw new Error(`no job "${jobId}" in ${ctx.paths.configPath}`);
@@ -1689,14 +2092,14 @@ async function postDeploy(ctx, opts = {}) {
1689
2092
  }
1690
2093
 
1691
2094
  // src/deploy/setup.ts
1692
- import { existsSync as existsSync3, readFileSync as readFileSync6 } from "node:fs";
1693
- import { resolve as resolve3 } from "node:path";
2095
+ import { existsSync as existsSync5, readFileSync as readFileSync8 } from "node:fs";
2096
+ import { resolve as resolve5 } from "node:path";
1694
2097
 
1695
2098
  // src/deploy/slack-manifest.ts
1696
- import { readFileSync as readFileSync5 } from "node:fs";
1697
- import { join as join5 } from "node:path";
2099
+ import { readFileSync as readFileSync7 } from "node:fs";
2100
+ import { join as join7 } from "node:path";
1698
2101
  import { parse as parse2 } from "yaml";
1699
- var SLACK_APP_MANIFEST_PATH = join5(PACKAGE_ASSETS, "slack-app-manifest.yml");
2102
+ var SLACK_APP_MANIFEST_PATH = join7(PACKAGE_ASSETS, "slack-app-manifest.yml");
1700
2103
  function buildSlackManifest(template, instance, env) {
1701
2104
  const events = env.EVENTS_REQUEST_URL;
1702
2105
  const commands = env.COMMANDS_REQUEST_URL ?? events?.replace(/\/events$/, "/commands");
@@ -1732,7 +2135,7 @@ function buildSlackManifest(template, instance, env) {
1732
2135
  return manifest;
1733
2136
  }
1734
2137
  function slackManifestFor(instance, env) {
1735
- return buildSlackManifest(readFileSync5(SLACK_APP_MANIFEST_PATH, "utf8"), {
2138
+ return buildSlackManifest(readFileSync7(SLACK_APP_MANIFEST_PATH, "utf8"), {
1736
2139
  displayName: instance.displayName,
1737
2140
  commandPrefix: slackCommandPrefix(instance),
1738
2141
  pages: instance.integrations.pages
@@ -1883,7 +2286,7 @@ async function ensureTransactionSearch(runner, options = {}) {
1883
2286
 
1884
2287
  // src/deploy/setup.ts
1885
2288
  function defaultCodexFile(instanceRoot) {
1886
- return resolve3(instanceRoot, ".foundation-local", "codex.json");
2289
+ return resolve5(instanceRoot, ".foundation-local", "codex.json");
1887
2290
  }
1888
2291
  function step(n, title) {
1889
2292
  console.log(`
@@ -1956,9 +2359,9 @@ async function writeRuntimeSecret(ctx) {
1956
2359
  console.log(` keys: ${(await syncRuntimeSecret(ctx)).join(", ")}`);
1957
2360
  }
1958
2361
  async function seedCodex(ctx, codexFile) {
1959
- if (!existsSync3(codexFile))
2362
+ if (!existsSync5(codexFile))
1960
2363
  throw new Error(`${codexFile} not found — sign in locally first, or pass --codex-file`);
1961
- const document = readFileSync6(codexFile, "utf8");
2364
+ const document = readFileSync8(codexFile, "utf8");
1962
2365
  JSON.parse(document);
1963
2366
  await putSecretString(ctx, ctx.names.secretCodex, document);
1964
2367
  console.log(` ${ctx.names.secretCodex} seeded from ${codexFile}`);
@@ -2197,7 +2600,7 @@ github-app-create options
2197
2600
  --org <org> GitHub org (default: the instance's github.org)
2198
2601
  --port <n> local callback port (default 8765)
2199
2602
  --secret <id> Secrets Manager id (default: the instance's github/app)`;
2200
- function flag(args, name) {
2603
+ function flag2(args, name) {
2201
2604
  const index = args.indexOf(name);
2202
2605
  return index === -1 ? undefined : args[index + 1];
2203
2606
  }
@@ -2205,8 +2608,8 @@ function contextFor(args) {
2205
2608
  const paths = loadInstanceContext(resolveInstanceFilePath(args, process.env));
2206
2609
  return awsContext({
2207
2610
  paths,
2208
- profile: flag(args, "--profile"),
2209
- region: flag(args, "--region"),
2611
+ profile: flag2(args, "--profile"),
2612
+ region: flag2(args, "--region"),
2210
2613
  dryRun: args.includes("--dry-run")
2211
2614
  });
2212
2615
  }
@@ -2255,7 +2658,7 @@ ${USAGE}`);
2255
2658
  return 1;
2256
2659
  }
2257
2660
  console.error(instanceBanner(ctx2.paths));
2258
- const putSecret = flag(args, "--put-secret");
2661
+ const putSecret = flag2(args, "--put-secret");
2259
2662
  const result = await jobKey(ctx2, jobId, putSecret === undefined ? {} : { putSecret });
2260
2663
  console.log(JSON.stringify(result));
2261
2664
  return 0;
@@ -2263,14 +2666,14 @@ ${USAGE}`);
2263
2666
  if (command === "github-app-create") {
2264
2667
  const paths = loadInstanceContext(resolveInstanceFilePath(args, process.env));
2265
2668
  console.log(instanceBanner(paths));
2266
- const port = flag(args, "--port");
2669
+ const port = flag2(args, "--port");
2267
2670
  await githubAppCreate({
2268
2671
  paths,
2269
- ...flag(args, "--org") === undefined ? {} : { org: flag(args, "--org") },
2672
+ ...flag2(args, "--org") === undefined ? {} : { org: flag2(args, "--org") },
2270
2673
  ...port === undefined ? {} : { port: Number(port) },
2271
- ...flag(args, "--secret") === undefined ? {} : { secret: flag(args, "--secret") },
2272
- ...flag(args, "--profile") === undefined ? {} : { profile: flag(args, "--profile") },
2273
- ...flag(args, "--region") === undefined ? {} : { region: flag(args, "--region") },
2674
+ ...flag2(args, "--secret") === undefined ? {} : { secret: flag2(args, "--secret") },
2675
+ ...flag2(args, "--profile") === undefined ? {} : { profile: flag2(args, "--profile") },
2676
+ ...flag2(args, "--region") === undefined ? {} : { region: flag2(args, "--region") },
2274
2677
  dryRun: args.includes("--dry-run")
2275
2678
  });
2276
2679
  return 0;
@@ -2281,8 +2684,8 @@ ${USAGE}`);
2281
2684
  console.log("dry run — no changes will be made");
2282
2685
  switch (command) {
2283
2686
  case "deploy": {
2284
- const tag = flag(args, "--tag");
2285
- const alarmEmail = flag(args, "--alarm-email");
2687
+ const tag = flag2(args, "--tag");
2688
+ const alarmEmail = flag2(args, "--alarm-email");
2286
2689
  const release = releaseRequest(args);
2287
2690
  await deploy(ctx, {
2288
2691
  ...tag === undefined ? {} : { tag },
@@ -2298,7 +2701,7 @@ ${USAGE}`);
2298
2701
  return 0;
2299
2702
  case "config:sync": {
2300
2703
  const customization = stageCustomization({ paths: ctx.paths, dryRun: ctx.dryRun });
2301
- const bucket = flag(args, "--bucket");
2704
+ const bucket = flag2(args, "--bucket");
2302
2705
  const request = releaseRequest(args);
2303
2706
  const release = request === undefined ? undefined : await resolveRelease(ctx, request);
2304
2707
  const synced = await configSync(ctx, {
@@ -2310,8 +2713,8 @@ ${USAGE}`);
2310
2713
  return 0;
2311
2714
  }
2312
2715
  case "setup": {
2313
- const codexFile = flag(args, "--codex-file");
2314
- const alarmEmail = flag(args, "--alarm-email");
2716
+ const codexFile = flag2(args, "--codex-file");
2717
+ const alarmEmail = flag2(args, "--alarm-email");
2315
2718
  await setup(ctx, {
2316
2719
  seedCodex: args.includes("--seed-codex"),
2317
2720
  ...codexFile === undefined ? {} : { codexFile },