@poncho-ai/cli 0.38.1 → 0.40.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.
@@ -25,7 +25,8 @@ export const normalizeDeployTarget = (target: string): DeployScaffoldTarget => {
25
25
  normalized === "vercel" ||
26
26
  normalized === "docker" ||
27
27
  normalized === "lambda" ||
28
- normalized === "fly"
28
+ normalized === "fly" ||
29
+ normalized === "railway"
29
30
  ) {
30
31
  return normalized;
31
32
  }
@@ -223,6 +224,25 @@ const port = Number.parseInt(process.env.PORT ?? "3000", 10);
223
224
  await startDevServer(Number.isNaN(port) ? 3000 : port, { workingDir: process.cwd() });
224
225
  `;
225
226
 
227
+ let browserEnabled = false;
228
+ try {
229
+ const cfg = await loadPonchoConfig(projectDir);
230
+ browserEnabled = !!cfg?.browser;
231
+ } catch { /* best-effort */ }
232
+
233
+ // Chromium runtime libs for Playwright when `browser: true`. Built as a
234
+ // single apt-get layer so it can be inserted verbatim into Dockerfiles
235
+ // for docker/railway/fly targets and stripped out otherwise.
236
+ const chromiumLibsLayer = browserEnabled
237
+ ? `RUN apt-get update && apt-get install -y --no-install-recommends \\
238
+ ca-certificates fonts-liberation libnss3 libatk1.0-0 libatk-bridge2.0-0 \\
239
+ libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 \\
240
+ libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 \\
241
+ && rm -rf /var/lib/apt/lists/*
242
+
243
+ `
244
+ : "";
245
+
226
246
  if (target === "vercel") {
227
247
  // Build @vercel/nft trace hints for packages that are dynamically loaded
228
248
  // at runtime. Bare `import("pkg")` with a string literal is enough for
@@ -231,12 +251,6 @@ await startDevServer(Number.isNaN(port) ? 3000 : port, { workingDir: process.cwd
231
251
  // an optional package isn't installed.
232
252
  const traceHints: string[] = [];
233
253
 
234
- let browserEnabled = false;
235
- try {
236
- const cfg = await loadPonchoConfig(projectDir);
237
- browserEnabled = !!cfg?.browser;
238
- } catch { /* best-effort */ }
239
-
240
254
  if (browserEnabled) {
241
255
  traceHints.push(`import("@poncho-ai/browser").catch(() => {});`);
242
256
 
@@ -346,16 +360,19 @@ export default async function handler(req, res) {
346
360
  const dockerfilePath = resolve(projectDir, "Dockerfile");
347
361
  await writeScaffoldFile(
348
362
  dockerfilePath,
349
- `FROM node:20-slim
363
+ `FROM node:22-slim
350
364
  WORKDIR /app
351
- COPY package.json package.json
365
+
366
+ ${chromiumLibsLayer}COPY package.json package.json
367
+ RUN npm install --omit=dev
368
+
352
369
  COPY AGENT.md AGENT.md
353
370
  COPY poncho.config.js poncho.config.js
354
371
  COPY skills skills
355
372
  COPY tests tests
356
373
  COPY .env.example .env.example
357
- RUN corepack enable && npm install -g @poncho-ai/cli@^${cliVersion}
358
374
  COPY server.js server.js
375
+
359
376
  EXPOSE 3000
360
377
  CMD ["node","server.js"]
361
378
  `,
@@ -389,6 +406,48 @@ export const handler = async (event = {}) => {
389
406
  //
390
407
  // Reminders: Create a CloudWatch Events rule that triggers GET /api/reminders/check
391
408
  // every 10 minutes (or your preferred interval) with Authorization: Bearer <PONCHO_AUTH_TOKEN>.
409
+ `,
410
+ { force: options?.force, writtenPaths, baseDir: projectDir },
411
+ );
412
+ } else if (target === "railway") {
413
+ await writeScaffoldFile(
414
+ resolve(projectDir, "Dockerfile"),
415
+ `FROM node:22-slim
416
+ WORKDIR /app
417
+
418
+ ${chromiumLibsLayer}COPY package.json package.json
419
+ RUN npm install --omit=dev
420
+
421
+ COPY AGENT.md AGENT.md
422
+ COPY poncho.config.js poncho.config.js
423
+ COPY skills skills
424
+ COPY tests tests
425
+ COPY .env.example .env.example
426
+ COPY server.js server.js
427
+
428
+ EXPOSE 3000
429
+ CMD ["node","server.js"]
430
+ `,
431
+ { force: options?.force, writtenPaths, baseDir: projectDir },
432
+ );
433
+ await writeScaffoldFile(resolve(projectDir, "server.js"), sharedServerEntrypoint, {
434
+ force: options?.force,
435
+ writtenPaths,
436
+ baseDir: projectDir,
437
+ });
438
+ // Pin Railway to the Dockerfile builder so it doesn't try Nixpacks (which
439
+ // misreads pnpm-workspace.yaml or missing lockfiles and fails the build
440
+ // before producing useful logs).
441
+ await writeScaffoldFile(
442
+ resolve(projectDir, "railway.toml"),
443
+ `[build]
444
+ builder = "dockerfile"
445
+ dockerfilePath = "Dockerfile"
446
+
447
+ [deploy]
448
+ startCommand = "node server.js"
449
+ restartPolicyType = "on_failure"
450
+ restartPolicyMaxRetries = 3
392
451
  `,
393
452
  { force: options?.force, writtenPaths, baseDir: projectDir },
394
453
  );
@@ -409,15 +468,18 @@ export const handler = async (event = {}) => {
409
468
  );
410
469
  await writeScaffoldFile(
411
470
  resolve(projectDir, "Dockerfile"),
412
- `FROM node:20-slim
471
+ `FROM node:22-slim
413
472
  WORKDIR /app
414
- COPY package.json package.json
473
+
474
+ ${chromiumLibsLayer}COPY package.json package.json
475
+ RUN npm install --omit=dev
476
+
415
477
  COPY AGENT.md AGENT.md
416
478
  COPY poncho.config.js poncho.config.js
417
479
  COPY skills skills
418
480
  COPY tests tests
419
- RUN npm install -g @poncho-ai/cli@^${cliVersion}
420
481
  COPY server.js server.js
482
+
421
483
  EXPOSE 3000
422
484
  CMD ["node","server.js"]
423
485
  `,
package/src/templates.ts CHANGED
@@ -369,7 +369,7 @@ cron:
369
369
 
370
370
  - \`poncho dev\`: jobs run via an in-process scheduler.
371
371
  - \`poncho build vercel\`: generates \`vercel.json\` cron entries. Set \`CRON_SECRET\` to the same value as \`PONCHO_AUTH_TOKEN\` so Vercel can authenticate.
372
- - Docker/Fly.io: scheduler runs automatically.
372
+ - Docker/Fly.io/Railway: scheduler runs automatically.
373
373
  - Lambda: use AWS EventBridge to trigger \`GET /api/cron/<jobName>\` with \`Authorization: Bearer <token>\`.
374
374
  - Trigger manually: \`curl http://localhost:3000/api/cron/daily-report\`
375
375
 
@@ -493,6 +493,10 @@ poncho build lambda
493
493
  # Fly.io
494
494
  poncho build fly
495
495
  fly deploy
496
+
497
+ # Railway
498
+ poncho build railway
499
+ railway up
496
500
  \`\`\`
497
501
 
498
502
  Set environment variables on your deployment platform:
package/src/vfs-zip.ts ADDED
@@ -0,0 +1,94 @@
1
+ // Minimal "store" (no-compression) zip writer. Used by the VFS folder-download
2
+ // endpoint. Avoids pulling in a zip dependency.
3
+
4
+ const CRC_TABLE: Uint32Array = (() => {
5
+ const table = new Uint32Array(256);
6
+ for (let i = 0; i < 256; i++) {
7
+ let c = i;
8
+ for (let k = 0; k < 8; k++) c = (c & 1) !== 0 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
9
+ table[i] = c >>> 0;
10
+ }
11
+ return table;
12
+ })();
13
+
14
+ const crc32 = (data: Uint8Array): number => {
15
+ let crc = 0xffffffff;
16
+ for (let i = 0; i < data.length; i++) crc = (CRC_TABLE[(crc ^ data[i]!) & 0xff]! ^ (crc >>> 8)) >>> 0;
17
+ return (crc ^ 0xffffffff) >>> 0;
18
+ };
19
+
20
+ const dosDateTime = (date: Date): { time: number; day: number } => {
21
+ const time = ((date.getHours() & 0x1f) << 11) | ((date.getMinutes() & 0x3f) << 5) | (Math.floor(date.getSeconds() / 2) & 0x1f);
22
+ const year = Math.max(1980, date.getFullYear());
23
+ const day = (((year - 1980) & 0x7f) << 9) | (((date.getMonth() + 1) & 0x0f) << 5) | (date.getDate() & 0x1f);
24
+ return { time, day };
25
+ };
26
+
27
+ export interface ZipEntry {
28
+ /** POSIX-style relative path (forward slashes). */
29
+ name: string;
30
+ content: Uint8Array;
31
+ mtime?: Date;
32
+ }
33
+
34
+ export const buildZip = (entries: ZipEntry[]): Buffer => {
35
+ const local: Buffer[] = [];
36
+ const central: Buffer[] = [];
37
+ let offset = 0;
38
+
39
+ for (const entry of entries) {
40
+ const nameBuf = Buffer.from(entry.name, "utf8");
41
+ const data = Buffer.from(entry.content);
42
+ const crc = crc32(entry.content);
43
+ const { time, day } = dosDateTime(entry.mtime ?? new Date());
44
+
45
+ const lfh = Buffer.alloc(30);
46
+ lfh.writeUInt32LE(0x04034b50, 0);
47
+ lfh.writeUInt16LE(20, 4); // version needed
48
+ lfh.writeUInt16LE(0x0800, 6); // general purpose flags (bit 11 = UTF-8 name)
49
+ lfh.writeUInt16LE(0, 8); // compression method: store
50
+ lfh.writeUInt16LE(time, 10);
51
+ lfh.writeUInt16LE(day, 12);
52
+ lfh.writeUInt32LE(crc, 14);
53
+ lfh.writeUInt32LE(data.length, 18);
54
+ lfh.writeUInt32LE(data.length, 22);
55
+ lfh.writeUInt16LE(nameBuf.length, 26);
56
+ lfh.writeUInt16LE(0, 28);
57
+ local.push(lfh, nameBuf, data);
58
+
59
+ const cdh = Buffer.alloc(46);
60
+ cdh.writeUInt32LE(0x02014b50, 0);
61
+ cdh.writeUInt16LE(20, 4); // version made by
62
+ cdh.writeUInt16LE(20, 6); // version needed
63
+ cdh.writeUInt16LE(0x0800, 8); // flags
64
+ cdh.writeUInt16LE(0, 10); // compression
65
+ cdh.writeUInt16LE(time, 12);
66
+ cdh.writeUInt16LE(day, 14);
67
+ cdh.writeUInt32LE(crc, 16);
68
+ cdh.writeUInt32LE(data.length, 20);
69
+ cdh.writeUInt32LE(data.length, 24);
70
+ cdh.writeUInt16LE(nameBuf.length, 28);
71
+ cdh.writeUInt16LE(0, 30);
72
+ cdh.writeUInt16LE(0, 32);
73
+ cdh.writeUInt16LE(0, 34); // disk number
74
+ cdh.writeUInt16LE(0, 36); // internal attrs
75
+ cdh.writeUInt32LE(0, 38); // external attrs
76
+ cdh.writeUInt32LE(offset, 42);
77
+ central.push(cdh, nameBuf);
78
+
79
+ offset += lfh.length + nameBuf.length + data.length;
80
+ }
81
+
82
+ const centralBuf = Buffer.concat(central);
83
+ const eocd = Buffer.alloc(22);
84
+ eocd.writeUInt32LE(0x06054b50, 0);
85
+ eocd.writeUInt16LE(0, 4); // disk number
86
+ eocd.writeUInt16LE(0, 6); // disk where central dir starts
87
+ eocd.writeUInt16LE(entries.length, 8); // entries on this disk
88
+ eocd.writeUInt16LE(entries.length, 10); // total entries
89
+ eocd.writeUInt32LE(centralBuf.length, 12);
90
+ eocd.writeUInt32LE(offset, 16); // central dir offset
91
+ eocd.writeUInt16LE(0, 20); // comment length
92
+
93
+ return Buffer.concat([...local, centralBuf, eocd]);
94
+ };