rastack 0.0.62 → 0.0.63
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/CHANGELOG.md +7 -0
- package/auth/provider.ts +71 -8
- package/dist/admin.js +9 -9
- package/dist/deploy/ci.d.ts +10 -3
- package/dist/deploy/ci.js +22 -11
- package/dist/deploy/config.d.ts +155 -5
- package/dist/deploy/config.js +216 -6
- package/dist/deploy/io.d.ts +7 -0
- package/dist/deploy/io.js +17 -0
- package/dist/deploy/workflows.d.ts +30 -0
- package/dist/deploy/workflows.js +117 -12
- package/dist/rastack-ci.js +19 -1
- package/dist/rastack-dev.js +48 -2
- package/dist/rastack-init.js +190 -64
- package/dist/rastack.js +1 -0
- package/dist/templates/cloudformation/app.yaml +133 -2
- package/dist/wasm/rastack_wasm_bg.wasm +0 -0
- package/package.json +1 -1
- package/src/deploy/ci.ts +50 -16
- package/src/deploy/config.ts +340 -7
- package/src/deploy/io.ts +17 -1
- package/src/deploy/workflows.ts +122 -12
- package/src/rastack-ci.ts +20 -2
- package/src/rastack-dev.ts +61 -6
- package/src/rastack-init.ts +235 -76
- package/src/rastack.ts +1 -0
package/src/rastack-init.ts
CHANGED
|
@@ -33,18 +33,28 @@ import {
|
|
|
33
33
|
BOOTSTRAP_TEMPLATES_OUTPUT,
|
|
34
34
|
DeployConfig,
|
|
35
35
|
DeployOutputs,
|
|
36
|
+
EnvName,
|
|
37
|
+
ENV_NAMES,
|
|
36
38
|
PartialDeployConfig,
|
|
37
39
|
bootstrapStackName,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
deployStackCommandForEnv,
|
|
41
|
+
deployStackArgsForEnv,
|
|
42
|
+
ghEnvironmentCreateArgs,
|
|
40
43
|
ghVariableSetArgs,
|
|
41
44
|
mergeConfig,
|
|
42
45
|
normalizeConfig,
|
|
43
46
|
parseGitHubRemote,
|
|
44
|
-
|
|
47
|
+
repoVariablesForEnv,
|
|
48
|
+
resolveEnvironment,
|
|
45
49
|
validateConfig,
|
|
46
50
|
} from "./deploy/config";
|
|
47
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
ENV_PATH,
|
|
53
|
+
GITIGNORE_PATH,
|
|
54
|
+
applyEnvValues,
|
|
55
|
+
gitignoreWith,
|
|
56
|
+
scaffoldFiles,
|
|
57
|
+
} from "./deploy/workflows";
|
|
48
58
|
import {
|
|
49
59
|
ask,
|
|
50
60
|
awsAvailable,
|
|
@@ -53,6 +63,7 @@ import {
|
|
|
53
63
|
ghAuthLogin,
|
|
54
64
|
ghAuthenticated,
|
|
55
65
|
ghAvailable,
|
|
66
|
+
ghEnvironmentCreate,
|
|
56
67
|
gitRemoteUrl,
|
|
57
68
|
loadConfig,
|
|
58
69
|
resolveDeployAsset,
|
|
@@ -65,18 +76,30 @@ import {
|
|
|
65
76
|
|
|
66
77
|
interface Flags {
|
|
67
78
|
patch: PartialDeployConfig;
|
|
79
|
+
/** Which environments to stand up (default: both). */
|
|
80
|
+
envs: EnvName[];
|
|
68
81
|
force: boolean;
|
|
69
82
|
noWorkflows: boolean;
|
|
70
83
|
noDeploy: boolean;
|
|
71
84
|
print: boolean;
|
|
72
85
|
}
|
|
73
86
|
|
|
87
|
+
function parseEnvFlag(value: string): EnvName[] {
|
|
88
|
+
const v = value.trim().toLowerCase();
|
|
89
|
+
if (v === "all" || v === "") return [...ENV_NAMES];
|
|
90
|
+
if ((ENV_NAMES as readonly string[]).includes(v)) return [v as EnvName];
|
|
91
|
+
console.error(`Unknown --env: expected dev | prod | all.`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
|
|
74
95
|
function parseFlags(argv: string[]): Flags {
|
|
75
|
-
const patch: PartialDeployConfig = { github: {} };
|
|
96
|
+
const patch: PartialDeployConfig = { github: {}, environments: {} };
|
|
97
|
+
let envs: EnvName[] = [...ENV_NAMES];
|
|
76
98
|
let force = false;
|
|
77
99
|
let noWorkflows = false;
|
|
78
100
|
let noDeploy = false;
|
|
79
101
|
let print = false;
|
|
102
|
+
const devPatch = () => (patch.environments!.dev ??= {});
|
|
80
103
|
for (let i = 0; i < argv.length; i++) {
|
|
81
104
|
const a = argv[i];
|
|
82
105
|
const next = () => argv[++i] ?? "";
|
|
@@ -93,6 +116,17 @@ function parseFlags(argv: string[]): Flags {
|
|
|
93
116
|
case "--branch":
|
|
94
117
|
patch.github!.branch = next();
|
|
95
118
|
break;
|
|
119
|
+
case "--dev-branch":
|
|
120
|
+
devPatch().branch = next();
|
|
121
|
+
break;
|
|
122
|
+
case "--dev-proxy":
|
|
123
|
+
case "--dev-proxy-upstream":
|
|
124
|
+
// The dev external-API proxy upstream (a UAT/prod backend URL).
|
|
125
|
+
devPatch().proxyUpstream = next();
|
|
126
|
+
break;
|
|
127
|
+
case "--env":
|
|
128
|
+
envs = parseEnvFlag(next());
|
|
129
|
+
break;
|
|
96
130
|
case "--region":
|
|
97
131
|
patch.region = next();
|
|
98
132
|
break;
|
|
@@ -122,7 +156,9 @@ function parseFlags(argv: string[]): Flags {
|
|
|
122
156
|
}
|
|
123
157
|
}
|
|
124
158
|
}
|
|
125
|
-
|
|
159
|
+
// Drop the scratch environments container when nothing was set on it.
|
|
160
|
+
if (patch.environments && !patch.environments.dev) delete patch.environments;
|
|
161
|
+
return { patch, envs, force, noWorkflows, noDeploy, print };
|
|
126
162
|
}
|
|
127
163
|
|
|
128
164
|
/** Seed a config: existing file → flags → git remote → defaults. */
|
|
@@ -166,6 +202,38 @@ async function promptMissing(cfg: DeployConfig): Promise<DeployConfig> {
|
|
|
166
202
|
});
|
|
167
203
|
}
|
|
168
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Ensure `.env` is git-ignored and (re)write the generated `.env` with the
|
|
207
|
+
* current config, preserving any secrets a developer already added. Returns the
|
|
208
|
+
* repo-relative paths touched, for the written/skipped summary.
|
|
209
|
+
*/
|
|
210
|
+
function writeEnvAndGitignore(cfg: DeployConfig): string[] {
|
|
211
|
+
const touched: string[] = [];
|
|
212
|
+
|
|
213
|
+
// 1. git-ignore `.env` (append-merge; never duplicate or clobber).
|
|
214
|
+
const giAbs = path.join(process.cwd(), GITIGNORE_PATH);
|
|
215
|
+
const existingGi = fs.existsSync(giAbs)
|
|
216
|
+
? fs.readFileSync(giAbs, "utf-8")
|
|
217
|
+
: "";
|
|
218
|
+
const nextGi = gitignoreWith(existingGi, ENV_PATH);
|
|
219
|
+
if (nextGi !== existingGi) {
|
|
220
|
+
fs.writeFileSync(giAbs, nextGi);
|
|
221
|
+
touched.push(GITIGNORE_PATH);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 2. Upsert the generated RASTACK_* values into `.env` (secrets preserved).
|
|
225
|
+
const envAbs = path.join(process.cwd(), ENV_PATH);
|
|
226
|
+
const existingEnv = fs.existsSync(envAbs)
|
|
227
|
+
? fs.readFileSync(envAbs, "utf-8")
|
|
228
|
+
: "";
|
|
229
|
+
const nextEnv = applyEnvValues(existingEnv, cfg);
|
|
230
|
+
if (nextEnv !== existingEnv) {
|
|
231
|
+
fs.writeFileSync(envAbs, nextEnv);
|
|
232
|
+
touched.push(ENV_PATH);
|
|
233
|
+
}
|
|
234
|
+
return touched;
|
|
235
|
+
}
|
|
236
|
+
|
|
169
237
|
/** Yes/no confirmation with a default of "no" — gates the cloud-mutating steps. */
|
|
170
238
|
async function confirm(question: string): Promise<boolean> {
|
|
171
239
|
return withPrompt(async (rl) => {
|
|
@@ -181,24 +249,42 @@ function table(rows: { key: string; value: string }[]): string {
|
|
|
181
249
|
}
|
|
182
250
|
|
|
183
251
|
/**
|
|
184
|
-
* Read
|
|
185
|
-
* not resolvable yet (best-effort — a freshly created stack has them all).
|
|
252
|
+
* Read one environment's app.yaml stack Outputs into a `DeployOutputs`, skipping
|
|
253
|
+
* any not resolvable yet (best-effort — a freshly created stack has them all).
|
|
186
254
|
*/
|
|
187
|
-
function
|
|
255
|
+
function readEnvOutputs(cfg: DeployConfig, env: EnvName): DeployOutputs {
|
|
256
|
+
const stackName = resolveEnvironment(cfg, env).stackName;
|
|
188
257
|
const outputs: DeployOutputs = {};
|
|
189
258
|
for (const { key, field } of APP_STACK_OUTPUTS) {
|
|
190
|
-
const value = stackOutput(
|
|
259
|
+
const value = stackOutput(stackName, key, cfg.region);
|
|
191
260
|
if (value) outputs[field] = value;
|
|
192
261
|
}
|
|
193
262
|
return outputs;
|
|
194
263
|
}
|
|
195
264
|
|
|
265
|
+
/** Merge an environment's resolved outputs back into the config in the right slot. */
|
|
266
|
+
function withEnvOutputs(
|
|
267
|
+
cfg: DeployConfig,
|
|
268
|
+
env: EnvName,
|
|
269
|
+
outputs: DeployOutputs,
|
|
270
|
+
): DeployConfig {
|
|
271
|
+
return env === "prod"
|
|
272
|
+
? mergeConfig(cfg, { outputs })
|
|
273
|
+
: mergeConfig(cfg, { environments: { dev: { outputs } } });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Human label for an environment ("dev" / "prod") plus its branch. */
|
|
277
|
+
function envLabel(cfg: DeployConfig, env: EnvName): string {
|
|
278
|
+
const e = resolveEnvironment(cfg, env);
|
|
279
|
+
return `${env} (${e.branch} → ${e.ghEnvironment})`;
|
|
280
|
+
}
|
|
281
|
+
|
|
196
282
|
/**
|
|
197
283
|
* Print the exact commands `init` would run, without touching the cloud
|
|
198
284
|
* (`--print`). The artifacts bucket is read from the bootstrap stack when the
|
|
199
285
|
* AWS CLI is available, else shown as a placeholder.
|
|
200
286
|
*/
|
|
201
|
-
function printPlan(cfg: DeployConfig): void {
|
|
287
|
+
function printPlan(cfg: DeployConfig, envs: EnvName[]): void {
|
|
202
288
|
const bucket = awsAvailable()
|
|
203
289
|
? stackOutput(
|
|
204
290
|
bootstrapStackName(cfg),
|
|
@@ -210,26 +296,37 @@ function printPlan(cfg: DeployConfig): void {
|
|
|
210
296
|
artifactsBucket: bucket ?? "<ArtifactsBucketName from bootstrap stack>",
|
|
211
297
|
});
|
|
212
298
|
const appTemplate = resolveDeployAsset("app.yaml") ?? undefined;
|
|
213
|
-
console.log(`\n Deployment plan (${
|
|
299
|
+
console.log(`\n Deployment plan (${envs.join(" + ")} @ ${cfg.region})\n`);
|
|
214
300
|
console.log(
|
|
215
301
|
` ── Prerequisite: the tier-0 bootstrap stack (${bootstrapStackName(cfg)}) must exist ──`,
|
|
216
302
|
);
|
|
217
303
|
console.log(
|
|
218
304
|
` Create it once per account+region (init does not): rastack bootstrap --region ${cfg.region}\n`,
|
|
219
305
|
);
|
|
220
|
-
console.log(" ── 1. Per-app backend (app.yaml) — what init deploys ──");
|
|
221
|
-
console.log("\n " + deployStackCommand(withBucket, appTemplate) + "\n");
|
|
222
306
|
console.log(
|
|
223
|
-
" ──
|
|
307
|
+
" ── 1. Per-app backend (app.yaml) — one stack per environment ──",
|
|
224
308
|
);
|
|
309
|
+
for (const env of envs) {
|
|
310
|
+
const e = resolveEnvironment(withBucket, env);
|
|
311
|
+
console.log(`\n # ${envLabel(withBucket, env)} — stack ${e.stackName}`);
|
|
312
|
+
console.log(" " + deployStackCommandForEnv(withBucket, env, appTemplate));
|
|
313
|
+
}
|
|
225
314
|
console.log(
|
|
226
|
-
|
|
227
|
-
repoVariables(cfg).map((v) => ({
|
|
228
|
-
key: v.name,
|
|
229
|
-
value: v.value || `<${v.from}>`,
|
|
230
|
-
})),
|
|
231
|
-
) + "\n",
|
|
315
|
+
"\n ── 2. Wire each GitHub Environment's Variables from its stack Outputs ──",
|
|
232
316
|
);
|
|
317
|
+
for (const env of envs) {
|
|
318
|
+
const e = resolveEnvironment(cfg, env);
|
|
319
|
+
console.log(`\n # GitHub Environment: ${e.ghEnvironment}`);
|
|
320
|
+
console.log(
|
|
321
|
+
table(
|
|
322
|
+
repoVariablesForEnv(cfg, env).map((v) => ({
|
|
323
|
+
key: v.name,
|
|
324
|
+
value: v.value || `<${v.from}>`,
|
|
325
|
+
})),
|
|
326
|
+
),
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
console.log("");
|
|
233
330
|
}
|
|
234
331
|
|
|
235
332
|
/**
|
|
@@ -239,6 +336,7 @@ function printPlan(cfg: DeployConfig): void {
|
|
|
239
336
|
*/
|
|
240
337
|
async function standUp(
|
|
241
338
|
cfg: DeployConfig,
|
|
339
|
+
envs: EnvName[],
|
|
242
340
|
interactive: boolean,
|
|
243
341
|
): Promise<DeployConfig> {
|
|
244
342
|
if (!awsAvailable()) {
|
|
@@ -278,7 +376,7 @@ async function standUp(
|
|
|
278
376
|
}
|
|
279
377
|
console.log(` ✓ artifacts bucket: ${artifactsBucket}`);
|
|
280
378
|
|
|
281
|
-
// 2. Upload the placeholder the function is created from.
|
|
379
|
+
// 2. Upload the placeholder the function is created from (shared by all envs).
|
|
282
380
|
console.log("\n▸ Uploading the Lambda placeholder bundle");
|
|
283
381
|
if (!uploadPlaceholder(artifactsBucket, cfg.region)) {
|
|
284
382
|
console.error(
|
|
@@ -287,7 +385,6 @@ async function standUp(
|
|
|
287
385
|
process.exit(1);
|
|
288
386
|
}
|
|
289
387
|
|
|
290
|
-
// 3. Deploy the per-app backend with the resolved bucket wired in.
|
|
291
388
|
cfg = mergeConfig(cfg, {
|
|
292
389
|
artifactsBucket,
|
|
293
390
|
templatesBucket: templatesBucket ?? undefined,
|
|
@@ -300,25 +397,28 @@ async function standUp(
|
|
|
300
397
|
);
|
|
301
398
|
process.exit(1);
|
|
302
399
|
}
|
|
303
|
-
console.log(`\n▸ Deploying ${cfg.stackName}`);
|
|
304
|
-
try {
|
|
305
|
-
awsRun(deployStackArgs(cfg, appTemplate));
|
|
306
|
-
} catch (err) {
|
|
307
|
-
console.error(
|
|
308
|
-
`\n ✗ aws cloudformation deploy failed for ${cfg.stackName}.\n ${(err as Error).message}`,
|
|
309
|
-
);
|
|
310
|
-
process.exit(1);
|
|
311
|
-
}
|
|
312
400
|
|
|
313
|
-
//
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
401
|
+
// 3. Deploy one stack per environment — each with its own Cognito pool,
|
|
402
|
+
// warehouse, Lambda and API — reading the Outputs back into the config.
|
|
403
|
+
for (const env of envs) {
|
|
404
|
+
const stackName = resolveEnvironment(cfg, env).stackName;
|
|
405
|
+
console.log(`\n▸ Deploying ${envLabel(cfg, env)} — ${stackName}`);
|
|
406
|
+
try {
|
|
407
|
+
awsRun(deployStackArgsForEnv(cfg, env, appTemplate));
|
|
408
|
+
} catch (err) {
|
|
409
|
+
console.error(
|
|
410
|
+
`\n ✗ aws cloudformation deploy failed for ${stackName}.\n ${(err as Error).message}`,
|
|
411
|
+
);
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
cfg = withEnvOutputs(cfg, env, readEnvOutputs(cfg, env));
|
|
415
|
+
saveConfig(cfg);
|
|
416
|
+
}
|
|
317
417
|
|
|
318
|
-
//
|
|
319
|
-
// push actually ships. Without this the deploy-backend workflow's
|
|
320
|
-
// vars.AWS_DEPLOY_ROLE_ARN != ''` guard silently skips every run.
|
|
321
|
-
|
|
418
|
+
// 4. Connect the GitHub CLI and wire each environment's scoped Variables, so
|
|
419
|
+
// the FIRST push actually ships. Without this the deploy-backend workflow's
|
|
420
|
+
// `if: vars.AWS_DEPLOY_ROLE_ARN != ''` guard silently skips every run.
|
|
421
|
+
wireEnvironments(cfg, envs, interactive);
|
|
322
422
|
return cfg;
|
|
323
423
|
}
|
|
324
424
|
|
|
@@ -333,20 +433,37 @@ async function standUp(
|
|
|
333
433
|
* (and isn't) done.
|
|
334
434
|
*/
|
|
335
435
|
const wiredVariables = new Set<string>();
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
436
|
+
|
|
437
|
+
/** Key a wired Variable by its GitHub Environment so dev/prod don't collide. */
|
|
438
|
+
function wiredKey(ghEnvironment: string | undefined, name: string): string {
|
|
439
|
+
return `${ghEnvironment ?? ""}:${name}`;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Create each environment's GitHub Actions Environment and wire its scoped
|
|
444
|
+
* `AWS_DEPLOY_ROLE_ARN` / `AWS_REGION` Variables via `gh`, so the GitHub →
|
|
445
|
+
* Lambda pipeline is live for both dev and prod the moment `init` finishes.
|
|
446
|
+
*/
|
|
447
|
+
function wireEnvironments(
|
|
448
|
+
cfg: DeployConfig,
|
|
449
|
+
envs: EnvName[],
|
|
450
|
+
interactive: boolean,
|
|
451
|
+
): void {
|
|
452
|
+
const anyValue = envs.some((env) =>
|
|
453
|
+
repoVariablesForEnv(cfg, env).some((v) => v.value),
|
|
454
|
+
);
|
|
455
|
+
if (!anyValue) return;
|
|
339
456
|
|
|
340
457
|
if (!ghAvailable()) {
|
|
341
458
|
console.log(
|
|
342
|
-
"\n▸ GitHub CLI (`gh`) not found — skipping
|
|
459
|
+
"\n▸ GitHub CLI (`gh`) not found — skipping environment + Variable wiring.\n" +
|
|
343
460
|
" Install `gh` (https://cli.github.com) and re-run, or wire the\n" +
|
|
344
461
|
" Variables below by hand so pushes deploy.",
|
|
345
462
|
);
|
|
346
463
|
return;
|
|
347
464
|
}
|
|
348
465
|
|
|
349
|
-
// Connect `gh` to GitHub if it isn't already, so it can
|
|
466
|
+
// Connect `gh` to GitHub if it isn't already, so it can create environments.
|
|
350
467
|
if (!ghAuthenticated()) {
|
|
351
468
|
if (interactive) {
|
|
352
469
|
console.log(
|
|
@@ -356,53 +473,86 @@ function wireRepoVariables(cfg: DeployConfig, interactive: boolean): void {
|
|
|
356
473
|
}
|
|
357
474
|
if (!ghAuthenticated()) {
|
|
358
475
|
console.log(
|
|
359
|
-
"\n▸ GitHub CLI is not connected — skipping
|
|
476
|
+
"\n▸ GitHub CLI is not connected — skipping environment wiring.\n" +
|
|
360
477
|
" Run `gh auth login`, then wire the Variables below by hand.",
|
|
361
478
|
);
|
|
362
479
|
return;
|
|
363
480
|
}
|
|
364
481
|
}
|
|
365
482
|
|
|
366
|
-
console.log(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
483
|
+
console.log(
|
|
484
|
+
"\n▸ Creating GitHub Environments + wiring their Variables (via `gh`)",
|
|
485
|
+
);
|
|
486
|
+
for (const env of envs) {
|
|
487
|
+
const e = resolveEnvironment(cfg, env);
|
|
488
|
+
// Create the Environment first — a Variable can only be scoped to one that
|
|
489
|
+
// already exists. Idempotent (PUT), so re-running init is safe.
|
|
490
|
+
if (!ghEnvironmentCreate(ghEnvironmentCreateArgs(cfg, e.ghEnvironment))) {
|
|
491
|
+
console.log(
|
|
492
|
+
` • could not create GitHub Environment ${e.ghEnvironment} — create it by hand`,
|
|
493
|
+
);
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
for (const v of repoVariablesForEnv(cfg, env).filter((x) => x.value)) {
|
|
497
|
+
const ok = setRepoVariable(
|
|
498
|
+
ghVariableSetArgs(cfg, v.name, v.value, v.ghEnvironment),
|
|
499
|
+
);
|
|
500
|
+
if (ok) {
|
|
501
|
+
wiredVariables.add(wiredKey(v.ghEnvironment, v.name));
|
|
502
|
+
console.log(` ✓ set ${e.ghEnvironment}/${v.name}`);
|
|
503
|
+
} else {
|
|
504
|
+
console.log(
|
|
505
|
+
` • could not set ${e.ghEnvironment}/${v.name} — wire it by hand (below)`,
|
|
506
|
+
);
|
|
507
|
+
}
|
|
374
508
|
}
|
|
375
509
|
}
|
|
376
510
|
}
|
|
377
511
|
|
|
378
|
-
function printSummary(cfg: DeployConfig): void {
|
|
379
|
-
console.log(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
512
|
+
function printSummary(cfg: DeployConfig, envs: EnvName[]): void {
|
|
513
|
+
console.log(`\n ✓ ${cfg.app} is deployed @ ${cfg.region}`);
|
|
514
|
+
for (const env of envs) {
|
|
515
|
+
const e = resolveEnvironment(cfg, env);
|
|
516
|
+
console.log(`\n ${env} — ${e.stackName}`);
|
|
517
|
+
if (e.outputs?.apiUrl) console.log(` API: ${e.outputs.apiUrl}`);
|
|
518
|
+
if (e.outputs?.hostedUiUrl)
|
|
519
|
+
console.log(` Hosted UI: ${e.outputs.hostedUiUrl}`);
|
|
520
|
+
if (e.outputs?.userPoolId)
|
|
521
|
+
console.log(` User Pool: ${e.outputs.userPoolId}`);
|
|
522
|
+
if (e.outputs?.proxyUrl)
|
|
523
|
+
console.log(` Proxy: ${e.outputs.proxyUrl}`);
|
|
524
|
+
}
|
|
383
525
|
|
|
384
526
|
// Only the Variables `init` could NOT wire itself remain a manual step.
|
|
385
|
-
const pending
|
|
527
|
+
const pending: { key: string; value: string }[] = [];
|
|
528
|
+
for (const env of envs) {
|
|
529
|
+
const e = resolveEnvironment(cfg, env);
|
|
530
|
+
for (const v of repoVariablesForEnv(cfg, env)) {
|
|
531
|
+
if (!wiredVariables.has(wiredKey(v.ghEnvironment, v.name)))
|
|
532
|
+
pending.push({
|
|
533
|
+
key: `${e.ghEnvironment}/${v.name}`,
|
|
534
|
+
value: v.value || `<${v.from}>`,
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
}
|
|
386
538
|
if (pending.length) {
|
|
387
539
|
console.log(
|
|
388
|
-
"\n Wire these GitHub
|
|
389
|
-
|
|
390
|
-
console.log(
|
|
391
|
-
table(
|
|
392
|
-
pending.map((v) => ({
|
|
393
|
-
key: v.name,
|
|
394
|
-
value: v.value || `<${v.from}>`,
|
|
395
|
-
})),
|
|
396
|
-
),
|
|
540
|
+
"\n Wire these per-environment GitHub Variables so CI can ship code\n" +
|
|
541
|
+
" (Settings → Environments → <env> → Variables):",
|
|
397
542
|
);
|
|
543
|
+
console.log(table(pending));
|
|
398
544
|
} else {
|
|
399
545
|
console.log(
|
|
400
|
-
"\n ✓ wired
|
|
546
|
+
"\n ✓ wired every GitHub Environment's Variables (AWS_DEPLOY_ROLE_ARN, AWS_REGION).",
|
|
401
547
|
);
|
|
402
548
|
}
|
|
549
|
+
|
|
550
|
+
const dev = resolveEnvironment(cfg, "dev");
|
|
551
|
+
const prod = resolveEnvironment(cfg, "prod");
|
|
403
552
|
console.log(
|
|
404
|
-
`\n
|
|
405
|
-
`
|
|
553
|
+
`\n Pushes now ship code via \`rastack ci deploy\`:\n` +
|
|
554
|
+
` ${prod.branch} → prod (${prod.ghEnvironment}) ${dev.branch} → dev (${dev.ghEnvironment})\n` +
|
|
555
|
+
` Run one locally with \`rastack deploy --env dev\` / \`--env prod\`.\n`,
|
|
406
556
|
);
|
|
407
557
|
}
|
|
408
558
|
|
|
@@ -446,6 +596,10 @@ async function main(argv: string[]): Promise<void> {
|
|
|
446
596
|
fs.writeFileSync(abs, f.content);
|
|
447
597
|
written.push(f.path);
|
|
448
598
|
}
|
|
599
|
+
// The `.env` (local app environment) + its `.gitignore` entry are standard —
|
|
600
|
+
// generated on every init, secrets preserved, refreshed with Outputs post-deploy.
|
|
601
|
+
for (const p of writeEnvAndGitignore(cfg)) written.push(p);
|
|
602
|
+
|
|
449
603
|
console.log("");
|
|
450
604
|
for (const w of written) console.log(` ✓ wrote ${w}`);
|
|
451
605
|
for (const s of skipped)
|
|
@@ -453,7 +607,7 @@ async function main(argv: string[]): Promise<void> {
|
|
|
453
607
|
|
|
454
608
|
// 2. Deploy (unless opted out).
|
|
455
609
|
if (flags.print) {
|
|
456
|
-
printPlan(cfg);
|
|
610
|
+
printPlan(cfg, flags.envs);
|
|
457
611
|
return;
|
|
458
612
|
}
|
|
459
613
|
if (flags.noDeploy) {
|
|
@@ -465,8 +619,11 @@ async function main(argv: string[]): Promise<void> {
|
|
|
465
619
|
}
|
|
466
620
|
|
|
467
621
|
if (canPrompt()) {
|
|
622
|
+
const stacks = flags.envs
|
|
623
|
+
.map((env) => resolveEnvironment(cfg, env).stackName)
|
|
624
|
+
.join(" + ");
|
|
468
625
|
const ok = await confirm(
|
|
469
|
-
`Deploy ${
|
|
626
|
+
`Deploy ${stacks} to AWS ${cfg.region} now? (requires the bootstrap stack to exist) [y/N]`,
|
|
470
627
|
);
|
|
471
628
|
if (!ok) {
|
|
472
629
|
console.log(
|
|
@@ -476,8 +633,10 @@ async function main(argv: string[]): Promise<void> {
|
|
|
476
633
|
}
|
|
477
634
|
}
|
|
478
635
|
|
|
479
|
-
cfg = await standUp(cfg, canPrompt());
|
|
480
|
-
|
|
636
|
+
cfg = await standUp(cfg, flags.envs, canPrompt());
|
|
637
|
+
// Refresh `.env` with the resolved stack Outputs (API URLs, pool/client ids).
|
|
638
|
+
for (const p of writeEnvAndGitignore(cfg)) console.log(` ✓ updated ${p}`);
|
|
639
|
+
printSummary(cfg, flags.envs);
|
|
481
640
|
}
|
|
482
641
|
|
|
483
642
|
if (require.main === module) {
|
package/src/rastack.ts
CHANGED
|
@@ -137,6 +137,7 @@ switch (command) {
|
|
|
137
137
|
case "deploy":
|
|
138
138
|
// Ship code to the Lambda — an alias for the CI code deploy. Infrastructure
|
|
139
139
|
// is stood up once by `rastack init`; `deploy` only ever ships code.
|
|
140
|
+
// `--env dev|prod` picks which environment's function to update.
|
|
140
141
|
run("rastack-ci.js", ["deploy", ...rest]);
|
|
141
142
|
break;
|
|
142
143
|
case "ci":
|