@vwork/cli 0.1.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.
@@ -0,0 +1,357 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { Command, Flags } from "@oclif/core";
6
+ import { PlatformClient } from "@vwork/platform-client";
7
+ import { runAppCommand } from "./apps.js";
8
+ import { FileCredentialStore, loadDefaultCredentials } from "./auth/store.js";
9
+ import { runCodegenCommand } from "./codegen.js";
10
+ import { resolveCliConfigFromFlags } from "./config.js";
11
+ import { runDbCommand } from "./db.js";
12
+ import { runFunctionCommand } from "./functions.js";
13
+ import { runLoginCommand, runWhoamiCommand } from "./login.js";
14
+ import { runKvCommand } from "./kv.js";
15
+ import { runMigrationCommand } from "./migrations.js";
16
+ import { runObservabilityCommand } from "./observability.js";
17
+ import { renderJson, renderTable } from "./output.js";
18
+ import { runQueueCommand } from "./queues.js";
19
+ import { runRuntimeCommand } from "./runtime.js";
20
+ import { runSecretCommand } from "./secrets.js";
21
+ const COMMAND_ROOTS = new Set(["apps", "db", "migrations", "functions", "codegen", "secrets", "kv", "queues", "runtime", "observability", "login", "whoami"]);
22
+ export class VWorkCliCommand extends Command {
23
+ static description = "Manage VWork apps, database rows, migrations, and functions.";
24
+ static strict = false;
25
+ static flags = {
26
+ "api-url": Flags.string({ description: "Platform API base URL" }),
27
+ app: Flags.string({ description: "App id or slug" }),
28
+ schema: Flags.string({ description: "Database schema" }),
29
+ format: Flags.string({ description: "Output format", options: ["table", "json"] }),
30
+ name: Flags.string({ description: "App display name" }),
31
+ slug: Flags.string({ description: "App slug" }),
32
+ host: Flags.string({ description: "App API host" }),
33
+ "data-source": Flags.string({ description: "Data source id" }),
34
+ isolation: Flags.string({ description: "Data isolation mode" }),
35
+ sql: Flags.string({ description: "Inline SQL" }),
36
+ filter: Flags.string({ description: "Row filter expression", multiple: true }),
37
+ limit: Flags.string({ description: "Result limit" }),
38
+ offset: Flags.string({ description: "Result offset" }),
39
+ order: Flags.string({ description: "Order as column.asc or column.desc" }),
40
+ data: Flags.string({ description: "JSON object payload" }),
41
+ pk: Flags.string({ description: "Primary key as column=value" }),
42
+ up: Flags.string({ description: "Migration up SQL file" }),
43
+ "up-sql": Flags.string({ description: "Inline migration up SQL" }),
44
+ down: Flags.string({ description: "Migration down SQL file" }),
45
+ "down-sql": Flags.string({ description: "Inline migration down SQL" }),
46
+ reason: Flags.string({ description: "Reason for migration down" }),
47
+ file: Flags.string({ description: "Runtime-ready JavaScript source file path" }),
48
+ bundle: Flags.string({ description: "Runtime-ready JavaScript bundle file path" }),
49
+ "source-code": Flags.string({ description: "Inline function source code" }),
50
+ function: Flags.string({ description: "Function name" }),
51
+ binding: Flags.string({ description: "Function binding name for code generation context", multiple: true }),
52
+ route: Flags.string({ description: "Function route path prefix" }),
53
+ artifact: Flags.string({ description: "Function artifact id" }),
54
+ body: Flags.string({ description: "JSON request body" }),
55
+ ttl: Flags.string({ description: "Preview TTL, such as 30m or 1h" }),
56
+ preview: Flags.string({ description: "Preview id" }),
57
+ table: Flags.string({ description: "Table name for schema-scoped codegen", multiple: true }),
58
+ prompt: Flags.string({ description: "Code generation prompt" }),
59
+ run: Flags.string({ description: "Code generation run id" }),
60
+ title: Flags.string({ description: "KV namespace display title" }),
61
+ namespace: Flags.string({ description: "KV namespace id" }),
62
+ prefix: Flags.string({ description: "KV key prefix filter" }),
63
+ cursor: Flags.string({ description: "Pagination cursor" }),
64
+ range: Flags.string({ description: "Metrics range" }),
65
+ step: Flags.string({ description: "Metrics step" }),
66
+ granularity: Flags.string({ description: "Metrics granularity" }),
67
+ from: Flags.string({ description: "Custom metrics window start ISO timestamp" }),
68
+ to: Flags.string({ description: "Custom metrics window end ISO timestamp" }),
69
+ source: Flags.string({ description: "Access log source" }),
70
+ "base-url": Flags.string({ description: "Raw repository base URL for skill install curl output" }),
71
+ workspace: Flags.string({ description: "Workspace path used by local MCP server snippets" }),
72
+ queue: Flags.string({ description: "Queue binding as <queue_id>[:producer|consumer|producer_consumer]", multiple: true }),
73
+ "batch-size": Flags.string({ description: "Queue batch size" }),
74
+ "wait-seconds": Flags.string({ description: "Queue wait or visibility timeout seconds" }),
75
+ "retry-attempts": Flags.string({ description: "Queue retry attempts" }),
76
+ "max-delay-seconds": Flags.string({ description: "Queue max delay seconds" }),
77
+ "queue-length": Flags.string({ description: "Queue max message count" }),
78
+ "message-bytes": Flags.string({ description: "Queue max message size in bytes" }),
79
+ "dead-letter-enabled": Flags.string({ description: "Enable dead letter handling: true or false" }),
80
+ out: Flags.string({ description: "Output path" }),
81
+ yes: Flags.boolean({ description: "Confirm the operation" }),
82
+ verbose: Flags.boolean({ description: "Enable verbose output" }),
83
+ "no-auth": Flags.boolean({ description: "Disable function auth requirement" }),
84
+ repair: Flags.boolean({ description: "Enable codegen repair attempts" }),
85
+ "draft-invoke-body": Flags.string({ description: "Draft invocation request body JSON object for codegen validation" }),
86
+ "draft-invoke-bindings": Flags.string({ description: "Draft invocation binding overrides JSON object for codegen validation" }),
87
+ version: Flags.boolean({ char: "v", description: "Show CLI version" }),
88
+ help: Flags.boolean({ char: "h", description: "Show CLI help" })
89
+ };
90
+ async run() {
91
+ const parsed = await this.parse(VWorkCliCommand);
92
+ const command = parsedCommandFromOclif(parsed.argv.map(String), parsedFlags(parsed.flags));
93
+ if (command.flags.version === true) {
94
+ this.log(packageVersion());
95
+ return;
96
+ }
97
+ if (command.flags.help === true) {
98
+ this.log(command.path.length === 1 ? resourceHelpText(command.path[0] ?? "") : helpText());
99
+ return;
100
+ }
101
+ if (command.path.length === 0) {
102
+ this.log(helpText());
103
+ return;
104
+ }
105
+ if (command.path[0] === "login") {
106
+ const config = resolveCliConfigFromFlags(command.flags, process.env, process.cwd());
107
+ const root = process.env.VWORK_HOME ?? join(homedir(), ".vwork");
108
+ const store = new FileCredentialStore(root);
109
+ const output = await runLoginCommand({
110
+ apiUrl: config.apiUrl,
111
+ openBrowser: async (url) => {
112
+ this.log(`Open this URL to continue login: ${url}`);
113
+ },
114
+ saveApiKey: (input) => store.saveApiKey(input)
115
+ });
116
+ process.stdout.write(output);
117
+ return;
118
+ }
119
+ if (command.path[0] === "whoami") {
120
+ const credentials = await loadDefaultCredentials(process.env, homedir());
121
+ if (!credentials)
122
+ throw new Error("Not logged in. Run vwork login first.");
123
+ const config = resolveCliConfigFromFlags(command.flags, process.env, process.cwd(), credentials.base_url);
124
+ assertStoredCredentialsMatchApiUrl(credentials, config.apiUrl);
125
+ process.stdout.write(await runWhoamiCommand({ apiUrl: config.apiUrl, credentials }));
126
+ return;
127
+ }
128
+ if (command.path.length === 1) {
129
+ this.log(resourceHelpText(command.path[0] ?? ""));
130
+ return;
131
+ }
132
+ const credentials = await loadDefaultCredentials(process.env, homedir());
133
+ const config = resolveCliConfigFromFlags(command.flags, process.env, process.cwd(), credentials?.base_url);
134
+ if (credentials)
135
+ assertStoredCredentialsMatchApiUrl(credentials, config.apiUrl);
136
+ const client = new PlatformClient({
137
+ apiUrl: config.apiUrl,
138
+ trustedUserId: credentials ? null : config.trustedUserId,
139
+ apiKey: credentials?.api_key ?? null
140
+ });
141
+ const result = await dispatchCommand(command, client, config, process.cwd());
142
+ if (!result)
143
+ throw new Error(`Unknown command: ${command.path.join(" ")}`);
144
+ const output = result.rows ?? (result.value === undefined ? [] : [result.value]);
145
+ const rendered = config.output === "json" ? renderJson(result.rows ?? result.value ?? null) : renderTable(output);
146
+ process.stdout.write(rendered);
147
+ }
148
+ }
149
+ export async function runOclifCli(argv) {
150
+ await VWorkCliCommand.run([...argv], { root: packageRoot() });
151
+ }
152
+ export function parsedCommandFromOclif(argv, flags) {
153
+ const path = [];
154
+ const positionals = [];
155
+ let commandStarted = false;
156
+ for (const token of argv) {
157
+ if (!commandStarted) {
158
+ if (COMMAND_ROOTS.has(token)) {
159
+ commandStarted = true;
160
+ path.push(token);
161
+ }
162
+ else {
163
+ positionals.push(token);
164
+ }
165
+ continue;
166
+ }
167
+ if (path.length < maxPathLength(path)) {
168
+ path.push(token);
169
+ }
170
+ else {
171
+ positionals.push(token);
172
+ }
173
+ }
174
+ return { path, positionals, flags };
175
+ }
176
+ function parsedFlags(flags) {
177
+ const result = {};
178
+ for (const [key, value] of Object.entries(flags)) {
179
+ if (value === undefined || value === false)
180
+ continue;
181
+ if (Array.isArray(value)) {
182
+ if (value.length > 0)
183
+ result[key] = value.map(String);
184
+ continue;
185
+ }
186
+ if (typeof value === "string" || typeof value === "boolean")
187
+ result[key] = value;
188
+ }
189
+ return result;
190
+ }
191
+ async function dispatchCommand(command, client, config, cwd) {
192
+ if (command.path[0] === "apps")
193
+ return runAppCommand(command, client);
194
+ if (command.path[0] === "codegen")
195
+ return runCodegenCommand(command, client, config);
196
+ if (command.path[0] === "db")
197
+ return runDbCommand(command, client, config);
198
+ if (command.path[0] === "kv")
199
+ return runKvCommand(command, client, config);
200
+ if (command.path[0] === "migrations")
201
+ return runMigrationCommand(command, client, config, cwd);
202
+ if (command.path[0] === "functions")
203
+ return runFunctionCommand(command, client, config, cwd);
204
+ if (command.path[0] === "observability")
205
+ return runObservabilityCommand(command, client);
206
+ if (command.path[0] === "queues")
207
+ return runQueueCommand(command, client, config);
208
+ if (command.path[0] === "runtime")
209
+ return runRuntimeCommand(command, client);
210
+ if (command.path[0] === "secrets")
211
+ return runSecretCommand(command, client, config);
212
+ return null;
213
+ }
214
+ function assertStoredCredentialsMatchApiUrl(credentials, apiUrl) {
215
+ if (normalizeApiBaseUrl(credentials.base_url) === normalizeApiBaseUrl(apiUrl))
216
+ return;
217
+ throw new Error(`Stored credentials are for ${normalizeApiBaseUrl(credentials.base_url)}. Run vwork login for ${normalizeApiBaseUrl(apiUrl)} before using that API URL.`);
218
+ }
219
+ function normalizeApiBaseUrl(value) {
220
+ return value.replace(/\/+$/, "");
221
+ }
222
+ function maxPathLength(path) {
223
+ if (path[0] === "db")
224
+ return 3;
225
+ if (path[0] === "codegen")
226
+ return 3;
227
+ if (path[0] === "kv")
228
+ return 3;
229
+ if (path[0] === "functions" && path[1] === "previews")
230
+ return 3;
231
+ if (path[0] === "functions" && path[1] === "queue-bindings")
232
+ return 3;
233
+ return 2;
234
+ }
235
+ function helpText() {
236
+ return [
237
+ "VWork CLI",
238
+ "",
239
+ "Usage: vwork <resource> <command> [flags]",
240
+ "",
241
+ "Resources:",
242
+ " apps list, inspect, create, delete, bind-data-source",
243
+ " db tables list, sql exec, sql preview, rows select|insert|update|delete",
244
+ " migrations list, runs, create, up, down, delete",
245
+ " functions list, inspect, create, deploy, invoke, artifacts, artifact, publish, rollback, metrics, previews, validate",
246
+ " codegen mobile from-schema, function generate, runs list|download|verify, skill install-curl",
247
+ " secrets list",
248
+ " kv namespaces list|create, entries list, stats, metrics",
249
+ " queues list, create, update, delete",
250
+ " runtime latest, publish",
251
+ " observability access-logs",
252
+ " login authenticate with VWork",
253
+ " whoami show the authenticated VWork user",
254
+ ""
255
+ ].join("\n");
256
+ }
257
+ function resourceHelpText(resource) {
258
+ const commands = {
259
+ apps: [
260
+ "list",
261
+ "inspect <app>",
262
+ "create --name <name> --slug <slug> [--host <host>] --data-source <id>",
263
+ "delete <app>",
264
+ "bind-data-source <app> --data-source <id>"
265
+ ],
266
+ db: [
267
+ "tables list --app <app>",
268
+ "sql exec --app <app> --sql <sql>",
269
+ "sql preview --app <app> --sql <sql>",
270
+ "rows select <table> --app <app>",
271
+ "rows insert <table> --app <app> --data <json>",
272
+ "rows update <table> --app <app> --pk <column=value> --data <json>",
273
+ "rows delete <table> --app <app> --pk <column=value>"
274
+ ],
275
+ migrations: [
276
+ "list --app <app>",
277
+ "runs --app <app>",
278
+ "create <version> --app <app> --up <file>",
279
+ "up <version> --app <app> --up <file>",
280
+ "down <version> --app <app> --reason <reason>",
281
+ "delete <version> --app <app>"
282
+ ],
283
+ functions: [
284
+ "list --app <app>",
285
+ "inspect <name> --app <app>",
286
+ "create <name> --app <app> --file <path>",
287
+ "deploy <name> --app <app> --bundle <path>",
288
+ "invoke <name> --app <app> --file <path>",
289
+ "artifact <name> --app <app> --file <path>",
290
+ "artifacts <name> --app <app>",
291
+ "publish <name> --app <app> --artifact <id>",
292
+ "rollback <name> --app <app> --artifact <id>",
293
+ "metrics <name> --app <app>",
294
+ "previews create <name> --app <app> --artifact <id>",
295
+ "queue-bindings list <name> --app <app>",
296
+ "queue-bindings replace <name> --app <app> --queue <queue_id:mode>",
297
+ "validate --file <path>"
298
+ ],
299
+ codegen: [
300
+ "mobile from-schema --app <app> [--schema <schema>] [--table <table>] [--prompt <text>]",
301
+ "function generate --app <app> --function <name> --prompt <text> [--source-code <code>] [--binding <name>...] [--repair] [--draft-invoke-body <json>] [--draft-invoke-bindings <json>]",
302
+ "skill install-curl [--base-url <raw-base-url>]",
303
+ "mcp config-snippet [--workspace <repo-path>] [--api-url <url>] [--app <app>] [--schema <schema>]",
304
+ "runs list --app <app>",
305
+ "runs download --app <app> --run <id> --out <zip>",
306
+ "runs verify --app <app> --run <id>"
307
+ ],
308
+ secrets: [
309
+ "list --app <app>"
310
+ ],
311
+ kv: [
312
+ "namespaces list --app <app>",
313
+ "namespaces create --app <app> --title <title>",
314
+ "entries list --app <app> --namespace <id> [--prefix <prefix>] [--limit <n>] [--cursor <cursor>]",
315
+ "stats --app <app> --namespace <id>",
316
+ "metrics --app <app> --namespace <id> [--range <range>] [--step <step>]"
317
+ ],
318
+ queues: [
319
+ "list --app <app>",
320
+ "create --app <app> --name <name> [--batch-size <n>] [--wait-seconds <n>]",
321
+ "update <queue-id> --app <app> [--batch-size <n>] [--dead-letter-enabled true|false]",
322
+ "delete <queue-id> --app <app>"
323
+ ],
324
+ runtime: [
325
+ "latest",
326
+ "publish"
327
+ ],
328
+ observability: [
329
+ "access-logs [--source <source>] [--app <app>] [--limit <n>]"
330
+ ],
331
+ login: [
332
+ "login"
333
+ ],
334
+ whoami: [
335
+ "whoami"
336
+ ]
337
+ };
338
+ const resourceCommands = commands[resource];
339
+ if (!resourceCommands)
340
+ return helpText();
341
+ return [
342
+ "VWork CLI",
343
+ "",
344
+ `Usage: vwork ${resource} <command> [flags]`,
345
+ "",
346
+ "Commands:",
347
+ ...resourceCommands.map((command) => ` ${command}`),
348
+ ""
349
+ ].join("\n");
350
+ }
351
+ function packageRoot() {
352
+ return fileURLToPath(new URL("..", import.meta.url));
353
+ }
354
+ function packageVersion() {
355
+ const parsed = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
356
+ return parsed.version ?? "0.0.0";
357
+ }
package/dist/output.js ADDED
@@ -0,0 +1,12 @@
1
+ export function renderJson(value) {
2
+ return `${JSON.stringify(value, null, 2)}\n`;
3
+ }
4
+ export function renderTable(rows) {
5
+ if (rows.length === 0)
6
+ return "No rows\n";
7
+ const headers = Object.keys(rows[0] ?? {});
8
+ const widths = headers.map((header) => Math.max(header.length, ...rows.map((row) => String(row[header] ?? "").length)));
9
+ const line = headers.map((header, index) => header.padEnd(widths[index] ?? header.length)).join(" ");
10
+ const body = rows.map((row) => headers.map((header, index) => String(row[header] ?? "").padEnd(widths[index] ?? header.length)).join(" "));
11
+ return `${[line, ...body].join("\n")}\n`;
12
+ }
package/dist/queues.js ADDED
@@ -0,0 +1,71 @@
1
+ import { stringFlag } from "./config.js";
2
+ export async function runQueueCommand(command, client, config) {
3
+ const action = command.path[1];
4
+ const app = await resolveApp(command, client, config);
5
+ const basePath = `/apps/${encodeURIComponent(String(app.id))}/queues`;
6
+ if (action === "list") {
7
+ const body = await client.request("GET", basePath);
8
+ return { rows: body.queues };
9
+ }
10
+ if (action === "create") {
11
+ return { value: await client.request("POST", basePath, queueMutationBody(command, true)) };
12
+ }
13
+ if (action === "update") {
14
+ const queueId = requiredPosition(command, 0, "queue id");
15
+ return { value: await client.request("PATCH", `${basePath}/${encodeURIComponent(queueId)}`, queueMutationBody(command, false)) };
16
+ }
17
+ if (action === "delete") {
18
+ const queueId = requiredPosition(command, 0, "queue id");
19
+ return { value: await client.request("DELETE", `${basePath}/${encodeURIComponent(queueId)}`) };
20
+ }
21
+ throw new Error(`Unknown queues command: ${action ?? ""}`);
22
+ }
23
+ async function resolveApp(command, client, config) {
24
+ const target = stringFlag(command.flags.app) ?? config.appId;
25
+ if (!target)
26
+ throw new Error("Missing --app");
27
+ return client.request("GET", `/apps/lookup/${encodeURIComponent(target)}`);
28
+ }
29
+ function queueMutationBody(command, requireName) {
30
+ const body = {};
31
+ const name = stringFlag(command.flags.name);
32
+ if (name)
33
+ body.name = name;
34
+ if (requireName && !name)
35
+ throw new Error("Missing --name");
36
+ copyNumberFlag(command, body, "max-messages", "max_messages");
37
+ copyNumberFlag(command, body, "queue-length", "max_messages");
38
+ copyNumberFlag(command, body, "max-message-bytes", "max_message_bytes");
39
+ copyNumberFlag(command, body, "message-bytes", "max_message_bytes");
40
+ copyNumberFlag(command, body, "max-delay-seconds", "max_delay_seconds");
41
+ copyNumberFlag(command, body, "max-delivery-attempts", "max_delivery_attempts");
42
+ copyNumberFlag(command, body, "retry-attempts", "max_delivery_attempts");
43
+ copyNumberFlag(command, body, "batch-size", "batch_size");
44
+ copyNumberFlag(command, body, "visibility-timeout-seconds", "visibility_timeout_seconds");
45
+ copyNumberFlag(command, body, "wait-seconds", "visibility_timeout_seconds");
46
+ copyBooleanFlag(command, body, "dead-letter-enabled", "dead_letter_enabled");
47
+ return body;
48
+ }
49
+ function copyNumberFlag(command, body, flag, key) {
50
+ const value = stringFlag(command.flags[flag]);
51
+ if (value === undefined)
52
+ return;
53
+ const parsed = Number(value);
54
+ if (!Number.isInteger(parsed))
55
+ throw new Error(`--${flag} must be an integer`);
56
+ body[key] = parsed;
57
+ }
58
+ function copyBooleanFlag(command, body, flag, key) {
59
+ const value = stringFlag(command.flags[flag]);
60
+ if (value === undefined)
61
+ return;
62
+ if (value !== "true" && value !== "false")
63
+ throw new Error(`--${flag} must be true or false`);
64
+ body[key] = value === "true";
65
+ }
66
+ function requiredPosition(command, index, label) {
67
+ const value = command.positionals[index];
68
+ if (!value)
69
+ throw new Error(`Missing ${label}`);
70
+ return value;
71
+ }
@@ -0,0 +1,11 @@
1
+ export async function runRuntimeCommand(command, client) {
2
+ const action = command.path[1];
3
+ if (action === "latest") {
4
+ const body = await client.request("GET", "/runtime-config/latest");
5
+ return { value: body.runtime_config };
6
+ }
7
+ if (action === "publish") {
8
+ return { value: await client.request("POST", "/runtime-config/publish") };
9
+ }
10
+ throw new Error(`Unknown runtime command: ${action ?? ""}`);
11
+ }
@@ -0,0 +1,16 @@
1
+ import { stringFlag } from "./config.js";
2
+ export async function runSecretCommand(command, client, config) {
3
+ const action = command.path[1];
4
+ if (action === "list") {
5
+ const app = await resolveApp(command, client, config);
6
+ const body = await client.request("GET", `/apps/${encodeURIComponent(String(app.id))}/secrets`);
7
+ return { rows: body.secrets };
8
+ }
9
+ throw new Error(`Unknown secrets command: ${action ?? ""}`);
10
+ }
11
+ async function resolveApp(command, client, config) {
12
+ const target = stringFlag(command.flags.app) ?? config.appId;
13
+ if (!target)
14
+ throw new Error("Missing --app");
15
+ return client.request("GET", `/apps/lookup/${encodeURIComponent(target)}`);
16
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@vwork/cli",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "files": [
6
+ "dist/*.js",
7
+ "dist/auth/*.js",
8
+ "package.json"
9
+ ],
10
+ "publishConfig": {
11
+ "registry": "https://registry.npmjs.org/"
12
+ },
13
+ "bin": {
14
+ "vwork": "dist/index.js"
15
+ },
16
+ "dependencies": {
17
+ "@oclif/core": "^4.11.7",
18
+ "@vwork/platform-client": "^0.1.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^22.10.2",
22
+ "tsx": "^4.19.2",
23
+ "typescript": "^5.7.2"
24
+ },
25
+ "scripts": {
26
+ "dev": "tsx src/index.ts",
27
+ "build": "tsc -p tsconfig.json",
28
+ "test": "pnpm --filter @vwork/platform-client build && tsx --test src/test/*.test.ts",
29
+ "typecheck": "pnpm --filter @vwork/platform-client build && tsc -p tsconfig.json --noEmit"
30
+ }
31
+ }