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.

env.js 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.loadFirebaseEnvs = exports.loadUserEnvs = exports.checkForDuplicateKeys = exports.writeUserEnvs = exports.hasUserEnvs = exports.parseStrict = exports.validateKey = exports.KeyValidationError = exports.parse = void 0;
  4. const clc = require("colorette");
  5. const fs = require("fs");
  6. const path = require("path");
  7. const error_1 = require("../error");
  8. const logger_1 = require("../logger");
  9. const utils_1 = require("../utils");
  10. const FUNCTIONS_EMULATOR_DOTENV = ".env.local";
  11. const RESERVED_PREFIXES = ["X_GOOGLE_", "FIREBASE_", "EXT_"];
  12. const RESERVED_KEYS = [
  13. "FIREBASE_CONFIG",
  14. "CLOUD_RUNTIME_CONFIG",
  15. "EVENTARC_CLOUD_EVENT_SOURCE",
  16. "ENTRY_POINT",
  17. "GCP_PROJECT",
  18. "GCLOUD_PROJECT",
  19. "GOOGLE_CLOUD_PROJECT",
  20. "FUNCTION_TRIGGER_TYPE",
  21. "FUNCTION_NAME",
  22. "FUNCTION_MEMORY_MB",
  23. "FUNCTION_TIMEOUT_SEC",
  24. "FUNCTION_IDENTITY",
  25. "FUNCTION_REGION",
  26. "FUNCTION_TARGET",
  27. "FUNCTION_SIGNATURE_TYPE",
  28. "K_SERVICE",
  29. "K_REVISION",
  30. "PORT",
  31. "K_CONFIGURATION",
  32. ];
  33. const LINE_RE = new RegExp("^" +
  34. "\\s*" +
  35. "([\\w./]+)" +
  36. "\\s*=[\\f\\t\\v]*" +
  37. "(" +
  38. "\\s*'(?:\\\\'|[^'])*'|" +
  39. '\\s*"(?:\\\\"|[^"])*"|' +
  40. "[^#\\r\\n]*" +
  41. ")?" +
  42. "\\s*" +
  43. "(?:#[^\\n]*)?" +
  44. "$", "gms");
  45. const ESCAPE_SEQUENCES_TO_CHARACTERS = {
  46. "\\n": "\n",
  47. "\\r": "\r",
  48. "\\t": "\t",
  49. "\\v": "\v",
  50. "\\\\": "\\",
  51. "\\'": "'",
  52. '\\"': '"',
  53. };
  54. const ALL_ESCAPE_SEQUENCES_RE = /\\[nrtv\\'"]/g;
  55. const CHARACTERS_TO_ESCAPE_SEQUENCES = {
  56. "\n": "\\n",
  57. "\r": "\\r",
  58. "\t": "\\t",
  59. "\v": "\\v",
  60. "\\": "\\\\",
  61. "'": "\\'",
  62. '"': '\\"',
  63. };
  64. const ALL_ESCAPABLE_CHARACTERS_RE = /[\n\r\t\v\\'"]/g;
  65. function parse(data) {
  66. const envs = {};
  67. const errors = [];
  68. data = data.replace(/\r\n?/, "\n");
  69. let match;
  70. while ((match = LINE_RE.exec(data))) {
  71. let [, k, v] = match;
  72. v = (v || "").trim();
  73. let quotesMatch;
  74. if ((quotesMatch = /^(["'])(.*)\1$/ms.exec(v)) != null) {
  75. v = quotesMatch[2];
  76. if (quotesMatch[1] === '"') {
  77. v = v.replace(ALL_ESCAPE_SEQUENCES_RE, (match) => ESCAPE_SEQUENCES_TO_CHARACTERS[match]);
  78. }
  79. }
  80. envs[k] = v;
  81. }
  82. const nonmatches = data.replace(LINE_RE, "");
  83. for (let line of nonmatches.split(/[\r\n]+/)) {
  84. line = line.trim();
  85. if (line.startsWith("#")) {
  86. continue;
  87. }
  88. if (line.length)
  89. errors.push(line);
  90. }
  91. return { envs, errors };
  92. }
  93. exports.parse = parse;
  94. class KeyValidationError extends Error {
  95. constructor(key, message) {
  96. super(`Failed to validate key ${key}: ${message}`);
  97. this.key = key;
  98. this.message = message;
  99. }
  100. }
  101. exports.KeyValidationError = KeyValidationError;
  102. function validateKey(key) {
  103. if (RESERVED_KEYS.includes(key)) {
  104. throw new KeyValidationError(key, `Key ${key} is reserved for internal use.`);
  105. }
  106. if (!/^[A-Z_][A-Z0-9_]*$/.test(key)) {
  107. throw new KeyValidationError(key, `Key ${key} must start with an uppercase ASCII letter or underscore` +
  108. ", and then consist of uppercase ASCII letters, digits, and underscores.");
  109. }
  110. if (RESERVED_PREFIXES.some((prefix) => key.startsWith(prefix))) {
  111. throw new KeyValidationError(key, `Key ${key} starts with a reserved prefix (${RESERVED_PREFIXES.join(" ")})`);
  112. }
  113. }
  114. exports.validateKey = validateKey;
  115. function parseStrict(data) {
  116. const { envs, errors } = parse(data);
  117. if (errors.length) {
  118. throw new error_1.FirebaseError(`Invalid dotenv file, error on lines: ${errors.join(",")}`);
  119. }
  120. const validationErrors = [];
  121. for (const key of Object.keys(envs)) {
  122. try {
  123. validateKey(key);
  124. }
  125. catch (err) {
  126. logger_1.logger.debug(`Failed to validate key ${key}: ${err}`);
  127. if (err instanceof KeyValidationError) {
  128. validationErrors.push(err);
  129. }
  130. else {
  131. throw err;
  132. }
  133. }
  134. }
  135. if (validationErrors.length > 0) {
  136. throw new error_1.FirebaseError("Validation failed", { children: validationErrors });
  137. }
  138. return envs;
  139. }
  140. exports.parseStrict = parseStrict;
  141. function findEnvfiles(functionsSource, projectId, projectAlias, isEmulator) {
  142. const files = [".env"];
  143. files.push(`.env.${projectId}`);
  144. if (projectAlias) {
  145. files.push(`.env.${projectAlias}`);
  146. }
  147. if (isEmulator) {
  148. files.push(FUNCTIONS_EMULATOR_DOTENV);
  149. }
  150. return files
  151. .map((f) => path.join(functionsSource, f))
  152. .filter(fs.existsSync)
  153. .map((p) => path.basename(p));
  154. }
  155. function hasUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, }) {
  156. return findEnvfiles(functionsSource, projectId, projectAlias, isEmulator).length > 0;
  157. }
  158. exports.hasUserEnvs = hasUserEnvs;
  159. function writeUserEnvs(toWrite, envOpts) {
  160. if (Object.keys(toWrite).length === 0) {
  161. return;
  162. }
  163. const { functionsSource, projectId, projectAlias, isEmulator } = envOpts;
  164. const allEnvFiles = findEnvfiles(functionsSource, projectId, projectAlias, isEmulator);
  165. const targetEnvFile = envOpts.isEmulator
  166. ? FUNCTIONS_EMULATOR_DOTENV
  167. : `.env.${envOpts.projectId}`;
  168. const targetEnvFileExists = allEnvFiles.includes(targetEnvFile);
  169. if (!targetEnvFileExists) {
  170. fs.writeFileSync(path.join(envOpts.functionsSource, targetEnvFile), "", { flag: "wx" });
  171. (0, utils_1.logBullet)(clc.yellow(clc.bold("functions: ")) +
  172. `Created new local file ${targetEnvFile} to store param values. We suggest explicitly adding or excluding this file from version control.`);
  173. }
  174. const fullEnvs = loadUserEnvs(envOpts);
  175. const prodEnvs = isEmulator
  176. ? loadUserEnvs(Object.assign(Object.assign({}, envOpts), { isEmulator: false }))
  177. : loadUserEnvs(envOpts);
  178. checkForDuplicateKeys(isEmulator || false, Object.keys(toWrite), fullEnvs, prodEnvs);
  179. for (const k of Object.keys(toWrite)) {
  180. validateKey(k);
  181. }
  182. (0, utils_1.logBullet)(clc.cyan(clc.bold("functions: ")) + `Writing new parameter values to disk: ${targetEnvFile}`);
  183. let lines = "";
  184. for (const k of Object.keys(toWrite)) {
  185. lines += formatUserEnvForWrite(k, toWrite[k]);
  186. }
  187. fs.appendFileSync(path.join(functionsSource, targetEnvFile), lines);
  188. }
  189. exports.writeUserEnvs = writeUserEnvs;
  190. function checkForDuplicateKeys(isEmulator, keys, fullEnv, envsWithoutLocal) {
  191. for (const key of keys) {
  192. const definedInEnv = fullEnv.hasOwnProperty(key);
  193. if (definedInEnv) {
  194. if (envsWithoutLocal && isEmulator && envsWithoutLocal.hasOwnProperty(key)) {
  195. (0, utils_1.logWarning)(clc.cyan(clc.yellow("functions: ")) +
  196. `Writing parameter ${key} to emulator-specific config .env.local. This will overwrite your existing definition only when emulating.`);
  197. continue;
  198. }
  199. throw new error_1.FirebaseError(`Attempted to write param-defined key ${key} to .env files, but it was already defined.`);
  200. }
  201. }
  202. }
  203. exports.checkForDuplicateKeys = checkForDuplicateKeys;
  204. function formatUserEnvForWrite(key, value) {
  205. const escapedValue = value.replace(ALL_ESCAPABLE_CHARACTERS_RE, (match) => CHARACTERS_TO_ESCAPE_SEQUENCES[match]);
  206. if (escapedValue !== value) {
  207. return `${key}="${escapedValue}"\n`;
  208. }
  209. return `${key}=${escapedValue}\n`;
  210. }
  211. function loadUserEnvs({ functionsSource, projectId, projectAlias, isEmulator, }) {
  212. var _a;
  213. const envFiles = findEnvfiles(functionsSource, projectId, projectAlias, isEmulator);
  214. if (envFiles.length === 0) {
  215. return {};
  216. }
  217. if (projectAlias) {
  218. if (envFiles.includes(`.env.${projectId}`) && envFiles.includes(`.env.${projectAlias}`)) {
  219. throw new error_1.FirebaseError(`Can't have both dotenv files with projectId (env.${projectId}) ` +
  220. `and projectAlias (.env.${projectAlias}) as extensions.`);
  221. }
  222. }
  223. let envs = {};
  224. for (const f of envFiles) {
  225. try {
  226. const data = fs.readFileSync(path.join(functionsSource, f), "utf8");
  227. envs = Object.assign(Object.assign({}, envs), parseStrict(data));
  228. }
  229. catch (err) {
  230. throw new error_1.FirebaseError(`Failed to load environment variables from ${f}.`, {
  231. exit: 2,
  232. children: ((_a = err.children) === null || _a === void 0 ? void 0 : _a.length) > 0 ? err.children : [err],
  233. });
  234. }
  235. }
  236. (0, utils_1.logBullet)(clc.cyan(clc.bold("functions: ")) + `Loaded environment variables from ${envFiles.join(", ")}.`);
  237. return envs;
  238. }
  239. exports.loadUserEnvs = loadUserEnvs;
  240. function loadFirebaseEnvs(firebaseConfig, projectId) {
  241. return {
  242. FIREBASE_CONFIG: JSON.stringify(firebaseConfig),
  243. GCLOUD_PROJECT: projectId,
  244. };
  245. }
  246. exports.loadFirebaseEnvs = loadFirebaseEnvs;