firebase-hosting-mcp-server 1.0.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.
Files changed (58) hide show
  1. package/README.md +191 -0
  2. package/dist/constants.d.ts +75 -0
  3. package/dist/constants.d.ts.map +1 -0
  4. package/dist/constants.js +179 -0
  5. package/dist/constants.js.map +1 -0
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +269 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/schemas/index.d.ts +246 -0
  11. package/dist/schemas/index.d.ts.map +1 -0
  12. package/dist/schemas/index.js +238 -0
  13. package/dist/schemas/index.js.map +1 -0
  14. package/dist/services/firebase-cli.d.ts +20 -0
  15. package/dist/services/firebase-cli.d.ts.map +1 -0
  16. package/dist/services/firebase-cli.js +164 -0
  17. package/dist/services/firebase-cli.js.map +1 -0
  18. package/dist/services/license.d.ts +6 -0
  19. package/dist/services/license.d.ts.map +1 -0
  20. package/dist/services/license.js +111 -0
  21. package/dist/services/license.js.map +1 -0
  22. package/dist/tools/configure.d.ts +3 -0
  23. package/dist/tools/configure.d.ts.map +1 -0
  24. package/dist/tools/configure.js +102 -0
  25. package/dist/tools/configure.js.map +1 -0
  26. package/dist/tools/delete-channel.d.ts +3 -0
  27. package/dist/tools/delete-channel.d.ts.map +1 -0
  28. package/dist/tools/delete-channel.js +33 -0
  29. package/dist/tools/delete-channel.js.map +1 -0
  30. package/dist/tools/deploy.d.ts +3 -0
  31. package/dist/tools/deploy.d.ts.map +1 -0
  32. package/dist/tools/deploy.js +123 -0
  33. package/dist/tools/deploy.js.map +1 -0
  34. package/dist/tools/harden.d.ts +3 -0
  35. package/dist/tools/harden.d.ts.map +1 -0
  36. package/dist/tools/harden.js +181 -0
  37. package/dist/tools/harden.js.map +1 -0
  38. package/dist/tools/init.d.ts +3 -0
  39. package/dist/tools/init.d.ts.map +1 -0
  40. package/dist/tools/init.js +60 -0
  41. package/dist/tools/init.js.map +1 -0
  42. package/dist/tools/preview.d.ts +3 -0
  43. package/dist/tools/preview.d.ts.map +1 -0
  44. package/dist/tools/preview.js +85 -0
  45. package/dist/tools/preview.js.map +1 -0
  46. package/dist/tools/rollback.d.ts +3 -0
  47. package/dist/tools/rollback.d.ts.map +1 -0
  48. package/dist/tools/rollback.js +87 -0
  49. package/dist/tools/rollback.js.map +1 -0
  50. package/dist/tools/status.d.ts +3 -0
  51. package/dist/tools/status.d.ts.map +1 -0
  52. package/dist/tools/status.js +80 -0
  53. package/dist/tools/status.js.map +1 -0
  54. package/dist/types.d.ts +81 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +3 -0
  57. package/dist/types.js.map +1 -0
  58. package/package.json +43 -0
@@ -0,0 +1,102 @@
1
+ // firebase_hosting_configure - Modifies firebase.json hosting config
2
+ import { readFirebaseJson, writeFirebaseJson, } from "../services/firebase-cli.js";
3
+ export async function handleConfigure(params) {
4
+ const { project_dir } = params;
5
+ const config = await readFirebaseJson(project_dir);
6
+ if (!config) {
7
+ return `❌ No firebase.json found in ${project_dir}. Run firebase_hosting_init first.`;
8
+ }
9
+ const changes = [];
10
+ // Update public directory
11
+ if (params.public_dir !== undefined) {
12
+ config.hosting.public = params.public_dir;
13
+ changes.push(`Public directory → ${params.public_dir}`);
14
+ }
15
+ // Toggle SPA rewrites
16
+ if (params.spa !== undefined) {
17
+ if (params.spa) {
18
+ // Add SPA rewrite if not exists
19
+ const hasSpRewrite = config.hosting.rewrites?.some((r) => r.source === "**" && r.destination === "/index.html");
20
+ if (!hasSpRewrite) {
21
+ config.hosting.rewrites = config.hosting.rewrites || [];
22
+ config.hosting.rewrites.push({
23
+ source: "**",
24
+ destination: "/index.html",
25
+ });
26
+ }
27
+ changes.push("SPA rewrites → enabled");
28
+ }
29
+ else {
30
+ config.hosting.rewrites = config.hosting.rewrites?.filter((r) => !(r.source === "**" && r.destination === "/index.html"));
31
+ changes.push("SPA rewrites → disabled");
32
+ }
33
+ }
34
+ // Clean URLs
35
+ if (params.clean_urls !== undefined) {
36
+ config.hosting.cleanUrls = params.clean_urls;
37
+ changes.push(`Clean URLs → ${params.clean_urls}`);
38
+ }
39
+ // Trailing slash
40
+ if (params.trailing_slash !== undefined) {
41
+ config.hosting.trailingSlash = params.trailing_slash;
42
+ changes.push(`Trailing slash → ${params.trailing_slash}`);
43
+ }
44
+ // Add redirect
45
+ if (params.add_redirect) {
46
+ config.hosting.redirects = config.hosting.redirects || [];
47
+ config.hosting.redirects.push({
48
+ source: params.add_redirect.source,
49
+ destination: params.add_redirect.destination,
50
+ type: params.add_redirect.type,
51
+ });
52
+ changes.push(`Added redirect: ${params.add_redirect.source} → ${params.add_redirect.destination} (${params.add_redirect.type})`);
53
+ }
54
+ // Add header
55
+ if (params.add_header) {
56
+ config.hosting.headers = config.hosting.headers || [];
57
+ // Check if source pattern already has headers
58
+ const existing = config.hosting.headers.find((h) => h.source === params.add_header.source);
59
+ if (existing) {
60
+ existing.headers.push({
61
+ key: params.add_header.key,
62
+ value: params.add_header.value,
63
+ });
64
+ }
65
+ else {
66
+ config.hosting.headers.push({
67
+ source: params.add_header.source,
68
+ headers: [
69
+ { key: params.add_header.key, value: params.add_header.value },
70
+ ],
71
+ });
72
+ }
73
+ changes.push(`Added header: ${params.add_header.key} on ${params.add_header.source}`);
74
+ }
75
+ // Add rewrite
76
+ if (params.add_rewrite) {
77
+ config.hosting.rewrites = config.hosting.rewrites || [];
78
+ const rewrite = {
79
+ source: params.add_rewrite.source,
80
+ };
81
+ if (params.add_rewrite.destination) {
82
+ rewrite.destination = params.add_rewrite.destination;
83
+ }
84
+ if (params.add_rewrite.function) {
85
+ rewrite.function = params.add_rewrite.function;
86
+ }
87
+ config.hosting.rewrites.push(rewrite);
88
+ changes.push(`Added rewrite: ${params.add_rewrite.source}`);
89
+ }
90
+ if (changes.length === 0) {
91
+ return `⚠️ No changes specified. Provide at least one configuration option to update.`;
92
+ }
93
+ // Write updated config
94
+ await writeFirebaseJson(project_dir, config);
95
+ return [
96
+ `✅ firebase.json updated!`,
97
+ ``,
98
+ `Changes:`,
99
+ ...changes.map((c) => ` - ${c}`),
100
+ ].join("\n");
101
+ }
102
+ //# sourceMappingURL=configure.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configure.js","sourceRoot":"","sources":["../../src/tools/configure.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { DeleteChannelInput } from "../schemas/index.js";
2
+ export declare function handleDeleteChannel(params: DeleteChannelInput): Promise<string>;
3
+ //# sourceMappingURL=delete-channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete-channel.d.ts","sourceRoot":"","sources":["../../src/tools/delete-channel.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,CAgCjB"}
@@ -0,0 +1,33 @@
1
+ // firebase_hosting_delete_channel - Deletes a preview channel
2
+ import { runFirebaseCommand, checkFirebaseCli, checkFirebaseAuth, } from "../services/firebase-cli.js";
3
+ export async function handleDeleteChannel(params) {
4
+ const { project_dir, channel_id, project_id } = params;
5
+ const cliCheck = await checkFirebaseCli();
6
+ if (!cliCheck.installed)
7
+ return `❌ ${cliCheck.message}`;
8
+ const authCheck = await checkFirebaseAuth();
9
+ if (!authCheck.authenticated)
10
+ return `❌ ${authCheck.message}`;
11
+ const deleteArgs = [
12
+ "hosting:channel:delete",
13
+ channel_id,
14
+ "--force",
15
+ "--json",
16
+ ];
17
+ if (project_id)
18
+ deleteArgs.push("--project", project_id);
19
+ const result = await runFirebaseCommand(deleteArgs, project_dir, 30_000);
20
+ if (!result.success) {
21
+ if (result.stderr.includes("NOT_FOUND") || result.stderr.includes("not found")) {
22
+ return `❌ Channel "${channel_id}" not found. Use firebase_hosting_status to see active channels.`;
23
+ }
24
+ return `❌ Failed to delete channel: ${result.stderr.slice(0, 500)}`;
25
+ }
26
+ return [
27
+ `✅ Preview channel deleted!`,
28
+ ``,
29
+ `Channel: ${channel_id}`,
30
+ `The preview URL is no longer accessible.`,
31
+ ].join("\n");
32
+ }
33
+ //# sourceMappingURL=delete-channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete-channel.js","sourceRoot":"","sources":["../../src/tools/delete-channel.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,6BAA6B,CAAC;AAGrC,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAA0B;IAE1B,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC;IAEvD,MAAM,QAAQ,GAAG,MAAM,gBAAgB,EAAE,CAAC;IAC1C,IAAI,CAAC,QAAQ,CAAC,SAAS;QAAE,OAAO,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC;IAExD,MAAM,SAAS,GAAG,MAAM,iBAAiB,EAAE,CAAC;IAC5C,IAAI,CAAC,SAAS,CAAC,aAAa;QAAE,OAAO,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;IAE9D,MAAM,UAAU,GAAG;QACjB,wBAAwB;QACxB,UAAU;QACV,SAAS;QACT,QAAQ;KACT,CAAC;IACF,IAAI,UAAU;QAAE,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IAEzD,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,UAAU,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;IAEzE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/E,OAAO,cAAc,UAAU,kEAAkE,CAAC;QACpG,CAAC;QACD,OAAO,+BAA+B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IACtE,CAAC;IAED,OAAO;QACL,4BAA4B;QAC5B,EAAE;QACF,YAAY,UAAU,EAAE;QACxB,0CAA0C;KAC3C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { DeployInput } from "../schemas/index.js";
2
+ export declare function handleDeploy(params: DeployInput): Promise<string>;
3
+ //# sourceMappingURL=deploy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/tools/deploy.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAkIvE"}
@@ -0,0 +1,123 @@
1
+ // firebase_hosting_deploy - All-in-one: init + build + deploy
2
+ // Vibe coders just say "Use firebase-hosting-mcp to deploy" and everything happens.
3
+ import { join } from "node:path";
4
+ import { runFirebaseCommand, checkFirebaseCli, checkFirebaseAuth, readFirebaseJson, detectFramework, runBuild, fileExists, } from "../services/firebase-cli.js";
5
+ import { handleInit } from "./init.js";
6
+ export async function handleDeploy(params) {
7
+ const { project_dir, project_id, message, build, build_command } = params;
8
+ const steps = [];
9
+ // 1. Check prerequisites
10
+ const cliCheck = await checkFirebaseCli();
11
+ if (!cliCheck.installed) {
12
+ return `❌ ${cliCheck.message}`;
13
+ }
14
+ const authCheck = await checkFirebaseAuth();
15
+ if (!authCheck.authenticated) {
16
+ return `❌ ${authCheck.message}`;
17
+ }
18
+ // 2. Auto-init if no firebase.json exists
19
+ let config = await readFirebaseJson(project_dir);
20
+ if (!config) {
21
+ steps.push("🔧 No firebase.json found — auto-initializing...");
22
+ const initResult = await handleInit({
23
+ api_key: params.api_key,
24
+ project_dir,
25
+ project_id,
26
+ });
27
+ if (!initResult.startsWith("✅")) {
28
+ return initResult; // Init failed, return the error
29
+ }
30
+ steps.push("✅ Firebase Hosting initialized");
31
+ // Re-read the freshly created config
32
+ config = await readFirebaseJson(project_dir);
33
+ if (!config) {
34
+ return `❌ Failed to create firebase.json. Check directory permissions.`;
35
+ }
36
+ }
37
+ // 3. Build if requested
38
+ if (build) {
39
+ const pkgExists = await fileExists(join(project_dir, "package.json"));
40
+ if (pkgExists) {
41
+ let cmd = build_command;
42
+ if (!cmd) {
43
+ const detected = await detectFramework(project_dir);
44
+ cmd = detected?.buildCommand || "npm run build";
45
+ }
46
+ steps.push(`🔨 Building... (${cmd})`);
47
+ const buildResult = await runBuild(project_dir, cmd);
48
+ if (!buildResult.success) {
49
+ return [
50
+ ...steps,
51
+ `❌ Build failed!`,
52
+ ``,
53
+ `Error output:`,
54
+ buildResult.stderr.slice(0, 2000),
55
+ ``,
56
+ `Fix the build errors and try again, or set build=false to skip.`,
57
+ ].join("\n");
58
+ }
59
+ steps.push("✅ Build successful");
60
+ }
61
+ }
62
+ // 4. Check public directory exists
63
+ const publicDir = join(project_dir, config.hosting.public);
64
+ if (!(await fileExists(publicDir))) {
65
+ return [
66
+ ...steps,
67
+ `❌ Public directory not found: ${config.hosting.public}`,
68
+ `Expected path: ${publicDir}`,
69
+ ``,
70
+ `Make sure your build output matches the public directory in firebase.json.`,
71
+ ].join("\n");
72
+ }
73
+ // 5. Deploy
74
+ steps.push("🚀 Deploying to Firebase Hosting...");
75
+ const deployArgs = ["deploy", "--only", "hosting", "--json"];
76
+ deployArgs.push("--project", project_id);
77
+ if (message) {
78
+ deployArgs.push("--message", message);
79
+ }
80
+ const result = await runFirebaseCommand(deployArgs, project_dir, 180_000);
81
+ if (!result.success) {
82
+ return [
83
+ ...steps,
84
+ `❌ Deploy failed!`,
85
+ ``,
86
+ `Error:`,
87
+ result.stderr.slice(0, 2000),
88
+ ``,
89
+ `Common fixes:`,
90
+ ` - Check your Firebase project permissions`,
91
+ ` - Verify the project ID "${project_id}" is correct`,
92
+ ` - Run "firebase login" to re-authenticate`,
93
+ ].join("\n");
94
+ }
95
+ // 6. Parse result for URL
96
+ let siteUrl = "";
97
+ try {
98
+ const jsonOutput = JSON.parse(result.stdout);
99
+ const hosting = jsonOutput?.result?.hosting;
100
+ if (typeof hosting === "string") {
101
+ siteUrl = hosting;
102
+ }
103
+ else if (hosting?.site) {
104
+ siteUrl = `https://${hosting.site}.web.app`;
105
+ }
106
+ }
107
+ catch {
108
+ const urlMatch = result.stdout.match(/https:\/\/[a-z0-9-]+\.(?:web\.app|firebaseapp\.com)/);
109
+ if (urlMatch) {
110
+ siteUrl = urlMatch[0];
111
+ }
112
+ }
113
+ // 7. Final report
114
+ const report = [...steps, ``, `✅ Deploy successful!`, ``];
115
+ report.push(`Project: ${project_id}`);
116
+ if (siteUrl)
117
+ report.push(`URL: ${siteUrl}`);
118
+ if (message)
119
+ report.push(`Message: ${message}`);
120
+ report.push(`Account: ${authCheck.accounts[0] || "unknown"}`);
121
+ return report.join("\n");
122
+ }
123
+ //# sourceMappingURL=deploy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/tools/deploy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { HardenInput } from "../schemas/index.js";
2
+ export declare function handleHarden(params: HardenInput): Promise<string>;
3
+ //# sourceMappingURL=harden.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harden.d.ts","sourceRoot":"","sources":["../../src/tools/harden.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAgFvD,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAoJvE"}
@@ -0,0 +1,181 @@
1
+ // firebase_hosting_harden - Apply security headers preset to firebase.json
2
+ // One-shot hardening: CSP, HSTS, X-Frame-Options, Permissions-Policy, caching.
3
+ // Merges with existing headers — never overwrites user-defined rules.
4
+ import { readFirebaseJson, writeFirebaseJson, } from "../services/firebase-cli.js";
5
+ import { DEFAULT_CSP_DIRECTIVES, SECURITY_HEADERS_GLOBAL, CACHE_IMMUTABLE_PATTERN, CACHE_IMMUTABLE_HEADERS, CACHE_HTML_PATTERN, CACHE_HTML_HEADERS, } from "../constants.js";
6
+ // ─── CSP Builder ─────────────────────────────────────────────────────
7
+ function buildCspHeader(params, strict) {
8
+ // Deep copy base directives
9
+ const directives = {};
10
+ for (const [key, values] of Object.entries(DEFAULT_CSP_DIRECTIVES)) {
11
+ directives[key] = [...values];
12
+ }
13
+ // Merge user-provided origins
14
+ if (params.csp_connect_src) {
15
+ directives["connect-src"].push(...params.csp_connect_src);
16
+ }
17
+ if (params.csp_script_src) {
18
+ directives["script-src"].push(...params.csp_script_src);
19
+ }
20
+ if (params.csp_style_src) {
21
+ directives["style-src"].push(...params.csp_style_src);
22
+ }
23
+ if (params.csp_frame_src) {
24
+ directives["frame-src"] = ["'self'", ...params.csp_frame_src];
25
+ }
26
+ // Standard preset: relax style-src to allow inline (most frameworks need it)
27
+ // Strict preset: keep it tight, only user-specified origins
28
+ if (!strict) {
29
+ // Ensure unsafe-inline is present for standard mode
30
+ if (!directives["style-src"].includes("'unsafe-inline'")) {
31
+ directives["style-src"].push("'unsafe-inline'");
32
+ }
33
+ }
34
+ // Strict mode additions
35
+ if (strict) {
36
+ directives["upgrade-insecure-requests"] = [];
37
+ }
38
+ // Build CSP string
39
+ return Object.entries(directives)
40
+ .map(([key, values]) => {
41
+ if (values.length === 0)
42
+ return key;
43
+ return `${key} ${values.join(" ")}`;
44
+ })
45
+ .join("; ");
46
+ }
47
+ // ─── Header Merge Logic ──────────────────────────────────────────────
48
+ /**
49
+ * Merges new security headers with existing ones.
50
+ * Strategy: for a given source pattern, add headers that don't already exist.
51
+ * Never overwrites a header the user already set (they may have customized it).
52
+ */
53
+ function mergeHeaders(existing, newRule) {
54
+ const result = [...existing];
55
+ const match = result.find((r) => r.source === newRule.source);
56
+ if (match) {
57
+ // Add only headers not already present (by key)
58
+ const existingKeys = new Set(match.headers.map((h) => h.key.toLowerCase()));
59
+ for (const header of newRule.headers) {
60
+ if (!existingKeys.has(header.key.toLowerCase())) {
61
+ match.headers.push(header);
62
+ }
63
+ }
64
+ }
65
+ else {
66
+ result.push(newRule);
67
+ }
68
+ return result;
69
+ }
70
+ // ─── Main Handler ────────────────────────────────────────────────────
71
+ export async function handleHarden(params) {
72
+ const { project_dir, preset, cache_immutable, dry_run } = params;
73
+ const strict = preset === "strict";
74
+ // 1. Read existing config
75
+ const config = await readFirebaseJson(project_dir);
76
+ if (!config) {
77
+ return `❌ No firebase.json found in ${project_dir}. Run firebase_hosting_deploy or firebase_hosting_init first.`;
78
+ }
79
+ const applied = [];
80
+ const skipped = [];
81
+ let headers = config.hosting.headers || [];
82
+ // 2. Build CSP
83
+ const cspValue = buildCspHeader(params, strict);
84
+ // 3. Assemble security headers for **
85
+ const globalHeaders = [
86
+ ...SECURITY_HEADERS_GLOBAL,
87
+ { key: "Content-Security-Policy", value: cspValue },
88
+ ];
89
+ // Strict mode: remove X-XSS-Protection (CSP makes it redundant and
90
+ // it can cause issues in modern browsers)
91
+ // Standard mode: keep it for legacy browser compat
92
+ if (!strict) {
93
+ globalHeaders.push({ key: "X-XSS-Protection", value: "1; mode=block" });
94
+ }
95
+ // 4. Merge global security headers
96
+ const globalRule = {
97
+ source: "**",
98
+ headers: globalHeaders,
99
+ };
100
+ const existingGlobal = headers.find((r) => r.source === "**");
101
+ const existingGlobalKeys = new Set((existingGlobal?.headers || []).map((h) => h.key.toLowerCase()));
102
+ // Track what's new vs already present
103
+ for (const h of globalHeaders) {
104
+ if (existingGlobalKeys.has(h.key.toLowerCase())) {
105
+ skipped.push(`${h.key} (already set)`);
106
+ }
107
+ else {
108
+ applied.push(`${h.key}`);
109
+ }
110
+ }
111
+ headers = mergeHeaders(headers, globalRule);
112
+ // 5. Cache headers for static assets
113
+ if (cache_immutable) {
114
+ const cacheRule = {
115
+ source: CACHE_IMMUTABLE_PATTERN,
116
+ headers: CACHE_IMMUTABLE_HEADERS,
117
+ };
118
+ const htmlCacheRule = {
119
+ source: CACHE_HTML_PATTERN,
120
+ headers: CACHE_HTML_HEADERS,
121
+ };
122
+ const existingCache = headers.find((r) => r.source === CACHE_IMMUTABLE_PATTERN);
123
+ if (existingCache) {
124
+ skipped.push("Cache-Control on static assets (already set)");
125
+ }
126
+ else {
127
+ headers = mergeHeaders(headers, cacheRule);
128
+ applied.push("Cache-Control: immutable on static assets");
129
+ }
130
+ const existingHtmlCache = headers.find((r) => r.source === CACHE_HTML_PATTERN);
131
+ if (existingHtmlCache) {
132
+ skipped.push("Cache-Control on HTML (already set)");
133
+ }
134
+ else {
135
+ headers = mergeHeaders(headers, htmlCacheRule);
136
+ applied.push("Cache-Control: no-cache on HTML");
137
+ }
138
+ }
139
+ // 6. Dry run — preview without writing
140
+ if (dry_run) {
141
+ const report = [
142
+ `🔍 Dry run — no changes written`,
143
+ ``,
144
+ `Preset: ${preset}`,
145
+ ``,
146
+ ];
147
+ if (applied.length > 0) {
148
+ report.push(`Would apply:`);
149
+ report.push(...applied.map((a) => ` ✅ ${a}`));
150
+ }
151
+ if (skipped.length > 0) {
152
+ report.push(``, `Already present (no change):`);
153
+ report.push(...skipped.map((s) => ` ⏭️ ${s}`));
154
+ }
155
+ report.push(``, `CSP directive:`, ` ${cspValue}`);
156
+ return report.join("\n");
157
+ }
158
+ // 7. Write updated config
159
+ config.hosting.headers = headers;
160
+ await writeFirebaseJson(project_dir, config);
161
+ // 8. Build report
162
+ const report = [
163
+ `✅ Security headers applied! (${preset} preset)`,
164
+ ``,
165
+ ];
166
+ if (applied.length > 0) {
167
+ report.push(`Applied:`);
168
+ report.push(...applied.map((a) => ` ✅ ${a}`));
169
+ }
170
+ if (skipped.length > 0) {
171
+ report.push(``, `Already present (kept your values):`);
172
+ report.push(...skipped.map((s) => ` ⏭️ ${s}`));
173
+ }
174
+ report.push(``, `CSP directive:`, ` ${cspValue}`);
175
+ report.push(``, `⚠️ Next steps:`, ` 1. Review the CSP — if your app loads external scripts/fonts/APIs, add their origins`, ` 2. Re-deploy: use firebase_hosting_deploy to push these headers live`, ` 3. Test with: https://securityheaders.com and browser DevTools Console`);
176
+ if (strict) {
177
+ report.push(``, `🔒 Strict mode enabled:`, ` - Cross-Origin-Opener-Policy: same-origin (may break popups/OAuth redirects)`, ` - Cross-Origin-Resource-Policy: same-origin (may break cross-origin assets)`, ` - upgrade-insecure-requests in CSP (forces HTTPS for all sub-resources)`, ` If you see broken features, switch to 'standard' preset.`);
178
+ }
179
+ return report.join("\n");
180
+ }
181
+ //# sourceMappingURL=harden.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harden.js","sourceRoot":"","sources":["../../src/tools/harden.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { InitInput } from "../schemas/index.js";
2
+ export declare function handleInit(params: InitInput): Promise<string>;
3
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/tools/init.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,wBAAsB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CA6EnE"}
@@ -0,0 +1,60 @@
1
+ // firebase_hosting_init - Scaffolds Firebase Hosting configuration
2
+ import { join } from "node:path";
3
+ import { detectFramework, readFirebaseJson, writeFirebaseJson, writeFirebaseRc, fileExists, } from "../services/firebase-cli.js";
4
+ import { DEFAULT_SPA_CONFIG, DEFAULT_STATIC_CONFIG } from "../constants.js";
5
+ export async function handleInit(params) {
6
+ const { project_dir, project_id } = params;
7
+ // Check if firebase.json already exists
8
+ const existing = await readFirebaseJson(project_dir);
9
+ if (existing) {
10
+ return `⚠️ firebase.json already exists in ${project_dir}. Use firebase_hosting_configure to modify it, or delete it first to re-initialize.`;
11
+ }
12
+ // Detect framework if not specified
13
+ let framework = params.framework;
14
+ let publicDir = params.public_dir;
15
+ let isSPA = params.spa;
16
+ if (!framework || framework !== "static") {
17
+ const detected = await detectFramework(project_dir);
18
+ if (detected && !framework) {
19
+ framework = detected.framework;
20
+ }
21
+ if (detected && !publicDir) {
22
+ publicDir = detected.publicDir;
23
+ }
24
+ if (detected && isSPA === undefined) {
25
+ isSPA = detected.isSPA;
26
+ }
27
+ }
28
+ // Build the config
29
+ const baseConfig = isSPA ? DEFAULT_SPA_CONFIG : DEFAULT_STATIC_CONFIG;
30
+ const config = JSON.parse(JSON.stringify(baseConfig));
31
+ if (publicDir) {
32
+ config.hosting.public = publicDir;
33
+ }
34
+ // Write firebase.json
35
+ await writeFirebaseJson(project_dir, config);
36
+ // Write .firebaserc
37
+ await writeFirebaseRc(project_dir, project_id);
38
+ // Check if .gitignore exists and add firebase cache
39
+ const gitignorePath = join(project_dir, ".gitignore");
40
+ if (await fileExists(gitignorePath)) {
41
+ const { readFile, appendFile } = await import("node:fs/promises");
42
+ const content = await readFile(gitignorePath, "utf-8");
43
+ if (!content.includes(".firebase")) {
44
+ await appendFile(gitignorePath, "\n# Firebase\n.firebase/\n");
45
+ }
46
+ }
47
+ // Build result message
48
+ const lines = [
49
+ `✅ Firebase Hosting initialized successfully!`,
50
+ ``,
51
+ `Project: ${project_id}`,
52
+ `Directory: ${project_dir}`,
53
+ ];
54
+ if (framework && framework !== "static") {
55
+ lines.push(`Framework detected: ${framework}`);
56
+ }
57
+ lines.push(`Public directory: ${config.hosting.public}`, `SPA mode: ${isSPA ? "enabled" : "disabled"}`, ``, `Files created:`, ` - firebase.json (hosting configuration)`, ` - .firebaserc (project alias)`, ``, `Next steps:`, ` 1. Build your project`, ` 2. Use firebase_hosting_deploy to deploy`);
58
+ return lines.join("\n");
59
+ }
60
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/tools/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { PreviewInput } from "../schemas/index.js";
2
+ export declare function handlePreview(params: PreviewInput): Promise<string>;
3
+ //# sourceMappingURL=preview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/tools/preview.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD,wBAAsB,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAwFzE"}