@vercel/microfrontends 2.2.1 → 2.2.2

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 (59) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/cli/index.cjs +0 -1
  3. package/dist/bin/cli.cjs +483 -394
  4. package/dist/config.cjs +191 -169
  5. package/dist/config.cjs.map +1 -1
  6. package/dist/config.d.ts +3 -2
  7. package/dist/config.js +192 -170
  8. package/dist/config.js.map +1 -1
  9. package/dist/experimental/sveltekit.cjs +583 -511
  10. package/dist/experimental/sveltekit.cjs.map +1 -1
  11. package/dist/experimental/sveltekit.js +589 -517
  12. package/dist/experimental/sveltekit.js.map +1 -1
  13. package/dist/experimental/vite.cjs +605 -533
  14. package/dist/experimental/vite.cjs.map +1 -1
  15. package/dist/experimental/vite.js +614 -542
  16. package/dist/experimental/vite.js.map +1 -1
  17. package/dist/microfrontends/server.cjs +601 -529
  18. package/dist/microfrontends/server.cjs.map +1 -1
  19. package/dist/microfrontends/server.d.ts +2 -2
  20. package/dist/microfrontends/server.js +607 -535
  21. package/dist/microfrontends/server.js.map +1 -1
  22. package/dist/microfrontends/utils.cjs +101 -50
  23. package/dist/microfrontends/utils.cjs.map +1 -1
  24. package/dist/microfrontends/utils.d.ts +4 -4
  25. package/dist/microfrontends/utils.js +102 -51
  26. package/dist/microfrontends/utils.js.map +1 -1
  27. package/dist/next/client.cjs +1 -1
  28. package/dist/next/client.cjs.map +1 -1
  29. package/dist/next/client.d.ts +8 -8
  30. package/dist/next/client.js +1 -1
  31. package/dist/next/client.js.map +1 -1
  32. package/dist/next/config.cjs +723 -647
  33. package/dist/next/config.cjs.map +1 -1
  34. package/dist/next/config.js +720 -644
  35. package/dist/next/config.js.map +1 -1
  36. package/dist/next/middleware.cjs +244 -222
  37. package/dist/next/middleware.cjs.map +1 -1
  38. package/dist/next/middleware.js +245 -223
  39. package/dist/next/middleware.js.map +1 -1
  40. package/dist/next/testing.cjs +192 -170
  41. package/dist/next/testing.cjs.map +1 -1
  42. package/dist/next/testing.d.ts +1 -1
  43. package/dist/next/testing.js +193 -171
  44. package/dist/next/testing.js.map +1 -1
  45. package/dist/overrides.cjs +5 -5
  46. package/dist/overrides.cjs.map +1 -1
  47. package/dist/overrides.d.ts +9 -9
  48. package/dist/overrides.js +5 -5
  49. package/dist/overrides.js.map +1 -1
  50. package/dist/utils/mfe-port.cjs +620 -533
  51. package/dist/utils/mfe-port.cjs.map +1 -1
  52. package/dist/utils/mfe-port.d.ts +9 -1
  53. package/dist/utils/mfe-port.js +632 -546
  54. package/dist/utils/mfe-port.js.map +1 -1
  55. package/dist/validation.cjs +8 -24
  56. package/dist/validation.cjs.map +1 -1
  57. package/dist/validation.js +8 -24
  58. package/dist/validation.js.map +1 -1
  59. package/package.json +4 -6
@@ -43,40 +43,7 @@ function displayLocalProxyInfo(port) {
43
43
 
44
44
  // src/config/microfrontends/server/index.ts
45
45
  import fs6 from "node:fs";
46
- import { dirname as dirname2, join as join2, resolve } from "node:path";
47
-
48
- // src/config/overrides/constants.ts
49
- var OVERRIDES_COOKIE_PREFIX = "vercel-micro-frontends-override";
50
- var OVERRIDES_ENV_COOKIE_PREFIX = `${OVERRIDES_COOKIE_PREFIX}:env:`;
51
-
52
- // src/config/overrides/is-override-cookie.ts
53
- function isOverrideCookie(cookie) {
54
- return Boolean(cookie.name?.startsWith(OVERRIDES_COOKIE_PREFIX));
55
- }
56
-
57
- // src/config/overrides/get-override-from-cookie.ts
58
- function getOverrideFromCookie(cookie) {
59
- if (!isOverrideCookie(cookie) || !cookie.value)
60
- return;
61
- return {
62
- application: cookie.name.replace(OVERRIDES_ENV_COOKIE_PREFIX, ""),
63
- host: cookie.value
64
- };
65
- }
66
-
67
- // src/config/overrides/parse-overrides.ts
68
- function parseOverrides(cookies) {
69
- const overridesConfig = { applications: {} };
70
- cookies.forEach((cookie) => {
71
- const override = getOverrideFromCookie(cookie);
72
- if (!override)
73
- return;
74
- overridesConfig.applications[override.application] = {
75
- environment: { host: override.host }
76
- };
77
- });
78
- return overridesConfig;
79
- }
46
+ import { dirname as dirname2, join as join3, resolve } from "node:path";
80
47
 
81
48
  // src/config/errors.ts
82
49
  var MicrofrontendError = class extends Error {
@@ -170,274 +137,47 @@ var MicrofrontendError = class extends Error {
170
137
  }
171
138
  };
172
139
 
173
- // src/config/microfrontends-config/utils/get-config-from-env.ts
174
- function getConfigStringFromEnv() {
175
- const config = process.env.MFE_CONFIG;
176
- if (!config) {
177
- throw new MicrofrontendError(`Missing "MFE_CONFIG" in environment.`, {
178
- type: "config",
179
- subtype: "not_found_in_env"
180
- });
181
- }
182
- return config;
183
- }
184
-
185
- // src/config/schema/utils/is-default-app.ts
186
- function isDefaultApp(a) {
187
- return !("routing" in a);
188
- }
189
-
190
- // src/config/microfrontends/utils/find-repository-root.ts
191
- import fs from "node:fs";
192
- import path from "node:path";
193
- var GIT_DIRECTORY = ".git";
194
- function hasGitDirectory(dir) {
195
- const gitPath = path.join(dir, GIT_DIRECTORY);
196
- return fs.existsSync(gitPath) && fs.statSync(gitPath).isDirectory();
197
- }
198
- function hasPnpmWorkspaces(dir) {
199
- return fs.existsSync(path.join(dir, "pnpm-workspace.yaml"));
200
- }
201
- function hasPackageJson(dir) {
202
- return fs.existsSync(path.join(dir, "package.json"));
203
- }
204
- function findRepositoryRoot(startDir) {
205
- if (process.env.NX_WORKSPACE_ROOT) {
206
- return process.env.NX_WORKSPACE_ROOT;
207
- }
208
- let currentDir = startDir || process.cwd();
209
- let lastPackageJsonDir = null;
210
- while (currentDir !== path.parse(currentDir).root) {
211
- if (hasGitDirectory(currentDir) || hasPnpmWorkspaces(currentDir)) {
212
- return currentDir;
213
- }
214
- if (hasPackageJson(currentDir)) {
215
- lastPackageJsonDir = currentDir;
216
- }
217
- currentDir = path.dirname(currentDir);
218
- }
219
- if (lastPackageJsonDir) {
220
- return lastPackageJsonDir;
221
- }
222
- throw new Error(
223
- `Could not find the root of the repository for ${startDir}. Please ensure that the directory is part of a Git repository. If you suspect that this should work, please file an issue to the Vercel team.`
224
- );
225
- }
226
-
227
- // src/config/microfrontends/utils/infer-microfrontends-location.ts
228
- import { dirname } from "node:path";
229
- import { readFileSync } from "node:fs";
140
+ // src/config/microfrontends-config/isomorphic/index.ts
230
141
  import { parse } from "jsonc-parser";
231
- import fg from "fast-glob";
232
-
233
- // src/config/microfrontends/utils/get-config-file-name.ts
234
- var DEFAULT_CONFIGURATION_FILENAMES = [
235
- "microfrontends.json",
236
- "microfrontends.jsonc"
237
- ];
238
- function getPossibleConfigurationFilenames({
239
- customConfigFilename
240
- }) {
241
- if (customConfigFilename) {
242
- if (!customConfigFilename.endsWith(".json") && !customConfigFilename.endsWith(".jsonc")) {
243
- throw new Error(
244
- `Found VC_MICROFRONTENDS_CONFIG_FILE_NAME but the name is invalid. Received: ${customConfigFilename}. The file name must end with '.json' or '.jsonc'. It's also possible for the env var to include the path, eg microfrontends-dev.json or /path/to/microfrontends-dev.json.`
245
- );
246
- }
247
- return Array.from(
248
- /* @__PURE__ */ new Set([customConfigFilename, ...DEFAULT_CONFIGURATION_FILENAMES])
249
- );
250
- }
251
- return DEFAULT_CONFIGURATION_FILENAMES;
252
- }
253
142
 
254
- // src/config/microfrontends/utils/infer-microfrontends-location.ts
255
- var configCache = {};
256
- function findPackageWithMicrofrontendsConfig({
257
- repositoryRoot,
258
- applicationContext,
259
- customConfigFilename
260
- }) {
261
- const applicationName = applicationContext.name;
262
- logger.debug(
263
- "[MFE Config] Searching repository for configs containing application:",
264
- applicationName
265
- );
266
- try {
267
- const microfrontendsJsonPaths = fg.globSync(
268
- `**/{${getPossibleConfigurationFilenames({ customConfigFilename }).join(",")}}`,
269
- {
270
- cwd: repositoryRoot,
271
- absolute: true,
272
- onlyFiles: true,
273
- followSymbolicLinks: false,
274
- ignore: ["**/node_modules/**", "**/.git/**"]
275
- }
276
- );
277
- logger.debug(
278
- "[MFE Config] Found",
279
- microfrontendsJsonPaths.length,
280
- "config file(s) in repository"
281
- );
282
- const matchingPaths = [];
283
- for (const microfrontendsJsonPath of microfrontendsJsonPaths) {
284
- try {
285
- const microfrontendsJsonContent = readFileSync(
286
- microfrontendsJsonPath,
287
- "utf-8"
288
- );
289
- const microfrontendsJson = parse(microfrontendsJsonContent);
290
- if (microfrontendsJson.applications[applicationName]) {
291
- logger.debug(
292
- "[MFE Config] Found application in config:",
293
- microfrontendsJsonPath
294
- );
295
- matchingPaths.push(microfrontendsJsonPath);
296
- } else {
297
- for (const [_, app] of Object.entries(
298
- microfrontendsJson.applications
299
- )) {
300
- if (app.packageName === applicationName) {
301
- logger.debug(
302
- "[MFE Config] Found application via packageName in config:",
303
- microfrontendsJsonPath
304
- );
305
- matchingPaths.push(microfrontendsJsonPath);
306
- }
307
- }
308
- }
309
- } catch (error2) {
310
- }
311
- }
312
- logger.debug(
313
- "[MFE Config] Total matching config files:",
314
- matchingPaths.length
315
- );
316
- if (matchingPaths.length > 1) {
317
- throw new MicrofrontendError(
318
- `Found multiple \`microfrontends.json\` files in the repository referencing the application "${applicationName}", but only one is allowed.
319
- ${matchingPaths.join("\n \u2022 ")}`,
320
- { type: "config", subtype: "inference_failed" }
321
- );
322
- }
323
- if (matchingPaths.length === 0) {
324
- let additionalErrorMessage = "";
325
- if (microfrontendsJsonPaths.length > 0) {
326
- if (!applicationContext.projectName) {
327
- additionalErrorMessage = `
328
-
329
- If the name in package.json (${applicationContext.packageJsonName}) differs from your Vercel Project name, set the \`packageName\` field for the application in \`microfrontends.json\` to ensure that the configuration can be found locally.`;
330
- } else {
331
- additionalErrorMessage = `
332
-
333
- Names of applications in \`microfrontends.json\` must match the Vercel Project name (${applicationContext.projectName}).`;
334
- }
335
- }
336
- throw new MicrofrontendError(
337
- `Could not find a \`microfrontends.json\` file in the repository that contains the "${applicationName}" application.${additionalErrorMessage}
338
-
339
- If your Vercel Microfrontends configuration is not in this repository, you can use the Vercel CLI to pull the Vercel Microfrontends configuration using the "vercel microfrontends pull" command, or you can specify the path manually using the VC_MICROFRONTENDS_CONFIG environment variable.
340
-
341
- If your Vercel Microfrontends configuration has a custom name, ensure the VC_MICROFRONTENDS_CONFIG_FILE_NAME environment variable is set, you can pull the vercel project environment variables using the "vercel env pull" command.
143
+ // src/config/overrides/constants.ts
144
+ var OVERRIDES_COOKIE_PREFIX = "vercel-micro-frontends-override";
145
+ var OVERRIDES_ENV_COOKIE_PREFIX = `${OVERRIDES_COOKIE_PREFIX}:env:`;
342
146
 
343
- If you suspect this is thrown in error, please reach out to the Vercel team.`,
344
- { type: "config", subtype: "inference_failed" }
345
- );
346
- }
347
- const [packageJsonPath] = matchingPaths;
348
- return dirname(packageJsonPath);
349
- } catch (error2) {
350
- if (error2 instanceof MicrofrontendError) {
351
- throw error2;
352
- }
353
- return null;
354
- }
355
- }
356
- function inferMicrofrontendsLocation(opts) {
357
- const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}${opts.customConfigFilename ? `-${opts.customConfigFilename}` : ""}`;
358
- if (configCache[cacheKey]) {
359
- return configCache[cacheKey];
360
- }
361
- const result = findPackageWithMicrofrontendsConfig(opts);
362
- if (!result) {
363
- throw new MicrofrontendError(
364
- `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationContext.name}" starting in directory "${opts.repositoryRoot}".`,
365
- { type: "config", subtype: "inference_failed" }
366
- );
367
- }
368
- configCache[cacheKey] = result;
369
- return result;
147
+ // src/config/overrides/is-override-cookie.ts
148
+ function isOverrideCookie(cookie) {
149
+ return Boolean(cookie.name?.startsWith(OVERRIDES_COOKIE_PREFIX));
370
150
  }
371
151
 
372
- // src/config/microfrontends/utils/is-monorepo.ts
373
- import fs2 from "node:fs";
374
- import path2 from "node:path";
375
- function isMonorepo({
376
- repositoryRoot
377
- }) {
378
- try {
379
- if (fs2.existsSync(path2.join(repositoryRoot, "pnpm-workspace.yaml"))) {
380
- return true;
381
- }
382
- if (fs2.existsSync(path2.join(repositoryRoot, "vlt-workspaces.json"))) {
383
- return true;
384
- }
385
- if (process.env.NX_WORKSPACE_ROOT === path2.resolve(repositoryRoot)) {
386
- return true;
387
- }
388
- const packageJsonPath = path2.join(repositoryRoot, "package.json");
389
- if (!fs2.existsSync(packageJsonPath)) {
390
- return false;
391
- }
392
- const packageJson = JSON.parse(
393
- fs2.readFileSync(packageJsonPath, "utf-8")
394
- );
395
- return packageJson.workspaces !== void 0;
396
- } catch (error2) {
397
- logger.error("Error determining if repository is a monorepo", error2);
398
- return false;
399
- }
152
+ // src/config/overrides/get-override-from-cookie.ts
153
+ function getOverrideFromCookie(cookie) {
154
+ if (!isOverrideCookie(cookie) || !cookie.value)
155
+ return;
156
+ return {
157
+ application: cookie.name.replace(OVERRIDES_ENV_COOKIE_PREFIX, ""),
158
+ host: cookie.value
159
+ };
400
160
  }
401
161
 
402
- // src/config/microfrontends/utils/find-package-root.ts
403
- import fs3 from "node:fs";
404
- import path3 from "node:path";
405
- var PACKAGE_JSON = "package.json";
406
- function findPackageRoot(startDir) {
407
- let currentDir = startDir || process.cwd();
408
- while (currentDir !== path3.parse(currentDir).root) {
409
- const pkgJsonPath = path3.join(currentDir, PACKAGE_JSON);
410
- if (fs3.existsSync(pkgJsonPath)) {
411
- return currentDir;
412
- }
413
- currentDir = path3.dirname(currentDir);
414
- }
415
- throw new Error(
416
- `The root of the package that contains the \`package.json\` file for the \`${startDir}\` directory could not be found.`
417
- );
162
+ // src/config/overrides/parse-overrides.ts
163
+ function parseOverrides(cookies) {
164
+ const overridesConfig = { applications: {} };
165
+ cookies.forEach((cookie) => {
166
+ const override = getOverrideFromCookie(cookie);
167
+ if (!override)
168
+ return;
169
+ overridesConfig.applications[override.application] = {
170
+ environment: { host: override.host }
171
+ };
172
+ });
173
+ return overridesConfig;
418
174
  }
419
175
 
420
- // src/config/microfrontends/utils/find-config.ts
421
- import fs4 from "node:fs";
422
- import { join } from "node:path";
423
- function findConfig({
424
- dir,
425
- customConfigFilename
426
- }) {
427
- for (const filename of getPossibleConfigurationFilenames({
428
- customConfigFilename
429
- })) {
430
- const maybeConfig = join(dir, filename);
431
- if (fs4.existsSync(maybeConfig)) {
432
- return maybeConfig;
433
- }
434
- }
435
- return null;
176
+ // src/config/schema/utils/is-default-app.ts
177
+ function isDefaultApp(a) {
178
+ return !("routing" in a);
436
179
  }
437
180
 
438
- // src/config/microfrontends-config/isomorphic/index.ts
439
- import { parse as parse2 } from "jsonc-parser";
440
-
441
181
  // src/config/microfrontends-config/client/index.ts
442
182
  import { pathToRegexp } from "path-to-regexp";
443
183
  var regexpCache = /* @__PURE__ */ new Map();
@@ -498,46 +238,223 @@ var MicrofrontendConfigClient = class {
498
238
  }
499
239
  return new MicrofrontendConfigClient(JSON.parse(config));
500
240
  }
501
- isEqual(other) {
502
- return this === other || JSON.stringify(this.applications) === JSON.stringify(other.applications);
241
+ isEqual(other) {
242
+ return this === other || JSON.stringify(this.applications) === JSON.stringify(other.applications);
243
+ }
244
+ getApplicationNameForPath(path6) {
245
+ if (!path6.startsWith("/")) {
246
+ throw new Error(`Path must start with a /`);
247
+ }
248
+ if (this.pathCache[path6]) {
249
+ return this.pathCache[path6];
250
+ }
251
+ const pathname = new URL(path6, "https://example.com").pathname;
252
+ for (const [name, application] of Object.entries(this.applications)) {
253
+ if (application.routing) {
254
+ for (const group of application.routing) {
255
+ for (const childPath of group.paths) {
256
+ const regexp = getRegexp(childPath);
257
+ if (regexp.test(pathname)) {
258
+ this.pathCache[path6] = name;
259
+ return name;
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+ const defaultApplication = Object.entries(this.applications).find(
266
+ ([, application]) => application.default
267
+ );
268
+ if (!defaultApplication) {
269
+ return null;
270
+ }
271
+ this.pathCache[path6] = defaultApplication[0];
272
+ return defaultApplication[0];
273
+ }
274
+ serialize() {
275
+ return this.serialized;
276
+ }
277
+ };
278
+
279
+ // src/config/microfrontends-config/utils/get-config-from-env.ts
280
+ function getConfigStringFromEnv() {
281
+ const config = process.env.MFE_CONFIG;
282
+ if (!config) {
283
+ throw new MicrofrontendError(`Missing "MFE_CONFIG" in environment.`, {
284
+ type: "config",
285
+ subtype: "not_found_in_env"
286
+ });
287
+ }
288
+ return config;
289
+ }
290
+
291
+ // src/config/microfrontends-config/isomorphic/constants.ts
292
+ var DEFAULT_LOCAL_PROXY_PORT = 3024;
293
+ var MFE_APP_PORT_ENV = "MFE_APP_PORT";
294
+ var MFE_LOCAL_PROXY_PORT_ENV = "MFE_LOCAL_PROXY_PORT";
295
+
296
+ // src/config/microfrontends-config/isomorphic/utils/generate-port.ts
297
+ function generatePortFromName({
298
+ name,
299
+ minPort = 3e3,
300
+ maxPort = 8e3
301
+ }) {
302
+ if (!name) {
303
+ throw new Error("Name is required to generate a port");
304
+ }
305
+ let hash = 0;
306
+ for (let i = 0; i < name.length; i++) {
307
+ hash = (hash << 5) - hash + name.charCodeAt(i);
308
+ hash |= 0;
309
+ }
310
+ hash = Math.abs(hash);
311
+ const range = maxPort - minPort;
312
+ const port = minPort + hash % range;
313
+ return port;
314
+ }
315
+
316
+ // src/config/microfrontends-config/isomorphic/host.ts
317
+ var Host = class {
318
+ constructor(hostConfig, options) {
319
+ if (typeof hostConfig === "string") {
320
+ ({
321
+ protocol: this.protocol,
322
+ host: this.host,
323
+ port: this.port
324
+ } = Host.parseUrl(hostConfig));
325
+ } else {
326
+ const { protocol = "https", host, port } = hostConfig;
327
+ this.protocol = protocol;
328
+ this.host = host;
329
+ this.port = port;
330
+ }
331
+ this.local = options?.isLocal;
332
+ }
333
+ static parseUrl(url, defaultProtocol = "https") {
334
+ let hostToParse = url;
335
+ if (!/^https?:\/\//.exec(hostToParse)) {
336
+ hostToParse = `${defaultProtocol}://${hostToParse}`;
337
+ }
338
+ const parsed = new URL(hostToParse);
339
+ if (!parsed.hostname) {
340
+ throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
341
+ }
342
+ if (parsed.hash) {
343
+ throw new Error(
344
+ Host.getMicrofrontendsError(url, "cannot have a fragment")
345
+ );
346
+ }
347
+ if (parsed.username || parsed.password) {
348
+ throw new Error(
349
+ Host.getMicrofrontendsError(
350
+ url,
351
+ "cannot have authentication credentials (username and/or password)"
352
+ )
353
+ );
354
+ }
355
+ if (parsed.pathname !== "/") {
356
+ throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
357
+ }
358
+ if (parsed.search) {
359
+ throw new Error(
360
+ Host.getMicrofrontendsError(url, "cannot have query parameters")
361
+ );
362
+ }
363
+ const protocol = parsed.protocol.slice(0, -1);
364
+ return {
365
+ protocol,
366
+ host: parsed.hostname,
367
+ port: parsed.port ? Number.parseInt(parsed.port, 10) : void 0
368
+ };
369
+ }
370
+ static getMicrofrontendsError(url, message) {
371
+ return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
372
+ }
373
+ isLocal() {
374
+ return this.local || this.host === "localhost" || this.host === "127.0.0.1";
503
375
  }
504
- getApplicationNameForPath(path6) {
505
- if (!path6.startsWith("/")) {
506
- throw new Error(`Path must start with a /`);
507
- }
508
- if (this.pathCache[path6]) {
509
- return this.pathCache[path6];
510
- }
511
- const pathname = new URL(path6, "https://example.com").pathname;
512
- for (const [name, application] of Object.entries(this.applications)) {
513
- if (application.routing) {
514
- for (const group of application.routing) {
515
- for (const childPath of group.paths) {
516
- const regexp = getRegexp(childPath);
517
- if (regexp.test(pathname)) {
518
- this.pathCache[path6] = name;
519
- return name;
520
- }
521
- }
522
- }
376
+ toString() {
377
+ const url = this.toUrl();
378
+ return url.toString().replace(/\/$/, "");
379
+ }
380
+ toUrl() {
381
+ const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
382
+ return new URL(url);
383
+ }
384
+ };
385
+ var LocalHost = class extends Host {
386
+ constructor({
387
+ appName,
388
+ local
389
+ }) {
390
+ const portOverride = process.env[MFE_APP_PORT_ENV];
391
+ if (portOverride) {
392
+ const overridePort = Number.parseInt(portOverride, 10);
393
+ if (!Number.isNaN(overridePort) && overridePort > 0 && overridePort < 65536) {
394
+ super({
395
+ protocol: "http",
396
+ host: "localhost",
397
+ port: overridePort
398
+ });
399
+ return;
523
400
  }
524
401
  }
525
- const defaultApplication = Object.entries(this.applications).find(
526
- ([, application]) => application.default
527
- );
528
- if (!defaultApplication) {
529
- return null;
402
+ let protocol;
403
+ let host;
404
+ let port;
405
+ if (typeof local === "number") {
406
+ port = local;
407
+ } else if (typeof local === "string") {
408
+ if (/^\d+$/.test(local)) {
409
+ port = Number.parseInt(local, 10);
410
+ } else {
411
+ const parsed = Host.parseUrl(local, "http");
412
+ protocol = parsed.protocol;
413
+ host = parsed.host;
414
+ port = parsed.port;
415
+ }
416
+ } else if (local) {
417
+ protocol = local.protocol;
418
+ host = local.host;
419
+ port = local.port;
530
420
  }
531
- this.pathCache[path6] = defaultApplication[0];
532
- return defaultApplication[0];
533
- }
534
- serialize() {
535
- return this.serialized;
421
+ super({
422
+ protocol: protocol ?? "http",
423
+ host: host ?? "localhost",
424
+ port: port ?? generatePortFromName({ name: appName })
425
+ });
536
426
  }
537
427
  };
538
428
 
429
+ // src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts
430
+ import md5 from "md5";
431
+ function hashApplicationName(name) {
432
+ if (!name) {
433
+ throw new Error("Application name is required to generate hash");
434
+ }
435
+ return md5(name).substring(0, 6).padStart(6, "0");
436
+ }
437
+
438
+ // src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts
439
+ var PREFIX = "vc-ap";
440
+ function generateAssetPrefixFromName({
441
+ name
442
+ }) {
443
+ if (!name) {
444
+ throw new Error("Name is required to generate an asset prefix");
445
+ }
446
+ return `${PREFIX}-${hashApplicationName(name)}`;
447
+ }
448
+
449
+ // src/config/microfrontends-config/isomorphic/utils/generate-automation-bypass-env-var-name.ts
450
+ function generateAutomationBypassEnvVarName({
451
+ name
452
+ }) {
453
+ return `AUTOMATION_BYPASS_${name.toUpperCase().replace(/[^a-zA-Z0-9]/g, "_")}`;
454
+ }
455
+
539
456
  // src/config/microfrontends-config/isomorphic/validation.ts
540
- import { pathToRegexp as pathToRegexp2, parse as parsePathRegexp } from "path-to-regexp";
457
+ import { parse as parsePathRegexp, pathToRegexp as pathToRegexp2 } from "path-to-regexp";
541
458
  var LIST_FORMATTER = new Intl.ListFormat("en", {
542
459
  style: "long",
543
460
  type: "conjunction"
@@ -696,175 +613,27 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
696
613
  ).filter(([, app]) => isDefaultApp(app));
697
614
  const numApplicationsWithoutRouting = applicationsWithoutRouting.reduce(
698
615
  (acc) => {
699
- return acc + 1;
700
- },
701
- 0
702
- );
703
- if (numApplicationsWithoutRouting === 0) {
704
- throw new MicrofrontendError(
705
- "No default application found. At least one application needs to be the default by omitting routing.",
706
- { type: "config", subtype: "no_default_application" }
707
- );
708
- }
709
- if (numApplicationsWithoutRouting > 1) {
710
- const applicationNamesMissingRouting = applicationsWithoutRouting.map(
711
- ([name]) => name
712
- );
713
- throw new MicrofrontendError(
714
- `All applications except for the default app must contain the "routing" field. Applications that are missing routing: ${LIST_FORMATTER.format(applicationNamesMissingRouting)}.`,
715
- { type: "config", subtype: "multiple_default_applications" }
716
- );
717
- }
718
- };
719
-
720
- // src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts
721
- import md5 from "md5";
722
- function hashApplicationName(name) {
723
- if (!name) {
724
- throw new Error("Application name is required to generate hash");
725
- }
726
- return md5(name).substring(0, 6).padStart(6, "0");
727
- }
728
-
729
- // src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts
730
- var PREFIX = "vc-ap";
731
- function generateAssetPrefixFromName({
732
- name
733
- }) {
734
- if (!name) {
735
- throw new Error("Name is required to generate an asset prefix");
736
- }
737
- return `${PREFIX}-${hashApplicationName(name)}`;
738
- }
739
-
740
- // src/config/microfrontends-config/isomorphic/utils/generate-port.ts
741
- function generatePortFromName({
742
- name,
743
- minPort = 3e3,
744
- maxPort = 8e3
745
- }) {
746
- if (!name) {
747
- throw new Error("Name is required to generate a port");
748
- }
749
- let hash = 0;
750
- for (let i = 0; i < name.length; i++) {
751
- hash = (hash << 5) - hash + name.charCodeAt(i);
752
- hash |= 0;
753
- }
754
- hash = Math.abs(hash);
755
- const range = maxPort - minPort;
756
- const port = minPort + hash % range;
757
- return port;
758
- }
759
-
760
- // src/config/microfrontends-config/isomorphic/host.ts
761
- var Host = class {
762
- constructor(hostConfig, options) {
763
- if (typeof hostConfig === "string") {
764
- ({
765
- protocol: this.protocol,
766
- host: this.host,
767
- port: this.port
768
- } = Host.parseUrl(hostConfig));
769
- } else {
770
- const { protocol = "https", host, port } = hostConfig;
771
- this.protocol = protocol;
772
- this.host = host;
773
- this.port = port;
774
- }
775
- this.local = options?.isLocal;
776
- }
777
- static parseUrl(url, defaultProtocol = "https") {
778
- let hostToParse = url;
779
- if (!/^https?:\/\//.exec(hostToParse)) {
780
- hostToParse = `${defaultProtocol}://${hostToParse}`;
781
- }
782
- const parsed = new URL(hostToParse);
783
- if (!parsed.hostname) {
784
- throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
785
- }
786
- if (parsed.hash) {
787
- throw new Error(
788
- Host.getMicrofrontendsError(url, "cannot have a fragment")
789
- );
790
- }
791
- if (parsed.username || parsed.password) {
792
- throw new Error(
793
- Host.getMicrofrontendsError(
794
- url,
795
- "cannot have authentication credentials (username and/or password)"
796
- )
797
- );
798
- }
799
- if (parsed.pathname !== "/") {
800
- throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
801
- }
802
- if (parsed.search) {
803
- throw new Error(
804
- Host.getMicrofrontendsError(url, "cannot have query parameters")
805
- );
806
- }
807
- const protocol = parsed.protocol.slice(0, -1);
808
- return {
809
- protocol,
810
- host: parsed.hostname,
811
- port: parsed.port ? Number.parseInt(parsed.port) : void 0
812
- };
813
- }
814
- static getMicrofrontendsError(url, message) {
815
- return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
816
- }
817
- isLocal() {
818
- return this.local || this.host === "localhost" || this.host === "127.0.0.1";
819
- }
820
- toString() {
821
- const url = this.toUrl();
822
- return url.toString().replace(/\/$/, "");
823
- }
824
- toUrl() {
825
- const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
826
- return new URL(url);
827
- }
828
- };
829
- var LocalHost = class extends Host {
830
- constructor({
831
- appName,
832
- local
833
- }) {
834
- let protocol;
835
- let host;
836
- let port;
837
- if (typeof local === "number") {
838
- port = local;
839
- } else if (typeof local === "string") {
840
- if (/^\d+$/.test(local)) {
841
- port = Number.parseInt(local);
842
- } else {
843
- const parsed = Host.parseUrl(local, "http");
844
- protocol = parsed.protocol;
845
- host = parsed.host;
846
- port = parsed.port;
847
- }
848
- } else if (local) {
849
- protocol = local.protocol;
850
- host = local.host;
851
- port = local.port;
852
- }
853
- super({
854
- protocol: protocol ?? "http",
855
- host: host ?? "localhost",
856
- port: port ?? generatePortFromName({ name: appName })
857
- });
616
+ return acc + 1;
617
+ },
618
+ 0
619
+ );
620
+ if (numApplicationsWithoutRouting === 0) {
621
+ throw new MicrofrontendError(
622
+ "No default application found. At least one application needs to be the default by omitting routing.",
623
+ { type: "config", subtype: "no_default_application" }
624
+ );
625
+ }
626
+ if (numApplicationsWithoutRouting > 1) {
627
+ const applicationNamesMissingRouting = applicationsWithoutRouting.map(
628
+ ([name]) => name
629
+ );
630
+ throw new MicrofrontendError(
631
+ `All applications except for the default app must contain the "routing" field. Applications that are missing routing: ${LIST_FORMATTER.format(applicationNamesMissingRouting)}.`,
632
+ { type: "config", subtype: "multiple_default_applications" }
633
+ );
858
634
  }
859
635
  };
860
636
 
861
- // src/config/microfrontends-config/isomorphic/utils/generate-automation-bypass-env-var-name.ts
862
- function generateAutomationBypassEnvVarName({
863
- name
864
- }) {
865
- return `AUTOMATION_BYPASS_${name.toUpperCase().replace(/[^a-zA-Z0-9]/g, "_")}`;
866
- }
867
-
868
637
  // src/config/microfrontends-config/isomorphic/application.ts
869
638
  var Application = class {
870
639
  constructor(name, {
@@ -945,9 +714,6 @@ var ChildApplication = class extends Application {
945
714
  }
946
715
  };
947
716
 
948
- // src/config/microfrontends-config/isomorphic/constants.ts
949
- var DEFAULT_LOCAL_PROXY_PORT = 3024;
950
-
951
717
  // src/config/microfrontends-config/isomorphic/index.ts
952
718
  var MicrofrontendConfigIsomorphic = class {
953
719
  constructor({
@@ -991,7 +757,7 @@ var MicrofrontendConfigIsomorphic = class {
991
757
  };
992
758
  }
993
759
  static validate(config) {
994
- const c = typeof config === "string" ? parse2(config) : config;
760
+ const c = typeof config === "string" ? parse(config) : config;
995
761
  validateConfigPaths(c.applications);
996
762
  validateConfigDefaultApplication(c.applications);
997
763
  return c;
@@ -1000,7 +766,7 @@ var MicrofrontendConfigIsomorphic = class {
1000
766
  cookies
1001
767
  }) {
1002
768
  return new MicrofrontendConfigIsomorphic({
1003
- config: parse2(getConfigStringFromEnv()),
769
+ config: parse(getConfigStringFromEnv()),
1004
770
  overrides: parseOverrides(cookies ?? [])
1005
771
  });
1006
772
  }
@@ -1066,9 +832,17 @@ var MicrofrontendConfigIsomorphic = class {
1066
832
  return this.defaultApplication;
1067
833
  }
1068
834
  /**
1069
- * Returns the configured port for the local proxy
835
+ * Returns the configured port for the local proxy.
836
+ * Can be overridden via MFE_LOCAL_PROXY_PORT environment variable.
1070
837
  */
1071
838
  getLocalProxyPort() {
839
+ const portOverride = process.env[MFE_LOCAL_PROXY_PORT_ENV];
840
+ if (portOverride) {
841
+ const port = Number.parseInt(portOverride, 10);
842
+ if (!Number.isNaN(port) && port > 0 && port < 65536) {
843
+ return port;
844
+ }
845
+ }
1072
846
  return this.config.options?.localProxyPort ?? DEFAULT_LOCAL_PROXY_PORT;
1073
847
  }
1074
848
  toClientConfig(options) {
@@ -1091,80 +865,394 @@ var MicrofrontendConfigIsomorphic = class {
1091
865
  {
1092
866
  removeFlaggedPaths: options?.removeFlaggedPaths
1093
867
  }
1094
- );
1095
- }
1096
- /**
1097
- * Serializes the class back to the Schema type.
1098
- *
1099
- * NOTE: This is used when writing the config to disk and must always match the input Schema
1100
- */
1101
- toSchemaJson() {
1102
- return this.serialized.config;
1103
- }
1104
- serialize() {
1105
- return this.serialized;
1106
- }
1107
- };
868
+ );
869
+ }
870
+ /**
871
+ * Serializes the class back to the Schema type.
872
+ *
873
+ * NOTE: This is used when writing the config to disk and must always match the input Schema
874
+ */
875
+ toSchemaJson() {
876
+ return this.serialized.config;
877
+ }
878
+ serialize() {
879
+ return this.serialized;
880
+ }
881
+ };
882
+
883
+ // src/config/microfrontends/utils/find-config.ts
884
+ import fs from "node:fs";
885
+ import { join } from "node:path";
886
+
887
+ // src/config/microfrontends/utils/get-config-file-name.ts
888
+ var DEFAULT_CONFIGURATION_FILENAMES = [
889
+ "microfrontends.json",
890
+ "microfrontends.jsonc"
891
+ ];
892
+ function getPossibleConfigurationFilenames({
893
+ customConfigFilename
894
+ }) {
895
+ if (customConfigFilename) {
896
+ if (!customConfigFilename.endsWith(".json") && !customConfigFilename.endsWith(".jsonc")) {
897
+ throw new Error(
898
+ `Found VC_MICROFRONTENDS_CONFIG_FILE_NAME but the name is invalid. Received: ${customConfigFilename}. The file name must end with '.json' or '.jsonc'. It's also possible for the env var to include the path, eg microfrontends-dev.json or /path/to/microfrontends-dev.json.`
899
+ );
900
+ }
901
+ return Array.from(
902
+ /* @__PURE__ */ new Set([customConfigFilename, ...DEFAULT_CONFIGURATION_FILENAMES])
903
+ );
904
+ }
905
+ return DEFAULT_CONFIGURATION_FILENAMES;
906
+ }
907
+
908
+ // src/config/microfrontends/utils/find-config.ts
909
+ function findConfig({
910
+ dir,
911
+ customConfigFilename
912
+ }) {
913
+ for (const filename of getPossibleConfigurationFilenames({
914
+ customConfigFilename
915
+ })) {
916
+ const maybeConfig = join(dir, filename);
917
+ if (fs.existsSync(maybeConfig)) {
918
+ return maybeConfig;
919
+ }
920
+ }
921
+ return null;
922
+ }
923
+
924
+ // src/config/microfrontends/utils/find-package-root.ts
925
+ import fs2 from "node:fs";
926
+ import path from "node:path";
927
+ var PACKAGE_JSON = "package.json";
928
+ function findPackageRoot(startDir) {
929
+ let currentDir = startDir || process.cwd();
930
+ while (currentDir !== path.parse(currentDir).root) {
931
+ const pkgJsonPath = path.join(currentDir, PACKAGE_JSON);
932
+ if (fs2.existsSync(pkgJsonPath)) {
933
+ return currentDir;
934
+ }
935
+ currentDir = path.dirname(currentDir);
936
+ }
937
+ throw new Error(
938
+ `The root of the package that contains the \`package.json\` file for the \`${startDir}\` directory could not be found.`
939
+ );
940
+ }
941
+
942
+ // src/config/microfrontends/utils/find-repository-root.ts
943
+ import fs3 from "node:fs";
944
+ import path2 from "node:path";
945
+ var GIT_DIRECTORY = ".git";
946
+ function hasGitDirectory(dir) {
947
+ const gitPath = path2.join(dir, GIT_DIRECTORY);
948
+ return fs3.existsSync(gitPath) && fs3.statSync(gitPath).isDirectory();
949
+ }
950
+ function hasPnpmWorkspaces(dir) {
951
+ return fs3.existsSync(path2.join(dir, "pnpm-workspace.yaml"));
952
+ }
953
+ function hasPackageJson(dir) {
954
+ return fs3.existsSync(path2.join(dir, "package.json"));
955
+ }
956
+ function findRepositoryRoot(startDir) {
957
+ if (process.env.NX_WORKSPACE_ROOT) {
958
+ return process.env.NX_WORKSPACE_ROOT;
959
+ }
960
+ let currentDir = startDir || process.cwd();
961
+ let lastPackageJsonDir = null;
962
+ while (currentDir !== path2.parse(currentDir).root) {
963
+ if (hasGitDirectory(currentDir) || hasPnpmWorkspaces(currentDir)) {
964
+ return currentDir;
965
+ }
966
+ if (hasPackageJson(currentDir)) {
967
+ lastPackageJsonDir = currentDir;
968
+ }
969
+ currentDir = path2.dirname(currentDir);
970
+ }
971
+ if (lastPackageJsonDir) {
972
+ return lastPackageJsonDir;
973
+ }
974
+ throw new Error(
975
+ `Could not find the root of the repository for ${startDir}. Please ensure that the directory is part of a Git repository. If you suspect that this should work, please file an issue to the Vercel team.`
976
+ );
977
+ }
978
+
979
+ // src/config/microfrontends/utils/get-application-context.ts
980
+ import fs4 from "node:fs";
981
+ import path3 from "node:path";
982
+ function getApplicationContext(opts) {
983
+ if (opts?.appName) {
984
+ logger.debug(
985
+ "[MFE Config] Application name from appName parameter:",
986
+ opts.appName
987
+ );
988
+ return { name: opts.appName };
989
+ }
990
+ if (process.env.VERCEL_PROJECT_NAME) {
991
+ logger.debug(
992
+ "[MFE Config] Application name from VERCEL_PROJECT_NAME:",
993
+ process.env.VERCEL_PROJECT_NAME
994
+ );
995
+ return {
996
+ name: process.env.VERCEL_PROJECT_NAME,
997
+ projectName: process.env.VERCEL_PROJECT_NAME
998
+ };
999
+ }
1000
+ if (process.env.NX_TASK_TARGET_PROJECT) {
1001
+ logger.debug(
1002
+ "[MFE Config] Application name from NX_TASK_TARGET_PROJECT:",
1003
+ process.env.NX_TASK_TARGET_PROJECT
1004
+ );
1005
+ return {
1006
+ name: process.env.NX_TASK_TARGET_PROJECT,
1007
+ packageJsonName: process.env.NX_TASK_TARGET_PROJECT
1008
+ };
1009
+ }
1010
+ try {
1011
+ const vercelProjectJsonPath = fs4.readFileSync(
1012
+ path3.join(opts?.packageRoot || ".", ".vercel", "project.json"),
1013
+ "utf-8"
1014
+ );
1015
+ const projectJson = JSON.parse(vercelProjectJsonPath);
1016
+ if (projectJson.projectName) {
1017
+ logger.debug(
1018
+ "[MFE Config] Application name from .vercel/project.json:",
1019
+ projectJson.projectName
1020
+ );
1021
+ return {
1022
+ name: projectJson.projectName,
1023
+ projectName: projectJson.projectName
1024
+ };
1025
+ }
1026
+ } catch (_) {
1027
+ }
1028
+ try {
1029
+ const packageJsonString = fs4.readFileSync(
1030
+ path3.join(opts?.packageRoot || ".", "package.json"),
1031
+ "utf-8"
1032
+ );
1033
+ const packageJson = JSON.parse(packageJsonString);
1034
+ if (!packageJson.name) {
1035
+ throw new MicrofrontendError(
1036
+ `package.json file missing required field "name"`,
1037
+ {
1038
+ type: "packageJson",
1039
+ subtype: "missing_field_name",
1040
+ source: "@vercel/microfrontends/next"
1041
+ }
1042
+ );
1043
+ }
1044
+ logger.debug(
1045
+ "[MFE Config] Application name from package.json:",
1046
+ packageJson.name
1047
+ );
1048
+ return { name: packageJson.name, packageJsonName: packageJson.name };
1049
+ } catch (err) {
1050
+ throw MicrofrontendError.handle(err, {
1051
+ fileName: "package.json"
1052
+ });
1053
+ }
1054
+ }
1055
+
1056
+ // src/config/microfrontends/utils/infer-microfrontends-location.ts
1057
+ import { readFileSync, statSync } from "node:fs";
1058
+ import { dirname, join as join2 } from "node:path";
1059
+ import fg from "fast-glob";
1060
+ import { parse as parse2 } from "jsonc-parser";
1061
+ var configCache = {};
1062
+ function findPackageWithMicrofrontendsConfig({
1063
+ repositoryRoot,
1064
+ applicationContext,
1065
+ customConfigFilename
1066
+ }) {
1067
+ const applicationName = applicationContext.name;
1068
+ logger.debug(
1069
+ "[MFE Config] Searching repository for configs containing application:",
1070
+ applicationName
1071
+ );
1072
+ try {
1073
+ const microfrontendsJsonPaths = fg.globSync(
1074
+ `**/{${getPossibleConfigurationFilenames({ customConfigFilename }).join(",")}}`,
1075
+ {
1076
+ cwd: repositoryRoot,
1077
+ absolute: true,
1078
+ onlyFiles: true,
1079
+ followSymbolicLinks: false,
1080
+ ignore: ["**/node_modules/**", "**/.git/**"]
1081
+ }
1082
+ );
1083
+ logger.debug(
1084
+ "[MFE Config] Found",
1085
+ microfrontendsJsonPaths.length,
1086
+ "config file(s) in repository"
1087
+ );
1088
+ const matchingPaths = [];
1089
+ for (const microfrontendsJsonPath of microfrontendsJsonPaths) {
1090
+ if (doesApplicationExistInConfig(microfrontendsJsonPath, applicationName)) {
1091
+ matchingPaths.push(microfrontendsJsonPath);
1092
+ }
1093
+ }
1094
+ logger.debug(
1095
+ "[MFE Config] Total matching config files:",
1096
+ matchingPaths.length
1097
+ );
1098
+ if (matchingPaths.length > 1) {
1099
+ throw new MicrofrontendError(
1100
+ `Found multiple \`microfrontends.json\` files in the repository referencing the application "${applicationName}", but only one is allowed.
1101
+ ${matchingPaths.join("\n \u2022 ")}`,
1102
+ { type: "config", subtype: "inference_failed" }
1103
+ );
1104
+ }
1105
+ if (matchingPaths.length === 0) {
1106
+ if (repositoryRoot && doesMisplacedConfigExist(
1107
+ repositoryRoot,
1108
+ applicationName,
1109
+ customConfigFilename
1110
+ )) {
1111
+ logger.debug(
1112
+ "[MFE Config] Found misplaced config in wrong .vercel directory in repository"
1113
+ );
1114
+ const misplacedConfigPath = join2(
1115
+ repositoryRoot,
1116
+ ".vercel",
1117
+ customConfigFilename || "microfrontends.json"
1118
+ );
1119
+ throw new MicrofrontendError(
1120
+ `Unable to automatically infer the location of the \`microfrontends.json\` file.
1121
+
1122
+ A microfrontends config was found in the \`.vercel\` directory at the repository root: ${misplacedConfigPath}
1123
+ However, in a monorepo, the config file should be placed in the \`.vercel\` directory in your application directory instead.
1124
+
1125
+ To fix this:
1126
+ 1. If using \`vercel link\`, run it with \`vercel link --repo\` to handle monorepos, or run \`vercel microfrontends pull --cwd=<application-directory>\` to make sure it pulls the \`microfrontends.json\` file to the correct location
1127
+ 2. If manually defined, move the config file to the \`.vercel\` directory in your application
1128
+ 3. Alternatively, set the VC_MICROFRONTENDS_CONFIG environment variable to the correct path
1129
+
1130
+ For more information, see: https://vercel.com/docs/cli/project-linking`,
1131
+ { type: "config", subtype: "inference_failed" }
1132
+ );
1133
+ }
1134
+ let additionalErrorMessage = "";
1135
+ if (microfrontendsJsonPaths.length > 0) {
1136
+ if (!applicationContext.projectName) {
1137
+ additionalErrorMessage = `
1138
+
1139
+ If the name in package.json (${applicationContext.packageJsonName}) differs from your Vercel Project name, set the \`packageName\` field for the application in \`microfrontends.json\` to ensure that the configuration can be found locally.`;
1140
+ } else {
1141
+ additionalErrorMessage = `
1142
+
1143
+ Names of applications in \`microfrontends.json\` must match the Vercel Project name (${applicationContext.projectName}).`;
1144
+ }
1145
+ }
1146
+ throw new MicrofrontendError(
1147
+ `Could not find a \`microfrontends.json\` file in the repository that contains the "${applicationName}" application.${additionalErrorMessage}
1108
1148
 
1109
- // src/config/microfrontends/utils/get-application-context.ts
1110
- import fs5 from "node:fs";
1111
- import path4 from "node:path";
1112
- function getApplicationContext(opts) {
1113
- if (opts?.appName) {
1114
- logger.debug("[MFE Config] Application name from appName parameter:", opts.appName);
1115
- return { name: opts.appName };
1149
+ If your Vercel Microfrontends configuration is not in this repository, you can use the Vercel CLI to pull the Vercel Microfrontends configuration using the "vercel microfrontends pull" command, or you can specify the path manually using the VC_MICROFRONTENDS_CONFIG environment variable.
1150
+
1151
+ If your Vercel Microfrontends configuration has a custom name, ensure the VC_MICROFRONTENDS_CONFIG_FILE_NAME environment variable is set, you can pull the vercel project environment variables using the "vercel env pull" command.
1152
+
1153
+ If you suspect this is thrown in error, please reach out to the Vercel team.`,
1154
+ { type: "config", subtype: "inference_failed" }
1155
+ );
1156
+ }
1157
+ const [packageJsonPath] = matchingPaths;
1158
+ return dirname(packageJsonPath);
1159
+ } catch (error2) {
1160
+ if (error2 instanceof MicrofrontendError) {
1161
+ throw error2;
1162
+ }
1163
+ return null;
1116
1164
  }
1117
- if (process.env.VERCEL_PROJECT_NAME) {
1118
- logger.debug("[MFE Config] Application name from VERCEL_PROJECT_NAME:", process.env.VERCEL_PROJECT_NAME);
1119
- return {
1120
- name: process.env.VERCEL_PROJECT_NAME,
1121
- projectName: process.env.VERCEL_PROJECT_NAME
1122
- };
1165
+ }
1166
+ function inferMicrofrontendsLocation(opts) {
1167
+ const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}${opts.customConfigFilename ? `-${opts.customConfigFilename}` : ""}`;
1168
+ if (configCache[cacheKey]) {
1169
+ return configCache[cacheKey];
1123
1170
  }
1124
- if (process.env.NX_TASK_TARGET_PROJECT) {
1125
- logger.debug("[MFE Config] Application name from NX_TASK_TARGET_PROJECT:", process.env.NX_TASK_TARGET_PROJECT);
1126
- return {
1127
- name: process.env.NX_TASK_TARGET_PROJECT,
1128
- packageJsonName: process.env.NX_TASK_TARGET_PROJECT
1129
- };
1171
+ const result = findPackageWithMicrofrontendsConfig(opts);
1172
+ if (!result) {
1173
+ throw new MicrofrontendError(
1174
+ `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationContext.name}" starting in directory "${opts.repositoryRoot}".`,
1175
+ { type: "config", subtype: "inference_failed" }
1176
+ );
1130
1177
  }
1178
+ configCache[cacheKey] = result;
1179
+ return result;
1180
+ }
1181
+ function existsSync(path6) {
1131
1182
  try {
1132
- const vercelProjectJsonPath = fs5.readFileSync(
1133
- path4.join(opts?.packageRoot || ".", ".vercel", "project.json"),
1134
- "utf-8"
1135
- );
1136
- const projectJson = JSON.parse(vercelProjectJsonPath);
1137
- if (projectJson.projectName) {
1138
- logger.debug("[MFE Config] Application name from .vercel/project.json:", projectJson.projectName);
1139
- return {
1140
- name: projectJson.projectName,
1141
- projectName: projectJson.projectName
1142
- };
1143
- }
1183
+ statSync(path6);
1184
+ return true;
1144
1185
  } catch (_) {
1186
+ return false;
1145
1187
  }
1188
+ }
1189
+ function doesMisplacedConfigExist(repositoryRoot, applicationName, customConfigFilename) {
1190
+ logger.debug(
1191
+ "[MFE Config] Looking for misplaced config in wrong .vercel directory"
1192
+ );
1193
+ const misplacedConfigPath = join2(
1194
+ repositoryRoot,
1195
+ ".vercel",
1196
+ customConfigFilename || "microfrontends.json"
1197
+ );
1198
+ return existsSync(misplacedConfigPath) && doesApplicationExistInConfig(misplacedConfigPath, applicationName);
1199
+ }
1200
+ function doesApplicationExistInConfig(microfrontendsJsonPath, applicationName) {
1146
1201
  try {
1147
- const packageJsonString = fs5.readFileSync(
1148
- path4.join(opts?.packageRoot || ".", "package.json"),
1202
+ const microfrontendsJsonContent = readFileSync(
1203
+ microfrontendsJsonPath,
1149
1204
  "utf-8"
1150
1205
  );
1151
- const packageJson = JSON.parse(packageJsonString);
1152
- if (!packageJson.name) {
1153
- throw new MicrofrontendError(
1154
- `package.json file missing required field "name"`,
1155
- {
1156
- type: "packageJson",
1157
- subtype: "missing_field_name",
1158
- source: "@vercel/microfrontends/next"
1159
- }
1206
+ const microfrontendsJson = parse2(microfrontendsJsonContent);
1207
+ if (microfrontendsJson.applications[applicationName]) {
1208
+ logger.debug(
1209
+ "[MFE Config] Found application in config:",
1210
+ microfrontendsJsonPath
1160
1211
  );
1212
+ return true;
1161
1213
  }
1162
- logger.debug("[MFE Config] Application name from package.json:", packageJson.name);
1163
- return { name: packageJson.name, packageJsonName: packageJson.name };
1164
- } catch (err) {
1165
- throw MicrofrontendError.handle(err, {
1166
- fileName: "package.json"
1167
- });
1214
+ for (const [_, app] of Object.entries(microfrontendsJson.applications)) {
1215
+ if (app.packageName === applicationName) {
1216
+ logger.debug(
1217
+ "[MFE Config] Found application via packageName in config:",
1218
+ microfrontendsJsonPath
1219
+ );
1220
+ return true;
1221
+ }
1222
+ }
1223
+ } catch (error2) {
1224
+ logger.debug("[MFE Config] Error checking application in config:", error2);
1225
+ }
1226
+ return false;
1227
+ }
1228
+
1229
+ // src/config/microfrontends/utils/is-monorepo.ts
1230
+ import fs5 from "node:fs";
1231
+ import path4 from "node:path";
1232
+ function isMonorepo({
1233
+ repositoryRoot
1234
+ }) {
1235
+ try {
1236
+ if (fs5.existsSync(path4.join(repositoryRoot, "pnpm-workspace.yaml"))) {
1237
+ return true;
1238
+ }
1239
+ if (fs5.existsSync(path4.join(repositoryRoot, "vlt-workspaces.json"))) {
1240
+ return true;
1241
+ }
1242
+ if (process.env.NX_WORKSPACE_ROOT === path4.resolve(repositoryRoot)) {
1243
+ return true;
1244
+ }
1245
+ const packageJsonPath = path4.join(repositoryRoot, "package.json");
1246
+ if (!fs5.existsSync(packageJsonPath)) {
1247
+ return false;
1248
+ }
1249
+ const packageJson = JSON.parse(
1250
+ fs5.readFileSync(packageJsonPath, "utf-8")
1251
+ );
1252
+ return packageJson.workspaces !== void 0;
1253
+ } catch (error2) {
1254
+ logger.error("Error determining if repository is a monorepo", error2);
1255
+ return false;
1168
1256
  }
1169
1257
  }
1170
1258
 
@@ -1181,8 +1269,8 @@ function getOutputFilePath() {
1181
1269
  }
1182
1270
 
1183
1271
  // src/config/microfrontends/server/validation.ts
1184
- import { parse as parse3 } from "jsonc-parser";
1185
1272
  import { Ajv } from "ajv";
1273
+ import { parse as parse3 } from "jsonc-parser";
1186
1274
 
1187
1275
  // schema/schema.json
1188
1276
  var schema_default = {
@@ -1210,9 +1298,7 @@ var schema_default = {
1210
1298
  description: "Optional configuration options for the microfrontend."
1211
1299
  }
1212
1300
  },
1213
- required: [
1214
- "applications"
1215
- ],
1301
+ required: ["applications"],
1216
1302
  additionalProperties: false,
1217
1303
  description: "The microfrontends configuration schema. See https://vercel.com/docs/microfrontends/configuration."
1218
1304
  },
@@ -1249,19 +1335,14 @@ var schema_default = {
1249
1335
  description: "Development configuration for the default application."
1250
1336
  }
1251
1337
  },
1252
- required: [
1253
- "development"
1254
- ],
1338
+ required: ["development"],
1255
1339
  additionalProperties: false
1256
1340
  },
1257
1341
  DefaultDevelopment: {
1258
1342
  type: "object",
1259
1343
  properties: {
1260
1344
  local: {
1261
- type: [
1262
- "number",
1263
- "string"
1264
- ],
1345
+ type: ["number", "string"],
1265
1346
  description: "A local port number or host that this application runs on when it is running locally. If passing a string, include the protocol (optional), host (required) and port (optional).\n\nExamples of valid values: 8080, my.localhost.me, my.localhost.me:8080, https://my.localhost.me, https://my.localhost.me:8080.\n\nThe default value is http://localhost:<port> where port is a stable, unique port number (based on the application name).\n\nSee https://vercel.com/docs/microfrontends/local-development."
1266
1347
  },
1267
1348
  task: {
@@ -1273,9 +1354,7 @@ var schema_default = {
1273
1354
  description: "Fallback for local development, could point to any environment. This is required for the default app. This value is used as the fallback for child apps as well if they do not have a fallback.\n\nIf passing a string, include the protocol (optional), host (required) and port (optional). For example: `https://this.ismyhost:8080`. If omitted, the protocol defaults to HTTPS. If omitted, the port defaults to `80` for HTTP and `443` for HTTPS.\n\nSee https://vercel.com/docs/microfrontends/local-development."
1274
1355
  }
1275
1356
  },
1276
- required: [
1277
- "fallback"
1278
- ],
1357
+ required: ["fallback"],
1279
1358
  additionalProperties: false
1280
1359
  },
1281
1360
  ChildApplication: {
@@ -1298,19 +1377,14 @@ var schema_default = {
1298
1377
  description: "The name of the asset prefix to use instead of the auto-generated name.\n\nThe asset prefix is used to prefix all paths to static assets, such as JS, CSS, or images that are served by a specific application. It is necessary to ensure there are no conflicts with other applications on the same domain.\n\nAn auto-generated asset prefix of the form `vc-ap-<hash>` is used when this field is not provided.\n\nWhen this field is provided, `/${assetPrefix}/:path*` must also be added to the list of paths in the `routing` field. Changing the asset prefix after a microfrontend application has already been deployed is not a forwards and backwards compatible change, and the asset prefix should be added to the `routing` field and deployed before setting the `assetPrefix` field.\n\nThe default value is the auto-generated asset prefix of the form `vc-ap-<hash>`.\n\nSee https://vercel.com/docs/microfrontends/path-routing#asset-prefix."
1299
1378
  }
1300
1379
  },
1301
- required: [
1302
- "routing"
1303
- ],
1380
+ required: ["routing"],
1304
1381
  additionalProperties: false
1305
1382
  },
1306
1383
  ChildDevelopment: {
1307
1384
  type: "object",
1308
1385
  properties: {
1309
1386
  local: {
1310
- type: [
1311
- "number",
1312
- "string"
1313
- ],
1387
+ type: ["number", "string"],
1314
1388
  description: "A local port number or host that this application runs on when it is running locally. If passing a string, include the protocol (optional), host (required) and port (optional).\n\nExamples of valid values: 8080, my.localhost.me, my.localhost.me:8080, https://my.localhost.me, https://my.localhost.me:8080.\n\nThe default value is http://localhost:<port> where port is a stable, unique port number (based on the application name).\n\nSee https://vercel.com/docs/microfrontends/local-development."
1315
1389
  },
1316
1390
  task: {
@@ -1350,9 +1424,7 @@ var schema_default = {
1350
1424
  description: "A list of path expressions that are routed to this application. See https://vercel.com/docs/microfrontends/path-routing#supported-path-expressions."
1351
1425
  }
1352
1426
  },
1353
- required: [
1354
- "paths"
1355
- ],
1427
+ required: ["paths"],
1356
1428
  additionalProperties: false,
1357
1429
  description: "A group of paths that is routed to this application."
1358
1430
  },
@@ -1595,7 +1667,7 @@ var MicrofrontendsServer = class {
1595
1667
  });
1596
1668
  }
1597
1669
  } else {
1598
- const vercelDir = join2(packageRoot, ".vercel");
1670
+ const vercelDir = join3(packageRoot, ".vercel");
1599
1671
  logger.debug(
1600
1672
  "[MFE Config] Searching for config in .vercel directory:",
1601
1673
  vercelDir
@@ -1714,6 +1786,51 @@ var MicrofrontendsServer = class {
1714
1786
  }
1715
1787
  };
1716
1788
 
1789
+ // src/next/config/env.ts
1790
+ function debugEnv(env) {
1791
+ const indent = " ".repeat(4);
1792
+ const header = "env (key \u2192 val)";
1793
+ const separator = "\u23AF".repeat(header.length);
1794
+ const maxKeyLength = Math.max(...Object.keys(env).map((key) => key.length));
1795
+ const table = Object.keys(env).map((key, idx) => {
1796
+ const paddedKey = key.padEnd(maxKeyLength);
1797
+ return `${indent} ${idx + 1}. ${paddedKey} = ${env[key]}`;
1798
+ }).join("\n");
1799
+ logger.debug(`${indent}${header}
1800
+ ${indent}${separator}
1801
+ ${table}
1802
+ `);
1803
+ }
1804
+ function setEnvironment({
1805
+ app,
1806
+ microfrontends
1807
+ }) {
1808
+ const clientEnvs = {
1809
+ NEXT_PUBLIC_MFE_CURRENT_APPLICATION: app.name,
1810
+ NEXT_PUBLIC_MFE_CURRENT_APPLICATION_HASH: hashApplicationName(app.name),
1811
+ NEXT_PUBLIC_MFE_CLIENT_CONFIG: JSON.stringify(
1812
+ microfrontends.config.toClientConfig({
1813
+ removeFlaggedPaths: true
1814
+ }).serialize()
1815
+ ),
1816
+ ...app.getAssetPrefix() ? {
1817
+ NEXT_PUBLIC_VERCEL_FIREWALL_PATH_PREFIX: `/${app.getAssetPrefix()}`
1818
+ } : {},
1819
+ ...process.env.ROUTE_OBSERVABILITY_TO_THIS_PROJECT && app.getAssetPrefix() ? {
1820
+ NEXT_PUBLIC_VERCEL_OBSERVABILITY_BASEPATH: `/${app.getAssetPrefix()}/_vercel`
1821
+ } : {}
1822
+ };
1823
+ const serverEnvs = {
1824
+ MFE_CURRENT_APPLICATION: app.name,
1825
+ MFE_CONFIG: JSON.stringify(microfrontends.config.getConfig())
1826
+ };
1827
+ const allEnvs = { ...clientEnvs, ...serverEnvs };
1828
+ for (const [key, value] of Object.entries(allEnvs)) {
1829
+ process.env[key] = value;
1830
+ }
1831
+ debugEnv(allEnvs);
1832
+ }
1833
+
1717
1834
  // src/next/config/transforms/asset-prefix.ts
1718
1835
  function transform(args) {
1719
1836
  const { next, app } = args;
@@ -1818,20 +1935,6 @@ ${indent} - Automatically redirecting all requests to local microfrontends proxy
1818
1935
  return { next };
1819
1936
  }
1820
1937
 
1821
- // src/next/config/transforms/transpile-packages.ts
1822
- function transform5(args) {
1823
- const { next } = args;
1824
- if (next.transpilePackages === void 0 || !next.transpilePackages.includes("@vercel/microfrontends")) {
1825
- next.transpilePackages = [
1826
- ...next.transpilePackages || [],
1827
- "@vercel/microfrontends"
1828
- ];
1829
- }
1830
- return {
1831
- next
1832
- };
1833
- }
1834
-
1835
1938
  // src/next/config/transforms/rewrites.ts
1836
1939
  function debugRewrites(rewrites) {
1837
1940
  const indent = " ".repeat(4);
@@ -1860,7 +1963,7 @@ function rewritesMapToArr(rewrites) {
1860
1963
  ];
1861
1964
  });
1862
1965
  }
1863
- function transform6(args) {
1966
+ function transform5(args) {
1864
1967
  const { app, next } = args;
1865
1968
  const buildBeforeFiles = () => {
1866
1969
  const rewrites = /* @__PURE__ */ new Map();
@@ -1923,6 +2026,20 @@ function transform6(args) {
1923
2026
  };
1924
2027
  }
1925
2028
 
2029
+ // src/next/config/transforms/transpile-packages.ts
2030
+ function transform6(args) {
2031
+ const { next } = args;
2032
+ if (next.transpilePackages === void 0 || !next.transpilePackages.includes("@vercel/microfrontends")) {
2033
+ next.transpilePackages = [
2034
+ ...next.transpilePackages || [],
2035
+ "@vercel/microfrontends"
2036
+ ];
2037
+ }
2038
+ return {
2039
+ next
2040
+ };
2041
+ }
2042
+
1926
2043
  // src/next/config/transforms/webpack.ts
1927
2044
  import fs7 from "node:fs";
1928
2045
  import { createRequire } from "node:module";
@@ -1955,8 +2072,12 @@ var nextVersion = getNextJsVersion();
1955
2072
  function transform7(args) {
1956
2073
  const useDefineServer = args.opts?.preferWebpackEnvironmentPlugin ? false : semver.gte(nextVersion, "15.4.0-canary.41");
1957
2074
  const { next, microfrontend, opts } = args;
2075
+ const isNext16OrHigher = semver.gte(nextVersion, "16.0.0");
2076
+ const hasTurbopackConfig = Boolean(next.turbopack);
2077
+ const turbopackConfig = isNext16OrHigher && !hasTurbopackConfig ? { turbopack: {} } : {};
1958
2078
  const configWithWebpack = {
1959
2079
  ...next,
2080
+ ...turbopackConfig,
1960
2081
  ...useDefineServer ? {
1961
2082
  compiler: {
1962
2083
  ...next.compiler,
@@ -2036,56 +2157,11 @@ var transforms = {
2036
2157
  buildId: transform2,
2037
2158
  draftMode: transform3,
2038
2159
  redirects: transform4,
2039
- rewrites: transform6,
2040
- transpilePackages: transform5,
2160
+ rewrites: transform5,
2161
+ transpilePackages: transform6,
2041
2162
  webpack: transform7
2042
2163
  };
2043
2164
 
2044
- // src/next/config/env.ts
2045
- function debugEnv(env) {
2046
- const indent = " ".repeat(4);
2047
- const header = "env (key \u2192 val)";
2048
- const separator = "\u23AF".repeat(header.length);
2049
- const maxKeyLength = Math.max(...Object.keys(env).map((key) => key.length));
2050
- const table = Object.keys(env).map((key, idx) => {
2051
- const paddedKey = key.padEnd(maxKeyLength);
2052
- return `${indent} ${idx + 1}. ${paddedKey} = ${env[key]}`;
2053
- }).join("\n");
2054
- logger.debug(`${indent}${header}
2055
- ${indent}${separator}
2056
- ${table}
2057
- `);
2058
- }
2059
- function setEnvironment({
2060
- app,
2061
- microfrontends
2062
- }) {
2063
- const clientEnvs = {
2064
- NEXT_PUBLIC_MFE_CURRENT_APPLICATION: app.name,
2065
- NEXT_PUBLIC_MFE_CURRENT_APPLICATION_HASH: hashApplicationName(app.name),
2066
- NEXT_PUBLIC_MFE_CLIENT_CONFIG: JSON.stringify(
2067
- microfrontends.config.toClientConfig({
2068
- removeFlaggedPaths: true
2069
- }).serialize()
2070
- ),
2071
- ...app.getAssetPrefix() ? {
2072
- NEXT_PUBLIC_VERCEL_FIREWALL_PATH_PREFIX: `/${app.getAssetPrefix()}`
2073
- } : {},
2074
- ...process.env.ROUTE_OBSERVABILITY_TO_THIS_PROJECT && app.getAssetPrefix() ? {
2075
- NEXT_PUBLIC_VERCEL_OBSERVABILITY_BASEPATH: `/${app.getAssetPrefix()}/_vercel`
2076
- } : {}
2077
- };
2078
- const serverEnvs = {
2079
- MFE_CURRENT_APPLICATION: app.name,
2080
- MFE_CONFIG: JSON.stringify(microfrontends.config.getConfig())
2081
- };
2082
- const allEnvs = { ...clientEnvs, ...serverEnvs };
2083
- for (const [key, value] of Object.entries(allEnvs)) {
2084
- process.env[key] = value;
2085
- }
2086
- debugEnv(allEnvs);
2087
- }
2088
-
2089
2165
  // src/next/config/index.ts
2090
2166
  function typedEntries(obj) {
2091
2167
  return Object.entries(obj);