@getjack/jack 0.1.16 → 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 +5 -2
- package/src/commands/community.ts +47 -0
- package/src/commands/services.ts +269 -5
- package/src/index.ts +7 -0
- 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/mcp/tools/index.ts +161 -0
- 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
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
renderTemplate,
|
|
15
15
|
resolveTemplateWithOrigin,
|
|
16
16
|
} from "../templates/index.ts";
|
|
17
|
-
import type { Template } from "../templates/types.ts";
|
|
17
|
+
import type { EnvVar, Template } from "../templates/types.ts";
|
|
18
18
|
import { generateAgentFiles } from "./agent-files.ts";
|
|
19
19
|
import {
|
|
20
20
|
getActiveAgents,
|
|
@@ -168,6 +168,129 @@ const noopReporter: OperationReporter = {
|
|
|
168
168
|
box() {},
|
|
169
169
|
};
|
|
170
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Check if an environment variable already exists in a .env file
|
|
173
|
+
* Returns the existing value if found, null otherwise
|
|
174
|
+
*/
|
|
175
|
+
async function checkEnvVarExists(envPath: string, key: string): Promise<string | null> {
|
|
176
|
+
if (!existsSync(envPath)) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const content = await Bun.file(envPath).text();
|
|
181
|
+
for (const line of content.split("\n")) {
|
|
182
|
+
const trimmed = line.trim();
|
|
183
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const eqIndex = trimmed.indexOf("=");
|
|
188
|
+
if (eqIndex === -1) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const lineKey = trimmed.slice(0, eqIndex).trim();
|
|
193
|
+
if (lineKey === key) {
|
|
194
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
195
|
+
// Remove surrounding quotes
|
|
196
|
+
if (
|
|
197
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
198
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
199
|
+
) {
|
|
200
|
+
value = value.slice(1, -1);
|
|
201
|
+
}
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Prompt for environment variables defined in a template
|
|
211
|
+
* Returns a record of env var name -> value for vars that were provided
|
|
212
|
+
*/
|
|
213
|
+
async function promptEnvVars(
|
|
214
|
+
envVars: EnvVar[],
|
|
215
|
+
targetDir: string,
|
|
216
|
+
reporter: OperationReporter,
|
|
217
|
+
interactive: boolean,
|
|
218
|
+
): Promise<Record<string, string>> {
|
|
219
|
+
const result: Record<string, string> = {};
|
|
220
|
+
const envPath = join(targetDir, ".env");
|
|
221
|
+
|
|
222
|
+
for (const envVar of envVars) {
|
|
223
|
+
// Check if already exists in .env
|
|
224
|
+
const existingValue = await checkEnvVarExists(envPath, envVar.name);
|
|
225
|
+
if (existingValue) {
|
|
226
|
+
reporter.stop();
|
|
227
|
+
reporter.success(`${envVar.name}: already configured`);
|
|
228
|
+
reporter.start("Creating project...");
|
|
229
|
+
result[envVar.name] = existingValue;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!interactive) {
|
|
234
|
+
// Non-interactive mode: use default if available, otherwise warn
|
|
235
|
+
if (envVar.defaultValue !== undefined) {
|
|
236
|
+
result[envVar.name] = envVar.defaultValue;
|
|
237
|
+
reporter.stop();
|
|
238
|
+
reporter.info(`${envVar.name}: using default value`);
|
|
239
|
+
reporter.start("Creating project...");
|
|
240
|
+
} else if (envVar.required !== false) {
|
|
241
|
+
reporter.stop();
|
|
242
|
+
reporter.warn(`${envVar.name}: required but not set (no default available)`);
|
|
243
|
+
reporter.start("Creating project...");
|
|
244
|
+
}
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Interactive mode: prompt user
|
|
249
|
+
reporter.stop();
|
|
250
|
+
const { isCancel, text } = await import("@clack/prompts");
|
|
251
|
+
|
|
252
|
+
console.error("");
|
|
253
|
+
console.error(` ${envVar.description}`);
|
|
254
|
+
if (envVar.setupUrl) {
|
|
255
|
+
console.error(` Get it at: ${envVar.setupUrl}`);
|
|
256
|
+
}
|
|
257
|
+
if (envVar.example) {
|
|
258
|
+
console.error(` Example: ${envVar.example}`);
|
|
259
|
+
}
|
|
260
|
+
console.error("");
|
|
261
|
+
|
|
262
|
+
const value = await text({
|
|
263
|
+
message: `${envVar.name}:`,
|
|
264
|
+
defaultValue: envVar.defaultValue,
|
|
265
|
+
placeholder: envVar.defaultValue ?? (envVar.example ? `e.g. ${envVar.example}` : undefined),
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (isCancel(value)) {
|
|
269
|
+
// User cancelled - skip this var
|
|
270
|
+
if (envVar.required !== false) {
|
|
271
|
+
reporter.warn(`Skipped required env var: ${envVar.name}`);
|
|
272
|
+
}
|
|
273
|
+
reporter.start("Creating project...");
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const trimmedValue = value.trim();
|
|
278
|
+
if (trimmedValue) {
|
|
279
|
+
result[envVar.name] = trimmedValue;
|
|
280
|
+
reporter.success(`Set ${envVar.name}`);
|
|
281
|
+
} else if (envVar.defaultValue !== undefined) {
|
|
282
|
+
result[envVar.name] = envVar.defaultValue;
|
|
283
|
+
reporter.info(`${envVar.name}: using default value`);
|
|
284
|
+
} else if (envVar.required !== false) {
|
|
285
|
+
reporter.warn(`Skipped required env var: ${envVar.name}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
reporter.start("Creating project...");
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
|
|
171
294
|
/**
|
|
172
295
|
* Get the wrangler config file path for a project
|
|
173
296
|
* Returns the first found: wrangler.jsonc, wrangler.toml, wrangler.json
|
|
@@ -198,9 +321,36 @@ async function runWranglerDeploy(
|
|
|
198
321
|
return await $`wrangler deploy ${configArg} ${dryRunArgs}`.cwd(projectPath).nothrow().quiet();
|
|
199
322
|
}
|
|
200
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Ensure Cloudflare authentication is in place before BYO operations.
|
|
326
|
+
* Checks wrangler auth and CLOUDFLARE_API_TOKEN env var.
|
|
327
|
+
*/
|
|
328
|
+
async function ensureCloudflareAuth(
|
|
329
|
+
interactive: boolean,
|
|
330
|
+
reporter: OperationReporter,
|
|
331
|
+
): Promise<void> {
|
|
332
|
+
const { isAuthenticated, ensureAuth } = await import("./wrangler.ts");
|
|
333
|
+
const cfAuthenticated = await isAuthenticated();
|
|
334
|
+
const hasApiToken = Boolean(process.env.CLOUDFLARE_API_TOKEN);
|
|
335
|
+
|
|
336
|
+
if (!cfAuthenticated && !hasApiToken) {
|
|
337
|
+
if (interactive) {
|
|
338
|
+
reporter.info("Cloudflare authentication required");
|
|
339
|
+
await ensureAuth();
|
|
340
|
+
} else {
|
|
341
|
+
throw new JackError(
|
|
342
|
+
JackErrorCode.AUTH_FAILED,
|
|
343
|
+
"Not authenticated with Cloudflare",
|
|
344
|
+
"Run: wrangler login\nOr set CLOUDFLARE_API_TOKEN environment variable",
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
201
350
|
/**
|
|
202
351
|
* Run bun install and managed project creation in parallel.
|
|
203
352
|
* Handles partial failures with cleanup.
|
|
353
|
+
* Optionally reports URL early via onRemoteReady callback.
|
|
204
354
|
*/
|
|
205
355
|
async function runParallelSetup(
|
|
206
356
|
targetDir: string,
|
|
@@ -208,32 +358,41 @@ async function runParallelSetup(
|
|
|
208
358
|
options: {
|
|
209
359
|
template?: string;
|
|
210
360
|
usePrebuilt?: boolean;
|
|
361
|
+
onRemoteReady?: (result: ManagedCreateResult) => void;
|
|
211
362
|
},
|
|
212
363
|
): Promise<{
|
|
213
364
|
installSuccess: boolean;
|
|
214
365
|
remoteResult: ManagedCreateResult;
|
|
215
366
|
}> {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
367
|
+
// Start both operations
|
|
368
|
+
const installPromise = (async () => {
|
|
369
|
+
const install = Bun.spawn(["bun", "install", "--prefer-offline"], {
|
|
370
|
+
cwd: targetDir,
|
|
371
|
+
stdout: "ignore",
|
|
372
|
+
stderr: "ignore",
|
|
373
|
+
});
|
|
374
|
+
await install.exited;
|
|
375
|
+
if (install.exitCode !== 0) {
|
|
376
|
+
throw new Error("Dependency installation failed");
|
|
377
|
+
}
|
|
378
|
+
return true;
|
|
379
|
+
})();
|
|
380
|
+
|
|
381
|
+
const remotePromise = createManagedProjectRemote(projectName, undefined, {
|
|
382
|
+
template: options.template || "hello",
|
|
383
|
+
usePrebuilt: options.usePrebuilt ?? true,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// Report URL as soon as remote is ready (don't wait for install)
|
|
387
|
+
remotePromise
|
|
388
|
+
.then((result) => {
|
|
389
|
+
if (result.status === "live" && options.onRemoteReady) {
|
|
390
|
+
options.onRemoteReady(result);
|
|
227
391
|
}
|
|
228
|
-
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
createManagedProjectRemote(projectName, undefined, {
|
|
233
|
-
template: options.template || "hello",
|
|
234
|
-
usePrebuilt: options.usePrebuilt ?? true,
|
|
235
|
-
}),
|
|
236
|
-
]);
|
|
392
|
+
})
|
|
393
|
+
.catch(() => {}); // Errors handled below in allSettled
|
|
394
|
+
|
|
395
|
+
const [installResult, remoteResult] = await Promise.allSettled([installPromise, remotePromise]);
|
|
237
396
|
|
|
238
397
|
const installFailed = installResult.status === "rejected";
|
|
239
398
|
const remoteFailed = remoteResult.status === "rejected";
|
|
@@ -900,6 +1059,12 @@ export async function createProject(
|
|
|
900
1059
|
}
|
|
901
1060
|
}
|
|
902
1061
|
|
|
1062
|
+
// Handle environment variables (non-secret configuration)
|
|
1063
|
+
let envVarsToUse: Record<string, string> = {};
|
|
1064
|
+
if (template.envVars?.length) {
|
|
1065
|
+
envVarsToUse = await promptEnvVars(template.envVars, targetDir, reporter, interactive);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
903
1068
|
// Track if we created the directory (for cleanup on failure)
|
|
904
1069
|
let directoryCreated = false;
|
|
905
1070
|
|
|
@@ -918,13 +1083,24 @@ export async function createProject(
|
|
|
918
1083
|
}
|
|
919
1084
|
reporter.start("Creating project...");
|
|
920
1085
|
|
|
921
|
-
// Write secrets
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1086
|
+
// Write secrets and env vars files
|
|
1087
|
+
// - Secrets go to: .env, .dev.vars, .secrets.json (for wrangler bulk upload)
|
|
1088
|
+
// - Env vars go to: .env, .dev.vars only (not secrets.json - they're not secrets)
|
|
1089
|
+
const hasSecrets = Object.keys(secretsToUse).length > 0;
|
|
1090
|
+
const hasEnvVars = Object.keys(envVarsToUse).length > 0;
|
|
1091
|
+
|
|
1092
|
+
if (hasSecrets || hasEnvVars) {
|
|
1093
|
+
// Combine secrets and env vars for .env and .dev.vars
|
|
1094
|
+
const allEnvVars = { ...secretsToUse, ...envVarsToUse };
|
|
1095
|
+
const envContent = generateEnvFile(allEnvVars);
|
|
925
1096
|
await Bun.write(join(targetDir, ".env"), envContent);
|
|
926
1097
|
await Bun.write(join(targetDir, ".dev.vars"), envContent);
|
|
927
|
-
|
|
1098
|
+
|
|
1099
|
+
// Only write secrets to .secrets.json (for wrangler secret bulk)
|
|
1100
|
+
if (hasSecrets) {
|
|
1101
|
+
const jsonContent = generateSecretsJson(secretsToUse);
|
|
1102
|
+
await Bun.write(join(targetDir, ".secrets.json"), jsonContent);
|
|
1103
|
+
}
|
|
928
1104
|
|
|
929
1105
|
const gitignorePath = join(targetDir, ".gitignore");
|
|
930
1106
|
const gitignoreExists = existsSync(gitignorePath);
|
|
@@ -970,6 +1146,7 @@ export async function createProject(
|
|
|
970
1146
|
|
|
971
1147
|
// Parallel setup for managed mode: install + remote creation
|
|
972
1148
|
let remoteResult: ManagedCreateResult | undefined;
|
|
1149
|
+
let urlShownEarly = false;
|
|
973
1150
|
|
|
974
1151
|
if (deployMode === "managed") {
|
|
975
1152
|
// Run install and remote creation in parallel
|
|
@@ -980,11 +1157,22 @@ export async function createProject(
|
|
|
980
1157
|
const result = await runParallelSetup(targetDir, projectName, {
|
|
981
1158
|
template: resolvedTemplate || "hello",
|
|
982
1159
|
usePrebuilt: templateOrigin.type === "builtin", // Only builtin templates have prebuilt bundles
|
|
1160
|
+
onRemoteReady: (remote) => {
|
|
1161
|
+
// Show URL immediately when prebuilt succeeds
|
|
1162
|
+
reporter.stop();
|
|
1163
|
+
reporter.success(`Live: ${remote.runjackUrl}`);
|
|
1164
|
+
reporter.start("Installing dependencies locally...");
|
|
1165
|
+
urlShownEarly = true;
|
|
1166
|
+
},
|
|
983
1167
|
});
|
|
984
1168
|
remoteResult = result.remoteResult;
|
|
985
1169
|
timings.push({ label: "Parallel setup", duration: timerEnd("parallel-setup") });
|
|
986
1170
|
reporter.stop();
|
|
987
|
-
|
|
1171
|
+
if (urlShownEarly) {
|
|
1172
|
+
reporter.success("Ready for local development");
|
|
1173
|
+
} else {
|
|
1174
|
+
reporter.success("Project setup complete");
|
|
1175
|
+
}
|
|
988
1176
|
} catch (err) {
|
|
989
1177
|
timerEnd("parallel-setup");
|
|
990
1178
|
reporter.stop();
|
|
@@ -995,11 +1183,11 @@ export async function createProject(
|
|
|
995
1183
|
throw err;
|
|
996
1184
|
}
|
|
997
1185
|
} else {
|
|
998
|
-
// BYO mode: just install dependencies
|
|
1186
|
+
// BYO mode: just install dependencies
|
|
999
1187
|
timerStart("bun-install");
|
|
1000
1188
|
reporter.start("Installing dependencies...");
|
|
1001
1189
|
|
|
1002
|
-
const install = Bun.spawn(["bun", "install"], {
|
|
1190
|
+
const install = Bun.spawn(["bun", "install", "--prefer-offline"], {
|
|
1003
1191
|
cwd: targetDir,
|
|
1004
1192
|
stdout: "ignore",
|
|
1005
1193
|
stderr: "ignore",
|
|
@@ -1109,6 +1297,7 @@ export async function createProject(
|
|
|
1109
1297
|
await writeTemplateMetadata(targetDir, templateOrigin);
|
|
1110
1298
|
await registerPath(remoteResult.projectId, targetDir);
|
|
1111
1299
|
} catch (err) {
|
|
1300
|
+
reporter.warn("Could not save project link (deploy still works)");
|
|
1112
1301
|
debug("Failed to link managed project:", err);
|
|
1113
1302
|
}
|
|
1114
1303
|
|
|
@@ -1116,7 +1305,10 @@ export async function createProject(
|
|
|
1116
1305
|
if (remoteResult.status === "live") {
|
|
1117
1306
|
// Prebuilt succeeded - skip the fresh build
|
|
1118
1307
|
workerUrl = remoteResult.runjackUrl;
|
|
1119
|
-
|
|
1308
|
+
// Only show if not already shown by parallel setup
|
|
1309
|
+
if (!urlShownEarly) {
|
|
1310
|
+
reporter.success(`Deployed: ${workerUrl}`);
|
|
1311
|
+
}
|
|
1120
1312
|
} else {
|
|
1121
1313
|
// Prebuilt not available - fall back to fresh build
|
|
1122
1314
|
if (remoteResult.prebuiltFailed) {
|
|
@@ -1226,6 +1418,7 @@ export async function createProject(
|
|
|
1226
1418
|
await writeTemplateMetadata(targetDir, templateOrigin);
|
|
1227
1419
|
await registerPath(byoProjectId, targetDir);
|
|
1228
1420
|
} catch (err) {
|
|
1421
|
+
reporter.warn("Could not save project link (deploy still works)");
|
|
1229
1422
|
debug("Failed to link BYO project:", err);
|
|
1230
1423
|
}
|
|
1231
1424
|
}
|
|
@@ -1249,7 +1442,7 @@ export async function createProject(
|
|
|
1249
1442
|
|
|
1250
1443
|
// Show final celebration if there were interactive prompts (URL might have scrolled away)
|
|
1251
1444
|
if (hookResult.hadInteractiveActions && reporter.celebrate) {
|
|
1252
|
-
reporter.celebrate("You're live!", [
|
|
1445
|
+
reporter.celebrate("You're live!", [workerUrl]);
|
|
1253
1446
|
}
|
|
1254
1447
|
}
|
|
1255
1448
|
|
|
@@ -1327,6 +1520,57 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
|
|
|
1327
1520
|
"No wrangler config found in current directory",
|
|
1328
1521
|
"Run: jack new <project-name>",
|
|
1329
1522
|
);
|
|
1523
|
+
} else if (hasWranglerConfig && !hasProjectLink) {
|
|
1524
|
+
// Orphaned state: wrangler config exists but no project link
|
|
1525
|
+
// This happens when: linking failed during jack new, user has existing wrangler project,
|
|
1526
|
+
// or project was moved/copied without .jack directory
|
|
1527
|
+
const { isLoggedIn } = await import("./auth/store.ts");
|
|
1528
|
+
const loggedIn = await isLoggedIn();
|
|
1529
|
+
|
|
1530
|
+
if (loggedIn && !options.byo) {
|
|
1531
|
+
// User is logged into Jack Cloud - create managed project
|
|
1532
|
+
const orphanedProjectName = await getProjectNameFromDir(projectPath);
|
|
1533
|
+
|
|
1534
|
+
reporter.info(`Linking "${orphanedProjectName}" to jack cloud...`);
|
|
1535
|
+
|
|
1536
|
+
// Get username for URL construction
|
|
1537
|
+
const { getCurrentUserProfile } = await import("./control-plane.ts");
|
|
1538
|
+
const profile = await getCurrentUserProfile();
|
|
1539
|
+
const ownerUsername = profile?.username ?? undefined;
|
|
1540
|
+
|
|
1541
|
+
// Create managed project on jack cloud
|
|
1542
|
+
const remoteResult = await createManagedProjectRemote(orphanedProjectName, reporter, {
|
|
1543
|
+
usePrebuilt: false,
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
// Link project locally
|
|
1547
|
+
await linkProject(projectPath, remoteResult.projectId, "managed", ownerUsername);
|
|
1548
|
+
await registerPath(remoteResult.projectId, projectPath);
|
|
1549
|
+
|
|
1550
|
+
// Set autoDetectResult so the rest of the flow uses managed mode
|
|
1551
|
+
autoDetectResult = {
|
|
1552
|
+
projectName: orphanedProjectName,
|
|
1553
|
+
projectId: remoteResult.projectId,
|
|
1554
|
+
deployMode: "managed",
|
|
1555
|
+
};
|
|
1556
|
+
|
|
1557
|
+
reporter.success("Linked to jack cloud");
|
|
1558
|
+
} else if (!options.managed) {
|
|
1559
|
+
// BYO path - ensure wrangler auth before proceeding
|
|
1560
|
+
await ensureCloudflareAuth(interactive, reporter);
|
|
1561
|
+
|
|
1562
|
+
// Create BYO link for tracking (non-blocking)
|
|
1563
|
+
const orphanedProjectName = await getProjectNameFromDir(projectPath);
|
|
1564
|
+
const byoProjectId = generateByoProjectId();
|
|
1565
|
+
|
|
1566
|
+
try {
|
|
1567
|
+
await linkProject(projectPath, byoProjectId, "byo");
|
|
1568
|
+
await registerPath(byoProjectId, projectPath);
|
|
1569
|
+
debug("Created BYO project link for orphaned project");
|
|
1570
|
+
} catch (err) {
|
|
1571
|
+
debug("Failed to create BYO project link:", err);
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1330
1574
|
}
|
|
1331
1575
|
|
|
1332
1576
|
// Get project name from directory (or auto-detect result)
|
|
@@ -1475,6 +1719,9 @@ export async function deployProject(options: DeployOptions = {}): Promise<Deploy
|
|
|
1475
1719
|
}
|
|
1476
1720
|
}
|
|
1477
1721
|
|
|
1722
|
+
// Ensure Cloudflare auth before BYO deploy
|
|
1723
|
+
await ensureCloudflareAuth(interactive, reporter);
|
|
1724
|
+
|
|
1478
1725
|
const spin = reporter.spinner("Deploying...");
|
|
1479
1726
|
const result = await runWranglerDeploy(projectPath);
|
|
1480
1727
|
|