rastack 0.0.55 → 0.0.57
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 +9 -0
- package/admin/AdminApp.tsx +307 -50
- package/admin/ui/DetailsModal.tsx +125 -0
- package/admin/ui/Sidebar.tsx +28 -10
- package/admin/ui/icons.tsx +41 -0
- package/admin/ui/motion.tsx +292 -0
- package/admin/ui/styles.ts +277 -0
- package/admin/useAdmin.ts +91 -0
- package/dist/admin.js +295 -18
- package/dist/compile/index.d.ts +1 -0
- package/dist/compile/index.js +8 -3
- package/dist/compile/run.d.ts +13 -0
- package/dist/compile/run.js +63 -0
- package/dist/deploy/ci.d.ts +8 -1
- package/dist/deploy/ci.js +15 -9
- package/dist/dev/harness.d.ts +44 -13
- package/dist/dev/harness.js +117 -16
- package/dist/rastack-admin.js +2 -9
- package/dist/rastack-ci.js +15 -3
- package/dist/rastack-compile.d.ts +6 -3
- package/dist/rastack-compile.js +10 -16
- package/dist/rastack-dev.d.ts +9 -5
- package/dist/rastack-dev.js +52 -32
- package/dist/rastack-update.js +11 -4
- package/dist/rastack.d.ts +4 -2
- package/dist/rastack.js +17 -6
- package/dist/wasm/rastack_wasm_bg.wasm +0 -0
- package/package.json +7 -7
- package/src/compile/index.ts +13 -4
- package/src/compile/run.ts +26 -0
- package/src/deploy/ci.ts +24 -10
- package/src/dev/harness.ts +121 -15
- package/src/rastack-admin.ts +6 -15
- package/src/rastack-ci.ts +16 -3
- package/src/rastack-compile.ts +10 -18
- package/src/rastack-dev.ts +59 -40
- package/src/rastack-update.ts +11 -4
- package/src/rastack.ts +19 -6
- package/test/deploy.spec.ts +5 -2
- package/test/dev.spec.ts +42 -1
- package/wasm/rastack_wasm_bg.wasm +0 -0
package/dist/rastack-dev.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
/**
|
|
4
|
-
* `rastack dev` — local development
|
|
4
|
+
* `rastack dev` — local development of **your app**, always over the WASM engine.
|
|
5
5
|
*
|
|
6
|
-
* rastack dev [resourcesDir] [--port <n>] [--warehouse <dir>] [--out <dir>] [--no-open]
|
|
6
|
+
* rastack dev [resourcesDir] [--port <n>] [--warehouse <dir>] [--out <dir>] [--app <dir>] [--no-open]
|
|
7
7
|
*
|
|
8
8
|
* Compiles the resources, then boots a dependency-free static server (à la
|
|
9
|
-
* `rastack design`) that hands the browser everything it needs to run the
|
|
10
|
-
*
|
|
11
|
-
* • the
|
|
9
|
+
* `rastack design`) that hands the browser everything it needs to run the
|
|
10
|
+
* developer's own app locally over WebAssembly — no native server, no cargo:
|
|
11
|
+
* • the developer's app (`index.html` from the resolved app root), or a
|
|
12
|
+
* Django-style welcome page when the project has no app yet,
|
|
12
13
|
* • the prebuilt WASM engine (`/wasm/…`) shipped in the package,
|
|
13
14
|
* • the compiled `schema.rastack.json` / `openapi.json`,
|
|
14
15
|
* • the committed Iceberg warehouse (`/data/warehouse/…`) to seed from.
|
|
15
16
|
*
|
|
17
|
+
* It does **not** serve the admin console — that is a separate command,
|
|
18
|
+
* `rastack admin`.
|
|
19
|
+
*
|
|
16
20
|
* The engine is resolved from the package's prebuilt bundle; if none is present
|
|
17
21
|
* but a Rust + wasm toolchain is, it is built from source as a fallback.
|
|
18
22
|
*/
|
|
@@ -54,25 +58,26 @@ const child_process_1 = require("child_process");
|
|
|
54
58
|
const fs = __importStar(require("fs"));
|
|
55
59
|
const http = __importStar(require("http"));
|
|
56
60
|
const path = __importStar(require("path"));
|
|
61
|
+
const compile_1 = require("./compile");
|
|
57
62
|
const harness_1 = require("./dev/harness");
|
|
58
63
|
const HERE = __dirname; // dist/ when installed
|
|
59
|
-
/** Compile the resources to schema.rastack.json + openapi.json in outDir. */
|
|
60
|
-
function compile(resourcesDir, outDir) {
|
|
61
|
-
const compiler = path.join(HERE, "rastack-compile.js");
|
|
62
|
-
const result = (0, child_process_1.spawnSync)(process.execPath, [compiler, "compile", resourcesDir, outDir], { stdio: "inherit" });
|
|
63
|
-
if (result.status !== 0)
|
|
64
|
-
return false;
|
|
65
|
-
return fs.existsSync(path.join(outDir, "schema.rastack.json"));
|
|
66
|
-
}
|
|
67
64
|
/** True when `cargo` is on PATH (the from-source wasm fallback needs it). */
|
|
68
65
|
function cargoAvailable() {
|
|
69
66
|
try {
|
|
70
|
-
return (
|
|
67
|
+
return (0, child_process_1.spawnSync)("cargo", ["--version"], { stdio: "ignore" }).status === 0;
|
|
71
68
|
}
|
|
72
69
|
catch {
|
|
73
70
|
return false;
|
|
74
71
|
}
|
|
75
72
|
}
|
|
73
|
+
/** First app-root candidate that holds an `index.html`, or null (→ welcome). */
|
|
74
|
+
function resolveAppRoot(appDir) {
|
|
75
|
+
for (const dir of (0, harness_1.appRootCandidates)(appDir)) {
|
|
76
|
+
if (fs.existsSync(path.join(dir, harness_1.APP_ENTRY)))
|
|
77
|
+
return dir;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
76
81
|
/** Locate the prebuilt wasm bundle dir, building from source as a fallback. */
|
|
77
82
|
function resolveWasmDir() {
|
|
78
83
|
for (const dir of (0, harness_1.wasmBundleCandidates)(HERE)) {
|
|
@@ -141,9 +146,9 @@ function openBrowser(url) {
|
|
|
141
146
|
}
|
|
142
147
|
function main(argv) {
|
|
143
148
|
const args = (0, harness_1.parseDevArgs)(argv);
|
|
144
|
-
console.log(`\n rastack dev —
|
|
149
|
+
console.log(`\n rastack dev — your app, in your browser over WASM\n`);
|
|
145
150
|
console.log(`▸ Compiling ${args.resourcesDir} → ${args.outDir}`);
|
|
146
|
-
if (!
|
|
151
|
+
if (!(0, compile_1.runCompile)(args.resourcesDir, args.outDir)) {
|
|
147
152
|
console.error(" ✗ compile failed — fix the resource graph and re-run.");
|
|
148
153
|
process.exit(1);
|
|
149
154
|
}
|
|
@@ -156,38 +161,53 @@ function main(argv) {
|
|
|
156
161
|
" cargo install wasm-bindgen-cli\n");
|
|
157
162
|
process.exit(1);
|
|
158
163
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
console.error(" ✗ admin bundle (dist/admin.js) not found — reinstall rastack.");
|
|
162
|
-
process.exit(1);
|
|
163
|
-
}
|
|
164
|
+
// Serve the developer's own app when there is one; otherwise the welcome page.
|
|
165
|
+
const appRoot = resolveAppRoot(args.appDir);
|
|
164
166
|
const server = http.createServer((req, res) => {
|
|
165
167
|
const url = (req.url || "/").split("?")[0];
|
|
166
168
|
if (req.method !== "GET") {
|
|
167
169
|
res.writeHead(405).end("Method not allowed");
|
|
168
170
|
return;
|
|
169
171
|
}
|
|
170
|
-
|
|
171
|
-
res
|
|
172
|
-
.writeHead(200, { "Content-Type": "text/html; charset=utf-8" })
|
|
173
|
-
.end((0, harness_1.renderShell)());
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
if (url === "/dev/admin.js")
|
|
177
|
-
return serveFile(res, adminBundle);
|
|
172
|
+
// Reserved backend routes always win over the app's static files.
|
|
178
173
|
if (url.startsWith("/wasm/"))
|
|
179
174
|
return serveUnder(res, wasmDir, url.slice("/wasm/".length));
|
|
180
175
|
if (url === "/schema.rastack.json" || url === "/openapi.json")
|
|
181
176
|
return serveUnder(res, args.outDir, path.basename(url));
|
|
182
177
|
if (url.startsWith("/data/warehouse/"))
|
|
183
178
|
return serveUnder(res, args.warehouseDir, url.slice("/data/warehouse/".length));
|
|
184
|
-
|
|
179
|
+
// No app → the Django-style welcome page (root only; else 404).
|
|
180
|
+
if (!appRoot) {
|
|
181
|
+
if (url === "/" || url === "/index.html") {
|
|
182
|
+
res
|
|
183
|
+
.writeHead(200, { "Content-Type": "text/html; charset=utf-8" })
|
|
184
|
+
.end((0, harness_1.renderWelcome)());
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
res.writeHead(404).end("Not found");
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// Serve the app: its index.html at `/`, an existing file as-is, and any
|
|
191
|
+
// other route back to index.html (SPA client-side routing).
|
|
192
|
+
if (url === "/" || url === "/index.html")
|
|
193
|
+
return serveFile(res, path.join(appRoot, harness_1.APP_ENTRY));
|
|
194
|
+
const rel = decodeURIComponent(url).replace(/^\/+/, "");
|
|
195
|
+
const target = path.resolve(appRoot, rel);
|
|
196
|
+
const safeRoot = path.resolve(appRoot);
|
|
197
|
+
const inRoot = target === safeRoot || target.startsWith(safeRoot + path.sep);
|
|
198
|
+
if (inRoot && fs.existsSync(target) && fs.statSync(target).isFile())
|
|
199
|
+
return serveFile(res, target);
|
|
200
|
+
return serveFile(res, path.join(appRoot, harness_1.APP_ENTRY));
|
|
185
201
|
});
|
|
186
202
|
server.listen(args.port, () => {
|
|
187
203
|
const url = `http://localhost:${args.port}`;
|
|
188
204
|
console.log(` ✓ WASM engine: ${path.relative(process.cwd(), wasmDir)}`);
|
|
189
|
-
|
|
190
|
-
|
|
205
|
+
if (appRoot)
|
|
206
|
+
console.log(` ✓ app: ${path.relative(process.cwd(), appRoot) || "."}`);
|
|
207
|
+
else
|
|
208
|
+
console.log(` ○ no app found — serving the welcome page`);
|
|
209
|
+
console.log(`\n ▸ ${url}\n`);
|
|
210
|
+
console.log(" Need the database console? Run `rastack admin`. Ctrl+C to stop.\n");
|
|
191
211
|
if (args.open)
|
|
192
212
|
openBrowser(url);
|
|
193
213
|
});
|
package/dist/rastack-update.js
CHANGED
|
@@ -82,10 +82,17 @@ function readVersion(file) {
|
|
|
82
82
|
* sanitized (`sanitizeVersion`). POSIX keeps the shell-free `execFile`.
|
|
83
83
|
*/
|
|
84
84
|
function runNpm(args, opts) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
if (NPM_SHELL) {
|
|
86
|
+
// Windows: launch the `npm.cmd` shim through a shell (a bare `.cmd` spawn is
|
|
87
|
+
// rejected with EINVAL since the Node CVE-2024-27980 fix). The only
|
|
88
|
+
// non-literal token — the version — is sanitized in `parseUpdateFlags`, and
|
|
89
|
+
// every other arg is a fixed literal, so nothing injectable reaches it. The
|
|
90
|
+
// nosemgrep must sit on the line directly above the `execSync` call for the
|
|
91
|
+
// engine to honour it.
|
|
92
|
+
// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
|
|
93
|
+
return (0, child_process_1.execSync)((0, update_1.npmShellCommand)(NPM, args), opts);
|
|
94
|
+
}
|
|
95
|
+
return (0, child_process_1.execFileSync)(NPM, args, opts);
|
|
89
96
|
}
|
|
90
97
|
/** Capture `npm <args>` stdout, or null when npm is absent / the call fails. */
|
|
91
98
|
function npmCapture(args) {
|
package/dist/rastack.d.ts
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* rastack — unified CLI for React API Stack
|
|
4
4
|
*
|
|
5
|
+
* Compilation is not a command of its own: it runs in-process inside the two
|
|
6
|
+
* modalities of use — `rastack dev`/`admin` (local) and `rastack ci` (deploy).
|
|
7
|
+
*
|
|
5
8
|
* Commands:
|
|
6
|
-
* rastack compile [resourcesDir] [outDir] TypeScript resources → schema.rastack.json + openapi.json
|
|
7
9
|
* rastack check [resourcesDir] Validate the resource graph
|
|
8
10
|
* rastack list [resourcesDir] Print resources, fields and relations
|
|
9
11
|
* rastack urls [resourcesDir] Print the generated /api/{app}/v1/{model}/ routes
|
|
10
12
|
* rastack generate [--schema-only] Generate typed hooks from OpenAPI
|
|
11
13
|
* rastack tokens [input] [outDir] Compile design tokens → CSS vars + typed theme
|
|
12
14
|
* rastack design [input] [--port n] Live design-system studio (showcase + edit)
|
|
13
|
-
* rastack dev [--port n] [--warehouse d] Local dev: run
|
|
15
|
+
* rastack dev [--port n] [--warehouse d] Local dev: run your app in-browser over WASM
|
|
14
16
|
* rastack admin [--warehouse dir] [--port n] Full admin console — React UI (rastack/components) over the native server
|
|
15
17
|
* rastack import <file> --resource app.model Load CSV/XLSX/PDF data and validate it (state machine)
|
|
16
18
|
* rastack scan [files...] [--fail-on-pii] Scan CSVs for PII and schema info
|
package/dist/rastack.js
CHANGED
|
@@ -3,15 +3,17 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* rastack — unified CLI for React API Stack
|
|
5
5
|
*
|
|
6
|
+
* Compilation is not a command of its own: it runs in-process inside the two
|
|
7
|
+
* modalities of use — `rastack dev`/`admin` (local) and `rastack ci` (deploy).
|
|
8
|
+
*
|
|
6
9
|
* Commands:
|
|
7
|
-
* rastack compile [resourcesDir] [outDir] TypeScript resources → schema.rastack.json + openapi.json
|
|
8
10
|
* rastack check [resourcesDir] Validate the resource graph
|
|
9
11
|
* rastack list [resourcesDir] Print resources, fields and relations
|
|
10
12
|
* rastack urls [resourcesDir] Print the generated /api/{app}/v1/{model}/ routes
|
|
11
13
|
* rastack generate [--schema-only] Generate typed hooks from OpenAPI
|
|
12
14
|
* rastack tokens [input] [outDir] Compile design tokens → CSS vars + typed theme
|
|
13
15
|
* rastack design [input] [--port n] Live design-system studio (showcase + edit)
|
|
14
|
-
* rastack dev [--port n] [--warehouse d] Local dev: run
|
|
16
|
+
* rastack dev [--port n] [--warehouse d] Local dev: run your app in-browser over WASM
|
|
15
17
|
* rastack admin [--warehouse dir] [--port n] Full admin console — React UI (rastack/components) over the native server
|
|
16
18
|
* rastack import <file> --resource app.model Load CSV/XLSX/PDF data and validate it (state machine)
|
|
17
19
|
* rastack scan [files...] [--fail-on-pii] Scan CSVs for PII and schema info
|
|
@@ -64,6 +66,15 @@ switch (command) {
|
|
|
64
66
|
printVersion();
|
|
65
67
|
break;
|
|
66
68
|
case "compile":
|
|
69
|
+
// Compilation is no longer a standalone command — it runs in-process as
|
|
70
|
+
// part of the two modalities of use. Point the way rather than 404.
|
|
71
|
+
console.error("`rastack compile` has been removed — compilation now runs automatically\n" +
|
|
72
|
+
"inside the two modalities of use:\n" +
|
|
73
|
+
" • rastack dev local development (compiles, then serves over WASM)\n" +
|
|
74
|
+
" • rastack ci the deploy pipeline (compiles the manifest + OpenAPI)\n\n" +
|
|
75
|
+
"Introspect the resource graph with: rastack check | list | urls\n");
|
|
76
|
+
process.exit(1);
|
|
77
|
+
break;
|
|
67
78
|
case "check":
|
|
68
79
|
case "list":
|
|
69
80
|
case "urls":
|
|
@@ -71,8 +82,9 @@ switch (command) {
|
|
|
71
82
|
run("rastack-compile.js", [command, ...rest]);
|
|
72
83
|
break;
|
|
73
84
|
case "dev":
|
|
74
|
-
// Local development: serve
|
|
75
|
-
//
|
|
85
|
+
// Local development: serve your app in the browser over the WASM engine
|
|
86
|
+
// (no native server, no cargo). The admin console is a separate command,
|
|
87
|
+
// `rastack admin`. Replaces `serve` + `wasm`.
|
|
76
88
|
run("rastack-dev.js", rest);
|
|
77
89
|
break;
|
|
78
90
|
case "admin":
|
|
@@ -132,12 +144,11 @@ switch (command) {
|
|
|
132
144
|
default:
|
|
133
145
|
console.error(`Unknown command: "${command}"\n\n` +
|
|
134
146
|
`Usage:\n` +
|
|
135
|
-
` rastack compile [resourcesDir] [outDir]\n` +
|
|
136
147
|
` rastack check | list | urls [resourcesDir]\n` +
|
|
137
148
|
` rastack generate [--schema-only]\n` +
|
|
138
149
|
` rastack tokens [input] [outDir] [--prefix p] Compile design tokens → CSS vars + typed theme\n` +
|
|
139
150
|
` rastack design [input] [--port n] Live design-system studio (showcase + edit)\n` +
|
|
140
|
-
` rastack dev [--port n] [--warehouse dir] [--no-open] Run
|
|
151
|
+
` rastack dev [--port n] [--warehouse dir] [--app dir] [--no-open] Run your app in-browser over WASM (local dev)\n` +
|
|
141
152
|
` rastack admin [--warehouse dir] [--port n] [--no-open] Full admin console (React UI over the native server; needs a built rastack-server)\n` +
|
|
142
153
|
` rastack import <file.csv|.xlsx|.pdf> --resource app.model [--related app.model=<file>] [--out cleaned.json]\n` +
|
|
143
154
|
` rastack scan [files...] [--fail-on-pii]\n` +
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rastack",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.57",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "runtime.ts",
|
|
6
6
|
"types": "runtime.ts",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"author": "",
|
|
22
22
|
"license": "ISC",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@apidevtools/json-schema-ref-parser": "^15.
|
|
24
|
+
"@apidevtools/json-schema-ref-parser": "^15.5.0",
|
|
25
25
|
"@tanstack/react-query": "^5.101.2",
|
|
26
26
|
"@ts-stack/openapi-spec": "^3.1.5",
|
|
27
27
|
"@types/json-schema": "^7.0.15",
|
|
@@ -37,9 +37,9 @@
|
|
|
37
37
|
"lodash": "^4.18.0",
|
|
38
38
|
"luxon": "^3.7.2",
|
|
39
39
|
"minimatch": "^10.2.5",
|
|
40
|
-
"prettier": "^3.9.
|
|
40
|
+
"prettier": "^3.9.5",
|
|
41
41
|
"react-query": "^3.39.3",
|
|
42
|
-
"typescript": "
|
|
42
|
+
"typescript": "~5.9.3",
|
|
43
43
|
"usehooks-ts": "^3.1.1"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"@types/jest": "^30.0.0",
|
|
50
50
|
"@types/js-yaml": "^4.0.9",
|
|
51
51
|
"@types/mocha": "^10.0.6",
|
|
52
|
-
"@types/node": "^26.1.
|
|
52
|
+
"@types/node": "^26.1.1",
|
|
53
53
|
"@types/react-dom": "^19.2.0",
|
|
54
|
-
"esbuild": "^0.
|
|
55
|
-
"eslint": "^10.
|
|
54
|
+
"esbuild": "^0.28.1",
|
|
55
|
+
"eslint": "^10.7.0",
|
|
56
56
|
"jest": "^30.4.2",
|
|
57
57
|
"react": "^19.2.0",
|
|
58
58
|
"react-dom": "^19.2.0",
|
package/src/compile/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type {
|
|
|
21
21
|
EntityTypeExport,
|
|
22
22
|
} from "./entities";
|
|
23
23
|
export { buildManifest } from "./manifest";
|
|
24
|
+
export { runCompile } from "./run";
|
|
24
25
|
export { buildOpenApi, writeSchema } from "./openapi";
|
|
25
26
|
export * from "./model";
|
|
26
27
|
|
|
@@ -60,7 +61,10 @@ export function resolveResourceFiles(input: string): string[] {
|
|
|
60
61
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
61
62
|
if (entry.isDirectory()) {
|
|
62
63
|
if (!IGNORE_DIRS.has(entry.name)) walk(path.join(dir, entry.name));
|
|
63
|
-
} else if (
|
|
64
|
+
} else if (
|
|
65
|
+
/\.tsx?$/.test(entry.name) &&
|
|
66
|
+
!entry.name.endsWith(".d.ts")
|
|
67
|
+
) {
|
|
64
68
|
files.push(path.resolve(dir, entry.name));
|
|
65
69
|
}
|
|
66
70
|
}
|
|
@@ -96,10 +100,14 @@ export function analyzeProject(input: string): ProjectAnalysis {
|
|
|
96
100
|
const defined = new Set(fromDsl.resources.map((r) => `${r.app}.${r.model}`));
|
|
97
101
|
const resources = [
|
|
98
102
|
...fromDsl.resources,
|
|
99
|
-
...fromInterfaces.resources.filter(
|
|
103
|
+
...fromInterfaces.resources.filter(
|
|
104
|
+
(r) => !defined.has(`${r.app}.${r.model}`),
|
|
105
|
+
),
|
|
100
106
|
];
|
|
101
107
|
resources.sort((a, b) =>
|
|
102
|
-
a.app === b.app
|
|
108
|
+
a.app === b.app
|
|
109
|
+
? a.model.localeCompare(b.model)
|
|
110
|
+
: a.app.localeCompare(b.app),
|
|
103
111
|
);
|
|
104
112
|
|
|
105
113
|
// Registry entries follow the same merge rule as the resources: the DSL
|
|
@@ -237,7 +245,8 @@ export function collisions(resources: ResourceModel[]): string[] {
|
|
|
237
245
|
}
|
|
238
246
|
}
|
|
239
247
|
for (const [key, count] of seen) {
|
|
240
|
-
if (count > 1)
|
|
248
|
+
if (count > 1)
|
|
249
|
+
errors.push(`Duplicate resource ${key} defined ${count} times.`);
|
|
241
250
|
}
|
|
242
251
|
return errors;
|
|
243
252
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import { compile } from "./index";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Run the compiler **in-process**, writing `schema.rastack.json` +
|
|
7
|
+
* `openapi.json` (and `rastack-env.d.ts`) into `outDir`. Prints any warnings
|
|
8
|
+
* and, on failure, the error; returns `true` on success.
|
|
9
|
+
*
|
|
10
|
+
* This is the single entry point the two real modalities of use share instead
|
|
11
|
+
* of shelling out to a standalone `compile` command:
|
|
12
|
+
* • `rastack dev` / `rastack admin` — local development (compile, then serve
|
|
13
|
+
* the app + admin in the browser over WASM), and
|
|
14
|
+
* • `rastack ci` — the deploy pipeline (compile the manifest the Rust backend
|
|
15
|
+
* reads + the OpenAPI contract, then build/ship).
|
|
16
|
+
*/
|
|
17
|
+
export function runCompile(resourcesDir: string, outDir: string): boolean {
|
|
18
|
+
try {
|
|
19
|
+
const result = compile(resourcesDir, outDir);
|
|
20
|
+
for (const d of result.diagnostics) console.warn(` ⚠ ${d.message}`);
|
|
21
|
+
return fs.existsSync(path.join(outDir, "schema.rastack.json"));
|
|
22
|
+
} catch (err) {
|
|
23
|
+
console.error(` ✗ ${(err as Error).message}`);
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/deploy/ci.ts
CHANGED
|
@@ -23,10 +23,31 @@ export type CiPhase = "check" | "build" | "deploy";
|
|
|
23
23
|
export interface CiStep {
|
|
24
24
|
/** Human label, echoed before the step runs. */
|
|
25
25
|
name: string;
|
|
26
|
-
/** The shell command to execute. */
|
|
26
|
+
/** The shell command to execute (or, for an in-process step, a description). */
|
|
27
27
|
run: string;
|
|
28
28
|
/** Working directory relative to the repo root (default: repo root). */
|
|
29
29
|
cwd?: string;
|
|
30
|
+
/**
|
|
31
|
+
* When set, the step is executed **in-process by the CLI** rather than through
|
|
32
|
+
* a shell — `run` is then a human-readable description, not a command. Today
|
|
33
|
+
* the only in-process action is `"compile"`, which the CLI runs via the shared
|
|
34
|
+
* compiler (`runCompile`) instead of shelling out to a standalone command.
|
|
35
|
+
*/
|
|
36
|
+
action?: "compile";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The in-process compile step shared by `check` and `build`: it produces
|
|
41
|
+
* `schema.rastack.json` + `openapi.json` in `cfg.outDir` without a subprocess,
|
|
42
|
+
* so compilation lives inside the two modalities of use (`dev` and `ci`) rather
|
|
43
|
+
* than in a separately-invoked `compile` command.
|
|
44
|
+
*/
|
|
45
|
+
function compileStep(cfg: DeployConfig): CiStep {
|
|
46
|
+
return {
|
|
47
|
+
name: "Compile manifest + OpenAPI",
|
|
48
|
+
run: `compile ${cfg.resourcesDir} ${cfg.outDir} (in-process)`,
|
|
49
|
+
action: "compile",
|
|
50
|
+
};
|
|
30
51
|
}
|
|
31
52
|
|
|
32
53
|
export interface CiPlanOptions {
|
|
@@ -72,10 +93,7 @@ export function checkSteps(cfg: DeployConfig, bin: string): CiStep[] {
|
|
|
72
93
|
name: "Validate resource graph",
|
|
73
94
|
run: `${bin} check ${cfg.resourcesDir}`,
|
|
74
95
|
},
|
|
75
|
-
|
|
76
|
-
name: "Compile manifest + OpenAPI",
|
|
77
|
-
run: `${bin} compile ${cfg.resourcesDir} ${cfg.outDir}`,
|
|
78
|
-
},
|
|
96
|
+
compileStep(cfg),
|
|
79
97
|
];
|
|
80
98
|
}
|
|
81
99
|
|
|
@@ -88,7 +106,6 @@ export function buildSteps(
|
|
|
88
106
|
cfg: DeployConfig,
|
|
89
107
|
opts: CiPlanOptions = {},
|
|
90
108
|
): CiStep[] {
|
|
91
|
-
const bin = opts.bin ?? "rastack";
|
|
92
109
|
const stage = stageDir(cfg);
|
|
93
110
|
const archFlag = cfg.lambda.arch === "arm64" ? "--arm64" : "--x86-64";
|
|
94
111
|
const jwks = stackOutput(
|
|
@@ -97,10 +114,7 @@ export function buildSteps(
|
|
|
97
114
|
opts.jwksUrl ?? cfg.outputs?.jwksUrl,
|
|
98
115
|
);
|
|
99
116
|
return [
|
|
100
|
-
|
|
101
|
-
name: "Compile manifest + OpenAPI",
|
|
102
|
-
run: `${bin} compile ${cfg.resourcesDir} ${cfg.outDir}`,
|
|
103
|
-
},
|
|
117
|
+
compileStep(cfg),
|
|
104
118
|
{
|
|
105
119
|
name: "Cross-build the Lambda binary",
|
|
106
120
|
run: `cargo lambda build --release ${archFlag} --bin ${cfg.lambda.bin} --features ${cfg.lambda.features}`,
|
package/src/dev/harness.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure core of `rastack dev` — the local, always-WASM development server.
|
|
3
3
|
*
|
|
4
|
-
* `rastack dev`
|
|
5
|
-
*
|
|
4
|
+
* `rastack dev` runs **your app** in the browser over the WebAssembly engine
|
|
5
|
+
* (the same app-generic `rastack-api-core` the Lambda runs, fed the app's
|
|
6
6
|
* compiled manifest). No native server, no cargo: a dependency-free static
|
|
7
|
-
* server hands the browser the prebuilt wasm engine, the compiled schema,
|
|
8
|
-
*
|
|
7
|
+
* server hands the browser the prebuilt wasm engine, the compiled schema, the
|
|
8
|
+
* committed Iceberg warehouse, and the developer's own app. It does **not**
|
|
9
|
+
* serve the admin console — that is a separate command, `rastack admin`.
|
|
10
|
+
*
|
|
11
|
+
* When the project has no app to serve, `dev` shows a Django-style welcome
|
|
12
|
+
* page (`renderWelcome`) so a fresh scaffold still boots to something friendly.
|
|
9
13
|
*
|
|
10
14
|
* Everything decidable without IO lives here (arg parsing, MIME types, locating
|
|
11
|
-
* the wasm bundle, the HTML
|
|
12
|
-
* filesystem; `rastack-dev.ts` is the thin IO wrapper that
|
|
13
|
-
* the engine, and serves.
|
|
15
|
+
* the wasm bundle, resolving the app root, the HTML pages) so it is unit-tested
|
|
16
|
+
* without a socket or a filesystem; `rastack-dev.ts` is the thin IO wrapper that
|
|
17
|
+
* compiles, resolves the engine and the app, and serves.
|
|
14
18
|
*/
|
|
15
19
|
|
|
16
20
|
import * as path from "path";
|
|
@@ -22,6 +26,12 @@ export interface DevArgs {
|
|
|
22
26
|
outDir: string;
|
|
23
27
|
/** The committed Iceberg warehouse served to seed the browser engine. */
|
|
24
28
|
warehouseDir: string;
|
|
29
|
+
/**
|
|
30
|
+
* Explicit app root to serve at `/` (its `index.html`). Empty when unset —
|
|
31
|
+
* the IO wrapper then probes `appRootCandidates`, falling back to the welcome
|
|
32
|
+
* page when none holds an `index.html`.
|
|
33
|
+
*/
|
|
34
|
+
appDir: string;
|
|
25
35
|
/** Port the dev server listens on. */
|
|
26
36
|
port: number;
|
|
27
37
|
/** Open the browser on start (false with `--no-open`). */
|
|
@@ -32,22 +42,25 @@ export const DEV_DEFAULTS: DevArgs = {
|
|
|
32
42
|
resourcesDir: "resources",
|
|
33
43
|
outDir: ".rastack",
|
|
34
44
|
warehouseDir: "data/warehouse",
|
|
45
|
+
appDir: "",
|
|
35
46
|
port: 4321,
|
|
36
47
|
open: true,
|
|
37
48
|
};
|
|
38
49
|
|
|
39
50
|
/**
|
|
40
|
-
* Parse `rastack dev` args: `--port`, `--warehouse`, `--out`, `--
|
|
41
|
-
* single positional resources dir. Unknown flags are ignored
|
|
51
|
+
* Parse `rastack dev` args: `--port`, `--warehouse`, `--out`, `--app`,
|
|
52
|
+
* `--no-open`, and a single positional resources dir. Unknown flags are ignored
|
|
53
|
+
* (forgiving CLI).
|
|
42
54
|
*/
|
|
43
55
|
export function parseDevArgs(argv: string[]): DevArgs {
|
|
44
56
|
const positional: string[] = [];
|
|
45
|
-
let { resourcesDir, outDir, warehouseDir, port, open } = DEV_DEFAULTS;
|
|
57
|
+
let { resourcesDir, outDir, warehouseDir, appDir, port, open } = DEV_DEFAULTS;
|
|
46
58
|
for (let i = 0; i < argv.length; i++) {
|
|
47
59
|
const a = argv[i];
|
|
48
60
|
if (a === "--port") port = Number(argv[++i]) || port;
|
|
49
61
|
else if (a === "--warehouse") warehouseDir = argv[++i] ?? warehouseDir;
|
|
50
62
|
else if (a === "--out") outDir = argv[++i] ?? outDir;
|
|
63
|
+
else if (a === "--app") appDir = argv[++i] ?? appDir;
|
|
51
64
|
else if (a === "--no-open") open = false;
|
|
52
65
|
else if (!a.startsWith("-")) positional.push(a);
|
|
53
66
|
}
|
|
@@ -55,11 +68,27 @@ export function parseDevArgs(argv: string[]): DevArgs {
|
|
|
55
68
|
resourcesDir: positional[0] || resourcesDir,
|
|
56
69
|
outDir,
|
|
57
70
|
warehouseDir,
|
|
71
|
+
appDir,
|
|
58
72
|
port,
|
|
59
73
|
open,
|
|
60
74
|
};
|
|
61
75
|
}
|
|
62
76
|
|
|
77
|
+
/** The file that marks a directory as a servable app root. */
|
|
78
|
+
export const APP_ENTRY = "index.html";
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Ordered directories to probe for the developer's app (`index.html`). An
|
|
82
|
+
* explicit `--app <dir>` wins outright; otherwise `dev` looks in the
|
|
83
|
+
* conventional static roots and finally the project root. The IO wrapper picks
|
|
84
|
+
* the first candidate that actually holds an `index.html`; if none does, it
|
|
85
|
+
* serves the welcome page instead.
|
|
86
|
+
*/
|
|
87
|
+
export function appRootCandidates(appDir: string, cwd = process.cwd()): string[] {
|
|
88
|
+
if (appDir) return [path.resolve(cwd, appDir)];
|
|
89
|
+
return ["public", "www", "app", "."].map((d) => path.resolve(cwd, d));
|
|
90
|
+
}
|
|
91
|
+
|
|
63
92
|
/** Args for `rastack admin` — like `DevArgs`, but bound to a real server port. */
|
|
64
93
|
export interface AdminArgs {
|
|
65
94
|
resourcesDir: string;
|
|
@@ -147,12 +176,14 @@ export function wasmBundleCandidates(
|
|
|
147
176
|
export const WASM_ENTRY = "rastack_wasm.js";
|
|
148
177
|
|
|
149
178
|
/**
|
|
150
|
-
* The HTML shell served at
|
|
151
|
-
* (`/dev/admin.js`), which fetches the schema/openapi, boots the
|
|
152
|
-
* seeds it from `/data/warehouse/`, and renders the admin.
|
|
153
|
-
* all logic lives in the bundle — mirroring the
|
|
179
|
+
* The HTML shell served at `/` by **`rastack admin`**. It loads the bundled
|
|
180
|
+
* React admin (`/dev/admin.js`), which fetches the schema/openapi, boots the
|
|
181
|
+
* wasm engine, seeds it from `/data/warehouse/`, and renders the admin console.
|
|
182
|
+
* Deliberately tiny — all logic lives in the bundle — mirroring the
|
|
183
|
+
* `rastack design` studio. `rastack dev` does **not** use this: it serves the
|
|
184
|
+
* developer's own app (or the welcome page below).
|
|
154
185
|
*/
|
|
155
|
-
export function renderShell(title = "React API Stack —
|
|
186
|
+
export function renderShell(title = "React API Stack — admin"): string {
|
|
156
187
|
return `<!doctype html>
|
|
157
188
|
<html lang="en">
|
|
158
189
|
<head>
|
|
@@ -172,3 +203,78 @@ export function renderShell(title = "React API Stack — dev"): string {
|
|
|
172
203
|
</html>
|
|
173
204
|
`;
|
|
174
205
|
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The Django-style welcome page `rastack dev` serves at `/` when the project
|
|
209
|
+
* has **no app** to serve (no `index.html` in any `appRootCandidates` dir).
|
|
210
|
+
* It confirms the dev server is up, points at the live backend endpoints the
|
|
211
|
+
* WASM engine exposes, and tells you how to add an app or open the admin — the
|
|
212
|
+
* "It worked!" moment of `django-admin runserver`, on brand for the stack.
|
|
213
|
+
*/
|
|
214
|
+
export function renderWelcome(title = "React API Stack — dev"): string {
|
|
215
|
+
return `<!doctype html>
|
|
216
|
+
<html lang="en">
|
|
217
|
+
<head>
|
|
218
|
+
<meta charset="utf-8" />
|
|
219
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
220
|
+
<title>${title}</title>
|
|
221
|
+
<style>
|
|
222
|
+
:root { color-scheme: light dark; }
|
|
223
|
+
* { box-sizing: border-box; }
|
|
224
|
+
html, body { margin: 0; min-height: 100%; }
|
|
225
|
+
body {
|
|
226
|
+
font: 15px/1.6 system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
227
|
+
color: #1f2328; background: #f6f8fa;
|
|
228
|
+
display: flex; align-items: center; justify-content: center; padding: 2rem;
|
|
229
|
+
}
|
|
230
|
+
.card {
|
|
231
|
+
max-width: 640px; width: 100%; background: #fff; border: 1px solid #d0d7de;
|
|
232
|
+
border-radius: 14px; padding: 2.5rem; box-shadow: 0 1px 3px rgba(0,0,0,.06);
|
|
233
|
+
}
|
|
234
|
+
.rocket { font-size: 2.5rem; line-height: 1; }
|
|
235
|
+
h1 { font-size: 1.5rem; margin: .75rem 0 .25rem; }
|
|
236
|
+
p.lead { color: #57606a; margin: 0 0 1.5rem; }
|
|
237
|
+
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
238
|
+
background: #eff1f3; padding: .1em .4em; border-radius: 5px; font-size: .9em; }
|
|
239
|
+
ul { padding-left: 1.1rem; margin: 0; }
|
|
240
|
+
li { margin: .4rem 0; }
|
|
241
|
+
a { color: #0969da; text-decoration: none; }
|
|
242
|
+
a:hover { text-decoration: underline; }
|
|
243
|
+
.foot { margin-top: 1.75rem; padding-top: 1.25rem; border-top: 1px solid #eaeef2;
|
|
244
|
+
color: #6e7781; font-size: .85rem; }
|
|
245
|
+
@media (prefers-color-scheme: dark) {
|
|
246
|
+
body { color: #e6edf3; background: #0d1117; }
|
|
247
|
+
.card { background: #161b22; border-color: #30363d; box-shadow: none; }
|
|
248
|
+
p.lead { color: #8b949e; }
|
|
249
|
+
code { background: #21262d; }
|
|
250
|
+
.foot { border-color: #21262d; color: #8b949e; }
|
|
251
|
+
}
|
|
252
|
+
</style>
|
|
253
|
+
</head>
|
|
254
|
+
<body>
|
|
255
|
+
<div class="card">
|
|
256
|
+
<div class="rocket">🚀</div>
|
|
257
|
+
<h1>The dev server is running.</h1>
|
|
258
|
+
<p class="lead">
|
|
259
|
+
Congratulations — <strong>rastack dev</strong> is up and your API is running
|
|
260
|
+
in the browser over WebAssembly. You're seeing this page because there's no
|
|
261
|
+
app to serve yet.
|
|
262
|
+
</p>
|
|
263
|
+
<ul>
|
|
264
|
+
<li>Add an <code>index.html</code> (in <code>public/</code>, <code>www/</code>,
|
|
265
|
+
<code>app/</code>, or the project root) and reload — <code>dev</code> will serve it.
|
|
266
|
+
Point it at a different folder with <code>rastack dev --app <dir></code>.</li>
|
|
267
|
+
<li>Wire your app to the local engine with
|
|
268
|
+
<code><RAStackProvider mode="local"></code>.</li>
|
|
269
|
+
<li>Live backend endpoints:
|
|
270
|
+
<a href="/schema.rastack.json">/schema.rastack.json</a>,
|
|
271
|
+
<a href="/openapi.json">/openapi.json</a>,
|
|
272
|
+
<code>/data/warehouse/…</code>, <code>/wasm/…</code>.</li>
|
|
273
|
+
<li>Want the database console instead? Run <code>rastack admin</code>.</li>
|
|
274
|
+
</ul>
|
|
275
|
+
<div class="foot">React API Stack — a full-stack framework on object storage.</div>
|
|
276
|
+
</div>
|
|
277
|
+
</body>
|
|
278
|
+
</html>
|
|
279
|
+
`;
|
|
280
|
+
}
|