forge-remote 0.1.4 → 0.1.7
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/google-auth.js +41 -0
- package/src/init.js +36 -51
package/package.json
CHANGED
package/src/google-auth.js
CHANGED
|
@@ -80,6 +80,47 @@ export async function getAccessToken() {
|
|
|
80
80
|
return data.access_token;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// ─── Web App SDK Config ─────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Fetch the Firebase web app SDK config via REST API.
|
|
87
|
+
* This bypasses the `firebase apps:sdkconfig` CLI command, which sometimes
|
|
88
|
+
* returns 403 on newly created projects.
|
|
89
|
+
*
|
|
90
|
+
* Returns an object with apiKey, authDomain, projectId, storageBucket,
|
|
91
|
+
* messagingSenderId, and appId — or throws on failure.
|
|
92
|
+
*/
|
|
93
|
+
export async function getWebAppConfig(projectId, appId) {
|
|
94
|
+
const token = await getAccessToken();
|
|
95
|
+
|
|
96
|
+
const response = await fetch(
|
|
97
|
+
`https://firebase.googleapis.com/v1beta1/projects/${projectId}/webApps/${appId}/config`,
|
|
98
|
+
{
|
|
99
|
+
headers: {
|
|
100
|
+
Authorization: `Bearer ${token}`,
|
|
101
|
+
"X-Goog-User-Project": projectId,
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const err = await response.json().catch(() => ({}));
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Failed to fetch web app config: ${err.error?.message || response.statusText}`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const data = await response.json();
|
|
114
|
+
return {
|
|
115
|
+
apiKey: data.apiKey || null,
|
|
116
|
+
authDomain: data.authDomain || null,
|
|
117
|
+
projectId: data.projectId || null,
|
|
118
|
+
storageBucket: data.storageBucket || null,
|
|
119
|
+
messagingSenderId: data.messagingSenderId || null,
|
|
120
|
+
appId: data.appId || null,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
83
124
|
// ─── Anonymous Authentication ───────────────────────────────────────────────
|
|
84
125
|
|
|
85
126
|
/**
|
package/src/init.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { execSync, execFileSync } from "child_process";
|
|
6
6
|
import { createInterface } from "readline";
|
|
7
7
|
import { hostname, platform, homedir } from "os";
|
|
8
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
|
|
8
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, rmSync } from "fs";
|
|
9
9
|
import { join, dirname } from "path";
|
|
10
10
|
import { fileURLToPath } from "url";
|
|
11
11
|
import { initializeApp, cert } from "firebase-admin/app";
|
|
@@ -15,6 +15,7 @@ import chalk from "chalk";
|
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
17
|
readRefreshToken,
|
|
18
|
+
getWebAppConfig,
|
|
18
19
|
enableAnonymousAuth,
|
|
19
20
|
enableApi,
|
|
20
21
|
findFirebaseAdminSdkAccount,
|
|
@@ -426,22 +427,40 @@ export async function runInit({ projectId: overrideProjectId } = {}) {
|
|
|
426
427
|
|
|
427
428
|
console.log(chalk.green(` ✓ App ID: ${appId}`));
|
|
428
429
|
|
|
429
|
-
// Get SDK config
|
|
430
|
+
// Get SDK config via REST API (bypasses flaky `firebase apps:sdkconfig` CLI).
|
|
430
431
|
let sdkConfig = {};
|
|
431
432
|
|
|
432
433
|
try {
|
|
433
|
-
|
|
434
|
+
// Primary method: REST API with retry for newly created apps.
|
|
434
435
|
const maxSdkRetries = 4;
|
|
435
436
|
const retryDelayMs = 5000; // 5 seconds between retries.
|
|
436
437
|
|
|
437
438
|
for (let attempt = 1; attempt <= maxSdkRetries; attempt++) {
|
|
438
439
|
try {
|
|
439
|
-
|
|
440
|
-
["firebase", "apps:sdkconfig", "web", appId, "--project", projectId],
|
|
441
|
-
"Failed to get SDK config.",
|
|
442
|
-
);
|
|
440
|
+
sdkConfig = await getWebAppConfig(projectId, appId);
|
|
443
441
|
break; // Success — exit retry loop.
|
|
444
442
|
} catch (e) {
|
|
443
|
+
const errMsg = e.message || "";
|
|
444
|
+
|
|
445
|
+
// Detect deleted projects — no point retrying, bail immediately.
|
|
446
|
+
if (errMsg.includes("has been deleted") || errMsg.includes("DELETED")) {
|
|
447
|
+
// Clear stale cached config so next run starts fresh.
|
|
448
|
+
try {
|
|
449
|
+
rmSync(forgeDir, { recursive: true, force: true });
|
|
450
|
+
} catch {
|
|
451
|
+
// Best-effort cleanup.
|
|
452
|
+
}
|
|
453
|
+
throw new Error(
|
|
454
|
+
`Project "${projectId}" has been deleted and is pending permanent removal (30 days).\n\n` +
|
|
455
|
+
chalk.bold(
|
|
456
|
+
" The cached config has been cleared. Re-run with a new project ID:\n\n",
|
|
457
|
+
) +
|
|
458
|
+
chalk.cyan(
|
|
459
|
+
` forge-remote init --project-id forge-remote-${sanitizedHostname()}-2\n`,
|
|
460
|
+
),
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
445
464
|
if (attempt < maxSdkRetries) {
|
|
446
465
|
console.log(
|
|
447
466
|
chalk.yellow(
|
|
@@ -451,23 +470,23 @@ export async function runInit({ projectId: overrideProjectId } = {}) {
|
|
|
451
470
|
);
|
|
452
471
|
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
453
472
|
} else {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
chalk.bold(
|
|
458
|
-
" This usually means the Firebase web app hasn't fully propagated yet.\n",
|
|
459
|
-
) +
|
|
473
|
+
throw new Error(
|
|
474
|
+
`Could not retrieve SDK config after ${maxSdkRetries} attempts.\n` +
|
|
475
|
+
` Last error: ${errMsg}\n\n` +
|
|
460
476
|
chalk.bold(" Try these steps:\n\n") +
|
|
461
477
|
chalk.cyan(
|
|
462
478
|
` 1. Wait 30 seconds, then re-run: forge-remote init\n`,
|
|
463
479
|
) +
|
|
464
|
-
chalk.cyan(
|
|
480
|
+
chalk.cyan(
|
|
481
|
+
` 2. Or get the config from the Firebase Console:\n`,
|
|
482
|
+
) +
|
|
465
483
|
chalk.dim(
|
|
466
|
-
` firebase
|
|
484
|
+
` https://console.firebase.google.com/project/${projectId}/settings/general\n`,
|
|
467
485
|
) +
|
|
468
486
|
chalk.dim(
|
|
469
|
-
`
|
|
487
|
+
` Scroll to "Your apps" → Web app → Config snippet\n`,
|
|
470
488
|
) +
|
|
489
|
+
chalk.dim(` Save it to: ~/.forge-remote/sdk-config.json\n`) +
|
|
471
490
|
chalk.dim(
|
|
472
491
|
` Format: { "apiKey": "...", "authDomain": "...", "projectId": "...",\n`,
|
|
473
492
|
) +
|
|
@@ -475,46 +494,12 @@ export async function runInit({ projectId: overrideProjectId } = {}) {
|
|
|
475
494
|
` "storageBucket": "...", "messagingSenderId": "...", "appId": "..." }\n`,
|
|
476
495
|
),
|
|
477
496
|
);
|
|
478
|
-
throw fallbackErr;
|
|
479
497
|
}
|
|
480
498
|
}
|
|
481
499
|
}
|
|
482
500
|
|
|
483
|
-
// Parse the config — output may be JSON or JS object.
|
|
484
|
-
const jsonMatch = sdkOutput.match(/\{[\s\S]*\}/);
|
|
485
|
-
if (jsonMatch) {
|
|
486
|
-
try {
|
|
487
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
488
|
-
sdkConfig = {
|
|
489
|
-
apiKey: parsed.apiKey || null,
|
|
490
|
-
authDomain: parsed.authDomain || null,
|
|
491
|
-
projectId: parsed.projectId || null,
|
|
492
|
-
storageBucket: parsed.storageBucket || null,
|
|
493
|
-
messagingSenderId: parsed.messagingSenderId || null,
|
|
494
|
-
appId: parsed.appId || null,
|
|
495
|
-
};
|
|
496
|
-
} catch {
|
|
497
|
-
// Not valid JSON — fall back to regex.
|
|
498
|
-
const parseField = (field) => {
|
|
499
|
-
const regex = new RegExp(`"?${field}"?:\\s*"([^"]+)"`);
|
|
500
|
-
const match = sdkOutput.match(regex);
|
|
501
|
-
return match ? match[1] : null;
|
|
502
|
-
};
|
|
503
|
-
sdkConfig = {
|
|
504
|
-
apiKey: parseField("apiKey"),
|
|
505
|
-
authDomain: parseField("authDomain"),
|
|
506
|
-
projectId: parseField("projectId"),
|
|
507
|
-
storageBucket: parseField("storageBucket"),
|
|
508
|
-
messagingSenderId: parseField("messagingSenderId"),
|
|
509
|
-
appId: parseField("appId"),
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
|
|
514
501
|
if (!sdkConfig.apiKey || !sdkConfig.projectId) {
|
|
515
|
-
throw new Error(
|
|
516
|
-
"Could not parse apiKey or projectId from SDK config output.",
|
|
517
|
-
);
|
|
502
|
+
throw new Error("SDK config is missing apiKey or projectId.");
|
|
518
503
|
}
|
|
519
504
|
|
|
520
505
|
console.log(chalk.green(" ✓ SDK config retrieved"));
|