@getjack/jack 0.1.14 → 0.1.16
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/package.json +2 -2
- package/src/commands/clone.ts +8 -7
- package/src/commands/down.ts +12 -5
- package/src/commands/link.ts +13 -8
- package/src/commands/projects.ts +2 -2
- package/src/commands/publish.ts +2 -2
- package/src/commands/secrets.ts +11 -14
- package/src/commands/services.ts +154 -14
- package/src/commands/sync.ts +6 -4
- package/src/commands/update.ts +53 -0
- package/src/index.ts +31 -0
- package/src/lib/auth/login-flow.ts +11 -3
- package/src/lib/build-helper.ts +3 -3
- package/src/lib/control-plane.ts +47 -0
- package/src/lib/hooks.ts +22 -45
- package/src/lib/project-operations.ts +19 -11
- package/src/lib/prompts.ts +23 -21
- package/src/lib/services/db-create.ts +187 -0
- package/src/lib/services/db-list.ts +56 -0
- package/src/lib/version-check.ts +170 -0
- package/src/lib/wrangler.ts +2 -0
- package/src/mcp/resources/index.ts +32 -0
- package/src/mcp/tools/index.ts +131 -1
- package/src/mcp/utils.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getjack/jack",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
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",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"bun-types": "latest"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@
|
|
44
|
+
"@clack/prompts": "^0.11.0",
|
|
45
45
|
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
46
46
|
"archiver": "^7.0.1",
|
|
47
47
|
"fflate": "^0.8.2",
|
package/src/commands/clone.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import { select } from "@
|
|
3
|
+
import { isCancel, select } from "@clack/prompts";
|
|
4
4
|
import { fetchProjectTags } from "../lib/control-plane.ts";
|
|
5
5
|
import { formatSize } from "../lib/format.ts";
|
|
6
6
|
import { box, error, info, spinner, success } from "../lib/output.ts";
|
|
@@ -35,14 +35,14 @@ export default async function clone(projectName?: string, flags: CloneFlags = {}
|
|
|
35
35
|
// Prompt user for action
|
|
36
36
|
const action = await select({
|
|
37
37
|
message: `Directory ${flags.as ?? projectName} already exists. What would you like to do?`,
|
|
38
|
-
|
|
39
|
-
{ value: "overwrite",
|
|
40
|
-
{ value: "merge",
|
|
41
|
-
{ value: "cancel",
|
|
38
|
+
options: [
|
|
39
|
+
{ value: "overwrite", label: "Overwrite (delete and recreate)" },
|
|
40
|
+
{ value: "merge", label: "Merge (keep existing files)" },
|
|
41
|
+
{ value: "cancel", label: "Cancel" },
|
|
42
42
|
],
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
if (action === "cancel") {
|
|
45
|
+
if (isCancel(action) || action === "cancel") {
|
|
46
46
|
info("Clone cancelled");
|
|
47
47
|
process.exit(0);
|
|
48
48
|
}
|
|
@@ -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
|
|
package/src/commands/down.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
+
import { getAuthState } from "../lib/auth/index.ts";
|
|
2
3
|
import {
|
|
3
4
|
checkWorkerExists,
|
|
4
5
|
deleteDatabase,
|
|
@@ -99,10 +100,10 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
|
|
103
|
+
// Track auth state for final messaging
|
|
104
|
+
let authState: Awaited<ReturnType<typeof getAuthState>> | null = null;
|
|
102
105
|
if (!resolved && !link) {
|
|
103
|
-
|
|
104
|
-
warn(`Project '${name}' not found`);
|
|
105
|
-
info("Will attempt to undeploy if deployed");
|
|
106
|
+
authState = await getAuthState();
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
// Check if this is a managed project (from link or resolved data)
|
|
@@ -135,8 +136,14 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
135
136
|
|
|
136
137
|
if (!workerExists) {
|
|
137
138
|
console.error("");
|
|
138
|
-
|
|
139
|
-
|
|
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
|
+
}
|
|
140
147
|
return;
|
|
141
148
|
}
|
|
142
149
|
|
package/src/commands/link.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
|
-
import { select } from "@
|
|
11
|
+
import { isCancel, select } from "@clack/prompts";
|
|
12
12
|
import { isLoggedIn } from "../lib/auth/index.ts";
|
|
13
13
|
import {
|
|
14
14
|
type ManagedProject,
|
|
@@ -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,21 +118,26 @@ 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
|
|
|
127
127
|
console.error("");
|
|
128
128
|
const choice = await select({
|
|
129
129
|
message: "Select a project to link:",
|
|
130
|
-
|
|
130
|
+
options: projects.map((p) => ({
|
|
131
131
|
value: p.id,
|
|
132
|
-
|
|
132
|
+
label: `${p.slug} (${p.status})`,
|
|
133
133
|
})),
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
+
if (isCancel(choice)) {
|
|
137
|
+
info("Cancelled");
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
140
|
+
|
|
136
141
|
const selected = projects.find((p) => p.id === choice);
|
|
137
142
|
if (!selected) {
|
|
138
143
|
error("No project selected");
|
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/secrets.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* For BYO projects: uses wrangler secret commands.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { password as passwordPrompt } from "@
|
|
9
|
+
import { isCancel, password as passwordPrompt } from "@clack/prompts";
|
|
10
10
|
import { $ } from "bun";
|
|
11
11
|
import { getControlApiUrl } from "../lib/control-plane.ts";
|
|
12
12
|
import { JackError, JackErrorCode } from "../lib/errors.ts";
|
|
@@ -96,22 +96,19 @@ async function resolveProjectContext(options: SecretsOptions): Promise<{
|
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
98
|
* Read a secret value interactively without echoing
|
|
99
|
-
* Uses @
|
|
99
|
+
* Uses @clack/prompts password for robust handling of typing, pasting, and TTY
|
|
100
100
|
*/
|
|
101
101
|
async function readSecretInteractive(keyName: string): Promise<string> {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// Handle Ctrl+C / Escape - inquirer throws ExitPromptError
|
|
110
|
-
if (err instanceof Error && err.name === "ExitPromptError") {
|
|
111
|
-
throw new Error("Cancelled");
|
|
112
|
-
}
|
|
113
|
-
throw err;
|
|
102
|
+
const value = await passwordPrompt({
|
|
103
|
+
message: `Enter value for ${keyName}`,
|
|
104
|
+
mask: "*",
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
if (isCancel(value)) {
|
|
108
|
+
throw new Error("Cancelled");
|
|
114
109
|
}
|
|
110
|
+
|
|
111
|
+
return value;
|
|
115
112
|
}
|
|
116
113
|
|
|
117
114
|
/**
|
package/src/commands/services.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { fetchProjectResources } from "../lib/control-plane.ts";
|
|
3
3
|
import { formatSize } from "../lib/format.ts";
|
|
4
|
-
import { promptSelect } from "../lib/hooks.ts";
|
|
5
4
|
import { error, info, item, output as outputSpinner, success, warn } from "../lib/output.ts";
|
|
6
5
|
import { readProjectLink } from "../lib/project-link.ts";
|
|
7
6
|
import { parseWranglerResources } from "../lib/resources.ts";
|
|
7
|
+
import { createDatabase } from "../lib/services/db-create.ts";
|
|
8
|
+
import { listDatabases } from "../lib/services/db-list.ts";
|
|
8
9
|
import {
|
|
9
10
|
deleteDatabase,
|
|
10
11
|
exportDatabase,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
getDatabaseInfo as getWranglerDatabaseInfo,
|
|
13
14
|
} from "../lib/services/db.ts";
|
|
14
15
|
import { getProjectNameFromDir } from "../lib/storage/index.ts";
|
|
16
|
+
import { Events, track } from "../lib/telemetry.ts";
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Database info from control plane or wrangler config
|
|
@@ -115,19 +117,45 @@ function showHelp(): void {
|
|
|
115
117
|
console.error("");
|
|
116
118
|
}
|
|
117
119
|
|
|
120
|
+
function showDbHelp(): void {
|
|
121
|
+
console.error("");
|
|
122
|
+
info("jack services db - Manage databases");
|
|
123
|
+
console.error("");
|
|
124
|
+
console.error("Actions:");
|
|
125
|
+
console.error(" info Show database information (default)");
|
|
126
|
+
console.error(" create Create a new database");
|
|
127
|
+
console.error(" list List all databases in the project");
|
|
128
|
+
console.error(" export Export database to SQL file");
|
|
129
|
+
console.error(" delete Delete a database");
|
|
130
|
+
console.error("");
|
|
131
|
+
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");
|
|
135
|
+
console.error("");
|
|
136
|
+
}
|
|
137
|
+
|
|
118
138
|
async function dbCommand(args: string[], options: ServiceOptions): Promise<void> {
|
|
119
139
|
const action = args[0] || "info"; // Default to info
|
|
120
140
|
|
|
121
141
|
switch (action) {
|
|
142
|
+
case "--help":
|
|
143
|
+
case "-h":
|
|
144
|
+
case "help":
|
|
145
|
+
return showDbHelp();
|
|
122
146
|
case "info":
|
|
123
147
|
return await dbInfo(options);
|
|
148
|
+
case "create":
|
|
149
|
+
return await dbCreate(args.slice(1), options);
|
|
150
|
+
case "list":
|
|
151
|
+
return await dbList(options);
|
|
124
152
|
case "export":
|
|
125
153
|
return await dbExport(options);
|
|
126
154
|
case "delete":
|
|
127
155
|
return await dbDelete(options);
|
|
128
156
|
default:
|
|
129
157
|
error(`Unknown action: ${action}`);
|
|
130
|
-
info("Available: info, export, delete");
|
|
158
|
+
info("Available: info, create, list, export, delete");
|
|
131
159
|
process.exit(1);
|
|
132
160
|
}
|
|
133
161
|
}
|
|
@@ -158,9 +186,8 @@ async function dbInfo(options: ServiceOptions): Promise<void> {
|
|
|
158
186
|
|
|
159
187
|
if (!dbInfo) {
|
|
160
188
|
console.error("");
|
|
161
|
-
error("No database found for this project
|
|
162
|
-
info("
|
|
163
|
-
info("For BYO projects, add d1_databases to your wrangler.jsonc");
|
|
189
|
+
error("No database found for this project");
|
|
190
|
+
info("Create one with: jack services db create");
|
|
164
191
|
console.error("");
|
|
165
192
|
return;
|
|
166
193
|
}
|
|
@@ -200,9 +227,8 @@ async function dbExport(options: ServiceOptions): Promise<void> {
|
|
|
200
227
|
|
|
201
228
|
if (!dbInfo) {
|
|
202
229
|
console.error("");
|
|
203
|
-
error("No database found for this project
|
|
204
|
-
info("
|
|
205
|
-
info("For BYO projects, add d1_databases to your wrangler.jsonc");
|
|
230
|
+
error("No database found for this project");
|
|
231
|
+
info("Create one with: jack services db create");
|
|
206
232
|
console.error("");
|
|
207
233
|
return;
|
|
208
234
|
}
|
|
@@ -239,9 +265,8 @@ async function dbDelete(options: ServiceOptions): Promise<void> {
|
|
|
239
265
|
|
|
240
266
|
if (!dbInfo) {
|
|
241
267
|
console.error("");
|
|
242
|
-
error("No database found for this project
|
|
243
|
-
info("
|
|
244
|
-
info("For BYO projects, add d1_databases to your wrangler.jsonc");
|
|
268
|
+
error("No database found for this project");
|
|
269
|
+
info("Create one with: jack services db create");
|
|
245
270
|
console.error("");
|
|
246
271
|
return;
|
|
247
272
|
}
|
|
@@ -263,10 +288,12 @@ async function dbDelete(options: ServiceOptions): Promise<void> {
|
|
|
263
288
|
console.error("");
|
|
264
289
|
|
|
265
290
|
// Confirm deletion
|
|
266
|
-
|
|
267
|
-
const
|
|
291
|
+
const { confirm } = await import("@clack/prompts");
|
|
292
|
+
const shouldDelete = await confirm({
|
|
293
|
+
message: `Delete database '${dbInfo.name}'?`,
|
|
294
|
+
});
|
|
268
295
|
|
|
269
|
-
if (
|
|
296
|
+
if (shouldDelete !== true) {
|
|
270
297
|
info("Cancelled");
|
|
271
298
|
return;
|
|
272
299
|
}
|
|
@@ -287,3 +314,116 @@ async function dbDelete(options: ServiceOptions): Promise<void> {
|
|
|
287
314
|
process.exit(1);
|
|
288
315
|
}
|
|
289
316
|
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Parse --name flag from args
|
|
320
|
+
* Supports: --name foo, --name=foo
|
|
321
|
+
*/
|
|
322
|
+
function parseNameFlag(args: string[]): string | undefined {
|
|
323
|
+
for (let i = 0; i < args.length; i++) {
|
|
324
|
+
const arg = args[i];
|
|
325
|
+
if (arg === "--name" && args[i + 1]) {
|
|
326
|
+
return args[i + 1];
|
|
327
|
+
}
|
|
328
|
+
if (arg.startsWith("--name=")) {
|
|
329
|
+
return arg.slice("--name=".length);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return undefined;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Create a new database
|
|
337
|
+
*/
|
|
338
|
+
async function dbCreate(args: string[], options: ServiceOptions): Promise<void> {
|
|
339
|
+
// Parse --name flag
|
|
340
|
+
const name = parseNameFlag(args);
|
|
341
|
+
|
|
342
|
+
outputSpinner.start("Creating database...");
|
|
343
|
+
try {
|
|
344
|
+
const result = await createDatabase(process.cwd(), {
|
|
345
|
+
name,
|
|
346
|
+
interactive: true,
|
|
347
|
+
});
|
|
348
|
+
outputSpinner.stop();
|
|
349
|
+
|
|
350
|
+
// Track telemetry
|
|
351
|
+
track(Events.SERVICE_CREATED, {
|
|
352
|
+
service_type: "d1",
|
|
353
|
+
binding_name: result.bindingName,
|
|
354
|
+
created: result.created,
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
console.error("");
|
|
358
|
+
if (result.created) {
|
|
359
|
+
success(`Database created: ${result.databaseName}`);
|
|
360
|
+
} else {
|
|
361
|
+
success(`Using existing database: ${result.databaseName}`);
|
|
362
|
+
}
|
|
363
|
+
console.error("");
|
|
364
|
+
item(`Binding: ${result.bindingName}`);
|
|
365
|
+
item(`ID: ${result.databaseId}`);
|
|
366
|
+
console.error("");
|
|
367
|
+
|
|
368
|
+
// Prompt to deploy
|
|
369
|
+
const { confirm } = await import("@clack/prompts");
|
|
370
|
+
const shouldDeploy = await confirm({
|
|
371
|
+
message: "Deploy now?",
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
if (shouldDeploy === true) {
|
|
375
|
+
const { deployProject } = await import("../lib/project-operations.ts");
|
|
376
|
+
await deployProject(process.cwd(), { interactive: true });
|
|
377
|
+
} else {
|
|
378
|
+
console.error("");
|
|
379
|
+
info("Run 'jack ship' when ready to deploy");
|
|
380
|
+
console.error("");
|
|
381
|
+
}
|
|
382
|
+
} catch (err) {
|
|
383
|
+
outputSpinner.stop();
|
|
384
|
+
console.error("");
|
|
385
|
+
error(`Failed to create database: ${err instanceof Error ? err.message : String(err)}`);
|
|
386
|
+
process.exit(1);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* List all databases in the project
|
|
392
|
+
*/
|
|
393
|
+
async function dbList(options: ServiceOptions): Promise<void> {
|
|
394
|
+
outputSpinner.start("Fetching databases...");
|
|
395
|
+
try {
|
|
396
|
+
const databases = await listDatabases(process.cwd());
|
|
397
|
+
outputSpinner.stop();
|
|
398
|
+
|
|
399
|
+
if (databases.length === 0) {
|
|
400
|
+
console.error("");
|
|
401
|
+
info("No databases found in this project.");
|
|
402
|
+
console.error("");
|
|
403
|
+
info("Create one with: jack services db create");
|
|
404
|
+
console.error("");
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
console.error("");
|
|
409
|
+
success(`Found ${databases.length} database${databases.length === 1 ? "" : "s"}:`);
|
|
410
|
+
console.error("");
|
|
411
|
+
|
|
412
|
+
for (const db of databases) {
|
|
413
|
+
item(`${db.name} (${db.binding})`);
|
|
414
|
+
if (db.sizeBytes !== undefined) {
|
|
415
|
+
item(` Size: ${formatSize(db.sizeBytes)}`);
|
|
416
|
+
}
|
|
417
|
+
if (db.numTables !== undefined) {
|
|
418
|
+
item(` Tables: ${db.numTables}`);
|
|
419
|
+
}
|
|
420
|
+
item(` ID: ${db.id}`);
|
|
421
|
+
console.error("");
|
|
422
|
+
}
|
|
423
|
+
} catch (err) {
|
|
424
|
+
outputSpinner.stop();
|
|
425
|
+
console.error("");
|
|
426
|
+
error(`Failed to list databases: ${err instanceof Error ? err.message : String(err)}`);
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
}
|
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
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* jack update - Self-update to the latest version
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { error, info, success, warn } from "../lib/output.ts";
|
|
6
|
+
import {
|
|
7
|
+
checkForUpdate,
|
|
8
|
+
getCurrentVersion,
|
|
9
|
+
isRunningViaBunx,
|
|
10
|
+
performUpdate,
|
|
11
|
+
} from "../lib/version-check.ts";
|
|
12
|
+
|
|
13
|
+
export default async function update(): Promise<void> {
|
|
14
|
+
const currentVersion = getCurrentVersion();
|
|
15
|
+
|
|
16
|
+
// Check if running via bunx
|
|
17
|
+
if (isRunningViaBunx()) {
|
|
18
|
+
info(`Running via bunx (current: v${currentVersion})`);
|
|
19
|
+
info("bunx automatically uses cached packages.");
|
|
20
|
+
info("To get the latest version, run:");
|
|
21
|
+
info(" bunx @getjack/jack@latest <command>");
|
|
22
|
+
info("");
|
|
23
|
+
info("Or install globally:");
|
|
24
|
+
info(" bun add -g @getjack/jack");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
info(`Current version: v${currentVersion}`);
|
|
29
|
+
|
|
30
|
+
// Check for updates
|
|
31
|
+
const latestVersion = await checkForUpdate();
|
|
32
|
+
|
|
33
|
+
if (!latestVersion) {
|
|
34
|
+
success("You're on the latest version!");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
info(`New version available: v${latestVersion}`);
|
|
39
|
+
info("Updating...");
|
|
40
|
+
|
|
41
|
+
const result = await performUpdate();
|
|
42
|
+
|
|
43
|
+
if (result.success) {
|
|
44
|
+
success(`Updated to v${result.version ?? latestVersion}`);
|
|
45
|
+
info("Restart your terminal to use the new version.");
|
|
46
|
+
} else {
|
|
47
|
+
error("Update failed");
|
|
48
|
+
if (result.error) {
|
|
49
|
+
warn(result.error);
|
|
50
|
+
}
|
|
51
|
+
info("Try manually: bun add -g @getjack/jack@latest");
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -35,6 +35,7 @@ const cli = meow(
|
|
|
35
35
|
login Sign in
|
|
36
36
|
logout Sign out
|
|
37
37
|
whoami Show current user
|
|
38
|
+
update Update jack to latest version
|
|
38
39
|
|
|
39
40
|
Project Management
|
|
40
41
|
link [name] Link directory to a project
|
|
@@ -168,6 +169,20 @@ identify({
|
|
|
168
169
|
...getEnvironmentProps(),
|
|
169
170
|
});
|
|
170
171
|
|
|
172
|
+
// Start non-blocking version check (skip for update command, CI, and help)
|
|
173
|
+
const skipVersionCheck =
|
|
174
|
+
!command ||
|
|
175
|
+
command === "update" ||
|
|
176
|
+
command === "upgrade" ||
|
|
177
|
+
process.env.CI ||
|
|
178
|
+
process.env.JACK_NO_UPDATE_CHECK;
|
|
179
|
+
|
|
180
|
+
let updateCheckPromise: Promise<string | null> | null = null;
|
|
181
|
+
if (!skipVersionCheck) {
|
|
182
|
+
const { checkForUpdate } = await import("./lib/version-check.ts");
|
|
183
|
+
updateCheckPromise = checkForUpdate().catch(() => null);
|
|
184
|
+
}
|
|
185
|
+
|
|
171
186
|
try {
|
|
172
187
|
switch (command) {
|
|
173
188
|
case "init": {
|
|
@@ -344,6 +359,12 @@ try {
|
|
|
344
359
|
await withTelemetry("whoami", whoami)();
|
|
345
360
|
break;
|
|
346
361
|
}
|
|
362
|
+
case "update":
|
|
363
|
+
case "upgrade": {
|
|
364
|
+
const { default: update } = await import("./commands/update.ts");
|
|
365
|
+
await withTelemetry("update", update)();
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
347
368
|
case "feedback": {
|
|
348
369
|
const { default: feedback } = await import("./commands/feedback.ts");
|
|
349
370
|
await withTelemetry("feedback", feedback)();
|
|
@@ -362,6 +383,16 @@ try {
|
|
|
362
383
|
default:
|
|
363
384
|
cli.showHelp(command ? 1 : 0);
|
|
364
385
|
}
|
|
386
|
+
|
|
387
|
+
// Show update notification if available (non-blocking check completed)
|
|
388
|
+
if (updateCheckPromise) {
|
|
389
|
+
const latestVersion = await updateCheckPromise;
|
|
390
|
+
if (latestVersion) {
|
|
391
|
+
info("");
|
|
392
|
+
info(`Update available: v${pkg.version} → v${latestVersion}`);
|
|
393
|
+
info("Run: jack update");
|
|
394
|
+
}
|
|
395
|
+
}
|
|
365
396
|
} catch (err) {
|
|
366
397
|
if (isJackError(err)) {
|
|
367
398
|
printError(err.message);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared login flow for CLI and programmatic use
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { isCancel, text } from "@clack/prompts";
|
|
5
5
|
import {
|
|
6
6
|
checkUsernameAvailable,
|
|
7
7
|
getCurrentUserProfile,
|
|
@@ -189,10 +189,18 @@ async function promptForUsername(email: string, firstName: string | null): Promi
|
|
|
189
189
|
|
|
190
190
|
if (choice === options.length - 1) {
|
|
191
191
|
// User chose to type custom username
|
|
192
|
-
|
|
192
|
+
const customInput = await text({
|
|
193
193
|
message: "Username:",
|
|
194
|
-
validate:
|
|
194
|
+
validate: (value) => {
|
|
195
|
+
const result = validateUsername(value);
|
|
196
|
+
if (result !== true) return result;
|
|
197
|
+
},
|
|
195
198
|
});
|
|
199
|
+
if (isCancel(customInput)) {
|
|
200
|
+
warn("Skipped username setup. You can set it later.");
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
inputUsername = customInput;
|
|
196
204
|
} else {
|
|
197
205
|
// User picked a suggestion (choice is guaranteed to be valid index)
|
|
198
206
|
inputUsername = suggestions[choice] as string;
|
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(),
|