@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 +1 -1
- package/package.json +6 -2
- package/src/commands/clone.ts +2 -1
- package/src/commands/community.ts +47 -0
- package/src/commands/down.ts +11 -22
- package/src/commands/link.ts +5 -5
- package/src/commands/projects.ts +2 -2
- package/src/commands/publish.ts +2 -2
- package/src/commands/services.ts +275 -14
- package/src/commands/sync.ts +6 -4
- package/src/index.ts +7 -0
- package/src/lib/build-helper.ts +3 -3
- package/src/lib/hooks.ts +20 -0
- package/src/lib/project-operations.ts +278 -31
- package/src/lib/services/db-execute.ts +485 -0
- package/src/lib/services/sql-classifier.test.ts +404 -0
- package/src/lib/services/sql-classifier.ts +346 -0
- package/src/lib/storage/file-filter.ts +4 -0
- package/src/lib/telemetry.ts +3 -0
- package/src/lib/wrangler-config.test.ts +322 -0
- package/src/lib/wrangler-config.ts +459 -0
- package/src/lib/wrangler.ts +2 -0
- package/src/mcp/tools/index.ts +161 -0
- package/src/mcp/utils.ts +1 -1
- package/src/templates/index.ts +4 -0
- package/src/templates/types.ts +12 -0
- package/templates/api/AGENTS.md +33 -0
- package/templates/hello/AGENTS.md +33 -0
- package/templates/miniapp/.jack.json +4 -5
- package/templates/miniapp/AGENTS.md +33 -0
- package/templates/nextjs/AGENTS.md +33 -0
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://
|
|
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.
|
|
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": [
|
|
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",
|
package/src/commands/clone.ts
CHANGED
|
@@ -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 || "
|
|
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
|
+
}
|
package/src/commands/down.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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
|
|
package/src/commands/link.ts
CHANGED
|
@@ -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("
|
|
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
|
|
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
|
|
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
|
|
121
|
+
error("No projects found");
|
|
122
122
|
info("Create one with: jack new");
|
|
123
|
-
info("Or link
|
|
123
|
+
info("Or link to your Cloudflare account: jack link --byo");
|
|
124
124
|
process.exit(1);
|
|
125
125
|
}
|
|
126
126
|
|
package/src/commands/projects.ts
CHANGED
|
@@ -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
|
|
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
|
|
499
|
+
info(`To take it offline: jack down ${name}`);
|
|
500
500
|
}
|
|
501
501
|
}
|
|
502
502
|
|
package/src/commands/publish.ts
CHANGED
|
@@ -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
|
|
16
|
-
output.info("Projects
|
|
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
|
|
package/src/commands/services.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import {
|
|
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(
|
|
133
|
-
|
|
134
|
-
|
|
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("
|
|
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("
|
|
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("
|
|
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
|
+
}
|
package/src/commands/sync.ts
CHANGED
|
@@ -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("
|
|
24
|
-
|
|
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.
|
|
42
|
+
syncSpin.stop();
|
|
43
|
+
error("Sync failed");
|
|
43
44
|
if (result.error) {
|
|
44
|
-
|
|
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 });
|
package/src/lib/build-helper.ts
CHANGED
|
@@ -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("
|
|
220
|
+
reporter?.error("Build failed");
|
|
221
221
|
throw new JackError(
|
|
222
222
|
JackErrorCode.BUILD_FAILED,
|
|
223
|
-
"
|
|
224
|
-
"Check your
|
|
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;
|