@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.
@@ -0,0 +1,259 @@
1
+ import {
2
+ instanceNames,
3
+ loadInstanceFile
4
+ } from "./chunk-rxetj2p9.js";
5
+
6
+ // src/deploy/aws.ts
7
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+
11
+ // src/deploy/instance.ts
12
+ import { dirname } from "node:path";
13
+ var INSTANCE_FILE_ENV = "FOUNDATION_INSTANCE_FILE";
14
+ function resolveInstanceFilePath(argv = [], env = {}) {
15
+ for (let i = 0;i < argv.length; i++) {
16
+ const arg = argv[i] ?? "";
17
+ if (arg === "--instance") {
18
+ const next = argv[i + 1];
19
+ if (next === undefined || next.startsWith("-"))
20
+ throw new Error("--instance needs a path, e.g. --instance .foundation/instance.yaml");
21
+ return next;
22
+ }
23
+ if (arg.startsWith("--instance=")) {
24
+ const value = arg.slice("--instance=".length);
25
+ if (value === "")
26
+ throw new Error("--instance needs a path");
27
+ return value;
28
+ }
29
+ }
30
+ const fromEnv = env[INSTANCE_FILE_ENV];
31
+ if (fromEnv !== undefined && fromEnv !== "")
32
+ return fromEnv;
33
+ throw new Error(`--instance <path> is required (or set ${INSTANCE_FILE_ENV}); it is the path to the deployment's instance YAML`);
34
+ }
35
+ function loadInstanceContext(path, cwd = process.cwd()) {
36
+ const file = loadInstanceFile(path, cwd);
37
+ return { ...file, root: dirname(file.path) };
38
+ }
39
+ function loadInstanceFromArgs(argv = process.argv.slice(2), env = process.env) {
40
+ return loadInstanceContext(resolveInstanceFilePath(argv, env));
41
+ }
42
+ function instanceBanner(context) {
43
+ const { instance } = context;
44
+ return `instance: ${instance.name} (account ${instance.aws.account}, region ${instance.aws.region})
45
+ file: ${context.path}
46
+ config: ${context.configPath}`;
47
+ }
48
+
49
+ // src/deploy/sh.ts
50
+ import { spawn as nodeSpawn } from "node:child_process";
51
+ function formatCommand(cmd, env) {
52
+ const prefix = Object.entries(env ?? {}).map(([k, v]) => `${k}=${quote(v)}`).join(" ");
53
+ const body = cmd.map(quote).join(" ");
54
+ return prefix === "" ? body : `${prefix} ${body}`;
55
+ }
56
+ function quote(value) {
57
+ return /^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
58
+ }
59
+ function start(cmd, opts, stdout) {
60
+ const [bin, ...rest] = cmd;
61
+ if (bin === undefined)
62
+ throw new Error("run: empty command");
63
+ const child = nodeSpawn(bin, rest, {
64
+ cwd: opts.cwd,
65
+ env: { ...process.env, ...opts.env },
66
+ stdio: [
67
+ opts.stdin === undefined ? "ignore" : "pipe",
68
+ stdout,
69
+ stdout === "inherit" ? "inherit" : "pipe"
70
+ ]
71
+ });
72
+ if (opts.stdin !== undefined && child.stdin !== null) {
73
+ child.stdin.end(opts.stdin);
74
+ }
75
+ return child;
76
+ }
77
+ function exited(child) {
78
+ return new Promise((resolve, reject) => {
79
+ child.once("error", reject);
80
+ child.once("close", (code, signal) => resolve(code ?? (signal === null ? 1 : 128)));
81
+ });
82
+ }
83
+ function collect(stream) {
84
+ if (stream === null)
85
+ return Promise.resolve("");
86
+ return new Promise((resolve, reject) => {
87
+ const chunks = [];
88
+ stream.on("data", (c) => chunks.push(c));
89
+ stream.once("error", reject);
90
+ stream.once("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
91
+ });
92
+ }
93
+ async function run(cmd, opts = {}) {
94
+ if (opts.dryRun === true) {
95
+ console.log(` $ ${formatCommand(cmd, opts.env)}`);
96
+ return;
97
+ }
98
+ const code = await exited(start(cmd, opts, "inherit"));
99
+ if (code !== 0)
100
+ throw new Error(`${formatCommand(cmd)} exited ${code}`);
101
+ }
102
+ async function runCapture(cmd, opts = {}) {
103
+ const child = start(cmd, opts, "pipe");
104
+ const [out, err, code] = await Promise.all([
105
+ collect(child.stdout),
106
+ collect(child.stderr),
107
+ exited(child)
108
+ ]);
109
+ if (code !== 0)
110
+ throw new Error(`${formatCommand(cmd)} exited ${code}: ${err.trim()}`);
111
+ return out.trim();
112
+ }
113
+
114
+ // src/deploy/aws.ts
115
+ function resolveProfile(fallback, explicit, env = process.env) {
116
+ if (env.FOUNDATION_NO_PROFILE === "1")
117
+ return "";
118
+ if (explicit !== undefined)
119
+ return explicit;
120
+ if (env.AWS_PROFILE !== undefined && env.AWS_PROFILE !== "")
121
+ return env.AWS_PROFILE;
122
+ if (env.GITHUB_ACTIONS === "true")
123
+ return "";
124
+ return fallback;
125
+ }
126
+ function awsContext(overrides = {}) {
127
+ const env = overrides.env ?? process.env;
128
+ const paths = overrides.paths ?? loadInstanceFromArgs();
129
+ const instance = paths.instance;
130
+ return {
131
+ profile: resolveProfile(instance.aws.profile, overrides.profile, env),
132
+ region: overrides.region ?? env.AWS_REGION ?? instance.aws.region,
133
+ dryRun: overrides.dryRun ?? false,
134
+ instance,
135
+ paths,
136
+ names: instanceNames(instance)
137
+ };
138
+ }
139
+ function argv(ctx, args) {
140
+ const profile = ctx.profile === "" ? [] : ["--profile", ctx.profile];
141
+ return ["aws", ...args, ...profile, "--region", ctx.region];
142
+ }
143
+ function cdkEnv(ctx) {
144
+ return {
145
+ ...ctx.profile === "" ? {} : { AWS_PROFILE: ctx.profile },
146
+ AWS_REGION: ctx.region,
147
+ CDK_DEFAULT_REGION: ctx.region
148
+ };
149
+ }
150
+ async function aws(ctx, args, stdin) {
151
+ return runCapture(argv(ctx, args), { stdin });
152
+ }
153
+ async function awsMutate(ctx, args, stdin) {
154
+ if (ctx.dryRun === true) {
155
+ console.log(` $ ${argv(ctx, args).join(" ")}${stdin === undefined ? "" : " < (stdin)"}`);
156
+ return;
157
+ }
158
+ await run(argv(ctx, args), { stdin });
159
+ }
160
+ async function stackOutput(ctx, stack, key) {
161
+ const value = await aws(ctx, [
162
+ "cloudformation",
163
+ "describe-stacks",
164
+ "--stack-name",
165
+ stack,
166
+ "--query",
167
+ `Stacks[0].Outputs[?OutputKey=='${key}'].OutputValue`,
168
+ "--output",
169
+ "text"
170
+ ]);
171
+ if (value === "" || value === "None")
172
+ throw new Error(`stack ${stack} has no output ${key} — has it been deployed?`);
173
+ return value;
174
+ }
175
+ async function stackExists(ctx, stack) {
176
+ try {
177
+ await aws(ctx, [
178
+ "cloudformation",
179
+ "describe-stacks",
180
+ "--stack-name",
181
+ stack,
182
+ "--output",
183
+ "text"
184
+ ]);
185
+ return true;
186
+ } catch {
187
+ return false;
188
+ }
189
+ }
190
+ async function secretExists(ctx, secretId) {
191
+ try {
192
+ await aws(ctx, [
193
+ "secretsmanager",
194
+ "describe-secret",
195
+ "--secret-id",
196
+ secretId,
197
+ "--output",
198
+ "text"
199
+ ]);
200
+ return true;
201
+ } catch {
202
+ return false;
203
+ }
204
+ }
205
+ async function readSecretString(ctx, secretId) {
206
+ return aws(ctx, [
207
+ "secretsmanager",
208
+ "get-secret-value",
209
+ "--secret-id",
210
+ secretId,
211
+ "--query",
212
+ "SecretString",
213
+ "--output",
214
+ "text"
215
+ ]);
216
+ }
217
+ async function readSecretJson(ctx, secretId) {
218
+ return JSON.parse(await readSecretString(ctx, secretId));
219
+ }
220
+ async function putSecretString(ctx, secretId, value) {
221
+ await throughSecretFile(value, (file) => awsMutate(ctx, [
222
+ "secretsmanager",
223
+ "put-secret-value",
224
+ "--secret-id",
225
+ secretId,
226
+ "--secret-string",
227
+ `file://${file}`
228
+ ]));
229
+ }
230
+ async function createSecretString(ctx, secretId, value, description) {
231
+ await throughSecretFile(value, (file) => awsMutate(ctx, [
232
+ "secretsmanager",
233
+ "create-secret",
234
+ "--name",
235
+ secretId,
236
+ "--description",
237
+ description,
238
+ "--secret-string",
239
+ `file://${file}`
240
+ ]));
241
+ }
242
+ async function throughSecretFile(value, body) {
243
+ const dir = mkdtempSync(join(tmpdir(), "foundation-secret-"));
244
+ const file = join(dir, "value.json");
245
+ try {
246
+ writeFileSync(file, value, { mode: 384 });
247
+ await body(file);
248
+ } finally {
249
+ rmSync(dir, { recursive: true, force: true });
250
+ }
251
+ }
252
+ async function putSecretJson(ctx, secretId, value) {
253
+ await putSecretString(ctx, secretId, JSON.stringify(value));
254
+ }
255
+ async function callerAccountId(ctx) {
256
+ return aws(ctx, ["sts", "get-caller-identity", "--query", "Account", "--output", "text"]);
257
+ }
258
+
259
+ export { resolveInstanceFilePath, loadInstanceContext, instanceBanner, run, runCapture, awsContext, argv, cdkEnv, aws, awsMutate, stackOutput, stackExists, secretExists, readSecretString, readSecretJson, putSecretString, createSecretString, putSecretJson, callerAccountId };