@getjack/jack 0.1.15 → 0.1.17

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 CHANGED
@@ -7,7 +7,7 @@
7
7
  <a href="https://www.npmjs.com/package/@getjack/jack"><img src="https://img.shields.io/npm/v/@getjack/jack" alt="npm"></a>
8
8
  <a href="https://github.com/getjack-org/jack/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-blue" alt="license"></a>
9
9
  <a href="https://docs.getjack.org"><img src="https://img.shields.io/badge/docs-getjack.org-green" alt="docs"></a>
10
- <a href="https://discord.gg/fb64krv48R"><img src="https://img.shields.io/badge/discord-community-5865F2" alt="discord"></a>
10
+ <a href="https://community.getjack.org"><img src="https://img.shields.io/badge/discord-community-5865F2" alt="discord"></a>
11
11
  </p>
12
12
 
13
13
  ---
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@getjack/jack",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Ship before you forget why you started. The vibecoder's deployment CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "jack": "./src/index.ts"
9
9
  },
10
- "files": ["src", "templates"],
10
+ "files": [
11
+ "src",
12
+ "templates"
13
+ ],
11
14
  "engines": {
12
15
  "bun": ">=1.0.0"
13
16
  },
@@ -41,6 +44,7 @@
41
44
  "bun-types": "latest"
42
45
  },
43
46
  "dependencies": {
47
+ "@clack/prompts": "^0.11.0",
44
48
  "@modelcontextprotocol/sdk": "^1.25.1",
45
49
  "archiver": "^7.0.1",
46
50
  "fflate": "^0.8.2",
@@ -72,7 +72,8 @@ export default async function clone(projectName?: string, flags: CloneFlags = {}
72
72
 
73
73
  if (!result.success) {
74
74
  downloadSpin.error("Clone failed");
75
- error(result.error || "Unknown error");
75
+ error(result.error || "Could not download project files");
76
+ info("Check your network connection and try again");
76
77
  process.exit(1);
77
78
  }
78
79
 
@@ -0,0 +1,47 @@
1
+ /**
2
+ * jack community - Open the jack community Discord
3
+ */
4
+
5
+ import { $ } from "bun";
6
+ import { error } from "../lib/output.ts";
7
+
8
+ const COMMUNITY_URL = "https://community.getjack.org";
9
+
10
+ export default async function community(): Promise<void> {
11
+ console.error("");
12
+ console.error(" Chat with other vibecoders and the jack team.");
13
+ console.error("");
14
+ console.error(` Press Enter to open the browser or visit ${COMMUNITY_URL}`);
15
+
16
+ // Wait for Enter key
17
+ await waitForEnter();
18
+
19
+ // Open browser using platform-specific command
20
+ const cmd =
21
+ process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
22
+
23
+ try {
24
+ await $`${cmd} ${COMMUNITY_URL}`;
25
+ } catch (err) {
26
+ error(`Failed to open browser: ${err instanceof Error ? err.message : String(err)}`);
27
+ console.error(` Visit: ${COMMUNITY_URL}`);
28
+ }
29
+ }
30
+
31
+ async function waitForEnter(): Promise<void> {
32
+ return new Promise((resolve) => {
33
+ if (!process.stdin.isTTY) {
34
+ // Non-interactive, just resolve immediately
35
+ resolve();
36
+ return;
37
+ }
38
+
39
+ process.stdin.setRawMode(true);
40
+ process.stdin.resume();
41
+ process.stdin.once("data", () => {
42
+ process.stdin.setRawMode(false);
43
+ process.stdin.pause();
44
+ resolve();
45
+ });
46
+ });
47
+ }
@@ -100,27 +100,10 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
100
100
  }
101
101
  }
102
102
 
103
+ // Track auth state for final messaging
104
+ let authState: Awaited<ReturnType<typeof getAuthState>> | null = null;
103
105
  if (!resolved && !link) {
104
- // Project not found - provide auth-aware messaging
105
- const authState = await getAuthState();
106
-
107
- if (authState === "logged-in") {
108
- // We're logged in and control plane didn't find it - definitely not a managed project
109
- warn(`Project '${name}' not tracked by jack`);
110
- info("Checking your Cloudflare account...");
111
- } else if (authState === "session-expired") {
112
- // Session expired - can't verify if this is a managed project
113
- warn(`Project '${name}' not found locally`);
114
- info("Session expired - can't check jack cloud");
115
- info("If this was deployed via jack, run: jack login");
116
- info("Checking your Cloudflare account...");
117
- } else {
118
- // Not logged in - can't verify if this is a managed project
119
- warn(`Project '${name}' not found locally`);
120
- info("Not logged in - can't check jack cloud");
121
- info("If this was deployed via jack, run: jack login");
122
- info("Checking your Cloudflare account...");
123
- }
106
+ authState = await getAuthState();
124
107
  }
125
108
 
126
109
  // Check if this is a managed project (from link or resolved data)
@@ -153,8 +136,14 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
153
136
 
154
137
  if (!workerExists) {
155
138
  console.error("");
156
- warn(`'${name}' is not deployed`);
157
- info("Nothing to undeploy");
139
+ error(`Project '${name}' not found`);
140
+ if (authState === "session-expired") {
141
+ info("Session expired. Run: jack login");
142
+ } else if (authState === "not-logged-in") {
143
+ info("Not logged in. Run: jack login");
144
+ } else {
145
+ info("Run jack projects to see your projects");
146
+ }
158
147
  return;
159
148
  }
160
149
 
@@ -46,11 +46,11 @@ export default async function link(projectName?: string, flags: LinkFlags = {}):
46
46
  // BYO mode - generate local ID
47
47
  if (flags.byo) {
48
48
  const projectId = generateByoProjectId();
49
- output.start("Creating BYO link...");
49
+ output.start("Linking to your Cloudflare account...");
50
50
  await linkProject(process.cwd(), projectId, "byo");
51
51
  await registerPath(projectId, process.cwd());
52
52
  output.stop();
53
- success("Linked as BYO project");
53
+ success("Linked to your Cloudflare account");
54
54
  info(`Project ID: ${projectId}`);
55
55
  return;
56
56
  }
@@ -62,7 +62,7 @@ export default async function link(projectName?: string, flags: LinkFlags = {}):
62
62
  // Not logged in and no project name - suggest options
63
63
  error("Not logged in to jack cloud");
64
64
  info("Login with: jack login");
65
- info("Or create a BYO link: jack link --byo");
65
+ info("Or link to your Cloudflare account: jack link --byo");
66
66
  process.exit(1);
67
67
  }
68
68
 
@@ -118,9 +118,9 @@ export default async function link(projectName?: string, flags: LinkFlags = {}):
118
118
  output.stop();
119
119
 
120
120
  if (projects.length === 0) {
121
- error("No managed projects found");
121
+ error("No projects found");
122
122
  info("Create one with: jack new");
123
- info("Or link as BYO: jack link --byo");
123
+ info("Or link to your Cloudflare account: jack link --byo");
124
124
  process.exit(1);
125
125
  }
126
126
 
@@ -448,7 +448,7 @@ async function removeProjectEntry(args: string[]): Promise<void> {
448
448
  // Warn if still deployed
449
449
  if (project.status === "live") {
450
450
  console.error("");
451
- warn("Project is still deployed; removal does not undeploy the worker");
451
+ warn("Project is still live; this only removes it from jack, not from Cloudflare");
452
452
  }
453
453
 
454
454
  console.error("");
@@ -496,7 +496,7 @@ async function removeProjectEntry(args: string[]): Promise<void> {
496
496
  // Hint about undeploying
497
497
  if (project.status === "live") {
498
498
  console.error("");
499
- info(`To undeploy the worker, run: jack down ${name}`);
499
+ info(`To take it offline: jack down ${name}`);
500
500
  }
501
501
  }
502
502
 
@@ -12,8 +12,8 @@ export default async function publish(): Promise<void> {
12
12
  }
13
13
 
14
14
  if (link.deploy_mode !== "managed") {
15
- output.error("Only managed projects can be published");
16
- output.info("Projects deployed with BYOC (bring your own cloud) cannot be published");
15
+ output.error("Only jack cloud projects can be published");
16
+ output.info("Projects on your own Cloudflare account cannot be published");
17
17
  process.exit(1);
18
18
  }
19
19
 
@@ -1,10 +1,17 @@
1
- import { join } from "node:path";
1
+ import { existsSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
2
3
  import { fetchProjectResources } from "../lib/control-plane.ts";
3
4
  import { formatSize } from "../lib/format.ts";
4
5
  import { error, info, item, output as outputSpinner, success, warn } from "../lib/output.ts";
5
6
  import { readProjectLink } from "../lib/project-link.ts";
6
7
  import { parseWranglerResources } from "../lib/resources.ts";
7
8
  import { createDatabase } from "../lib/services/db-create.ts";
9
+ import {
10
+ DestructiveOperationError,
11
+ WriteNotAllowedError,
12
+ executeSql,
13
+ executeSqlFile,
14
+ } from "../lib/services/db-execute.ts";
8
15
  import { listDatabases } from "../lib/services/db-list.ts";
9
16
  import {
10
17
  deleteDatabase,
@@ -12,6 +19,7 @@ import {
12
19
  generateExportFilename,
13
20
  getDatabaseInfo as getWranglerDatabaseInfo,
14
21
  } from "../lib/services/db.ts";
22
+ import { getRiskDescription } from "../lib/services/sql-classifier.ts";
15
23
  import { getProjectNameFromDir } from "../lib/storage/index.ts";
16
24
  import { Events, track } from "../lib/telemetry.ts";
17
25
 
@@ -125,13 +133,19 @@ function showDbHelp(): void {
125
133
  console.error(" info Show database information (default)");
126
134
  console.error(" create Create a new database");
127
135
  console.error(" list List all databases in the project");
136
+ console.error(" execute Execute SQL against the database");
128
137
  console.error(" export Export database to SQL file");
129
138
  console.error(" delete Delete a database");
130
139
  console.error("");
131
140
  console.error("Examples:");
132
- console.error(" jack services db Show info about the default database");
133
- console.error(" jack services db create Create a new database");
134
- console.error(" jack services db list List all databases");
141
+ console.error(
142
+ " jack services db Show info about the default database",
143
+ );
144
+ console.error(" jack services db create Create a new database");
145
+ console.error(" jack services db list List all databases");
146
+ console.error(' jack services db execute "SELECT * FROM users" Run a read query');
147
+ console.error(' jack services db execute "INSERT..." --write Run a write query');
148
+ console.error(" jack services db execute --file schema.sql --write Run SQL from file");
135
149
  console.error("");
136
150
  }
137
151
 
@@ -149,13 +163,15 @@ async function dbCommand(args: string[], options: ServiceOptions): Promise<void>
149
163
  return await dbCreate(args.slice(1), options);
150
164
  case "list":
151
165
  return await dbList(options);
166
+ case "execute":
167
+ return await dbExecute(args.slice(1), options);
152
168
  case "export":
153
169
  return await dbExport(options);
154
170
  case "delete":
155
171
  return await dbDelete(options);
156
172
  default:
157
173
  error(`Unknown action: ${action}`);
158
- info("Available: info, create, list, export, delete");
174
+ info("Available: info, create, list, execute, export, delete");
159
175
  process.exit(1);
160
176
  }
161
177
  }
@@ -186,9 +202,8 @@ async function dbInfo(options: ServiceOptions): Promise<void> {
186
202
 
187
203
  if (!dbInfo) {
188
204
  console.error("");
189
- error("No database found for this project.");
190
- info("For managed projects, create a database with: jack services db create");
191
- info("For BYO projects, add d1_databases to your wrangler.jsonc");
205
+ error("No database found for this project");
206
+ info("Create one with: jack services db create");
192
207
  console.error("");
193
208
  return;
194
209
  }
@@ -228,9 +243,8 @@ async function dbExport(options: ServiceOptions): Promise<void> {
228
243
 
229
244
  if (!dbInfo) {
230
245
  console.error("");
231
- error("No database found for this project.");
232
- info("For managed projects, create a database with: jack services db create");
233
- info("For BYO projects, add d1_databases to your wrangler.jsonc");
246
+ error("No database found for this project");
247
+ info("Create one with: jack services db create");
234
248
  console.error("");
235
249
  return;
236
250
  }
@@ -267,9 +281,8 @@ async function dbDelete(options: ServiceOptions): Promise<void> {
267
281
 
268
282
  if (!dbInfo) {
269
283
  console.error("");
270
- error("No database found for this project.");
271
- info("For managed projects, create a database with: jack services db create");
272
- info("For BYO projects, add d1_databases to your wrangler.jsonc");
284
+ error("No database found for this project");
285
+ info("Create one with: jack services db create");
273
286
  console.error("");
274
287
  return;
275
288
  }
@@ -430,3 +443,251 @@ async function dbList(options: ServiceOptions): Promise<void> {
430
443
  process.exit(1);
431
444
  }
432
445
  }
446
+
447
+ /**
448
+ * Parse execute command arguments
449
+ * Supports:
450
+ * jack services db execute "SELECT * FROM users"
451
+ * jack services db execute "INSERT..." --write
452
+ * jack services db execute --file schema.sql --write
453
+ * jack services db execute --db my-other-db "SELECT..."
454
+ */
455
+ interface ExecuteArgs {
456
+ sql?: string;
457
+ filePath?: string;
458
+ allowWrite: boolean;
459
+ databaseName?: string;
460
+ }
461
+
462
+ function parseExecuteArgs(args: string[]): ExecuteArgs {
463
+ const result: ExecuteArgs = {
464
+ allowWrite: false,
465
+ };
466
+
467
+ for (let i = 0; i < args.length; i++) {
468
+ const arg = args[i];
469
+ if (!arg) continue;
470
+
471
+ if (arg === "--write" || arg === "-w") {
472
+ result.allowWrite = true;
473
+ continue;
474
+ }
475
+
476
+ if (arg === "--file" || arg === "-f") {
477
+ result.filePath = args[i + 1];
478
+ i++; // Skip the next arg
479
+ continue;
480
+ }
481
+
482
+ if (arg.startsWith("--file=")) {
483
+ result.filePath = arg.slice("--file=".length);
484
+ continue;
485
+ }
486
+
487
+ if (arg === "--db" || arg === "--database") {
488
+ result.databaseName = args[i + 1];
489
+ i++; // Skip the next arg
490
+ continue;
491
+ }
492
+
493
+ if (arg.startsWith("--db=")) {
494
+ result.databaseName = arg.slice("--db=".length);
495
+ continue;
496
+ }
497
+
498
+ if (arg.startsWith("--database=")) {
499
+ result.databaseName = arg.slice("--database=".length);
500
+ continue;
501
+ }
502
+
503
+ // Any other non-flag argument is the SQL query
504
+ if (!arg.startsWith("-")) {
505
+ result.sql = arg;
506
+ }
507
+ }
508
+
509
+ return result;
510
+ }
511
+
512
+ /**
513
+ * Execute SQL against the database
514
+ */
515
+ async function dbExecute(args: string[], _options: ServiceOptions): Promise<void> {
516
+ const execArgs = parseExecuteArgs(args);
517
+
518
+ // Validate input
519
+ if (!execArgs.sql && !execArgs.filePath) {
520
+ console.error("");
521
+ error("No SQL provided");
522
+ info('Usage: jack services db execute "SELECT * FROM users"');
523
+ info(" jack services db execute --file schema.sql --write");
524
+ console.error("");
525
+ process.exit(1);
526
+ }
527
+
528
+ // Cannot specify both SQL and file
529
+ if (execArgs.sql && execArgs.filePath) {
530
+ console.error("");
531
+ error("Cannot specify both inline SQL and --file");
532
+ info("Use either inline SQL or --file, not both");
533
+ console.error("");
534
+ process.exit(1);
535
+ }
536
+
537
+ // If using --file, verify file exists
538
+ if (execArgs.filePath) {
539
+ const absPath = resolve(process.cwd(), execArgs.filePath);
540
+ if (!existsSync(absPath)) {
541
+ console.error("");
542
+ error(`File not found: ${execArgs.filePath}`);
543
+ console.error("");
544
+ process.exit(1);
545
+ }
546
+ execArgs.filePath = absPath;
547
+ }
548
+
549
+ const projectDir = process.cwd();
550
+
551
+ try {
552
+ outputSpinner.start("Executing SQL...");
553
+
554
+ let result;
555
+ if (execArgs.filePath) {
556
+ result = await executeSqlFile({
557
+ projectDir,
558
+ filePath: execArgs.filePath,
559
+ databaseName: execArgs.databaseName,
560
+ allowWrite: execArgs.allowWrite,
561
+ interactive: true,
562
+ });
563
+ } else {
564
+ result = await executeSql({
565
+ projectDir,
566
+ sql: execArgs.sql!,
567
+ databaseName: execArgs.databaseName,
568
+ allowWrite: execArgs.allowWrite,
569
+ interactive: true,
570
+ });
571
+ }
572
+
573
+ // Handle destructive operations - need confirmation BEFORE execution
574
+ if (result.requiresConfirmation) {
575
+ outputSpinner.stop();
576
+
577
+ // Find the destructive statements
578
+ const destructiveStmts = result.statements.filter((s) => s.risk === "destructive");
579
+
580
+ console.error("");
581
+ warn("This SQL contains destructive operations:");
582
+ for (const stmt of destructiveStmts) {
583
+ item(`${stmt.operation}: ${stmt.sql.slice(0, 60)}${stmt.sql.length > 60 ? "..." : ""}`);
584
+ }
585
+ console.error("");
586
+
587
+ // Require typed confirmation
588
+ const { text } = await import("@clack/prompts");
589
+ const confirmText = destructiveStmts
590
+ .map((s) => s.operation)
591
+ .join(" ")
592
+ .toUpperCase();
593
+
594
+ const userInput = await text({
595
+ message: `Type "${confirmText}" to confirm:`,
596
+ validate: (value) => {
597
+ if (value.toUpperCase() !== confirmText) {
598
+ return `Please type "${confirmText}" exactly to confirm`;
599
+ }
600
+ },
601
+ });
602
+
603
+ if (typeof userInput !== "string") {
604
+ info("Cancelled");
605
+ return;
606
+ }
607
+
608
+ // NOW execute with confirmation (interactive: false means "already confirmed")
609
+ outputSpinner.start("Executing SQL...");
610
+ if (execArgs.filePath) {
611
+ result = await executeSqlFile({
612
+ projectDir,
613
+ filePath: execArgs.filePath,
614
+ databaseName: execArgs.databaseName,
615
+ allowWrite: true,
616
+ interactive: false, // Already confirmed, execute now
617
+ });
618
+ } else {
619
+ result = await executeSql({
620
+ projectDir,
621
+ sql: execArgs.sql!,
622
+ databaseName: execArgs.databaseName,
623
+ allowWrite: true,
624
+ interactive: false, // Already confirmed, execute now
625
+ });
626
+ }
627
+ }
628
+
629
+ outputSpinner.stop();
630
+
631
+ if (!result.success) {
632
+ console.error("");
633
+ error(result.error || "SQL execution failed");
634
+ console.error("");
635
+ process.exit(1);
636
+ }
637
+
638
+ // Show results
639
+ console.error("");
640
+ success(`SQL executed (${getRiskDescription(result.risk)})`);
641
+
642
+ if (result.meta?.changes !== undefined && result.meta.changes > 0) {
643
+ item(`Rows affected: ${result.meta.changes}`);
644
+ }
645
+
646
+ if (result.meta?.duration_ms !== undefined) {
647
+ item(`Duration: ${result.meta.duration_ms}ms`);
648
+ }
649
+
650
+ if (result.warning) {
651
+ console.error("");
652
+ warn(result.warning);
653
+ }
654
+
655
+ // Output query results
656
+ if (result.results && result.results.length > 0) {
657
+ console.error("");
658
+ console.log(JSON.stringify(result.results, null, 2));
659
+ }
660
+ console.error("");
661
+
662
+ // Track telemetry
663
+ track(Events.SQL_EXECUTED, {
664
+ risk_level: result.risk,
665
+ statement_count: result.statements.length,
666
+ from_file: !!execArgs.filePath,
667
+ });
668
+ } catch (err) {
669
+ outputSpinner.stop();
670
+
671
+ if (err instanceof WriteNotAllowedError) {
672
+ console.error("");
673
+ error(err.message);
674
+ info("Add the --write flag to allow data modification:");
675
+ info(` jack services db execute "${execArgs.sql || `--file ${execArgs.filePath}`}" --write`);
676
+ console.error("");
677
+ process.exit(1);
678
+ }
679
+
680
+ if (err instanceof DestructiveOperationError) {
681
+ console.error("");
682
+ error(err.message);
683
+ info("Destructive operations require confirmation via CLI.");
684
+ console.error("");
685
+ process.exit(1);
686
+ }
687
+
688
+ console.error("");
689
+ error(`SQL execution failed: ${err instanceof Error ? err.message : String(err)}`);
690
+ console.error("");
691
+ process.exit(1);
692
+ }
693
+ }
@@ -20,8 +20,8 @@ export default async function sync(flags: SyncFlags = {}): Promise<void> {
20
20
 
21
21
  // Check for wrangler config
22
22
  if (!hasWranglerConfig()) {
23
- error("No wrangler config found in current directory");
24
- error("Run: jack new <project-name>");
23
+ error("Not in a project directory");
24
+ info("Run jack new <name> to create a project");
25
25
  process.exit(1);
26
26
  }
27
27
 
@@ -39,10 +39,12 @@ export default async function sync(flags: SyncFlags = {}): Promise<void> {
39
39
  const result = await syncToCloud(projectDir, { force, dryRun, verbose });
40
40
 
41
41
  if (!result.success) {
42
- syncSpin.error("Sync failed");
42
+ syncSpin.stop();
43
+ error("Sync failed");
43
44
  if (result.error) {
44
- error(result.error);
45
+ info(result.error);
45
46
  }
47
+ info("Check your network connection and try again");
46
48
  process.exit(1);
47
49
  }
48
50
 
package/src/index.ts CHANGED
@@ -49,6 +49,7 @@ const cli = meow(
49
49
  mcp MCP server for AI agents
50
50
  telemetry Usage data settings
51
51
  feedback Share feedback or report issues
52
+ community Join the jack Discord
52
53
 
53
54
  Run 'jack <command> --help' for command-specific options.
54
55
 
@@ -370,6 +371,12 @@ try {
370
371
  await withTelemetry("feedback", feedback)();
371
372
  break;
372
373
  }
374
+ case "community":
375
+ case "discord": {
376
+ const { default: community } = await import("./commands/community.ts");
377
+ await withTelemetry("community", community)();
378
+ break;
379
+ }
373
380
  case "link": {
374
381
  const { default: link } = await import("./commands/link.ts");
375
382
  await withTelemetry("link", link)(args[0], { byo: cli.flags.byo });
@@ -217,11 +217,11 @@ export async function buildProject(options: BuildOptions): Promise<BuildOutput>
217
217
 
218
218
  if (dryRunResult.exitCode !== 0) {
219
219
  reporter?.stop();
220
- reporter?.error("Worker build failed");
220
+ reporter?.error("Build failed");
221
221
  throw new JackError(
222
222
  JackErrorCode.BUILD_FAILED,
223
- "Worker build failed",
224
- "Check your wrangler.jsonc and worker code for errors",
223
+ "Build failed",
224
+ "Check your code for syntax errors",
225
225
  {
226
226
  exitCode: dryRunResult.exitCode,
227
227
  stderr: dryRunResult.stderr.toString(),
package/src/lib/hooks.ts CHANGED
@@ -478,6 +478,26 @@ const actionHandlers: {
478
478
  if (action.successMessage) {
479
479
  ui.success(substituteVars(action.successMessage, context));
480
480
  }
481
+
482
+ // Redeploy if deployAfter is set and we have a valid project directory
483
+ if (action.deployAfter && context.projectDir) {
484
+ const deployMsg = action.deployMessage || "Deploying...";
485
+ ui.info(deployMsg);
486
+
487
+ const proc = Bun.spawn(["wrangler", "deploy"], {
488
+ cwd: context.projectDir,
489
+ stdout: "ignore",
490
+ stderr: "pipe",
491
+ });
492
+ await proc.exited;
493
+
494
+ if (proc.exitCode === 0) {
495
+ ui.success("Deployed");
496
+ } else {
497
+ const stderr = await new Response(proc.stderr).text();
498
+ ui.warn(`Deploy failed: ${stderr.slice(0, 200)}`);
499
+ }
500
+ }
481
501
  }
482
502
 
483
503
  return true;