@meetploy/cli 1.14.0 → 1.15.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/index.js CHANGED
@@ -54,30 +54,22 @@ async function detectMonorepo(projectDir) {
54
54
  const hasLerna = await fileExists(join(projectDir, "lerna.json"));
55
55
  if (hasLerna) {
56
56
  indicators.push("lerna.json");
57
- if (!type) {
58
- type = "lerna";
59
- }
57
+ type ??= "lerna";
60
58
  }
61
59
  const hasNx = await fileExists(join(projectDir, "nx.json"));
62
60
  if (hasNx) {
63
61
  indicators.push("nx.json");
64
- if (!type) {
65
- type = "nx";
66
- }
62
+ type ??= "nx";
67
63
  }
68
64
  const hasTurbo = await fileExists(join(projectDir, "turbo.json"));
69
65
  if (hasTurbo) {
70
66
  indicators.push("turbo.json");
71
- if (!type) {
72
- type = "turbo";
73
- }
67
+ type ??= "turbo";
74
68
  }
75
69
  const hasRush = await fileExists(join(projectDir, "rush.json"));
76
70
  if (hasRush) {
77
71
  indicators.push("rush.json");
78
- if (!type) {
79
- type = "rush";
80
- }
72
+ type ??= "rush";
81
73
  }
82
74
  return {
83
75
  isMonorepo: indicators.length > 0,
@@ -191,6 +183,19 @@ function validatePloyConfig(config, configFile = "ploy.yaml", options = {}) {
191
183
  validatedConfig.out = validateRelativePath(config.out, "out", configFile);
192
184
  validatedConfig.base = validateRelativePath(config.base, "base", configFile);
193
185
  validatedConfig.main = validateRelativePath(config.main, "main", configFile);
186
+ if (config.env !== void 0) {
187
+ if (typeof config.env !== "object" || config.env === null) {
188
+ throw new Error(`'env' in ${configFile} must be a map of KEY: value`);
189
+ }
190
+ for (const [key, value] of Object.entries(config.env)) {
191
+ if (!BINDING_NAME_REGEX.test(key)) {
192
+ throw new Error(`Invalid env key '${key}' in ${configFile}. Env keys must be uppercase with underscores (e.g., FOO, MY_VAR)`);
193
+ }
194
+ if (typeof value !== "string") {
195
+ throw new Error(`Env key '${key}' in ${configFile} must have a string value`);
196
+ }
197
+ }
198
+ }
194
199
  validateBindings(config.db, "db", configFile);
195
200
  validateBindings(config.queue, "queue", configFile);
196
201
  validateBindings(config.cache, "cache", configFile);
@@ -271,7 +276,50 @@ function readAndValidatePloyConfigSync(projectDir, configPath, validationOptions
271
276
  return validatePloyConfig(config, configFile, validationOptions);
272
277
  }
273
278
  function hasBindings(config) {
274
- return !!(config.db || config.queue || config.cache || config.state || config.workflow || config.ai || config.auth);
279
+ return !!(config.env ?? config.db ?? config.queue ?? config.cache ?? config.state ?? config.workflow ?? config.ai ?? config.auth);
280
+ }
281
+ function parseDotEnv(content) {
282
+ const result = {};
283
+ for (const line of content.split("\n")) {
284
+ const trimmed = line.trim();
285
+ if (!trimmed || trimmed.startsWith("#")) {
286
+ continue;
287
+ }
288
+ const eqIndex = trimmed.indexOf("=");
289
+ if (eqIndex === -1) {
290
+ continue;
291
+ }
292
+ const key = trimmed.slice(0, eqIndex).trim();
293
+ let value = trimmed.slice(eqIndex + 1).trim();
294
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
295
+ value = value.slice(1, -1);
296
+ }
297
+ result[key] = value;
298
+ }
299
+ return result;
300
+ }
301
+ function loadDotEnvSync(projectDir) {
302
+ const envPath = join(projectDir, ".env");
303
+ if (!existsSync(envPath)) {
304
+ return {};
305
+ }
306
+ const content = readFileSync(envPath, "utf-8");
307
+ return parseDotEnv(content);
308
+ }
309
+ function resolveEnvVars(configEnv, dotEnv, processEnv) {
310
+ const result = {};
311
+ for (const [key, value] of Object.entries(configEnv)) {
312
+ if (value.startsWith("$")) {
313
+ const refName = value.slice(1);
314
+ const resolved = dotEnv[refName] ?? processEnv[refName];
315
+ if (resolved !== void 0) {
316
+ result[key] = resolved;
317
+ }
318
+ } else {
319
+ result[key] = value;
320
+ }
321
+ }
322
+ return result;
275
323
  }
276
324
  function getWorkerEntryPoint(projectDir, config) {
277
325
  if (config.main) {
@@ -314,10 +362,13 @@ __export(cli_exports, {
314
362
  getWorkerEntryPoint: () => getWorkerEntryPoint,
315
363
  hasBindings: () => hasBindings,
316
364
  isPnpmWorkspace: () => isPnpmWorkspace,
365
+ loadDotEnvSync: () => loadDotEnvSync,
366
+ parseDotEnv: () => parseDotEnv,
317
367
  readAndValidatePloyConfig: () => readAndValidatePloyConfig,
318
368
  readAndValidatePloyConfigSync: () => readAndValidatePloyConfigSync,
319
369
  readPloyConfig: () => readPloyConfig,
320
370
  readPloyConfigSync: () => readPloyConfigSync,
371
+ resolveEnvVars: () => resolveEnvVars,
321
372
  validatePloyConfig: () => validatePloyConfig
322
373
  });
323
374
  var init_cli = __esm({
@@ -1043,7 +1094,7 @@ export async function executeWorkflow<TInput, TOutput, TEnv>(
1043
1094
  `;
1044
1095
  }
1045
1096
  });
1046
- function generateWrapperCode(config, mockServiceUrl) {
1097
+ function generateWrapperCode(config, mockServiceUrl, envVars) {
1047
1098
  const imports = [];
1048
1099
  const bindings = [];
1049
1100
  if (config.db) {
@@ -1133,6 +1184,11 @@ function generateWrapperCode(config, mockServiceUrl) {
1133
1184
  }
1134
1185
  }
1135
1186
  }` : "";
1187
+ const envVarsEntries = envVars && Object.keys(envVars).length > 0 ? Object.entries(envVars).map(([key, value]) => ` ${JSON.stringify(key)}: ${JSON.stringify(value)}`).join(",\n") : "";
1188
+ const envVarsCode = envVarsEntries ? `
1189
+ injectedEnv.vars = {
1190
+ ${envVarsEntries}
1191
+ };` : "";
1136
1192
  return `${imports.join("\n")}
1137
1193
 
1138
1194
  const ployBindings = {
@@ -1141,7 +1197,7 @@ ${bindings.join("\n")}
1141
1197
 
1142
1198
  export default {
1143
1199
  async fetch(request, env, ctx) {
1144
- const injectedEnv = { ...env, ...ployBindings };
1200
+ const injectedEnv = { ...env, ...ployBindings };${envVarsCode}
1145
1201
  ${workflowHandlerCode}
1146
1202
  ${queueHandlerCode}
1147
1203
 
@@ -1154,7 +1210,7 @@ export default {
1154
1210
 
1155
1211
  async scheduled(event, env, ctx) {
1156
1212
  if (userWorker.scheduled) {
1157
- const injectedEnv = { ...env, ...ployBindings };
1213
+ const injectedEnv = { ...env, ...ployBindings };${envVarsCode}
1158
1214
  return userWorker.scheduled(event, injectedEnv, ctx);
1159
1215
  }
1160
1216
  }
@@ -1209,8 +1265,8 @@ function createRuntimePlugin(_config) {
1209
1265
  };
1210
1266
  }
1211
1267
  async function bundleWorker(options) {
1212
- const { projectDir, tempDir, entryPoint, config, mockServiceUrl } = options;
1213
- const wrapperCode = generateWrapperCode(config, mockServiceUrl);
1268
+ const { projectDir, tempDir, entryPoint, config, mockServiceUrl, envVars } = options;
1269
+ const wrapperCode = generateWrapperCode(config, mockServiceUrl, envVars);
1214
1270
  const wrapperPath = join(tempDir, "wrapper.ts");
1215
1271
  writeFileSync(wrapperPath, wrapperCode);
1216
1272
  const bundlePath = join(tempDir, "worker.bundle.js");
@@ -1352,16 +1408,15 @@ function createFileWatcher(srcDir, onRebuild) {
1352
1408
  if (debounceTimer) {
1353
1409
  clearTimeout(debounceTimer);
1354
1410
  }
1355
- debounceTimer = setTimeout(async () => {
1411
+ debounceTimer = setTimeout(() => {
1356
1412
  if (isRebuilding) {
1357
1413
  return;
1358
1414
  }
1359
1415
  isRebuilding = true;
1360
- try {
1361
- await onRebuild();
1362
- } finally {
1416
+ onRebuild().finally(() => {
1363
1417
  isRebuilding = false;
1364
- }
1418
+ }).catch(() => {
1419
+ });
1365
1420
  }, 100);
1366
1421
  }
1367
1422
  return {
@@ -1402,7 +1457,7 @@ function createFileWatcher(srcDir, onRebuild) {
1402
1457
  debounceTimer = null;
1403
1458
  }
1404
1459
  if (watcher) {
1405
- watcher.close();
1460
+ void watcher.close();
1406
1461
  watcher = null;
1407
1462
  }
1408
1463
  }
@@ -1421,14 +1476,28 @@ var init_watcher = __esm({
1421
1476
  }
1422
1477
  });
1423
1478
 
1479
+ // ../emulator/dist/config/env.js
1480
+ function resolveEnvVars2(projectDir, config) {
1481
+ if (!config.env) {
1482
+ return {};
1483
+ }
1484
+ const dotEnv = loadDotEnvSync(projectDir);
1485
+ return resolveEnvVars(config.env, dotEnv, process.env);
1486
+ }
1487
+ var init_env = __esm({
1488
+ "../emulator/dist/config/env.js"() {
1489
+ init_cli();
1490
+ }
1491
+ });
1492
+
1424
1493
  // ../emulator/dist/config/ploy-config.js
1425
1494
  function readPloyConfig2(projectDir, configPath) {
1426
1495
  const config = readPloyConfigSync(projectDir, configPath);
1427
1496
  if (!config.kind) {
1428
- throw new Error(`Missing required field 'kind' in ${configPath || "ploy.yaml"}`);
1497
+ throw new Error(`Missing required field 'kind' in ${configPath ?? "ploy.yaml"}`);
1429
1498
  }
1430
1499
  if (config.kind !== "dynamic" && config.kind !== "worker") {
1431
- throw new Error(`Invalid kind '${config.kind}' in ${configPath || "ploy.yaml"}. Must be 'dynamic' or 'worker'`);
1500
+ throw new Error(`Invalid kind '${config.kind}' in ${configPath ?? "ploy.yaml"}. Must be 'dynamic' or 'worker'`);
1432
1501
  }
1433
1502
  return config;
1434
1503
  }
@@ -1444,7 +1513,7 @@ function generateWorkerdConfig(options) {
1444
1513
  const { port, mockServicePort } = options;
1445
1514
  const services = [
1446
1515
  '(name = "main", worker = .worker)',
1447
- `(name = "mock", external = (address = "localhost:${mockServicePort}", http = ()))`,
1516
+ `(name = "mock", external = (address = "localhost:${String(mockServicePort)}", http = ()))`,
1448
1517
  '(name = "internet", network = (allow = ["public", "private", "local"], tlsOptions = (trustBrowserCas = true)))'
1449
1518
  ];
1450
1519
  const bindings = [
@@ -1458,7 +1527,7 @@ const config :Workerd.Config = (
1458
1527
  ${services.join(",\n ")}
1459
1528
  ],
1460
1529
  sockets = [
1461
- (name = "http", address = "*:${port}", http = (), service = "main")
1530
+ (name = "http", address = "*:${String(port)}", http = (), service = "main")
1462
1531
  ]
1463
1532
  );
1464
1533
 
@@ -1664,7 +1733,7 @@ function createAuthHandlers(db) {
1664
1733
  return c.json({ error: message }, 500);
1665
1734
  }
1666
1735
  };
1667
- const meHandler = async (c) => {
1736
+ const meHandler = (c) => {
1668
1737
  try {
1669
1738
  const cookieToken = getCookie(c, "ploy_session");
1670
1739
  const authHeader = c.req.header("Authorization");
@@ -1708,7 +1777,7 @@ function createAuthHandlers(db) {
1708
1777
  return c.json({ error: message }, 500);
1709
1778
  }
1710
1779
  };
1711
- const signoutHandler = async (c) => {
1780
+ const signoutHandler = (c) => {
1712
1781
  try {
1713
1782
  const sessionToken = getCookie(c, "ploy_session");
1714
1783
  if (sessionToken) {
@@ -1823,8 +1892,8 @@ function createDashboardRoutes(app, dbManager2, config) {
1823
1892
  if (tableName !== "auth_users" && tableName !== "auth_sessions") {
1824
1893
  return c.json({ error: "Table not found" }, 404);
1825
1894
  }
1826
- const limit = parseInt(c.req.query("limit") || "50", 10);
1827
- const offset = parseInt(c.req.query("offset") || "0", 10);
1895
+ const limit = parseInt(c.req.query("limit") ?? "50", 10);
1896
+ const offset = parseInt(c.req.query("offset") ?? "0", 10);
1828
1897
  try {
1829
1898
  const db = dbManager2.emulatorDb;
1830
1899
  const columnsResult = db.prepare(`PRAGMA table_info("${tableName}")`).all();
@@ -2036,8 +2105,8 @@ function createDashboardRoutes(app, dbManager2, config) {
2036
2105
  return c.json({ error: `Database binding '${binding}' not found` }, 404);
2037
2106
  }
2038
2107
  const tableName = c.req.param("tableName");
2039
- const limit = parseInt(c.req.query("limit") || "50", 10);
2040
- const offset = parseInt(c.req.query("offset") || "0", 10);
2108
+ const limit = parseInt(c.req.query("limit") ?? "50", 10);
2109
+ const offset = parseInt(c.req.query("offset") ?? "0", 10);
2041
2110
  try {
2042
2111
  const db = dbManager2.getD1Database(resourceName);
2043
2112
  const columnsResult = db.prepare(`PRAGMA table_info("${tableName}")`).all();
@@ -2118,7 +2187,7 @@ function createDashboardRoutes(app, dbManager2, config) {
2118
2187
  app.get("/api/queue/:binding/messages", (c) => {
2119
2188
  const binding = c.req.param("binding");
2120
2189
  const queueName = config.queue?.[binding];
2121
- const limit = parseInt(c.req.query("limit") || "10", 10);
2190
+ const limit = parseInt(c.req.query("limit") ?? "10", 10);
2122
2191
  if (!queueName) {
2123
2192
  return c.json({ error: "Queue not found" }, 404);
2124
2193
  }
@@ -2145,8 +2214,8 @@ function createDashboardRoutes(app, dbManager2, config) {
2145
2214
  app.get("/api/cache/:binding/entries", (c) => {
2146
2215
  const binding = c.req.param("binding");
2147
2216
  const cacheName = config.cache?.[binding];
2148
- const limit = parseInt(c.req.query("limit") || "20", 10);
2149
- const offset = parseInt(c.req.query("offset") || "0", 10);
2217
+ const limit = parseInt(c.req.query("limit") ?? "20", 10);
2218
+ const offset = parseInt(c.req.query("offset") ?? "0", 10);
2150
2219
  const now = Math.floor(Date.now() / 1e3);
2151
2220
  if (!cacheName) {
2152
2221
  return c.json({ error: "Cache binding not found" }, 404);
@@ -2178,9 +2247,9 @@ function createDashboardRoutes(app, dbManager2, config) {
2178
2247
  app.get("/api/state/:binding/entries", (c) => {
2179
2248
  const binding = c.req.param("binding");
2180
2249
  const stateName = config.state?.[binding];
2181
- const limit = parseInt(c.req.query("limit") || "20", 10);
2182
- const offset = parseInt(c.req.query("offset") || "0", 10);
2183
- const search = c.req.query("search") || "";
2250
+ const limit = parseInt(c.req.query("limit") ?? "20", 10);
2251
+ const offset = parseInt(c.req.query("offset") ?? "0", 10);
2252
+ const search = c.req.query("search") ?? "";
2184
2253
  if (!stateName) {
2185
2254
  return c.json({ error: "State binding not found" }, 404);
2186
2255
  }
@@ -2210,7 +2279,7 @@ function createDashboardRoutes(app, dbManager2, config) {
2210
2279
  app.get("/api/workflow/:binding/executions", (c) => {
2211
2280
  const binding = c.req.param("binding");
2212
2281
  const workflowConfig = config.workflow?.[binding];
2213
- const limit = parseInt(c.req.query("limit") || "20", 10);
2282
+ const limit = parseInt(c.req.query("limit") ?? "20", 10);
2214
2283
  if (!workflowConfig) {
2215
2284
  return c.json({ error: "Workflow not found" }, 404);
2216
2285
  }
@@ -2473,7 +2542,7 @@ function createQueueHandlers(db) {
2473
2542
  const transaction = db.transaction(() => {
2474
2543
  for (const msg of messages) {
2475
2544
  const id = randomUUID();
2476
- const visibleAt = now + (msg.delaySeconds || 0);
2545
+ const visibleAt = now + (msg.delaySeconds ?? 0);
2477
2546
  insert.run(id, queueName, JSON.stringify(msg.payload), visibleAt);
2478
2547
  messageIds.push(id);
2479
2548
  }
@@ -3102,6 +3171,7 @@ var init_emulator = __esm({
3102
3171
  "../emulator/dist/emulator.js"() {
3103
3172
  init_bundler();
3104
3173
  init_watcher();
3174
+ init_env();
3105
3175
  init_ploy_config2();
3106
3176
  init_workerd_config();
3107
3177
  init_mock_server();
@@ -3119,6 +3189,7 @@ var init_emulator = __esm({
3119
3189
  workerdProcess = null;
3120
3190
  fileWatcher = null;
3121
3191
  queueProcessor = null;
3192
+ resolvedEnvVars = {};
3122
3193
  constructor(options = {}) {
3123
3194
  const port = options.port ?? 8787;
3124
3195
  this.options = {
@@ -3136,20 +3207,24 @@ var init_emulator = __esm({
3136
3207
  try {
3137
3208
  this.config = readPloyConfig2(this.projectDir, this.options.configPath);
3138
3209
  debug(`Loaded config: ${JSON.stringify(this.config)}`, this.options.verbose);
3210
+ this.resolvedEnvVars = resolveEnvVars2(this.projectDir, this.config);
3211
+ if (Object.keys(this.resolvedEnvVars).length > 0) {
3212
+ debug(`Resolved env vars: ${Object.keys(this.resolvedEnvVars).join(", ")}`, this.options.verbose);
3213
+ }
3139
3214
  this.tempDir = ensureTempDir(this.projectDir);
3140
3215
  ensureDataDir(this.projectDir);
3141
3216
  debug(`Temp dir: ${this.tempDir}`, this.options.verbose);
3142
3217
  this.dbManager = initializeDatabases(this.projectDir);
3143
3218
  debug("Initialized databases", this.options.verbose);
3144
- const workerUrl = `http://${this.options.host}:${this.options.port}`;
3219
+ const workerUrl = `http://${this.options.host}:${String(this.options.port)}`;
3145
3220
  const dashboardPort = this.options.dashboardPort;
3146
3221
  this.mockServer = await startMockServer(this.dbManager, this.config, {
3147
3222
  workerUrl,
3148
3223
  port: dashboardPort,
3149
3224
  dashboardEnabled: true
3150
3225
  });
3151
- debug(`Mock server started on port ${this.mockServer.port}`, this.options.verbose);
3152
- const mockServiceUrl = `http://localhost:${this.mockServer.port}`;
3226
+ debug(`Mock server started on port ${String(this.mockServer.port)}`, this.options.verbose);
3227
+ const mockServiceUrl = `http://localhost:${String(this.mockServer.port)}`;
3153
3228
  const entryPoint = getWorkerEntryPoint2(this.projectDir, this.config);
3154
3229
  debug(`Entry point: ${entryPoint}`, this.options.verbose);
3155
3230
  await this.bundle(entryPoint, mockServiceUrl);
@@ -3176,13 +3251,16 @@ var init_emulator = __esm({
3176
3251
  debug(`Watching ${srcDir} for changes`, this.options.verbose);
3177
3252
  }
3178
3253
  if (this.config.queue) {
3179
- const workerUrl2 = `http://${this.options.host}:${this.options.port}`;
3254
+ const workerUrl2 = `http://${this.options.host}:${String(this.options.port)}`;
3180
3255
  this.queueProcessor = createQueueProcessor(this.dbManager.emulatorDb, this.config.queue, workerUrl2);
3181
3256
  this.queueProcessor.start();
3182
3257
  debug("Queue processor started", this.options.verbose);
3183
3258
  }
3184
- success(`Emulator running at http://${this.options.host}:${this.options.port}`);
3185
- log(` Dashboard: http://${this.options.host}:${this.mockServer.port}`);
3259
+ success(`Emulator running at http://${this.options.host}:${String(this.options.port)}`);
3260
+ log(` Dashboard: http://${this.options.host}:${String(this.mockServer.port)}`);
3261
+ if (Object.keys(this.resolvedEnvVars).length > 0) {
3262
+ log(` Env vars: ${Object.keys(this.resolvedEnvVars).join(", ")}`);
3263
+ }
3186
3264
  if (this.config.db) {
3187
3265
  log(` DB bindings: ${Object.keys(this.config.db).join(", ")}`);
3188
3266
  }
@@ -3211,7 +3289,8 @@ var init_emulator = __esm({
3211
3289
  tempDir: this.tempDir,
3212
3290
  entryPoint,
3213
3291
  config: this.config,
3214
- mockServiceUrl
3292
+ mockServiceUrl,
3293
+ envVars: this.resolvedEnvVars
3215
3294
  });
3216
3295
  }
3217
3296
  async startWorkerd(configPath) {
@@ -3230,16 +3309,14 @@ var init_emulator = __esm({
3230
3309
  shell: true,
3231
3310
  env: {
3232
3311
  ...process.env,
3233
- PATH: `${workerdBinDir}:${process.env.PATH || ""}`
3312
+ PATH: `${workerdBinDir}:${process.env.PATH ?? ""}`
3234
3313
  }
3235
3314
  });
3236
3315
  let started = false;
3237
3316
  let stderrOutput = "";
3238
3317
  this.workerdProcess.stdout?.on("data", (data) => {
3239
3318
  const output = data.toString();
3240
- if (this.options.verbose) {
3241
- process.stdout.write(output);
3242
- }
3319
+ log(`[workerd stdout] ${output.trim()}`);
3243
3320
  if (!started && (output.includes("Listening") || output.includes("running"))) {
3244
3321
  started = true;
3245
3322
  resolve(void 0);
@@ -3267,13 +3344,13 @@ var init_emulator = __esm({
3267
3344
  });
3268
3345
  this.workerdProcess.on("exit", (code) => {
3269
3346
  if (code !== 0 && code !== null) {
3270
- error(`workerd exited with code ${code}`);
3347
+ error(`workerd exited with code ${String(code)}`);
3271
3348
  if (stderrOutput) {
3272
3349
  error(`workerd stderr: ${stderrOutput.trim()}`);
3273
3350
  }
3274
3351
  }
3275
3352
  if (!started) {
3276
- reject(new Error(`workerd exited with code ${code}`));
3353
+ reject(new Error(`workerd exited with code ${String(code)}`));
3277
3354
  }
3278
3355
  });
3279
3356
  setTimeout(() => {
@@ -3310,10 +3387,11 @@ var init_emulator = __esm({
3310
3387
  return this.projectDir;
3311
3388
  }
3312
3389
  setupSignalHandlers() {
3313
- const handler = async () => {
3390
+ const handler = () => {
3314
3391
  log("\nShutting down...");
3315
- await this.stop();
3316
- process.exit(0);
3392
+ void this.stop().then(() => {
3393
+ process.exit(0);
3394
+ });
3317
3395
  };
3318
3396
  process.on("SIGINT", handler);
3319
3397
  process.on("SIGTERM", handler);
@@ -3428,7 +3506,7 @@ function createDevD1(databaseId, apiUrl) {
3428
3506
  async batch(statements) {
3429
3507
  const stmts = statements.map((s) => {
3430
3508
  const data = s.__db_data;
3431
- return data || s;
3509
+ return data ?? s;
3432
3510
  });
3433
3511
  const response = await fetch(`${apiUrl}/db`, {
3434
3512
  method: "POST",
@@ -3488,7 +3566,7 @@ async function initPloyForDev(config) {
3488
3566
  globalThis.__PLOY_DEV_INITIALIZED__ = true;
3489
3567
  const cliMockServerUrl = process.env.PLOY_MOCK_SERVER_URL;
3490
3568
  if (cliMockServerUrl) {
3491
- const configPath2 = config?.configPath || "./ploy.yaml";
3569
+ const configPath2 = config?.configPath ?? "./ploy.yaml";
3492
3570
  const projectDir2 = process.cwd();
3493
3571
  let ployConfig2;
3494
3572
  try {
@@ -3536,7 +3614,7 @@ async function initPloyForDev(config) {
3536
3614
  console.log(`[Ploy] Using CLI mock server at ${cliMockServerUrl} (${features2.join(", ")})`);
3537
3615
  return;
3538
3616
  }
3539
- const configPath = config?.configPath || "./ploy.yaml";
3617
+ const configPath = config?.configPath ?? "./ploy.yaml";
3540
3618
  const projectDir = process.cwd();
3541
3619
  let ployConfig;
3542
3620
  try {
@@ -3559,7 +3637,7 @@ async function initPloyForDev(config) {
3559
3637
  ensureDataDir(projectDir);
3560
3638
  dbManager = initializeDatabases(projectDir);
3561
3639
  mockServer = await startMockServer(dbManager, ployConfig, {});
3562
- const apiUrl = `http://localhost:${mockServer.port}`;
3640
+ const apiUrl = `http://localhost:${String(mockServer.port)}`;
3563
3641
  const env = {};
3564
3642
  if (hasDbBindings && ployConfig.db) {
3565
3643
  for (const [bindingName, databaseId] of Object.entries(ployConfig.db)) {
@@ -3607,13 +3685,15 @@ async function initPloyForDev(config) {
3607
3685
  dbManager.close();
3608
3686
  }
3609
3687
  });
3610
- process.on("SIGINT", async () => {
3611
- await cleanup();
3612
- process.exit(0);
3688
+ process.on("SIGINT", () => {
3689
+ void cleanup().then(() => {
3690
+ process.exit(0);
3691
+ });
3613
3692
  });
3614
- process.on("SIGTERM", async () => {
3615
- await cleanup();
3616
- process.exit(0);
3693
+ process.on("SIGTERM", () => {
3694
+ void cleanup().then(() => {
3695
+ process.exit(0);
3696
+ });
3617
3697
  });
3618
3698
  }
3619
3699
  var PLOY_CONTEXT_SYMBOL, mockServer, dbManager;
@@ -3650,7 +3730,7 @@ async function startDevDashboard(options = {}) {
3650
3730
  port,
3651
3731
  dashboardEnabled: true
3652
3732
  });
3653
- debug(`Mock server started on port ${mockServer2.port}`, verbose);
3733
+ debug(`Mock server started on port ${String(mockServer2.port)}`, verbose);
3654
3734
  if (config.db) {
3655
3735
  log(` DB bindings: ${Object.keys(config.db).join(", ")}`);
3656
3736
  }
@@ -3945,8 +4025,8 @@ async function listTables(options) {
3945
4025
  );
3946
4026
  if (error2 || !data) {
3947
4027
  throw new ApiClientError(
3948
- error2?.message || "Failed to fetch tables",
3949
- response?.status || 500
4028
+ error2?.message ?? "Failed to fetch tables",
4029
+ response.status
3950
4030
  );
3951
4031
  }
3952
4032
  const tablesResult = data;
@@ -3967,13 +4047,13 @@ async function listTables(options) {
3967
4047
  });
3968
4048
  if (schemaError || !schemaData) {
3969
4049
  throw new ApiClientError(
3970
- schemaError?.message || "Failed to fetch schema",
3971
- schemaResponse?.status || 500
4050
+ schemaError?.message ?? "Failed to fetch schema",
4051
+ schemaResponse.status
3972
4052
  );
3973
4053
  }
3974
4054
  const schemaResult = schemaData;
3975
4055
  for (const table of schemaResult.tables) {
3976
- console.log(`Table: ${table.name}`);
4056
+ console.log(`Table: ${String(table.name)}`);
3977
4057
  console.log("-".repeat(40));
3978
4058
  console.log(
3979
4059
  " " + "COLUMN".padEnd(20) + "TYPE".padEnd(15) + "NULLABLE".padEnd(10) + "PK"
@@ -3983,7 +4063,7 @@ async function listTables(options) {
3983
4063
  const nullable = col.notNull ? "NO" : "YES";
3984
4064
  const pk = col.primaryKey ? "YES" : "";
3985
4065
  console.log(
3986
- " " + col.name.padEnd(20) + col.type.padEnd(15) + nullable.padEnd(10) + pk
4066
+ " " + String(col.name).padEnd(20) + String(col.type).padEnd(15) + nullable.padEnd(10) + pk
3987
4067
  );
3988
4068
  }
3989
4069
  console.log("");
@@ -3992,7 +4072,7 @@ async function listTables(options) {
3992
4072
  console.log("NAME".padEnd(30) + "ROWS".padEnd(12) + "SIZE");
3993
4073
  console.log("-".repeat(55));
3994
4074
  for (const table of tablesResult.tables) {
3995
- console.log(table.name.padEnd(30) + "-".padEnd(12) + "-");
4075
+ console.log(String(table.name).padEnd(30) + "-".padEnd(12) + "-");
3996
4076
  }
3997
4077
  }
3998
4078
  console.log("");
@@ -4044,23 +4124,23 @@ async function viewAnalytics(options) {
4044
4124
  );
4045
4125
  if (error2 || !data) {
4046
4126
  throw new ApiClientError(
4047
- error2?.message || "Failed to fetch analytics",
4048
- response?.status || 500
4127
+ error2?.message ?? "Failed to fetch analytics",
4128
+ response.status
4049
4129
  );
4050
4130
  }
4051
4131
  const insightsResult = data;
4052
4132
  console.log("Database Analytics");
4053
4133
  console.log("==================");
4054
4134
  console.log("");
4055
- console.log(` Period: ${insightsResult.timeRange}`);
4135
+ console.log(` Period: ${String(insightsResult.timeRange)}`);
4056
4136
  console.log(
4057
- ` Total Queries: ${insightsResult.summary.totalQueries.toLocaleString()}`
4137
+ ` Total Queries: ${String(insightsResult.summary.totalQueries.toLocaleString())}`
4058
4138
  );
4059
4139
  console.log(
4060
- ` Total Rows Read: ${insightsResult.summary.totalRowsRead.toLocaleString()}`
4140
+ ` Total Rows Read: ${String(insightsResult.summary.totalRowsRead.toLocaleString())}`
4061
4141
  );
4062
4142
  console.log(
4063
- ` Total Rows Written: ${insightsResult.summary.totalRowsWritten.toLocaleString()}`
4143
+ ` Total Rows Written: ${String(insightsResult.summary.totalRowsWritten.toLocaleString())}`
4064
4144
  );
4065
4145
  const avgDuration = insightsResult.summary.totalQueries > 0 ? insightsResult.summary.totalDurationMs / insightsResult.summary.totalQueries : 0;
4066
4146
  console.log(` Avg Query Duration: ${avgDuration.toFixed(2)}ms`);
@@ -4071,15 +4151,21 @@ async function viewAnalytics(options) {
4071
4151
  console.log("");
4072
4152
  for (let i = 0; i < insightsResult.topQueries.length; i++) {
4073
4153
  const insight = insightsResult.topQueries[i];
4074
- console.log(`${i + 1}. ${truncateQuery(insight.queryText, 60)}`);
4075
- console.log(` Calls: ${insight.totalCalls.toLocaleString()}`);
4076
- console.log(` Avg Duration: ${insight.avgDurationMs.toFixed(2)}ms`);
4077
4154
  console.log(
4078
- ` Total Duration: ${insight.totalDurationMs.toFixed(2)}ms`
4155
+ `${String(i + 1)}. ${truncateQuery(String(insight.queryText), 60)}`
4156
+ );
4157
+ console.log(` Calls: ${String(insight.totalCalls.toLocaleString())}`);
4158
+ console.log(
4159
+ ` Avg Duration: ${String(insight.avgDurationMs.toFixed(2))}ms`
4160
+ );
4161
+ console.log(
4162
+ ` Total Duration: ${String(insight.totalDurationMs.toFixed(2))}ms`
4079
4163
  );
4080
- console.log(` Rows Read: ${insight.totalRowsRead.toLocaleString()}`);
4081
4164
  console.log(
4082
- ` Rows Written: ${insight.totalRowsWritten.toLocaleString()}`
4165
+ ` Rows Read: ${String(insight.totalRowsRead.toLocaleString())}`
4166
+ );
4167
+ console.log(
4168
+ ` Rows Written: ${String(insight.totalRowsWritten.toLocaleString())}`
4083
4169
  );
4084
4170
  console.log("");
4085
4171
  }
@@ -4118,8 +4204,8 @@ async function listDatabases(options) {
4118
4204
  );
4119
4205
  if (error2 || !data) {
4120
4206
  throw new ApiClientError(
4121
- error2?.message || "Failed to list databases",
4122
- response?.status || 500
4207
+ error2?.message ?? "Failed to list databases",
4208
+ response.status
4123
4209
  );
4124
4210
  }
4125
4211
  const databases = data.databases;
@@ -4136,10 +4222,10 @@ async function listDatabases(options) {
4136
4222
  );
4137
4223
  console.log("-".repeat(80));
4138
4224
  for (const db of databases) {
4139
- const id = db.id.substring(0, Math.min(24, db.id.length));
4140
- const name = db.name.substring(0, Math.min(23, db.name.length)).padEnd(25);
4141
- const region = (db.region || "-").padEnd(15);
4142
- const created = db.createdAt ? new Date(db.createdAt).toLocaleDateString() : "-";
4225
+ const id = String(db.id).substring(0, Math.min(24, String(db.id).length));
4226
+ const name = String(db.name).substring(0, Math.min(23, String(db.name).length)).padEnd(25);
4227
+ const region = (String(db.region) || "-").padEnd(15);
4228
+ const created = db.createdAt ? new Date(String(db.createdAt)).toLocaleDateString() : "-";
4143
4229
  console.log(`${id} ${name}${region}${created}`);
4144
4230
  }
4145
4231
  console.log("");
@@ -4286,8 +4372,8 @@ async function retryDeployment(deploymentId) {
4286
4372
  );
4287
4373
  if (error2 || !data) {
4288
4374
  throw new ApiClientError(
4289
- error2?.message || "Failed to retry deployment",
4290
- response?.status || 500
4375
+ error2?.message ?? "Failed to retry deployment",
4376
+ response.status
4291
4377
  );
4292
4378
  }
4293
4379
  const retryResult = data;
@@ -4325,8 +4411,8 @@ async function viewDeploymentLogs(deploymentId, _options) {
4325
4411
  );
4326
4412
  if (error2 || !data) {
4327
4413
  throw new ApiClientError(
4328
- error2?.message || "Failed to fetch logs",
4329
- response?.status || 500
4414
+ error2?.message ?? "Failed to fetch logs",
4415
+ response.status
4330
4416
  );
4331
4417
  }
4332
4418
  const logsResult = data;
@@ -4363,8 +4449,8 @@ async function viewDeployment(deploymentId, _options) {
4363
4449
  );
4364
4450
  if (error2 || !data) {
4365
4451
  throw new ApiClientError(
4366
- error2?.message || "Failed to fetch deployment",
4367
- response?.status || 500
4452
+ error2?.message ?? "Failed to fetch deployment",
4453
+ response.status
4368
4454
  );
4369
4455
  }
4370
4456
  const deployment = data.deployment;
@@ -4426,14 +4512,14 @@ async function listDeployments(options) {
4426
4512
  query: {
4427
4513
  projectId: options.projectId,
4428
4514
  branch: options.branch,
4429
- limit: options.limit?.toString() || "20"
4515
+ limit: options.limit?.toString() ?? "20"
4430
4516
  }
4431
4517
  }
4432
4518
  });
4433
4519
  if (error2 || !data) {
4434
4520
  throw new ApiClientError(
4435
- error2?.message || "Failed to list deployments",
4436
- response?.status || 500
4521
+ error2?.message ?? "Failed to list deployments",
4522
+ response.status
4437
4523
  );
4438
4524
  }
4439
4525
  const deployments = data.deployments;
@@ -4452,7 +4538,7 @@ async function listDeployments(options) {
4452
4538
  for (const d of deployments) {
4453
4539
  const id = d.id.substring(0, Math.min(10, d.id.length));
4454
4540
  const status = d.status.padEnd(10);
4455
- const branchSource = d.branch || "-";
4541
+ const branchSource = d.branch ?? "-";
4456
4542
  const branch = branchSource.substring(0, Math.min(18, branchSource.length)).padEnd(20);
4457
4543
  const commit = d.commitSha ? d.commitSha.substring(0, Math.min(8, d.commitSha.length)) : "-";
4458
4544
  const created = new Date(d.createdAt).toLocaleDateString();
@@ -4705,7 +4791,9 @@ async function exchangeCodeForToken(apiUrl, code, redirectUri) {
4705
4791
  });
4706
4792
  if (!response.ok) {
4707
4793
  const errorBody = await response.text();
4708
- throw new Error(`Token exchange failed: ${response.status} - ${errorBody}`);
4794
+ throw new Error(
4795
+ `Token exchange failed: ${String(response.status)} - ${errorBody}`
4796
+ );
4709
4797
  }
4710
4798
  return await response.json();
4711
4799
  }
@@ -4729,43 +4817,46 @@ function createCallbackServer(expectedState, apiUrl) {
4729
4817
  },
4730
4818
  5 * 60 * 1e3
4731
4819
  );
4732
- server = createServer(async (req, res) => {
4733
- const reqUrl = new URL(
4734
- req.url || "/",
4735
- `http://${CALLBACK_HOST}:${CALLBACK_PORT}`
4736
- );
4737
- if (reqUrl.pathname !== "/callback") {
4738
- res.writeHead(404);
4739
- res.end("Not found");
4740
- return;
4741
- }
4742
- const code = reqUrl.searchParams.get("code");
4743
- const state = reqUrl.searchParams.get("state");
4744
- const error2 = reqUrl.searchParams.get("error");
4745
- const errorDescription = reqUrl.searchParams.get("error_description");
4746
- if (error2) {
4747
- clearTimeout(timeout);
4748
- res.writeHead(400, { "Content-Type": "text/html" });
4749
- res.end(`
4820
+ server = createServer((req, res) => {
4821
+ void (async () => {
4822
+ const reqUrl = new URL(
4823
+ req.url ?? "/",
4824
+ `http://${CALLBACK_HOST}:${String(CALLBACK_PORT)}`
4825
+ );
4826
+ if (reqUrl.pathname !== "/callback") {
4827
+ res.writeHead(404);
4828
+ res.end("Not found");
4829
+ return;
4830
+ }
4831
+ const code = reqUrl.searchParams.get("code");
4832
+ const state = reqUrl.searchParams.get("state");
4833
+ const error2 = reqUrl.searchParams.get("error");
4834
+ const errorDescription = reqUrl.searchParams.get("error_description");
4835
+ if (error2) {
4836
+ clearTimeout(timeout);
4837
+ res.writeHead(400, { "Content-Type": "text/html" });
4838
+ res.end(`
4750
4839
  <!DOCTYPE html>
4751
4840
  <html>
4752
4841
  <head><title>Login Failed</title></head>
4753
4842
  <body style="font-family: system-ui; padding: 40px; text-align: center;">
4754
4843
  <h1>Login Failed</h1>
4755
4844
  <p>Error: ${error2}</p>
4756
- <p>${errorDescription || ""}</p>
4845
+ <p>${errorDescription ?? ""}</p>
4757
4846
  <p>You can close this window.</p>
4758
4847
  </body>
4759
4848
  </html>
4760
4849
  `);
4761
- server?.close();
4762
- reject(new Error(`OAuth error: ${error2} - ${errorDescription || ""}`));
4763
- return;
4764
- }
4765
- if (!code) {
4766
- clearTimeout(timeout);
4767
- res.writeHead(400, { "Content-Type": "text/html" });
4768
- res.end(`
4850
+ server?.close();
4851
+ reject(
4852
+ new Error(`OAuth error: ${error2} - ${errorDescription ?? ""}`)
4853
+ );
4854
+ return;
4855
+ }
4856
+ if (!code) {
4857
+ clearTimeout(timeout);
4858
+ res.writeHead(400, { "Content-Type": "text/html" });
4859
+ res.end(`
4769
4860
  <!DOCTYPE html>
4770
4861
  <html>
4771
4862
  <head><title>Login Failed</title></head>
@@ -4776,14 +4867,14 @@ function createCallbackServer(expectedState, apiUrl) {
4776
4867
  </body>
4777
4868
  </html>
4778
4869
  `);
4779
- server?.close();
4780
- reject(new Error("No authorization code received"));
4781
- return;
4782
- }
4783
- if (state !== expectedState) {
4784
- clearTimeout(timeout);
4785
- res.writeHead(400, { "Content-Type": "text/html" });
4786
- res.end(`
4870
+ server?.close();
4871
+ reject(new Error("No authorization code received"));
4872
+ return;
4873
+ }
4874
+ if (state !== expectedState) {
4875
+ clearTimeout(timeout);
4876
+ res.writeHead(400, { "Content-Type": "text/html" });
4877
+ res.end(`
4787
4878
  <!DOCTYPE html>
4788
4879
  <html>
4789
4880
  <head><title>Login Failed</title></head>
@@ -4794,20 +4885,20 @@ function createCallbackServer(expectedState, apiUrl) {
4794
4885
  </body>
4795
4886
  </html>
4796
4887
  `);
4797
- server?.close();
4798
- reject(new Error("State mismatch - possible CSRF attack"));
4799
- return;
4800
- }
4801
- try {
4802
- const redirectUri = `http://${CALLBACK_HOST}:${CALLBACK_PORT}/callback`;
4803
- const tokenResponse = await exchangeCodeForToken(
4804
- apiUrl,
4805
- code,
4806
- redirectUri
4807
- );
4808
- clearTimeout(timeout);
4809
- res.writeHead(200, { "Content-Type": "text/html" });
4810
- res.end(`
4888
+ server?.close();
4889
+ reject(new Error("State mismatch - possible CSRF attack"));
4890
+ return;
4891
+ }
4892
+ try {
4893
+ const redirectUri = `http://${CALLBACK_HOST}:${String(CALLBACK_PORT)}/callback`;
4894
+ const tokenResponse = await exchangeCodeForToken(
4895
+ apiUrl,
4896
+ code,
4897
+ redirectUri
4898
+ );
4899
+ clearTimeout(timeout);
4900
+ res.writeHead(200, { "Content-Type": "text/html" });
4901
+ res.end(`
4811
4902
  <!DOCTYPE html>
4812
4903
  <html>
4813
4904
  <head><title>Login Successful</title></head>
@@ -4818,12 +4909,12 @@ function createCallbackServer(expectedState, apiUrl) {
4818
4909
  </body>
4819
4910
  </html>
4820
4911
  `);
4821
- server?.close();
4822
- resolve(tokenResponse);
4823
- } catch (err) {
4824
- clearTimeout(timeout);
4825
- res.writeHead(500, { "Content-Type": "text/html" });
4826
- res.end(`
4912
+ server?.close();
4913
+ resolve(tokenResponse);
4914
+ } catch (err) {
4915
+ clearTimeout(timeout);
4916
+ res.writeHead(500, { "Content-Type": "text/html" });
4917
+ res.end(`
4827
4918
  <!DOCTYPE html>
4828
4919
  <html>
4829
4920
  <head><title>Login Failed</title></head>
@@ -4835,15 +4926,16 @@ function createCallbackServer(expectedState, apiUrl) {
4835
4926
  </body>
4836
4927
  </html>
4837
4928
  `);
4838
- server?.close();
4839
- reject(err instanceof Error ? err : new Error(String(err)));
4840
- }
4929
+ server?.close();
4930
+ reject(err instanceof Error ? err : new Error(String(err)));
4931
+ }
4932
+ })();
4841
4933
  });
4842
4934
  server.on("error", (err) => {
4843
4935
  clearTimeout(timeout);
4844
4936
  if (err.code === "EADDRINUSE") {
4845
4937
  const portError = new Error(
4846
- `Port ${CALLBACK_PORT} is already in use. Please close any other process using this port and try again.`
4938
+ `Port ${String(CALLBACK_PORT)} is already in use. Please close any other process using this port and try again.`
4847
4939
  );
4848
4940
  reject(portError);
4849
4941
  rejectListening(portError);
@@ -4862,9 +4954,9 @@ function createCallbackServer(expectedState, apiUrl) {
4862
4954
  return { tokenPromise, waitForListening };
4863
4955
  }
4864
4956
  async function loginCommand(options = {}) {
4865
- const apiUrl = options.apiUrl || await getApiUrl();
4957
+ const apiUrl = options.apiUrl ?? await getApiUrl();
4866
4958
  const state = generateState();
4867
- const redirectUri = `http://${CALLBACK_HOST}:${CALLBACK_PORT}/callback`;
4959
+ const redirectUri = `http://${CALLBACK_HOST}:${String(CALLBACK_PORT)}/callback`;
4868
4960
  console.log("Starting login flow...");
4869
4961
  console.log("");
4870
4962
  const authUrl = new URL(`${apiUrl}/oauth/authorize`);
@@ -5160,6 +5252,12 @@ async function updateTsConfigInclude(cwd, outputPath) {
5160
5252
  function generateEnvType(config) {
5161
5253
  const imports = [];
5162
5254
  const properties = [];
5255
+ if (config.env && Object.keys(config.env).length > 0) {
5256
+ const varProps = Object.keys(config.env).map((key) => ` ${key}: string;`).join("\n");
5257
+ properties.push(` vars: {
5258
+ ${varProps}
5259
+ };`);
5260
+ }
5163
5261
  if (config.ai) {
5164
5262
  properties.push(" AI_URL: string;");
5165
5263
  properties.push(" AI_TOKEN: string;");
@@ -5235,7 +5333,7 @@ async function typesCommand(options = {}) {
5235
5333
  console.error("Error: ploy.yaml not found in current directory");
5236
5334
  process.exit(1);
5237
5335
  }
5238
- const hasBindings2 = config.ai || config.db || config.queue || config.cache || config.state || config.workflow;
5336
+ const hasBindings2 = config.env || config.ai || config.db || config.queue || config.cache || config.state || config.workflow;
5239
5337
  if (!hasBindings2) {
5240
5338
  console.log("No bindings found in ploy.yaml. Generating empty Env.");
5241
5339
  }