@getjack/jack 0.1.22 → 0.1.23
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 +1 -1
- package/src/commands/clone.ts +5 -5
- package/src/commands/down.ts +44 -69
- package/src/commands/link.ts +9 -6
- package/src/commands/new.ts +54 -76
- package/src/commands/publish.ts +7 -2
- package/src/commands/secrets.ts +2 -1
- package/src/commands/services.ts +41 -15
- package/src/commands/update.ts +2 -2
- package/src/index.ts +43 -2
- package/src/lib/agent-integration.ts +217 -0
- package/src/lib/auth/login-flow.ts +2 -1
- package/src/lib/binding-validator.ts +2 -3
- package/src/lib/build-helper.ts +7 -1
- package/src/lib/hooks.ts +101 -21
- package/src/lib/managed-down.ts +32 -55
- package/src/lib/project-detection.ts +48 -21
- package/src/lib/project-operations.ts +31 -13
- package/src/lib/prompts.ts +16 -23
- package/src/lib/services/db-execute.ts +39 -0
- package/src/lib/services/sql-classifier.test.ts +2 -2
- package/src/lib/services/sql-classifier.ts +5 -4
- package/src/lib/version-check.ts +15 -10
- package/src/lib/zip-packager.ts +16 -0
- package/src/mcp/resources/index.ts +42 -2
- package/src/templates/index.ts +63 -3
- package/templates/ai-chat/.jack.json +29 -0
- package/templates/ai-chat/bun.lock +18 -0
- package/templates/ai-chat/package.json +14 -0
- package/templates/ai-chat/public/chat.js +149 -0
- package/templates/ai-chat/public/index.html +209 -0
- package/templates/ai-chat/src/index.ts +105 -0
- package/templates/ai-chat/wrangler.jsonc +12 -0
- package/templates/semantic-search/.jack.json +26 -0
- package/templates/semantic-search/bun.lock +18 -0
- package/templates/semantic-search/package.json +12 -0
- package/templates/semantic-search/public/app.js +120 -0
- package/templates/semantic-search/public/index.html +210 -0
- package/templates/semantic-search/schema.sql +5 -0
- package/templates/semantic-search/src/index.ts +144 -0
- package/templates/semantic-search/tsconfig.json +13 -0
- package/templates/semantic-search/wrangler.jsonc +27 -0
package/package.json
CHANGED
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 { isCancel,
|
|
3
|
+
import { isCancel, promptSelectValue } from "../lib/hooks.ts";
|
|
4
4
|
import { downloadProjectSource, 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";
|
|
@@ -34,14 +34,14 @@ export default async function clone(projectName?: string, flags: CloneFlags = {}
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
// Prompt user for action
|
|
37
|
-
const action = await
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const action = await promptSelectValue(
|
|
38
|
+
`Directory ${flags.as ?? projectName} already exists. What would you like to do?`,
|
|
39
|
+
[
|
|
40
40
|
{ value: "overwrite", label: "Overwrite (delete and recreate)" },
|
|
41
41
|
{ value: "merge", label: "Merge (keep existing files)" },
|
|
42
42
|
{ value: "cancel", label: "Cancel" },
|
|
43
43
|
],
|
|
44
|
-
|
|
44
|
+
);
|
|
45
45
|
|
|
46
46
|
if (isCancel(action) || action === "cancel") {
|
|
47
47
|
info("Clone cancelled");
|
package/src/commands/down.ts
CHANGED
|
@@ -59,6 +59,7 @@ async function resolveDatabaseName(
|
|
|
59
59
|
|
|
60
60
|
export interface DownFlags {
|
|
61
61
|
force?: boolean;
|
|
62
|
+
includeBackup?: boolean;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
export default async function down(projectName?: string, flags: DownFlags = {}): Promise<void> {
|
|
@@ -186,9 +187,12 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
186
187
|
}
|
|
187
188
|
console.error("");
|
|
188
189
|
|
|
189
|
-
//
|
|
190
|
+
// Single confirmation with clear description
|
|
190
191
|
console.error("");
|
|
191
|
-
|
|
192
|
+
const confirmMsg = dbName
|
|
193
|
+
? "Undeploy this project? Worker and database will be deleted."
|
|
194
|
+
: "Undeploy this project?";
|
|
195
|
+
info(confirmMsg);
|
|
192
196
|
const action = await promptSelect(["Yes", "No"]);
|
|
193
197
|
|
|
194
198
|
if (action !== 0) {
|
|
@@ -196,77 +200,38 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
196
200
|
return;
|
|
197
201
|
}
|
|
198
202
|
|
|
199
|
-
//
|
|
200
|
-
let
|
|
201
|
-
|
|
203
|
+
// Auto-export database if it exists (no prompt)
|
|
204
|
+
let exportPath: string | null = null;
|
|
202
205
|
if (dbName) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
const exportPath = join(process.cwd(), `${dbName}-backup.sql`);
|
|
213
|
-
output.start(`Exporting database to ${exportPath}...`);
|
|
214
|
-
try {
|
|
215
|
-
await exportDatabase(dbName, exportPath);
|
|
216
|
-
output.stop();
|
|
217
|
-
success(`Database exported to ${exportPath}`);
|
|
218
|
-
} catch (err) {
|
|
219
|
-
output.stop();
|
|
220
|
-
error(`Failed to export database: ${err instanceof Error ? err.message : String(err)}`);
|
|
221
|
-
console.error("");
|
|
222
|
-
info("Continue without exporting?");
|
|
223
|
-
const continueAction = await promptSelect(["Yes", "No"]);
|
|
224
|
-
if (continueAction !== 0) {
|
|
225
|
-
info("Cancelled");
|
|
226
|
-
return;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
206
|
+
exportPath = join(process.cwd(), `${dbName}-backup.sql`);
|
|
207
|
+
output.start(`Exporting database to ${exportPath}...`);
|
|
208
|
+
try {
|
|
209
|
+
await exportDatabase(dbName, exportPath);
|
|
210
|
+
output.stop();
|
|
211
|
+
} catch (err) {
|
|
212
|
+
output.stop();
|
|
213
|
+
warn(`Could not export database: ${err instanceof Error ? err.message : String(err)}`);
|
|
214
|
+
exportPath = null;
|
|
229
215
|
}
|
|
230
|
-
|
|
231
|
-
// Ask if they want to delete the database
|
|
232
|
-
console.error("");
|
|
233
|
-
info(`Delete database '${dbName}'?`);
|
|
234
|
-
const deleteAction = await promptSelect(["Yes", "No"]);
|
|
235
|
-
|
|
236
|
-
shouldDeleteDb = deleteAction === 0;
|
|
237
216
|
}
|
|
238
217
|
|
|
239
|
-
//
|
|
240
|
-
let shouldDeleteR2 = false;
|
|
241
|
-
console.error("");
|
|
242
|
-
info("Delete backup for this project?");
|
|
243
|
-
const deleteR2Action = await promptSelect(["Yes", "No"]);
|
|
244
|
-
shouldDeleteR2 = deleteR2Action === 0;
|
|
245
|
-
|
|
246
|
-
// Execute deletions
|
|
247
|
-
console.error("");
|
|
248
|
-
info("Executing cleanup...");
|
|
249
|
-
console.error("");
|
|
250
|
-
|
|
251
|
-
// Undeploy service
|
|
218
|
+
// Undeploy worker
|
|
252
219
|
output.start("Undeploying...");
|
|
253
220
|
try {
|
|
254
221
|
await deleteWorker(name);
|
|
255
222
|
output.stop();
|
|
256
|
-
success(`'${name}' undeployed`);
|
|
257
223
|
} catch (err) {
|
|
258
224
|
output.stop();
|
|
259
225
|
error(`Failed to undeploy: ${err instanceof Error ? err.message : String(err)}`);
|
|
260
226
|
process.exit(1);
|
|
261
227
|
}
|
|
262
228
|
|
|
263
|
-
// Delete database if
|
|
264
|
-
if (
|
|
229
|
+
// Delete database if it exists
|
|
230
|
+
if (dbName) {
|
|
265
231
|
output.start(`Deleting database '${dbName}'...`);
|
|
266
232
|
try {
|
|
267
233
|
await deleteDatabase(dbName);
|
|
268
234
|
output.stop();
|
|
269
|
-
success(`Database '${dbName}' deleted`);
|
|
270
235
|
} catch (err) {
|
|
271
236
|
output.stop();
|
|
272
237
|
warn(
|
|
@@ -275,25 +240,35 @@ export default async function down(projectName?: string, flags: DownFlags = {}):
|
|
|
275
240
|
}
|
|
276
241
|
}
|
|
277
242
|
|
|
278
|
-
//
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
243
|
+
// Only prompt for backup deletion if --include-backup flag is passed
|
|
244
|
+
if (flags.includeBackup) {
|
|
245
|
+
console.error("");
|
|
246
|
+
info("Also delete cloud backup?");
|
|
247
|
+
const deleteR2Action = await promptSelect(["Yes", "No"]);
|
|
248
|
+
|
|
249
|
+
if (deleteR2Action === 0) {
|
|
250
|
+
output.start("Deleting backup...");
|
|
251
|
+
try {
|
|
252
|
+
const deleted = await deleteCloudProject(name);
|
|
253
|
+
output.stop();
|
|
254
|
+
if (deleted) {
|
|
255
|
+
success("Backup deleted");
|
|
256
|
+
} else {
|
|
257
|
+
warn("No backup found or already deleted");
|
|
258
|
+
}
|
|
259
|
+
} catch (err) {
|
|
260
|
+
output.stop();
|
|
261
|
+
warn(`Failed to delete backup: ${err instanceof Error ? err.message : String(err)}`);
|
|
288
262
|
}
|
|
289
|
-
} catch (err) {
|
|
290
|
-
output.stop();
|
|
291
|
-
warn(`Failed to delete backup: ${err instanceof Error ? err.message : String(err)}`);
|
|
292
263
|
}
|
|
293
264
|
}
|
|
294
265
|
|
|
266
|
+
// Final success message
|
|
295
267
|
console.error("");
|
|
296
|
-
success(`
|
|
268
|
+
success(`Undeployed '${name}'`);
|
|
269
|
+
if (exportPath) {
|
|
270
|
+
info(`Backup saved to ./${dbName}-backup.sql`);
|
|
271
|
+
}
|
|
297
272
|
console.error("");
|
|
298
273
|
} catch (err) {
|
|
299
274
|
console.error("");
|
package/src/commands/link.ts
CHANGED
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
|
-
import { isCancel, select } from "@clack/prompts";
|
|
12
11
|
import { isLoggedIn } from "../lib/auth/index.ts";
|
|
12
|
+
import { isCancel, promptSelectValue } from "../lib/hooks.ts";
|
|
13
13
|
import {
|
|
14
14
|
type ManagedProject,
|
|
15
15
|
findProjectBySlug,
|
|
@@ -39,7 +39,10 @@ export default async function link(projectName?: string, flags: LinkFlags = {}):
|
|
|
39
39
|
|
|
40
40
|
if (!hasWranglerConfig) {
|
|
41
41
|
error("No wrangler config found");
|
|
42
|
-
|
|
42
|
+
console.error("");
|
|
43
|
+
info("Jack needs a wrangler.toml or wrangler.jsonc to deploy.");
|
|
44
|
+
info(" → For a new project: jack new my-project");
|
|
45
|
+
info(" → For existing code: jack init");
|
|
43
46
|
process.exit(1);
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -125,13 +128,13 @@ export default async function link(projectName?: string, flags: LinkFlags = {}):
|
|
|
125
128
|
}
|
|
126
129
|
|
|
127
130
|
console.error("");
|
|
128
|
-
const choice = await
|
|
129
|
-
|
|
130
|
-
|
|
131
|
+
const choice = await promptSelectValue(
|
|
132
|
+
"Select a project to link:",
|
|
133
|
+
projects.map((p) => ({
|
|
131
134
|
value: p.id,
|
|
132
135
|
label: `${p.slug} (${p.status})`,
|
|
133
136
|
})),
|
|
134
|
-
|
|
137
|
+
);
|
|
135
138
|
|
|
136
139
|
if (isCancel(choice)) {
|
|
137
140
|
info("Cancelled");
|
package/src/commands/new.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getAgentDefinition,
|
|
3
|
-
getPreferredLaunchAgent,
|
|
4
|
-
launchAgent,
|
|
5
|
-
scanAgents,
|
|
6
|
-
updateAgent,
|
|
7
|
-
} from "../lib/agents.ts";
|
|
1
|
+
import { getPreferredLaunchAgent, launchAgent, scanAgents, updateAgent } from "../lib/agents.ts";
|
|
8
2
|
import { debug } from "../lib/debug.ts";
|
|
9
3
|
import { getErrorDetails } from "../lib/errors.ts";
|
|
10
|
-
import { promptSelect } from "../lib/hooks.ts";
|
|
11
4
|
import { isIntentPhrase } from "../lib/intent.ts";
|
|
12
5
|
import { output, spinner } from "../lib/output.ts";
|
|
13
6
|
import { createProject } from "../lib/project-operations.ts";
|
|
@@ -20,6 +13,8 @@ export default async function newProject(
|
|
|
20
13
|
managed?: boolean;
|
|
21
14
|
byo?: boolean;
|
|
22
15
|
ci?: boolean;
|
|
16
|
+
open?: boolean;
|
|
17
|
+
noOpen?: boolean;
|
|
23
18
|
} = {},
|
|
24
19
|
): Promise<void> {
|
|
25
20
|
// Immediate feedback
|
|
@@ -104,77 +99,60 @@ export default async function newProject(
|
|
|
104
99
|
console.error("");
|
|
105
100
|
output.info(`Project: ${result.targetDir}`);
|
|
106
101
|
|
|
107
|
-
//
|
|
108
|
-
if (process.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
102
|
+
// Skip agent section entirely if --no-open or env var
|
|
103
|
+
if (options.noOpen || process.env.JACK_NO_OPEN === "1") {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Skip in CI mode
|
|
108
|
+
if (!process.stdout.isTTY || isCi) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Get preferred agent
|
|
113
|
+
let preferred = await getPreferredLaunchAgent();
|
|
114
|
+
|
|
115
|
+
// If no preferred agent, scan and auto-enable detected agents
|
|
116
|
+
if (!preferred) {
|
|
117
|
+
const detectionResult = await scanAgents();
|
|
118
|
+
|
|
119
|
+
if (detectionResult.detected.length > 0) {
|
|
120
|
+
// Auto-enable newly detected agents
|
|
121
|
+
for (const { id, path, launch } of detectionResult.detected) {
|
|
122
|
+
await updateAgent(id, {
|
|
123
|
+
active: true,
|
|
124
|
+
path,
|
|
125
|
+
detectedAt: new Date().toISOString(),
|
|
126
|
+
launch,
|
|
120
127
|
});
|
|
121
|
-
if (!launchResult.success) {
|
|
122
|
-
output.warn(`Failed to launch ${preferred.definition.name}`);
|
|
123
|
-
if (launchResult.command?.length) {
|
|
124
|
-
output.info(`Run manually: ${launchResult.command.join(" ")}`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
128
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
// Build menu options: detected agents + Skip
|
|
144
|
-
const menuOptions = detectionResult.detected.map(({ id }) => {
|
|
145
|
-
const definition = getAgentDefinition(id);
|
|
146
|
-
return definition?.name ?? id;
|
|
147
|
-
});
|
|
148
|
-
menuOptions.push("Skip");
|
|
149
|
-
|
|
150
|
-
console.error("");
|
|
151
|
-
console.error(" Open project in:");
|
|
152
|
-
console.error("");
|
|
153
|
-
const choice = await promptSelect(menuOptions);
|
|
154
|
-
|
|
155
|
-
// Launch selected agent (unless Skip or cancelled)
|
|
156
|
-
if (choice >= 0 && choice < detectionResult.detected.length) {
|
|
157
|
-
const selected = detectionResult.detected[choice];
|
|
158
|
-
const launchConfig = selected.launch;
|
|
159
|
-
if (launchConfig) {
|
|
160
|
-
const launchResult = await launchAgent(launchConfig, result.targetDir, {
|
|
161
|
-
projectName: result.projectName,
|
|
162
|
-
url: result.workerUrl,
|
|
163
|
-
});
|
|
164
|
-
if (!launchResult.success) {
|
|
165
|
-
const definition = getAgentDefinition(selected.id);
|
|
166
|
-
output.warn(`Failed to launch ${definition?.name ?? selected.id}`);
|
|
167
|
-
if (launchResult.command?.length) {
|
|
168
|
-
output.info(`Run manually: ${launchResult.command.join(" ")}`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
} else {
|
|
174
|
-
console.error("");
|
|
175
|
-
output.info("No AI agents detected");
|
|
176
|
-
output.info("Install Claude Code or Codex, then run: jack agents scan");
|
|
129
|
+
// Use the first detected agent as preferred
|
|
130
|
+
preferred = await getPreferredLaunchAgent();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Auto-open if --open flag
|
|
135
|
+
if (options.open && preferred) {
|
|
136
|
+
const launchResult = await launchAgent(preferred.launch, result.targetDir, {
|
|
137
|
+
projectName: result.projectName,
|
|
138
|
+
url: result.workerUrl,
|
|
139
|
+
});
|
|
140
|
+
if (!launchResult.success) {
|
|
141
|
+
output.warn(`Failed to launch ${preferred.definition.name}`);
|
|
142
|
+
if (launchResult.command?.length) {
|
|
143
|
+
output.info(`Run manually: ${launchResult.command.join(" ")}`);
|
|
177
144
|
}
|
|
178
145
|
}
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Default: show next steps (no prompt)
|
|
150
|
+
if (preferred) {
|
|
151
|
+
console.error("");
|
|
152
|
+
output.info(`Next: cd ${result.targetDir} && ${preferred.launch.command}`);
|
|
153
|
+
} else {
|
|
154
|
+
console.error("");
|
|
155
|
+
output.info("No AI agents detected");
|
|
156
|
+
output.info("Install Claude Code or Codex, then run: jack agents scan");
|
|
179
157
|
}
|
|
180
158
|
}
|
package/src/commands/publish.ts
CHANGED
|
@@ -7,7 +7,10 @@ export default async function publish(): Promise<void> {
|
|
|
7
7
|
const link = await readProjectLink(process.cwd());
|
|
8
8
|
if (!link) {
|
|
9
9
|
output.error("Not in a jack project directory");
|
|
10
|
-
|
|
10
|
+
console.error("");
|
|
11
|
+
output.info("This command requires a jack project.");
|
|
12
|
+
output.info(" → cd into an existing project, or");
|
|
13
|
+
output.info(" → Run: jack new my-project");
|
|
11
14
|
process.exit(1);
|
|
12
15
|
}
|
|
13
16
|
|
|
@@ -27,7 +30,9 @@ export default async function publish(): Promise<void> {
|
|
|
27
30
|
const profile = await getCurrentUserProfile();
|
|
28
31
|
if (!profile?.username) {
|
|
29
32
|
output.error("You need a username to publish projects");
|
|
30
|
-
|
|
33
|
+
console.error("");
|
|
34
|
+
output.info("Usernames identify your published templates.");
|
|
35
|
+
output.info(" → Run: jack login");
|
|
31
36
|
process.exit(1);
|
|
32
37
|
}
|
|
33
38
|
|
package/src/commands/secrets.ts
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* For BYO projects: uses wrangler secret commands.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { password as passwordPrompt } from "@clack/prompts";
|
|
10
|
+
import { isCancel } from "../lib/hooks.ts";
|
|
10
11
|
import { $ } from "bun";
|
|
11
12
|
import { getControlApiUrl } from "../lib/control-plane.ts";
|
|
12
13
|
import { JackError, JackErrorCode } from "../lib/errors.ts";
|
package/src/commands/services.ts
CHANGED
|
@@ -310,12 +310,10 @@ async function dbDelete(options: ServiceOptions): Promise<void> {
|
|
|
310
310
|
console.error("");
|
|
311
311
|
|
|
312
312
|
// Confirm deletion
|
|
313
|
-
const {
|
|
314
|
-
const
|
|
315
|
-
message: `Delete database '${dbInfo.name}'?`,
|
|
316
|
-
});
|
|
313
|
+
const { promptSelect } = await import("../lib/hooks.ts");
|
|
314
|
+
const choice = await promptSelect(["Yes, delete", "No, cancel"], `Delete database '${dbInfo.name}'?`);
|
|
317
315
|
|
|
318
|
-
if (
|
|
316
|
+
if (choice !== 0) {
|
|
319
317
|
info("Cancelled");
|
|
320
318
|
return;
|
|
321
319
|
}
|
|
@@ -460,20 +458,21 @@ async function dbCreate(args: string[], options: ServiceOptions): Promise<void>
|
|
|
460
458
|
console.error("");
|
|
461
459
|
item(`Binding: ${result.bindingName}`);
|
|
462
460
|
item(`ID: ${result.databaseId}`);
|
|
463
|
-
console.error("");
|
|
464
|
-
|
|
465
|
-
// Prompt to deploy
|
|
466
|
-
const { confirm } = await import("@clack/prompts");
|
|
467
|
-
const shouldDeploy = await confirm({
|
|
468
|
-
message: "Deploy now?",
|
|
469
|
-
});
|
|
470
461
|
|
|
471
|
-
|
|
462
|
+
// Auto-deploy to activate the database binding
|
|
463
|
+
console.error("");
|
|
464
|
+
outputSpinner.start("Deploying to activate database binding...");
|
|
465
|
+
try {
|
|
472
466
|
const { deployProject } = await import("../lib/project-operations.ts");
|
|
473
467
|
await deployProject(process.cwd(), { interactive: true });
|
|
474
|
-
|
|
468
|
+
outputSpinner.stop();
|
|
469
|
+
console.error("");
|
|
470
|
+
success("Database ready");
|
|
471
|
+
console.error("");
|
|
472
|
+
} catch (err) {
|
|
473
|
+
outputSpinner.stop();
|
|
475
474
|
console.error("");
|
|
476
|
-
|
|
475
|
+
warn("Deploy failed - run 'jack ship' to activate the binding");
|
|
477
476
|
console.error("");
|
|
478
477
|
}
|
|
479
478
|
} catch (err) {
|
|
@@ -710,6 +709,15 @@ async function dbExecute(args: string[], _options: ServiceOptions): Promise<void
|
|
|
710
709
|
outputSpinner.stop();
|
|
711
710
|
|
|
712
711
|
if (!result.success) {
|
|
712
|
+
// Track failed execution
|
|
713
|
+
track(Events.SQL_EXECUTED, {
|
|
714
|
+
success: false,
|
|
715
|
+
risk_level: result.risk,
|
|
716
|
+
statement_count: result.statements.length,
|
|
717
|
+
from_file: !!execArgs.filePath,
|
|
718
|
+
error_type: "execution_failed",
|
|
719
|
+
});
|
|
720
|
+
|
|
713
721
|
console.error("");
|
|
714
722
|
error(result.error || "SQL execution failed");
|
|
715
723
|
console.error("");
|
|
@@ -742,6 +750,7 @@ async function dbExecute(args: string[], _options: ServiceOptions): Promise<void
|
|
|
742
750
|
|
|
743
751
|
// Track telemetry
|
|
744
752
|
track(Events.SQL_EXECUTED, {
|
|
753
|
+
success: true,
|
|
745
754
|
risk_level: result.risk,
|
|
746
755
|
statement_count: result.statements.length,
|
|
747
756
|
from_file: !!execArgs.filePath,
|
|
@@ -750,6 +759,12 @@ async function dbExecute(args: string[], _options: ServiceOptions): Promise<void
|
|
|
750
759
|
outputSpinner.stop();
|
|
751
760
|
|
|
752
761
|
if (err instanceof WriteNotAllowedError) {
|
|
762
|
+
track(Events.SQL_EXECUTED, {
|
|
763
|
+
success: false,
|
|
764
|
+
error_type: "write_not_allowed",
|
|
765
|
+
risk_level: err.risk,
|
|
766
|
+
});
|
|
767
|
+
|
|
753
768
|
console.error("");
|
|
754
769
|
error(err.message);
|
|
755
770
|
info("Add the --write flag to allow data modification:");
|
|
@@ -759,6 +774,12 @@ async function dbExecute(args: string[], _options: ServiceOptions): Promise<void
|
|
|
759
774
|
}
|
|
760
775
|
|
|
761
776
|
if (err instanceof DestructiveOperationError) {
|
|
777
|
+
track(Events.SQL_EXECUTED, {
|
|
778
|
+
success: false,
|
|
779
|
+
error_type: "destructive_blocked",
|
|
780
|
+
risk_level: "destructive",
|
|
781
|
+
});
|
|
782
|
+
|
|
762
783
|
console.error("");
|
|
763
784
|
error(err.message);
|
|
764
785
|
info("Destructive operations require confirmation via CLI.");
|
|
@@ -766,6 +787,11 @@ async function dbExecute(args: string[], _options: ServiceOptions): Promise<void
|
|
|
766
787
|
process.exit(1);
|
|
767
788
|
}
|
|
768
789
|
|
|
790
|
+
track(Events.SQL_EXECUTED, {
|
|
791
|
+
success: false,
|
|
792
|
+
error_type: "unknown",
|
|
793
|
+
});
|
|
794
|
+
|
|
769
795
|
console.error("");
|
|
770
796
|
error(`SQL execution failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
771
797
|
console.error("");
|
package/src/commands/update.ts
CHANGED
|
@@ -33,9 +33,9 @@ export default async function update(): Promise<void> {
|
|
|
33
33
|
|
|
34
34
|
info(`Current version: v${currentVersion}`);
|
|
35
35
|
|
|
36
|
-
// Check for updates
|
|
36
|
+
// Check for updates - skip cache since user explicitly requested update
|
|
37
37
|
debug("Fetching latest version from npm...");
|
|
38
|
-
const latestVersion = await checkForUpdate();
|
|
38
|
+
const latestVersion = await checkForUpdate(true);
|
|
39
39
|
debug(`Latest version from npm: ${latestVersion ?? "none (you're up to date)"}`);
|
|
40
40
|
|
|
41
41
|
if (!latestVersion) {
|
package/src/index.ts
CHANGED
|
@@ -149,6 +149,30 @@ const cli = meow(
|
|
|
149
149
|
type: "string",
|
|
150
150
|
isMultiple: true,
|
|
151
151
|
},
|
|
152
|
+
includeBackup: {
|
|
153
|
+
type: "boolean",
|
|
154
|
+
default: false,
|
|
155
|
+
},
|
|
156
|
+
open: {
|
|
157
|
+
type: "boolean",
|
|
158
|
+
default: false,
|
|
159
|
+
},
|
|
160
|
+
noOpen: {
|
|
161
|
+
type: "boolean",
|
|
162
|
+
default: false,
|
|
163
|
+
},
|
|
164
|
+
write: {
|
|
165
|
+
type: "boolean",
|
|
166
|
+
shortFlag: "w",
|
|
167
|
+
default: false,
|
|
168
|
+
},
|
|
169
|
+
file: {
|
|
170
|
+
type: "string",
|
|
171
|
+
shortFlag: "f",
|
|
172
|
+
},
|
|
173
|
+
db: {
|
|
174
|
+
type: "string",
|
|
175
|
+
},
|
|
152
176
|
},
|
|
153
177
|
},
|
|
154
178
|
);
|
|
@@ -200,6 +224,8 @@ try {
|
|
|
200
224
|
managed: cli.flags.managed,
|
|
201
225
|
byo: cli.flags.byo,
|
|
202
226
|
ci: cli.flags.ci,
|
|
227
|
+
open: cli.flags.open,
|
|
228
|
+
noOpen: cli.flags.noOpen,
|
|
203
229
|
});
|
|
204
230
|
break;
|
|
205
231
|
}
|
|
@@ -212,6 +238,11 @@ try {
|
|
|
212
238
|
});
|
|
213
239
|
break;
|
|
214
240
|
}
|
|
241
|
+
case "shit": {
|
|
242
|
+
// Easter egg for typo
|
|
243
|
+
console.error("💩💩💩");
|
|
244
|
+
// Fall through to ship
|
|
245
|
+
}
|
|
215
246
|
case "ship":
|
|
216
247
|
case "push":
|
|
217
248
|
case "up":
|
|
@@ -279,7 +310,10 @@ try {
|
|
|
279
310
|
}
|
|
280
311
|
case "down": {
|
|
281
312
|
const { default: down } = await import("./commands/down.ts");
|
|
282
|
-
await withTelemetry("down", down)(args[0], {
|
|
313
|
+
await withTelemetry("down", down)(args[0], {
|
|
314
|
+
force: cli.flags.force,
|
|
315
|
+
includeBackup: cli.flags.includeBackup,
|
|
316
|
+
});
|
|
283
317
|
break;
|
|
284
318
|
}
|
|
285
319
|
case "publish": {
|
|
@@ -305,7 +339,11 @@ try {
|
|
|
305
339
|
}
|
|
306
340
|
case "services": {
|
|
307
341
|
const { default: services } = await import("./commands/services.ts");
|
|
308
|
-
|
|
342
|
+
const serviceArgs = [...args.slice(1)];
|
|
343
|
+
if (cli.flags.write) serviceArgs.push("--write");
|
|
344
|
+
if (cli.flags.file) serviceArgs.push("--file", cli.flags.file);
|
|
345
|
+
if (cli.flags.db) serviceArgs.push("--db", cli.flags.db);
|
|
346
|
+
await withTelemetry("services", services)(args[0], serviceArgs, {
|
|
309
347
|
project: cli.flags.project,
|
|
310
348
|
});
|
|
311
349
|
break;
|
|
@@ -414,3 +452,6 @@ try {
|
|
|
414
452
|
}
|
|
415
453
|
|
|
416
454
|
await shutdown();
|
|
455
|
+
|
|
456
|
+
// Ensure clean exit (stdin listeners from prompts can keep event loop alive)
|
|
457
|
+
process.exit(0);
|