firebase-tools 11.30.0 → 12.0.0

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 (55) hide show
  1. package/lib/api.js +4 -2
  2. package/lib/commands/ext-configure.js +2 -1
  3. package/lib/commands/ext-dev-deprecate.js +24 -20
  4. package/lib/commands/ext-dev-list.js +12 -11
  5. package/lib/commands/ext-dev-publish.js +13 -47
  6. package/lib/commands/ext-dev-register.js +8 -5
  7. package/lib/commands/ext-dev-undeprecate.js +4 -4
  8. package/lib/commands/ext-dev-upload.js +88 -0
  9. package/lib/commands/ext-dev-usage.js +3 -3
  10. package/lib/commands/ext-install.js +5 -10
  11. package/lib/commands/ext-uninstall.js +0 -1
  12. package/lib/commands/ext-update.js +4 -10
  13. package/lib/commands/index.js +9 -19
  14. package/lib/deploy/extensions/planner.js +13 -7
  15. package/lib/deploy/extensions/prepare.js +16 -32
  16. package/lib/emulator/storage/rules/config.js +17 -7
  17. package/lib/experiments.js +7 -6
  18. package/lib/extensions/extensionsApi.js +24 -151
  19. package/lib/extensions/extensionsHelper.js +282 -145
  20. package/lib/extensions/manifest.js +1 -8
  21. package/lib/extensions/publisherApi.js +215 -0
  22. package/lib/extensions/refs.js +1 -1
  23. package/lib/extensions/resolveSource.js +1 -18
  24. package/lib/extensions/tos.js +78 -0
  25. package/lib/extensions/warnings.js +21 -41
  26. package/lib/frameworks/angular/index.js +67 -185
  27. package/lib/frameworks/angular/interfaces.js +2 -0
  28. package/lib/frameworks/angular/utils.js +274 -0
  29. package/lib/frameworks/constants.js +5 -2
  30. package/lib/frameworks/index.js +60 -28
  31. package/lib/frameworks/next/index.js +86 -40
  32. package/lib/frameworks/next/utils.js +29 -12
  33. package/lib/frameworks/utils.js +11 -4
  34. package/lib/functions/python.js +1 -1
  35. package/lib/hosting/api.js +32 -1
  36. package/package.json +2 -2
  37. package/templates/extensions/POSTINSTALL.md +2 -2
  38. package/templates/extensions/PREINSTALL.md +1 -1
  39. package/templates/extensions/extension.yaml +10 -6
  40. package/templates/extensions/javascript/WELCOME.md +1 -1
  41. package/templates/extensions/typescript/WELCOME.md +1 -1
  42. package/templates/extensions/typescript/index.ts +1 -1
  43. package/templates/init/functions/javascript/index.js +16 -6
  44. package/templates/init/functions/javascript/package.lint.json +4 -4
  45. package/templates/init/functions/javascript/package.nolint.json +4 -4
  46. package/templates/init/functions/typescript/index.ts +16 -6
  47. package/templates/init/functions/typescript/package.lint.json +4 -4
  48. package/templates/init/functions/typescript/package.nolint.json +4 -4
  49. package/lib/commands/ext-dev-emulators-exec.js +0 -27
  50. package/lib/commands/ext-dev-emulators-start.js +0 -24
  51. package/lib/commands/ext-dev-extension-delete.js +0 -45
  52. package/lib/commands/ext-dev-unpublish.js +0 -49
  53. package/lib/commands/ext-sources-create.js +0 -24
  54. package/lib/extensions/askUserForConsent.js +0 -33
  55. package/npm-shrinkwrap.json +0 -12368
@@ -29,6 +29,7 @@ const constants_2 = require("./constants");
29
29
  Object.defineProperty(exports, "WebFrameworks", { enumerable: true, get: function () { return constants_2.WebFrameworks; } });
30
30
  const utils_2 = require("../utils");
31
31
  const ensureTargeted_1 = require("../functions/ensureTargeted");
32
+ const util_1 = require("util");
32
33
  async function discover(dir, warn = true) {
33
34
  const allFrameworkTypes = [
34
35
  ...new Set(Object.values(constants_2.WebFrameworks).map(({ type }) => type)),
@@ -58,9 +59,21 @@ async function discover(dir, warn = true) {
58
59
  return;
59
60
  }
60
61
  exports.discover = discover;
62
+ const BUILD_MEMO = new Map();
63
+ function memoizeBuild(dir, build, deps) {
64
+ const key = [dir, ...deps];
65
+ for (const existingKey of BUILD_MEMO.keys()) {
66
+ if ((0, util_1.isDeepStrictEqual)(existingKey, key)) {
67
+ return BUILD_MEMO.get(existingKey);
68
+ }
69
+ }
70
+ const value = build(dir);
71
+ BUILD_MEMO.set(key, value);
72
+ return value;
73
+ }
61
74
  async function prepareFrameworks(targetNames, context, options, emulators = []) {
62
- var _a, _b, _c;
63
- var _d, _e, _f, _g, _h;
75
+ var _a, _b, _c, _d, _e;
76
+ var _f, _g, _h, _j, _k;
64
77
  const nodeVersion = process.version;
65
78
  if (!semver.satisfies(nodeVersion, ">=16.0.0")) {
66
79
  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.`);
@@ -72,7 +85,7 @@ async function prepareFrameworks(targetNames, context, options, emulators = [])
72
85
  try {
73
86
  await (0, requireHostingSite_1.requireHostingSite)(options);
74
87
  }
75
- catch (_j) {
88
+ catch (_l) {
76
89
  options.site = project;
77
90
  }
78
91
  }
@@ -98,6 +111,7 @@ async function prepareFrameworks(targetNames, context, options, emulators = [])
98
111
  throw new Error(`hosting.public and hosting.source cannot both be set in firebase.json`);
99
112
  }
100
113
  const ssrRegion = (_b = frameworksBackend === null || frameworksBackend === void 0 ? void 0 : frameworksBackend.region) !== null && _b !== void 0 ? _b : constants_2.DEFAULT_REGION;
114
+ const omitCloudFunction = (_c = frameworksBackend === null || frameworksBackend === void 0 ? void 0 : frameworksBackend.omit) !== null && _c !== void 0 ? _c : false;
101
115
  if (!allowedRegionsValues.includes(ssrRegion)) {
102
116
  const validRegions = (0, utils_1.conjoinOptions)(allowedRegionsValues);
103
117
  throw new error_1.FirebaseError(`Hosting config for site ${site} places server-side content in region ${ssrRegion} which is not known. Valid regions are ${validRegions}`);
@@ -190,17 +204,22 @@ async function prepareFrameworks(targetNames, context, options, emulators = [])
190
204
  }
191
205
  }
192
206
  else {
193
- const { wantsBackend = false, rewrites = [], redirects = [], headers = [], trailingSlash, } = (await build(getProjectPath())) || {};
207
+ const { wantsBackend = false, rewrites = [], redirects = [], headers = [], trailingSlash, i18n = false, } = (await memoizeBuild(getProjectPath(), build, [firebaseDefaults])) || {};
194
208
  config.rewrites.push(...rewrites);
195
209
  config.redirects.push(...redirects);
196
210
  config.headers.push(...headers);
197
- (_c = config.trailingSlash) !== null && _c !== void 0 ? _c : (config.trailingSlash = trailingSlash);
211
+ (_d = config.trailingSlash) !== null && _d !== void 0 ? _d : (config.trailingSlash = trailingSlash);
212
+ if (i18n)
213
+ (_e = config.i18n) !== null && _e !== void 0 ? _e : (config.i18n = { root: constants_2.I18N_ROOT });
198
214
  if (await (0, fs_extra_1.pathExists)(hostingDist))
199
215
  await (0, promises_1.rm)(hostingDist, { recursive: true });
200
216
  await (0, fs_extra_1.mkdirp)(hostingDist);
201
- await ɵcodegenPublicDirectory(getProjectPath(), hostingDist);
217
+ await ɵcodegenPublicDirectory(getProjectPath(), hostingDist, {
218
+ project,
219
+ site,
220
+ });
202
221
  config.public = (0, path_1.relative)(projectRoot, hostingDist);
203
- if (wantsBackend)
222
+ if (wantsBackend && !omitCloudFunction)
204
223
  codegenFunctionsDirectory = codegenProdModeFunctionsDirectory;
205
224
  }
206
225
  config.webFramework = `${framework}${codegenFunctionsDirectory ? "_ssr" : ""}`;
@@ -212,14 +231,6 @@ async function prepareFrameworks(targetNames, context, options, emulators = [])
212
231
  if (context.hostingChannel) {
213
232
  experiments.assertEnabled("pintags", "deploy an app that requires a backend to a preview channel");
214
233
  }
215
- config.rewrites.push({
216
- source: "**",
217
- function: {
218
- functionId,
219
- region: ssrRegion,
220
- pinTag: experiments.isEnabled("pintags"),
221
- },
222
- });
223
234
  const codebase = `firebase-frameworks-${site}`;
224
235
  const existingFunctionsConfig = options.config.get("functions")
225
236
  ? [].concat(options.config.get("functions"))
@@ -258,27 +269,35 @@ async function prepareFrameworks(targetNames, context, options, emulators = [])
258
269
  else {
259
270
  await (0, fs_extra_1.mkdirp)(functionsDist);
260
271
  }
261
- const { packageJson, bootstrapScript, frameworksEntry = framework, } = await codegenFunctionsDirectory(getProjectPath(), functionsDist);
272
+ const { packageJson, bootstrapScript, frameworksEntry = framework, baseUrl = "", dotEnv = {}, } = await codegenFunctionsDirectory(getProjectPath(), functionsDist);
273
+ config.rewrites.push({
274
+ source: path_1.posix.normalize(path_1.posix.join(baseUrl, "**")),
275
+ function: {
276
+ functionId,
277
+ region: ssrRegion,
278
+ pinTag: experiments.isEnabled("pintags"),
279
+ },
280
+ });
262
281
  process.env.__FIREBASE_FRAMEWORKS_ENTRY__ = frameworksEntry;
263
282
  packageJson.main = "server.js";
264
283
  packageJson.dependencies || (packageJson.dependencies = {});
265
- (_d = packageJson.dependencies)["firebase-frameworks"] || (_d["firebase-frameworks"] = constants_2.FIREBASE_FRAMEWORKS_VERSION);
266
- (_e = packageJson.dependencies)["firebase-functions"] || (_e["firebase-functions"] = constants_2.FIREBASE_FUNCTIONS_VERSION);
267
- (_f = packageJson.dependencies)["firebase-admin"] || (_f["firebase-admin"] = constants_2.FIREBASE_ADMIN_VERSION);
284
+ (_f = packageJson.dependencies)["firebase-frameworks"] || (_f["firebase-frameworks"] = constants_2.FIREBASE_FRAMEWORKS_VERSION);
285
+ (_g = packageJson.dependencies)["firebase-functions"] || (_g["firebase-functions"] = constants_2.FIREBASE_FUNCTIONS_VERSION);
286
+ (_h = packageJson.dependencies)["firebase-admin"] || (_h["firebase-admin"] = constants_2.FIREBASE_ADMIN_VERSION);
268
287
  packageJson.engines || (packageJson.engines = {});
269
288
  const validEngines = constants_2.VALID_ENGINES.node.filter((it) => it <= constants_2.NODE_VERSION);
270
289
  const engine = validEngines[validEngines.length - 1] || constants_2.VALID_ENGINES.node[0];
271
290
  if (engine !== constants_2.NODE_VERSION) {
272
291
  (0, utils_2.logWarning)(`This integration expects Node version ${(0, utils_1.conjoinOptions)(constants_2.VALID_ENGINES.node, "or")}. You're running version ${constants_2.NODE_VERSION}, problems may be encountered.`);
273
292
  }
274
- (_g = packageJson.engines).node || (_g.node = engine.toString());
293
+ (_j = packageJson.engines).node || (_j.node = engine.toString());
275
294
  delete packageJson.scripts;
276
295
  delete packageJson.devDependencies;
277
296
  const bundledDependencies = packageJson.bundledDependencies || {};
278
297
  if (Object.keys(bundledDependencies).length) {
279
298
  (0, utils_2.logWarning)("Bundled dependencies aren't supported in Cloud Functions, converting to dependencies.");
280
299
  for (const [dep, version] of Object.entries(bundledDependencies)) {
281
- (_h = packageJson.dependencies)[dep] || (_h[dep] = version);
300
+ (_k = packageJson.dependencies)[dep] || (_k[dep] = version);
282
301
  }
283
302
  delete packageJson.bundledDependencies;
284
303
  }
@@ -289,12 +308,12 @@ async function prepareFrameworks(targetNames, context, options, emulators = [])
289
308
  continue;
290
309
  const stats = await (0, fs_extra_1.stat)(path);
291
310
  if (stats.isDirectory()) {
292
- const result = (0, cross_spawn_1.sync)("npm", ["pack", (0, path_1.relative)(functionsDist, path)], {
311
+ const result = (0, cross_spawn_1.sync)("npm", ["pack", (0, path_1.relative)(functionsDist, path), "--json=true"], {
293
312
  cwd: functionsDist,
294
313
  });
295
- if (!result.stdout)
314
+ if (result.status)
296
315
  throw new Error(`Error running \`npm pack\` at ${path}`);
297
- const filename = result.stdout.toString().trim();
316
+ const { filename } = JSON.parse(result.stdout.toString())[0];
298
317
  packageJson.dependencies[name] = `file:${filename}`;
299
318
  }
300
319
  else {
@@ -310,13 +329,16 @@ async function prepareFrameworks(targetNames, context, options, emulators = [])
310
329
  if (await (0, fs_extra_1.pathExists)(getProjectPath(".npmrc"))) {
311
330
  await (0, promises_1.copyFile)(getProjectPath(".npmrc"), (0, path_1.join)(functionsDist, ".npmrc"));
312
331
  }
313
- let existingDotEnvContents = "";
332
+ let dotEnvContents = "";
314
333
  if (await (0, fs_extra_1.pathExists)(getProjectPath(".env"))) {
315
- existingDotEnvContents = (await (0, promises_1.readFile)(getProjectPath(".env"))).toString();
334
+ dotEnvContents = (await (0, promises_1.readFile)(getProjectPath(".env"))).toString();
335
+ }
336
+ for (const [key, value] of Object.entries(dotEnv)) {
337
+ dotEnvContents += `\n${key}=${value}`;
316
338
  }
317
- await (0, promises_1.writeFile)((0, path_1.join)(functionsDist, ".env"), `${existingDotEnvContents}
339
+ await (0, promises_1.writeFile)((0, path_1.join)(functionsDist, ".env"), `${dotEnvContents}
318
340
  __FIREBASE_FRAMEWORKS_ENTRY__=${frameworksEntry}
319
- ${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\n` : ""}`);
341
+ ${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\n` : ""}`.trimStart());
320
342
  const envs = await new Promise((resolve, reject) => glob(getProjectPath(".env.*"), (err, matches) => {
321
343
  if (err)
322
344
  reject(err);
@@ -343,6 +365,9 @@ ${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\
343
365
  }
344
366
  }
345
367
  else {
368
+ if (await (0, fs_extra_1.pathExists)(functionsDist)) {
369
+ await (0, promises_1.rm)(functionsDist, { recursive: true });
370
+ }
346
371
  config.rewrites.push({
347
372
  source: "**",
348
373
  destination: "/index.html",
@@ -364,6 +389,13 @@ ${firebaseDefaults ? `__FIREBASE_DEFAULTS__=${JSON.stringify(firebaseDefaults)}\
364
389
  });
365
390
  }
366
391
  }
392
+ if (process.env.DEBUG) {
393
+ console.log("Effective firebase.json:");
394
+ console.log(JSON.stringify(configs, undefined, 2));
395
+ }
396
+ BUILD_MEMO.clear();
397
+ delete process.env.__FIREBASE_DEFAULTS__;
398
+ delete process.env.__FIREBASE_FRAMEWORKS_ENTRY__;
367
399
  }
368
400
  exports.prepareFrameworks = prepareFrameworks;
369
401
  function codegenDevModeFunctionsDirectory() {
@@ -17,10 +17,11 @@ const StreamObject_1 = require("stream-json/streamers/StreamObject");
17
17
  const fsutils_1 = require("../../fsutils");
18
18
  const prompt_1 = require("../../prompt");
19
19
  const error_1 = require("../../error");
20
- const constants_1 = require("../constants");
21
20
  const utils_1 = require("../utils");
22
21
  const utils_2 = require("./utils");
22
+ const constants_1 = require("../constants");
23
23
  const constants_2 = require("./constants");
24
+ const api_1 = require("../../hosting/api");
24
25
  const DEFAULT_BUILD_SCRIPT = ["next build"];
25
26
  const PUBLIC_DIR = "public";
26
27
  exports.name = "Next.js";
@@ -57,7 +58,7 @@ async function build(dir) {
57
58
  throw e;
58
59
  });
59
60
  const reasonsForBackend = new Set();
60
- const { distDir, trailingSlash } = await getConfig(dir);
61
+ const { distDir, trailingSlash, basePath } = await getConfig(dir);
61
62
  if (await (0, utils_2.isUsingMiddleware)((0, path_1.join)(dir, distDir), false)) {
62
63
  reasonsForBackend.add("middleware");
63
64
  }
@@ -85,12 +86,13 @@ async function build(dir) {
85
86
  reasonsForBackend.add(`non-static route ${key}`);
86
87
  }
87
88
  const manifest = await (0, utils_1.readJSON)((0, path_1.join)(dir, distDir, constants_2.ROUTES_MANIFEST));
88
- const { headers: nextJsHeaders = [], redirects: nextJsRedirects = [], rewrites: nextJsRewrites = [], } = manifest;
89
- const isEveryHeaderSupported = nextJsHeaders.every(utils_2.isHeaderSupportedByHosting);
89
+ const { headers: nextJsHeaders = [], redirects: nextJsRedirects = [], rewrites: nextJsRewrites = [], i18n: nextjsI18n, } = manifest;
90
+ const isEveryHeaderSupported = nextJsHeaders.map(utils_2.cleanI18n).every(utils_2.isHeaderSupportedByHosting);
90
91
  if (!isEveryHeaderSupported) {
91
92
  reasonsForBackend.add("advanced headers");
92
93
  }
93
94
  const headers = nextJsHeaders
95
+ .map(utils_2.cleanI18n)
94
96
  .filter(utils_2.isHeaderSupportedByHosting)
95
97
  .map(({ source, headers }) => ({
96
98
  source: (0, utils_2.cleanEscapedChars)(source),
@@ -101,7 +103,7 @@ async function build(dir) {
101
103
  (0, utils_1.readJSON)((0, path_1.join)(dir, distDir, constants_2.APP_PATH_ROUTES_MANIFEST)).catch(() => undefined),
102
104
  ]);
103
105
  if (appPathRoutesManifest) {
104
- const headersFromMetaFiles = await (0, utils_2.getHeadersFromMetaFiles)(dir, distDir, appPathRoutesManifest);
106
+ const headersFromMetaFiles = await (0, utils_2.getHeadersFromMetaFiles)(dir, distDir, basePath, appPathRoutesManifest);
105
107
  headers.push(...headersFromMetaFiles);
106
108
  if (appPathsManifest) {
107
109
  const unrenderedServerComponents = (0, utils_2.getNonStaticServerComponents)(appPathsManifest, appPathRoutesManifest, prerenderedRoutes, dynamicRoutes);
@@ -117,6 +119,7 @@ async function build(dir) {
117
119
  reasonsForBackend.add("advanced redirects");
118
120
  }
119
121
  const redirects = nextJsRedirects
122
+ .map(utils_2.cleanI18n)
120
123
  .filter(utils_2.isRedirectSupportedByHosting)
121
124
  .map(({ source, destination, statusCode: type }) => ({
122
125
  source: (0, utils_2.cleanEscapedChars)(source),
@@ -134,6 +137,7 @@ async function build(dir) {
134
137
  }
135
138
  const rewrites = nextJsRewritesToUse
136
139
  .filter(utils_2.isRewriteSupportedByHosting)
140
+ .map(utils_2.cleanI18n)
137
141
  .map(({ source, destination }) => ({
138
142
  source: (0, utils_2.cleanEscapedChars)(source),
139
143
  destination,
@@ -150,7 +154,15 @@ async function build(dir) {
150
154
  }
151
155
  console.log("");
152
156
  }
153
- return { wantsBackend, headers, redirects, rewrites, trailingSlash };
157
+ const i18n = !!nextjsI18n;
158
+ return {
159
+ wantsBackend,
160
+ headers,
161
+ redirects,
162
+ rewrites,
163
+ trailingSlash,
164
+ i18n,
165
+ };
154
166
  }
155
167
  exports.build = build;
156
168
  async function init(setup, config) {
@@ -163,25 +175,20 @@ async function init(setup, config) {
163
175
  (0, child_process_1.execSync)(`npx --yes create-next-app@latest -e hello-world ${setup.hosting.source} --use-npm ${language === "TypeScript" ? "--ts" : "--js"}`, { stdio: "inherit", cwd: config.projectDir });
164
176
  }
165
177
  exports.init = init;
166
- async function ɵcodegenPublicDirectory(sourceDir, destDir) {
167
- const { distDir } = await getConfig(sourceDir);
178
+ async function ɵcodegenPublicDirectory(sourceDir, destDir, context) {
179
+ const { distDir, i18n, basePath } = await getConfig(sourceDir);
180
+ let matchingI18nDomain = undefined;
181
+ if (i18n === null || i18n === void 0 ? void 0 : i18n.domains) {
182
+ const siteDomains = await (0, api_1.getAllSiteDomains)(context.project, context.site);
183
+ matchingI18nDomain = i18n.domains.find(({ domain }) => siteDomains.includes(domain));
184
+ }
185
+ const singleLocaleDomain = !i18n || ((matchingI18nDomain || i18n).locales || []).length <= 1;
168
186
  const publicPath = (0, path_1.join)(sourceDir, "public");
169
- await (0, promises_1.mkdir)((0, path_1.join)(destDir, "_next", "static"), { recursive: true });
187
+ await (0, promises_1.mkdir)((0, path_1.join)(destDir, basePath, "_next", "static"), { recursive: true });
170
188
  if (await (0, fs_extra_1.pathExists)(publicPath)) {
171
- await (0, fs_extra_1.copy)(publicPath, destDir);
172
- }
173
- await (0, fs_extra_1.copy)((0, path_1.join)(sourceDir, distDir, "static"), (0, path_1.join)(destDir, "_next", "static"));
174
- for (const file of ["index.html", "404.html", "500.html"]) {
175
- const pagesPath = (0, path_1.join)(sourceDir, distDir, "server", "pages", file);
176
- if (await (0, fs_extra_1.pathExists)(pagesPath)) {
177
- await (0, promises_1.copyFile)(pagesPath, (0, path_1.join)(destDir, file));
178
- continue;
179
- }
180
- const appPath = (0, path_1.join)(sourceDir, distDir, "server", "app", file);
181
- if (await (0, fs_extra_1.pathExists)(appPath)) {
182
- await (0, promises_1.copyFile)(appPath, (0, path_1.join)(destDir, file));
183
- }
189
+ await (0, fs_extra_1.copy)(publicPath, (0, path_1.join)(destDir, basePath));
184
190
  }
191
+ await (0, fs_extra_1.copy)((0, path_1.join)(sourceDir, distDir, "static"), (0, path_1.join)(destDir, basePath, "_next", "static"));
185
192
  const [middlewareManifest, prerenderManifest, routesManifest, pagesManifest, appPathRoutesManifest,] = await Promise.all([
186
193
  (0, utils_1.readJSON)((0, path_1.join)(sourceDir, distDir, "server", constants_2.MIDDLEWARE_MANIFEST)),
187
194
  (0, utils_1.readJSON)((0, path_1.join)(sourceDir, distDir, constants_2.PRERENDER_MANIFEST)),
@@ -194,9 +201,12 @@ async function ɵcodegenPublicDirectory(sourceDir, destDir) {
194
201
  const { redirects = [], rewrites = [], headers = [] } = routesManifest;
195
202
  const rewritesRegexesNotSupportedByHosting = (0, utils_2.getNextjsRewritesToUse)(rewrites)
196
203
  .filter((rewrite) => !(0, utils_2.isRewriteSupportedByHosting)(rewrite))
204
+ .map(utils_2.cleanI18n)
197
205
  .map((rewrite) => new RegExp(rewrite.regex));
198
206
  const redirectsRegexesNotSupportedByHosting = redirects
207
+ .filter((it) => !it.internal)
199
208
  .filter((redirect) => !(0, utils_2.isRedirectSupportedByHosting)(redirect))
209
+ .map(utils_2.cleanI18n)
200
210
  .map((redirect) => new RegExp(redirect.regex));
201
211
  const headersRegexesNotSupportedByHosting = headers
202
212
  .filter((header) => !(0, utils_2.isHeaderSupportedByHosting)(header))
@@ -214,33 +224,67 @@ async function ɵcodegenPublicDirectory(sourceDir, destDir) {
214
224
  }));
215
225
  const routesToCopy = Object.assign(Object.assign({}, prerenderManifest.routes), pagesManifestLikePrerender);
216
226
  await Promise.all(Object.entries(routesToCopy).map(async ([path, route]) => {
217
- var _a;
218
- if (route.initialRevalidateSeconds ||
219
- pathsUsingsFeaturesNotSupportedByHosting.some((it) => path.match(it))) {
227
+ var _a, _b;
228
+ if (route.initialRevalidateSeconds) {
229
+ if (process.env.DEBUG)
230
+ console.log(`skipping ${path} due to revalidate`);
231
+ return;
232
+ }
233
+ if (pathsUsingsFeaturesNotSupportedByHosting.some((it) => path.match(it))) {
234
+ if (process.env.DEBUG)
235
+ console.log(`skipping ${path} due to it matching an unsupported rewrite/redirect/header or middlware`);
220
236
  return;
221
237
  }
222
238
  const appPathRoute = route.srcRoute && ((_a = appPathRoutesEntries.find(([, it]) => it === route.srcRoute)) === null || _a === void 0 ? void 0 : _a[0]);
223
239
  const contentDist = (0, path_1.join)(sourceDir, distDir, "server", appPathRoute ? "app" : "pages");
224
- const parts = path.split("/").filter((it) => !!it);
225
- const partsOrIndex = parts.length > 0 ? parts : ["index"];
226
- let sourcePath = (0, path_1.join)(contentDist, ...partsOrIndex);
227
- let destPath = (0, path_1.join)(destDir, ...partsOrIndex);
240
+ const sourceParts = path.split("/").filter((it) => !!it);
241
+ const locale = (i18n === null || i18n === void 0 ? void 0 : i18n.locales.includes(sourceParts[0])) ? sourceParts[0] : undefined;
242
+ const includeOnThisDomain = !locale ||
243
+ !matchingI18nDomain ||
244
+ matchingI18nDomain.defaultLocale === locale ||
245
+ !matchingI18nDomain.locales ||
246
+ matchingI18nDomain.locales.includes(locale);
247
+ if (!includeOnThisDomain) {
248
+ if (process.env.DEBUG)
249
+ console.log(`skipping ${path} since it is for a locale not deployed on this domain`);
250
+ return;
251
+ }
252
+ const sourcePartsOrIndex = sourceParts.length > 0 ? sourceParts : ["index"];
253
+ const destParts = sourceParts.slice(locale ? 1 : 0);
254
+ const destPartsOrIndex = destParts.length > 0 ? destParts : ["index"];
255
+ const isDefaultLocale = !locale || ((_b = (matchingI18nDomain || i18n)) === null || _b === void 0 ? void 0 : _b.defaultLocale) === locale;
256
+ let sourcePath = (0, path_1.join)(contentDist, ...sourcePartsOrIndex);
257
+ let localizedDestPath = !singleLocaleDomain &&
258
+ locale &&
259
+ (0, path_1.join)(destDir, constants_1.I18N_ROOT, locale, basePath, ...destPartsOrIndex);
260
+ let defaultDestPath = isDefaultLocale && (0, path_1.join)(destDir, basePath, ...destPartsOrIndex);
228
261
  if (!(0, fsutils_1.fileExistsSync)(sourcePath) && (0, fsutils_1.fileExistsSync)(`${sourcePath}.html`)) {
229
262
  sourcePath += ".html";
230
- destPath += ".html";
263
+ if (localizedDestPath)
264
+ localizedDestPath += ".html";
265
+ if (defaultDestPath)
266
+ defaultDestPath += ".html";
231
267
  }
232
- else if (appPathRoute && (0, path_1.basename)(appPathRoute) === "route" && (0, fsutils_1.dirExistsSync)(sourcePath)) {
268
+ else if (appPathRoute &&
269
+ (0, path_1.basename)(appPathRoute) === "route" &&
270
+ (0, fsutils_1.fileExistsSync)(`${sourcePath}.body`)) {
233
271
  sourcePath += ".body";
234
272
  }
235
- if (!(0, fs_extra_1.pathExistsSync)(sourcePath)) {
273
+ else if (!(0, fs_extra_1.pathExistsSync)(sourcePath)) {
236
274
  console.error(`Cannot find ${path} in your compiled Next.js application.`);
237
275
  return;
238
276
  }
239
- await (0, promises_1.mkdir)((0, path_1.dirname)(destPath), { recursive: true });
240
- await (0, promises_1.copyFile)(sourcePath, destPath);
277
+ if (localizedDestPath) {
278
+ await (0, promises_1.mkdir)((0, path_1.dirname)(localizedDestPath), { recursive: true });
279
+ await (0, promises_1.copyFile)(sourcePath, localizedDestPath);
280
+ }
281
+ if (defaultDestPath) {
282
+ await (0, promises_1.mkdir)((0, path_1.dirname)(defaultDestPath), { recursive: true });
283
+ await (0, promises_1.copyFile)(sourcePath, defaultDestPath);
284
+ }
241
285
  if (route.dataRoute && !appPathRoute) {
242
- const dataSourcePath = `${(0, path_1.join)(...partsOrIndex)}.json`;
243
- const dataDestPath = (0, path_1.join)(destDir, route.dataRoute);
286
+ const dataSourcePath = `${(0, path_1.join)(...sourcePartsOrIndex)}.json`;
287
+ const dataDestPath = (0, path_1.join)(destDir, basePath, route.dataRoute);
244
288
  await (0, promises_1.mkdir)((0, path_1.dirname)(dataDestPath), { recursive: true });
245
289
  await (0, promises_1.copyFile)((0, path_1.join)(contentDist, dataSourcePath), dataDestPath);
246
290
  }
@@ -249,7 +293,7 @@ async function ɵcodegenPublicDirectory(sourceDir, destDir) {
249
293
  exports.ɵcodegenPublicDirectory = ɵcodegenPublicDirectory;
250
294
  const BUNDLE_NEXT_CONFIG_TIMEOUT = 10000;
251
295
  async function ɵcodegenFunctionsDirectory(sourceDir, destDir) {
252
- const { distDir } = await getConfig(sourceDir);
296
+ const { distDir, basePath } = await getConfig(sourceDir);
253
297
  const packageJson = await (0, utils_1.readJSON)((0, path_1.join)(sourceDir, "package.json"));
254
298
  if ((0, fs_1.existsSync)((0, path_1.join)(sourceDir, "next.config.js"))) {
255
299
  try {
@@ -297,11 +341,11 @@ async function ɵcodegenFunctionsDirectory(sourceDir, destDir) {
297
341
  }
298
342
  if (!(await (0, utils_2.hasUnoptimizedImage)(sourceDir, distDir)) &&
299
343
  ((0, utils_2.usesAppDirRouter)(sourceDir) || (await (0, utils_2.usesNextImage)(sourceDir, distDir)))) {
300
- packageJson.dependencies["sharp"] = "latest";
344
+ packageJson.dependencies["sharp"] = constants_1.SHARP_VERSION;
301
345
  }
302
346
  await (0, fs_extra_1.mkdirp)((0, path_1.join)(destDir, distDir));
303
347
  await (0, fs_extra_1.copy)((0, path_1.join)(sourceDir, distDir), (0, path_1.join)(destDir, distDir));
304
- return { packageJson, frameworksEntry: "next.js" };
348
+ return { packageJson, frameworksEntry: "next.js", basePath };
305
349
  }
306
350
  exports.ɵcodegenFunctionsDirectory = ɵcodegenFunctionsDirectory;
307
351
  async function getDevModeHandle(dir, hostingEmulatorInfo) {
@@ -329,6 +373,7 @@ async function getDevModeHandle(dir, hostingEmulatorInfo) {
329
373
  exports.getDevModeHandle = getDevModeHandle;
330
374
  async function getConfig(dir) {
331
375
  var _a;
376
+ var _b;
332
377
  let config = {};
333
378
  if ((0, fs_1.existsSync)((0, path_1.join)(dir, "next.config.js"))) {
334
379
  const version = getNextVersion(dir);
@@ -348,5 +393,6 @@ async function getConfig(dir) {
348
393
  }
349
394
  }
350
395
  }
351
- return Object.assign({ distDir: ".next", trailingSlash: false }, config);
396
+ (0, utils_1.validateLocales)((_b = config.i18n) === null || _b === void 0 ? void 0 : _b.locales);
397
+ return Object.assign({ distDir: ".next", trailingSlash: false, basePath: "/" }, config);
352
398
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getBuildId = exports.getHeadersFromMetaFiles = exports.getNonStaticServerComponents = exports.getNonStaticRoutes = exports.getMiddlewareMatcherRegexes = exports.allDependencyNames = exports.isUsingAppDirectory = exports.isUsingImageOptimization = exports.isUsingMiddleware = exports.hasUnoptimizedImage = exports.usesNextImage = exports.usesAppDirRouter = exports.getNextjsRewritesToUse = exports.isHeaderSupportedByHosting = exports.isRedirectSupportedByHosting = exports.isRewriteSupportedByHosting = exports.cleanEscapedChars = exports.pathHasRegex = void 0;
3
+ exports.getBuildId = exports.getHeadersFromMetaFiles = exports.getNonStaticServerComponents = exports.getNonStaticRoutes = exports.getMiddlewareMatcherRegexes = exports.allDependencyNames = exports.isUsingAppDirectory = exports.isUsingImageOptimization = exports.isUsingMiddleware = exports.hasUnoptimizedImage = exports.usesNextImage = exports.usesAppDirRouter = exports.getNextjsRewritesToUse = exports.isHeaderSupportedByHosting = exports.isRedirectSupportedByHosting = exports.isRewriteSupportedByHosting = exports.cleanI18n = exports.cleanCustomRouteI18n = exports.cleanEscapedChars = exports.I18N_SOURCE = void 0;
4
4
  const fs_1 = require("fs");
5
5
  const fs_extra_1 = require("fs-extra");
6
6
  const path_1 = require("path");
@@ -8,32 +8,49 @@ const utils_1 = require("../utils");
8
8
  const constants_1 = require("./constants");
9
9
  const fsutils_1 = require("../../fsutils");
10
10
  const promises_1 = require("fs/promises");
11
- function pathHasRegex(path) {
12
- return /(?<!\\)\(/.test(path);
13
- }
14
- exports.pathHasRegex = pathHasRegex;
11
+ exports.I18N_SOURCE = /\/:nextInternalLocale(\([^\)]+\))?/;
15
12
  function cleanEscapedChars(path) {
16
13
  return path.replace(/\\([(){}:+?*])/g, (a, b) => b);
17
14
  }
18
15
  exports.cleanEscapedChars = cleanEscapedChars;
16
+ function cleanCustomRouteI18n(path) {
17
+ return path.replace(exports.I18N_SOURCE, "");
18
+ }
19
+ exports.cleanCustomRouteI18n = cleanCustomRouteI18n;
20
+ function cleanI18n(it) {
21
+ const [, localesRegex] = it.source.match(exports.I18N_SOURCE) || [undefined, undefined];
22
+ const source = localesRegex ? cleanCustomRouteI18n(it.source) : it.source;
23
+ const destination = "destination" in it && localesRegex ? cleanCustomRouteI18n(it.destination) : it.destination;
24
+ const regex = "regex" in it && localesRegex ? it.regex.replace(`(?:/${localesRegex})`, "") : it.regex;
25
+ return Object.assign(Object.assign({}, it), { source,
26
+ destination,
27
+ regex });
28
+ }
29
+ exports.cleanI18n = cleanI18n;
19
30
  function isRewriteSupportedByHosting(rewrite) {
20
- return !("has" in rewrite || pathHasRegex(rewrite.source) || (0, utils_1.isUrl)(rewrite.destination));
31
+ return !("has" in rewrite ||
32
+ "missing" in rewrite ||
33
+ (0, utils_1.isUrl)(rewrite.destination) ||
34
+ rewrite.destination.includes("?"));
21
35
  }
22
36
  exports.isRewriteSupportedByHosting = isRewriteSupportedByHosting;
23
37
  function isRedirectSupportedByHosting(redirect) {
24
- return !("has" in redirect || pathHasRegex(redirect.source) || "internal" in redirect);
38
+ return !("has" in redirect ||
39
+ "missing" in redirect ||
40
+ "internal" in redirect ||
41
+ redirect.destination.includes("?"));
25
42
  }
26
43
  exports.isRedirectSupportedByHosting = isRedirectSupportedByHosting;
27
44
  function isHeaderSupportedByHosting(header) {
28
- return !("has" in header || pathHasRegex(header.source));
45
+ return !("has" in header || "missing" in header);
29
46
  }
30
47
  exports.isHeaderSupportedByHosting = isHeaderSupportedByHosting;
31
48
  function getNextjsRewritesToUse(nextJsRewrites) {
32
49
  if (Array.isArray(nextJsRewrites)) {
33
- return nextJsRewrites;
50
+ return nextJsRewrites.map(cleanI18n);
34
51
  }
35
52
  if (nextJsRewrites === null || nextJsRewrites === void 0 ? void 0 : nextJsRewrites.beforeFiles) {
36
- return nextJsRewrites.beforeFiles;
53
+ return nextJsRewrites.beforeFiles.map(cleanI18n);
37
54
  }
38
55
  return [];
39
56
  }
@@ -129,7 +146,7 @@ function getNonStaticServerComponents(appPathsManifest, appPathRoutesManifest, p
129
146
  return nonStaticServerComponents;
130
147
  }
131
148
  exports.getNonStaticServerComponents = getNonStaticServerComponents;
132
- async function getHeadersFromMetaFiles(sourceDir, distDir, appPathRoutesManifest) {
149
+ async function getHeadersFromMetaFiles(sourceDir, distDir, basePath, appPathRoutesManifest) {
133
150
  const headers = [];
134
151
  await Promise.all(Object.entries(appPathRoutesManifest).map(async ([key, source]) => {
135
152
  if ((0, path_1.basename)(key) !== "route")
@@ -142,7 +159,7 @@ async function getHeadersFromMetaFiles(sourceDir, distDir, appPathRoutesManifest
142
159
  const meta = await (0, utils_1.readJSON)(metadataPath);
143
160
  if (meta.headers)
144
161
  headers.push({
145
- source,
162
+ source: path_1.posix.join(basePath, source),
146
163
  headers: Object.entries(meta.headers).map(([key, value]) => ({ key, value })),
147
164
  });
148
165
  }
@@ -1,11 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.frameworksCallToAction = exports.conjoinOptions = exports.relativeRequire = exports.findDependency = exports.getNodeModuleBin = exports.getNpmRoot = exports.simpleProxy = exports.warnIfCustomBuildScript = exports.readJSON = exports.isUrl = void 0;
3
+ exports.validateLocales = exports.frameworksCallToAction = exports.conjoinOptions = exports.relativeRequire = exports.findDependency = exports.getNodeModuleBin = exports.getNpmRoot = exports.simpleProxy = exports.warnIfCustomBuildScript = exports.readJSON = exports.isUrl = void 0;
4
4
  const fs_extra_1 = require("fs-extra");
5
5
  const path_1 = require("path");
6
6
  const promises_1 = require("fs/promises");
7
7
  const http_1 = require("http");
8
- const child_process_1 = require("child_process");
8
+ const cross_spawn_1 = require("cross-spawn");
9
9
  const clc = require("colorette");
10
10
  const logger_1 = require("../logger");
11
11
  const error_1 = require("../error");
@@ -87,7 +87,7 @@ function scanDependencyTree(searchingFor, dependencies = {}) {
87
87
  }
88
88
  function getNpmRoot(cwd) {
89
89
  var _a;
90
- return (_a = (0, child_process_1.spawnSync)("npm", ["root"], { cwd, timeout: NPM_ROOT_TIMEOUT_MILLIES })
90
+ return (_a = (0, cross_spawn_1.sync)("npm", ["root"], { cwd, timeout: NPM_ROOT_TIMEOUT_MILLIES })
91
91
  .stdout) === null || _a === void 0 ? void 0 : _a.toString().trim();
92
92
  }
93
93
  exports.getNpmRoot = getNpmRoot;
@@ -115,7 +115,7 @@ function findDependency(name, options = {}) {
115
115
  return;
116
116
  const env = Object.assign({}, process.env);
117
117
  delete env.NODE_ENV;
118
- const result = (0, child_process_1.spawnSync)("npm", [
118
+ const result = (0, cross_spawn_1.sync)("npm", [
119
119
  "list",
120
120
  name,
121
121
  "--json=true",
@@ -167,3 +167,10 @@ ${prefix}${clc.bold("Submit a feature request:")} ${constants_1.FEATURE_REQUEST_
167
167
  ${prefix}We'd love to learn from you. Express your interest in helping us shape the future of Firebase Hosting: ${constants_1.MAILING_LIST_URL}`;
168
168
  }
169
169
  exports.frameworksCallToAction = frameworksCallToAction;
170
+ function validateLocales(locales = []) {
171
+ const invalidLocales = locales.filter((locale) => !constants_1.VALID_LOCALE_FORMATS.some((format) => locale.match(format)));
172
+ if (invalidLocales.length) {
173
+ throw new error_1.FirebaseError(`Invalid i18n locales (${invalidLocales.join(", ")}) for Firebase. See our docs for more information https://firebase.google.com/docs/hosting/i18n-rewrites#country-and-language-codes`);
174
+ }
175
+ }
176
+ exports.validateLocales = validateLocales;
@@ -9,7 +9,7 @@ function virtualEnvCmd(cwd, venvDir) {
9
9
  const activateScriptPath = process.platform === "win32" ? ["Scripts", "activate.bat"] : ["bin", "activate"];
10
10
  const venvActivate = path.join(cwd, venvDir, ...activateScriptPath);
11
11
  return {
12
- command: process.platform === "win32" ? venvActivate : "source",
12
+ command: process.platform === "win32" ? venvActivate : ".",
13
13
  args: [process.platform === "win32" ? "" : venvActivate],
14
14
  };
15
15
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.cleanAuthState = exports.getCleanDomains = exports.removeAuthDomain = exports.addAuthDomains = exports.deleteSite = exports.updateSite = exports.createSite = exports.getSite = exports.listSites = exports.createRelease = exports.cloneVersion = exports.listVersions = exports.updateVersion = exports.createVersion = exports.deleteChannel = exports.updateChannelTtl = exports.createChannel = exports.listChannels = exports.getChannel = exports.normalizeName = void 0;
3
+ exports.getAllSiteDomains = exports.getSiteDomains = exports.cleanAuthState = exports.getCleanDomains = exports.removeAuthDomain = exports.addAuthDomains = exports.deleteSite = exports.updateSite = exports.createSite = exports.getSite = exports.listSites = exports.createRelease = exports.cloneVersion = exports.listVersions = exports.updateVersion = exports.createVersion = exports.deleteChannel = exports.updateChannelTtl = exports.createChannel = exports.listChannels = exports.getChannel = exports.normalizeName = void 0;
4
4
  const error_1 = require("../error");
5
5
  const api_1 = require("../api");
6
6
  const apiv2_1 = require("../apiv2");
@@ -246,3 +246,34 @@ async function cleanAuthState(project, sites) {
246
246
  return siteDomainMap;
247
247
  }
248
248
  exports.cleanAuthState = cleanAuthState;
249
+ async function getSiteDomains(project, site) {
250
+ var _a;
251
+ try {
252
+ const res = await apiClient.get(`/projects/${project}/sites/${site}/domains`);
253
+ return (_a = res.body.domains) !== null && _a !== void 0 ? _a : [];
254
+ }
255
+ catch (e) {
256
+ if (e instanceof error_1.FirebaseError && e.status === 404) {
257
+ throw new error_1.FirebaseError(`could not find site "${site}" for project "${project}"`, {
258
+ original: e,
259
+ });
260
+ }
261
+ throw e;
262
+ }
263
+ }
264
+ exports.getSiteDomains = getSiteDomains;
265
+ async function getAllSiteDomains(projectId, siteId) {
266
+ const [hostingDomains, defaultDomain] = await Promise.all([
267
+ getSiteDomains(projectId, siteId),
268
+ getSite(projectId, siteId),
269
+ ]);
270
+ const defaultDomainWithoutHttp = defaultDomain.defaultUrl.replace(/^https?:\/\//, "");
271
+ const allSiteDomains = new Set([
272
+ ...hostingDomains.map(({ domainName }) => domainName),
273
+ defaultDomainWithoutHttp,
274
+ `${siteId}.web.app`,
275
+ `${siteId}.firebaseapp.com`,
276
+ ]);
277
+ return Array.from(allSiteDomains);
278
+ }
279
+ exports.getAllSiteDomains = getAllSiteDomains;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firebase-tools",
3
- "version": "11.30.0",
3
+ "version": "12.0.0",
4
4
  "description": "Command-Line Interface for Firebase",
5
5
  "main": "./lib/index.js",
6
6
  "bin": {
@@ -29,7 +29,7 @@
29
29
  ],
30
30
  "preferGlobal": true,
31
31
  "engines": {
32
- "node": "^14.18.0 || >=16.4.0"
32
+ "node": ">=16.13.0 || >=18.0.0"
33
33
  },
34
34
  "author": "Firebase (https://firebase.google.com/)",
35
35
  "license": "MIT",