rastack 0.0.62 → 0.0.64
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 +14 -0
- package/admin/AdminApp.tsx +56 -3
- package/admin/index.tsx +8 -2
- package/admin/ui/icons.tsx +9 -0
- package/admin/ui/styles.ts +3 -0
- package/admin/useAdmin.ts +41 -4
- package/auth/provider.ts +71 -8
- package/dist/admin.js +17 -14
- package/dist/compile/analyze.js +11 -1
- package/dist/define/index.d.ts +8 -1
- package/dist/define/index.js +2 -2
- package/dist/define/manifest.js +7 -1
- 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 +57 -0
- package/dist/deploy/io.js +141 -0
- package/dist/deploy/workflows.d.ts +30 -0
- package/dist/deploy/workflows.js +117 -12
- package/dist/rastack-bootstrap.js +18 -47
- package/dist/rastack-ci.js +19 -1
- package/dist/rastack-dev.js +48 -2
- package/dist/rastack-init.d.ts +6 -3
- package/dist/rastack-init.js +231 -92
- package/dist/rastack.js +1 -0
- package/dist/templates/cloudformation/app.yaml +133 -2
- package/dist/wasm/rastack_wasm.js +1 -1
- package/dist/wasm/rastack_wasm_bg.wasm +0 -0
- package/package.json +1 -1
- package/provider/provider.tsx +23 -0
- package/provider/types.ts +9 -0
- package/src/compile/analyze.ts +11 -1
- package/src/define/index.ts +5 -1
- package/src/define/manifest.ts +7 -1
- package/src/deploy/ci.ts +50 -16
- package/src/deploy/config.ts +340 -7
- package/src/deploy/io.ts +179 -2
- package/src/deploy/workflows.ts +122 -12
- package/src/rastack-bootstrap.ts +24 -69
- package/src/rastack-ci.ts +20 -2
- package/src/rastack-dev.ts +61 -6
- package/src/rastack-init.ts +283 -115
- package/src/rastack.ts +1 -0
package/dist/rastack-init.js
CHANGED
|
@@ -12,14 +12,17 @@
|
|
|
12
12
|
* 1. writes `.rastack/deploy.json` + `.github/workflows/{ci,deploy-backend}.yml`
|
|
13
13
|
* (GitHub coordinates default from the `origin` remote; missing fields are
|
|
14
14
|
* prompted for on a TTY);
|
|
15
|
-
* 2.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* 2. verifies the AWS CLI is logged in, then ensures the tier-0 bootstrap stack
|
|
16
|
+
* exists — creating it (the S3 template/placeholder buckets + GitHub OIDC
|
|
17
|
+
* provider) when missing, reconciling it when it already does;
|
|
18
18
|
* 3. uploads the Lambda placeholder bundle to that bucket;
|
|
19
19
|
* 4. deploys the per-app backend (app.yaml) with the resolved ArtifactsBucket;
|
|
20
20
|
* 5. reads the stack Outputs back into the config and prints the two GitHub repo
|
|
21
21
|
* Variables CI needs.
|
|
22
22
|
*
|
|
23
|
+
* The CloudFormation templates ship inside the `rastack` package; a partial
|
|
24
|
+
* install falls back to downloading them from reactapistack.com.
|
|
25
|
+
*
|
|
23
26
|
* Thereafter the recurring code deploy is CI's job — `rastack ci deploy`, which
|
|
24
27
|
* `rastack deploy` also runs. `--no-deploy` scaffolds only; `--print` shows the
|
|
25
28
|
* exact single-line `aws cloudformation deploy` commands without running them.
|
|
@@ -63,12 +66,23 @@ const path = __importStar(require("path"));
|
|
|
63
66
|
const config_1 = require("./deploy/config");
|
|
64
67
|
const workflows_1 = require("./deploy/workflows");
|
|
65
68
|
const io_1 = require("./deploy/io");
|
|
69
|
+
function parseEnvFlag(value) {
|
|
70
|
+
const v = value.trim().toLowerCase();
|
|
71
|
+
if (v === "all" || v === "")
|
|
72
|
+
return [...config_1.ENV_NAMES];
|
|
73
|
+
if (config_1.ENV_NAMES.includes(v))
|
|
74
|
+
return [v];
|
|
75
|
+
console.error(`Unknown --env: expected dev | prod | all.`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
66
78
|
function parseFlags(argv) {
|
|
67
|
-
const patch = { github: {} };
|
|
79
|
+
const patch = { github: {}, environments: {} };
|
|
80
|
+
let envs = [...config_1.ENV_NAMES];
|
|
68
81
|
let force = false;
|
|
69
82
|
let noWorkflows = false;
|
|
70
83
|
let noDeploy = false;
|
|
71
84
|
let print = false;
|
|
85
|
+
const devPatch = () => { var _a; return ((_a = patch.environments).dev ?? (_a.dev = {})); };
|
|
72
86
|
for (let i = 0; i < argv.length; i++) {
|
|
73
87
|
const a = argv[i];
|
|
74
88
|
const next = () => argv[++i] ?? "";
|
|
@@ -85,6 +99,17 @@ function parseFlags(argv) {
|
|
|
85
99
|
case "--branch":
|
|
86
100
|
patch.github.branch = next();
|
|
87
101
|
break;
|
|
102
|
+
case "--dev-branch":
|
|
103
|
+
devPatch().branch = next();
|
|
104
|
+
break;
|
|
105
|
+
case "--dev-proxy":
|
|
106
|
+
case "--dev-proxy-upstream":
|
|
107
|
+
// The dev external-API proxy upstream (a UAT/prod backend URL).
|
|
108
|
+
devPatch().proxyUpstream = next();
|
|
109
|
+
break;
|
|
110
|
+
case "--env":
|
|
111
|
+
envs = parseEnvFlag(next());
|
|
112
|
+
break;
|
|
88
113
|
case "--region":
|
|
89
114
|
patch.region = next();
|
|
90
115
|
break;
|
|
@@ -114,7 +139,10 @@ function parseFlags(argv) {
|
|
|
114
139
|
}
|
|
115
140
|
}
|
|
116
141
|
}
|
|
117
|
-
|
|
142
|
+
// Drop the scratch environments container when nothing was set on it.
|
|
143
|
+
if (patch.environments && !patch.environments.dev)
|
|
144
|
+
delete patch.environments;
|
|
145
|
+
return { patch, envs, force, noWorkflows, noDeploy, print };
|
|
118
146
|
}
|
|
119
147
|
/** Seed a config: existing file → flags → git remote → defaults. */
|
|
120
148
|
function seedConfig(flags) {
|
|
@@ -151,6 +179,35 @@ async function promptMissing(cfg) {
|
|
|
151
179
|
});
|
|
152
180
|
});
|
|
153
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Ensure `.env` is git-ignored and (re)write the generated `.env` with the
|
|
184
|
+
* current config, preserving any secrets a developer already added. Returns the
|
|
185
|
+
* repo-relative paths touched, for the written/skipped summary.
|
|
186
|
+
*/
|
|
187
|
+
function writeEnvAndGitignore(cfg) {
|
|
188
|
+
const touched = [];
|
|
189
|
+
// 1. git-ignore `.env` (append-merge; never duplicate or clobber).
|
|
190
|
+
const giAbs = path.join(process.cwd(), workflows_1.GITIGNORE_PATH);
|
|
191
|
+
const existingGi = fs.existsSync(giAbs)
|
|
192
|
+
? fs.readFileSync(giAbs, "utf-8")
|
|
193
|
+
: "";
|
|
194
|
+
const nextGi = (0, workflows_1.gitignoreWith)(existingGi, workflows_1.ENV_PATH);
|
|
195
|
+
if (nextGi !== existingGi) {
|
|
196
|
+
fs.writeFileSync(giAbs, nextGi);
|
|
197
|
+
touched.push(workflows_1.GITIGNORE_PATH);
|
|
198
|
+
}
|
|
199
|
+
// 2. Upsert the generated RASTACK_* values into `.env` (secrets preserved).
|
|
200
|
+
const envAbs = path.join(process.cwd(), workflows_1.ENV_PATH);
|
|
201
|
+
const existingEnv = fs.existsSync(envAbs)
|
|
202
|
+
? fs.readFileSync(envAbs, "utf-8")
|
|
203
|
+
: "";
|
|
204
|
+
const nextEnv = (0, workflows_1.applyEnvValues)(existingEnv, cfg);
|
|
205
|
+
if (nextEnv !== existingEnv) {
|
|
206
|
+
fs.writeFileSync(envAbs, nextEnv);
|
|
207
|
+
touched.push(workflows_1.ENV_PATH);
|
|
208
|
+
}
|
|
209
|
+
return touched;
|
|
210
|
+
}
|
|
154
211
|
/** Yes/no confirmation with a default of "no" — gates the cloud-mutating steps. */
|
|
155
212
|
async function confirm(question) {
|
|
156
213
|
return (0, io_1.withPrompt)(async (rl) => {
|
|
@@ -164,104 +221,137 @@ function table(rows) {
|
|
|
164
221
|
return rows.map((r) => ` ${r.key.padEnd(width)} ${r.value}`).join("\n");
|
|
165
222
|
}
|
|
166
223
|
/**
|
|
167
|
-
* Read
|
|
168
|
-
* not resolvable yet (best-effort — a freshly created stack has them all).
|
|
224
|
+
* Read one environment's app.yaml stack Outputs into a `DeployOutputs`, skipping
|
|
225
|
+
* any not resolvable yet (best-effort — a freshly created stack has them all).
|
|
169
226
|
*/
|
|
170
|
-
function
|
|
227
|
+
function readEnvOutputs(cfg, env) {
|
|
228
|
+
const stackName = (0, config_1.resolveEnvironment)(cfg, env).stackName;
|
|
171
229
|
const outputs = {};
|
|
172
230
|
for (const { key, field } of config_1.APP_STACK_OUTPUTS) {
|
|
173
|
-
const value = (0, io_1.stackOutput)(
|
|
231
|
+
const value = (0, io_1.stackOutput)(stackName, key, cfg.region);
|
|
174
232
|
if (value)
|
|
175
233
|
outputs[field] = value;
|
|
176
234
|
}
|
|
177
235
|
return outputs;
|
|
178
236
|
}
|
|
237
|
+
/** Merge an environment's resolved outputs back into the config in the right slot. */
|
|
238
|
+
function withEnvOutputs(cfg, env, outputs) {
|
|
239
|
+
return env === "prod"
|
|
240
|
+
? (0, config_1.mergeConfig)(cfg, { outputs })
|
|
241
|
+
: (0, config_1.mergeConfig)(cfg, { environments: { dev: { outputs } } });
|
|
242
|
+
}
|
|
243
|
+
/** Human label for an environment ("dev" / "prod") plus its branch. */
|
|
244
|
+
function envLabel(cfg, env) {
|
|
245
|
+
const e = (0, config_1.resolveEnvironment)(cfg, env);
|
|
246
|
+
return `${env} (${e.branch} → ${e.ghEnvironment})`;
|
|
247
|
+
}
|
|
179
248
|
/**
|
|
180
249
|
* Print the exact commands `init` would run, without touching the cloud
|
|
181
|
-
* (`--print`).
|
|
182
|
-
*
|
|
250
|
+
* (`--print`). init stands up *both* stacks, so both `aws cloudformation deploy`
|
|
251
|
+
* commands are shown: the tier-0 bootstrap stack (created only when missing) and
|
|
252
|
+
* the per-app backend. The artifacts bucket is read from an existing bootstrap
|
|
253
|
+
* stack when the AWS CLI is available, else shown as a placeholder.
|
|
183
254
|
*/
|
|
184
|
-
function printPlan(cfg) {
|
|
255
|
+
function printPlan(cfg, envs) {
|
|
185
256
|
const bucket = (0, io_1.awsAvailable)()
|
|
186
257
|
? (0, io_1.stackOutput)((0, config_1.bootstrapStackName)(cfg), config_1.BOOTSTRAP_ARTIFACTS_OUTPUT, cfg.region)
|
|
187
258
|
: null;
|
|
188
259
|
const withBucket = (0, config_1.mergeConfig)(cfg, {
|
|
189
260
|
artifactsBucket: bucket ?? "<ArtifactsBucketName from bootstrap stack>",
|
|
190
261
|
});
|
|
262
|
+
const bootstrapTemplate = (0, io_1.resolveDeployAsset)("bootstrap.yaml") ?? undefined;
|
|
191
263
|
const appTemplate = (0, io_1.resolveDeployAsset)("app.yaml") ?? undefined;
|
|
192
|
-
console.log(`\n Deployment plan (${
|
|
193
|
-
console.log(` ──
|
|
194
|
-
console.log(
|
|
195
|
-
console.log(" ──
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
264
|
+
console.log(`\n Deployment plan (${envs.join(" + ")} @ ${cfg.region})\n`);
|
|
265
|
+
console.log(` ── 1. Tier-0 bootstrap stack (${(0, config_1.bootstrapStackName)(cfg)}) — created only when missing ──`);
|
|
266
|
+
console.log("\n " + (0, config_1.bootstrapStackCommand)(cfg, false, bootstrapTemplate) + "\n");
|
|
267
|
+
console.log(" ── 2. Per-app backend (app.yaml) — one stack per environment ──");
|
|
268
|
+
for (const env of envs) {
|
|
269
|
+
const e = (0, config_1.resolveEnvironment)(withBucket, env);
|
|
270
|
+
console.log(`\n # ${envLabel(withBucket, env)} — stack ${e.stackName}`);
|
|
271
|
+
console.log(" " + (0, config_1.deployStackCommandForEnv)(withBucket, env, appTemplate));
|
|
272
|
+
}
|
|
273
|
+
console.log("\n ── 2. Wire each GitHub Environment's Variables from its stack Outputs ──");
|
|
274
|
+
for (const env of envs) {
|
|
275
|
+
const e = (0, config_1.resolveEnvironment)(cfg, env);
|
|
276
|
+
console.log(`\n # GitHub Environment: ${e.ghEnvironment}`);
|
|
277
|
+
console.log(table((0, config_1.repoVariablesForEnv)(cfg, env).map((v) => ({
|
|
278
|
+
key: v.name,
|
|
279
|
+
value: v.value || `<${v.from}>`,
|
|
280
|
+
}))));
|
|
281
|
+
}
|
|
282
|
+
console.log("");
|
|
202
283
|
}
|
|
203
284
|
/**
|
|
204
|
-
* Stand the infrastructure up:
|
|
205
|
-
*
|
|
206
|
-
*
|
|
285
|
+
* Stand the infrastructure up: verify the AWS CLI is logged in, ensure the
|
|
286
|
+
* tier-0 bootstrap stack (creating it when missing), upload the Lambda
|
|
287
|
+
* placeholder, deploy app.yaml, and read the Outputs back into the config.
|
|
288
|
+
* `init` is the single command that stands up *everything* — there is no
|
|
289
|
+
* separate manual bootstrap step. Returns the config enriched with the resolved
|
|
290
|
+
* buckets + outputs.
|
|
207
291
|
*/
|
|
208
|
-
async function standUp(cfg, interactive) {
|
|
292
|
+
async function standUp(cfg, envs, interactive) {
|
|
209
293
|
if (!(0, io_1.awsAvailable)()) {
|
|
210
294
|
console.error(" ✗ the AWS CLI is not on PATH. Install it and configure credentials,\n" +
|
|
211
295
|
" or re-run with --print to see the commands, or --no-deploy to skip.");
|
|
212
296
|
process.exit(1);
|
|
213
297
|
}
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
console.error(`\n ✗ Bootstrap stack ${(0, config_1.bootstrapStackName)(cfg)} not found in ${cfg.region}.\n` +
|
|
225
|
-
" It is the once-per-account+region step that hosts the CloudFormation\n" +
|
|
226
|
-
" templates and the Lambda placeholder every app deploy reads. Create it\n" +
|
|
227
|
-
" first (an account admin does this once), then re-run `rastack init`:\n\n" +
|
|
228
|
-
` rastack bootstrap --region ${cfg.region}\n`);
|
|
298
|
+
// Verify the AWS CLI is actually logged in before creating any stacks, so a
|
|
299
|
+
// missing/expired credential fails fast with an actionable message rather than
|
|
300
|
+
// a cryptic `cloudformation deploy` error partway through.
|
|
301
|
+
const account = (0, io_1.awsIdentity)(cfg.region);
|
|
302
|
+
if (!account) {
|
|
303
|
+
console.error(" ✗ the AWS CLI has no usable credentials — it is not logged in.\n" +
|
|
304
|
+
" Configure credentials, then re-run `rastack init`:\n" +
|
|
305
|
+
" aws configure # static access key / secret\n" +
|
|
306
|
+
" aws sso login # or an existing SSO session\n" +
|
|
307
|
+
" Or re-run with --print to see the commands, or --no-deploy to skip.");
|
|
229
308
|
process.exit(1);
|
|
230
309
|
}
|
|
310
|
+
console.log(` ✓ AWS account ${account} (${cfg.region})`);
|
|
311
|
+
// 1. Ensure the tier-0 bootstrap stack exists — the S3 buckets that host the
|
|
312
|
+
// templates + Lambda placeholder and the GitHub OIDC provider. init creates
|
|
313
|
+
// it when missing (idempotent for an existing one), so a single command
|
|
314
|
+
// takes an account+repo from nothing to a deployed backend.
|
|
315
|
+
const { artifactsBucket, templatesBucket } = await (0, io_1.ensureBootstrapStack)(cfg).catch((err) => {
|
|
316
|
+
console.error(`\n ✗ ${err.message}`);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
});
|
|
231
319
|
console.log(` ✓ artifacts bucket: ${artifactsBucket}`);
|
|
232
|
-
// 2. Upload the placeholder the function is created from.
|
|
320
|
+
// 2. Upload the placeholder the function is created from (shared by all envs).
|
|
233
321
|
console.log("\n▸ Uploading the Lambda placeholder bundle");
|
|
234
322
|
if (!(0, io_1.uploadPlaceholder)(artifactsBucket, cfg.region)) {
|
|
235
323
|
console.error(" ✗ Lambda placeholder bundle not found in the rastack package — cannot create the function.");
|
|
236
324
|
process.exit(1);
|
|
237
325
|
}
|
|
238
|
-
// 3. Deploy the per-app backend with the resolved bucket wired in.
|
|
239
326
|
cfg = (0, config_1.mergeConfig)(cfg, {
|
|
240
327
|
artifactsBucket,
|
|
241
328
|
templatesBucket: templatesBucket ?? undefined,
|
|
242
329
|
});
|
|
243
|
-
const appTemplate = (0, io_1.
|
|
330
|
+
const appTemplate = await (0, io_1.resolveTemplateFile)("app.yaml");
|
|
244
331
|
if (!appTemplate) {
|
|
245
|
-
console.error(" ✗ app.yaml template not found in the rastack package
|
|
246
|
-
" (`npm i -g rastack`)
|
|
332
|
+
console.error(" ✗ app.yaml template not found in the rastack package and could not be\n" +
|
|
333
|
+
" downloaded from reactapistack.com. Reinstall rastack (`npm i -g rastack`).");
|
|
247
334
|
process.exit(1);
|
|
248
335
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
336
|
+
// 3. Deploy one stack per environment — each with its own Cognito pool,
|
|
337
|
+
// warehouse, Lambda and API — reading the Outputs back into the config.
|
|
338
|
+
for (const env of envs) {
|
|
339
|
+
const stackName = (0, config_1.resolveEnvironment)(cfg, env).stackName;
|
|
340
|
+
console.log(`\n▸ Deploying ${envLabel(cfg, env)} — ${stackName}`);
|
|
341
|
+
try {
|
|
342
|
+
(0, io_1.awsRun)((0, config_1.deployStackArgsForEnv)(cfg, env, appTemplate));
|
|
343
|
+
}
|
|
344
|
+
catch (err) {
|
|
345
|
+
console.error(`\n ✗ aws cloudformation deploy failed for ${stackName}.\n ${err.message}`);
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
cfg = withEnvOutputs(cfg, env, readEnvOutputs(cfg, env));
|
|
349
|
+
(0, io_1.saveConfig)(cfg);
|
|
256
350
|
}
|
|
257
|
-
// 4.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
(
|
|
261
|
-
// 5. Connect the GitHub CLI and wire the repo Variables CI reads, so the FIRST
|
|
262
|
-
// push actually ships. Without this the deploy-backend workflow's `if:
|
|
263
|
-
// vars.AWS_DEPLOY_ROLE_ARN != ''` guard silently skips every run.
|
|
264
|
-
wireRepoVariables(cfg, interactive);
|
|
351
|
+
// 4. Connect the GitHub CLI and wire each environment's scoped Variables, so
|
|
352
|
+
// the FIRST push actually ships. Without this the deploy-backend workflow's
|
|
353
|
+
// `if: vars.AWS_DEPLOY_ROLE_ARN != ''` guard silently skips every run.
|
|
354
|
+
wireEnvironments(cfg, envs, interactive);
|
|
265
355
|
return cfg;
|
|
266
356
|
}
|
|
267
357
|
/**
|
|
@@ -275,58 +365,97 @@ async function standUp(cfg, interactive) {
|
|
|
275
365
|
* (and isn't) done.
|
|
276
366
|
*/
|
|
277
367
|
const wiredVariables = new Set();
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
368
|
+
/** Key a wired Variable by its GitHub Environment so dev/prod don't collide. */
|
|
369
|
+
function wiredKey(ghEnvironment, name) {
|
|
370
|
+
return `${ghEnvironment ?? ""}:${name}`;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Create each environment's GitHub Actions Environment and wire its scoped
|
|
374
|
+
* `AWS_DEPLOY_ROLE_ARN` / `AWS_REGION` Variables via `gh`, so the GitHub →
|
|
375
|
+
* Lambda pipeline is live for both dev and prod the moment `init` finishes.
|
|
376
|
+
*/
|
|
377
|
+
function wireEnvironments(cfg, envs, interactive) {
|
|
378
|
+
const anyValue = envs.some((env) => (0, config_1.repoVariablesForEnv)(cfg, env).some((v) => v.value));
|
|
379
|
+
if (!anyValue)
|
|
281
380
|
return;
|
|
282
381
|
if (!(0, io_1.ghAvailable)()) {
|
|
283
|
-
console.log("\n▸ GitHub CLI (`gh`) not found — skipping
|
|
382
|
+
console.log("\n▸ GitHub CLI (`gh`) not found — skipping environment + Variable wiring.\n" +
|
|
284
383
|
" Install `gh` (https://cli.github.com) and re-run, or wire the\n" +
|
|
285
384
|
" Variables below by hand so pushes deploy.");
|
|
286
385
|
return;
|
|
287
386
|
}
|
|
288
|
-
// Connect `gh` to GitHub if it isn't already, so it can
|
|
387
|
+
// Connect `gh` to GitHub if it isn't already, so it can create environments.
|
|
289
388
|
if (!(0, io_1.ghAuthenticated)()) {
|
|
290
389
|
if (interactive) {
|
|
291
390
|
console.log("\n▸ Connecting the GitHub CLI to your account (`gh auth login`)");
|
|
292
391
|
(0, io_1.ghAuthLogin)();
|
|
293
392
|
}
|
|
294
393
|
if (!(0, io_1.ghAuthenticated)()) {
|
|
295
|
-
console.log("\n▸ GitHub CLI is not connected — skipping
|
|
394
|
+
console.log("\n▸ GitHub CLI is not connected — skipping environment wiring.\n" +
|
|
296
395
|
" Run `gh auth login`, then wire the Variables below by hand.");
|
|
297
396
|
return;
|
|
298
397
|
}
|
|
299
398
|
}
|
|
300
|
-
console.log("\n▸
|
|
301
|
-
for (const
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
399
|
+
console.log("\n▸ Creating GitHub Environments + wiring their Variables (via `gh`)");
|
|
400
|
+
for (const env of envs) {
|
|
401
|
+
const e = (0, config_1.resolveEnvironment)(cfg, env);
|
|
402
|
+
// Create the Environment first — a Variable can only be scoped to one that
|
|
403
|
+
// already exists. Idempotent (PUT), so re-running init is safe.
|
|
404
|
+
if (!(0, io_1.ghEnvironmentCreate)((0, config_1.ghEnvironmentCreateArgs)(cfg, e.ghEnvironment))) {
|
|
405
|
+
console.log(` • could not create GitHub Environment ${e.ghEnvironment} — create it by hand`);
|
|
406
|
+
continue;
|
|
306
407
|
}
|
|
307
|
-
|
|
308
|
-
|
|
408
|
+
for (const v of (0, config_1.repoVariablesForEnv)(cfg, env).filter((x) => x.value)) {
|
|
409
|
+
const ok = (0, io_1.setRepoVariable)((0, config_1.ghVariableSetArgs)(cfg, v.name, v.value, v.ghEnvironment));
|
|
410
|
+
if (ok) {
|
|
411
|
+
wiredVariables.add(wiredKey(v.ghEnvironment, v.name));
|
|
412
|
+
console.log(` ✓ set ${e.ghEnvironment}/${v.name}`);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
console.log(` • could not set ${e.ghEnvironment}/${v.name} — wire it by hand (below)`);
|
|
416
|
+
}
|
|
309
417
|
}
|
|
310
418
|
}
|
|
311
419
|
}
|
|
312
|
-
function printSummary(cfg) {
|
|
313
|
-
console.log(`\n ✓ ${cfg.app} is deployed
|
|
314
|
-
|
|
315
|
-
|
|
420
|
+
function printSummary(cfg, envs) {
|
|
421
|
+
console.log(`\n ✓ ${cfg.app} is deployed @ ${cfg.region}`);
|
|
422
|
+
for (const env of envs) {
|
|
423
|
+
const e = (0, config_1.resolveEnvironment)(cfg, env);
|
|
424
|
+
console.log(`\n ${env} — ${e.stackName}`);
|
|
425
|
+
if (e.outputs?.apiUrl)
|
|
426
|
+
console.log(` API: ${e.outputs.apiUrl}`);
|
|
427
|
+
if (e.outputs?.hostedUiUrl)
|
|
428
|
+
console.log(` Hosted UI: ${e.outputs.hostedUiUrl}`);
|
|
429
|
+
if (e.outputs?.userPoolId)
|
|
430
|
+
console.log(` User Pool: ${e.outputs.userPoolId}`);
|
|
431
|
+
if (e.outputs?.proxyUrl)
|
|
432
|
+
console.log(` Proxy: ${e.outputs.proxyUrl}`);
|
|
433
|
+
}
|
|
316
434
|
// Only the Variables `init` could NOT wire itself remain a manual step.
|
|
317
|
-
const pending =
|
|
435
|
+
const pending = [];
|
|
436
|
+
for (const env of envs) {
|
|
437
|
+
const e = (0, config_1.resolveEnvironment)(cfg, env);
|
|
438
|
+
for (const v of (0, config_1.repoVariablesForEnv)(cfg, env)) {
|
|
439
|
+
if (!wiredVariables.has(wiredKey(v.ghEnvironment, v.name)))
|
|
440
|
+
pending.push({
|
|
441
|
+
key: `${e.ghEnvironment}/${v.name}`,
|
|
442
|
+
value: v.value || `<${v.from}>`,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
318
446
|
if (pending.length) {
|
|
319
|
-
console.log("\n Wire these GitHub
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
value: v.value || `<${v.from}>`,
|
|
323
|
-
}))));
|
|
447
|
+
console.log("\n Wire these per-environment GitHub Variables so CI can ship code\n" +
|
|
448
|
+
" (Settings → Environments → <env> → Variables):");
|
|
449
|
+
console.log(table(pending));
|
|
324
450
|
}
|
|
325
451
|
else {
|
|
326
|
-
console.log("\n ✓ wired
|
|
452
|
+
console.log("\n ✓ wired every GitHub Environment's Variables (AWS_DEPLOY_ROLE_ARN, AWS_REGION).");
|
|
327
453
|
}
|
|
328
|
-
|
|
329
|
-
|
|
454
|
+
const dev = (0, config_1.resolveEnvironment)(cfg, "dev");
|
|
455
|
+
const prod = (0, config_1.resolveEnvironment)(cfg, "prod");
|
|
456
|
+
console.log(`\n Pushes now ship code via \`rastack ci deploy\`:\n` +
|
|
457
|
+
` ${prod.branch} → prod (${prod.ghEnvironment}) ${dev.branch} → dev (${dev.ghEnvironment})\n` +
|
|
458
|
+
` Run one locally with \`rastack deploy --env dev\` / \`--env prod\`.\n`);
|
|
330
459
|
}
|
|
331
460
|
async function main(argv) {
|
|
332
461
|
const flags = parseFlags(argv);
|
|
@@ -362,6 +491,10 @@ async function main(argv) {
|
|
|
362
491
|
fs.writeFileSync(abs, f.content);
|
|
363
492
|
written.push(f.path);
|
|
364
493
|
}
|
|
494
|
+
// The `.env` (local app environment) + its `.gitignore` entry are standard —
|
|
495
|
+
// generated on every init, secrets preserved, refreshed with Outputs post-deploy.
|
|
496
|
+
for (const p of writeEnvAndGitignore(cfg))
|
|
497
|
+
written.push(p);
|
|
365
498
|
console.log("");
|
|
366
499
|
for (const w of written)
|
|
367
500
|
console.log(` ✓ wrote ${w}`);
|
|
@@ -369,7 +502,7 @@ async function main(argv) {
|
|
|
369
502
|
console.log(` • skipped ${s} (exists — pass --force to overwrite)`);
|
|
370
503
|
// 2. Deploy (unless opted out).
|
|
371
504
|
if (flags.print) {
|
|
372
|
-
printPlan(cfg);
|
|
505
|
+
printPlan(cfg, flags.envs);
|
|
373
506
|
return;
|
|
374
507
|
}
|
|
375
508
|
if (flags.noDeploy) {
|
|
@@ -378,14 +511,20 @@ async function main(argv) {
|
|
|
378
511
|
return;
|
|
379
512
|
}
|
|
380
513
|
if ((0, io_1.canPrompt)()) {
|
|
381
|
-
const
|
|
514
|
+
const stacks = flags.envs
|
|
515
|
+
.map((env) => (0, config_1.resolveEnvironment)(cfg, env).stackName)
|
|
516
|
+
.join(" + ");
|
|
517
|
+
const ok = await confirm(`Deploy ${stacks} to AWS ${cfg.region} now? (creates the bootstrap stack if missing) [y/N]`);
|
|
382
518
|
if (!ok) {
|
|
383
519
|
console.log("\n Skipped the deploy. Re-run to deploy, or `rastack init --print` for the commands.\n");
|
|
384
520
|
return;
|
|
385
521
|
}
|
|
386
522
|
}
|
|
387
|
-
cfg = await standUp(cfg, (0, io_1.canPrompt)());
|
|
388
|
-
|
|
523
|
+
cfg = await standUp(cfg, flags.envs, (0, io_1.canPrompt)());
|
|
524
|
+
// Refresh `.env` with the resolved stack Outputs (API URLs, pool/client ids).
|
|
525
|
+
for (const p of writeEnvAndGitignore(cfg))
|
|
526
|
+
console.log(` ✓ updated ${p}`);
|
|
527
|
+
printSummary(cfg, flags.envs);
|
|
389
528
|
}
|
|
390
529
|
if (require.main === module) {
|
|
391
530
|
main(process.argv.slice(2)).catch((err) => {
|
package/dist/rastack.js
CHANGED
|
@@ -130,6 +130,7 @@ switch (command) {
|
|
|
130
130
|
case "deploy":
|
|
131
131
|
// Ship code to the Lambda — an alias for the CI code deploy. Infrastructure
|
|
132
132
|
// is stood up once by `rastack init`; `deploy` only ever ships code.
|
|
133
|
+
// `--env dev|prod` picks which environment's function to update.
|
|
133
134
|
run("rastack-ci.js", ["deploy", ...rest]);
|
|
134
135
|
break;
|
|
135
136
|
case "ci":
|