@deployfoundation/foundation-deploy 0.2.37 → 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.
- package/dist/bin/app.js +3 -2
- package/dist/bin/foundation-deploy.js +503 -100
- package/dist/bin/release-account.js +1 -2
- package/dist/{chunk-9yz4hnte.js → chunk-6v0ewr5b.js} +20 -5
- package/dist/{chunk-hps5vshg.js → chunk-7rd8c1th.js} +28 -2
- package/dist/chunk-dz91avrb.js +16 -0
- package/dist/src/index.js +5 -2
- package/package.json +1 -1
- package/src/artifacts.ts +33 -0
- package/src/deploy/release.ts +1 -35
- package/src/deploy-permissions.ts +37 -2
- package/src/index.ts +2 -0
- package/src/release/trust.ts +52 -0
- package/src/stacks/api-stack.ts +2 -1
- package/src/stacks/ci-stack.ts +2 -1
- package/src/stacks/pipeline-stack.ts +2 -1
- package/dist/chunk-gmdzzrc4.js +0 -442
|
@@ -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-gmdzzrc4.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-
|
|
25
|
+
} from "../chunk-7rd8c1th.js";
|
|
26
|
+
import {
|
|
27
|
+
DEFAULT_RELEASE_BUCKET,
|
|
28
|
+
expectedKeyArn
|
|
29
|
+
} from "../chunk-dz91avrb.js";
|
|
51
30
|
|
|
52
|
-
// src/deploy/
|
|
53
|
-
import {
|
|
31
|
+
// src/deploy/aws.ts
|
|
32
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
54
33
|
import { tmpdir } from "node:os";
|
|
55
|
-
import { join
|
|
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 =
|
|
58
|
-
if (!
|
|
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 =
|
|
117
|
-
const tarball =
|
|
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 =
|
|
525
|
+
const contents = join3(dir, "skills");
|
|
123
526
|
await run(["tar", "-xzf", tarball, "-C", dir], { dryRun: opts.dryRun });
|
|
124
|
-
if (opts.dryRun !== true && !
|
|
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
|
-
|
|
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
|
|
536
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
134
537
|
import { createRequire } from "node:module";
|
|
135
|
-
import { join as
|
|
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(
|
|
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
|
|
528
|
-
import { join as
|
|
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((
|
|
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(
|
|
662
|
-
const outFile =
|
|
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
|
|
1000
|
-
readFileSync as
|
|
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
|
|
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}`) && !
|
|
1435
|
+
return pathFromRoot !== "" && pathFromRoot !== ".." && !pathFromRoot.startsWith(`..${sep}`) && !isAbsolute2(pathFromRoot);
|
|
1033
1436
|
}
|
|
1034
1437
|
function customizationFile(sourceRoot, configuredPath) {
|
|
1035
1438
|
assertCustomizationPath(configuredPath);
|
|
1036
|
-
const candidate =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 =
|
|
1523
|
+
const yaml = readFileSync4(sourceFile);
|
|
1121
1524
|
const tenantConfig = parseInstanceConfig(yaml.toString("utf8"));
|
|
1122
|
-
const platformConfig = parseInstanceConfig(
|
|
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
|
-
|
|
1129
|
-
|
|
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 =
|
|
1207
|
-
if (
|
|
1609
|
+
const source = join5(infraRoot, "bin", "app.ts");
|
|
1610
|
+
if (existsSync4(source))
|
|
1208
1611
|
return `bun run ${source}`;
|
|
1209
|
-
return `${process.execPath} ${
|
|
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
|
|
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:
|
|
1294
|
-
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
|
|
1480
|
-
import { join as
|
|
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 =
|
|
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 ??
|
|
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
|
|
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(
|
|
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
|
|
1693
|
-
import { resolve as
|
|
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
|
|
1697
|
-
import { join as
|
|
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 =
|
|
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(
|
|
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
|
|
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 (!
|
|
2362
|
+
if (!existsSync5(codexFile))
|
|
1960
2363
|
throw new Error(`${codexFile} not found — sign in locally first, or pass --codex-file`);
|
|
1961
|
-
const document =
|
|
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
|
|
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:
|
|
2209
|
-
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 =
|
|
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 =
|
|
2669
|
+
const port = flag2(args, "--port");
|
|
2267
2670
|
await githubAppCreate({
|
|
2268
2671
|
paths,
|
|
2269
|
-
...
|
|
2672
|
+
...flag2(args, "--org") === undefined ? {} : { org: flag2(args, "--org") },
|
|
2270
2673
|
...port === undefined ? {} : { port: Number(port) },
|
|
2271
|
-
...
|
|
2272
|
-
...
|
|
2273
|
-
...
|
|
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 =
|
|
2285
|
-
const alarmEmail =
|
|
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 =
|
|
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 =
|
|
2314
|
-
const alarmEmail =
|
|
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 },
|