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/deploy/workflows.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Pure string builders — `rastack-init.ts` is the thin wrapper that writes them.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { DeployConfig } from "./config";
|
|
14
|
+
import { DeployConfig, EnvName, resolveEnvironment } from "./config";
|
|
15
15
|
|
|
16
16
|
/** A templated file: repo-relative path + its contents. */
|
|
17
17
|
export interface ScaffoldFile {
|
|
@@ -27,6 +27,8 @@ const NODE_VERSION = "22";
|
|
|
27
27
|
* every push/PR. The clean replacement for hand-written compile/check steps.
|
|
28
28
|
*/
|
|
29
29
|
export function renderCiWorkflow(cfg: DeployConfig): string {
|
|
30
|
+
const prod = resolveEnvironment(cfg, "prod");
|
|
31
|
+
const dev = resolveEnvironment(cfg, "dev");
|
|
30
32
|
return `name: CI
|
|
31
33
|
|
|
32
34
|
# Validate the resource graph and compile the manifest on every change.
|
|
@@ -35,7 +37,7 @@ export function renderCiWorkflow(cfg: DeployConfig): string {
|
|
|
35
37
|
|
|
36
38
|
on:
|
|
37
39
|
push:
|
|
38
|
-
branches: [${
|
|
40
|
+
branches: [${prod.branch}, ${dev.branch}]
|
|
39
41
|
pull_request:
|
|
40
42
|
|
|
41
43
|
permissions:
|
|
@@ -65,19 +67,28 @@ jobs:
|
|
|
65
67
|
*/
|
|
66
68
|
export function renderDeployWorkflow(cfg: DeployConfig): string {
|
|
67
69
|
const arch = cfg.lambda.arch === "arm64" ? "arm64" : "x86_64";
|
|
70
|
+
const prod = resolveEnvironment(cfg, "prod");
|
|
71
|
+
const dev = 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' }}`;
|
|
68
75
|
return `name: Deploy backend
|
|
69
76
|
|
|
70
|
-
# Ship the Rust API (rastack-server) to AWS Lambda. Infrastructure
|
|
71
|
-
# once by
|
|
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
|
|
72
79
|
# only ever runs \`aws lambda update-function-code\`, wrapped by \`rastack ci\`.
|
|
73
80
|
#
|
|
74
|
-
#
|
|
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):
|
|
75
86
|
# AWS_DEPLOY_ROLE_ARN DeployRoleArn (the OIDC role this job assumes)
|
|
76
87
|
# AWS_REGION Region (${cfg.region})
|
|
77
88
|
|
|
78
89
|
on:
|
|
79
90
|
push:
|
|
80
|
-
branches: [${
|
|
91
|
+
branches: [${prod.branch}, ${dev.branch}]
|
|
81
92
|
|
|
82
93
|
# Least privilege: read the repo + mint an OIDC token to assume the deploy role.
|
|
83
94
|
permissions:
|
|
@@ -85,15 +96,20 @@ permissions:
|
|
|
85
96
|
id-token: write
|
|
86
97
|
|
|
87
98
|
concurrency:
|
|
88
|
-
|
|
99
|
+
# One in-flight deploy per branch, so dev and prod never cancel each other.
|
|
100
|
+
group: deploy-backend-\${{ github.ref_name }}
|
|
89
101
|
cancel-in-progress: true
|
|
90
102
|
|
|
91
103
|
jobs:
|
|
92
104
|
deploy:
|
|
93
105
|
name: Build & deploy the Lambda (${arch})
|
|
94
106
|
runs-on: ubuntu-latest
|
|
95
|
-
#
|
|
96
|
-
#
|
|
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.
|
|
97
113
|
if: vars.AWS_DEPLOY_ROLE_ARN != ''
|
|
98
114
|
steps:
|
|
99
115
|
- uses: actions/checkout@v4
|
|
@@ -120,10 +136,10 @@ jobs:
|
|
|
120
136
|
role-to-assume: \${{ vars.AWS_DEPLOY_ROLE_ARN }}
|
|
121
137
|
aws-region: \${{ vars.AWS_REGION }}
|
|
122
138
|
|
|
123
|
-
# One line: compile the manifest, cross-build the bundle, bake the
|
|
124
|
-
# and update-function-code
|
|
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.
|
|
125
141
|
- name: Build & deploy
|
|
126
|
-
run: rastack ci deploy
|
|
142
|
+
run: rastack ci deploy --env ${envFlag}
|
|
127
143
|
`;
|
|
128
144
|
}
|
|
129
145
|
|
|
@@ -135,6 +151,100 @@ export function renderConfigFile(cfg: DeployConfig): string {
|
|
|
135
151
|
export const CONFIG_PATH = ".rastack/deploy.json";
|
|
136
152
|
export const CI_WORKFLOW_PATH = ".github/workflows/ci.yml";
|
|
137
153
|
export const DEPLOY_WORKFLOW_PATH = ".github/workflows/deploy-backend.yml";
|
|
154
|
+
export const ENV_PATH = ".env";
|
|
155
|
+
export const GITIGNORE_PATH = ".gitignore";
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The generated `.env` — the app's **local** environment. Every value is
|
|
159
|
+
* non-secret (client ids, pool ids, public URLs from the stack Outputs), but the
|
|
160
|
+
* file is git-ignored by convention (`gitignoreWith`) so a developer can add
|
|
161
|
+
* their own secrets beside these without ever committing them. Values are blank
|
|
162
|
+
* until the matching stack is deployed; `rastack init` rewrites the file with the
|
|
163
|
+
* resolved Outputs after standing the backends up.
|
|
164
|
+
*
|
|
165
|
+
* Keys are `RASTACK_<ENV>_*` (a neutral prefix). For a browser bundle, re-export
|
|
166
|
+
* the ones you need under your bundler's public prefix (EXPO_PUBLIC_/VITE_/…);
|
|
167
|
+
* `rastack dev` also serves the dev pool config live at `/__rastack/cognito.json`.
|
|
168
|
+
*/
|
|
169
|
+
export function renderEnvFile(cfg: DeployConfig): string {
|
|
170
|
+
const lines: string[] = [
|
|
171
|
+
"# Generated by `rastack init` — local app environment (git-ignored).",
|
|
172
|
+
"# Non-secret values from the CloudFormation stack Outputs; add your own",
|
|
173
|
+
"# secrets below. Re-run `rastack init` to refresh after a deploy.",
|
|
174
|
+
"",
|
|
175
|
+
`RASTACK_APP=${cfg.app}`,
|
|
176
|
+
`RASTACK_REGION=${cfg.region}`,
|
|
177
|
+
"",
|
|
178
|
+
];
|
|
179
|
+
const envKeys: [EnvName, string][] = [
|
|
180
|
+
["dev", "DEV"],
|
|
181
|
+
["prod", "PROD"],
|
|
182
|
+
];
|
|
183
|
+
for (const [env, prefix] of envKeys) {
|
|
184
|
+
const e = resolveEnvironment(cfg, env);
|
|
185
|
+
const o = e.outputs ?? {};
|
|
186
|
+
lines.push(
|
|
187
|
+
`# ${env} (${e.branch} → ${e.ghEnvironment})`,
|
|
188
|
+
`RASTACK_${prefix}_API_URL=${o.apiUrl ?? ""}`,
|
|
189
|
+
`RASTACK_${prefix}_USER_POOL_ID=${o.userPoolId ?? ""}`,
|
|
190
|
+
`RASTACK_${prefix}_USER_POOL_CLIENT_ID=${o.userPoolClientId ?? ""}`,
|
|
191
|
+
`RASTACK_${prefix}_HOSTED_UI_URL=${o.hostedUiUrl ?? ""}`,
|
|
192
|
+
`RASTACK_${prefix}_PROXY_URL=${o.proxyUrl ?? ""}`,
|
|
193
|
+
"",
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
return lines.join("\n");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Upsert the generated `RASTACK_*` keys from `renderEnvFile(cfg)` into an
|
|
201
|
+
* existing `.env`, preserving every other line (a developer's own secrets,
|
|
202
|
+
* comments, extra keys). A matching `RASTACK_*=` line is updated in place;
|
|
203
|
+
* generated keys not yet present are appended. When there is no existing file,
|
|
204
|
+
* this is exactly `renderEnvFile(cfg)`. Pure — the IO wrapper reads/writes.
|
|
205
|
+
*/
|
|
206
|
+
export function applyEnvValues(existing: string, cfg: DeployConfig): string {
|
|
207
|
+
const rendered = renderEnvFile(cfg);
|
|
208
|
+
if (!existing.trim()) return rendered;
|
|
209
|
+
|
|
210
|
+
const generated = new Map<string, string>();
|
|
211
|
+
for (const line of rendered.split("\n")) {
|
|
212
|
+
const m = /^(RASTACK_[A-Z0-9_]+)=(.*)$/.exec(line);
|
|
213
|
+
if (m) generated.set(m[1], m[2]);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const seen = new Set<string>();
|
|
217
|
+
const out = existing.split("\n").map((line) => {
|
|
218
|
+
const m = /^(RASTACK_[A-Z0-9_]+)=/.exec(line);
|
|
219
|
+
if (m && generated.has(m[1])) {
|
|
220
|
+
seen.add(m[1]);
|
|
221
|
+
return `${m[1]}=${generated.get(m[1])}`;
|
|
222
|
+
}
|
|
223
|
+
return line;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const missing = [...generated].filter(([k]) => !seen.has(k));
|
|
227
|
+
if (missing.length) {
|
|
228
|
+
if (out.length && out[out.length - 1].trim() !== "") out.push("");
|
|
229
|
+
for (const [k, v] of missing) out.push(`${k}=${v}`);
|
|
230
|
+
out.push("");
|
|
231
|
+
}
|
|
232
|
+
return out.join("\n");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Return `.gitignore` content with a `.env` entry present (idempotent). Appends
|
|
237
|
+
* under a labelled block when missing, leaves the file untouched when `.env` is
|
|
238
|
+
* already ignored — so `rastack init` never duplicates the entry or clobbers a
|
|
239
|
+
* developer's existing ignore rules.
|
|
240
|
+
*/
|
|
241
|
+
export function gitignoreWith(existing: string, entry = ".env"): string {
|
|
242
|
+
const lines = existing.split("\n").map((l) => l.trim());
|
|
243
|
+
if (lines.includes(entry)) return existing;
|
|
244
|
+
const base = existing.replace(/\n+$/, "");
|
|
245
|
+
const block = `# rastack — local environment (secrets)\n${entry}\n`;
|
|
246
|
+
return base ? `${base}\n\n${block}` : block;
|
|
247
|
+
}
|
|
138
248
|
|
|
139
249
|
export interface ScaffoldOptions {
|
|
140
250
|
/** Omit the GitHub Actions workflows (config only). */
|
package/src/rastack-ci.ts
CHANGED
|
@@ -19,12 +19,20 @@
|
|
|
19
19
|
|
|
20
20
|
import { execSync } from "child_process";
|
|
21
21
|
import { runCompile } from "./compile";
|
|
22
|
-
import { DeployConfig } from "./deploy/config";
|
|
22
|
+
import { DeployConfig, EnvName, ENV_NAMES } from "./deploy/config";
|
|
23
23
|
import { CiPhase, CiPlanOptions, CiStep, ciPlan } from "./deploy/ci";
|
|
24
24
|
import { loadConfig } from "./deploy/io";
|
|
25
25
|
|
|
26
26
|
const PHASES: CiPhase[] = ["check", "build", "deploy"];
|
|
27
27
|
|
|
28
|
+
/** Coerce a string to an `EnvName`, or undefined when unset/unrecognised. */
|
|
29
|
+
function parseEnv(value: string | undefined): EnvName | undefined {
|
|
30
|
+
const v = (value ?? "").trim();
|
|
31
|
+
return (ENV_NAMES as readonly string[]).includes(v)
|
|
32
|
+
? (v as EnvName)
|
|
33
|
+
: undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
interface Flags {
|
|
29
37
|
phase: CiPhase;
|
|
30
38
|
plan: boolean;
|
|
@@ -38,6 +46,7 @@ function parseFlags(argv: string[]): Flags {
|
|
|
38
46
|
bin: process.env.RASTACK_BIN || "rastack",
|
|
39
47
|
functionName: process.env.RASTACK_FUNCTION || undefined,
|
|
40
48
|
jwksUrl: process.env.RASTACK_JWKS_URL || undefined,
|
|
49
|
+
env: parseEnv(process.env.RASTACK_ENV),
|
|
41
50
|
};
|
|
42
51
|
for (let i = 0; i < argv.length; i++) {
|
|
43
52
|
const a = argv[i];
|
|
@@ -56,6 +65,15 @@ function parseFlags(argv: string[]): Flags {
|
|
|
56
65
|
case "--jwks-url":
|
|
57
66
|
opts.jwksUrl = next();
|
|
58
67
|
break;
|
|
68
|
+
case "--env": {
|
|
69
|
+
const env = parseEnv(next());
|
|
70
|
+
if (!env) {
|
|
71
|
+
console.error(`Unknown --env: expected ${ENV_NAMES.join(" | ")}.`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
opts.env = env;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
59
77
|
case "--bin":
|
|
60
78
|
opts.bin = next();
|
|
61
79
|
break;
|
|
@@ -75,7 +93,7 @@ function parseFlags(argv: string[]): Flags {
|
|
|
75
93
|
}
|
|
76
94
|
if (!phase) {
|
|
77
95
|
console.error(
|
|
78
|
-
`Usage: rastack ci <${PHASES.join(" | ")}> [--plan] [--skip-build]\n`,
|
|
96
|
+
`Usage: rastack ci <${PHASES.join(" | ")}> [--env dev|prod] [--plan] [--skip-build]\n`,
|
|
79
97
|
);
|
|
80
98
|
process.exit(1);
|
|
81
99
|
}
|
package/src/rastack-dev.ts
CHANGED
|
@@ -21,12 +21,19 @@
|
|
|
21
21
|
* but a Rust + wasm toolchain is, it is built from source as a fallback.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
execFileSync,
|
|
26
|
+
spawn,
|
|
27
|
+
spawnSync,
|
|
28
|
+
type ChildProcess,
|
|
29
|
+
} from "child_process";
|
|
25
30
|
import * as fs from "fs";
|
|
26
31
|
import * as http from "http";
|
|
27
32
|
import * as net from "net";
|
|
28
33
|
import * as path from "path";
|
|
29
34
|
import { runCompile } from "./compile";
|
|
35
|
+
import { cognitoRuntimeConfig } from "./deploy/config";
|
|
36
|
+
import { loadConfig } from "./deploy/io";
|
|
30
37
|
import {
|
|
31
38
|
APP_ENTRY,
|
|
32
39
|
WASM_ENTRY,
|
|
@@ -115,6 +122,37 @@ function serveFile(res: http.ServerResponse, file: string): void {
|
|
|
115
122
|
});
|
|
116
123
|
}
|
|
117
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Serve the dev environment's Cognito coordinates (`/__rastack/cognito.json`) so
|
|
127
|
+
* a locally-running app can sign in against the *real* dev user pool. Read from
|
|
128
|
+
* `.rastack/deploy.json`'s dev outputs; `{}` when the dev stack isn't deployed
|
|
129
|
+
* yet (the app then falls back to `mode:"local"`). Returns whether a real config
|
|
130
|
+
* was found, for the startup hint.
|
|
131
|
+
*/
|
|
132
|
+
function serveDevCognito(res: http.ServerResponse, cwd: string): void {
|
|
133
|
+
let body = "{}";
|
|
134
|
+
try {
|
|
135
|
+
const cfg = loadConfig(cwd);
|
|
136
|
+
const runtime = cfg ? cognitoRuntimeConfig(cfg, "dev") : null;
|
|
137
|
+
if (runtime) body = JSON.stringify(runtime);
|
|
138
|
+
} catch {
|
|
139
|
+
/* no/invalid config → empty object, app uses the local shim */
|
|
140
|
+
}
|
|
141
|
+
res
|
|
142
|
+
.writeHead(200, { "Content-Type": "application/json; charset=utf-8" })
|
|
143
|
+
.end(body);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Whether a real dev Cognito config is available to advertise on startup. */
|
|
147
|
+
function hasDevCognito(cwd: string): boolean {
|
|
148
|
+
try {
|
|
149
|
+
const cfg = loadConfig(cwd);
|
|
150
|
+
return Boolean(cfg && cognitoRuntimeConfig(cfg, "dev"));
|
|
151
|
+
} catch {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
118
156
|
/** Best-effort open the default browser at `url`. */
|
|
119
157
|
function openBrowser(url: string): void {
|
|
120
158
|
const cmd =
|
|
@@ -177,7 +215,9 @@ function getFreePort(): Promise<number> {
|
|
|
177
215
|
srv.listen(0, "127.0.0.1", () => {
|
|
178
216
|
const addr = srv.address();
|
|
179
217
|
const port = addr && typeof addr === "object" ? addr.port : 0;
|
|
180
|
-
srv.close(() =>
|
|
218
|
+
srv.close(() =>
|
|
219
|
+
port ? resolve(port) : reject(new Error("no free port")),
|
|
220
|
+
);
|
|
181
221
|
});
|
|
182
222
|
});
|
|
183
223
|
}
|
|
@@ -241,7 +281,13 @@ function proxyToMetro(
|
|
|
241
281
|
port: number,
|
|
242
282
|
): void {
|
|
243
283
|
const proxyReq = http.request(
|
|
244
|
-
{
|
|
284
|
+
{
|
|
285
|
+
host: "127.0.0.1",
|
|
286
|
+
port,
|
|
287
|
+
method: req.method,
|
|
288
|
+
path: req.url,
|
|
289
|
+
headers: req.headers,
|
|
290
|
+
},
|
|
245
291
|
(proxyRes) => {
|
|
246
292
|
res.writeHead(proxyRes.statusCode || 502, proxyRes.headers);
|
|
247
293
|
proxyRes.pipe(res);
|
|
@@ -318,8 +364,13 @@ async function runMetroMode(args: DevArgs, cwd: string): Promise<void> {
|
|
|
318
364
|
return serveUnder(res, wasmDir, url.slice("/wasm/".length));
|
|
319
365
|
if (url === "/schema.rastack.json" || url === "/openapi.json")
|
|
320
366
|
return serveUnder(res, args.outDir, path.basename(url));
|
|
367
|
+
if (url === "/__rastack/cognito.json") return serveDevCognito(res, cwd);
|
|
321
368
|
if (url.startsWith("/data/warehouse/"))
|
|
322
|
-
return serveUnder(
|
|
369
|
+
return serveUnder(
|
|
370
|
+
res,
|
|
371
|
+
args.warehouseDir,
|
|
372
|
+
url.slice("/data/warehouse/".length),
|
|
373
|
+
);
|
|
323
374
|
// Everything else → the live Metro bundle (HTML, JS, assets, HMR polls).
|
|
324
375
|
return proxyToMetro(req, res, metroPort);
|
|
325
376
|
});
|
|
@@ -341,8 +392,7 @@ async function runMetroMode(args: DevArgs, cwd: string): Promise<void> {
|
|
|
341
392
|
server.listen(args.port, () => {
|
|
342
393
|
const url = `http://localhost:${args.port}`;
|
|
343
394
|
console.log(` ✓ Metro live on :${metroPort} — fast refresh on`);
|
|
344
|
-
if (wasmDir)
|
|
345
|
-
console.log(` ✓ WASM engine: ${path.relative(cwd, wasmDir)}`);
|
|
395
|
+
if (wasmDir) console.log(` ✓ WASM engine: ${path.relative(cwd, wasmDir)}`);
|
|
346
396
|
console.log(`\n ▸ ${url}\n`);
|
|
347
397
|
console.log(
|
|
348
398
|
" Edits reload live. Need the database console? Run `rastack admin`. Ctrl+C to stop.\n",
|
|
@@ -393,6 +443,7 @@ async function main(argv: string[]): Promise<void> {
|
|
|
393
443
|
return serveUnder(res, wasmDir, url.slice("/wasm/".length));
|
|
394
444
|
if (url === "/schema.rastack.json" || url === "/openapi.json")
|
|
395
445
|
return serveUnder(res, args.outDir, path.basename(url));
|
|
446
|
+
if (url === "/__rastack/cognito.json") return serveDevCognito(res, cwd);
|
|
396
447
|
if (url.startsWith("/data/warehouse/"))
|
|
397
448
|
return serveUnder(
|
|
398
449
|
res,
|
|
@@ -432,6 +483,10 @@ async function main(argv: string[]): Promise<void> {
|
|
|
432
483
|
if (appRoot)
|
|
433
484
|
console.log(` ✓ app: ${path.relative(process.cwd(), appRoot) || "."}`);
|
|
434
485
|
else console.log(` ○ no app found — serving the welcome page`);
|
|
486
|
+
if (hasDevCognito(cwd))
|
|
487
|
+
console.log(
|
|
488
|
+
` ✓ dev Cognito: /__rastack/cognito.json (sign in against the real dev pool)`,
|
|
489
|
+
);
|
|
435
490
|
console.log(`\n ▸ ${url}\n`);
|
|
436
491
|
console.log(
|
|
437
492
|
" Need the database console? Run `rastack admin`. Ctrl+C to stop.\n",
|