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/dev.js CHANGED
@@ -3330,7 +3330,7 @@ var FirebaseSetup = class {
3330
3330
  return { success: true, message: `Web app "${displayName}" selected.` };
3331
3331
  }
3332
3332
  // ─── Deploy ─────────────────────────────────────────────────────────
3333
- async deployHosting() {
3333
+ async deployHosting(targets = "hosting") {
3334
3334
  const firebaseJsonPath = resolve5(this.projectDir, "firebase.json");
3335
3335
  if (!existsSync6(firebaseJsonPath)) {
3336
3336
  return { success: false, message: "firebase.json not found. Enable hosting first." };
@@ -3343,12 +3343,12 @@ var FirebaseSetup = class {
3343
3343
  } catch {
3344
3344
  return { success: false, message: "Invalid firebase.json." };
3345
3345
  }
3346
+ const timeoutMs = targets.includes("functions") ? 3e5 : 12e4;
3346
3347
  try {
3347
3348
  const output = await this.execTimeout(
3348
3349
  "firebase",
3349
- ["deploy", "--only", "hosting", "--json"],
3350
- 12e4
3351
- // 2 min timeout
3350
+ ["deploy", "--only", targets, "--json"],
3351
+ timeoutMs
3352
3352
  );
3353
3353
  let url = "";
3354
3354
  try {
@@ -3371,12 +3371,147 @@ var FirebaseSetup = class {
3371
3371
  url = `https://${state.projectId}.web.app`;
3372
3372
  }
3373
3373
  }
3374
- return { success: true, url: url || void 0, message: "Hosting deployed successfully!" };
3374
+ const label = targets.includes("functions") ? "Hosting + Functions" : "Hosting";
3375
+ return { success: true, url: url || void 0, message: `${label} deployed successfully!` };
3375
3376
  } catch (err) {
3376
3377
  const msg = err instanceof Error ? err.message : "Unknown error";
3377
3378
  return { success: false, message: `Deploy failed: ${msg}` };
3378
3379
  }
3379
3380
  }
3381
+ // ─── Full Deploy Pipeline ────────────────────────────────────────────
3382
+ /**
3383
+ * Sync environment variables to functions/.env for production.
3384
+ * Copies project root .env vars + injects CLAWFIRE_FIREBASE_CONFIG.
3385
+ */
3386
+ syncEnvToFunctions(firebaseConfig) {
3387
+ const functionsDir = resolve5(this.projectDir, "functions");
3388
+ if (!existsSync6(functionsDir)) {
3389
+ return { success: false, message: "functions/ directory not found.", count: 0 };
3390
+ }
3391
+ const lines = [];
3392
+ const rootEnvPath = resolve5(this.projectDir, ".env");
3393
+ if (existsSync6(rootEnvPath)) {
3394
+ const content = readFileSync4(rootEnvPath, "utf-8");
3395
+ for (const rawLine of content.split("\n")) {
3396
+ let trimmed = rawLine.trim();
3397
+ if (!trimmed || trimmed.startsWith("#")) continue;
3398
+ if (trimmed.startsWith("export ")) {
3399
+ trimmed = trimmed.slice(7);
3400
+ }
3401
+ const eqIdx = trimmed.indexOf("=");
3402
+ if (eqIdx === -1) continue;
3403
+ const key = trimmed.slice(0, eqIdx).trim();
3404
+ if (key === "NPM_TOKEN" || key === "NODE_ENV") continue;
3405
+ lines.push(trimmed);
3406
+ }
3407
+ }
3408
+ if (firebaseConfig && Object.keys(firebaseConfig).length > 0) {
3409
+ const filtered = lines.filter((l) => !l.startsWith("CLAWFIRE_FIREBASE_CONFIG="));
3410
+ filtered.push(`CLAWFIRE_FIREBASE_CONFIG=${JSON.stringify(firebaseConfig)}`);
3411
+ lines.length = 0;
3412
+ lines.push(...filtered);
3413
+ }
3414
+ const functionsEnvPath = resolve5(functionsDir, ".env");
3415
+ writeFileSync3(functionsEnvPath, lines.join("\n") + "\n", "utf-8");
3416
+ return { success: true, message: `Synced ${lines.length} env vars to functions/.env`, count: lines.length };
3417
+ }
3418
+ /**
3419
+ * Ensure firebase.json is fully configured for deployment:
3420
+ * - functions config exists
3421
+ * - hosting has cleanUrls
3422
+ * - hosting has API rewrite (/api/** → api function) before catch-all
3423
+ *
3424
+ * Fixes existing configs created by older versions.
3425
+ */
3426
+ ensureDeployConfig() {
3427
+ const firebaseJsonPath = resolve5(this.projectDir, "firebase.json");
3428
+ if (!existsSync6(firebaseJsonPath)) {
3429
+ return { success: false, message: "firebase.json not found.", changes: [] };
3430
+ }
3431
+ try {
3432
+ const config = JSON.parse(readFileSync4(firebaseJsonPath, "utf-8"));
3433
+ const changes = [];
3434
+ if (!config.functions) {
3435
+ config.functions = {
3436
+ source: "functions",
3437
+ runtime: "nodejs20",
3438
+ codebase: "clawfire"
3439
+ };
3440
+ changes.push("Added functions config");
3441
+ }
3442
+ if (config.hosting && !config.hosting.cleanUrls) {
3443
+ config.hosting.cleanUrls = true;
3444
+ changes.push("Added cleanUrls");
3445
+ }
3446
+ if (config.hosting) {
3447
+ if (!config.hosting.rewrites) {
3448
+ config.hosting.rewrites = [];
3449
+ }
3450
+ const rewrites = config.hosting.rewrites;
3451
+ const hasApiRewrite = rewrites.some(
3452
+ (r) => r.source === "/api/**" && r.function === "api"
3453
+ );
3454
+ if (!hasApiRewrite) {
3455
+ const apiRewrite = { source: "/api/**", function: "api" };
3456
+ const catchAllIndex = rewrites.findIndex((r) => r.source === "**");
3457
+ if (catchAllIndex >= 0) {
3458
+ rewrites.splice(catchAllIndex, 0, apiRewrite);
3459
+ } else {
3460
+ rewrites.push(apiRewrite);
3461
+ rewrites.push({ source: "**", destination: "/index.html" });
3462
+ }
3463
+ changes.push("Added API rewrite (/api/** \u2192 Cloud Function)");
3464
+ }
3465
+ }
3466
+ if (changes.length > 0) {
3467
+ writeFileSync3(firebaseJsonPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
3468
+ }
3469
+ return {
3470
+ success: true,
3471
+ message: changes.length > 0 ? changes.join(", ") : "Deploy config up to date.",
3472
+ changes
3473
+ };
3474
+ } catch {
3475
+ return { success: false, message: "Invalid firebase.json.", changes: [] };
3476
+ }
3477
+ }
3478
+ /**
3479
+ * Install dependencies and build functions for deployment.
3480
+ */
3481
+ async buildFunctions() {
3482
+ const functionsDir = resolve5(this.projectDir, "functions");
3483
+ if (!existsSync6(functionsDir)) {
3484
+ return { success: false, message: "functions/ directory not found." };
3485
+ }
3486
+ const pkgPath = resolve5(functionsDir, "package.json");
3487
+ if (!existsSync6(pkgPath)) {
3488
+ return { success: false, message: "functions/package.json not found." };
3489
+ }
3490
+ const nodeModulesPath = resolve5(functionsDir, "node_modules");
3491
+ if (!existsSync6(nodeModulesPath)) {
3492
+ try {
3493
+ await this.execTimeout("npm", ["install"], 12e4, functionsDir);
3494
+ } catch (err) {
3495
+ const msg = err instanceof Error ? err.message : "Unknown error";
3496
+ return { success: false, message: `npm install in functions/ failed: ${msg}` };
3497
+ }
3498
+ }
3499
+ try {
3500
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
3501
+ if (pkg.scripts?.build) {
3502
+ await this.execTimeout("npm", ["run", "build"], 6e4, functionsDir);
3503
+ } else {
3504
+ const tsconfigPath = resolve5(functionsDir, "tsconfig.json");
3505
+ if (existsSync6(tsconfigPath)) {
3506
+ await this.execTimeout("npx", ["tsc"], 6e4, functionsDir);
3507
+ }
3508
+ }
3509
+ return { success: true, message: "Functions built successfully." };
3510
+ } catch (err) {
3511
+ const msg = err instanceof Error ? err.message : "Unknown error";
3512
+ return { success: false, message: `Functions build failed: ${msg}` };
3513
+ }
3514
+ }
3380
3515
  // ─── Service Enable ────────────────────────────────────────────────
3381
3516
  enableService(service) {
3382
3517
  const firebaseJsonPath = resolve5(this.projectDir, "firebase.json");
@@ -3625,9 +3760,9 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
3625
3760
  }
3626
3761
  }
3627
3762
  // ─── Helpers ───────────────────────────────────────────────────────
3628
- execTimeout(command, args, timeoutMs) {
3763
+ execTimeout(command, args, timeoutMs, cwd) {
3629
3764
  return new Promise((resolve7, reject) => {
3630
- const proc = execFile2(command, args, { cwd: this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {
3765
+ const proc = execFile2(command, args, { cwd: cwd || this.projectDir, timeout: timeoutMs }, (err, stdout, stderr) => {
3631
3766
  if (err) {
3632
3767
  const detail = stderr?.trim() || stdout?.trim() || "";
3633
3768
  const enriched = new Error(`${err.message}${detail ? "\n" + detail : ""}`);
@@ -4790,22 +4925,27 @@ ${liveReloadScript}
4790
4925
  if (url.pathname === "/__dev/deploy/hosting" && req.method === "POST") {
4791
4926
  (async () => {
4792
4927
  try {
4793
- if (this.pageCompiler.isActive()) {
4794
- const projectConfig = this.readProjectConfig();
4795
- const firebaseConfig = {};
4796
- for (const field of projectConfig.fields) {
4797
- if (!field.isPlaceholder) {
4798
- firebaseConfig[field.key] = field.value;
4799
- }
4928
+ const steps = [];
4929
+ const functionsDir = resolve6(this.options.projectDir, "functions");
4930
+ const hasFunctions = existsSync7(functionsDir);
4931
+ const projectConfig = this.readProjectConfig();
4932
+ const firebaseConfig = {};
4933
+ for (const field of projectConfig.fields) {
4934
+ if (!field.isPlaceholder) {
4935
+ firebaseConfig[field.key] = field.value;
4800
4936
  }
4937
+ }
4938
+ if (this.pageCompiler.isActive()) {
4801
4939
  const configScript = Object.keys(firebaseConfig).length > 0 ? `
4802
4940
  <script>window.__CLAWFIRE_CONFIG__=${JSON.stringify(firebaseConfig)};</script>` : "";
4803
4941
  const routerScript = generateProductionRouterScript();
4804
4942
  const scriptToInject = configScript + routerScript;
4805
4943
  const buildResult = this.pageCompiler.buildForProduction(this.publicDir, scriptToInject);
4806
4944
  console.log(` \x1B[32m\u2713\x1B[0m Built ${buildResult.pages.length} pages for production`);
4945
+ steps.push(`${buildResult.pages.length} pages built`);
4807
4946
  if (configScript) {
4808
4947
  console.log(` \x1B[32m\u2713\x1B[0m Firebase config injected (projectId: ${firebaseConfig.projectId || "?"})`);
4948
+ steps.push("Firebase config injected");
4809
4949
  }
4810
4950
  if (buildResult.errors.length > 0) {
4811
4951
  for (const err of buildResult.errors) {
@@ -4813,9 +4953,42 @@ ${liveReloadScript}
4813
4953
  }
4814
4954
  }
4815
4955
  }
4816
- const result = await this.firebaseSetup.deployHosting();
4956
+ const deployConfig = this.firebaseSetup.ensureDeployConfig();
4957
+ if (deployConfig.success && deployConfig.changes.length > 0) {
4958
+ for (const change of deployConfig.changes) {
4959
+ console.log(` \x1B[32m\u2713\x1B[0m firebase.json: ${change}`);
4960
+ }
4961
+ steps.push(`firebase.json updated (${deployConfig.changes.length} fixes)`);
4962
+ }
4963
+ let deployFunctions = false;
4964
+ if (hasFunctions) {
4965
+ const syncResult = this.firebaseSetup.syncEnvToFunctions(firebaseConfig);
4966
+ if (syncResult.success) {
4967
+ console.log(` \x1B[32m\u2713\x1B[0m ${syncResult.message}`);
4968
+ steps.push(`${syncResult.count} env vars synced`);
4969
+ } else {
4970
+ console.log(` \x1B[33m\u26A0\x1B[0m Env sync: ${syncResult.message}`);
4971
+ }
4972
+ console.log(" Building functions...");
4973
+ const buildResult = await this.firebaseSetup.buildFunctions();
4974
+ if (buildResult.success) {
4975
+ console.log(` \x1B[32m\u2713\x1B[0m ${buildResult.message}`);
4976
+ steps.push("Functions built");
4977
+ deployFunctions = true;
4978
+ } else {
4979
+ console.log(` \x1B[31m\u2717\x1B[0m ${buildResult.message}`);
4980
+ console.log(" \x1B[33m\u26A0\x1B[0m Deploying hosting only (functions build failed)");
4981
+ steps.push("Functions build failed \u2014 hosting only");
4982
+ }
4983
+ }
4984
+ const targets = deployFunctions ? "hosting,functions" : "hosting";
4985
+ console.log(` Deploying ${targets}...`);
4986
+ const result = await this.firebaseSetup.deployHosting(targets);
4817
4987
  clearFirebaseStatusCache();
4818
- sendJson(result);
4988
+ sendJson({
4989
+ ...result,
4990
+ message: result.message + (steps.length > 0 ? ` (${steps.join(", ")})` : "")
4991
+ });
4819
4992
  } catch (err) {
4820
4993
  sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500);
4821
4994
  }