@cloudwerk/cli 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1835 -18
- 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,14 +271,15 @@ 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,
|
|
282
|
+
loadConfig as loadConfig2,
|
|
268
283
|
resolveRoutesPath,
|
|
269
284
|
scanQueues,
|
|
270
285
|
buildQueueManifest,
|
|
@@ -304,7 +319,7 @@ async function build(pathArg, options) {
|
|
|
304
319
|
if (!fs2.existsSync(tempDir)) {
|
|
305
320
|
fs2.mkdirSync(tempDir, { recursive: true });
|
|
306
321
|
}
|
|
307
|
-
const cloudwerkConfig = await
|
|
322
|
+
const cloudwerkConfig = await loadConfig2(cwd);
|
|
308
323
|
const appDir = cloudwerkConfig.appDir;
|
|
309
324
|
const routesDir = cloudwerkConfig.routesDir ?? "routes";
|
|
310
325
|
const routesPath = resolveRoutesPath(routesDir, appDir, cwd);
|
|
@@ -364,7 +379,7 @@ async function build(pathArg, options) {
|
|
|
364
379
|
fs2.writeFileSync(tempEntryPath, serverEntryCode);
|
|
365
380
|
logger.debug(`Generated temp entry: ${tempEntryPath}`);
|
|
366
381
|
logger.debug(`Building client assets...`);
|
|
367
|
-
const
|
|
382
|
+
const baseClientConfig = {
|
|
368
383
|
root: cwd,
|
|
369
384
|
mode: "production",
|
|
370
385
|
logLevel: verbose ? "info" : "warn",
|
|
@@ -386,6 +401,15 @@ async function build(pathArg, options) {
|
|
|
386
401
|
}
|
|
387
402
|
}
|
|
388
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
|
+
}
|
|
389
413
|
try {
|
|
390
414
|
await viteBuild(clientConfig);
|
|
391
415
|
logger.debug(`Client assets built successfully`);
|
|
@@ -395,7 +419,7 @@ async function build(pathArg, options) {
|
|
|
395
419
|
}
|
|
396
420
|
}
|
|
397
421
|
logger.debug(`Building server bundle...`);
|
|
398
|
-
const
|
|
422
|
+
const baseServerConfig = {
|
|
399
423
|
root: cwd,
|
|
400
424
|
mode: "production",
|
|
401
425
|
logLevel: verbose ? "info" : "warn",
|
|
@@ -430,8 +454,31 @@ async function build(pathArg, options) {
|
|
|
430
454
|
conditions: ["workerd", "worker", "browser", "import", "module", "default"]
|
|
431
455
|
}
|
|
432
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
|
+
}
|
|
433
466
|
await viteBuild(serverConfig);
|
|
434
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
|
+
}
|
|
435
482
|
const buildDuration = Date.now() - startTime;
|
|
436
483
|
const serverBundlePath = path2.join(outputDir, "index.js");
|
|
437
484
|
const serverSize = fs2.existsSync(serverBundlePath) ? fs2.statSync(serverBundlePath).size : 0;
|
|
@@ -448,7 +495,7 @@ async function build(pathArg, options) {
|
|
|
448
495
|
}
|
|
449
496
|
}
|
|
450
497
|
console.log();
|
|
451
|
-
printBuildSummary(serverSize, clientSize, buildDuration, logger);
|
|
498
|
+
printBuildSummary(serverSize, clientSize, ssgPaths.length, buildDuration, logger);
|
|
452
499
|
console.log();
|
|
453
500
|
logger.success(`Build completed in ${buildDuration}ms`);
|
|
454
501
|
} catch (error) {
|
|
@@ -477,7 +524,7 @@ async function build(pathArg, options) {
|
|
|
477
524
|
}
|
|
478
525
|
}
|
|
479
526
|
}
|
|
480
|
-
function printBuildSummary(serverSize, clientSize, buildDuration, logger) {
|
|
527
|
+
function printBuildSummary(serverSize, clientSize, ssgPageCount, buildDuration, logger) {
|
|
481
528
|
logger.log("Build Output:");
|
|
482
529
|
logger.log("");
|
|
483
530
|
logger.log(" Server:");
|
|
@@ -488,6 +535,11 @@ function printBuildSummary(serverSize, clientSize, buildDuration, logger) {
|
|
|
488
535
|
logger.log(` Total: ${formatSize(clientSize)}`);
|
|
489
536
|
logger.log("");
|
|
490
537
|
}
|
|
538
|
+
if (ssgPageCount > 0) {
|
|
539
|
+
logger.log(" Static Pages:");
|
|
540
|
+
logger.log(` Generated: ${ssgPageCount}`);
|
|
541
|
+
logger.log("");
|
|
542
|
+
}
|
|
491
543
|
const totalSize = serverSize + clientSize;
|
|
492
544
|
logger.log(` Total: ${formatSize(totalSize)}`);
|
|
493
545
|
logger.log(` Duration: ${buildDuration}ms`);
|
|
@@ -501,6 +553,76 @@ function formatSize(bytes) {
|
|
|
501
553
|
}
|
|
502
554
|
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
503
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
|
+
}
|
|
504
626
|
|
|
505
627
|
// src/commands/deploy.ts
|
|
506
628
|
import * as path3 from "path";
|
|
@@ -598,7 +720,7 @@ import pc2 from "picocolors";
|
|
|
598
720
|
// src/utils/configWriter.ts
|
|
599
721
|
import * as fs4 from "fs";
|
|
600
722
|
import * as path4 from "path";
|
|
601
|
-
import { loadConfig as
|
|
723
|
+
import { loadConfig as loadConfig3 } from "@cloudwerk/core/build";
|
|
602
724
|
var CONFIG_FILE_NAMES = [
|
|
603
725
|
"cloudwerk.config.ts",
|
|
604
726
|
"cloudwerk.config.js",
|
|
@@ -3189,7 +3311,7 @@ async function bindingsGenerateTypes(options) {
|
|
|
3189
3311
|
// src/commands/triggers.ts
|
|
3190
3312
|
import pc9 from "picocolors";
|
|
3191
3313
|
import {
|
|
3192
|
-
loadConfig as
|
|
3314
|
+
loadConfig as loadConfig4,
|
|
3193
3315
|
scanTriggers,
|
|
3194
3316
|
buildTriggerManifest,
|
|
3195
3317
|
getTriggerSummary
|
|
@@ -3213,7 +3335,7 @@ async function triggers(options = {}) {
|
|
|
3213
3335
|
try {
|
|
3214
3336
|
const cwd = process.cwd();
|
|
3215
3337
|
logger.debug("Loading configuration...");
|
|
3216
|
-
const config = await
|
|
3338
|
+
const config = await loadConfig4(cwd);
|
|
3217
3339
|
const appDir = config.appDir;
|
|
3218
3340
|
logger.debug(`Scanning for triggers in ${appDir}/triggers/...`);
|
|
3219
3341
|
const scanResult = await scanTriggers(appDir, {
|
|
@@ -3309,7 +3431,7 @@ function getSourceInfo(trigger) {
|
|
|
3309
3431
|
// src/commands/triggers/list.ts
|
|
3310
3432
|
import pc10 from "picocolors";
|
|
3311
3433
|
import {
|
|
3312
|
-
loadConfig as
|
|
3434
|
+
loadConfig as loadConfig5,
|
|
3313
3435
|
scanTriggers as scanTriggers2,
|
|
3314
3436
|
buildTriggerManifest as buildTriggerManifest2
|
|
3315
3437
|
} from "@cloudwerk/core/build";
|
|
@@ -3328,7 +3450,7 @@ async function triggersList(options = {}) {
|
|
|
3328
3450
|
try {
|
|
3329
3451
|
const cwd = process.cwd();
|
|
3330
3452
|
logger.debug("Loading configuration...");
|
|
3331
|
-
const config = await
|
|
3453
|
+
const config = await loadConfig5(cwd);
|
|
3332
3454
|
const appDir = config.appDir;
|
|
3333
3455
|
logger.debug(`Scanning for triggers in ${appDir}/triggers/...`);
|
|
3334
3456
|
const scanResult = await scanTriggers2(appDir, {
|
|
@@ -3431,7 +3553,7 @@ function formatTriggerJson(trigger) {
|
|
|
3431
3553
|
// src/commands/triggers/validate.ts
|
|
3432
3554
|
import pc11 from "picocolors";
|
|
3433
3555
|
import {
|
|
3434
|
-
loadConfig as
|
|
3556
|
+
loadConfig as loadConfig6,
|
|
3435
3557
|
scanTriggers as scanTriggers3,
|
|
3436
3558
|
buildTriggerManifest as buildTriggerManifest3,
|
|
3437
3559
|
hasTriggerErrors,
|
|
@@ -3444,7 +3566,7 @@ async function triggersValidate(options = {}) {
|
|
|
3444
3566
|
try {
|
|
3445
3567
|
const cwd = process.cwd();
|
|
3446
3568
|
logger.debug("Loading configuration...");
|
|
3447
|
-
const config = await
|
|
3569
|
+
const config = await loadConfig6(cwd);
|
|
3448
3570
|
const appDir = config.appDir;
|
|
3449
3571
|
logger.debug(`Scanning for triggers in ${appDir}/triggers/...`);
|
|
3450
3572
|
const scanResult = await scanTriggers3(appDir, {
|
|
@@ -3519,7 +3641,7 @@ async function triggersValidate(options = {}) {
|
|
|
3519
3641
|
// src/commands/triggers/generate.ts
|
|
3520
3642
|
import pc12 from "picocolors";
|
|
3521
3643
|
import {
|
|
3522
|
-
loadConfig as
|
|
3644
|
+
loadConfig as loadConfig7,
|
|
3523
3645
|
scanTriggers as scanTriggers4,
|
|
3524
3646
|
buildTriggerManifest as buildTriggerManifest4
|
|
3525
3647
|
} from "@cloudwerk/core/build";
|
|
@@ -3881,7 +4003,7 @@ async function triggersGenerate(options = {}) {
|
|
|
3881
4003
|
try {
|
|
3882
4004
|
const cwd = process.cwd();
|
|
3883
4005
|
logger.debug("Loading configuration...");
|
|
3884
|
-
const config = await
|
|
4006
|
+
const config = await loadConfig7(cwd);
|
|
3885
4007
|
const appDir = config.appDir;
|
|
3886
4008
|
logger.debug(`Scanning for triggers in ${appDir}/triggers/...`);
|
|
3887
4009
|
const scanResult = await scanTriggers4(appDir, {
|
|
@@ -3977,6 +4099,1689 @@ async function triggersGenerate(options = {}) {
|
|
|
3977
4099
|
}
|
|
3978
4100
|
}
|
|
3979
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
|
+
|
|
3980
5785
|
// src/index.ts
|
|
3981
5786
|
program.name("cloudwerk").description("Cloudwerk CLI - Build and deploy full-stack apps to Cloudflare").version(VERSION).enablePositionalOptions();
|
|
3982
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);
|
|
@@ -3994,4 +5799,16 @@ var triggersCmd = program.command("triggers").description("Manage Cloudwerk trig
|
|
|
3994
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);
|
|
3995
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);
|
|
3996
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);
|
|
3997
5814
|
program.parse();
|