@cloudwerk/cli 0.12.0 → 0.14.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
@@ -7,9 +7,11 @@ import { program } from "commander";
7
7
  import * as path from "path";
8
8
  import * as fs from "fs";
9
9
  import * as os from "os";
10
- import { createServer } from "vite";
10
+ import { createServer, mergeConfig as mergeViteConfig } from "vite";
11
11
  import devServer from "@hono/vite-dev-server";
12
+ import cloudflareAdapter from "@hono/vite-dev-server/cloudflare";
12
13
  import cloudwerk from "@cloudwerk/vite-plugin";
14
+ import { loadConfig } from "@cloudwerk/core";
13
15
 
14
16
  // src/types.ts
15
17
  var CliError = class extends Error {
@@ -138,7 +140,9 @@ async function dev(pathArg, options) {
138
140
  "Port must be a number between 1 and 65535"
139
141
  );
140
142
  }
141
- const viteConfig = {
143
+ const config = await loadConfig(cwd);
144
+ logger.debug(`Loaded cloudwerk config`);
145
+ const baseViteConfig = {
142
146
  root: cwd,
143
147
  mode: "development",
144
148
  server: {
@@ -151,6 +155,7 @@ async function dev(pathArg, options) {
151
155
  verbose
152
156
  }),
153
157
  devServer({
158
+ adapter: cloudflareAdapter,
154
159
  entry: "virtual:cloudwerk/server-entry"
155
160
  })
156
161
  ],
@@ -158,6 +163,15 @@ async function dev(pathArg, options) {
158
163
  logLevel: verbose ? "info" : "warn",
159
164
  clearScreen: false
160
165
  };
166
+ let viteConfig = baseViteConfig;
167
+ if (config.vite) {
168
+ const userPlugins = config.vite.plugins;
169
+ const { plugins: _, ...userConfigWithoutPlugins } = config.vite;
170
+ viteConfig = mergeViteConfig(baseViteConfig, userConfigWithoutPlugins);
171
+ if (userPlugins) {
172
+ viteConfig.plugins = [...userPlugins, ...baseViteConfig.plugins ?? []];
173
+ }
174
+ }
161
175
  logger.debug(`Starting Vite dev server...`);
162
176
  const server = await createServer(viteConfig);
163
177
  await server.listen();
@@ -257,15 +271,22 @@ function setupGracefulShutdown(server, logger) {
257
271
  import { builtinModules } from "module";
258
272
  import * as path2 from "path";
259
273
  import * as fs2 from "fs";
260
- import { build as viteBuild } from "vite";
274
+ import { build as viteBuild, mergeConfig as mergeViteConfig2 } from "vite";
275
+ import { getPlatformProxy } from "wrangler";
261
276
  import cloudwerk2, { generateServerEntry } from "@cloudwerk/vite-plugin";
262
277
  import {
263
278
  scanRoutes,
264
279
  buildRouteManifest,
265
280
  resolveLayouts,
266
281
  resolveMiddleware,
267
- loadConfig,
268
- resolveRoutesPath
282
+ loadConfig as loadConfig2,
283
+ resolveRoutesPath,
284
+ scanQueues,
285
+ buildQueueManifest,
286
+ QUEUES_DIR,
287
+ scanServices,
288
+ buildServiceManifest,
289
+ SERVICES_DIR
269
290
  } from "@cloudwerk/core/build";
270
291
  var DEFAULT_OUTPUT_DIR = "./dist";
271
292
  var BUILD_TEMP_DIR = ".cloudwerk-build";
@@ -298,7 +319,7 @@ async function build(pathArg, options) {
298
319
  if (!fs2.existsSync(tempDir)) {
299
320
  fs2.mkdirSync(tempDir, { recursive: true });
300
321
  }
301
- const cloudwerkConfig = await loadConfig(cwd);
322
+ const cloudwerkConfig = await loadConfig2(cwd);
302
323
  const appDir = cloudwerkConfig.appDir;
303
324
  const routesDir = cloudwerkConfig.routesDir ?? "routes";
304
325
  const routesPath = resolveRoutesPath(routesDir, appDir, cwd);
@@ -313,6 +334,30 @@ async function build(pathArg, options) {
313
334
  resolveMiddleware
314
335
  );
315
336
  logger.debug(`Found ${manifest.routes.length} routes`);
337
+ const queuesPath = path2.resolve(cwd, appDir, QUEUES_DIR);
338
+ let queueManifest = null;
339
+ if (fs2.existsSync(queuesPath)) {
340
+ const queueScanResult = await scanQueues(
341
+ path2.resolve(cwd, appDir),
342
+ { extensions: cloudwerkConfig.extensions }
343
+ );
344
+ queueManifest = buildQueueManifest(queueScanResult, cwd, { appName: "cloudwerk" });
345
+ if (queueManifest.queues.length > 0) {
346
+ logger.debug(`Found ${queueManifest.queues.length} queue(s)`);
347
+ }
348
+ }
349
+ const servicesPath = path2.resolve(cwd, appDir, SERVICES_DIR);
350
+ let serviceManifest = null;
351
+ if (fs2.existsSync(servicesPath)) {
352
+ const serviceScanResult = await scanServices(
353
+ path2.resolve(cwd, appDir),
354
+ { extensions: cloudwerkConfig.extensions }
355
+ );
356
+ serviceManifest = buildServiceManifest(serviceScanResult, cwd);
357
+ if (serviceManifest.services.length > 0) {
358
+ logger.debug(`Found ${serviceManifest.services.length} service(s)`);
359
+ }
360
+ }
316
361
  const renderer = cloudwerkConfig.ui?.renderer ?? "hono-jsx";
317
362
  const serverEntryCode = generateServerEntry(manifest, scanResult, {
318
363
  appDir,
@@ -326,12 +371,15 @@ async function build(pathArg, options) {
326
371
  publicDir: cloudwerkConfig.publicDir ?? "public",
327
372
  root: cwd,
328
373
  isProduction: true
374
+ }, {
375
+ queueManifest,
376
+ serviceManifest
329
377
  });
330
378
  const tempEntryPath = path2.join(tempDir, "_server-entry.ts");
331
379
  fs2.writeFileSync(tempEntryPath, serverEntryCode);
332
380
  logger.debug(`Generated temp entry: ${tempEntryPath}`);
333
381
  logger.debug(`Building client assets...`);
334
- const clientConfig = {
382
+ const baseClientConfig = {
335
383
  root: cwd,
336
384
  mode: "production",
337
385
  logLevel: verbose ? "info" : "warn",
@@ -353,6 +401,15 @@ async function build(pathArg, options) {
353
401
  }
354
402
  }
355
403
  };
404
+ let clientConfig = baseClientConfig;
405
+ if (cloudwerkConfig.vite) {
406
+ const userPlugins = cloudwerkConfig.vite.plugins;
407
+ const { plugins: _, ...userConfigWithoutPlugins } = cloudwerkConfig.vite;
408
+ clientConfig = mergeViteConfig2(baseClientConfig, userConfigWithoutPlugins);
409
+ if (userPlugins) {
410
+ clientConfig.plugins = [...userPlugins, ...baseClientConfig.plugins ?? []];
411
+ }
412
+ }
356
413
  try {
357
414
  await viteBuild(clientConfig);
358
415
  logger.debug(`Client assets built successfully`);
@@ -362,7 +419,7 @@ async function build(pathArg, options) {
362
419
  }
363
420
  }
364
421
  logger.debug(`Building server bundle...`);
365
- const serverConfig = {
422
+ const baseServerConfig = {
366
423
  root: cwd,
367
424
  mode: "production",
368
425
  logLevel: verbose ? "info" : "warn",
@@ -397,8 +454,31 @@ async function build(pathArg, options) {
397
454
  conditions: ["workerd", "worker", "browser", "import", "module", "default"]
398
455
  }
399
456
  };
457
+ let serverConfig = baseServerConfig;
458
+ if (cloudwerkConfig.vite) {
459
+ const userPlugins = cloudwerkConfig.vite.plugins;
460
+ const { plugins: _, ...userConfigWithoutPlugins } = cloudwerkConfig.vite;
461
+ serverConfig = mergeViteConfig2(baseServerConfig, userConfigWithoutPlugins);
462
+ if (userPlugins) {
463
+ serverConfig.plugins = [...userPlugins, ...baseServerConfig.plugins ?? []];
464
+ }
465
+ }
400
466
  await viteBuild(serverConfig);
401
467
  logger.debug(`Server bundle built successfully`);
468
+ let ssgPaths = [];
469
+ if (options.ssg) {
470
+ logger.info(`Generating static pages...`);
471
+ ssgPaths = await generateStaticPages(
472
+ manifest,
473
+ cwd,
474
+ outputDir,
475
+ logger,
476
+ verbose
477
+ );
478
+ if (ssgPaths.length > 0) {
479
+ logger.debug(`Generated ${ssgPaths.length} static page(s)`);
480
+ }
481
+ }
402
482
  const buildDuration = Date.now() - startTime;
403
483
  const serverBundlePath = path2.join(outputDir, "index.js");
404
484
  const serverSize = fs2.existsSync(serverBundlePath) ? fs2.statSync(serverBundlePath).size : 0;
@@ -415,7 +495,7 @@ async function build(pathArg, options) {
415
495
  }
416
496
  }
417
497
  console.log();
418
- printBuildSummary(serverSize, clientSize, buildDuration, logger);
498
+ printBuildSummary(serverSize, clientSize, ssgPaths.length, buildDuration, logger);
419
499
  console.log();
420
500
  logger.success(`Build completed in ${buildDuration}ms`);
421
501
  } catch (error) {
@@ -444,7 +524,7 @@ async function build(pathArg, options) {
444
524
  }
445
525
  }
446
526
  }
447
- function printBuildSummary(serverSize, clientSize, buildDuration, logger) {
527
+ function printBuildSummary(serverSize, clientSize, ssgPageCount, buildDuration, logger) {
448
528
  logger.log("Build Output:");
449
529
  logger.log("");
450
530
  logger.log(" Server:");
@@ -455,6 +535,11 @@ function printBuildSummary(serverSize, clientSize, buildDuration, logger) {
455
535
  logger.log(` Total: ${formatSize(clientSize)}`);
456
536
  logger.log("");
457
537
  }
538
+ if (ssgPageCount > 0) {
539
+ logger.log(" Static Pages:");
540
+ logger.log(` Generated: ${ssgPageCount}`);
541
+ logger.log("");
542
+ }
458
543
  const totalSize = serverSize + clientSize;
459
544
  logger.log(` Total: ${formatSize(totalSize)}`);
460
545
  logger.log(` Duration: ${buildDuration}ms`);
@@ -468,6 +553,76 @@ function formatSize(bytes) {
468
553
  }
469
554
  return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
470
555
  }
556
+ async function generateStaticPages(_manifest, cwd, outputDir, logger, verbose) {
557
+ const generatedPaths = [];
558
+ const { env, dispose } = await getPlatformProxy({
559
+ configPath: path2.join(cwd, "wrangler.toml")
560
+ });
561
+ if (verbose) {
562
+ logger.debug(`SSG env bindings: ${Object.keys(env).join(", ")}`);
563
+ }
564
+ try {
565
+ const { createServer: createServer2 } = await import("vite");
566
+ const { toSSG } = await import("./ssg-DR2ZOVQ7.js");
567
+ const fsPromises = await import("fs/promises");
568
+ const vite = await createServer2({
569
+ root: cwd,
570
+ server: { middlewareMode: true },
571
+ appType: "custom",
572
+ logLevel: verbose ? "info" : "warn",
573
+ plugins: [cloudwerk2({ verbose })]
574
+ });
575
+ try {
576
+ const tempEntryPath = path2.join(cwd, ".cloudwerk-build", "_server-entry.ts");
577
+ const appModule = await vite.ssrLoadModule(
578
+ fs2.existsSync(tempEntryPath) ? tempEntryPath : "virtual:cloudwerk/server-entry"
579
+ );
580
+ const app = appModule.default;
581
+ if (!app || typeof app.fetch !== "function") {
582
+ logger.debug("No valid Hono app found for SSG");
583
+ return generatedPaths;
584
+ }
585
+ const originalFetch = app.fetch.bind(app);
586
+ app.fetch = (request, passedEnv, executionCtx) => {
587
+ const mergedEnv = { ...env, ...passedEnv };
588
+ if (verbose) {
589
+ const envKeys = Object.keys(mergedEnv);
590
+ logger.debug(`SSG fetch ${request.url} with env: ${envKeys.join(", ")}`);
591
+ }
592
+ return originalFetch(request, mergedEnv, executionCtx);
593
+ };
594
+ const staticDir = path2.join(outputDir, "static");
595
+ const result = await toSSG(app, fsPromises, {
596
+ dir: staticDir
597
+ // Only generate pages that have ssgParams middleware
598
+ // (pages with generateStaticParams export)
599
+ });
600
+ if (result.files) {
601
+ for (const file of result.files) {
602
+ const relativePath = path2.relative(staticDir, file);
603
+ const urlPath = "/" + relativePath.replace(/\/index\.html$/, "").replace(/\.html$/, "");
604
+ generatedPaths.push(urlPath || "/");
605
+ if (verbose) {
606
+ logger.debug(`Generated: ${urlPath || "/"} -> ${relativePath}`);
607
+ }
608
+ }
609
+ }
610
+ if (generatedPaths.length > 0) {
611
+ logger.info(`Generated ${generatedPaths.length} static page(s)`);
612
+ }
613
+ } finally {
614
+ await vite.close();
615
+ }
616
+ } catch (error) {
617
+ if (verbose) {
618
+ logger.debug(`SSG error: ${error instanceof Error ? error.message : String(error)}`);
619
+ }
620
+ logger.warn(`Static generation failed: ${error instanceof Error ? error.message : String(error)}`);
621
+ } finally {
622
+ await dispose();
623
+ }
624
+ return generatedPaths;
625
+ }
471
626
 
472
627
  // src/commands/deploy.ts
473
628
  import * as path3 from "path";
@@ -565,7 +720,7 @@ import pc2 from "picocolors";
565
720
  // src/utils/configWriter.ts
566
721
  import * as fs4 from "fs";
567
722
  import * as path4 from "path";
568
- import { loadConfig as loadConfig2 } from "@cloudwerk/core/build";
723
+ import { loadConfig as loadConfig3 } from "@cloudwerk/core/build";
569
724
  var CONFIG_FILE_NAMES = [
570
725
  "cloudwerk.config.ts",
571
726
  "cloudwerk.config.js",
@@ -1206,6 +1361,17 @@ function writeWranglerToml(cwd, config) {
1206
1361
  const content = TOML.stringify(config);
1207
1362
  fs7.writeFileSync(configPath, content, "utf-8");
1208
1363
  }
1364
+ function readWranglerTomlRaw(cwd) {
1365
+ const configPath = findWranglerToml(cwd);
1366
+ if (!configPath || configPath.endsWith(".json")) {
1367
+ return "";
1368
+ }
1369
+ return fs7.readFileSync(configPath, "utf-8");
1370
+ }
1371
+ function writeWranglerTomlRaw(cwd, content) {
1372
+ const configPath = path7.join(cwd, "wrangler.toml");
1373
+ fs7.writeFileSync(configPath, content, "utf-8");
1374
+ }
1209
1375
  function getEnvConfig(config, env) {
1210
1376
  if (!env || !config.env?.[env]) {
1211
1377
  return config;
@@ -3142,6 +3308,2480 @@ async function bindingsGenerateTypes(options) {
3142
3308
  }
3143
3309
  }
3144
3310
 
3311
+ // src/commands/triggers.ts
3312
+ import pc9 from "picocolors";
3313
+ import {
3314
+ loadConfig as loadConfig4,
3315
+ scanTriggers,
3316
+ buildTriggerManifest,
3317
+ getTriggerSummary
3318
+ } from "@cloudwerk/core/build";
3319
+ var SOURCE_TYPE_LABELS = {
3320
+ scheduled: { label: "cron", color: pc9.blue },
3321
+ queue: { label: "queue", color: pc9.green },
3322
+ r2: { label: "R2", color: pc9.yellow },
3323
+ webhook: { label: "webhook", color: pc9.magenta },
3324
+ email: { label: "email", color: pc9.cyan },
3325
+ d1: { label: "D1", color: pc9.red },
3326
+ tail: { label: "tail", color: pc9.gray }
3327
+ };
3328
+ function formatSourceType(type) {
3329
+ const config = SOURCE_TYPE_LABELS[type] || { label: type, color: pc9.dim };
3330
+ return config.color(config.label);
3331
+ }
3332
+ async function triggers(options = {}) {
3333
+ const verbose = options.verbose ?? false;
3334
+ const logger = createLogger(verbose);
3335
+ try {
3336
+ const cwd = process.cwd();
3337
+ logger.debug("Loading configuration...");
3338
+ const config = await loadConfig4(cwd);
3339
+ const appDir = config.appDir;
3340
+ logger.debug(`Scanning for triggers in ${appDir}/triggers/...`);
3341
+ const scanResult = await scanTriggers(appDir, {
3342
+ extensions: config.extensions
3343
+ });
3344
+ const manifest = buildTriggerManifest(scanResult, appDir);
3345
+ const summary = getTriggerSummary(manifest);
3346
+ console.log();
3347
+ console.log(pc9.bold("Cloudwerk Triggers"));
3348
+ console.log();
3349
+ console.log(pc9.dim(` Found ${summary.total} triggers:`));
3350
+ if (summary.total > 0) {
3351
+ if (summary.scheduled > 0) {
3352
+ console.log(pc9.dim(` - ${summary.scheduled} scheduled (cron)`));
3353
+ }
3354
+ if (summary.queue > 0) {
3355
+ console.log(pc9.dim(` - ${summary.queue} queue consumers`));
3356
+ }
3357
+ if (summary.r2 > 0) {
3358
+ console.log(pc9.dim(` - ${summary.r2} R2 notifications`));
3359
+ }
3360
+ if (summary.webhook > 0) {
3361
+ console.log(pc9.dim(` - ${summary.webhook} webhooks`));
3362
+ }
3363
+ if (summary.email > 0) {
3364
+ console.log(pc9.dim(` - ${summary.email} email handlers`));
3365
+ }
3366
+ }
3367
+ console.log();
3368
+ if (manifest.triggers.length > 0) {
3369
+ for (const trigger of manifest.triggers) {
3370
+ const sourceType = formatSourceType(trigger.source?.type ?? "unknown");
3371
+ const sourceInfo = getSourceInfo(trigger);
3372
+ console.log(
3373
+ ` ${pc9.cyan(trigger.name)} ${pc9.dim("(")}${sourceType}${sourceInfo ? pc9.dim(": ") + pc9.dim(sourceInfo) : ""}${pc9.dim(")")}`
3374
+ );
3375
+ }
3376
+ console.log();
3377
+ }
3378
+ if (manifest.warnings.length > 0) {
3379
+ console.log(pc9.yellow("Warnings:"));
3380
+ for (const warning of manifest.warnings) {
3381
+ console.log(pc9.dim(` - ${warning.file}: ${warning.message}`));
3382
+ }
3383
+ console.log();
3384
+ }
3385
+ console.log(pc9.bold("Commands:"));
3386
+ console.log();
3387
+ console.log(pc9.dim(" cloudwerk triggers list ") + "List all triggers with details");
3388
+ console.log(pc9.dim(" cloudwerk triggers validate ") + "Validate trigger configurations");
3389
+ console.log(pc9.dim(" cloudwerk triggers generate ") + "Regenerate wrangler config");
3390
+ console.log();
3391
+ if (manifest.triggers.length === 0) {
3392
+ console.log(pc9.bold("Quick Start:"));
3393
+ console.log();
3394
+ console.log(pc9.dim(" Create a scheduled trigger at app/triggers/daily-cleanup.ts:"));
3395
+ console.log();
3396
+ console.log(pc9.cyan(" import { defineTrigger } from '@cloudwerk/trigger'"));
3397
+ console.log();
3398
+ console.log(pc9.cyan(" export default defineTrigger({"));
3399
+ console.log(pc9.cyan(" source: { type: 'scheduled', cron: '0 0 * * *' },"));
3400
+ console.log(pc9.cyan(" async handle(event, ctx) {"));
3401
+ console.log(pc9.cyan(" console.log(`[${ctx.traceId}] Running cleanup`)"));
3402
+ console.log(pc9.cyan(" await cleanupOldRecords()"));
3403
+ console.log(pc9.cyan(" }"));
3404
+ console.log(pc9.cyan(" })"));
3405
+ console.log();
3406
+ }
3407
+ } catch (error) {
3408
+ handleCommandError(error, verbose);
3409
+ }
3410
+ }
3411
+ function getSourceInfo(trigger) {
3412
+ const source = trigger.source;
3413
+ if (!source) return "";
3414
+ const type = source.type;
3415
+ switch (type) {
3416
+ case "scheduled":
3417
+ return String(source.cron ?? "");
3418
+ case "queue":
3419
+ return String(source.queue ?? "");
3420
+ case "r2":
3421
+ return String(source.bucket ?? "");
3422
+ case "webhook":
3423
+ return String(source.path ?? "");
3424
+ case "email":
3425
+ return String(source.address ?? "");
3426
+ default:
3427
+ return "";
3428
+ }
3429
+ }
3430
+
3431
+ // src/commands/triggers/list.ts
3432
+ import pc10 from "picocolors";
3433
+ import {
3434
+ loadConfig as loadConfig5,
3435
+ scanTriggers as scanTriggers2,
3436
+ buildTriggerManifest as buildTriggerManifest2
3437
+ } from "@cloudwerk/core/build";
3438
+ var SOURCE_TYPE_COLORS = {
3439
+ scheduled: pc10.blue,
3440
+ queue: pc10.green,
3441
+ r2: pc10.yellow,
3442
+ webhook: pc10.magenta,
3443
+ email: pc10.cyan,
3444
+ d1: pc10.red,
3445
+ tail: pc10.gray
3446
+ };
3447
+ async function triggersList(options = {}) {
3448
+ const verbose = options.verbose ?? false;
3449
+ const logger = createLogger(verbose);
3450
+ try {
3451
+ const cwd = process.cwd();
3452
+ logger.debug("Loading configuration...");
3453
+ const config = await loadConfig5(cwd);
3454
+ const appDir = config.appDir;
3455
+ logger.debug(`Scanning for triggers in ${appDir}/triggers/...`);
3456
+ const scanResult = await scanTriggers2(appDir, {
3457
+ extensions: config.extensions
3458
+ });
3459
+ const manifest = buildTriggerManifest2(scanResult, appDir);
3460
+ let triggers2 = manifest.triggers;
3461
+ if (options.type) {
3462
+ triggers2 = triggers2.filter((t) => t.source?.type === options.type);
3463
+ }
3464
+ if (options.json) {
3465
+ console.log(JSON.stringify({
3466
+ triggers: triggers2.map(formatTriggerJson),
3467
+ count: triggers2.length,
3468
+ errors: manifest.errors,
3469
+ warnings: manifest.warnings
3470
+ }, null, 2));
3471
+ return;
3472
+ }
3473
+ console.log();
3474
+ console.log(pc10.bold("Triggers"));
3475
+ console.log();
3476
+ if (triggers2.length === 0) {
3477
+ if (options.type) {
3478
+ console.log(pc10.dim(` No triggers of type '${options.type}' found.`));
3479
+ } else {
3480
+ console.log(pc10.dim(" No triggers found."));
3481
+ }
3482
+ console.log();
3483
+ return;
3484
+ }
3485
+ console.log(
3486
+ ` ${pc10.dim("NAME".padEnd(25))} ${pc10.dim("TYPE".padEnd(12))} ${pc10.dim("SOURCE".padEnd(30))} ${pc10.dim("ERROR HANDLER")}`
3487
+ );
3488
+ console.log(pc10.dim(" " + "-".repeat(80)));
3489
+ for (const trigger of triggers2) {
3490
+ const color = SOURCE_TYPE_COLORS[trigger.source?.type ?? "unknown"] ?? pc10.dim;
3491
+ const sourceType = trigger.source?.type ?? "unknown";
3492
+ const sourceInfo = getSourceInfo2(trigger);
3493
+ const hasError = trigger.hasOnError ? pc10.green("yes") : pc10.dim("no");
3494
+ console.log(
3495
+ ` ${pc10.cyan(trigger.name.padEnd(25))} ${color(sourceType.padEnd(12))} ${pc10.dim(sourceInfo.padEnd(30))} ${hasError}`
3496
+ );
3497
+ }
3498
+ console.log();
3499
+ console.log(pc10.dim(` ${triggers2.length} trigger(s)`));
3500
+ console.log();
3501
+ if (manifest.errors.length > 0) {
3502
+ console.log(pc10.red("Errors:"));
3503
+ for (const error of manifest.errors) {
3504
+ console.log(pc10.dim(` - ${error.file}: ${error.message}`));
3505
+ }
3506
+ console.log();
3507
+ }
3508
+ if (manifest.warnings.length > 0) {
3509
+ console.log(pc10.yellow("Warnings:"));
3510
+ for (const warning of manifest.warnings) {
3511
+ console.log(pc10.dim(` - ${warning.file}: ${warning.message}`));
3512
+ }
3513
+ console.log();
3514
+ }
3515
+ } catch (error) {
3516
+ handleCommandError(error, verbose);
3517
+ }
3518
+ }
3519
+ function getSourceInfo2(trigger) {
3520
+ const source = trigger.source;
3521
+ if (!source) return "unknown";
3522
+ switch (source.type) {
3523
+ case "scheduled":
3524
+ return source.cron;
3525
+ case "queue":
3526
+ return source.queue;
3527
+ case "r2": {
3528
+ const r2 = source;
3529
+ return `${r2.bucket} (${r2.events.join(", ")})`;
3530
+ }
3531
+ case "webhook":
3532
+ return source.path;
3533
+ case "email":
3534
+ return source.address;
3535
+ default:
3536
+ return source.type;
3537
+ }
3538
+ }
3539
+ function formatTriggerJson(trigger) {
3540
+ return {
3541
+ name: trigger.name,
3542
+ bindingName: trigger.bindingName,
3543
+ filePath: trigger.filePath,
3544
+ sourceType: trigger.source?.type,
3545
+ source: trigger.source,
3546
+ hasOnError: trigger.hasOnError,
3547
+ retry: trigger.retry,
3548
+ timeout: trigger.timeout,
3549
+ fanOutGroup: trigger.fanOutGroup
3550
+ };
3551
+ }
3552
+
3553
+ // src/commands/triggers/validate.ts
3554
+ import pc11 from "picocolors";
3555
+ import {
3556
+ loadConfig as loadConfig6,
3557
+ scanTriggers as scanTriggers3,
3558
+ buildTriggerManifest as buildTriggerManifest3,
3559
+ hasTriggerErrors,
3560
+ hasTriggerWarnings
3561
+ } from "@cloudwerk/core/build";
3562
+ async function triggersValidate(options = {}) {
3563
+ const verbose = options.verbose ?? false;
3564
+ const strict = options.strict ?? false;
3565
+ const logger = createLogger(verbose);
3566
+ try {
3567
+ const cwd = process.cwd();
3568
+ logger.debug("Loading configuration...");
3569
+ const config = await loadConfig6(cwd);
3570
+ const appDir = config.appDir;
3571
+ logger.debug(`Scanning for triggers in ${appDir}/triggers/...`);
3572
+ const scanResult = await scanTriggers3(appDir, {
3573
+ extensions: config.extensions
3574
+ });
3575
+ const manifest = buildTriggerManifest3(scanResult, appDir);
3576
+ const hasErrors = hasTriggerErrors(manifest);
3577
+ const hasWarnings = hasTriggerWarnings(manifest);
3578
+ if (options.json) {
3579
+ console.log(JSON.stringify({
3580
+ valid: !hasErrors && (!strict || !hasWarnings),
3581
+ triggerCount: manifest.triggers.length,
3582
+ errors: manifest.errors,
3583
+ warnings: manifest.warnings
3584
+ }, null, 2));
3585
+ if (hasErrors || strict && hasWarnings) {
3586
+ process.exit(1);
3587
+ }
3588
+ return;
3589
+ }
3590
+ console.log();
3591
+ console.log(pc11.bold("Validating Triggers"));
3592
+ console.log();
3593
+ if (manifest.triggers.length === 0) {
3594
+ console.log(pc11.dim(" No triggers found to validate."));
3595
+ console.log();
3596
+ return;
3597
+ }
3598
+ for (const trigger of manifest.triggers) {
3599
+ const triggerErrors = manifest.errors.filter(
3600
+ (e) => e.file === trigger.filePath
3601
+ );
3602
+ const triggerWarnings = manifest.warnings.filter(
3603
+ (w) => w.file === trigger.filePath
3604
+ );
3605
+ if (triggerErrors.length > 0) {
3606
+ console.log(` ${pc11.red("\u2717")} ${pc11.cyan(trigger.name)} ${pc11.dim(`(${trigger.filePath})`)}`);
3607
+ for (const error of triggerErrors) {
3608
+ console.log(` ${pc11.red("error:")} ${error.message}`);
3609
+ }
3610
+ } else if (triggerWarnings.length > 0) {
3611
+ console.log(` ${pc11.yellow("!")} ${pc11.cyan(trigger.name)} ${pc11.dim(`(${trigger.filePath})`)}`);
3612
+ for (const warning of triggerWarnings) {
3613
+ console.log(` ${pc11.yellow("warning:")} ${warning.message}`);
3614
+ }
3615
+ } else {
3616
+ console.log(` ${pc11.green("\u2713")} ${pc11.cyan(trigger.name)} ${pc11.dim("- valid")}`);
3617
+ }
3618
+ }
3619
+ console.log();
3620
+ const errorCount = manifest.errors.length;
3621
+ const warningCount = manifest.warnings.length;
3622
+ if (hasErrors) {
3623
+ console.log(pc11.red(` ${errorCount} error(s), ${warningCount} warning(s)`));
3624
+ console.log();
3625
+ process.exit(1);
3626
+ } else if (hasWarnings) {
3627
+ console.log(pc11.yellow(` ${warningCount} warning(s)`));
3628
+ console.log();
3629
+ if (strict) {
3630
+ process.exit(1);
3631
+ }
3632
+ } else {
3633
+ console.log(pc11.green(` All ${manifest.triggers.length} trigger(s) valid`));
3634
+ console.log();
3635
+ }
3636
+ } catch (error) {
3637
+ handleCommandError(error, verbose);
3638
+ }
3639
+ }
3640
+
3641
+ // src/commands/triggers/generate.ts
3642
+ import pc12 from "picocolors";
3643
+ import {
3644
+ loadConfig as loadConfig7,
3645
+ scanTriggers as scanTriggers4,
3646
+ buildTriggerManifest as buildTriggerManifest4
3647
+ } from "@cloudwerk/core/build";
3648
+
3649
+ // src/utils/trigger-wrangler.ts
3650
+ import * as fs11 from "fs";
3651
+ import * as path12 from "path";
3652
+ function generateCronToml(manifest, includeComments) {
3653
+ const cronTriggers = manifest.triggers.filter(
3654
+ (t) => t.source?.type === "scheduled"
3655
+ );
3656
+ if (cronTriggers.length === 0) {
3657
+ return "";
3658
+ }
3659
+ const lines = [];
3660
+ if (includeComments) {
3661
+ lines.push("# Scheduled (Cron) Triggers");
3662
+ }
3663
+ lines.push("[triggers]");
3664
+ const cronExpressions = /* @__PURE__ */ new Set();
3665
+ for (const trigger of cronTriggers) {
3666
+ const source = trigger.source;
3667
+ cronExpressions.add(source.cron);
3668
+ }
3669
+ const crons = Array.from(cronExpressions).map((cron) => `"${cron}"`).join(", ");
3670
+ lines.push(`crons = [${crons}]`);
3671
+ return lines.join("\n");
3672
+ }
3673
+ function generateQueueConsumerToml(manifest, includeComments) {
3674
+ const queueTriggers = manifest.triggers.filter(
3675
+ (t) => t.source?.type === "queue"
3676
+ );
3677
+ if (queueTriggers.length === 0) {
3678
+ return "";
3679
+ }
3680
+ const lines = [];
3681
+ for (const trigger of queueTriggers) {
3682
+ const source = trigger.source;
3683
+ if (includeComments) {
3684
+ lines.push(`# Queue trigger '${trigger.name}' (from app/triggers/${trigger.filePath})`);
3685
+ }
3686
+ lines.push("[[queues.consumers]]");
3687
+ lines.push(`queue = "${source.queue}"`);
3688
+ if (source.batch?.size !== void 0 && source.batch.size !== 10) {
3689
+ lines.push(`max_batch_size = ${source.batch.size}`);
3690
+ }
3691
+ if (source.batch?.timeout !== void 0) {
3692
+ const timeoutSeconds = parseDurationToSeconds(source.batch.timeout);
3693
+ if (timeoutSeconds !== 5) {
3694
+ lines.push(`max_batch_timeout = ${timeoutSeconds}`);
3695
+ }
3696
+ }
3697
+ lines.push("");
3698
+ }
3699
+ return lines.join("\n").trim();
3700
+ }
3701
+ function generateR2NotificationToml(manifest, includeComments) {
3702
+ const r2Triggers = manifest.triggers.filter(
3703
+ (t) => t.source?.type === "r2"
3704
+ );
3705
+ if (r2Triggers.length === 0) {
3706
+ return "";
3707
+ }
3708
+ const lines = [];
3709
+ const bucketTriggers = /* @__PURE__ */ new Map();
3710
+ for (const trigger of r2Triggers) {
3711
+ const source = trigger.source;
3712
+ const existing = bucketTriggers.get(source.bucket) || [];
3713
+ existing.push(trigger);
3714
+ bucketTriggers.set(source.bucket, existing);
3715
+ }
3716
+ for (const [bucket, triggers2] of bucketTriggers) {
3717
+ if (includeComments) {
3718
+ const triggerNames = triggers2.map((t) => t.name).join(", ");
3719
+ lines.push(`# R2 bucket notifications for '${bucket}' (triggers: ${triggerNames})`);
3720
+ }
3721
+ const eventTypes = /* @__PURE__ */ new Set();
3722
+ let prefix;
3723
+ let suffix;
3724
+ for (const trigger of triggers2) {
3725
+ const source = trigger.source;
3726
+ for (const event of source.events) {
3727
+ eventTypes.add(event);
3728
+ }
3729
+ if (!prefix && source.prefix) prefix = source.prefix;
3730
+ if (!suffix && source.suffix) suffix = source.suffix;
3731
+ }
3732
+ lines.push("[[r2_buckets]]");
3733
+ lines.push(`binding = "${bucket.toUpperCase()}_BUCKET"`);
3734
+ lines.push(`bucket_name = "${bucket}"`);
3735
+ lines.push("");
3736
+ lines.push("[[r2_buckets.event_notifications.rules]]");
3737
+ if (prefix) {
3738
+ lines.push(`prefix = "${prefix}"`);
3739
+ }
3740
+ if (suffix) {
3741
+ lines.push(`suffix = "${suffix}"`);
3742
+ }
3743
+ const actions = [];
3744
+ if (eventTypes.has("object-create")) {
3745
+ actions.push('"PutObject"', '"CopyObject"', '"CompleteMultipartUpload"');
3746
+ }
3747
+ if (eventTypes.has("object-delete")) {
3748
+ actions.push('"DeleteObject"');
3749
+ }
3750
+ if (actions.length > 0) {
3751
+ lines.push(`actions = [${actions.join(", ")}]`);
3752
+ }
3753
+ lines.push("");
3754
+ }
3755
+ return lines.join("\n").trim();
3756
+ }
3757
+ function parseDurationToSeconds(duration) {
3758
+ if (typeof duration === "number") {
3759
+ return duration;
3760
+ }
3761
+ const match = duration.match(/^(\d+)(s|m|h)$/);
3762
+ if (!match) {
3763
+ return 5;
3764
+ }
3765
+ const value = parseInt(match[1], 10);
3766
+ const unit = match[2];
3767
+ switch (unit) {
3768
+ case "s":
3769
+ return value;
3770
+ case "m":
3771
+ return value * 60;
3772
+ case "h":
3773
+ return value * 3600;
3774
+ default:
3775
+ return 5;
3776
+ }
3777
+ }
3778
+ function generateTriggerToml(manifest, includeComments = true) {
3779
+ if (manifest.triggers.length === 0) {
3780
+ return "";
3781
+ }
3782
+ const lines = [];
3783
+ if (includeComments) {
3784
+ lines.push("# ============================================================================");
3785
+ lines.push("# Cloudwerk Triggers - Auto-generated from app/triggers/");
3786
+ lines.push("# ============================================================================");
3787
+ lines.push("");
3788
+ }
3789
+ const cronToml = generateCronToml(manifest, includeComments);
3790
+ if (cronToml) {
3791
+ lines.push(cronToml);
3792
+ lines.push("");
3793
+ }
3794
+ const queueToml = generateQueueConsumerToml(manifest, includeComments);
3795
+ if (queueToml) {
3796
+ lines.push(queueToml);
3797
+ lines.push("");
3798
+ }
3799
+ const r2Toml = generateR2NotificationToml(manifest, includeComments);
3800
+ if (r2Toml) {
3801
+ lines.push(r2Toml);
3802
+ lines.push("");
3803
+ }
3804
+ return lines.join("\n").trim();
3805
+ }
3806
+ var TRIGGER_SECTION_START = "# ============================================================================";
3807
+ var TRIGGER_SECTION_MARKER = "# Cloudwerk Triggers - Auto-generated";
3808
+ function hasTriggerSection(content) {
3809
+ return content.includes(TRIGGER_SECTION_MARKER);
3810
+ }
3811
+ function removeTriggerSection(content) {
3812
+ const startIndex = content.indexOf(TRIGGER_SECTION_START);
3813
+ if (startIndex === -1 || !content.includes(TRIGGER_SECTION_MARKER)) {
3814
+ return content;
3815
+ }
3816
+ const lines = content.split("\n");
3817
+ let sectionStartLine = -1;
3818
+ let sectionEndLine = lines.length;
3819
+ for (let i = 0; i < lines.length; i++) {
3820
+ if (lines[i].includes(TRIGGER_SECTION_MARKER)) {
3821
+ sectionStartLine = i > 0 && lines[i - 1].includes("===") ? i - 1 : i;
3822
+ break;
3823
+ }
3824
+ }
3825
+ if (sectionStartLine === -1) {
3826
+ return content;
3827
+ }
3828
+ for (let i = sectionStartLine + 2; i < lines.length; i++) {
3829
+ const line = lines[i].trim();
3830
+ if (line === "" || line.startsWith("#")) {
3831
+ continue;
3832
+ }
3833
+ if (line.startsWith("[[") || line.startsWith("[")) {
3834
+ if (line.includes("triggers") || line.includes("queues.consumers") || line.includes("r2_buckets")) {
3835
+ continue;
3836
+ }
3837
+ sectionEndLine = i;
3838
+ break;
3839
+ }
3840
+ }
3841
+ const before = lines.slice(0, sectionStartLine);
3842
+ const after = lines.slice(sectionEndLine);
3843
+ while (before.length > 0 && before[before.length - 1].trim() === "") {
3844
+ before.pop();
3845
+ }
3846
+ return [...before, "", ...after].join("\n");
3847
+ }
3848
+ function generateTriggerWrangler(cwd, manifest, options = {}) {
3849
+ const { dryRun = false, includeComments = true } = options;
3850
+ const wranglerPath = findWranglerToml(cwd) || path12.join(cwd, "wrangler.toml");
3851
+ const generatedToml = generateTriggerToml(manifest, includeComments);
3852
+ const result = {
3853
+ wranglerPath,
3854
+ changed: false,
3855
+ triggers: manifest.triggers.map((t) => ({
3856
+ name: t.name,
3857
+ sourceType: t.source?.type ?? "unknown",
3858
+ bindingName: t.bindingName
3859
+ })),
3860
+ generatedToml
3861
+ };
3862
+ if (manifest.triggers.length === 0) {
3863
+ return result;
3864
+ }
3865
+ if (dryRun) {
3866
+ result.changed = true;
3867
+ return result;
3868
+ }
3869
+ let content = "";
3870
+ if (fs11.existsSync(wranglerPath)) {
3871
+ content = readWranglerTomlRaw(cwd);
3872
+ }
3873
+ if (hasTriggerSection(content)) {
3874
+ content = removeTriggerSection(content);
3875
+ }
3876
+ const newContent = content.trim() + "\n\n" + generatedToml + "\n";
3877
+ writeWranglerTomlRaw(cwd, newContent);
3878
+ result.changed = true;
3879
+ return result;
3880
+ }
3881
+
3882
+ // src/utils/trigger-type-generator.ts
3883
+ import * as fs12 from "fs";
3884
+ import * as path13 from "path";
3885
+ var CLOUDWERK_TYPES_DIR2 = ".cloudwerk/types";
3886
+ var TRIGGERS_DTS = "triggers.d.ts";
3887
+ function generateTriggerTypes(cwd, manifest, options = {}) {
3888
+ const includeTimestamp = options.includeTimestamp ?? true;
3889
+ const typesDir = path13.join(cwd, CLOUDWERK_TYPES_DIR2);
3890
+ fs12.mkdirSync(typesDir, { recursive: true });
3891
+ const triggersPath = path13.join(typesDir, TRIGGERS_DTS);
3892
+ const triggersContent = generateTriggersDts(manifest.triggers, includeTimestamp);
3893
+ fs12.writeFileSync(triggersPath, triggersContent, "utf-8");
3894
+ const triggerInfo = manifest.triggers.map((t) => ({
3895
+ name: t.name,
3896
+ bindingName: t.bindingName,
3897
+ sourceType: t.source?.type ?? "unknown"
3898
+ }));
3899
+ return {
3900
+ typesDir,
3901
+ file: triggersPath,
3902
+ triggerCount: manifest.triggers.length,
3903
+ triggers: triggerInfo
3904
+ };
3905
+ }
3906
+ function generateTriggersDts(triggers2, includeTimestamp) {
3907
+ const lines = [];
3908
+ lines.push("// Auto-generated by cloudwerk triggers - DO NOT EDIT");
3909
+ if (includeTimestamp) {
3910
+ lines.push(`// Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
3911
+ }
3912
+ lines.push("//");
3913
+ lines.push("// This file provides type information for triggers");
3914
+ lines.push('// Add ".cloudwerk/types" to your tsconfig.json include array');
3915
+ lines.push("");
3916
+ lines.push("import type { EmitOptions, EmitResult } from '@cloudwerk/trigger'");
3917
+ lines.push("");
3918
+ const scheduled = triggers2.filter((t) => t.source?.type === "scheduled");
3919
+ const queue = triggers2.filter((t) => t.source?.type === "queue");
3920
+ const r2 = triggers2.filter((t) => t.source?.type === "r2");
3921
+ const webhook = triggers2.filter((t) => t.source?.type === "webhook");
3922
+ const email = triggers2.filter((t) => t.source?.type === "email");
3923
+ lines.push("// ============================================================================");
3924
+ lines.push("// Trigger Names");
3925
+ lines.push("// ============================================================================");
3926
+ lines.push("");
3927
+ const allNames = triggers2.map((t) => `'${t.name}'`);
3928
+ if (allNames.length > 0) {
3929
+ lines.push(`type TriggerName = ${allNames.join(" | ")}`);
3930
+ } else {
3931
+ lines.push("type TriggerName = never");
3932
+ }
3933
+ lines.push("");
3934
+ if (scheduled.length > 0) {
3935
+ const names = scheduled.map((t) => `'${t.name}'`);
3936
+ lines.push(`type ScheduledTriggerName = ${names.join(" | ")}`);
3937
+ }
3938
+ if (queue.length > 0) {
3939
+ const names = queue.map((t) => `'${t.name}'`);
3940
+ lines.push(`type QueueTriggerName = ${names.join(" | ")}`);
3941
+ }
3942
+ if (r2.length > 0) {
3943
+ const names = r2.map((t) => `'${t.name}'`);
3944
+ lines.push(`type R2TriggerName = ${names.join(" | ")}`);
3945
+ }
3946
+ if (webhook.length > 0) {
3947
+ const names = webhook.map((t) => `'${t.name}'`);
3948
+ lines.push(`type WebhookTriggerName = ${names.join(" | ")}`);
3949
+ }
3950
+ if (email.length > 0) {
3951
+ const names = email.map((t) => `'${t.name}'`);
3952
+ lines.push(`type EmailTriggerName = ${names.join(" | ")}`);
3953
+ }
3954
+ lines.push("");
3955
+ lines.push("// ============================================================================");
3956
+ lines.push("// Trigger Registry");
3957
+ lines.push("// ============================================================================");
3958
+ lines.push("");
3959
+ lines.push("interface TriggerRegistry {");
3960
+ for (const trigger of triggers2) {
3961
+ const sourceType = trigger.source?.type ?? "unknown";
3962
+ lines.push(` /** ${sourceType} trigger: ${trigger.name} */`);
3963
+ lines.push(` '${trigger.name}': {`);
3964
+ lines.push(` name: '${trigger.name}'`);
3965
+ lines.push(` bindingName: '${trigger.bindingName}'`);
3966
+ lines.push(` sourceType: '${sourceType}'`);
3967
+ lines.push(" }");
3968
+ }
3969
+ lines.push("}");
3970
+ lines.push("");
3971
+ lines.push("// ============================================================================");
3972
+ lines.push("// Module Augmentation");
3973
+ lines.push("// ============================================================================");
3974
+ lines.push("");
3975
+ lines.push("declare module '@cloudwerk/trigger' {");
3976
+ lines.push(" /**");
3977
+ lines.push(" * Type-safe emit to other triggers.");
3978
+ lines.push(" * Only triggers defined in app/triggers/ are valid targets.");
3979
+ lines.push(" */");
3980
+ lines.push(" export function emit<T extends TriggerName>(");
3981
+ lines.push(" trigger: T,");
3982
+ lines.push(" payload: unknown,");
3983
+ lines.push(" options?: EmitOptions");
3984
+ lines.push(" ): Promise<EmitResult>");
3985
+ lines.push("");
3986
+ lines.push(" /** Get list of all trigger names */");
3987
+ lines.push(" export function getTriggerNames(): TriggerName[]");
3988
+ lines.push("");
3989
+ lines.push(" /** Check if a trigger exists */");
3990
+ lines.push(" export function triggerExists(name: string): name is TriggerName");
3991
+ lines.push("}");
3992
+ lines.push("");
3993
+ return lines.join("\n");
3994
+ }
3995
+
3996
+ // src/commands/triggers/generate.ts
3997
+ async function triggersGenerate(options = {}) {
3998
+ const verbose = options.verbose ?? false;
3999
+ const dryRun = options.dryRun ?? false;
4000
+ const logger = createLogger(verbose);
4001
+ const generateWrangler = options.wrangler ?? !options.types;
4002
+ const generateTypes = options.types ?? !options.wrangler;
4003
+ try {
4004
+ const cwd = process.cwd();
4005
+ logger.debug("Loading configuration...");
4006
+ const config = await loadConfig7(cwd);
4007
+ const appDir = config.appDir;
4008
+ logger.debug(`Scanning for triggers in ${appDir}/triggers/...`);
4009
+ const scanResult = await scanTriggers4(appDir, {
4010
+ extensions: config.extensions
4011
+ });
4012
+ const manifest = buildTriggerManifest4(scanResult, appDir);
4013
+ if (options.json) {
4014
+ const result = {
4015
+ triggerCount: manifest.triggers.length,
4016
+ dryRun
4017
+ };
4018
+ if (generateWrangler) {
4019
+ const wranglerResult = generateTriggerWrangler(cwd, manifest, { dryRun });
4020
+ result.wrangler = {
4021
+ path: wranglerResult.wranglerPath,
4022
+ changed: wranglerResult.changed,
4023
+ triggers: wranglerResult.triggers,
4024
+ generatedToml: dryRun ? wranglerResult.generatedToml : void 0
4025
+ };
4026
+ }
4027
+ if (generateTypes) {
4028
+ const typesResult = generateTriggerTypes(cwd, manifest, { includeTimestamp: !dryRun });
4029
+ result.types = {
4030
+ path: typesResult.file,
4031
+ triggerCount: typesResult.triggerCount,
4032
+ triggers: typesResult.triggers
4033
+ };
4034
+ }
4035
+ console.log(JSON.stringify(result, null, 2));
4036
+ return;
4037
+ }
4038
+ console.log();
4039
+ console.log(pc12.bold("Generating Trigger Configuration"));
4040
+ console.log();
4041
+ if (manifest.triggers.length === 0) {
4042
+ console.log(pc12.dim(" No triggers found. Nothing to generate."));
4043
+ console.log();
4044
+ return;
4045
+ }
4046
+ console.log(pc12.dim(` Found ${manifest.triggers.length} trigger(s)`));
4047
+ console.log();
4048
+ if (generateWrangler) {
4049
+ const wranglerResult = generateTriggerWrangler(cwd, manifest, {
4050
+ dryRun,
4051
+ includeComments: true
4052
+ });
4053
+ if (dryRun) {
4054
+ console.log(pc12.cyan(" wrangler.toml (dry run):"));
4055
+ console.log();
4056
+ const lines = wranglerResult.generatedToml.split("\n");
4057
+ for (const line of lines) {
4058
+ console.log(` ${line}`);
4059
+ }
4060
+ console.log();
4061
+ } else {
4062
+ console.log(` ${pc12.green("\u2713")} Updated ${pc12.dim(wranglerResult.wranglerPath)}`);
4063
+ for (const trigger of wranglerResult.triggers) {
4064
+ console.log(pc12.dim(` - ${trigger.name} (${trigger.sourceType})`));
4065
+ }
4066
+ }
4067
+ }
4068
+ if (generateTypes) {
4069
+ if (!dryRun) {
4070
+ const typesResult = generateTriggerTypes(cwd, manifest, { includeTimestamp: true });
4071
+ console.log(` ${pc12.green("\u2713")} Generated ${pc12.dim(typesResult.file)}`);
4072
+ console.log(pc12.dim(` - ${typesResult.triggerCount} trigger type(s)`));
4073
+ } else {
4074
+ console.log(pc12.cyan(" TypeScript types (dry run):"));
4075
+ console.log(pc12.dim(` Would generate .cloudwerk/types/triggers.d.ts`));
4076
+ }
4077
+ }
4078
+ console.log();
4079
+ if (!dryRun && generateTypes) {
4080
+ console.log(pc12.dim(' Tip: Add ".cloudwerk/types" to your tsconfig.json include array'));
4081
+ console.log();
4082
+ }
4083
+ if (manifest.errors.length > 0) {
4084
+ console.log(pc12.red("Errors:"));
4085
+ for (const error of manifest.errors) {
4086
+ console.log(pc12.dim(` - ${error.file}: ${error.message}`));
4087
+ }
4088
+ console.log();
4089
+ }
4090
+ if (manifest.warnings.length > 0) {
4091
+ console.log(pc12.yellow("Warnings:"));
4092
+ for (const warning of manifest.warnings) {
4093
+ console.log(pc12.dim(` - ${warning.file}: ${warning.message}`));
4094
+ }
4095
+ console.log();
4096
+ }
4097
+ } catch (error) {
4098
+ handleCommandError(error, verbose);
4099
+ }
4100
+ }
4101
+
4102
+ // src/commands/objects.ts
4103
+ import pc13 from "picocolors";
4104
+ import {
4105
+ loadConfig as loadConfig8,
4106
+ scanDurableObjects,
4107
+ buildDurableObjectManifest
4108
+ } from "@cloudwerk/core/build";
4109
+ async function objects(options = {}) {
4110
+ const verbose = options.verbose ?? false;
4111
+ const logger = createLogger(verbose);
4112
+ try {
4113
+ const cwd = process.cwd();
4114
+ logger.debug("Loading configuration...");
4115
+ const config = await loadConfig8(cwd);
4116
+ const appDir = config.appDir;
4117
+ logger.debug(`Scanning for durable objects in ${appDir}/objects/...`);
4118
+ const scanResult = await scanDurableObjects(appDir, {
4119
+ extensions: config.extensions
4120
+ });
4121
+ const manifest = buildDurableObjectManifest(scanResult, appDir);
4122
+ console.log();
4123
+ console.log(pc13.bold("Cloudwerk Durable Objects"));
4124
+ console.log();
4125
+ const sqliteCount = manifest.durableObjects.filter((o) => o.sqlite).length;
4126
+ const regularCount = manifest.durableObjects.length - sqliteCount;
4127
+ console.log(pc13.dim(` Found ${manifest.durableObjects.length} durable objects:`));
4128
+ if (manifest.durableObjects.length > 0) {
4129
+ console.log(pc13.dim(` - ${sqliteCount} with SQLite storage`));
4130
+ console.log(pc13.dim(` - ${regularCount} with KV storage`));
4131
+ }
4132
+ console.log();
4133
+ if (manifest.durableObjects.length > 0) {
4134
+ for (const obj of manifest.durableObjects) {
4135
+ const storage = obj.sqlite ? pc13.yellow("SQLite") : pc13.green("KV");
4136
+ const methods = obj.methodNames.length > 0 ? pc13.dim(`${obj.methodNames.length} methods`) : pc13.dim("no methods");
4137
+ console.log(
4138
+ ` ${pc13.cyan(obj.className)} ${pc13.dim("(")}${storage}${pc13.dim(", ")}${methods}${pc13.dim(")")}`
4139
+ );
4140
+ }
4141
+ console.log();
4142
+ }
4143
+ console.log(pc13.bold("Commands:"));
4144
+ console.log();
4145
+ console.log(pc13.dim(" cloudwerk objects list ") + "List all durable objects");
4146
+ console.log(pc13.dim(" cloudwerk objects info <name> ") + "Show durable object details");
4147
+ console.log(pc13.dim(" cloudwerk objects migrations ") + "Show migration history");
4148
+ console.log(pc13.dim(" cloudwerk objects generate ") + "Regenerate wrangler config");
4149
+ console.log();
4150
+ if (manifest.durableObjects.length === 0) {
4151
+ console.log(pc13.bold("Quick Start:"));
4152
+ console.log();
4153
+ console.log(pc13.dim(" Create a durable object at app/objects/counter.ts:"));
4154
+ console.log();
4155
+ console.log(pc13.cyan(" import { defineDurableObject } from '@cloudwerk/durable-object'"));
4156
+ console.log();
4157
+ console.log(pc13.cyan(" interface CounterState { value: number }"));
4158
+ console.log();
4159
+ console.log(pc13.cyan(" export default defineDurableObject<CounterState>({"));
4160
+ console.log(pc13.cyan(" init: () => ({ value: 0 }),"));
4161
+ console.log();
4162
+ console.log(pc13.cyan(" methods: {"));
4163
+ console.log(pc13.cyan(" async increment(amount = 1) {"));
4164
+ console.log(pc13.cyan(" this.state.value += amount"));
4165
+ console.log(pc13.cyan(" return this.state.value"));
4166
+ console.log(pc13.cyan(" }"));
4167
+ console.log(pc13.cyan(" }"));
4168
+ console.log(pc13.cyan(" })"));
4169
+ console.log();
4170
+ console.log(pc13.dim(" Then use it in your routes:"));
4171
+ console.log();
4172
+ console.log(pc13.cyan(" import { durableObjects } from '@cloudwerk/core/bindings'"));
4173
+ console.log();
4174
+ console.log(pc13.cyan(" export async function POST(request, { params }) {"));
4175
+ console.log(pc13.cyan(" const id = durableObjects.Counter.idFromName(params.id)"));
4176
+ console.log(pc13.cyan(" const counter = durableObjects.Counter.get(id)"));
4177
+ console.log(pc13.cyan(" const value = await counter.increment(1)"));
4178
+ console.log(pc13.cyan(" return Response.json({ value })"));
4179
+ console.log(pc13.cyan(" }"));
4180
+ console.log();
4181
+ }
4182
+ } catch (error) {
4183
+ handleCommandError(error, verbose);
4184
+ }
4185
+ }
4186
+
4187
+ // src/commands/objects/list.ts
4188
+ import pc14 from "picocolors";
4189
+ import {
4190
+ loadConfig as loadConfig9,
4191
+ scanDurableObjects as scanDurableObjects2,
4192
+ buildDurableObjectManifest as buildDurableObjectManifest2
4193
+ } from "@cloudwerk/core/build";
4194
+ async function objectsList(options = {}) {
4195
+ const verbose = options.verbose ?? false;
4196
+ const format = options.format ?? "table";
4197
+ const logger = createLogger(verbose);
4198
+ try {
4199
+ const cwd = process.cwd();
4200
+ logger.debug("Loading configuration...");
4201
+ const config = await loadConfig9(cwd);
4202
+ const appDir = config.appDir;
4203
+ logger.debug(`Scanning for durable objects in ${appDir}/objects/...`);
4204
+ const scanResult = await scanDurableObjects2(appDir, {
4205
+ extensions: config.extensions
4206
+ });
4207
+ const manifest = buildDurableObjectManifest2(scanResult, appDir);
4208
+ if (format === "json") {
4209
+ console.log(JSON.stringify(manifest, null, 2));
4210
+ return;
4211
+ }
4212
+ console.log();
4213
+ console.log(
4214
+ pc14.bold("Durable Objects") + pc14.dim(` (${manifest.durableObjects.length} found):`)
4215
+ );
4216
+ console.log();
4217
+ if (manifest.durableObjects.length === 0) {
4218
+ console.log(pc14.dim(" No durable objects found."));
4219
+ console.log();
4220
+ console.log(pc14.dim(" Create a durable object at app/objects/counter.ts:"));
4221
+ console.log();
4222
+ console.log(pc14.dim(" import { defineDurableObject } from '@cloudwerk/durable-object'"));
4223
+ console.log(pc14.dim(" export default defineDurableObject({"));
4224
+ console.log(pc14.dim(" methods: {"));
4225
+ console.log(pc14.dim(" async increment(amount) { ... }"));
4226
+ console.log(pc14.dim(" }"));
4227
+ console.log(pc14.dim(" })"));
4228
+ console.log();
4229
+ return;
4230
+ }
4231
+ console.log(
4232
+ pc14.dim(
4233
+ " Class Binding Storage Methods Handlers"
4234
+ )
4235
+ );
4236
+ console.log(
4237
+ pc14.dim(
4238
+ " " + "\u2500".repeat(18) + " " + "\u2500".repeat(18) + " " + "\u2500".repeat(7) + " " + "\u2500".repeat(7) + " " + "\u2500".repeat(20)
4239
+ )
4240
+ );
4241
+ for (const obj of manifest.durableObjects) {
4242
+ const className = obj.className.padEnd(18);
4243
+ const binding = obj.bindingName.padEnd(18);
4244
+ const storage = obj.sqlite ? pc14.yellow("SQLite ") : pc14.green("KV ");
4245
+ const methods = obj.methodNames.length.toString().padEnd(7);
4246
+ const handlers = [];
4247
+ if (obj.hasFetch) handlers.push("fetch");
4248
+ if (obj.hasAlarm) handlers.push("alarm");
4249
+ if (obj.hasWebSocket) handlers.push("websocket");
4250
+ const handlersStr = handlers.length > 0 ? handlers.join(", ") : pc14.dim("none");
4251
+ console.log(
4252
+ ` ${pc14.cyan(className)} ${pc14.dim(binding)} ${storage} ${methods} ${handlersStr}`
4253
+ );
4254
+ }
4255
+ console.log();
4256
+ if (manifest.errors.length > 0) {
4257
+ console.log(pc14.red("Errors:"));
4258
+ for (const error of manifest.errors) {
4259
+ console.log(pc14.red(` - ${error.file}: ${error.message}`));
4260
+ }
4261
+ console.log();
4262
+ }
4263
+ if (manifest.warnings.length > 0) {
4264
+ console.log(pc14.yellow("Warnings:"));
4265
+ for (const warning of manifest.warnings) {
4266
+ console.log(pc14.yellow(` - ${warning.file}: ${warning.message}`));
4267
+ }
4268
+ console.log();
4269
+ }
4270
+ console.log(pc14.dim("Use 'cloudwerk objects info <name>' for details."));
4271
+ console.log(pc14.dim("Use 'cloudwerk objects generate' to update wrangler.toml."));
4272
+ console.log();
4273
+ } catch (error) {
4274
+ handleCommandError(error, verbose);
4275
+ }
4276
+ }
4277
+
4278
+ // src/commands/objects/info.ts
4279
+ import pc15 from "picocolors";
4280
+ import {
4281
+ loadConfig as loadConfig10,
4282
+ scanDurableObjects as scanDurableObjects3,
4283
+ buildDurableObjectManifest as buildDurableObjectManifest3
4284
+ } from "@cloudwerk/core/build";
4285
+ async function objectsInfo(name, options = {}) {
4286
+ const verbose = options.verbose ?? false;
4287
+ const format = options.format ?? "table";
4288
+ const logger = createLogger(verbose);
4289
+ try {
4290
+ const cwd = process.cwd();
4291
+ logger.debug("Loading configuration...");
4292
+ const config = await loadConfig10(cwd);
4293
+ const appDir = config.appDir;
4294
+ logger.debug(`Scanning for durable objects in ${appDir}/objects/...`);
4295
+ const scanResult = await scanDurableObjects3(appDir, {
4296
+ extensions: config.extensions
4297
+ });
4298
+ const manifest = buildDurableObjectManifest3(scanResult, appDir);
4299
+ const obj = manifest.durableObjects.find(
4300
+ (o) => o.name === name || o.className === name || o.bindingName === name
4301
+ );
4302
+ if (!obj) {
4303
+ console.log();
4304
+ console.log(pc15.red(`Durable object '${name}' not found.`));
4305
+ console.log();
4306
+ if (manifest.durableObjects.length > 0) {
4307
+ console.log(pc15.dim("Available durable objects:"));
4308
+ for (const o of manifest.durableObjects) {
4309
+ console.log(pc15.dim(` - ${o.className} (${o.name})`));
4310
+ }
4311
+ } else {
4312
+ console.log(pc15.dim("No durable objects found in app/objects/."));
4313
+ }
4314
+ console.log();
4315
+ return;
4316
+ }
4317
+ if (format === "json") {
4318
+ console.log(JSON.stringify(obj, null, 2));
4319
+ return;
4320
+ }
4321
+ console.log();
4322
+ console.log(pc15.bold(`Durable Object: ${obj.className}`));
4323
+ console.log();
4324
+ console.log(pc15.dim(" Configuration:"));
4325
+ console.log(` Name: ${pc15.cyan(obj.name)}`);
4326
+ console.log(` Class: ${pc15.cyan(obj.className)}`);
4327
+ console.log(` Binding: ${pc15.yellow(obj.bindingName)}`);
4328
+ console.log(` File: ${pc15.dim(obj.filePath)}`);
4329
+ console.log(
4330
+ ` Storage: ${obj.sqlite ? pc15.yellow("SQLite") : pc15.green("KV")}`
4331
+ );
4332
+ console.log();
4333
+ console.log(pc15.dim(" Handlers:"));
4334
+ console.log(
4335
+ ` fetch: ${obj.hasFetch ? pc15.green("yes") : pc15.dim("no")}`
4336
+ );
4337
+ console.log(
4338
+ ` alarm: ${obj.hasAlarm ? pc15.green("yes") : pc15.dim("no")}`
4339
+ );
4340
+ console.log(
4341
+ ` webSocket: ${obj.hasWebSocket ? pc15.green("yes") : pc15.dim("no")}`
4342
+ );
4343
+ console.log();
4344
+ console.log(pc15.dim(" RPC Methods:"));
4345
+ if (obj.methodNames.length > 0) {
4346
+ for (const method of obj.methodNames) {
4347
+ console.log(` - ${pc15.cyan(method)}()`);
4348
+ }
4349
+ } else {
4350
+ console.log(pc15.dim(" No RPC methods defined"));
4351
+ }
4352
+ console.log();
4353
+ console.log(pc15.dim(" wrangler.toml binding:"));
4354
+ console.log();
4355
+ console.log(pc15.cyan(" [durable_objects]"));
4356
+ console.log(pc15.cyan(" bindings = ["));
4357
+ console.log(
4358
+ pc15.cyan(
4359
+ ` { name = "${obj.bindingName}", class_name = "${obj.className}" }`
4360
+ )
4361
+ );
4362
+ console.log(pc15.cyan(" ]"));
4363
+ console.log();
4364
+ console.log(pc15.cyan(" [[migrations]]"));
4365
+ console.log(pc15.cyan(' tag = "v1"'));
4366
+ if (obj.sqlite) {
4367
+ console.log(pc15.cyan(` new_sqlite_classes = ["${obj.className}"]`));
4368
+ } else {
4369
+ console.log(pc15.cyan(` new_classes = ["${obj.className}"]`));
4370
+ }
4371
+ console.log();
4372
+ console.log(pc15.dim(" Usage in routes:"));
4373
+ console.log();
4374
+ console.log(pc15.cyan(" import { durableObjects } from '@cloudwerk/core/bindings'"));
4375
+ console.log();
4376
+ console.log(pc15.cyan(" export async function POST(request, { params }) {"));
4377
+ console.log(
4378
+ pc15.cyan(` const id = durableObjects.${obj.className}.idFromName(params.id)`)
4379
+ );
4380
+ console.log(
4381
+ pc15.cyan(` const stub = durableObjects.${obj.className}.get(id)`)
4382
+ );
4383
+ if (obj.methodNames.length > 0) {
4384
+ console.log(
4385
+ pc15.cyan(` const result = await stub.${obj.methodNames[0]}()`)
4386
+ );
4387
+ } else if (obj.hasFetch) {
4388
+ console.log(pc15.cyan(" const response = await stub.fetch(request)"));
4389
+ }
4390
+ console.log(pc15.cyan(" return Response.json({ result })"));
4391
+ console.log(pc15.cyan(" }"));
4392
+ console.log();
4393
+ } catch (error) {
4394
+ handleCommandError(error, verbose);
4395
+ }
4396
+ }
4397
+
4398
+ // src/commands/objects/migrations.ts
4399
+ import pc16 from "picocolors";
4400
+ import {
4401
+ loadConfig as loadConfig11,
4402
+ scanDurableObjects as scanDurableObjects4,
4403
+ buildDurableObjectManifest as buildDurableObjectManifest4
4404
+ } from "@cloudwerk/core/build";
4405
+
4406
+ // src/utils/durable-object-wrangler.ts
4407
+ import * as fs13 from "fs";
4408
+ import * as path14 from "path";
4409
+ function generateBindingsToml(entries, includeComments) {
4410
+ const lines = [];
4411
+ if (includeComments) {
4412
+ lines.push("# Durable object bindings");
4413
+ }
4414
+ lines.push("[durable_objects]");
4415
+ lines.push("bindings = [");
4416
+ for (let i = 0; i < entries.length; i++) {
4417
+ const entry = entries[i];
4418
+ const comma = i < entries.length - 1 ? "," : "";
4419
+ if (includeComments) {
4420
+ lines.push(` # ${entry.name} (from app/objects/${entry.filePath})`);
4421
+ }
4422
+ lines.push(` { name = "${entry.bindingName}", class_name = "${entry.className}" }${comma}`);
4423
+ }
4424
+ lines.push("]");
4425
+ return lines.join("\n");
4426
+ }
4427
+ function generateMigrationToml(entries, migrationTag, includeComments) {
4428
+ const sqliteClasses = entries.filter((e) => e.sqlite).map((e) => e.className);
4429
+ const regularClasses = entries.filter((e) => !e.sqlite).map((e) => e.className);
4430
+ if (sqliteClasses.length === 0 && regularClasses.length === 0) {
4431
+ return "";
4432
+ }
4433
+ const lines = [];
4434
+ if (includeComments) {
4435
+ lines.push("# Durable object migrations");
4436
+ }
4437
+ lines.push("[[migrations]]");
4438
+ lines.push(`tag = "${migrationTag}"`);
4439
+ if (sqliteClasses.length > 0) {
4440
+ const classList = sqliteClasses.map((c) => `"${c}"`).join(", ");
4441
+ lines.push(`new_sqlite_classes = [${classList}]`);
4442
+ }
4443
+ if (regularClasses.length > 0) {
4444
+ const classList = regularClasses.map((c) => `"${c}"`).join(", ");
4445
+ lines.push(`new_classes = [${classList}]`);
4446
+ }
4447
+ return lines.join("\n");
4448
+ }
4449
+ function generateDurableObjectToml(manifest, includeComments = true, migrationTag) {
4450
+ if (manifest.durableObjects.length === 0) {
4451
+ return "";
4452
+ }
4453
+ const lines = [];
4454
+ if (includeComments) {
4455
+ lines.push("# ============================================================================");
4456
+ lines.push("# Cloudwerk Durable Objects - Auto-generated from app/objects/");
4457
+ lines.push("# ============================================================================");
4458
+ lines.push("");
4459
+ }
4460
+ lines.push(generateBindingsToml(manifest.durableObjects, includeComments));
4461
+ lines.push("");
4462
+ const tag = migrationTag || generateMigrationTag();
4463
+ const migrationToml = generateMigrationToml(manifest.durableObjects, tag, includeComments);
4464
+ if (migrationToml) {
4465
+ lines.push(migrationToml);
4466
+ lines.push("");
4467
+ }
4468
+ return lines.join("\n").trim();
4469
+ }
4470
+ function generateMigrationTag() {
4471
+ const now = /* @__PURE__ */ new Date();
4472
+ const year = now.getFullYear();
4473
+ const month = String(now.getMonth() + 1).padStart(2, "0");
4474
+ const day = String(now.getDate()).padStart(2, "0");
4475
+ const hour = String(now.getHours()).padStart(2, "0");
4476
+ const min = String(now.getMinutes()).padStart(2, "0");
4477
+ return `v${year}${month}${day}${hour}${min}`;
4478
+ }
4479
+ function extractExistingMigrationTags(content) {
4480
+ const tags = [];
4481
+ const regex = /^\s*tag\s*=\s*"([^"]+)"/gm;
4482
+ let match;
4483
+ while ((match = regex.exec(content)) !== null) {
4484
+ tags.push(match[1]);
4485
+ }
4486
+ return tags;
4487
+ }
4488
+ var DO_SECTION_START = "# ============================================================================";
4489
+ var DO_SECTION_MARKER = "# Cloudwerk Durable Objects - Auto-generated";
4490
+ function hasDurableObjectSection(content) {
4491
+ return content.includes(DO_SECTION_MARKER);
4492
+ }
4493
+ function removeDurableObjectSection(content) {
4494
+ const startIndex = content.indexOf(DO_SECTION_START);
4495
+ if (startIndex === -1 || !content.includes(DO_SECTION_MARKER)) {
4496
+ return content;
4497
+ }
4498
+ const lines = content.split("\n");
4499
+ let sectionStartLine = -1;
4500
+ let sectionEndLine = lines.length;
4501
+ for (let i = 0; i < lines.length; i++) {
4502
+ if (lines[i].includes(DO_SECTION_MARKER)) {
4503
+ sectionStartLine = i > 0 && lines[i - 1].includes("===") ? i - 1 : i;
4504
+ break;
4505
+ }
4506
+ }
4507
+ if (sectionStartLine === -1) {
4508
+ return content;
4509
+ }
4510
+ for (let i = sectionStartLine + 2; i < lines.length; i++) {
4511
+ const line = lines[i].trim();
4512
+ if (line === "" || line.startsWith("#")) {
4513
+ continue;
4514
+ }
4515
+ if (line.startsWith("[[") && !line.includes("durable_objects") && !line.includes("migrations")) {
4516
+ sectionEndLine = i;
4517
+ break;
4518
+ }
4519
+ if (line.startsWith("[") && !line.startsWith("[[") && !line.includes("durable_objects")) {
4520
+ sectionEndLine = i;
4521
+ break;
4522
+ }
4523
+ }
4524
+ const before = lines.slice(0, sectionStartLine);
4525
+ const after = lines.slice(sectionEndLine);
4526
+ while (before.length > 0 && before[before.length - 1].trim() === "") {
4527
+ before.pop();
4528
+ }
4529
+ return [...before, "", ...after].join("\n");
4530
+ }
4531
+ function generateDurableObjectWrangler(cwd, manifest, options = {}) {
4532
+ const { dryRun = false, includeComments = true, migrationTag } = options;
4533
+ const wranglerPath = findWranglerToml(cwd) || path14.join(cwd, "wrangler.toml");
4534
+ const generatedToml = generateDurableObjectToml(manifest, includeComments, migrationTag);
4535
+ const result = {
4536
+ wranglerPath,
4537
+ changed: false,
4538
+ durableObjects: manifest.durableObjects.map((obj) => ({
4539
+ name: obj.name,
4540
+ bindingName: obj.bindingName,
4541
+ className: obj.className
4542
+ })),
4543
+ generatedToml
4544
+ };
4545
+ if (manifest.durableObjects.length === 0) {
4546
+ return result;
4547
+ }
4548
+ if (dryRun) {
4549
+ result.changed = true;
4550
+ return result;
4551
+ }
4552
+ let content = "";
4553
+ if (fs13.existsSync(wranglerPath)) {
4554
+ content = readWranglerTomlRaw(cwd);
4555
+ }
4556
+ if (hasDurableObjectSection(content)) {
4557
+ content = removeDurableObjectSection(content);
4558
+ }
4559
+ const newContent = content.trim() + "\n\n" + generatedToml + "\n";
4560
+ writeWranglerTomlRaw(cwd, newContent);
4561
+ result.changed = true;
4562
+ return result;
4563
+ }
4564
+
4565
+ // src/commands/objects/migrations.ts
4566
+ async function objectsMigrations(options = {}) {
4567
+ const verbose = options.verbose ?? false;
4568
+ const logger = createLogger(verbose);
4569
+ try {
4570
+ const cwd = process.cwd();
4571
+ logger.debug("Loading configuration...");
4572
+ const config = await loadConfig11(cwd);
4573
+ const appDir = config.appDir;
4574
+ logger.debug(`Scanning for durable objects in ${appDir}/objects/...`);
4575
+ const scanResult = await scanDurableObjects4(appDir, {
4576
+ extensions: config.extensions
4577
+ });
4578
+ const manifest = buildDurableObjectManifest4(scanResult, appDir);
4579
+ console.log();
4580
+ console.log(pc16.bold("Durable Object Migrations"));
4581
+ console.log();
4582
+ const wranglerPath = findWranglerToml(cwd);
4583
+ if (!wranglerPath) {
4584
+ console.log(pc16.dim(" No wrangler.toml found."));
4585
+ console.log();
4586
+ console.log(pc16.dim(" Run 'cloudwerk objects generate' to create configuration."));
4587
+ console.log();
4588
+ return;
4589
+ }
4590
+ const content = readWranglerTomlRaw(cwd);
4591
+ const tags = extractExistingMigrationTags(content);
4592
+ if (tags.length === 0) {
4593
+ console.log(pc16.dim(" No migrations found in wrangler.toml."));
4594
+ console.log();
4595
+ if (manifest.durableObjects.length > 0) {
4596
+ console.log(pc16.dim(" Run 'cloudwerk objects generate' to create migrations."));
4597
+ console.log();
4598
+ }
4599
+ return;
4600
+ }
4601
+ console.log(pc16.dim(` Found ${tags.length} migration(s):`));
4602
+ console.log();
4603
+ for (let i = 0; i < tags.length; i++) {
4604
+ const tag = tags[i];
4605
+ const isLatest = i === tags.length - 1;
4606
+ console.log(
4607
+ ` ${isLatest ? pc16.green("\u25CF") : pc16.dim("\u25CB")} ${pc16.cyan(tag)}${isLatest ? pc16.green(" (current)") : ""}`
4608
+ );
4609
+ }
4610
+ console.log();
4611
+ console.log(pc16.dim(" Current durable objects:"));
4612
+ console.log();
4613
+ if (manifest.durableObjects.length === 0) {
4614
+ console.log(pc16.dim(" No durable objects defined."));
4615
+ } else {
4616
+ for (const obj of manifest.durableObjects) {
4617
+ const storage = obj.sqlite ? pc16.yellow("SQLite") : pc16.green("KV");
4618
+ console.log(` ${pc16.cyan(obj.className)} ${pc16.dim("(")}${storage}${pc16.dim(")")}`);
4619
+ }
4620
+ }
4621
+ console.log();
4622
+ console.log(pc16.dim(" Tips:"));
4623
+ console.log(pc16.dim(" - Migrations are applied automatically on deploy"));
4624
+ console.log(pc16.dim(" - Each tag should be unique and incremental"));
4625
+ console.log(
4626
+ pc16.dim(" - Deleting a class will delete all data for that class")
4627
+ );
4628
+ console.log();
4629
+ } catch (error) {
4630
+ handleCommandError(error, verbose);
4631
+ }
4632
+ }
4633
+
4634
+ // src/commands/objects/generate.ts
4635
+ import pc17 from "picocolors";
4636
+ import {
4637
+ loadConfig as loadConfig12,
4638
+ scanDurableObjects as scanDurableObjects5,
4639
+ buildDurableObjectManifest as buildDurableObjectManifest5
4640
+ } from "@cloudwerk/core/build";
4641
+
4642
+ // src/utils/durable-object-type-generator.ts
4643
+ import * as fs14 from "fs";
4644
+ import * as path15 from "path";
4645
+ var CLOUDWERK_TYPES_DIR3 = ".cloudwerk/types";
4646
+ var DURABLE_OBJECTS_DTS = "durable-objects.d.ts";
4647
+ function generateDurableObjectTypes(cwd, manifest, options = {}) {
4648
+ const includeTimestamp = options.includeTimestamp ?? true;
4649
+ const typesDir = path15.join(cwd, CLOUDWERK_TYPES_DIR3);
4650
+ fs14.mkdirSync(typesDir, { recursive: true });
4651
+ const dtsPath = path15.join(typesDir, DURABLE_OBJECTS_DTS);
4652
+ const dtsContent = generateDurableObjectsDts(manifest.durableObjects, includeTimestamp);
4653
+ fs14.writeFileSync(dtsPath, dtsContent, "utf-8");
4654
+ const objectInfo = manifest.durableObjects.map((obj) => ({
4655
+ name: obj.name,
4656
+ bindingName: obj.bindingName,
4657
+ className: obj.className
4658
+ }));
4659
+ return {
4660
+ typesDir,
4661
+ file: dtsPath,
4662
+ objectCount: manifest.durableObjects.length,
4663
+ durableObjects: objectInfo
4664
+ };
4665
+ }
4666
+ function generateDurableObjectsDts(durableObjects, includeTimestamp) {
4667
+ const lines = [];
4668
+ lines.push("// Auto-generated by cloudwerk objects - DO NOT EDIT");
4669
+ if (includeTimestamp) {
4670
+ lines.push(`// Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
4671
+ }
4672
+ lines.push("//");
4673
+ lines.push("// This file provides type information for durable object namespaces");
4674
+ lines.push('// Add ".cloudwerk/types" to your tsconfig.json include array');
4675
+ lines.push("");
4676
+ lines.push("import type { DurableObjectNamespace, DurableObjectStub } from '@cloudwerk/core/bindings'");
4677
+ lines.push("");
4678
+ lines.push("// ============================================================================");
4679
+ lines.push("// Durable Object Stub Interfaces");
4680
+ lines.push("// ============================================================================");
4681
+ lines.push("//");
4682
+ lines.push("// Define method signatures for your durable objects.");
4683
+ lines.push("// The RPC methods from your defineDurableObject() will be available on stubs.");
4684
+ lines.push("//");
4685
+ lines.push("");
4686
+ for (const obj of durableObjects) {
4687
+ lines.push(`/** Stub interface for the '${obj.name}' durable object */`);
4688
+ lines.push(`interface ${obj.className}Stub extends DurableObjectStub {`);
4689
+ if (obj.methodNames.length > 0) {
4690
+ lines.push(" // RPC methods defined in app/objects/" + obj.filePath);
4691
+ for (const methodName of obj.methodNames) {
4692
+ lines.push(` ${methodName}(...args: unknown[]): Promise<unknown>`);
4693
+ }
4694
+ } else {
4695
+ lines.push(" // No RPC methods defined - add methods to defineDurableObject({ methods: { ... } })");
4696
+ }
4697
+ lines.push("}");
4698
+ lines.push("");
4699
+ }
4700
+ lines.push("// ============================================================================");
4701
+ lines.push("// Durable Object Namespace Interfaces");
4702
+ lines.push("// ============================================================================");
4703
+ lines.push("");
4704
+ for (const obj of durableObjects) {
4705
+ lines.push(`/** Namespace for the '${obj.name}' durable object (binding: ${obj.bindingName}) */`);
4706
+ lines.push(`interface ${obj.className}Namespace extends DurableObjectNamespace<${obj.className}Stub> {`);
4707
+ lines.push(` get(id: import('@cloudwerk/core/bindings').DurableObjectId): ${obj.className}Stub`);
4708
+ lines.push("}");
4709
+ lines.push("");
4710
+ }
4711
+ lines.push("// ============================================================================");
4712
+ lines.push("// Combined Durable Objects Interface");
4713
+ lines.push("// ============================================================================");
4714
+ lines.push("");
4715
+ lines.push("interface CloudwerkDurableObjects {");
4716
+ for (const obj of durableObjects) {
4717
+ lines.push(` /** Durable object namespace for '${obj.name}' (binding: ${obj.bindingName}) */`);
4718
+ lines.push(` ${obj.className}: ${obj.className}Namespace`);
4719
+ }
4720
+ lines.push("}");
4721
+ lines.push("");
4722
+ lines.push("// ============================================================================");
4723
+ lines.push("// Module Augmentation");
4724
+ lines.push("// ============================================================================");
4725
+ lines.push("");
4726
+ lines.push("declare module '@cloudwerk/core/bindings' {");
4727
+ lines.push(" /** Typed durable object namespaces based on app/objects/ definitions */");
4728
+ lines.push(" export const durableObjects: CloudwerkDurableObjects");
4729
+ lines.push("");
4730
+ lines.push(" /** Get a typed durable object namespace by name */");
4731
+ lines.push(" export function getDurableObject<K extends keyof CloudwerkDurableObjects>(");
4732
+ lines.push(" name: K");
4733
+ lines.push(" ): CloudwerkDurableObjects[K]");
4734
+ lines.push("}");
4735
+ lines.push("");
4736
+ lines.push("// ============================================================================");
4737
+ lines.push("// Env Type Extension");
4738
+ lines.push("// ============================================================================");
4739
+ lines.push("//");
4740
+ lines.push("// Extend your Env type with these bindings:");
4741
+ lines.push("//");
4742
+ lines.push("// interface Env {");
4743
+ for (const obj of durableObjects) {
4744
+ lines.push(`// ${obj.bindingName}: DurableObjectNamespace<${obj.className}Stub>`);
4745
+ }
4746
+ lines.push("// }");
4747
+ lines.push("");
4748
+ return lines.join("\n");
4749
+ }
4750
+
4751
+ // src/commands/objects/generate.ts
4752
+ async function objectsGenerate(options = {}) {
4753
+ const verbose = options.verbose ?? false;
4754
+ const dryRun = options.dryRun ?? false;
4755
+ const skipTypes = options.skipTypes ?? false;
4756
+ const logger = createLogger(verbose);
4757
+ try {
4758
+ const cwd = process.cwd();
4759
+ logger.debug("Loading configuration...");
4760
+ const config = await loadConfig12(cwd);
4761
+ const appDir = config.appDir;
4762
+ logger.debug(`Scanning for durable objects in ${appDir}/objects/...`);
4763
+ const scanResult = await scanDurableObjects5(appDir, {
4764
+ extensions: config.extensions
4765
+ });
4766
+ const manifest = buildDurableObjectManifest5(scanResult, appDir);
4767
+ console.log();
4768
+ console.log(pc17.bold("Generating Durable Object Configuration"));
4769
+ console.log();
4770
+ if (manifest.durableObjects.length === 0) {
4771
+ console.log(pc17.dim(" No durable objects found."));
4772
+ console.log();
4773
+ console.log(pc17.dim(" Create a durable object at app/objects/counter.ts first."));
4774
+ console.log();
4775
+ return;
4776
+ }
4777
+ console.log(pc17.dim(` Found ${manifest.durableObjects.length} durable object(s):`));
4778
+ for (const obj of manifest.durableObjects) {
4779
+ const storage = obj.sqlite ? pc17.yellow("SQLite") : pc17.green("KV");
4780
+ console.log(` - ${pc17.cyan(obj.className)} ${pc17.dim("(")}${storage}${pc17.dim(")")}`);
4781
+ }
4782
+ console.log();
4783
+ if (dryRun) {
4784
+ console.log(pc17.yellow(" Dry run - no files will be written."));
4785
+ console.log();
4786
+ }
4787
+ console.log(pc17.dim(" Generating wrangler.toml bindings..."));
4788
+ const wranglerResult = generateDurableObjectWrangler(cwd, manifest, {
4789
+ dryRun,
4790
+ includeComments: true
4791
+ });
4792
+ if (wranglerResult.changed) {
4793
+ console.log(pc17.green(` \u2713 Updated ${wranglerResult.wranglerPath}`));
4794
+ } else {
4795
+ console.log(pc17.dim(" No changes needed"));
4796
+ }
4797
+ if (!skipTypes) {
4798
+ console.log();
4799
+ console.log(pc17.dim(" Generating TypeScript types..."));
4800
+ if (!dryRun) {
4801
+ const typesResult = generateDurableObjectTypes(cwd, manifest);
4802
+ console.log(pc17.green(` \u2713 Generated ${typesResult.file}`));
4803
+ } else {
4804
+ console.log(pc17.dim(" Would generate .cloudwerk/types/durable-objects.d.ts"));
4805
+ }
4806
+ }
4807
+ console.log();
4808
+ if (verbose || dryRun) {
4809
+ console.log(pc17.dim(" Generated wrangler.toml section:"));
4810
+ console.log();
4811
+ for (const line of wranglerResult.generatedToml.split("\n")) {
4812
+ console.log(pc17.cyan(` ${line}`));
4813
+ }
4814
+ console.log();
4815
+ }
4816
+ if (manifest.errors.length > 0) {
4817
+ console.log(pc17.red(" Errors:"));
4818
+ for (const error of manifest.errors) {
4819
+ console.log(pc17.red(` - ${error.file}: ${error.message}`));
4820
+ }
4821
+ console.log();
4822
+ }
4823
+ if (manifest.warnings.length > 0) {
4824
+ console.log(pc17.yellow(" Warnings:"));
4825
+ for (const warning of manifest.warnings) {
4826
+ console.log(pc17.yellow(` - ${warning.file}: ${warning.message}`));
4827
+ }
4828
+ console.log();
4829
+ }
4830
+ if (!dryRun) {
4831
+ console.log(pc17.green(" Configuration generated successfully!"));
4832
+ console.log();
4833
+ console.log(pc17.dim(" Next steps:"));
4834
+ console.log(pc17.dim(" 1. Review wrangler.toml for correctness"));
4835
+ console.log(pc17.dim(" 2. Add .cloudwerk/types to your tsconfig.json include"));
4836
+ console.log(pc17.dim(" 3. Run 'pnpm dev' to test locally"));
4837
+ console.log(pc17.dim(" 4. Run 'pnpm deploy' to deploy to Cloudflare"));
4838
+ console.log();
4839
+ }
4840
+ } catch (error) {
4841
+ handleCommandError(error, verbose);
4842
+ }
4843
+ }
4844
+
4845
+ // src/commands/services.ts
4846
+ import pc18 from "picocolors";
4847
+ import { loadConfig as loadConfig13, scanServices as scanServices2, buildServiceManifest as buildServiceManifest2 } from "@cloudwerk/core/build";
4848
+ async function services(options = {}) {
4849
+ const verbose = options.verbose ?? false;
4850
+ const logger = createLogger(verbose);
4851
+ try {
4852
+ const cwd = process.cwd();
4853
+ logger.debug("Loading configuration...");
4854
+ const config = await loadConfig13(cwd);
4855
+ const appDir = config.appDir;
4856
+ logger.debug(`Scanning for services in ${appDir}/services/...`);
4857
+ const scanResult = await scanServices2(appDir, { extensions: config.extensions });
4858
+ const manifest = buildServiceManifest2(scanResult, appDir);
4859
+ console.log();
4860
+ console.log(pc18.bold("Cloudwerk Services"));
4861
+ console.log();
4862
+ const localCount = manifest.services.filter((s) => s.mode === "local").length;
4863
+ const extractedCount = manifest.services.filter((s) => s.mode === "extracted").length;
4864
+ console.log(pc18.dim(` Found ${manifest.services.length} services:`));
4865
+ if (manifest.services.length > 0) {
4866
+ console.log(pc18.dim(` - ${localCount} local (direct calls)`));
4867
+ console.log(pc18.dim(` - ${extractedCount} extracted (separate Workers)`));
4868
+ }
4869
+ console.log();
4870
+ if (manifest.services.length > 0) {
4871
+ for (const service of manifest.services) {
4872
+ const mode = service.mode === "extracted" ? pc18.yellow("extracted") : pc18.green("local");
4873
+ console.log(` ${pc18.cyan(service.name)} ${pc18.dim("(")}${mode}${pc18.dim(")")}`);
4874
+ }
4875
+ console.log();
4876
+ }
4877
+ console.log(pc18.bold("Commands:"));
4878
+ console.log();
4879
+ console.log(pc18.dim(" cloudwerk services list ") + "List all services");
4880
+ console.log(pc18.dim(" cloudwerk services info <name> ") + "Show service details");
4881
+ console.log(pc18.dim(" cloudwerk services extract <name> ") + "Extract to separate Worker");
4882
+ console.log(pc18.dim(" cloudwerk services inline <name> ") + "Convert back to local mode");
4883
+ console.log(pc18.dim(" cloudwerk services deploy <name> ") + "Deploy extracted service");
4884
+ console.log(pc18.dim(" cloudwerk services status ") + "Show all services status");
4885
+ console.log();
4886
+ if (manifest.services.length === 0) {
4887
+ console.log(pc18.bold("Quick Start:"));
4888
+ console.log();
4889
+ console.log(pc18.dim(" Create a service at app/services/<name>/service.ts:"));
4890
+ console.log();
4891
+ console.log(pc18.cyan(" import { defineService } from '@cloudwerk/service'"));
4892
+ console.log();
4893
+ console.log(pc18.cyan(" export default defineService({"));
4894
+ console.log(pc18.cyan(" methods: {"));
4895
+ console.log(pc18.cyan(" async send({ to, subject, body }) {"));
4896
+ console.log(pc18.cyan(" // Your service logic here"));
4897
+ console.log(pc18.cyan(" return { success: true }"));
4898
+ console.log(pc18.cyan(" }"));
4899
+ console.log(pc18.cyan(" }"));
4900
+ console.log(pc18.cyan(" })"));
4901
+ console.log();
4902
+ console.log(pc18.dim(" Then use it in your routes:"));
4903
+ console.log();
4904
+ console.log(pc18.cyan(" import { services } from '@cloudwerk/core/bindings'"));
4905
+ console.log();
4906
+ console.log(pc18.cyan(" export async function POST() {"));
4907
+ console.log(pc18.cyan(" const result = await services.email.send({ to: '...' })"));
4908
+ console.log(pc18.cyan(" return json(result)"));
4909
+ console.log(pc18.cyan(" }"));
4910
+ console.log();
4911
+ }
4912
+ } catch (error) {
4913
+ handleCommandError(error, verbose);
4914
+ }
4915
+ }
4916
+
4917
+ // src/commands/services/list.ts
4918
+ import pc19 from "picocolors";
4919
+ import { loadConfig as loadConfig14, scanServices as scanServices3, buildServiceManifest as buildServiceManifest3 } from "@cloudwerk/core/build";
4920
+ async function servicesList(options = {}) {
4921
+ const verbose = options.verbose ?? false;
4922
+ const format = options.format ?? "table";
4923
+ const logger = createLogger(verbose);
4924
+ try {
4925
+ const cwd = process.cwd();
4926
+ logger.debug("Loading configuration...");
4927
+ const config = await loadConfig14(cwd);
4928
+ const appDir = config.appDir;
4929
+ logger.debug(`Scanning for services in ${appDir}/services/...`);
4930
+ const scanResult = await scanServices3(appDir, { extensions: config.extensions });
4931
+ const manifest = buildServiceManifest3(scanResult, appDir);
4932
+ if (format === "json") {
4933
+ console.log(JSON.stringify(manifest, null, 2));
4934
+ return;
4935
+ }
4936
+ console.log();
4937
+ console.log(pc19.bold("Services") + pc19.dim(` (${manifest.services.length} found):`));
4938
+ console.log();
4939
+ if (manifest.services.length === 0) {
4940
+ console.log(pc19.dim(" No services found."));
4941
+ console.log();
4942
+ console.log(pc19.dim(" Create a service at app/services/<name>/service.ts:"));
4943
+ console.log();
4944
+ console.log(pc19.dim(" import { defineService } from '@cloudwerk/service'"));
4945
+ console.log(pc19.dim(" export default defineService({"));
4946
+ console.log(pc19.dim(" methods: {"));
4947
+ console.log(pc19.dim(" async myMethod(params) { ... }"));
4948
+ console.log(pc19.dim(" }"));
4949
+ console.log(pc19.dim(" })"));
4950
+ console.log();
4951
+ return;
4952
+ }
4953
+ console.log(pc19.dim(" Name Mode Methods Bindings"));
4954
+ console.log(pc19.dim(" " + "\u2500".repeat(16) + " " + "\u2500".repeat(9) + " " + "\u2500".repeat(7) + " " + "\u2500".repeat(20)));
4955
+ for (const service of manifest.services) {
4956
+ const name = service.name.padEnd(16);
4957
+ const mode = service.mode === "extracted" ? pc19.yellow("extracted") : pc19.green("local ");
4958
+ const methods = service.methodNames.length.toString().padEnd(7);
4959
+ const bindings2 = service.requiredBindings.length > 0 ? service.requiredBindings.join(", ") : pc19.dim("none");
4960
+ console.log(` ${pc19.cyan(name)} ${mode} ${methods} ${bindings2}`);
4961
+ }
4962
+ console.log();
4963
+ if (manifest.errors.length > 0) {
4964
+ console.log(pc19.red("Errors:"));
4965
+ for (const error of manifest.errors) {
4966
+ console.log(pc19.red(` - ${error.file}: ${error.message}`));
4967
+ }
4968
+ console.log();
4969
+ }
4970
+ if (manifest.warnings.length > 0) {
4971
+ console.log(pc19.yellow("Warnings:"));
4972
+ for (const warning of manifest.warnings) {
4973
+ console.log(pc19.yellow(` - ${warning.file}: ${warning.message}`));
4974
+ }
4975
+ console.log();
4976
+ }
4977
+ console.log(pc19.dim("Use 'cloudwerk services info <name>' for details."));
4978
+ console.log(pc19.dim("Use 'cloudwerk services extract <name>' to extract a service."));
4979
+ console.log();
4980
+ } catch (error) {
4981
+ handleCommandError(error, verbose);
4982
+ }
4983
+ }
4984
+
4985
+ // src/commands/services/info.ts
4986
+ import pc20 from "picocolors";
4987
+ import { loadConfig as loadConfig15, scanServices as scanServices4, buildServiceManifest as buildServiceManifest4 } from "@cloudwerk/core/build";
4988
+ async function servicesInfo(serviceName, options = {}) {
4989
+ const verbose = options.verbose ?? false;
4990
+ const format = options.format ?? "text";
4991
+ const logger = createLogger(verbose);
4992
+ try {
4993
+ const cwd = process.cwd();
4994
+ logger.debug("Loading configuration...");
4995
+ const config = await loadConfig15(cwd);
4996
+ const appDir = config.appDir;
4997
+ logger.debug(`Scanning for services in ${appDir}/services/...`);
4998
+ const scanResult = await scanServices4(appDir, { extensions: config.extensions });
4999
+ const manifest = buildServiceManifest4(scanResult, appDir);
5000
+ const service = manifest.services.find((s) => s.name === serviceName);
5001
+ if (!service) {
5002
+ const available = manifest.services.map((s) => s.name);
5003
+ throw new CliError(
5004
+ `Service '${serviceName}' not found`,
5005
+ "ENOENT",
5006
+ available.length > 0 ? `Available services: ${available.join(", ")}` : "No services found in app/services/"
5007
+ );
5008
+ }
5009
+ if (format === "json") {
5010
+ console.log(JSON.stringify(service, null, 2));
5011
+ return;
5012
+ }
5013
+ console.log();
5014
+ console.log(pc20.bold(`Service: ${service.name}`));
5015
+ console.log();
5016
+ console.log(pc20.dim(" Location: ") + service.filePath);
5017
+ console.log(pc20.dim(" Mode: ") + (service.mode === "extracted" ? pc20.yellow("extracted") : pc20.green("local")));
5018
+ console.log(pc20.dim(" Binding: ") + service.bindingName);
5019
+ console.log(pc20.dim(" Worker Name: ") + service.workerName);
5020
+ console.log(pc20.dim(" Entrypoint: ") + service.entrypointClass);
5021
+ console.log();
5022
+ console.log(pc20.dim(" Methods:"));
5023
+ if (service.methodNames.length > 0) {
5024
+ for (const method of service.methodNames) {
5025
+ console.log(` - ${pc20.cyan(method)}()`);
5026
+ }
5027
+ } else {
5028
+ console.log(pc20.dim(" (methods will be detected after service is loaded)"));
5029
+ }
5030
+ console.log();
5031
+ console.log(pc20.dim(" Required Bindings:"));
5032
+ if (service.requiredBindings.length > 0) {
5033
+ for (const binding of service.requiredBindings) {
5034
+ console.log(` - ${binding}`);
5035
+ }
5036
+ } else {
5037
+ console.log(pc20.dim(" none configured"));
5038
+ }
5039
+ console.log();
5040
+ console.log(pc20.dim(" Hooks: ") + (service.hasHooks ? pc20.green("yes") : pc20.dim("no")));
5041
+ console.log();
5042
+ if (service.mode === "local") {
5043
+ console.log(pc20.dim("Actions:"));
5044
+ console.log(pc20.dim(` Extract: cloudwerk services extract ${serviceName}`));
5045
+ } else {
5046
+ console.log(pc20.dim("Actions:"));
5047
+ console.log(pc20.dim(` Inline: cloudwerk services inline ${serviceName}`));
5048
+ console.log(pc20.dim(` Deploy: cloudwerk services deploy ${serviceName}`));
5049
+ }
5050
+ console.log();
5051
+ } catch (error) {
5052
+ handleCommandError(error, verbose);
5053
+ }
5054
+ }
5055
+
5056
+ // src/commands/services/extract.ts
5057
+ import pc21 from "picocolors";
5058
+ import { loadConfig as loadConfig16, scanServices as scanServices5, buildServiceManifest as buildServiceManifest5 } from "@cloudwerk/core/build";
5059
+
5060
+ // src/utils/service-worker-generator.ts
5061
+ import * as fs15 from "fs";
5062
+ import * as path16 from "path";
5063
+ var EXTRACTED_DIR = ".cloudwerk/extracted";
5064
+ function generateServiceWorker(cwd, service, options = {}) {
5065
+ const includeTimestamp = options.includeTimestamp ?? true;
5066
+ const workerDir = path16.join(cwd, EXTRACTED_DIR, service.workerName);
5067
+ fs15.mkdirSync(workerDir, { recursive: true });
5068
+ const workerPath = path16.join(workerDir, "worker.ts");
5069
+ const workerContent = generateWorkerEntrypoint(cwd, service, includeTimestamp);
5070
+ fs15.writeFileSync(workerPath, workerContent, "utf-8");
5071
+ const wranglerPath = path16.join(workerDir, "wrangler.toml");
5072
+ const wranglerContent = generateWorkerWranglerToml(service, includeTimestamp);
5073
+ fs15.writeFileSync(wranglerPath, wranglerContent, "utf-8");
5074
+ return {
5075
+ workerDir,
5076
+ workerFile: workerPath,
5077
+ wranglerFile: wranglerPath,
5078
+ service
5079
+ };
5080
+ }
5081
+ function generateWorkerEntrypoint(cwd, service, includeTimestamp) {
5082
+ const lines = [];
5083
+ const workerDir = path16.join(cwd, EXTRACTED_DIR, service.workerName);
5084
+ const relativePath = path16.relative(workerDir, service.absolutePath).replace(/\\/g, "/").replace(/\.tsx?$/, "");
5085
+ lines.push("// Auto-generated by cloudwerk services - DO NOT EDIT");
5086
+ if (includeTimestamp) {
5087
+ lines.push(`// Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
5088
+ }
5089
+ lines.push("//");
5090
+ lines.push(`// WorkerEntrypoint wrapper for ${service.name} service`);
5091
+ lines.push(`// Source: ${service.filePath}`);
5092
+ lines.push("");
5093
+ lines.push("import { WorkerEntrypoint } from 'cloudflare:workers'");
5094
+ lines.push(`import service from '${relativePath}'`);
5095
+ lines.push("");
5096
+ lines.push("// Environment bindings for this service");
5097
+ lines.push("interface Env {");
5098
+ if (service.requiredBindings.length > 0) {
5099
+ for (const binding of service.requiredBindings) {
5100
+ lines.push(` ${binding}: unknown`);
5101
+ }
5102
+ } else {
5103
+ lines.push(" // Add binding types here");
5104
+ }
5105
+ lines.push("}");
5106
+ lines.push("");
5107
+ lines.push("/**");
5108
+ lines.push(` * ${service.entrypointClass} - WorkerEntrypoint for ${service.name} service`);
5109
+ lines.push(" *");
5110
+ lines.push(" * This class wraps the service methods and handles lifecycle hooks.");
5111
+ lines.push(" * Cloudflare's service binding RPC will call methods directly on this class.");
5112
+ lines.push(" */");
5113
+ lines.push(`export class ${service.entrypointClass} extends WorkerEntrypoint<Env> {`);
5114
+ lines.push(" private initialized = false");
5115
+ lines.push("");
5116
+ lines.push(" /**");
5117
+ lines.push(" * Initialize the service (runs onInit hook once).");
5118
+ lines.push(" */");
5119
+ lines.push(" private async ensureInitialized(): Promise<void> {");
5120
+ lines.push(" if (this.initialized) return");
5121
+ lines.push(" this.initialized = true");
5122
+ lines.push("");
5123
+ lines.push(" if (service.hooks?.onInit) {");
5124
+ lines.push(" await service.hooks.onInit()");
5125
+ lines.push(" }");
5126
+ lines.push(" }");
5127
+ lines.push("");
5128
+ if (service.methodNames.length > 0) {
5129
+ for (const methodName of service.methodNames) {
5130
+ lines.push(` /**`);
5131
+ lines.push(` * ${methodName} - wrapped service method`);
5132
+ lines.push(" */");
5133
+ lines.push(` async ${methodName}(...args: unknown[]): Promise<unknown> {`);
5134
+ lines.push(" await this.ensureInitialized()");
5135
+ lines.push("");
5136
+ lines.push(" // Run onBefore hook");
5137
+ lines.push(" if (service.hooks?.onBefore) {");
5138
+ lines.push(` await service.hooks.onBefore('${methodName}', args)`);
5139
+ lines.push(" }");
5140
+ lines.push("");
5141
+ lines.push(" try {");
5142
+ lines.push(" // Call the actual service method");
5143
+ lines.push(` const method = service.methods.${methodName}`);
5144
+ lines.push(" const result = await method.apply({ env: this.env }, args)");
5145
+ lines.push("");
5146
+ lines.push(" // Run onAfter hook");
5147
+ lines.push(" if (service.hooks?.onAfter) {");
5148
+ lines.push(` await service.hooks.onAfter('${methodName}', result)`);
5149
+ lines.push(" }");
5150
+ lines.push("");
5151
+ lines.push(" return result");
5152
+ lines.push(" } catch (error) {");
5153
+ lines.push(" // Run onError hook");
5154
+ lines.push(" if (service.hooks?.onError) {");
5155
+ lines.push(` await service.hooks.onError('${methodName}', error as Error)`);
5156
+ lines.push(" }");
5157
+ lines.push(" throw error");
5158
+ lines.push(" }");
5159
+ lines.push(" }");
5160
+ lines.push("");
5161
+ }
5162
+ } else {
5163
+ lines.push(" // Method wrappers will be generated once service is loaded");
5164
+ lines.push(" // Run `cloudwerk services extract` after defining methods");
5165
+ lines.push("");
5166
+ }
5167
+ lines.push("}");
5168
+ lines.push("");
5169
+ lines.push("// Default export for direct worker access");
5170
+ lines.push("export default {");
5171
+ lines.push(" async fetch(): Promise<Response> {");
5172
+ lines.push(` return new Response('${service.name} service is running. Use service binding RPC to call methods.')`);
5173
+ lines.push(" },");
5174
+ lines.push("}");
5175
+ lines.push("");
5176
+ return lines.join("\n");
5177
+ }
5178
+ function generateWorkerWranglerToml(service, includeTimestamp) {
5179
+ const lines = [];
5180
+ lines.push("# Auto-generated by cloudwerk services - DO NOT EDIT");
5181
+ if (includeTimestamp) {
5182
+ lines.push(`# Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
5183
+ }
5184
+ lines.push("#");
5185
+ lines.push(`# Wrangler configuration for ${service.name} service`);
5186
+ lines.push(`# Source: ${service.filePath}`);
5187
+ lines.push("");
5188
+ lines.push(`name = "${service.workerName}"`);
5189
+ lines.push('main = "worker.ts"');
5190
+ lines.push('compatibility_date = "2024-09-23"');
5191
+ lines.push("");
5192
+ if (service.requiredBindings.length > 0) {
5193
+ lines.push("# Bindings required by this service");
5194
+ lines.push("# Update these with your actual binding configurations");
5195
+ lines.push("");
5196
+ for (const binding of service.requiredBindings) {
5197
+ if (binding.includes("DB") || binding.includes("D1")) {
5198
+ lines.push("[[d1_databases]]");
5199
+ lines.push(`binding = "${binding}"`);
5200
+ lines.push(`database_name = "\${${binding}_NAME}"`);
5201
+ lines.push(`database_id = "\${${binding}_ID}"`);
5202
+ } else if (binding.includes("KV") || binding.includes("CACHE")) {
5203
+ lines.push("[[kv_namespaces]]");
5204
+ lines.push(`binding = "${binding}"`);
5205
+ lines.push(`id = "\${${binding}_ID}"`);
5206
+ } else if (binding.includes("R2") || binding.includes("BUCKET") || binding.includes("STORAGE")) {
5207
+ lines.push("[[r2_buckets]]");
5208
+ lines.push(`binding = "${binding}"`);
5209
+ lines.push(`bucket_name = "\${${binding}_NAME}"`);
5210
+ } else if (binding.includes("QUEUE")) {
5211
+ lines.push("[[queues.producers]]");
5212
+ lines.push(`queue = "\${${binding}_NAME}"`);
5213
+ lines.push(`binding = "${binding}"`);
5214
+ } else {
5215
+ lines.push("[vars]");
5216
+ lines.push(`# ${binding} = "..."`);
5217
+ }
5218
+ lines.push("");
5219
+ }
5220
+ } else {
5221
+ lines.push("# No bindings configured");
5222
+ lines.push("# Add bindings in your service definition:");
5223
+ lines.push("# config: {");
5224
+ lines.push("# extraction: {");
5225
+ lines.push("# bindings: ['DB', 'CACHE'],");
5226
+ lines.push("# }");
5227
+ lines.push("# }");
5228
+ lines.push("");
5229
+ }
5230
+ return lines.join("\n");
5231
+ }
5232
+ function serviceWorkerExists(cwd, serviceName) {
5233
+ const workerDir = path16.join(cwd, EXTRACTED_DIR, `${serviceName}-service`);
5234
+ return fs15.existsSync(workerDir);
5235
+ }
5236
+ function deleteServiceWorker(cwd, serviceName) {
5237
+ const workerDir = path16.join(cwd, EXTRACTED_DIR, `${serviceName}-service`);
5238
+ if (fs15.existsSync(workerDir)) {
5239
+ fs15.rmSync(workerDir, { recursive: true, force: true });
5240
+ return true;
5241
+ }
5242
+ return false;
5243
+ }
5244
+ function getExtractedDir(cwd) {
5245
+ return path16.join(cwd, EXTRACTED_DIR);
5246
+ }
5247
+ function getExtractedServiceDirs(cwd) {
5248
+ const extractedDir = path16.join(cwd, EXTRACTED_DIR);
5249
+ if (!fs15.existsSync(extractedDir)) {
5250
+ return [];
5251
+ }
5252
+ return fs15.readdirSync(extractedDir).map((name) => path16.join(extractedDir, name)).filter((p) => fs15.statSync(p).isDirectory());
5253
+ }
5254
+
5255
+ // src/utils/service-wrangler.ts
5256
+ import * as fs16 from "fs";
5257
+ import * as path17 from "path";
5258
+ function generateServiceBindingToml(service, includeComments) {
5259
+ const lines = [];
5260
+ if (includeComments) {
5261
+ lines.push(`# Service binding for '${service.name}' (from app/services/${service.filePath})`);
5262
+ }
5263
+ lines.push("[[services]]");
5264
+ lines.push(`binding = "${service.bindingName}"`);
5265
+ lines.push(`service = "${service.workerName}"`);
5266
+ lines.push(`entrypoint = "${service.entrypointClass}"`);
5267
+ return lines.join("\n");
5268
+ }
5269
+ function generateServiceToml(manifest, includeComments = true) {
5270
+ const extractedServices = manifest.services.filter((s) => s.mode === "extracted");
5271
+ if (extractedServices.length === 0) {
5272
+ return "";
5273
+ }
5274
+ const lines = [];
5275
+ if (includeComments) {
5276
+ lines.push("# ============================================================================");
5277
+ lines.push("# Cloudwerk Services - Auto-generated from app/services/");
5278
+ lines.push("# ============================================================================");
5279
+ lines.push("#");
5280
+ lines.push("# These service bindings connect to extracted Workers.");
5281
+ lines.push("# Each service runs as a separate Worker and is called via RPC.");
5282
+ lines.push("#");
5283
+ lines.push("# To deploy extracted services, run:");
5284
+ lines.push("# cloudwerk services deploy <name>");
5285
+ lines.push("#");
5286
+ lines.push("");
5287
+ }
5288
+ for (const service of extractedServices) {
5289
+ lines.push(generateServiceBindingToml(service, includeComments));
5290
+ lines.push("");
5291
+ }
5292
+ return lines.join("\n").trim();
5293
+ }
5294
+ var SERVICE_SECTION_START = "# ============================================================================";
5295
+ var SERVICE_SECTION_MARKER = "# Cloudwerk Services - Auto-generated";
5296
+ function hasServiceSection(content) {
5297
+ return content.includes(SERVICE_SECTION_MARKER);
5298
+ }
5299
+ function removeServiceSection(content) {
5300
+ const startIndex = content.indexOf(SERVICE_SECTION_START);
5301
+ if (startIndex === -1 || !content.includes(SERVICE_SECTION_MARKER)) {
5302
+ return content;
5303
+ }
5304
+ const lines = content.split("\n");
5305
+ let sectionStartLine = -1;
5306
+ let sectionEndLine = lines.length;
5307
+ for (let i = 0; i < lines.length; i++) {
5308
+ if (lines[i].includes(SERVICE_SECTION_MARKER)) {
5309
+ sectionStartLine = i > 0 && lines[i - 1].includes("===") ? i - 1 : i;
5310
+ break;
5311
+ }
5312
+ }
5313
+ if (sectionStartLine === -1) {
5314
+ return content;
5315
+ }
5316
+ for (let i = sectionStartLine + 2; i < lines.length; i++) {
5317
+ const line = lines[i].trim();
5318
+ if (line === "" || line.startsWith("#")) {
5319
+ continue;
5320
+ }
5321
+ if (line.startsWith("[[") && !line.includes("services]]")) {
5322
+ sectionEndLine = i;
5323
+ break;
5324
+ }
5325
+ if (line.startsWith("[") && !line.startsWith("[[") && !line.includes("services")) {
5326
+ sectionEndLine = i;
5327
+ break;
5328
+ }
5329
+ }
5330
+ const before = lines.slice(0, sectionStartLine);
5331
+ const after = lines.slice(sectionEndLine);
5332
+ while (before.length > 0 && before[before.length - 1].trim() === "") {
5333
+ before.pop();
5334
+ }
5335
+ return [...before, "", ...after].join("\n");
5336
+ }
5337
+ function generateServiceWrangler(cwd, manifest, options = {}) {
5338
+ const { dryRun = false, includeComments = true } = options;
5339
+ const wranglerPath = findWranglerToml(cwd) || path17.join(cwd, "wrangler.toml");
5340
+ const generatedToml = generateServiceToml(manifest, includeComments);
5341
+ const extractedServices = manifest.services.filter((s) => s.mode === "extracted");
5342
+ const result = {
5343
+ wranglerPath,
5344
+ changed: false,
5345
+ services: extractedServices.map((s) => ({
5346
+ name: s.name,
5347
+ workerName: s.workerName,
5348
+ bindingName: s.bindingName,
5349
+ mode: s.mode
5350
+ })),
5351
+ generatedToml
5352
+ };
5353
+ if (extractedServices.length === 0) {
5354
+ return result;
5355
+ }
5356
+ if (dryRun) {
5357
+ result.changed = true;
5358
+ return result;
5359
+ }
5360
+ let content = "";
5361
+ if (fs16.existsSync(wranglerPath)) {
5362
+ content = readWranglerTomlRaw(cwd);
5363
+ }
5364
+ if (hasServiceSection(content)) {
5365
+ content = removeServiceSection(content);
5366
+ }
5367
+ const newContent = content.trim() + "\n\n" + generatedToml + "\n";
5368
+ writeWranglerTomlRaw(cwd, newContent);
5369
+ result.changed = true;
5370
+ return result;
5371
+ }
5372
+ function removeServiceWrangler(cwd) {
5373
+ const wranglerPath = findWranglerToml(cwd);
5374
+ if (!wranglerPath) {
5375
+ return false;
5376
+ }
5377
+ const content = readWranglerTomlRaw(cwd);
5378
+ if (!hasServiceSection(content)) {
5379
+ return false;
5380
+ }
5381
+ const newContent = removeServiceSection(content);
5382
+ writeWranglerTomlRaw(cwd, newContent);
5383
+ return true;
5384
+ }
5385
+ function hasServiceInWrangler(cwd, serviceName) {
5386
+ const wranglerPath = findWranglerToml(cwd);
5387
+ if (!wranglerPath) {
5388
+ return false;
5389
+ }
5390
+ const content = readWranglerTomlRaw(cwd);
5391
+ const bindingPattern = new RegExp(`binding\\s*=\\s*["']${serviceName.toUpperCase()}_SERVICE["']`);
5392
+ return bindingPattern.test(content);
5393
+ }
5394
+
5395
+ // src/utils/service-type-generator.ts
5396
+ import * as fs17 from "fs";
5397
+ import * as path18 from "path";
5398
+ var CLOUDWERK_TYPES_DIR4 = ".cloudwerk/types";
5399
+ var SERVICES_DTS = "services.d.ts";
5400
+ function generateServiceTypes(cwd, manifest, options = {}) {
5401
+ const includeTimestamp = options.includeTimestamp ?? true;
5402
+ const typesDir = path18.join(cwd, CLOUDWERK_TYPES_DIR4);
5403
+ fs17.mkdirSync(typesDir, { recursive: true });
5404
+ const servicesPath = path18.join(typesDir, SERVICES_DTS);
5405
+ const servicesContent = generateServicesDts(manifest.services, includeTimestamp);
5406
+ fs17.writeFileSync(servicesPath, servicesContent, "utf-8");
5407
+ const serviceInfo = manifest.services.map((s) => ({
5408
+ name: s.name,
5409
+ bindingName: s.bindingName,
5410
+ mode: s.mode
5411
+ }));
5412
+ return {
5413
+ typesDir,
5414
+ file: servicesPath,
5415
+ serviceCount: manifest.services.length,
5416
+ services: serviceInfo
5417
+ };
5418
+ }
5419
+ function generateServicesDts(services2, includeTimestamp) {
5420
+ const lines = [];
5421
+ lines.push("// Auto-generated by cloudwerk services - DO NOT EDIT");
5422
+ if (includeTimestamp) {
5423
+ lines.push(`// Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
5424
+ }
5425
+ lines.push("//");
5426
+ lines.push("// This file provides type information for service interfaces");
5427
+ lines.push('// Add ".cloudwerk/types" to your tsconfig.json include array');
5428
+ lines.push("");
5429
+ lines.push("// ============================================================================");
5430
+ lines.push("// Service Interfaces");
5431
+ lines.push("// ============================================================================");
5432
+ lines.push("//");
5433
+ lines.push("// Define your service interfaces below or in separate type files.");
5434
+ lines.push("// These interfaces should match the methods defined in your service.ts files.");
5435
+ lines.push("//");
5436
+ lines.push("// Example:");
5437
+ lines.push("// interface EmailServiceMethods {");
5438
+ lines.push("// send(params: { to: string; subject: string; body: string }): Promise<{ success: boolean }>");
5439
+ lines.push("// sendBatch(emails: Array<{ to: string; subject: string }>): Promise<{ sent: number }>");
5440
+ lines.push("// }");
5441
+ lines.push("//");
5442
+ lines.push("");
5443
+ for (const service of services2) {
5444
+ const interfaceName = `${capitalizeFirst(service.name)}ServiceMethods`;
5445
+ const modeComment = service.mode === "extracted" ? " (extracted)" : " (local)";
5446
+ lines.push(`/** Methods for the '${service.name}' service${modeComment} */`);
5447
+ lines.push(`// eslint-disable-next-line @typescript-eslint/no-empty-interface`);
5448
+ lines.push(`interface ${interfaceName} {`);
5449
+ if (service.methodNames.length > 0) {
5450
+ for (const methodName of service.methodNames) {
5451
+ lines.push(` /** Method: ${methodName} */`);
5452
+ lines.push(` ${methodName}(...args: unknown[]): Promise<unknown>`);
5453
+ }
5454
+ } else {
5455
+ lines.push(" // Add method signatures here");
5456
+ }
5457
+ lines.push("}");
5458
+ lines.push("");
5459
+ }
5460
+ lines.push("// ============================================================================");
5461
+ lines.push("// Services Interface");
5462
+ lines.push("// ============================================================================");
5463
+ lines.push("");
5464
+ lines.push("interface CloudwerkServices {");
5465
+ for (const service of services2) {
5466
+ const interfaceName = `${capitalizeFirst(service.name)}ServiceMethods`;
5467
+ const modeComment = service.mode === "extracted" ? `extracted (binding: ${service.bindingName})` : "local (direct calls)";
5468
+ lines.push(` /** Service '${service.name}' - ${modeComment} */`);
5469
+ lines.push(` ${service.name}: ${interfaceName}`);
5470
+ }
5471
+ lines.push("}");
5472
+ lines.push("");
5473
+ lines.push("// ============================================================================");
5474
+ lines.push("// Module Augmentation");
5475
+ lines.push("// ============================================================================");
5476
+ lines.push("");
5477
+ lines.push("declare module '@cloudwerk/core/bindings' {");
5478
+ lines.push(" /** Typed services based on app/services/ definitions */");
5479
+ lines.push(" export const services: CloudwerkServices");
5480
+ lines.push("");
5481
+ lines.push(" /** Get a typed service by name */");
5482
+ lines.push(" export function getService<K extends keyof CloudwerkServices>(");
5483
+ lines.push(" name: K");
5484
+ lines.push(" ): CloudwerkServices[K]");
5485
+ lines.push("}");
5486
+ lines.push("");
5487
+ return lines.join("\n");
5488
+ }
5489
+ function capitalizeFirst(str) {
5490
+ return str.charAt(0).toUpperCase() + str.slice(1);
5491
+ }
5492
+
5493
+ // src/commands/services/extract.ts
5494
+ async function servicesExtract(serviceName, options = {}) {
5495
+ const verbose = options.verbose ?? false;
5496
+ const dryRun = options.dryRun ?? false;
5497
+ const logger = createLogger(verbose);
5498
+ try {
5499
+ const cwd = process.cwd();
5500
+ logger.debug("Loading configuration...");
5501
+ const config = await loadConfig16(cwd);
5502
+ const appDir = config.appDir;
5503
+ logger.debug(`Scanning for services in ${appDir}/services/...`);
5504
+ const scanResult = await scanServices5(appDir, { extensions: config.extensions });
5505
+ const serviceModes = { [serviceName]: "extracted" };
5506
+ const manifest = buildServiceManifest5(scanResult, appDir, {
5507
+ defaultMode: "local",
5508
+ serviceModes
5509
+ });
5510
+ const service = manifest.services.find((s) => s.name === serviceName);
5511
+ if (!service) {
5512
+ const available = manifest.services.map((s) => s.name);
5513
+ throw new CliError(
5514
+ `Service '${serviceName}' not found`,
5515
+ "ENOENT",
5516
+ available.length > 0 ? `Available services: ${available.join(", ")}` : "No services found in app/services/"
5517
+ );
5518
+ }
5519
+ console.log();
5520
+ console.log(pc21.bold(`Extracting service: ${serviceName}`));
5521
+ console.log();
5522
+ if (dryRun) {
5523
+ console.log(pc21.yellow("Dry run - no changes will be made"));
5524
+ console.log();
5525
+ }
5526
+ logger.debug("Generating WorkerEntrypoint wrapper...");
5527
+ const workerResult = generateServiceWorker(cwd, service, { includeTimestamp: true });
5528
+ console.log(` ${pc21.green("\u2713")} Worker generated: ${workerResult.workerFile}`);
5529
+ console.log(` ${pc21.green("\u2713")} Wrangler config: ${workerResult.wranglerFile}`);
5530
+ if (!dryRun) {
5531
+ logger.debug("Updating wrangler.toml with service binding...");
5532
+ const wranglerResult = generateServiceWrangler(cwd, manifest);
5533
+ if (wranglerResult.changed) {
5534
+ console.log(` ${pc21.green("\u2713")} Service binding added to ${wranglerResult.wranglerPath}`);
5535
+ }
5536
+ }
5537
+ if (!dryRun) {
5538
+ logger.debug("Updating type definitions...");
5539
+ const typesResult = generateServiceTypes(cwd, manifest);
5540
+ console.log(` ${pc21.green("\u2713")} Types updated: ${typesResult.file}`);
5541
+ }
5542
+ console.log();
5543
+ console.log(pc21.bold("Next steps:"));
5544
+ console.log();
5545
+ console.log(pc21.dim(` 1. Review the generated files in .cloudwerk/extracted/${service.workerName}/`));
5546
+ console.log(pc21.dim(` 2. Update the wrangler.toml with your binding configurations`));
5547
+ console.log(pc21.dim(` 3. Deploy the extracted service:`));
5548
+ console.log();
5549
+ console.log(` cloudwerk services deploy ${serviceName}`);
5550
+ console.log();
5551
+ console.log(pc21.dim(` 4. Deploy the main worker to use the service binding:`));
5552
+ console.log();
5553
+ console.log(" cloudwerk deploy");
5554
+ console.log();
5555
+ } catch (error) {
5556
+ handleCommandError(error, verbose);
5557
+ }
5558
+ }
5559
+
5560
+ // src/commands/services/inline.ts
5561
+ import pc22 from "picocolors";
5562
+ import { loadConfig as loadConfig17, scanServices as scanServices6, buildServiceManifest as buildServiceManifest6 } from "@cloudwerk/core/build";
5563
+ async function servicesInline(serviceName, options = {}) {
5564
+ const verbose = options.verbose ?? false;
5565
+ const logger = createLogger(verbose);
5566
+ try {
5567
+ const cwd = process.cwd();
5568
+ logger.debug("Loading configuration...");
5569
+ const config = await loadConfig17(cwd);
5570
+ const appDir = config.appDir;
5571
+ logger.debug(`Scanning for services in ${appDir}/services/...`);
5572
+ const scanResult = await scanServices6(appDir, { extensions: config.extensions });
5573
+ const manifest = buildServiceManifest6(scanResult, appDir, {
5574
+ defaultMode: "local"
5575
+ });
5576
+ const service = manifest.services.find((s) => s.name === serviceName);
5577
+ if (!service) {
5578
+ const available = manifest.services.map((s) => s.name);
5579
+ throw new CliError(
5580
+ `Service '${serviceName}' not found`,
5581
+ "ENOENT",
5582
+ available.length > 0 ? `Available services: ${available.join(", ")}` : "No services found in app/services/"
5583
+ );
5584
+ }
5585
+ console.log();
5586
+ console.log(pc22.bold(`Converting service to local mode: ${serviceName}`));
5587
+ console.log();
5588
+ const hasExtracted = serviceWorkerExists(cwd, serviceName);
5589
+ if (hasExtracted) {
5590
+ logger.debug("Removing extracted worker files...");
5591
+ const deleted = deleteServiceWorker(cwd, serviceName);
5592
+ if (deleted) {
5593
+ console.log(` ${pc22.green("\u2713")} Removed extracted worker files`);
5594
+ }
5595
+ } else {
5596
+ console.log(` ${pc22.dim("\u2713")} No extracted worker files to remove`);
5597
+ }
5598
+ logger.debug("Updating wrangler.toml...");
5599
+ const removed = removeServiceWrangler(cwd);
5600
+ if (removed) {
5601
+ console.log(` ${pc22.green("\u2713")} Removed service binding from wrangler.toml`);
5602
+ } else {
5603
+ console.log(` ${pc22.dim("\u2713")} No service binding to remove`);
5604
+ }
5605
+ logger.debug("Updating type definitions...");
5606
+ const typesResult = generateServiceTypes(cwd, manifest);
5607
+ console.log(` ${pc22.green("\u2713")} Types updated: ${typesResult.file}`);
5608
+ console.log();
5609
+ console.log(pc22.green(`Service '${serviceName}' is now running in local mode.`));
5610
+ console.log();
5611
+ console.log(pc22.dim("The service will be called directly within your main Worker."));
5612
+ console.log(pc22.dim("Deploy your main Worker to apply the changes:"));
5613
+ console.log();
5614
+ console.log(" cloudwerk deploy");
5615
+ console.log();
5616
+ } catch (error) {
5617
+ handleCommandError(error, verbose);
5618
+ }
5619
+ }
5620
+
5621
+ // src/commands/services/deploy.ts
5622
+ import * as path19 from "path";
5623
+ import { spawn as spawn4 } from "child_process";
5624
+ import pc23 from "picocolors";
5625
+ import { loadConfig as loadConfig18, scanServices as scanServices7, buildServiceManifest as buildServiceManifest7 } from "@cloudwerk/core/build";
5626
+ async function servicesDeploy(serviceName, options = {}) {
5627
+ const verbose = options.verbose ?? false;
5628
+ const dryRun = options.dryRun ?? false;
5629
+ const logger = createLogger(verbose);
5630
+ try {
5631
+ const cwd = process.cwd();
5632
+ logger.debug("Loading configuration...");
5633
+ const config = await loadConfig18(cwd);
5634
+ const appDir = config.appDir;
5635
+ logger.debug(`Scanning for services in ${appDir}/services/...`);
5636
+ const scanResult = await scanServices7(appDir, { extensions: config.extensions });
5637
+ const manifest = buildServiceManifest7(scanResult, appDir);
5638
+ const service = manifest.services.find((s) => s.name === serviceName);
5639
+ if (!service) {
5640
+ const available = manifest.services.map((s) => s.name);
5641
+ throw new CliError(
5642
+ `Service '${serviceName}' not found`,
5643
+ "ENOENT",
5644
+ available.length > 0 ? `Available services: ${available.join(", ")}` : "No services found in app/services/"
5645
+ );
5646
+ }
5647
+ const hasExtracted = serviceWorkerExists(cwd, serviceName);
5648
+ if (!hasExtracted) {
5649
+ throw new CliError(
5650
+ `Service '${serviceName}' is not extracted`,
5651
+ "EINVAL",
5652
+ `Run 'cloudwerk services extract ${serviceName}' first.`
5653
+ );
5654
+ }
5655
+ const workerDir = path19.join(getExtractedDir(cwd), service.workerName);
5656
+ console.log();
5657
+ console.log(pc23.bold(`Deploying service: ${serviceName}`));
5658
+ console.log();
5659
+ console.log(pc23.dim(` Worker: ${service.workerName}`));
5660
+ console.log(pc23.dim(` Location: ${workerDir}`));
5661
+ console.log();
5662
+ if (dryRun) {
5663
+ console.log(pc23.yellow("Dry run - would run:"));
5664
+ console.log(` cd ${workerDir} && wrangler deploy`);
5665
+ console.log();
5666
+ return;
5667
+ }
5668
+ console.log(pc23.dim("Running wrangler deploy..."));
5669
+ console.log();
5670
+ await new Promise((resolve4, reject) => {
5671
+ const wrangler = spawn4("wrangler", ["deploy"], {
5672
+ cwd: workerDir,
5673
+ stdio: "inherit",
5674
+ shell: true
5675
+ });
5676
+ wrangler.on("close", (code) => {
5677
+ if (code === 0) {
5678
+ resolve4();
5679
+ } else {
5680
+ reject(new CliError(
5681
+ `wrangler deploy failed with code ${code}`,
5682
+ "EEXEC",
5683
+ "Check the output above for details."
5684
+ ));
5685
+ }
5686
+ });
5687
+ wrangler.on("error", (err) => {
5688
+ reject(new CliError(
5689
+ `Failed to run wrangler: ${err.message}`,
5690
+ "EEXEC",
5691
+ "Make sure wrangler is installed: npm install -g wrangler"
5692
+ ));
5693
+ });
5694
+ });
5695
+ console.log();
5696
+ console.log(pc23.green(`Service '${serviceName}' deployed successfully!`));
5697
+ console.log();
5698
+ console.log(pc23.dim("Next steps:"));
5699
+ console.log(pc23.dim(" 1. Deploy your main worker to use the service binding:"));
5700
+ console.log();
5701
+ console.log(" cloudwerk deploy");
5702
+ console.log();
5703
+ } catch (error) {
5704
+ handleCommandError(error, verbose);
5705
+ }
5706
+ }
5707
+
5708
+ // src/commands/services/status.ts
5709
+ import pc24 from "picocolors";
5710
+ import { loadConfig as loadConfig19, scanServices as scanServices8, buildServiceManifest as buildServiceManifest8 } from "@cloudwerk/core/build";
5711
+ async function servicesStatus(options = {}) {
5712
+ const verbose = options.verbose ?? false;
5713
+ const logger = createLogger(verbose);
5714
+ try {
5715
+ const cwd = process.cwd();
5716
+ logger.debug("Loading configuration...");
5717
+ const config = await loadConfig19(cwd);
5718
+ const appDir = config.appDir;
5719
+ logger.debug(`Scanning for services in ${appDir}/services/...`);
5720
+ const scanResult = await scanServices8(appDir, { extensions: config.extensions });
5721
+ const manifest = buildServiceManifest8(scanResult, appDir);
5722
+ console.log();
5723
+ console.log(pc24.bold("Services Status"));
5724
+ console.log();
5725
+ if (manifest.services.length === 0) {
5726
+ console.log(pc24.dim(" No services found in app/services/"));
5727
+ console.log();
5728
+ return;
5729
+ }
5730
+ const localCount = manifest.services.filter((s) => s.mode === "local").length;
5731
+ const extractedCount = manifest.services.filter((s) => s.mode === "extracted").length;
5732
+ console.log(pc24.dim(` Total: ${manifest.services.length} services`));
5733
+ console.log(pc24.dim(` Local: ${localCount}`));
5734
+ console.log(pc24.dim(` Extracted: ${extractedCount}`));
5735
+ console.log();
5736
+ console.log(pc24.dim(" Service Mode Worker Files Binding"));
5737
+ console.log(pc24.dim(" " + "\u2500".repeat(16) + " " + "\u2500".repeat(9) + " " + "\u2500".repeat(12) + " " + "\u2500".repeat(10)));
5738
+ for (const service of manifest.services) {
5739
+ const name = service.name.padEnd(16);
5740
+ const mode = service.mode === "extracted" ? pc24.yellow("extracted") : pc24.green("local ");
5741
+ const hasFiles = serviceWorkerExists(cwd, service.name);
5742
+ const filesStatus = hasFiles ? pc24.green("\u2713 generated ") : pc24.dim("- not needed");
5743
+ const hasBinding = hasServiceInWrangler(cwd, service.name);
5744
+ const bindingStatus = service.mode === "extracted" ? hasBinding ? pc24.green("\u2713 yes") : pc24.yellow("\u2717 missing") : pc24.dim("n/a ");
5745
+ console.log(` ${pc24.cyan(name)} ${mode} ${filesStatus} ${bindingStatus}`);
5746
+ }
5747
+ console.log();
5748
+ const extractedDirs = getExtractedServiceDirs(cwd);
5749
+ const serviceWorkerNames = manifest.services.map((s) => s.workerName);
5750
+ const orphanedDirs = extractedDirs.filter((dir) => {
5751
+ const dirName = dir.split("/").pop() || "";
5752
+ return !serviceWorkerNames.includes(dirName);
5753
+ });
5754
+ if (orphanedDirs.length > 0) {
5755
+ console.log(pc24.yellow("Orphaned extracted workers (no matching service):"));
5756
+ for (const dir of orphanedDirs) {
5757
+ console.log(pc24.yellow(` - ${dir}`));
5758
+ }
5759
+ console.log();
5760
+ console.log(pc24.dim(" These can be safely deleted if no longer needed."));
5761
+ console.log();
5762
+ }
5763
+ if (manifest.errors.length > 0) {
5764
+ console.log(pc24.red("Errors:"));
5765
+ for (const error of manifest.errors) {
5766
+ console.log(pc24.red(` - ${error.file}: ${error.message}`));
5767
+ }
5768
+ console.log();
5769
+ }
5770
+ if (manifest.warnings.length > 0) {
5771
+ console.log(pc24.yellow("Warnings:"));
5772
+ for (const warning of manifest.warnings) {
5773
+ console.log(pc24.yellow(` - ${warning.file}: ${warning.message}`));
5774
+ }
5775
+ console.log();
5776
+ }
5777
+ console.log(pc24.dim("Use 'cloudwerk services list' for a simple list."));
5778
+ console.log(pc24.dim("Use 'cloudwerk services info <name>' for details on a specific service."));
5779
+ console.log();
5780
+ } catch (error) {
5781
+ handleCommandError(error, verbose);
5782
+ }
5783
+ }
5784
+
3145
5785
  // src/index.ts
3146
5786
  program.name("cloudwerk").description("Cloudwerk CLI - Build and deploy full-stack apps to Cloudflare").version(VERSION).enablePositionalOptions();
3147
5787
  program.command("dev [path]").description("Start development server").option("-p, --port <number>", "Port to listen on", String(DEFAULT_PORT)).option("-H, --host <host>", "Host to bind", DEFAULT_HOST).option("-c, --config <path>", "Path to config file").option("--verbose", "Enable verbose logging").action(dev);
@@ -3155,4 +5795,20 @@ bindingsCmd.command("add [type]").description("Add a new binding (d1, kv, r2, qu
3155
5795
  bindingsCmd.command("remove [name]").description("Remove a binding").option("-e, --env <environment>", "Environment to remove binding from").option("-f, --force", "Skip confirmation prompt").option("--skip-types", "Skip TypeScript type generation").option("--verbose", "Enable verbose logging").action(bindingsRemove);
3156
5796
  bindingsCmd.command("update [name]").description("Update an existing binding").option("-e, --env <environment>", "Environment to update binding in").option("--skip-types", "Skip TypeScript type generation").option("--verbose", "Enable verbose logging").action(bindingsUpdate);
3157
5797
  bindingsCmd.command("generate-types").description("Regenerate TypeScript type definitions").option("--verbose", "Enable verbose logging").action(bindingsGenerateTypes);
5798
+ var triggersCmd = program.command("triggers").description("Manage Cloudwerk triggers (scheduled, queue, R2, webhook, etc.)").enablePositionalOptions().passThroughOptions().option("--verbose", "Enable verbose logging").action(triggers);
5799
+ triggersCmd.command("list").description("List all triggers with details").option("-t, --type <type>", "Filter by source type (scheduled, queue, r2, webhook, email)").option("--json", "Output as JSON").option("--verbose", "Enable verbose logging").action(triggersList);
5800
+ triggersCmd.command("validate").description("Validate trigger configurations").option("-s, --strict", "Exit with error code if warnings are present").option("--json", "Output as JSON").option("--verbose", "Enable verbose logging").action(triggersValidate);
5801
+ triggersCmd.command("generate").description("Regenerate wrangler.toml and TypeScript types").option("--wrangler", "Only generate wrangler.toml config").option("--types", "Only generate TypeScript types").option("--dry-run", "Show what would be generated without writing").option("--json", "Output as JSON").option("--verbose", "Enable verbose logging").action(triggersGenerate);
5802
+ var objectsCmd = program.command("objects").description("Manage Cloudwerk Durable Objects").enablePositionalOptions().passThroughOptions().option("--verbose", "Enable verbose logging").action(objects);
5803
+ objectsCmd.command("list").description("List all durable objects with details").option("--json", "Output as JSON").option("--verbose", "Enable verbose logging").action(objectsList);
5804
+ objectsCmd.command("info <name>").description("Show durable object details").option("--json", "Output as JSON").option("--verbose", "Enable verbose logging").action(objectsInfo);
5805
+ objectsCmd.command("migrations").description("Show migration history for SQLite durable objects").option("--json", "Output as JSON").option("--verbose", "Enable verbose logging").action(objectsMigrations);
5806
+ objectsCmd.command("generate").description("Regenerate wrangler.toml and TypeScript types").option("--wrangler", "Only generate wrangler.toml config").option("--types", "Only generate TypeScript types").option("--dry-run", "Show what would be generated without writing").option("--json", "Output as JSON").option("--verbose", "Enable verbose logging").action(objectsGenerate);
5807
+ var servicesCmd = program.command("services").description("Manage Cloudwerk services (RPC bindings)").enablePositionalOptions().passThroughOptions().option("--verbose", "Enable verbose logging").action(services);
5808
+ servicesCmd.command("list").description("List all services with details").option("--json", "Output as JSON").option("--verbose", "Enable verbose logging").action(servicesList);
5809
+ servicesCmd.command("info <name>").description("Show service details").option("--json", "Output as JSON").option("--verbose", "Enable verbose logging").action(servicesInfo);
5810
+ servicesCmd.command("extract <name>").description("Extract service to separate Worker").option("--verbose", "Enable verbose logging").action(servicesExtract);
5811
+ servicesCmd.command("inline <name>").description("Convert extracted service back to local mode").option("--verbose", "Enable verbose logging").action(servicesInline);
5812
+ servicesCmd.command("deploy <name>").description("Deploy extracted service").option("-e, --env <environment>", "Environment to deploy to").option("--verbose", "Enable verbose logging").action(servicesDeploy);
5813
+ servicesCmd.command("status").description("Show status of all services").option("--json", "Output as JSON").option("--verbose", "Enable verbose logging").action(servicesStatus);
3158
5814
  program.parse();