@getjack/jack 0.1.34 → 0.1.35
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 +6 -6
- package/package.json +1 -1
- package/src/commands/down.ts +39 -7
- package/src/commands/link.ts +2 -4
- package/src/commands/logs.ts +2 -4
- package/src/commands/mcp.ts +12 -10
- package/src/commands/services.ts +4 -2
- package/src/commands/sync.ts +5 -6
- package/src/lib/auth/client.ts +5 -2
- package/src/lib/binding-validator.ts +39 -3
- package/src/lib/build-helper.ts +18 -19
- package/src/lib/control-plane.ts +1 -0
- package/src/lib/do-config.ts +110 -0
- package/src/lib/do-export-validator.ts +26 -0
- package/src/lib/jsonc-edit.ts +292 -0
- package/src/lib/managed-deploy.ts +36 -1
- package/src/lib/project-link.ts +37 -0
- package/src/lib/project-operations.ts +13 -38
- package/src/lib/resources.ts +4 -5
- package/src/lib/schema.ts +8 -12
- package/src/lib/services/db-create.ts +2 -2
- package/src/lib/services/db-execute.ts +9 -6
- package/src/lib/services/db-list.ts +6 -4
- package/src/lib/services/endpoint-test.ts +275 -0
- package/src/lib/services/project-delete.ts +190 -0
- package/src/lib/services/project-environment.ts +457 -0
- package/src/lib/services/storage-config.ts +7 -309
- package/src/lib/services/storage-create.ts +2 -1
- package/src/lib/services/storage-delete.ts +3 -2
- package/src/lib/services/storage-info.ts +2 -1
- package/src/lib/services/storage-list.ts +6 -3
- package/src/lib/services/vectorize-config.ts +7 -264
- package/src/lib/services/vectorize-create.ts +2 -1
- package/src/lib/services/vectorize-delete.ts +6 -4
- package/src/lib/services/vectorize-list.ts +6 -3
- package/src/lib/storage/index.ts +21 -23
- package/src/lib/telemetry.ts +1 -0
- package/src/lib/wrangler-config.ts +43 -312
- package/src/lib/zip-packager.ts +28 -0
- package/src/mcp/test-utils.ts +31 -0
- package/src/mcp/tools/index.ts +271 -0
- package/src/templates/index.ts +5 -0
- package/src/templates/types.ts +4 -0
- package/templates/AI-BINDINGS.md +34 -76
- package/templates/CLAUDE.md +1 -1
- package/templates/ai-chat/src/index.ts +7 -14
- package/templates/ai-chat/src/jack-ai.ts +0 -6
- package/templates/chat/.jack.json +45 -0
- package/templates/chat/bun.lock +1588 -0
- package/templates/chat/components.json +23 -0
- package/templates/chat/index.html +12 -0
- package/templates/chat/package.json +41 -0
- package/templates/chat/src/chat-agent.ts +61 -0
- package/templates/chat/src/client/app.tsx +189 -0
- package/templates/chat/src/client/chat.tsx +222 -0
- package/templates/chat/src/client/components/prompt-kit/chat-container.tsx +47 -0
- package/templates/chat/src/client/components/prompt-kit/loader.tsx +33 -0
- package/templates/chat/src/client/components/prompt-kit/markdown.tsx +84 -0
- package/templates/chat/src/client/components/prompt-kit/message.tsx +54 -0
- package/templates/chat/src/client/components/prompt-kit/prompt-suggestion.tsx +20 -0
- package/templates/chat/src/client/components/prompt-kit/reasoning.tsx +134 -0
- package/templates/chat/src/client/components/prompt-kit/scroll-button.tsx +28 -0
- package/templates/chat/src/client/components/ui/button.tsx +38 -0
- package/templates/chat/src/client/lib/utils.ts +6 -0
- package/templates/chat/src/client/main.tsx +11 -0
- package/templates/chat/src/client/styles.css +125 -0
- package/templates/chat/src/index.ts +25 -0
- package/templates/chat/src/jack-ai.ts +94 -0
- package/templates/chat/tsconfig.json +18 -0
- package/templates/chat/vite.config.ts +14 -0
- package/templates/chat/wrangler.jsonc +18 -0
- package/templates/cron/.jack.json +18 -28
- package/templates/cron/schema.sql +10 -20
- package/templates/cron/src/admin.ts +321 -0
- package/templates/cron/src/index.ts +151 -81
- package/templates/cron/src/monitor.ts +124 -0
- package/templates/semantic-search/src/index.ts +5 -43
- package/templates/semantic-search/src/jack-ai.ts +0 -6
- package/templates/telegram-bot/.jack.json +56 -0
- package/templates/telegram-bot/bun.lock +41 -0
- package/templates/telegram-bot/package.json +16 -0
- package/templates/telegram-bot/src/index.ts +236 -0
- package/templates/telegram-bot/src/jack-ai.ts +100 -0
- package/templates/telegram-bot/tsconfig.json +11 -0
- package/templates/telegram-bot/wrangler.jsonc +8 -0
- package/templates/cron/src/jobs.ts +0 -139
- package/templates/cron/src/webhooks.ts +0 -95
- package/templates/semantic-search/src/jack-vectorize.ts +0 -169
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared JSONC text-manipulation helpers.
|
|
3
|
+
*
|
|
4
|
+
* These operate on raw JSONC strings so that comments and formatting
|
|
5
|
+
* are preserved when adding/removing sections.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Find the matching closing bracket/brace for an opening one,
|
|
10
|
+
* respecting strings and comments.
|
|
11
|
+
*/
|
|
12
|
+
export function findMatchingBracket(
|
|
13
|
+
content: string,
|
|
14
|
+
startIndex: number,
|
|
15
|
+
openChar: string,
|
|
16
|
+
closeChar: string,
|
|
17
|
+
): number {
|
|
18
|
+
let depth = 0;
|
|
19
|
+
let inString = false;
|
|
20
|
+
let stringChar = "";
|
|
21
|
+
let escaped = false;
|
|
22
|
+
let inLineComment = false;
|
|
23
|
+
let inBlockComment = false;
|
|
24
|
+
|
|
25
|
+
for (let i = startIndex; i < content.length; i++) {
|
|
26
|
+
const char = content[i] ?? "";
|
|
27
|
+
const next = content[i + 1] ?? "";
|
|
28
|
+
|
|
29
|
+
if (inLineComment) {
|
|
30
|
+
if (char === "\n") inLineComment = false;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (inBlockComment) {
|
|
35
|
+
if (char === "*" && next === "/") {
|
|
36
|
+
inBlockComment = false;
|
|
37
|
+
i++;
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (inString) {
|
|
43
|
+
if (escaped) {
|
|
44
|
+
escaped = false;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (char === "\\") {
|
|
48
|
+
escaped = true;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (char === stringChar) {
|
|
52
|
+
inString = false;
|
|
53
|
+
stringChar = "";
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (char === "/" && next === "/") {
|
|
59
|
+
inLineComment = true;
|
|
60
|
+
i++;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (char === "/" && next === "*") {
|
|
64
|
+
inBlockComment = true;
|
|
65
|
+
i++;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (char === '"' || char === "'") {
|
|
70
|
+
inString = true;
|
|
71
|
+
stringChar = char;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (char === openChar) {
|
|
76
|
+
depth++;
|
|
77
|
+
} else if (char === closeChar) {
|
|
78
|
+
depth--;
|
|
79
|
+
if (depth === 0) return i;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return -1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if content is only whitespace and comments.
|
|
88
|
+
*/
|
|
89
|
+
export function isOnlyCommentsAndWhitespace(content: string): boolean {
|
|
90
|
+
let inLineComment = false;
|
|
91
|
+
let inBlockComment = false;
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < content.length; i++) {
|
|
94
|
+
const char = content[i] ?? "";
|
|
95
|
+
const next = content[i + 1] ?? "";
|
|
96
|
+
|
|
97
|
+
if (inLineComment) {
|
|
98
|
+
if (char === "\n") inLineComment = false;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (inBlockComment) {
|
|
103
|
+
if (char === "*" && next === "/") {
|
|
104
|
+
inBlockComment = false;
|
|
105
|
+
i++;
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (char === "/" && next === "/") {
|
|
111
|
+
inLineComment = true;
|
|
112
|
+
i++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (char === "/" && next === "*") {
|
|
117
|
+
inBlockComment = true;
|
|
118
|
+
i++;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!/\s/.test(char)) return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Find the last closing brace of an object within an array range.
|
|
130
|
+
*/
|
|
131
|
+
export function findLastObjectEndInArray(
|
|
132
|
+
content: string,
|
|
133
|
+
startIndex: number,
|
|
134
|
+
endIndex: number,
|
|
135
|
+
): number {
|
|
136
|
+
let lastBraceIndex = -1;
|
|
137
|
+
let inString = false;
|
|
138
|
+
let stringChar = "";
|
|
139
|
+
let escaped = false;
|
|
140
|
+
let inLineComment = false;
|
|
141
|
+
let inBlockComment = false;
|
|
142
|
+
|
|
143
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
144
|
+
const char = content[i] ?? "";
|
|
145
|
+
const next = content[i + 1] ?? "";
|
|
146
|
+
|
|
147
|
+
if (inLineComment) {
|
|
148
|
+
if (char === "\n") inLineComment = false;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (inBlockComment) {
|
|
153
|
+
if (char === "*" && next === "/") {
|
|
154
|
+
inBlockComment = false;
|
|
155
|
+
i++;
|
|
156
|
+
}
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (inString) {
|
|
161
|
+
if (escaped) {
|
|
162
|
+
escaped = false;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (char === "\\") {
|
|
166
|
+
escaped = true;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (char === stringChar) {
|
|
170
|
+
inString = false;
|
|
171
|
+
stringChar = "";
|
|
172
|
+
}
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (char === "/" && next === "/") {
|
|
177
|
+
inLineComment = true;
|
|
178
|
+
i++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (char === "/" && next === "*") {
|
|
183
|
+
inBlockComment = true;
|
|
184
|
+
i++;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (char === '"' || char === "'") {
|
|
189
|
+
inString = true;
|
|
190
|
+
stringChar = char;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (char === "}") lastBraceIndex = i;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return lastBraceIndex;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Find the start of a line comment (//) in a string, respecting strings.
|
|
202
|
+
*/
|
|
203
|
+
export function findLineCommentStart(line: string): number {
|
|
204
|
+
let inString = false;
|
|
205
|
+
let stringChar = "";
|
|
206
|
+
let escaped = false;
|
|
207
|
+
|
|
208
|
+
for (let i = 0; i < line.length - 1; i++) {
|
|
209
|
+
const char = line[i] ?? "";
|
|
210
|
+
const next = line[i + 1] ?? "";
|
|
211
|
+
|
|
212
|
+
if (inString) {
|
|
213
|
+
if (escaped) {
|
|
214
|
+
escaped = false;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (char === "\\") {
|
|
218
|
+
escaped = true;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (char === stringChar) {
|
|
222
|
+
inString = false;
|
|
223
|
+
stringChar = "";
|
|
224
|
+
}
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (char === '"' || char === "'") {
|
|
229
|
+
inString = true;
|
|
230
|
+
stringChar = char;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (char === "/" && next === "/") return i;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return -1;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Determine if we need to add a comma before new content.
|
|
242
|
+
* Looks at the last non-whitespace, non-comment character.
|
|
243
|
+
*/
|
|
244
|
+
export function shouldAddCommaBefore(content: string): boolean {
|
|
245
|
+
let i = content.length - 1;
|
|
246
|
+
|
|
247
|
+
// First pass: find where any trailing line comment starts
|
|
248
|
+
for (let j = content.length - 1; j >= 0; j--) {
|
|
249
|
+
if (content[j] === "\n") {
|
|
250
|
+
const lineStart = content.lastIndexOf("\n", j - 1) + 1;
|
|
251
|
+
const line = content.slice(lineStart, j);
|
|
252
|
+
const commentIndex = findLineCommentStart(line);
|
|
253
|
+
if (commentIndex !== -1) {
|
|
254
|
+
i = lineStart + commentIndex - 1;
|
|
255
|
+
}
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Skip whitespace
|
|
261
|
+
while (i >= 0 && /\s/.test(content[i] ?? "")) {
|
|
262
|
+
i--;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (i < 0) return false;
|
|
266
|
+
|
|
267
|
+
const lastChar = content[i];
|
|
268
|
+
return lastChar !== "{" && lastChar !== "[" && lastChar !== ",";
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Add a new top-level section (key + value) to a JSONC file before the
|
|
273
|
+
* final closing brace, preserving existing comments and formatting.
|
|
274
|
+
*
|
|
275
|
+
* @param content Raw JSONC file content
|
|
276
|
+
* @param sectionJson The `"key": value` string to insert (no trailing comma)
|
|
277
|
+
* @returns Updated file content
|
|
278
|
+
*/
|
|
279
|
+
export function addSectionBeforeClosingBrace(content: string, sectionJson: string): string {
|
|
280
|
+
const lastBraceIndex = content.lastIndexOf("}");
|
|
281
|
+
if (lastBraceIndex === -1) {
|
|
282
|
+
throw new Error("Invalid JSON: no closing brace found");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const beforeBrace = content.slice(0, lastBraceIndex);
|
|
286
|
+
const needsComma = shouldAddCommaBefore(beforeBrace);
|
|
287
|
+
const trimmedBefore = beforeBrace.trimEnd();
|
|
288
|
+
|
|
289
|
+
const insertion = needsComma ? `,\n\t${sectionJson}` : `\n\t${sectionJson}`;
|
|
290
|
+
|
|
291
|
+
return `${trimmedBefore + insertion}\n${content.slice(lastBraceIndex)}`;
|
|
292
|
+
}
|
|
@@ -5,17 +5,21 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { stat } from "node:fs/promises";
|
|
8
|
+
import { join } from "node:path";
|
|
8
9
|
import { validateBindings } from "./binding-validator.ts";
|
|
9
10
|
import { buildProject, parseWranglerConfig } from "./build-helper.ts";
|
|
10
11
|
import { createManagedProject, syncProjectTags } from "./control-plane.ts";
|
|
11
12
|
import { debug } from "./debug.ts";
|
|
12
13
|
import { uploadDeployment } from "./deploy-upload.ts";
|
|
14
|
+
import { ensureMigrations, ensureNodejsCompat } from "./do-config.ts";
|
|
15
|
+
import { validateDoExports } from "./do-export-validator.ts";
|
|
13
16
|
import { JackError, JackErrorCode } from "./errors.ts";
|
|
14
17
|
import { formatSize } from "./format.ts";
|
|
15
18
|
import { createFileCountProgress, createUploadProgress } from "./progress.ts";
|
|
16
19
|
import type { OperationReporter } from "./project-operations.ts";
|
|
17
20
|
import { getProjectTags } from "./tags.ts";
|
|
18
21
|
import { Events, track, trackActivationIfFirst } from "./telemetry.ts";
|
|
22
|
+
import { findWranglerConfig } from "./wrangler-config.ts";
|
|
19
23
|
import { packageForDeploy } from "./zip-packager.ts";
|
|
20
24
|
|
|
21
25
|
export interface ManagedCreateResult {
|
|
@@ -101,12 +105,43 @@ export async function deployCodeToManagedProject(
|
|
|
101
105
|
let pkg: Awaited<ReturnType<typeof packageForDeploy>> | null = null;
|
|
102
106
|
|
|
103
107
|
try {
|
|
104
|
-
|
|
108
|
+
let config = await parseWranglerConfig(projectPath);
|
|
105
109
|
|
|
106
110
|
// Step 1: Build the project (must happen before validation, as build creates dist/)
|
|
107
111
|
reporter?.start("Building project...");
|
|
108
112
|
const buildOutput = await buildProject({ projectPath, reporter });
|
|
109
113
|
|
|
114
|
+
// Step 1.5: Auto-fix DO prerequisites (after build so we have output to validate)
|
|
115
|
+
if (config.durable_objects?.bindings?.length) {
|
|
116
|
+
const configPath = findWranglerConfig(projectPath) ?? join(projectPath, "wrangler.jsonc");
|
|
117
|
+
const fixes: string[] = [];
|
|
118
|
+
|
|
119
|
+
const addedCompat = await ensureNodejsCompat(configPath, config);
|
|
120
|
+
if (addedCompat) fixes.push("nodejs_compat");
|
|
121
|
+
|
|
122
|
+
const migratedClasses = await ensureMigrations(configPath, config);
|
|
123
|
+
if (migratedClasses.length > 0) fixes.push(`migrations for ${migratedClasses.join(", ")}`);
|
|
124
|
+
|
|
125
|
+
if (fixes.length > 0) {
|
|
126
|
+
config = await parseWranglerConfig(projectPath);
|
|
127
|
+
reporter?.success(`Auto-configured: ${fixes.join(", ")}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Validate DO class exports
|
|
131
|
+
const missing = await validateDoExports(
|
|
132
|
+
buildOutput.outDir,
|
|
133
|
+
buildOutput.entrypoint,
|
|
134
|
+
config.durable_objects.bindings.map((b) => b.class_name),
|
|
135
|
+
);
|
|
136
|
+
if (missing.length > 0) {
|
|
137
|
+
throw new JackError(
|
|
138
|
+
JackErrorCode.VALIDATION_ERROR,
|
|
139
|
+
`Durable Object class${missing.length > 1 ? "es" : ""} not exported: ${missing.join(", ")}`,
|
|
140
|
+
missing.map((c) => `Add "export" before "class ${c}" in your source code`).join("\n"),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
110
145
|
// Step 2: Validate bindings are supported (after build, so assets dir exists)
|
|
111
146
|
const validation = validateBindings(config, projectPath);
|
|
112
147
|
if (!validation.valid) {
|
package/src/lib/project-link.ts
CHANGED
|
@@ -86,6 +86,43 @@ export function generateByoProjectId(): string {
|
|
|
86
86
|
return `byo_${uuid}`;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Build the managed project URL for a given slug.
|
|
91
|
+
*
|
|
92
|
+
* If the link has owner_username, uses it directly. Otherwise fetches it
|
|
93
|
+
* from the control plane and backfills the local link for future calls.
|
|
94
|
+
*
|
|
95
|
+
* @param slug - Project slug (e.g. "cozy-paws-relate")
|
|
96
|
+
* @param ownerUsername - Owner username if already known
|
|
97
|
+
* @param projectDir - Project directory (for backfilling the link). If omitted, no backfill.
|
|
98
|
+
*/
|
|
99
|
+
export async function buildManagedUrl(
|
|
100
|
+
slug: string,
|
|
101
|
+
ownerUsername?: string | null,
|
|
102
|
+
projectDir?: string,
|
|
103
|
+
): Promise<string> {
|
|
104
|
+
if (ownerUsername) {
|
|
105
|
+
return `https://${ownerUsername}-${slug}.runjack.xyz`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Fetch from control plane to resolve owner_username
|
|
109
|
+
try {
|
|
110
|
+
const { findProjectBySlug } = await import("./control-plane.ts");
|
|
111
|
+
const project = await findProjectBySlug(slug);
|
|
112
|
+
if (project?.owner_username) {
|
|
113
|
+
// Backfill local link so subsequent calls are fast
|
|
114
|
+
if (projectDir) {
|
|
115
|
+
updateProjectLink(projectDir, { owner_username: project.owner_username }).catch(() => {});
|
|
116
|
+
}
|
|
117
|
+
return `https://${project.owner_username}-${slug}.runjack.xyz`;
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// Control plane unavailable, fall through
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return `https://${slug}.runjack.xyz`;
|
|
124
|
+
}
|
|
125
|
+
|
|
89
126
|
/**
|
|
90
127
|
* Link a local directory to a control plane project (managed)
|
|
91
128
|
* or create a local-only link (BYO with provided/generated ID)
|
|
@@ -60,6 +60,7 @@ import { detectProjectType, validateProject } from "./project-detection.ts";
|
|
|
60
60
|
import {
|
|
61
61
|
type DeployMode,
|
|
62
62
|
type TemplateMetadata as TemplateOrigin,
|
|
63
|
+
buildManagedUrl,
|
|
63
64
|
generateByoProjectId,
|
|
64
65
|
linkProject,
|
|
65
66
|
readProjectLink,
|
|
@@ -71,6 +72,7 @@ import { applySchema, getD1Bindings, getD1DatabaseName, hasD1Config } from "./sc
|
|
|
71
72
|
import { getSavedSecrets, saveSecrets } from "./secrets.ts";
|
|
72
73
|
import { getProjectNameFromDir, getRemoteManifest } from "./storage/index.ts";
|
|
73
74
|
import { Events, track, trackActivationIfFirst } from "./telemetry.ts";
|
|
75
|
+
import { findWranglerConfig, hasWranglerConfig } from "./wrangler-config.ts";
|
|
74
76
|
|
|
75
77
|
// ============================================================================
|
|
76
78
|
// Type Definitions
|
|
@@ -304,20 +306,6 @@ async function promptEnvVars(
|
|
|
304
306
|
return result;
|
|
305
307
|
}
|
|
306
308
|
|
|
307
|
-
/**
|
|
308
|
-
* Get the wrangler config file path for a project
|
|
309
|
-
* Returns the first found: wrangler.jsonc, wrangler.toml, wrangler.json
|
|
310
|
-
*/
|
|
311
|
-
function getWranglerConfigPath(projectPath: string): string | null {
|
|
312
|
-
const configs = ["wrangler.jsonc", "wrangler.toml", "wrangler.json"];
|
|
313
|
-
for (const config of configs) {
|
|
314
|
-
if (existsSync(join(projectPath, config))) {
|
|
315
|
-
return config;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
return null;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
309
|
/**
|
|
322
310
|
* Run wrangler deploy with explicit config to avoid parent directory conflicts
|
|
323
311
|
*/
|
|
@@ -325,7 +313,7 @@ async function runWranglerDeploy(
|
|
|
325
313
|
projectPath: string,
|
|
326
314
|
options: { dryRun?: boolean; outDir?: string } = {},
|
|
327
315
|
) {
|
|
328
|
-
const configFile =
|
|
316
|
+
const configFile = findWranglerConfig(projectPath);
|
|
329
317
|
const configArg = configFile ? ["--config", configFile] : [];
|
|
330
318
|
const dryRunArgs = options.dryRun
|
|
331
319
|
? ["--dry-run", ...(options.outDir ? ["--outdir", options.outDir] : [])]
|
|
@@ -1606,25 +1594,22 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
|
|
|
1606
1594
|
const interactive = interactiveOption ?? !isCi;
|
|
1607
1595
|
|
|
1608
1596
|
// Check for wrangler config
|
|
1609
|
-
const
|
|
1610
|
-
existsSync(join(projectPath, "wrangler.toml")) ||
|
|
1611
|
-
existsSync(join(projectPath, "wrangler.jsonc")) ||
|
|
1612
|
-
existsSync(join(projectPath, "wrangler.json"));
|
|
1597
|
+
const hasConfig = hasWranglerConfig(projectPath);
|
|
1613
1598
|
|
|
1614
1599
|
// Check for existing project link
|
|
1615
1600
|
const hasProjectLink = existsSync(join(projectPath, ".jack", "project.json"));
|
|
1616
1601
|
|
|
1617
1602
|
// Auto-detect flow: no wrangler config and no project link
|
|
1618
1603
|
let autoDetectResult: AutoDetectResult | null = null;
|
|
1619
|
-
if (!
|
|
1604
|
+
if (!hasConfig && !hasProjectLink) {
|
|
1620
1605
|
autoDetectResult = await runAutoDetectFlow(projectPath, reporter, interactive, dryRun);
|
|
1621
|
-
} else if (!
|
|
1606
|
+
} else if (!hasConfig) {
|
|
1622
1607
|
throw new JackError(
|
|
1623
1608
|
JackErrorCode.PROJECT_NOT_FOUND,
|
|
1624
1609
|
"No wrangler config found in current directory",
|
|
1625
1610
|
"Run: jack new <project-name>",
|
|
1626
1611
|
);
|
|
1627
|
-
} else if (
|
|
1612
|
+
} else if (hasConfig && !hasProjectLink) {
|
|
1628
1613
|
// Orphaned state: wrangler config exists but no project link
|
|
1629
1614
|
// This happens when: linking failed during jack new, user has existing wrangler project,
|
|
1630
1615
|
// or project was moved/copied without .jack directory
|
|
@@ -1814,9 +1799,7 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
|
|
|
1814
1799
|
managedDeployResult = await deployToManagedProject(managedProjectId as string, projectPath, reporter, options.message);
|
|
1815
1800
|
|
|
1816
1801
|
// Construct URL with username if available
|
|
1817
|
-
workerUrl = link?.owner_username
|
|
1818
|
-
? `https://${link.owner_username}-${projectName}.runjack.xyz`
|
|
1819
|
-
: `https://${projectName}.runjack.xyz`;
|
|
1802
|
+
workerUrl = await buildManagedUrl(projectName, link?.owner_username, projectPath);
|
|
1820
1803
|
} else {
|
|
1821
1804
|
// BYO mode: deploy via wrangler
|
|
1822
1805
|
|
|
@@ -2006,11 +1989,8 @@ export async function getProjectStatus(
|
|
|
2006
1989
|
const link = await readProjectLink(resolvedPath);
|
|
2007
1990
|
|
|
2008
1991
|
// Check if local project exists at the resolved path
|
|
2009
|
-
const
|
|
2010
|
-
|
|
2011
|
-
existsSync(join(resolvedPath, "wrangler.toml")) ||
|
|
2012
|
-
existsSync(join(resolvedPath, "wrangler.json"));
|
|
2013
|
-
const localExists = hasWranglerConfig;
|
|
1992
|
+
const hasConfig = hasWranglerConfig(resolvedPath);
|
|
1993
|
+
const localExists = hasConfig;
|
|
2014
1994
|
const localPath = localExists ? resolvedPath : null;
|
|
2015
1995
|
|
|
2016
1996
|
// If no link and no local project, return null
|
|
@@ -2030,9 +2010,7 @@ export async function getProjectStatus(
|
|
|
2030
2010
|
// Determine URL based on mode
|
|
2031
2011
|
let workerUrl: string | null = null;
|
|
2032
2012
|
if (link?.deploy_mode === "managed") {
|
|
2033
|
-
workerUrl = link.owner_username
|
|
2034
|
-
? `https://${link.owner_username}-${projectName}.runjack.xyz`
|
|
2035
|
-
: `https://${projectName}.runjack.xyz`;
|
|
2013
|
+
workerUrl = await buildManagedUrl(projectName, link.owner_username, resolvedPath);
|
|
2036
2014
|
}
|
|
2037
2015
|
|
|
2038
2016
|
// Get database name on-demand
|
|
@@ -2140,12 +2118,9 @@ export async function scanStaleProjects(): Promise<StaleProjectScan> {
|
|
|
2140
2118
|
|
|
2141
2119
|
for (const projectPath of paths) {
|
|
2142
2120
|
// Check if path exists and has valid wrangler config
|
|
2143
|
-
const
|
|
2144
|
-
existsSync(join(projectPath, "wrangler.jsonc")) ||
|
|
2145
|
-
existsSync(join(projectPath, "wrangler.toml")) ||
|
|
2146
|
-
existsSync(join(projectPath, "wrangler.json"));
|
|
2121
|
+
const hasConfig = hasWranglerConfig(projectPath);
|
|
2147
2122
|
|
|
2148
|
-
if (!
|
|
2123
|
+
if (!hasConfig) {
|
|
2149
2124
|
// Type 1: No wrangler config at path (dir deleted/moved)
|
|
2150
2125
|
const name = projectPath.split("/").pop() || projectId;
|
|
2151
2126
|
stale.push({
|
package/src/lib/resources.ts
CHANGED
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
* or parsed from wrangler.jsonc (BYO).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync } from "node:fs";
|
|
9
|
-
import { join } from "node:path";
|
|
10
8
|
import { parseJsonc } from "./jsonc.ts";
|
|
9
|
+
import { findWranglerConfig } from "./wrangler-config.ts";
|
|
11
10
|
|
|
12
11
|
// Resource types matching control plane schema
|
|
13
12
|
export type ResourceType =
|
|
@@ -88,10 +87,10 @@ export function convertControlPlaneResources(resources: ControlPlaneResource[]):
|
|
|
88
87
|
* Returns a unified resource view.
|
|
89
88
|
*/
|
|
90
89
|
export async function parseWranglerResources(projectPath: string): Promise<ResolvedResources> {
|
|
91
|
-
const wranglerPath =
|
|
90
|
+
const wranglerPath = findWranglerConfig(projectPath);
|
|
92
91
|
|
|
93
|
-
if (!
|
|
94
|
-
return {};
|
|
92
|
+
if (!wranglerPath) {
|
|
93
|
+
return {} as ResolvedResources;
|
|
95
94
|
}
|
|
96
95
|
|
|
97
96
|
try {
|
package/src/lib/schema.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { $ } from "bun";
|
|
|
4
4
|
import { debug } from "./debug.ts";
|
|
5
5
|
import { parseJsonc } from "./jsonc.ts";
|
|
6
6
|
import { output } from "./output.ts";
|
|
7
|
+
import { findWranglerConfig } from "./wrangler-config.ts";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Execute schema.sql on a D1 database after deploy
|
|
@@ -47,9 +48,9 @@ export async function applySchema(bindingOrDbName: string, projectDir: string):
|
|
|
47
48
|
* Check if project has D1 database configured (has d1_databases in wrangler config)
|
|
48
49
|
*/
|
|
49
50
|
export async function hasD1Config(projectDir: string): Promise<boolean> {
|
|
50
|
-
const wranglerPath =
|
|
51
|
+
const wranglerPath = findWranglerConfig(projectDir);
|
|
51
52
|
|
|
52
|
-
if (!
|
|
53
|
+
if (!wranglerPath) {
|
|
53
54
|
return false;
|
|
54
55
|
}
|
|
55
56
|
|
|
@@ -71,9 +72,9 @@ export interface D1Binding {
|
|
|
71
72
|
* Read D1 bindings from wrangler.jsonc
|
|
72
73
|
*/
|
|
73
74
|
export async function getD1Bindings(projectDir: string): Promise<D1Binding[]> {
|
|
74
|
-
const wranglerPath =
|
|
75
|
+
const wranglerPath = findWranglerConfig(projectDir);
|
|
75
76
|
|
|
76
|
-
if (!
|
|
77
|
+
if (!wranglerPath) {
|
|
77
78
|
return [];
|
|
78
79
|
}
|
|
79
80
|
|
|
@@ -91,20 +92,15 @@ export async function getD1Bindings(projectDir: string): Promise<D1Binding[]> {
|
|
|
91
92
|
* Returns the database_name field which is needed for wrangler d1 execute
|
|
92
93
|
*/
|
|
93
94
|
export async function getD1DatabaseName(projectDir: string): Promise<string | null> {
|
|
94
|
-
const wranglerPath =
|
|
95
|
+
const wranglerPath = findWranglerConfig(projectDir);
|
|
95
96
|
|
|
96
|
-
if (!
|
|
97
|
+
if (!wranglerPath) {
|
|
97
98
|
return null;
|
|
98
99
|
}
|
|
99
100
|
|
|
100
101
|
try {
|
|
101
102
|
const content = await Bun.file(wranglerPath).text();
|
|
102
|
-
|
|
103
|
-
// Note: Only remove line comments at the start of a line to avoid breaking URLs
|
|
104
|
-
const cleaned = content
|
|
105
|
-
.replace(/\/\*[\s\S]*?\*\//g, "") // block comments
|
|
106
|
-
.replace(/^\s*\/\/.*$/gm, ""); // line comments at start of line only
|
|
107
|
-
const config = JSON.parse(cleaned);
|
|
103
|
+
const config = parseJsonc<{ d1_databases?: { database_name?: string }[] }>(content);
|
|
108
104
|
|
|
109
105
|
return config.d1_databases?.[0]?.database_name || null;
|
|
110
106
|
} catch {
|
|
@@ -9,7 +9,7 @@ import { $ } from "bun";
|
|
|
9
9
|
import { createProjectResource } from "../control-plane.ts";
|
|
10
10
|
import { readProjectLink } from "../project-link.ts";
|
|
11
11
|
import { getProjectNameFromDir } from "../storage/index.ts";
|
|
12
|
-
import { addD1Binding, getExistingD1Bindings } from "../wrangler-config.ts";
|
|
12
|
+
import { addD1Binding, findWranglerConfig, getExistingD1Bindings } from "../wrangler-config.ts";
|
|
13
13
|
|
|
14
14
|
export interface CreateDatabaseOptions {
|
|
15
15
|
name?: string;
|
|
@@ -135,7 +135,7 @@ export async function createDatabase(
|
|
|
135
135
|
const projectName = await getProjectNameFromDir(projectDir);
|
|
136
136
|
|
|
137
137
|
// Get existing D1 bindings to determine naming
|
|
138
|
-
const wranglerPath = join(projectDir, "wrangler.jsonc");
|
|
138
|
+
const wranglerPath = findWranglerConfig(projectDir) ?? join(projectDir, "wrangler.jsonc");
|
|
139
139
|
const existingBindings = await getExistingD1Bindings(wranglerPath);
|
|
140
140
|
const existingCount = existingBindings.length;
|
|
141
141
|
|
|
@@ -9,11 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { existsSync } from "node:fs";
|
|
12
|
-
import { join } from "node:path";
|
|
13
12
|
import { $ } from "bun";
|
|
14
13
|
import { type ExecuteSqlResponse, executeManagedSql } from "../control-plane.ts";
|
|
15
14
|
import { readProjectLink } from "../project-link.ts";
|
|
16
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
type D1BindingConfig,
|
|
17
|
+
findWranglerConfig,
|
|
18
|
+
getExistingD1Bindings,
|
|
19
|
+
} from "../wrangler-config.ts";
|
|
17
20
|
import {
|
|
18
21
|
type ClassifiedStatement,
|
|
19
22
|
type RiskLevel,
|
|
@@ -98,9 +101,9 @@ export class DestructiveOperationError extends Error {
|
|
|
98
101
|
* Get the first D1 database configured for a project
|
|
99
102
|
*/
|
|
100
103
|
export async function getDefaultDatabase(projectDir: string): Promise<D1BindingConfig | null> {
|
|
101
|
-
const wranglerPath =
|
|
104
|
+
const wranglerPath = findWranglerConfig(projectDir);
|
|
102
105
|
|
|
103
|
-
if (!
|
|
106
|
+
if (!wranglerPath) {
|
|
104
107
|
return null;
|
|
105
108
|
}
|
|
106
109
|
|
|
@@ -119,9 +122,9 @@ export async function getDatabaseByName(
|
|
|
119
122
|
projectDir: string,
|
|
120
123
|
databaseName: string,
|
|
121
124
|
): Promise<D1BindingConfig | null> {
|
|
122
|
-
const wranglerPath =
|
|
125
|
+
const wranglerPath = findWranglerConfig(projectDir);
|
|
123
126
|
|
|
124
|
-
if (!
|
|
127
|
+
if (!wranglerPath) {
|
|
125
128
|
return null;
|
|
126
129
|
}
|
|
127
130
|
|
|
@@ -5,9 +5,8 @@
|
|
|
5
5
|
* For managed projects, fetches metadata via control plane instead of wrangler.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { join } from "node:path";
|
|
9
8
|
import { readProjectLink } from "../project-link.ts";
|
|
10
|
-
import { getExistingD1Bindings } from "../wrangler-config.ts";
|
|
9
|
+
import { findWranglerConfig, getExistingD1Bindings } from "../wrangler-config.ts";
|
|
11
10
|
import { getDatabaseInfo } from "./db.ts";
|
|
12
11
|
|
|
13
12
|
export interface DatabaseListEntry {
|
|
@@ -25,9 +24,12 @@ export interface DatabaseListEntry {
|
|
|
25
24
|
* For BYO projects: reads bindings from wrangler.jsonc and fetches metadata via wrangler.
|
|
26
25
|
*/
|
|
27
26
|
export async function listDatabases(projectDir: string): Promise<DatabaseListEntry[]> {
|
|
28
|
-
const wranglerPath =
|
|
27
|
+
const wranglerPath = findWranglerConfig(projectDir);
|
|
28
|
+
if (!wranglerPath) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
29
31
|
|
|
30
|
-
// Get existing D1 bindings from wrangler
|
|
32
|
+
// Get existing D1 bindings from wrangler config
|
|
31
33
|
const bindings = await getExistingD1Bindings(wranglerPath);
|
|
32
34
|
|
|
33
35
|
if (bindings.length === 0) {
|