@cloudwerk/cli 0.12.0 → 0.13.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.
Files changed (2) hide show
  1. package/dist/index.js +840 -1
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -265,7 +265,13 @@ import {
265
265
  resolveLayouts,
266
266
  resolveMiddleware,
267
267
  loadConfig,
268
- resolveRoutesPath
268
+ resolveRoutesPath,
269
+ scanQueues,
270
+ buildQueueManifest,
271
+ QUEUES_DIR,
272
+ scanServices,
273
+ buildServiceManifest,
274
+ SERVICES_DIR
269
275
  } from "@cloudwerk/core/build";
270
276
  var DEFAULT_OUTPUT_DIR = "./dist";
271
277
  var BUILD_TEMP_DIR = ".cloudwerk-build";
@@ -313,6 +319,30 @@ async function build(pathArg, options) {
313
319
  resolveMiddleware
314
320
  );
315
321
  logger.debug(`Found ${manifest.routes.length} routes`);
322
+ const queuesPath = path2.resolve(cwd, appDir, QUEUES_DIR);
323
+ let queueManifest = null;
324
+ if (fs2.existsSync(queuesPath)) {
325
+ const queueScanResult = await scanQueues(
326
+ path2.resolve(cwd, appDir),
327
+ { extensions: cloudwerkConfig.extensions }
328
+ );
329
+ queueManifest = buildQueueManifest(queueScanResult, cwd, { appName: "cloudwerk" });
330
+ if (queueManifest.queues.length > 0) {
331
+ logger.debug(`Found ${queueManifest.queues.length} queue(s)`);
332
+ }
333
+ }
334
+ const servicesPath = path2.resolve(cwd, appDir, SERVICES_DIR);
335
+ let serviceManifest = null;
336
+ if (fs2.existsSync(servicesPath)) {
337
+ const serviceScanResult = await scanServices(
338
+ path2.resolve(cwd, appDir),
339
+ { extensions: cloudwerkConfig.extensions }
340
+ );
341
+ serviceManifest = buildServiceManifest(serviceScanResult, cwd);
342
+ if (serviceManifest.services.length > 0) {
343
+ logger.debug(`Found ${serviceManifest.services.length} service(s)`);
344
+ }
345
+ }
316
346
  const renderer = cloudwerkConfig.ui?.renderer ?? "hono-jsx";
317
347
  const serverEntryCode = generateServerEntry(manifest, scanResult, {
318
348
  appDir,
@@ -326,6 +356,9 @@ async function build(pathArg, options) {
326
356
  publicDir: cloudwerkConfig.publicDir ?? "public",
327
357
  root: cwd,
328
358
  isProduction: true
359
+ }, {
360
+ queueManifest,
361
+ serviceManifest
329
362
  });
330
363
  const tempEntryPath = path2.join(tempDir, "_server-entry.ts");
331
364
  fs2.writeFileSync(tempEntryPath, serverEntryCode);
@@ -1206,6 +1239,17 @@ function writeWranglerToml(cwd, config) {
1206
1239
  const content = TOML.stringify(config);
1207
1240
  fs7.writeFileSync(configPath, content, "utf-8");
1208
1241
  }
1242
+ function readWranglerTomlRaw(cwd) {
1243
+ const configPath = findWranglerToml(cwd);
1244
+ if (!configPath || configPath.endsWith(".json")) {
1245
+ return "";
1246
+ }
1247
+ return fs7.readFileSync(configPath, "utf-8");
1248
+ }
1249
+ function writeWranglerTomlRaw(cwd, content) {
1250
+ const configPath = path7.join(cwd, "wrangler.toml");
1251
+ fs7.writeFileSync(configPath, content, "utf-8");
1252
+ }
1209
1253
  function getEnvConfig(config, env) {
1210
1254
  if (!env || !config.env?.[env]) {
1211
1255
  return config;
@@ -3142,6 +3186,797 @@ async function bindingsGenerateTypes(options) {
3142
3186
  }
3143
3187
  }
3144
3188
 
3189
+ // src/commands/triggers.ts
3190
+ import pc9 from "picocolors";
3191
+ import {
3192
+ loadConfig as loadConfig3,
3193
+ scanTriggers,
3194
+ buildTriggerManifest,
3195
+ getTriggerSummary
3196
+ } from "@cloudwerk/core/build";
3197
+ var SOURCE_TYPE_LABELS = {
3198
+ scheduled: { label: "cron", color: pc9.blue },
3199
+ queue: { label: "queue", color: pc9.green },
3200
+ r2: { label: "R2", color: pc9.yellow },
3201
+ webhook: { label: "webhook", color: pc9.magenta },
3202
+ email: { label: "email", color: pc9.cyan },
3203
+ d1: { label: "D1", color: pc9.red },
3204
+ tail: { label: "tail", color: pc9.gray }
3205
+ };
3206
+ function formatSourceType(type) {
3207
+ const config = SOURCE_TYPE_LABELS[type] || { label: type, color: pc9.dim };
3208
+ return config.color(config.label);
3209
+ }
3210
+ async function triggers(options = {}) {
3211
+ const verbose = options.verbose ?? false;
3212
+ const logger = createLogger(verbose);
3213
+ try {
3214
+ const cwd = process.cwd();
3215
+ logger.debug("Loading configuration...");
3216
+ const config = await loadConfig3(cwd);
3217
+ const appDir = config.appDir;
3218
+ logger.debug(`Scanning for triggers in ${appDir}/triggers/...`);
3219
+ const scanResult = await scanTriggers(appDir, {
3220
+ extensions: config.extensions
3221
+ });
3222
+ const manifest = buildTriggerManifest(scanResult, appDir);
3223
+ const summary = getTriggerSummary(manifest);
3224
+ console.log();
3225
+ console.log(pc9.bold("Cloudwerk Triggers"));
3226
+ console.log();
3227
+ console.log(pc9.dim(` Found ${summary.total} triggers:`));
3228
+ if (summary.total > 0) {
3229
+ if (summary.scheduled > 0) {
3230
+ console.log(pc9.dim(` - ${summary.scheduled} scheduled (cron)`));
3231
+ }
3232
+ if (summary.queue > 0) {
3233
+ console.log(pc9.dim(` - ${summary.queue} queue consumers`));
3234
+ }
3235
+ if (summary.r2 > 0) {
3236
+ console.log(pc9.dim(` - ${summary.r2} R2 notifications`));
3237
+ }
3238
+ if (summary.webhook > 0) {
3239
+ console.log(pc9.dim(` - ${summary.webhook} webhooks`));
3240
+ }
3241
+ if (summary.email > 0) {
3242
+ console.log(pc9.dim(` - ${summary.email} email handlers`));
3243
+ }
3244
+ }
3245
+ console.log();
3246
+ if (manifest.triggers.length > 0) {
3247
+ for (const trigger of manifest.triggers) {
3248
+ const sourceType = formatSourceType(trigger.source?.type ?? "unknown");
3249
+ const sourceInfo = getSourceInfo(trigger);
3250
+ console.log(
3251
+ ` ${pc9.cyan(trigger.name)} ${pc9.dim("(")}${sourceType}${sourceInfo ? pc9.dim(": ") + pc9.dim(sourceInfo) : ""}${pc9.dim(")")}`
3252
+ );
3253
+ }
3254
+ console.log();
3255
+ }
3256
+ if (manifest.warnings.length > 0) {
3257
+ console.log(pc9.yellow("Warnings:"));
3258
+ for (const warning of manifest.warnings) {
3259
+ console.log(pc9.dim(` - ${warning.file}: ${warning.message}`));
3260
+ }
3261
+ console.log();
3262
+ }
3263
+ console.log(pc9.bold("Commands:"));
3264
+ console.log();
3265
+ console.log(pc9.dim(" cloudwerk triggers list ") + "List all triggers with details");
3266
+ console.log(pc9.dim(" cloudwerk triggers validate ") + "Validate trigger configurations");
3267
+ console.log(pc9.dim(" cloudwerk triggers generate ") + "Regenerate wrangler config");
3268
+ console.log();
3269
+ if (manifest.triggers.length === 0) {
3270
+ console.log(pc9.bold("Quick Start:"));
3271
+ console.log();
3272
+ console.log(pc9.dim(" Create a scheduled trigger at app/triggers/daily-cleanup.ts:"));
3273
+ console.log();
3274
+ console.log(pc9.cyan(" import { defineTrigger } from '@cloudwerk/trigger'"));
3275
+ console.log();
3276
+ console.log(pc9.cyan(" export default defineTrigger({"));
3277
+ console.log(pc9.cyan(" source: { type: 'scheduled', cron: '0 0 * * *' },"));
3278
+ console.log(pc9.cyan(" async handle(event, ctx) {"));
3279
+ console.log(pc9.cyan(" console.log(`[${ctx.traceId}] Running cleanup`)"));
3280
+ console.log(pc9.cyan(" await cleanupOldRecords()"));
3281
+ console.log(pc9.cyan(" }"));
3282
+ console.log(pc9.cyan(" })"));
3283
+ console.log();
3284
+ }
3285
+ } catch (error) {
3286
+ handleCommandError(error, verbose);
3287
+ }
3288
+ }
3289
+ function getSourceInfo(trigger) {
3290
+ const source = trigger.source;
3291
+ if (!source) return "";
3292
+ const type = source.type;
3293
+ switch (type) {
3294
+ case "scheduled":
3295
+ return String(source.cron ?? "");
3296
+ case "queue":
3297
+ return String(source.queue ?? "");
3298
+ case "r2":
3299
+ return String(source.bucket ?? "");
3300
+ case "webhook":
3301
+ return String(source.path ?? "");
3302
+ case "email":
3303
+ return String(source.address ?? "");
3304
+ default:
3305
+ return "";
3306
+ }
3307
+ }
3308
+
3309
+ // src/commands/triggers/list.ts
3310
+ import pc10 from "picocolors";
3311
+ import {
3312
+ loadConfig as loadConfig4,
3313
+ scanTriggers as scanTriggers2,
3314
+ buildTriggerManifest as buildTriggerManifest2
3315
+ } from "@cloudwerk/core/build";
3316
+ var SOURCE_TYPE_COLORS = {
3317
+ scheduled: pc10.blue,
3318
+ queue: pc10.green,
3319
+ r2: pc10.yellow,
3320
+ webhook: pc10.magenta,
3321
+ email: pc10.cyan,
3322
+ d1: pc10.red,
3323
+ tail: pc10.gray
3324
+ };
3325
+ async function triggersList(options = {}) {
3326
+ const verbose = options.verbose ?? false;
3327
+ const logger = createLogger(verbose);
3328
+ try {
3329
+ const cwd = process.cwd();
3330
+ logger.debug("Loading configuration...");
3331
+ const config = await loadConfig4(cwd);
3332
+ const appDir = config.appDir;
3333
+ logger.debug(`Scanning for triggers in ${appDir}/triggers/...`);
3334
+ const scanResult = await scanTriggers2(appDir, {
3335
+ extensions: config.extensions
3336
+ });
3337
+ const manifest = buildTriggerManifest2(scanResult, appDir);
3338
+ let triggers2 = manifest.triggers;
3339
+ if (options.type) {
3340
+ triggers2 = triggers2.filter((t) => t.source?.type === options.type);
3341
+ }
3342
+ if (options.json) {
3343
+ console.log(JSON.stringify({
3344
+ triggers: triggers2.map(formatTriggerJson),
3345
+ count: triggers2.length,
3346
+ errors: manifest.errors,
3347
+ warnings: manifest.warnings
3348
+ }, null, 2));
3349
+ return;
3350
+ }
3351
+ console.log();
3352
+ console.log(pc10.bold("Triggers"));
3353
+ console.log();
3354
+ if (triggers2.length === 0) {
3355
+ if (options.type) {
3356
+ console.log(pc10.dim(` No triggers of type '${options.type}' found.`));
3357
+ } else {
3358
+ console.log(pc10.dim(" No triggers found."));
3359
+ }
3360
+ console.log();
3361
+ return;
3362
+ }
3363
+ console.log(
3364
+ ` ${pc10.dim("NAME".padEnd(25))} ${pc10.dim("TYPE".padEnd(12))} ${pc10.dim("SOURCE".padEnd(30))} ${pc10.dim("ERROR HANDLER")}`
3365
+ );
3366
+ console.log(pc10.dim(" " + "-".repeat(80)));
3367
+ for (const trigger of triggers2) {
3368
+ const color = SOURCE_TYPE_COLORS[trigger.source?.type ?? "unknown"] ?? pc10.dim;
3369
+ const sourceType = trigger.source?.type ?? "unknown";
3370
+ const sourceInfo = getSourceInfo2(trigger);
3371
+ const hasError = trigger.hasOnError ? pc10.green("yes") : pc10.dim("no");
3372
+ console.log(
3373
+ ` ${pc10.cyan(trigger.name.padEnd(25))} ${color(sourceType.padEnd(12))} ${pc10.dim(sourceInfo.padEnd(30))} ${hasError}`
3374
+ );
3375
+ }
3376
+ console.log();
3377
+ console.log(pc10.dim(` ${triggers2.length} trigger(s)`));
3378
+ console.log();
3379
+ if (manifest.errors.length > 0) {
3380
+ console.log(pc10.red("Errors:"));
3381
+ for (const error of manifest.errors) {
3382
+ console.log(pc10.dim(` - ${error.file}: ${error.message}`));
3383
+ }
3384
+ console.log();
3385
+ }
3386
+ if (manifest.warnings.length > 0) {
3387
+ console.log(pc10.yellow("Warnings:"));
3388
+ for (const warning of manifest.warnings) {
3389
+ console.log(pc10.dim(` - ${warning.file}: ${warning.message}`));
3390
+ }
3391
+ console.log();
3392
+ }
3393
+ } catch (error) {
3394
+ handleCommandError(error, verbose);
3395
+ }
3396
+ }
3397
+ function getSourceInfo2(trigger) {
3398
+ const source = trigger.source;
3399
+ if (!source) return "unknown";
3400
+ switch (source.type) {
3401
+ case "scheduled":
3402
+ return source.cron;
3403
+ case "queue":
3404
+ return source.queue;
3405
+ case "r2": {
3406
+ const r2 = source;
3407
+ return `${r2.bucket} (${r2.events.join(", ")})`;
3408
+ }
3409
+ case "webhook":
3410
+ return source.path;
3411
+ case "email":
3412
+ return source.address;
3413
+ default:
3414
+ return source.type;
3415
+ }
3416
+ }
3417
+ function formatTriggerJson(trigger) {
3418
+ return {
3419
+ name: trigger.name,
3420
+ bindingName: trigger.bindingName,
3421
+ filePath: trigger.filePath,
3422
+ sourceType: trigger.source?.type,
3423
+ source: trigger.source,
3424
+ hasOnError: trigger.hasOnError,
3425
+ retry: trigger.retry,
3426
+ timeout: trigger.timeout,
3427
+ fanOutGroup: trigger.fanOutGroup
3428
+ };
3429
+ }
3430
+
3431
+ // src/commands/triggers/validate.ts
3432
+ import pc11 from "picocolors";
3433
+ import {
3434
+ loadConfig as loadConfig5,
3435
+ scanTriggers as scanTriggers3,
3436
+ buildTriggerManifest as buildTriggerManifest3,
3437
+ hasTriggerErrors,
3438
+ hasTriggerWarnings
3439
+ } from "@cloudwerk/core/build";
3440
+ async function triggersValidate(options = {}) {
3441
+ const verbose = options.verbose ?? false;
3442
+ const strict = options.strict ?? false;
3443
+ const logger = createLogger(verbose);
3444
+ try {
3445
+ const cwd = process.cwd();
3446
+ logger.debug("Loading configuration...");
3447
+ const config = await loadConfig5(cwd);
3448
+ const appDir = config.appDir;
3449
+ logger.debug(`Scanning for triggers in ${appDir}/triggers/...`);
3450
+ const scanResult = await scanTriggers3(appDir, {
3451
+ extensions: config.extensions
3452
+ });
3453
+ const manifest = buildTriggerManifest3(scanResult, appDir);
3454
+ const hasErrors = hasTriggerErrors(manifest);
3455
+ const hasWarnings = hasTriggerWarnings(manifest);
3456
+ if (options.json) {
3457
+ console.log(JSON.stringify({
3458
+ valid: !hasErrors && (!strict || !hasWarnings),
3459
+ triggerCount: manifest.triggers.length,
3460
+ errors: manifest.errors,
3461
+ warnings: manifest.warnings
3462
+ }, null, 2));
3463
+ if (hasErrors || strict && hasWarnings) {
3464
+ process.exit(1);
3465
+ }
3466
+ return;
3467
+ }
3468
+ console.log();
3469
+ console.log(pc11.bold("Validating Triggers"));
3470
+ console.log();
3471
+ if (manifest.triggers.length === 0) {
3472
+ console.log(pc11.dim(" No triggers found to validate."));
3473
+ console.log();
3474
+ return;
3475
+ }
3476
+ for (const trigger of manifest.triggers) {
3477
+ const triggerErrors = manifest.errors.filter(
3478
+ (e) => e.file === trigger.filePath
3479
+ );
3480
+ const triggerWarnings = manifest.warnings.filter(
3481
+ (w) => w.file === trigger.filePath
3482
+ );
3483
+ if (triggerErrors.length > 0) {
3484
+ console.log(` ${pc11.red("\u2717")} ${pc11.cyan(trigger.name)} ${pc11.dim(`(${trigger.filePath})`)}`);
3485
+ for (const error of triggerErrors) {
3486
+ console.log(` ${pc11.red("error:")} ${error.message}`);
3487
+ }
3488
+ } else if (triggerWarnings.length > 0) {
3489
+ console.log(` ${pc11.yellow("!")} ${pc11.cyan(trigger.name)} ${pc11.dim(`(${trigger.filePath})`)}`);
3490
+ for (const warning of triggerWarnings) {
3491
+ console.log(` ${pc11.yellow("warning:")} ${warning.message}`);
3492
+ }
3493
+ } else {
3494
+ console.log(` ${pc11.green("\u2713")} ${pc11.cyan(trigger.name)} ${pc11.dim("- valid")}`);
3495
+ }
3496
+ }
3497
+ console.log();
3498
+ const errorCount = manifest.errors.length;
3499
+ const warningCount = manifest.warnings.length;
3500
+ if (hasErrors) {
3501
+ console.log(pc11.red(` ${errorCount} error(s), ${warningCount} warning(s)`));
3502
+ console.log();
3503
+ process.exit(1);
3504
+ } else if (hasWarnings) {
3505
+ console.log(pc11.yellow(` ${warningCount} warning(s)`));
3506
+ console.log();
3507
+ if (strict) {
3508
+ process.exit(1);
3509
+ }
3510
+ } else {
3511
+ console.log(pc11.green(` All ${manifest.triggers.length} trigger(s) valid`));
3512
+ console.log();
3513
+ }
3514
+ } catch (error) {
3515
+ handleCommandError(error, verbose);
3516
+ }
3517
+ }
3518
+
3519
+ // src/commands/triggers/generate.ts
3520
+ import pc12 from "picocolors";
3521
+ import {
3522
+ loadConfig as loadConfig6,
3523
+ scanTriggers as scanTriggers4,
3524
+ buildTriggerManifest as buildTriggerManifest4
3525
+ } from "@cloudwerk/core/build";
3526
+
3527
+ // src/utils/trigger-wrangler.ts
3528
+ import * as fs11 from "fs";
3529
+ import * as path12 from "path";
3530
+ function generateCronToml(manifest, includeComments) {
3531
+ const cronTriggers = manifest.triggers.filter(
3532
+ (t) => t.source?.type === "scheduled"
3533
+ );
3534
+ if (cronTriggers.length === 0) {
3535
+ return "";
3536
+ }
3537
+ const lines = [];
3538
+ if (includeComments) {
3539
+ lines.push("# Scheduled (Cron) Triggers");
3540
+ }
3541
+ lines.push("[triggers]");
3542
+ const cronExpressions = /* @__PURE__ */ new Set();
3543
+ for (const trigger of cronTriggers) {
3544
+ const source = trigger.source;
3545
+ cronExpressions.add(source.cron);
3546
+ }
3547
+ const crons = Array.from(cronExpressions).map((cron) => `"${cron}"`).join(", ");
3548
+ lines.push(`crons = [${crons}]`);
3549
+ return lines.join("\n");
3550
+ }
3551
+ function generateQueueConsumerToml(manifest, includeComments) {
3552
+ const queueTriggers = manifest.triggers.filter(
3553
+ (t) => t.source?.type === "queue"
3554
+ );
3555
+ if (queueTriggers.length === 0) {
3556
+ return "";
3557
+ }
3558
+ const lines = [];
3559
+ for (const trigger of queueTriggers) {
3560
+ const source = trigger.source;
3561
+ if (includeComments) {
3562
+ lines.push(`# Queue trigger '${trigger.name}' (from app/triggers/${trigger.filePath})`);
3563
+ }
3564
+ lines.push("[[queues.consumers]]");
3565
+ lines.push(`queue = "${source.queue}"`);
3566
+ if (source.batch?.size !== void 0 && source.batch.size !== 10) {
3567
+ lines.push(`max_batch_size = ${source.batch.size}`);
3568
+ }
3569
+ if (source.batch?.timeout !== void 0) {
3570
+ const timeoutSeconds = parseDurationToSeconds(source.batch.timeout);
3571
+ if (timeoutSeconds !== 5) {
3572
+ lines.push(`max_batch_timeout = ${timeoutSeconds}`);
3573
+ }
3574
+ }
3575
+ lines.push("");
3576
+ }
3577
+ return lines.join("\n").trim();
3578
+ }
3579
+ function generateR2NotificationToml(manifest, includeComments) {
3580
+ const r2Triggers = manifest.triggers.filter(
3581
+ (t) => t.source?.type === "r2"
3582
+ );
3583
+ if (r2Triggers.length === 0) {
3584
+ return "";
3585
+ }
3586
+ const lines = [];
3587
+ const bucketTriggers = /* @__PURE__ */ new Map();
3588
+ for (const trigger of r2Triggers) {
3589
+ const source = trigger.source;
3590
+ const existing = bucketTriggers.get(source.bucket) || [];
3591
+ existing.push(trigger);
3592
+ bucketTriggers.set(source.bucket, existing);
3593
+ }
3594
+ for (const [bucket, triggers2] of bucketTriggers) {
3595
+ if (includeComments) {
3596
+ const triggerNames = triggers2.map((t) => t.name).join(", ");
3597
+ lines.push(`# R2 bucket notifications for '${bucket}' (triggers: ${triggerNames})`);
3598
+ }
3599
+ const eventTypes = /* @__PURE__ */ new Set();
3600
+ let prefix;
3601
+ let suffix;
3602
+ for (const trigger of triggers2) {
3603
+ const source = trigger.source;
3604
+ for (const event of source.events) {
3605
+ eventTypes.add(event);
3606
+ }
3607
+ if (!prefix && source.prefix) prefix = source.prefix;
3608
+ if (!suffix && source.suffix) suffix = source.suffix;
3609
+ }
3610
+ lines.push("[[r2_buckets]]");
3611
+ lines.push(`binding = "${bucket.toUpperCase()}_BUCKET"`);
3612
+ lines.push(`bucket_name = "${bucket}"`);
3613
+ lines.push("");
3614
+ lines.push("[[r2_buckets.event_notifications.rules]]");
3615
+ if (prefix) {
3616
+ lines.push(`prefix = "${prefix}"`);
3617
+ }
3618
+ if (suffix) {
3619
+ lines.push(`suffix = "${suffix}"`);
3620
+ }
3621
+ const actions = [];
3622
+ if (eventTypes.has("object-create")) {
3623
+ actions.push('"PutObject"', '"CopyObject"', '"CompleteMultipartUpload"');
3624
+ }
3625
+ if (eventTypes.has("object-delete")) {
3626
+ actions.push('"DeleteObject"');
3627
+ }
3628
+ if (actions.length > 0) {
3629
+ lines.push(`actions = [${actions.join(", ")}]`);
3630
+ }
3631
+ lines.push("");
3632
+ }
3633
+ return lines.join("\n").trim();
3634
+ }
3635
+ function parseDurationToSeconds(duration) {
3636
+ if (typeof duration === "number") {
3637
+ return duration;
3638
+ }
3639
+ const match = duration.match(/^(\d+)(s|m|h)$/);
3640
+ if (!match) {
3641
+ return 5;
3642
+ }
3643
+ const value = parseInt(match[1], 10);
3644
+ const unit = match[2];
3645
+ switch (unit) {
3646
+ case "s":
3647
+ return value;
3648
+ case "m":
3649
+ return value * 60;
3650
+ case "h":
3651
+ return value * 3600;
3652
+ default:
3653
+ return 5;
3654
+ }
3655
+ }
3656
+ function generateTriggerToml(manifest, includeComments = true) {
3657
+ if (manifest.triggers.length === 0) {
3658
+ return "";
3659
+ }
3660
+ const lines = [];
3661
+ if (includeComments) {
3662
+ lines.push("# ============================================================================");
3663
+ lines.push("# Cloudwerk Triggers - Auto-generated from app/triggers/");
3664
+ lines.push("# ============================================================================");
3665
+ lines.push("");
3666
+ }
3667
+ const cronToml = generateCronToml(manifest, includeComments);
3668
+ if (cronToml) {
3669
+ lines.push(cronToml);
3670
+ lines.push("");
3671
+ }
3672
+ const queueToml = generateQueueConsumerToml(manifest, includeComments);
3673
+ if (queueToml) {
3674
+ lines.push(queueToml);
3675
+ lines.push("");
3676
+ }
3677
+ const r2Toml = generateR2NotificationToml(manifest, includeComments);
3678
+ if (r2Toml) {
3679
+ lines.push(r2Toml);
3680
+ lines.push("");
3681
+ }
3682
+ return lines.join("\n").trim();
3683
+ }
3684
+ var TRIGGER_SECTION_START = "# ============================================================================";
3685
+ var TRIGGER_SECTION_MARKER = "# Cloudwerk Triggers - Auto-generated";
3686
+ function hasTriggerSection(content) {
3687
+ return content.includes(TRIGGER_SECTION_MARKER);
3688
+ }
3689
+ function removeTriggerSection(content) {
3690
+ const startIndex = content.indexOf(TRIGGER_SECTION_START);
3691
+ if (startIndex === -1 || !content.includes(TRIGGER_SECTION_MARKER)) {
3692
+ return content;
3693
+ }
3694
+ const lines = content.split("\n");
3695
+ let sectionStartLine = -1;
3696
+ let sectionEndLine = lines.length;
3697
+ for (let i = 0; i < lines.length; i++) {
3698
+ if (lines[i].includes(TRIGGER_SECTION_MARKER)) {
3699
+ sectionStartLine = i > 0 && lines[i - 1].includes("===") ? i - 1 : i;
3700
+ break;
3701
+ }
3702
+ }
3703
+ if (sectionStartLine === -1) {
3704
+ return content;
3705
+ }
3706
+ for (let i = sectionStartLine + 2; i < lines.length; i++) {
3707
+ const line = lines[i].trim();
3708
+ if (line === "" || line.startsWith("#")) {
3709
+ continue;
3710
+ }
3711
+ if (line.startsWith("[[") || line.startsWith("[")) {
3712
+ if (line.includes("triggers") || line.includes("queues.consumers") || line.includes("r2_buckets")) {
3713
+ continue;
3714
+ }
3715
+ sectionEndLine = i;
3716
+ break;
3717
+ }
3718
+ }
3719
+ const before = lines.slice(0, sectionStartLine);
3720
+ const after = lines.slice(sectionEndLine);
3721
+ while (before.length > 0 && before[before.length - 1].trim() === "") {
3722
+ before.pop();
3723
+ }
3724
+ return [...before, "", ...after].join("\n");
3725
+ }
3726
+ function generateTriggerWrangler(cwd, manifest, options = {}) {
3727
+ const { dryRun = false, includeComments = true } = options;
3728
+ const wranglerPath = findWranglerToml(cwd) || path12.join(cwd, "wrangler.toml");
3729
+ const generatedToml = generateTriggerToml(manifest, includeComments);
3730
+ const result = {
3731
+ wranglerPath,
3732
+ changed: false,
3733
+ triggers: manifest.triggers.map((t) => ({
3734
+ name: t.name,
3735
+ sourceType: t.source?.type ?? "unknown",
3736
+ bindingName: t.bindingName
3737
+ })),
3738
+ generatedToml
3739
+ };
3740
+ if (manifest.triggers.length === 0) {
3741
+ return result;
3742
+ }
3743
+ if (dryRun) {
3744
+ result.changed = true;
3745
+ return result;
3746
+ }
3747
+ let content = "";
3748
+ if (fs11.existsSync(wranglerPath)) {
3749
+ content = readWranglerTomlRaw(cwd);
3750
+ }
3751
+ if (hasTriggerSection(content)) {
3752
+ content = removeTriggerSection(content);
3753
+ }
3754
+ const newContent = content.trim() + "\n\n" + generatedToml + "\n";
3755
+ writeWranglerTomlRaw(cwd, newContent);
3756
+ result.changed = true;
3757
+ return result;
3758
+ }
3759
+
3760
+ // src/utils/trigger-type-generator.ts
3761
+ import * as fs12 from "fs";
3762
+ import * as path13 from "path";
3763
+ var CLOUDWERK_TYPES_DIR2 = ".cloudwerk/types";
3764
+ var TRIGGERS_DTS = "triggers.d.ts";
3765
+ function generateTriggerTypes(cwd, manifest, options = {}) {
3766
+ const includeTimestamp = options.includeTimestamp ?? true;
3767
+ const typesDir = path13.join(cwd, CLOUDWERK_TYPES_DIR2);
3768
+ fs12.mkdirSync(typesDir, { recursive: true });
3769
+ const triggersPath = path13.join(typesDir, TRIGGERS_DTS);
3770
+ const triggersContent = generateTriggersDts(manifest.triggers, includeTimestamp);
3771
+ fs12.writeFileSync(triggersPath, triggersContent, "utf-8");
3772
+ const triggerInfo = manifest.triggers.map((t) => ({
3773
+ name: t.name,
3774
+ bindingName: t.bindingName,
3775
+ sourceType: t.source?.type ?? "unknown"
3776
+ }));
3777
+ return {
3778
+ typesDir,
3779
+ file: triggersPath,
3780
+ triggerCount: manifest.triggers.length,
3781
+ triggers: triggerInfo
3782
+ };
3783
+ }
3784
+ function generateTriggersDts(triggers2, includeTimestamp) {
3785
+ const lines = [];
3786
+ lines.push("// Auto-generated by cloudwerk triggers - DO NOT EDIT");
3787
+ if (includeTimestamp) {
3788
+ lines.push(`// Last updated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
3789
+ }
3790
+ lines.push("//");
3791
+ lines.push("// This file provides type information for triggers");
3792
+ lines.push('// Add ".cloudwerk/types" to your tsconfig.json include array');
3793
+ lines.push("");
3794
+ lines.push("import type { EmitOptions, EmitResult } from '@cloudwerk/trigger'");
3795
+ lines.push("");
3796
+ const scheduled = triggers2.filter((t) => t.source?.type === "scheduled");
3797
+ const queue = triggers2.filter((t) => t.source?.type === "queue");
3798
+ const r2 = triggers2.filter((t) => t.source?.type === "r2");
3799
+ const webhook = triggers2.filter((t) => t.source?.type === "webhook");
3800
+ const email = triggers2.filter((t) => t.source?.type === "email");
3801
+ lines.push("// ============================================================================");
3802
+ lines.push("// Trigger Names");
3803
+ lines.push("// ============================================================================");
3804
+ lines.push("");
3805
+ const allNames = triggers2.map((t) => `'${t.name}'`);
3806
+ if (allNames.length > 0) {
3807
+ lines.push(`type TriggerName = ${allNames.join(" | ")}`);
3808
+ } else {
3809
+ lines.push("type TriggerName = never");
3810
+ }
3811
+ lines.push("");
3812
+ if (scheduled.length > 0) {
3813
+ const names = scheduled.map((t) => `'${t.name}'`);
3814
+ lines.push(`type ScheduledTriggerName = ${names.join(" | ")}`);
3815
+ }
3816
+ if (queue.length > 0) {
3817
+ const names = queue.map((t) => `'${t.name}'`);
3818
+ lines.push(`type QueueTriggerName = ${names.join(" | ")}`);
3819
+ }
3820
+ if (r2.length > 0) {
3821
+ const names = r2.map((t) => `'${t.name}'`);
3822
+ lines.push(`type R2TriggerName = ${names.join(" | ")}`);
3823
+ }
3824
+ if (webhook.length > 0) {
3825
+ const names = webhook.map((t) => `'${t.name}'`);
3826
+ lines.push(`type WebhookTriggerName = ${names.join(" | ")}`);
3827
+ }
3828
+ if (email.length > 0) {
3829
+ const names = email.map((t) => `'${t.name}'`);
3830
+ lines.push(`type EmailTriggerName = ${names.join(" | ")}`);
3831
+ }
3832
+ lines.push("");
3833
+ lines.push("// ============================================================================");
3834
+ lines.push("// Trigger Registry");
3835
+ lines.push("// ============================================================================");
3836
+ lines.push("");
3837
+ lines.push("interface TriggerRegistry {");
3838
+ for (const trigger of triggers2) {
3839
+ const sourceType = trigger.source?.type ?? "unknown";
3840
+ lines.push(` /** ${sourceType} trigger: ${trigger.name} */`);
3841
+ lines.push(` '${trigger.name}': {`);
3842
+ lines.push(` name: '${trigger.name}'`);
3843
+ lines.push(` bindingName: '${trigger.bindingName}'`);
3844
+ lines.push(` sourceType: '${sourceType}'`);
3845
+ lines.push(" }");
3846
+ }
3847
+ lines.push("}");
3848
+ lines.push("");
3849
+ lines.push("// ============================================================================");
3850
+ lines.push("// Module Augmentation");
3851
+ lines.push("// ============================================================================");
3852
+ lines.push("");
3853
+ lines.push("declare module '@cloudwerk/trigger' {");
3854
+ lines.push(" /**");
3855
+ lines.push(" * Type-safe emit to other triggers.");
3856
+ lines.push(" * Only triggers defined in app/triggers/ are valid targets.");
3857
+ lines.push(" */");
3858
+ lines.push(" export function emit<T extends TriggerName>(");
3859
+ lines.push(" trigger: T,");
3860
+ lines.push(" payload: unknown,");
3861
+ lines.push(" options?: EmitOptions");
3862
+ lines.push(" ): Promise<EmitResult>");
3863
+ lines.push("");
3864
+ lines.push(" /** Get list of all trigger names */");
3865
+ lines.push(" export function getTriggerNames(): TriggerName[]");
3866
+ lines.push("");
3867
+ lines.push(" /** Check if a trigger exists */");
3868
+ lines.push(" export function triggerExists(name: string): name is TriggerName");
3869
+ lines.push("}");
3870
+ lines.push("");
3871
+ return lines.join("\n");
3872
+ }
3873
+
3874
+ // src/commands/triggers/generate.ts
3875
+ async function triggersGenerate(options = {}) {
3876
+ const verbose = options.verbose ?? false;
3877
+ const dryRun = options.dryRun ?? false;
3878
+ const logger = createLogger(verbose);
3879
+ const generateWrangler = options.wrangler ?? !options.types;
3880
+ const generateTypes = options.types ?? !options.wrangler;
3881
+ try {
3882
+ const cwd = process.cwd();
3883
+ logger.debug("Loading configuration...");
3884
+ const config = await loadConfig6(cwd);
3885
+ const appDir = config.appDir;
3886
+ logger.debug(`Scanning for triggers in ${appDir}/triggers/...`);
3887
+ const scanResult = await scanTriggers4(appDir, {
3888
+ extensions: config.extensions
3889
+ });
3890
+ const manifest = buildTriggerManifest4(scanResult, appDir);
3891
+ if (options.json) {
3892
+ const result = {
3893
+ triggerCount: manifest.triggers.length,
3894
+ dryRun
3895
+ };
3896
+ if (generateWrangler) {
3897
+ const wranglerResult = generateTriggerWrangler(cwd, manifest, { dryRun });
3898
+ result.wrangler = {
3899
+ path: wranglerResult.wranglerPath,
3900
+ changed: wranglerResult.changed,
3901
+ triggers: wranglerResult.triggers,
3902
+ generatedToml: dryRun ? wranglerResult.generatedToml : void 0
3903
+ };
3904
+ }
3905
+ if (generateTypes) {
3906
+ const typesResult = generateTriggerTypes(cwd, manifest, { includeTimestamp: !dryRun });
3907
+ result.types = {
3908
+ path: typesResult.file,
3909
+ triggerCount: typesResult.triggerCount,
3910
+ triggers: typesResult.triggers
3911
+ };
3912
+ }
3913
+ console.log(JSON.stringify(result, null, 2));
3914
+ return;
3915
+ }
3916
+ console.log();
3917
+ console.log(pc12.bold("Generating Trigger Configuration"));
3918
+ console.log();
3919
+ if (manifest.triggers.length === 0) {
3920
+ console.log(pc12.dim(" No triggers found. Nothing to generate."));
3921
+ console.log();
3922
+ return;
3923
+ }
3924
+ console.log(pc12.dim(` Found ${manifest.triggers.length} trigger(s)`));
3925
+ console.log();
3926
+ if (generateWrangler) {
3927
+ const wranglerResult = generateTriggerWrangler(cwd, manifest, {
3928
+ dryRun,
3929
+ includeComments: true
3930
+ });
3931
+ if (dryRun) {
3932
+ console.log(pc12.cyan(" wrangler.toml (dry run):"));
3933
+ console.log();
3934
+ const lines = wranglerResult.generatedToml.split("\n");
3935
+ for (const line of lines) {
3936
+ console.log(` ${line}`);
3937
+ }
3938
+ console.log();
3939
+ } else {
3940
+ console.log(` ${pc12.green("\u2713")} Updated ${pc12.dim(wranglerResult.wranglerPath)}`);
3941
+ for (const trigger of wranglerResult.triggers) {
3942
+ console.log(pc12.dim(` - ${trigger.name} (${trigger.sourceType})`));
3943
+ }
3944
+ }
3945
+ }
3946
+ if (generateTypes) {
3947
+ if (!dryRun) {
3948
+ const typesResult = generateTriggerTypes(cwd, manifest, { includeTimestamp: true });
3949
+ console.log(` ${pc12.green("\u2713")} Generated ${pc12.dim(typesResult.file)}`);
3950
+ console.log(pc12.dim(` - ${typesResult.triggerCount} trigger type(s)`));
3951
+ } else {
3952
+ console.log(pc12.cyan(" TypeScript types (dry run):"));
3953
+ console.log(pc12.dim(` Would generate .cloudwerk/types/triggers.d.ts`));
3954
+ }
3955
+ }
3956
+ console.log();
3957
+ if (!dryRun && generateTypes) {
3958
+ console.log(pc12.dim(' Tip: Add ".cloudwerk/types" to your tsconfig.json include array'));
3959
+ console.log();
3960
+ }
3961
+ if (manifest.errors.length > 0) {
3962
+ console.log(pc12.red("Errors:"));
3963
+ for (const error of manifest.errors) {
3964
+ console.log(pc12.dim(` - ${error.file}: ${error.message}`));
3965
+ }
3966
+ console.log();
3967
+ }
3968
+ if (manifest.warnings.length > 0) {
3969
+ console.log(pc12.yellow("Warnings:"));
3970
+ for (const warning of manifest.warnings) {
3971
+ console.log(pc12.dim(` - ${warning.file}: ${warning.message}`));
3972
+ }
3973
+ console.log();
3974
+ }
3975
+ } catch (error) {
3976
+ handleCommandError(error, verbose);
3977
+ }
3978
+ }
3979
+
3145
3980
  // src/index.ts
3146
3981
  program.name("cloudwerk").description("Cloudwerk CLI - Build and deploy full-stack apps to Cloudflare").version(VERSION).enablePositionalOptions();
3147
3982
  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 +3990,8 @@ bindingsCmd.command("add [type]").description("Add a new binding (d1, kv, r2, qu
3155
3990
  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
3991
  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
3992
  bindingsCmd.command("generate-types").description("Regenerate TypeScript type definitions").option("--verbose", "Enable verbose logging").action(bindingsGenerateTypes);
3993
+ var triggersCmd = program.command("triggers").description("Manage Cloudwerk triggers (scheduled, queue, R2, webhook, etc.)").enablePositionalOptions().passThroughOptions().option("--verbose", "Enable verbose logging").action(triggers);
3994
+ 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);
3995
+ 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);
3996
+ 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);
3158
3997
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudwerk/cli",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Dev server, build, deploy commands for Cloudwerk",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,9 +30,9 @@
30
30
  "esbuild": "^0.25.0",
31
31
  "picocolors": "^1.1.0",
32
32
  "vite": "^6.0.0",
33
- "@cloudwerk/ui": "^0.12.0",
34
- "@cloudwerk/vite-plugin": "^0.5.0",
35
- "@cloudwerk/core": "^0.12.0"
33
+ "@cloudwerk/core": "^0.13.0",
34
+ "@cloudwerk/ui": "^0.13.0",
35
+ "@cloudwerk/vite-plugin": "^0.6.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/node": "^20.0.0",