@meetploy/cli 1.15.0 → 1.17.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.
@@ -4,8 +4,8 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Ploy Dev Dashboard</title>
7
- <script type="module" crossorigin src="/assets/main-UY1Z1kG0.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/main-qA3kxECS.css">
7
+ <script type="module" crossorigin src="/assets/main-duAiLjPq.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/main-B-euJxpr.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/dist/dev.js CHANGED
@@ -1,20 +1,32 @@
1
1
  import { createRequire } from 'module';
2
2
  import 'child_process';
3
- import { readFile, existsSync, readFileSync, mkdirSync } from 'fs';
3
+ import { readFile, existsSync, readFileSync, mkdirSync, unlinkSync, writeFileSync } from 'fs';
4
4
  import { dirname, join } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import 'esbuild';
7
7
  import 'chokidar';
8
8
  import { promisify } from 'util';
9
9
  import { parse } from 'yaml';
10
+ import { randomUUID, createHmac, pbkdf2Sync, timingSafeEqual, randomBytes } from 'crypto';
10
11
  import { serve } from '@hono/node-server';
11
12
  import { Hono } from 'hono';
12
- import { randomUUID, createHmac, pbkdf2Sync, timingSafeEqual, randomBytes } from 'crypto';
13
- import { getCookie, deleteCookie, setCookie } from 'hono/cookie';
14
13
  import 'os';
14
+ import { getCookie, deleteCookie, setCookie } from 'hono/cookie';
15
15
  import Database from 'better-sqlite3';
16
16
 
17
17
  createRequire(import.meta.url);
18
+
19
+ // ../emulator/dist/utils/logger.js
20
+ var COLORS = {
21
+ reset: "\x1B[0m",
22
+ dim: "\x1B[2m",
23
+ red: "\x1B[31m"};
24
+ function timestamp() {
25
+ return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
26
+ }
27
+ function error(message) {
28
+ console.error(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.red}[ploy]${COLORS.reset} ${message}`);
29
+ }
18
30
  promisify(readFile);
19
31
  function readPloyConfigSync(projectDir, configPath) {
20
32
  const configFile = configPath ?? "ploy.yaml";
@@ -37,6 +49,18 @@ function readPloyConfig(projectDir, configPath) {
37
49
  }
38
50
  return config;
39
51
  }
52
+ function getDataDir(projectDir) {
53
+ return join(projectDir, ".ploy");
54
+ }
55
+ function ensureDir(dir) {
56
+ mkdirSync(dir, { recursive: true });
57
+ }
58
+ function ensureDataDir(projectDir) {
59
+ const dataDir = getDataDir(projectDir);
60
+ ensureDir(dataDir);
61
+ ensureDir(join(dataDir, "db"));
62
+ return dataDir;
63
+ }
40
64
  function generateId() {
41
65
  return randomBytes(16).toString("hex");
42
66
  }
@@ -354,7 +378,9 @@ function createDashboardRoutes(app, dbManager2, config) {
354
378
  queue: config.queue,
355
379
  cache: config.cache,
356
380
  state: config.state,
381
+ fs: config.fs,
357
382
  workflow: config.workflow,
383
+ cron: config.cron,
358
384
  auth: config.auth
359
385
  });
360
386
  });
@@ -848,6 +874,67 @@ function createDashboardRoutes(app, dbManager2, config) {
848
874
  return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
849
875
  }
850
876
  });
877
+ app.get("/api/fs/:binding/entries", (c) => {
878
+ const binding = c.req.param("binding");
879
+ const fsName = config.fs?.[binding];
880
+ const limit = parseInt(c.req.query("limit") || "20", 10);
881
+ const offset = parseInt(c.req.query("offset") || "0", 10);
882
+ if (!fsName) {
883
+ return c.json({ error: "File storage binding not found" }, 404);
884
+ }
885
+ try {
886
+ const db = dbManager2.emulatorDb;
887
+ const total = db.prepare(`SELECT COUNT(*) as count FROM fs_entries WHERE fs_name = ?`).get(fsName).count;
888
+ const entries = db.prepare(`SELECT key, size, content_type, created_at
889
+ FROM fs_entries
890
+ WHERE fs_name = ?
891
+ ORDER BY key ASC
892
+ LIMIT ? OFFSET ?`).all(fsName, limit, offset);
893
+ return c.json({
894
+ entries: entries.map((e) => ({
895
+ key: e.key,
896
+ size: e.size,
897
+ contentType: e.content_type,
898
+ createdAt: new Date(e.created_at * 1e3).toISOString()
899
+ })),
900
+ total,
901
+ limit,
902
+ offset
903
+ });
904
+ } catch (err) {
905
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
906
+ }
907
+ });
908
+ app.get("/api/cron/:binding/executions", (c) => {
909
+ const binding = c.req.param("binding");
910
+ const cronExpression = config.cron?.[binding];
911
+ const limit = parseInt(c.req.query("limit") ?? "20", 10);
912
+ if (!cronExpression) {
913
+ return c.json({ error: "Cron binding not found" }, 404);
914
+ }
915
+ try {
916
+ const db = dbManager2.emulatorDb;
917
+ const executions = db.prepare(`SELECT id, cron_name, cron_expression, status, error, duration_ms, triggered_at, completed_at, created_at
918
+ FROM cron_executions
919
+ WHERE cron_name = ?
920
+ ORDER BY triggered_at DESC
921
+ LIMIT ?`).all(binding, limit);
922
+ return c.json({
923
+ cronExpression,
924
+ executions: executions.map((e) => ({
925
+ id: e.id,
926
+ status: e.status.toUpperCase(),
927
+ error: e.error,
928
+ durationMs: e.duration_ms,
929
+ triggeredAt: new Date(e.triggered_at * 1e3).toISOString(),
930
+ completedAt: e.completed_at ? new Date(e.completed_at * 1e3).toISOString() : null,
931
+ createdAt: new Date(e.created_at * 1e3).toISOString()
932
+ }))
933
+ });
934
+ } catch (err) {
935
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
936
+ }
937
+ });
851
938
  if (hasDashboard) {
852
939
  app.get("/assets/*", (c) => {
853
940
  const path = c.req.path;
@@ -975,6 +1062,120 @@ function createDbHandler(getDatabase) {
975
1062
  }
976
1063
  };
977
1064
  }
1065
+ function createFsHandlers(db, dataDir) {
1066
+ const fsBaseDir = join(dataDir, "fs");
1067
+ mkdirSync(fsBaseDir, { recursive: true });
1068
+ function getFsDir(fsName) {
1069
+ const dir = join(fsBaseDir, fsName);
1070
+ mkdirSync(dir, { recursive: true });
1071
+ return dir;
1072
+ }
1073
+ function encodedKeyPath(fsDir, key) {
1074
+ return join(fsDir, encodeURIComponent(key));
1075
+ }
1076
+ const putHandler = async (c) => {
1077
+ try {
1078
+ const body = await c.req.json();
1079
+ const { fsName, key, value, contentType } = body;
1080
+ if (!fsName || !key) {
1081
+ return c.json({ error: "Missing required fields: fsName, key, value" }, 400);
1082
+ }
1083
+ const fsDir = getFsDir(fsName);
1084
+ const filePath = encodedKeyPath(fsDir, key);
1085
+ writeFileSync(filePath, value, "utf-8");
1086
+ const size = Buffer.byteLength(value, "utf-8");
1087
+ const now = Math.floor(Date.now() / 1e3);
1088
+ db.prepare(`INSERT OR REPLACE INTO fs_entries (fs_name, key, content_type, size, created_at) VALUES (?, ?, ?, ?, ?)`).run(fsName, key, contentType ?? "application/octet-stream", size, now);
1089
+ return c.json({ success: true });
1090
+ } catch (err) {
1091
+ error(`[fs-service] put error: ${err instanceof Error ? err.message : String(err)}`);
1092
+ return c.json({
1093
+ error: `put failed: ${err instanceof Error ? err.message : String(err)}`
1094
+ }, 500);
1095
+ }
1096
+ };
1097
+ const getHandler = async (c) => {
1098
+ try {
1099
+ const body = await c.req.json();
1100
+ const { fsName, key } = body;
1101
+ if (!fsName || !key) {
1102
+ return c.json({ error: "Missing required fields: fsName, key" }, 400);
1103
+ }
1104
+ const row = db.prepare(`SELECT content_type, size FROM fs_entries WHERE fs_name = ? AND key = ?`).get(fsName, key);
1105
+ if (!row) {
1106
+ return c.json({ found: false });
1107
+ }
1108
+ const fsDir = getFsDir(fsName);
1109
+ const filePath = encodedKeyPath(fsDir, key);
1110
+ if (!existsSync(filePath)) {
1111
+ db.prepare(`DELETE FROM fs_entries WHERE fs_name = ? AND key = ?`).run(fsName, key);
1112
+ return c.json({ found: false });
1113
+ }
1114
+ const fileContent = readFileSync(filePath, "utf-8");
1115
+ return c.json({
1116
+ found: true,
1117
+ body: fileContent,
1118
+ contentType: row.content_type,
1119
+ size: row.size
1120
+ });
1121
+ } catch (err) {
1122
+ error(`[fs-service] get error: ${err instanceof Error ? err.message : String(err)}`);
1123
+ return c.json({
1124
+ error: `get failed: ${err instanceof Error ? err.message : String(err)}`
1125
+ }, 500);
1126
+ }
1127
+ };
1128
+ const deleteHandler = async (c) => {
1129
+ try {
1130
+ const body = await c.req.json();
1131
+ const { fsName, key } = body;
1132
+ if (!fsName || !key) {
1133
+ return c.json({ error: "Missing required fields: fsName, key" }, 400);
1134
+ }
1135
+ db.prepare(`DELETE FROM fs_entries WHERE fs_name = ? AND key = ?`).run(fsName, key);
1136
+ const fsDir = getFsDir(fsName);
1137
+ const filePath = encodedKeyPath(fsDir, key);
1138
+ if (existsSync(filePath)) {
1139
+ unlinkSync(filePath);
1140
+ }
1141
+ return c.json({ success: true });
1142
+ } catch (err) {
1143
+ error(`[fs-service] delete error: ${err instanceof Error ? err.message : String(err)}`);
1144
+ return c.json({
1145
+ error: `delete failed: ${err instanceof Error ? err.message : String(err)}`
1146
+ }, 500);
1147
+ }
1148
+ };
1149
+ const listHandler = async (c) => {
1150
+ try {
1151
+ const body = await c.req.json();
1152
+ const { fsName, prefix, limit } = body;
1153
+ if (!fsName) {
1154
+ return c.json({ error: "Missing required field: fsName" }, 400);
1155
+ }
1156
+ const effectiveLimit = limit ?? 1e3;
1157
+ const keys = prefix ? db.prepare(`SELECT key, size, content_type FROM fs_entries WHERE fs_name = ? AND key LIKE ? ORDER BY key ASC LIMIT ?`).all(fsName, `${prefix}%`, effectiveLimit) : db.prepare(`SELECT key, size, content_type FROM fs_entries WHERE fs_name = ? ORDER BY key ASC LIMIT ?`).all(fsName, effectiveLimit);
1158
+ return c.json({
1159
+ keys: keys.map((k) => ({
1160
+ key: k.key,
1161
+ size: k.size,
1162
+ contentType: k.content_type
1163
+ }))
1164
+ });
1165
+ } catch (err) {
1166
+ error(`[fs-service] list error: ${err instanceof Error ? err.message : String(err)}`);
1167
+ return c.json({
1168
+ error: `list failed: ${err instanceof Error ? err.message : String(err)}`
1169
+ }, 500);
1170
+ }
1171
+ };
1172
+ return {
1173
+ putHandler,
1174
+ getHandler,
1175
+ deleteHandler,
1176
+ listHandler
1177
+ };
1178
+ }
978
1179
  function createQueueHandlers(db) {
979
1180
  const sendHandler = async (c) => {
980
1181
  try {
@@ -1329,6 +1530,14 @@ async function startMockServer(dbManager2, config, options = {}) {
1329
1530
  app.post("/state/set", stateHandlers.setHandler);
1330
1531
  app.post("/state/delete", stateHandlers.deleteHandler);
1331
1532
  }
1533
+ if (config.fs) {
1534
+ const dataDir = getDataDir(process.cwd());
1535
+ const fsHandlers = createFsHandlers(dbManager2.emulatorDb, dataDir);
1536
+ app.post("/fs/put", fsHandlers.putHandler);
1537
+ app.post("/fs/get", fsHandlers.getHandler);
1538
+ app.post("/fs/delete", fsHandlers.deleteHandler);
1539
+ app.post("/fs/list", fsHandlers.listHandler);
1540
+ }
1332
1541
  if (config.auth) {
1333
1542
  const authHandlers = createAuthHandlers(dbManager2.emulatorDb);
1334
1543
  app.post("/auth/signup", authHandlers.signupHandler);
@@ -1357,18 +1566,6 @@ async function startMockServer(dbManager2, config, options = {}) {
1357
1566
  });
1358
1567
  });
1359
1568
  }
1360
- function getDataDir(projectDir) {
1361
- return join(projectDir, ".ploy");
1362
- }
1363
- function ensureDir(dir) {
1364
- mkdirSync(dir, { recursive: true });
1365
- }
1366
- function ensureDataDir(projectDir) {
1367
- const dataDir = getDataDir(projectDir);
1368
- ensureDir(dataDir);
1369
- ensureDir(join(dataDir, "db"));
1370
- return dataDir;
1371
- }
1372
1569
  var EMULATOR_SCHEMA = `
1373
1570
  -- Queue messages table
1374
1571
  CREATE TABLE IF NOT EXISTS queue_messages (
@@ -1478,6 +1675,32 @@ CREATE TABLE IF NOT EXISTS state_entries (
1478
1675
  value TEXT NOT NULL,
1479
1676
  PRIMARY KEY (state_name, key)
1480
1677
  );
1678
+
1679
+ -- File storage entries table (metadata for stored files)
1680
+ CREATE TABLE IF NOT EXISTS fs_entries (
1681
+ fs_name TEXT NOT NULL,
1682
+ key TEXT NOT NULL,
1683
+ content_type TEXT NOT NULL DEFAULT 'application/octet-stream',
1684
+ size INTEGER NOT NULL DEFAULT 0,
1685
+ created_at INTEGER DEFAULT (strftime('%s', 'now')),
1686
+ PRIMARY KEY (fs_name, key)
1687
+ );
1688
+
1689
+ -- Cron executions table
1690
+ CREATE TABLE IF NOT EXISTS cron_executions (
1691
+ id TEXT PRIMARY KEY,
1692
+ cron_name TEXT NOT NULL,
1693
+ cron_expression TEXT NOT NULL,
1694
+ status TEXT DEFAULT 'running',
1695
+ error TEXT,
1696
+ duration_ms INTEGER,
1697
+ triggered_at INTEGER NOT NULL,
1698
+ completed_at INTEGER,
1699
+ created_at INTEGER DEFAULT (strftime('%s', 'now'))
1700
+ );
1701
+
1702
+ CREATE INDEX IF NOT EXISTS idx_cron_executions_name
1703
+ ON cron_executions(cron_name, triggered_at);
1481
1704
  `;
1482
1705
  function initializeDatabases(projectDir) {
1483
1706
  const dataDir = ensureDataDir(projectDir);