@velajs/cli 1.22.1 → 1.24.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/README.md +98 -12
- package/dist/config.d.ts +18 -9
- package/dist/config.js +48 -17
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +37 -8
- package/dist/index.js +596 -71
- package/dist/index.js.map +1 -1
- package/package.json +15 -12
- package/templates/worker/.swcrc +10 -0
- package/templates/worker/README.md +53 -0
- package/templates/worker/gitignore +8 -0
- package/templates/worker/package.json +28 -0
- package/templates/worker/pnpm-workspace.yaml +4 -0
- package/templates/worker/src/app.controller.ts +18 -0
- package/templates/worker/src/app.module.ts +9 -0
- package/templates/worker/src/app.service.ts +8 -0
- package/templates/worker/src/worker.ts +7 -0
- package/templates/worker/tsconfig.json +18 -0
- package/templates/worker/vela.config.mjs +9 -0
- package/templates/worker/wrangler.jsonc +12 -0
package/dist/index.js
CHANGED
|
@@ -1,13 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { defineVelaConfig, loadConfig } from "./config.js";
|
|
2
|
+
import { defineVelaConfig, loadConfig, resolveConfig } from "./config.js";
|
|
3
3
|
import { t as generateClientContract } from "./client-contract-C7P2btFE.js";
|
|
4
|
-
import { Builtins, Cli, Command, Option } from "clipanion";
|
|
5
|
-
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
6
|
-
import { createOpenApiDocument, describeToken, getEntrypointKinds } from "@velajs/vela";
|
|
7
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { Builtins, Cli, Command, Option, UsageError } from "clipanion";
|
|
5
|
+
import { lstat, mkdir, open, readFile, readdir, rmdir, unlink, writeFile } from "node:fs/promises";
|
|
6
|
+
import { createOpenApiDocument, describeToken, getEntrypointKinds, parseCron, parseCronMetadata } from "@velajs/vela";
|
|
7
|
+
import { dirname, extname, join, resolve } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
import { z } from "zod";
|
|
10
|
-
import { runSeeders } from "@velajs/vela/seeder";
|
|
10
|
+
import { SeederRegistry, runSeeders } from "@velajs/vela/seeder";
|
|
11
|
+
import { execFile } from "node:child_process";
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import { promisify } from "node:util";
|
|
14
|
+
import { parse } from "jsonc-parser";
|
|
15
|
+
import { parse as parse$1 } from "smol-toml";
|
|
16
|
+
//#region package.json
|
|
17
|
+
var version = "1.24.0";
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/with-app.ts
|
|
20
|
+
/** Own one app for the whole command, including output and long-lived transports. */
|
|
21
|
+
async function withApp(config, work, warn) {
|
|
22
|
+
const app = await config.createApp();
|
|
23
|
+
const report = (error) => {
|
|
24
|
+
try {
|
|
25
|
+
warn(`Warning: teardown failed: ${String(error)}`);
|
|
26
|
+
} catch {}
|
|
27
|
+
};
|
|
28
|
+
try {
|
|
29
|
+
return await work(app);
|
|
30
|
+
} finally {
|
|
31
|
+
try {
|
|
32
|
+
if (typeof app.dispose === "function") await app.dispose();
|
|
33
|
+
else await app.getContainer().dispose();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
report(error);
|
|
36
|
+
try {
|
|
37
|
+
await app.getContainer().dispose();
|
|
38
|
+
} catch (cleanupError) {
|
|
39
|
+
report(cleanupError);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
11
45
|
//#region src/format.ts
|
|
12
46
|
/**
|
|
13
47
|
* Render seeder results to a logger and return a process exit code
|
|
@@ -145,17 +179,9 @@ var AppCommand = class extends Command {
|
|
|
145
179
|
config = Option.String("--config", { description: "Path to the vela config file." });
|
|
146
180
|
json = Option.Boolean("--json", false, { description: "Emit machine-readable JSON." });
|
|
147
181
|
async execute() {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
} finally {
|
|
152
|
-
const dispose = app.dispose;
|
|
153
|
-
if (typeof dispose === "function") try {
|
|
154
|
-
await dispose.call(app);
|
|
155
|
-
} catch (error) {
|
|
156
|
-
this.context.stderr.write(`Warning: teardown failed: ${String(error)}\n`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
182
|
+
return withApp(await loadConfig(process.cwd(), this.config), (app) => this.run(app), (message) => {
|
|
183
|
+
this.context.stderr.write(`${message}\n`);
|
|
184
|
+
});
|
|
159
185
|
}
|
|
160
186
|
print(text) {
|
|
161
187
|
this.context.stdout.write(`${text}\n`);
|
|
@@ -258,12 +284,12 @@ var OpenApiDumpCommand = class extends Command {
|
|
|
258
284
|
this.context.stderr.write("openapi dump needs the root module. Add it to your vela.config:\n\n export default defineVelaConfig({\n rootModule: AppModule,\n async createApp() { ... },\n });\n");
|
|
259
285
|
return 1;
|
|
260
286
|
}
|
|
261
|
-
const
|
|
262
|
-
|
|
287
|
+
const rootModule = velaConfig.rootModule;
|
|
288
|
+
return withApp(velaConfig, async (app) => {
|
|
263
289
|
const info = {};
|
|
264
290
|
if (this.title) info.title = this.title;
|
|
265
291
|
if (this.apiVersion) info.version = this.apiVersion;
|
|
266
|
-
const document = createOpenApiDocument(
|
|
292
|
+
const document = createOpenApiDocument(rootModule, {
|
|
267
293
|
globalPrefix: this.globalPrefix ?? app.getGlobalPrefix(),
|
|
268
294
|
...Object.keys(info).length > 0 ? { info } : {}
|
|
269
295
|
});
|
|
@@ -273,14 +299,7 @@ var OpenApiDumpCommand = class extends Command {
|
|
|
273
299
|
this.context.stdout.write(`Wrote ${this.out}\n`);
|
|
274
300
|
} else this.context.stdout.write(`${text}\n`);
|
|
275
301
|
return 0;
|
|
276
|
-
}
|
|
277
|
-
const dispose = app.dispose;
|
|
278
|
-
if (typeof dispose === "function") try {
|
|
279
|
-
await dispose.call(app);
|
|
280
|
-
} catch (error) {
|
|
281
|
-
this.context.stderr.write(`Warning: teardown failed: ${String(error)}\n`);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
302
|
+
}, (message) => this.context.stderr.write(`${message}\n`));
|
|
284
303
|
}
|
|
285
304
|
};
|
|
286
305
|
//#endregion
|
|
@@ -351,10 +370,8 @@ function describeToken$1(app, token) {
|
|
|
351
370
|
* introspection as the `route`/`module`/`entrypoint`/`openapi` commands, so an
|
|
352
371
|
* AI agent can query a Vela app's shape over the Model Context Protocol.
|
|
353
372
|
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
* the transport closes. stdout is reserved for JSON-RPC framing; every human
|
|
357
|
-
* message goes to stderr.
|
|
373
|
+
* The application lifetime includes the transport's close promise. stdout is
|
|
374
|
+
* reserved for JSON-RPC framing; every human message goes to stderr.
|
|
358
375
|
*/
|
|
359
376
|
var McpServeCommand = class extends Command {
|
|
360
377
|
static paths = [["mcp", "serve"]];
|
|
@@ -372,10 +389,10 @@ var McpServeCommand = class extends Command {
|
|
|
372
389
|
this.context.stderr.write(`${message}\n`);
|
|
373
390
|
};
|
|
374
391
|
const velaConfig = await loadConfig(process.cwd(), this.config);
|
|
375
|
-
const app = await velaConfig.createApp();
|
|
376
392
|
const rootModule = velaConfig.rootModule;
|
|
377
|
-
|
|
378
|
-
const
|
|
393
|
+
return withApp(velaConfig, async (app) => {
|
|
394
|
+
const identity = await readCliIdentity();
|
|
395
|
+
const server = new McpServer(identity);
|
|
379
396
|
server.registerTool("route_list", {
|
|
380
397
|
description: "The app's HTTP route table: framework-composed controller routes (method, full path, Controller#handler) plus everything else mounted on the router, labeled (mounted). Empty when the app builds no HTTP routes.",
|
|
381
398
|
inputSchema: {}
|
|
@@ -431,14 +448,7 @@ var McpServeCommand = class extends Command {
|
|
|
431
448
|
log(`vela mcp serve — ready (5 tools${rootModule ? " + vela://openapi resource" : ""}). Awaiting client on stdio; stdout is JSON-RPC only.`);
|
|
432
449
|
await closed;
|
|
433
450
|
return 0;
|
|
434
|
-
}
|
|
435
|
-
const dispose = app.dispose;
|
|
436
|
-
if (typeof dispose === "function") try {
|
|
437
|
-
await dispose.call(app);
|
|
438
|
-
} catch (error) {
|
|
439
|
-
this.context.stderr.write(`Warning: teardown failed: ${String(error)}\n`);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
451
|
+
}, log);
|
|
442
452
|
}
|
|
443
453
|
};
|
|
444
454
|
//#endregion
|
|
@@ -450,22 +460,44 @@ var SeedCommand = class extends Command {
|
|
|
450
460
|
category: "Database",
|
|
451
461
|
description: "Run database seeders for the Vela app.",
|
|
452
462
|
details: "Loads vela.config.{js,mjs,ts}, builds the app, and runs all @Seeder() classes in order.",
|
|
453
|
-
examples: [
|
|
463
|
+
examples: [
|
|
464
|
+
["Run all seeders", "vela db seed"],
|
|
465
|
+
["Use a specific config", "vela db seed --config ./config/vela.config.js"],
|
|
466
|
+
["List seeders and their module owners", "vela db seed --list --json"]
|
|
467
|
+
]
|
|
454
468
|
});
|
|
455
469
|
config = Option.String("--config", { description: "Path to the vela config file." });
|
|
456
470
|
continueOnError = Option.Boolean("--continue-on-error", false, { description: "Run all seeders even if one fails." });
|
|
471
|
+
list = Option.Boolean("--list", false, { description: "List registered seeders and their owners without running them." });
|
|
472
|
+
json = Option.Boolean("--json", false, { description: "Emit --list inventory as JSON." });
|
|
457
473
|
async execute() {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
this.
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
474
|
+
if (this.json && !this.list) throw new UsageError("--json requires --list.");
|
|
475
|
+
if (this.list && this.continueOnError) throw new UsageError("--list cannot be combined with --continue-on-error.");
|
|
476
|
+
return withApp(await loadConfig(process.cwd(), this.config), async (app) => {
|
|
477
|
+
if (this.list) {
|
|
478
|
+
const inventory = app.get(SeederRegistry).list().map((seeder) => ({
|
|
479
|
+
name: seeder.name,
|
|
480
|
+
order: seeder.order,
|
|
481
|
+
target: describeToken(seeder.target),
|
|
482
|
+
moduleId: seeder.moduleId ?? null
|
|
483
|
+
}));
|
|
484
|
+
const output = this.json ? JSON.stringify(inventory, null, 2) : renderTable([
|
|
485
|
+
"ORDER",
|
|
486
|
+
"NAME",
|
|
487
|
+
"MODULE",
|
|
488
|
+
"TARGET"
|
|
489
|
+
], inventory.map((entry) => [
|
|
490
|
+
String(entry.order),
|
|
491
|
+
entry.name,
|
|
492
|
+
entry.moduleId ?? "(unknown)",
|
|
493
|
+
entry.target
|
|
494
|
+
])).join("\n");
|
|
495
|
+
this.context.stdout.write(`${output}\n`);
|
|
496
|
+
return 0;
|
|
497
|
+
}
|
|
498
|
+
this.context.stdout.write("Running seeders…\n");
|
|
499
|
+
return formatSeedResults(await runSeeders(app, { stopOnError: !this.continueOnError }), (message) => this.context.stdout.write(`${message}\n`));
|
|
500
|
+
}, (message) => this.context.stderr.write(`${message}\n`));
|
|
469
501
|
}
|
|
470
502
|
};
|
|
471
503
|
//#endregion
|
|
@@ -479,7 +511,7 @@ var SeedCommand = class extends Command {
|
|
|
479
511
|
const HOST_PACKAGE = "@velajs/studio-host";
|
|
480
512
|
/** True for a failed dynamic `import()` of a missing module (ESM or CJS code). */
|
|
481
513
|
function isModuleNotFound(error, specifier) {
|
|
482
|
-
const code = error.code;
|
|
514
|
+
const code = error !== null && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
483
515
|
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") return true;
|
|
484
516
|
return (error instanceof Error ? error.message : "").includes(specifier);
|
|
485
517
|
}
|
|
@@ -517,9 +549,9 @@ var StudioCommand = class extends Command {
|
|
|
517
549
|
}
|
|
518
550
|
let port;
|
|
519
551
|
if (this.port !== void 0) {
|
|
520
|
-
port = Number
|
|
521
|
-
if (Number.
|
|
522
|
-
this.context.stderr.write(`vela studio: --port must be a
|
|
552
|
+
port = Number(this.port);
|
|
553
|
+
if (!/^\d+$/.test(this.port) || !Number.isInteger(port) || port < 0 || port > 65535) {
|
|
554
|
+
this.context.stderr.write(`vela studio: --port must be a decimal integer 0-65535, got: ${this.port}\n`);
|
|
523
555
|
return 1;
|
|
524
556
|
}
|
|
525
557
|
}
|
|
@@ -579,7 +611,7 @@ var ClientGenerateCommand = class extends Command {
|
|
|
579
611
|
async execute() {
|
|
580
612
|
if (this.input && this.config) throw new Error("Use either --input or --config, not both.");
|
|
581
613
|
if (this.check && !this.out) throw new Error("--check requires --out.");
|
|
582
|
-
const document = this.input ? await this
|
|
614
|
+
const document = this.input ? await this.#readDocument(this.input) : await this.#fromApp();
|
|
583
615
|
const { source, warnings } = generateClientContract(document);
|
|
584
616
|
for (const warning of warnings) this.context.stderr.write(`Warning: ${warning}\n`);
|
|
585
617
|
if (this.strict && warnings.length) return 1;
|
|
@@ -601,27 +633,517 @@ var ClientGenerateCommand = class extends Command {
|
|
|
601
633
|
} else this.context.stdout.write(source);
|
|
602
634
|
return 0;
|
|
603
635
|
}
|
|
604
|
-
async readDocument(file) {
|
|
636
|
+
async #readDocument(file) {
|
|
605
637
|
return JSON.parse(await readFile(file, "utf8"));
|
|
606
638
|
}
|
|
607
|
-
async fromApp() {
|
|
639
|
+
async #fromApp() {
|
|
608
640
|
const config = await loadConfig(process.cwd(), this.config);
|
|
609
641
|
if (!config.rootModule) throw new Error("client generate needs rootModule in vela.config, or pass --input openapi.json.");
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
const document = createOpenApiDocument(
|
|
642
|
+
const rootModule = config.rootModule;
|
|
643
|
+
return withApp(config, async (app) => {
|
|
644
|
+
const document = createOpenApiDocument(rootModule, { globalPrefix: app.getGlobalPrefix() });
|
|
613
645
|
for (const route of app.describeRoutes()) {
|
|
614
646
|
const path = route.path.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, "{$1}");
|
|
615
647
|
const item = document.paths[path];
|
|
616
648
|
if (!item || !Object.hasOwn(item, route.method.toLowerCase())) throw new Error(`OpenAPI is missing ${route.method} ${route.path}. Update Vela or pass a complete document with --input.`);
|
|
617
649
|
}
|
|
618
650
|
return document;
|
|
619
|
-
}
|
|
651
|
+
}, (message) => this.context.stderr.write(`${message}\n`));
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
//#endregion
|
|
655
|
+
//#region src/new-project.ts
|
|
656
|
+
const template = new URL("../templates/worker/", import.meta.url);
|
|
657
|
+
const files = [
|
|
658
|
+
"package.json",
|
|
659
|
+
"pnpm-workspace.yaml",
|
|
660
|
+
"tsconfig.json",
|
|
661
|
+
".swcrc",
|
|
662
|
+
"wrangler.jsonc",
|
|
663
|
+
"vela.config.mjs",
|
|
664
|
+
"gitignore",
|
|
665
|
+
"README.md",
|
|
666
|
+
"src/worker.ts",
|
|
667
|
+
"src/app.module.ts",
|
|
668
|
+
"src/app.controller.ts",
|
|
669
|
+
"src/app.service.ts"
|
|
670
|
+
];
|
|
671
|
+
function hasCode(error, code) {
|
|
672
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
673
|
+
}
|
|
674
|
+
async function createProject(name, cwd) {
|
|
675
|
+
if (name.length > 63 || name !== name.trim() || !/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(name) || /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/.test(name)) throw new UsageError("Use a project name of at most 63 lowercase letters, digits, and single hyphens, starting with a letter. Paths and reserved device names are not supported.");
|
|
676
|
+
const contents = await Promise.all(files.map(async (file) => ({
|
|
677
|
+
file: file === "gitignore" ? ".gitignore" : file,
|
|
678
|
+
content: (await readFile(new URL(file, template), "utf8")).replaceAll("__PROJECT_NAME__", name)
|
|
679
|
+
})));
|
|
680
|
+
const destination = join(cwd, name);
|
|
681
|
+
const directories = [];
|
|
682
|
+
const written = [];
|
|
683
|
+
try {
|
|
684
|
+
try {
|
|
685
|
+
await mkdir(destination);
|
|
686
|
+
directories.push(destination);
|
|
687
|
+
} catch (error) {
|
|
688
|
+
if (!hasCode(error, "EEXIST")) throw error;
|
|
689
|
+
const stat = await lstat(destination);
|
|
690
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) throw new UsageError(`Destination is not a regular directory: ${destination}`);
|
|
691
|
+
if ((await readdir(destination)).length) throw new UsageError(`Destination is not empty: ${destination}. Choose a new project name.`);
|
|
692
|
+
}
|
|
693
|
+
const source = join(destination, "src");
|
|
694
|
+
await mkdir(source);
|
|
695
|
+
directories.push(source);
|
|
696
|
+
for (const { file, content } of contents) {
|
|
697
|
+
const path = join(destination, file);
|
|
698
|
+
const handle = await open(path, "wx");
|
|
699
|
+
written.push(path);
|
|
620
700
|
try {
|
|
621
|
-
await
|
|
622
|
-
}
|
|
623
|
-
|
|
701
|
+
await writeFile(handle, content, "utf8");
|
|
702
|
+
} finally {
|
|
703
|
+
await handle.close();
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
} catch (error) {
|
|
707
|
+
for (const path of written.toReversed()) await unlink(path).catch(() => {});
|
|
708
|
+
for (const path of directories.toReversed()) await rmdir(path).catch(() => {});
|
|
709
|
+
throw error;
|
|
710
|
+
}
|
|
711
|
+
return destination;
|
|
712
|
+
}
|
|
713
|
+
//#endregion
|
|
714
|
+
//#region src/commands/new.command.ts
|
|
715
|
+
var NewCommand = class extends Command {
|
|
716
|
+
static paths = [["new"]];
|
|
717
|
+
static usage = Command.Usage({
|
|
718
|
+
category: "Project",
|
|
719
|
+
description: "Create a minimal Vela application for Cloudflare Workers.",
|
|
720
|
+
details: "Creates a directory in the current working directory. An existing directory must be empty. Dependencies are installed separately with pnpm install.",
|
|
721
|
+
examples: [["Create an API", "vela new my-api"]]
|
|
722
|
+
});
|
|
723
|
+
name = Option.String({
|
|
724
|
+
name: "name",
|
|
725
|
+
required: true
|
|
726
|
+
});
|
|
727
|
+
async execute() {
|
|
728
|
+
await createProject(this.name, process.cwd());
|
|
729
|
+
this.context.stdout.write(`Created ${this.name}.\n\nNext steps:\n cd ${this.name}\n pnpm install\n pnpm typecheck\n pnpm build\n pnpm dev\n\nThen visit http://localhost:8787 or run: curl http://localhost:8787\n`);
|
|
730
|
+
return 0;
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
//#endregion
|
|
734
|
+
//#region src/commands/doctor.command.ts
|
|
735
|
+
/** Read only app-owned description APIs; never resolve providers or serialize their values. */
|
|
736
|
+
function describeApplication(app) {
|
|
737
|
+
return {
|
|
738
|
+
globalPrefix: app.getGlobalPrefix(),
|
|
739
|
+
modules: collectModules(app),
|
|
740
|
+
routes: collectRoutes(app),
|
|
741
|
+
entrypoints: app.entrypoints.kinds().flatMap((kind) => app.entrypoints.ofKind(kind).map((entry) => ({
|
|
742
|
+
kind,
|
|
743
|
+
target: `${describeToken(entry.token)}${entry.methodName === void 0 ? "" : `#${String(entry.methodName)}`}`,
|
|
744
|
+
..."moduleId" in entry && typeof entry.moduleId === "string" ? { moduleId: entry.moduleId } : {}
|
|
745
|
+
})))
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
var DoctorCommand = class extends Command {
|
|
749
|
+
static paths = [["doctor"]];
|
|
750
|
+
static usage = Command.Usage({
|
|
751
|
+
category: "Introspection",
|
|
752
|
+
description: "Explain config resolution and optionally inspect the application graph.",
|
|
753
|
+
details: "Checks config file resolution without importing it. --app opts into config import and application bootstrap, then reads app-local module, route and entrypoint snapshots and disposes the app. No files are written, providers are not resolved by the snapshot, and entrypoint metadata is omitted.",
|
|
754
|
+
examples: [["Explain config selection", "vela doctor --json"], ["Inspect a built app", "vela doctor --app --config vela.config.mjs --json"]]
|
|
755
|
+
});
|
|
756
|
+
config = Option.String("--config", { description: "Path to the Vela config file." });
|
|
757
|
+
app = Option.Boolean("--app", false, { description: "Import config, bootstrap the app and inspect its graph." });
|
|
758
|
+
json = Option.Boolean("--json", false, { description: "Emit machine-readable diagnostics." });
|
|
759
|
+
async execute() {
|
|
760
|
+
const report = {
|
|
761
|
+
schemaVersion: 1,
|
|
762
|
+
cwd: process.cwd(),
|
|
763
|
+
nodeVersion: process.versions.node,
|
|
764
|
+
config: null,
|
|
765
|
+
issues: []
|
|
766
|
+
};
|
|
767
|
+
try {
|
|
768
|
+
report.config = await resolveConfig(report.cwd, this.config);
|
|
769
|
+
if (this.app) report.application = await withApp(await loadConfig(report.cwd, report.config.path), describeApplication, (message) => {
|
|
770
|
+
report.issues.push(message);
|
|
771
|
+
});
|
|
772
|
+
} catch (error) {
|
|
773
|
+
report.issues.push(error instanceof Error ? error.message : String(error));
|
|
774
|
+
}
|
|
775
|
+
if (this.json) this.context.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
776
|
+
else {
|
|
777
|
+
this.context.stdout.write(`Node ${report.nodeVersion}\nWorking directory: ${report.cwd}\n`);
|
|
778
|
+
if (report.config) {
|
|
779
|
+
this.context.stdout.write(`Config: ${report.config.path} (${report.config.source})\n`);
|
|
780
|
+
for (const candidate of report.config.candidates) this.context.stdout.write(` Checked: ${candidate}\n`);
|
|
781
|
+
}
|
|
782
|
+
if (report.application) {
|
|
783
|
+
const { modules, routes, entrypoints } = report.application;
|
|
784
|
+
this.context.stdout.write(`Application: ${modules.length} modules, ${routes?.length ?? 0} routes, ${entrypoints.length} entrypoints\n`);
|
|
785
|
+
this.context.stdout.write("Use --json for the full graph.\n");
|
|
786
|
+
} else if (!this.app) this.context.stdout.write("Config was not imported. Use --app to bootstrap and inspect a built application.\n");
|
|
787
|
+
for (const issue of report.issues) this.context.stderr.write(`${issue}\n`);
|
|
788
|
+
}
|
|
789
|
+
return report.issues.length ? 1 : 0;
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
//#endregion
|
|
793
|
+
//#region src/commands/deploy-check.config.ts
|
|
794
|
+
const record = z.record(z.string(), z.unknown());
|
|
795
|
+
const text = z.string().min(1).refine((value) => value.trim() === value && !/\p{Cc}/u.test(value));
|
|
796
|
+
const bindingName = text.regex(/^[A-Za-z_$][A-Za-z0-9_$]*$/u);
|
|
797
|
+
const workerName = text.max(255).regex(/^[a-zA-Z0-9-]+$/u);
|
|
798
|
+
const date = z.string().regex(/^\d{4}-\d{2}-\d{2}$/u).refine((value) => {
|
|
799
|
+
const parsed = new Date(value);
|
|
800
|
+
return Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 10) === value;
|
|
801
|
+
});
|
|
802
|
+
/** Decode only data; parser errors deliberately omit configuration values. */
|
|
803
|
+
function parseDeploymentConfig(source, path) {
|
|
804
|
+
const extension = extname(path).toLowerCase();
|
|
805
|
+
if (extension === ".toml") try {
|
|
806
|
+
return parse$1(source);
|
|
807
|
+
} catch {
|
|
808
|
+
throw new Error("Invalid Wrangler TOML configuration.");
|
|
809
|
+
}
|
|
810
|
+
if (extension !== ".json" && extension !== ".jsonc") throw new Error("Wrangler configuration must be a .json, .jsonc or .toml file.");
|
|
811
|
+
const errors = [];
|
|
812
|
+
const result = parse(source, errors, {
|
|
813
|
+
allowTrailingComma: extension === ".jsonc",
|
|
814
|
+
disallowComments: extension === ".json"
|
|
815
|
+
});
|
|
816
|
+
if (errors.length > 0) throw new Error("Invalid Wrangler JSON configuration.");
|
|
817
|
+
return result;
|
|
818
|
+
}
|
|
819
|
+
function checked(schema, value, field) {
|
|
820
|
+
const result = schema.safeParse(value);
|
|
821
|
+
if (!result.success) throw new Error(`Invalid Wrangler field: ${field}.`);
|
|
822
|
+
return result.data;
|
|
823
|
+
}
|
|
824
|
+
/** A projection, not a replacement for Wrangler's full configuration validator. */
|
|
825
|
+
function selectDeploymentTarget(raw, environment) {
|
|
826
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(environment)) throw new Error("An explicit environment name using letters, digits, underscores or dashes is required.");
|
|
827
|
+
const root = checked(record, raw, "configuration");
|
|
828
|
+
const environments = checked(record, root.env, "env");
|
|
829
|
+
if (!Object.hasOwn(environments, environment)) throw new Error("The requested environment is not declared in Wrangler configuration.");
|
|
830
|
+
const selected = checked(record, environments[environment], `env.${environment}`);
|
|
831
|
+
const inherit = (key) => Object.hasOwn(selected, key) ? selected[key] : root[key];
|
|
832
|
+
const worker = checked(workerName, Object.hasOwn(selected, "name") ? selected.name : `${checked(workerName, root.name, "name")}-${environment}`, "name");
|
|
833
|
+
const main = checked(text.optional(), inherit("main"), "main") ?? null;
|
|
834
|
+
if (main === null) {
|
|
835
|
+
const assets = checked(record, inherit("assets"), "assets (required without main)");
|
|
836
|
+
checked(text, assets.directory, "assets.directory");
|
|
837
|
+
}
|
|
838
|
+
const compatibilityDate = checked(date, inherit("compatibility_date"), "compatibility_date");
|
|
839
|
+
const compatibilityFlags = checked(z.array(text).optional(), inherit("compatibility_flags"), "compatibility_flags") ?? [];
|
|
840
|
+
const triggers = checked(z.object({ crons: z.array(text) }).optional(), inherit("triggers"), "triggers");
|
|
841
|
+
const build = checked(z.object({ command: text.optional() }).optional(), inherit("build"), "build");
|
|
842
|
+
const bindings = [];
|
|
843
|
+
const names = /* @__PURE__ */ new Set();
|
|
844
|
+
const add = (name, kind) => {
|
|
845
|
+
if (names.has(name)) throw new Error(`Duplicate Worker binding name: ${name}.`);
|
|
846
|
+
names.add(name);
|
|
847
|
+
bindings.push({
|
|
848
|
+
name,
|
|
849
|
+
kind
|
|
850
|
+
});
|
|
851
|
+
};
|
|
852
|
+
const variables = checked(record.optional(), selected.vars, "vars") ?? {};
|
|
853
|
+
for (const name of Object.keys(variables)) add(checked(bindingName, name, "vars binding name"), "var");
|
|
854
|
+
for (const kind of [
|
|
855
|
+
"kv_namespaces",
|
|
856
|
+
"d1_databases",
|
|
857
|
+
"r2_buckets",
|
|
858
|
+
"services",
|
|
859
|
+
"hyperdrive",
|
|
860
|
+
"vectorize",
|
|
861
|
+
"workflows",
|
|
862
|
+
"analytics_engine_datasets"
|
|
863
|
+
]) {
|
|
864
|
+
const rows = checked(z.array(record).optional(), selected[kind], kind) ?? [];
|
|
865
|
+
for (const row of rows) {
|
|
866
|
+
add(checked(bindingName, row.binding, `${kind}.binding`), kind);
|
|
867
|
+
for (const field of [
|
|
868
|
+
"id",
|
|
869
|
+
"database_id",
|
|
870
|
+
"database_name",
|
|
871
|
+
"bucket_name"
|
|
872
|
+
]) if (Object.hasOwn(row, field)) checked(text, row[field], `${kind}.${field}`);
|
|
873
|
+
if (kind === "services") checked(workerName, row.service, "services.service");
|
|
874
|
+
if (kind === "workflows") checked(text, row.class_name, "workflows.class_name");
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const durable = checked(z.object({ bindings: z.array(z.object({
|
|
878
|
+
name: bindingName,
|
|
879
|
+
class_name: text,
|
|
880
|
+
script_name: workerName.optional()
|
|
881
|
+
})) }).optional(), selected.durable_objects, "durable_objects");
|
|
882
|
+
for (const row of durable?.bindings ?? []) add(row.name, "durable_objects");
|
|
883
|
+
const queues = checked(z.object({
|
|
884
|
+
producers: z.array(z.object({
|
|
885
|
+
binding: bindingName,
|
|
886
|
+
queue: text.optional()
|
|
887
|
+
})).optional(),
|
|
888
|
+
consumers: z.array(z.object({ queue: text })).optional()
|
|
889
|
+
}).optional(), selected.queues, "queues");
|
|
890
|
+
for (const producer of queues?.producers ?? []) add(producer.binding, "queues");
|
|
891
|
+
const queueConsumers = queues?.consumers?.map((row) => row.queue) ?? [];
|
|
892
|
+
if (new Set(queueConsumers).size !== queueConsumers.length) throw new Error("Duplicate queue consumer configuration.");
|
|
893
|
+
return {
|
|
894
|
+
environment,
|
|
895
|
+
worker,
|
|
896
|
+
main,
|
|
897
|
+
compatibilityDate,
|
|
898
|
+
compatibilityFlags,
|
|
899
|
+
crons: triggers?.crons ?? [],
|
|
900
|
+
bindings,
|
|
901
|
+
queueConsumers,
|
|
902
|
+
customBuild: build?.command !== void 0
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
//#endregion
|
|
906
|
+
//#region src/commands/deploy-check.plan.ts
|
|
907
|
+
const rowsSchema = z.array(z.object({
|
|
908
|
+
kind: z.string().min(1),
|
|
909
|
+
target: z.string().min(1),
|
|
910
|
+
meta: z.unknown()
|
|
911
|
+
}));
|
|
912
|
+
const metadataSchema = z.record(z.string(), z.unknown());
|
|
913
|
+
/** Accept existing `vela entrypoint list --json` output without loading an application. */
|
|
914
|
+
function entrypoints(value) {
|
|
915
|
+
const parsed = rowsSchema.safeParse(value);
|
|
916
|
+
if (!parsed.success) throw new Error("Entrypoint snapshot must be an array of { kind, target, meta } rows.");
|
|
917
|
+
return parsed.data.filter((row) => !(row.target === "(no entrypoints)" && row.meta === "")).map((row) => {
|
|
918
|
+
let meta = row.meta;
|
|
919
|
+
if (typeof meta === "string") try {
|
|
920
|
+
meta = JSON.parse(meta);
|
|
921
|
+
} catch {
|
|
922
|
+
throw new Error("Invalid JSON metadata in entrypoint snapshot.");
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
kind: row.kind,
|
|
926
|
+
meta
|
|
927
|
+
};
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
/** Compare literal dispatch keys; equivalent cron expressions are not interchangeable. */
|
|
931
|
+
function checkDeployment(rawConfig, environment, snapshot) {
|
|
932
|
+
const target = selectDeploymentTarget(rawConfig, environment);
|
|
933
|
+
const errors = [];
|
|
934
|
+
const warnings = [];
|
|
935
|
+
const report = (code, message) => errors.push({
|
|
936
|
+
code,
|
|
937
|
+
message
|
|
938
|
+
});
|
|
939
|
+
const validateCron = (cron) => {
|
|
940
|
+
if (!parseCron(cron, {
|
|
941
|
+
dialect: "cloudflare",
|
|
942
|
+
timeZone: "UTC"
|
|
943
|
+
})) report("invalid-cron", "A trigger or handler has an invalid Cloudflare cron expression.");
|
|
944
|
+
};
|
|
945
|
+
const crons = new Set(target.crons);
|
|
946
|
+
if (crons.size !== target.crons.length) report("duplicate-cron", "Duplicate cron trigger configuration.");
|
|
947
|
+
for (const cron of crons) validateCron(cron);
|
|
948
|
+
const handlerCrons = /* @__PURE__ */ new Set();
|
|
949
|
+
const handlerQueues = /* @__PURE__ */ new Set();
|
|
950
|
+
for (const row of entrypoints(snapshot)) {
|
|
951
|
+
if (row.kind === "schedule:interval") {
|
|
952
|
+
report("unsupported-interval", "Workers cannot drive @Interval handlers; use a scheduled trigger or a separate Node adapter.");
|
|
953
|
+
continue;
|
|
954
|
+
}
|
|
955
|
+
if (![
|
|
956
|
+
"cf:scheduled",
|
|
957
|
+
"cf:vela-cron",
|
|
958
|
+
"schedule:cron",
|
|
959
|
+
"cf:queue",
|
|
960
|
+
"websocket"
|
|
961
|
+
].includes(row.kind)) continue;
|
|
962
|
+
const parsed = metadataSchema.safeParse(row.meta);
|
|
963
|
+
if (!parsed.success) {
|
|
964
|
+
report("invalid-metadata", `Invalid ${row.kind} metadata.`);
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
const meta = parsed.data;
|
|
968
|
+
if (row.kind === "cf:vela-cron" || row.kind === "schedule:cron") try {
|
|
969
|
+
const cron = parseCronMetadata(meta);
|
|
970
|
+
if (cron.dialect !== void 0 && cron.dialect !== "cloudflare" || cron.timeZone !== void 0 && cron.timeZone !== "UTC") report("incompatible-cron-options", "A cron handler explicitly requests options incompatible with Cloudflare UTC delivery.");
|
|
971
|
+
} catch {
|
|
972
|
+
report("invalid-metadata", `Invalid ${row.kind} metadata.`);
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
if (row.kind === "websocket") {
|
|
976
|
+
const options = Object.hasOwn(meta, "options") ? metadataSchema.safeParse(meta.options) : parsed;
|
|
977
|
+
const binding = options.success ? options.data.binding : void 0;
|
|
978
|
+
if (typeof binding !== "string" || !target.bindings.some((b) => b.kind === "durable_objects" && b.name === binding)) report("missing-durable-binding", "A WebSocket gateway requires a Durable Object binding in the selected environment.");
|
|
979
|
+
continue;
|
|
980
|
+
}
|
|
981
|
+
const key = row.kind === "cf:scheduled" ? "cron" : row.kind === "cf:queue" ? "queueName" : "expression";
|
|
982
|
+
const value = meta[key];
|
|
983
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
984
|
+
report("invalid-metadata", `Invalid ${row.kind}.${key} metadata.`);
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
if (row.kind === "cf:queue") handlerQueues.add(value);
|
|
988
|
+
else {
|
|
989
|
+
handlerCrons.add(value);
|
|
990
|
+
validateCron(value);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
for (const cron of handlerCrons) if (!crons.has(cron)) report("missing-cron-trigger", `No exact Wrangler trigger for handler cron ${JSON.stringify(cron)}.`);
|
|
994
|
+
for (const cron of crons) if (!handlerCrons.has(cron)) report("unhandled-cron-trigger", `No metadata handler for Wrangler cron ${JSON.stringify(cron)}.`);
|
|
995
|
+
for (const queue of handlerQueues) if (!target.queueConsumers.includes(queue)) report("missing-queue-consumer", `No selected queue consumer for handler queue ${JSON.stringify(queue)}.`);
|
|
996
|
+
for (const queue of target.queueConsumers) if (!handlerQueues.has(queue)) report("unhandled-queue-consumer", `No metadata handler for selected queue ${JSON.stringify(queue)}.`);
|
|
997
|
+
warnings.push({
|
|
998
|
+
code: "static-only",
|
|
999
|
+
message: "Snapshot freshness, custom platform handlers, deployed resources, secrets and bundle/runtime behavior are not verified. Run Wrangler and native tests separately."
|
|
1000
|
+
});
|
|
1001
|
+
if (target.customBuild) warnings.push({
|
|
1002
|
+
code: "custom-build",
|
|
1003
|
+
message: "Wrangler has a custom build command. It was not executed by this check."
|
|
1004
|
+
});
|
|
1005
|
+
return {
|
|
1006
|
+
status: errors.length ? "failed" : "passed",
|
|
1007
|
+
target,
|
|
1008
|
+
errors,
|
|
1009
|
+
warnings
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
//#endregion
|
|
1013
|
+
//#region src/commands/deploy-check.command.ts
|
|
1014
|
+
const exec = promisify(execFile);
|
|
1015
|
+
const MAX_INPUT_BYTES = 1048576;
|
|
1016
|
+
async function readInput(path) {
|
|
1017
|
+
const file = await open(path, "r");
|
|
1018
|
+
try {
|
|
1019
|
+
if (!(await file.stat()).isFile()) throw new Error("Input must be a regular file.");
|
|
1020
|
+
const buffer = Buffer.alloc(1048577);
|
|
1021
|
+
let length = 0;
|
|
1022
|
+
while (length < buffer.length) {
|
|
1023
|
+
const { bytesRead } = await file.read(buffer, length, buffer.length - length, length);
|
|
1024
|
+
if (bytesRead === 0) break;
|
|
1025
|
+
length += bytesRead;
|
|
1026
|
+
}
|
|
1027
|
+
if (length > MAX_INPUT_BYTES) throw new Error("Deployment inputs must not exceed 1 MiB.");
|
|
1028
|
+
return buffer.subarray(0, length).toString("utf8");
|
|
1029
|
+
} finally {
|
|
1030
|
+
await file.close();
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
async function gitProvenance(cwd) {
|
|
1034
|
+
try {
|
|
1035
|
+
const options = {
|
|
1036
|
+
cwd,
|
|
1037
|
+
timeout: 5e3,
|
|
1038
|
+
maxBuffer: 1048576
|
|
1039
|
+
};
|
|
1040
|
+
const [head, status] = await Promise.all([exec("git", [
|
|
1041
|
+
"rev-parse",
|
|
1042
|
+
"--verify",
|
|
1043
|
+
"HEAD"
|
|
1044
|
+
], options), exec("git", [
|
|
1045
|
+
"--no-optional-locks",
|
|
1046
|
+
"-c",
|
|
1047
|
+
"core.fsmonitor=false",
|
|
1048
|
+
"status",
|
|
1049
|
+
"--porcelain=v1",
|
|
1050
|
+
"--untracked-files=normal"
|
|
1051
|
+
], options)]);
|
|
1052
|
+
const commit = head.stdout.trim();
|
|
1053
|
+
if (!/^[a-f0-9]{40,64}$/u.test(commit)) throw new Error("Invalid git commit.");
|
|
1054
|
+
return {
|
|
1055
|
+
commit,
|
|
1056
|
+
dirty: status.stdout.length > 0
|
|
1057
|
+
};
|
|
1058
|
+
} catch {
|
|
1059
|
+
return {
|
|
1060
|
+
commit: null,
|
|
1061
|
+
dirty: null
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
const digest = (text) => createHash("sha256").update(text).digest("hex");
|
|
1066
|
+
/** Static application deployment preflight; never invokes Wrangler or application code. */
|
|
1067
|
+
var DeployCheckCommand = class extends Command {
|
|
1068
|
+
static paths = [["deploy", "check"]];
|
|
1069
|
+
static usage = Command.Usage({
|
|
1070
|
+
category: "Deployment",
|
|
1071
|
+
description: "Check an explicit Wrangler target against a saved entrypoint snapshot.",
|
|
1072
|
+
details: "Read-only: no app bootstrap, custom build, credential loading or upload. Compares cron/queue dispatch keys and WebSocket Durable Object bindings. Wrangler remains the deployment tool.",
|
|
1073
|
+
examples: [["Check staging", "vela deploy check --config wrangler.jsonc --env staging --entrypoints entrypoints.json"]]
|
|
1074
|
+
});
|
|
1075
|
+
config = Option.String("--config", {
|
|
1076
|
+
required: true,
|
|
1077
|
+
description: "Explicit Wrangler .json/.jsonc/.toml file."
|
|
1078
|
+
});
|
|
1079
|
+
environment = Option.String("--env", {
|
|
1080
|
+
required: true,
|
|
1081
|
+
description: "Exact named environment in the Wrangler file."
|
|
1082
|
+
});
|
|
1083
|
+
entrypoints = Option.String("--entrypoints", {
|
|
1084
|
+
required: true,
|
|
1085
|
+
description: "Saved vela entrypoint list --json array."
|
|
1086
|
+
});
|
|
1087
|
+
json = Option.Boolean("--json", false, { description: "Emit the redacted report as JSON." });
|
|
1088
|
+
async execute() {
|
|
1089
|
+
try {
|
|
1090
|
+
if (!this.config.trim() || !this.entrypoints.trim()) throw new Error("Explicit configuration and snapshot paths are required.");
|
|
1091
|
+
const configPath = resolve(this.config);
|
|
1092
|
+
const snapshotPath = resolve(this.entrypoints);
|
|
1093
|
+
const [config, snapshot] = await Promise.all([readInput(configPath), readInput(snapshotPath)]);
|
|
1094
|
+
let rows;
|
|
1095
|
+
try {
|
|
1096
|
+
rows = JSON.parse(snapshot);
|
|
1097
|
+
} catch {
|
|
1098
|
+
throw new Error("Invalid entrypoint snapshot JSON.");
|
|
1099
|
+
}
|
|
1100
|
+
const plan = checkDeployment(parseDeploymentConfig(config, configPath), this.environment, rows);
|
|
1101
|
+
const provenance = {
|
|
1102
|
+
...await gitProvenance(dirname(configPath)),
|
|
1103
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1104
|
+
config: {
|
|
1105
|
+
path: configPath,
|
|
1106
|
+
sha256: digest(config)
|
|
1107
|
+
},
|
|
1108
|
+
entrypoints: {
|
|
1109
|
+
path: snapshotPath,
|
|
1110
|
+
sha256: digest(snapshot)
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
const wrangler = {
|
|
1114
|
+
command: "pnpm",
|
|
1115
|
+
args: [
|
|
1116
|
+
"exec",
|
|
1117
|
+
"wrangler",
|
|
1118
|
+
"deploy",
|
|
1119
|
+
"--config",
|
|
1120
|
+
configPath,
|
|
1121
|
+
"--env",
|
|
1122
|
+
this.environment,
|
|
1123
|
+
"--dry-run"
|
|
1124
|
+
]
|
|
1125
|
+
};
|
|
1126
|
+
const result = {
|
|
1127
|
+
...plan,
|
|
1128
|
+
provenance,
|
|
1129
|
+
nextStep: wrangler
|
|
1130
|
+
};
|
|
1131
|
+
if (this.json) this.context.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
1132
|
+
else {
|
|
1133
|
+
this.context.stdout.write(`Deployment check: ${plan.status}\nWorker: ${plan.target.worker}\nEnvironment: ${plan.target.environment}\nConfig: ${configPath}\nCommit: ${provenance.commit ?? "unavailable"} (${provenance.dirty === null ? "cleanliness unknown" : provenance.dirty ? "dirty" : "clean"})\nConfig SHA-256: ${provenance.config.sha256}\nSnapshot SHA-256: ${provenance.entrypoints.sha256}\nBindings: ${plan.target.bindings.map((binding) => `${binding.name} (${binding.kind})`).join(", ") || "(none)"}\n`);
|
|
1134
|
+
for (const issue of plan.errors) this.context.stdout.write(`Error [${issue.code}]: ${issue.message}\n`);
|
|
1135
|
+
for (const issue of plan.warnings) this.context.stdout.write(`Warning [${issue.code}]: ${issue.message}\n`);
|
|
1136
|
+
this.context.stdout.write(`Next step (not executed): ${[wrangler.command, ...wrangler.args].map((arg) => `'${arg.replaceAll("'", "'\\''")}'`).join(" ")}\n`);
|
|
624
1137
|
}
|
|
1138
|
+
return plan.status === "passed" ? 0 : 1;
|
|
1139
|
+
} catch (error) {
|
|
1140
|
+
const message = error instanceof Error ? error.message : "Deployment check failed.";
|
|
1141
|
+
if (this.json) this.context.stdout.write(`${JSON.stringify({
|
|
1142
|
+
status: "failed",
|
|
1143
|
+
error: message
|
|
1144
|
+
})}\n`);
|
|
1145
|
+
else this.context.stderr.write(`${message}\n`);
|
|
1146
|
+
return 1;
|
|
625
1147
|
}
|
|
626
1148
|
}
|
|
627
1149
|
};
|
|
@@ -630,10 +1152,11 @@ var ClientGenerateCommand = class extends Command {
|
|
|
630
1152
|
const cli = new Cli({
|
|
631
1153
|
binaryName: "vela",
|
|
632
1154
|
binaryLabel: "Vela CLI",
|
|
633
|
-
binaryVersion:
|
|
1155
|
+
binaryVersion: version
|
|
634
1156
|
});
|
|
635
1157
|
cli.register(Builtins.HelpCommand);
|
|
636
1158
|
cli.register(Builtins.VersionCommand);
|
|
1159
|
+
cli.register(NewCommand);
|
|
637
1160
|
cli.register(SeedCommand);
|
|
638
1161
|
cli.register(RouteListCommand);
|
|
639
1162
|
cli.register(ModuleGraphCommand);
|
|
@@ -642,8 +1165,10 @@ cli.register(OpenApiDumpCommand);
|
|
|
642
1165
|
cli.register(McpServeCommand);
|
|
643
1166
|
cli.register(StudioCommand);
|
|
644
1167
|
cli.register(ClientGenerateCommand);
|
|
1168
|
+
cli.register(DoctorCommand);
|
|
1169
|
+
cli.register(DeployCheckCommand);
|
|
645
1170
|
cli.runExit(process.argv.slice(2));
|
|
646
1171
|
//#endregion
|
|
647
|
-
export { ClientGenerateCommand, EntrypointListCommand, McpServeCommand, ModuleGraphCommand, OpenApiDumpCommand, RouteListCommand, SeedCommand, StudioCommand, collectEntrypoints, collectModules, collectRoutes, defineVelaConfig, formatSeedResults, generateClientContract, loadConfig, renderModuleTree, renderTable };
|
|
1172
|
+
export { ClientGenerateCommand, DeployCheckCommand, DoctorCommand, EntrypointListCommand, McpServeCommand, ModuleGraphCommand, NewCommand, OpenApiDumpCommand, RouteListCommand, SeedCommand, StudioCommand, collectEntrypoints, collectModules, collectRoutes, defineVelaConfig, formatSeedResults, generateClientContract, loadConfig, renderModuleTree, renderTable, resolveConfig };
|
|
648
1173
|
|
|
649
1174
|
//# sourceMappingURL=index.js.map
|