clawfire 0.6.8 → 0.6.10
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-ESMIOA6C.js → dev-server-FNRFWJD3.js} +189 -16
- package/dist/dev.cjs +189 -16
- package/dist/dev.cjs.map +1 -1
- package/dist/dev.js +189 -16
- 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-FNRFWJD3.js");
|
|
273
273
|
await startDevServer({
|
|
274
274
|
projectDir,
|
|
275
275
|
port,
|
|
@@ -2956,7 +2956,7 @@ var FirebaseSetup = class {
|
|
|
2956
2956
|
return { success: true, message: `Web app "${displayName}" selected.` };
|
|
2957
2957
|
}
|
|
2958
2958
|
// ─── Deploy ─────────────────────────────────────────────────────────
|
|
2959
|
-
async deployHosting() {
|
|
2959
|
+
async deployHosting(targets = "hosting") {
|
|
2960
2960
|
const firebaseJsonPath = resolve4(this.projectDir, "firebase.json");
|
|
2961
2961
|
if (!existsSync5(firebaseJsonPath)) {
|
|
2962
2962
|
return { success: false, message: "firebase.json not found. Enable hosting first." };
|
|
@@ -2969,12 +2969,12 @@ var FirebaseSetup = class {
|
|
|
2969
2969
|
} catch {
|
|
2970
2970
|
return { success: false, message: "Invalid firebase.json." };
|
|
2971
2971
|
}
|
|
2972
|
+
const timeoutMs = targets.includes("functions") ? 3e5 : 12e4;
|
|
2972
2973
|
try {
|
|
2973
2974
|
const output = await this.execTimeout(
|
|
2974
2975
|
"firebase",
|
|
2975
|
-
["deploy", "--only",
|
|
2976
|
-
|
|
2977
|
-
// 2 min timeout
|
|
2976
|
+
["deploy", "--only", targets, "--json"],
|
|
2977
|
+
timeoutMs
|
|
2978
2978
|
);
|
|
2979
2979
|
let url = "";
|
|
2980
2980
|
try {
|
|
@@ -2997,12 +2997,147 @@ var FirebaseSetup = class {
|
|
|
2997
2997
|
url = `https://${state.projectId}.web.app`;
|
|
2998
2998
|
}
|
|
2999
2999
|
}
|
|
3000
|
-
|
|
3000
|
+
const label = targets.includes("functions") ? "Hosting + Functions" : "Hosting";
|
|
3001
|
+
return { success: true, url: url || void 0, message: `${label} deployed successfully!` };
|
|
3001
3002
|
} catch (err) {
|
|
3002
3003
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3003
3004
|
return { success: false, message: `Deploy failed: ${msg}` };
|
|
3004
3005
|
}
|
|
3005
3006
|
}
|
|
3007
|
+
// ─── Full Deploy Pipeline ────────────────────────────────────────────
|
|
3008
|
+
/**
|
|
3009
|
+
* Sync environment variables to functions/.env for production.
|
|
3010
|
+
* Copies project root .env vars + injects CLAWFIRE_FIREBASE_CONFIG.
|
|
3011
|
+
*/
|
|
3012
|
+
syncEnvToFunctions(firebaseConfig) {
|
|
3013
|
+
const functionsDir = resolve4(this.projectDir, "functions");
|
|
3014
|
+
if (!existsSync5(functionsDir)) {
|
|
3015
|
+
return { success: false, message: "functions/ directory not found.", count: 0 };
|
|
3016
|
+
}
|
|
3017
|
+
const lines = [];
|
|
3018
|
+
const rootEnvPath = resolve4(this.projectDir, ".env");
|
|
3019
|
+
if (existsSync5(rootEnvPath)) {
|
|
3020
|
+
const content = readFileSync4(rootEnvPath, "utf-8");
|
|
3021
|
+
for (const rawLine of content.split("\n")) {
|
|
3022
|
+
let trimmed = rawLine.trim();
|
|
3023
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
3024
|
+
if (trimmed.startsWith("export ")) {
|
|
3025
|
+
trimmed = trimmed.slice(7);
|
|
3026
|
+
}
|
|
3027
|
+
const eqIdx = trimmed.indexOf("=");
|
|
3028
|
+
if (eqIdx === -1) continue;
|
|
3029
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
3030
|
+
if (key === "NPM_TOKEN" || key === "NODE_ENV") continue;
|
|
3031
|
+
lines.push(trimmed);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
if (firebaseConfig && Object.keys(firebaseConfig).length > 0) {
|
|
3035
|
+
const filtered = lines.filter((l) => !l.startsWith("CLAWFIRE_FIREBASE_CONFIG="));
|
|
3036
|
+
filtered.push(`CLAWFIRE_FIREBASE_CONFIG=${JSON.stringify(firebaseConfig)}`);
|
|
3037
|
+
lines.length = 0;
|
|
3038
|
+
lines.push(...filtered);
|
|
3039
|
+
}
|
|
3040
|
+
const functionsEnvPath = resolve4(functionsDir, ".env");
|
|
3041
|
+
writeFileSync3(functionsEnvPath, lines.join("\n") + "\n", "utf-8");
|
|
3042
|
+
return { success: true, message: `Synced ${lines.length} env vars to functions/.env`, count: lines.length };
|
|
3043
|
+
}
|
|
3044
|
+
/**
|
|
3045
|
+
* Ensure firebase.json is fully configured for deployment:
|
|
3046
|
+
* - functions config exists
|
|
3047
|
+
* - hosting has cleanUrls
|
|
3048
|
+
* - hosting has API rewrite (/api/** → api function) before catch-all
|
|
3049
|
+
*
|
|
3050
|
+
* Fixes existing configs created by older versions.
|
|
3051
|
+
*/
|
|
3052
|
+
ensureDeployConfig() {
|
|
3053
|
+
const firebaseJsonPath = resolve4(this.projectDir, "firebase.json");
|
|
3054
|
+
if (!existsSync5(firebaseJsonPath)) {
|
|
3055
|
+
return { success: false, message: "firebase.json not found.", changes: [] };
|
|
3056
|
+
}
|
|
3057
|
+
try {
|
|
3058
|
+
const config = JSON.parse(readFileSync4(firebaseJsonPath, "utf-8"));
|
|
3059
|
+
const changes = [];
|
|
3060
|
+
if (!config.functions) {
|
|
3061
|
+
config.functions = {
|
|
3062
|
+
source: "functions",
|
|
3063
|
+
runtime: "nodejs20",
|
|
3064
|
+
codebase: "clawfire"
|
|
3065
|
+
};
|
|
3066
|
+
changes.push("Added functions config");
|
|
3067
|
+
}
|
|
3068
|
+
if (config.hosting && !config.hosting.cleanUrls) {
|
|
3069
|
+
config.hosting.cleanUrls = true;
|
|
3070
|
+
changes.push("Added cleanUrls");
|
|
3071
|
+
}
|
|
3072
|
+
if (config.hosting) {
|
|
3073
|
+
if (!config.hosting.rewrites) {
|
|
3074
|
+
config.hosting.rewrites = [];
|
|
3075
|
+
}
|
|
3076
|
+
const rewrites = config.hosting.rewrites;
|
|
3077
|
+
const hasApiRewrite = rewrites.some(
|
|
3078
|
+
(r) => r.source === "/api/**" && r.function === "api"
|
|
3079
|
+
);
|
|
3080
|
+
if (!hasApiRewrite) {
|
|
3081
|
+
const apiRewrite = { source: "/api/**", function: "api" };
|
|
3082
|
+
const catchAllIndex = rewrites.findIndex((r) => r.source === "**");
|
|
3083
|
+
if (catchAllIndex >= 0) {
|
|
3084
|
+
rewrites.splice(catchAllIndex, 0, apiRewrite);
|
|
3085
|
+
} else {
|
|
3086
|
+
rewrites.push(apiRewrite);
|
|
3087
|
+
rewrites.push({ source: "**", destination: "/index.html" });
|
|
3088
|
+
}
|
|
3089
|
+
changes.push("Added API rewrite (/api/** \u2192 Cloud Function)");
|
|
3090
|
+
}
|
|
3091
|
+
}
|
|
3092
|
+
if (changes.length > 0) {
|
|
3093
|
+
writeFileSync3(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
3094
|
+
}
|
|
3095
|
+
return {
|
|
3096
|
+
success: true,
|
|
3097
|
+
message: changes.length > 0 ? changes.join(", ") : "Deploy config up to date.",
|
|
3098
|
+
changes
|
|
3099
|
+
};
|
|
3100
|
+
} catch {
|
|
3101
|
+
return { success: false, message: "Invalid firebase.json.", changes: [] };
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
/**
|
|
3105
|
+
* Install dependencies and build functions for deployment.
|
|
3106
|
+
*/
|
|
3107
|
+
async buildFunctions() {
|
|
3108
|
+
const functionsDir = resolve4(this.projectDir, "functions");
|
|
3109
|
+
if (!existsSync5(functionsDir)) {
|
|
3110
|
+
return { success: false, message: "functions/ directory not found." };
|
|
3111
|
+
}
|
|
3112
|
+
const pkgPath = resolve4(functionsDir, "package.json");
|
|
3113
|
+
if (!existsSync5(pkgPath)) {
|
|
3114
|
+
return { success: false, message: "functions/package.json not found." };
|
|
3115
|
+
}
|
|
3116
|
+
const nodeModulesPath = resolve4(functionsDir, "node_modules");
|
|
3117
|
+
if (!existsSync5(nodeModulesPath)) {
|
|
3118
|
+
try {
|
|
3119
|
+
await this.execTimeout("npm", ["install"], 12e4, functionsDir);
|
|
3120
|
+
} catch (err) {
|
|
3121
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3122
|
+
return { success: false, message: `npm install in functions/ failed: ${msg}` };
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
try {
|
|
3126
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
3127
|
+
if (pkg.scripts?.build) {
|
|
3128
|
+
await this.execTimeout("npm", ["run", "build"], 6e4, functionsDir);
|
|
3129
|
+
} else {
|
|
3130
|
+
const tsconfigPath = resolve4(functionsDir, "tsconfig.json");
|
|
3131
|
+
if (existsSync5(tsconfigPath)) {
|
|
3132
|
+
await this.execTimeout("npx", ["tsc"], 6e4, functionsDir);
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
return { success: true, message: "Functions built successfully." };
|
|
3136
|
+
} catch (err) {
|
|
3137
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3138
|
+
return { success: false, message: `Functions build failed: ${msg}` };
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3006
3141
|
// ─── Service Enable ────────────────────────────────────────────────
|
|
3007
3142
|
enableService(service) {
|
|
3008
3143
|
const firebaseJsonPath = resolve4(this.projectDir, "firebase.json");
|
|
@@ -3251,9 +3386,9 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
|
|
|
3251
3386
|
}
|
|
3252
3387
|
}
|
|
3253
3388
|
// ─── Helpers ───────────────────────────────────────────────────────
|
|
3254
|
-
execTimeout(command, args, timeoutMs) {
|
|
3389
|
+
execTimeout(command, args, timeoutMs, cwd) {
|
|
3255
3390
|
return new Promise((resolve6, reject) => {
|
|
3256
|
-
const proc = execFile2(command, args, { cwd: this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {
|
|
3391
|
+
const proc = execFile2(command, args, { cwd: cwd || this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {
|
|
3257
3392
|
if (err) {
|
|
3258
3393
|
const detail = stderr?.trim() || stdout?.trim() || "";
|
|
3259
3394
|
const enriched = new Error(`${err.message}${detail ? "\n" + detail : ""}`);
|
|
@@ -4416,22 +4551,27 @@ ${liveReloadScript}
|
|
|
4416
4551
|
if (url.pathname === "/__dev/deploy/hosting" && req.method === "POST") {
|
|
4417
4552
|
(async () => {
|
|
4418
4553
|
try {
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4554
|
+
const steps = [];
|
|
4555
|
+
const functionsDir = resolve5(this.options.projectDir, "functions");
|
|
4556
|
+
const hasFunctions = existsSync6(functionsDir);
|
|
4557
|
+
const projectConfig = this.readProjectConfig();
|
|
4558
|
+
const firebaseConfig = {};
|
|
4559
|
+
for (const field of projectConfig.fields) {
|
|
4560
|
+
if (!field.isPlaceholder) {
|
|
4561
|
+
firebaseConfig[field.key] = field.value;
|
|
4426
4562
|
}
|
|
4563
|
+
}
|
|
4564
|
+
if (this.pageCompiler.isActive()) {
|
|
4427
4565
|
const configScript = Object.keys(firebaseConfig).length > 0 ? `
|
|
4428
4566
|
<script>window.__CLAWFIRE_CONFIG__=${JSON.stringify(firebaseConfig)};</script>` : "";
|
|
4429
4567
|
const routerScript = generateProductionRouterScript();
|
|
4430
4568
|
const scriptToInject = configScript + routerScript;
|
|
4431
4569
|
const buildResult = this.pageCompiler.buildForProduction(this.publicDir, scriptToInject);
|
|
4432
4570
|
console.log(` \x1B[32m\u2713\x1B[0m Built ${buildResult.pages.length} pages for production`);
|
|
4571
|
+
steps.push(`${buildResult.pages.length} pages built`);
|
|
4433
4572
|
if (configScript) {
|
|
4434
4573
|
console.log(` \x1B[32m\u2713\x1B[0m Firebase config injected (projectId: ${firebaseConfig.projectId || "?"})`);
|
|
4574
|
+
steps.push("Firebase config injected");
|
|
4435
4575
|
}
|
|
4436
4576
|
if (buildResult.errors.length > 0) {
|
|
4437
4577
|
for (const err of buildResult.errors) {
|
|
@@ -4439,9 +4579,42 @@ ${liveReloadScript}
|
|
|
4439
4579
|
}
|
|
4440
4580
|
}
|
|
4441
4581
|
}
|
|
4442
|
-
const
|
|
4582
|
+
const deployConfig = this.firebaseSetup.ensureDeployConfig();
|
|
4583
|
+
if (deployConfig.success && deployConfig.changes.length > 0) {
|
|
4584
|
+
for (const change of deployConfig.changes) {
|
|
4585
|
+
console.log(` \x1B[32m\u2713\x1B[0m firebase.json: ${change}`);
|
|
4586
|
+
}
|
|
4587
|
+
steps.push(`firebase.json updated (${deployConfig.changes.length} fixes)`);
|
|
4588
|
+
}
|
|
4589
|
+
let deployFunctions = false;
|
|
4590
|
+
if (hasFunctions) {
|
|
4591
|
+
const syncResult = this.firebaseSetup.syncEnvToFunctions(firebaseConfig);
|
|
4592
|
+
if (syncResult.success) {
|
|
4593
|
+
console.log(` \x1B[32m\u2713\x1B[0m ${syncResult.message}`);
|
|
4594
|
+
steps.push(`${syncResult.count} env vars synced`);
|
|
4595
|
+
} else {
|
|
4596
|
+
console.log(` \x1B[33m\u26A0\x1B[0m Env sync: ${syncResult.message}`);
|
|
4597
|
+
}
|
|
4598
|
+
console.log(" Building functions...");
|
|
4599
|
+
const buildResult = await this.firebaseSetup.buildFunctions();
|
|
4600
|
+
if (buildResult.success) {
|
|
4601
|
+
console.log(` \x1B[32m\u2713\x1B[0m ${buildResult.message}`);
|
|
4602
|
+
steps.push("Functions built");
|
|
4603
|
+
deployFunctions = true;
|
|
4604
|
+
} else {
|
|
4605
|
+
console.log(` \x1B[31m\u2717\x1B[0m ${buildResult.message}`);
|
|
4606
|
+
console.log(" \x1B[33m\u26A0\x1B[0m Deploying hosting only (functions build failed)");
|
|
4607
|
+
steps.push("Functions build failed \u2014 hosting only");
|
|
4608
|
+
}
|
|
4609
|
+
}
|
|
4610
|
+
const targets = deployFunctions ? "hosting,functions" : "hosting";
|
|
4611
|
+
console.log(` Deploying ${targets}...`);
|
|
4612
|
+
const result = await this.firebaseSetup.deployHosting(targets);
|
|
4443
4613
|
clearFirebaseStatusCache();
|
|
4444
|
-
sendJson(
|
|
4614
|
+
sendJson({
|
|
4615
|
+
...result,
|
|
4616
|
+
message: result.message + (steps.length > 0 ? ` (${steps.join(", ")})` : "")
|
|
4617
|
+
});
|
|
4445
4618
|
} catch (err) {
|
|
4446
4619
|
sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500);
|
|
4447
4620
|
}
|
package/dist/dev.cjs
CHANGED
|
@@ -3368,7 +3368,7 @@ var FirebaseSetup = class {
|
|
|
3368
3368
|
return { success: true, message: `Web app "${displayName}" selected.` };
|
|
3369
3369
|
}
|
|
3370
3370
|
// ─── Deploy ─────────────────────────────────────────────────────────
|
|
3371
|
-
async deployHosting() {
|
|
3371
|
+
async deployHosting(targets = "hosting") {
|
|
3372
3372
|
const firebaseJsonPath = (0, import_node_path4.resolve)(this.projectDir, "firebase.json");
|
|
3373
3373
|
if (!(0, import_node_fs4.existsSync)(firebaseJsonPath)) {
|
|
3374
3374
|
return { success: false, message: "firebase.json not found. Enable hosting first." };
|
|
@@ -3381,12 +3381,12 @@ var FirebaseSetup = class {
|
|
|
3381
3381
|
} catch {
|
|
3382
3382
|
return { success: false, message: "Invalid firebase.json." };
|
|
3383
3383
|
}
|
|
3384
|
+
const timeoutMs = targets.includes("functions") ? 3e5 : 12e4;
|
|
3384
3385
|
try {
|
|
3385
3386
|
const output = await this.execTimeout(
|
|
3386
3387
|
"firebase",
|
|
3387
|
-
["deploy", "--only",
|
|
3388
|
-
|
|
3389
|
-
// 2 min timeout
|
|
3388
|
+
["deploy", "--only", targets, "--json"],
|
|
3389
|
+
timeoutMs
|
|
3390
3390
|
);
|
|
3391
3391
|
let url = "";
|
|
3392
3392
|
try {
|
|
@@ -3409,12 +3409,147 @@ var FirebaseSetup = class {
|
|
|
3409
3409
|
url = `https://${state.projectId}.web.app`;
|
|
3410
3410
|
}
|
|
3411
3411
|
}
|
|
3412
|
-
|
|
3412
|
+
const label = targets.includes("functions") ? "Hosting + Functions" : "Hosting";
|
|
3413
|
+
return { success: true, url: url || void 0, message: `${label} deployed successfully!` };
|
|
3413
3414
|
} catch (err) {
|
|
3414
3415
|
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3415
3416
|
return { success: false, message: `Deploy failed: ${msg}` };
|
|
3416
3417
|
}
|
|
3417
3418
|
}
|
|
3419
|
+
// ─── Full Deploy Pipeline ────────────────────────────────────────────
|
|
3420
|
+
/**
|
|
3421
|
+
* Sync environment variables to functions/.env for production.
|
|
3422
|
+
* Copies project root .env vars + injects CLAWFIRE_FIREBASE_CONFIG.
|
|
3423
|
+
*/
|
|
3424
|
+
syncEnvToFunctions(firebaseConfig) {
|
|
3425
|
+
const functionsDir = (0, import_node_path4.resolve)(this.projectDir, "functions");
|
|
3426
|
+
if (!(0, import_node_fs4.existsSync)(functionsDir)) {
|
|
3427
|
+
return { success: false, message: "functions/ directory not found.", count: 0 };
|
|
3428
|
+
}
|
|
3429
|
+
const lines = [];
|
|
3430
|
+
const rootEnvPath = (0, import_node_path4.resolve)(this.projectDir, ".env");
|
|
3431
|
+
if ((0, import_node_fs4.existsSync)(rootEnvPath)) {
|
|
3432
|
+
const content = (0, import_node_fs4.readFileSync)(rootEnvPath, "utf-8");
|
|
3433
|
+
for (const rawLine of content.split("\n")) {
|
|
3434
|
+
let trimmed = rawLine.trim();
|
|
3435
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
3436
|
+
if (trimmed.startsWith("export ")) {
|
|
3437
|
+
trimmed = trimmed.slice(7);
|
|
3438
|
+
}
|
|
3439
|
+
const eqIdx = trimmed.indexOf("=");
|
|
3440
|
+
if (eqIdx === -1) continue;
|
|
3441
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
3442
|
+
if (key === "NPM_TOKEN" || key === "NODE_ENV") continue;
|
|
3443
|
+
lines.push(trimmed);
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
if (firebaseConfig && Object.keys(firebaseConfig).length > 0) {
|
|
3447
|
+
const filtered = lines.filter((l) => !l.startsWith("CLAWFIRE_FIREBASE_CONFIG="));
|
|
3448
|
+
filtered.push(`CLAWFIRE_FIREBASE_CONFIG=${JSON.stringify(firebaseConfig)}`);
|
|
3449
|
+
lines.length = 0;
|
|
3450
|
+
lines.push(...filtered);
|
|
3451
|
+
}
|
|
3452
|
+
const functionsEnvPath = (0, import_node_path4.resolve)(functionsDir, ".env");
|
|
3453
|
+
(0, import_node_fs4.writeFileSync)(functionsEnvPath, lines.join("\n") + "\n", "utf-8");
|
|
3454
|
+
return { success: true, message: `Synced ${lines.length} env vars to functions/.env`, count: lines.length };
|
|
3455
|
+
}
|
|
3456
|
+
/**
|
|
3457
|
+
* Ensure firebase.json is fully configured for deployment:
|
|
3458
|
+
* - functions config exists
|
|
3459
|
+
* - hosting has cleanUrls
|
|
3460
|
+
* - hosting has API rewrite (/api/** → api function) before catch-all
|
|
3461
|
+
*
|
|
3462
|
+
* Fixes existing configs created by older versions.
|
|
3463
|
+
*/
|
|
3464
|
+
ensureDeployConfig() {
|
|
3465
|
+
const firebaseJsonPath = (0, import_node_path4.resolve)(this.projectDir, "firebase.json");
|
|
3466
|
+
if (!(0, import_node_fs4.existsSync)(firebaseJsonPath)) {
|
|
3467
|
+
return { success: false, message: "firebase.json not found.", changes: [] };
|
|
3468
|
+
}
|
|
3469
|
+
try {
|
|
3470
|
+
const config = JSON.parse((0, import_node_fs4.readFileSync)(firebaseJsonPath, "utf-8"));
|
|
3471
|
+
const changes = [];
|
|
3472
|
+
if (!config.functions) {
|
|
3473
|
+
config.functions = {
|
|
3474
|
+
source: "functions",
|
|
3475
|
+
runtime: "nodejs20",
|
|
3476
|
+
codebase: "clawfire"
|
|
3477
|
+
};
|
|
3478
|
+
changes.push("Added functions config");
|
|
3479
|
+
}
|
|
3480
|
+
if (config.hosting && !config.hosting.cleanUrls) {
|
|
3481
|
+
config.hosting.cleanUrls = true;
|
|
3482
|
+
changes.push("Added cleanUrls");
|
|
3483
|
+
}
|
|
3484
|
+
if (config.hosting) {
|
|
3485
|
+
if (!config.hosting.rewrites) {
|
|
3486
|
+
config.hosting.rewrites = [];
|
|
3487
|
+
}
|
|
3488
|
+
const rewrites = config.hosting.rewrites;
|
|
3489
|
+
const hasApiRewrite = rewrites.some(
|
|
3490
|
+
(r) => r.source === "/api/**" && r.function === "api"
|
|
3491
|
+
);
|
|
3492
|
+
if (!hasApiRewrite) {
|
|
3493
|
+
const apiRewrite = { source: "/api/**", function: "api" };
|
|
3494
|
+
const catchAllIndex = rewrites.findIndex((r) => r.source === "**");
|
|
3495
|
+
if (catchAllIndex >= 0) {
|
|
3496
|
+
rewrites.splice(catchAllIndex, 0, apiRewrite);
|
|
3497
|
+
} else {
|
|
3498
|
+
rewrites.push(apiRewrite);
|
|
3499
|
+
rewrites.push({ source: "**", destination: "/index.html" });
|
|
3500
|
+
}
|
|
3501
|
+
changes.push("Added API rewrite (/api/** \u2192 Cloud Function)");
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
if (changes.length > 0) {
|
|
3505
|
+
(0, import_node_fs4.writeFileSync)(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
3506
|
+
}
|
|
3507
|
+
return {
|
|
3508
|
+
success: true,
|
|
3509
|
+
message: changes.length > 0 ? changes.join(", ") : "Deploy config up to date.",
|
|
3510
|
+
changes
|
|
3511
|
+
};
|
|
3512
|
+
} catch {
|
|
3513
|
+
return { success: false, message: "Invalid firebase.json.", changes: [] };
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
/**
|
|
3517
|
+
* Install dependencies and build functions for deployment.
|
|
3518
|
+
*/
|
|
3519
|
+
async buildFunctions() {
|
|
3520
|
+
const functionsDir = (0, import_node_path4.resolve)(this.projectDir, "functions");
|
|
3521
|
+
if (!(0, import_node_fs4.existsSync)(functionsDir)) {
|
|
3522
|
+
return { success: false, message: "functions/ directory not found." };
|
|
3523
|
+
}
|
|
3524
|
+
const pkgPath = (0, import_node_path4.resolve)(functionsDir, "package.json");
|
|
3525
|
+
if (!(0, import_node_fs4.existsSync)(pkgPath)) {
|
|
3526
|
+
return { success: false, message: "functions/package.json not found." };
|
|
3527
|
+
}
|
|
3528
|
+
const nodeModulesPath = (0, import_node_path4.resolve)(functionsDir, "node_modules");
|
|
3529
|
+
if (!(0, import_node_fs4.existsSync)(nodeModulesPath)) {
|
|
3530
|
+
try {
|
|
3531
|
+
await this.execTimeout("npm", ["install"], 12e4, functionsDir);
|
|
3532
|
+
} catch (err) {
|
|
3533
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3534
|
+
return { success: false, message: `npm install in functions/ failed: ${msg}` };
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
try {
|
|
3538
|
+
const pkg = JSON.parse((0, import_node_fs4.readFileSync)(pkgPath, "utf-8"));
|
|
3539
|
+
if (pkg.scripts?.build) {
|
|
3540
|
+
await this.execTimeout("npm", ["run", "build"], 6e4, functionsDir);
|
|
3541
|
+
} else {
|
|
3542
|
+
const tsconfigPath = (0, import_node_path4.resolve)(functionsDir, "tsconfig.json");
|
|
3543
|
+
if ((0, import_node_fs4.existsSync)(tsconfigPath)) {
|
|
3544
|
+
await this.execTimeout("npx", ["tsc"], 6e4, functionsDir);
|
|
3545
|
+
}
|
|
3546
|
+
}
|
|
3547
|
+
return { success: true, message: "Functions built successfully." };
|
|
3548
|
+
} catch (err) {
|
|
3549
|
+
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
3550
|
+
return { success: false, message: `Functions build failed: ${msg}` };
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3418
3553
|
// ─── Service Enable ────────────────────────────────────────────────
|
|
3419
3554
|
enableService(service) {
|
|
3420
3555
|
const firebaseJsonPath = (0, import_node_path4.resolve)(this.projectDir, "firebase.json");
|
|
@@ -3663,9 +3798,9 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
|
|
|
3663
3798
|
}
|
|
3664
3799
|
}
|
|
3665
3800
|
// ─── Helpers ───────────────────────────────────────────────────────
|
|
3666
|
-
execTimeout(command, args, timeoutMs) {
|
|
3801
|
+
execTimeout(command, args, timeoutMs, cwd) {
|
|
3667
3802
|
return new Promise((resolve7, reject) => {
|
|
3668
|
-
const proc = (0, import_node_child_process2.execFile)(command, args, { cwd: this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {
|
|
3803
|
+
const proc = (0, import_node_child_process2.execFile)(command, args, { cwd: cwd || this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {
|
|
3669
3804
|
if (err) {
|
|
3670
3805
|
const detail = stderr?.trim() || stdout?.trim() || "";
|
|
3671
3806
|
const enriched = new Error(`${err.message}${detail ? "\n" + detail : ""}`);
|
|
@@ -4828,22 +4963,27 @@ ${liveReloadScript}
|
|
|
4828
4963
|
if (url.pathname === "/__dev/deploy/hosting" && req.method === "POST") {
|
|
4829
4964
|
(async () => {
|
|
4830
4965
|
try {
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4966
|
+
const steps = [];
|
|
4967
|
+
const functionsDir = (0, import_node_path5.resolve)(this.options.projectDir, "functions");
|
|
4968
|
+
const hasFunctions = (0, import_node_fs5.existsSync)(functionsDir);
|
|
4969
|
+
const projectConfig = this.readProjectConfig();
|
|
4970
|
+
const firebaseConfig = {};
|
|
4971
|
+
for (const field of projectConfig.fields) {
|
|
4972
|
+
if (!field.isPlaceholder) {
|
|
4973
|
+
firebaseConfig[field.key] = field.value;
|
|
4838
4974
|
}
|
|
4975
|
+
}
|
|
4976
|
+
if (this.pageCompiler.isActive()) {
|
|
4839
4977
|
const configScript = Object.keys(firebaseConfig).length > 0 ? `
|
|
4840
4978
|
<script>window.__CLAWFIRE_CONFIG__=${JSON.stringify(firebaseConfig)};</script>` : "";
|
|
4841
4979
|
const routerScript = generateProductionRouterScript();
|
|
4842
4980
|
const scriptToInject = configScript + routerScript;
|
|
4843
4981
|
const buildResult = this.pageCompiler.buildForProduction(this.publicDir, scriptToInject);
|
|
4844
4982
|
console.log(` \x1B[32m\u2713\x1B[0m Built ${buildResult.pages.length} pages for production`);
|
|
4983
|
+
steps.push(`${buildResult.pages.length} pages built`);
|
|
4845
4984
|
if (configScript) {
|
|
4846
4985
|
console.log(` \x1B[32m\u2713\x1B[0m Firebase config injected (projectId: ${firebaseConfig.projectId || "?"})`);
|
|
4986
|
+
steps.push("Firebase config injected");
|
|
4847
4987
|
}
|
|
4848
4988
|
if (buildResult.errors.length > 0) {
|
|
4849
4989
|
for (const err of buildResult.errors) {
|
|
@@ -4851,9 +4991,42 @@ ${liveReloadScript}
|
|
|
4851
4991
|
}
|
|
4852
4992
|
}
|
|
4853
4993
|
}
|
|
4854
|
-
const
|
|
4994
|
+
const deployConfig = this.firebaseSetup.ensureDeployConfig();
|
|
4995
|
+
if (deployConfig.success && deployConfig.changes.length > 0) {
|
|
4996
|
+
for (const change of deployConfig.changes) {
|
|
4997
|
+
console.log(` \x1B[32m\u2713\x1B[0m firebase.json: ${change}`);
|
|
4998
|
+
}
|
|
4999
|
+
steps.push(`firebase.json updated (${deployConfig.changes.length} fixes)`);
|
|
5000
|
+
}
|
|
5001
|
+
let deployFunctions = false;
|
|
5002
|
+
if (hasFunctions) {
|
|
5003
|
+
const syncResult = this.firebaseSetup.syncEnvToFunctions(firebaseConfig);
|
|
5004
|
+
if (syncResult.success) {
|
|
5005
|
+
console.log(` \x1B[32m\u2713\x1B[0m ${syncResult.message}`);
|
|
5006
|
+
steps.push(`${syncResult.count} env vars synced`);
|
|
5007
|
+
} else {
|
|
5008
|
+
console.log(` \x1B[33m\u26A0\x1B[0m Env sync: ${syncResult.message}`);
|
|
5009
|
+
}
|
|
5010
|
+
console.log(" Building functions...");
|
|
5011
|
+
const buildResult = await this.firebaseSetup.buildFunctions();
|
|
5012
|
+
if (buildResult.success) {
|
|
5013
|
+
console.log(` \x1B[32m\u2713\x1B[0m ${buildResult.message}`);
|
|
5014
|
+
steps.push("Functions built");
|
|
5015
|
+
deployFunctions = true;
|
|
5016
|
+
} else {
|
|
5017
|
+
console.log(` \x1B[31m\u2717\x1B[0m ${buildResult.message}`);
|
|
5018
|
+
console.log(" \x1B[33m\u26A0\x1B[0m Deploying hosting only (functions build failed)");
|
|
5019
|
+
steps.push("Functions build failed \u2014 hosting only");
|
|
5020
|
+
}
|
|
5021
|
+
}
|
|
5022
|
+
const targets = deployFunctions ? "hosting,functions" : "hosting";
|
|
5023
|
+
console.log(` Deploying ${targets}...`);
|
|
5024
|
+
const result = await this.firebaseSetup.deployHosting(targets);
|
|
4855
5025
|
clearFirebaseStatusCache();
|
|
4856
|
-
sendJson(
|
|
5026
|
+
sendJson({
|
|
5027
|
+
...result,
|
|
5028
|
+
message: result.message + (steps.length > 0 ? ` (${steps.join(", ")})` : "")
|
|
5029
|
+
});
|
|
4857
5030
|
} catch (err) {
|
|
4858
5031
|
sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500);
|
|
4859
5032
|
}
|