clawfire 0.6.4 → 0.6.6
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/dist/cli.js +1 -1
- package/dist/{dev-server-7JCKX7OD.js → dev-server-PQP33VSE.js} +178 -49
- package/dist/dev.cjs +177 -48
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.js +178 -49
- package/dist/dev.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -269,7 +269,7 @@ async function runDevServer() {
|
|
|
269
269
|
const port = portArg ? parseInt(portArg.split("=")[1], 10) : 3e3;
|
|
270
270
|
const apiPort = apiPortArg ? parseInt(apiPortArg.split("=")[1], 10) : 3456;
|
|
271
271
|
const noHotReload = args.includes("--no-hot-reload");
|
|
272
|
-
const { startDevServer } = await import("./dev-server-
|
|
272
|
+
const { startDevServer } = await import("./dev-server-PQP33VSE.js");
|
|
273
273
|
await startDevServer({
|
|
274
274
|
projectDir,
|
|
275
275
|
port,
|
|
@@ -1856,8 +1856,9 @@ function generateDashboardHtml(options) {
|
|
|
1856
1856
|
}
|
|
1857
1857
|
|
|
1858
1858
|
btn.disabled = true;
|
|
1859
|
-
btn.textContent = 'Setting...';
|
|
1860
|
-
status.
|
|
1859
|
+
btn.textContent = 'Setting up...';
|
|
1860
|
+
status.textContent = 'Selecting project, detecting web app, auto-filling config...';
|
|
1861
|
+
status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a0a1c;border:1px solid #3b82f6;color:#93c5fd;';
|
|
1861
1862
|
|
|
1862
1863
|
fetch(API + '/__dev/setup/select-project', {
|
|
1863
1864
|
method: 'POST',
|
|
@@ -1867,8 +1868,11 @@ function generateDashboardHtml(options) {
|
|
|
1867
1868
|
.then(function(r) { return r.json(); })
|
|
1868
1869
|
.then(function(data) {
|
|
1869
1870
|
if (data.success) {
|
|
1870
|
-
|
|
1871
|
-
status.
|
|
1871
|
+
var msg = data.steps ? data.steps.join('\\n') : data.message;
|
|
1872
|
+
status.textContent = msg;
|
|
1873
|
+
status.style.whiteSpace = 'pre-line';
|
|
1874
|
+
status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;white-space:pre-line;';
|
|
1875
|
+
btn.textContent = 'Done';
|
|
1872
1876
|
setTimeout(refreshSetupStatus, 1000);
|
|
1873
1877
|
} else {
|
|
1874
1878
|
status.textContent = data.message;
|
|
@@ -2579,7 +2583,7 @@ function generateDashboardHtml(options) {
|
|
|
2579
2583
|
import { execFile as execFile2, spawn } from "child_process";
|
|
2580
2584
|
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
2581
2585
|
import { resolve as resolve4, join as join3 } from "path";
|
|
2582
|
-
import { tmpdir, platform } from "os";
|
|
2586
|
+
import { tmpdir, platform, homedir } from "os";
|
|
2583
2587
|
var FirebaseSetup = class {
|
|
2584
2588
|
projectDir;
|
|
2585
2589
|
stateFilePath;
|
|
@@ -3004,8 +3008,57 @@ var FirebaseSetup = class {
|
|
|
3004
3008
|
}
|
|
3005
3009
|
// ─── Firestore Database Automation ──────────────────────────────────
|
|
3006
3010
|
/**
|
|
3007
|
-
*
|
|
3008
|
-
*
|
|
3011
|
+
* Read Firebase CLI's stored OAuth token and exchange for access token.
|
|
3012
|
+
* No gcloud CLI needed — uses the token from `firebase login`.
|
|
3013
|
+
*/
|
|
3014
|
+
async getFirebaseAccessToken() {
|
|
3015
|
+
const home = homedir();
|
|
3016
|
+
const configPath = join3(home, ".config", "configstore", "firebase-tools.json");
|
|
3017
|
+
if (!existsSync5(configPath)) return null;
|
|
3018
|
+
try {
|
|
3019
|
+
const config = JSON.parse(readFileSync4(configPath, "utf-8"));
|
|
3020
|
+
let refreshToken = null;
|
|
3021
|
+
if (config?.activeAccounts) {
|
|
3022
|
+
const activeEmail = config?.user?.email;
|
|
3023
|
+
if (activeEmail && config.activeAccounts[activeEmail]?.tokens?.refresh_token) {
|
|
3024
|
+
refreshToken = config.activeAccounts[activeEmail].tokens.refresh_token;
|
|
3025
|
+
}
|
|
3026
|
+
if (!refreshToken) {
|
|
3027
|
+
for (const account of Object.values(config.activeAccounts)) {
|
|
3028
|
+
if (account?.tokens?.refresh_token) {
|
|
3029
|
+
refreshToken = account.tokens.refresh_token;
|
|
3030
|
+
break;
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
if (!refreshToken && config?.tokens?.refresh_token) {
|
|
3036
|
+
refreshToken = config.tokens.refresh_token;
|
|
3037
|
+
}
|
|
3038
|
+
if (!refreshToken) return null;
|
|
3039
|
+
const body = new URLSearchParams({
|
|
3040
|
+
grant_type: "refresh_token",
|
|
3041
|
+
refresh_token: refreshToken,
|
|
3042
|
+
client_id: "563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com",
|
|
3043
|
+
client_secret: "j9iVZfS8kkCEFUPaAeJV0sAi"
|
|
3044
|
+
});
|
|
3045
|
+
const res = await fetch("https://oauth2.googleapis.com/token", {
|
|
3046
|
+
method: "POST",
|
|
3047
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3048
|
+
body: body.toString()
|
|
3049
|
+
});
|
|
3050
|
+
if (res.ok) {
|
|
3051
|
+
const data = await res.json();
|
|
3052
|
+
return data.access_token || null;
|
|
3053
|
+
}
|
|
3054
|
+
} catch {
|
|
3055
|
+
}
|
|
3056
|
+
return null;
|
|
3057
|
+
}
|
|
3058
|
+
/**
|
|
3059
|
+
* Enable Firestore API using Google Service Usage REST API.
|
|
3060
|
+
* Uses Firebase CLI's stored auth token — no gcloud CLI needed.
|
|
3061
|
+
* Falls back to gcloud CLI, then manual URL.
|
|
3009
3062
|
*/
|
|
3010
3063
|
async enableFirestoreApi() {
|
|
3011
3064
|
const state = this.loadState();
|
|
@@ -3023,24 +3076,46 @@ var FirebaseSetup = class {
|
|
|
3023
3076
|
if (!projectId) {
|
|
3024
3077
|
return { success: false, message: "No project ID found. Select a project first." };
|
|
3025
3078
|
}
|
|
3079
|
+
try {
|
|
3080
|
+
const accessToken = await this.getFirebaseAccessToken();
|
|
3081
|
+
if (accessToken) {
|
|
3082
|
+
const res = await fetch(
|
|
3083
|
+
`https://serviceusage.googleapis.com/v1/projects/${projectId}/services/firestore.googleapis.com:enable`,
|
|
3084
|
+
{
|
|
3085
|
+
method: "POST",
|
|
3086
|
+
headers: {
|
|
3087
|
+
Authorization: `Bearer ${accessToken}`,
|
|
3088
|
+
"Content-Type": "application/json"
|
|
3089
|
+
}
|
|
3090
|
+
}
|
|
3091
|
+
);
|
|
3092
|
+
if (res.ok) {
|
|
3093
|
+
return { success: true, message: "Firestore API enabled via REST API." };
|
|
3094
|
+
}
|
|
3095
|
+
if (res.status === 409) {
|
|
3096
|
+
return { success: true, message: "Firestore API already enabled." };
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
} catch {
|
|
3100
|
+
}
|
|
3026
3101
|
try {
|
|
3027
3102
|
await this.execTimeout(
|
|
3028
3103
|
"gcloud",
|
|
3029
3104
|
["services", "enable", "firestore.googleapis.com", "--project", projectId],
|
|
3030
3105
|
6e4
|
|
3031
3106
|
);
|
|
3032
|
-
return { success: true, message: "Firestore API enabled." };
|
|
3107
|
+
return { success: true, message: "Firestore API enabled via gcloud." };
|
|
3033
3108
|
} catch (err) {
|
|
3034
3109
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3035
|
-
if (msg.includes("ENOENT") || msg.includes("not found") || msg.includes("command not found")) {
|
|
3036
|
-
return {
|
|
3037
|
-
success: false,
|
|
3038
|
-
message: `gcloud CLI not found. Please enable the Firestore API manually:
|
|
3039
|
-
https://console.developers.google.com/apis/api/firestore.googleapis.com/overview?project=${projectId}`
|
|
3040
|
-
};
|
|
3110
|
+
if (!(msg.includes("ENOENT") || msg.includes("not found") || msg.includes("command not found"))) {
|
|
3111
|
+
return { success: false, message: `Failed to enable Firestore API: ${msg}` };
|
|
3041
3112
|
}
|
|
3042
|
-
return { success: false, message: `Failed to enable Firestore API: ${msg}` };
|
|
3043
3113
|
}
|
|
3114
|
+
return {
|
|
3115
|
+
success: false,
|
|
3116
|
+
message: `Failed to enable Firestore API automatically. Please enable it manually:
|
|
3117
|
+
https://console.developers.google.com/apis/api/firestore.googleapis.com/overview?project=${projectId}`
|
|
3118
|
+
};
|
|
3044
3119
|
}
|
|
3045
3120
|
/**
|
|
3046
3121
|
* Create Firestore database via CLI.
|
|
@@ -3048,41 +3123,48 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
|
|
|
3048
3123
|
* Handles "ALREADY_EXISTS" gracefully — returns success.
|
|
3049
3124
|
*/
|
|
3050
3125
|
async createFirestoreDatabase(location = "nam5") {
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
)
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
|
|
3061
|
-
return { success: true, message: "Firestore database already exists." };
|
|
3062
|
-
}
|
|
3063
|
-
if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
|
|
3064
|
-
const enableResult = await this.enableFirestoreApi();
|
|
3065
|
-
if (!enableResult.success) {
|
|
3066
|
-
return enableResult;
|
|
3126
|
+
const dbArgs = ["firestore:databases:create", "(default)", "--location", location, "--json"];
|
|
3127
|
+
const tryCreate = async () => {
|
|
3128
|
+
try {
|
|
3129
|
+
await this.execTimeout("firebase", dbArgs, 6e4);
|
|
3130
|
+
return { ok: true, alreadyExists: false, needsApi: false, msg: "" };
|
|
3131
|
+
} catch (err) {
|
|
3132
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3133
|
+
if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
|
|
3134
|
+
return { ok: true, alreadyExists: true, needsApi: false, msg };
|
|
3067
3135
|
}
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
await this.execTimeout(
|
|
3071
|
-
"firebase",
|
|
3072
|
-
["firestore:databases:create", "(default)", "--location", location, "--json"],
|
|
3073
|
-
6e4
|
|
3074
|
-
);
|
|
3075
|
-
return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
|
|
3076
|
-
} catch (retryErr) {
|
|
3077
|
-
const retryMsg = retryErr instanceof Error ? retryErr.message : "Unknown error";
|
|
3078
|
-
if (retryMsg.includes("ALREADY_EXISTS") || retryMsg.includes("already exists")) {
|
|
3079
|
-
return { success: true, message: "Firestore database already exists." };
|
|
3080
|
-
}
|
|
3081
|
-
return { success: false, message: `Failed to create Firestore database after enabling API: ${retryMsg}` };
|
|
3136
|
+
if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
|
|
3137
|
+
return { ok: false, alreadyExists: false, needsApi: true, msg };
|
|
3082
3138
|
}
|
|
3139
|
+
return { ok: false, alreadyExists: false, needsApi: false, msg };
|
|
3140
|
+
}
|
|
3141
|
+
};
|
|
3142
|
+
const first = await tryCreate();
|
|
3143
|
+
if (first.ok) {
|
|
3144
|
+
return { success: true, message: first.alreadyExists ? "Firestore database already exists." : `Firestore database created (location: ${location}).` };
|
|
3145
|
+
}
|
|
3146
|
+
if (!first.needsApi) {
|
|
3147
|
+
return { success: false, message: `Failed to create Firestore database: ${first.msg}` };
|
|
3148
|
+
}
|
|
3149
|
+
const enableResult = await this.enableFirestoreApi();
|
|
3150
|
+
if (!enableResult.success) {
|
|
3151
|
+
return enableResult;
|
|
3152
|
+
}
|
|
3153
|
+
const waits = [5e3, 5e3, 1e4, 1e4, 15e3];
|
|
3154
|
+
for (let i = 0; i < waits.length; i++) {
|
|
3155
|
+
await new Promise((r) => setTimeout(r, waits[i]));
|
|
3156
|
+
const retry = await tryCreate();
|
|
3157
|
+
if (retry.ok) {
|
|
3158
|
+
return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
|
|
3159
|
+
}
|
|
3160
|
+
if (!retry.needsApi) {
|
|
3161
|
+
return { success: false, message: `Failed to create Firestore database: ${retry.msg}` };
|
|
3083
3162
|
}
|
|
3084
|
-
return { success: false, message: `Failed to create Firestore database: ${msg}` };
|
|
3085
3163
|
}
|
|
3164
|
+
return {
|
|
3165
|
+
success: false,
|
|
3166
|
+
message: "Firestore API was enabled but database creation timed out waiting for propagation. Please wait a minute and try again."
|
|
3167
|
+
};
|
|
3086
3168
|
}
|
|
3087
3169
|
/**
|
|
3088
3170
|
* Deploy open Firestore security rules for dev testing.
|
|
@@ -4102,10 +4184,57 @@ ${liveReloadScript}
|
|
|
4102
4184
|
sendJson({ success: false, message: "projectId is required" }, 400);
|
|
4103
4185
|
return;
|
|
4104
4186
|
}
|
|
4105
|
-
|
|
4187
|
+
(async () => {
|
|
4188
|
+
const selectResult = await this.firebaseSetup.selectProject(data.projectId);
|
|
4189
|
+
if (!selectResult.success) {
|
|
4190
|
+
sendJson(selectResult);
|
|
4191
|
+
return;
|
|
4192
|
+
}
|
|
4193
|
+
const steps = [selectResult.message];
|
|
4194
|
+
try {
|
|
4195
|
+
const { apps } = await this.firebaseSetup.listWebApps();
|
|
4196
|
+
let webAppId = "";
|
|
4197
|
+
if (apps.length > 0) {
|
|
4198
|
+
webAppId = apps[0].appId;
|
|
4199
|
+
this.firebaseSetup.selectWebApp(apps[0].appId, apps[0].displayName);
|
|
4200
|
+
steps.push(`Web app "${apps[0].displayName}" selected.`);
|
|
4201
|
+
} else {
|
|
4202
|
+
const createResult = await this.firebaseSetup.createWebApp(data.projectId);
|
|
4203
|
+
if (createResult.success && createResult.appId) {
|
|
4204
|
+
webAppId = createResult.appId;
|
|
4205
|
+
steps.push(`Web app "${data.projectId}" created.`);
|
|
4206
|
+
} else {
|
|
4207
|
+
steps.push(`Web app creation skipped: ${createResult.message}`);
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
if (webAppId) {
|
|
4211
|
+
try {
|
|
4212
|
+
const sdkConfig = await fetchFirebaseSdkConfig(this.options.projectDir, webAppId);
|
|
4213
|
+
const fields = {};
|
|
4214
|
+
for (const [key, value] of Object.entries(sdkConfig)) {
|
|
4215
|
+
if (value && typeof value === "string") {
|
|
4216
|
+
fields[key] = value;
|
|
4217
|
+
}
|
|
4218
|
+
}
|
|
4219
|
+
if (Object.keys(fields).length > 0) {
|
|
4220
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
4221
|
+
try {
|
|
4222
|
+
this.updateProjectConfig(key, value);
|
|
4223
|
+
} catch {
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
4226
|
+
steps.push("Config auto-filled in clawfire.config.ts.");
|
|
4227
|
+
}
|
|
4228
|
+
} catch (configErr) {
|
|
4229
|
+
steps.push(`Config auto-fill skipped: ${configErr instanceof Error ? configErr.message : "unknown error"}`);
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
} catch (autoFillErr) {
|
|
4233
|
+
steps.push(`Auto-setup partial: ${autoFillErr instanceof Error ? autoFillErr.message : "unknown error"}`);
|
|
4234
|
+
}
|
|
4106
4235
|
clearFirebaseStatusCache();
|
|
4107
|
-
sendJson(
|
|
4108
|
-
}).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
|
|
4236
|
+
sendJson({ success: true, message: steps.join(" "), steps });
|
|
4237
|
+
})().catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
|
|
4109
4238
|
} catch {
|
|
4110
4239
|
sendJson({ success: false, message: "Invalid JSON body" }, 400);
|
|
4111
4240
|
}
|
package/dist/dev.cjs
CHANGED
|
@@ -2268,8 +2268,9 @@ function generateDashboardHtml(options) {
|
|
|
2268
2268
|
}
|
|
2269
2269
|
|
|
2270
2270
|
btn.disabled = true;
|
|
2271
|
-
btn.textContent = 'Setting...';
|
|
2272
|
-
status.
|
|
2271
|
+
btn.textContent = 'Setting up...';
|
|
2272
|
+
status.textContent = 'Selecting project, detecting web app, auto-filling config...';
|
|
2273
|
+
status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a0a1c;border:1px solid #3b82f6;color:#93c5fd;';
|
|
2273
2274
|
|
|
2274
2275
|
fetch(API + '/__dev/setup/select-project', {
|
|
2275
2276
|
method: 'POST',
|
|
@@ -2279,8 +2280,11 @@ function generateDashboardHtml(options) {
|
|
|
2279
2280
|
.then(function(r) { return r.json(); })
|
|
2280
2281
|
.then(function(data) {
|
|
2281
2282
|
if (data.success) {
|
|
2282
|
-
|
|
2283
|
-
status.
|
|
2283
|
+
var msg = data.steps ? data.steps.join('\\n') : data.message;
|
|
2284
|
+
status.textContent = msg;
|
|
2285
|
+
status.style.whiteSpace = 'pre-line';
|
|
2286
|
+
status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;white-space:pre-line;';
|
|
2287
|
+
btn.textContent = 'Done';
|
|
2284
2288
|
setTimeout(refreshSetupStatus, 1000);
|
|
2285
2289
|
} else {
|
|
2286
2290
|
status.textContent = data.message;
|
|
@@ -3416,8 +3420,57 @@ var FirebaseSetup = class {
|
|
|
3416
3420
|
}
|
|
3417
3421
|
// ─── Firestore Database Automation ──────────────────────────────────
|
|
3418
3422
|
/**
|
|
3419
|
-
*
|
|
3420
|
-
*
|
|
3423
|
+
* Read Firebase CLI's stored OAuth token and exchange for access token.
|
|
3424
|
+
* No gcloud CLI needed — uses the token from `firebase login`.
|
|
3425
|
+
*/
|
|
3426
|
+
async getFirebaseAccessToken() {
|
|
3427
|
+
const home = (0, import_node_os.homedir)();
|
|
3428
|
+
const configPath = (0, import_node_path4.join)(home, ".config", "configstore", "firebase-tools.json");
|
|
3429
|
+
if (!(0, import_node_fs4.existsSync)(configPath)) return null;
|
|
3430
|
+
try {
|
|
3431
|
+
const config = JSON.parse((0, import_node_fs4.readFileSync)(configPath, "utf-8"));
|
|
3432
|
+
let refreshToken = null;
|
|
3433
|
+
if (config?.activeAccounts) {
|
|
3434
|
+
const activeEmail = config?.user?.email;
|
|
3435
|
+
if (activeEmail && config.activeAccounts[activeEmail]?.tokens?.refresh_token) {
|
|
3436
|
+
refreshToken = config.activeAccounts[activeEmail].tokens.refresh_token;
|
|
3437
|
+
}
|
|
3438
|
+
if (!refreshToken) {
|
|
3439
|
+
for (const account of Object.values(config.activeAccounts)) {
|
|
3440
|
+
if (account?.tokens?.refresh_token) {
|
|
3441
|
+
refreshToken = account.tokens.refresh_token;
|
|
3442
|
+
break;
|
|
3443
|
+
}
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
if (!refreshToken && config?.tokens?.refresh_token) {
|
|
3448
|
+
refreshToken = config.tokens.refresh_token;
|
|
3449
|
+
}
|
|
3450
|
+
if (!refreshToken) return null;
|
|
3451
|
+
const body = new URLSearchParams({
|
|
3452
|
+
grant_type: "refresh_token",
|
|
3453
|
+
refresh_token: refreshToken,
|
|
3454
|
+
client_id: "563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com",
|
|
3455
|
+
client_secret: "j9iVZfS8kkCEFUPaAeJV0sAi"
|
|
3456
|
+
});
|
|
3457
|
+
const res = await fetch("https://oauth2.googleapis.com/token", {
|
|
3458
|
+
method: "POST",
|
|
3459
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3460
|
+
body: body.toString()
|
|
3461
|
+
});
|
|
3462
|
+
if (res.ok) {
|
|
3463
|
+
const data = await res.json();
|
|
3464
|
+
return data.access_token || null;
|
|
3465
|
+
}
|
|
3466
|
+
} catch {
|
|
3467
|
+
}
|
|
3468
|
+
return null;
|
|
3469
|
+
}
|
|
3470
|
+
/**
|
|
3471
|
+
* Enable Firestore API using Google Service Usage REST API.
|
|
3472
|
+
* Uses Firebase CLI's stored auth token — no gcloud CLI needed.
|
|
3473
|
+
* Falls back to gcloud CLI, then manual URL.
|
|
3421
3474
|
*/
|
|
3422
3475
|
async enableFirestoreApi() {
|
|
3423
3476
|
const state = this.loadState();
|
|
@@ -3435,24 +3488,46 @@ var FirebaseSetup = class {
|
|
|
3435
3488
|
if (!projectId) {
|
|
3436
3489
|
return { success: false, message: "No project ID found. Select a project first." };
|
|
3437
3490
|
}
|
|
3491
|
+
try {
|
|
3492
|
+
const accessToken = await this.getFirebaseAccessToken();
|
|
3493
|
+
if (accessToken) {
|
|
3494
|
+
const res = await fetch(
|
|
3495
|
+
`https://serviceusage.googleapis.com/v1/projects/${projectId}/services/firestore.googleapis.com:enable`,
|
|
3496
|
+
{
|
|
3497
|
+
method: "POST",
|
|
3498
|
+
headers: {
|
|
3499
|
+
Authorization: `Bearer ${accessToken}`,
|
|
3500
|
+
"Content-Type": "application/json"
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
);
|
|
3504
|
+
if (res.ok) {
|
|
3505
|
+
return { success: true, message: "Firestore API enabled via REST API." };
|
|
3506
|
+
}
|
|
3507
|
+
if (res.status === 409) {
|
|
3508
|
+
return { success: true, message: "Firestore API already enabled." };
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
} catch {
|
|
3512
|
+
}
|
|
3438
3513
|
try {
|
|
3439
3514
|
await this.execTimeout(
|
|
3440
3515
|
"gcloud",
|
|
3441
3516
|
["services", "enable", "firestore.googleapis.com", "--project", projectId],
|
|
3442
3517
|
6e4
|
|
3443
3518
|
);
|
|
3444
|
-
return { success: true, message: "Firestore API enabled." };
|
|
3519
|
+
return { success: true, message: "Firestore API enabled via gcloud." };
|
|
3445
3520
|
} catch (err) {
|
|
3446
3521
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3447
|
-
if (msg.includes("ENOENT") || msg.includes("not found") || msg.includes("command not found")) {
|
|
3448
|
-
return {
|
|
3449
|
-
success: false,
|
|
3450
|
-
message: `gcloud CLI not found. Please enable the Firestore API manually:
|
|
3451
|
-
https://console.developers.google.com/apis/api/firestore.googleapis.com/overview?project=${projectId}`
|
|
3452
|
-
};
|
|
3522
|
+
if (!(msg.includes("ENOENT") || msg.includes("not found") || msg.includes("command not found"))) {
|
|
3523
|
+
return { success: false, message: `Failed to enable Firestore API: ${msg}` };
|
|
3453
3524
|
}
|
|
3454
|
-
return { success: false, message: `Failed to enable Firestore API: ${msg}` };
|
|
3455
3525
|
}
|
|
3526
|
+
return {
|
|
3527
|
+
success: false,
|
|
3528
|
+
message: `Failed to enable Firestore API automatically. Please enable it manually:
|
|
3529
|
+
https://console.developers.google.com/apis/api/firestore.googleapis.com/overview?project=${projectId}`
|
|
3530
|
+
};
|
|
3456
3531
|
}
|
|
3457
3532
|
/**
|
|
3458
3533
|
* Create Firestore database via CLI.
|
|
@@ -3460,41 +3535,48 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
|
|
|
3460
3535
|
* Handles "ALREADY_EXISTS" gracefully — returns success.
|
|
3461
3536
|
*/
|
|
3462
3537
|
async createFirestoreDatabase(location = "nam5") {
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
)
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
|
|
3473
|
-
return { success: true, message: "Firestore database already exists." };
|
|
3474
|
-
}
|
|
3475
|
-
if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
|
|
3476
|
-
const enableResult = await this.enableFirestoreApi();
|
|
3477
|
-
if (!enableResult.success) {
|
|
3478
|
-
return enableResult;
|
|
3538
|
+
const dbArgs = ["firestore:databases:create", "(default)", "--location", location, "--json"];
|
|
3539
|
+
const tryCreate = async () => {
|
|
3540
|
+
try {
|
|
3541
|
+
await this.execTimeout("firebase", dbArgs, 6e4);
|
|
3542
|
+
return { ok: true, alreadyExists: false, needsApi: false, msg: "" };
|
|
3543
|
+
} catch (err) {
|
|
3544
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3545
|
+
if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
|
|
3546
|
+
return { ok: true, alreadyExists: true, needsApi: false, msg };
|
|
3479
3547
|
}
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
await this.execTimeout(
|
|
3483
|
-
"firebase",
|
|
3484
|
-
["firestore:databases:create", "(default)", "--location", location, "--json"],
|
|
3485
|
-
6e4
|
|
3486
|
-
);
|
|
3487
|
-
return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
|
|
3488
|
-
} catch (retryErr) {
|
|
3489
|
-
const retryMsg = retryErr instanceof Error ? retryErr.message : "Unknown error";
|
|
3490
|
-
if (retryMsg.includes("ALREADY_EXISTS") || retryMsg.includes("already exists")) {
|
|
3491
|
-
return { success: true, message: "Firestore database already exists." };
|
|
3492
|
-
}
|
|
3493
|
-
return { success: false, message: `Failed to create Firestore database after enabling API: ${retryMsg}` };
|
|
3548
|
+
if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
|
|
3549
|
+
return { ok: false, alreadyExists: false, needsApi: true, msg };
|
|
3494
3550
|
}
|
|
3551
|
+
return { ok: false, alreadyExists: false, needsApi: false, msg };
|
|
3552
|
+
}
|
|
3553
|
+
};
|
|
3554
|
+
const first = await tryCreate();
|
|
3555
|
+
if (first.ok) {
|
|
3556
|
+
return { success: true, message: first.alreadyExists ? "Firestore database already exists." : `Firestore database created (location: ${location}).` };
|
|
3557
|
+
}
|
|
3558
|
+
if (!first.needsApi) {
|
|
3559
|
+
return { success: false, message: `Failed to create Firestore database: ${first.msg}` };
|
|
3560
|
+
}
|
|
3561
|
+
const enableResult = await this.enableFirestoreApi();
|
|
3562
|
+
if (!enableResult.success) {
|
|
3563
|
+
return enableResult;
|
|
3564
|
+
}
|
|
3565
|
+
const waits = [5e3, 5e3, 1e4, 1e4, 15e3];
|
|
3566
|
+
for (let i = 0; i < waits.length; i++) {
|
|
3567
|
+
await new Promise((r) => setTimeout(r, waits[i]));
|
|
3568
|
+
const retry = await tryCreate();
|
|
3569
|
+
if (retry.ok) {
|
|
3570
|
+
return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
|
|
3571
|
+
}
|
|
3572
|
+
if (!retry.needsApi) {
|
|
3573
|
+
return { success: false, message: `Failed to create Firestore database: ${retry.msg}` };
|
|
3495
3574
|
}
|
|
3496
|
-
return { success: false, message: `Failed to create Firestore database: ${msg}` };
|
|
3497
3575
|
}
|
|
3576
|
+
return {
|
|
3577
|
+
success: false,
|
|
3578
|
+
message: "Firestore API was enabled but database creation timed out waiting for propagation. Please wait a minute and try again."
|
|
3579
|
+
};
|
|
3498
3580
|
}
|
|
3499
3581
|
/**
|
|
3500
3582
|
* Deploy open Firestore security rules for dev testing.
|
|
@@ -4514,10 +4596,57 @@ ${liveReloadScript}
|
|
|
4514
4596
|
sendJson({ success: false, message: "projectId is required" }, 400);
|
|
4515
4597
|
return;
|
|
4516
4598
|
}
|
|
4517
|
-
|
|
4599
|
+
(async () => {
|
|
4600
|
+
const selectResult = await this.firebaseSetup.selectProject(data.projectId);
|
|
4601
|
+
if (!selectResult.success) {
|
|
4602
|
+
sendJson(selectResult);
|
|
4603
|
+
return;
|
|
4604
|
+
}
|
|
4605
|
+
const steps = [selectResult.message];
|
|
4606
|
+
try {
|
|
4607
|
+
const { apps } = await this.firebaseSetup.listWebApps();
|
|
4608
|
+
let webAppId = "";
|
|
4609
|
+
if (apps.length > 0) {
|
|
4610
|
+
webAppId = apps[0].appId;
|
|
4611
|
+
this.firebaseSetup.selectWebApp(apps[0].appId, apps[0].displayName);
|
|
4612
|
+
steps.push(`Web app "${apps[0].displayName}" selected.`);
|
|
4613
|
+
} else {
|
|
4614
|
+
const createResult = await this.firebaseSetup.createWebApp(data.projectId);
|
|
4615
|
+
if (createResult.success && createResult.appId) {
|
|
4616
|
+
webAppId = createResult.appId;
|
|
4617
|
+
steps.push(`Web app "${data.projectId}" created.`);
|
|
4618
|
+
} else {
|
|
4619
|
+
steps.push(`Web app creation skipped: ${createResult.message}`);
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
if (webAppId) {
|
|
4623
|
+
try {
|
|
4624
|
+
const sdkConfig = await fetchFirebaseSdkConfig(this.options.projectDir, webAppId);
|
|
4625
|
+
const fields = {};
|
|
4626
|
+
for (const [key, value] of Object.entries(sdkConfig)) {
|
|
4627
|
+
if (value && typeof value === "string") {
|
|
4628
|
+
fields[key] = value;
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
if (Object.keys(fields).length > 0) {
|
|
4632
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
4633
|
+
try {
|
|
4634
|
+
this.updateProjectConfig(key, value);
|
|
4635
|
+
} catch {
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
steps.push("Config auto-filled in clawfire.config.ts.");
|
|
4639
|
+
}
|
|
4640
|
+
} catch (configErr) {
|
|
4641
|
+
steps.push(`Config auto-fill skipped: ${configErr instanceof Error ? configErr.message : "unknown error"}`);
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
} catch (autoFillErr) {
|
|
4645
|
+
steps.push(`Auto-setup partial: ${autoFillErr instanceof Error ? autoFillErr.message : "unknown error"}`);
|
|
4646
|
+
}
|
|
4518
4647
|
clearFirebaseStatusCache();
|
|
4519
|
-
sendJson(
|
|
4520
|
-
}).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
|
|
4648
|
+
sendJson({ success: true, message: steps.join(" "), steps });
|
|
4649
|
+
})().catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
|
|
4521
4650
|
} catch {
|
|
4522
4651
|
sendJson({ success: false, message: "Invalid JSON body" }, 400);
|
|
4523
4652
|
}
|