@deployfoundation/foundation-deploy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +174 -0
  2. package/agent-image/Dockerfile +254 -0
  3. package/agent-image/bin/aws +36 -0
  4. package/agent-image/bin/gh +193 -0
  5. package/agent-image/bin/git-credential-sky +89 -0
  6. package/agent-image/security-overlay.yml +176 -0
  7. package/cdk.json +6 -0
  8. package/dist/bin/app.js +112 -0
  9. package/dist/bin/foundation-deploy.js +1906 -0
  10. package/dist/bin/release-account.js +154 -0
  11. package/dist/chunk-4aye5cee.js +2416 -0
  12. package/dist/chunk-9ddxyvq2.js +1455 -0
  13. package/dist/chunk-v7tz8g50.js +428 -0
  14. package/dist/src/index.js +88 -0
  15. package/package.json +38 -0
  16. package/pipeline/buildspec.yml +34 -0
  17. package/src/artifacts.ts +318 -0
  18. package/src/deploy/assets/github-app-manifest.yml +29 -0
  19. package/src/deploy/assets/slack-app-manifest.yml +95 -0
  20. package/src/deploy/aws.ts +265 -0
  21. package/src/deploy/cli.ts +212 -0
  22. package/src/deploy/config-sync.ts +93 -0
  23. package/src/deploy/config.ts +29 -0
  24. package/src/deploy/deploy.ts +566 -0
  25. package/src/deploy/endpoint.ts +242 -0
  26. package/src/deploy/github-app-create.ts +154 -0
  27. package/src/deploy/github-app-manifest.ts +53 -0
  28. package/src/deploy/image.ts +80 -0
  29. package/src/deploy/instance.ts +87 -0
  30. package/src/deploy/license-cache.ts +47 -0
  31. package/src/deploy/license.ts +272 -0
  32. package/src/deploy/paths.ts +65 -0
  33. package/src/deploy/post-deploy.ts +97 -0
  34. package/src/deploy/release.ts +282 -0
  35. package/src/deploy/runtime-secret.ts +241 -0
  36. package/src/deploy/setup.ts +393 -0
  37. package/src/deploy/sh.ts +74 -0
  38. package/src/deploy/slack-manifest.ts +112 -0
  39. package/src/deploy/stage-customization.ts +224 -0
  40. package/src/deploy/tracing.ts +243 -0
  41. package/src/deploy-permissions.ts +165 -0
  42. package/src/index.ts +60 -0
  43. package/src/lambda-bundle-context.ts +64 -0
  44. package/src/names.ts +170 -0
  45. package/src/release/kms.ts +86 -0
  46. package/src/release/manifest.ts +265 -0
  47. package/src/stacks/agent-stack.ts +938 -0
  48. package/src/stacks/api-stack.ts +1005 -0
  49. package/src/stacks/ci-stack.ts +96 -0
  50. package/src/stacks/data-stack.ts +446 -0
  51. package/src/stacks/network-stack.ts +282 -0
  52. package/src/stacks/newsletter-stack.ts +572 -0
  53. package/src/stacks/pipeline-stack.ts +242 -0
  54. package/src/stacks/release-account-stack.ts +229 -0
@@ -0,0 +1,428 @@
1
+ import {
2
+ RELEASE_BUCKET_ENV,
3
+ RELEASE_VERSION_RE,
4
+ instanceNames,
5
+ loadInstanceFile,
6
+ manifestKey,
7
+ releaseCacheDir,
8
+ verifyManifest
9
+ } from "./chunk-9ddxyvq2.js";
10
+
11
+ // src/deploy/release.ts
12
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2 } from "node:fs";
13
+ import { dirname as dirname3, isAbsolute, join as join2, resolve as resolve2 } from "node:path";
14
+
15
+ // src/release/kms.ts
16
+ import { KMSClient, SignCommand, VerifyCommand } from "@aws-sdk/client-kms";
17
+ function regionOfKeyArn(keyArn) {
18
+ const region = keyArn.split(":")[3];
19
+ if (region === undefined || region === "")
20
+ throw new Error(`not a KMS key ARN: ${keyArn} (expected arn:aws:kms:<region>:<account>:key/…)`);
21
+ return region;
22
+ }
23
+ function client(keyArn, options = {}) {
24
+ const profile = options.profile ?? "";
25
+ return new KMSClient({
26
+ region: regionOfKeyArn(keyArn),
27
+ ...profile === "" ? {} : { profile }
28
+ });
29
+ }
30
+ function kmsVerifier(keyArn, options = {}) {
31
+ const kms = client(keyArn, options);
32
+ return {
33
+ async verify(input) {
34
+ const result = await kms.send(new VerifyCommand(input));
35
+ return {
36
+ ...result.SignatureValid === undefined ? {} : { SignatureValid: result.SignatureValid }
37
+ };
38
+ }
39
+ };
40
+ }
41
+
42
+ // src/deploy/aws.ts
43
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
44
+ import { tmpdir } from "node:os";
45
+ import { join } from "node:path";
46
+
47
+ // src/deploy/instance.ts
48
+ import { dirname } from "node:path";
49
+ var INSTANCE_FILE_ENV = "FOUNDATION_INSTANCE_FILE";
50
+ function resolveInstanceFilePath(argv = [], env = {}) {
51
+ for (let i = 0;i < argv.length; i++) {
52
+ const arg = argv[i] ?? "";
53
+ if (arg === "--instance") {
54
+ const next = argv[i + 1];
55
+ if (next === undefined || next.startsWith("-"))
56
+ throw new Error("--instance needs a path, e.g. --instance .foundation/instance.yaml");
57
+ return next;
58
+ }
59
+ if (arg.startsWith("--instance=")) {
60
+ const value = arg.slice("--instance=".length);
61
+ if (value === "")
62
+ throw new Error("--instance needs a path");
63
+ return value;
64
+ }
65
+ }
66
+ const fromEnv = env[INSTANCE_FILE_ENV];
67
+ if (fromEnv !== undefined && fromEnv !== "")
68
+ return fromEnv;
69
+ throw new Error(`--instance <path> is required (or set ${INSTANCE_FILE_ENV}); it is the path to the deployment's instance YAML`);
70
+ }
71
+ function loadInstanceContext(path, cwd = process.cwd()) {
72
+ const file = loadInstanceFile(path, cwd);
73
+ return { ...file, root: dirname(file.path) };
74
+ }
75
+ function loadInstanceFromArgs(argv = process.argv.slice(2), env = process.env) {
76
+ return loadInstanceContext(resolveInstanceFilePath(argv, env));
77
+ }
78
+ function instanceBanner(context) {
79
+ const { instance } = context;
80
+ return `instance: ${instance.name} (account ${instance.aws.account}, region ${instance.aws.region})
81
+ file: ${context.path}
82
+ config: ${context.configPath}`;
83
+ }
84
+
85
+ // src/deploy/sh.ts
86
+ function formatCommand(cmd, env) {
87
+ const prefix = Object.entries(env ?? {}).map(([k, v]) => `${k}=${quote(v)}`).join(" ");
88
+ const body = cmd.map(quote).join(" ");
89
+ return prefix === "" ? body : `${prefix} ${body}`;
90
+ }
91
+ function quote(value) {
92
+ return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
93
+ }
94
+ function spawn(cmd, opts, stdout) {
95
+ const [bin, ...rest] = cmd;
96
+ if (bin === undefined)
97
+ throw new Error("run: empty command");
98
+ return Bun.spawn([bin, ...rest], {
99
+ cwd: opts.cwd,
100
+ env: { ...process.env, ...opts.env },
101
+ stdin: opts.stdin === undefined ? "ignore" : "pipe",
102
+ stdout,
103
+ stderr: stdout === "inherit" ? "inherit" : "pipe"
104
+ });
105
+ }
106
+ async function feed(proc, stdin) {
107
+ if (stdin === undefined)
108
+ return;
109
+ const sink = proc.stdin;
110
+ if (sink === null)
111
+ return;
112
+ sink.write(stdin);
113
+ sink.end();
114
+ }
115
+ async function run(cmd, opts = {}) {
116
+ if (opts.dryRun === true) {
117
+ console.log(` $ ${formatCommand(cmd, opts.env)}`);
118
+ return;
119
+ }
120
+ const proc = spawn(cmd, opts, "inherit");
121
+ await feed(proc, opts.stdin);
122
+ const code = await proc.exited;
123
+ if (code !== 0)
124
+ throw new Error(`${formatCommand(cmd)} exited ${code}`);
125
+ }
126
+ async function runCapture(cmd, opts = {}) {
127
+ const proc = spawn(cmd, opts, "pipe");
128
+ await feed(proc, opts.stdin);
129
+ const [out, err, code] = await Promise.all([
130
+ new Response(proc.stdout).text(),
131
+ new Response(proc.stderr).text(),
132
+ proc.exited
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/paths.ts
285
+ import { existsSync, readFileSync } from "node:fs";
286
+ import { dirname as dirname2, resolve } from "node:path";
287
+ import { fileURLToPath } from "node:url";
288
+ var PACKAGE_NAME = "@deployfoundation/foundation-deploy";
289
+ function packageRoot(from) {
290
+ let dir = from;
291
+ for (;; ) {
292
+ const manifest = resolve(dir, "package.json");
293
+ if (existsSync(manifest)) {
294
+ try {
295
+ const { name } = JSON.parse(readFileSync(manifest, "utf8"));
296
+ if (name === PACKAGE_NAME)
297
+ return dir;
298
+ } catch {}
299
+ }
300
+ const parent = dirname2(dir);
301
+ if (parent === dir)
302
+ throw new Error(`cannot find the ${PACKAGE_NAME} package root above ${from}`);
303
+ dir = parent;
304
+ }
305
+ }
306
+ var INFRA_ROOT = packageRoot(dirname2(fileURLToPath(import.meta.url)));
307
+ var FOUNDATION_ROOT = resolve(INFRA_ROOT, "..", "..");
308
+ var AGENT_DOCKERFILE = "packages/infra/agent-image/Dockerfile";
309
+ var PACKAGE_ASSETS = resolve(INFRA_ROOT, "src", "deploy", "assets");
310
+
311
+ // src/deploy/release.ts
312
+ var DEFAULT_RELEASE_BUCKET = "foundry41-foundation-releases";
313
+ var RELEASE_KMS_KEY_ARN_ENV = "FOUNDATION_RELEASE_KMS_KEY_ARN";
314
+ var RELEASE_KMS_KEY_ARN = "arn:aws:kms:us-east-1:793593623536:key/fba7d9f3-e9b6-49ae-b4a5-c8c5f1429231";
315
+ function flag(args, name) {
316
+ const index = args.indexOf(name);
317
+ const value = index === -1 ? undefined : args[index + 1];
318
+ if (value?.startsWith("-") === true)
319
+ throw new Error(`${name} needs a value, e.g. ${name} ${name === "--release" ? "v0.1.0" : "…"}`);
320
+ const inline = args.find((arg) => arg.startsWith(`${name}=`));
321
+ return value ?? (inline === undefined ? undefined : inline.slice(name.length + 1));
322
+ }
323
+ function toolVersion(infraRoot = INFRA_ROOT) {
324
+ const { version } = JSON.parse(readFileSync2(join2(infraRoot, "package.json"), "utf8"));
325
+ return version;
326
+ }
327
+ function hasFoundationWorkspace(foundationRoot = FOUNDATION_ROOT) {
328
+ return existsSync2(join2(foundationRoot, "packages", "gateway", "package.json"));
329
+ }
330
+ function releaseRequest(args, options = {}) {
331
+ const env = options.env ?? process.env;
332
+ const bucket = flag(args, "--release-bucket") ?? env[RELEASE_BUCKET_ENV] ?? DEFAULT_RELEASE_BUCKET;
333
+ const manifestRef = flag(args, "--manifest");
334
+ const explicit = flag(args, "--release");
335
+ if (explicit !== undefined) {
336
+ if (!RELEASE_VERSION_RE.test(explicit))
337
+ throw new Error(`--release wants a version like v0.1.0, not "${explicit}"`);
338
+ return { version: explicit, bucket, ...manifestRef === undefined ? {} : { manifestRef } };
339
+ }
340
+ if (manifestRef !== undefined)
341
+ return { manifestRef, bucket };
342
+ const workspace = options.workspace ?? hasFoundationWorkspace();
343
+ if (workspace)
344
+ return;
345
+ const version = `v${options.version ?? toolVersion()}`;
346
+ if (!RELEASE_VERSION_RE.test(version))
347
+ throw new Error(`this copy of foundation-deploy is version ${version.slice(1)}, which is not a release; pass --release <version>`);
348
+ return { version, bucket };
349
+ }
350
+ function expectedKeyArn(env = process.env) {
351
+ const configured = env[RELEASE_KMS_KEY_ARN_ENV] ?? RELEASE_KMS_KEY_ARN;
352
+ if (configured === undefined || configured === "")
353
+ throw new Error(`no Foundation release signing key is configured; set ${RELEASE_KMS_KEY_ARN_ENV} to the key ARN published with the release`);
354
+ return configured;
355
+ }
356
+ async function s3Download(ctx, bucket, key, dest) {
357
+ mkdirSync(dirname3(dest), { recursive: true });
358
+ await aws(ctx, ["s3", "cp", `s3://${bucket}/${key}`, dest]);
359
+ }
360
+ async function verifyRelease(ctx, request, deps = {}) {
361
+ const env = deps.env ?? process.env;
362
+ const kmsKeyArn = deps.kmsKeyArn ?? expectedKeyArn(env);
363
+ const download = deps.download ?? s3Download;
364
+ const local = localManifestRef(request);
365
+ const version = request.version;
366
+ const cacheDir = deps.cacheDir ?? releaseCacheDir(version ?? "unversioned", env);
367
+ const manifestPath = local ?? join2(cacheDir, "manifest.json");
368
+ if (local === undefined) {
369
+ const source = s3ManifestRef(request);
370
+ await download(ctx, source.bucket, source.key, manifestPath);
371
+ }
372
+ const manifest = await verifyManifest(JSON.parse(readFileSync2(manifestPath, "utf8")), {
373
+ kmsKeyArn,
374
+ kms: deps.kms ?? kmsVerifier(kmsKeyArn, ctx.profile === "" ? {} : { profile: ctx.profile }),
375
+ ...version === undefined ? {} : { expectVersion: version },
376
+ ...deps.verifyArtifacts === false ? {} : {
377
+ readArtifact: async (key) => {
378
+ const dest = join2(cacheDir, "artifacts", key.split("/").slice(-1)[0] ?? "artifact");
379
+ await download(ctx, request.bucket, key, dest);
380
+ return readFileSync2(dest);
381
+ }
382
+ }
383
+ });
384
+ return { version: manifest.version, bucket: request.bucket, manifestPath, manifest };
385
+ }
386
+ async function resolveRelease(ctx, request, deps = {}) {
387
+ return ctx.dryRun === true ? plannedRelease(request) : verifyRelease(ctx, request, deps);
388
+ }
389
+ function plannedRelease(request) {
390
+ const version = request.version ?? "<from the manifest>";
391
+ const local = localManifestRef(request);
392
+ return {
393
+ version,
394
+ bucket: request.bucket,
395
+ manifestPath: local ?? join2(releaseCacheDir(version), "manifest.json")
396
+ };
397
+ }
398
+ function localManifestRef(request) {
399
+ const ref = request.manifestRef;
400
+ if (ref === undefined || ref.startsWith("s3://"))
401
+ return;
402
+ return isAbsolute(ref) ? ref : resolve2(process.cwd(), ref);
403
+ }
404
+ function s3ManifestRef(request) {
405
+ const ref = request.manifestRef;
406
+ if (ref?.startsWith("s3://") === true) {
407
+ const [bucket, ...rest] = ref.slice("s3://".length).split("/");
408
+ const key = rest.join("/");
409
+ if (bucket === undefined || bucket === "" || key === "")
410
+ throw new Error(`--manifest ${ref} is not an s3://bucket/key URI`);
411
+ return { bucket, key };
412
+ }
413
+ if (request.version === undefined)
414
+ throw new Error("a release needs a version or a manifest to fetch");
415
+ return { bucket: request.bucket, key: manifestKey(request.version) };
416
+ }
417
+ function releaseContext(release) {
418
+ return [
419
+ "-c",
420
+ `release=${release.version}`,
421
+ "-c",
422
+ `releaseManifest=${release.manifestPath}`,
423
+ "-c",
424
+ `releaseBucket=${release.bucket}`
425
+ ];
426
+ }
427
+
428
+ export { resolveInstanceFilePath, loadInstanceContext, instanceBanner, run, runCapture, awsContext, argv, cdkEnv, aws, awsMutate, stackOutput, stackExists, secretExists, readSecretString, readSecretJson, putSecretString, createSecretString, putSecretJson, callerAccountId, INFRA_ROOT, FOUNDATION_ROOT, AGENT_DOCKERFILE, PACKAGE_ASSETS, DEFAULT_RELEASE_BUCKET, toolVersion, releaseRequest, resolveRelease, releaseContext };
@@ -0,0 +1,88 @@
1
+ import {
2
+ BUILD_TIMEOUT,
3
+ DEFAULT_BUILDSPEC_PATH,
4
+ FoundationAgent,
5
+ FoundationApi,
6
+ FoundationCi,
7
+ FoundationData,
8
+ FoundationNetwork,
9
+ FoundationPipeline,
10
+ NewsletterStack,
11
+ deployStatements
12
+ } from "../chunk-4aye5cee.js";
13
+ import {
14
+ BUN_VERSION,
15
+ LAMBDA_ENTRY_POINTS,
16
+ LIVE_ENDPOINT_NAME,
17
+ RELEASE_BUCKET_ENV,
18
+ RELEASE_DIR_ENV,
19
+ WEB_SEARCH_CONNECTOR_VERSION,
20
+ WEB_SEARCH_TARGET,
21
+ adminsFor,
22
+ agentImage,
23
+ agentImageTagRequired,
24
+ buildManifest,
25
+ capabilityEnabled,
26
+ configPathFor,
27
+ ecrRepositoryArn,
28
+ enabledIntegrations,
29
+ instanceNames,
30
+ lambdaBundleContext,
31
+ lambdaCode,
32
+ lambdaKey,
33
+ loadInstanceFile,
34
+ manifestKey,
35
+ manifestSigningPayload,
36
+ parseManifest,
37
+ provisionsIntegration,
38
+ releaseCacheDir,
39
+ releaseImageRepositoryArn,
40
+ releaseSource,
41
+ requiresAuthenticatedQueue,
42
+ skillsKey,
43
+ skipBundle,
44
+ verifyManifest
45
+ } from "../chunk-9ddxyvq2.js";
46
+ export {
47
+ BUILD_TIMEOUT,
48
+ BUN_VERSION,
49
+ DEFAULT_BUILDSPEC_PATH,
50
+ FoundationAgent,
51
+ FoundationApi,
52
+ FoundationCi,
53
+ FoundationData,
54
+ FoundationNetwork,
55
+ FoundationPipeline,
56
+ LAMBDA_ENTRY_POINTS,
57
+ LIVE_ENDPOINT_NAME,
58
+ NewsletterStack,
59
+ RELEASE_BUCKET_ENV,
60
+ RELEASE_DIR_ENV,
61
+ WEB_SEARCH_CONNECTOR_VERSION,
62
+ WEB_SEARCH_TARGET,
63
+ adminsFor,
64
+ agentImage,
65
+ agentImageTagRequired,
66
+ buildManifest,
67
+ capabilityEnabled,
68
+ configPathFor,
69
+ deployStatements,
70
+ ecrRepositoryArn,
71
+ enabledIntegrations,
72
+ lambdaBundleContext,
73
+ lambdaCode,
74
+ lambdaKey,
75
+ loadInstanceFile,
76
+ manifestKey,
77
+ manifestSigningPayload,
78
+ instanceNames as namesFor,
79
+ parseManifest,
80
+ provisionsIntegration,
81
+ releaseCacheDir,
82
+ releaseImageRepositoryArn,
83
+ releaseSource,
84
+ requiresAuthenticatedQueue,
85
+ skillsKey,
86
+ skipBundle,
87
+ verifyManifest
88
+ };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@deployfoundation/foundation-deploy",
3
+ "version": "0.1.0",
4
+ "description": "Deploys one Foundation instance from a published, signed release: the CDK app and the deploy tool.",
5
+ "license": "UNLICENSED",
6
+ "private": false,
7
+ "type": "module",
8
+ "bin": {
9
+ "foundation-deploy": "./dist/bin/foundation-deploy.js"
10
+ },
11
+ "exports": {
12
+ ".": "./dist/src/index.js"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src",
17
+ "agent-image",
18
+ "!agent-image/test",
19
+ "pipeline/buildspec.yml",
20
+ "cdk.json",
21
+ "README.md"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "scripts": {
27
+ "build": "bun run scripts/build.ts",
28
+ "synth": "cdk synth --quiet"
29
+ },
30
+ "dependencies": {
31
+ "@aws-sdk/client-kms": "3.1111.0",
32
+ "aws-cdk": "2.1142.0",
33
+ "aws-cdk-lib": "2.270.0",
34
+ "constructs": "10.8.1",
35
+ "yaml": "2.9.0",
36
+ "zod": "3.24.1"
37
+ }
38
+ }
@@ -0,0 +1,34 @@
1
+ # Reference buildspec for an instance's own CodePipeline.
2
+ #
3
+ # This file is NOT read from Foundation: CodeBuild reads it from the INSTANCE
4
+ # repository the pipeline sources (`pipeline-stack.ts` names the path, default
5
+ # `buildspec.yml` at that repo's root). An instance repo copies this once and
6
+ # owns it from then on, because its build is its own.
7
+ #
8
+ # It is two lines because the instance repository builds nothing. The pinned
9
+ # version in `foundation.version` IS the Foundation release: `npx` fetches the
10
+ # deploy tool at that version, and the tool fetches the release's manifest,
11
+ # verifies its signature and every artifact digest, and deploys the image and
12
+ # bundles it names. No Foundation checkout, no Docker, no Bun.
13
+ #
14
+ # Everything that could differ between deployers — the AWS profile, which step
15
+ # promotes `live` — is an environment variable the pipeline stack sets, not a
16
+ # difference in the steps. `FOUNDATION_INSTANCE_FILE` is one of them, which is
17
+ # why neither command passes `--instance`.
18
+ #
19
+ # `cdk deploy --all` inside `deploy` includes the pipeline stack, so a change
20
+ # to the pipeline applies on the next run.
21
+ version: 0.2
22
+
23
+ phases:
24
+ install:
25
+ runtime-versions:
26
+ nodejs: 22
27
+
28
+ build:
29
+ commands:
30
+ # Verify the release, cdk deploy --all, runtime secret, config sync.
31
+ - npx --yes @deployfoundation/foundation-deploy@$(cat foundation.version) deploy
32
+ # Smoke DEFAULT, probe the persistent mount, promote `live`, smoke `live`.
33
+ # A failure here fails the build, and `live` keeps serving the old version.
34
+ - npx --yes @deployfoundation/foundation-deploy@$(cat foundation.version) post-deploy