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.
Files changed (44) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/admin/AdminApp.tsx +56 -3
  3. package/admin/index.tsx +8 -2
  4. package/admin/ui/icons.tsx +9 -0
  5. package/admin/ui/styles.ts +3 -0
  6. package/admin/useAdmin.ts +41 -4
  7. package/auth/provider.ts +71 -8
  8. package/dist/admin.js +17 -14
  9. package/dist/compile/analyze.js +11 -1
  10. package/dist/define/index.d.ts +8 -1
  11. package/dist/define/index.js +2 -2
  12. package/dist/define/manifest.js +7 -1
  13. package/dist/deploy/ci.d.ts +10 -3
  14. package/dist/deploy/ci.js +22 -11
  15. package/dist/deploy/config.d.ts +155 -5
  16. package/dist/deploy/config.js +216 -6
  17. package/dist/deploy/io.d.ts +57 -0
  18. package/dist/deploy/io.js +141 -0
  19. package/dist/deploy/workflows.d.ts +30 -0
  20. package/dist/deploy/workflows.js +117 -12
  21. package/dist/rastack-bootstrap.js +18 -47
  22. package/dist/rastack-ci.js +19 -1
  23. package/dist/rastack-dev.js +48 -2
  24. package/dist/rastack-init.d.ts +6 -3
  25. package/dist/rastack-init.js +231 -92
  26. package/dist/rastack.js +1 -0
  27. package/dist/templates/cloudformation/app.yaml +133 -2
  28. package/dist/wasm/rastack_wasm.js +1 -1
  29. package/dist/wasm/rastack_wasm_bg.wasm +0 -0
  30. package/package.json +1 -1
  31. package/provider/provider.tsx +23 -0
  32. package/provider/types.ts +9 -0
  33. package/src/compile/analyze.ts +11 -1
  34. package/src/define/index.ts +5 -1
  35. package/src/define/manifest.ts +7 -1
  36. package/src/deploy/ci.ts +50 -16
  37. package/src/deploy/config.ts +340 -7
  38. package/src/deploy/io.ts +179 -2
  39. package/src/deploy/workflows.ts +122 -12
  40. package/src/rastack-bootstrap.ts +24 -69
  41. package/src/rastack-ci.ts +20 -2
  42. package/src/rastack-dev.ts +61 -6
  43. package/src/rastack-init.ts +283 -115
  44. package/src/rastack.ts +1 -0
@@ -12,11 +12,15 @@
12
12
  * Pure string builders — `rastack-init.ts` is the thin wrapper that writes them.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.DEPLOY_WORKFLOW_PATH = exports.CI_WORKFLOW_PATH = exports.CONFIG_PATH = void 0;
15
+ exports.GITIGNORE_PATH = exports.ENV_PATH = exports.DEPLOY_WORKFLOW_PATH = exports.CI_WORKFLOW_PATH = exports.CONFIG_PATH = void 0;
16
16
  exports.renderCiWorkflow = renderCiWorkflow;
17
17
  exports.renderDeployWorkflow = renderDeployWorkflow;
18
18
  exports.renderConfigFile = renderConfigFile;
19
+ exports.renderEnvFile = renderEnvFile;
20
+ exports.applyEnvValues = applyEnvValues;
21
+ exports.gitignoreWith = gitignoreWith;
19
22
  exports.scaffoldFiles = scaffoldFiles;
23
+ const config_1 = require("./config");
20
24
  /** Node version the templated workflows pin (LTS — safe for a fresh repo). */
21
25
  const NODE_VERSION = "22";
22
26
  /**
@@ -24,6 +28,8 @@ const NODE_VERSION = "22";
24
28
  * every push/PR. The clean replacement for hand-written compile/check steps.
25
29
  */
26
30
  function renderCiWorkflow(cfg) {
31
+ const prod = (0, config_1.resolveEnvironment)(cfg, "prod");
32
+ const dev = (0, config_1.resolveEnvironment)(cfg, "dev");
27
33
  return `name: CI
28
34
 
29
35
  # Validate the resource graph and compile the manifest on every change.
@@ -32,7 +38,7 @@ function renderCiWorkflow(cfg) {
32
38
 
33
39
  on:
34
40
  push:
35
- branches: [${cfg.github.branch}]
41
+ branches: [${prod.branch}, ${dev.branch}]
36
42
  pull_request:
37
43
 
38
44
  permissions:
@@ -61,19 +67,28 @@ jobs:
61
67
  */
62
68
  function renderDeployWorkflow(cfg) {
63
69
  const arch = cfg.lambda.arch === "arm64" ? "arm64" : "x86_64";
70
+ const prod = (0, config_1.resolveEnvironment)(cfg, "prod");
71
+ const dev = (0, config_1.resolveEnvironment)(cfg, "dev");
72
+ // Pick the environment (and rastack --env flag) from the pushed branch.
73
+ const ghEnv = `\${{ github.ref_name == '${prod.branch}' && '${prod.ghEnvironment}' || '${dev.ghEnvironment}' }}`;
74
+ const envFlag = `\${{ github.ref_name == '${prod.branch}' && 'prod' || 'dev' }}`;
64
75
  return `name: Deploy backend
65
76
 
66
- # Ship the Rust API (rastack-server) to AWS Lambda. Infrastructure is created
67
- # once by a human via CloudFormation (docs/serverless-deploy.md); this workflow
77
+ # Ship the Rust API (rastack-server) to AWS Lambda — dev and prod. Infrastructure
78
+ # is created once by \`rastack init\` (docs/serverless-deploy.md); this workflow
68
79
  # only ever runs \`aws lambda update-function-code\`, wrapped by \`rastack ci\`.
69
80
  #
70
- # Wire these from the CloudFormation stack Outputs as GitHub repo Variables:
81
+ # The environment is chosen from the branch:
82
+ # ${dev.branch} → ${dev.ghEnvironment} (dev stack) ${prod.branch} → ${prod.ghEnvironment} (prod stack)
83
+ #
84
+ # Each GitHub Actions Environment holds its own AWS Variables (wired by
85
+ # \`rastack init\` from that environment's stack Outputs):
71
86
  # AWS_DEPLOY_ROLE_ARN DeployRoleArn (the OIDC role this job assumes)
72
87
  # AWS_REGION Region (${cfg.region})
73
88
 
74
89
  on:
75
90
  push:
76
- branches: [${cfg.github.branch}]
91
+ branches: [${prod.branch}, ${dev.branch}]
77
92
 
78
93
  # Least privilege: read the repo + mint an OIDC token to assume the deploy role.
79
94
  permissions:
@@ -81,15 +96,20 @@ permissions:
81
96
  id-token: write
82
97
 
83
98
  concurrency:
84
- group: deploy-backend
99
+ # One in-flight deploy per branch, so dev and prod never cancel each other.
100
+ group: deploy-backend-\${{ github.ref_name }}
85
101
  cancel-in-progress: true
86
102
 
87
103
  jobs:
88
104
  deploy:
89
105
  name: Build & deploy the Lambda (${arch})
90
106
  runs-on: ubuntu-latest
91
- # Skip on forks / repos that never wired the deploy role, so the job does
92
- # not fail red for contributors who cannot (and should not) deploy.
107
+ # Select the GitHub Environment from the branch. Its scoped Variables
108
+ # (AWS_DEPLOY_ROLE_ARN / AWS_REGION) then resolve to the right stack, and
109
+ # prod protection rules (if any) gate the run.
110
+ environment: ${ghEnv}
111
+ # Skip on forks / environments that never wired the deploy role, so the job
112
+ # does not fail red for contributors who cannot (and should not) deploy.
93
113
  if: vars.AWS_DEPLOY_ROLE_ARN != ''
94
114
  steps:
95
115
  - uses: actions/checkout@v4
@@ -116,10 +136,10 @@ jobs:
116
136
  role-to-assume: \${{ vars.AWS_DEPLOY_ROLE_ARN }}
117
137
  aws-region: \${{ vars.AWS_REGION }}
118
138
 
119
- # One line: compile the manifest, cross-build the bundle, bake the JWKS,
120
- # and update-function-code. See \`rastack ci --plan deploy\`.
139
+ # One line: compile the manifest, cross-build the bundle, bake the env's
140
+ # Cognito JWKS, and update-function-code for that env's function.
121
141
  - name: Build & deploy
122
- run: rastack ci deploy
142
+ run: rastack ci deploy --env ${envFlag}
123
143
  `;
124
144
  }
125
145
  /** The deploy config written to `.rastack/deploy.json`, pretty-printed. */
@@ -129,6 +149,91 @@ function renderConfigFile(cfg) {
129
149
  exports.CONFIG_PATH = ".rastack/deploy.json";
130
150
  exports.CI_WORKFLOW_PATH = ".github/workflows/ci.yml";
131
151
  exports.DEPLOY_WORKFLOW_PATH = ".github/workflows/deploy-backend.yml";
152
+ exports.ENV_PATH = ".env";
153
+ exports.GITIGNORE_PATH = ".gitignore";
154
+ /**
155
+ * The generated `.env` — the app's **local** environment. Every value is
156
+ * non-secret (client ids, pool ids, public URLs from the stack Outputs), but the
157
+ * file is git-ignored by convention (`gitignoreWith`) so a developer can add
158
+ * their own secrets beside these without ever committing them. Values are blank
159
+ * until the matching stack is deployed; `rastack init` rewrites the file with the
160
+ * resolved Outputs after standing the backends up.
161
+ *
162
+ * Keys are `RASTACK_<ENV>_*` (a neutral prefix). For a browser bundle, re-export
163
+ * the ones you need under your bundler's public prefix (EXPO_PUBLIC_/VITE_/…);
164
+ * `rastack dev` also serves the dev pool config live at `/__rastack/cognito.json`.
165
+ */
166
+ function renderEnvFile(cfg) {
167
+ const lines = [
168
+ "# Generated by `rastack init` — local app environment (git-ignored).",
169
+ "# Non-secret values from the CloudFormation stack Outputs; add your own",
170
+ "# secrets below. Re-run `rastack init` to refresh after a deploy.",
171
+ "",
172
+ `RASTACK_APP=${cfg.app}`,
173
+ `RASTACK_REGION=${cfg.region}`,
174
+ "",
175
+ ];
176
+ const envKeys = [
177
+ ["dev", "DEV"],
178
+ ["prod", "PROD"],
179
+ ];
180
+ for (const [env, prefix] of envKeys) {
181
+ const e = (0, config_1.resolveEnvironment)(cfg, env);
182
+ const o = e.outputs ?? {};
183
+ lines.push(`# ${env} (${e.branch} → ${e.ghEnvironment})`, `RASTACK_${prefix}_API_URL=${o.apiUrl ?? ""}`, `RASTACK_${prefix}_USER_POOL_ID=${o.userPoolId ?? ""}`, `RASTACK_${prefix}_USER_POOL_CLIENT_ID=${o.userPoolClientId ?? ""}`, `RASTACK_${prefix}_HOSTED_UI_URL=${o.hostedUiUrl ?? ""}`, `RASTACK_${prefix}_PROXY_URL=${o.proxyUrl ?? ""}`, "");
184
+ }
185
+ return lines.join("\n");
186
+ }
187
+ /**
188
+ * Upsert the generated `RASTACK_*` keys from `renderEnvFile(cfg)` into an
189
+ * existing `.env`, preserving every other line (a developer's own secrets,
190
+ * comments, extra keys). A matching `RASTACK_*=` line is updated in place;
191
+ * generated keys not yet present are appended. When there is no existing file,
192
+ * this is exactly `renderEnvFile(cfg)`. Pure — the IO wrapper reads/writes.
193
+ */
194
+ function applyEnvValues(existing, cfg) {
195
+ const rendered = renderEnvFile(cfg);
196
+ if (!existing.trim())
197
+ return rendered;
198
+ const generated = new Map();
199
+ for (const line of rendered.split("\n")) {
200
+ const m = /^(RASTACK_[A-Z0-9_]+)=(.*)$/.exec(line);
201
+ if (m)
202
+ generated.set(m[1], m[2]);
203
+ }
204
+ const seen = new Set();
205
+ const out = existing.split("\n").map((line) => {
206
+ const m = /^(RASTACK_[A-Z0-9_]+)=/.exec(line);
207
+ if (m && generated.has(m[1])) {
208
+ seen.add(m[1]);
209
+ return `${m[1]}=${generated.get(m[1])}`;
210
+ }
211
+ return line;
212
+ });
213
+ const missing = [...generated].filter(([k]) => !seen.has(k));
214
+ if (missing.length) {
215
+ if (out.length && out[out.length - 1].trim() !== "")
216
+ out.push("");
217
+ for (const [k, v] of missing)
218
+ out.push(`${k}=${v}`);
219
+ out.push("");
220
+ }
221
+ return out.join("\n");
222
+ }
223
+ /**
224
+ * Return `.gitignore` content with a `.env` entry present (idempotent). Appends
225
+ * under a labelled block when missing, leaves the file untouched when `.env` is
226
+ * already ignored — so `rastack init` never duplicates the entry or clobbers a
227
+ * developer's existing ignore rules.
228
+ */
229
+ function gitignoreWith(existing, entry = ".env") {
230
+ const lines = existing.split("\n").map((l) => l.trim());
231
+ if (lines.includes(entry))
232
+ return existing;
233
+ const base = existing.replace(/\n+$/, "");
234
+ const block = `# rastack — local environment (secrets)\n${entry}\n`;
235
+ return base ? `${base}\n\n${block}` : block;
236
+ }
132
237
  /**
133
238
  * The full set of files `rastack init` templates: the deploy config plus the two
134
239
  * base workflows. Returned as data so the caller decides what to write, skip, or
@@ -124,60 +124,31 @@ function printPlan(cfg, reuseOidc) {
124
124
  " ArtifactsBucket to create app backends. Nothing here is app-specific.\n");
125
125
  }
126
126
  /**
127
- * Deploy the bootstrap stack, retrying once against an account that already has
128
- * a GitHub OIDC provider (AWS allows only one per URL) when the caller did not
129
- * already ask to reuse it.
127
+ * Stand the tier-0 bootstrap stack up and upload the Lambda placeholder. The
128
+ * deploy (with the GitHub-OIDC-reuse retry) and template resolution are shared
129
+ * with `rastack init` via `ensureBootstrapStack`, so both stand the stack up
130
+ * identically.
130
131
  */
131
- function deployBootstrap(cfg, reuseOidc) {
132
- const template = (0, io_1.resolveDeployAsset)("bootstrap.yaml");
133
- if (!template) {
134
- console.error(" ✗ bootstrap.yaml template not found in the rastack package. Reinstall\n" +
135
- " rastack (`npm i -g rastack`) or run from the framework repo.");
136
- process.exit(1);
137
- }
138
- try {
139
- (0, io_1.awsRun)((0, config_1.bootstrapStackArgs)(cfg, reuseOidc, template));
140
- }
141
- catch (err) {
142
- if (reuseOidc) {
143
- console.error(`\n ✗ bootstrap stack deploy failed.\n ${err.message}`);
144
- process.exit(1);
145
- }
146
- // The most common cause is an account that already has a GitHub OIDC
147
- // provider. Retry telling the template to reuse it instead of creating a
148
- // second (which CloudFormation rejects).
149
- console.log(" • retrying bootstrap, reusing the account's existing GitHub OIDC provider");
150
- try {
151
- (0, io_1.awsRun)((0, config_1.bootstrapStackArgs)(cfg, true, template));
152
- }
153
- catch (retryErr) {
154
- console.error(`\n ✗ bootstrap stack deploy failed.\n ${retryErr.message}`);
155
- process.exit(1);
156
- }
157
- }
158
- }
159
- /** Stand the tier-0 bootstrap stack up and upload the Lambda placeholder. */
160
- function standUp(cfg, reuseOidc) {
132
+ async function standUp(cfg, reuseOidc) {
161
133
  if (!(0, io_1.awsAvailable)()) {
162
134
  console.error(" ✗ the AWS CLI is not on PATH. Install it and configure credentials,\n" +
163
135
  " or re-run with --print to see the command.");
164
136
  process.exit(1);
165
137
  }
166
- const stack = (0, config_1.bootstrapStackName)(cfg);
167
- const existing = (0, io_1.stackOutput)(stack, config_1.BOOTSTRAP_ARTIFACTS_OUTPUT, cfg.region);
168
- if (existing) {
169
- console.log(`\n▸ Bootstrap stack ${stack} already exists — reconciling (idempotent).`);
170
- }
171
- else {
172
- console.log(`\n▸ Creating bootstrap stack ${stack} (@ ${cfg.region})`);
173
- }
174
- deployBootstrap(cfg, reuseOidc);
175
- const artifactsBucket = (0, io_1.stackOutput)(stack, config_1.BOOTSTRAP_ARTIFACTS_OUTPUT, cfg.region);
176
- const templatesBucket = (0, io_1.stackOutput)(stack, config_1.BOOTSTRAP_TEMPLATES_OUTPUT, cfg.region);
177
- if (!artifactsBucket) {
178
- console.error(" ✗ bootstrap stack deployed but its ArtifactsBucket output could not be read.");
138
+ // Verify the AWS CLI is logged in before attempting a stack deploy.
139
+ const account = (0, io_1.awsIdentity)(cfg.region);
140
+ if (!account) {
141
+ console.error(" ✗ the AWS CLI has no usable credentials — it is not logged in.\n" +
142
+ " Run `aws configure` (or `aws sso login`), then re-run `rastack bootstrap`.\n" +
143
+ " Or re-run with --print to see the command.");
179
144
  process.exit(1);
180
145
  }
146
+ console.log(` ✓ AWS account ${account} (${cfg.region})`);
147
+ const stack = (0, config_1.bootstrapStackName)(cfg);
148
+ const { artifactsBucket, templatesBucket } = await (0, io_1.ensureBootstrapStack)(cfg, reuseOidc).catch((err) => {
149
+ console.error(`\n ✗ ${err.message}`);
150
+ process.exit(1);
151
+ });
181
152
  console.log(` ✓ artifacts bucket: ${artifactsBucket}`);
182
153
  if (templatesBucket)
183
154
  console.log(` ✓ templates bucket: ${templatesBucket}`);
@@ -220,7 +191,7 @@ async function main(argv) {
220
191
  return;
221
192
  }
222
193
  }
223
- standUp(cfg, flags.reuseOidc);
194
+ await standUp(cfg, flags.reuseOidc);
224
195
  }
225
196
  if (require.main === module) {
226
197
  main(process.argv.slice(2)).catch((err) => {
@@ -19,9 +19,17 @@
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  const child_process_1 = require("child_process");
21
21
  const compile_1 = require("./compile");
22
+ const config_1 = require("./deploy/config");
22
23
  const ci_1 = require("./deploy/ci");
23
24
  const io_1 = require("./deploy/io");
24
25
  const PHASES = ["check", "build", "deploy"];
26
+ /** Coerce a string to an `EnvName`, or undefined when unset/unrecognised. */
27
+ function parseEnv(value) {
28
+ const v = (value ?? "").trim();
29
+ return config_1.ENV_NAMES.includes(v)
30
+ ? v
31
+ : undefined;
32
+ }
25
33
  function parseFlags(argv) {
26
34
  let phase = "";
27
35
  let plan = false;
@@ -29,6 +37,7 @@ function parseFlags(argv) {
29
37
  bin: process.env.RASTACK_BIN || "rastack",
30
38
  functionName: process.env.RASTACK_FUNCTION || undefined,
31
39
  jwksUrl: process.env.RASTACK_JWKS_URL || undefined,
40
+ env: parseEnv(process.env.RASTACK_ENV),
32
41
  };
33
42
  for (let i = 0; i < argv.length; i++) {
34
43
  const a = argv[i];
@@ -47,6 +56,15 @@ function parseFlags(argv) {
47
56
  case "--jwks-url":
48
57
  opts.jwksUrl = next();
49
58
  break;
59
+ case "--env": {
60
+ const env = parseEnv(next());
61
+ if (!env) {
62
+ console.error(`Unknown --env: expected ${config_1.ENV_NAMES.join(" | ")}.`);
63
+ process.exit(1);
64
+ }
65
+ opts.env = env;
66
+ break;
67
+ }
50
68
  case "--bin":
51
69
  opts.bin = next();
52
70
  break;
@@ -65,7 +83,7 @@ function parseFlags(argv) {
65
83
  }
66
84
  }
67
85
  if (!phase) {
68
- console.error(`Usage: rastack ci <${PHASES.join(" | ")}> [--plan] [--skip-build]\n`);
86
+ console.error(`Usage: rastack ci <${PHASES.join(" | ")}> [--env dev|prod] [--plan] [--skip-build]\n`);
69
87
  process.exit(1);
70
88
  }
71
89
  return { phase, plan, opts };
@@ -60,6 +60,8 @@ const http = __importStar(require("http"));
60
60
  const net = __importStar(require("net"));
61
61
  const path = __importStar(require("path"));
62
62
  const compile_1 = require("./compile");
63
+ const config_1 = require("./deploy/config");
64
+ const io_1 = require("./deploy/io");
63
65
  const harness_1 = require("./dev/harness");
64
66
  const HERE = __dirname; // dist/ when installed
65
67
  /** True when `cargo` is on PATH (the from-source wasm fallback needs it). */
@@ -131,6 +133,38 @@ function serveFile(res, file) {
131
133
  res.writeHead(200, { "Content-Type": (0, harness_1.contentType)(file) }).end(data);
132
134
  });
133
135
  }
136
+ /**
137
+ * Serve the dev environment's Cognito coordinates (`/__rastack/cognito.json`) so
138
+ * a locally-running app can sign in against the *real* dev user pool. Read from
139
+ * `.rastack/deploy.json`'s dev outputs; `{}` when the dev stack isn't deployed
140
+ * yet (the app then falls back to `mode:"local"`). Returns whether a real config
141
+ * was found, for the startup hint.
142
+ */
143
+ function serveDevCognito(res, cwd) {
144
+ let body = "{}";
145
+ try {
146
+ const cfg = (0, io_1.loadConfig)(cwd);
147
+ const runtime = cfg ? (0, config_1.cognitoRuntimeConfig)(cfg, "dev") : null;
148
+ if (runtime)
149
+ body = JSON.stringify(runtime);
150
+ }
151
+ catch {
152
+ /* no/invalid config → empty object, app uses the local shim */
153
+ }
154
+ res
155
+ .writeHead(200, { "Content-Type": "application/json; charset=utf-8" })
156
+ .end(body);
157
+ }
158
+ /** Whether a real dev Cognito config is available to advertise on startup. */
159
+ function hasDevCognito(cwd) {
160
+ try {
161
+ const cfg = (0, io_1.loadConfig)(cwd);
162
+ return Boolean(cfg && (0, config_1.cognitoRuntimeConfig)(cfg, "dev"));
163
+ }
164
+ catch {
165
+ return false;
166
+ }
167
+ }
134
168
  /** Best-effort open the default browser at `url`. */
135
169
  function openBrowser(url) {
136
170
  const cmd = process.platform === "darwin"
@@ -190,7 +224,7 @@ function getFreePort() {
190
224
  srv.listen(0, "127.0.0.1", () => {
191
225
  const addr = srv.address();
192
226
  const port = addr && typeof addr === "object" ? addr.port : 0;
193
- srv.close(() => (port ? resolve(port) : reject(new Error("no free port"))));
227
+ srv.close(() => port ? resolve(port) : reject(new Error("no free port")));
194
228
  });
195
229
  });
196
230
  }
@@ -240,7 +274,13 @@ function waitForMetro(port, timeoutMs = 120000) {
240
274
  }
241
275
  /** Reverse-proxy one HTTP request/response pair to Metro. */
242
276
  function proxyToMetro(req, res, port) {
243
- const proxyReq = http.request({ host: "127.0.0.1", port, method: req.method, path: req.url, headers: req.headers }, (proxyRes) => {
277
+ const proxyReq = http.request({
278
+ host: "127.0.0.1",
279
+ port,
280
+ method: req.method,
281
+ path: req.url,
282
+ headers: req.headers,
283
+ }, (proxyRes) => {
244
284
  res.writeHead(proxyRes.statusCode || 502, proxyRes.headers);
245
285
  proxyRes.pipe(res);
246
286
  });
@@ -310,6 +350,8 @@ async function runMetroMode(args, cwd) {
310
350
  return serveUnder(res, wasmDir, url.slice("/wasm/".length));
311
351
  if (url === "/schema.rastack.json" || url === "/openapi.json")
312
352
  return serveUnder(res, args.outDir, path.basename(url));
353
+ if (url === "/__rastack/cognito.json")
354
+ return serveDevCognito(res, cwd);
313
355
  if (url.startsWith("/data/warehouse/"))
314
356
  return serveUnder(res, args.warehouseDir, url.slice("/data/warehouse/".length));
315
357
  // Everything else → the live Metro bundle (HTML, JS, assets, HMR polls).
@@ -372,6 +414,8 @@ async function main(argv) {
372
414
  return serveUnder(res, wasmDir, url.slice("/wasm/".length));
373
415
  if (url === "/schema.rastack.json" || url === "/openapi.json")
374
416
  return serveUnder(res, args.outDir, path.basename(url));
417
+ if (url === "/__rastack/cognito.json")
418
+ return serveDevCognito(res, cwd);
375
419
  if (url.startsWith("/data/warehouse/"))
376
420
  return serveUnder(res, args.warehouseDir, url.slice("/data/warehouse/".length));
377
421
  // No app → the Django-style welcome page (root only; else 404).
@@ -404,6 +448,8 @@ async function main(argv) {
404
448
  console.log(` ✓ app: ${path.relative(process.cwd(), appRoot) || "."}`);
405
449
  else
406
450
  console.log(` ○ no app found — serving the welcome page`);
451
+ if (hasDevCognito(cwd))
452
+ console.log(` ✓ dev Cognito: /__rastack/cognito.json (sign in against the real dev pool)`);
407
453
  console.log(`\n ▸ ${url}\n`);
408
454
  console.log(" Need the database console? Run `rastack admin`. Ctrl+C to stop.\n");
409
455
  if (args.open)
@@ -11,14 +11,17 @@
11
11
  * 1. writes `.rastack/deploy.json` + `.github/workflows/{ci,deploy-backend}.yml`
12
12
  * (GitHub coordinates default from the `origin` remote; missing fields are
13
13
  * prompted for on a TTY);
14
- * 2. resolves the tier-0 bootstrap stack's artifacts bucket — erroring with a
15
- * pointer to `rastack bootstrap` when that account+region stack is missing
16
- * (init is per-app and never creates account-level infrastructure);
14
+ * 2. verifies the AWS CLI is logged in, then ensures the tier-0 bootstrap stack
15
+ * exists — creating it (the S3 template/placeholder buckets + GitHub OIDC
16
+ * provider) when missing, reconciling it when it already does;
17
17
  * 3. uploads the Lambda placeholder bundle to that bucket;
18
18
  * 4. deploys the per-app backend (app.yaml) with the resolved ArtifactsBucket;
19
19
  * 5. reads the stack Outputs back into the config and prints the two GitHub repo
20
20
  * Variables CI needs.
21
21
  *
22
+ * The CloudFormation templates ship inside the `rastack` package; a partial
23
+ * install falls back to downloading them from reactapistack.com.
24
+ *
22
25
  * Thereafter the recurring code deploy is CI's job — `rastack ci deploy`, which
23
26
  * `rastack deploy` also runs. `--no-deploy` scaffolds only; `--print` shows the
24
27
  * exact single-line `aws cloudformation deploy` commands without running them.