@meetploy/cli 1.11.4 → 1.12.2
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-BnI0iIIW.css +1 -0
- package/dist/dashboard-dist/assets/main-DnrX4BgS.js +319 -0
- package/dist/dashboard-dist/index.html +2 -2
- package/dist/dev.js +579 -14
- package/dist/index.js +680 -51
- package/package.json +1 -1
- package/dist/dashboard-dist/assets/main-B1TTAu_I.js +0 -166
- package/dist/dashboard-dist/assets/main-BXQv-ae0.css +0 -1
package/dist/index.js
CHANGED
|
@@ -7,8 +7,9 @@ import { promisify } from 'util';
|
|
|
7
7
|
import { parse } from 'yaml';
|
|
8
8
|
import { build } from 'esbuild';
|
|
9
9
|
import { watch } from 'chokidar';
|
|
10
|
+
import { randomBytes, randomUUID, createHmac, pbkdf2Sync, timingSafeEqual, createHash } from 'crypto';
|
|
11
|
+
import { getCookie, deleteCookie, setCookie } from 'hono/cookie';
|
|
10
12
|
import { URL, fileURLToPath } from 'url';
|
|
11
|
-
import { randomBytes, randomUUID, createHash } from 'crypto';
|
|
12
13
|
import { serve } from '@hono/node-server';
|
|
13
14
|
import { Hono } from 'hono';
|
|
14
15
|
import { homedir, tmpdir } from 'os';
|
|
@@ -220,6 +221,20 @@ function validatePloyConfig(config, configFile = "ploy.yaml", options = {}) {
|
|
|
220
221
|
throw new Error(`'compatibility_date' in ${configFile} must be in YYYY-MM-DD format (e.g., 2025-04-02)`);
|
|
221
222
|
}
|
|
222
223
|
}
|
|
224
|
+
if (config.auth !== void 0) {
|
|
225
|
+
if (typeof config.auth !== "object" || config.auth === null) {
|
|
226
|
+
throw new Error(`'auth' in ${configFile} must be an object`);
|
|
227
|
+
}
|
|
228
|
+
if (!config.auth.binding) {
|
|
229
|
+
throw new Error(`'auth.binding' is required in ${configFile} when auth is configured`);
|
|
230
|
+
}
|
|
231
|
+
if (typeof config.auth.binding !== "string") {
|
|
232
|
+
throw new Error(`'auth.binding' in ${configFile} must be a string`);
|
|
233
|
+
}
|
|
234
|
+
if (!BINDING_NAME_REGEX.test(config.auth.binding)) {
|
|
235
|
+
throw new Error(`Invalid auth binding name '${config.auth.binding}' in ${configFile}. Binding names must be uppercase with underscores (e.g., AUTH_DB, AUTH)`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
223
238
|
return validatedConfig;
|
|
224
239
|
}
|
|
225
240
|
async function readPloyConfig(projectDir, configPath) {
|
|
@@ -260,7 +275,7 @@ function readAndValidatePloyConfigSync(projectDir, configPath, validationOptions
|
|
|
260
275
|
return validatePloyConfig(config, configFile, validationOptions);
|
|
261
276
|
}
|
|
262
277
|
function hasBindings(config) {
|
|
263
|
-
return !!(config.db || config.queue || config.workflow || config.ai);
|
|
278
|
+
return !!(config.db || config.queue || config.workflow || config.ai || config.auth);
|
|
264
279
|
}
|
|
265
280
|
function getWorkerEntryPoint(projectDir, config) {
|
|
266
281
|
if (config.main) {
|
|
@@ -465,10 +480,12 @@ export function initializeDB(bindingName: string, serviceUrl: string): DBDatabas
|
|
|
465
480
|
return {
|
|
466
481
|
prepare(query) {
|
|
467
482
|
let boundParams = [];
|
|
468
|
-
|
|
483
|
+
const stmt = {
|
|
484
|
+
__db_data: { query, params: boundParams },
|
|
469
485
|
bind(...values) {
|
|
470
486
|
boundParams = values;
|
|
471
|
-
|
|
487
|
+
stmt.__db_data = { query, params: boundParams };
|
|
488
|
+
return stmt;
|
|
472
489
|
},
|
|
473
490
|
async run() {
|
|
474
491
|
const response = await fetch(serviceUrl, {
|
|
@@ -516,6 +533,7 @@ export function initializeDB(bindingName: string, serviceUrl: string): DBDatabas
|
|
|
516
533
|
return arrayRows;
|
|
517
534
|
},
|
|
518
535
|
};
|
|
536
|
+
return stmt;
|
|
519
537
|
},
|
|
520
538
|
async dump() {
|
|
521
539
|
const response = await fetch(serviceUrl, {
|
|
@@ -594,6 +612,18 @@ var init_trace_event = __esm({
|
|
|
594
612
|
}
|
|
595
613
|
});
|
|
596
614
|
|
|
615
|
+
// ../shared/dist/trace-id.js
|
|
616
|
+
var init_trace_id = __esm({
|
|
617
|
+
"../shared/dist/trace-id.js"() {
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// ../shared/dist/unified-log.js
|
|
622
|
+
var init_unified_log = __esm({
|
|
623
|
+
"../shared/dist/unified-log.js"() {
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
|
|
597
627
|
// ../shared/dist/url-validation.js
|
|
598
628
|
var init_url_validation = __esm({
|
|
599
629
|
"../shared/dist/url-validation.js"() {
|
|
@@ -607,6 +637,8 @@ var init_dist = __esm({
|
|
|
607
637
|
init_error();
|
|
608
638
|
init_health_check();
|
|
609
639
|
init_trace_event();
|
|
640
|
+
init_trace_id();
|
|
641
|
+
init_unified_log();
|
|
610
642
|
init_url_validation();
|
|
611
643
|
}
|
|
612
644
|
});
|
|
@@ -1113,6 +1145,42 @@ var init_bundler = __esm({
|
|
|
1113
1145
|
];
|
|
1114
1146
|
}
|
|
1115
1147
|
});
|
|
1148
|
+
|
|
1149
|
+
// ../emulator/dist/utils/logger.js
|
|
1150
|
+
function timestamp() {
|
|
1151
|
+
return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
|
|
1152
|
+
}
|
|
1153
|
+
function log(message) {
|
|
1154
|
+
console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.cyan}[ploy]${COLORS.reset} ${message}`);
|
|
1155
|
+
}
|
|
1156
|
+
function success(message) {
|
|
1157
|
+
console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.green}[ploy]${COLORS.reset} ${message}`);
|
|
1158
|
+
}
|
|
1159
|
+
function warn(message) {
|
|
1160
|
+
console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.yellow}[ploy]${COLORS.reset} ${message}`);
|
|
1161
|
+
}
|
|
1162
|
+
function error(message) {
|
|
1163
|
+
console.error(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.red}[ploy]${COLORS.reset} ${message}`);
|
|
1164
|
+
}
|
|
1165
|
+
function debug(message, verbose) {
|
|
1166
|
+
if (verbose) {
|
|
1167
|
+
console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.magenta}[ploy:debug]${COLORS.reset} ${message}`);
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
var COLORS;
|
|
1171
|
+
var init_logger = __esm({
|
|
1172
|
+
"../emulator/dist/utils/logger.js"() {
|
|
1173
|
+
COLORS = {
|
|
1174
|
+
reset: "\x1B[0m",
|
|
1175
|
+
dim: "\x1B[2m",
|
|
1176
|
+
cyan: "\x1B[36m",
|
|
1177
|
+
green: "\x1B[32m",
|
|
1178
|
+
yellow: "\x1B[33m",
|
|
1179
|
+
red: "\x1B[31m",
|
|
1180
|
+
magenta: "\x1B[35m"
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1116
1184
|
function createFileWatcher(srcDir, onRebuild) {
|
|
1117
1185
|
let watcher = null;
|
|
1118
1186
|
let debounceTimer = null;
|
|
@@ -1145,7 +1213,27 @@ function createFileWatcher(srcDir, onRebuild) {
|
|
|
1145
1213
|
watcher = watch(srcDir, {
|
|
1146
1214
|
persistent: true,
|
|
1147
1215
|
ignoreInitial: true,
|
|
1148
|
-
ignored: [
|
|
1216
|
+
ignored: [
|
|
1217
|
+
"**/node_modules/**",
|
|
1218
|
+
"**/dist/**",
|
|
1219
|
+
"**/.*",
|
|
1220
|
+
"**/.*/**",
|
|
1221
|
+
"**/coverage/**",
|
|
1222
|
+
"**/build/**",
|
|
1223
|
+
"**/*.log",
|
|
1224
|
+
"**/pnpm-lock.yaml",
|
|
1225
|
+
"**/package-lock.json",
|
|
1226
|
+
"**/yarn.lock"
|
|
1227
|
+
]
|
|
1228
|
+
});
|
|
1229
|
+
watcher.on("error", (err) => {
|
|
1230
|
+
const error2 = err;
|
|
1231
|
+
if (error2.code === "EMFILE") {
|
|
1232
|
+
warn("Warning: Too many open files. Some file changes may not be detected.");
|
|
1233
|
+
warn("Consider increasing your system's file descriptor limit (ulimit -n).");
|
|
1234
|
+
} else {
|
|
1235
|
+
error(`File watcher error: ${error2.message || String(err)}`);
|
|
1236
|
+
}
|
|
1149
1237
|
});
|
|
1150
1238
|
watcher.on("change", (filePath) => {
|
|
1151
1239
|
if (shouldRebuild(filePath)) {
|
|
@@ -1177,6 +1265,7 @@ function createFileWatcher(srcDir, onRebuild) {
|
|
|
1177
1265
|
}
|
|
1178
1266
|
var init_watcher = __esm({
|
|
1179
1267
|
"../emulator/dist/bundler/watcher.js"() {
|
|
1268
|
+
init_logger();
|
|
1180
1269
|
}
|
|
1181
1270
|
});
|
|
1182
1271
|
|
|
@@ -1249,6 +1338,255 @@ var init_workerd_config = __esm({
|
|
|
1249
1338
|
"../emulator/dist/config/workerd-config.js"() {
|
|
1250
1339
|
}
|
|
1251
1340
|
});
|
|
1341
|
+
function generateId() {
|
|
1342
|
+
return randomBytes(16).toString("hex");
|
|
1343
|
+
}
|
|
1344
|
+
function hashPassword(password) {
|
|
1345
|
+
const salt = randomBytes(32).toString("hex");
|
|
1346
|
+
const hash = pbkdf2Sync(password, salt, 1e5, 64, "sha512").toString("hex");
|
|
1347
|
+
return `${salt}:${hash}`;
|
|
1348
|
+
}
|
|
1349
|
+
function verifyPassword(password, storedHash) {
|
|
1350
|
+
const [salt, hash] = storedHash.split(":");
|
|
1351
|
+
const derivedHash = pbkdf2Sync(password, salt, 1e5, 64, "sha512").toString("hex");
|
|
1352
|
+
return timingSafeEqual(Buffer.from(hash, "hex"), Buffer.from(derivedHash, "hex"));
|
|
1353
|
+
}
|
|
1354
|
+
function hashToken(token) {
|
|
1355
|
+
return createHmac("sha256", "emulator-secret").update(token).digest("hex");
|
|
1356
|
+
}
|
|
1357
|
+
function base64UrlEncode(str) {
|
|
1358
|
+
return Buffer.from(str).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
1359
|
+
}
|
|
1360
|
+
function base64UrlDecode(str) {
|
|
1361
|
+
let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
|
|
1362
|
+
while (base64.length % 4) {
|
|
1363
|
+
base64 += "=";
|
|
1364
|
+
}
|
|
1365
|
+
return Buffer.from(base64, "base64").toString();
|
|
1366
|
+
}
|
|
1367
|
+
function createJWT(payload) {
|
|
1368
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
1369
|
+
const headerB64 = base64UrlEncode(JSON.stringify(header));
|
|
1370
|
+
const payloadB64 = base64UrlEncode(JSON.stringify(payload));
|
|
1371
|
+
const signature = createHmac("sha256", JWT_SECRET).update(`${headerB64}.${payloadB64}`).digest("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
1372
|
+
return `${headerB64}.${payloadB64}.${signature}`;
|
|
1373
|
+
}
|
|
1374
|
+
function verifyJWT(token) {
|
|
1375
|
+
try {
|
|
1376
|
+
const parts = token.split(".");
|
|
1377
|
+
if (parts.length !== 3) {
|
|
1378
|
+
return null;
|
|
1379
|
+
}
|
|
1380
|
+
const [headerB64, payloadB64, signature] = parts;
|
|
1381
|
+
const expectedSig = createHmac("sha256", JWT_SECRET).update(`${headerB64}.${payloadB64}`).digest("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
1382
|
+
if (signature !== expectedSig) {
|
|
1383
|
+
return null;
|
|
1384
|
+
}
|
|
1385
|
+
const payload = JSON.parse(base64UrlDecode(payloadB64));
|
|
1386
|
+
if (payload.exp < Math.floor(Date.now() / 1e3)) {
|
|
1387
|
+
return null;
|
|
1388
|
+
}
|
|
1389
|
+
return payload;
|
|
1390
|
+
} catch {
|
|
1391
|
+
return null;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
function createSessionToken(userId, email) {
|
|
1395
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1396
|
+
const sessionId = generateId();
|
|
1397
|
+
const token = createJWT({
|
|
1398
|
+
sub: userId,
|
|
1399
|
+
email,
|
|
1400
|
+
iat: now,
|
|
1401
|
+
exp: now + SESSION_TOKEN_EXPIRY,
|
|
1402
|
+
jti: sessionId
|
|
1403
|
+
});
|
|
1404
|
+
return {
|
|
1405
|
+
token,
|
|
1406
|
+
sessionId,
|
|
1407
|
+
expiresAt: new Date((now + SESSION_TOKEN_EXPIRY) * 1e3)
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
function validateEmail(email) {
|
|
1411
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1412
|
+
if (!emailRegex.test(email)) {
|
|
1413
|
+
return "Invalid email format";
|
|
1414
|
+
}
|
|
1415
|
+
return null;
|
|
1416
|
+
}
|
|
1417
|
+
function validatePassword(password) {
|
|
1418
|
+
if (password.length < 8) {
|
|
1419
|
+
return "Password must be at least 8 characters";
|
|
1420
|
+
}
|
|
1421
|
+
return null;
|
|
1422
|
+
}
|
|
1423
|
+
function setSessionCookie(c, sessionToken) {
|
|
1424
|
+
setCookie(c, "ploy_session", sessionToken, {
|
|
1425
|
+
httpOnly: true,
|
|
1426
|
+
secure: false,
|
|
1427
|
+
sameSite: "Lax",
|
|
1428
|
+
path: "/",
|
|
1429
|
+
maxAge: SESSION_TOKEN_EXPIRY
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
function clearSessionCookie(c) {
|
|
1433
|
+
deleteCookie(c, "ploy_session", { path: "/" });
|
|
1434
|
+
}
|
|
1435
|
+
function createAuthHandlers(db) {
|
|
1436
|
+
const signupHandler = async (c) => {
|
|
1437
|
+
try {
|
|
1438
|
+
const body = await c.req.json();
|
|
1439
|
+
const { email, password, metadata } = body;
|
|
1440
|
+
const emailError = validateEmail(email);
|
|
1441
|
+
if (emailError) {
|
|
1442
|
+
return c.json({ error: emailError }, 400);
|
|
1443
|
+
}
|
|
1444
|
+
const passwordError = validatePassword(password);
|
|
1445
|
+
if (passwordError) {
|
|
1446
|
+
return c.json({ error: passwordError }, 400);
|
|
1447
|
+
}
|
|
1448
|
+
const existingUser = db.prepare("SELECT id FROM auth_users WHERE email = ?").get(email.toLowerCase());
|
|
1449
|
+
if (existingUser) {
|
|
1450
|
+
return c.json({ error: "User already exists" }, 409);
|
|
1451
|
+
}
|
|
1452
|
+
const userId = generateId();
|
|
1453
|
+
const passwordHash = hashPassword(password);
|
|
1454
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1455
|
+
db.prepare(`INSERT INTO auth_users (id, email, password_hash, created_at, updated_at, metadata)
|
|
1456
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(userId, email.toLowerCase(), passwordHash, now, now, metadata ? JSON.stringify(metadata) : null);
|
|
1457
|
+
const { token: sessionToken, sessionId, expiresAt } = createSessionToken(userId, email.toLowerCase());
|
|
1458
|
+
const sessionTokenHash = hashToken(sessionToken);
|
|
1459
|
+
db.prepare(`INSERT INTO auth_sessions (id, user_id, token_hash, expires_at, created_at)
|
|
1460
|
+
VALUES (?, ?, ?, ?, ?)`).run(sessionId, userId, sessionTokenHash, expiresAt.toISOString(), now);
|
|
1461
|
+
setSessionCookie(c, sessionToken);
|
|
1462
|
+
return c.json({
|
|
1463
|
+
user: {
|
|
1464
|
+
id: userId,
|
|
1465
|
+
email: email.toLowerCase(),
|
|
1466
|
+
emailVerified: false,
|
|
1467
|
+
createdAt: now,
|
|
1468
|
+
metadata: metadata ?? null
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
} catch (err) {
|
|
1472
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1473
|
+
return c.json({ error: message }, 500);
|
|
1474
|
+
}
|
|
1475
|
+
};
|
|
1476
|
+
const signinHandler = async (c) => {
|
|
1477
|
+
try {
|
|
1478
|
+
const body = await c.req.json();
|
|
1479
|
+
const { email, password } = body;
|
|
1480
|
+
const user = db.prepare("SELECT * FROM auth_users WHERE email = ?").get(email.toLowerCase());
|
|
1481
|
+
if (!user) {
|
|
1482
|
+
return c.json({ error: "Invalid credentials" }, 401);
|
|
1483
|
+
}
|
|
1484
|
+
if (!verifyPassword(password, user.password_hash)) {
|
|
1485
|
+
return c.json({ error: "Invalid credentials" }, 401);
|
|
1486
|
+
}
|
|
1487
|
+
const { token: sessionToken, sessionId, expiresAt } = createSessionToken(user.id, user.email);
|
|
1488
|
+
const sessionTokenHash = hashToken(sessionToken);
|
|
1489
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1490
|
+
db.prepare(`INSERT INTO auth_sessions (id, user_id, token_hash, expires_at, created_at)
|
|
1491
|
+
VALUES (?, ?, ?, ?, ?)`).run(sessionId, user.id, sessionTokenHash, expiresAt.toISOString(), now);
|
|
1492
|
+
let metadata = null;
|
|
1493
|
+
if (user.metadata) {
|
|
1494
|
+
try {
|
|
1495
|
+
metadata = JSON.parse(user.metadata);
|
|
1496
|
+
} catch {
|
|
1497
|
+
metadata = null;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
setSessionCookie(c, sessionToken);
|
|
1501
|
+
return c.json({
|
|
1502
|
+
user: {
|
|
1503
|
+
id: user.id,
|
|
1504
|
+
email: user.email,
|
|
1505
|
+
emailVerified: user.email_verified === 1,
|
|
1506
|
+
createdAt: user.created_at,
|
|
1507
|
+
metadata
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
} catch (err) {
|
|
1511
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1512
|
+
return c.json({ error: message }, 500);
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
const meHandler = async (c) => {
|
|
1516
|
+
try {
|
|
1517
|
+
const cookieToken = getCookie(c, "ploy_session");
|
|
1518
|
+
const authHeader = c.req.header("Authorization");
|
|
1519
|
+
let token;
|
|
1520
|
+
if (cookieToken) {
|
|
1521
|
+
token = cookieToken;
|
|
1522
|
+
} else if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
1523
|
+
token = authHeader.slice(7);
|
|
1524
|
+
}
|
|
1525
|
+
if (!token) {
|
|
1526
|
+
return c.json({ error: "Missing authentication" }, 401);
|
|
1527
|
+
}
|
|
1528
|
+
const payload = verifyJWT(token);
|
|
1529
|
+
if (!payload) {
|
|
1530
|
+
return c.json({ error: "Invalid or expired session" }, 401);
|
|
1531
|
+
}
|
|
1532
|
+
const user = db.prepare("SELECT id, email, email_verified, created_at, updated_at, metadata FROM auth_users WHERE id = ?").get(payload.sub);
|
|
1533
|
+
if (!user) {
|
|
1534
|
+
return c.json({ error: "User not found" }, 401);
|
|
1535
|
+
}
|
|
1536
|
+
let metadata = null;
|
|
1537
|
+
if (user.metadata) {
|
|
1538
|
+
try {
|
|
1539
|
+
metadata = JSON.parse(user.metadata);
|
|
1540
|
+
} catch {
|
|
1541
|
+
metadata = null;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
return c.json({
|
|
1545
|
+
user: {
|
|
1546
|
+
id: user.id,
|
|
1547
|
+
email: user.email,
|
|
1548
|
+
emailVerified: user.email_verified === 1,
|
|
1549
|
+
createdAt: user.created_at,
|
|
1550
|
+
updatedAt: user.updated_at,
|
|
1551
|
+
metadata
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
} catch (err) {
|
|
1555
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1556
|
+
return c.json({ error: message }, 500);
|
|
1557
|
+
}
|
|
1558
|
+
};
|
|
1559
|
+
const signoutHandler = async (c) => {
|
|
1560
|
+
try {
|
|
1561
|
+
const sessionToken = getCookie(c, "ploy_session");
|
|
1562
|
+
if (sessionToken) {
|
|
1563
|
+
const payload = verifyJWT(sessionToken);
|
|
1564
|
+
if (payload) {
|
|
1565
|
+
const tokenHash = hashToken(sessionToken);
|
|
1566
|
+
db.prepare("UPDATE auth_sessions SET revoked = 1 WHERE token_hash = ?").run(tokenHash);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
clearSessionCookie(c);
|
|
1570
|
+
return c.json({ success: true });
|
|
1571
|
+
} catch (err) {
|
|
1572
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1573
|
+
return c.json({ error: message }, 500);
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
return {
|
|
1577
|
+
signupHandler,
|
|
1578
|
+
signinHandler,
|
|
1579
|
+
meHandler,
|
|
1580
|
+
signoutHandler
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
var JWT_SECRET, SESSION_TOKEN_EXPIRY;
|
|
1584
|
+
var init_auth_service = __esm({
|
|
1585
|
+
"../emulator/dist/services/auth-service.js"() {
|
|
1586
|
+
JWT_SECRET = "ploy-emulator-dev-secret";
|
|
1587
|
+
SESSION_TOKEN_EXPIRY = 7 * 24 * 60 * 60;
|
|
1588
|
+
}
|
|
1589
|
+
});
|
|
1252
1590
|
function findDashboardDistPath() {
|
|
1253
1591
|
const possiblePaths = [
|
|
1254
1592
|
join(__dirname, "dashboard-dist"),
|
|
@@ -1276,9 +1614,174 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
1276
1614
|
return c.json({
|
|
1277
1615
|
db: config.db,
|
|
1278
1616
|
queue: config.queue,
|
|
1279
|
-
workflow: config.workflow
|
|
1617
|
+
workflow: config.workflow,
|
|
1618
|
+
auth: config.auth
|
|
1280
1619
|
});
|
|
1281
1620
|
});
|
|
1621
|
+
if (config.auth) {
|
|
1622
|
+
app.get("/api/auth/tables", (c) => {
|
|
1623
|
+
try {
|
|
1624
|
+
const db = dbManager2.emulatorDb;
|
|
1625
|
+
const tables = db.prepare(`SELECT name FROM sqlite_master
|
|
1626
|
+
WHERE type='table' AND (name = 'auth_users' OR name = 'auth_sessions')
|
|
1627
|
+
ORDER BY name`).all();
|
|
1628
|
+
return c.json({ tables });
|
|
1629
|
+
} catch (err) {
|
|
1630
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
app.get("/api/auth/tables/:tableName", (c) => {
|
|
1634
|
+
const tableName = c.req.param("tableName");
|
|
1635
|
+
if (tableName !== "auth_users" && tableName !== "auth_sessions") {
|
|
1636
|
+
return c.json({ error: "Table not found" }, 404);
|
|
1637
|
+
}
|
|
1638
|
+
const limit = parseInt(c.req.query("limit") || "50", 10);
|
|
1639
|
+
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
1640
|
+
try {
|
|
1641
|
+
const db = dbManager2.emulatorDb;
|
|
1642
|
+
const columnsResult = db.prepare(`PRAGMA table_info("${tableName}")`).all();
|
|
1643
|
+
const columns = columnsResult.map((col) => col.name);
|
|
1644
|
+
const countResult = db.prepare(`SELECT COUNT(*) as count FROM "${tableName}"`).get();
|
|
1645
|
+
const total = countResult.count;
|
|
1646
|
+
let data;
|
|
1647
|
+
if (tableName === "auth_users") {
|
|
1648
|
+
data = db.prepare(`SELECT id, email, email_verified, created_at, updated_at, metadata FROM "${tableName}" LIMIT ? OFFSET ?`).all(limit, offset);
|
|
1649
|
+
} else {
|
|
1650
|
+
data = db.prepare(`SELECT * FROM "${tableName}" LIMIT ? OFFSET ?`).all(limit, offset);
|
|
1651
|
+
}
|
|
1652
|
+
const visibleColumns = tableName === "auth_users" ? columns.filter((c2) => c2 !== "password_hash") : columns;
|
|
1653
|
+
return c.json({ data, columns: visibleColumns, total });
|
|
1654
|
+
} catch (err) {
|
|
1655
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1658
|
+
app.get("/api/auth/schema", (c) => {
|
|
1659
|
+
try {
|
|
1660
|
+
const db = dbManager2.emulatorDb;
|
|
1661
|
+
const tables = ["auth_users", "auth_sessions"].map((tableName) => {
|
|
1662
|
+
const columnsResult = db.prepare(`PRAGMA table_info("${tableName}")`).all();
|
|
1663
|
+
const visibleColumns = tableName === "auth_users" ? columnsResult.filter((col) => col.name !== "password_hash") : columnsResult;
|
|
1664
|
+
return {
|
|
1665
|
+
name: tableName,
|
|
1666
|
+
columns: visibleColumns.map((col) => ({
|
|
1667
|
+
name: col.name,
|
|
1668
|
+
type: col.type,
|
|
1669
|
+
notNull: col.notnull === 1,
|
|
1670
|
+
primaryKey: col.pk === 1
|
|
1671
|
+
}))
|
|
1672
|
+
};
|
|
1673
|
+
});
|
|
1674
|
+
return c.json({ tables });
|
|
1675
|
+
} catch (err) {
|
|
1676
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1677
|
+
}
|
|
1678
|
+
});
|
|
1679
|
+
app.post("/api/auth/query", async (c) => {
|
|
1680
|
+
const body = await c.req.json();
|
|
1681
|
+
const { query } = body;
|
|
1682
|
+
if (!query) {
|
|
1683
|
+
return c.json({ error: "Query is required" }, 400);
|
|
1684
|
+
}
|
|
1685
|
+
const normalizedQuery = query.trim().toUpperCase();
|
|
1686
|
+
if (!normalizedQuery.startsWith("SELECT")) {
|
|
1687
|
+
return c.json({ error: "Only SELECT queries are allowed on auth tables" }, 400);
|
|
1688
|
+
}
|
|
1689
|
+
const allowedTables = ["auth_users", "auth_sessions"];
|
|
1690
|
+
const hasDisallowedTable = !allowedTables.some((table) => query.toLowerCase().includes(`from ${table}`) || query.toLowerCase().includes(`join ${table}`));
|
|
1691
|
+
if (hasDisallowedTable) {
|
|
1692
|
+
return c.json({
|
|
1693
|
+
error: "Query must reference auth tables (auth_users or auth_sessions)"
|
|
1694
|
+
}, 400);
|
|
1695
|
+
}
|
|
1696
|
+
try {
|
|
1697
|
+
const db = dbManager2.emulatorDb;
|
|
1698
|
+
const startTime = Date.now();
|
|
1699
|
+
const stmt = db.prepare(query);
|
|
1700
|
+
const results = stmt.all();
|
|
1701
|
+
const sanitizedResults = results.map((row) => {
|
|
1702
|
+
const { password_hash: _, ...rest } = row;
|
|
1703
|
+
return rest;
|
|
1704
|
+
});
|
|
1705
|
+
const duration = Date.now() - startTime;
|
|
1706
|
+
return c.json({
|
|
1707
|
+
results: sanitizedResults,
|
|
1708
|
+
success: true,
|
|
1709
|
+
meta: {
|
|
1710
|
+
duration,
|
|
1711
|
+
rows_read: results.length,
|
|
1712
|
+
rows_written: 0
|
|
1713
|
+
}
|
|
1714
|
+
});
|
|
1715
|
+
} catch (err) {
|
|
1716
|
+
return c.json({
|
|
1717
|
+
results: [],
|
|
1718
|
+
success: false,
|
|
1719
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1720
|
+
meta: { duration: 0, rows_read: 0, rows_written: 0 }
|
|
1721
|
+
}, 400);
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
app.get("/api/auth/settings", (c) => {
|
|
1725
|
+
try {
|
|
1726
|
+
const db = dbManager2.emulatorDb;
|
|
1727
|
+
const settings = db.prepare("SELECT * FROM auth_settings WHERE id = 1").get();
|
|
1728
|
+
if (!settings) {
|
|
1729
|
+
return c.json({
|
|
1730
|
+
sessionTokenExpiry: 604800,
|
|
1731
|
+
allowSignups: true,
|
|
1732
|
+
requireEmailVerification: false,
|
|
1733
|
+
requireName: false
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
return c.json({
|
|
1737
|
+
sessionTokenExpiry: settings.session_token_expiry,
|
|
1738
|
+
allowSignups: settings.allow_signups === 1,
|
|
1739
|
+
requireEmailVerification: settings.require_email_verification === 1,
|
|
1740
|
+
requireName: settings.require_name === 1
|
|
1741
|
+
});
|
|
1742
|
+
} catch (err) {
|
|
1743
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1746
|
+
app.patch("/api/auth/settings", async (c) => {
|
|
1747
|
+
try {
|
|
1748
|
+
const body = await c.req.json();
|
|
1749
|
+
const db = dbManager2.emulatorDb;
|
|
1750
|
+
const updates = [];
|
|
1751
|
+
const values = [];
|
|
1752
|
+
if (body.sessionTokenExpiry !== void 0) {
|
|
1753
|
+
updates.push("session_token_expiry = ?");
|
|
1754
|
+
values.push(body.sessionTokenExpiry);
|
|
1755
|
+
}
|
|
1756
|
+
if (body.allowSignups !== void 0) {
|
|
1757
|
+
updates.push("allow_signups = ?");
|
|
1758
|
+
values.push(body.allowSignups ? 1 : 0);
|
|
1759
|
+
}
|
|
1760
|
+
if (body.requireEmailVerification !== void 0) {
|
|
1761
|
+
updates.push("require_email_verification = ?");
|
|
1762
|
+
values.push(body.requireEmailVerification ? 1 : 0);
|
|
1763
|
+
}
|
|
1764
|
+
if (body.requireName !== void 0) {
|
|
1765
|
+
updates.push("require_name = ?");
|
|
1766
|
+
values.push(body.requireName ? 1 : 0);
|
|
1767
|
+
}
|
|
1768
|
+
if (updates.length > 0) {
|
|
1769
|
+
updates.push("updated_at = strftime('%s', 'now')");
|
|
1770
|
+
const sql = `UPDATE auth_settings SET ${updates.join(", ")} WHERE id = 1`;
|
|
1771
|
+
db.prepare(sql).run(...values);
|
|
1772
|
+
}
|
|
1773
|
+
const settings = db.prepare("SELECT * FROM auth_settings WHERE id = 1").get();
|
|
1774
|
+
return c.json({
|
|
1775
|
+
sessionTokenExpiry: settings.session_token_expiry,
|
|
1776
|
+
allowSignups: settings.allow_signups === 1,
|
|
1777
|
+
requireEmailVerification: settings.require_email_verification === 1,
|
|
1778
|
+
requireName: settings.require_name === 1
|
|
1779
|
+
});
|
|
1780
|
+
} catch (err) {
|
|
1781
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1282
1785
|
app.post("/api/db/:binding/query", async (c) => {
|
|
1283
1786
|
const binding = c.req.param("binding");
|
|
1284
1787
|
const resourceName = getDbResourceName(binding);
|
|
@@ -1501,7 +2004,7 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
1501
2004
|
}
|
|
1502
2005
|
try {
|
|
1503
2006
|
const db = dbManager2.emulatorDb;
|
|
1504
|
-
const execution = db.prepare(`SELECT id, workflow_name, status, error, started_at, completed_at, created_at
|
|
2007
|
+
const execution = db.prepare(`SELECT id, workflow_name, status, input, output, error, started_at, completed_at, created_at
|
|
1505
2008
|
FROM workflow_executions
|
|
1506
2009
|
WHERE id = ?`).get(executionId);
|
|
1507
2010
|
if (!execution) {
|
|
@@ -1515,6 +2018,8 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
1515
2018
|
execution: {
|
|
1516
2019
|
id: execution.id,
|
|
1517
2020
|
status: execution.status.toUpperCase(),
|
|
2021
|
+
input: execution.input ? JSON.parse(execution.input) : null,
|
|
2022
|
+
output: execution.output ? JSON.parse(execution.output) : null,
|
|
1518
2023
|
startedAt: execution.started_at ? new Date(execution.started_at * 1e3).toISOString() : null,
|
|
1519
2024
|
completedAt: execution.completed_at ? new Date(execution.completed_at * 1e3).toISOString() : null,
|
|
1520
2025
|
durationMs: execution.started_at && execution.completed_at ? (execution.completed_at - execution.started_at) * 1e3 : null,
|
|
@@ -1593,8 +2098,12 @@ function createDbHandler(getDatabase) {
|
|
|
1593
2098
|
const startTime = Date.now();
|
|
1594
2099
|
try {
|
|
1595
2100
|
const body = await c.req.json();
|
|
1596
|
-
const { bindingName, method, query, params, statements } = body;
|
|
1597
|
-
const
|
|
2101
|
+
const { bindingName, databaseId, method, query, params, statements } = body;
|
|
2102
|
+
const dbName = bindingName ?? databaseId;
|
|
2103
|
+
if (!dbName) {
|
|
2104
|
+
return c.json({ error: "Missing bindingName or databaseId" }, 400);
|
|
2105
|
+
}
|
|
2106
|
+
const db = getDatabase(dbName);
|
|
1598
2107
|
if (method === "prepare" && query) {
|
|
1599
2108
|
const stmt = db.prepare(query);
|
|
1600
2109
|
const isSelect = query.trim().toUpperCase().startsWith("SELECT");
|
|
@@ -1759,8 +2268,10 @@ function createQueueHandlers(db) {
|
|
|
1759
2268
|
try {
|
|
1760
2269
|
const body = await c.req.json();
|
|
1761
2270
|
const { messageId, deliveryId } = body;
|
|
1762
|
-
const
|
|
1763
|
-
|
|
2271
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2272
|
+
const result = db.prepare(`UPDATE queue_messages
|
|
2273
|
+
SET status = 'acknowledged', updated_at = ?
|
|
2274
|
+
WHERE id = ? AND delivery_id = ?`).run(now, messageId, deliveryId);
|
|
1764
2275
|
if (result.changes === 0) {
|
|
1765
2276
|
return c.json({ success: false, error: "Message not found or already processed" }, 404);
|
|
1766
2277
|
}
|
|
@@ -1835,7 +2346,7 @@ function createQueueProcessor(db, queueBindings, workerUrl) {
|
|
|
1835
2346
|
body: row.payload
|
|
1836
2347
|
});
|
|
1837
2348
|
if (response.ok) {
|
|
1838
|
-
db.prepare(`
|
|
2349
|
+
db.prepare(`UPDATE queue_messages SET status = 'acknowledged', updated_at = ? WHERE id = ?`).run(now, row.id);
|
|
1839
2350
|
} else {
|
|
1840
2351
|
db.prepare(`UPDATE queue_messages
|
|
1841
2352
|
SET status = 'pending', delivery_id = NULL, visible_at = ?, updated_at = ?
|
|
@@ -2072,6 +2583,13 @@ async function startMockServer(dbManager2, config, options = {}) {
|
|
|
2072
2583
|
app.post("/workflow/complete", workflowHandlers.completeHandler);
|
|
2073
2584
|
app.post("/workflow/fail", workflowHandlers.failHandler);
|
|
2074
2585
|
}
|
|
2586
|
+
if (config.auth) {
|
|
2587
|
+
const authHandlers = createAuthHandlers(dbManager2.emulatorDb);
|
|
2588
|
+
app.post("/auth/signup", authHandlers.signupHandler);
|
|
2589
|
+
app.post("/auth/signin", authHandlers.signinHandler);
|
|
2590
|
+
app.get("/auth/me", authHandlers.meHandler);
|
|
2591
|
+
app.post("/auth/signout", authHandlers.signoutHandler);
|
|
2592
|
+
}
|
|
2075
2593
|
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
2076
2594
|
if (options.dashboardEnabled !== false) {
|
|
2077
2595
|
createDashboardRoutes(app, dbManager2, config);
|
|
@@ -2094,6 +2612,7 @@ async function startMockServer(dbManager2, config, options = {}) {
|
|
|
2094
2612
|
var DEFAULT_MOCK_SERVER_PORT;
|
|
2095
2613
|
var init_mock_server = __esm({
|
|
2096
2614
|
"../emulator/dist/services/mock-server.js"() {
|
|
2615
|
+
init_auth_service();
|
|
2097
2616
|
init_dashboard_routes();
|
|
2098
2617
|
init_db_service();
|
|
2099
2618
|
init_queue_service();
|
|
@@ -2101,39 +2620,6 @@ var init_mock_server = __esm({
|
|
|
2101
2620
|
DEFAULT_MOCK_SERVER_PORT = 4003;
|
|
2102
2621
|
}
|
|
2103
2622
|
});
|
|
2104
|
-
|
|
2105
|
-
// ../emulator/dist/utils/logger.js
|
|
2106
|
-
function timestamp() {
|
|
2107
|
-
return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
|
|
2108
|
-
}
|
|
2109
|
-
function log(message) {
|
|
2110
|
-
console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.cyan}[ploy]${COLORS.reset} ${message}`);
|
|
2111
|
-
}
|
|
2112
|
-
function success(message) {
|
|
2113
|
-
console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.green}[ploy]${COLORS.reset} ${message}`);
|
|
2114
|
-
}
|
|
2115
|
-
function error(message) {
|
|
2116
|
-
console.error(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.red}[ploy]${COLORS.reset} ${message}`);
|
|
2117
|
-
}
|
|
2118
|
-
function debug(message, verbose) {
|
|
2119
|
-
if (verbose) {
|
|
2120
|
-
console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.magenta}[ploy:debug]${COLORS.reset} ${message}`);
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
var COLORS;
|
|
2124
|
-
var init_logger = __esm({
|
|
2125
|
-
"../emulator/dist/utils/logger.js"() {
|
|
2126
|
-
COLORS = {
|
|
2127
|
-
reset: "\x1B[0m",
|
|
2128
|
-
dim: "\x1B[2m",
|
|
2129
|
-
cyan: "\x1B[36m",
|
|
2130
|
-
green: "\x1B[32m",
|
|
2131
|
-
yellow: "\x1B[33m",
|
|
2132
|
-
red: "\x1B[31m",
|
|
2133
|
-
magenta: "\x1B[35m"
|
|
2134
|
-
};
|
|
2135
|
-
}
|
|
2136
|
-
});
|
|
2137
2623
|
function getProjectHash(projectDir) {
|
|
2138
2624
|
return createHash("sha256").update(projectDir).digest("hex").slice(0, 12);
|
|
2139
2625
|
}
|
|
@@ -2243,6 +2729,49 @@ CREATE TABLE IF NOT EXISTS workflow_steps (
|
|
|
2243
2729
|
|
|
2244
2730
|
CREATE INDEX IF NOT EXISTS idx_workflow_steps_execution
|
|
2245
2731
|
ON workflow_steps(execution_id, step_index);
|
|
2732
|
+
|
|
2733
|
+
-- Auth users table
|
|
2734
|
+
CREATE TABLE IF NOT EXISTS auth_users (
|
|
2735
|
+
id TEXT PRIMARY KEY,
|
|
2736
|
+
email TEXT UNIQUE NOT NULL,
|
|
2737
|
+
email_verified INTEGER NOT NULL DEFAULT 0,
|
|
2738
|
+
password_hash TEXT NOT NULL,
|
|
2739
|
+
created_at TEXT NOT NULL,
|
|
2740
|
+
updated_at TEXT NOT NULL,
|
|
2741
|
+
metadata TEXT
|
|
2742
|
+
);
|
|
2743
|
+
|
|
2744
|
+
CREATE INDEX IF NOT EXISTS idx_auth_users_email
|
|
2745
|
+
ON auth_users(email);
|
|
2746
|
+
|
|
2747
|
+
-- Auth sessions table
|
|
2748
|
+
CREATE TABLE IF NOT EXISTS auth_sessions (
|
|
2749
|
+
id TEXT PRIMARY KEY,
|
|
2750
|
+
user_id TEXT NOT NULL,
|
|
2751
|
+
token_hash TEXT UNIQUE NOT NULL,
|
|
2752
|
+
expires_at TEXT NOT NULL,
|
|
2753
|
+
created_at TEXT NOT NULL,
|
|
2754
|
+
revoked INTEGER NOT NULL DEFAULT 0,
|
|
2755
|
+
FOREIGN KEY (user_id) REFERENCES auth_users(id) ON DELETE CASCADE
|
|
2756
|
+
);
|
|
2757
|
+
|
|
2758
|
+
CREATE INDEX IF NOT EXISTS idx_auth_sessions_user
|
|
2759
|
+
ON auth_sessions(user_id);
|
|
2760
|
+
CREATE INDEX IF NOT EXISTS idx_auth_sessions_hash
|
|
2761
|
+
ON auth_sessions(token_hash);
|
|
2762
|
+
|
|
2763
|
+
-- Auth settings table
|
|
2764
|
+
CREATE TABLE IF NOT EXISTS auth_settings (
|
|
2765
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
2766
|
+
session_token_expiry INTEGER NOT NULL DEFAULT 604800,
|
|
2767
|
+
allow_signups INTEGER NOT NULL DEFAULT 1,
|
|
2768
|
+
require_email_verification INTEGER NOT NULL DEFAULT 0,
|
|
2769
|
+
require_name INTEGER NOT NULL DEFAULT 0,
|
|
2770
|
+
updated_at INTEGER DEFAULT (strftime('%s', 'now'))
|
|
2771
|
+
);
|
|
2772
|
+
|
|
2773
|
+
-- Insert default settings if not exists
|
|
2774
|
+
INSERT OR IGNORE INTO auth_settings (id) VALUES (1);
|
|
2246
2775
|
`;
|
|
2247
2776
|
}
|
|
2248
2777
|
});
|
|
@@ -2597,6 +3126,38 @@ function createDevD1(databaseId, apiUrl) {
|
|
|
2597
3126
|
}
|
|
2598
3127
|
};
|
|
2599
3128
|
}
|
|
3129
|
+
function createDevPloyAuth(apiUrl) {
|
|
3130
|
+
return {
|
|
3131
|
+
async getUser(token) {
|
|
3132
|
+
try {
|
|
3133
|
+
const response = await fetch(`${apiUrl}/auth/me`, {
|
|
3134
|
+
headers: {
|
|
3135
|
+
Authorization: `Bearer ${token}`
|
|
3136
|
+
}
|
|
3137
|
+
});
|
|
3138
|
+
if (!response.ok) {
|
|
3139
|
+
return null;
|
|
3140
|
+
}
|
|
3141
|
+
const data = await response.json();
|
|
3142
|
+
return data.user;
|
|
3143
|
+
} catch {
|
|
3144
|
+
return null;
|
|
3145
|
+
}
|
|
3146
|
+
},
|
|
3147
|
+
async verifyToken(token) {
|
|
3148
|
+
try {
|
|
3149
|
+
const response = await fetch(`${apiUrl}/auth/me`, {
|
|
3150
|
+
headers: {
|
|
3151
|
+
Authorization: `Bearer ${token}`
|
|
3152
|
+
}
|
|
3153
|
+
});
|
|
3154
|
+
return response.ok;
|
|
3155
|
+
} catch {
|
|
3156
|
+
return false;
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
};
|
|
3160
|
+
}
|
|
2600
3161
|
async function initPloyForDev(config) {
|
|
2601
3162
|
if (process.env.NODE_ENV !== "development") {
|
|
2602
3163
|
return;
|
|
@@ -2605,6 +3166,56 @@ async function initPloyForDev(config) {
|
|
|
2605
3166
|
return;
|
|
2606
3167
|
}
|
|
2607
3168
|
globalThis.__PLOY_DEV_INITIALIZED__ = true;
|
|
3169
|
+
const cliMockServerUrl = process.env.PLOY_MOCK_SERVER_URL;
|
|
3170
|
+
if (cliMockServerUrl) {
|
|
3171
|
+
const configPath2 = config?.configPath || "./ploy.yaml";
|
|
3172
|
+
const projectDir2 = process.cwd();
|
|
3173
|
+
let ployConfig2;
|
|
3174
|
+
try {
|
|
3175
|
+
ployConfig2 = readPloyConfig2(projectDir2, configPath2);
|
|
3176
|
+
} catch {
|
|
3177
|
+
if (config?.bindings?.db) {
|
|
3178
|
+
ployConfig2 = { db: config.bindings.db };
|
|
3179
|
+
} else {
|
|
3180
|
+
return;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
if (config?.bindings?.db) {
|
|
3184
|
+
ployConfig2 = { ...ployConfig2, db: config.bindings.db };
|
|
3185
|
+
}
|
|
3186
|
+
const hasDbBindings2 = ployConfig2.db && Object.keys(ployConfig2.db).length > 0;
|
|
3187
|
+
const hasAuthConfig2 = !!ployConfig2.auth;
|
|
3188
|
+
if (!hasDbBindings2 && !hasAuthConfig2) {
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
const env2 = {};
|
|
3192
|
+
if (hasDbBindings2 && ployConfig2.db) {
|
|
3193
|
+
for (const [bindingName, databaseId] of Object.entries(ployConfig2.db)) {
|
|
3194
|
+
env2[bindingName] = createDevD1(databaseId, cliMockServerUrl);
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
if (hasAuthConfig2) {
|
|
3198
|
+
env2.PLOY_AUTH = createDevPloyAuth(cliMockServerUrl);
|
|
3199
|
+
}
|
|
3200
|
+
const context2 = { env: env2, cf: void 0, ctx: void 0 };
|
|
3201
|
+
globalThis.__PLOY_DEV_CONTEXT__ = context2;
|
|
3202
|
+
Object.defineProperty(globalThis, PLOY_CONTEXT_SYMBOL, {
|
|
3203
|
+
get() {
|
|
3204
|
+
return context2;
|
|
3205
|
+
},
|
|
3206
|
+
configurable: true
|
|
3207
|
+
});
|
|
3208
|
+
const bindingNames2 = Object.keys(env2);
|
|
3209
|
+
const features2 = [];
|
|
3210
|
+
if (bindingNames2.length > 0) {
|
|
3211
|
+
features2.push(`bindings: ${bindingNames2.join(", ")}`);
|
|
3212
|
+
}
|
|
3213
|
+
if (hasAuthConfig2) {
|
|
3214
|
+
features2.push("auth");
|
|
3215
|
+
}
|
|
3216
|
+
console.log(`[Ploy] Using CLI mock server at ${cliMockServerUrl} (${features2.join(", ")})`);
|
|
3217
|
+
return;
|
|
3218
|
+
}
|
|
2608
3219
|
const configPath = config?.configPath || "./ploy.yaml";
|
|
2609
3220
|
const projectDir = process.cwd();
|
|
2610
3221
|
let ployConfig;
|
|
@@ -2620,7 +3231,9 @@ async function initPloyForDev(config) {
|
|
|
2620
3231
|
if (config?.bindings?.db) {
|
|
2621
3232
|
ployConfig = { ...ployConfig, db: config.bindings.db };
|
|
2622
3233
|
}
|
|
2623
|
-
|
|
3234
|
+
const hasDbBindings = ployConfig.db && Object.keys(ployConfig.db).length > 0;
|
|
3235
|
+
const hasAuthConfig = !!ployConfig.auth;
|
|
3236
|
+
if (!hasDbBindings && !hasAuthConfig) {
|
|
2624
3237
|
return;
|
|
2625
3238
|
}
|
|
2626
3239
|
ensureDataDir(projectDir);
|
|
@@ -2628,8 +3241,14 @@ async function initPloyForDev(config) {
|
|
|
2628
3241
|
mockServer = await startMockServer(dbManager, ployConfig, {});
|
|
2629
3242
|
const apiUrl = `http://localhost:${mockServer.port}`;
|
|
2630
3243
|
const env = {};
|
|
2631
|
-
|
|
2632
|
-
|
|
3244
|
+
if (hasDbBindings && ployConfig.db) {
|
|
3245
|
+
for (const [bindingName, databaseId] of Object.entries(ployConfig.db)) {
|
|
3246
|
+
env[bindingName] = createDevD1(databaseId, apiUrl);
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
if (hasAuthConfig) {
|
|
3250
|
+
env.PLOY_AUTH = createDevPloyAuth(apiUrl);
|
|
3251
|
+
process.env.NEXT_PUBLIC_PLOY_AUTH_URL = `${apiUrl}/auth`;
|
|
2633
3252
|
}
|
|
2634
3253
|
const context = {
|
|
2635
3254
|
env,
|
|
@@ -2644,7 +3263,14 @@ async function initPloyForDev(config) {
|
|
|
2644
3263
|
configurable: true
|
|
2645
3264
|
});
|
|
2646
3265
|
const bindingNames = Object.keys(env);
|
|
2647
|
-
|
|
3266
|
+
const features = [];
|
|
3267
|
+
if (bindingNames.length > 0) {
|
|
3268
|
+
features.push(`bindings: ${bindingNames.join(", ")}`);
|
|
3269
|
+
}
|
|
3270
|
+
if (hasAuthConfig) {
|
|
3271
|
+
features.push("auth");
|
|
3272
|
+
}
|
|
3273
|
+
console.log(`[Ploy] Development context initialized with ${features.join(", ")}`);
|
|
2648
3274
|
console.log(`[Ploy] Mock server running at ${apiUrl}`);
|
|
2649
3275
|
const cleanup = async () => {
|
|
2650
3276
|
if (mockServer) {
|
|
@@ -4438,7 +5064,9 @@ async function startNextJsDev(options) {
|
|
|
4438
5064
|
env: {
|
|
4439
5065
|
...process.env,
|
|
4440
5066
|
// Set environment variable so initPloyForDev knows the mock server URL
|
|
4441
|
-
PLOY_MOCK_SERVER_URL: `http://localhost:${dashboard.port}
|
|
5067
|
+
PLOY_MOCK_SERVER_URL: `http://localhost:${dashboard.port}`,
|
|
5068
|
+
// Set PORT so initPloyForDev can construct the correct handler URL
|
|
5069
|
+
PORT: String(nextPort)
|
|
4442
5070
|
}
|
|
4443
5071
|
});
|
|
4444
5072
|
} else {
|
|
@@ -4448,7 +5076,8 @@ async function startNextJsDev(options) {
|
|
|
4448
5076
|
shell: true,
|
|
4449
5077
|
env: {
|
|
4450
5078
|
...process.env,
|
|
4451
|
-
PLOY_MOCK_SERVER_URL: `http://localhost:${dashboard.port}
|
|
5079
|
+
PLOY_MOCK_SERVER_URL: `http://localhost:${dashboard.port}`,
|
|
5080
|
+
PORT: String(nextPort)
|
|
4452
5081
|
}
|
|
4453
5082
|
});
|
|
4454
5083
|
}
|