@meetploy/cli 1.16.0 → 1.17.1

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-duAiLjPq.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/main-B-euJxpr.css">
7
+ <script type="module" crossorigin src="/assets/main-Ch3tPHX5.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/main-5Kt9I_hM.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
package/dist/dev.js CHANGED
@@ -7,9 +7,9 @@ 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
13
  import 'os';
14
14
  import { getCookie, deleteCookie, setCookie } from 'hono/cookie';
15
15
  import Database from 'better-sqlite3';
@@ -49,6 +49,62 @@ function readPloyConfig(projectDir, configPath) {
49
49
  }
50
50
  return config;
51
51
  }
52
+
53
+ // ../emulator/dist/services/alarm-service.js
54
+ function createAlarmHandlers(db) {
55
+ const setHandler = async (c) => {
56
+ try {
57
+ const body = await c.req.json();
58
+ const { alarmName, id, scheduledTime, payload, intervalMs } = body;
59
+ const now = Math.floor(Date.now() / 1e3);
60
+ const scheduledTimeSec = Math.floor(scheduledTime / 1e3);
61
+ db.prepare(`INSERT OR REPLACE INTO alarm_entries (id, alarm_name, scheduled_time, payload, interval_ms, status, created_at)
62
+ VALUES (?, ?, ?, ?, ?, 'pending', ?)`).run(id, alarmName, scheduledTimeSec, payload !== void 0 ? JSON.stringify(payload) : null, intervalMs ?? null, now);
63
+ return c.json({ success: true });
64
+ } catch (err) {
65
+ const message = err instanceof Error ? err.message : String(err);
66
+ return c.json({ success: false, error: message }, 500);
67
+ }
68
+ };
69
+ const getHandler = async (c) => {
70
+ try {
71
+ const body = await c.req.json();
72
+ const { alarmName, id } = body;
73
+ const row = db.prepare(`SELECT id, scheduled_time, payload, interval_ms FROM alarm_entries
74
+ WHERE alarm_name = ? AND id = ? AND status = 'pending'`).get(alarmName, id);
75
+ if (!row) {
76
+ return c.json({ alarm: null });
77
+ }
78
+ return c.json({
79
+ alarm: {
80
+ id: row.id,
81
+ scheduledTime: row.scheduled_time * 1e3,
82
+ payload: row.payload ? JSON.parse(row.payload) : void 0,
83
+ intervalMs: row.interval_ms ?? void 0
84
+ }
85
+ });
86
+ } catch (err) {
87
+ const message = err instanceof Error ? err.message : String(err);
88
+ return c.json({ success: false, error: message }, 500);
89
+ }
90
+ };
91
+ const deleteHandler = async (c) => {
92
+ try {
93
+ const body = await c.req.json();
94
+ const { alarmName, id } = body;
95
+ db.prepare(`DELETE FROM alarm_entries WHERE alarm_name = ? AND id = ?`).run(alarmName, id);
96
+ return c.json({ success: true });
97
+ } catch (err) {
98
+ const message = err instanceof Error ? err.message : String(err);
99
+ return c.json({ success: false, error: message }, 500);
100
+ }
101
+ };
102
+ return {
103
+ setHandler,
104
+ getHandler,
105
+ deleteHandler
106
+ };
107
+ }
52
108
  function getDataDir(projectDir) {
53
109
  return join(projectDir, ".ploy");
54
110
  }
@@ -380,6 +436,8 @@ function createDashboardRoutes(app, dbManager2, config) {
380
436
  state: config.state,
381
437
  fs: config.fs,
382
438
  workflow: config.workflow,
439
+ cron: config.cron,
440
+ alarm: config.alarm,
383
441
  auth: config.auth
384
442
  });
385
443
  });
@@ -904,6 +962,123 @@ function createDashboardRoutes(app, dbManager2, config) {
904
962
  return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
905
963
  }
906
964
  });
965
+ app.get("/api/cron/:binding/executions", (c) => {
966
+ const binding = c.req.param("binding");
967
+ const cronExpression = config.cron?.[binding];
968
+ const limit = parseInt(c.req.query("limit") ?? "20", 10);
969
+ if (!cronExpression) {
970
+ return c.json({ error: "Cron binding not found" }, 404);
971
+ }
972
+ try {
973
+ const db = dbManager2.emulatorDb;
974
+ const executions = db.prepare(`SELECT id, cron_name, cron_expression, status, error, duration_ms, triggered_at, completed_at, created_at
975
+ FROM cron_executions
976
+ WHERE cron_name = ?
977
+ ORDER BY triggered_at DESC
978
+ LIMIT ?`).all(binding, limit);
979
+ return c.json({
980
+ cronExpression,
981
+ executions: executions.map((e) => ({
982
+ id: e.id,
983
+ status: e.status.toUpperCase(),
984
+ error: e.error,
985
+ durationMs: e.duration_ms,
986
+ triggeredAt: new Date(e.triggered_at * 1e3).toISOString(),
987
+ completedAt: e.completed_at ? new Date(e.completed_at * 1e3).toISOString() : null,
988
+ createdAt: new Date(e.created_at * 1e3).toISOString()
989
+ }))
990
+ });
991
+ } catch (err) {
992
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
993
+ }
994
+ });
995
+ app.get("/api/alarm/:binding/entries", (c) => {
996
+ const binding = c.req.param("binding");
997
+ const alarmName = config.alarm?.[binding];
998
+ const limit = parseInt(c.req.query("limit") ?? "20", 10);
999
+ const offset = parseInt(c.req.query("offset") ?? "0", 10);
1000
+ if (!alarmName) {
1001
+ return c.json({ error: "Alarm binding not found" }, 404);
1002
+ }
1003
+ try {
1004
+ const db = dbManager2.emulatorDb;
1005
+ const total = db.prepare(`SELECT COUNT(*) as count FROM alarm_entries WHERE alarm_name = ?`).get(alarmName).count;
1006
+ const entries = db.prepare(`SELECT id, scheduled_time, payload, interval_ms, status, created_at
1007
+ FROM alarm_entries
1008
+ WHERE alarm_name = ?
1009
+ ORDER BY scheduled_time DESC
1010
+ LIMIT ? OFFSET ?`).all(alarmName, limit, offset);
1011
+ return c.json({
1012
+ entries: entries.map((e) => ({
1013
+ id: e.id,
1014
+ scheduledTime: new Date(e.scheduled_time * 1e3).toISOString(),
1015
+ payload: e.payload ? JSON.parse(e.payload) : null,
1016
+ intervalMs: e.interval_ms,
1017
+ status: e.status.toUpperCase(),
1018
+ createdAt: new Date(e.created_at * 1e3).toISOString()
1019
+ })),
1020
+ total,
1021
+ limit,
1022
+ offset
1023
+ });
1024
+ } catch (err) {
1025
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1026
+ }
1027
+ });
1028
+ app.get("/api/alarm/:binding/metrics", (c) => {
1029
+ const binding = c.req.param("binding");
1030
+ const alarmName = config.alarm?.[binding];
1031
+ if (!alarmName) {
1032
+ return c.json({ error: "Alarm binding not found" }, 404);
1033
+ }
1034
+ try {
1035
+ const db = dbManager2.emulatorDb;
1036
+ const metrics = { pending: 0, fired: 0 };
1037
+ const statusCounts = db.prepare(`SELECT status, COUNT(*) as count
1038
+ FROM alarm_entries
1039
+ WHERE alarm_name = ?
1040
+ GROUP BY status`).all(alarmName);
1041
+ for (const row of statusCounts) {
1042
+ if (row.status === "pending") {
1043
+ metrics.pending = row.count;
1044
+ } else if (row.status === "fired") {
1045
+ metrics.fired = row.count;
1046
+ }
1047
+ }
1048
+ return c.json({ metrics });
1049
+ } catch (err) {
1050
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1051
+ }
1052
+ });
1053
+ app.get("/api/alarm/:binding/executions", (c) => {
1054
+ const binding = c.req.param("binding");
1055
+ const alarmName = config.alarm?.[binding];
1056
+ const limit = parseInt(c.req.query("limit") ?? "20", 10);
1057
+ if (!alarmName) {
1058
+ return c.json({ error: "Alarm binding not found" }, 404);
1059
+ }
1060
+ try {
1061
+ const db = dbManager2.emulatorDb;
1062
+ const executions = db.prepare(`SELECT id, alarm_id, scheduled_time, status, error, duration_ms, created_at
1063
+ FROM alarm_executions
1064
+ WHERE alarm_name = ?
1065
+ ORDER BY created_at DESC
1066
+ LIMIT ?`).all(alarmName, limit);
1067
+ return c.json({
1068
+ executions: executions.map((e) => ({
1069
+ id: e.id,
1070
+ alarmId: e.alarm_id,
1071
+ scheduledTime: new Date(e.scheduled_time * 1e3).toISOString(),
1072
+ status: e.status.toUpperCase(),
1073
+ error: e.error,
1074
+ durationMs: e.duration_ms,
1075
+ createdAt: new Date(e.created_at * 1e3).toISOString()
1076
+ }))
1077
+ });
1078
+ } catch (err) {
1079
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1080
+ }
1081
+ });
907
1082
  if (hasDashboard) {
908
1083
  app.get("/assets/*", (c) => {
909
1084
  const path = c.req.path;
@@ -1507,6 +1682,12 @@ async function startMockServer(dbManager2, config, options = {}) {
1507
1682
  app.post("/fs/delete", fsHandlers.deleteHandler);
1508
1683
  app.post("/fs/list", fsHandlers.listHandler);
1509
1684
  }
1685
+ if (config.alarm) {
1686
+ const alarmHandlers = createAlarmHandlers(dbManager2.emulatorDb);
1687
+ app.post("/alarm/set", alarmHandlers.setHandler);
1688
+ app.post("/alarm/get", alarmHandlers.getHandler);
1689
+ app.post("/alarm/delete", alarmHandlers.deleteHandler);
1690
+ }
1510
1691
  if (config.auth) {
1511
1692
  const authHandlers = createAuthHandlers(dbManager2.emulatorDb);
1512
1693
  app.post("/auth/signup", authHandlers.signupHandler);
@@ -1654,6 +1835,52 @@ CREATE TABLE IF NOT EXISTS fs_entries (
1654
1835
  created_at INTEGER DEFAULT (strftime('%s', 'now')),
1655
1836
  PRIMARY KEY (fs_name, key)
1656
1837
  );
1838
+
1839
+ -- Cron executions table
1840
+ CREATE TABLE IF NOT EXISTS cron_executions (
1841
+ id TEXT PRIMARY KEY,
1842
+ cron_name TEXT NOT NULL,
1843
+ cron_expression TEXT NOT NULL,
1844
+ status TEXT DEFAULT 'running',
1845
+ error TEXT,
1846
+ duration_ms INTEGER,
1847
+ triggered_at INTEGER NOT NULL,
1848
+ completed_at INTEGER,
1849
+ created_at INTEGER DEFAULT (strftime('%s', 'now'))
1850
+ );
1851
+
1852
+ CREATE INDEX IF NOT EXISTS idx_cron_executions_name
1853
+ ON cron_executions(cron_name, triggered_at);
1854
+
1855
+ -- Alarm entries table (durable timers, optionally recurring)
1856
+ CREATE TABLE IF NOT EXISTS alarm_entries (
1857
+ id TEXT NOT NULL,
1858
+ alarm_name TEXT NOT NULL,
1859
+ scheduled_time INTEGER NOT NULL,
1860
+ payload TEXT,
1861
+ interval_ms INTEGER,
1862
+ status TEXT DEFAULT 'pending',
1863
+ created_at INTEGER DEFAULT (strftime('%s', 'now')),
1864
+ PRIMARY KEY (alarm_name, id)
1865
+ );
1866
+
1867
+ CREATE INDEX IF NOT EXISTS idx_alarm_entries_status_time
1868
+ ON alarm_entries(status, scheduled_time);
1869
+
1870
+ -- Alarm execution log
1871
+ CREATE TABLE IF NOT EXISTS alarm_executions (
1872
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1873
+ alarm_id TEXT NOT NULL,
1874
+ alarm_name TEXT NOT NULL,
1875
+ scheduled_time INTEGER NOT NULL,
1876
+ status TEXT DEFAULT 'pending',
1877
+ error TEXT,
1878
+ duration_ms INTEGER,
1879
+ created_at INTEGER DEFAULT (strftime('%s', 'now'))
1880
+ );
1881
+
1882
+ CREATE INDEX IF NOT EXISTS idx_alarm_executions_name
1883
+ ON alarm_executions(alarm_name, created_at);
1657
1884
  `;
1658
1885
  function initializeDatabases(projectDir) {
1659
1886
  const dataDir = ensureDataDir(projectDir);