@deployfoundation/foundation-deploy 0.2.50 → 0.2.52
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/README.md +4 -1
- package/agent-image/Dockerfile +36 -1
- package/dist/bin/app.js +6 -3
- package/dist/bin/foundation-deploy.js +139 -305
- package/dist/bin/release-account.js +1 -0
- package/dist/chunk-3614v9rr.js +907 -0
- package/dist/{chunk-efqdhd2e.js → chunk-59dpqyap.js} +254 -90
- package/dist/chunk-5wgwscd0.js +4 -0
- package/dist/{chunk-gnz747z2.js → chunk-96fjaysn.js} +204 -884
- package/dist/chunk-9zx95xs6.js +259 -0
- package/dist/chunk-jf5rw6h4.js +1117 -0
- package/dist/chunk-m76jt5rc.js +376 -0
- package/dist/chunk-mh1206ne.js +1764 -0
- package/dist/chunk-p7zkm4pp.js +151 -0
- package/dist/chunk-pxhftba1.js +336 -0
- package/dist/chunk-rxetj2p9.js +466 -0
- package/dist/chunk-sgdcmme0.js +301 -0
- package/dist/chunk-tey0gh86.js +38 -0
- package/dist/src/index.js +17 -13
- package/package.json +3 -1
- package/src/deploy/cli.ts +71 -0
- package/src/deploy/crm.ts +491 -0
- package/src/stacks/agent-stack.ts +34 -11
- package/src/stacks/api-stack.ts +340 -87
- package/src/stacks/data-stack.ts +27 -1
|
@@ -1,297 +1,73 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @bun
|
|
3
3
|
import {
|
|
4
|
-
BASE_SLACK_EVENTS,
|
|
5
|
-
CAPABILITY_IDS,
|
|
6
|
-
CUSTOMIZATION_ARTIFACT_NAME,
|
|
7
4
|
RELEASE_BUCKET_ENV,
|
|
8
5
|
RELEASE_VERSION_RE,
|
|
6
|
+
manifestKey,
|
|
7
|
+
releaseCacheDir,
|
|
8
|
+
skillsKey,
|
|
9
|
+
verifyManifest
|
|
10
|
+
} from "../chunk-pxhftba1.js";
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_RELEASE_BUCKET,
|
|
13
|
+
expectedKeyArn
|
|
14
|
+
} from "../chunk-dz91avrb.js";
|
|
15
|
+
import {
|
|
16
|
+
argv,
|
|
17
|
+
aws,
|
|
18
|
+
awsContext,
|
|
19
|
+
awsMutate,
|
|
20
|
+
callerAccountId,
|
|
21
|
+
cdkEnv,
|
|
22
|
+
createSecretString,
|
|
23
|
+
instanceBanner,
|
|
24
|
+
loadInstanceContext,
|
|
25
|
+
putSecretJson,
|
|
26
|
+
putSecretString,
|
|
27
|
+
readSecretJson,
|
|
28
|
+
readSecretString,
|
|
29
|
+
resolveInstanceFilePath,
|
|
30
|
+
run,
|
|
31
|
+
runCapture,
|
|
32
|
+
secretExists,
|
|
33
|
+
stackExists,
|
|
34
|
+
stackOutput
|
|
35
|
+
} from "../chunk-9zx95xs6.js";
|
|
36
|
+
import {
|
|
37
|
+
CUSTOMIZATION_ARTIFACT_NAME,
|
|
9
38
|
instanceNames,
|
|
39
|
+
slackCommandPrefix,
|
|
40
|
+
versionedConfigKey
|
|
41
|
+
} from "../chunk-rxetj2p9.js";
|
|
42
|
+
import"../chunk-m76jt5rc.js";
|
|
43
|
+
import"../chunk-3614v9rr.js";
|
|
44
|
+
import {
|
|
45
|
+
BASE_SLACK_EVENTS,
|
|
46
|
+
CAPABILITY_IDS,
|
|
10
47
|
isScheduledJob,
|
|
11
48
|
isWebhookJob,
|
|
12
49
|
jobWebhookKey,
|
|
13
|
-
loadInstanceFile,
|
|
14
|
-
manifestKey,
|
|
15
50
|
parseInstanceConfig,
|
|
16
51
|
parseSchedule,
|
|
17
|
-
releaseCacheDir,
|
|
18
52
|
requiredGithubPermissions,
|
|
19
53
|
requiredSlackCommands,
|
|
20
|
-
requiredSlackScopes
|
|
21
|
-
|
|
22
|
-
slackCommandPrefix,
|
|
23
|
-
verifyManifest,
|
|
24
|
-
versionedConfigKey
|
|
25
|
-
} from "../chunk-gnz747z2.js";
|
|
54
|
+
requiredSlackScopes
|
|
55
|
+
} from "../chunk-96fjaysn.js";
|
|
26
56
|
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
} from "../chunk-dz91avrb.js";
|
|
57
|
+
__require
|
|
58
|
+
} from "../chunk-5wgwscd0.js";
|
|
30
59
|
|
|
31
60
|
// src/deploy/cli.ts
|
|
32
61
|
import { readFileSync as readFileSync10 } from "node:fs";
|
|
33
62
|
|
|
34
|
-
// src/deploy/aws.ts
|
|
35
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
36
|
-
import { tmpdir } from "node:os";
|
|
37
|
-
import { join } from "node:path";
|
|
38
|
-
|
|
39
|
-
// src/deploy/instance.ts
|
|
40
|
-
import { dirname } from "node:path";
|
|
41
|
-
var INSTANCE_FILE_ENV = "FOUNDATION_INSTANCE_FILE";
|
|
42
|
-
function resolveInstanceFilePath(argv = [], env = {}) {
|
|
43
|
-
for (let i = 0;i < argv.length; i++) {
|
|
44
|
-
const arg = argv[i] ?? "";
|
|
45
|
-
if (arg === "--instance") {
|
|
46
|
-
const next = argv[i + 1];
|
|
47
|
-
if (next === undefined || next.startsWith("-"))
|
|
48
|
-
throw new Error("--instance needs a path, e.g. --instance .foundation/instance.yaml");
|
|
49
|
-
return next;
|
|
50
|
-
}
|
|
51
|
-
if (arg.startsWith("--instance=")) {
|
|
52
|
-
const value = arg.slice("--instance=".length);
|
|
53
|
-
if (value === "")
|
|
54
|
-
throw new Error("--instance needs a path");
|
|
55
|
-
return value;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
const fromEnv = env[INSTANCE_FILE_ENV];
|
|
59
|
-
if (fromEnv !== undefined && fromEnv !== "")
|
|
60
|
-
return fromEnv;
|
|
61
|
-
throw new Error(`--instance <path> is required (or set ${INSTANCE_FILE_ENV}); it is the path to the deployment's instance YAML`);
|
|
62
|
-
}
|
|
63
|
-
function loadInstanceContext(path, cwd = process.cwd()) {
|
|
64
|
-
const file = loadInstanceFile(path, cwd);
|
|
65
|
-
return { ...file, root: dirname(file.path) };
|
|
66
|
-
}
|
|
67
|
-
function loadInstanceFromArgs(argv = process.argv.slice(2), env = process.env) {
|
|
68
|
-
return loadInstanceContext(resolveInstanceFilePath(argv, env));
|
|
69
|
-
}
|
|
70
|
-
function instanceBanner(context) {
|
|
71
|
-
const { instance } = context;
|
|
72
|
-
return `instance: ${instance.name} (account ${instance.aws.account}, region ${instance.aws.region})
|
|
73
|
-
file: ${context.path}
|
|
74
|
-
config: ${context.configPath}`;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// src/deploy/sh.ts
|
|
78
|
-
import { spawn as nodeSpawn } from "node:child_process";
|
|
79
|
-
function formatCommand(cmd, env) {
|
|
80
|
-
const prefix = Object.entries(env ?? {}).map(([k, v]) => `${k}=${quote(v)}`).join(" ");
|
|
81
|
-
const body = cmd.map(quote).join(" ");
|
|
82
|
-
return prefix === "" ? body : `${prefix} ${body}`;
|
|
83
|
-
}
|
|
84
|
-
function quote(value) {
|
|
85
|
-
return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
|
|
86
|
-
}
|
|
87
|
-
function start(cmd, opts, stdout) {
|
|
88
|
-
const [bin, ...rest] = cmd;
|
|
89
|
-
if (bin === undefined)
|
|
90
|
-
throw new Error("run: empty command");
|
|
91
|
-
const child = nodeSpawn(bin, rest, {
|
|
92
|
-
cwd: opts.cwd,
|
|
93
|
-
env: { ...process.env, ...opts.env },
|
|
94
|
-
stdio: [
|
|
95
|
-
opts.stdin === undefined ? "ignore" : "pipe",
|
|
96
|
-
stdout,
|
|
97
|
-
stdout === "inherit" ? "inherit" : "pipe"
|
|
98
|
-
]
|
|
99
|
-
});
|
|
100
|
-
if (opts.stdin !== undefined && child.stdin !== null) {
|
|
101
|
-
child.stdin.end(opts.stdin);
|
|
102
|
-
}
|
|
103
|
-
return child;
|
|
104
|
-
}
|
|
105
|
-
function exited(child) {
|
|
106
|
-
return new Promise((resolve, reject) => {
|
|
107
|
-
child.once("error", reject);
|
|
108
|
-
child.once("close", (code, signal) => resolve(code ?? (signal === null ? 1 : 128)));
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
function collect(stream) {
|
|
112
|
-
if (stream === null)
|
|
113
|
-
return Promise.resolve("");
|
|
114
|
-
return new Promise((resolve, reject) => {
|
|
115
|
-
const chunks = [];
|
|
116
|
-
stream.on("data", (c) => chunks.push(c));
|
|
117
|
-
stream.once("error", reject);
|
|
118
|
-
stream.once("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
async function run(cmd, opts = {}) {
|
|
122
|
-
if (opts.dryRun === true) {
|
|
123
|
-
console.log(` $ ${formatCommand(cmd, opts.env)}`);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
const code = await exited(start(cmd, opts, "inherit"));
|
|
127
|
-
if (code !== 0)
|
|
128
|
-
throw new Error(`${formatCommand(cmd)} exited ${code}`);
|
|
129
|
-
}
|
|
130
|
-
async function runCapture(cmd, opts = {}) {
|
|
131
|
-
const child = start(cmd, opts, "pipe");
|
|
132
|
-
const [out, err, code] = await Promise.all([
|
|
133
|
-
collect(child.stdout),
|
|
134
|
-
collect(child.stderr),
|
|
135
|
-
exited(child)
|
|
136
|
-
]);
|
|
137
|
-
if (code !== 0)
|
|
138
|
-
throw new Error(`${formatCommand(cmd)} exited ${code}: ${err.trim()}`);
|
|
139
|
-
return out.trim();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// src/deploy/aws.ts
|
|
143
|
-
function resolveProfile(fallback, explicit, env = process.env) {
|
|
144
|
-
if (env.FOUNDATION_NO_PROFILE === "1")
|
|
145
|
-
return "";
|
|
146
|
-
if (explicit !== undefined)
|
|
147
|
-
return explicit;
|
|
148
|
-
if (env.AWS_PROFILE !== undefined && env.AWS_PROFILE !== "")
|
|
149
|
-
return env.AWS_PROFILE;
|
|
150
|
-
if (env.GITHUB_ACTIONS === "true")
|
|
151
|
-
return "";
|
|
152
|
-
return fallback;
|
|
153
|
-
}
|
|
154
|
-
function awsContext(overrides = {}) {
|
|
155
|
-
const env = overrides.env ?? process.env;
|
|
156
|
-
const paths = overrides.paths ?? loadInstanceFromArgs();
|
|
157
|
-
const instance = paths.instance;
|
|
158
|
-
return {
|
|
159
|
-
profile: resolveProfile(instance.aws.profile, overrides.profile, env),
|
|
160
|
-
region: overrides.region ?? env.AWS_REGION ?? instance.aws.region,
|
|
161
|
-
dryRun: overrides.dryRun ?? false,
|
|
162
|
-
instance,
|
|
163
|
-
paths,
|
|
164
|
-
names: instanceNames(instance)
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
function argv(ctx, args) {
|
|
168
|
-
const profile = ctx.profile === "" ? [] : ["--profile", ctx.profile];
|
|
169
|
-
return ["aws", ...args, ...profile, "--region", ctx.region];
|
|
170
|
-
}
|
|
171
|
-
function cdkEnv(ctx) {
|
|
172
|
-
return {
|
|
173
|
-
...ctx.profile === "" ? {} : { AWS_PROFILE: ctx.profile },
|
|
174
|
-
AWS_REGION: ctx.region,
|
|
175
|
-
CDK_DEFAULT_REGION: ctx.region
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
async function aws(ctx, args, stdin) {
|
|
179
|
-
return runCapture(argv(ctx, args), { stdin });
|
|
180
|
-
}
|
|
181
|
-
async function awsMutate(ctx, args, stdin) {
|
|
182
|
-
if (ctx.dryRun === true) {
|
|
183
|
-
console.log(` $ ${argv(ctx, args).join(" ")}${stdin === undefined ? "" : " < (stdin)"}`);
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
await run(argv(ctx, args), { stdin });
|
|
187
|
-
}
|
|
188
|
-
async function stackOutput(ctx, stack, key) {
|
|
189
|
-
const value = await aws(ctx, [
|
|
190
|
-
"cloudformation",
|
|
191
|
-
"describe-stacks",
|
|
192
|
-
"--stack-name",
|
|
193
|
-
stack,
|
|
194
|
-
"--query",
|
|
195
|
-
`Stacks[0].Outputs[?OutputKey=='${key}'].OutputValue`,
|
|
196
|
-
"--output",
|
|
197
|
-
"text"
|
|
198
|
-
]);
|
|
199
|
-
if (value === "" || value === "None")
|
|
200
|
-
throw new Error(`stack ${stack} has no output ${key} — has it been deployed?`);
|
|
201
|
-
return value;
|
|
202
|
-
}
|
|
203
|
-
async function stackExists(ctx, stack) {
|
|
204
|
-
try {
|
|
205
|
-
await aws(ctx, [
|
|
206
|
-
"cloudformation",
|
|
207
|
-
"describe-stacks",
|
|
208
|
-
"--stack-name",
|
|
209
|
-
stack,
|
|
210
|
-
"--output",
|
|
211
|
-
"text"
|
|
212
|
-
]);
|
|
213
|
-
return true;
|
|
214
|
-
} catch {
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
async function secretExists(ctx, secretId) {
|
|
219
|
-
try {
|
|
220
|
-
await aws(ctx, [
|
|
221
|
-
"secretsmanager",
|
|
222
|
-
"describe-secret",
|
|
223
|
-
"--secret-id",
|
|
224
|
-
secretId,
|
|
225
|
-
"--output",
|
|
226
|
-
"text"
|
|
227
|
-
]);
|
|
228
|
-
return true;
|
|
229
|
-
} catch {
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
async function readSecretString(ctx, secretId) {
|
|
234
|
-
return aws(ctx, [
|
|
235
|
-
"secretsmanager",
|
|
236
|
-
"get-secret-value",
|
|
237
|
-
"--secret-id",
|
|
238
|
-
secretId,
|
|
239
|
-
"--query",
|
|
240
|
-
"SecretString",
|
|
241
|
-
"--output",
|
|
242
|
-
"text"
|
|
243
|
-
]);
|
|
244
|
-
}
|
|
245
|
-
async function readSecretJson(ctx, secretId) {
|
|
246
|
-
return JSON.parse(await readSecretString(ctx, secretId));
|
|
247
|
-
}
|
|
248
|
-
async function putSecretString(ctx, secretId, value) {
|
|
249
|
-
await throughSecretFile(value, (file) => awsMutate(ctx, [
|
|
250
|
-
"secretsmanager",
|
|
251
|
-
"put-secret-value",
|
|
252
|
-
"--secret-id",
|
|
253
|
-
secretId,
|
|
254
|
-
"--secret-string",
|
|
255
|
-
`file://${file}`
|
|
256
|
-
]));
|
|
257
|
-
}
|
|
258
|
-
async function createSecretString(ctx, secretId, value, description) {
|
|
259
|
-
await throughSecretFile(value, (file) => awsMutate(ctx, [
|
|
260
|
-
"secretsmanager",
|
|
261
|
-
"create-secret",
|
|
262
|
-
"--name",
|
|
263
|
-
secretId,
|
|
264
|
-
"--description",
|
|
265
|
-
description,
|
|
266
|
-
"--secret-string",
|
|
267
|
-
`file://${file}`
|
|
268
|
-
]));
|
|
269
|
-
}
|
|
270
|
-
async function throughSecretFile(value, body) {
|
|
271
|
-
const dir = mkdtempSync(join(tmpdir(), "foundation-secret-"));
|
|
272
|
-
const file = join(dir, "value.json");
|
|
273
|
-
try {
|
|
274
|
-
writeFileSync(file, value, { mode: 384 });
|
|
275
|
-
await body(file);
|
|
276
|
-
} finally {
|
|
277
|
-
rmSync(dir, { recursive: true, force: true });
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
async function putSecretJson(ctx, secretId, value) {
|
|
281
|
-
await putSecretString(ctx, secretId, JSON.stringify(value));
|
|
282
|
-
}
|
|
283
|
-
async function callerAccountId(ctx) {
|
|
284
|
-
return aws(ctx, ["sts", "get-caller-identity", "--query", "Account", "--output", "text"]);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
63
|
// src/deploy/config-sync.ts
|
|
288
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, mkdtempSync
|
|
289
|
-
import { tmpdir
|
|
290
|
-
import { join as
|
|
64
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, mkdtempSync, readFileSync as readFileSync3, readdirSync } from "node:fs";
|
|
65
|
+
import { tmpdir } from "node:os";
|
|
66
|
+
import { join as join2, resolve as resolve3 } from "node:path";
|
|
291
67
|
|
|
292
68
|
// src/deploy/paths.ts
|
|
293
69
|
import { existsSync, readFileSync } from "node:fs";
|
|
294
|
-
import { dirname
|
|
70
|
+
import { dirname, resolve } from "node:path";
|
|
295
71
|
import { fileURLToPath } from "node:url";
|
|
296
72
|
var PACKAGE_NAME = "@deployfoundation/foundation-deploy";
|
|
297
73
|
function packageRoot(from) {
|
|
@@ -305,20 +81,20 @@ function packageRoot(from) {
|
|
|
305
81
|
return dir;
|
|
306
82
|
} catch {}
|
|
307
83
|
}
|
|
308
|
-
const parent =
|
|
84
|
+
const parent = dirname(dir);
|
|
309
85
|
if (parent === dir)
|
|
310
86
|
throw new Error(`cannot find the ${PACKAGE_NAME} package root above ${from}`);
|
|
311
87
|
dir = parent;
|
|
312
88
|
}
|
|
313
89
|
}
|
|
314
|
-
var INFRA_ROOT = packageRoot(
|
|
90
|
+
var INFRA_ROOT = packageRoot(dirname(fileURLToPath(import.meta.url)));
|
|
315
91
|
var FOUNDATION_ROOT = resolve(INFRA_ROOT, "..", "..");
|
|
316
92
|
var AGENT_DOCKERFILE = "packages/infra/agent-image/Dockerfile";
|
|
317
93
|
var PACKAGE_ASSETS = resolve(INFRA_ROOT, "src", "deploy", "assets");
|
|
318
94
|
|
|
319
95
|
// src/deploy/release.ts
|
|
320
96
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
321
|
-
import { dirname as
|
|
97
|
+
import { dirname as dirname2, isAbsolute, join, resolve as resolve2 } from "node:path";
|
|
322
98
|
|
|
323
99
|
// src/release/kms.ts
|
|
324
100
|
import { KMSClient, SignCommand, VerifyCommand } from "@aws-sdk/client-kms";
|
|
@@ -357,14 +133,14 @@ function flag(args, name) {
|
|
|
357
133
|
return value ?? (inline === undefined ? undefined : inline.slice(name.length + 1));
|
|
358
134
|
}
|
|
359
135
|
function toolVersion(infraRoot = INFRA_ROOT) {
|
|
360
|
-
const { version } = JSON.parse(readFileSync2(
|
|
136
|
+
const { version } = JSON.parse(readFileSync2(join(infraRoot, "package.json"), "utf8"));
|
|
361
137
|
return version;
|
|
362
138
|
}
|
|
363
139
|
function foundationVersion(release) {
|
|
364
140
|
return release?.version ?? `v${toolVersion()}`;
|
|
365
141
|
}
|
|
366
142
|
function hasFoundationWorkspace(foundationRoot = FOUNDATION_ROOT) {
|
|
367
|
-
return existsSync2(
|
|
143
|
+
return existsSync2(join(foundationRoot, "packages", "gateway", "package.json"));
|
|
368
144
|
}
|
|
369
145
|
function releaseRequest(args, options = {}) {
|
|
370
146
|
const env = options.env ?? process.env;
|
|
@@ -387,7 +163,7 @@ function releaseRequest(args, options = {}) {
|
|
|
387
163
|
return { version, bucket };
|
|
388
164
|
}
|
|
389
165
|
async function s3Download(ctx, bucket, key, dest) {
|
|
390
|
-
mkdirSync(
|
|
166
|
+
mkdirSync(dirname2(dest), { recursive: true });
|
|
391
167
|
await aws(ctx, ["s3", "cp", `s3://${bucket}/${key}`, dest]);
|
|
392
168
|
}
|
|
393
169
|
async function verifyRelease(ctx, request, deps = {}) {
|
|
@@ -397,7 +173,7 @@ async function verifyRelease(ctx, request, deps = {}) {
|
|
|
397
173
|
const local = localManifestRef(request);
|
|
398
174
|
const version = request.version;
|
|
399
175
|
const cacheDir = deps.cacheDir ?? releaseCacheDir(version ?? "unversioned", env);
|
|
400
|
-
const manifestPath = local ??
|
|
176
|
+
const manifestPath = local ?? join(cacheDir, "manifest.json");
|
|
401
177
|
if (local === undefined) {
|
|
402
178
|
const source = s3ManifestRef(request);
|
|
403
179
|
await download(ctx, source.bucket, source.key, manifestPath);
|
|
@@ -408,7 +184,7 @@ async function verifyRelease(ctx, request, deps = {}) {
|
|
|
408
184
|
...version === undefined ? {} : { expectVersion: version },
|
|
409
185
|
...deps.verifyArtifacts === false ? {} : {
|
|
410
186
|
readArtifact: async (key) => {
|
|
411
|
-
const dest =
|
|
187
|
+
const dest = join(cacheDir, "artifacts", key.split("/").slice(-1)[0] ?? "artifact");
|
|
412
188
|
await download(ctx, request.bucket, key, dest);
|
|
413
189
|
return readFileSync2(dest);
|
|
414
190
|
}
|
|
@@ -425,7 +201,7 @@ function plannedRelease(request) {
|
|
|
425
201
|
return {
|
|
426
202
|
version,
|
|
427
203
|
bucket: request.bucket,
|
|
428
|
-
manifestPath: local ??
|
|
204
|
+
manifestPath: local ?? join(releaseCacheDir(version), "manifest.json")
|
|
429
205
|
};
|
|
430
206
|
}
|
|
431
207
|
function localManifestRef(request) {
|
|
@@ -524,13 +300,13 @@ async function configSync(ctx, opts = {}) {
|
|
|
524
300
|
}
|
|
525
301
|
async function unpackReleaseSkills(ctx, release) {
|
|
526
302
|
const key = release.manifest?.skills.key ?? skillsKey(release.version);
|
|
527
|
-
const dir =
|
|
528
|
-
const tarball =
|
|
303
|
+
const dir = mkdtempSync(join2(tmpdir(), "foundation-skills-"));
|
|
304
|
+
const tarball = join2(dir, "skills.tar.gz");
|
|
529
305
|
await awsMutate(ctx, ["s3", "cp", `s3://${release.bucket}/${key}`, tarball]);
|
|
530
306
|
return await extractSkillsTarball(tarball, dir, { dryRun: ctx.dryRun });
|
|
531
307
|
}
|
|
532
308
|
async function extractSkillsTarball(tarball, dir, opts = {}) {
|
|
533
|
-
const contents =
|
|
309
|
+
const contents = join2(dir, "skills");
|
|
534
310
|
await run(["tar", "-xzf", tarball, "-C", dir], { dryRun: opts.dryRun });
|
|
535
311
|
if (opts.dryRun !== true && !existsSync3(contents))
|
|
536
312
|
throw new Error(`${tarball} has no top-level skills/ directory`);
|
|
@@ -543,7 +319,7 @@ async function extractSkillsTarball(tarball, dir, opts = {}) {
|
|
|
543
319
|
import { createHash } from "node:crypto";
|
|
544
320
|
import { existsSync as existsSync4 } from "node:fs";
|
|
545
321
|
import { createRequire } from "node:module";
|
|
546
|
-
import { join as
|
|
322
|
+
import { join as join4 } from "node:path";
|
|
547
323
|
|
|
548
324
|
// ../connectors/src/github.ts
|
|
549
325
|
var GITHUB_TOKEN_RESPONSE_MAX_BYTES = 128 * 1024;
|
|
@@ -773,9 +549,6 @@ function isSafeOpaqueString(value, max) {
|
|
|
773
549
|
function isPlainObject(value) {
|
|
774
550
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
775
551
|
}
|
|
776
|
-
// ../connectors/src/crm.ts
|
|
777
|
-
var CRM_MAX_REQUEST_BYTES = 512 * 1024;
|
|
778
|
-
var CRM_MAX_RESPONSE_BYTES = 512 * 1024;
|
|
779
552
|
// ../connectors/src/upwork.ts
|
|
780
553
|
var UPWORK_MAX_RESPONSE_BYTES = 1024 * 1024;
|
|
781
554
|
var UPWORK_MAX_ACCESS_TOKEN_TTL_SECONDS = 24 * 60 * 60;
|
|
@@ -962,8 +735,8 @@ function adminsCsvFromFile(path) {
|
|
|
962
735
|
|
|
963
736
|
// src/deploy/endpoint.ts
|
|
964
737
|
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
965
|
-
import { tmpdir as
|
|
966
|
-
import { join as
|
|
738
|
+
import { tmpdir as tmpdir2 } from "node:os";
|
|
739
|
+
import { join as join3 } from "node:path";
|
|
967
740
|
function cliRunner(ctx) {
|
|
968
741
|
return {
|
|
969
742
|
capture: (args) => aws(ctx, args),
|
|
@@ -1111,8 +884,8 @@ async function smokeInvoke(runner, opts) {
|
|
|
1111
884
|
await runner.mutate([...args, "/dev/stdout"]);
|
|
1112
885
|
return;
|
|
1113
886
|
}
|
|
1114
|
-
const dir = await mkdtemp(
|
|
1115
|
-
const outFile =
|
|
887
|
+
const dir = await mkdtemp(join3(tmpdir2(), "foundation-smoke-"));
|
|
888
|
+
const outFile = join3(dir, "response.json");
|
|
1116
889
|
let out;
|
|
1117
890
|
try {
|
|
1118
891
|
const printed = await runner.capture([...args, outFile]);
|
|
@@ -1453,11 +1226,11 @@ import {
|
|
|
1453
1226
|
mkdirSync as mkdirSync3,
|
|
1454
1227
|
readFileSync as readFileSync5,
|
|
1455
1228
|
realpathSync,
|
|
1456
|
-
rmSync
|
|
1229
|
+
rmSync,
|
|
1457
1230
|
statSync,
|
|
1458
|
-
writeFileSync
|
|
1231
|
+
writeFileSync
|
|
1459
1232
|
} from "node:fs";
|
|
1460
|
-
import { dirname as
|
|
1233
|
+
import { dirname as dirname3, isAbsolute as isAbsolute2, posix, relative, resolve as resolve4, sep } from "node:path";
|
|
1461
1234
|
import { isDeepStrictEqual } from "node:util";
|
|
1462
1235
|
function sourceRootFor(sourceDir) {
|
|
1463
1236
|
if (sourceDir === undefined || sourceDir === "")
|
|
@@ -1561,7 +1334,7 @@ function stageCustomization(options) {
|
|
|
1561
1334
|
const platformConfigPath = options.paths.configPath;
|
|
1562
1335
|
const stagedPath = stagedCustomizationPath(options.paths);
|
|
1563
1336
|
if (options.dryRun !== true)
|
|
1564
|
-
|
|
1337
|
+
rmSync(stagedPath, { force: true });
|
|
1565
1338
|
const customization = instance.customization;
|
|
1566
1339
|
if (customization === undefined)
|
|
1567
1340
|
return { status: "not-configured", runtimeConfigPath: platformConfigPath };
|
|
@@ -1579,8 +1352,8 @@ function stageCustomization(options) {
|
|
|
1579
1352
|
assertValidTenantJobs(tenantConfig);
|
|
1580
1353
|
assertProtectedPolicy(platformConfig, tenantConfig);
|
|
1581
1354
|
if (options.dryRun !== true) {
|
|
1582
|
-
mkdirSync3(
|
|
1583
|
-
|
|
1355
|
+
mkdirSync3(dirname3(stagedPath), { recursive: true });
|
|
1356
|
+
writeFileSync(stagedPath, yaml);
|
|
1584
1357
|
}
|
|
1585
1358
|
return {
|
|
1586
1359
|
status: "staged",
|
|
@@ -1657,10 +1430,10 @@ function cdkCommand() {
|
|
|
1657
1430
|
}
|
|
1658
1431
|
}
|
|
1659
1432
|
function cdkAppCommand(infraRoot = INFRA_ROOT) {
|
|
1660
|
-
const source =
|
|
1433
|
+
const source = join4(infraRoot, "bin", "app.ts");
|
|
1661
1434
|
if (existsSync4(source))
|
|
1662
1435
|
return `bun run ${source}`;
|
|
1663
|
-
return `${process.execPath} ${
|
|
1436
|
+
return `${process.execPath} ${join4(infraRoot, "dist", "bin", "app.js")}`;
|
|
1664
1437
|
}
|
|
1665
1438
|
async function cdkDeploy(ctx, opts) {
|
|
1666
1439
|
const context = [
|
|
@@ -1931,9 +1704,9 @@ import { createServer } from "node:http";
|
|
|
1931
1704
|
|
|
1932
1705
|
// src/deploy/github-app-manifest.ts
|
|
1933
1706
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
1934
|
-
import { join as
|
|
1707
|
+
import { join as join5 } from "node:path";
|
|
1935
1708
|
import { parse as parseYaml } from "yaml";
|
|
1936
|
-
var GITHUB_APP_MANIFEST_PATH =
|
|
1709
|
+
var GITHUB_APP_MANIFEST_PATH = join5(PACKAGE_ASSETS, "github-app-manifest.yml");
|
|
1937
1710
|
function manifestUrl(instance) {
|
|
1938
1711
|
const repo = instance.github.defaultRepo ?? instanceNames(instance).defaultRepo;
|
|
1939
1712
|
return `https://github.com/${repo === "" ? instance.github.org : repo}`;
|
|
@@ -2148,9 +1921,9 @@ import { resolve as resolve5 } from "node:path";
|
|
|
2148
1921
|
|
|
2149
1922
|
// src/deploy/slack-manifest.ts
|
|
2150
1923
|
import { readFileSync as readFileSync8 } from "node:fs";
|
|
2151
|
-
import { join as
|
|
1924
|
+
import { join as join6 } from "node:path";
|
|
2152
1925
|
import { parse as parse2 } from "yaml";
|
|
2153
|
-
var SLACK_APP_MANIFEST_PATH =
|
|
1926
|
+
var SLACK_APP_MANIFEST_PATH = join6(PACKAGE_ASSETS, "slack-app-manifest.yml");
|
|
2154
1927
|
function buildSlackManifest(template, instance, env) {
|
|
2155
1928
|
const events = env.EVENTS_REQUEST_URL;
|
|
2156
1929
|
const commands = env.COMMANDS_REQUEST_URL ?? events?.replace(/\/events$/, "/commands");
|
|
@@ -2598,7 +2371,10 @@ var COMMANDS = [
|
|
|
2598
2371
|
"github-app-create",
|
|
2599
2372
|
"slack-manifest",
|
|
2600
2373
|
"stage-customization",
|
|
2601
|
-
"job-key"
|
|
2374
|
+
"job-key",
|
|
2375
|
+
"crm-key",
|
|
2376
|
+
"crm-check",
|
|
2377
|
+
"crm-replay"
|
|
2602
2378
|
];
|
|
2603
2379
|
var USAGE = `usage: foundation-deploy <command> --instance <path> [options]
|
|
2604
2380
|
|
|
@@ -2617,6 +2393,12 @@ commands
|
|
|
2617
2393
|
slack-manifest print the instance's Slack app manifest as JSON
|
|
2618
2394
|
stage-customization validate and stage a tenant-owned runtime config
|
|
2619
2395
|
job-key <id> print a webhook job's URL and signing key as JSON
|
|
2396
|
+
crm-key write the company CRM's crm/1 key, read from stdin, into
|
|
2397
|
+
the instance's <secretPrefix>/crm/http secret
|
|
2398
|
+
crm-check run the crm/1 conformance suite against the instance's
|
|
2399
|
+
CRM (crm.provider kind http) with its stored key
|
|
2400
|
+
crm-replay replay the agent's CRM writes (JSONL) into the
|
|
2401
|
+
instance's own table after a rollback to dynamodb
|
|
2620
2402
|
|
|
2621
2403
|
options
|
|
2622
2404
|
--instance <path> REQUIRED: path to the deployment's instance YAML
|
|
@@ -2652,6 +2434,24 @@ job-key options
|
|
|
2652
2434
|
--put-secret <id> write {url, key} into this Secrets Manager secret
|
|
2653
2435
|
(created if absent) instead of printing the key
|
|
2654
2436
|
|
|
2437
|
+
crm-key options
|
|
2438
|
+
--stdin REQUIRED: read the key from a pipe, never argv, e.g.
|
|
2439
|
+
pbpaste | foundation-deploy crm-key --instance <path> --stdin
|
|
2440
|
+
|
|
2441
|
+
crm-check options
|
|
2442
|
+
--allow-skips pass despite cases only a person can set up (they need
|
|
2443
|
+
the CRM's own harness); without it they count as failed
|
|
2444
|
+
--base-url <url> check any crm/1 server instead of the instance's: needs
|
|
2445
|
+
--tenant <id> the tenant its /meta names, and the key on stdin
|
|
2446
|
+
--key-stdin (--key-stdin). No --instance in this form.
|
|
2447
|
+
--write with --base-url only: also run the write cases, which
|
|
2448
|
+
create records and spend the week's outreach allowance;
|
|
2449
|
+
point it at a sandbox tenant
|
|
2450
|
+
|
|
2451
|
+
crm-replay options
|
|
2452
|
+
--from <file> REQUIRED: the writes to replay, one JSON object per line
|
|
2453
|
+
(docs/crm-provider-contract.md); --dry-run validates only
|
|
2454
|
+
|
|
2655
2455
|
github-app-create options
|
|
2656
2456
|
--org <org> GitHub org (default: the instance's github.org)
|
|
2657
2457
|
--port <n> local callback port (default 8765)
|
|
@@ -2731,6 +2531,21 @@ ${USAGE}`);
|
|
|
2731
2531
|
console.log(JSON.stringify(result));
|
|
2732
2532
|
return 0;
|
|
2733
2533
|
}
|
|
2534
|
+
if (command === "crm-check" && args.includes("--base-url")) {
|
|
2535
|
+
if (args.some((arg) => arg === "--instance" || arg.startsWith("--instance=")))
|
|
2536
|
+
throw new Error("crm-check takes --instance or --base-url, not both");
|
|
2537
|
+
const baseUrl = flag2(args, "--base-url");
|
|
2538
|
+
const tenantId = flag2(args, "--tenant");
|
|
2539
|
+
if (baseUrl === undefined || tenantId === undefined || !args.includes("--key-stdin"))
|
|
2540
|
+
throw new Error("crm-check --base-url needs --tenant <id> and --key-stdin");
|
|
2541
|
+
const { crmCheckTarget, defaultCrmCommandDeps } = await import("../chunk-sgdcmme0.js");
|
|
2542
|
+
return crmCheckTarget({
|
|
2543
|
+
baseUrl,
|
|
2544
|
+
tenantId,
|
|
2545
|
+
write: args.includes("--write"),
|
|
2546
|
+
allowSkips: args.includes("--allow-skips")
|
|
2547
|
+
}, defaultCrmCommandDeps());
|
|
2548
|
+
}
|
|
2734
2549
|
if (command === "github-app-create") {
|
|
2735
2550
|
const paths = loadInstanceContext(resolveInstanceFilePath(args, process.env));
|
|
2736
2551
|
console.log(instanceBanner(paths));
|
|
@@ -2795,6 +2610,25 @@ ${USAGE}`);
|
|
|
2795
2610
|
console.log(` ${result.status}: ${result.runtimeConfigPath}`);
|
|
2796
2611
|
return 0;
|
|
2797
2612
|
}
|
|
2613
|
+
case "crm-key": {
|
|
2614
|
+
const { crmKey, defaultCrmCommandDeps } = await import("../chunk-sgdcmme0.js");
|
|
2615
|
+
await crmKey(ctx, { stdin: args.includes("--stdin") }, defaultCrmCommandDeps());
|
|
2616
|
+
return 0;
|
|
2617
|
+
}
|
|
2618
|
+
case "crm-check": {
|
|
2619
|
+
if (args.includes("--write"))
|
|
2620
|
+
throw new Error("crm-check --write creates records and spends the week's outreach allowance, so it never runs against the instance's own tenant: use --base-url with a sandbox tenant");
|
|
2621
|
+
const { crmCheck, defaultCrmCommandDeps } = await import("../chunk-sgdcmme0.js");
|
|
2622
|
+
return crmCheck(ctx, { allowSkips: args.includes("--allow-skips") }, defaultCrmCommandDeps());
|
|
2623
|
+
}
|
|
2624
|
+
case "crm-replay": {
|
|
2625
|
+
const from = flag2(args, "--from");
|
|
2626
|
+
if (from === undefined || from.startsWith("-"))
|
|
2627
|
+
throw new Error("crm-replay needs --from <file>: the writes to replay, as JSONL");
|
|
2628
|
+
const { crmReplay, defaultCrmCommandDeps } = await import("../chunk-sgdcmme0.js");
|
|
2629
|
+
const result = await crmReplay(ctx, { from }, defaultCrmCommandDeps());
|
|
2630
|
+
return result.refused.length > 0 ? 1 : 0;
|
|
2631
|
+
}
|
|
2798
2632
|
default:
|
|
2799
2633
|
console.error(`unhandled command: ${command}
|
|
2800
2634
|
|