@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 +2667 -11
- package/dist/ssg-DR2ZOVQ7.js +512 -0
- package/package.json +5 -4
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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();
|