@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.
- package/dist/dashboard-dist/assets/{main-qA3kxECS.css → main-B-euJxpr.css} +1 -1
- package/dist/dashboard-dist/assets/main-duAiLjPq.js +339 -0
- package/dist/dashboard-dist/index.html +2 -2
- package/dist/dev.js +238 -15
- package/dist/index.js +625 -32
- package/package.json +1 -1
- package/dist/dashboard-dist/assets/main-UY1Z1kG0.js +0 -334
|
@@ -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-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/main-
|
|
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);
|