robodev 0.6.0 → 0.8.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robodev",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "CLI for Robodev Starbase — create, auth, link, and deploy hosted apps",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -15,6 +15,10 @@ test("collectFiles includes hosted sources and skips denied paths", async () =>
15
15
  await writeFile(join(root, "src/main.tsx"), "export {}");
16
16
  await mkdir(join(root, "api"), { recursive: true });
17
17
  await writeFile(join(root, "api/x.ts"), "export {}");
18
+ await mkdir(join(root, "api/invoices"), { recursive: true });
19
+ await writeFile(join(root, "api/invoices/[id].ts"), "export {}");
20
+ await mkdir(join(root, "jobs"), { recursive: true });
21
+ await writeFile(join(root, "jobs/x.ts"), "export {}");
18
22
  await writeFile(join(root, "package.json"), "{}");
19
23
  await mkdir(join(root, "node_modules"), { recursive: true });
20
24
  await writeFile(join(root, "node_modules/x.js"), "nope");
@@ -30,6 +34,8 @@ test("collectFiles includes hosted sources and skips denied paths", async () =>
30
34
  assert.ok(paths.has("src/main.tsx"));
31
35
  assert.ok(paths.has("database.ts"));
32
36
  assert.ok(paths.has("api/x.ts"));
37
+ assert.ok(paths.has("api/invoices/[id].ts"));
38
+ assert.ok(paths.has("jobs/x.ts"));
33
39
  assert.ok(paths.has("package.json"));
34
40
  assert.ok(!paths.has("node_modules/x.js"));
35
41
  assert.ok(!paths.has("dist/x.js"));
@@ -96,6 +96,7 @@ function isDenied(path: string): boolean {
96
96
  function isAllowed(path: string): boolean {
97
97
  if (path === "database.ts") return true;
98
98
  if (path.startsWith("api/")) return path.endsWith(".ts");
99
+ if (path.startsWith("jobs/")) return path.endsWith(".ts");
99
100
  if (path === "index.html") return true;
100
101
  if (path.startsWith("src/")) return SRC_EXTENSIONS.has(extname(path));
101
102
  if (path.startsWith("public/")) return PUBLIC_EXTENSIONS.has(extname(path));
@@ -2,7 +2,12 @@ import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
- export type Starter = { id: string; title: string; description: string };
5
+ export type Starter = {
6
+ id: string;
7
+ title: string;
8
+ description: string;
9
+ kind?: "app" | "backend";
10
+ };
6
11
 
7
12
  export type PackageVersions = {
8
13
  robodev: string;
package/src/index.ts CHANGED
@@ -40,7 +40,7 @@ Commands:
40
40
  projects List your projects
41
41
  create [path] [--starter <id>] Create a project, scaffold a starter, and deploy
42
42
  link [projectId] Write .robodev in the current folder
43
- deploy [--force] Deploy the project file tree (schema, APIs, and frontend)
43
+ deploy [--force] Deploy database.ts, api/*.ts, jobs, and frontend files (no Vite / npm run build)
44
44
  `);
45
45
  process.exit(1);
46
46
  }
@@ -426,7 +426,14 @@ async function runCreate(args: string[]): Promise<void> {
426
426
  await npmInstall(root);
427
427
  } catch (error) {
428
428
  console.log(error instanceof Error ? error.message : error);
429
- console.log("Run `npm install` in the project folder, then `npm run dev`.");
429
+ if (starter.kind === "backend") {
430
+ console.log(
431
+ "npm install failed. Copy the API URL printed above into Lovable, Bolt, or v0 as VITE_API_URL.",
432
+ );
433
+ console.log("https://robodev.povio.dev/docs/frontend");
434
+ } else {
435
+ console.log("Run `npm install` in the project folder, then `npm run dev`.");
436
+ }
430
437
  return;
431
438
  }
432
439
 
@@ -436,7 +443,13 @@ async function runCreate(args: string[]): Promise<void> {
436
443
  if (displayPath !== ".") {
437
444
  console.log(` cd ${displayPath}`);
438
445
  }
439
- console.log(" npm run dev");
446
+ if (starter.kind === "backend") {
447
+ console.log(" Copy the API URL printed above into Lovable, Bolt, or v0 as VITE_API_URL.");
448
+ console.log(" Do not run robodev deploy or npm run build inside those tools.");
449
+ console.log(" https://robodev.povio.dev/docs/frontend");
450
+ } else {
451
+ console.log(" npm run dev");
452
+ }
440
453
  }
441
454
 
442
455
  async function main(): Promise<void> {
@@ -11,7 +11,7 @@
11
11
  "dependencies": {
12
12
  "react": "^18.3.1",
13
13
  "react-dom": "^18.3.1",
14
- "@robodev-ai/sdk": "^0.5.0",
14
+ "@robodev-ai/sdk": "^0.6.0",
15
15
  "@robodev-ai/client": "^0.2.0"
16
16
  },
17
17
  "devDependencies": {
@@ -20,6 +20,6 @@
20
20
  "@vitejs/plugin-react": "^4.7.0",
21
21
  "typescript": "^5.9.2",
22
22
  "vite": "^6.3.5",
23
- "robodev": "^0.6.0"
23
+ "robodev": "^0.8.0"
24
24
  }
25
25
  }
@@ -0,0 +1,17 @@
1
+ # {{NAME}}
2
+
3
+ Space APIs only — no Vite UI. For Lovable, Bolt, or v0.
4
+
5
+ Deploy `database.ts` and `api/` from a real terminal:
6
+
7
+ ```bash
8
+ npx robodev deploy
9
+ ```
10
+
11
+ Build the UI elsewhere against `VITE_API_URL` (written to `.env` by `robodev create` / `robodev link`). Paste that URL into the frontend tool.
12
+
13
+ Do not run `npm run dev` or `npm run build` in this folder. Do not run `robodev` (auth, create, deploy) or `npm run build` inside Lovable, Bolt, or v0 — those tools cannot complete loopback OAuth and must not treat this folder as a Vite app.
14
+
15
+ Frontend how-to: https://robodev.povio.dev/docs/frontend
16
+
17
+ `GET /me` (`api/me.ts`) requires a Robodev Auth Bearer token. Planet and rocket routes stay public.
@@ -0,0 +1,7 @@
1
+ /** GET /health — liveness check for the deployed project host. */
2
+ import { defineApi, z } from "@robodev-ai/sdk";
3
+
4
+ export const get = defineApi({
5
+ response: z.object({ ok: z.literal(true) }),
6
+ handler: async () => ({ ok: true as const }),
7
+ });
@@ -0,0 +1,29 @@
1
+ /** POST /launch — marks a rocket as launched. */
2
+ import { rockets } from "../database";
3
+ import { defineApi, eq, z } from "@robodev-ai/sdk";
4
+
5
+ const Rocket = z.object({
6
+ id: z.string(),
7
+ name: z.string(),
8
+ destinationId: z.string(),
9
+ launched: z.boolean(),
10
+ });
11
+
12
+ export const post = defineApi({
13
+ body: z.object({
14
+ id: z.string().uuid(),
15
+ }),
16
+ response: Rocket,
17
+ handler: async ({ db, body }) => {
18
+ const rows = await db
19
+ .update(rockets)
20
+ .set({ launched: true })
21
+ .where(eq(rockets.id, body.id))
22
+ .returning();
23
+ const row = rows[0];
24
+ if (!row) {
25
+ throw new Error("Rocket not found");
26
+ }
27
+ return Rocket.parse(row);
28
+ },
29
+ });
@@ -0,0 +1,14 @@
1
+ /** GET /me — current project user (Robodev Auth required). */
2
+ import { defineApi, z } from "@robodev-ai/sdk";
3
+
4
+ export const get = defineApi({
5
+ auth: "required",
6
+ response: z.object({
7
+ user: z.object({
8
+ id: z.string(),
9
+ email: z.string(),
10
+ name: z.string().nullable().optional(),
11
+ }),
12
+ }),
13
+ handler: async ({ user }) => ({ user }),
14
+ });
@@ -0,0 +1,9 @@
1
+ /** GET /motto — plain-text envelope response. */
2
+ import { defineApi } from "@robodev-ai/sdk";
3
+
4
+ export const get = defineApi({
5
+ handler: async () => ({
6
+ contentType: "text/plain; charset=utf-8",
7
+ body: "Ad astra",
8
+ }),
9
+ });
@@ -0,0 +1,21 @@
1
+ /** GET /planets/:id — one planet by path param. */
2
+ import { defineApi, eq, z } from "@robodev-ai/sdk";
3
+ import { planets } from "../../database";
4
+
5
+ const Planet = z.object({
6
+ id: z.string(),
7
+ name: z.string(),
8
+ climate: z.string(),
9
+ moons: z.coerce.number(),
10
+ });
11
+
12
+ export const get = defineApi({
13
+ response: Planet,
14
+ handler: async ({ db, params }) => {
15
+ const [row] = await db.select().from(planets).where(eq(planets.id, params.id)).limit(1);
16
+ if (!row) {
17
+ return { status: 404, body: { error: "not_found" } };
18
+ }
19
+ return Planet.parse(row);
20
+ },
21
+ });
@@ -0,0 +1,60 @@
1
+ /** GET/POST /planets — list (seeds sample rows on first GET) and create planets. */
2
+ import { planets, rockets } from "../database";
3
+ import { defineApi, z } from "@robodev-ai/sdk";
4
+
5
+ const EARTH = "11111111-1111-1111-1111-111111111111";
6
+ const MARS = "22222222-2222-2222-2222-222222222222";
7
+ const KEPLER = "33333333-3333-3333-3333-333333333333";
8
+ const VOYAGER = "44444444-4444-4444-4444-444444444444";
9
+ const ODYSSEY = "55555555-5555-5555-5555-555555555555";
10
+
11
+ const Planet = z.object({
12
+ id: z.string(),
13
+ name: z.string(),
14
+ climate: z.string(),
15
+ moons: z.coerce.number(),
16
+ });
17
+
18
+ export const get = defineApi({
19
+ response: z.array(Planet),
20
+ handler: async ({ db }) => {
21
+ const existing = await db.select().from(planets).limit(1);
22
+ if (existing.length === 0) {
23
+ try {
24
+ await db.insert(planets).values([
25
+ { id: EARTH, name: "Earth", climate: "temperate", moons: 1 },
26
+ { id: MARS, name: "Mars", climate: "arid", moons: 2 },
27
+ { id: KEPLER, name: "Kepler-452b", climate: "unknown", moons: 0 },
28
+ ]);
29
+ await db.insert(rockets).values([
30
+ { id: VOYAGER, name: "Voyager", destinationId: MARS, launched: false },
31
+ { id: ODYSSEY, name: "Odyssey", destinationId: KEPLER, launched: true },
32
+ ]);
33
+ } catch {
34
+ // First request won the seed race.
35
+ }
36
+ }
37
+ const rows = await db.select().from(planets);
38
+ return Planet.array().parse(rows);
39
+ },
40
+ });
41
+
42
+ export const post = defineApi({
43
+ body: z.object({
44
+ name: z.string().min(1),
45
+ climate: z.string().min(1),
46
+ moons: z.coerce.number().int().min(0).default(0),
47
+ }),
48
+ response: Planet,
49
+ handler: async ({ db, body }) => {
50
+ const [row] = await db
51
+ .insert(planets)
52
+ .values({
53
+ name: body.name,
54
+ climate: body.climate,
55
+ moons: body.moons,
56
+ })
57
+ .returning();
58
+ return Planet.parse(row);
59
+ },
60
+ });
@@ -0,0 +1,43 @@
1
+ /** GET/POST /rockets — list (optional `?destinationId=`) and create rockets. */
2
+ import { rockets } from "../database";
3
+ import { defineApi, eq, z } from "@robodev-ai/sdk";
4
+
5
+ const Rocket = z.object({
6
+ id: z.string(),
7
+ name: z.string(),
8
+ destinationId: z.string(),
9
+ launched: z.boolean(),
10
+ });
11
+
12
+ export const get = defineApi({
13
+ query: z.object({
14
+ destinationId: z.string().optional(),
15
+ }),
16
+ response: z.array(Rocket),
17
+ handler: async ({ db, query }) => {
18
+ const rows = query.destinationId
19
+ ? await db.select().from(rockets).where(eq(rockets.destinationId, query.destinationId))
20
+ : await db.select().from(rockets);
21
+ return Rocket.array().parse(rows);
22
+ },
23
+ });
24
+
25
+ export const post = defineApi({
26
+ body: z.object({
27
+ name: z.string().min(1),
28
+ destinationId: z.string().uuid(),
29
+ launched: z.boolean().optional(),
30
+ }),
31
+ response: Rocket,
32
+ handler: async ({ db, body }) => {
33
+ const [row] = await db
34
+ .insert(rockets)
35
+ .values({
36
+ name: body.name,
37
+ destinationId: body.destinationId,
38
+ launched: body.launched ?? false,
39
+ })
40
+ .returning();
41
+ return Rocket.parse(row);
42
+ },
43
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Starter schema. `robodev deploy` creates the `space` database and these tables
3
+ * (no migration files). Edit here and deploy again to sync.
4
+ */
5
+ import { boolean, defineDatabase, integer, pgTable, text, timestamp, uuid } from "@robodev-ai/sdk";
6
+
7
+ export const planets = pgTable("planets", {
8
+ id: uuid("id").primaryKey().defaultRandom(),
9
+ name: text("name").notNull(),
10
+ climate: text("climate").notNull(),
11
+ moons: integer("moons").default(0),
12
+ });
13
+
14
+ export const rockets = pgTable("rockets", {
15
+ id: uuid("id").primaryKey().defaultRandom(),
16
+ name: text("name").notNull(),
17
+ destinationId: uuid("destinationId")
18
+ .notNull()
19
+ .references(() => planets.id, { onDelete: "cascade" }),
20
+ launched: boolean("launched").default(false),
21
+ createdAt: timestamp("createdAt", { withTimezone: true }).defaultNow(),
22
+ });
23
+
24
+ export default defineDatabase({
25
+ name: "space",
26
+ tables: { planets, rockets },
27
+ });
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "{{PACKAGE_NAME}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "deploy": "robodev deploy"
8
+ },
9
+ "dependencies": {
10
+ "@robodev-ai/sdk": "^0.6.0"
11
+ },
12
+ "devDependencies": {
13
+ "robodev": "^0.8.0",
14
+ "typescript": "^5.9.2"
15
+ }
16
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "skipLibCheck": true,
8
+ "noEmit": true
9
+ },
10
+ "include": ["database.ts", "api/**/*.ts"]
11
+ }
@@ -9,6 +9,12 @@
9
9
  "id": "auth-chat",
10
10
  "title": "Auth chat",
11
11
  "description": "Email + Google sign-in and a shared chat room"
12
+ },
13
+ {
14
+ "id": "backend",
15
+ "title": "Backend",
16
+ "description": "Space APIs only — no Vite UI. For Lovable, Bolt, or v0",
17
+ "kind": "backend"
12
18
  }
13
19
  ]
14
20
  }
@@ -0,0 +1,9 @@
1
+ /** GET /motto — plain-text envelope response. */
2
+ import { defineApi } from "@robodev-ai/sdk";
3
+
4
+ export const get = defineApi({
5
+ handler: async () => ({
6
+ contentType: "text/plain; charset=utf-8",
7
+ body: "Ad astra",
8
+ }),
9
+ });
@@ -0,0 +1,21 @@
1
+ /** GET /planets/:id — one planet by path param. */
2
+ import { defineApi, eq, z } from "@robodev-ai/sdk";
3
+ import { planets } from "../../database";
4
+
5
+ const Planet = z.object({
6
+ id: z.string(),
7
+ name: z.string(),
8
+ climate: z.string(),
9
+ moons: z.coerce.number(),
10
+ });
11
+
12
+ export const get = defineApi({
13
+ response: Planet,
14
+ handler: async ({ db, params }) => {
15
+ const [row] = await db.select().from(planets).where(eq(planets.id, params.id)).limit(1);
16
+ if (!row) {
17
+ return { status: 404, body: { error: "not_found" } };
18
+ }
19
+ return Planet.parse(row);
20
+ },
21
+ });
@@ -11,7 +11,7 @@
11
11
  "dependencies": {
12
12
  "react": "^18.3.1",
13
13
  "react-dom": "^18.3.1",
14
- "@robodev-ai/sdk": "^0.5.0",
14
+ "@robodev-ai/sdk": "^0.6.0",
15
15
  "@robodev-ai/client": "^0.2.0"
16
16
  },
17
17
  "devDependencies": {
@@ -20,6 +20,6 @@
20
20
  "@vitejs/plugin-react": "^4.7.0",
21
21
  "typescript": "^5.9.2",
22
22
  "vite": "^6.3.5",
23
- "robodev": "^0.6.0"
23
+ "robodev": "^0.8.0"
24
24
  }
25
25
  }