No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.createServerResponseProxy = exports.prepareFrameworks = exports.findDependency = exports.discover = exports.relativeRequire = exports.WebFrameworks = exports.NODE_VERSION = exports.DEFAULT_REGION = exports.FIREBASE_ADMIN_VERSION = exports.FIREBASE_FUNCTIONS_VERSION = exports.FIREBASE_FRAMEWORKS_VERSION = void 0;
  4. const path_1 = require("path");
  5. const process_1 = require("process");
  6. const child_process_1 = require("child_process");
  7. const fs_1 = require("fs");
  8. const url_1 = require("url");
  9. const http_1 = require("http");
  10. const promises_1 = require("fs/promises");
  11. const fs_extra_1 = require("fs-extra");
  12. const clc = require("colorette");
  13. const process = require("node:process");
  14. const semver = require("semver");
  15. const projectUtils_1 = require("../projectUtils");
  16. const config_1 = require("../hosting/config");
  17. const api_1 = require("../hosting/api");
  18. const apps_1 = require("../management/apps");
  19. const prompt_1 = require("../prompt");
  20. const types_1 = require("../emulator/types");
  21. const defaultCredentials_1 = require("../defaultCredentials");
  22. const auth_1 = require("../auth");
  23. const functionsEmulatorShared_1 = require("../emulator/functionsEmulatorShared");
  24. const constants_1 = require("../emulator/constants");
  25. const error_1 = require("../error");
  26. const requireHostingSite_1 = require("../requireHostingSite");
  27. const experiments = require("../experiments");
  28. const ensureTargeted_1 = require("../functions/ensureTargeted");
  29. const implicitInit_1 = require("../hosting/implicitInit");
  30. const { dynamicImport } = require(true && "../dynamicImport");
  31. const SupportLevelWarnings = {
  32. ["experimental"]: clc.yellow(`This is an experimental integration, proceed with caution.`),
  33. ["community-supported"]: clc.yellow(`This is a community-supported integration, support is best effort.`),
  34. };
  35. exports.FIREBASE_FRAMEWORKS_VERSION = "^0.6.0";
  36. exports.FIREBASE_FUNCTIONS_VERSION = "^3.23.0";
  37. exports.FIREBASE_ADMIN_VERSION = "^11.0.1";
  38. exports.DEFAULT_REGION = "us-central1";
  39. exports.NODE_VERSION = parseInt(process.versions.node, 10).toString();
  40. const DEFAULT_FIND_DEP_OPTIONS = {
  41. cwd: process.cwd(),
  42. omitDev: true,
  43. };
  44. const NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm";
  45. exports.WebFrameworks = Object.fromEntries((0, fs_1.readdirSync)(__dirname)
  46. .filter((path) => (0, fs_1.statSync)((0, path_1.join)(__dirname, path)).isDirectory())
  47. .map((path) => [path, require((0, path_1.join)(__dirname, path))])
  48. .filter(([, obj]) => obj.name && obj.discover && obj.build && obj.type !== undefined && obj.support));
  49. function relativeRequire(dir, mod) {
  50. try {
  51. const path = require.resolve(mod, { paths: [dir] });
  52. if ((0, path_1.extname)(path) === ".mjs") {
  53. return dynamicImport((0, url_1.pathToFileURL)(path).toString());
  54. }
  55. else {
  56. return require(path);
  57. }
  58. }
  59. catch (e) {
  60. const path = (0, path_1.relative)(process.cwd(), dir);
  61. console.error(`Could not load dependency ${mod} in ${path.startsWith("..") ? path : `./${path}`}, have you run \`npm install\`?`);
  62. throw e;
  63. }
  64. }
  65. exports.relativeRequire = relativeRequire;
  66. async function discover(dir, warn = true) {
  67. const allFrameworkTypes = [
  68. ...new Set(Object.values(exports.WebFrameworks).map(({ type }) => type)),
  69. ].sort();
  70. for (const discoveryType of allFrameworkTypes) {
  71. const frameworksDiscovered = [];
  72. for (const framework in exports.WebFrameworks) {
  73. if (exports.WebFrameworks[framework]) {
  74. const { discover, type } = exports.WebFrameworks[framework];
  75. if (type !== discoveryType)
  76. continue;
  77. const result = await discover(dir);
  78. if (result)
  79. frameworksDiscovered.push(Object.assign({ framework }, result));
  80. }
  81. }
  82. if (frameworksDiscovered.length > 1) {
  83. if (warn)
  84. console.error("Multiple conflicting frameworks discovered.");
  85. return;
  86. }
  87. if (frameworksDiscovered.length === 1)
  88. return frameworksDiscovered[0];
  89. }
  90. if (warn)
  91. console.warn("Could not determine the web framework in use.");
  92. return;
  93. }
  94. exports.discover = discover;
  95. function scanDependencyTree(searchingFor, dependencies = {}) {
  96. for (const [name, dependency] of Object.entries(dependencies)) {
  97. if (name === searchingFor)
  98. return dependency;
  99. const result = scanDependencyTree(searchingFor, dependency.dependencies);
  100. if (result)
  101. return result;
  102. }
  103. return;
  104. }
  105. function findDependency(name, options = {}) {
  106. const { cwd, depth, omitDev } = Object.assign(Object.assign({}, DEFAULT_FIND_DEP_OPTIONS), options);
  107. const env = Object.assign({}, process.env);
  108. delete env.NODE_ENV;
  109. const result = (0, child_process_1.spawnSync)(NPM_COMMAND, [
  110. "list",
  111. name,
  112. "--json",
  113. ...(omitDev ? ["--omit", "dev"] : []),
  114. ...(depth === undefined ? [] : ["--depth", depth.toString(10)]),
  115. ], { cwd, env });
  116. if (!result.stdout)
  117. return;
  118. const json = JSON.parse(result.stdout.toString());
  119. return scanDependencyTree(name, json.dependencies);
  120. }
  121. exports.findDependency = findDependency;
  122. async function prepareFrameworks(targetNames, context, options, emulators = []) {
  123. var _a;
  124. var _b, _c, _d, _e;
  125. const nodeVersion = process.version;
  126. if (!semver.satisfies(nodeVersion, ">=16.0.0")) {
  127. throw new error_1.FirebaseError(`The frameworks awareness feature requires Node.JS >= 16 and npm >= 8 in order to work correctly, due to some of the downstream dependencies. Please upgrade your version of Node.JS, reinstall firebase-tools, and give it another go.`);
  128. }
  129. const project = (0, projectUtils_1.needProjectId)(context);
  130. const { projectRoot } = options;
  131. const account = (0, auth_1.getProjectDefaultAccount)(projectRoot);
  132. if (!options.site) {
  133. try {
  134. await (0, requireHostingSite_1.requireHostingSite)(options);
  135. }
  136. catch (_f) {
  137. options.site = project;
  138. }
  139. }
  140. const configs = (0, config_1.hostingConfig)(options);
  141. let firebaseDefaults = undefined;
  142. if (configs.length === 0) {
  143. return;
  144. }
  145. for (const config of configs) {
  146. const { source, site, public: publicDir } = config;
  147. if (!source) {
  148. continue;
  149. }
  150. config.rewrites || (config.rewrites = []);
  151. config.redirects || (config.redirects = []);
  152. config.headers || (config.headers = []);
  153. (_a = config.cleanUrls) !== null && _a !== void 0 ? _a : (config.cleanUrls = true);
  154. const dist = (0, path_1.join)(projectRoot, ".firebase", site);
  155. const hostingDist = (0, path_1.join)(dist, "hosting");
  156. const functionsDist = (0, path_1.join)(dist, "functions");
  157. if (publicDir) {
  158. throw new Error(`hosting.public and hosting.source cannot both be set in firebase.json`);
  159. }
  160. const getProjectPath = (...args) => (0, path_1.join)(projectRoot, source, ...args);
  161. const functionName = `ssr${site.toLowerCase().replace(/-/g, "")}`;
  162. const usesFirebaseAdminSdk = !!findDependency("firebase-admin", { cwd: getProjectPath() });
  163. const usesFirebaseJsSdk = !!findDependency("@firebase/app", { cwd: getProjectPath() });
  164. if (usesFirebaseAdminSdk) {
  165. process.env.GOOGLE_CLOUD_PROJECT = project;
  166. if (account && !process.env.GOOGLE_APPLICATION_CREDENTIALS) {
  167. const defaultCredPath = await (0, defaultCredentials_1.getCredentialPathAsync)(account);
  168. if (defaultCredPath)
  169. process.env.GOOGLE_APPLICATION_CREDENTIALS = defaultCredPath;
  170. }
  171. }
  172. emulators.forEach((info) => {
  173. if (usesFirebaseAdminSdk) {
  174. if (info.name === types_1.Emulators.FIRESTORE)
  175. process.env[constants_1.Constants.FIRESTORE_EMULATOR_HOST] = (0, functionsEmulatorShared_1.formatHost)(info);
  176. if (info.name === types_1.Emulators.AUTH)
  177. process.env[constants_1.Constants.FIREBASE_AUTH_EMULATOR_HOST] = (0, functionsEmulatorShared_1.formatHost)(info);
  178. if (info.name === types_1.Emulators.DATABASE)
  179. process.env[constants_1.Constants.FIREBASE_DATABASE_EMULATOR_HOST] = (0, functionsEmulatorShared_1.formatHost)(info);
  180. if (info.name === types_1.Emulators.STORAGE)
  181. process.env[constants_1.Constants.FIREBASE_STORAGE_EMULATOR_HOST] = (0, functionsEmulatorShared_1.formatHost)(info);
  182. }
  183. if (usesFirebaseJsSdk && types_1.EMULATORS_SUPPORTED_BY_USE_EMULATOR.includes(info.name)) {
  184. firebaseDefaults || (firebaseDefaults = {});
  185. firebaseDefaults.emulatorHosts || (firebaseDefaults.emulatorHosts = {});
  186. firebaseDefaults.emulatorHosts[info.name] = (0, functionsEmulatorShared_1.formatHost)(info);
  187. }
  188. });
  189. let firebaseConfig = null;
  190. if (usesFirebaseJsSdk) {
  191. const sites = await (0, api_1.listSites)(project);
  192. const selectedSite = sites.find((it) => it.name && it.name.split("/").pop() === site);
  193. if (selectedSite) {
  194. const { appId } = selectedSite;
  195. if (appId) {
  196. firebaseConfig = await (0, apps_1.getAppConfig)(appId, apps_1.AppPlatform.WEB);
  197. firebaseDefaults || (firebaseDefaults = {});
  198. firebaseDefaults.config = firebaseConfig;
  199. }
  200. else {
  201. const defaultConfig = await (0, implicitInit_1.implicitInit)(options);
  202. if (defaultConfig.json) {
  203. console.warn(`No Firebase app associated with site ${site}, injecting project default config.
  204. You can link a Web app to a Hosting site here https://console.firebase.google.com/project/${project}/settings/general/web`);
  205. firebaseDefaults || (firebaseDefaults = {});
  206. firebaseDefaults.config = JSON.parse(defaultConfig.json);
  207. }
  208. else {
  209. console.warn(`No Firebase app associated with site ${site}, unable to provide authenticated server context.
  210. You can link a Web app to a Hosting site here https://console.firebase.google.com/project/${project}/settings/general/web`);
  211. if (!options.nonInteractive) {
  212. const continueDeploy = await (0, prompt_1.promptOnce)({
  213. type: "confirm",
  214. default: true,
  215. message: "Would you like to continue with the deploy?",
  216. });
  217. if (!continueDeploy)
  218. (0, process_1.exit)(1);
  219. }
  220. }
  221. }
  222. }
  223. }
  224. if (firebaseDefaults)
  225. process.env.__FIREBASE_DEFAULTS__ = JSON.stringify(firebaseDefaults);
  226. const results = await discover(getProjectPath());
  227. if (!results)
  228. throw new Error("Epic fail.");
  229. const { framework, mayWantBackend, publicDirectory } = results;
  230. const { build, ɵcodegenPublicDirectory, ɵcodegenFunctionsDirectory: codegenProdModeFunctionsDirectory, getDevModeHandle, name, support, } = exports.WebFrameworks[framework];
  231. console.log(`Detected a ${name} codebase. ${SupportLevelWarnings[support] || ""}\n`);
  232. const isDevMode = context._name === "serve" || context._name === "emulators:start";
  233. const hostingEmulatorInfo = emulators.find((e) => e.name === types_1.Emulators.HOSTING);
  234. const devModeHandle = isDevMode &&
  235. getDevModeHandle &&
  236. (await getDevModeHandle(getProjectPath(), hostingEmulatorInfo));
  237. let codegenFunctionsDirectory;
  238. if (devModeHandle) {
  239. config.public = (0, path_1.relative)(projectRoot, publicDirectory);
  240. options.frameworksDevModeHandle = devModeHandle;
  241. if (mayWantBackend && firebaseDefaults) {
  242. codegenFunctionsDirectory = codegenDevModeFunctionsDirectory;
  243. }
  244. }
  245. else {
  246. const { wantsBackend = false, rewrites = [], redirects = [], headers = [], } = (await build(getProjectPath())) || {};
  247. config.rewrites.push(...rewrites);
  248. config.redirects.push(...redirects);
  249. config.headers.push(...headers);
  250. if (await (0, fs_extra_1.pathExists)(hostingDist))
  251. await (0, promises_1.rm)(hostingDist, { recursive: true });
  252. await (0, fs_extra_1.mkdirp)(hostingDist);
  253. await ɵcodegenPublicDirectory(getProjectPath(), hostingDist);
  254. config.public = (0, path_1.relative)(projectRoot, hostingDist);
  255. if (wantsBackend)
  256. codegenFunctionsDirectory = codegenProdModeFunctionsDirectory;
  257. }
  258. config.webFramework = `${framework}${codegenFunctionsDirectory ? "_ssr" : ""}`;
  259. if (codegenFunctionsDirectory) {
  260. if (firebaseDefaults)
  261. firebaseDefaults._authTokenSyncURL = "/__session";
  262. const rewrite = {
  263. source: "**",
  264. function: {
  265. functionId: functionName,
  266. },
  267. };
  268. if (experiments.isEnabled("pintags")) {
  269. rewrite.function.pinTag = true;
  270. }
  271. config.rewrites.push(rewrite);
  272. const codebase = `firebase-frameworks-${site}`;
  273. const existingFunctionsConfig = options.config.get("functions")
  274. ? [].concat(options.config.get("functions"))
  275. : [];
  276. options.config.set("functions", [
  277. ...existingFunctionsConfig,
  278. {
  279. source: (0, path_1.relative)(projectRoot, functionsDist),
  280. codebase,
  281. },
  282. ]);
  283. if (!targetNames.includes("functions")) {
  284. targetNames.unshift("functions");
  285. }
  286. if (options.only) {
  287. options.only = (0, ensureTargeted_1.ensureTargeted)(options.only, codebase);
  288. }
  289. if (await (0, fs_extra_1.pathExists)(functionsDist)) {
  290. const functionsDistStat = await (0, fs_extra_1.stat)(functionsDist);
  291. if (functionsDistStat === null || functionsDistStat === void 0 ? void 0 : functionsDistStat.isDirectory()) {
  292. const files = await (0, promises_1.readdir)(functionsDist);
  293. for (const file of files) {
  294. if (file !== "node_modules" && file !== "package-lock.json")
  295. await (0, promises_1.rm)((0, path_1.join)(functionsDist, file), { recursive: true });
  296. }
  297. }
  298. else {
  299. await (0, promises_1.rm)(functionsDist);
  300. }
  301. }
  302. else {
  303. await (0, fs_extra_1.mkdirp)(functionsDist);
  304. }
  305. const { packageJson, bootstrapScript, frameworksEntry = framework, } = await codegenFunctionsDirectory(getProjectPath(), functionsDist);
  306. await (0, promises_1.writeFile)((0, path_1.join)(functionsDist, "functions.yaml"), JSON.stringify({
  307. endpoints: {
  308. [functionName]: {
  309. platform: "gcfv2",
  310. region: [exports.DEFAULT_REGION],
  311. labels: {},
  312. httpsTrigger: {},
  313. entryPoint: "ssr",
  314. },
  315. },
  316. specVersion: "v1alpha1",
  317. requiredAPIs: [],
  318. }, null, 2));
  319. packageJson.main = "server.js";
  320. delete packageJson.devDependencies;
  321. packageJson.dependencies || (packageJson.dependencies = {});
  322. (_b = packageJson.dependencies)["firebase-frameworks"] || (_b["firebase-frameworks"] = exports.FIREBASE_FRAMEWORKS_VERSION);
  323. (_c = packageJson.dependencies)["firebase-functions"] || (_c["firebase-functions"] = exports.FIREBASE_FUNCTIONS_VERSION);
  324. (_d = packageJson.dependencies)["firebase-admin"] || (_d["firebase-admin"] = exports.FIREBASE_ADMIN_VERSION);
  325. packageJson.engines || (packageJson.engines = {});
  326. (_e = packageJson.engines).node || (_e.node = exports.NODE_VERSION);
  327. await (0, promises_1.writeFile)((0, path_1.join)(functionsDist, "package.json"), JSON.stringify(packageJson, null, 2));
  328. await (0, promises_1.writeFile)((0, path_1.join)(functionsDist, ".env"), `__FIREBASE_FRAMEWORKS_ENTRY__=${frameworksEntry}
  329. ${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\n` : ""}`);
  330. await (0, promises_1.copyFile)(getProjectPath("package-lock.json"), (0, path_1.join)(functionsDist, "package-lock.json")).catch(() => {
  331. });
  332. if (await (0, fs_extra_1.pathExists)(getProjectPath(".npmrc"))) {
  333. await (0, promises_1.copyFile)(getProjectPath(".npmrc"), (0, path_1.join)(functionsDist, ".npmrc"));
  334. }
  335. (0, child_process_1.execSync)(`${NPM_COMMAND} i --omit dev --no-audit`, {
  336. cwd: functionsDist,
  337. stdio: "inherit",
  338. });
  339. if (bootstrapScript)
  340. await (0, promises_1.writeFile)((0, path_1.join)(functionsDist, "bootstrap.js"), bootstrapScript);
  341. await (0, promises_1.writeFile)((0, path_1.join)(functionsDist, "server.js"), `const { onRequest } = require('firebase-functions/v2/https');
  342. const server = import('firebase-frameworks');
  343. exports.ssr = onRequest((req, res) => server.then(it => it.handle(req, res)));
  344. `);
  345. }
  346. else {
  347. config.rewrites.push({
  348. source: "**",
  349. destination: "/index.html",
  350. });
  351. }
  352. if (firebaseDefaults) {
  353. const encodedDefaults = Buffer.from(JSON.stringify(firebaseDefaults)).toString("base64url");
  354. const expires = new Date(new Date().getTime() + 60000000000);
  355. const sameSite = "Strict";
  356. const path = `/`;
  357. config.headers.push({
  358. source: "**/*.js",
  359. headers: [
  360. {
  361. key: "Set-Cookie",
  362. value: `__FIREBASE_DEFAULTS__=${encodedDefaults}; SameSite=${sameSite}; Expires=${expires.toISOString()}; Path=${path};`,
  363. },
  364. ],
  365. });
  366. }
  367. }
  368. }
  369. exports.prepareFrameworks = prepareFrameworks;
  370. function codegenDevModeFunctionsDirectory() {
  371. const packageJson = {};
  372. return Promise.resolve({ packageJson, frameworksEntry: "_devMode" });
  373. }
  374. function createServerResponseProxy(req, res, next) {
  375. const proxiedRes = new http_1.ServerResponse(req);
  376. const buffer = [];
  377. proxiedRes.write = new Proxy(proxiedRes.write.bind(proxiedRes), {
  378. apply: (target, thisArg, args) => {
  379. target.call(thisArg, ...args);
  380. buffer.push(["write", args]);
  381. },
  382. });
  383. proxiedRes.setHeader = new Proxy(proxiedRes.setHeader.bind(proxiedRes), {
  384. apply: (target, thisArg, args) => {
  385. target.call(thisArg, ...args);
  386. buffer.push(["setHeader", args]);
  387. },
  388. });
  389. proxiedRes.removeHeader = new Proxy(proxiedRes.removeHeader.bind(proxiedRes), {
  390. apply: (target, thisArg, args) => {
  391. target.call(thisArg, ...args);
  392. buffer.push(["removeHeader", args]);
  393. },
  394. });
  395. proxiedRes.writeHead = new Proxy(proxiedRes.writeHead.bind(proxiedRes), {
  396. apply: (target, thisArg, args) => {
  397. target.call(thisArg, ...args);
  398. buffer.push(["writeHead", args]);
  399. },
  400. });
  401. proxiedRes.end = new Proxy(proxiedRes.end.bind(proxiedRes), {
  402. apply: (target, thisArg, args) => {
  403. target.call(thisArg, ...args);
  404. if (proxiedRes.statusCode === 404) {
  405. next();
  406. }
  407. else {
  408. for (const [fn, args] of buffer) {
  409. res[fn](...args);
  410. }
  411. res.end(...args);
  412. }
  413. },
  414. });
  415. return proxiedRes;
  416. }
  417. exports.createServerResponseProxy = createServerResponseProxy;