@vercel/microfrontends 2.2.1 → 2.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/cli/index.cjs +0 -1
  3. package/dist/bin/cli.cjs +483 -394
  4. package/dist/config.cjs +191 -169
  5. package/dist/config.cjs.map +1 -1
  6. package/dist/config.d.ts +3 -2
  7. package/dist/config.js +192 -170
  8. package/dist/config.js.map +1 -1
  9. package/dist/experimental/sveltekit.cjs +583 -511
  10. package/dist/experimental/sveltekit.cjs.map +1 -1
  11. package/dist/experimental/sveltekit.js +589 -517
  12. package/dist/experimental/sveltekit.js.map +1 -1
  13. package/dist/experimental/vite.cjs +605 -533
  14. package/dist/experimental/vite.cjs.map +1 -1
  15. package/dist/experimental/vite.js +614 -542
  16. package/dist/experimental/vite.js.map +1 -1
  17. package/dist/microfrontends/server.cjs +601 -529
  18. package/dist/microfrontends/server.cjs.map +1 -1
  19. package/dist/microfrontends/server.d.ts +2 -2
  20. package/dist/microfrontends/server.js +607 -535
  21. package/dist/microfrontends/server.js.map +1 -1
  22. package/dist/microfrontends/utils.cjs +101 -50
  23. package/dist/microfrontends/utils.cjs.map +1 -1
  24. package/dist/microfrontends/utils.d.ts +4 -4
  25. package/dist/microfrontends/utils.js +102 -51
  26. package/dist/microfrontends/utils.js.map +1 -1
  27. package/dist/next/client.cjs +1 -1
  28. package/dist/next/client.cjs.map +1 -1
  29. package/dist/next/client.d.ts +8 -8
  30. package/dist/next/client.js +1 -1
  31. package/dist/next/client.js.map +1 -1
  32. package/dist/next/config.cjs +723 -647
  33. package/dist/next/config.cjs.map +1 -1
  34. package/dist/next/config.js +720 -644
  35. package/dist/next/config.js.map +1 -1
  36. package/dist/next/middleware.cjs +244 -222
  37. package/dist/next/middleware.cjs.map +1 -1
  38. package/dist/next/middleware.js +245 -223
  39. package/dist/next/middleware.js.map +1 -1
  40. package/dist/next/testing.cjs +192 -170
  41. package/dist/next/testing.cjs.map +1 -1
  42. package/dist/next/testing.d.ts +1 -1
  43. package/dist/next/testing.js +193 -171
  44. package/dist/next/testing.js.map +1 -1
  45. package/dist/overrides.cjs +5 -5
  46. package/dist/overrides.cjs.map +1 -1
  47. package/dist/overrides.d.ts +9 -9
  48. package/dist/overrides.js +5 -5
  49. package/dist/overrides.js.map +1 -1
  50. package/dist/utils/mfe-port.cjs +620 -533
  51. package/dist/utils/mfe-port.cjs.map +1 -1
  52. package/dist/utils/mfe-port.d.ts +9 -1
  53. package/dist/utils/mfe-port.js +632 -546
  54. package/dist/utils/mfe-port.js.map +1 -1
  55. package/dist/validation.cjs +8 -24
  56. package/dist/validation.cjs.map +1 -1
  57. package/dist/validation.js +8 -24
  58. package/dist/validation.js.map +1 -1
  59. package/package.json +4 -6
@@ -1,43 +1,32 @@
1
1
  // src/utils/mfe-port.ts
2
- import path6 from "node:path";
3
2
  import fs7 from "node:fs";
3
+ import path6 from "node:path";
4
4
 
5
5
  // src/config/microfrontends/server/index.ts
6
6
  import fs6 from "node:fs";
7
- import { dirname as dirname2, join as join2, resolve } from "node:path";
8
-
9
- // src/config/overrides/constants.ts
10
- var OVERRIDES_COOKIE_PREFIX = "vercel-micro-frontends-override";
11
- var OVERRIDES_ENV_COOKIE_PREFIX = `${OVERRIDES_COOKIE_PREFIX}:env:`;
7
+ import { dirname as dirname2, join as join3, resolve } from "node:path";
12
8
 
13
- // src/config/overrides/is-override-cookie.ts
14
- function isOverrideCookie(cookie) {
15
- return Boolean(cookie.name?.startsWith(OVERRIDES_COOKIE_PREFIX));
9
+ // src/bin/logger.ts
10
+ function debug(...args) {
11
+ if (process.env.MFE_DEBUG) {
12
+ console.log(...args);
13
+ }
16
14
  }
17
-
18
- // src/config/overrides/get-override-from-cookie.ts
19
- function getOverrideFromCookie(cookie) {
20
- if (!isOverrideCookie(cookie) || !cookie.value)
21
- return;
22
- return {
23
- application: cookie.name.replace(OVERRIDES_ENV_COOKIE_PREFIX, ""),
24
- host: cookie.value
25
- };
15
+ function info(...args) {
16
+ console.log(...args);
26
17
  }
27
-
28
- // src/config/overrides/parse-overrides.ts
29
- function parseOverrides(cookies) {
30
- const overridesConfig = { applications: {} };
31
- cookies.forEach((cookie) => {
32
- const override = getOverrideFromCookie(cookie);
33
- if (!override)
34
- return;
35
- overridesConfig.applications[override.application] = {
36
- environment: { host: override.host }
37
- };
38
- });
39
- return overridesConfig;
18
+ function warn(...args) {
19
+ console.warn(...args);
40
20
  }
21
+ function error(...args) {
22
+ console.error(...args);
23
+ }
24
+ var logger = {
25
+ debug,
26
+ info,
27
+ warn,
28
+ error
29
+ };
41
30
 
42
31
  // src/config/errors.ts
43
32
  var MicrofrontendError = class extends Error {
@@ -131,296 +120,47 @@ var MicrofrontendError = class extends Error {
131
120
  }
132
121
  };
133
122
 
134
- // src/config/microfrontends-config/utils/get-config-from-env.ts
135
- function getConfigStringFromEnv() {
136
- const config = process.env.MFE_CONFIG;
137
- if (!config) {
138
- throw new MicrofrontendError(`Missing "MFE_CONFIG" in environment.`, {
139
- type: "config",
140
- subtype: "not_found_in_env"
141
- });
142
- }
143
- return config;
144
- }
145
-
146
- // src/config/schema/utils/is-default-app.ts
147
- function isDefaultApp(a) {
148
- return !("routing" in a);
149
- }
150
-
151
- // src/config/microfrontends/utils/find-repository-root.ts
152
- import fs from "node:fs";
153
- import path from "node:path";
154
- var GIT_DIRECTORY = ".git";
155
- function hasGitDirectory(dir) {
156
- const gitPath = path.join(dir, GIT_DIRECTORY);
157
- return fs.existsSync(gitPath) && fs.statSync(gitPath).isDirectory();
158
- }
159
- function hasPnpmWorkspaces(dir) {
160
- return fs.existsSync(path.join(dir, "pnpm-workspace.yaml"));
161
- }
162
- function hasPackageJson(dir) {
163
- return fs.existsSync(path.join(dir, "package.json"));
164
- }
165
- function findRepositoryRoot(startDir) {
166
- if (process.env.NX_WORKSPACE_ROOT) {
167
- return process.env.NX_WORKSPACE_ROOT;
168
- }
169
- let currentDir = startDir || process.cwd();
170
- let lastPackageJsonDir = null;
171
- while (currentDir !== path.parse(currentDir).root) {
172
- if (hasGitDirectory(currentDir) || hasPnpmWorkspaces(currentDir)) {
173
- return currentDir;
174
- }
175
- if (hasPackageJson(currentDir)) {
176
- lastPackageJsonDir = currentDir;
177
- }
178
- currentDir = path.dirname(currentDir);
179
- }
180
- if (lastPackageJsonDir) {
181
- return lastPackageJsonDir;
182
- }
183
- throw new Error(
184
- `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.`
185
- );
186
- }
187
-
188
- // src/config/microfrontends/utils/infer-microfrontends-location.ts
189
- import { dirname } from "node:path";
190
- import { readFileSync } from "node:fs";
123
+ // src/config/microfrontends-config/isomorphic/index.ts
191
124
  import { parse } from "jsonc-parser";
192
- import fg from "fast-glob";
193
-
194
- // src/bin/logger.ts
195
- function debug(...args) {
196
- if (process.env.MFE_DEBUG) {
197
- console.log(...args);
198
- }
199
- }
200
- function info(...args) {
201
- console.log(...args);
202
- }
203
- function warn(...args) {
204
- console.warn(...args);
205
- }
206
- function error(...args) {
207
- console.error(...args);
208
- }
209
- var logger = {
210
- debug,
211
- info,
212
- warn,
213
- error
214
- };
215
-
216
- // src/config/microfrontends/utils/get-config-file-name.ts
217
- var DEFAULT_CONFIGURATION_FILENAMES = [
218
- "microfrontends.json",
219
- "microfrontends.jsonc"
220
- ];
221
- function getPossibleConfigurationFilenames({
222
- customConfigFilename
223
- }) {
224
- if (customConfigFilename) {
225
- if (!customConfigFilename.endsWith(".json") && !customConfigFilename.endsWith(".jsonc")) {
226
- throw new Error(
227
- `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.`
228
- );
229
- }
230
- return Array.from(
231
- /* @__PURE__ */ new Set([customConfigFilename, ...DEFAULT_CONFIGURATION_FILENAMES])
232
- );
233
- }
234
- return DEFAULT_CONFIGURATION_FILENAMES;
235
- }
236
-
237
- // src/config/microfrontends/utils/infer-microfrontends-location.ts
238
- var configCache = {};
239
- function findPackageWithMicrofrontendsConfig({
240
- repositoryRoot,
241
- applicationContext,
242
- customConfigFilename
243
- }) {
244
- const applicationName = applicationContext.name;
245
- logger.debug(
246
- "[MFE Config] Searching repository for configs containing application:",
247
- applicationName
248
- );
249
- try {
250
- const microfrontendsJsonPaths = fg.globSync(
251
- `**/{${getPossibleConfigurationFilenames({ customConfigFilename }).join(",")}}`,
252
- {
253
- cwd: repositoryRoot,
254
- absolute: true,
255
- onlyFiles: true,
256
- followSymbolicLinks: false,
257
- ignore: ["**/node_modules/**", "**/.git/**"]
258
- }
259
- );
260
- logger.debug(
261
- "[MFE Config] Found",
262
- microfrontendsJsonPaths.length,
263
- "config file(s) in repository"
264
- );
265
- const matchingPaths = [];
266
- for (const microfrontendsJsonPath of microfrontendsJsonPaths) {
267
- try {
268
- const microfrontendsJsonContent = readFileSync(
269
- microfrontendsJsonPath,
270
- "utf-8"
271
- );
272
- const microfrontendsJson = parse(microfrontendsJsonContent);
273
- if (microfrontendsJson.applications[applicationName]) {
274
- logger.debug(
275
- "[MFE Config] Found application in config:",
276
- microfrontendsJsonPath
277
- );
278
- matchingPaths.push(microfrontendsJsonPath);
279
- } else {
280
- for (const [_, app] of Object.entries(
281
- microfrontendsJson.applications
282
- )) {
283
- if (app.packageName === applicationName) {
284
- logger.debug(
285
- "[MFE Config] Found application via packageName in config:",
286
- microfrontendsJsonPath
287
- );
288
- matchingPaths.push(microfrontendsJsonPath);
289
- }
290
- }
291
- }
292
- } catch (error2) {
293
- }
294
- }
295
- logger.debug(
296
- "[MFE Config] Total matching config files:",
297
- matchingPaths.length
298
- );
299
- if (matchingPaths.length > 1) {
300
- throw new MicrofrontendError(
301
- `Found multiple \`microfrontends.json\` files in the repository referencing the application "${applicationName}", but only one is allowed.
302
- ${matchingPaths.join("\n \u2022 ")}`,
303
- { type: "config", subtype: "inference_failed" }
304
- );
305
- }
306
- if (matchingPaths.length === 0) {
307
- let additionalErrorMessage = "";
308
- if (microfrontendsJsonPaths.length > 0) {
309
- if (!applicationContext.projectName) {
310
- additionalErrorMessage = `
311
-
312
- 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.`;
313
- } else {
314
- additionalErrorMessage = `
315
-
316
- Names of applications in \`microfrontends.json\` must match the Vercel Project name (${applicationContext.projectName}).`;
317
- }
318
- }
319
- throw new MicrofrontendError(
320
- `Could not find a \`microfrontends.json\` file in the repository that contains the "${applicationName}" application.${additionalErrorMessage}
321
-
322
- 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.
323
125
 
324
- 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.
126
+ // src/config/overrides/constants.ts
127
+ var OVERRIDES_COOKIE_PREFIX = "vercel-micro-frontends-override";
128
+ var OVERRIDES_ENV_COOKIE_PREFIX = `${OVERRIDES_COOKIE_PREFIX}:env:`;
325
129
 
326
- If you suspect this is thrown in error, please reach out to the Vercel team.`,
327
- { type: "config", subtype: "inference_failed" }
328
- );
329
- }
330
- const [packageJsonPath] = matchingPaths;
331
- return dirname(packageJsonPath);
332
- } catch (error2) {
333
- if (error2 instanceof MicrofrontendError) {
334
- throw error2;
335
- }
336
- return null;
337
- }
338
- }
339
- function inferMicrofrontendsLocation(opts) {
340
- const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}${opts.customConfigFilename ? `-${opts.customConfigFilename}` : ""}`;
341
- if (configCache[cacheKey]) {
342
- return configCache[cacheKey];
343
- }
344
- const result = findPackageWithMicrofrontendsConfig(opts);
345
- if (!result) {
346
- throw new MicrofrontendError(
347
- `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationContext.name}" starting in directory "${opts.repositoryRoot}".`,
348
- { type: "config", subtype: "inference_failed" }
349
- );
350
- }
351
- configCache[cacheKey] = result;
352
- return result;
130
+ // src/config/overrides/is-override-cookie.ts
131
+ function isOverrideCookie(cookie) {
132
+ return Boolean(cookie.name?.startsWith(OVERRIDES_COOKIE_PREFIX));
353
133
  }
354
134
 
355
- // src/config/microfrontends/utils/is-monorepo.ts
356
- import fs2 from "node:fs";
357
- import path2 from "node:path";
358
- function isMonorepo({
359
- repositoryRoot
360
- }) {
361
- try {
362
- if (fs2.existsSync(path2.join(repositoryRoot, "pnpm-workspace.yaml"))) {
363
- return true;
364
- }
365
- if (fs2.existsSync(path2.join(repositoryRoot, "vlt-workspaces.json"))) {
366
- return true;
367
- }
368
- if (process.env.NX_WORKSPACE_ROOT === path2.resolve(repositoryRoot)) {
369
- return true;
370
- }
371
- const packageJsonPath = path2.join(repositoryRoot, "package.json");
372
- if (!fs2.existsSync(packageJsonPath)) {
373
- return false;
374
- }
375
- const packageJson = JSON.parse(
376
- fs2.readFileSync(packageJsonPath, "utf-8")
377
- );
378
- return packageJson.workspaces !== void 0;
379
- } catch (error2) {
380
- logger.error("Error determining if repository is a monorepo", error2);
381
- return false;
382
- }
135
+ // src/config/overrides/get-override-from-cookie.ts
136
+ function getOverrideFromCookie(cookie) {
137
+ if (!isOverrideCookie(cookie) || !cookie.value)
138
+ return;
139
+ return {
140
+ application: cookie.name.replace(OVERRIDES_ENV_COOKIE_PREFIX, ""),
141
+ host: cookie.value
142
+ };
383
143
  }
384
144
 
385
- // src/config/microfrontends/utils/find-package-root.ts
386
- import fs3 from "node:fs";
387
- import path3 from "node:path";
388
- var PACKAGE_JSON = "package.json";
389
- function findPackageRoot(startDir) {
390
- let currentDir = startDir || process.cwd();
391
- while (currentDir !== path3.parse(currentDir).root) {
392
- const pkgJsonPath = path3.join(currentDir, PACKAGE_JSON);
393
- if (fs3.existsSync(pkgJsonPath)) {
394
- return currentDir;
395
- }
396
- currentDir = path3.dirname(currentDir);
397
- }
398
- throw new Error(
399
- `The root of the package that contains the \`package.json\` file for the \`${startDir}\` directory could not be found.`
400
- );
145
+ // src/config/overrides/parse-overrides.ts
146
+ function parseOverrides(cookies) {
147
+ const overridesConfig = { applications: {} };
148
+ cookies.forEach((cookie) => {
149
+ const override = getOverrideFromCookie(cookie);
150
+ if (!override)
151
+ return;
152
+ overridesConfig.applications[override.application] = {
153
+ environment: { host: override.host }
154
+ };
155
+ });
156
+ return overridesConfig;
401
157
  }
402
158
 
403
- // src/config/microfrontends/utils/find-config.ts
404
- import fs4 from "node:fs";
405
- import { join } from "node:path";
406
- function findConfig({
407
- dir,
408
- customConfigFilename
409
- }) {
410
- for (const filename of getPossibleConfigurationFilenames({
411
- customConfigFilename
412
- })) {
413
- const maybeConfig = join(dir, filename);
414
- if (fs4.existsSync(maybeConfig)) {
415
- return maybeConfig;
416
- }
417
- }
418
- return null;
159
+ // src/config/schema/utils/is-default-app.ts
160
+ function isDefaultApp(a) {
161
+ return !("routing" in a);
419
162
  }
420
163
 
421
- // src/config/microfrontends-config/isomorphic/index.ts
422
- import { parse as parse2 } from "jsonc-parser";
423
-
424
164
  // src/config/microfrontends-config/client/index.ts
425
165
  import { pathToRegexp } from "path-to-regexp";
426
166
  var regexpCache = /* @__PURE__ */ new Map();
@@ -519,8 +259,185 @@ var MicrofrontendConfigClient = class {
519
259
  }
520
260
  };
521
261
 
262
+ // src/config/microfrontends-config/utils/get-config-from-env.ts
263
+ function getConfigStringFromEnv() {
264
+ const config = process.env.MFE_CONFIG;
265
+ if (!config) {
266
+ throw new MicrofrontendError(`Missing "MFE_CONFIG" in environment.`, {
267
+ type: "config",
268
+ subtype: "not_found_in_env"
269
+ });
270
+ }
271
+ return config;
272
+ }
273
+
274
+ // src/config/microfrontends-config/isomorphic/constants.ts
275
+ var DEFAULT_LOCAL_PROXY_PORT = 3024;
276
+ var MFE_APP_PORT_ENV = "MFE_APP_PORT";
277
+ var MFE_LOCAL_PROXY_PORT_ENV = "MFE_LOCAL_PROXY_PORT";
278
+
279
+ // src/config/microfrontends-config/isomorphic/utils/generate-port.ts
280
+ function generatePortFromName({
281
+ name,
282
+ minPort = 3e3,
283
+ maxPort = 8e3
284
+ }) {
285
+ if (!name) {
286
+ throw new Error("Name is required to generate a port");
287
+ }
288
+ let hash = 0;
289
+ for (let i = 0; i < name.length; i++) {
290
+ hash = (hash << 5) - hash + name.charCodeAt(i);
291
+ hash |= 0;
292
+ }
293
+ hash = Math.abs(hash);
294
+ const range = maxPort - minPort;
295
+ const port = minPort + hash % range;
296
+ return port;
297
+ }
298
+
299
+ // src/config/microfrontends-config/isomorphic/host.ts
300
+ var Host = class {
301
+ constructor(hostConfig, options) {
302
+ if (typeof hostConfig === "string") {
303
+ ({
304
+ protocol: this.protocol,
305
+ host: this.host,
306
+ port: this.port
307
+ } = Host.parseUrl(hostConfig));
308
+ } else {
309
+ const { protocol = "https", host, port } = hostConfig;
310
+ this.protocol = protocol;
311
+ this.host = host;
312
+ this.port = port;
313
+ }
314
+ this.local = options?.isLocal;
315
+ }
316
+ static parseUrl(url, defaultProtocol = "https") {
317
+ let hostToParse = url;
318
+ if (!/^https?:\/\//.exec(hostToParse)) {
319
+ hostToParse = `${defaultProtocol}://${hostToParse}`;
320
+ }
321
+ const parsed = new URL(hostToParse);
322
+ if (!parsed.hostname) {
323
+ throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
324
+ }
325
+ if (parsed.hash) {
326
+ throw new Error(
327
+ Host.getMicrofrontendsError(url, "cannot have a fragment")
328
+ );
329
+ }
330
+ if (parsed.username || parsed.password) {
331
+ throw new Error(
332
+ Host.getMicrofrontendsError(
333
+ url,
334
+ "cannot have authentication credentials (username and/or password)"
335
+ )
336
+ );
337
+ }
338
+ if (parsed.pathname !== "/") {
339
+ throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
340
+ }
341
+ if (parsed.search) {
342
+ throw new Error(
343
+ Host.getMicrofrontendsError(url, "cannot have query parameters")
344
+ );
345
+ }
346
+ const protocol = parsed.protocol.slice(0, -1);
347
+ return {
348
+ protocol,
349
+ host: parsed.hostname,
350
+ port: parsed.port ? Number.parseInt(parsed.port, 10) : void 0
351
+ };
352
+ }
353
+ static getMicrofrontendsError(url, message) {
354
+ return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
355
+ }
356
+ isLocal() {
357
+ return this.local || this.host === "localhost" || this.host === "127.0.0.1";
358
+ }
359
+ toString() {
360
+ const url = this.toUrl();
361
+ return url.toString().replace(/\/$/, "");
362
+ }
363
+ toUrl() {
364
+ const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
365
+ return new URL(url);
366
+ }
367
+ };
368
+ var LocalHost = class extends Host {
369
+ constructor({
370
+ appName,
371
+ local
372
+ }) {
373
+ const portOverride = process.env[MFE_APP_PORT_ENV];
374
+ if (portOverride) {
375
+ const overridePort = Number.parseInt(portOverride, 10);
376
+ if (!Number.isNaN(overridePort) && overridePort > 0 && overridePort < 65536) {
377
+ super({
378
+ protocol: "http",
379
+ host: "localhost",
380
+ port: overridePort
381
+ });
382
+ return;
383
+ }
384
+ }
385
+ let protocol;
386
+ let host;
387
+ let port;
388
+ if (typeof local === "number") {
389
+ port = local;
390
+ } else if (typeof local === "string") {
391
+ if (/^\d+$/.test(local)) {
392
+ port = Number.parseInt(local, 10);
393
+ } else {
394
+ const parsed = Host.parseUrl(local, "http");
395
+ protocol = parsed.protocol;
396
+ host = parsed.host;
397
+ port = parsed.port;
398
+ }
399
+ } else if (local) {
400
+ protocol = local.protocol;
401
+ host = local.host;
402
+ port = local.port;
403
+ }
404
+ super({
405
+ protocol: protocol ?? "http",
406
+ host: host ?? "localhost",
407
+ port: port ?? generatePortFromName({ name: appName })
408
+ });
409
+ }
410
+ };
411
+
412
+ // src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts
413
+ import md5 from "md5";
414
+ function hashApplicationName(name) {
415
+ if (!name) {
416
+ throw new Error("Application name is required to generate hash");
417
+ }
418
+ return md5(name).substring(0, 6).padStart(6, "0");
419
+ }
420
+
421
+ // src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts
422
+ var PREFIX = "vc-ap";
423
+ function generateAssetPrefixFromName({
424
+ name
425
+ }) {
426
+ if (!name) {
427
+ throw new Error("Name is required to generate an asset prefix");
428
+ }
429
+ return `${PREFIX}-${hashApplicationName(name)}`;
430
+ }
431
+
432
+ // src/config/microfrontends-config/isomorphic/utils/generate-automation-bypass-env-var-name.ts
433
+ function generateAutomationBypassEnvVarName({
434
+ name
435
+ }) {
436
+ return `AUTOMATION_BYPASS_${name.toUpperCase().replace(/[^a-zA-Z0-9]/g, "_")}`;
437
+ }
438
+
522
439
  // src/config/microfrontends-config/isomorphic/validation.ts
523
- import { pathToRegexp as pathToRegexp2, parse as parsePathRegexp } from "path-to-regexp";
440
+ import { parse as parsePathRegexp, pathToRegexp as pathToRegexp2 } from "path-to-regexp";
524
441
  var LIST_FORMATTER = new Intl.ListFormat("en", {
525
442
  style: "long",
526
443
  type: "conjunction"
@@ -700,154 +617,6 @@ var validateConfigDefaultApplication = (applicationConfigsById) => {
700
617
  }
701
618
  };
702
619
 
703
- // src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts
704
- import md5 from "md5";
705
- function hashApplicationName(name) {
706
- if (!name) {
707
- throw new Error("Application name is required to generate hash");
708
- }
709
- return md5(name).substring(0, 6).padStart(6, "0");
710
- }
711
-
712
- // src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts
713
- var PREFIX = "vc-ap";
714
- function generateAssetPrefixFromName({
715
- name
716
- }) {
717
- if (!name) {
718
- throw new Error("Name is required to generate an asset prefix");
719
- }
720
- return `${PREFIX}-${hashApplicationName(name)}`;
721
- }
722
-
723
- // src/config/microfrontends-config/isomorphic/utils/generate-port.ts
724
- function generatePortFromName({
725
- name,
726
- minPort = 3e3,
727
- maxPort = 8e3
728
- }) {
729
- if (!name) {
730
- throw new Error("Name is required to generate a port");
731
- }
732
- let hash = 0;
733
- for (let i = 0; i < name.length; i++) {
734
- hash = (hash << 5) - hash + name.charCodeAt(i);
735
- hash |= 0;
736
- }
737
- hash = Math.abs(hash);
738
- const range = maxPort - minPort;
739
- const port = minPort + hash % range;
740
- return port;
741
- }
742
-
743
- // src/config/microfrontends-config/isomorphic/host.ts
744
- var Host = class {
745
- constructor(hostConfig, options) {
746
- if (typeof hostConfig === "string") {
747
- ({
748
- protocol: this.protocol,
749
- host: this.host,
750
- port: this.port
751
- } = Host.parseUrl(hostConfig));
752
- } else {
753
- const { protocol = "https", host, port } = hostConfig;
754
- this.protocol = protocol;
755
- this.host = host;
756
- this.port = port;
757
- }
758
- this.local = options?.isLocal;
759
- }
760
- static parseUrl(url, defaultProtocol = "https") {
761
- let hostToParse = url;
762
- if (!/^https?:\/\//.exec(hostToParse)) {
763
- hostToParse = `${defaultProtocol}://${hostToParse}`;
764
- }
765
- const parsed = new URL(hostToParse);
766
- if (!parsed.hostname) {
767
- throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
768
- }
769
- if (parsed.hash) {
770
- throw new Error(
771
- Host.getMicrofrontendsError(url, "cannot have a fragment")
772
- );
773
- }
774
- if (parsed.username || parsed.password) {
775
- throw new Error(
776
- Host.getMicrofrontendsError(
777
- url,
778
- "cannot have authentication credentials (username and/or password)"
779
- )
780
- );
781
- }
782
- if (parsed.pathname !== "/") {
783
- throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
784
- }
785
- if (parsed.search) {
786
- throw new Error(
787
- Host.getMicrofrontendsError(url, "cannot have query parameters")
788
- );
789
- }
790
- const protocol = parsed.protocol.slice(0, -1);
791
- return {
792
- protocol,
793
- host: parsed.hostname,
794
- port: parsed.port ? Number.parseInt(parsed.port) : void 0
795
- };
796
- }
797
- static getMicrofrontendsError(url, message) {
798
- return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
799
- }
800
- isLocal() {
801
- return this.local || this.host === "localhost" || this.host === "127.0.0.1";
802
- }
803
- toString() {
804
- const url = this.toUrl();
805
- return url.toString().replace(/\/$/, "");
806
- }
807
- toUrl() {
808
- const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
809
- return new URL(url);
810
- }
811
- };
812
- var LocalHost = class extends Host {
813
- constructor({
814
- appName,
815
- local
816
- }) {
817
- let protocol;
818
- let host;
819
- let port;
820
- if (typeof local === "number") {
821
- port = local;
822
- } else if (typeof local === "string") {
823
- if (/^\d+$/.test(local)) {
824
- port = Number.parseInt(local);
825
- } else {
826
- const parsed = Host.parseUrl(local, "http");
827
- protocol = parsed.protocol;
828
- host = parsed.host;
829
- port = parsed.port;
830
- }
831
- } else if (local) {
832
- protocol = local.protocol;
833
- host = local.host;
834
- port = local.port;
835
- }
836
- super({
837
- protocol: protocol ?? "http",
838
- host: host ?? "localhost",
839
- port: port ?? generatePortFromName({ name: appName })
840
- });
841
- }
842
- };
843
-
844
- // src/config/microfrontends-config/isomorphic/utils/generate-automation-bypass-env-var-name.ts
845
- function generateAutomationBypassEnvVarName({
846
- name
847
- }) {
848
- return `AUTOMATION_BYPASS_${name.toUpperCase().replace(/[^a-zA-Z0-9]/g, "_")}`;
849
- }
850
-
851
620
  // src/config/microfrontends-config/isomorphic/application.ts
852
621
  var Application = class {
853
622
  constructor(name, {
@@ -928,9 +697,6 @@ var ChildApplication = class extends Application {
928
697
  }
929
698
  };
930
699
 
931
- // src/config/microfrontends-config/isomorphic/constants.ts
932
- var DEFAULT_LOCAL_PROXY_PORT = 3024;
933
-
934
700
  // src/config/microfrontends-config/isomorphic/index.ts
935
701
  var MicrofrontendConfigIsomorphic = class {
936
702
  constructor({
@@ -974,7 +740,7 @@ var MicrofrontendConfigIsomorphic = class {
974
740
  };
975
741
  }
976
742
  static validate(config) {
977
- const c = typeof config === "string" ? parse2(config) : config;
743
+ const c = typeof config === "string" ? parse(config) : config;
978
744
  validateConfigPaths(c.applications);
979
745
  validateConfigDefaultApplication(c.applications);
980
746
  return c;
@@ -983,7 +749,7 @@ var MicrofrontendConfigIsomorphic = class {
983
749
  cookies
984
750
  }) {
985
751
  return new MicrofrontendConfigIsomorphic({
986
- config: parse2(getConfigStringFromEnv()),
752
+ config: parse(getConfigStringFromEnv()),
987
753
  overrides: parseOverrides(cookies ?? [])
988
754
  });
989
755
  }
@@ -1038,87 +804,203 @@ var MicrofrontendConfigIsomorphic = class {
1038
804
  if (this.defaultApplication.name === projectName) {
1039
805
  return this.defaultApplication;
1040
806
  }
1041
- return Object.values(this.childApplications).find(
1042
- (app) => app.name === projectName
1043
- );
1044
- }
1045
- /**
1046
- * Returns the default application.
1047
- */
1048
- getDefaultApplication() {
1049
- return this.defaultApplication;
1050
- }
1051
- /**
1052
- * Returns the configured port for the local proxy
1053
- */
1054
- getLocalProxyPort() {
1055
- return this.config.options?.localProxyPort ?? DEFAULT_LOCAL_PROXY_PORT;
807
+ return Object.values(this.childApplications).find(
808
+ (app) => app.name === projectName
809
+ );
810
+ }
811
+ /**
812
+ * Returns the default application.
813
+ */
814
+ getDefaultApplication() {
815
+ return this.defaultApplication;
816
+ }
817
+ /**
818
+ * Returns the configured port for the local proxy.
819
+ * Can be overridden via MFE_LOCAL_PROXY_PORT environment variable.
820
+ */
821
+ getLocalProxyPort() {
822
+ const portOverride = process.env[MFE_LOCAL_PROXY_PORT_ENV];
823
+ if (portOverride) {
824
+ const port = Number.parseInt(portOverride, 10);
825
+ if (!Number.isNaN(port) && port > 0 && port < 65536) {
826
+ return port;
827
+ }
828
+ }
829
+ return this.config.options?.localProxyPort ?? DEFAULT_LOCAL_PROXY_PORT;
830
+ }
831
+ toClientConfig(options) {
832
+ const applications = Object.fromEntries(
833
+ Object.entries(this.childApplications).map(([name, application]) => [
834
+ hashApplicationName(name),
835
+ {
836
+ default: false,
837
+ routing: application.routing
838
+ }
839
+ ])
840
+ );
841
+ applications[hashApplicationName(this.defaultApplication.name)] = {
842
+ default: true
843
+ };
844
+ return new MicrofrontendConfigClient(
845
+ {
846
+ applications
847
+ },
848
+ {
849
+ removeFlaggedPaths: options?.removeFlaggedPaths
850
+ }
851
+ );
852
+ }
853
+ /**
854
+ * Serializes the class back to the Schema type.
855
+ *
856
+ * NOTE: This is used when writing the config to disk and must always match the input Schema
857
+ */
858
+ toSchemaJson() {
859
+ return this.serialized.config;
860
+ }
861
+ serialize() {
862
+ return this.serialized;
863
+ }
864
+ };
865
+
866
+ // src/config/microfrontends/utils/find-config.ts
867
+ import fs from "node:fs";
868
+ import { join } from "node:path";
869
+
870
+ // src/config/microfrontends/utils/get-config-file-name.ts
871
+ var DEFAULT_CONFIGURATION_FILENAMES = [
872
+ "microfrontends.json",
873
+ "microfrontends.jsonc"
874
+ ];
875
+ function getPossibleConfigurationFilenames({
876
+ customConfigFilename
877
+ }) {
878
+ if (customConfigFilename) {
879
+ if (!customConfigFilename.endsWith(".json") && !customConfigFilename.endsWith(".jsonc")) {
880
+ throw new Error(
881
+ `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.`
882
+ );
883
+ }
884
+ return Array.from(
885
+ /* @__PURE__ */ new Set([customConfigFilename, ...DEFAULT_CONFIGURATION_FILENAMES])
886
+ );
887
+ }
888
+ return DEFAULT_CONFIGURATION_FILENAMES;
889
+ }
890
+
891
+ // src/config/microfrontends/utils/find-config.ts
892
+ function findConfig({
893
+ dir,
894
+ customConfigFilename
895
+ }) {
896
+ for (const filename of getPossibleConfigurationFilenames({
897
+ customConfigFilename
898
+ })) {
899
+ const maybeConfig = join(dir, filename);
900
+ if (fs.existsSync(maybeConfig)) {
901
+ return maybeConfig;
902
+ }
903
+ }
904
+ return null;
905
+ }
906
+
907
+ // src/config/microfrontends/utils/find-package-root.ts
908
+ import fs2 from "node:fs";
909
+ import path from "node:path";
910
+ var PACKAGE_JSON = "package.json";
911
+ function findPackageRoot(startDir) {
912
+ let currentDir = startDir || process.cwd();
913
+ while (currentDir !== path.parse(currentDir).root) {
914
+ const pkgJsonPath = path.join(currentDir, PACKAGE_JSON);
915
+ if (fs2.existsSync(pkgJsonPath)) {
916
+ return currentDir;
917
+ }
918
+ currentDir = path.dirname(currentDir);
1056
919
  }
1057
- toClientConfig(options) {
1058
- const applications = Object.fromEntries(
1059
- Object.entries(this.childApplications).map(([name, application]) => [
1060
- hashApplicationName(name),
1061
- {
1062
- default: false,
1063
- routing: application.routing
1064
- }
1065
- ])
1066
- );
1067
- applications[hashApplicationName(this.defaultApplication.name)] = {
1068
- default: true
1069
- };
1070
- return new MicrofrontendConfigClient(
1071
- {
1072
- applications
1073
- },
1074
- {
1075
- removeFlaggedPaths: options?.removeFlaggedPaths
1076
- }
1077
- );
920
+ throw new Error(
921
+ `The root of the package that contains the \`package.json\` file for the \`${startDir}\` directory could not be found.`
922
+ );
923
+ }
924
+
925
+ // src/config/microfrontends/utils/find-repository-root.ts
926
+ import fs3 from "node:fs";
927
+ import path2 from "node:path";
928
+ var GIT_DIRECTORY = ".git";
929
+ function hasGitDirectory(dir) {
930
+ const gitPath = path2.join(dir, GIT_DIRECTORY);
931
+ return fs3.existsSync(gitPath) && fs3.statSync(gitPath).isDirectory();
932
+ }
933
+ function hasPnpmWorkspaces(dir) {
934
+ return fs3.existsSync(path2.join(dir, "pnpm-workspace.yaml"));
935
+ }
936
+ function hasPackageJson(dir) {
937
+ return fs3.existsSync(path2.join(dir, "package.json"));
938
+ }
939
+ function findRepositoryRoot(startDir) {
940
+ if (process.env.NX_WORKSPACE_ROOT) {
941
+ return process.env.NX_WORKSPACE_ROOT;
1078
942
  }
1079
- /**
1080
- * Serializes the class back to the Schema type.
1081
- *
1082
- * NOTE: This is used when writing the config to disk and must always match the input Schema
1083
- */
1084
- toSchemaJson() {
1085
- return this.serialized.config;
943
+ let currentDir = startDir || process.cwd();
944
+ let lastPackageJsonDir = null;
945
+ while (currentDir !== path2.parse(currentDir).root) {
946
+ if (hasGitDirectory(currentDir) || hasPnpmWorkspaces(currentDir)) {
947
+ return currentDir;
948
+ }
949
+ if (hasPackageJson(currentDir)) {
950
+ lastPackageJsonDir = currentDir;
951
+ }
952
+ currentDir = path2.dirname(currentDir);
1086
953
  }
1087
- serialize() {
1088
- return this.serialized;
954
+ if (lastPackageJsonDir) {
955
+ return lastPackageJsonDir;
1089
956
  }
1090
- };
957
+ throw new Error(
958
+ `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.`
959
+ );
960
+ }
1091
961
 
1092
962
  // src/config/microfrontends/utils/get-application-context.ts
1093
- import fs5 from "node:fs";
1094
- import path4 from "node:path";
963
+ import fs4 from "node:fs";
964
+ import path3 from "node:path";
1095
965
  function getApplicationContext(opts) {
1096
966
  if (opts?.appName) {
1097
- logger.debug("[MFE Config] Application name from appName parameter:", opts.appName);
967
+ logger.debug(
968
+ "[MFE Config] Application name from appName parameter:",
969
+ opts.appName
970
+ );
1098
971
  return { name: opts.appName };
1099
972
  }
1100
973
  if (process.env.VERCEL_PROJECT_NAME) {
1101
- logger.debug("[MFE Config] Application name from VERCEL_PROJECT_NAME:", process.env.VERCEL_PROJECT_NAME);
974
+ logger.debug(
975
+ "[MFE Config] Application name from VERCEL_PROJECT_NAME:",
976
+ process.env.VERCEL_PROJECT_NAME
977
+ );
1102
978
  return {
1103
979
  name: process.env.VERCEL_PROJECT_NAME,
1104
980
  projectName: process.env.VERCEL_PROJECT_NAME
1105
981
  };
1106
982
  }
1107
983
  if (process.env.NX_TASK_TARGET_PROJECT) {
1108
- logger.debug("[MFE Config] Application name from NX_TASK_TARGET_PROJECT:", process.env.NX_TASK_TARGET_PROJECT);
984
+ logger.debug(
985
+ "[MFE Config] Application name from NX_TASK_TARGET_PROJECT:",
986
+ process.env.NX_TASK_TARGET_PROJECT
987
+ );
1109
988
  return {
1110
989
  name: process.env.NX_TASK_TARGET_PROJECT,
1111
990
  packageJsonName: process.env.NX_TASK_TARGET_PROJECT
1112
991
  };
1113
992
  }
1114
993
  try {
1115
- const vercelProjectJsonPath = fs5.readFileSync(
1116
- path4.join(opts?.packageRoot || ".", ".vercel", "project.json"),
994
+ const vercelProjectJsonPath = fs4.readFileSync(
995
+ path3.join(opts?.packageRoot || ".", ".vercel", "project.json"),
1117
996
  "utf-8"
1118
997
  );
1119
998
  const projectJson = JSON.parse(vercelProjectJsonPath);
1120
999
  if (projectJson.projectName) {
1121
- logger.debug("[MFE Config] Application name from .vercel/project.json:", projectJson.projectName);
1000
+ logger.debug(
1001
+ "[MFE Config] Application name from .vercel/project.json:",
1002
+ projectJson.projectName
1003
+ );
1122
1004
  return {
1123
1005
  name: projectJson.projectName,
1124
1006
  projectName: projectJson.projectName
@@ -1127,8 +1009,8 @@ function getApplicationContext(opts) {
1127
1009
  } catch (_) {
1128
1010
  }
1129
1011
  try {
1130
- const packageJsonString = fs5.readFileSync(
1131
- path4.join(opts?.packageRoot || ".", "package.json"),
1012
+ const packageJsonString = fs4.readFileSync(
1013
+ path3.join(opts?.packageRoot || ".", "package.json"),
1132
1014
  "utf-8"
1133
1015
  );
1134
1016
  const packageJson = JSON.parse(packageJsonString);
@@ -1142,7 +1024,10 @@ function getApplicationContext(opts) {
1142
1024
  }
1143
1025
  );
1144
1026
  }
1145
- logger.debug("[MFE Config] Application name from package.json:", packageJson.name);
1027
+ logger.debug(
1028
+ "[MFE Config] Application name from package.json:",
1029
+ packageJson.name
1030
+ );
1146
1031
  return { name: packageJson.name, packageJsonName: packageJson.name };
1147
1032
  } catch (err) {
1148
1033
  throw MicrofrontendError.handle(err, {
@@ -1151,6 +1036,209 @@ function getApplicationContext(opts) {
1151
1036
  }
1152
1037
  }
1153
1038
 
1039
+ // src/config/microfrontends/utils/infer-microfrontends-location.ts
1040
+ import { readFileSync, statSync } from "node:fs";
1041
+ import { dirname, join as join2 } from "node:path";
1042
+ import fg from "fast-glob";
1043
+ import { parse as parse2 } from "jsonc-parser";
1044
+ var configCache = {};
1045
+ function findPackageWithMicrofrontendsConfig({
1046
+ repositoryRoot,
1047
+ applicationContext,
1048
+ customConfigFilename
1049
+ }) {
1050
+ const applicationName = applicationContext.name;
1051
+ logger.debug(
1052
+ "[MFE Config] Searching repository for configs containing application:",
1053
+ applicationName
1054
+ );
1055
+ try {
1056
+ const microfrontendsJsonPaths = fg.globSync(
1057
+ `**/{${getPossibleConfigurationFilenames({ customConfigFilename }).join(",")}}`,
1058
+ {
1059
+ cwd: repositoryRoot,
1060
+ absolute: true,
1061
+ onlyFiles: true,
1062
+ followSymbolicLinks: false,
1063
+ ignore: ["**/node_modules/**", "**/.git/**"]
1064
+ }
1065
+ );
1066
+ logger.debug(
1067
+ "[MFE Config] Found",
1068
+ microfrontendsJsonPaths.length,
1069
+ "config file(s) in repository"
1070
+ );
1071
+ const matchingPaths = [];
1072
+ for (const microfrontendsJsonPath of microfrontendsJsonPaths) {
1073
+ if (doesApplicationExistInConfig(microfrontendsJsonPath, applicationName)) {
1074
+ matchingPaths.push(microfrontendsJsonPath);
1075
+ }
1076
+ }
1077
+ logger.debug(
1078
+ "[MFE Config] Total matching config files:",
1079
+ matchingPaths.length
1080
+ );
1081
+ if (matchingPaths.length > 1) {
1082
+ throw new MicrofrontendError(
1083
+ `Found multiple \`microfrontends.json\` files in the repository referencing the application "${applicationName}", but only one is allowed.
1084
+ ${matchingPaths.join("\n \u2022 ")}`,
1085
+ { type: "config", subtype: "inference_failed" }
1086
+ );
1087
+ }
1088
+ if (matchingPaths.length === 0) {
1089
+ if (repositoryRoot && doesMisplacedConfigExist(
1090
+ repositoryRoot,
1091
+ applicationName,
1092
+ customConfigFilename
1093
+ )) {
1094
+ logger.debug(
1095
+ "[MFE Config] Found misplaced config in wrong .vercel directory in repository"
1096
+ );
1097
+ const misplacedConfigPath = join2(
1098
+ repositoryRoot,
1099
+ ".vercel",
1100
+ customConfigFilename || "microfrontends.json"
1101
+ );
1102
+ throw new MicrofrontendError(
1103
+ `Unable to automatically infer the location of the \`microfrontends.json\` file.
1104
+
1105
+ A microfrontends config was found in the \`.vercel\` directory at the repository root: ${misplacedConfigPath}
1106
+ However, in a monorepo, the config file should be placed in the \`.vercel\` directory in your application directory instead.
1107
+
1108
+ To fix this:
1109
+ 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
1110
+ 2. If manually defined, move the config file to the \`.vercel\` directory in your application
1111
+ 3. Alternatively, set the VC_MICROFRONTENDS_CONFIG environment variable to the correct path
1112
+
1113
+ For more information, see: https://vercel.com/docs/cli/project-linking`,
1114
+ { type: "config", subtype: "inference_failed" }
1115
+ );
1116
+ }
1117
+ let additionalErrorMessage = "";
1118
+ if (microfrontendsJsonPaths.length > 0) {
1119
+ if (!applicationContext.projectName) {
1120
+ additionalErrorMessage = `
1121
+
1122
+ 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.`;
1123
+ } else {
1124
+ additionalErrorMessage = `
1125
+
1126
+ Names of applications in \`microfrontends.json\` must match the Vercel Project name (${applicationContext.projectName}).`;
1127
+ }
1128
+ }
1129
+ throw new MicrofrontendError(
1130
+ `Could not find a \`microfrontends.json\` file in the repository that contains the "${applicationName}" application.${additionalErrorMessage}
1131
+
1132
+ 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.
1133
+
1134
+ 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.
1135
+
1136
+ If you suspect this is thrown in error, please reach out to the Vercel team.`,
1137
+ { type: "config", subtype: "inference_failed" }
1138
+ );
1139
+ }
1140
+ const [packageJsonPath] = matchingPaths;
1141
+ return dirname(packageJsonPath);
1142
+ } catch (error2) {
1143
+ if (error2 instanceof MicrofrontendError) {
1144
+ throw error2;
1145
+ }
1146
+ return null;
1147
+ }
1148
+ }
1149
+ function inferMicrofrontendsLocation(opts) {
1150
+ const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}${opts.customConfigFilename ? `-${opts.customConfigFilename}` : ""}`;
1151
+ if (configCache[cacheKey]) {
1152
+ return configCache[cacheKey];
1153
+ }
1154
+ const result = findPackageWithMicrofrontendsConfig(opts);
1155
+ if (!result) {
1156
+ throw new MicrofrontendError(
1157
+ `Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationContext.name}" starting in directory "${opts.repositoryRoot}".`,
1158
+ { type: "config", subtype: "inference_failed" }
1159
+ );
1160
+ }
1161
+ configCache[cacheKey] = result;
1162
+ return result;
1163
+ }
1164
+ function existsSync(path7) {
1165
+ try {
1166
+ statSync(path7);
1167
+ return true;
1168
+ } catch (_) {
1169
+ return false;
1170
+ }
1171
+ }
1172
+ function doesMisplacedConfigExist(repositoryRoot, applicationName, customConfigFilename) {
1173
+ logger.debug(
1174
+ "[MFE Config] Looking for misplaced config in wrong .vercel directory"
1175
+ );
1176
+ const misplacedConfigPath = join2(
1177
+ repositoryRoot,
1178
+ ".vercel",
1179
+ customConfigFilename || "microfrontends.json"
1180
+ );
1181
+ return existsSync(misplacedConfigPath) && doesApplicationExistInConfig(misplacedConfigPath, applicationName);
1182
+ }
1183
+ function doesApplicationExistInConfig(microfrontendsJsonPath, applicationName) {
1184
+ try {
1185
+ const microfrontendsJsonContent = readFileSync(
1186
+ microfrontendsJsonPath,
1187
+ "utf-8"
1188
+ );
1189
+ const microfrontendsJson = parse2(microfrontendsJsonContent);
1190
+ if (microfrontendsJson.applications[applicationName]) {
1191
+ logger.debug(
1192
+ "[MFE Config] Found application in config:",
1193
+ microfrontendsJsonPath
1194
+ );
1195
+ return true;
1196
+ }
1197
+ for (const [_, app] of Object.entries(microfrontendsJson.applications)) {
1198
+ if (app.packageName === applicationName) {
1199
+ logger.debug(
1200
+ "[MFE Config] Found application via packageName in config:",
1201
+ microfrontendsJsonPath
1202
+ );
1203
+ return true;
1204
+ }
1205
+ }
1206
+ } catch (error2) {
1207
+ logger.debug("[MFE Config] Error checking application in config:", error2);
1208
+ }
1209
+ return false;
1210
+ }
1211
+
1212
+ // src/config/microfrontends/utils/is-monorepo.ts
1213
+ import fs5 from "node:fs";
1214
+ import path4 from "node:path";
1215
+ function isMonorepo({
1216
+ repositoryRoot
1217
+ }) {
1218
+ try {
1219
+ if (fs5.existsSync(path4.join(repositoryRoot, "pnpm-workspace.yaml"))) {
1220
+ return true;
1221
+ }
1222
+ if (fs5.existsSync(path4.join(repositoryRoot, "vlt-workspaces.json"))) {
1223
+ return true;
1224
+ }
1225
+ if (process.env.NX_WORKSPACE_ROOT === path4.resolve(repositoryRoot)) {
1226
+ return true;
1227
+ }
1228
+ const packageJsonPath = path4.join(repositoryRoot, "package.json");
1229
+ if (!fs5.existsSync(packageJsonPath)) {
1230
+ return false;
1231
+ }
1232
+ const packageJson = JSON.parse(
1233
+ fs5.readFileSync(packageJsonPath, "utf-8")
1234
+ );
1235
+ return packageJson.workspaces !== void 0;
1236
+ } catch (error2) {
1237
+ logger.error("Error determining if repository is a monorepo", error2);
1238
+ return false;
1239
+ }
1240
+ }
1241
+
1154
1242
  // src/config/microfrontends/server/utils/get-output-file-path.ts
1155
1243
  import path5 from "node:path";
1156
1244
 
@@ -1164,8 +1252,8 @@ function getOutputFilePath() {
1164
1252
  }
1165
1253
 
1166
1254
  // src/config/microfrontends/server/validation.ts
1167
- import { parse as parse3 } from "jsonc-parser";
1168
1255
  import { Ajv } from "ajv";
1256
+ import { parse as parse3 } from "jsonc-parser";
1169
1257
 
1170
1258
  // schema/schema.json
1171
1259
  var schema_default = {
@@ -1193,9 +1281,7 @@ var schema_default = {
1193
1281
  description: "Optional configuration options for the microfrontend."
1194
1282
  }
1195
1283
  },
1196
- required: [
1197
- "applications"
1198
- ],
1284
+ required: ["applications"],
1199
1285
  additionalProperties: false,
1200
1286
  description: "The microfrontends configuration schema. See https://vercel.com/docs/microfrontends/configuration."
1201
1287
  },
@@ -1232,19 +1318,14 @@ var schema_default = {
1232
1318
  description: "Development configuration for the default application."
1233
1319
  }
1234
1320
  },
1235
- required: [
1236
- "development"
1237
- ],
1321
+ required: ["development"],
1238
1322
  additionalProperties: false
1239
1323
  },
1240
1324
  DefaultDevelopment: {
1241
1325
  type: "object",
1242
1326
  properties: {
1243
1327
  local: {
1244
- type: [
1245
- "number",
1246
- "string"
1247
- ],
1328
+ type: ["number", "string"],
1248
1329
  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."
1249
1330
  },
1250
1331
  task: {
@@ -1256,9 +1337,7 @@ var schema_default = {
1256
1337
  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."
1257
1338
  }
1258
1339
  },
1259
- required: [
1260
- "fallback"
1261
- ],
1340
+ required: ["fallback"],
1262
1341
  additionalProperties: false
1263
1342
  },
1264
1343
  ChildApplication: {
@@ -1281,19 +1360,14 @@ var schema_default = {
1281
1360
  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."
1282
1361
  }
1283
1362
  },
1284
- required: [
1285
- "routing"
1286
- ],
1363
+ required: ["routing"],
1287
1364
  additionalProperties: false
1288
1365
  },
1289
1366
  ChildDevelopment: {
1290
1367
  type: "object",
1291
1368
  properties: {
1292
1369
  local: {
1293
- type: [
1294
- "number",
1295
- "string"
1296
- ],
1370
+ type: ["number", "string"],
1297
1371
  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."
1298
1372
  },
1299
1373
  task: {
@@ -1333,9 +1407,7 @@ var schema_default = {
1333
1407
  description: "A list of path expressions that are routed to this application. See https://vercel.com/docs/microfrontends/path-routing#supported-path-expressions."
1334
1408
  }
1335
1409
  },
1336
- required: [
1337
- "paths"
1338
- ],
1410
+ required: ["paths"],
1339
1411
  additionalProperties: false,
1340
1412
  description: "A group of paths that is routed to this application."
1341
1413
  },
@@ -1578,7 +1650,7 @@ var MicrofrontendsServer = class {
1578
1650
  });
1579
1651
  }
1580
1652
  } else {
1581
- const vercelDir = join2(packageRoot, ".vercel");
1653
+ const vercelDir = join3(packageRoot, ".vercel");
1582
1654
  logger.debug(
1583
1655
  "[MFE Config] Searching for config in .vercel directory:",
1584
1656
  vercelDir
@@ -1698,8 +1770,21 @@ var MicrofrontendsServer = class {
1698
1770
  };
1699
1771
 
1700
1772
  // src/utils/mfe-port.ts
1773
+ var MFE_APP_PORT_ENV2 = "MFE_APP_PORT";
1701
1774
  function mfePort(packageDir) {
1702
1775
  const { name: appName, version } = getPackageJson(packageDir);
1776
+ const portOverride = process.env[MFE_APP_PORT_ENV2];
1777
+ if (portOverride) {
1778
+ const port = Number.parseInt(portOverride, 10);
1779
+ if (!Number.isNaN(port) && port > 0 && port < 65536) {
1780
+ return {
1781
+ name: appName,
1782
+ version,
1783
+ port,
1784
+ overridden: true
1785
+ };
1786
+ }
1787
+ }
1703
1788
  try {
1704
1789
  const result = loadConfig({ packageDir, appName });
1705
1790
  const { port } = result;
@@ -1730,6 +1815,7 @@ function loadConfig({
1730
1815
  return { port };
1731
1816
  }
1732
1817
  export {
1818
+ MFE_APP_PORT_ENV2 as MFE_APP_PORT_ENV,
1733
1819
  mfePort
1734
1820
  };
1735
1821
  //# sourceMappingURL=mfe-port.js.map