@vercel/microfrontends 2.2.0 → 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 +25 -0
  2. package/cli/index.cjs +0 -1
  3. package/dist/bin/cli.cjs +551 -381
  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 +643 -489
  10. package/dist/experimental/sveltekit.cjs.map +1 -1
  11. package/dist/experimental/sveltekit.js +645 -491
  12. package/dist/experimental/sveltekit.js.map +1 -1
  13. package/dist/experimental/vite.cjs +671 -519
  14. package/dist/experimental/vite.cjs.map +1 -1
  15. package/dist/experimental/vite.js +669 -517
  16. package/dist/experimental/vite.js.map +1 -1
  17. package/dist/microfrontends/server.cjs +661 -509
  18. package/dist/microfrontends/server.cjs.map +1 -1
  19. package/dist/microfrontends/server.d.ts +2 -2
  20. package/dist/microfrontends/server.js +663 -511
  21. package/dist/microfrontends/server.js.map +1 -1
  22. package/dist/microfrontends/utils.cjs +117 -23
  23. package/dist/microfrontends/utils.cjs.map +1 -1
  24. package/dist/microfrontends/utils.d.ts +4 -4
  25. package/dist/microfrontends/utils.js +118 -24
  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 +784 -626
  33. package/dist/next/config.cjs.map +1 -1
  34. package/dist/next/config.js +782 -624
  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 +689 -522
  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 +687 -521
  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
@@ -30,48 +30,38 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/utils/mfe-port.ts
31
31
  var mfe_port_exports = {};
32
32
  __export(mfe_port_exports, {
33
+ MFE_APP_PORT_ENV: () => MFE_APP_PORT_ENV2,
33
34
  mfePort: () => mfePort
34
35
  });
35
36
  module.exports = __toCommonJS(mfe_port_exports);
36
- var import_node_path9 = __toESM(require("path"), 1);
37
37
  var import_node_fs8 = __toESM(require("fs"), 1);
38
+ var import_node_path9 = __toESM(require("path"), 1);
38
39
 
39
40
  // src/config/microfrontends/server/index.ts
40
41
  var import_node_fs7 = __toESM(require("fs"), 1);
41
42
  var import_node_path8 = require("path");
42
43
 
43
- // src/config/overrides/constants.ts
44
- var OVERRIDES_COOKIE_PREFIX = "vercel-micro-frontends-override";
45
- var OVERRIDES_ENV_COOKIE_PREFIX = `${OVERRIDES_COOKIE_PREFIX}:env:`;
46
-
47
- // src/config/overrides/is-override-cookie.ts
48
- function isOverrideCookie(cookie) {
49
- return Boolean(cookie.name?.startsWith(OVERRIDES_COOKIE_PREFIX));
44
+ // src/bin/logger.ts
45
+ function debug(...args) {
46
+ if (process.env.MFE_DEBUG) {
47
+ console.log(...args);
48
+ }
50
49
  }
51
-
52
- // src/config/overrides/get-override-from-cookie.ts
53
- function getOverrideFromCookie(cookie) {
54
- if (!isOverrideCookie(cookie) || !cookie.value)
55
- return;
56
- return {
57
- application: cookie.name.replace(OVERRIDES_ENV_COOKIE_PREFIX, ""),
58
- host: cookie.value
59
- };
50
+ function info(...args) {
51
+ console.log(...args);
60
52
  }
61
-
62
- // src/config/overrides/parse-overrides.ts
63
- function parseOverrides(cookies) {
64
- const overridesConfig = { applications: {} };
65
- cookies.forEach((cookie) => {
66
- const override = getOverrideFromCookie(cookie);
67
- if (!override)
68
- return;
69
- overridesConfig.applications[override.application] = {
70
- environment: { host: override.host }
71
- };
72
- });
73
- return overridesConfig;
53
+ function warn(...args) {
54
+ console.warn(...args);
55
+ }
56
+ function error(...args) {
57
+ console.error(...args);
74
58
  }
59
+ var logger = {
60
+ debug,
61
+ info,
62
+ warn,
63
+ error
64
+ };
75
65
 
76
66
  // src/config/errors.ts
77
67
  var MicrofrontendError = class extends Error {
@@ -165,277 +155,47 @@ var MicrofrontendError = class extends Error {
165
155
  }
166
156
  };
167
157
 
168
- // src/config/microfrontends-config/utils/get-config-from-env.ts
169
- function getConfigStringFromEnv() {
170
- const config = process.env.MFE_CONFIG;
171
- if (!config) {
172
- throw new MicrofrontendError(`Missing "MFE_CONFIG" in environment.`, {
173
- type: "config",
174
- subtype: "not_found_in_env"
175
- });
176
- }
177
- return config;
178
- }
179
-
180
- // src/config/schema/utils/is-default-app.ts
181
- function isDefaultApp(a) {
182
- return !("routing" in a);
183
- }
184
-
185
- // src/config/microfrontends/utils/find-repository-root.ts
186
- var import_node_fs = __toESM(require("fs"), 1);
187
- var import_node_path = __toESM(require("path"), 1);
188
- var GIT_DIRECTORY = ".git";
189
- function hasGitDirectory(dir) {
190
- const gitPath = import_node_path.default.join(dir, GIT_DIRECTORY);
191
- return import_node_fs.default.existsSync(gitPath) && import_node_fs.default.statSync(gitPath).isDirectory();
192
- }
193
- function hasPnpmWorkspaces(dir) {
194
- return import_node_fs.default.existsSync(import_node_path.default.join(dir, "pnpm-workspace.yaml"));
195
- }
196
- function hasPackageJson(dir) {
197
- return import_node_fs.default.existsSync(import_node_path.default.join(dir, "package.json"));
198
- }
199
- function findRepositoryRoot(startDir) {
200
- if (process.env.NX_WORKSPACE_ROOT) {
201
- return process.env.NX_WORKSPACE_ROOT;
202
- }
203
- let currentDir = startDir || process.cwd();
204
- let lastPackageJsonDir = null;
205
- while (currentDir !== import_node_path.default.parse(currentDir).root) {
206
- if (hasGitDirectory(currentDir) || hasPnpmWorkspaces(currentDir)) {
207
- return currentDir;
208
- }
209
- if (hasPackageJson(currentDir)) {
210
- lastPackageJsonDir = currentDir;
211
- }
212
- currentDir = import_node_path.default.dirname(currentDir);
213
- }
214
- if (lastPackageJsonDir) {
215
- return lastPackageJsonDir;
216
- }
217
- throw new Error(
218
- `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.`
219
- );
220
- }
221
-
222
- // src/config/microfrontends/utils/infer-microfrontends-location.ts
223
- var import_node_path2 = require("path");
224
- var import_node_fs2 = require("fs");
158
+ // src/config/microfrontends-config/isomorphic/index.ts
225
159
  var import_jsonc_parser = require("jsonc-parser");
226
- var import_fast_glob = __toESM(require("fast-glob"), 1);
227
160
 
228
- // src/config/microfrontends/utils/get-config-file-name.ts
229
- var DEFAULT_CONFIGURATION_FILENAMES = [
230
- "microfrontends.json",
231
- "microfrontends.jsonc"
232
- ];
233
- function getPossibleConfigurationFilenames({
234
- customConfigFilename
235
- }) {
236
- if (customConfigFilename) {
237
- if (!customConfigFilename.endsWith(".json") && !customConfigFilename.endsWith(".jsonc")) {
238
- throw new Error(
239
- `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.`
240
- );
241
- }
242
- return Array.from(
243
- /* @__PURE__ */ new Set([customConfigFilename, ...DEFAULT_CONFIGURATION_FILENAMES])
244
- );
245
- }
246
- return DEFAULT_CONFIGURATION_FILENAMES;
247
- }
248
-
249
- // src/config/microfrontends/utils/infer-microfrontends-location.ts
250
- var configCache = {};
251
- function findPackageWithMicrofrontendsConfig({
252
- repositoryRoot,
253
- applicationContext,
254
- customConfigFilename
255
- }) {
256
- const applicationName = applicationContext.name;
257
- try {
258
- const microfrontendsJsonPaths = import_fast_glob.default.globSync(
259
- `**/{${getPossibleConfigurationFilenames({ customConfigFilename }).join(",")}}`,
260
- {
261
- cwd: repositoryRoot,
262
- absolute: true,
263
- onlyFiles: true,
264
- followSymbolicLinks: false,
265
- ignore: ["**/node_modules/**", "**/.git/**"]
266
- }
267
- );
268
- const matchingPaths = [];
269
- for (const microfrontendsJsonPath of microfrontendsJsonPaths) {
270
- try {
271
- const microfrontendsJsonContent = (0, import_node_fs2.readFileSync)(
272
- microfrontendsJsonPath,
273
- "utf-8"
274
- );
275
- const microfrontendsJson = (0, import_jsonc_parser.parse)(microfrontendsJsonContent);
276
- if (microfrontendsJson.applications[applicationName]) {
277
- matchingPaths.push(microfrontendsJsonPath);
278
- } else {
279
- for (const [_, app] of Object.entries(
280
- microfrontendsJson.applications
281
- )) {
282
- if (app.packageName === applicationName) {
283
- matchingPaths.push(microfrontendsJsonPath);
284
- }
285
- }
286
- }
287
- } catch (error2) {
288
- }
289
- }
290
- if (matchingPaths.length > 1) {
291
- throw new MicrofrontendError(
292
- `Found multiple \`microfrontends.json\` files in the repository referencing the application "${applicationName}", but only one is allowed.
293
- ${matchingPaths.join("\n \u2022 ")}`,
294
- { type: "config", subtype: "inference_failed" }
295
- );
296
- }
297
- if (matchingPaths.length === 0) {
298
- let additionalErrorMessage = "";
299
- if (microfrontendsJsonPaths.length > 0) {
300
- if (!applicationContext.projectName) {
301
- additionalErrorMessage = `
302
-
303
- 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.`;
304
- } else {
305
- additionalErrorMessage = `
306
-
307
- Names of applications in \`microfrontends.json\` must match the Vercel Project name (${applicationContext.projectName}).`;
308
- }
309
- }
310
- throw new MicrofrontendError(
311
- `Could not find a \`microfrontends.json\` file in the repository that contains the "${applicationName}" application.${additionalErrorMessage}
312
-
313
- 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.
314
-
315
- 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.
316
-
317
- If you suspect this is thrown in error, please reach out to the Vercel team.`,
318
- { type: "config", subtype: "inference_failed" }
319
- );
320
- }
321
- const [packageJsonPath] = matchingPaths;
322
- return (0, import_node_path2.dirname)(packageJsonPath);
323
- } catch (error2) {
324
- if (error2 instanceof MicrofrontendError) {
325
- throw error2;
326
- }
327
- return null;
328
- }
329
- }
330
- function inferMicrofrontendsLocation(opts) {
331
- const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}${opts.customConfigFilename ? `-${opts.customConfigFilename}` : ""}`;
332
- if (configCache[cacheKey]) {
333
- return configCache[cacheKey];
334
- }
335
- const result = findPackageWithMicrofrontendsConfig(opts);
336
- if (!result) {
337
- throw new MicrofrontendError(
338
- `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationContext.name}" starting in directory "${opts.repositoryRoot}".`,
339
- { type: "config", subtype: "inference_failed" }
340
- );
341
- }
342
- configCache[cacheKey] = result;
343
- return result;
344
- }
345
-
346
- // src/config/microfrontends/utils/is-monorepo.ts
347
- var import_node_fs3 = __toESM(require("fs"), 1);
348
- var import_node_path3 = __toESM(require("path"), 1);
161
+ // src/config/overrides/constants.ts
162
+ var OVERRIDES_COOKIE_PREFIX = "vercel-micro-frontends-override";
163
+ var OVERRIDES_ENV_COOKIE_PREFIX = `${OVERRIDES_COOKIE_PREFIX}:env:`;
349
164
 
350
- // src/bin/logger.ts
351
- function debug(...args) {
352
- if (process.env.MFE_DEBUG) {
353
- console.log(...args);
354
- }
355
- }
356
- function info(...args) {
357
- console.log(...args);
358
- }
359
- function warn(...args) {
360
- console.warn(...args);
361
- }
362
- function error(...args) {
363
- console.error(...args);
165
+ // src/config/overrides/is-override-cookie.ts
166
+ function isOverrideCookie(cookie) {
167
+ return Boolean(cookie.name?.startsWith(OVERRIDES_COOKIE_PREFIX));
364
168
  }
365
- var logger = {
366
- debug,
367
- info,
368
- warn,
369
- error
370
- };
371
169
 
372
- // src/config/microfrontends/utils/is-monorepo.ts
373
- function isMonorepo({
374
- repositoryRoot
375
- }) {
376
- try {
377
- if (import_node_fs3.default.existsSync(import_node_path3.default.join(repositoryRoot, "pnpm-workspace.yaml"))) {
378
- return true;
379
- }
380
- if (import_node_fs3.default.existsSync(import_node_path3.default.join(repositoryRoot, "vlt-workspaces.json"))) {
381
- return true;
382
- }
383
- if (process.env.NX_WORKSPACE_ROOT === import_node_path3.default.resolve(repositoryRoot)) {
384
- return true;
385
- }
386
- const packageJsonPath = import_node_path3.default.join(repositoryRoot, "package.json");
387
- if (!import_node_fs3.default.existsSync(packageJsonPath)) {
388
- return false;
389
- }
390
- const packageJson = JSON.parse(
391
- import_node_fs3.default.readFileSync(packageJsonPath, "utf-8")
392
- );
393
- return packageJson.workspaces !== void 0;
394
- } catch (error2) {
395
- logger.error("Error determining if repository is a monorepo", error2);
396
- return false;
397
- }
170
+ // src/config/overrides/get-override-from-cookie.ts
171
+ function getOverrideFromCookie(cookie) {
172
+ if (!isOverrideCookie(cookie) || !cookie.value)
173
+ return;
174
+ return {
175
+ application: cookie.name.replace(OVERRIDES_ENV_COOKIE_PREFIX, ""),
176
+ host: cookie.value
177
+ };
398
178
  }
399
179
 
400
- // src/config/microfrontends/utils/find-package-root.ts
401
- var import_node_fs4 = __toESM(require("fs"), 1);
402
- var import_node_path4 = __toESM(require("path"), 1);
403
- var PACKAGE_JSON = "package.json";
404
- function findPackageRoot(startDir) {
405
- let currentDir = startDir || process.cwd();
406
- while (currentDir !== import_node_path4.default.parse(currentDir).root) {
407
- const pkgJsonPath = import_node_path4.default.join(currentDir, PACKAGE_JSON);
408
- if (import_node_fs4.default.existsSync(pkgJsonPath)) {
409
- return currentDir;
410
- }
411
- currentDir = import_node_path4.default.dirname(currentDir);
412
- }
413
- throw new Error(
414
- `The root of the package that contains the \`package.json\` file for the \`${startDir}\` directory could not be found.`
415
- );
180
+ // src/config/overrides/parse-overrides.ts
181
+ function parseOverrides(cookies) {
182
+ const overridesConfig = { applications: {} };
183
+ cookies.forEach((cookie) => {
184
+ const override = getOverrideFromCookie(cookie);
185
+ if (!override)
186
+ return;
187
+ overridesConfig.applications[override.application] = {
188
+ environment: { host: override.host }
189
+ };
190
+ });
191
+ return overridesConfig;
416
192
  }
417
193
 
418
- // src/config/microfrontends/utils/find-config.ts
419
- var import_node_fs5 = __toESM(require("fs"), 1);
420
- var import_node_path5 = require("path");
421
- function findConfig({
422
- dir,
423
- customConfigFilename
424
- }) {
425
- for (const filename of getPossibleConfigurationFilenames({
426
- customConfigFilename
427
- })) {
428
- const maybeConfig = (0, import_node_path5.join)(dir, filename);
429
- if (import_node_fs5.default.existsSync(maybeConfig)) {
430
- return maybeConfig;
431
- }
432
- }
433
- return null;
194
+ // src/config/schema/utils/is-default-app.ts
195
+ function isDefaultApp(a) {
196
+ return !("routing" in a);
434
197
  }
435
198
 
436
- // src/config/microfrontends-config/isomorphic/index.ts
437
- var import_jsonc_parser2 = require("jsonc-parser");
438
-
439
199
  // src/config/microfrontends-config/client/index.ts
440
200
  var import_path_to_regexp = require("path-to-regexp");
441
201
  var regexpCache = /* @__PURE__ */ new Map();
@@ -484,56 +244,233 @@ var MicrofrontendConfigClient = class {
484
244
  }
485
245
  this.applications = config.applications;
486
246
  }
487
- /**
488
- * Create a new `MicrofrontendConfigClient` from a JSON string.
489
- * Config must be passed in to remain framework agnostic
490
- */
491
- static fromEnv(config) {
492
- if (!config) {
493
- throw new Error(
494
- "Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`? Is the local proxy running and this application is being accessed via the proxy port? See https://vercel.com/docs/microfrontends/local-development#setting-up-microfrontends-proxy"
495
- );
496
- }
497
- return new MicrofrontendConfigClient(JSON.parse(config));
247
+ /**
248
+ * Create a new `MicrofrontendConfigClient` from a JSON string.
249
+ * Config must be passed in to remain framework agnostic
250
+ */
251
+ static fromEnv(config) {
252
+ if (!config) {
253
+ throw new Error(
254
+ "Could not construct MicrofrontendConfigClient: configuration is empty or undefined. Did you set up your application with `withMicrofrontends`? Is the local proxy running and this application is being accessed via the proxy port? See https://vercel.com/docs/microfrontends/local-development#setting-up-microfrontends-proxy"
255
+ );
256
+ }
257
+ return new MicrofrontendConfigClient(JSON.parse(config));
258
+ }
259
+ isEqual(other) {
260
+ return this === other || JSON.stringify(this.applications) === JSON.stringify(other.applications);
261
+ }
262
+ getApplicationNameForPath(path7) {
263
+ if (!path7.startsWith("/")) {
264
+ throw new Error(`Path must start with a /`);
265
+ }
266
+ if (this.pathCache[path7]) {
267
+ return this.pathCache[path7];
268
+ }
269
+ const pathname = new URL(path7, "https://example.com").pathname;
270
+ for (const [name, application] of Object.entries(this.applications)) {
271
+ if (application.routing) {
272
+ for (const group of application.routing) {
273
+ for (const childPath of group.paths) {
274
+ const regexp = getRegexp(childPath);
275
+ if (regexp.test(pathname)) {
276
+ this.pathCache[path7] = name;
277
+ return name;
278
+ }
279
+ }
280
+ }
281
+ }
282
+ }
283
+ const defaultApplication = Object.entries(this.applications).find(
284
+ ([, application]) => application.default
285
+ );
286
+ if (!defaultApplication) {
287
+ return null;
288
+ }
289
+ this.pathCache[path7] = defaultApplication[0];
290
+ return defaultApplication[0];
291
+ }
292
+ serialize() {
293
+ return this.serialized;
294
+ }
295
+ };
296
+
297
+ // src/config/microfrontends-config/utils/get-config-from-env.ts
298
+ function getConfigStringFromEnv() {
299
+ const config = process.env.MFE_CONFIG;
300
+ if (!config) {
301
+ throw new MicrofrontendError(`Missing "MFE_CONFIG" in environment.`, {
302
+ type: "config",
303
+ subtype: "not_found_in_env"
304
+ });
305
+ }
306
+ return config;
307
+ }
308
+
309
+ // src/config/microfrontends-config/isomorphic/constants.ts
310
+ var DEFAULT_LOCAL_PROXY_PORT = 3024;
311
+ var MFE_APP_PORT_ENV = "MFE_APP_PORT";
312
+ var MFE_LOCAL_PROXY_PORT_ENV = "MFE_LOCAL_PROXY_PORT";
313
+
314
+ // src/config/microfrontends-config/isomorphic/utils/generate-port.ts
315
+ function generatePortFromName({
316
+ name,
317
+ minPort = 3e3,
318
+ maxPort = 8e3
319
+ }) {
320
+ if (!name) {
321
+ throw new Error("Name is required to generate a port");
322
+ }
323
+ let hash = 0;
324
+ for (let i = 0; i < name.length; i++) {
325
+ hash = (hash << 5) - hash + name.charCodeAt(i);
326
+ hash |= 0;
327
+ }
328
+ hash = Math.abs(hash);
329
+ const range = maxPort - minPort;
330
+ const port = minPort + hash % range;
331
+ return port;
332
+ }
333
+
334
+ // src/config/microfrontends-config/isomorphic/host.ts
335
+ var Host = class {
336
+ constructor(hostConfig, options) {
337
+ if (typeof hostConfig === "string") {
338
+ ({
339
+ protocol: this.protocol,
340
+ host: this.host,
341
+ port: this.port
342
+ } = Host.parseUrl(hostConfig));
343
+ } else {
344
+ const { protocol = "https", host, port } = hostConfig;
345
+ this.protocol = protocol;
346
+ this.host = host;
347
+ this.port = port;
348
+ }
349
+ this.local = options?.isLocal;
350
+ }
351
+ static parseUrl(url, defaultProtocol = "https") {
352
+ let hostToParse = url;
353
+ if (!/^https?:\/\//.exec(hostToParse)) {
354
+ hostToParse = `${defaultProtocol}://${hostToParse}`;
355
+ }
356
+ const parsed = new URL(hostToParse);
357
+ if (!parsed.hostname) {
358
+ throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
359
+ }
360
+ if (parsed.hash) {
361
+ throw new Error(
362
+ Host.getMicrofrontendsError(url, "cannot have a fragment")
363
+ );
364
+ }
365
+ if (parsed.username || parsed.password) {
366
+ throw new Error(
367
+ Host.getMicrofrontendsError(
368
+ url,
369
+ "cannot have authentication credentials (username and/or password)"
370
+ )
371
+ );
372
+ }
373
+ if (parsed.pathname !== "/") {
374
+ throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
375
+ }
376
+ if (parsed.search) {
377
+ throw new Error(
378
+ Host.getMicrofrontendsError(url, "cannot have query parameters")
379
+ );
380
+ }
381
+ const protocol = parsed.protocol.slice(0, -1);
382
+ return {
383
+ protocol,
384
+ host: parsed.hostname,
385
+ port: parsed.port ? Number.parseInt(parsed.port, 10) : void 0
386
+ };
387
+ }
388
+ static getMicrofrontendsError(url, message) {
389
+ return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
390
+ }
391
+ isLocal() {
392
+ return this.local || this.host === "localhost" || this.host === "127.0.0.1";
393
+ }
394
+ toString() {
395
+ const url = this.toUrl();
396
+ return url.toString().replace(/\/$/, "");
498
397
  }
499
- isEqual(other) {
500
- return this === other || JSON.stringify(this.applications) === JSON.stringify(other.applications);
398
+ toUrl() {
399
+ const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
400
+ return new URL(url);
501
401
  }
502
- getApplicationNameForPath(path7) {
503
- if (!path7.startsWith("/")) {
504
- throw new Error(`Path must start with a /`);
505
- }
506
- if (this.pathCache[path7]) {
507
- return this.pathCache[path7];
508
- }
509
- const pathname = new URL(path7, "https://example.com").pathname;
510
- for (const [name, application] of Object.entries(this.applications)) {
511
- if (application.routing) {
512
- for (const group of application.routing) {
513
- for (const childPath of group.paths) {
514
- const regexp = getRegexp(childPath);
515
- if (regexp.test(pathname)) {
516
- this.pathCache[path7] = name;
517
- return name;
518
- }
519
- }
520
- }
402
+ };
403
+ var LocalHost = class extends Host {
404
+ constructor({
405
+ appName,
406
+ local
407
+ }) {
408
+ const portOverride = process.env[MFE_APP_PORT_ENV];
409
+ if (portOverride) {
410
+ const overridePort = Number.parseInt(portOverride, 10);
411
+ if (!Number.isNaN(overridePort) && overridePort > 0 && overridePort < 65536) {
412
+ super({
413
+ protocol: "http",
414
+ host: "localhost",
415
+ port: overridePort
416
+ });
417
+ return;
521
418
  }
522
419
  }
523
- const defaultApplication = Object.entries(this.applications).find(
524
- ([, application]) => application.default
525
- );
526
- if (!defaultApplication) {
527
- return null;
420
+ let protocol;
421
+ let host;
422
+ let port;
423
+ if (typeof local === "number") {
424
+ port = local;
425
+ } else if (typeof local === "string") {
426
+ if (/^\d+$/.test(local)) {
427
+ port = Number.parseInt(local, 10);
428
+ } else {
429
+ const parsed = Host.parseUrl(local, "http");
430
+ protocol = parsed.protocol;
431
+ host = parsed.host;
432
+ port = parsed.port;
433
+ }
434
+ } else if (local) {
435
+ protocol = local.protocol;
436
+ host = local.host;
437
+ port = local.port;
528
438
  }
529
- this.pathCache[path7] = defaultApplication[0];
530
- return defaultApplication[0];
531
- }
532
- serialize() {
533
- return this.serialized;
439
+ super({
440
+ protocol: protocol ?? "http",
441
+ host: host ?? "localhost",
442
+ port: port ?? generatePortFromName({ name: appName })
443
+ });
534
444
  }
535
445
  };
536
446
 
447
+ // src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts
448
+ var import_md5 = __toESM(require("md5"), 1);
449
+ function hashApplicationName(name) {
450
+ if (!name) {
451
+ throw new Error("Application name is required to generate hash");
452
+ }
453
+ return (0, import_md5.default)(name).substring(0, 6).padStart(6, "0");
454
+ }
455
+
456
+ // src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts
457
+ var PREFIX = "vc-ap";
458
+ function generateAssetPrefixFromName({
459
+ name
460
+ }) {
461
+ if (!name) {
462
+ throw new Error("Name is required to generate an asset prefix");
463
+ }
464
+ return `${PREFIX}-${hashApplicationName(name)}`;
465
+ }
466
+
467
+ // src/config/microfrontends-config/isomorphic/utils/generate-automation-bypass-env-var-name.ts
468
+ function generateAutomationBypassEnvVarName({
469
+ name
470
+ }) {
471
+ return `AUTOMATION_BYPASS_${name.toUpperCase().replace(/[^a-zA-Z0-9]/g, "_")}`;
472
+ }
473
+
537
474
  // src/config/microfrontends-config/isomorphic/validation.ts
538
475
  var import_path_to_regexp2 = require("path-to-regexp");
539
476
  var LIST_FORMATTER = new Intl.ListFormat("en", {
@@ -709,159 +646,11 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
709
646
  ([name]) => name
710
647
  );
711
648
  throw new MicrofrontendError(
712
- `All applications except for the default app must contain the "routing" field. Applications that are missing routing: ${LIST_FORMATTER.format(applicationNamesMissingRouting)}.`,
713
- { type: "config", subtype: "multiple_default_applications" }
714
- );
715
- }
716
- };
717
-
718
- // src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts
719
- var import_md5 = __toESM(require("md5"), 1);
720
- function hashApplicationName(name) {
721
- if (!name) {
722
- throw new Error("Application name is required to generate hash");
723
- }
724
- return (0, import_md5.default)(name).substring(0, 6).padStart(6, "0");
725
- }
726
-
727
- // src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts
728
- var PREFIX = "vc-ap";
729
- function generateAssetPrefixFromName({
730
- name
731
- }) {
732
- if (!name) {
733
- throw new Error("Name is required to generate an asset prefix");
734
- }
735
- return `${PREFIX}-${hashApplicationName(name)}`;
736
- }
737
-
738
- // src/config/microfrontends-config/isomorphic/utils/generate-port.ts
739
- function generatePortFromName({
740
- name,
741
- minPort = 3e3,
742
- maxPort = 8e3
743
- }) {
744
- if (!name) {
745
- throw new Error("Name is required to generate a port");
746
- }
747
- let hash = 0;
748
- for (let i = 0; i < name.length; i++) {
749
- hash = (hash << 5) - hash + name.charCodeAt(i);
750
- hash |= 0;
751
- }
752
- hash = Math.abs(hash);
753
- const range = maxPort - minPort;
754
- const port = minPort + hash % range;
755
- return port;
756
- }
757
-
758
- // src/config/microfrontends-config/isomorphic/host.ts
759
- var Host = class {
760
- constructor(hostConfig, options) {
761
- if (typeof hostConfig === "string") {
762
- ({
763
- protocol: this.protocol,
764
- host: this.host,
765
- port: this.port
766
- } = Host.parseUrl(hostConfig));
767
- } else {
768
- const { protocol = "https", host, port } = hostConfig;
769
- this.protocol = protocol;
770
- this.host = host;
771
- this.port = port;
772
- }
773
- this.local = options?.isLocal;
774
- }
775
- static parseUrl(url, defaultProtocol = "https") {
776
- let hostToParse = url;
777
- if (!/^https?:\/\//.exec(hostToParse)) {
778
- hostToParse = `${defaultProtocol}://${hostToParse}`;
779
- }
780
- const parsed = new URL(hostToParse);
781
- if (!parsed.hostname) {
782
- throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
783
- }
784
- if (parsed.hash) {
785
- throw new Error(
786
- Host.getMicrofrontendsError(url, "cannot have a fragment")
787
- );
788
- }
789
- if (parsed.username || parsed.password) {
790
- throw new Error(
791
- Host.getMicrofrontendsError(
792
- url,
793
- "cannot have authentication credentials (username and/or password)"
794
- )
795
- );
796
- }
797
- if (parsed.pathname !== "/") {
798
- throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
799
- }
800
- if (parsed.search) {
801
- throw new Error(
802
- Host.getMicrofrontendsError(url, "cannot have query parameters")
803
- );
804
- }
805
- const protocol = parsed.protocol.slice(0, -1);
806
- return {
807
- protocol,
808
- host: parsed.hostname,
809
- port: parsed.port ? Number.parseInt(parsed.port) : void 0
810
- };
811
- }
812
- static getMicrofrontendsError(url, message) {
813
- return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
814
- }
815
- isLocal() {
816
- return this.local || this.host === "localhost" || this.host === "127.0.0.1";
817
- }
818
- toString() {
819
- const url = this.toUrl();
820
- return url.toString().replace(/\/$/, "");
821
- }
822
- toUrl() {
823
- const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
824
- return new URL(url);
825
- }
826
- };
827
- var LocalHost = class extends Host {
828
- constructor({
829
- appName,
830
- local
831
- }) {
832
- let protocol;
833
- let host;
834
- let port;
835
- if (typeof local === "number") {
836
- port = local;
837
- } else if (typeof local === "string") {
838
- if (/^\d+$/.test(local)) {
839
- port = Number.parseInt(local);
840
- } else {
841
- const parsed = Host.parseUrl(local, "http");
842
- protocol = parsed.protocol;
843
- host = parsed.host;
844
- port = parsed.port;
845
- }
846
- } else if (local) {
847
- protocol = local.protocol;
848
- host = local.host;
849
- port = local.port;
850
- }
851
- super({
852
- protocol: protocol ?? "http",
853
- host: host ?? "localhost",
854
- port: port ?? generatePortFromName({ name: appName })
855
- });
856
- }
857
- };
858
-
859
- // src/config/microfrontends-config/isomorphic/utils/generate-automation-bypass-env-var-name.ts
860
- function generateAutomationBypassEnvVarName({
861
- name
862
- }) {
863
- return `AUTOMATION_BYPASS_${name.toUpperCase().replace(/[^a-zA-Z0-9]/g, "_")}`;
864
- }
649
+ `All applications except for the default app must contain the "routing" field. Applications that are missing routing: ${LIST_FORMATTER.format(applicationNamesMissingRouting)}.`,
650
+ { type: "config", subtype: "multiple_default_applications" }
651
+ );
652
+ }
653
+ };
865
654
 
866
655
  // src/config/microfrontends-config/isomorphic/application.ts
867
656
  var Application = class {
@@ -943,9 +732,6 @@ var ChildApplication = class extends Application {
943
732
  }
944
733
  };
945
734
 
946
- // src/config/microfrontends-config/isomorphic/constants.ts
947
- var DEFAULT_LOCAL_PROXY_PORT = 3024;
948
-
949
735
  // src/config/microfrontends-config/isomorphic/index.ts
950
736
  var MicrofrontendConfigIsomorphic = class {
951
737
  constructor({
@@ -989,7 +775,7 @@ var MicrofrontendConfigIsomorphic = class {
989
775
  };
990
776
  }
991
777
  static validate(config) {
992
- const c = typeof config === "string" ? (0, import_jsonc_parser2.parse)(config) : config;
778
+ const c = typeof config === "string" ? (0, import_jsonc_parser.parse)(config) : config;
993
779
  validateConfigPaths(c.applications);
994
780
  validateConfigDefaultApplication(c.applications);
995
781
  return c;
@@ -998,7 +784,7 @@ var MicrofrontendConfigIsomorphic = class {
998
784
  cookies
999
785
  }) {
1000
786
  return new MicrofrontendConfigIsomorphic({
1001
- config: (0, import_jsonc_parser2.parse)(getConfigStringFromEnv()),
787
+ config: (0, import_jsonc_parser.parse)(getConfigStringFromEnv()),
1002
788
  overrides: parseOverrides(cookies ?? [])
1003
789
  });
1004
790
  }
@@ -1064,9 +850,17 @@ var MicrofrontendConfigIsomorphic = class {
1064
850
  return this.defaultApplication;
1065
851
  }
1066
852
  /**
1067
- * Returns the configured port for the local proxy
853
+ * Returns the configured port for the local proxy.
854
+ * Can be overridden via MFE_LOCAL_PROXY_PORT environment variable.
1068
855
  */
1069
856
  getLocalProxyPort() {
857
+ const portOverride = process.env[MFE_LOCAL_PROXY_PORT_ENV];
858
+ if (portOverride) {
859
+ const port = Number.parseInt(portOverride, 10);
860
+ if (!Number.isNaN(port) && port > 0 && port < 65536) {
861
+ return port;
862
+ }
863
+ }
1070
864
  return this.config.options?.localProxyPort ?? DEFAULT_LOCAL_PROXY_PORT;
1071
865
  }
1072
866
  toClientConfig(options) {
@@ -1104,32 +898,144 @@ var MicrofrontendConfigIsomorphic = class {
1104
898
  }
1105
899
  };
1106
900
 
901
+ // src/config/microfrontends/utils/find-config.ts
902
+ var import_node_fs = __toESM(require("fs"), 1);
903
+ var import_node_path = require("path");
904
+
905
+ // src/config/microfrontends/utils/get-config-file-name.ts
906
+ var DEFAULT_CONFIGURATION_FILENAMES = [
907
+ "microfrontends.json",
908
+ "microfrontends.jsonc"
909
+ ];
910
+ function getPossibleConfigurationFilenames({
911
+ customConfigFilename
912
+ }) {
913
+ if (customConfigFilename) {
914
+ if (!customConfigFilename.endsWith(".json") && !customConfigFilename.endsWith(".jsonc")) {
915
+ throw new Error(
916
+ `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.`
917
+ );
918
+ }
919
+ return Array.from(
920
+ /* @__PURE__ */ new Set([customConfigFilename, ...DEFAULT_CONFIGURATION_FILENAMES])
921
+ );
922
+ }
923
+ return DEFAULT_CONFIGURATION_FILENAMES;
924
+ }
925
+
926
+ // src/config/microfrontends/utils/find-config.ts
927
+ function findConfig({
928
+ dir,
929
+ customConfigFilename
930
+ }) {
931
+ for (const filename of getPossibleConfigurationFilenames({
932
+ customConfigFilename
933
+ })) {
934
+ const maybeConfig = (0, import_node_path.join)(dir, filename);
935
+ if (import_node_fs.default.existsSync(maybeConfig)) {
936
+ return maybeConfig;
937
+ }
938
+ }
939
+ return null;
940
+ }
941
+
942
+ // src/config/microfrontends/utils/find-package-root.ts
943
+ var import_node_fs2 = __toESM(require("fs"), 1);
944
+ var import_node_path2 = __toESM(require("path"), 1);
945
+ var PACKAGE_JSON = "package.json";
946
+ function findPackageRoot(startDir) {
947
+ let currentDir = startDir || process.cwd();
948
+ while (currentDir !== import_node_path2.default.parse(currentDir).root) {
949
+ const pkgJsonPath = import_node_path2.default.join(currentDir, PACKAGE_JSON);
950
+ if (import_node_fs2.default.existsSync(pkgJsonPath)) {
951
+ return currentDir;
952
+ }
953
+ currentDir = import_node_path2.default.dirname(currentDir);
954
+ }
955
+ throw new Error(
956
+ `The root of the package that contains the \`package.json\` file for the \`${startDir}\` directory could not be found.`
957
+ );
958
+ }
959
+
960
+ // src/config/microfrontends/utils/find-repository-root.ts
961
+ var import_node_fs3 = __toESM(require("fs"), 1);
962
+ var import_node_path3 = __toESM(require("path"), 1);
963
+ var GIT_DIRECTORY = ".git";
964
+ function hasGitDirectory(dir) {
965
+ const gitPath = import_node_path3.default.join(dir, GIT_DIRECTORY);
966
+ return import_node_fs3.default.existsSync(gitPath) && import_node_fs3.default.statSync(gitPath).isDirectory();
967
+ }
968
+ function hasPnpmWorkspaces(dir) {
969
+ return import_node_fs3.default.existsSync(import_node_path3.default.join(dir, "pnpm-workspace.yaml"));
970
+ }
971
+ function hasPackageJson(dir) {
972
+ return import_node_fs3.default.existsSync(import_node_path3.default.join(dir, "package.json"));
973
+ }
974
+ function findRepositoryRoot(startDir) {
975
+ if (process.env.NX_WORKSPACE_ROOT) {
976
+ return process.env.NX_WORKSPACE_ROOT;
977
+ }
978
+ let currentDir = startDir || process.cwd();
979
+ let lastPackageJsonDir = null;
980
+ while (currentDir !== import_node_path3.default.parse(currentDir).root) {
981
+ if (hasGitDirectory(currentDir) || hasPnpmWorkspaces(currentDir)) {
982
+ return currentDir;
983
+ }
984
+ if (hasPackageJson(currentDir)) {
985
+ lastPackageJsonDir = currentDir;
986
+ }
987
+ currentDir = import_node_path3.default.dirname(currentDir);
988
+ }
989
+ if (lastPackageJsonDir) {
990
+ return lastPackageJsonDir;
991
+ }
992
+ throw new Error(
993
+ `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.`
994
+ );
995
+ }
996
+
1107
997
  // src/config/microfrontends/utils/get-application-context.ts
1108
- var import_node_fs6 = __toESM(require("fs"), 1);
1109
- var import_node_path6 = __toESM(require("path"), 1);
998
+ var import_node_fs4 = __toESM(require("fs"), 1);
999
+ var import_node_path4 = __toESM(require("path"), 1);
1110
1000
  function getApplicationContext(opts) {
1111
1001
  if (opts?.appName) {
1002
+ logger.debug(
1003
+ "[MFE Config] Application name from appName parameter:",
1004
+ opts.appName
1005
+ );
1112
1006
  return { name: opts.appName };
1113
1007
  }
1114
1008
  if (process.env.VERCEL_PROJECT_NAME) {
1009
+ logger.debug(
1010
+ "[MFE Config] Application name from VERCEL_PROJECT_NAME:",
1011
+ process.env.VERCEL_PROJECT_NAME
1012
+ );
1115
1013
  return {
1116
1014
  name: process.env.VERCEL_PROJECT_NAME,
1117
1015
  projectName: process.env.VERCEL_PROJECT_NAME
1118
1016
  };
1119
1017
  }
1120
1018
  if (process.env.NX_TASK_TARGET_PROJECT) {
1019
+ logger.debug(
1020
+ "[MFE Config] Application name from NX_TASK_TARGET_PROJECT:",
1021
+ process.env.NX_TASK_TARGET_PROJECT
1022
+ );
1121
1023
  return {
1122
1024
  name: process.env.NX_TASK_TARGET_PROJECT,
1123
1025
  packageJsonName: process.env.NX_TASK_TARGET_PROJECT
1124
1026
  };
1125
1027
  }
1126
1028
  try {
1127
- const vercelProjectJsonPath = import_node_fs6.default.readFileSync(
1128
- import_node_path6.default.join(opts?.packageRoot || ".", ".vercel", "project.json"),
1029
+ const vercelProjectJsonPath = import_node_fs4.default.readFileSync(
1030
+ import_node_path4.default.join(opts?.packageRoot || ".", ".vercel", "project.json"),
1129
1031
  "utf-8"
1130
1032
  );
1131
1033
  const projectJson = JSON.parse(vercelProjectJsonPath);
1132
1034
  if (projectJson.projectName) {
1035
+ logger.debug(
1036
+ "[MFE Config] Application name from .vercel/project.json:",
1037
+ projectJson.projectName
1038
+ );
1133
1039
  return {
1134
1040
  name: projectJson.projectName,
1135
1041
  projectName: projectJson.projectName
@@ -1138,8 +1044,8 @@ function getApplicationContext(opts) {
1138
1044
  } catch (_) {
1139
1045
  }
1140
1046
  try {
1141
- const packageJsonString = import_node_fs6.default.readFileSync(
1142
- import_node_path6.default.join(opts?.packageRoot || ".", "package.json"),
1047
+ const packageJsonString = import_node_fs4.default.readFileSync(
1048
+ import_node_path4.default.join(opts?.packageRoot || ".", "package.json"),
1143
1049
  "utf-8"
1144
1050
  );
1145
1051
  const packageJson = JSON.parse(packageJsonString);
@@ -1153,6 +1059,10 @@ function getApplicationContext(opts) {
1153
1059
  }
1154
1060
  );
1155
1061
  }
1062
+ logger.debug(
1063
+ "[MFE Config] Application name from package.json:",
1064
+ packageJson.name
1065
+ );
1156
1066
  return { name: packageJson.name, packageJsonName: packageJson.name };
1157
1067
  } catch (err) {
1158
1068
  throw MicrofrontendError.handle(err, {
@@ -1161,6 +1071,209 @@ function getApplicationContext(opts) {
1161
1071
  }
1162
1072
  }
1163
1073
 
1074
+ // src/config/microfrontends/utils/infer-microfrontends-location.ts
1075
+ var import_node_fs5 = require("fs");
1076
+ var import_node_path5 = require("path");
1077
+ var import_fast_glob = __toESM(require("fast-glob"), 1);
1078
+ var import_jsonc_parser2 = require("jsonc-parser");
1079
+ var configCache = {};
1080
+ function findPackageWithMicrofrontendsConfig({
1081
+ repositoryRoot,
1082
+ applicationContext,
1083
+ customConfigFilename
1084
+ }) {
1085
+ const applicationName = applicationContext.name;
1086
+ logger.debug(
1087
+ "[MFE Config] Searching repository for configs containing application:",
1088
+ applicationName
1089
+ );
1090
+ try {
1091
+ const microfrontendsJsonPaths = import_fast_glob.default.globSync(
1092
+ `**/{${getPossibleConfigurationFilenames({ customConfigFilename }).join(",")}}`,
1093
+ {
1094
+ cwd: repositoryRoot,
1095
+ absolute: true,
1096
+ onlyFiles: true,
1097
+ followSymbolicLinks: false,
1098
+ ignore: ["**/node_modules/**", "**/.git/**"]
1099
+ }
1100
+ );
1101
+ logger.debug(
1102
+ "[MFE Config] Found",
1103
+ microfrontendsJsonPaths.length,
1104
+ "config file(s) in repository"
1105
+ );
1106
+ const matchingPaths = [];
1107
+ for (const microfrontendsJsonPath of microfrontendsJsonPaths) {
1108
+ if (doesApplicationExistInConfig(microfrontendsJsonPath, applicationName)) {
1109
+ matchingPaths.push(microfrontendsJsonPath);
1110
+ }
1111
+ }
1112
+ logger.debug(
1113
+ "[MFE Config] Total matching config files:",
1114
+ matchingPaths.length
1115
+ );
1116
+ if (matchingPaths.length > 1) {
1117
+ throw new MicrofrontendError(
1118
+ `Found multiple \`microfrontends.json\` files in the repository referencing the application "${applicationName}", but only one is allowed.
1119
+ ${matchingPaths.join("\n \u2022 ")}`,
1120
+ { type: "config", subtype: "inference_failed" }
1121
+ );
1122
+ }
1123
+ if (matchingPaths.length === 0) {
1124
+ if (repositoryRoot && doesMisplacedConfigExist(
1125
+ repositoryRoot,
1126
+ applicationName,
1127
+ customConfigFilename
1128
+ )) {
1129
+ logger.debug(
1130
+ "[MFE Config] Found misplaced config in wrong .vercel directory in repository"
1131
+ );
1132
+ const misplacedConfigPath = (0, import_node_path5.join)(
1133
+ repositoryRoot,
1134
+ ".vercel",
1135
+ customConfigFilename || "microfrontends.json"
1136
+ );
1137
+ throw new MicrofrontendError(
1138
+ `Unable to automatically infer the location of the \`microfrontends.json\` file.
1139
+
1140
+ A microfrontends config was found in the \`.vercel\` directory at the repository root: ${misplacedConfigPath}
1141
+ However, in a monorepo, the config file should be placed in the \`.vercel\` directory in your application directory instead.
1142
+
1143
+ To fix this:
1144
+ 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
1145
+ 2. If manually defined, move the config file to the \`.vercel\` directory in your application
1146
+ 3. Alternatively, set the VC_MICROFRONTENDS_CONFIG environment variable to the correct path
1147
+
1148
+ For more information, see: https://vercel.com/docs/cli/project-linking`,
1149
+ { type: "config", subtype: "inference_failed" }
1150
+ );
1151
+ }
1152
+ let additionalErrorMessage = "";
1153
+ if (microfrontendsJsonPaths.length > 0) {
1154
+ if (!applicationContext.projectName) {
1155
+ additionalErrorMessage = `
1156
+
1157
+ 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.`;
1158
+ } else {
1159
+ additionalErrorMessage = `
1160
+
1161
+ Names of applications in \`microfrontends.json\` must match the Vercel Project name (${applicationContext.projectName}).`;
1162
+ }
1163
+ }
1164
+ throw new MicrofrontendError(
1165
+ `Could not find a \`microfrontends.json\` file in the repository that contains the "${applicationName}" application.${additionalErrorMessage}
1166
+
1167
+ 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.
1168
+
1169
+ 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.
1170
+
1171
+ If you suspect this is thrown in error, please reach out to the Vercel team.`,
1172
+ { type: "config", subtype: "inference_failed" }
1173
+ );
1174
+ }
1175
+ const [packageJsonPath] = matchingPaths;
1176
+ return (0, import_node_path5.dirname)(packageJsonPath);
1177
+ } catch (error2) {
1178
+ if (error2 instanceof MicrofrontendError) {
1179
+ throw error2;
1180
+ }
1181
+ return null;
1182
+ }
1183
+ }
1184
+ function inferMicrofrontendsLocation(opts) {
1185
+ const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}${opts.customConfigFilename ? `-${opts.customConfigFilename}` : ""}`;
1186
+ if (configCache[cacheKey]) {
1187
+ return configCache[cacheKey];
1188
+ }
1189
+ const result = findPackageWithMicrofrontendsConfig(opts);
1190
+ if (!result) {
1191
+ throw new MicrofrontendError(
1192
+ `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationContext.name}" starting in directory "${opts.repositoryRoot}".`,
1193
+ { type: "config", subtype: "inference_failed" }
1194
+ );
1195
+ }
1196
+ configCache[cacheKey] = result;
1197
+ return result;
1198
+ }
1199
+ function existsSync(path7) {
1200
+ try {
1201
+ (0, import_node_fs5.statSync)(path7);
1202
+ return true;
1203
+ } catch (_) {
1204
+ return false;
1205
+ }
1206
+ }
1207
+ function doesMisplacedConfigExist(repositoryRoot, applicationName, customConfigFilename) {
1208
+ logger.debug(
1209
+ "[MFE Config] Looking for misplaced config in wrong .vercel directory"
1210
+ );
1211
+ const misplacedConfigPath = (0, import_node_path5.join)(
1212
+ repositoryRoot,
1213
+ ".vercel",
1214
+ customConfigFilename || "microfrontends.json"
1215
+ );
1216
+ return existsSync(misplacedConfigPath) && doesApplicationExistInConfig(misplacedConfigPath, applicationName);
1217
+ }
1218
+ function doesApplicationExistInConfig(microfrontendsJsonPath, applicationName) {
1219
+ try {
1220
+ const microfrontendsJsonContent = (0, import_node_fs5.readFileSync)(
1221
+ microfrontendsJsonPath,
1222
+ "utf-8"
1223
+ );
1224
+ const microfrontendsJson = (0, import_jsonc_parser2.parse)(microfrontendsJsonContent);
1225
+ if (microfrontendsJson.applications[applicationName]) {
1226
+ logger.debug(
1227
+ "[MFE Config] Found application in config:",
1228
+ microfrontendsJsonPath
1229
+ );
1230
+ return true;
1231
+ }
1232
+ for (const [_, app] of Object.entries(microfrontendsJson.applications)) {
1233
+ if (app.packageName === applicationName) {
1234
+ logger.debug(
1235
+ "[MFE Config] Found application via packageName in config:",
1236
+ microfrontendsJsonPath
1237
+ );
1238
+ return true;
1239
+ }
1240
+ }
1241
+ } catch (error2) {
1242
+ logger.debug("[MFE Config] Error checking application in config:", error2);
1243
+ }
1244
+ return false;
1245
+ }
1246
+
1247
+ // src/config/microfrontends/utils/is-monorepo.ts
1248
+ var import_node_fs6 = __toESM(require("fs"), 1);
1249
+ var import_node_path6 = __toESM(require("path"), 1);
1250
+ function isMonorepo({
1251
+ repositoryRoot
1252
+ }) {
1253
+ try {
1254
+ if (import_node_fs6.default.existsSync(import_node_path6.default.join(repositoryRoot, "pnpm-workspace.yaml"))) {
1255
+ return true;
1256
+ }
1257
+ if (import_node_fs6.default.existsSync(import_node_path6.default.join(repositoryRoot, "vlt-workspaces.json"))) {
1258
+ return true;
1259
+ }
1260
+ if (process.env.NX_WORKSPACE_ROOT === import_node_path6.default.resolve(repositoryRoot)) {
1261
+ return true;
1262
+ }
1263
+ const packageJsonPath = import_node_path6.default.join(repositoryRoot, "package.json");
1264
+ if (!import_node_fs6.default.existsSync(packageJsonPath)) {
1265
+ return false;
1266
+ }
1267
+ const packageJson = JSON.parse(
1268
+ import_node_fs6.default.readFileSync(packageJsonPath, "utf-8")
1269
+ );
1270
+ return packageJson.workspaces !== void 0;
1271
+ } catch (error2) {
1272
+ logger.error("Error determining if repository is a monorepo", error2);
1273
+ return false;
1274
+ }
1275
+ }
1276
+
1164
1277
  // src/config/microfrontends/server/utils/get-output-file-path.ts
1165
1278
  var import_node_path7 = __toESM(require("path"), 1);
1166
1279
 
@@ -1174,8 +1287,8 @@ function getOutputFilePath() {
1174
1287
  }
1175
1288
 
1176
1289
  // src/config/microfrontends/server/validation.ts
1177
- var import_jsonc_parser3 = require("jsonc-parser");
1178
1290
  var import_ajv = require("ajv");
1291
+ var import_jsonc_parser3 = require("jsonc-parser");
1179
1292
 
1180
1293
  // schema/schema.json
1181
1294
  var schema_default = {
@@ -1203,9 +1316,7 @@ var schema_default = {
1203
1316
  description: "Optional configuration options for the microfrontend."
1204
1317
  }
1205
1318
  },
1206
- required: [
1207
- "applications"
1208
- ],
1319
+ required: ["applications"],
1209
1320
  additionalProperties: false,
1210
1321
  description: "The microfrontends configuration schema. See https://vercel.com/docs/microfrontends/configuration."
1211
1322
  },
@@ -1242,19 +1353,14 @@ var schema_default = {
1242
1353
  description: "Development configuration for the default application."
1243
1354
  }
1244
1355
  },
1245
- required: [
1246
- "development"
1247
- ],
1356
+ required: ["development"],
1248
1357
  additionalProperties: false
1249
1358
  },
1250
1359
  DefaultDevelopment: {
1251
1360
  type: "object",
1252
1361
  properties: {
1253
1362
  local: {
1254
- type: [
1255
- "number",
1256
- "string"
1257
- ],
1363
+ type: ["number", "string"],
1258
1364
  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."
1259
1365
  },
1260
1366
  task: {
@@ -1266,9 +1372,7 @@ var schema_default = {
1266
1372
  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."
1267
1373
  }
1268
1374
  },
1269
- required: [
1270
- "fallback"
1271
- ],
1375
+ required: ["fallback"],
1272
1376
  additionalProperties: false
1273
1377
  },
1274
1378
  ChildApplication: {
@@ -1291,19 +1395,14 @@ var schema_default = {
1291
1395
  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."
1292
1396
  }
1293
1397
  },
1294
- required: [
1295
- "routing"
1296
- ],
1398
+ required: ["routing"],
1297
1399
  additionalProperties: false
1298
1400
  },
1299
1401
  ChildDevelopment: {
1300
1402
  type: "object",
1301
1403
  properties: {
1302
1404
  local: {
1303
- type: [
1304
- "number",
1305
- "string"
1306
- ],
1405
+ type: ["number", "string"],
1307
1406
  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."
1308
1407
  },
1309
1408
  task: {
@@ -1343,9 +1442,7 @@ var schema_default = {
1343
1442
  description: "A list of path expressions that are routed to this application. See https://vercel.com/docs/microfrontends/path-routing#supported-path-expressions."
1344
1443
  }
1345
1444
  },
1346
- required: [
1347
- "paths"
1348
- ],
1445
+ required: ["paths"],
1349
1446
  additionalProperties: false,
1350
1447
  description: "A group of paths that is routed to this application."
1351
1448
  },
@@ -1524,7 +1621,13 @@ var MicrofrontendsServer = class {
1524
1621
  filePath,
1525
1622
  cookies
1526
1623
  } = {}) {
1624
+ logger.debug("[MFE Config] Starting config inference", {
1625
+ appName,
1626
+ directory: directory || process.cwd(),
1627
+ filePath
1628
+ });
1527
1629
  if (filePath) {
1630
+ logger.debug("[MFE Config] Using explicit filePath:", filePath);
1528
1631
  return MicrofrontendsServer.fromFile({
1529
1632
  filePath,
1530
1633
  cookies
@@ -1532,16 +1635,25 @@ var MicrofrontendsServer = class {
1532
1635
  }
1533
1636
  try {
1534
1637
  const packageRoot = findPackageRoot(directory);
1638
+ logger.debug("[MFE Config] Package root:", packageRoot);
1535
1639
  const applicationContext = getApplicationContext({
1536
1640
  appName,
1537
1641
  packageRoot
1538
1642
  });
1643
+ logger.debug("[MFE Config] Application context:", applicationContext);
1539
1644
  const customConfigFilename = process.env.VC_MICROFRONTENDS_CONFIG_FILE_NAME;
1645
+ if (customConfigFilename) {
1646
+ logger.debug(
1647
+ "[MFE Config] Custom config filename from VC_MICROFRONTENDS_CONFIG_FILE_NAME:",
1648
+ customConfigFilename
1649
+ );
1650
+ }
1540
1651
  const maybeConfig = findConfig({
1541
1652
  dir: packageRoot,
1542
1653
  customConfigFilename
1543
1654
  });
1544
1655
  if (maybeConfig) {
1656
+ logger.debug("[MFE Config] Config found at package root:", maybeConfig);
1545
1657
  return MicrofrontendsServer.fromFile({
1546
1658
  filePath: maybeConfig,
1547
1659
  cookies
@@ -1549,42 +1661,78 @@ var MicrofrontendsServer = class {
1549
1661
  }
1550
1662
  const repositoryRoot = findRepositoryRoot();
1551
1663
  const isMonorepo2 = isMonorepo({ repositoryRoot });
1664
+ logger.debug(
1665
+ "[MFE Config] Repository root:",
1666
+ repositoryRoot,
1667
+ "Is monorepo:",
1668
+ isMonorepo2
1669
+ );
1552
1670
  const configFromEnv = process.env.VC_MICROFRONTENDS_CONFIG;
1553
1671
  if (typeof configFromEnv === "string") {
1672
+ logger.debug(
1673
+ "[MFE Config] Checking VC_MICROFRONTENDS_CONFIG:",
1674
+ configFromEnv
1675
+ );
1554
1676
  const maybeConfigFromEnv = (0, import_node_path8.resolve)(packageRoot, configFromEnv);
1555
1677
  if (maybeConfigFromEnv) {
1678
+ logger.debug(
1679
+ "[MFE Config] Config loaded from VC_MICROFRONTENDS_CONFIG:",
1680
+ maybeConfigFromEnv
1681
+ );
1556
1682
  return MicrofrontendsServer.fromFile({
1557
1683
  filePath: maybeConfigFromEnv,
1558
1684
  cookies
1559
1685
  });
1560
1686
  }
1561
1687
  } else {
1688
+ const vercelDir = (0, import_node_path8.join)(packageRoot, ".vercel");
1689
+ logger.debug(
1690
+ "[MFE Config] Searching for config in .vercel directory:",
1691
+ vercelDir
1692
+ );
1562
1693
  const maybeConfigFromVercel = findConfig({
1563
- dir: (0, import_node_path8.join)(packageRoot, ".vercel"),
1694
+ dir: vercelDir,
1564
1695
  customConfigFilename
1565
1696
  });
1566
1697
  if (maybeConfigFromVercel) {
1698
+ logger.debug(
1699
+ "[MFE Config] Config found in .vercel directory:",
1700
+ maybeConfigFromVercel
1701
+ );
1567
1702
  return MicrofrontendsServer.fromFile({
1568
1703
  filePath: maybeConfigFromVercel,
1569
1704
  cookies
1570
1705
  });
1571
1706
  }
1572
1707
  if (isMonorepo2) {
1708
+ logger.debug(
1709
+ "[MFE Config] Inferring microfrontends location in monorepo for application:",
1710
+ applicationContext.name
1711
+ );
1573
1712
  const defaultPackage = inferMicrofrontendsLocation({
1574
1713
  repositoryRoot,
1575
1714
  applicationContext,
1576
1715
  customConfigFilename
1577
1716
  });
1717
+ logger.debug(
1718
+ "[MFE Config] Inferred package location:",
1719
+ defaultPackage
1720
+ );
1578
1721
  const maybeConfigFromDefault = findConfig({
1579
1722
  dir: defaultPackage,
1580
1723
  customConfigFilename
1581
1724
  });
1582
1725
  if (maybeConfigFromDefault) {
1726
+ logger.debug(
1727
+ "[MFE Config] Config found in inferred package:",
1728
+ maybeConfigFromDefault
1729
+ );
1583
1730
  return MicrofrontendsServer.fromFile({
1584
1731
  filePath: maybeConfigFromDefault,
1585
1732
  cookies
1586
1733
  });
1587
1734
  }
1735
+ logger.debug("[MFE Config] No config found in inferred package");
1588
1736
  }
1589
1737
  }
1590
1738
  throw new MicrofrontendError(
@@ -1610,8 +1758,13 @@ var MicrofrontendsServer = class {
1610
1758
  cookies
1611
1759
  }) {
1612
1760
  try {
1761
+ logger.debug("[MFE Config] Reading config from file:", filePath);
1613
1762
  const configJson = import_node_fs7.default.readFileSync(filePath, "utf-8");
1614
1763
  const config = MicrofrontendsServer.validate(configJson);
1764
+ logger.debug(
1765
+ "[MFE Config] Config loaded with applications:",
1766
+ Object.keys(config.applications)
1767
+ );
1615
1768
  return new MicrofrontendsServer({
1616
1769
  config,
1617
1770
  overrides: cookies ? parseOverrides(cookies) : void 0
@@ -1652,8 +1805,21 @@ var MicrofrontendsServer = class {
1652
1805
  };
1653
1806
 
1654
1807
  // src/utils/mfe-port.ts
1808
+ var MFE_APP_PORT_ENV2 = "MFE_APP_PORT";
1655
1809
  function mfePort(packageDir) {
1656
1810
  const { name: appName, version } = getPackageJson(packageDir);
1811
+ const portOverride = process.env[MFE_APP_PORT_ENV2];
1812
+ if (portOverride) {
1813
+ const port = Number.parseInt(portOverride, 10);
1814
+ if (!Number.isNaN(port) && port > 0 && port < 65536) {
1815
+ return {
1816
+ name: appName,
1817
+ version,
1818
+ port,
1819
+ overridden: true
1820
+ };
1821
+ }
1822
+ }
1657
1823
  try {
1658
1824
  const result = loadConfig({ packageDir, appName });
1659
1825
  const { port } = result;
@@ -1685,6 +1851,7 @@ function loadConfig({
1685
1851
  }
1686
1852
  // Annotate the CommonJS export names for ESM import in node:
1687
1853
  0 && (module.exports = {
1854
+ MFE_APP_PORT_ENV,
1688
1855
  mfePort
1689
1856
  });
1690
1857
  //# sourceMappingURL=mfe-port.cjs.map