robodev 0.37.0 → 0.38.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 +3 -3
- package/src/dev-clients.test.ts +38 -0
- package/src/dev-clients.ts +47 -7
- package/src/dev-inspect.test.ts +19 -0
- package/src/dev-inspect.ts +13 -3
- package/src/dev-server.ts +2 -0
- package/src/dev.ts +2 -0
- package/templates/auth-chat/package.json +2 -2
- package/templates/backend/package.json +2 -2
- package/templates/empty/package.json +2 -2
- package/templates/marketplace/package.json +2 -2
- package/templates/space/package.json +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "robodev",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "CLI for Robodev Starbase — create, auth, link, deploy, and run a project locally with robodev dev",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"embedded-postgres": "17.10.0-beta.17",
|
|
27
27
|
"fastify": "^5.12.1",
|
|
28
28
|
"tsx": "^4.20.5",
|
|
29
|
-
"@robodev-ai/runtime": "0.
|
|
30
|
-
"@robodev-ai/sdk": "0.
|
|
29
|
+
"@robodev-ai/runtime": "0.5.0",
|
|
30
|
+
"@robodev-ai/sdk": "0.12.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^24.3.0",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { consoleEmailClient } from "./dev-clients.js";
|
|
7
|
+
import { createDevInspect } from "./dev-inspect.js";
|
|
8
|
+
|
|
9
|
+
test("consoleEmailClient renders a local robodev/emails catalog file", async () => {
|
|
10
|
+
const root = await mkdtemp(join(tmpdir(), "robodev-dev-email-"));
|
|
11
|
+
await mkdir(join(root, "robodev", "emails"), { recursive: true });
|
|
12
|
+
await writeFile(join(root, "robodev", "emails", "welcome.html"), "<p>Hi {{name}}</p>", "utf8");
|
|
13
|
+
await writeFile(
|
|
14
|
+
join(root, "robodev", "emails", "welcome.json"),
|
|
15
|
+
JSON.stringify({ subject: "Hello {{name}}", text: "Hi {{name}}" }),
|
|
16
|
+
"utf8",
|
|
17
|
+
);
|
|
18
|
+
const lines: string[] = [];
|
|
19
|
+
const inspect = createDevInspect({ max: 20 });
|
|
20
|
+
const email = consoleEmailClient((line) => lines.push(line), inspect, root);
|
|
21
|
+
const result = await email.send({
|
|
22
|
+
to: "ada@example.com",
|
|
23
|
+
template: "welcome",
|
|
24
|
+
vars: { name: "Ada" },
|
|
25
|
+
});
|
|
26
|
+
assert.equal(result.status, "sent");
|
|
27
|
+
assert.match(lines[0] ?? "", /Hello Ada/);
|
|
28
|
+
assert.match(lines[1] ?? "", /Hi Ada/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("consoleEmailClient throws template_not_found without a local file or body", async () => {
|
|
32
|
+
const inspect = createDevInspect({ max: 20 });
|
|
33
|
+
const email = consoleEmailClient(() => undefined, inspect, await mkdtemp(join(tmpdir(), "robodev-dev-email-")));
|
|
34
|
+
await assert.rejects(
|
|
35
|
+
() => email.send({ to: "ada@example.com", template: "missing" }),
|
|
36
|
+
/template_not_found/,
|
|
37
|
+
);
|
|
38
|
+
});
|
package/src/dev-clients.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
+
createHopPdfClient,
|
|
2
3
|
createLocalStorageClient,
|
|
3
4
|
createProjectSocketsClient,
|
|
5
|
+
loadLocalEmailTemplate,
|
|
6
|
+
resolveEmailSendInput,
|
|
4
7
|
type StorageQueryable,
|
|
5
8
|
} from "@robodev-ai/runtime";
|
|
6
9
|
import type { RouteClients } from "@robodev-ai/runtime";
|
|
@@ -8,6 +11,7 @@ import type {
|
|
|
8
11
|
EmailClient,
|
|
9
12
|
JobEnqueueInput,
|
|
10
13
|
LlmClient,
|
|
14
|
+
PdfClient,
|
|
11
15
|
SocketsClient,
|
|
12
16
|
StorageClient,
|
|
13
17
|
} from "@robodev-ai/sdk";
|
|
@@ -19,8 +23,8 @@ import {
|
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
25
|
* The `ctx.*` clients `robodev dev` can offer offline. `email` prints to the terminal;
|
|
22
|
-
* `jobs`, `storage`, and `sockets` run locally. llm / agent / push
|
|
23
|
-
* not-configured codes rather than silently doing nothing.
|
|
26
|
+
* `jobs`, `storage`, and `sockets` run locally. `pdf` hops to Starbase. llm / agent / push
|
|
27
|
+
* throw hosted-style not-configured codes rather than silently doing nothing.
|
|
24
28
|
*/
|
|
25
29
|
|
|
26
30
|
function notConfigured(code: string): never {
|
|
@@ -57,6 +61,29 @@ function inspectStorageClient(client: StorageClient, inspect: DevInspectStore):
|
|
|
57
61
|
};
|
|
58
62
|
}
|
|
59
63
|
|
|
64
|
+
function inspectPdfClient(client: PdfClient, inspect: DevInspectStore): PdfClient {
|
|
65
|
+
return {
|
|
66
|
+
async fromHtml(html, options) {
|
|
67
|
+
const htmlBytes = Buffer.byteLength(html, "utf8");
|
|
68
|
+
const format = options?.format ?? "A4";
|
|
69
|
+
try {
|
|
70
|
+
const result = await client.fromHtml(html, options);
|
|
71
|
+
inspect.record({ kind: "pdf", htmlBytes, format, ok: true });
|
|
72
|
+
return result;
|
|
73
|
+
} catch (error) {
|
|
74
|
+
inspect.record({
|
|
75
|
+
kind: "pdf",
|
|
76
|
+
htmlBytes,
|
|
77
|
+
format,
|
|
78
|
+
ok: false,
|
|
79
|
+
error: errorMessage(error),
|
|
80
|
+
});
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
60
87
|
function inspectSocketsClient(client: SocketsClient, inspect: DevInspectStore): SocketsClient {
|
|
61
88
|
function tracked(
|
|
62
89
|
socketKind: "send" | "broadcast",
|
|
@@ -97,13 +124,24 @@ function inspectSocketsClient(client: SocketsClient, inspect: DevInspectStore):
|
|
|
97
124
|
export function consoleEmailClient(
|
|
98
125
|
log: (line: string) => void,
|
|
99
126
|
inspect: DevInspectStore,
|
|
127
|
+
projectRoot?: string,
|
|
100
128
|
): EmailClient {
|
|
101
129
|
return {
|
|
102
130
|
async send(input) {
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
131
|
+
const catalog =
|
|
132
|
+
input.template && projectRoot
|
|
133
|
+
? await loadLocalEmailTemplate(projectRoot, input.template)
|
|
134
|
+
: null;
|
|
135
|
+
const resolved = resolveEmailSendInput(input, catalog);
|
|
136
|
+
const to = Array.isArray(resolved.to) ? resolved.to.join(", ") : resolved.to;
|
|
137
|
+
log(`email → ${to}: ${resolved.subject ?? ""}`);
|
|
138
|
+
if (resolved.text) log(` ${resolved.text}`);
|
|
139
|
+
inspect.record({
|
|
140
|
+
kind: "email",
|
|
141
|
+
to,
|
|
142
|
+
subject: resolved.subject ?? "",
|
|
143
|
+
text: resolved.text,
|
|
144
|
+
});
|
|
107
145
|
return { id: `local_${Date.now().toString(36)}`, status: "sent" };
|
|
108
146
|
},
|
|
109
147
|
};
|
|
@@ -126,9 +164,10 @@ export function devRouteClients(input: {
|
|
|
126
164
|
publicBaseUrl: string;
|
|
127
165
|
projectId: string;
|
|
128
166
|
jwtSecret: string;
|
|
167
|
+
projectRoot?: string;
|
|
129
168
|
}): RouteClients {
|
|
130
169
|
return {
|
|
131
|
-
email: consoleEmailClient(input.log, input.inspect),
|
|
170
|
+
email: consoleEmailClient(input.log, input.inspect, input.projectRoot),
|
|
132
171
|
llm: offlineLlmClient(),
|
|
133
172
|
agent: {
|
|
134
173
|
createSession: () => notConfigured("llm_not_configured"),
|
|
@@ -147,6 +186,7 @@ export function devRouteClients(input: {
|
|
|
147
186
|
}),
|
|
148
187
|
input.inspect,
|
|
149
188
|
),
|
|
189
|
+
pdf: inspectPdfClient(createHopPdfClient(), input.inspect),
|
|
150
190
|
push: { send: () => notConfigured("push_not_configured") },
|
|
151
191
|
jobs: {
|
|
152
192
|
enqueue: async (job) => {
|
package/src/dev-inspect.test.ts
CHANGED
|
@@ -383,3 +383,22 @@ test("storage object count is 0 when the query fails", async () => {
|
|
|
383
383
|
assert.equal(state.overview.storageObjectCount, 0);
|
|
384
384
|
assert.deepEqual(state.storage.objects, []);
|
|
385
385
|
});
|
|
386
|
+
|
|
387
|
+
test("pdf inspect records byte length and format, not HTML text", () => {
|
|
388
|
+
const inspect = createDevInspect();
|
|
389
|
+
const html = "<html><body>secret invoice</body></html>";
|
|
390
|
+
const event = inspect.record({
|
|
391
|
+
kind: "pdf",
|
|
392
|
+
htmlBytes: Buffer.byteLength(html, "utf8"),
|
|
393
|
+
format: "Letter",
|
|
394
|
+
ok: false,
|
|
395
|
+
error: "pdf_failed",
|
|
396
|
+
});
|
|
397
|
+
assert.equal(event.kind, "pdf");
|
|
398
|
+
if (event.kind !== "pdf") throw new Error("expected pdf");
|
|
399
|
+
assert.equal(event.htmlBytes, Buffer.byteLength(html, "utf8"));
|
|
400
|
+
assert.equal(event.format, "Letter");
|
|
401
|
+
assert.equal(event.ok, false);
|
|
402
|
+
assert.equal(event.error, "pdf_failed");
|
|
403
|
+
assert.equal(JSON.stringify(event).includes("secret invoice"), false);
|
|
404
|
+
});
|
package/src/dev-inspect.ts
CHANGED
|
@@ -55,7 +55,7 @@ export const RESERVED_AUTH_ROUTES: Array<{ method: string; path: string; file: s
|
|
|
55
55
|
export const SOCKET_PREVIEW_MAX = 200;
|
|
56
56
|
|
|
57
57
|
export type DevInspectKind =
|
|
58
|
-
"request" | "email" | "auth_code" | "job" | "job_run" | "log" | "storage" | "socket";
|
|
58
|
+
"request" | "email" | "auth_code" | "job" | "job_run" | "log" | "storage" | "socket" | "pdf";
|
|
59
59
|
|
|
60
60
|
export type StorageInspectOp = "upload" | "get" | "getUrl" | "delete" | "list" | "serve";
|
|
61
61
|
export type SocketInspectKind = "connect" | "message" | "close" | "send" | "broadcast";
|
|
@@ -127,6 +127,14 @@ export type DevInspectSocketEvent = EventBase & {
|
|
|
127
127
|
preview?: string;
|
|
128
128
|
};
|
|
129
129
|
|
|
130
|
+
export type DevInspectPdfEvent = EventBase & {
|
|
131
|
+
kind: "pdf";
|
|
132
|
+
htmlBytes: number;
|
|
133
|
+
format: string;
|
|
134
|
+
ok: boolean;
|
|
135
|
+
error?: string;
|
|
136
|
+
};
|
|
137
|
+
|
|
130
138
|
export type DevInspectEvent =
|
|
131
139
|
| DevInspectRequestEvent
|
|
132
140
|
| DevInspectEmailEvent
|
|
@@ -135,7 +143,8 @@ export type DevInspectEvent =
|
|
|
135
143
|
| DevInspectJobRunEvent
|
|
136
144
|
| DevInspectLogEvent
|
|
137
145
|
| DevInspectStorageEvent
|
|
138
|
-
| DevInspectSocketEvent
|
|
146
|
+
| DevInspectSocketEvent
|
|
147
|
+
| DevInspectPdfEvent;
|
|
139
148
|
|
|
140
149
|
export type DevInspectRecordInput =
|
|
141
150
|
| Omit<DevInspectRequestEvent, "id" | "at">
|
|
@@ -145,7 +154,8 @@ export type DevInspectRecordInput =
|
|
|
145
154
|
| Omit<DevInspectJobRunEvent, "id" | "at">
|
|
146
155
|
| Omit<DevInspectLogEvent, "id" | "at">
|
|
147
156
|
| Omit<DevInspectStorageEvent, "id" | "at">
|
|
148
|
-
| Omit<DevInspectSocketEvent, "id" | "at"
|
|
157
|
+
| Omit<DevInspectSocketEvent, "id" | "at">
|
|
158
|
+
| Omit<DevInspectPdfEvent, "id" | "at">;
|
|
149
159
|
|
|
150
160
|
export type DashboardGoogle = "broker" | "local_override" | "incomplete";
|
|
151
161
|
|
package/src/dev-server.ts
CHANGED
|
@@ -63,6 +63,7 @@ export type DevServerOptions = {
|
|
|
63
63
|
googleClientId?: string;
|
|
64
64
|
googleClientSecret?: string;
|
|
65
65
|
storageDir: string;
|
|
66
|
+
projectRoot?: string;
|
|
66
67
|
};
|
|
67
68
|
|
|
68
69
|
const SWAGGER_PATHS = new Set(["/_robodev/docs", "/_robodev/swagger", "/docs", "/swagger"]);
|
|
@@ -129,6 +130,7 @@ function routeClients(options: DevServerOptions, getRuntime: () => DevRuntime |
|
|
|
129
130
|
publicBaseUrl: options.listenOrigin,
|
|
130
131
|
projectId: options.projectId,
|
|
131
132
|
jwtSecret: options.jwtSecret,
|
|
133
|
+
projectRoot: options.projectRoot,
|
|
132
134
|
});
|
|
133
135
|
}
|
|
134
136
|
|
package/src/dev.ts
CHANGED
|
@@ -191,6 +191,7 @@ export async function main(args: string[]): Promise<void> {
|
|
|
191
191
|
publicBaseUrl: `http://localhost:${parsed.port}`,
|
|
192
192
|
projectId: DEV_PROJECT_ID,
|
|
193
193
|
jwtSecret: DEV_JWT_SECRET,
|
|
194
|
+
projectRoot: project.root,
|
|
194
195
|
}),
|
|
195
196
|
onJobFinished: (event) => {
|
|
196
197
|
inspect.record({
|
|
@@ -310,6 +311,7 @@ export async function main(args: string[]): Promise<void> {
|
|
|
310
311
|
googleClientId: env.GOOGLE_CLIENT_ID,
|
|
311
312
|
googleClientSecret: env.GOOGLE_CLIENT_SECRET,
|
|
312
313
|
storageDir,
|
|
314
|
+
projectRoot: project.root,
|
|
313
315
|
},
|
|
314
316
|
() => current,
|
|
315
317
|
);
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"rules:gen": "rulesync generate"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@robodev-ai/sdk": "^0.
|
|
12
|
+
"@robodev-ai/sdk": "^0.12.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.38.0",
|
|
16
16
|
"rulesync": "^8.18.0",
|
|
17
17
|
"typescript": "^5.9.2"
|
|
18
18
|
}
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"rules:gen": "rulesync generate"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@robodev-ai/sdk": "^0.
|
|
12
|
+
"@robodev-ai/sdk": "^0.12.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.38.0",
|
|
16
16
|
"rulesync": "^8.18.0",
|
|
17
17
|
"typescript": "^5.9.2"
|
|
18
18
|
}
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"rules:gen": "rulesync generate"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@robodev-ai/sdk": "^0.
|
|
12
|
+
"@robodev-ai/sdk": "^0.12.0"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"robodev": "^0.
|
|
15
|
+
"robodev": "^0.38.0",
|
|
16
16
|
"rulesync": "^8.18.0",
|
|
17
17
|
"typescript": "^5.9.2"
|
|
18
18
|
}
|