forge-remote 0.1.29 → 2.1.1
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/firestore.rules +30 -1
- package/package.json +2 -2
- package/src/build-manager.js +168 -10
- package/src/session-manager.js +59 -19
package/firestore.rules
CHANGED
|
@@ -54,12 +54,14 @@ service cloud.firestore {
|
|
|
54
54
|
&& request.resource.data.keys().hasAll(['ownerUid', 'desktopId', 'status']);
|
|
55
55
|
allow update: if isSignedIn()
|
|
56
56
|
&& isValidSize();
|
|
57
|
+
allow delete: if isSignedIn();
|
|
57
58
|
|
|
58
59
|
// ---- Messages (subcollection) ----
|
|
59
60
|
match /messages/{messageId} {
|
|
60
61
|
allow read: if isSignedIn();
|
|
61
62
|
allow create: if isSignedIn()
|
|
62
|
-
&& request.resource.data.size() < 500000;
|
|
63
|
+
&& request.resource.data.size() < 500000;
|
|
64
|
+
allow delete: if isSignedIn();
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
// ---- Commands (subcollection) ----
|
|
@@ -67,22 +69,49 @@ service cloud.firestore {
|
|
|
67
69
|
allow read: if isSignedIn();
|
|
68
70
|
allow create: if isSignedIn();
|
|
69
71
|
allow update: if isSignedIn();
|
|
72
|
+
allow delete: if isSignedIn();
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
// ---- Permissions (subcollection) ----
|
|
73
76
|
match /permissions/{permId} {
|
|
74
77
|
allow read: if isSignedIn();
|
|
75
78
|
allow update: if isSignedIn();
|
|
79
|
+
allow delete: if isSignedIn();
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
// ---- Tool calls (subcollection) ----
|
|
79
83
|
match /toolCalls/{toolCallId} {
|
|
80
84
|
allow read: if isSignedIn();
|
|
85
|
+
allow delete: if isSignedIn();
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
// ---- Git data (subcollection) ----
|
|
84
89
|
match /gitData/{docId} {
|
|
85
90
|
allow read: if isSignedIn();
|
|
91
|
+
allow delete: if isSignedIn();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---- Builds (subcollection) ----
|
|
95
|
+
match /builds/{buildId} {
|
|
96
|
+
allow read: if isSignedIn();
|
|
97
|
+
allow delete: if isSignedIn();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---- Deploys (subcollection) ----
|
|
101
|
+
match /deploys/{deployId} {
|
|
102
|
+
allow read: if isSignedIn();
|
|
103
|
+
allow delete: if isSignedIn();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---- Build data (subcollection — project info, ADB status, deploy readiness) ----
|
|
107
|
+
match /buildData/{docId} {
|
|
108
|
+
allow read: if isSignedIn();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---- Session summary (subcollection) ----
|
|
112
|
+
match /summary/{docId} {
|
|
113
|
+
allow read: if isSignedIn();
|
|
114
|
+
allow delete: if isSignedIn();
|
|
86
115
|
}
|
|
87
116
|
}
|
|
88
117
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forge-remote",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Desktop relay for Forge Remote —
|
|
3
|
+
"version": "2.1.1",
|
|
4
|
+
"description": "Desktop relay for Forge Remote — mobile command center for AI coding agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"author": "Daniel Wendel <daniel@ironforgeapps.com> (https://ironforgeapps.com)",
|
package/src/build-manager.js
CHANGED
|
@@ -4,9 +4,52 @@
|
|
|
4
4
|
|
|
5
5
|
import { execSync, spawn } from "child_process";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
existsSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
writeFileSync,
|
|
11
|
+
readdirSync,
|
|
12
|
+
statSync,
|
|
13
|
+
} from "node:fs";
|
|
8
14
|
import * as log from "./logger.js";
|
|
9
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Resolve a path that may contain glob patterns (e.g., *.ipa) to an actual file.
|
|
18
|
+
* Returns the resolved absolute path, or the original if no glob.
|
|
19
|
+
*/
|
|
20
|
+
function resolveGlobPath(basePath, filePath) {
|
|
21
|
+
const fullPath = path.isAbsolute(filePath)
|
|
22
|
+
? filePath
|
|
23
|
+
: path.join(basePath, filePath);
|
|
24
|
+
|
|
25
|
+
if (!fullPath.includes("*")) {
|
|
26
|
+
return fullPath;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Manual glob resolution for simple *.ext patterns
|
|
30
|
+
const dir = path.dirname(fullPath);
|
|
31
|
+
const pattern = path.basename(fullPath);
|
|
32
|
+
|
|
33
|
+
if (!existsSync(dir)) return fullPath;
|
|
34
|
+
|
|
35
|
+
const ext = pattern.replace("*", "");
|
|
36
|
+
try {
|
|
37
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(ext));
|
|
38
|
+
if (files.length > 0) {
|
|
39
|
+
// Return the most recently modified file
|
|
40
|
+
const resolved = files
|
|
41
|
+
.map((f) => {
|
|
42
|
+
const p = path.join(dir, f);
|
|
43
|
+
return { name: f, path: p, mtime: statSync(p).mtimeMs };
|
|
44
|
+
})
|
|
45
|
+
.sort((a, b) => b.mtime - a.mtime)[0];
|
|
46
|
+
return resolved.path;
|
|
47
|
+
}
|
|
48
|
+
} catch {}
|
|
49
|
+
|
|
50
|
+
return fullPath;
|
|
51
|
+
}
|
|
52
|
+
|
|
10
53
|
// ---------------------------------------------------------------------------
|
|
11
54
|
// Project Detection
|
|
12
55
|
// ---------------------------------------------------------------------------
|
|
@@ -179,14 +222,40 @@ export async function runBuild(projectPath, platform, onOutput) {
|
|
|
179
222
|
const outputLines = [];
|
|
180
223
|
|
|
181
224
|
return new Promise((resolve, reject) => {
|
|
182
|
-
|
|
183
|
-
const
|
|
225
|
+
// Use login shell with full Xcode environment for iOS builds.
|
|
226
|
+
const shell = process.env.SHELL || "/bin/zsh";
|
|
227
|
+
const buildEnv = {
|
|
228
|
+
...process.env,
|
|
229
|
+
// Ensure Xcode tools are available
|
|
230
|
+
DEVELOPER_DIR:
|
|
231
|
+
process.env.DEVELOPER_DIR ||
|
|
232
|
+
"/Applications/Xcode.app/Contents/Developer",
|
|
233
|
+
};
|
|
234
|
+
// For iOS builds, run flutter clean first to avoid stale storyboard caches
|
|
235
|
+
const fullCmd =
|
|
236
|
+
platform === "ios"
|
|
237
|
+
? `flutter clean > /dev/null 2>&1; ${buildCmd}`
|
|
238
|
+
: buildCmd;
|
|
239
|
+
const proc = spawn(shell, ["-l", "-c", fullCmd], {
|
|
184
240
|
cwd: projectPath,
|
|
185
|
-
|
|
186
|
-
env: { ...process.env },
|
|
241
|
+
env: buildEnv,
|
|
187
242
|
stdio: ["pipe", "pipe", "pipe"],
|
|
188
243
|
});
|
|
189
244
|
|
|
245
|
+
// Timeout: 10 minutes max for any build
|
|
246
|
+
const buildTimeout = setTimeout(() => {
|
|
247
|
+
log.warn(`Build timed out after 10 minutes: ${buildCmd}`);
|
|
248
|
+
try {
|
|
249
|
+
proc.kill("SIGTERM");
|
|
250
|
+
} catch {}
|
|
251
|
+
setTimeout(() => {
|
|
252
|
+
try {
|
|
253
|
+
proc.kill("SIGKILL");
|
|
254
|
+
} catch {}
|
|
255
|
+
}, 5000);
|
|
256
|
+
reject(new Error(`Build timed out after 10 minutes`));
|
|
257
|
+
}, 600000);
|
|
258
|
+
|
|
190
259
|
proc.stdout.on("data", (data) => {
|
|
191
260
|
const text = data.toString();
|
|
192
261
|
for (const line of text.split("\n")) {
|
|
@@ -209,7 +278,13 @@ export async function runBuild(projectPath, platform, onOutput) {
|
|
|
209
278
|
}
|
|
210
279
|
});
|
|
211
280
|
|
|
281
|
+
proc.on("error", (err) => {
|
|
282
|
+
clearTimeout(buildTimeout);
|
|
283
|
+
reject(new Error(`Build process error: ${err.message}`));
|
|
284
|
+
});
|
|
285
|
+
|
|
212
286
|
proc.on("close", (code) => {
|
|
287
|
+
clearTimeout(buildTimeout);
|
|
213
288
|
const duration = Math.round((Date.now() - startTime) / 1000);
|
|
214
289
|
const outputPath =
|
|
215
290
|
projectInfo.outputPaths[platform] ||
|
|
@@ -307,11 +382,59 @@ export async function deployToAppDistribution(
|
|
|
307
382
|
buildPath,
|
|
308
383
|
options = {},
|
|
309
384
|
) {
|
|
310
|
-
const { projectId, groups, testers, releaseNotes } = options;
|
|
385
|
+
const { projectId, appId, groups, testers, releaseNotes } = options;
|
|
311
386
|
|
|
312
387
|
assertCliAvailable("firebase");
|
|
313
388
|
|
|
314
|
-
|
|
389
|
+
// Resolve glob patterns (e.g., build/ios/ipa/*.ipa → actual filename)
|
|
390
|
+
const resolvedPath = resolveGlobPath(projectPath, buildPath);
|
|
391
|
+
if (!existsSync(resolvedPath)) {
|
|
392
|
+
throw new Error(`Build artifact not found: ${resolvedPath}`);
|
|
393
|
+
}
|
|
394
|
+
log.info(`Resolved build path: ${resolvedPath}`);
|
|
395
|
+
|
|
396
|
+
// Auto-detect Firebase App ID if not provided
|
|
397
|
+
let detectedAppId = appId;
|
|
398
|
+
if (!detectedAppId) {
|
|
399
|
+
// Try GoogleService-Info.plist (iOS)
|
|
400
|
+
if (resolvedPath.endsWith(".ipa")) {
|
|
401
|
+
const plistPath = path.join(
|
|
402
|
+
projectPath,
|
|
403
|
+
"ios/Runner/GoogleService-Info.plist",
|
|
404
|
+
);
|
|
405
|
+
if (existsSync(plistPath)) {
|
|
406
|
+
try {
|
|
407
|
+
const plist = readFileSync(plistPath, "utf-8");
|
|
408
|
+
const match = plist.match(
|
|
409
|
+
/<key>GOOGLE_APP_ID<\/key>\s*<string>([^<]+)<\/string>/,
|
|
410
|
+
);
|
|
411
|
+
if (match) detectedAppId = match[1];
|
|
412
|
+
} catch {}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// Try google-services.json (Android)
|
|
416
|
+
if (resolvedPath.endsWith(".apk") || resolvedPath.endsWith(".aab")) {
|
|
417
|
+
const jsonPath = path.join(
|
|
418
|
+
projectPath,
|
|
419
|
+
"android/app/google-services.json",
|
|
420
|
+
);
|
|
421
|
+
if (existsSync(jsonPath)) {
|
|
422
|
+
try {
|
|
423
|
+
const gs = JSON.parse(readFileSync(jsonPath, "utf-8"));
|
|
424
|
+
detectedAppId = gs.client?.[0]?.client_info?.mobilesdk_app_id;
|
|
425
|
+
} catch {}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (!detectedAppId) {
|
|
431
|
+
throw new Error(
|
|
432
|
+
"Firebase App ID not found. Add GoogleService-Info.plist (iOS) or google-services.json (Android) to the project, or pass appId in the command payload.",
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
log.info(`Using Firebase App ID: ${detectedAppId}`);
|
|
437
|
+
let cmd = `firebase appdistribution:distribute "${resolvedPath}" --app "${detectedAppId}"`;
|
|
315
438
|
|
|
316
439
|
if (projectId) cmd += ` --project ${projectId}`;
|
|
317
440
|
if (groups) cmd += ` --groups "${groups}"`;
|
|
@@ -321,17 +444,52 @@ export async function deployToAppDistribution(
|
|
|
321
444
|
cmd += ` --release-notes "${escaped}"`;
|
|
322
445
|
}
|
|
323
446
|
|
|
324
|
-
|
|
447
|
+
// Auto-add the device owner as a tester if no testers/groups specified
|
|
448
|
+
if (!groups && !testers) {
|
|
449
|
+
// Try to get the Firebase auth email
|
|
450
|
+
try {
|
|
451
|
+
const authEmail = execSync("firebase login:list 2>/dev/null", {
|
|
452
|
+
timeout: 5000,
|
|
453
|
+
encoding: "utf-8",
|
|
454
|
+
});
|
|
455
|
+
const emailMatch = authEmail.match(/[\w.-]+@[\w.-]+\.\w+/);
|
|
456
|
+
if (emailMatch) {
|
|
457
|
+
cmd += ` --testers "${emailMatch[0]}"`;
|
|
458
|
+
log.info(`Auto-adding tester: ${emailMatch[0]}`);
|
|
459
|
+
}
|
|
460
|
+
} catch {}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
log.info(`Distributing build via App Distribution: ${resolvedPath}`);
|
|
325
464
|
|
|
326
465
|
const result = execSync(cmd, {
|
|
327
466
|
cwd: projectPath,
|
|
328
|
-
timeout: 300_000,
|
|
467
|
+
timeout: 300_000,
|
|
329
468
|
encoding: "utf-8",
|
|
330
469
|
env: { ...process.env },
|
|
331
470
|
});
|
|
332
471
|
|
|
472
|
+
// Try to extract the testing URI from the output
|
|
473
|
+
// Firebase CLI outputs: "View this release in the Firebase console: <url>"
|
|
474
|
+
const consoleUrlMatch = result.match(
|
|
475
|
+
/(?:View this release.*?|Firebase console.*?)(https:\/\/console\.firebase\.google\.com[^\s]+)/i,
|
|
476
|
+
);
|
|
477
|
+
// Also look for the tester download link
|
|
478
|
+
const testerUrlMatch = result.match(
|
|
479
|
+
/(https:\/\/appdistribution\.firebase\.google\.com[^\s]+)/i,
|
|
480
|
+
);
|
|
481
|
+
// Firebase App Tester — the user needs to open the App Tester app
|
|
482
|
+
// on their device to download. The console URL lets them manage releases.
|
|
483
|
+
const appTesterUrl = "https://appdistribution.firebase.google.com/testerapps";
|
|
484
|
+
|
|
333
485
|
log.info("App Distribution upload complete");
|
|
334
|
-
return {
|
|
486
|
+
return {
|
|
487
|
+
output: result,
|
|
488
|
+
consoleUrl: consoleUrlMatch?.[1] || null,
|
|
489
|
+
testerUrl: testerUrlMatch?.[1] || null,
|
|
490
|
+
appTesterUrl,
|
|
491
|
+
appId: detectedAppId,
|
|
492
|
+
};
|
|
335
493
|
}
|
|
336
494
|
|
|
337
495
|
// ---------------------------------------------------------------------------
|
package/src/session-manager.js
CHANGED
|
@@ -447,13 +447,22 @@ function watchSessionCommands(sessionId) {
|
|
|
447
447
|
activeSessions.set(`cmd-watcher-${sessionId}`, true);
|
|
448
448
|
|
|
449
449
|
const db = getDb();
|
|
450
|
+
console.log(
|
|
451
|
+
`[DEBUG] watchSessionCommands: watching session ${sessionId.slice(0, 8)}`,
|
|
452
|
+
);
|
|
450
453
|
db.collection("sessions")
|
|
451
454
|
.doc(sessionId)
|
|
452
455
|
.collection("commands")
|
|
453
456
|
.where("status", "==", "pending")
|
|
454
457
|
.onSnapshot((snap) => {
|
|
458
|
+
console.log(
|
|
459
|
+
`[DEBUG] commands snapshot for ${sessionId.slice(0, 8)}: ${snap.docChanges().length} changes`,
|
|
460
|
+
);
|
|
455
461
|
for (const change of snap.docChanges()) {
|
|
456
462
|
if (change.type === "added") {
|
|
463
|
+
console.log(
|
|
464
|
+
`[DEBUG] pending command: ${change.doc.data().type} for ${sessionId.slice(0, 8)}`,
|
|
465
|
+
);
|
|
457
466
|
handleSessionCommand(sessionId, change.doc);
|
|
458
467
|
}
|
|
459
468
|
}
|
|
@@ -492,6 +501,10 @@ async function handleSessionCommand(sessionId, commandDoc) {
|
|
|
492
501
|
const data = commandDoc.data();
|
|
493
502
|
if (data.status !== "pending") return;
|
|
494
503
|
|
|
504
|
+
console.log(
|
|
505
|
+
`[DEBUG] handleSessionCommand: type=${data.type} session=${sessionId.slice(0, 8)} cmdId=${commandDoc.id.slice(0, 8)}`,
|
|
506
|
+
);
|
|
507
|
+
|
|
495
508
|
const db = getDb();
|
|
496
509
|
const cmdRef = db
|
|
497
510
|
.collection("sessions")
|
|
@@ -533,6 +546,21 @@ async function handleSessionCommand(sessionId, commandDoc) {
|
|
|
533
546
|
|
|
534
547
|
await cmdRef.update({ status: "processing" });
|
|
535
548
|
|
|
549
|
+
// Helper: get session info from activeSessions or fall back to Firestore.
|
|
550
|
+
// Needed for build/deploy/git commands that can run on non-relay sessions.
|
|
551
|
+
async function getSessionInfo() {
|
|
552
|
+
const local = activeSessions.get(sessionId);
|
|
553
|
+
if (local) return local;
|
|
554
|
+
const doc = await db.collection("sessions").doc(sessionId).get();
|
|
555
|
+
if (!doc.exists) throw new Error("Session not found");
|
|
556
|
+
const d = doc.data();
|
|
557
|
+
return {
|
|
558
|
+
projectPath: d.projectPath,
|
|
559
|
+
projectName: d.projectName,
|
|
560
|
+
desktopId: d.desktopId,
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
|
|
536
564
|
try {
|
|
537
565
|
switch (data.type) {
|
|
538
566
|
case "send_prompt":
|
|
@@ -689,9 +717,15 @@ async function handleSessionCommand(sessionId, commandDoc) {
|
|
|
689
717
|
|
|
690
718
|
case "detect_project": {
|
|
691
719
|
log.command("detect_project", sessionId.slice(0, 8));
|
|
692
|
-
const sess =
|
|
693
|
-
|
|
720
|
+
const sess = await getSessionInfo();
|
|
721
|
+
console.log(
|
|
722
|
+
`[DEBUG] detect_project: projectPath="${sess.projectPath}"`,
|
|
723
|
+
);
|
|
694
724
|
const projectInfo = detectProjectType(sess.projectPath);
|
|
725
|
+
console.log(
|
|
726
|
+
`[DEBUG] detect_project: result=`,
|
|
727
|
+
JSON.stringify(projectInfo),
|
|
728
|
+
);
|
|
695
729
|
await db
|
|
696
730
|
.collection("sessions")
|
|
697
731
|
.doc(sessionId)
|
|
@@ -707,8 +741,7 @@ async function handleSessionCommand(sessionId, commandDoc) {
|
|
|
707
741
|
case "build_project": {
|
|
708
742
|
const platform = data.payload?.platform || "web";
|
|
709
743
|
log.command("build_project", `${sessionId.slice(0, 8)} [${platform}]`);
|
|
710
|
-
const sess =
|
|
711
|
-
if (!sess) throw new Error("Session not found");
|
|
744
|
+
const sess = await getSessionInfo();
|
|
712
745
|
|
|
713
746
|
// Create a build record with status: building
|
|
714
747
|
const buildRef = db
|
|
@@ -727,7 +760,10 @@ async function handleSessionCommand(sessionId, commandDoc) {
|
|
|
727
760
|
const result = await runBuild(
|
|
728
761
|
sess.projectPath,
|
|
729
762
|
platform,
|
|
730
|
-
(line,
|
|
763
|
+
(line, stream) => {
|
|
764
|
+
// Log build output to relay console
|
|
765
|
+
const prefix = stream === "stderr" ? "⚠" : "▸";
|
|
766
|
+
console.log(` ${prefix} [build] ${line}`);
|
|
731
767
|
// Stream latest output line to Firestore (best-effort)
|
|
732
768
|
buildRef
|
|
733
769
|
.update({
|
|
@@ -767,8 +803,7 @@ async function handleSessionCommand(sessionId, commandDoc) {
|
|
|
767
803
|
|
|
768
804
|
case "deploy_hosting": {
|
|
769
805
|
log.command("deploy_hosting", sessionId.slice(0, 8));
|
|
770
|
-
const sess =
|
|
771
|
-
if (!sess) throw new Error("Session not found");
|
|
806
|
+
const sess = await getSessionInfo();
|
|
772
807
|
|
|
773
808
|
const deployRef = db
|
|
774
809
|
.collection("sessions")
|
|
@@ -821,8 +856,7 @@ async function handleSessionCommand(sessionId, commandDoc) {
|
|
|
821
856
|
|
|
822
857
|
case "deploy_app_dist": {
|
|
823
858
|
log.command("deploy_app_dist", sessionId.slice(0, 8));
|
|
824
|
-
const sess =
|
|
825
|
-
if (!sess) throw new Error("Session not found");
|
|
859
|
+
const sess = await getSessionInfo();
|
|
826
860
|
|
|
827
861
|
const deployRef = db
|
|
828
862
|
.collection("sessions")
|
|
@@ -844,15 +878,23 @@ async function handleSessionCommand(sessionId, commandDoc) {
|
|
|
844
878
|
const testers = data.payload?.testers;
|
|
845
879
|
const releaseNotes = data.payload?.releaseNotes;
|
|
846
880
|
|
|
847
|
-
await deployToAppDistribution(
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
881
|
+
const distResult = await deployToAppDistribution(
|
|
882
|
+
sess.projectPath,
|
|
883
|
+
buildPath,
|
|
884
|
+
{
|
|
885
|
+
projectId,
|
|
886
|
+
groups,
|
|
887
|
+
testers,
|
|
888
|
+
releaseNotes,
|
|
889
|
+
},
|
|
890
|
+
);
|
|
853
891
|
|
|
854
892
|
await deployRef.update({
|
|
855
893
|
status: "success",
|
|
894
|
+
consoleUrl: distResult.consoleUrl || null,
|
|
895
|
+
testerUrl: distResult.testerUrl || null,
|
|
896
|
+
appTesterUrl: distResult.appTesterUrl || null,
|
|
897
|
+
appId: distResult.appId || null,
|
|
856
898
|
completedAt: FieldValue.serverTimestamp(),
|
|
857
899
|
});
|
|
858
900
|
|
|
@@ -930,8 +972,7 @@ async function handleSessionCommand(sessionId, commandDoc) {
|
|
|
930
972
|
|
|
931
973
|
case "check_deploy_ready": {
|
|
932
974
|
log.command("check_deploy_ready", sessionId.slice(0, 8));
|
|
933
|
-
const sess =
|
|
934
|
-
if (!sess) throw new Error("Session not found");
|
|
975
|
+
const sess = await getSessionInfo();
|
|
935
976
|
const report = checkDeployReadiness(sess.projectPath);
|
|
936
977
|
await db
|
|
937
978
|
.collection("sessions")
|
|
@@ -962,8 +1003,7 @@ async function handleSessionCommand(sessionId, commandDoc) {
|
|
|
962
1003
|
|
|
963
1004
|
case "init_firebase_hosting": {
|
|
964
1005
|
log.command("init_firebase_hosting", sessionId.slice(0, 8));
|
|
965
|
-
const sess =
|
|
966
|
-
if (!sess) throw new Error("Session not found");
|
|
1006
|
+
const sess = await getSessionInfo();
|
|
967
1007
|
const projectId = data.payload?.projectId;
|
|
968
1008
|
const publicDir = data.payload?.publicDir || "build/web";
|
|
969
1009
|
if (!projectId) throw new Error("projectId is required");
|