robodev 0.29.0 → 0.31.0
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/package.json +14 -10
- package/src/collect-files.test.ts +26 -1
- package/src/collect-files.ts +51 -13
- package/src/dev-clients.ts +65 -0
- package/src/dev-postgres.ts +161 -0
- package/src/dev-project.test.ts +168 -0
- package/src/dev-project.ts +147 -0
- package/src/dev-server.ts +201 -0
- package/src/dev.ts +348 -0
- package/src/frontend-build.test.ts +34 -7
- package/src/frontend-build.ts +29 -15
- package/src/index.ts +22 -1
- package/templates/auth-chat/.rulesync/rules/project-overview.md +1 -1
- package/templates/auth-chat/apps/fe/package.json +1 -1
- package/templates/auth-chat/package.json +3 -2
- package/templates/backend/package.json +3 -2
- package/templates/empty/package.json +3 -2
- package/templates/marketplace/.rulesync/rules/project-overview.md +1 -1
- package/templates/marketplace/apps/fe/package.json +1 -1
- package/templates/marketplace/package.json +3 -2
- package/templates/space/.rulesync/rules/project-overview.md +1 -1
- package/templates/space/apps/fe/package.json +1 -1
- package/templates/space/package.json +3 -2
|
@@ -32,27 +32,54 @@ test("resolveFrontendPaths keeps outDir inside the project", () => {
|
|
|
32
32
|
);
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
test("mapFrontendOutDir remaps text files and
|
|
35
|
+
test("mapFrontendOutDir remaps text files and includes public binaries", async () => {
|
|
36
36
|
const out = await mkdtemp(join(tmpdir(), "rd-out-"));
|
|
37
37
|
try {
|
|
38
38
|
await writeFile(join(out, "index.html"), "<html><body>app</body></html>");
|
|
39
39
|
await mkdir(join(out, "assets"), { recursive: true });
|
|
40
40
|
await writeFile(join(out, "assets/index-abc.js"), "console.log(1)");
|
|
41
41
|
await writeFile(join(out, "assets/index-abc.css"), "body{color:red}");
|
|
42
|
-
await writeFile(join(out, "assets/font.woff2"), "binary-font");
|
|
43
|
-
await writeFile(join(out, "icon.png"), "binary-png");
|
|
44
|
-
const
|
|
42
|
+
await writeFile(join(out, "assets/font.woff2"), Buffer.from("binary-font"));
|
|
43
|
+
await writeFile(join(out, "icon.png"), Buffer.from("binary-png"));
|
|
44
|
+
const files = await mapFrontendOutDir(out);
|
|
45
45
|
const paths = new Set(files.map((file) => file.path));
|
|
46
46
|
assert.ok(paths.has("index.html"));
|
|
47
47
|
assert.ok(paths.has("public/assets/index-abc.js"));
|
|
48
48
|
assert.ok(paths.has("public/assets/index-abc.css"));
|
|
49
|
-
assert.ok(
|
|
50
|
-
assert.ok(
|
|
51
|
-
assert.equal(skippedBinaries, true);
|
|
49
|
+
assert.ok(paths.has("public/assets/font.woff2"));
|
|
50
|
+
assert.ok(paths.has("public/icon.png"));
|
|
52
51
|
assert.equal(
|
|
53
52
|
files.find((file) => file.path === "index.html")?.content,
|
|
54
53
|
"<html><body>app</body></html>",
|
|
55
54
|
);
|
|
55
|
+
assert.equal(
|
|
56
|
+
files.find((file) => file.path === "public/assets/font.woff2")?.content,
|
|
57
|
+
Buffer.from("binary-font").toString("base64"),
|
|
58
|
+
);
|
|
59
|
+
assert.equal(
|
|
60
|
+
files.find((file) => file.path === "public/icon.png")?.content,
|
|
61
|
+
Buffer.from("binary-png").toString("base64"),
|
|
62
|
+
);
|
|
63
|
+
} finally {
|
|
64
|
+
await rm(out, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("mapFrontendOutDir throws when dist still has denied files", async () => {
|
|
69
|
+
const out = await mkdtemp(join(tmpdir(), "rd-out-skip-"));
|
|
70
|
+
try {
|
|
71
|
+
await writeFile(join(out, "index.html"), "<html></html>");
|
|
72
|
+
await mkdir(join(out, "assets"), { recursive: true });
|
|
73
|
+
await writeFile(join(out, "assets/app.wasm"), Buffer.from("wasm"));
|
|
74
|
+
await writeFile(join(out, "clip.mp4"), Buffer.from("mp4"));
|
|
75
|
+
await assert.rejects(
|
|
76
|
+
() => mapFrontendOutDir(out),
|
|
77
|
+
(err: unknown) =>
|
|
78
|
+
err instanceof Error &&
|
|
79
|
+
/cannot be uploaded/.test(err.message) &&
|
|
80
|
+
err.message.includes("public/assets/app.wasm") &&
|
|
81
|
+
err.message.includes("public/clip.mp4"),
|
|
82
|
+
);
|
|
56
83
|
} finally {
|
|
57
84
|
await rm(out, { recursive: true, force: true });
|
|
58
85
|
}
|
package/src/frontend-build.ts
CHANGED
|
@@ -2,7 +2,12 @@ import { execFile, spawn } from "node:child_process";
|
|
|
2
2
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
3
3
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
assertCollectedFile,
|
|
7
|
+
type CollectedFile,
|
|
8
|
+
readCollectedContent,
|
|
9
|
+
shouldCollectPath,
|
|
10
|
+
} from "./collect-files.js";
|
|
6
11
|
import { isFrontendConfig, normalizeFrontendConfig, type FrontendConfig } from "./config.js";
|
|
7
12
|
|
|
8
13
|
const execFileAsync = promisify(execFile);
|
|
@@ -132,12 +137,9 @@ function mappedDeployPath(relFromOutDir: string): string {
|
|
|
132
137
|
return `public/${rel}`;
|
|
133
138
|
}
|
|
134
139
|
|
|
135
|
-
export async function mapFrontendOutDir(outDirAbs: string): Promise<{
|
|
136
|
-
files: CollectedFile[];
|
|
137
|
-
skippedBinaries: boolean;
|
|
138
|
-
}> {
|
|
140
|
+
export async function mapFrontendOutDir(outDirAbs: string): Promise<CollectedFile[]> {
|
|
139
141
|
const files: CollectedFile[] = [];
|
|
140
|
-
|
|
142
|
+
const skipped: string[] = [];
|
|
141
143
|
|
|
142
144
|
async function walk(dir: string): Promise<void> {
|
|
143
145
|
let entries;
|
|
@@ -156,10 +158,10 @@ export async function mapFrontendOutDir(outDirAbs: string): Promise<{
|
|
|
156
158
|
const rel = relative(outDirAbs, abs).replaceAll("\\", "/");
|
|
157
159
|
const deployPath = mappedDeployPath(rel);
|
|
158
160
|
if (!shouldCollectPath(deployPath)) {
|
|
159
|
-
|
|
161
|
+
skipped.push(deployPath);
|
|
160
162
|
continue;
|
|
161
163
|
}
|
|
162
|
-
const content = await
|
|
164
|
+
const content = await readCollectedContent(abs, deployPath);
|
|
163
165
|
const file = { path: deployPath, content };
|
|
164
166
|
assertCollectedFile(file);
|
|
165
167
|
files.push(file);
|
|
@@ -167,7 +169,24 @@ export async function mapFrontendOutDir(outDirAbs: string): Promise<{
|
|
|
167
169
|
}
|
|
168
170
|
|
|
169
171
|
await walk(outDirAbs);
|
|
170
|
-
|
|
172
|
+
if (skipped.length > 0) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Frontend build produced files that cannot be uploaded: ${skipped.sort().join(", ")}`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
return files;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function frontendOutIndexExists(
|
|
181
|
+
projectRoot: string,
|
|
182
|
+
frontend: FrontendConfig,
|
|
183
|
+
): Promise<boolean> {
|
|
184
|
+
try {
|
|
185
|
+
const { outAbs } = resolveFrontendPaths(projectRoot, frontend);
|
|
186
|
+
return (await stat(join(outAbs, "index.html"))).isFile();
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
171
190
|
}
|
|
172
191
|
|
|
173
192
|
export async function buildAndCollectFrontend(
|
|
@@ -199,14 +218,9 @@ export async function buildAndCollectFrontend(
|
|
|
199
218
|
throw new Error(`Frontend build did not produce ${join(outAbs, "index.html")}`);
|
|
200
219
|
}
|
|
201
220
|
|
|
202
|
-
const
|
|
221
|
+
const files = await mapFrontendOutDir(outAbs);
|
|
203
222
|
if (!files.some((file) => file.path === "index.html")) {
|
|
204
223
|
throw new Error(`Frontend build did not produce ${join(outAbs, "index.html")}`);
|
|
205
224
|
}
|
|
206
|
-
if (skippedBinaries) {
|
|
207
|
-
console.log(
|
|
208
|
-
"Skipped binary frontend assets (fonts/images). Hosted UI may use system font fallback.",
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
225
|
return files;
|
|
212
226
|
}
|
package/src/index.ts
CHANGED
|
@@ -23,7 +23,11 @@ import {
|
|
|
23
23
|
} from "./config.js";
|
|
24
24
|
import { waitUntilLive } from "./wait-until-live.js";
|
|
25
25
|
import { collectFiles, mergeCollectedFiles, type CollectedFile } from "./collect-files.js";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
buildAndCollectFrontend,
|
|
28
|
+
commandAvailable,
|
|
29
|
+
frontendOutIndexExists,
|
|
30
|
+
} from "./frontend-build.js";
|
|
27
31
|
import {
|
|
28
32
|
emitStarter,
|
|
29
33
|
loadCatalog,
|
|
@@ -49,6 +53,15 @@ Commands:
|
|
|
49
53
|
create [path] [--starter <id>] [--empty] [--org <id>] Create a project, scaffold a starter, and deploy
|
|
50
54
|
link [projectId] Write robodev/.robodev in the current folder
|
|
51
55
|
deploy [--force] Deploy schema, APIs, jobs, and frontend (local Vite build when .robodev frontend is set)
|
|
56
|
+
dev [options] Run this project's APIs locally on an embedded Postgres, no Starbase needed
|
|
57
|
+
|
|
58
|
+
dev options:
|
|
59
|
+
--port <n> API port (default 4000)
|
|
60
|
+
--db-port <n> Postgres port (default 0 = pick a free one)
|
|
61
|
+
--database-url <url> Use an existing Postgres instead of the embedded one
|
|
62
|
+
--force Apply destructive schema changes
|
|
63
|
+
--no-watch Compile once and serve, without watching files
|
|
64
|
+
--with-fe Also start apps/fe with VITE_API_URL pointed at the local API
|
|
52
65
|
`);
|
|
53
66
|
process.exit(1);
|
|
54
67
|
}
|
|
@@ -538,6 +551,9 @@ async function runCreate(args: string[]): Promise<void> {
|
|
|
538
551
|
});
|
|
539
552
|
feHosted = true;
|
|
540
553
|
} catch (error) {
|
|
554
|
+
if (await frontendOutIndexExists(root, defaultFrontendConfig())) {
|
|
555
|
+
throw error;
|
|
556
|
+
}
|
|
541
557
|
console.log(error instanceof Error ? error.message : error);
|
|
542
558
|
console.log("Frontend build failed. Deploying schema and APIs only.");
|
|
543
559
|
skipFrontendBuild = true;
|
|
@@ -618,6 +634,11 @@ async function main(): Promise<void> {
|
|
|
618
634
|
case "deploy":
|
|
619
635
|
await runDeploy(args.includes("--force"));
|
|
620
636
|
break;
|
|
637
|
+
case "dev": {
|
|
638
|
+
const { main: runDev } = await import("./dev.js");
|
|
639
|
+
await runDev(args);
|
|
640
|
+
break;
|
|
641
|
+
}
|
|
621
642
|
default:
|
|
622
643
|
usage();
|
|
623
644
|
}
|
|
@@ -13,7 +13,7 @@ cursor:
|
|
|
13
13
|
|
|
14
14
|
This project is a Vite React frontend in `apps/fe` plus a Robodev backend under **`robodev/`** (`robodev/database.ts` + `robodev/api/**/*.ts` + `robodev/hooks/auth.ts` using `@robodev-ai/sdk`). There is no Expo / mobile app in this starter. End-user auth is reserved on the project host at `/api/user/*`. There is no in-browser fake backend.
|
|
15
15
|
|
|
16
|
-
`robodev deploy` from this root uploads schema and
|
|
16
|
+
`robodev deploy` from this root uploads schema, APIs, and (when `.robodev` has a `frontend` block) the local Vite build as a hosted static SPA. Use bun in `apps/fe` to iterate locally.
|
|
17
17
|
|
|
18
18
|
Shared AI rules and skills are authored in `.rulesync/`. Generated files for Codex, Claude Code, and Cursor are local outputs and should be regenerated with `bun rules:gen`.
|
|
19
19
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"description": "Auth chat starter frontend",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"dev": "
|
|
8
|
+
"dev": "vite --host",
|
|
9
9
|
"build": "bun run openapi:gen && bunx --bun vite build",
|
|
10
10
|
"clean": "rimraf dist src/openapi",
|
|
11
11
|
"lint": "oxlint --type-aware . ../../robodev/api ../../robodev/database.ts --fix",
|
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"dev": "robodev dev --with-fe",
|
|
7
8
|
"deploy": "robodev deploy",
|
|
8
9
|
"rules:gen": "rulesync generate"
|
|
9
10
|
},
|
|
10
11
|
"dependencies": {
|
|
11
|
-
"@robodev-ai/sdk": "^0.
|
|
12
|
+
"@robodev-ai/sdk": "^0.10.0"
|
|
12
13
|
},
|
|
13
14
|
"devDependencies": {
|
|
14
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.31.0",
|
|
15
16
|
"rulesync": "^8.18.0",
|
|
16
17
|
"typescript": "^5.9.2"
|
|
17
18
|
}
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"dev": "robodev dev",
|
|
7
8
|
"deploy": "robodev deploy"
|
|
8
9
|
},
|
|
9
10
|
"dependencies": {
|
|
10
|
-
"@robodev-ai/sdk": "^0.
|
|
11
|
+
"@robodev-ai/sdk": "^0.10.0"
|
|
11
12
|
},
|
|
12
13
|
"devDependencies": {
|
|
13
|
-
"robodev": "^0.
|
|
14
|
+
"robodev": "^0.31.0",
|
|
14
15
|
"typescript": "^5.9.2"
|
|
15
16
|
}
|
|
16
17
|
}
|
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"dev": "robodev dev",
|
|
7
8
|
"deploy": "robodev deploy"
|
|
8
9
|
},
|
|
9
10
|
"dependencies": {
|
|
10
|
-
"@robodev-ai/sdk": "^0.
|
|
11
|
+
"@robodev-ai/sdk": "^0.10.0"
|
|
11
12
|
},
|
|
12
13
|
"devDependencies": {
|
|
13
|
-
"robodev": "^0.
|
|
14
|
+
"robodev": "^0.31.0",
|
|
14
15
|
"typescript": "^5.9.2"
|
|
15
16
|
}
|
|
16
17
|
}
|
|
@@ -13,7 +13,7 @@ cursor:
|
|
|
13
13
|
|
|
14
14
|
This project is a Vite React frontend in `apps/fe` plus a Robodev backend under **`robodev/`** (`robodev/database.ts` + `robodev/api/**/*.ts` using `@robodev-ai/sdk`). There is no Expo / mobile app in this starter. End-user auth is reserved on the project host at `/api/user/*`. There is no in-browser fake backend.
|
|
15
15
|
|
|
16
|
-
`robodev deploy` from this root uploads schema and
|
|
16
|
+
`robodev deploy` from this root uploads schema, APIs, and (when `.robodev` has a `frontend` block) the local Vite build as a hosted static SPA. Use bun in `apps/fe` to iterate locally.
|
|
17
17
|
|
|
18
18
|
Shared AI rules and skills are authored in `.rulesync/`. Generated files for Codex, Claude Code, and Cursor are local outputs and should be regenerated with `bun rules:gen`.
|
|
19
19
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"description": "Marketplace starter frontend",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"dev": "
|
|
8
|
+
"dev": "vite --host",
|
|
9
9
|
"build": "bun run openapi:gen && bunx --bun vite build",
|
|
10
10
|
"clean": "rimraf dist src/openapi",
|
|
11
11
|
"lint": "oxlint --type-aware . ../../robodev/api ../../robodev/database.ts --fix",
|
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"dev": "robodev dev --with-fe",
|
|
7
8
|
"deploy": "robodev deploy",
|
|
8
9
|
"rules:gen": "rulesync generate"
|
|
9
10
|
},
|
|
10
11
|
"dependencies": {
|
|
11
|
-
"@robodev-ai/sdk": "^0.
|
|
12
|
+
"@robodev-ai/sdk": "^0.10.0"
|
|
12
13
|
},
|
|
13
14
|
"devDependencies": {
|
|
14
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.31.0",
|
|
15
16
|
"rulesync": "^8.18.0",
|
|
16
17
|
"typescript": "^5.9.2"
|
|
17
18
|
}
|
|
@@ -13,7 +13,7 @@ cursor:
|
|
|
13
13
|
|
|
14
14
|
This project is a Vite React frontend in `apps/fe` plus a Robodev backend under **`robodev/`** (`robodev/database.ts` + `robodev/api/**/*.ts` using `@robodev-ai/sdk`). There is no Expo / mobile app in this starter. End-user auth is reserved on the project host at `/api/user/*`. There is no in-browser fake backend.
|
|
15
15
|
|
|
16
|
-
`robodev deploy` from this root uploads schema and
|
|
16
|
+
`robodev deploy` from this root uploads schema, APIs, and (when `.robodev` has a `frontend` block) the local Vite build as a hosted static SPA. Use bun in `apps/fe` to iterate locally.
|
|
17
17
|
|
|
18
18
|
Shared AI rules and skills are authored in `.rulesync/`. Generated files for Codex, Claude Code, and Cursor are local outputs and should be regenerated with `bun rules:gen`.
|
|
19
19
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"description": "Space starter frontend",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"dev": "
|
|
8
|
+
"dev": "vite --host",
|
|
9
9
|
"build": "bun run openapi:gen && bunx --bun vite build",
|
|
10
10
|
"clean": "rimraf dist src/openapi",
|
|
11
11
|
"lint": "oxlint --type-aware . ../../robodev/api ../../robodev/database.ts --fix",
|
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
+
"dev": "robodev dev --with-fe",
|
|
7
8
|
"deploy": "robodev deploy",
|
|
8
9
|
"rules:gen": "rulesync generate"
|
|
9
10
|
},
|
|
10
11
|
"dependencies": {
|
|
11
|
-
"@robodev-ai/sdk": "^0.
|
|
12
|
+
"@robodev-ai/sdk": "^0.10.0"
|
|
12
13
|
},
|
|
13
14
|
"devDependencies": {
|
|
14
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.31.0",
|
|
15
16
|
"rulesync": "^8.18.0",
|
|
16
17
|
"typescript": "^5.9.2"
|
|
17
18
|
}
|