robodev 0.7.0 → 0.9.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.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "CLI for Robodev Starbase — create, auth, link, and deploy hosted apps",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -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
@@ -38,9 +38,9 @@ Commands:
38
38
  logout Remove stored credentials
39
39
  whoami Show the signed-in user
40
40
  projects List your projects
41
- create [path] [--starter <id>] Create a project, scaffold a starter, and deploy
41
+ create [path] [--starter <id>] [--empty] Create a project, scaffold a starter, and deploy
42
42
  link [projectId] Write .robodev in the current folder
43
- deploy [--force] Deploy schema, APIs, jobs, and frontend (includes jobs/**/*.ts and api/**/[id].ts)
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
  }
@@ -282,8 +282,13 @@ function validIdsMessage(starters: Starter[]): string {
282
282
  function parseCreateArgs(args: string[]): { path?: string; starterId?: string } {
283
283
  let path: string | undefined;
284
284
  let starterId: string | undefined;
285
+ let empty = false;
285
286
  for (let i = 0; i < args.length; i++) {
286
287
  const arg = args[i];
288
+ if (arg === "--empty") {
289
+ empty = true;
290
+ continue;
291
+ }
287
292
  if (arg === "--starter") {
288
293
  const value = args[i + 1];
289
294
  if (!value || value.startsWith("-")) {
@@ -309,6 +314,12 @@ function parseCreateArgs(args: string[]): { path?: string; starterId?: string }
309
314
  }
310
315
  path = arg;
311
316
  }
317
+ if (empty && starterId !== undefined) {
318
+ throw new Error("Use either --empty or --starter, not both.");
319
+ }
320
+ if (empty) {
321
+ starterId = "empty";
322
+ }
312
323
  return { path, starterId };
313
324
  }
314
325
 
@@ -426,7 +437,14 @@ async function runCreate(args: string[]): Promise<void> {
426
437
  await npmInstall(root);
427
438
  } catch (error) {
428
439
  console.log(error instanceof Error ? error.message : error);
429
- console.log("Run `npm install` in the project folder, then `npm run dev`.");
440
+ if (starter.kind === "backend") {
441
+ console.log(
442
+ "npm install failed. Copy the API URL printed above into Lovable, Bolt, or v0 as VITE_API_URL.",
443
+ );
444
+ console.log("https://robodev.povio.dev/docs/frontend");
445
+ } else {
446
+ console.log("Run `npm install` in the project folder, then `npm run dev`.");
447
+ }
430
448
  return;
431
449
  }
432
450
 
@@ -436,7 +454,13 @@ async function runCreate(args: string[]): Promise<void> {
436
454
  if (displayPath !== ".") {
437
455
  console.log(` cd ${displayPath}`);
438
456
  }
439
- console.log(" npm run dev");
457
+ if (starter.kind === "backend") {
458
+ console.log(" Copy the API URL printed above into Lovable, Bolt, or v0 as VITE_API_URL.");
459
+ console.log(" Do not run robodev deploy or npm run build inside those tools.");
460
+ console.log(" https://robodev.povio.dev/docs/frontend");
461
+ } else {
462
+ console.log(" npm run dev");
463
+ }
440
464
  }
441
465
 
442
466
  async function main(): Promise<void> {
@@ -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.7.0"
23
+ "robodev": "^0.9.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.9.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,18 @@
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"
18
+ },
19
+ {
20
+ "id": "empty",
21
+ "title": "Empty",
22
+ "description": "Blank database.ts and GET /health — no sample schema or UI. For Lovable, Bolt, or v0",
23
+ "kind": "backend"
12
24
  }
13
25
  ]
14
26
  }
@@ -0,0 +1,15 @@
1
+ # {{NAME}}
2
+
3
+ Blank `database.ts` and `GET /health` — no sample schema or 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
@@ -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,14 @@
1
+ /**
2
+ * Placeholder schema. Replace this table or add tables, then `robodev deploy`.
3
+ */
4
+ import { defineDatabase, pgTable, text, uuid } from "@robodev-ai/sdk";
5
+
6
+ export const items = pgTable("items", {
7
+ id: uuid("id").primaryKey().defaultRandom(),
8
+ name: text("name").notNull(),
9
+ });
10
+
11
+ export default defineDatabase({
12
+ name: "app",
13
+ tables: { items },
14
+ });
@@ -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.9.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
+ }
@@ -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.7.0"
23
+ "robodev": "^0.9.0"
24
24
  }
25
25
  }