@meetploy/cli 1.6.0 → 1.8.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
@@ -1,184 +1,4332 @@
1
1
  #!/usr/bin/env node
2
- import { spawn } from "node:child_process";
3
- import { existsSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { buildCommand, parseBuildArgs } from "./commands/build.js";
6
- import { typesCommand, parseTypesArgs } from "./commands/types.js";
7
- const args = process.argv.slice(2);
8
- const command = args[0];
9
- if (!command) {
10
- console.error("Usage: ploy <command>");
11
- console.error("\nAvailable commands:");
12
- console.error(" build Build your Ploy project");
13
- console.error(" dev Start development server");
14
- console.error(" types Generate TypeScript types from ploy.yaml");
15
- process.exit(1);
16
- }
17
- function parseDevArgs(args) {
18
- const options = {
19
- port: undefined,
20
- host: undefined,
21
- config: undefined,
22
- watch: true,
23
- verbose: false,
24
- dashboardPort: undefined,
25
- };
26
- for (let i = 0; i < args.length; i++) {
27
- const arg = args[i];
28
- if (arg === "-p" || arg === "--port") {
29
- const value = args[++i];
30
- if (value) {
31
- options.port = parseInt(value, 10);
32
- }
2
+ import { createRequire } from 'module';
3
+ import { mkdir, writeFile, readFile, chmod, access } from 'fs/promises';
4
+ import { join, dirname } from 'path';
5
+ import { existsSync, readFileSync, readFile as readFile$1, mkdirSync, writeFileSync } from 'fs';
6
+ import { promisify } from 'util';
7
+ import { parse } from 'yaml';
8
+ import { build } from 'esbuild';
9
+ import { watch } from 'chokidar';
10
+ import { URL, fileURLToPath } from 'url';
11
+ import { randomBytes, randomUUID, createHash } from 'crypto';
12
+ import { serve } from '@hono/node-server';
13
+ import { Hono } from 'hono';
14
+ import { homedir, tmpdir } from 'os';
15
+ import Database from 'better-sqlite3';
16
+ import { spawn, exec } from 'child_process';
17
+ import createClient from 'openapi-fetch';
18
+ import { createServer } from 'http';
19
+
20
+ createRequire(import.meta.url);
21
+ var __defProp = Object.defineProperty;
22
+ var __getOwnPropNames = Object.getOwnPropertyNames;
23
+ var __esm = (fn, res) => function __init() {
24
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
25
+ };
26
+ var __export = (target, all) => {
27
+ for (var name in all)
28
+ __defProp(target, name, { get: all[name], enumerable: true });
29
+ };
30
+ async function isPnpmWorkspace(projectDir) {
31
+ try {
32
+ await access(join(projectDir, "pnpm-workspace.yaml"));
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+ function getAddDevDependencyCommand(packageManager, packages, isPnpmWorkspace2 = false) {
39
+ const packageList = packages.join(" ");
40
+ switch (packageManager) {
41
+ case "npm":
42
+ return `npm install --save-dev ${packageList}`;
43
+ case "yarn":
44
+ return `yarn add -D ${packageList}`;
45
+ case "pnpm":
46
+ return isPnpmWorkspace2 ? `pnpm add -w -D ${packageList}` : `pnpm add -D ${packageList}`;
47
+ }
48
+ }
49
+ function getRunCommand(packageManager, command2) {
50
+ switch (packageManager) {
51
+ case "npm":
52
+ return `npx ${command2}`;
53
+ case "yarn":
54
+ return `yarn ${command2}`;
55
+ case "pnpm":
56
+ return `pnpm ${command2}`;
57
+ }
58
+ }
59
+ var init_package_manager = __esm({
60
+ "../tools/dist/package-manager.js"() {
61
+ }
62
+ });
63
+ function validateBindings(bindings, bindingType, configFile) {
64
+ if (bindings === void 0) {
65
+ return;
66
+ }
67
+ if (typeof bindings !== "object" || bindings === null) {
68
+ throw new Error(`'${bindingType}' in ${configFile} must be an object`);
69
+ }
70
+ for (const [bindingName, resourceName] of Object.entries(bindings)) {
71
+ if (!BINDING_NAME_REGEX.test(bindingName)) {
72
+ throw new Error(`Invalid ${bindingType} binding name '${bindingName}' in ${configFile}. Binding names must be uppercase with underscores (e.g., DB, USERS_DB)`);
73
+ }
74
+ if (typeof resourceName !== "string") {
75
+ throw new Error(`${bindingType} binding '${bindingName}' in ${configFile} must have a string value`);
76
+ }
77
+ if (!RESOURCE_NAME_REGEX.test(resourceName)) {
78
+ throw new Error(`Invalid ${bindingType} resource name '${resourceName}' for binding '${bindingName}' in ${configFile}. Resource names must be lowercase with underscores (e.g., default, users_db)`);
79
+ }
80
+ }
81
+ }
82
+ function validateRelativePath(path, fieldName, configFile) {
83
+ if (path === void 0) {
84
+ return void 0;
85
+ }
86
+ if (typeof path !== "string") {
87
+ throw new Error(`'${fieldName}' in ${configFile} must be a string`);
88
+ }
89
+ const normalized = path.replace(/\/+$/, "");
90
+ if (normalized.startsWith("/")) {
91
+ throw new Error(`'${fieldName}' in ${configFile} must be a relative path, not absolute`);
92
+ }
93
+ if (normalized.includes("..")) {
94
+ throw new Error(`'${fieldName}' in ${configFile} cannot contain path traversal (..)`);
95
+ }
96
+ return normalized;
97
+ }
98
+ function validatePloyConfig(config, configFile = "ploy.yaml", options = {}) {
99
+ const { requireKind = false, requireBuildForStatic = true, allowedKinds = ["nextjs", "static", "dynamic", "worker"] } = options;
100
+ const validatedConfig = { ...config };
101
+ if (requireKind && !config.kind) {
102
+ throw new Error(`Missing required field 'kind' in ${configFile}`);
103
+ }
104
+ if (config.kind !== void 0) {
105
+ if (!allowedKinds.includes(config.kind)) {
106
+ throw new Error(`Invalid kind '${config.kind}' in ${configFile}. Must be one of: ${allowedKinds.join(", ")}`);
107
+ }
108
+ }
109
+ if (requireBuildForStatic && config.kind === "static" && !config.build) {
110
+ throw new Error(`Build command is required when kind is 'static' in ${configFile}`);
111
+ }
112
+ if (config.build !== void 0 && typeof config.build !== "string") {
113
+ throw new Error(`'build' in ${configFile} must be a string`);
114
+ }
115
+ validatedConfig.out = validateRelativePath(config.out, "out", configFile);
116
+ validatedConfig.base = validateRelativePath(config.base, "base", configFile);
117
+ validatedConfig.main = validateRelativePath(config.main, "main", configFile);
118
+ validateBindings(config.db, "db", configFile);
119
+ validateBindings(config.queue, "queue", configFile);
120
+ validateBindings(config.workflow, "workflow", configFile);
121
+ if (config.ai !== void 0 && typeof config.ai !== "boolean") {
122
+ throw new Error(`'ai' in ${configFile} must be a boolean`);
123
+ }
124
+ if (config.monorepo !== void 0 && typeof config.monorepo !== "boolean") {
125
+ throw new Error(`'monorepo' in ${configFile} must be a boolean`);
126
+ }
127
+ return validatedConfig;
128
+ }
129
+ async function readPloyConfig(projectDir, configPath) {
130
+ const configFile = configPath || "ploy.yaml";
131
+ const fullPath = join(projectDir, configFile);
132
+ try {
133
+ const content = await readFileAsync(fullPath, "utf-8");
134
+ return parse(content);
135
+ } catch (error2) {
136
+ if (error2 && typeof error2 === "object" && "code" in error2) {
137
+ if (error2.code === "ENOENT") {
138
+ return null;
139
+ }
140
+ }
141
+ throw error2;
142
+ }
143
+ }
144
+ function readPloyConfigSync(projectDir, configPath) {
145
+ const configFile = configPath || "ploy.yaml";
146
+ const fullPath = join(projectDir, configFile);
147
+ if (!existsSync(fullPath)) {
148
+ throw new Error(`Config file not found: ${fullPath}`);
149
+ }
150
+ const content = readFileSync(fullPath, "utf-8");
151
+ return parse(content);
152
+ }
153
+ async function readAndValidatePloyConfig(projectDir, configPath, validationOptions) {
154
+ const configFile = configPath || "ploy.yaml";
155
+ const config = await readPloyConfig(projectDir, configPath);
156
+ if (!config) {
157
+ return null;
158
+ }
159
+ return validatePloyConfig(config, configFile, validationOptions);
160
+ }
161
+ function readAndValidatePloyConfigSync(projectDir, configPath, validationOptions) {
162
+ const configFile = configPath || "ploy.yaml";
163
+ const config = readPloyConfigSync(projectDir, configPath);
164
+ return validatePloyConfig(config, configFile, validationOptions);
165
+ }
166
+ function hasBindings(config) {
167
+ return !!(config.db || config.queue || config.workflow || config.ai);
168
+ }
169
+ function getWorkerEntryPoint(projectDir, config) {
170
+ if (config.main) {
171
+ return join(projectDir, config.main);
172
+ }
173
+ const defaultPaths = [
174
+ join(projectDir, "index.ts"),
175
+ join(projectDir, "index.js"),
176
+ join(projectDir, "src", "index.ts"),
177
+ join(projectDir, "src", "index.js")
178
+ ];
179
+ for (const path of defaultPaths) {
180
+ if (existsSync(path)) {
181
+ return path;
182
+ }
183
+ }
184
+ throw new Error("Could not find worker entry point. Specify 'main' in ploy.yaml");
185
+ }
186
+ var readFileAsync, BINDING_NAME_REGEX, RESOURCE_NAME_REGEX;
187
+ var init_ploy_config = __esm({
188
+ "../tools/dist/ploy-config.js"() {
189
+ readFileAsync = promisify(readFile$1);
190
+ BINDING_NAME_REGEX = /^[A-Z][A-Z0-9_]*$/;
191
+ RESOURCE_NAME_REGEX = /^[a-z][a-z0-9_]*$/;
192
+ }
193
+ });
194
+
195
+ // ../tools/dist/cli.js
196
+ var cli_exports = {};
197
+ __export(cli_exports, {
198
+ getAddDevDependencyCommand: () => getAddDevDependencyCommand,
199
+ getRunCommand: () => getRunCommand,
200
+ getWorkerEntryPoint: () => getWorkerEntryPoint,
201
+ hasBindings: () => hasBindings,
202
+ isPnpmWorkspace: () => isPnpmWorkspace,
203
+ readAndValidatePloyConfig: () => readAndValidatePloyConfig,
204
+ readAndValidatePloyConfigSync: () => readAndValidatePloyConfigSync,
205
+ readPloyConfig: () => readPloyConfig,
206
+ readPloyConfigSync: () => readPloyConfigSync,
207
+ validatePloyConfig: () => validatePloyConfig
208
+ });
209
+ var init_cli = __esm({
210
+ "../tools/dist/cli.js"() {
211
+ init_package_manager();
212
+ init_ploy_config();
213
+ }
214
+ });
215
+
216
+ // ../shared/dist/d1-runtime.js
217
+ var DB_RUNTIME_CODE, DB_RUNTIME_CODE_PRODUCTION;
218
+ var init_d1_runtime = __esm({
219
+ "../shared/dist/d1-runtime.js"() {
220
+ DB_RUNTIME_CODE = `
221
+ interface DBResult {
222
+ results: unknown[];
223
+ success: boolean;
224
+ meta: {
225
+ duration: number;
226
+ rows_read: number;
227
+ rows_written: number;
228
+ };
229
+ }
230
+
231
+ interface DBPreparedStatement {
232
+ bind: (...values: unknown[]) => DBPreparedStatement;
233
+ run: () => Promise<DBResult>;
234
+ all: () => Promise<DBResult>;
235
+ first: (colName?: string) => Promise<unknown | null>;
236
+ raw: (options?: { columnNames?: boolean }) => Promise<unknown[][]>;
237
+ }
238
+
239
+ interface DBDatabase {
240
+ prepare: (query: string) => DBPreparedStatement;
241
+ dump: () => Promise<ArrayBuffer>;
242
+ batch: (statements: DBPreparedStatement[]) => Promise<DBResult[]>;
243
+ exec: (query: string) => Promise<DBResult>;
244
+ }
245
+
246
+ export function initializeDB(bindingName: string, serviceUrl: string): DBDatabase {
247
+ return {
248
+ prepare(query: string): DBPreparedStatement {
249
+ let boundParams: unknown[] = [];
250
+
251
+ const stmt: DBPreparedStatement = {
252
+ bind(...values: unknown[]) {
253
+ boundParams = values;
254
+ return this;
255
+ },
256
+
257
+ async run(): Promise<DBResult> {
258
+ const response = await fetch(serviceUrl, {
259
+ method: "POST",
260
+ headers: { "Content-Type": "application/json" },
261
+ body: JSON.stringify({
262
+ bindingName,
263
+ method: "prepare",
264
+ query,
265
+ params: boundParams,
266
+ }),
267
+ });
268
+
269
+ if (!response.ok) {
270
+ const errorText = await response.text();
271
+ throw new Error("DB query failed: " + errorText);
272
+ }
273
+
274
+ return await response.json();
275
+ },
276
+
277
+ async all(): Promise<DBResult> {
278
+ return await this.run();
279
+ },
280
+
281
+ async first(colName?: string): Promise<unknown | null> {
282
+ const result = await this.run();
283
+ if (result.results.length === 0) {
284
+ return null;
285
+ }
286
+
287
+ const firstRow = result.results[0] as Record<string, unknown>;
288
+ if (colName) {
289
+ return firstRow[colName] ?? null;
290
+ }
291
+ return firstRow;
292
+ },
293
+
294
+ async raw(options?: { columnNames?: boolean }): Promise<unknown[][]> {
295
+ const result = await this.run();
296
+ const rows = result.results as Record<string, unknown>[];
297
+
298
+ if (rows.length === 0) {
299
+ return [];
300
+ }
301
+
302
+ const keys = Object.keys(rows[0]);
303
+ const arrayRows = rows.map((row) => keys.map((key) => row[key]));
304
+
305
+ if (options?.columnNames) {
306
+ return [keys, ...arrayRows];
307
+ }
308
+
309
+ return arrayRows;
310
+ },
311
+ };
312
+
313
+ return stmt;
314
+ },
315
+
316
+ async dump(): Promise<ArrayBuffer> {
317
+ const response = await fetch(serviceUrl, {
318
+ method: "POST",
319
+ headers: { "Content-Type": "application/json" },
320
+ body: JSON.stringify({
321
+ bindingName,
322
+ method: "dump",
323
+ }),
324
+ });
325
+
326
+ if (!response.ok) {
327
+ throw new Error("DB dump failed: " + response.statusText);
328
+ }
329
+
330
+ return await response.arrayBuffer();
331
+ },
332
+
333
+ async batch(statements: DBPreparedStatement[]): Promise<DBResult[]> {
334
+ const results: DBResult[] = [];
335
+ for (const stmt of statements) {
336
+ results.push(await stmt.run());
337
+ }
338
+ return results;
339
+ },
340
+
341
+ async exec(query: string): Promise<DBResult> {
342
+ const response = await fetch(serviceUrl, {
343
+ method: "POST",
344
+ headers: { "Content-Type": "application/json" },
345
+ body: JSON.stringify({
346
+ bindingName,
347
+ method: "exec",
348
+ query,
349
+ }),
350
+ });
351
+
352
+ if (!response.ok) {
353
+ throw new Error("DB exec failed: " + response.statusText);
354
+ }
355
+
356
+ return await response.json();
357
+ },
358
+ };
359
+ }
360
+ `;
361
+ DB_RUNTIME_CODE_PRODUCTION = `export function initializeDB(databaseId, organizationId, serviceUrl) {
362
+ return {
363
+ prepare(query) {
364
+ let boundParams = [];
365
+ return {
366
+ bind(...values) {
367
+ boundParams = values;
368
+ return this;
369
+ },
370
+ async run() {
371
+ const response = await fetch(serviceUrl, {
372
+ method: "POST",
373
+ headers: { "Content-Type": "application/json" },
374
+ body: JSON.stringify({
375
+ databaseId,
376
+ organizationId,
377
+ method: "prepare",
378
+ query,
379
+ params: boundParams,
380
+ }),
381
+ });
382
+ if (!response.ok) {
383
+ const errorText = await response.text();
384
+ throw new Error(\`DB query failed: \${errorText}\`);
385
+ }
386
+ return await response.json();
387
+ },
388
+ async all() {
389
+ return await this.run();
390
+ },
391
+ async first(colName) {
392
+ const result = await this.run();
393
+ if (result.results.length === 0) {
394
+ return null;
395
+ }
396
+ const firstRow = result.results[0];
397
+ if (colName) {
398
+ return firstRow[colName] ?? null;
399
+ }
400
+ return firstRow;
401
+ },
402
+ async raw(options) {
403
+ const result = await this.run();
404
+ const rows = result.results;
405
+ if (rows.length === 0) {
406
+ return [];
407
+ }
408
+ const keys = Object.keys(rows[0]);
409
+ const arrayRows = rows.map((row) => keys.map((key) => row[key]));
410
+ if (options?.columnNames) {
411
+ return [keys, ...arrayRows];
412
+ }
413
+ return arrayRows;
414
+ },
415
+ };
416
+ },
417
+ async dump() {
418
+ const response = await fetch(serviceUrl, {
419
+ method: "POST",
420
+ headers: { "Content-Type": "application/json" },
421
+ body: JSON.stringify({
422
+ databaseId,
423
+ organizationId,
424
+ method: "dump",
425
+ }),
426
+ });
427
+ if (!response.ok) {
428
+ throw new Error(\`DB dump failed: \${response.statusText}\`);
429
+ }
430
+ return await response.arrayBuffer();
431
+ },
432
+ async batch(statements) {
433
+ const stmts = [];
434
+ for (const stmt of statements) {
435
+ const stmtData = stmt.__db_data;
436
+ if (stmtData) {
437
+ stmts.push(stmtData);
438
+ }
439
+ }
440
+ const response = await fetch(serviceUrl, {
441
+ method: "POST",
442
+ headers: { "Content-Type": "application/json" },
443
+ body: JSON.stringify({
444
+ databaseId,
445
+ organizationId,
446
+ method: "batch",
447
+ statements: stmts,
448
+ }),
449
+ });
450
+ if (!response.ok) {
451
+ throw new Error(\`DB batch failed: \${response.statusText}\`);
452
+ }
453
+ return await response.json();
454
+ },
455
+ async exec(query) {
456
+ const response = await fetch(serviceUrl, {
457
+ method: "POST",
458
+ headers: { "Content-Type": "application/json" },
459
+ body: JSON.stringify({
460
+ databaseId,
461
+ organizationId,
462
+ method: "exec",
463
+ query,
464
+ }),
465
+ });
466
+ if (!response.ok) {
467
+ throw new Error(\`DB exec failed: \${response.statusText}\`);
468
+ }
469
+ return await response.json();
470
+ },
471
+ };
472
+ }`;
473
+ }
474
+ });
475
+
476
+ // ../shared/dist/error.js
477
+ var init_error = __esm({
478
+ "../shared/dist/error.js"() {
479
+ }
480
+ });
481
+
482
+ // ../shared/dist/health-check.js
483
+ var init_health_check = __esm({
484
+ "../shared/dist/health-check.js"() {
485
+ }
486
+ });
487
+
488
+ // ../shared/dist/trace-event.js
489
+ var init_trace_event = __esm({
490
+ "../shared/dist/trace-event.js"() {
491
+ }
492
+ });
493
+
494
+ // ../shared/dist/url-validation.js
495
+ var init_url_validation = __esm({
496
+ "../shared/dist/url-validation.js"() {
497
+ }
498
+ });
499
+
500
+ // ../shared/dist/index.js
501
+ var init_dist = __esm({
502
+ "../shared/dist/index.js"() {
503
+ init_d1_runtime();
504
+ init_error();
505
+ init_health_check();
506
+ init_trace_event();
507
+ init_url_validation();
508
+ }
509
+ });
510
+
511
+ // ../emulator/dist/runtime/db-runtime.js
512
+ var init_db_runtime = __esm({
513
+ "../emulator/dist/runtime/db-runtime.js"() {
514
+ init_dist();
515
+ }
516
+ });
517
+
518
+ // ../emulator/dist/runtime/queue-runtime.js
519
+ var QUEUE_RUNTIME_CODE;
520
+ var init_queue_runtime = __esm({
521
+ "../emulator/dist/runtime/queue-runtime.js"() {
522
+ QUEUE_RUNTIME_CODE = `
523
+ interface QueueSendResult {
524
+ success: boolean;
525
+ messageId?: string;
526
+ error?: string;
527
+ }
528
+
529
+ interface QueueBatchSendResult {
530
+ success: boolean;
531
+ messageIds?: string[];
532
+ error?: string;
533
+ }
534
+
535
+ interface Queue<T = unknown> {
536
+ send: (payload: T, options?: { delaySeconds?: number }) => Promise<QueueSendResult>;
537
+ sendBatch: (messages: Array<{ payload: T; delaySeconds?: number }>) => Promise<QueueBatchSendResult>;
538
+ }
539
+
540
+ export function initializeQueue<T = unknown>(queueName: string, serviceUrl: string): Queue<T> {
541
+ return {
542
+ async send(payload: T, options?: { delaySeconds?: number }): Promise<QueueSendResult> {
543
+ const response = await fetch(serviceUrl + "/queue/send", {
544
+ method: "POST",
545
+ headers: { "Content-Type": "application/json" },
546
+ body: JSON.stringify({
547
+ queueName,
548
+ payload,
549
+ delaySeconds: options?.delaySeconds,
550
+ }),
551
+ });
552
+
553
+ if (!response.ok) {
554
+ const errorText = await response.text();
555
+ return { success: false, error: "Queue send failed: " + errorText };
556
+ }
557
+
558
+ return await response.json();
559
+ },
560
+
561
+ async sendBatch(messages: Array<{ payload: T; delaySeconds?: number }>): Promise<QueueBatchSendResult> {
562
+ const response = await fetch(serviceUrl + "/queue/batch-send", {
563
+ method: "POST",
564
+ headers: { "Content-Type": "application/json" },
565
+ body: JSON.stringify({
566
+ queueName,
567
+ messages,
568
+ }),
569
+ });
570
+
571
+ if (!response.ok) {
572
+ const errorText = await response.text();
573
+ return { success: false, error: "Queue batch send failed: " + errorText };
574
+ }
575
+
576
+ return await response.json();
577
+ },
578
+ };
579
+ }
580
+ `;
581
+ }
582
+ });
583
+
584
+ // ../emulator/dist/runtime/workflow-runtime.js
585
+ var WORKFLOW_RUNTIME_CODE;
586
+ var init_workflow_runtime = __esm({
587
+ "../emulator/dist/runtime/workflow-runtime.js"() {
588
+ WORKFLOW_RUNTIME_CODE = `
589
+ interface WorkflowExecution {
590
+ id: string;
591
+ status: "pending" | "running" | "completed" | "failed" | "cancelled";
592
+ result?: unknown;
593
+ error?: string;
594
+ }
595
+
596
+ interface Workflow<TInput = unknown> {
597
+ trigger: (input: TInput) => Promise<{ executionId: string }>;
598
+ getExecution: (executionId: string) => Promise<WorkflowExecution>;
599
+ cancel: (executionId: string) => Promise<void>;
600
+ }
601
+
602
+ export function initializeWorkflow<TInput = unknown>(
603
+ workflowName: string,
604
+ serviceUrl: string
605
+ ): Workflow<TInput> {
606
+ return {
607
+ async trigger(input: TInput): Promise<{ executionId: string }> {
608
+ const response = await fetch(serviceUrl + "/workflow/trigger", {
609
+ method: "POST",
610
+ headers: { "Content-Type": "application/json" },
611
+ body: JSON.stringify({ workflowName, input }),
612
+ });
613
+
614
+ if (!response.ok) {
615
+ const errorText = await response.text();
616
+ throw new Error("Workflow trigger failed: " + errorText);
617
+ }
618
+
619
+ const result = await response.json();
620
+ if (!result.success) {
621
+ throw new Error(result.error || "Workflow trigger failed");
622
+ }
623
+
624
+ return { executionId: result.executionId };
625
+ },
626
+
627
+ async getExecution(executionId: string): Promise<WorkflowExecution> {
628
+ const response = await fetch(serviceUrl + "/workflow/status", {
629
+ method: "POST",
630
+ headers: { "Content-Type": "application/json" },
631
+ body: JSON.stringify({ executionId }),
632
+ });
633
+
634
+ if (!response.ok) {
635
+ const errorText = await response.text();
636
+ throw new Error("Workflow status failed: " + errorText);
637
+ }
638
+
639
+ const result = await response.json();
640
+ if (!result.success) {
641
+ throw new Error(result.error || "Workflow status failed");
642
+ }
643
+
644
+ const exec = result.execution;
645
+ return {
646
+ id: exec.id,
647
+ status: exec.status,
648
+ result: exec.output,
649
+ error: exec.error,
650
+ };
651
+ },
652
+
653
+ async cancel(executionId: string): Promise<void> {
654
+ const response = await fetch(serviceUrl + "/workflow/cancel", {
655
+ method: "POST",
656
+ headers: { "Content-Type": "application/json" },
657
+ body: JSON.stringify({ executionId }),
658
+ });
659
+
660
+ if (!response.ok) {
661
+ const errorText = await response.text();
662
+ throw new Error("Workflow cancel failed: " + errorText);
663
+ }
664
+
665
+ const result = await response.json();
666
+ if (!result.success) {
667
+ throw new Error(result.error || "Workflow cancel failed");
668
+ }
669
+ },
670
+ };
671
+ }
672
+
673
+ interface WorkflowStepContext {
674
+ run: <T>(stepName: string, fn: () => T | Promise<T>, options?: { retries?: number }) => Promise<T>;
675
+ sleep: (durationMs: number) => Promise<void>;
676
+ }
677
+
678
+ interface WorkflowContext<TInput = unknown, TEnv = unknown> {
679
+ input: TInput;
680
+ env: TEnv;
681
+ step: WorkflowStepContext;
682
+ }
683
+
684
+ export function createStepContext(executionId: string, serviceUrl: string): WorkflowStepContext {
685
+ let stepIndex = 0;
686
+
687
+ return {
688
+ async run<T>(stepName: string, fn: () => T | Promise<T>, options?: { retries?: number }): Promise<T> {
689
+ const currentStepIndex = stepIndex++;
690
+ const maxRetries = options?.retries ?? 0;
691
+
692
+ const startResponse = await fetch(serviceUrl + "/workflow/step/start", {
693
+ method: "POST",
694
+ headers: { "Content-Type": "application/json" },
695
+ body: JSON.stringify({ executionId, stepName, stepIndex: currentStepIndex }),
696
+ });
697
+
698
+ if (!startResponse.ok) {
699
+ const errorText = await startResponse.text();
700
+ throw new Error("Failed to start step: " + errorText);
701
+ }
702
+
703
+ const startResult = await startResponse.json();
704
+
705
+ if (startResult.alreadyCompleted) {
706
+ return startResult.output as T;
707
+ }
708
+
709
+ const startTime = Date.now();
710
+ let lastError: Error | null = null;
711
+
712
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
713
+ try {
714
+ const result = await fn();
715
+ const durationMs = Date.now() - startTime;
716
+
717
+ await fetch(serviceUrl + "/workflow/step/complete", {
718
+ method: "POST",
719
+ headers: { "Content-Type": "application/json" },
720
+ body: JSON.stringify({ executionId, stepName, output: result, durationMs }),
721
+ });
722
+
723
+ return result;
724
+ } catch (error) {
725
+ lastError = error instanceof Error ? error : new Error(String(error));
726
+ if (attempt < maxRetries) {
727
+ await new Promise((resolve) => setTimeout(resolve, Math.min(1000 * Math.pow(2, attempt), 30000)));
728
+ }
33
729
  }
34
- else if (arg === "-h" || arg === "--host") {
35
- options.host = args[++i];
730
+ }
731
+
732
+ const durationMs = Date.now() - startTime;
733
+ await fetch(serviceUrl + "/workflow/step/fail", {
734
+ method: "POST",
735
+ headers: { "Content-Type": "application/json" },
736
+ body: JSON.stringify({ executionId, stepName, error: lastError?.message || "Step execution failed", durationMs }),
737
+ });
738
+
739
+ throw lastError;
740
+ },
741
+
742
+ async sleep(durationMs: number): Promise<void> {
743
+ const stepName = "__sleep_" + stepIndex++;
744
+
745
+ await fetch(serviceUrl + "/workflow/step/start", {
746
+ method: "POST",
747
+ headers: { "Content-Type": "application/json" },
748
+ body: JSON.stringify({ executionId, stepName, stepIndex: stepIndex - 1 }),
749
+ });
750
+
751
+ await new Promise((resolve) => setTimeout(resolve, durationMs));
752
+
753
+ await fetch(serviceUrl + "/workflow/step/complete", {
754
+ method: "POST",
755
+ headers: { "Content-Type": "application/json" },
756
+ body: JSON.stringify({ executionId, stepName, output: { sleptMs: durationMs }, durationMs }),
757
+ });
758
+ },
759
+ };
760
+ }
761
+
762
+ export async function executeWorkflow<TInput, TOutput, TEnv>(
763
+ executionId: string,
764
+ workflowFn: (context: WorkflowContext<TInput, TEnv>) => Promise<TOutput>,
765
+ input: TInput,
766
+ env: TEnv,
767
+ serviceUrl: string
768
+ ): Promise<TOutput> {
769
+ const step = createStepContext(executionId, serviceUrl);
770
+ const context: WorkflowContext<TInput, TEnv> = { input, env, step };
771
+
772
+ try {
773
+ const output = await workflowFn(context);
774
+
775
+ await fetch(serviceUrl + "/workflow/complete", {
776
+ method: "POST",
777
+ headers: { "Content-Type": "application/json" },
778
+ body: JSON.stringify({ executionId, output }),
779
+ });
780
+
781
+ return output;
782
+ } catch (error) {
783
+ await fetch(serviceUrl + "/workflow/fail", {
784
+ method: "POST",
785
+ headers: { "Content-Type": "application/json" },
786
+ body: JSON.stringify({ executionId, error: error instanceof Error ? error.message : String(error) }),
787
+ });
788
+
789
+ throw error;
790
+ }
791
+ }
792
+ `;
793
+ }
794
+ });
795
+ function generateWrapperCode(config, mockServiceUrl) {
796
+ const imports = [];
797
+ const bindings = [];
798
+ if (config.db) {
799
+ imports.push('import { initializeDB } from "__ploy_db_runtime__";');
800
+ for (const [bindingName, dbName] of Object.entries(config.db)) {
801
+ bindings.push(` ${bindingName}: initializeDB("${dbName}", "${mockServiceUrl}/db"),`);
802
+ }
803
+ }
804
+ if (config.queue) {
805
+ imports.push('import { initializeQueue } from "__ploy_queue_runtime__";');
806
+ for (const [bindingName, queueName] of Object.entries(config.queue)) {
807
+ bindings.push(` ${bindingName}: initializeQueue("${queueName}", "${mockServiceUrl}"),`);
808
+ }
809
+ }
810
+ if (config.workflow) {
811
+ imports.push('import { initializeWorkflow, createStepContext, executeWorkflow } from "__ploy_workflow_runtime__";');
812
+ for (const [bindingName, workflowName] of Object.entries(config.workflow)) {
813
+ bindings.push(` ${bindingName}: initializeWorkflow("${workflowName}", "${mockServiceUrl}"),`);
814
+ }
815
+ }
816
+ imports.push('import userWorker from "__ploy_user_worker__";');
817
+ const workflowHandlerCode = config.workflow ? `
818
+ // Handle workflow execution requests
819
+ if (request.headers.get("X-Ploy-Workflow-Execution") === "true") {
820
+ const workflowName = request.headers.get("X-Ploy-Workflow-Name");
821
+ const executionId = request.headers.get("X-Ploy-Execution-Id");
822
+
823
+ if (workflowName && executionId && userWorker.workflows && userWorker.workflows[workflowName]) {
824
+ const input = await request.json();
825
+ try {
826
+ await executeWorkflow(
827
+ executionId,
828
+ userWorker.workflows[workflowName],
829
+ input,
830
+ injectedEnv,
831
+ "${mockServiceUrl}"
832
+ );
833
+ return new Response(JSON.stringify({ success: true }), {
834
+ headers: { "Content-Type": "application/json" }
835
+ });
836
+ } catch (error) {
837
+ return new Response(JSON.stringify({ success: false, error: String(error) }), {
838
+ status: 500,
839
+ headers: { "Content-Type": "application/json" }
840
+ });
841
+ }
842
+ }
843
+ }` : "";
844
+ const queueHandlerCode = config.queue ? `
845
+ // Handle queue message delivery
846
+ if (request.headers.get("X-Ploy-Queue-Delivery") === "true") {
847
+ const queueName = request.headers.get("X-Ploy-Queue-Name");
848
+ const messageId = request.headers.get("X-Ploy-Message-Id");
849
+ const deliveryId = request.headers.get("X-Ploy-Delivery-Id");
850
+ const attempt = parseInt(request.headers.get("X-Ploy-Message-Attempt") || "1", 10);
851
+
852
+ if (queueName && messageId && userWorker.message) {
853
+ const payload = await request.json();
854
+ try {
855
+ await userWorker.message({
856
+ id: messageId,
857
+ queueName,
858
+ payload,
859
+ attempt,
860
+ timestamp: Date.now()
861
+ }, injectedEnv, ctx);
862
+ return new Response(JSON.stringify({ success: true }), {
863
+ headers: { "Content-Type": "application/json" }
864
+ });
865
+ } catch (error) {
866
+ return new Response(JSON.stringify({ success: false, error: String(error) }), {
867
+ status: 500,
868
+ headers: { "Content-Type": "application/json" }
869
+ });
870
+ }
871
+ }
872
+ }` : "";
873
+ return `${imports.join("\n")}
874
+
875
+ const ployBindings = {
876
+ ${bindings.join("\n")}
877
+ };
878
+
879
+ export default {
880
+ async fetch(request, env, ctx) {
881
+ const injectedEnv = { ...env, ...ployBindings };
882
+ ${workflowHandlerCode}
883
+ ${queueHandlerCode}
884
+
885
+ if (userWorker.fetch) {
886
+ return userWorker.fetch(request, injectedEnv, ctx);
887
+ }
888
+
889
+ return new Response("Worker has no fetch handler", { status: 500 });
890
+ },
891
+
892
+ async scheduled(event, env, ctx) {
893
+ if (userWorker.scheduled) {
894
+ const injectedEnv = { ...env, ...ployBindings };
895
+ return userWorker.scheduled(event, injectedEnv, ctx);
896
+ }
897
+ }
898
+ };
899
+ `;
900
+ }
901
+ function createRuntimePlugin(_config) {
902
+ return {
903
+ name: "ploy-runtime",
904
+ setup(build2) {
905
+ build2.onResolve({ filter: /^__ploy_db_runtime__$/ }, () => ({
906
+ path: "__ploy_db_runtime__",
907
+ namespace: "ploy-runtime"
908
+ }));
909
+ build2.onResolve({ filter: /^__ploy_queue_runtime__$/ }, () => ({
910
+ path: "__ploy_queue_runtime__",
911
+ namespace: "ploy-runtime"
912
+ }));
913
+ build2.onResolve({ filter: /^__ploy_workflow_runtime__$/ }, () => ({
914
+ path: "__ploy_workflow_runtime__",
915
+ namespace: "ploy-runtime"
916
+ }));
917
+ build2.onLoad({ filter: /^__ploy_db_runtime__$/, namespace: "ploy-runtime" }, () => ({
918
+ contents: DB_RUNTIME_CODE,
919
+ loader: "ts"
920
+ }));
921
+ build2.onLoad({ filter: /^__ploy_queue_runtime__$/, namespace: "ploy-runtime" }, () => ({
922
+ contents: QUEUE_RUNTIME_CODE,
923
+ loader: "ts"
924
+ }));
925
+ build2.onLoad({ filter: /^__ploy_workflow_runtime__$/, namespace: "ploy-runtime" }, () => ({
926
+ contents: WORKFLOW_RUNTIME_CODE,
927
+ loader: "ts"
928
+ }));
929
+ }
930
+ };
931
+ }
932
+ async function bundleWorker(options) {
933
+ const { projectDir, tempDir, entryPoint, config, mockServiceUrl } = options;
934
+ const wrapperCode = generateWrapperCode(config, mockServiceUrl);
935
+ const wrapperPath = join(tempDir, "wrapper.ts");
936
+ writeFileSync(wrapperPath, wrapperCode);
937
+ const bundlePath = join(tempDir, "worker.bundle.js");
938
+ const buildOptions = {
939
+ entryPoints: [wrapperPath],
940
+ bundle: true,
941
+ format: "esm",
942
+ platform: "neutral",
943
+ target: "es2022",
944
+ outfile: bundlePath,
945
+ minify: false,
946
+ sourcemap: false,
947
+ external: ["cloudflare:*"],
948
+ alias: {
949
+ __ploy_user_worker__: entryPoint
950
+ },
951
+ plugins: [createRuntimePlugin()],
952
+ absWorkingDir: projectDir,
953
+ logLevel: "warning"
954
+ };
955
+ await build(buildOptions);
956
+ return bundlePath;
957
+ }
958
+ var init_bundler = __esm({
959
+ "../emulator/dist/bundler/bundler.js"() {
960
+ init_db_runtime();
961
+ init_queue_runtime();
962
+ init_workflow_runtime();
963
+ }
964
+ });
965
+ function createFileWatcher(srcDir, onRebuild) {
966
+ let watcher = null;
967
+ let debounceTimer = null;
968
+ let isRebuilding = false;
969
+ function shouldRebuild(filePath) {
970
+ const extensions = [".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs"];
971
+ return extensions.some((ext) => filePath.endsWith(ext));
972
+ }
973
+ function scheduleRebuild() {
974
+ if (debounceTimer) {
975
+ clearTimeout(debounceTimer);
976
+ }
977
+ debounceTimer = setTimeout(async () => {
978
+ if (isRebuilding) {
979
+ return;
980
+ }
981
+ isRebuilding = true;
982
+ try {
983
+ await onRebuild();
984
+ } finally {
985
+ isRebuilding = false;
986
+ }
987
+ }, 100);
988
+ }
989
+ return {
990
+ start() {
991
+ if (watcher) {
992
+ return;
993
+ }
994
+ watcher = watch(srcDir, {
995
+ persistent: true,
996
+ ignoreInitial: true,
997
+ ignored: ["**/node_modules/**", "**/dist/**", "**/.ploy/**", "**/.*"]
998
+ });
999
+ watcher.on("change", (filePath) => {
1000
+ if (shouldRebuild(filePath)) {
1001
+ scheduleRebuild();
36
1002
  }
37
- else if (arg === "-c" || arg === "--config") {
38
- options.config = args[++i];
1003
+ });
1004
+ watcher.on("add", (filePath) => {
1005
+ if (shouldRebuild(filePath)) {
1006
+ scheduleRebuild();
39
1007
  }
40
- else if (arg === "--no-watch") {
41
- options.watch = false;
1008
+ });
1009
+ watcher.on("unlink", (filePath) => {
1010
+ if (shouldRebuild(filePath)) {
1011
+ scheduleRebuild();
42
1012
  }
43
- else if (arg === "-v" || arg === "--verbose") {
44
- options.verbose = true;
1013
+ });
1014
+ },
1015
+ stop() {
1016
+ if (debounceTimer) {
1017
+ clearTimeout(debounceTimer);
1018
+ debounceTimer = null;
1019
+ }
1020
+ if (watcher) {
1021
+ watcher.close();
1022
+ watcher = null;
1023
+ }
1024
+ }
1025
+ };
1026
+ }
1027
+ var init_watcher = __esm({
1028
+ "../emulator/dist/bundler/watcher.js"() {
1029
+ }
1030
+ });
1031
+
1032
+ // ../emulator/dist/config/ploy-config.js
1033
+ function readPloyConfig2(projectDir, configPath) {
1034
+ const config = readPloyConfigSync(projectDir, configPath);
1035
+ if (!config.kind) {
1036
+ throw new Error(`Missing required field 'kind' in ${configPath || "ploy.yaml"}`);
1037
+ }
1038
+ if (config.kind !== "dynamic" && config.kind !== "worker") {
1039
+ throw new Error(`Invalid kind '${config.kind}' in ${configPath || "ploy.yaml"}. Must be 'dynamic' or 'worker'`);
1040
+ }
1041
+ return config;
1042
+ }
1043
+ function getWorkerEntryPoint2(projectDir, config) {
1044
+ return getWorkerEntryPoint(projectDir, config);
1045
+ }
1046
+ var init_ploy_config2 = __esm({
1047
+ "../emulator/dist/config/ploy-config.js"() {
1048
+ init_cli();
1049
+ }
1050
+ });
1051
+ function generateWorkerdConfig(options) {
1052
+ const { port, mockServicePort } = options;
1053
+ const services = [
1054
+ '(name = "main", worker = .worker)',
1055
+ `(name = "mock", external = (address = "localhost:${mockServicePort}", http = ()))`,
1056
+ '(name = "internet", network = (allow = ["public", "private", "local"], tlsOptions = (trustBrowserCas = true)))'
1057
+ ];
1058
+ const bindings = [
1059
+ '(name = "mock", service = "mock")',
1060
+ '(name = "internet", service = "internet")'
1061
+ ];
1062
+ const configContent = `using Workerd = import "/workerd/workerd.capnp";
1063
+
1064
+ const config :Workerd.Config = (
1065
+ services = [
1066
+ ${services.join(",\n ")}
1067
+ ],
1068
+ sockets = [
1069
+ (name = "http", address = "*:${port}", http = (), service = "main")
1070
+ ]
1071
+ );
1072
+
1073
+ const worker :Workerd.Worker = (
1074
+ modules = [
1075
+ (name = "worker.js", esModule = embed "worker.bundle.js")
1076
+ ],
1077
+ compatibilityDate = "2025-09-15",
1078
+ compatibilityFlags = [
1079
+ "experimental",
1080
+ "nodejs_compat",
1081
+ "nodejs_als"
1082
+ ],
1083
+ globalOutbound = "internet",
1084
+ bindings = [
1085
+ ${bindings.join(",\n ")}
1086
+ ]
1087
+ );
1088
+ `;
1089
+ return configContent;
1090
+ }
1091
+ function writeWorkerdConfig(options) {
1092
+ const configContent = generateWorkerdConfig(options);
1093
+ const configPath = join(options.tempDir, "workerd.capnp");
1094
+ writeFileSync(configPath, configContent);
1095
+ return configPath;
1096
+ }
1097
+ var init_workerd_config = __esm({
1098
+ "../emulator/dist/config/workerd-config.js"() {
1099
+ }
1100
+ });
1101
+ function findDashboardDistPath() {
1102
+ const possiblePaths = [
1103
+ join(__dirname, "..", "dashboard-dist"),
1104
+ join(__dirname, "..", "..", "src", "dashboard-dist")
1105
+ ];
1106
+ for (const p of possiblePaths) {
1107
+ if (existsSync(p)) {
1108
+ return p;
1109
+ }
1110
+ }
1111
+ return null;
1112
+ }
1113
+ function getMimeType(filePath) {
1114
+ const ext = filePath.substring(filePath.lastIndexOf("."));
1115
+ return MIME_TYPES[ext] || "application/octet-stream";
1116
+ }
1117
+ function createDashboardRoutes(app, dbManager2, config) {
1118
+ const dashboardDistPath = findDashboardDistPath();
1119
+ const hasDashboard = dashboardDistPath !== null;
1120
+ function getDbResourceName(bindingName) {
1121
+ return config.db?.[bindingName] ?? null;
1122
+ }
1123
+ app.get("/api/config", (c) => {
1124
+ return c.json({
1125
+ db: config.db,
1126
+ queue: config.queue,
1127
+ workflow: config.workflow
1128
+ });
1129
+ });
1130
+ app.post("/api/db/:binding/query", async (c) => {
1131
+ const binding = c.req.param("binding");
1132
+ const resourceName = getDbResourceName(binding);
1133
+ if (!resourceName) {
1134
+ return c.json({ error: `Database binding '${binding}' not found` }, 404);
1135
+ }
1136
+ const body = await c.req.json();
1137
+ const { query } = body;
1138
+ if (!query) {
1139
+ return c.json({ error: "Query is required" }, 400);
1140
+ }
1141
+ try {
1142
+ const db = dbManager2.getD1Database(resourceName);
1143
+ const startTime = Date.now();
1144
+ const stmt = db.prepare(query);
1145
+ const isSelect = query.trim().toUpperCase().startsWith("SELECT");
1146
+ let results = [];
1147
+ let changes = 0;
1148
+ if (isSelect) {
1149
+ results = stmt.all();
1150
+ } else {
1151
+ const info = stmt.run();
1152
+ changes = info.changes;
1153
+ }
1154
+ const duration = Date.now() - startTime;
1155
+ return c.json({
1156
+ results,
1157
+ success: true,
1158
+ meta: {
1159
+ duration,
1160
+ rows_read: results.length,
1161
+ rows_written: changes
45
1162
  }
46
- else if (arg === "--dashboard-port") {
47
- const value = args[++i];
48
- if (value) {
49
- options.dashboardPort = parseInt(value, 10);
50
- }
1163
+ });
1164
+ } catch (err) {
1165
+ return c.json({
1166
+ results: [],
1167
+ success: false,
1168
+ error: err instanceof Error ? err.message : String(err),
1169
+ meta: { duration: 0, rows_read: 0, rows_written: 0 }
1170
+ }, 400);
1171
+ }
1172
+ });
1173
+ app.get("/api/db/:binding/tables", (c) => {
1174
+ const binding = c.req.param("binding");
1175
+ const resourceName = getDbResourceName(binding);
1176
+ if (!resourceName) {
1177
+ return c.json({ error: `Database binding '${binding}' not found` }, 404);
1178
+ }
1179
+ try {
1180
+ const db = dbManager2.getD1Database(resourceName);
1181
+ const tables = db.prepare(`SELECT name FROM sqlite_master
1182
+ WHERE type='table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_litestream_%'
1183
+ ORDER BY name`).all();
1184
+ return c.json({ tables });
1185
+ } catch (err) {
1186
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1187
+ }
1188
+ });
1189
+ app.get("/api/db/:binding/tables/:tableName", (c) => {
1190
+ const binding = c.req.param("binding");
1191
+ const resourceName = getDbResourceName(binding);
1192
+ if (!resourceName) {
1193
+ return c.json({ error: `Database binding '${binding}' not found` }, 404);
1194
+ }
1195
+ const tableName = c.req.param("tableName");
1196
+ const limit = parseInt(c.req.query("limit") || "50", 10);
1197
+ const offset = parseInt(c.req.query("offset") || "0", 10);
1198
+ try {
1199
+ const db = dbManager2.getD1Database(resourceName);
1200
+ const columnsResult = db.prepare(`PRAGMA table_info("${tableName}")`).all();
1201
+ const columns = columnsResult.map((col) => col.name);
1202
+ const countResult = db.prepare(`SELECT COUNT(*) as count FROM "${tableName}"`).get();
1203
+ const total = countResult.count;
1204
+ const data = db.prepare(`SELECT * FROM "${tableName}" LIMIT ? OFFSET ?`).all(limit, offset);
1205
+ return c.json({ data, columns, total });
1206
+ } catch (err) {
1207
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1208
+ }
1209
+ });
1210
+ app.get("/api/db/:binding/schema", (c) => {
1211
+ const binding = c.req.param("binding");
1212
+ const resourceName = getDbResourceName(binding);
1213
+ if (!resourceName) {
1214
+ return c.json({ error: `Database binding '${binding}' not found` }, 404);
1215
+ }
1216
+ try {
1217
+ const db = dbManager2.getD1Database(resourceName);
1218
+ const tablesResult = db.prepare(`SELECT name FROM sqlite_master
1219
+ WHERE type='table' AND name NOT LIKE 'sqlite_%'
1220
+ ORDER BY name`).all();
1221
+ const tables = tablesResult.map((table) => {
1222
+ const columnsResult = db.prepare(`PRAGMA table_info("${table.name}")`).all();
1223
+ return {
1224
+ name: table.name,
1225
+ columns: columnsResult.map((col) => ({
1226
+ name: col.name,
1227
+ type: col.type,
1228
+ notNull: col.notnull === 1,
1229
+ primaryKey: col.pk === 1
1230
+ }))
1231
+ };
1232
+ });
1233
+ return c.json({ tables });
1234
+ } catch (err) {
1235
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1236
+ }
1237
+ });
1238
+ app.get("/api/queue/:binding/metrics", (c) => {
1239
+ const binding = c.req.param("binding");
1240
+ const queueName = config.queue?.[binding];
1241
+ if (!queueName) {
1242
+ return c.json({ error: "Queue not found" }, 404);
1243
+ }
1244
+ try {
1245
+ const db = dbManager2.emulatorDb;
1246
+ const metrics = {
1247
+ pending: 0,
1248
+ processing: 0,
1249
+ acknowledged: 0,
1250
+ failed: 0,
1251
+ deadLetter: 0
1252
+ };
1253
+ const statusCounts = db.prepare(`SELECT status, COUNT(*) as count
1254
+ FROM queue_messages
1255
+ WHERE queue_name = ?
1256
+ GROUP BY status`).all(queueName);
1257
+ for (const row of statusCounts) {
1258
+ if (row.status === "pending") {
1259
+ metrics.pending = row.count;
1260
+ } else if (row.status === "processing") {
1261
+ metrics.processing = row.count;
1262
+ } else if (row.status === "acknowledged") {
1263
+ metrics.acknowledged = row.count;
1264
+ } else if (row.status === "failed") {
1265
+ metrics.failed = row.count;
1266
+ } else if (row.status === "dead_letter") {
1267
+ metrics.deadLetter = row.count;
51
1268
  }
1269
+ }
1270
+ return c.json({ metrics });
1271
+ } catch (err) {
1272
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1273
+ }
1274
+ });
1275
+ app.get("/api/queue/:binding/messages", (c) => {
1276
+ const binding = c.req.param("binding");
1277
+ const queueName = config.queue?.[binding];
1278
+ const limit = parseInt(c.req.query("limit") || "10", 10);
1279
+ if (!queueName) {
1280
+ return c.json({ error: "Queue not found" }, 404);
1281
+ }
1282
+ try {
1283
+ const db = dbManager2.emulatorDb;
1284
+ const messages = db.prepare(`SELECT id, status, payload, attempt, created_at
1285
+ FROM queue_messages
1286
+ WHERE queue_name = ?
1287
+ ORDER BY created_at DESC
1288
+ LIMIT ?`).all(queueName, limit);
1289
+ return c.json({
1290
+ messages: messages.map((m) => ({
1291
+ id: m.id,
1292
+ status: m.status.toUpperCase(),
1293
+ payload: JSON.parse(m.payload),
1294
+ attempt: m.attempt,
1295
+ createdAt: new Date(m.created_at * 1e3).toISOString()
1296
+ }))
1297
+ });
1298
+ } catch (err) {
1299
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1300
+ }
1301
+ });
1302
+ app.get("/api/workflow/:binding/executions", (c) => {
1303
+ const binding = c.req.param("binding");
1304
+ const workflowConfig = config.workflow?.[binding];
1305
+ const limit = parseInt(c.req.query("limit") || "20", 10);
1306
+ if (!workflowConfig) {
1307
+ return c.json({ error: "Workflow not found" }, 404);
52
1308
  }
53
- return options;
1309
+ try {
1310
+ const db = dbManager2.emulatorDb;
1311
+ const workflowName = workflowConfig;
1312
+ const executions = db.prepare(`SELECT
1313
+ e.id,
1314
+ e.workflow_name,
1315
+ e.status,
1316
+ e.error,
1317
+ e.started_at,
1318
+ e.completed_at,
1319
+ e.created_at,
1320
+ (SELECT COUNT(*) FROM workflow_steps WHERE execution_id = e.id) as steps_count,
1321
+ (SELECT COUNT(*) FROM workflow_steps WHERE execution_id = e.id AND status = 'completed') as steps_completed
1322
+ FROM workflow_executions e
1323
+ WHERE e.workflow_name = ?
1324
+ ORDER BY e.created_at DESC
1325
+ LIMIT ?`).all(workflowName, limit);
1326
+ return c.json({
1327
+ executions: executions.map((e) => ({
1328
+ id: e.id,
1329
+ status: e.status.toUpperCase(),
1330
+ startedAt: e.started_at ? new Date(e.started_at * 1e3).toISOString() : null,
1331
+ completedAt: e.completed_at ? new Date(e.completed_at * 1e3).toISOString() : null,
1332
+ durationMs: e.started_at && e.completed_at ? (e.completed_at - e.started_at) * 1e3 : null,
1333
+ stepsCount: e.steps_count,
1334
+ stepsCompleted: e.steps_completed,
1335
+ errorMessage: e.error,
1336
+ createdAt: new Date(e.created_at * 1e3).toISOString()
1337
+ }))
1338
+ });
1339
+ } catch (err) {
1340
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1341
+ }
1342
+ });
1343
+ app.get("/api/workflow/:binding/executions/:executionId", (c) => {
1344
+ const binding = c.req.param("binding");
1345
+ const executionId = c.req.param("executionId");
1346
+ const workflowConfig = config.workflow?.[binding];
1347
+ if (!workflowConfig) {
1348
+ return c.json({ error: "Workflow not found" }, 404);
1349
+ }
1350
+ try {
1351
+ const db = dbManager2.emulatorDb;
1352
+ const execution = db.prepare(`SELECT id, workflow_name, status, error, started_at, completed_at, created_at
1353
+ FROM workflow_executions
1354
+ WHERE id = ?`).get(executionId);
1355
+ if (!execution) {
1356
+ return c.json({ error: "Execution not found" }, 404);
1357
+ }
1358
+ const steps = db.prepare(`SELECT id, step_name, step_index, status, output, error, duration_ms, created_at
1359
+ FROM workflow_steps
1360
+ WHERE execution_id = ?
1361
+ ORDER BY step_index`).all(executionId);
1362
+ return c.json({
1363
+ execution: {
1364
+ id: execution.id,
1365
+ status: execution.status.toUpperCase(),
1366
+ startedAt: execution.started_at ? new Date(execution.started_at * 1e3).toISOString() : null,
1367
+ completedAt: execution.completed_at ? new Date(execution.completed_at * 1e3).toISOString() : null,
1368
+ durationMs: execution.started_at && execution.completed_at ? (execution.completed_at - execution.started_at) * 1e3 : null,
1369
+ stepsCount: steps.length,
1370
+ stepsCompleted: steps.filter((s) => s.status === "completed").length,
1371
+ errorMessage: execution.error,
1372
+ createdAt: new Date(execution.created_at * 1e3).toISOString()
1373
+ },
1374
+ steps: steps.map((s) => ({
1375
+ id: String(s.id),
1376
+ stepName: s.step_name,
1377
+ stepIndex: s.step_index,
1378
+ status: s.status.toUpperCase(),
1379
+ output: s.output ? JSON.parse(s.output) : null,
1380
+ errorMessage: s.error,
1381
+ durationMs: s.duration_ms,
1382
+ createdAt: new Date(s.created_at * 1e3).toISOString()
1383
+ }))
1384
+ });
1385
+ } catch (err) {
1386
+ return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
1387
+ }
1388
+ });
1389
+ if (hasDashboard) {
1390
+ app.get("/assets/*", (c) => {
1391
+ const path = c.req.path;
1392
+ const filePath = join(dashboardDistPath, path);
1393
+ if (!existsSync(filePath)) {
1394
+ return c.notFound();
1395
+ }
1396
+ const content = readFileSync(filePath);
1397
+ const mimeType = getMimeType(filePath);
1398
+ return new Response(content, {
1399
+ headers: {
1400
+ "Content-Type": mimeType,
1401
+ "Cache-Control": "public, max-age=31536000"
1402
+ }
1403
+ });
1404
+ });
1405
+ app.get("*", (c) => {
1406
+ if (c.req.path.startsWith("/api/") || c.req.path.startsWith("/db") || c.req.path.startsWith("/queue") || c.req.path.startsWith("/workflow")) {
1407
+ return c.notFound();
1408
+ }
1409
+ const indexPath = join(dashboardDistPath, "index.html");
1410
+ if (!existsSync(indexPath)) {
1411
+ return c.text("Dashboard not found. Run 'pnpm build' in packages/dev-dashboard.", 404);
1412
+ }
1413
+ const content = readFileSync(indexPath, "utf-8");
1414
+ return c.html(content);
1415
+ });
1416
+ }
54
1417
  }
55
- async function isNextJsProject(projectDir, configPath) {
56
- const { readPloyConfig } = await import("@meetploy/tools");
1418
+ var __filename, __dirname, MIME_TYPES;
1419
+ var init_dashboard_routes = __esm({
1420
+ "../emulator/dist/services/dashboard-routes.js"() {
1421
+ __filename = fileURLToPath(import.meta.url);
1422
+ __dirname = dirname(__filename);
1423
+ MIME_TYPES = {
1424
+ ".html": "text/html",
1425
+ ".js": "application/javascript",
1426
+ ".css": "text/css",
1427
+ ".json": "application/json",
1428
+ ".png": "image/png",
1429
+ ".jpg": "image/jpeg",
1430
+ ".svg": "image/svg+xml",
1431
+ ".ico": "image/x-icon",
1432
+ ".woff": "font/woff",
1433
+ ".woff2": "font/woff2"
1434
+ };
1435
+ }
1436
+ });
1437
+
1438
+ // ../emulator/dist/services/db-service.js
1439
+ function createDbHandler(getDatabase) {
1440
+ return async (c) => {
1441
+ const startTime = Date.now();
57
1442
  try {
58
- const config = await readPloyConfig(projectDir, configPath);
59
- if (config?.kind === "nextjs") {
60
- return true;
1443
+ const body = await c.req.json();
1444
+ const { bindingName, method, query, params, statements } = body;
1445
+ const db = getDatabase(bindingName);
1446
+ if (method === "prepare" && query) {
1447
+ const stmt = db.prepare(query);
1448
+ const isSelect = query.trim().toUpperCase().startsWith("SELECT");
1449
+ let results;
1450
+ let changes = 0;
1451
+ if (isSelect) {
1452
+ results = params?.length ? stmt.all(...params) : stmt.all();
1453
+ } else {
1454
+ const info = params?.length ? stmt.run(...params) : stmt.run();
1455
+ changes = info.changes;
1456
+ results = [];
61
1457
  }
1458
+ const duration = Date.now() - startTime;
1459
+ return c.json({
1460
+ results,
1461
+ success: true,
1462
+ meta: {
1463
+ duration,
1464
+ rows_read: results.length,
1465
+ rows_written: changes
1466
+ }
1467
+ });
1468
+ }
1469
+ if (method === "exec" && query) {
1470
+ db.exec(query);
1471
+ const duration = Date.now() - startTime;
1472
+ return c.json({
1473
+ results: [],
1474
+ success: true,
1475
+ meta: {
1476
+ duration,
1477
+ rows_read: 0,
1478
+ rows_written: 0
1479
+ }
1480
+ });
1481
+ }
1482
+ if (method === "batch" && statements) {
1483
+ const results = [];
1484
+ const transaction = db.transaction(() => {
1485
+ for (const stmt of statements) {
1486
+ const prepared = db.prepare(stmt.query);
1487
+ const isSelect = stmt.query.trim().toUpperCase().startsWith("SELECT");
1488
+ if (isSelect) {
1489
+ const rows = stmt.params?.length ? prepared.all(...stmt.params) : prepared.all();
1490
+ results.push({
1491
+ results: rows,
1492
+ success: true,
1493
+ meta: {
1494
+ duration: 0,
1495
+ rows_read: rows.length,
1496
+ rows_written: 0
1497
+ }
1498
+ });
1499
+ } else {
1500
+ const info = stmt.params?.length ? prepared.run(...stmt.params) : prepared.run();
1501
+ results.push({
1502
+ results: [],
1503
+ success: true,
1504
+ meta: {
1505
+ duration: 0,
1506
+ rows_read: 0,
1507
+ rows_written: info.changes
1508
+ }
1509
+ });
1510
+ }
1511
+ }
1512
+ });
1513
+ transaction();
1514
+ return c.json(results);
1515
+ }
1516
+ if (method === "dump") {
1517
+ const buffer = db.serialize();
1518
+ return new Response(new Uint8Array(buffer), {
1519
+ headers: {
1520
+ "Content-Type": "application/octet-stream"
1521
+ }
1522
+ });
1523
+ }
1524
+ return c.json({ error: "Invalid method" }, 400);
1525
+ } catch (err) {
1526
+ const message = err instanceof Error ? err.message : String(err);
1527
+ return c.json({ error: message }, 500);
62
1528
  }
63
- catch {
1529
+ };
1530
+ }
1531
+ var init_db_service = __esm({
1532
+ "../emulator/dist/services/db-service.js"() {
1533
+ }
1534
+ });
1535
+ function createQueueHandlers(db) {
1536
+ const sendHandler = async (c) => {
1537
+ try {
1538
+ const body = await c.req.json();
1539
+ const { queueName, payload, delaySeconds = 0 } = body;
1540
+ const id = randomUUID();
1541
+ const now = Math.floor(Date.now() / 1e3);
1542
+ const visibleAt = now + delaySeconds;
1543
+ db.prepare(`INSERT INTO queue_messages (id, queue_name, payload, visible_at)
1544
+ VALUES (?, ?, ?, ?)`).run(id, queueName, JSON.stringify(payload), visibleAt);
1545
+ return c.json({ success: true, messageId: id });
1546
+ } catch (err) {
1547
+ const message = err instanceof Error ? err.message : String(err);
1548
+ return c.json({ success: false, error: message }, 500);
64
1549
  }
65
- const nextConfigFiles = [
66
- "next.config.ts",
67
- "next.config.js",
68
- "next.config.mts",
69
- "next.config.mjs",
70
- ];
71
- for (const file of nextConfigFiles) {
72
- if (existsSync(join(projectDir, file))) {
73
- return true;
1550
+ };
1551
+ const batchSendHandler = async (c) => {
1552
+ try {
1553
+ const body = await c.req.json();
1554
+ const { queueName, messages } = body;
1555
+ const now = Math.floor(Date.now() / 1e3);
1556
+ const messageIds = [];
1557
+ const insert = db.prepare(`INSERT INTO queue_messages (id, queue_name, payload, visible_at)
1558
+ VALUES (?, ?, ?, ?)`);
1559
+ const transaction = db.transaction(() => {
1560
+ for (const msg of messages) {
1561
+ const id = randomUUID();
1562
+ const visibleAt = now + (msg.delaySeconds || 0);
1563
+ insert.run(id, queueName, JSON.stringify(msg.payload), visibleAt);
1564
+ messageIds.push(id);
74
1565
  }
1566
+ });
1567
+ transaction();
1568
+ return c.json({ success: true, messageIds });
1569
+ } catch (err) {
1570
+ const message = err instanceof Error ? err.message : String(err);
1571
+ return c.json({ success: false, error: message }, 500);
75
1572
  }
76
- return false;
1573
+ };
1574
+ const receiveHandler = async (c) => {
1575
+ try {
1576
+ const body = await c.req.json();
1577
+ const { queueName, maxMessages = 10, visibilityTimeoutSeconds = 30 } = body;
1578
+ const now = Math.floor(Date.now() / 1e3);
1579
+ const deliveryId = randomUUID();
1580
+ const newVisibleAt = now + visibilityTimeoutSeconds;
1581
+ const rows = db.prepare(`SELECT id, queue_name, payload, attempt
1582
+ FROM queue_messages
1583
+ WHERE queue_name = ? AND status = 'pending' AND visible_at <= ?
1584
+ ORDER BY visible_at ASC
1585
+ LIMIT ?`).all(queueName, now, maxMessages);
1586
+ if (rows.length === 0) {
1587
+ return c.json({ success: true, messages: [] });
1588
+ }
1589
+ const ids = rows.map((r) => r.id);
1590
+ const placeholders = ids.map(() => "?").join(",");
1591
+ db.prepare(`UPDATE queue_messages
1592
+ SET status = 'processing', delivery_id = ?, visible_at = ?, attempt = attempt + 1, updated_at = ?
1593
+ WHERE id IN (${placeholders})`).run(deliveryId, newVisibleAt, now, ...ids);
1594
+ const messages = rows.map((row) => ({
1595
+ id: row.id,
1596
+ payload: JSON.parse(row.payload),
1597
+ attempt: row.attempt + 1,
1598
+ deliveryId
1599
+ }));
1600
+ return c.json({ success: true, messages });
1601
+ } catch (err) {
1602
+ const message = err instanceof Error ? err.message : String(err);
1603
+ return c.json({ success: false, messages: [], error: message }, 500);
1604
+ }
1605
+ };
1606
+ const ackHandler = async (c) => {
1607
+ try {
1608
+ const body = await c.req.json();
1609
+ const { messageId, deliveryId } = body;
1610
+ const result = db.prepare(`DELETE FROM queue_messages
1611
+ WHERE id = ? AND delivery_id = ?`).run(messageId, deliveryId);
1612
+ if (result.changes === 0) {
1613
+ return c.json({ success: false, error: "Message not found or already processed" }, 404);
1614
+ }
1615
+ return c.json({ success: true });
1616
+ } catch (err) {
1617
+ const message = err instanceof Error ? err.message : String(err);
1618
+ return c.json({ success: false, error: message }, 500);
1619
+ }
1620
+ };
1621
+ const retryHandler = async (c) => {
1622
+ try {
1623
+ const body = await c.req.json();
1624
+ const { messageId, deliveryId } = body;
1625
+ const now = Math.floor(Date.now() / 1e3);
1626
+ const backoffSeconds = 5;
1627
+ const result = db.prepare(`UPDATE queue_messages
1628
+ SET status = 'pending', delivery_id = NULL, visible_at = ?, updated_at = ?
1629
+ WHERE id = ? AND delivery_id = ?`).run(now + backoffSeconds, now, messageId, deliveryId);
1630
+ if (result.changes === 0) {
1631
+ return c.json({ success: false, error: "Message not found or already processed" }, 404);
1632
+ }
1633
+ return c.json({ success: true });
1634
+ } catch (err) {
1635
+ const message = err instanceof Error ? err.message : String(err);
1636
+ return c.json({ success: false, error: message }, 500);
1637
+ }
1638
+ };
1639
+ return {
1640
+ sendHandler,
1641
+ batchSendHandler,
1642
+ receiveHandler,
1643
+ ackHandler,
1644
+ retryHandler
1645
+ };
77
1646
  }
78
- function findNextBinary(projectDir) {
79
- const possiblePaths = [
80
- join(projectDir, "node_modules", ".bin", "next"),
81
- "npx next",
82
- ];
83
- for (const p of possiblePaths) {
84
- if (p.includes("npx") || existsSync(p)) {
85
- return p;
1647
+ function createQueueProcessor(db, queueBindings, workerUrl) {
1648
+ let interval = null;
1649
+ async function processQueues() {
1650
+ for (const [bindingName, queueName] of Object.entries(queueBindings)) {
1651
+ await processQueue(bindingName, queueName);
1652
+ }
1653
+ }
1654
+ async function processQueue(bindingName, queueName) {
1655
+ const now = Math.floor(Date.now() / 1e3);
1656
+ const deliveryId = randomUUID();
1657
+ const visibilityTimeout = 30;
1658
+ const rows = db.prepare(`SELECT id, payload, attempt
1659
+ FROM queue_messages
1660
+ WHERE queue_name = ? AND status = 'pending' AND visible_at <= ?
1661
+ LIMIT 10`).all(queueName, now);
1662
+ if (rows.length === 0) {
1663
+ return;
1664
+ }
1665
+ const ids = rows.map((r) => r.id);
1666
+ const placeholders = ids.map(() => "?").join(",");
1667
+ db.prepare(`UPDATE queue_messages
1668
+ SET status = 'processing', delivery_id = ?, visible_at = ?, attempt = attempt + 1, updated_at = ?
1669
+ WHERE id IN (${placeholders})`).run(deliveryId, now + visibilityTimeout, now, ...ids);
1670
+ for (const row of rows) {
1671
+ try {
1672
+ const response = await fetch(workerUrl, {
1673
+ method: "POST",
1674
+ headers: {
1675
+ "Content-Type": "application/json",
1676
+ "X-Ploy-Queue-Delivery": "true",
1677
+ "X-Ploy-Queue-Name": queueName,
1678
+ "X-Ploy-Queue-Binding": bindingName,
1679
+ "X-Ploy-Message-Id": row.id,
1680
+ "X-Ploy-Delivery-Id": deliveryId,
1681
+ "X-Ploy-Message-Attempt": String(row.attempt + 1)
1682
+ },
1683
+ body: row.payload
1684
+ });
1685
+ if (response.ok) {
1686
+ db.prepare(`DELETE FROM queue_messages WHERE id = ?`).run(row.id);
1687
+ } else {
1688
+ db.prepare(`UPDATE queue_messages
1689
+ SET status = 'pending', delivery_id = NULL, visible_at = ?, updated_at = ?
1690
+ WHERE id = ?`).run(now + 5, now, row.id);
86
1691
  }
1692
+ } catch {
1693
+ db.prepare(`UPDATE queue_messages
1694
+ SET status = 'pending', delivery_id = NULL, visible_at = ?, updated_at = ?
1695
+ WHERE id = ?`).run(now + 5, now, row.id);
1696
+ }
87
1697
  }
88
- return "npx next";
1698
+ }
1699
+ return {
1700
+ start() {
1701
+ if (interval) {
1702
+ return;
1703
+ }
1704
+ interval = setInterval(() => {
1705
+ processQueues().catch(() => {
1706
+ });
1707
+ }, 1e3);
1708
+ },
1709
+ stop() {
1710
+ if (interval) {
1711
+ clearInterval(interval);
1712
+ interval = null;
1713
+ }
1714
+ }
1715
+ };
89
1716
  }
90
- async function startNextJsDev(options) {
91
- const projectDir = process.cwd();
92
- const nextPort = options.port ?? 3000;
93
- const dashboardPort = options.dashboardPort ?? nextPort + 1000;
94
- const { startDevDashboard } = await import("@meetploy/emulator");
95
- const dashboard = await startDevDashboard({
96
- configPath: options.config,
97
- port: dashboardPort,
98
- verbose: options.verbose,
99
- });
100
- console.log(`\n Ploy Dashboard: http://${options.host ?? "localhost"}:${dashboard.port}`);
101
- const nextBin = findNextBinary(projectDir);
102
- const nextArgs = ["dev", "-p", String(nextPort)];
103
- if (options.host) {
104
- nextArgs.push("-H", options.host);
105
- }
106
- console.log(` Starting Next.js dev server...`);
107
- let nextProcess;
108
- if (nextBin.includes("npx")) {
109
- nextProcess = spawn("npx", ["next", ...nextArgs], {
110
- cwd: projectDir,
111
- stdio: "inherit",
112
- shell: true,
113
- env: {
114
- ...process.env,
115
- PLOY_MOCK_SERVER_URL: `http://localhost:${dashboard.port}`,
116
- },
1717
+ var init_queue_service = __esm({
1718
+ "../emulator/dist/services/queue-service.js"() {
1719
+ }
1720
+ });
1721
+ function createWorkflowHandlers(db, workerUrl) {
1722
+ const triggerHandler = async (c) => {
1723
+ try {
1724
+ const body = await c.req.json();
1725
+ const { workflowName, input } = body;
1726
+ const id = randomUUID();
1727
+ const now = Math.floor(Date.now() / 1e3);
1728
+ db.prepare(`INSERT INTO workflow_executions (id, workflow_name, status, input, started_at, created_at)
1729
+ VALUES (?, ?, 'running', ?, ?, ?)`).run(id, workflowName, JSON.stringify(input), now, now);
1730
+ if (workerUrl) {
1731
+ fetch(workerUrl, {
1732
+ method: "POST",
1733
+ headers: {
1734
+ "Content-Type": "application/json",
1735
+ "X-Ploy-Workflow-Execution": "true",
1736
+ "X-Ploy-Workflow-Name": workflowName,
1737
+ "X-Ploy-Execution-Id": id
1738
+ },
1739
+ body: JSON.stringify(input)
1740
+ }).catch(() => {
117
1741
  });
1742
+ }
1743
+ return c.json({ success: true, executionId: id });
1744
+ } catch (err) {
1745
+ const message = err instanceof Error ? err.message : String(err);
1746
+ return c.json({ success: false, error: message }, 500);
1747
+ }
1748
+ };
1749
+ const statusHandler = async (c) => {
1750
+ try {
1751
+ const body = await c.req.json();
1752
+ const { executionId } = body;
1753
+ const execution = db.prepare(`SELECT id, workflow_name, status, input, output, error, started_at, completed_at, created_at
1754
+ FROM workflow_executions WHERE id = ?`).get(executionId);
1755
+ if (!execution) {
1756
+ return c.json({ success: false, error: "Execution not found" }, 404);
1757
+ }
1758
+ const steps = db.prepare(`SELECT step_name, step_index, status, output, error, duration_ms
1759
+ FROM workflow_steps WHERE execution_id = ? ORDER BY step_index`).all(executionId);
1760
+ return c.json({
1761
+ success: true,
1762
+ execution: {
1763
+ id: execution.id,
1764
+ workflowName: execution.workflow_name,
1765
+ status: execution.status,
1766
+ input: execution.input ? JSON.parse(execution.input) : null,
1767
+ output: execution.output ? JSON.parse(execution.output) : null,
1768
+ error: execution.error,
1769
+ startedAt: execution.started_at ? new Date(execution.started_at * 1e3).toISOString() : null,
1770
+ completedAt: execution.completed_at ? new Date(execution.completed_at * 1e3).toISOString() : null,
1771
+ steps: steps.map((s) => ({
1772
+ stepName: s.step_name,
1773
+ stepIndex: s.step_index,
1774
+ status: s.status,
1775
+ output: s.output ? JSON.parse(s.output) : null,
1776
+ error: s.error,
1777
+ durationMs: s.duration_ms
1778
+ }))
1779
+ }
1780
+ });
1781
+ } catch (err) {
1782
+ const message = err instanceof Error ? err.message : String(err);
1783
+ return c.json({ success: false, error: message }, 500);
1784
+ }
1785
+ };
1786
+ const cancelHandler = async (c) => {
1787
+ try {
1788
+ const body = await c.req.json();
1789
+ const { executionId } = body;
1790
+ const now = Math.floor(Date.now() / 1e3);
1791
+ const result = db.prepare(`UPDATE workflow_executions
1792
+ SET status = 'cancelled', completed_at = ?
1793
+ WHERE id = ? AND status IN ('pending', 'running')`).run(now, executionId);
1794
+ if (result.changes === 0) {
1795
+ return c.json({ success: false, error: "Execution not found or already completed" }, 404);
1796
+ }
1797
+ return c.json({ success: true });
1798
+ } catch (err) {
1799
+ const message = err instanceof Error ? err.message : String(err);
1800
+ return c.json({ success: false, error: message }, 500);
118
1801
  }
119
- else {
120
- nextProcess = spawn(nextBin, nextArgs, {
121
- cwd: projectDir,
122
- stdio: "inherit",
1802
+ };
1803
+ const stepStartHandler = async (c) => {
1804
+ try {
1805
+ const body = await c.req.json();
1806
+ const { executionId, stepName, stepIndex } = body;
1807
+ const existing = db.prepare(`SELECT status, output FROM workflow_steps
1808
+ WHERE execution_id = ? AND step_name = ? AND step_index = ?`).get(executionId, stepName, stepIndex);
1809
+ if (existing && existing.status === "completed") {
1810
+ return c.json({
1811
+ success: true,
1812
+ alreadyCompleted: true,
1813
+ output: existing.output ? JSON.parse(existing.output) : null
1814
+ });
1815
+ }
1816
+ const now = Math.floor(Date.now() / 1e3);
1817
+ if (existing) {
1818
+ db.prepare(`UPDATE workflow_steps SET status = 'running' WHERE execution_id = ? AND step_name = ?`).run(executionId, stepName);
1819
+ } else {
1820
+ db.prepare(`INSERT INTO workflow_steps (execution_id, step_name, step_index, status, created_at)
1821
+ VALUES (?, ?, ?, 'running', ?)`).run(executionId, stepName, stepIndex, now);
1822
+ }
1823
+ return c.json({ success: true, alreadyCompleted: false });
1824
+ } catch (err) {
1825
+ const message = err instanceof Error ? err.message : String(err);
1826
+ return c.json({ success: false, error: message }, 500);
1827
+ }
1828
+ };
1829
+ const stepCompleteHandler = async (c) => {
1830
+ try {
1831
+ const body = await c.req.json();
1832
+ const { executionId, stepName, output, durationMs } = body;
1833
+ db.prepare(`UPDATE workflow_steps
1834
+ SET status = 'completed', output = ?, duration_ms = ?
1835
+ WHERE execution_id = ? AND step_name = ?`).run(JSON.stringify(output), durationMs, executionId, stepName);
1836
+ return c.json({ success: true });
1837
+ } catch (err) {
1838
+ const message = err instanceof Error ? err.message : String(err);
1839
+ return c.json({ success: false, error: message }, 500);
1840
+ }
1841
+ };
1842
+ const stepFailHandler = async (c) => {
1843
+ try {
1844
+ const body = await c.req.json();
1845
+ const { executionId, stepName, error: error2, durationMs } = body;
1846
+ db.prepare(`UPDATE workflow_steps
1847
+ SET status = 'failed', error = ?, duration_ms = ?
1848
+ WHERE execution_id = ? AND step_name = ?`).run(error2, durationMs, executionId, stepName);
1849
+ return c.json({ success: true });
1850
+ } catch (err) {
1851
+ const message = err instanceof Error ? err.message : String(err);
1852
+ return c.json({ success: false, error: message }, 500);
1853
+ }
1854
+ };
1855
+ const completeHandler = async (c) => {
1856
+ try {
1857
+ const body = await c.req.json();
1858
+ const { executionId, output } = body;
1859
+ const now = Math.floor(Date.now() / 1e3);
1860
+ db.prepare(`UPDATE workflow_executions
1861
+ SET status = 'completed', output = ?, completed_at = ?
1862
+ WHERE id = ?`).run(JSON.stringify(output), now, executionId);
1863
+ return c.json({ success: true });
1864
+ } catch (err) {
1865
+ const message = err instanceof Error ? err.message : String(err);
1866
+ return c.json({ success: false, error: message }, 500);
1867
+ }
1868
+ };
1869
+ const failHandler = async (c) => {
1870
+ try {
1871
+ const body = await c.req.json();
1872
+ const { executionId, error: error2 } = body;
1873
+ const now = Math.floor(Date.now() / 1e3);
1874
+ db.prepare(`UPDATE workflow_executions
1875
+ SET status = 'failed', error = ?, completed_at = ?
1876
+ WHERE id = ?`).run(error2, now, executionId);
1877
+ return c.json({ success: true });
1878
+ } catch (err) {
1879
+ const message = err instanceof Error ? err.message : String(err);
1880
+ return c.json({ success: false, error: message }, 500);
1881
+ }
1882
+ };
1883
+ return {
1884
+ triggerHandler,
1885
+ statusHandler,
1886
+ cancelHandler,
1887
+ stepStartHandler,
1888
+ stepCompleteHandler,
1889
+ stepFailHandler,
1890
+ completeHandler,
1891
+ failHandler
1892
+ };
1893
+ }
1894
+ var init_workflow_service = __esm({
1895
+ "../emulator/dist/services/workflow-service.js"() {
1896
+ }
1897
+ });
1898
+ async function startMockServer(dbManager2, config, options = {}) {
1899
+ const app = new Hono();
1900
+ if (config.db) {
1901
+ const dbHandler = createDbHandler(dbManager2.getD1Database);
1902
+ app.post("/db", dbHandler);
1903
+ }
1904
+ if (config.queue) {
1905
+ const queueHandlers = createQueueHandlers(dbManager2.emulatorDb);
1906
+ app.post("/queue/send", queueHandlers.sendHandler);
1907
+ app.post("/queue/batch-send", queueHandlers.batchSendHandler);
1908
+ app.post("/queue/receive", queueHandlers.receiveHandler);
1909
+ app.post("/queue/ack", queueHandlers.ackHandler);
1910
+ app.post("/queue/retry", queueHandlers.retryHandler);
1911
+ }
1912
+ if (config.workflow) {
1913
+ const workflowHandlers = createWorkflowHandlers(dbManager2.emulatorDb, options.workerUrl);
1914
+ app.post("/workflow/trigger", workflowHandlers.triggerHandler);
1915
+ app.post("/workflow/status", workflowHandlers.statusHandler);
1916
+ app.post("/workflow/cancel", workflowHandlers.cancelHandler);
1917
+ app.post("/workflow/step/start", workflowHandlers.stepStartHandler);
1918
+ app.post("/workflow/step/complete", workflowHandlers.stepCompleteHandler);
1919
+ app.post("/workflow/step/fail", workflowHandlers.stepFailHandler);
1920
+ app.post("/workflow/complete", workflowHandlers.completeHandler);
1921
+ app.post("/workflow/fail", workflowHandlers.failHandler);
1922
+ }
1923
+ app.get("/health", (c) => c.json({ status: "ok" }));
1924
+ if (options.dashboardEnabled !== false) {
1925
+ createDashboardRoutes(app, dbManager2, config);
1926
+ }
1927
+ const serverPort = options.port ?? DEFAULT_MOCK_SERVER_PORT;
1928
+ return await new Promise((resolve) => {
1929
+ const server = serve({
1930
+ fetch: app.fetch,
1931
+ port: serverPort
1932
+ }, (info) => {
1933
+ resolve({
1934
+ port: info.port,
1935
+ close: () => new Promise((res) => {
1936
+ server.close(() => res());
1937
+ })
1938
+ });
1939
+ });
1940
+ });
1941
+ }
1942
+ var DEFAULT_MOCK_SERVER_PORT;
1943
+ var init_mock_server = __esm({
1944
+ "../emulator/dist/services/mock-server.js"() {
1945
+ init_dashboard_routes();
1946
+ init_db_service();
1947
+ init_queue_service();
1948
+ init_workflow_service();
1949
+ DEFAULT_MOCK_SERVER_PORT = 4003;
1950
+ }
1951
+ });
1952
+
1953
+ // ../emulator/dist/utils/logger.js
1954
+ function timestamp() {
1955
+ return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false });
1956
+ }
1957
+ function log(message) {
1958
+ console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.cyan}[ploy]${COLORS.reset} ${message}`);
1959
+ }
1960
+ function success(message) {
1961
+ console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.green}[ploy]${COLORS.reset} ${message}`);
1962
+ }
1963
+ function error(message) {
1964
+ console.error(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.red}[ploy]${COLORS.reset} ${message}`);
1965
+ }
1966
+ function debug(message, verbose) {
1967
+ if (verbose) {
1968
+ console.log(`${COLORS.dim}[${timestamp()}]${COLORS.reset} ${COLORS.magenta}[ploy:debug]${COLORS.reset} ${message}`);
1969
+ }
1970
+ }
1971
+ var COLORS;
1972
+ var init_logger = __esm({
1973
+ "../emulator/dist/utils/logger.js"() {
1974
+ COLORS = {
1975
+ reset: "\x1B[0m",
1976
+ dim: "\x1B[2m",
1977
+ cyan: "\x1B[36m",
1978
+ green: "\x1B[32m",
1979
+ yellow: "\x1B[33m",
1980
+ red: "\x1B[31m",
1981
+ magenta: "\x1B[35m"
1982
+ };
1983
+ }
1984
+ });
1985
+ function getProjectHash(projectDir) {
1986
+ return createHash("sha256").update(projectDir).digest("hex").slice(0, 12);
1987
+ }
1988
+ function getTempDir(projectDir) {
1989
+ const hash = getProjectHash(projectDir);
1990
+ return join(tmpdir(), `ploy-emulator-${hash}`);
1991
+ }
1992
+ function getDataDir(projectDir) {
1993
+ return join(projectDir, ".ploy");
1994
+ }
1995
+ function ensureDir(dir) {
1996
+ mkdirSync(dir, { recursive: true });
1997
+ }
1998
+ function ensureTempDir(projectDir) {
1999
+ const tempDir = getTempDir(projectDir);
2000
+ ensureDir(tempDir);
2001
+ return tempDir;
2002
+ }
2003
+ function ensureDataDir(projectDir) {
2004
+ const dataDir = getDataDir(projectDir);
2005
+ ensureDir(dataDir);
2006
+ ensureDir(join(dataDir, "db"));
2007
+ return dataDir;
2008
+ }
2009
+ var init_paths = __esm({
2010
+ "../emulator/dist/utils/paths.js"() {
2011
+ }
2012
+ });
2013
+ function initializeDatabases(projectDir) {
2014
+ const dataDir = ensureDataDir(projectDir);
2015
+ const d1Databases = /* @__PURE__ */ new Map();
2016
+ const emulatorDb = new Database(join(dataDir, "emulator.db"));
2017
+ emulatorDb.pragma("journal_mode = WAL");
2018
+ emulatorDb.exec(EMULATOR_SCHEMA);
2019
+ function getD1Database(bindingName) {
2020
+ let db = d1Databases.get(bindingName);
2021
+ if (!db) {
2022
+ db = new Database(join(dataDir, "db", `${bindingName}.db`));
2023
+ db.pragma("journal_mode = WAL");
2024
+ d1Databases.set(bindingName, db);
2025
+ }
2026
+ return db;
2027
+ }
2028
+ function close() {
2029
+ emulatorDb.close();
2030
+ for (const db of d1Databases.values()) {
2031
+ db.close();
2032
+ }
2033
+ d1Databases.clear();
2034
+ }
2035
+ return {
2036
+ emulatorDb,
2037
+ getD1Database,
2038
+ close
2039
+ };
2040
+ }
2041
+ var EMULATOR_SCHEMA;
2042
+ var init_sqlite = __esm({
2043
+ "../emulator/dist/utils/sqlite.js"() {
2044
+ init_paths();
2045
+ EMULATOR_SCHEMA = `
2046
+ -- Queue messages table
2047
+ CREATE TABLE IF NOT EXISTS queue_messages (
2048
+ id TEXT PRIMARY KEY,
2049
+ queue_name TEXT NOT NULL,
2050
+ payload TEXT NOT NULL,
2051
+ status TEXT DEFAULT 'pending',
2052
+ attempt INTEGER DEFAULT 0,
2053
+ delivery_id TEXT,
2054
+ visible_at INTEGER NOT NULL,
2055
+ created_at INTEGER DEFAULT (strftime('%s', 'now')),
2056
+ updated_at INTEGER DEFAULT (strftime('%s', 'now'))
2057
+ );
2058
+
2059
+ CREATE INDEX IF NOT EXISTS idx_queue_messages_status
2060
+ ON queue_messages(queue_name, status, visible_at);
2061
+
2062
+ -- Workflow executions table
2063
+ CREATE TABLE IF NOT EXISTS workflow_executions (
2064
+ id TEXT PRIMARY KEY,
2065
+ workflow_name TEXT NOT NULL,
2066
+ status TEXT DEFAULT 'pending',
2067
+ input TEXT,
2068
+ output TEXT,
2069
+ error TEXT,
2070
+ started_at INTEGER,
2071
+ completed_at INTEGER,
2072
+ created_at INTEGER DEFAULT (strftime('%s', 'now'))
2073
+ );
2074
+
2075
+ CREATE INDEX IF NOT EXISTS idx_workflow_executions_status
2076
+ ON workflow_executions(workflow_name, status);
2077
+
2078
+ -- Workflow steps table
2079
+ CREATE TABLE IF NOT EXISTS workflow_steps (
2080
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
2081
+ execution_id TEXT NOT NULL,
2082
+ step_name TEXT NOT NULL,
2083
+ step_index INTEGER NOT NULL,
2084
+ status TEXT DEFAULT 'pending',
2085
+ output TEXT,
2086
+ error TEXT,
2087
+ duration_ms INTEGER,
2088
+ created_at INTEGER DEFAULT (strftime('%s', 'now')),
2089
+ FOREIGN KEY (execution_id) REFERENCES workflow_executions(id)
2090
+ );
2091
+
2092
+ CREATE INDEX IF NOT EXISTS idx_workflow_steps_execution
2093
+ ON workflow_steps(execution_id, step_index);
2094
+ `;
2095
+ }
2096
+ });
2097
+ async function startEmulator(options = {}) {
2098
+ const emulator = new EmulatorServer(options);
2099
+ await emulator.start();
2100
+ return emulator;
2101
+ }
2102
+ var EmulatorServer;
2103
+ var init_emulator = __esm({
2104
+ "../emulator/dist/emulator.js"() {
2105
+ init_bundler();
2106
+ init_watcher();
2107
+ init_ploy_config2();
2108
+ init_workerd_config();
2109
+ init_mock_server();
2110
+ init_queue_service();
2111
+ init_logger();
2112
+ init_paths();
2113
+ init_sqlite();
2114
+ EmulatorServer = class {
2115
+ options;
2116
+ projectDir;
2117
+ tempDir = "";
2118
+ config = null;
2119
+ dbManager = null;
2120
+ mockServer = null;
2121
+ workerdProcess = null;
2122
+ fileWatcher = null;
2123
+ queueProcessor = null;
2124
+ constructor(options = {}) {
2125
+ const port = options.port ?? 8787;
2126
+ this.options = {
2127
+ port,
2128
+ host: options.host ?? "localhost",
2129
+ configPath: options.configPath ?? "ploy.yaml",
2130
+ watch: options.watch ?? true,
2131
+ verbose: options.verbose ?? false,
2132
+ dashboardPort: options.dashboardPort ?? port + 1e3
2133
+ };
2134
+ this.projectDir = process.cwd();
2135
+ }
2136
+ async start() {
2137
+ log("Starting Ploy emulator...");
2138
+ try {
2139
+ this.config = readPloyConfig2(this.projectDir, this.options.configPath);
2140
+ debug(`Loaded config: ${JSON.stringify(this.config)}`, this.options.verbose);
2141
+ this.tempDir = ensureTempDir(this.projectDir);
2142
+ ensureDataDir(this.projectDir);
2143
+ debug(`Temp dir: ${this.tempDir}`, this.options.verbose);
2144
+ this.dbManager = initializeDatabases(this.projectDir);
2145
+ debug("Initialized databases", this.options.verbose);
2146
+ const workerUrl = `http://${this.options.host}:${this.options.port}`;
2147
+ const dashboardPort = this.options.dashboardPort;
2148
+ this.mockServer = await startMockServer(this.dbManager, this.config, {
2149
+ workerUrl,
2150
+ port: dashboardPort,
2151
+ dashboardEnabled: true
2152
+ });
2153
+ debug(`Mock server started on port ${this.mockServer.port}`, this.options.verbose);
2154
+ const mockServiceUrl = `http://localhost:${this.mockServer.port}`;
2155
+ const entryPoint = getWorkerEntryPoint2(this.projectDir, this.config);
2156
+ debug(`Entry point: ${entryPoint}`, this.options.verbose);
2157
+ await this.bundle(entryPoint, mockServiceUrl);
2158
+ const workerdConfigPath = writeWorkerdConfig({
2159
+ tempDir: this.tempDir,
2160
+ bundlePath: join(this.tempDir, "worker.bundle.js"),
2161
+ port: this.options.port,
2162
+ mockServicePort: this.mockServer.port,
2163
+ config: this.config
2164
+ });
2165
+ await this.startWorkerd(workerdConfigPath);
2166
+ if (this.options.watch) {
2167
+ const srcDir = this.getSrcDir();
2168
+ this.fileWatcher = createFileWatcher(srcDir, async () => {
2169
+ log("Changes detected, rebuilding...");
2170
+ try {
2171
+ await this.bundle(entryPoint, mockServiceUrl);
2172
+ success("Rebuild complete");
2173
+ } catch (err) {
2174
+ error(`Rebuild failed: ${err instanceof Error ? err.message : String(err)}`);
2175
+ }
2176
+ });
2177
+ this.fileWatcher.start();
2178
+ debug(`Watching ${srcDir} for changes`, this.options.verbose);
2179
+ }
2180
+ if (this.config.queue) {
2181
+ const workerUrl2 = `http://${this.options.host}:${this.options.port}`;
2182
+ this.queueProcessor = createQueueProcessor(this.dbManager.emulatorDb, this.config.queue, workerUrl2);
2183
+ this.queueProcessor.start();
2184
+ debug("Queue processor started", this.options.verbose);
2185
+ }
2186
+ success(`Emulator running at http://${this.options.host}:${this.options.port}`);
2187
+ log(` Dashboard: http://${this.options.host}:${this.mockServer.port}`);
2188
+ if (this.config.db) {
2189
+ log(` DB bindings: ${Object.keys(this.config.db).join(", ")}`);
2190
+ }
2191
+ if (this.config.queue) {
2192
+ log(` Queue bindings: ${Object.keys(this.config.queue).join(", ")}`);
2193
+ }
2194
+ if (this.config.workflow) {
2195
+ log(` Workflow bindings: ${Object.keys(this.config.workflow).join(", ")}`);
2196
+ }
2197
+ this.setupSignalHandlers();
2198
+ } catch (err) {
2199
+ error(`Failed to start emulator: ${err instanceof Error ? err.message : String(err)}`);
2200
+ await this.stop();
2201
+ throw err;
2202
+ }
2203
+ }
2204
+ async bundle(entryPoint, mockServiceUrl) {
2205
+ if (!this.config) {
2206
+ throw new Error("Config not loaded");
2207
+ }
2208
+ await bundleWorker({
2209
+ projectDir: this.projectDir,
2210
+ tempDir: this.tempDir,
2211
+ entryPoint,
2212
+ config: this.config,
2213
+ mockServiceUrl
2214
+ });
2215
+ }
2216
+ async startWorkerd(configPath) {
2217
+ const args2 = ["serve", "--experimental", "--verbose", configPath];
2218
+ if (this.options.watch) {
2219
+ args2.push("--watch");
2220
+ }
2221
+ const workerdBin = this.findWorkerdBinary();
2222
+ log(`[ploy] Using workerd binary: ${workerdBin}`);
2223
+ debug(`Starting workerd: ${workerdBin} ${args2.join(" ")}`, this.options.verbose);
2224
+ return await new Promise((resolve, reject) => {
2225
+ const workerdBinDir = dirname(workerdBin);
2226
+ this.workerdProcess = spawn(workerdBin, args2, {
2227
+ cwd: this.tempDir,
2228
+ stdio: ["pipe", "pipe", "pipe"],
123
2229
  shell: true,
124
2230
  env: {
125
- ...process.env,
126
- PLOY_MOCK_SERVER_URL: `http://localhost:${dashboard.port}`,
127
- },
2231
+ ...process.env,
2232
+ PATH: `${workerdBinDir}:${process.env.PATH || ""}`
2233
+ }
2234
+ });
2235
+ let started = false;
2236
+ let stderrOutput = "";
2237
+ this.workerdProcess.stdout?.on("data", (data) => {
2238
+ const output = data.toString();
2239
+ if (this.options.verbose) {
2240
+ process.stdout.write(output);
2241
+ }
2242
+ if (!started && (output.includes("Listening") || output.includes("running"))) {
2243
+ started = true;
2244
+ resolve();
2245
+ }
2246
+ });
2247
+ this.workerdProcess.stderr?.on("data", (data) => {
2248
+ const output = data.toString();
2249
+ stderrOutput += output;
2250
+ log(`[workerd stderr] ${output.trim()}`);
2251
+ if (output.includes("error") || output.includes("Error")) {
2252
+ error(output.trim());
2253
+ } else if (this.options.verbose) {
2254
+ process.stderr.write(output);
2255
+ }
2256
+ if (!started && output.includes("Listening")) {
2257
+ started = true;
2258
+ resolve();
2259
+ }
2260
+ });
2261
+ this.workerdProcess.on("error", (err) => {
2262
+ error(`workerd error: ${err.message}`);
2263
+ if (!started) {
2264
+ reject(err);
2265
+ }
2266
+ });
2267
+ this.workerdProcess.on("exit", (code) => {
2268
+ if (code !== 0 && code !== null) {
2269
+ error(`workerd exited with code ${code}`);
2270
+ if (stderrOutput) {
2271
+ error(`workerd stderr: ${stderrOutput.trim()}`);
2272
+ }
2273
+ }
2274
+ if (!started) {
2275
+ reject(new Error(`workerd exited with code ${code}`));
2276
+ }
2277
+ });
2278
+ setTimeout(() => {
2279
+ if (!started) {
2280
+ started = true;
2281
+ resolve();
2282
+ }
2283
+ }, 2e3);
128
2284
  });
129
- }
130
- const cleanup = async () => {
131
- console.log("\nShutting down...");
132
- nextProcess.kill("SIGTERM");
133
- await dashboard.close();
134
- process.exit(0);
2285
+ }
2286
+ findWorkerdBinary() {
2287
+ const __filename2 = fileURLToPath(import.meta.url);
2288
+ const __dirname2 = dirname(__filename2);
2289
+ const emulatorPkgDir = join(__dirname2, "..");
2290
+ const possiblePaths = [
2291
+ join(emulatorPkgDir, "node_modules", ".bin", "workerd"),
2292
+ join(this.projectDir, "node_modules", ".bin", "workerd"),
2293
+ "workerd"
2294
+ ];
2295
+ for (const p of possiblePaths) {
2296
+ if (p === "workerd" || existsSync(p)) {
2297
+ return p;
2298
+ }
2299
+ }
2300
+ return "workerd";
2301
+ }
2302
+ getSrcDir() {
2303
+ const possibleDirs = [join(this.projectDir, "src"), this.projectDir];
2304
+ for (const dir of possibleDirs) {
2305
+ if (existsSync(dir)) {
2306
+ return dir;
2307
+ }
2308
+ }
2309
+ return this.projectDir;
2310
+ }
2311
+ setupSignalHandlers() {
2312
+ const handler = async () => {
2313
+ log("\nShutting down...");
2314
+ await this.stop();
2315
+ process.exit(0);
2316
+ };
2317
+ process.on("SIGINT", handler);
2318
+ process.on("SIGTERM", handler);
2319
+ }
2320
+ async stop() {
2321
+ if (this.queueProcessor) {
2322
+ this.queueProcessor.stop();
2323
+ this.queueProcessor = null;
2324
+ }
2325
+ if (this.fileWatcher) {
2326
+ this.fileWatcher.stop();
2327
+ this.fileWatcher = null;
2328
+ }
2329
+ if (this.workerdProcess) {
2330
+ this.workerdProcess.kill("SIGTERM");
2331
+ this.workerdProcess = null;
2332
+ }
2333
+ if (this.mockServer) {
2334
+ await this.mockServer.close();
2335
+ this.mockServer = null;
2336
+ }
2337
+ if (this.dbManager) {
2338
+ this.dbManager.close();
2339
+ this.dbManager = null;
2340
+ }
2341
+ log("Emulator stopped");
2342
+ }
135
2343
  };
136
- process.on("SIGINT", cleanup);
137
- process.on("SIGTERM", cleanup);
138
- nextProcess.on("exit", async (code) => {
139
- await dashboard.close();
140
- process.exit(code ?? 0);
2344
+ }
2345
+ });
2346
+
2347
+ // ../emulator/dist/nextjs-dev.js
2348
+ function createDevD1(databaseId, apiUrl) {
2349
+ return {
2350
+ async dump() {
2351
+ const response = await fetch(`${apiUrl}/db`, {
2352
+ method: "POST",
2353
+ headers: { "Content-Type": "application/json" },
2354
+ body: JSON.stringify({
2355
+ databaseId,
2356
+ method: "dump"
2357
+ })
2358
+ });
2359
+ if (!response.ok) {
2360
+ throw new Error(`DB dump failed: ${await response.text()}`);
2361
+ }
2362
+ return await response.arrayBuffer();
2363
+ },
2364
+ prepare(query) {
2365
+ let boundParams = [];
2366
+ const stmt = {
2367
+ bind(...values) {
2368
+ boundParams = values;
2369
+ return stmt;
2370
+ },
2371
+ async run() {
2372
+ const response = await fetch(`${apiUrl}/db`, {
2373
+ method: "POST",
2374
+ headers: { "Content-Type": "application/json" },
2375
+ body: JSON.stringify({
2376
+ databaseId,
2377
+ method: "prepare",
2378
+ query,
2379
+ params: boundParams
2380
+ })
2381
+ });
2382
+ if (!response.ok) {
2383
+ throw new Error(`DB query failed: ${await response.text()}`);
2384
+ }
2385
+ return await response.json();
2386
+ },
2387
+ async all() {
2388
+ return await stmt.run();
2389
+ },
2390
+ async first(colName) {
2391
+ const result = await stmt.run();
2392
+ if (result.results.length === 0) {
2393
+ return null;
2394
+ }
2395
+ const firstRow = result.results[0];
2396
+ if (colName) {
2397
+ return firstRow[colName] ?? null;
2398
+ }
2399
+ return firstRow;
2400
+ },
2401
+ async raw() {
2402
+ const result = await stmt.run();
2403
+ if (result.results.length === 0) {
2404
+ return [];
2405
+ }
2406
+ const keys = Object.keys(result.results[0]);
2407
+ return result.results.map((row) => keys.map((k) => row[k]));
2408
+ }
2409
+ };
2410
+ return stmt;
2411
+ },
2412
+ async exec(query) {
2413
+ const response = await fetch(`${apiUrl}/db`, {
2414
+ method: "POST",
2415
+ headers: { "Content-Type": "application/json" },
2416
+ body: JSON.stringify({
2417
+ databaseId,
2418
+ method: "exec",
2419
+ query
2420
+ })
2421
+ });
2422
+ if (!response.ok) {
2423
+ throw new Error(`DB exec failed: ${await response.text()}`);
2424
+ }
2425
+ return await response.json();
2426
+ },
2427
+ async batch(statements) {
2428
+ const stmts = statements.map((s) => {
2429
+ const data = s.__db_data;
2430
+ return data || s;
2431
+ });
2432
+ const response = await fetch(`${apiUrl}/db`, {
2433
+ method: "POST",
2434
+ headers: { "Content-Type": "application/json" },
2435
+ body: JSON.stringify({
2436
+ databaseId,
2437
+ method: "batch",
2438
+ statements: stmts
2439
+ })
2440
+ });
2441
+ if (!response.ok) {
2442
+ throw new Error(`DB batch failed: ${await response.text()}`);
2443
+ }
2444
+ return await response.json();
2445
+ }
2446
+ };
2447
+ }
2448
+ async function initPloyForDev(config) {
2449
+ if (process.env.NODE_ENV !== "development") {
2450
+ return;
2451
+ }
2452
+ if (globalThis.__PLOY_DEV_INITIALIZED__) {
2453
+ return;
2454
+ }
2455
+ globalThis.__PLOY_DEV_INITIALIZED__ = true;
2456
+ const configPath = config?.configPath || "./ploy.yaml";
2457
+ const projectDir = process.cwd();
2458
+ let ployConfig;
2459
+ try {
2460
+ ployConfig = readPloyConfig2(projectDir, configPath);
2461
+ } catch {
2462
+ if (config?.bindings?.db) {
2463
+ ployConfig = { db: config.bindings.db };
2464
+ } else {
2465
+ return;
2466
+ }
2467
+ }
2468
+ if (config?.bindings?.db) {
2469
+ ployConfig = { ...ployConfig, db: config.bindings.db };
2470
+ }
2471
+ if (!ployConfig.db || Object.keys(ployConfig.db).length === 0) {
2472
+ return;
2473
+ }
2474
+ ensureDataDir(projectDir);
2475
+ dbManager = initializeDatabases(projectDir);
2476
+ mockServer = await startMockServer(dbManager, ployConfig, {});
2477
+ const apiUrl = `http://localhost:${mockServer.port}`;
2478
+ const env = {};
2479
+ for (const [bindingName, databaseId] of Object.entries(ployConfig.db)) {
2480
+ env[bindingName] = createDevD1(databaseId, apiUrl);
2481
+ }
2482
+ const context = {
2483
+ env,
2484
+ cf: void 0,
2485
+ ctx: void 0
2486
+ };
2487
+ globalThis.__PLOY_DEV_CONTEXT__ = context;
2488
+ Object.defineProperty(globalThis, PLOY_CONTEXT_SYMBOL, {
2489
+ get() {
2490
+ return context;
2491
+ },
2492
+ configurable: true
2493
+ });
2494
+ const bindingNames = Object.keys(env);
2495
+ console.log(`[Ploy] Development context initialized with bindings: ${bindingNames.join(", ")}`);
2496
+ console.log(`[Ploy] Mock server running at ${apiUrl}`);
2497
+ const cleanup = async () => {
2498
+ if (mockServer) {
2499
+ await mockServer.close();
2500
+ mockServer = null;
2501
+ }
2502
+ if (dbManager) {
2503
+ dbManager.close();
2504
+ dbManager = null;
2505
+ }
2506
+ };
2507
+ process.on("exit", () => {
2508
+ if (dbManager) {
2509
+ dbManager.close();
2510
+ }
2511
+ });
2512
+ process.on("SIGINT", async () => {
2513
+ await cleanup();
2514
+ process.exit(0);
2515
+ });
2516
+ process.on("SIGTERM", async () => {
2517
+ await cleanup();
2518
+ process.exit(0);
2519
+ });
2520
+ }
2521
+ var PLOY_CONTEXT_SYMBOL, mockServer, dbManager;
2522
+ var init_nextjs_dev = __esm({
2523
+ "../emulator/dist/nextjs-dev.js"() {
2524
+ init_ploy_config2();
2525
+ init_mock_server();
2526
+ init_paths();
2527
+ init_sqlite();
2528
+ PLOY_CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("__ploy-context__");
2529
+ mockServer = null;
2530
+ dbManager = null;
2531
+ }
2532
+ });
2533
+
2534
+ // ../emulator/dist/dev-dashboard.js
2535
+ async function startDevDashboard(options = {}) {
2536
+ const projectDir = process.cwd();
2537
+ const configPath = options.configPath ?? "ploy.yaml";
2538
+ const port = options.port ?? 4e3;
2539
+ const verbose = options.verbose ?? false;
2540
+ log("Starting Ploy development dashboard...");
2541
+ let config;
2542
+ try {
2543
+ config = readPloyConfig2(projectDir, configPath);
2544
+ } catch {
2545
+ config = {};
2546
+ }
2547
+ debug(`Loaded config: ${JSON.stringify(config)}`, verbose);
2548
+ ensureDataDir(projectDir);
2549
+ const dbManager2 = initializeDatabases(projectDir);
2550
+ debug("Initialized databases", verbose);
2551
+ const mockServer2 = await startMockServer(dbManager2, config, {
2552
+ port,
2553
+ dashboardEnabled: true
2554
+ });
2555
+ debug(`Mock server started on port ${mockServer2.port}`, verbose);
2556
+ if (config.db) {
2557
+ log(` DB bindings: ${Object.keys(config.db).join(", ")}`);
2558
+ }
2559
+ if (config.queue) {
2560
+ log(` Queue bindings: ${Object.keys(config.queue).join(", ")}`);
2561
+ }
2562
+ if (config.workflow) {
2563
+ log(` Workflow bindings: ${Object.keys(config.workflow).join(", ")}`);
2564
+ }
2565
+ return {
2566
+ port: mockServer2.port,
2567
+ close: async () => {
2568
+ await mockServer2.close();
2569
+ dbManager2.close();
2570
+ log("Dashboard stopped");
2571
+ }
2572
+ };
2573
+ }
2574
+ var init_dev_dashboard = __esm({
2575
+ "../emulator/dist/dev-dashboard.js"() {
2576
+ init_ploy_config2();
2577
+ init_mock_server();
2578
+ init_logger();
2579
+ init_paths();
2580
+ init_sqlite();
2581
+ }
2582
+ });
2583
+
2584
+ // ../emulator/dist/index.js
2585
+ var dist_exports = {};
2586
+ __export(dist_exports, {
2587
+ DB_RUNTIME_CODE: () => DB_RUNTIME_CODE,
2588
+ DB_RUNTIME_CODE_PRODUCTION: () => DB_RUNTIME_CODE_PRODUCTION,
2589
+ EmulatorServer: () => EmulatorServer,
2590
+ initPloyForDev: () => initPloyForDev,
2591
+ startDevDashboard: () => startDevDashboard,
2592
+ startEmulator: () => startEmulator
2593
+ });
2594
+ var init_dist2 = __esm({
2595
+ "../emulator/dist/index.js"() {
2596
+ init_emulator();
2597
+ init_nextjs_dev();
2598
+ init_dev_dashboard();
2599
+ init_db_runtime();
2600
+ }
2601
+ });
2602
+ var CONFIG_DIR = join(homedir(), ".ploy");
2603
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
2604
+ function getConfigFile() {
2605
+ return CONFIG_FILE;
2606
+ }
2607
+ async function ensureConfigDir() {
2608
+ if (!existsSync(CONFIG_DIR)) {
2609
+ await mkdir(CONFIG_DIR, { recursive: true });
2610
+ }
2611
+ }
2612
+ async function readConfig() {
2613
+ try {
2614
+ if (!existsSync(CONFIG_FILE)) {
2615
+ return {};
2616
+ }
2617
+ const content = await readFile(CONFIG_FILE, "utf-8");
2618
+ return JSON.parse(content);
2619
+ } catch (error2) {
2620
+ console.warn(`Warning: Could not read config file: ${error2}`);
2621
+ return {};
2622
+ }
2623
+ }
2624
+ async function writeConfig(config) {
2625
+ await ensureConfigDir();
2626
+ await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
2627
+ await chmod(CONFIG_FILE, 384);
2628
+ }
2629
+ async function updateConfig(updates) {
2630
+ const config = await readConfig();
2631
+ const newConfig = { ...config, ...updates };
2632
+ await writeConfig(newConfig);
2633
+ return newConfig;
2634
+ }
2635
+ async function getAuthToken() {
2636
+ const envToken = process.env.PLOY_API_TOKEN || process.env.PLOY_API_KEY;
2637
+ if (envToken) {
2638
+ return envToken;
2639
+ }
2640
+ const config = await readConfig();
2641
+ if (config.auth?.accessToken) {
2642
+ if (config.auth.expiresAt) {
2643
+ const expiresAt = new Date(config.auth.expiresAt);
2644
+ if (expiresAt <= /* @__PURE__ */ new Date()) {
2645
+ console.warn(
2646
+ "Warning: Stored access token has expired. Please run 'ploy auth login' again."
2647
+ );
2648
+ await clearAuthToken();
2649
+ return void 0;
2650
+ }
2651
+ }
2652
+ return config.auth.accessToken;
2653
+ }
2654
+ return void 0;
2655
+ }
2656
+ async function setAuthToken(accessToken, expiresInSeconds) {
2657
+ const auth = {
2658
+ accessToken
2659
+ };
2660
+ if (expiresInSeconds) {
2661
+ const expiresAt = new Date(Date.now() + expiresInSeconds * 1e3);
2662
+ auth.expiresAt = expiresAt.toISOString();
2663
+ }
2664
+ await updateConfig({ auth });
2665
+ }
2666
+ async function clearAuthToken() {
2667
+ const config = await readConfig();
2668
+ delete config.auth;
2669
+ await writeConfig(config);
2670
+ }
2671
+ async function getApiUrl() {
2672
+ if (process.env.PLOY_API_URL) {
2673
+ return process.env.PLOY_API_URL;
2674
+ }
2675
+ const config = await readConfig();
2676
+ if (config.apiUrl) {
2677
+ return config.apiUrl;
2678
+ }
2679
+ return "https://api.meetploy.com";
2680
+ }
2681
+
2682
+ // src/lib/api-client.ts
2683
+ var ApiClientError = class extends Error {
2684
+ constructor(message, status, code) {
2685
+ super(message);
2686
+ this.status = status;
2687
+ this.code = code;
2688
+ this.name = "ApiClientError";
2689
+ }
2690
+ };
2691
+ async function requireAuthToken() {
2692
+ const token = await getAuthToken();
2693
+ if (!token) {
2694
+ console.error("Error: Authentication required");
2695
+ console.error("");
2696
+ console.error("Please authenticate using one of the following methods:");
2697
+ console.error("");
2698
+ console.error(" 1. Run 'ploy auth login' to authenticate via browser");
2699
+ console.error("");
2700
+ console.error(" 2. Set an environment variable:");
2701
+ console.error(" PLOY_API_TOKEN - Your Ploy API token");
2702
+ console.error(" PLOY_API_KEY - Your Ploy API key");
2703
+ console.error("");
2704
+ process.exit(1);
2705
+ }
2706
+ return token;
2707
+ }
2708
+ var clientInstance = null;
2709
+ async function getClient() {
2710
+ if (!clientInstance) {
2711
+ const baseUrl = await getApiUrl();
2712
+ const token = await requireAuthToken();
2713
+ clientInstance = createClient({
2714
+ baseUrl,
2715
+ headers: {
2716
+ Authorization: `Bearer ${token}`,
2717
+ "User-Agent": "ploy-cli/1.0.0"
2718
+ }
141
2719
  });
2720
+ }
2721
+ return clientInstance;
142
2722
  }
143
- if (command === "dev") {
144
- const options = parseDevArgs(args.slice(1));
145
- const projectDir = process.cwd();
146
- const isNextJs = await isNextJsProject(projectDir, options.config);
147
- if (isNextJs) {
148
- await startNextJsDev({
149
- port: options.port,
150
- host: options.host,
151
- config: options.config,
152
- verbose: options.verbose,
153
- dashboardPort: options.dashboardPort,
154
- });
2723
+ async function getApiClient() {
2724
+ return await getClient();
2725
+ }
2726
+
2727
+ // src/commands/api/db.ts
2728
+ function printDbHelp() {
2729
+ console.log("Usage: ploy api db <action> [options]");
2730
+ console.log("");
2731
+ console.log("Database operations via the Ploy API");
2732
+ console.log("");
2733
+ console.log("Actions:");
2734
+ console.log(" tables <database> List tables in a database");
2735
+ console.log(" analytics <database> View database analytics");
2736
+ console.log(" list List all databases (requires --org)");
2737
+ console.log("");
2738
+ console.log("Options:");
2739
+ console.log(
2740
+ " --org <id> Organization ID (required for most actions)"
2741
+ );
2742
+ console.log(" --project <id> Project ID (optional filter for list)");
2743
+ console.log(
2744
+ " --period <period> Analytics period: 24h, 7d, 30d (default: 24h)"
2745
+ );
2746
+ console.log(" --limit <n> Limit number of results (default: 20)");
2747
+ console.log(
2748
+ " --schema Show table schema details (for tables action)"
2749
+ );
2750
+ console.log(" -h, --help Show this help message");
2751
+ console.log("");
2752
+ console.log("Examples:");
2753
+ console.log(" ploy api db tables my-database --org org123");
2754
+ console.log(" ploy api db tables my-database --org org123 --schema");
2755
+ console.log(" ploy api db analytics my-database --org org123");
2756
+ console.log(" ploy api db analytics my-database --org org123 --period 7d");
2757
+ console.log(" ploy api db list --org org123");
2758
+ }
2759
+ function parseDbArgs(args2) {
2760
+ const options = {};
2761
+ if (args2.length === 0 || args2[0] === "-h" || args2[0] === "--help") {
2762
+ printDbHelp();
2763
+ process.exit(0);
2764
+ }
2765
+ options.action = args2[0];
2766
+ if (args2[1] && !args2[1].startsWith("-")) {
2767
+ options.databaseName = args2[1];
2768
+ }
2769
+ for (let i = 1; i < args2.length; i++) {
2770
+ const arg = args2[i];
2771
+ if (arg === "--org") {
2772
+ const nextArg = args2[i + 1];
2773
+ if (!nextArg || nextArg.startsWith("-")) {
2774
+ console.error("Error: --org requires a value");
2775
+ process.exit(1);
2776
+ }
2777
+ options.orgId = nextArg;
2778
+ i++;
2779
+ } else if (arg === "--project") {
2780
+ const nextArg = args2[i + 1];
2781
+ if (!nextArg || nextArg.startsWith("-")) {
2782
+ console.error("Error: --project requires a value");
2783
+ process.exit(1);
2784
+ }
2785
+ options.projectId = nextArg;
2786
+ i++;
2787
+ } else if (arg === "--period") {
2788
+ const nextArg = args2[i + 1];
2789
+ if (!nextArg || nextArg.startsWith("-")) {
2790
+ console.error("Error: --period requires a value");
2791
+ process.exit(1);
2792
+ }
2793
+ const validPeriods = ["24h", "7d", "30d"];
2794
+ if (!validPeriods.includes(nextArg)) {
2795
+ console.error(
2796
+ `Error: Invalid period. Valid options: ${validPeriods.join(", ")}`
2797
+ );
2798
+ process.exit(1);
2799
+ }
2800
+ options.period = nextArg;
2801
+ i++;
2802
+ } else if (arg === "--limit") {
2803
+ const nextArg = args2[i + 1];
2804
+ if (!nextArg || nextArg.startsWith("-")) {
2805
+ console.error("Error: --limit requires a number");
2806
+ process.exit(1);
2807
+ }
2808
+ const limit = parseInt(nextArg, 10);
2809
+ if (Number.isNaN(limit)) {
2810
+ console.error("Error: --limit must be a valid number");
2811
+ process.exit(1);
2812
+ }
2813
+ options.limit = limit;
2814
+ i++;
2815
+ } else if (arg === "--schema") {
2816
+ options.showSchema = true;
2817
+ } else if (arg === "-h" || arg === "--help") {
2818
+ printDbHelp();
2819
+ process.exit(0);
155
2820
  }
156
- else {
157
- const { startEmulator } = await import("@meetploy/emulator");
158
- await startEmulator({
159
- port: options.port,
160
- host: options.host,
161
- configPath: options.config,
162
- watch: options.watch,
163
- verbose: options.verbose,
164
- dashboardPort: options.dashboardPort,
165
- });
2821
+ }
2822
+ return options;
2823
+ }
2824
+ async function listTables(options) {
2825
+ const { databaseName, orgId, showSchema } = options;
2826
+ if (!databaseName) {
2827
+ console.error("Error: Database name/ID is required");
2828
+ console.error("");
2829
+ console.error("Usage: ploy api db tables <database>");
2830
+ process.exit(1);
2831
+ }
2832
+ if (!orgId) {
2833
+ console.error("Error: --org is required for database operations");
2834
+ console.error("");
2835
+ console.error("Usage: ploy api db tables <database> --org <org-id>");
2836
+ process.exit(1);
2837
+ }
2838
+ console.log(`Fetching tables for database: ${databaseName}...`);
2839
+ console.log("");
2840
+ try {
2841
+ const client = await getApiClient();
2842
+ const { data, error: error2, response } = await client.GET(
2843
+ "/projects/org/{orgId}/db/{databaseId}/tables",
2844
+ {
2845
+ params: { path: { orgId, databaseId: databaseName } }
2846
+ }
2847
+ );
2848
+ if (error2 || !data) {
2849
+ throw new ApiClientError(
2850
+ error2?.message || "Failed to fetch tables",
2851
+ response?.status || 500
2852
+ );
2853
+ }
2854
+ const tablesResult = data;
2855
+ if (!tablesResult.tables || tablesResult.tables.length === 0) {
2856
+ console.log("No tables found in this database.");
2857
+ return;
2858
+ }
2859
+ console.log("Tables");
2860
+ console.log("======");
2861
+ console.log("");
2862
+ if (showSchema) {
2863
+ const {
2864
+ data: schemaData,
2865
+ error: schemaError,
2866
+ response: schemaResponse
2867
+ } = await client.GET("/projects/org/{orgId}/db/{databaseId}/schema", {
2868
+ params: { path: { orgId, databaseId: databaseName } }
2869
+ });
2870
+ if (schemaError || !schemaData) {
2871
+ throw new ApiClientError(
2872
+ schemaError?.message || "Failed to fetch schema",
2873
+ schemaResponse?.status || 500
2874
+ );
2875
+ }
2876
+ const schemaResult = schemaData;
2877
+ for (const table of schemaResult.tables) {
2878
+ console.log(`Table: ${table.name}`);
2879
+ console.log("-".repeat(40));
2880
+ console.log(
2881
+ " " + "COLUMN".padEnd(20) + "TYPE".padEnd(15) + "NULLABLE".padEnd(10) + "PK"
2882
+ );
2883
+ console.log(" " + "-".repeat(50));
2884
+ for (const col of table.columns) {
2885
+ const nullable = col.notNull ? "NO" : "YES";
2886
+ const pk = col.primaryKey ? "YES" : "";
2887
+ console.log(
2888
+ " " + col.name.padEnd(20) + col.type.padEnd(15) + nullable.padEnd(10) + pk
2889
+ );
2890
+ }
2891
+ console.log("");
2892
+ }
2893
+ } else {
2894
+ console.log("NAME".padEnd(30) + "ROWS".padEnd(12) + "SIZE");
2895
+ console.log("-".repeat(55));
2896
+ for (const table of tablesResult.tables) {
2897
+ console.log(table.name.padEnd(30) + "-".padEnd(12) + "-");
2898
+ }
2899
+ }
2900
+ console.log("");
2901
+ } catch (error2) {
2902
+ if (error2 instanceof ApiClientError) {
2903
+ console.error(`Error: ${error2.message}`);
2904
+ if (error2.status === 404) {
2905
+ console.error(
2906
+ "The database was not found. Check the database name/ID."
2907
+ );
2908
+ }
2909
+ } else {
2910
+ throw error2;
166
2911
  }
2912
+ process.exit(1);
2913
+ }
2914
+ }
2915
+ async function viewAnalytics(options) {
2916
+ const { databaseName, orgId, period = "24h", limit = 10 } = options;
2917
+ if (!databaseName) {
2918
+ console.error("Error: Database name/ID is required");
2919
+ console.error("");
2920
+ console.error("Usage: ploy api db analytics <database>");
2921
+ process.exit(1);
2922
+ }
2923
+ if (!orgId) {
2924
+ console.error("Error: --org is required for database operations");
2925
+ console.error("");
2926
+ console.error("Usage: ploy api db analytics <database> --org <org-id>");
2927
+ process.exit(1);
2928
+ }
2929
+ console.log(
2930
+ `Fetching analytics for database: ${databaseName} (${period})...`
2931
+ );
2932
+ console.log("");
2933
+ try {
2934
+ const client = await getApiClient();
2935
+ const { data, error: error2, response } = await client.GET(
2936
+ "/projects/org/{orgId}/db/{databaseId}/insights",
2937
+ {
2938
+ params: {
2939
+ path: { orgId, databaseId: databaseName },
2940
+ query: {
2941
+ timeRange: period,
2942
+ limit: limit.toString()
2943
+ }
2944
+ }
2945
+ }
2946
+ );
2947
+ if (error2 || !data) {
2948
+ throw new ApiClientError(
2949
+ error2?.message || "Failed to fetch analytics",
2950
+ response?.status || 500
2951
+ );
2952
+ }
2953
+ const insightsResult = data;
2954
+ console.log("Database Analytics");
2955
+ console.log("==================");
2956
+ console.log("");
2957
+ console.log(` Period: ${insightsResult.timeRange}`);
2958
+ console.log(
2959
+ ` Total Queries: ${insightsResult.summary.totalQueries.toLocaleString()}`
2960
+ );
2961
+ console.log(
2962
+ ` Total Rows Read: ${insightsResult.summary.totalRowsRead.toLocaleString()}`
2963
+ );
2964
+ console.log(
2965
+ ` Total Rows Written: ${insightsResult.summary.totalRowsWritten.toLocaleString()}`
2966
+ );
2967
+ const avgDuration = insightsResult.summary.totalQueries > 0 ? insightsResult.summary.totalDurationMs / insightsResult.summary.totalQueries : 0;
2968
+ console.log(` Avg Query Duration: ${avgDuration.toFixed(2)}ms`);
2969
+ console.log("");
2970
+ if (insightsResult.topQueries && insightsResult.topQueries.length > 0) {
2971
+ console.log("Top Queries by Total Duration");
2972
+ console.log("-----------------------------");
2973
+ console.log("");
2974
+ for (let i = 0; i < insightsResult.topQueries.length; i++) {
2975
+ const insight = insightsResult.topQueries[i];
2976
+ console.log(`${i + 1}. ${truncateQuery(insight.queryText, 60)}`);
2977
+ console.log(` Calls: ${insight.totalCalls.toLocaleString()}`);
2978
+ console.log(` Avg Duration: ${insight.avgDurationMs.toFixed(2)}ms`);
2979
+ console.log(
2980
+ ` Total Duration: ${insight.totalDurationMs.toFixed(2)}ms`
2981
+ );
2982
+ console.log(` Rows Read: ${insight.totalRowsRead.toLocaleString()}`);
2983
+ console.log(
2984
+ ` Rows Written: ${insight.totalRowsWritten.toLocaleString()}`
2985
+ );
2986
+ console.log("");
2987
+ }
2988
+ }
2989
+ } catch (error2) {
2990
+ if (error2 instanceof ApiClientError) {
2991
+ console.error(`Error: ${error2.message}`);
2992
+ if (error2.status === 404) {
2993
+ console.error(
2994
+ "The database was not found. Check the database name/ID."
2995
+ );
2996
+ }
2997
+ } else {
2998
+ throw error2;
2999
+ }
3000
+ process.exit(1);
3001
+ }
3002
+ }
3003
+ async function listDatabases(options) {
3004
+ if (!options.orgId) {
3005
+ console.error("Error: --org is required for listing databases");
3006
+ console.error("");
3007
+ console.error("Usage: ploy api db list --org <org-id>");
3008
+ process.exit(1);
3009
+ }
3010
+ try {
3011
+ const client = await getApiClient();
3012
+ const { data, error: error2, response } = await client.GET(
3013
+ "/orgs/{id}/databases",
3014
+ {
3015
+ params: {
3016
+ path: { id: options.orgId },
3017
+ query: options.projectId ? { projectId: options.projectId } : void 0
3018
+ }
3019
+ }
3020
+ );
3021
+ if (error2 || !data) {
3022
+ throw new ApiClientError(
3023
+ error2?.message || "Failed to list databases",
3024
+ response?.status || 500
3025
+ );
3026
+ }
3027
+ const databases = data.databases;
3028
+ if (!databases || databases.length === 0) {
3029
+ console.log("No databases found.");
3030
+ return;
3031
+ }
3032
+ console.log("");
3033
+ console.log("Databases");
3034
+ console.log("=========");
3035
+ console.log("");
3036
+ console.log(
3037
+ "ID".padEnd(26) + "NAME".padEnd(25) + "REGION".padEnd(15) + "CREATED"
3038
+ );
3039
+ console.log("-".repeat(80));
3040
+ for (const db of databases) {
3041
+ const id = db.id.substring(0, Math.min(24, db.id.length));
3042
+ const name = db.name.substring(0, Math.min(23, db.name.length)).padEnd(25);
3043
+ const region = (db.region || "-").padEnd(15);
3044
+ const created = db.createdAt ? new Date(db.createdAt).toLocaleDateString() : "-";
3045
+ console.log(`${id} ${name}${region}${created}`);
3046
+ }
3047
+ console.log("");
3048
+ } catch (error2) {
3049
+ if (error2 instanceof ApiClientError) {
3050
+ console.error(`Error: ${error2.message}`);
3051
+ } else {
3052
+ throw error2;
3053
+ }
3054
+ process.exit(1);
3055
+ }
3056
+ }
3057
+ function truncateQuery(query, maxLength) {
3058
+ const normalized = query.replace(/\s+/g, " ").trim();
3059
+ if (normalized.length <= maxLength) {
3060
+ return normalized;
3061
+ }
3062
+ return normalized.substring(0, maxLength - 3) + "...";
3063
+ }
3064
+ async function dbCommand(options) {
3065
+ const { action } = options;
3066
+ if (!action) {
3067
+ printDbHelp();
3068
+ process.exit(1);
3069
+ }
3070
+ switch (action) {
3071
+ case "tables":
3072
+ await listTables(options);
3073
+ break;
3074
+ case "analytics":
3075
+ await viewAnalytics(options);
3076
+ break;
3077
+ case "list":
3078
+ await listDatabases(options);
3079
+ break;
3080
+ case "-h":
3081
+ case "--help":
3082
+ printDbHelp();
3083
+ process.exit(0);
3084
+ break;
3085
+ default:
3086
+ console.error(`Unknown action: ${action}`);
3087
+ console.error("");
3088
+ console.error("Available actions: tables, analytics, list");
3089
+ console.error("");
3090
+ console.error("Run 'ploy api db --help' for more information");
3091
+ process.exit(1);
3092
+ }
3093
+ }
3094
+
3095
+ // src/commands/api/deployment.ts
3096
+ function printDeploymentHelp() {
3097
+ console.log("Usage: ploy api deployment <action> [options]");
3098
+ console.log("");
3099
+ console.log("Manage deployments via the Ploy API");
3100
+ console.log("");
3101
+ console.log("Actions:");
3102
+ console.log(" retry <id> Retry a failed deployment");
3103
+ console.log(" logs <id> View deployment build logs");
3104
+ console.log(" view <id> View deployment details");
3105
+ console.log(" list List deployments (requires --project)");
3106
+ console.log("");
3107
+ console.log("Options:");
3108
+ console.log(" --branch <name> Filter by branch name (for view/list)");
3109
+ console.log(" --commit <sha> Filter by commit SHA (for view)");
3110
+ console.log(" --project <id> Project ID (required for list)");
3111
+ console.log(" --limit <n> Limit number of results (default: 20)");
3112
+ console.log(" -h, --help Show this help message");
3113
+ console.log("");
3114
+ console.log("Examples:");
3115
+ console.log(" ploy api deployment retry abc123");
3116
+ console.log(" ploy api deployment logs abc123");
3117
+ console.log(" ploy api deployment view abc123");
3118
+ console.log(" ploy api deployment view abc123 --branch main");
3119
+ console.log(" ploy api deployment list --project proj123");
167
3120
  }
168
- else if (command === "types") {
169
- const options = parseTypesArgs(args.slice(1));
170
- await typesCommand(options);
3121
+ function parseDeploymentArgs(args2) {
3122
+ const options = {};
3123
+ if (args2.length === 0 || args2[0] === "-h" || args2[0] === "--help") {
3124
+ printDeploymentHelp();
3125
+ process.exit(0);
3126
+ }
3127
+ options.action = args2[0];
3128
+ if (args2[1] && !args2[1].startsWith("-")) {
3129
+ options.deploymentId = args2[1];
3130
+ }
3131
+ for (let i = 1; i < args2.length; i++) {
3132
+ const arg = args2[i];
3133
+ if (arg === "--branch") {
3134
+ const nextArg = args2[i + 1];
3135
+ if (!nextArg || nextArg.startsWith("-")) {
3136
+ console.error("Error: --branch requires a value");
3137
+ process.exit(1);
3138
+ }
3139
+ options.branch = nextArg;
3140
+ i++;
3141
+ } else if (arg === "--commit") {
3142
+ const nextArg = args2[i + 1];
3143
+ if (!nextArg || nextArg.startsWith("-")) {
3144
+ console.error("Error: --commit requires a value");
3145
+ process.exit(1);
3146
+ }
3147
+ options.commitSha = nextArg;
3148
+ i++;
3149
+ } else if (arg === "--project") {
3150
+ const nextArg = args2[i + 1];
3151
+ if (!nextArg || nextArg.startsWith("-")) {
3152
+ console.error("Error: --project requires a value");
3153
+ process.exit(1);
3154
+ }
3155
+ options.projectId = nextArg;
3156
+ i++;
3157
+ } else if (arg === "--limit") {
3158
+ const nextArg = args2[i + 1];
3159
+ if (!nextArg || nextArg.startsWith("-")) {
3160
+ console.error("Error: --limit requires a number");
3161
+ process.exit(1);
3162
+ }
3163
+ const limit = parseInt(nextArg, 10);
3164
+ if (Number.isNaN(limit)) {
3165
+ console.error("Error: --limit requires a valid number");
3166
+ process.exit(1);
3167
+ }
3168
+ options.limit = limit;
3169
+ i++;
3170
+ } else if (arg === "-f" || arg === "--follow") {
3171
+ options.follow = true;
3172
+ } else if (arg === "-h" || arg === "--help") {
3173
+ printDeploymentHelp();
3174
+ process.exit(0);
3175
+ }
3176
+ }
3177
+ return options;
171
3178
  }
172
- else if (command === "build") {
173
- const options = parseBuildArgs(args.slice(1));
174
- await buildCommand(options);
3179
+ async function retryDeployment(deploymentId) {
3180
+ console.log(`Retrying deployment ${deploymentId}...`);
3181
+ try {
3182
+ const client = await getApiClient();
3183
+ const { data, error: error2, response } = await client.POST(
3184
+ "/deployments/{deploymentId}/retry",
3185
+ {
3186
+ params: { path: { deploymentId } }
3187
+ }
3188
+ );
3189
+ if (error2 || !data) {
3190
+ throw new ApiClientError(
3191
+ error2?.message || "Failed to retry deployment",
3192
+ response?.status || 500
3193
+ );
3194
+ }
3195
+ const retryResult = data;
3196
+ console.log("");
3197
+ console.log("Deployment retry initiated successfully");
3198
+ console.log("");
3199
+ console.log(` New Deployment ID: ${retryResult.newDeploymentId}`);
3200
+ console.log(` Message: ${retryResult.message}`);
3201
+ } catch (error2) {
3202
+ if (error2 instanceof ApiClientError) {
3203
+ console.error(`Error: ${error2.message}`);
3204
+ if (error2.status === 404) {
3205
+ console.error("The deployment was not found. Check the deployment ID.");
3206
+ } else if (error2.status === 400) {
3207
+ console.error(
3208
+ "The deployment cannot be retried (may not be in a failed state)."
3209
+ );
3210
+ }
3211
+ } else {
3212
+ throw error2;
3213
+ }
3214
+ process.exit(1);
3215
+ }
175
3216
  }
176
- else {
177
- console.error(`Unknown command: ${command}`);
178
- console.error("\nAvailable commands:");
179
- console.error(" build Build your Ploy project");
180
- console.error(" dev Start development server");
181
- console.error(" types Generate TypeScript types from ploy.yaml");
3217
+ async function viewDeploymentLogs(deploymentId, _options) {
3218
+ console.log(`Fetching logs for deployment ${deploymentId}...`);
3219
+ console.log("");
3220
+ try {
3221
+ const client = await getApiClient();
3222
+ const { data, error: error2, response } = await client.GET(
3223
+ "/deployments/{deploymentId}/logs",
3224
+ {
3225
+ params: { path: { deploymentId } }
3226
+ }
3227
+ );
3228
+ if (error2 || !data) {
3229
+ throw new ApiClientError(
3230
+ error2?.message || "Failed to fetch logs",
3231
+ response?.status || 500
3232
+ );
3233
+ }
3234
+ const logsResult = data;
3235
+ if (!logsResult.logs || logsResult.logs.length === 0) {
3236
+ console.log("No logs available for this deployment.");
3237
+ return;
3238
+ }
3239
+ for (const log2 of logsResult.logs) {
3240
+ const timestamp2 = new Date(log2.createdAt).toLocaleTimeString();
3241
+ const level = log2.level.toUpperCase().padEnd(5);
3242
+ const prefix = log2.command ? `[${log2.command}] ` : "";
3243
+ console.log(`${timestamp2} ${level} ${prefix}${log2.message}`);
3244
+ }
3245
+ } catch (error2) {
3246
+ if (error2 instanceof ApiClientError) {
3247
+ console.error(`Error: ${error2.message}`);
3248
+ if (error2.status === 404) {
3249
+ console.error("The deployment was not found. Check the deployment ID.");
3250
+ }
3251
+ } else {
3252
+ throw error2;
3253
+ }
182
3254
  process.exit(1);
3255
+ }
3256
+ }
3257
+ async function viewDeployment(deploymentId, _options) {
3258
+ try {
3259
+ const client = await getApiClient();
3260
+ const { data, error: error2, response } = await client.GET(
3261
+ "/deployments/{deploymentId}",
3262
+ {
3263
+ params: { path: { deploymentId } }
3264
+ }
3265
+ );
3266
+ if (error2 || !data) {
3267
+ throw new ApiClientError(
3268
+ error2?.message || "Failed to fetch deployment",
3269
+ response?.status || 500
3270
+ );
3271
+ }
3272
+ const deployment = data.deployment;
3273
+ console.log("");
3274
+ console.log("Deployment Details");
3275
+ console.log("==================");
3276
+ console.log("");
3277
+ console.log(` ID: ${deployment.id}`);
3278
+ console.log(` Status: ${formatStatus(deployment.status)}`);
3279
+ if (deployment.project) {
3280
+ console.log(` Project: ${deployment.project.name}`);
3281
+ }
3282
+ if (deployment.repository) {
3283
+ console.log(` Repository: ${deployment.repository.fullName}`);
3284
+ }
3285
+ if (deployment.branch) {
3286
+ console.log(` Branch: ${deployment.branch}`);
3287
+ }
3288
+ if (deployment.commitSha) {
3289
+ const commitDisplay = deployment.commitSha.length >= 8 ? deployment.commitSha.substring(0, 8) : deployment.commitSha;
3290
+ console.log(` Commit: ${commitDisplay}`);
3291
+ }
3292
+ if (deployment.commitMessage) {
3293
+ console.log(` Message: ${deployment.commitMessage}`);
3294
+ }
3295
+ if (deployment.url) {
3296
+ console.log(` URL: ${deployment.url}`);
3297
+ }
3298
+ console.log(
3299
+ ` Created: ${new Date(deployment.createdAt).toLocaleString()}`
3300
+ );
3301
+ console.log(
3302
+ ` Updated: ${new Date(deployment.updatedAt).toLocaleString()}`
3303
+ );
3304
+ console.log("");
3305
+ } catch (error2) {
3306
+ if (error2 instanceof ApiClientError) {
3307
+ console.error(`Error: ${error2.message}`);
3308
+ if (error2.status === 404) {
3309
+ console.error("The deployment was not found. Check the deployment ID.");
3310
+ }
3311
+ } else {
3312
+ throw error2;
3313
+ }
3314
+ process.exit(1);
3315
+ }
3316
+ }
3317
+ async function listDeployments(options) {
3318
+ if (!options.projectId) {
3319
+ console.error("Error: --project is required for listing deployments");
3320
+ console.error("");
3321
+ console.error("Usage: ploy api deployment list --project <project-id>");
3322
+ process.exit(1);
3323
+ }
3324
+ try {
3325
+ const client = await getApiClient();
3326
+ const { data, error: error2, response } = await client.GET("/deployments", {
3327
+ params: {
3328
+ query: {
3329
+ projectId: options.projectId,
3330
+ branch: options.branch,
3331
+ limit: options.limit?.toString() || "20"
3332
+ }
3333
+ }
3334
+ });
3335
+ if (error2 || !data) {
3336
+ throw new ApiClientError(
3337
+ error2?.message || "Failed to list deployments",
3338
+ response?.status || 500
3339
+ );
3340
+ }
3341
+ const deployments = data.deployments;
3342
+ if (!deployments || deployments.length === 0) {
3343
+ console.log("No deployments found.");
3344
+ return;
3345
+ }
3346
+ console.log("");
3347
+ console.log("Deployments");
3348
+ console.log("===========");
3349
+ console.log("");
3350
+ console.log(
3351
+ "ID".padEnd(12) + "STATUS".padEnd(12) + "BRANCH".padEnd(20) + "COMMIT".padEnd(10) + "CREATED"
3352
+ );
3353
+ console.log("-".repeat(70));
3354
+ for (const d of deployments) {
3355
+ const id = d.id.substring(0, Math.min(10, d.id.length));
3356
+ const status = d.status.padEnd(10);
3357
+ const branchSource = d.branch || "-";
3358
+ const branch = branchSource.substring(0, Math.min(18, branchSource.length)).padEnd(20);
3359
+ const commit = d.commitSha ? d.commitSha.substring(0, Math.min(8, d.commitSha.length)) : "-";
3360
+ const created = new Date(d.createdAt).toLocaleDateString();
3361
+ console.log(`${id} ${status} ${branch}${commit.padEnd(10)}${created}`);
3362
+ }
3363
+ console.log("");
3364
+ } catch (error2) {
3365
+ if (error2 instanceof ApiClientError) {
3366
+ console.error(`Error: ${error2.message}`);
3367
+ } else {
3368
+ throw error2;
3369
+ }
3370
+ process.exit(1);
3371
+ }
3372
+ }
3373
+ function formatStatus(status) {
3374
+ if (!process.stdout.isTTY) {
3375
+ return status;
3376
+ }
3377
+ const statusColors = {
3378
+ success: "\x1B[32msuccess\x1B[0m",
3379
+ // green
3380
+ failure: "\x1B[31mfailure\x1B[0m",
3381
+ // red
3382
+ pending: "\x1B[33mpending\x1B[0m",
3383
+ // yellow
3384
+ building: "\x1B[34mbuilding\x1B[0m",
3385
+ // blue
3386
+ syncing: "\x1B[36msyncing\x1B[0m",
3387
+ // cyan
3388
+ uploading: "\x1B[36muploading\x1B[0m",
3389
+ // cyan
3390
+ timeout: "\x1B[31mtimeout\x1B[0m"
3391
+ // red
3392
+ };
3393
+ return statusColors[status] || status;
3394
+ }
3395
+ async function deploymentCommand(options) {
3396
+ const { action, deploymentId } = options;
3397
+ if (!action) {
3398
+ printDeploymentHelp();
3399
+ process.exit(1);
3400
+ }
3401
+ switch (action) {
3402
+ case "retry":
3403
+ if (!deploymentId) {
3404
+ console.error("Error: Deployment ID is required");
3405
+ console.error("");
3406
+ console.error("Usage: ploy api deployment retry <deployment-id>");
3407
+ process.exit(1);
3408
+ }
3409
+ await retryDeployment(deploymentId);
3410
+ break;
3411
+ case "logs":
3412
+ if (!deploymentId) {
3413
+ console.error("Error: Deployment ID is required");
3414
+ console.error("");
3415
+ console.error("Usage: ploy api deployment logs <deployment-id>");
3416
+ process.exit(1);
3417
+ }
3418
+ await viewDeploymentLogs(deploymentId);
3419
+ break;
3420
+ case "view":
3421
+ if (!deploymentId) {
3422
+ console.error("Error: Deployment ID is required");
3423
+ console.error("");
3424
+ console.error("Usage: ploy api deployment view <deployment-id>");
3425
+ process.exit(1);
3426
+ }
3427
+ await viewDeployment(deploymentId);
3428
+ break;
3429
+ case "list":
3430
+ await listDeployments(options);
3431
+ break;
3432
+ case "-h":
3433
+ case "--help":
3434
+ printDeploymentHelp();
3435
+ process.exit(0);
3436
+ break;
3437
+ default:
3438
+ console.error(`Unknown action: ${action}`);
3439
+ console.error("");
3440
+ console.error("Available actions: retry, logs, view, list");
3441
+ console.error("");
3442
+ console.error("Run 'ploy api deployment --help' for more information");
3443
+ process.exit(1);
3444
+ }
3445
+ }
3446
+
3447
+ // src/commands/api/index.ts
3448
+ function printApiHelp() {
3449
+ console.log("Usage: ploy api <subcommand> [options]");
3450
+ console.log("");
3451
+ console.log("Interact with the Ploy API from the command line");
3452
+ console.log("");
3453
+ console.log("Subcommands:");
3454
+ console.log(" deployment Manage deployments (retry, logs, view)");
3455
+ console.log(" db Database operations (tables, analytics)");
3456
+ console.log("");
3457
+ console.log("Options:");
3458
+ console.log(" -h, --help Show this help message");
3459
+ console.log("");
3460
+ console.log("Authentication:");
3461
+ console.log(
3462
+ " Run 'ploy auth login' to authenticate via browser (recommended)"
3463
+ );
3464
+ console.log("");
3465
+ console.log(" Or set environment variables:");
3466
+ console.log(" PLOY_API_TOKEN Your Ploy API token");
3467
+ console.log(
3468
+ " PLOY_API_URL API base URL (default: https://api.meetploy.com)"
3469
+ );
3470
+ console.log("");
3471
+ console.log("Examples:");
3472
+ console.log(" ploy api deployment retry abc123");
3473
+ console.log(" ploy api deployment logs abc123");
3474
+ console.log(" ploy api deployment view abc123 --branch main");
3475
+ console.log(" ploy api db tables my-database");
3476
+ console.log(" ploy api db analytics my-database");
3477
+ console.log("");
3478
+ console.log(
3479
+ "Run 'ploy api <subcommand> --help' for subcommand-specific help"
3480
+ );
3481
+ }
3482
+ function parseApiArgs(args2) {
3483
+ const options = {
3484
+ subArgs: []
3485
+ };
3486
+ if (args2.length === 0 || args2[0] === "-h" || args2[0] === "--help") {
3487
+ printApiHelp();
3488
+ process.exit(0);
3489
+ }
3490
+ options.subcommand = args2[0];
3491
+ options.subArgs = args2.slice(1);
3492
+ return options;
3493
+ }
3494
+ async function apiCommand(options) {
3495
+ const { subcommand, subArgs } = options;
3496
+ if (!subcommand) {
3497
+ printApiHelp();
3498
+ process.exit(1);
3499
+ }
3500
+ switch (subcommand) {
3501
+ case "deployment":
3502
+ case "deploy": {
3503
+ const deploymentOptions = parseDeploymentArgs(subArgs);
3504
+ await deploymentCommand(deploymentOptions);
3505
+ break;
3506
+ }
3507
+ case "db":
3508
+ case "database": {
3509
+ const dbOptions = parseDbArgs(subArgs);
3510
+ await dbCommand(dbOptions);
3511
+ break;
3512
+ }
3513
+ case "-h":
3514
+ case "--help":
3515
+ printApiHelp();
3516
+ process.exit(0);
3517
+ break;
3518
+ default:
3519
+ console.error(`Unknown subcommand: ${subcommand}`);
3520
+ console.error("");
3521
+ console.error("Available subcommands:");
3522
+ console.error(" deployment Manage deployments");
3523
+ console.error(" db Database operations");
3524
+ console.error("");
3525
+ console.error("Run 'ploy api --help' for more information");
3526
+ process.exit(1);
3527
+ }
3528
+ }
3529
+ var OAUTH_CLIENT_ID = "ploy-cli";
3530
+ var CALLBACK_PORT = 9876;
3531
+ var CALLBACK_HOST = "127.0.0.1";
3532
+ function printLoginHelp() {
3533
+ console.log("Usage: ploy login [options]");
3534
+ console.log("");
3535
+ console.log("Authenticate with the Ploy API via browser");
3536
+ console.log("");
3537
+ console.log("Options:");
3538
+ console.log(
3539
+ " --api-url <url> API base URL (default: https://api.meetploy.com)"
3540
+ );
3541
+ console.log(" -h, --help Show this help message");
3542
+ console.log("");
3543
+ console.log("This command will:");
3544
+ console.log(" 1. Open your browser to authenticate");
3545
+ console.log(" 2. Store the access token in ~/.ploy/config.json");
3546
+ console.log("");
3547
+ console.log("Environment Variables:");
3548
+ console.log(" PLOY_API_URL API base URL (alternative to --api-url)");
3549
+ }
3550
+ function parseLoginArgs(args2) {
3551
+ const options = {};
3552
+ for (let i = 0; i < args2.length; i++) {
3553
+ const arg = args2[i];
3554
+ if (arg === "--api-url") {
3555
+ const nextArg = args2[i + 1];
3556
+ if (!nextArg || nextArg.startsWith("-")) {
3557
+ console.error("Error: --api-url requires a value");
3558
+ process.exit(1);
3559
+ }
3560
+ options.apiUrl = nextArg;
3561
+ i++;
3562
+ } else if (arg === "-h" || arg === "--help") {
3563
+ printLoginHelp();
3564
+ process.exit(0);
3565
+ }
3566
+ }
3567
+ return options;
3568
+ }
3569
+ function openBrowser(url) {
3570
+ const platform = process.platform;
3571
+ let command2;
3572
+ if (platform === "darwin") {
3573
+ command2 = `open "${url}"`;
3574
+ } else if (platform === "win32") {
3575
+ command2 = `start "" "${url}"`;
3576
+ } else {
3577
+ command2 = `xdg-open "${url}"`;
3578
+ }
3579
+ exec(command2, (error2) => {
3580
+ if (error2) {
3581
+ console.log("");
3582
+ console.log("Could not open browser automatically.");
3583
+ console.log("Please open the following URL manually:");
3584
+ console.log("");
3585
+ console.log(` ${url}`);
3586
+ console.log("");
3587
+ }
3588
+ });
3589
+ }
3590
+ function generateState() {
3591
+ return randomBytes(16).toString("hex");
3592
+ }
3593
+ async function exchangeCodeForToken(apiUrl, code, redirectUri) {
3594
+ const tokenUrl = `${apiUrl}/oauth/token`;
3595
+ const response = await fetch(tokenUrl, {
3596
+ method: "POST",
3597
+ headers: {
3598
+ "Content-Type": "application/json"
3599
+ },
3600
+ body: JSON.stringify({
3601
+ grant_type: "authorization_code",
3602
+ code,
3603
+ redirect_uri: redirectUri,
3604
+ client_id: OAUTH_CLIENT_ID
3605
+ // Note: client_secret may not be needed for public clients
3606
+ })
3607
+ });
3608
+ if (!response.ok) {
3609
+ const errorBody = await response.text();
3610
+ throw new Error(`Token exchange failed: ${response.status} - ${errorBody}`);
3611
+ }
3612
+ return await response.json();
3613
+ }
3614
+ function createCallbackServer(expectedState, apiUrl) {
3615
+ let server = null;
3616
+ let resolveListening;
3617
+ let rejectListening;
3618
+ const waitForListening = new Promise((resolve, reject) => {
3619
+ resolveListening = resolve;
3620
+ rejectListening = reject;
3621
+ });
3622
+ const tokenPromise = new Promise((resolve, reject) => {
3623
+ const timeout = setTimeout(
3624
+ () => {
3625
+ if (server) {
3626
+ server.close();
3627
+ }
3628
+ reject(
3629
+ new Error("Login timeout - no response received within 5 minutes")
3630
+ );
3631
+ },
3632
+ 5 * 60 * 1e3
3633
+ );
3634
+ server = createServer(async (req, res) => {
3635
+ const reqUrl = new URL(
3636
+ req.url || "/",
3637
+ `http://${CALLBACK_HOST}:${CALLBACK_PORT}`
3638
+ );
3639
+ if (reqUrl.pathname !== "/callback") {
3640
+ res.writeHead(404);
3641
+ res.end("Not found");
3642
+ return;
3643
+ }
3644
+ const code = reqUrl.searchParams.get("code");
3645
+ const state = reqUrl.searchParams.get("state");
3646
+ const error2 = reqUrl.searchParams.get("error");
3647
+ const errorDescription = reqUrl.searchParams.get("error_description");
3648
+ if (error2) {
3649
+ clearTimeout(timeout);
3650
+ res.writeHead(400, { "Content-Type": "text/html" });
3651
+ res.end(`
3652
+ <!DOCTYPE html>
3653
+ <html>
3654
+ <head><title>Login Failed</title></head>
3655
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
3656
+ <h1>Login Failed</h1>
3657
+ <p>Error: ${error2}</p>
3658
+ <p>${errorDescription || ""}</p>
3659
+ <p>You can close this window.</p>
3660
+ </body>
3661
+ </html>
3662
+ `);
3663
+ server?.close();
3664
+ reject(new Error(`OAuth error: ${error2} - ${errorDescription || ""}`));
3665
+ return;
3666
+ }
3667
+ if (!code) {
3668
+ clearTimeout(timeout);
3669
+ res.writeHead(400, { "Content-Type": "text/html" });
3670
+ res.end(`
3671
+ <!DOCTYPE html>
3672
+ <html>
3673
+ <head><title>Login Failed</title></head>
3674
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
3675
+ <h1>Login Failed</h1>
3676
+ <p>No authorization code received.</p>
3677
+ <p>You can close this window.</p>
3678
+ </body>
3679
+ </html>
3680
+ `);
3681
+ server?.close();
3682
+ reject(new Error("No authorization code received"));
3683
+ return;
3684
+ }
3685
+ if (state !== expectedState) {
3686
+ clearTimeout(timeout);
3687
+ res.writeHead(400, { "Content-Type": "text/html" });
3688
+ res.end(`
3689
+ <!DOCTYPE html>
3690
+ <html>
3691
+ <head><title>Login Failed</title></head>
3692
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
3693
+ <h1>Login Failed</h1>
3694
+ <p>State mismatch - possible CSRF attack.</p>
3695
+ <p>You can close this window.</p>
3696
+ </body>
3697
+ </html>
3698
+ `);
3699
+ server?.close();
3700
+ reject(new Error("State mismatch - possible CSRF attack"));
3701
+ return;
3702
+ }
3703
+ try {
3704
+ const redirectUri = `http://${CALLBACK_HOST}:${CALLBACK_PORT}/callback`;
3705
+ const tokenResponse = await exchangeCodeForToken(
3706
+ apiUrl,
3707
+ code,
3708
+ redirectUri
3709
+ );
3710
+ clearTimeout(timeout);
3711
+ res.writeHead(200, { "Content-Type": "text/html" });
3712
+ res.end(`
3713
+ <!DOCTYPE html>
3714
+ <html>
3715
+ <head><title>Login Successful</title></head>
3716
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
3717
+ <h1>Login Successful!</h1>
3718
+ <p>You have been authenticated with Ploy.</p>
3719
+ <p>You can close this window and return to your terminal.</p>
3720
+ </body>
3721
+ </html>
3722
+ `);
3723
+ server?.close();
3724
+ resolve(tokenResponse);
3725
+ } catch (err) {
3726
+ clearTimeout(timeout);
3727
+ res.writeHead(500, { "Content-Type": "text/html" });
3728
+ res.end(`
3729
+ <!DOCTYPE html>
3730
+ <html>
3731
+ <head><title>Login Failed</title></head>
3732
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
3733
+ <h1>Login Failed</h1>
3734
+ <p>Failed to exchange authorization code for token.</p>
3735
+ <p>${err instanceof Error ? err.message : "Unknown error"}</p>
3736
+ <p>You can close this window.</p>
3737
+ </body>
3738
+ </html>
3739
+ `);
3740
+ server?.close();
3741
+ reject(err);
3742
+ }
3743
+ });
3744
+ server.on("error", (err) => {
3745
+ clearTimeout(timeout);
3746
+ if (err.code === "EADDRINUSE") {
3747
+ const portError = new Error(
3748
+ `Port ${CALLBACK_PORT} is already in use. Please close any other process using this port and try again.`
3749
+ );
3750
+ reject(portError);
3751
+ rejectListening(portError);
3752
+ } else {
3753
+ const serverError = new Error(
3754
+ `Failed to start callback server: ${err.message}`
3755
+ );
3756
+ reject(serverError);
3757
+ rejectListening(serverError);
3758
+ }
3759
+ });
3760
+ server.listen(CALLBACK_PORT, CALLBACK_HOST, () => {
3761
+ resolveListening();
3762
+ });
3763
+ });
3764
+ return { tokenPromise, waitForListening };
3765
+ }
3766
+ async function loginCommand(options = {}) {
3767
+ const apiUrl = options.apiUrl || await getApiUrl();
3768
+ const state = generateState();
3769
+ const redirectUri = `http://${CALLBACK_HOST}:${CALLBACK_PORT}/callback`;
3770
+ console.log("Starting login flow...");
3771
+ console.log("");
3772
+ const authUrl = new URL(`${apiUrl}/oauth/authorize`);
3773
+ authUrl.searchParams.set("response_type", "code");
3774
+ authUrl.searchParams.set("client_id", OAUTH_CLIENT_ID);
3775
+ authUrl.searchParams.set("redirect_uri", redirectUri);
3776
+ authUrl.searchParams.set("state", state);
3777
+ authUrl.searchParams.set("scope", "openid profile email");
3778
+ const { tokenPromise, waitForListening } = createCallbackServer(
3779
+ state,
3780
+ apiUrl
3781
+ );
3782
+ try {
3783
+ await waitForListening;
3784
+ } catch (error2) {
3785
+ console.error(
3786
+ `Error: ${error2 instanceof Error ? error2.message : "Failed to start server"}`
3787
+ );
3788
+ process.exit(1);
3789
+ }
3790
+ console.log("Opening browser for authentication...");
3791
+ openBrowser(authUrl.toString());
3792
+ console.log("");
3793
+ console.log("Waiting for authentication...");
3794
+ console.log("(Press Ctrl+C to cancel)");
3795
+ console.log("");
3796
+ try {
3797
+ const tokenResponse = await tokenPromise;
3798
+ await setAuthToken(tokenResponse.access_token, tokenResponse.expires_in);
3799
+ console.log("Login successful!");
3800
+ console.log("");
3801
+ console.log(`Credentials saved to: ${getConfigFile()}`);
3802
+ console.log("");
3803
+ console.log("You can now use 'ploy api' commands.");
3804
+ process.exit(0);
3805
+ } catch (error2) {
3806
+ console.error("");
3807
+ console.error(
3808
+ `Login failed: ${error2 instanceof Error ? error2.message : "Unknown error"}`
3809
+ );
3810
+ process.exit(1);
3811
+ }
3812
+ }
3813
+
3814
+ // src/commands/auth/logout.ts
3815
+ function printLogoutHelp() {
3816
+ console.log("Usage: ploy logout [options]");
3817
+ console.log("");
3818
+ console.log("Log out from the Ploy API");
3819
+ console.log("");
3820
+ console.log("Options:");
3821
+ console.log(" -h, --help Show this help message");
3822
+ console.log("");
3823
+ console.log(
3824
+ "This command removes stored credentials from ~/.ploy/config.json"
3825
+ );
3826
+ }
3827
+ function parseLogoutArgs(args2) {
3828
+ for (const arg of args2) {
3829
+ if (arg === "-h" || arg === "--help") {
3830
+ printLogoutHelp();
3831
+ process.exit(0);
3832
+ }
3833
+ }
3834
+ }
3835
+ async function logoutCommand() {
3836
+ const config = await readConfig();
3837
+ if (!config.auth?.accessToken) {
3838
+ console.log("You are not logged in.");
3839
+ return;
3840
+ }
3841
+ await clearAuthToken();
3842
+ console.log("Logged out successfully.");
3843
+ console.log("");
3844
+ console.log(`Credentials removed from: ${getConfigFile()}`);
3845
+ }
3846
+
3847
+ // src/commands/auth/index.ts
3848
+ function printAuthHelp() {
3849
+ console.log("Usage: ploy auth <subcommand> [options]");
3850
+ console.log("");
3851
+ console.log("Manage authentication with Ploy");
3852
+ console.log("");
3853
+ console.log("Subcommands:");
3854
+ console.log(" login Authenticate with Ploy via browser");
3855
+ console.log(" logout Clear stored credentials");
3856
+ console.log("");
3857
+ console.log("Options:");
3858
+ console.log(" -h, --help Show this help message");
3859
+ console.log("");
3860
+ console.log("Examples:");
3861
+ console.log(" ploy auth login");
3862
+ console.log(" ploy auth logout");
3863
+ console.log("");
3864
+ console.log(
3865
+ "Run 'ploy auth <subcommand> --help' for subcommand-specific help"
3866
+ );
3867
+ }
3868
+ function parseAuthArgs(args2) {
3869
+ const options = {
3870
+ subArgs: []
3871
+ };
3872
+ if (args2.length === 0 || args2[0] === "-h" || args2[0] === "--help") {
3873
+ printAuthHelp();
3874
+ process.exit(0);
3875
+ }
3876
+ options.subcommand = args2[0];
3877
+ options.subArgs = args2.slice(1);
3878
+ return options;
3879
+ }
3880
+ async function authCommand(options) {
3881
+ const { subcommand, subArgs } = options;
3882
+ if (!subcommand) {
3883
+ printAuthHelp();
3884
+ process.exit(1);
3885
+ }
3886
+ switch (subcommand) {
3887
+ case "login": {
3888
+ const loginOptions = parseLoginArgs(subArgs);
3889
+ await loginCommand(loginOptions);
3890
+ break;
3891
+ }
3892
+ case "logout": {
3893
+ parseLogoutArgs(subArgs);
3894
+ await logoutCommand();
3895
+ break;
3896
+ }
3897
+ case "-h":
3898
+ case "--help":
3899
+ printAuthHelp();
3900
+ process.exit(0);
3901
+ break;
3902
+ default:
3903
+ console.error(`Unknown subcommand: ${subcommand}`);
3904
+ console.error("");
3905
+ console.error("Available subcommands:");
3906
+ console.error(" login Authenticate with Ploy");
3907
+ console.error(" logout Clear stored credentials");
3908
+ console.error("");
3909
+ console.error("Run 'ploy auth --help' for more information");
3910
+ process.exit(1);
3911
+ }
3912
+ }
3913
+
3914
+ // src/commands/build.ts
3915
+ init_cli();
3916
+ function detectPackageManager() {
3917
+ const cwd = process.cwd();
3918
+ if (existsSync(join(cwd, "pnpm-lock.yaml"))) {
3919
+ return "pnpm";
3920
+ }
3921
+ if (existsSync(join(cwd, "yarn.lock"))) {
3922
+ return "yarn";
3923
+ }
3924
+ return "npm";
3925
+ }
3926
+ function runWranglerBuild() {
3927
+ return new Promise((resolve, reject) => {
3928
+ const packageManager = detectPackageManager();
3929
+ let command2;
3930
+ let args2;
3931
+ if (packageManager === "npm") {
3932
+ command2 = "npx";
3933
+ args2 = ["wrangler", "build"];
3934
+ } else if (packageManager === "yarn") {
3935
+ command2 = "yarn";
3936
+ args2 = ["wrangler", "build"];
3937
+ } else {
3938
+ command2 = "pnpm";
3939
+ args2 = ["wrangler", "build"];
3940
+ }
3941
+ console.log(`Running: ${command2} ${args2.join(" ")}`);
3942
+ const child = spawn(command2, args2, {
3943
+ cwd: process.cwd(),
3944
+ stdio: "inherit",
3945
+ shell: process.platform === "win32"
3946
+ });
3947
+ child.on("error", (error2) => {
3948
+ reject(new Error(`Failed to run wrangler build: ${error2.message}`));
3949
+ });
3950
+ child.on("close", (code) => {
3951
+ if (code === 0) {
3952
+ resolve();
3953
+ } else {
3954
+ reject(new Error(`wrangler build exited with code ${code}`));
3955
+ }
3956
+ });
3957
+ });
3958
+ }
3959
+ async function buildCommand(options = {}) {
3960
+ const cwd = process.cwd();
3961
+ const configFile = options.config || "ploy.yaml";
3962
+ console.log("Validating ploy.yaml...");
3963
+ const config = readPloyConfigSync(cwd, options.config);
3964
+ validatePloyConfig(config, configFile);
3965
+ console.log("Configuration valid.");
3966
+ console.log("");
3967
+ await runWranglerBuild();
3968
+ }
3969
+ function parseBuildArgs(args2) {
3970
+ const options = {};
3971
+ for (let i = 0; i < args2.length; i++) {
3972
+ const arg = args2[i];
3973
+ if (arg === "-c" || arg === "--config") {
3974
+ const nextArg = args2[i + 1];
3975
+ if (!nextArg || nextArg.startsWith("-")) {
3976
+ console.error(`Error: ${arg} requires a path argument`);
3977
+ process.exit(1);
3978
+ }
3979
+ options.config = nextArg;
3980
+ i++;
3981
+ } else if (arg === "-h" || arg === "--help") {
3982
+ printBuildHelp();
3983
+ process.exit(0);
3984
+ }
3985
+ }
3986
+ return options;
3987
+ }
3988
+ function printBuildHelp() {
3989
+ console.log("Usage: ploy build [options]");
3990
+ console.log("");
3991
+ console.log("Build your Ploy project");
3992
+ console.log("");
3993
+ console.log("Options:");
3994
+ console.log(
3995
+ " -c, --config <path> Path to config file (default: ploy.yaml)"
3996
+ );
3997
+ console.log(" -h, --help Show this help message");
3998
+ console.log("");
3999
+ console.log("Examples:");
4000
+ console.log(
4001
+ " ploy build Build using ploy.yaml in current directory"
4002
+ );
4003
+ console.log(
4004
+ " ploy build -c custom.yaml Build using a custom config file"
4005
+ );
4006
+ }
4007
+
4008
+ // src/commands/types.ts
4009
+ init_cli();
4010
+ async function updateTsConfigInclude(cwd, outputPath) {
4011
+ const tsconfigPath = join(cwd, "tsconfig.json");
4012
+ try {
4013
+ const content = await readFile(tsconfigPath, "utf-8");
4014
+ const tsconfig = JSON.parse(content);
4015
+ if (!tsconfig.include) {
4016
+ tsconfig.include = [];
4017
+ }
4018
+ if (tsconfig.include.includes(outputPath)) {
4019
+ return false;
4020
+ }
4021
+ tsconfig.include.push(outputPath);
4022
+ await writeFile(tsconfigPath, JSON.stringify(tsconfig, null, " ") + "\n");
4023
+ return true;
4024
+ } catch (error2) {
4025
+ if (error2 && typeof error2 === "object" && "code" in error2) {
4026
+ if (error2.code === "ENOENT") {
4027
+ return false;
4028
+ }
4029
+ }
4030
+ console.warn(`Warning: Could not update tsconfig.json: ${error2}`);
4031
+ return false;
4032
+ }
4033
+ }
4034
+ function generateEnvType(config) {
4035
+ const imports = [];
4036
+ const properties = [];
4037
+ if (config.ai) {
4038
+ properties.push(" AI_URL: string;");
4039
+ properties.push(" AI_TOKEN: string;");
4040
+ }
4041
+ if (config.db) {
4042
+ imports.push("D1Database");
4043
+ for (const bindingName of Object.keys(config.db)) {
4044
+ properties.push(` ${bindingName}: D1Database;`);
4045
+ }
4046
+ }
4047
+ if (config.queue) {
4048
+ imports.push("QueueBinding");
4049
+ for (const bindingName of Object.keys(config.queue)) {
4050
+ properties.push(` ${bindingName}: QueueBinding;`);
4051
+ }
4052
+ }
4053
+ if (config.workflow) {
4054
+ imports.push("WorkflowBinding");
4055
+ for (const bindingName of Object.keys(config.workflow)) {
4056
+ properties.push(` ${bindingName}: WorkflowBinding;`);
4057
+ }
4058
+ }
4059
+ const lines = [
4060
+ "// This file is auto-generated by `ploy types`. Do not edit manually.",
4061
+ ""
4062
+ ];
4063
+ if (imports.length > 0) {
4064
+ lines.push(`import type { ${imports.join(", ")} } from "@meetploy/types";`);
4065
+ lines.push("");
4066
+ }
4067
+ lines.push('declare module "@meetploy/nextjs" {');
4068
+ lines.push(" interface PloyEnv {");
4069
+ for (const prop of properties) {
4070
+ lines.push(` ${prop}`);
4071
+ }
4072
+ lines.push(" }");
4073
+ lines.push("}");
4074
+ lines.push("");
4075
+ lines.push("declare global {");
4076
+ lines.push(" interface PloyEnv {");
4077
+ for (const prop of properties) {
4078
+ lines.push(` ${prop}`);
4079
+ }
4080
+ lines.push(" }");
4081
+ lines.push("}");
4082
+ lines.push("");
4083
+ lines.push("export {};");
4084
+ lines.push("");
4085
+ return lines.join("\n");
4086
+ }
4087
+ function getOutputPath(options) {
4088
+ if (options.output) {
4089
+ return options.output;
4090
+ }
4091
+ return "env.d.ts";
4092
+ }
4093
+ async function typesCommand(options = {}) {
4094
+ const cwd = process.cwd();
4095
+ const config = await readPloyConfig(cwd);
4096
+ if (!config) {
4097
+ console.error("Error: ploy.yaml not found in current directory");
4098
+ process.exit(1);
4099
+ }
4100
+ const hasBindings2 = config.ai || config.db || config.queue || config.workflow;
4101
+ if (!hasBindings2) {
4102
+ console.log("No bindings found in ploy.yaml. Generating empty Env.");
4103
+ }
4104
+ const content = generateEnvType(config);
4105
+ const outputPath = getOutputPath(options);
4106
+ const fullOutputPath = join(cwd, outputPath);
4107
+ const outputDir = dirname(fullOutputPath);
4108
+ await mkdir(outputDir, { recursive: true });
4109
+ await writeFile(fullOutputPath, content, "utf-8");
4110
+ console.log(`Generated ${outputPath}`);
4111
+ const updated = await updateTsConfigInclude(cwd, outputPath);
4112
+ if (updated) {
4113
+ console.log(`Updated tsconfig.json to include ${outputPath}`);
4114
+ }
4115
+ process.exit(0);
4116
+ }
4117
+ function parseTypesArgs(args2) {
4118
+ const options = {};
4119
+ for (let i = 0; i < args2.length; i++) {
4120
+ const arg = args2[i];
4121
+ if (arg === "-o" || arg === "--output") {
4122
+ const nextArg = args2[i + 1];
4123
+ if (!nextArg || nextArg.startsWith("-")) {
4124
+ console.error(`Error: ${arg} requires a path argument`);
4125
+ process.exit(1);
4126
+ }
4127
+ options.output = nextArg;
4128
+ i++;
4129
+ } else if (arg === "-h" || arg === "--help") {
4130
+ printTypesHelp();
4131
+ process.exit(0);
4132
+ }
4133
+ }
4134
+ return options;
4135
+ }
4136
+ function printTypesHelp() {
4137
+ console.log("Usage: ploy types [options]");
4138
+ console.log("");
4139
+ console.log("Generate TypeScript types from ploy.yaml bindings");
4140
+ console.log("");
4141
+ console.log("Options:");
4142
+ console.log(" -o, --output <path> Output file path (default: env.d.ts)");
4143
+ console.log(" -h, --help Show this help message");
4144
+ console.log("");
4145
+ console.log("Examples:");
4146
+ console.log(
4147
+ " ploy types Generate env.d.ts in current directory"
4148
+ );
4149
+ console.log(" ploy types -o env.d.ts Generate types to specific path");
4150
+ }
4151
+
4152
+ // src/index.ts
4153
+ var args = process.argv.slice(2);
4154
+ var command = args[0];
4155
+ if (!command) {
4156
+ console.error("Usage: ploy <command>");
4157
+ console.error("\nAvailable commands:");
4158
+ console.error(" api Interact with the Ploy API");
4159
+ console.error(" auth Manage authentication (login, logout)");
4160
+ console.error(" build Build your Ploy project");
4161
+ console.error(" dev Start development server");
4162
+ console.error(" types Generate TypeScript types from ploy.yaml");
4163
+ process.exit(1);
4164
+ }
4165
+ function parseDevArgs(args2) {
4166
+ const options = {
4167
+ port: void 0,
4168
+ host: void 0,
4169
+ config: void 0,
4170
+ watch: true,
4171
+ verbose: false,
4172
+ dashboardPort: void 0
4173
+ };
4174
+ for (let i = 0; i < args2.length; i++) {
4175
+ const arg = args2[i];
4176
+ if (arg === "-p" || arg === "--port") {
4177
+ const value = args2[++i];
4178
+ if (value) {
4179
+ options.port = parseInt(value, 10);
4180
+ }
4181
+ } else if (arg === "-h" || arg === "--host") {
4182
+ options.host = args2[++i];
4183
+ } else if (arg === "-c" || arg === "--config") {
4184
+ options.config = args2[++i];
4185
+ } else if (arg === "--no-watch") {
4186
+ options.watch = false;
4187
+ } else if (arg === "-v" || arg === "--verbose") {
4188
+ options.verbose = true;
4189
+ } else if (arg === "--dashboard-port") {
4190
+ const value = args2[++i];
4191
+ if (value) {
4192
+ options.dashboardPort = parseInt(value, 10);
4193
+ }
4194
+ }
4195
+ }
4196
+ return options;
4197
+ }
4198
+ async function isNextJsProject(projectDir, configPath) {
4199
+ const { readPloyConfig: readPloyConfig3 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
4200
+ try {
4201
+ const config = await readPloyConfig3(projectDir, configPath);
4202
+ if (config?.kind === "nextjs") {
4203
+ return true;
4204
+ }
4205
+ } catch {
4206
+ }
4207
+ const nextConfigFiles = [
4208
+ "next.config.ts",
4209
+ "next.config.js",
4210
+ "next.config.mts",
4211
+ "next.config.mjs"
4212
+ ];
4213
+ for (const file of nextConfigFiles) {
4214
+ if (existsSync(join(projectDir, file))) {
4215
+ return true;
4216
+ }
4217
+ }
4218
+ return false;
4219
+ }
4220
+ function findNextBinary(projectDir) {
4221
+ const possiblePaths = [
4222
+ join(projectDir, "node_modules", ".bin", "next"),
4223
+ "npx next"
4224
+ ];
4225
+ for (const p of possiblePaths) {
4226
+ if (p.includes("npx") || existsSync(p)) {
4227
+ return p;
4228
+ }
4229
+ }
4230
+ return "npx next";
4231
+ }
4232
+ async function startNextJsDev(options) {
4233
+ const projectDir = process.cwd();
4234
+ const nextPort = options.port ?? 3e3;
4235
+ const dashboardPort = options.dashboardPort ?? nextPort + 1e3;
4236
+ const { startDevDashboard: startDevDashboard2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
4237
+ const dashboard = await startDevDashboard2({
4238
+ configPath: options.config,
4239
+ port: dashboardPort,
4240
+ verbose: options.verbose
4241
+ });
4242
+ console.log(
4243
+ `
4244
+ Ploy Dashboard: http://${options.host ?? "localhost"}:${dashboard.port}`
4245
+ );
4246
+ const nextBin = findNextBinary(projectDir);
4247
+ const nextArgs = ["dev", "-p", String(nextPort)];
4248
+ if (options.host) {
4249
+ nextArgs.push("-H", options.host);
4250
+ }
4251
+ console.log(` Starting Next.js dev server...`);
4252
+ let nextProcess;
4253
+ if (nextBin.includes("npx")) {
4254
+ nextProcess = spawn("npx", ["next", ...nextArgs], {
4255
+ cwd: projectDir,
4256
+ stdio: "inherit",
4257
+ shell: true,
4258
+ env: {
4259
+ ...process.env,
4260
+ // Set environment variable so initPloyForDev knows the mock server URL
4261
+ PLOY_MOCK_SERVER_URL: `http://localhost:${dashboard.port}`
4262
+ }
4263
+ });
4264
+ } else {
4265
+ nextProcess = spawn(nextBin, nextArgs, {
4266
+ cwd: projectDir,
4267
+ stdio: "inherit",
4268
+ shell: true,
4269
+ env: {
4270
+ ...process.env,
4271
+ PLOY_MOCK_SERVER_URL: `http://localhost:${dashboard.port}`
4272
+ }
4273
+ });
4274
+ }
4275
+ const cleanup = async () => {
4276
+ console.log("\nShutting down...");
4277
+ nextProcess.kill("SIGTERM");
4278
+ await dashboard.close();
4279
+ process.exit(0);
4280
+ };
4281
+ process.on("SIGINT", cleanup);
4282
+ process.on("SIGTERM", cleanup);
4283
+ nextProcess.on("exit", async (code) => {
4284
+ await dashboard.close();
4285
+ process.exit(code ?? 0);
4286
+ });
4287
+ }
4288
+ if (command === "dev") {
4289
+ const options = parseDevArgs(args.slice(1));
4290
+ const projectDir = process.cwd();
4291
+ const isNextJs = await isNextJsProject(projectDir, options.config);
4292
+ if (isNextJs) {
4293
+ await startNextJsDev({
4294
+ port: options.port,
4295
+ host: options.host,
4296
+ config: options.config,
4297
+ verbose: options.verbose,
4298
+ dashboardPort: options.dashboardPort
4299
+ });
4300
+ } else {
4301
+ const { startEmulator: startEmulator2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
4302
+ await startEmulator2({
4303
+ port: options.port,
4304
+ host: options.host,
4305
+ configPath: options.config,
4306
+ watch: options.watch,
4307
+ verbose: options.verbose,
4308
+ dashboardPort: options.dashboardPort
4309
+ });
4310
+ }
4311
+ } else if (command === "types") {
4312
+ const options = parseTypesArgs(args.slice(1));
4313
+ await typesCommand(options);
4314
+ } else if (command === "build") {
4315
+ const options = parseBuildArgs(args.slice(1));
4316
+ await buildCommand(options);
4317
+ } else if (command === "api") {
4318
+ const options = parseApiArgs(args.slice(1));
4319
+ await apiCommand(options);
4320
+ } else if (command === "auth") {
4321
+ const options = parseAuthArgs(args.slice(1));
4322
+ await authCommand(options);
4323
+ } else {
4324
+ console.error(`Unknown command: ${command}`);
4325
+ console.error("\nAvailable commands:");
4326
+ console.error(" api Interact with the Ploy API");
4327
+ console.error(" auth Manage authentication (login, logout)");
4328
+ console.error(" build Build your Ploy project");
4329
+ console.error(" dev Start development server");
4330
+ console.error(" types Generate TypeScript types from ploy.yaml");
4331
+ process.exit(1);
183
4332
  }
184
- //# sourceMappingURL=index.js.map