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