@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.
- package/CHANGELOG.md +25 -0
- package/cli/index.cjs +0 -1
- package/dist/bin/cli.cjs +551 -381
- package/dist/config.cjs +191 -169
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.ts +3 -2
- package/dist/config.js +192 -170
- package/dist/config.js.map +1 -1
- package/dist/experimental/sveltekit.cjs +643 -489
- package/dist/experimental/sveltekit.cjs.map +1 -1
- package/dist/experimental/sveltekit.js +645 -491
- package/dist/experimental/sveltekit.js.map +1 -1
- package/dist/experimental/vite.cjs +671 -519
- package/dist/experimental/vite.cjs.map +1 -1
- package/dist/experimental/vite.js +669 -517
- package/dist/experimental/vite.js.map +1 -1
- package/dist/microfrontends/server.cjs +661 -509
- package/dist/microfrontends/server.cjs.map +1 -1
- package/dist/microfrontends/server.d.ts +2 -2
- package/dist/microfrontends/server.js +663 -511
- package/dist/microfrontends/server.js.map +1 -1
- package/dist/microfrontends/utils.cjs +117 -23
- package/dist/microfrontends/utils.cjs.map +1 -1
- package/dist/microfrontends/utils.d.ts +4 -4
- package/dist/microfrontends/utils.js +118 -24
- package/dist/microfrontends/utils.js.map +1 -1
- package/dist/next/client.cjs +1 -1
- package/dist/next/client.cjs.map +1 -1
- package/dist/next/client.d.ts +8 -8
- package/dist/next/client.js +1 -1
- package/dist/next/client.js.map +1 -1
- package/dist/next/config.cjs +784 -626
- package/dist/next/config.cjs.map +1 -1
- package/dist/next/config.js +782 -624
- package/dist/next/config.js.map +1 -1
- package/dist/next/middleware.cjs +244 -222
- package/dist/next/middleware.cjs.map +1 -1
- package/dist/next/middleware.js +245 -223
- package/dist/next/middleware.js.map +1 -1
- package/dist/next/testing.cjs +192 -170
- package/dist/next/testing.cjs.map +1 -1
- package/dist/next/testing.d.ts +1 -1
- package/dist/next/testing.js +193 -171
- package/dist/next/testing.js.map +1 -1
- package/dist/overrides.cjs +5 -5
- package/dist/overrides.cjs.map +1 -1
- package/dist/overrides.d.ts +9 -9
- package/dist/overrides.js +5 -5
- package/dist/overrides.js.map +1 -1
- package/dist/utils/mfe-port.cjs +689 -522
- package/dist/utils/mfe-port.cjs.map +1 -1
- package/dist/utils/mfe-port.d.ts +9 -1
- package/dist/utils/mfe-port.js +687 -521
- package/dist/utils/mfe-port.js.map +1 -1
- package/dist/validation.cjs +8 -24
- package/dist/validation.cjs.map +1 -1
- package/dist/validation.js +8 -24
- package/dist/validation.js.map +1 -1
- package/package.json +4 -6
package/dist/next/config.cjs
CHANGED
|
@@ -73,39 +73,6 @@ function displayLocalProxyInfo(port) {
|
|
|
73
73
|
var import_node_fs7 = __toESM(require("fs"), 1);
|
|
74
74
|
var import_node_path8 = require("path");
|
|
75
75
|
|
|
76
|
-
// src/config/overrides/constants.ts
|
|
77
|
-
var OVERRIDES_COOKIE_PREFIX = "vercel-micro-frontends-override";
|
|
78
|
-
var OVERRIDES_ENV_COOKIE_PREFIX = `${OVERRIDES_COOKIE_PREFIX}:env:`;
|
|
79
|
-
|
|
80
|
-
// src/config/overrides/is-override-cookie.ts
|
|
81
|
-
function isOverrideCookie(cookie) {
|
|
82
|
-
return Boolean(cookie.name?.startsWith(OVERRIDES_COOKIE_PREFIX));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// src/config/overrides/get-override-from-cookie.ts
|
|
86
|
-
function getOverrideFromCookie(cookie) {
|
|
87
|
-
if (!isOverrideCookie(cookie) || !cookie.value)
|
|
88
|
-
return;
|
|
89
|
-
return {
|
|
90
|
-
application: cookie.name.replace(OVERRIDES_ENV_COOKIE_PREFIX, ""),
|
|
91
|
-
host: cookie.value
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// src/config/overrides/parse-overrides.ts
|
|
96
|
-
function parseOverrides(cookies) {
|
|
97
|
-
const overridesConfig = { applications: {} };
|
|
98
|
-
cookies.forEach((cookie) => {
|
|
99
|
-
const override = getOverrideFromCookie(cookie);
|
|
100
|
-
if (!override)
|
|
101
|
-
return;
|
|
102
|
-
overridesConfig.applications[override.application] = {
|
|
103
|
-
environment: { host: override.host }
|
|
104
|
-
};
|
|
105
|
-
});
|
|
106
|
-
return overridesConfig;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
76
|
// src/config/errors.ts
|
|
110
77
|
var MicrofrontendError = class extends Error {
|
|
111
78
|
constructor(message, opts) {
|
|
@@ -198,253 +165,47 @@ var MicrofrontendError = class extends Error {
|
|
|
198
165
|
}
|
|
199
166
|
};
|
|
200
167
|
|
|
201
|
-
// src/config/microfrontends-config/
|
|
202
|
-
function getConfigStringFromEnv() {
|
|
203
|
-
const config = process.env.MFE_CONFIG;
|
|
204
|
-
if (!config) {
|
|
205
|
-
throw new MicrofrontendError(`Missing "MFE_CONFIG" in environment.`, {
|
|
206
|
-
type: "config",
|
|
207
|
-
subtype: "not_found_in_env"
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
return config;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// src/config/schema/utils/is-default-app.ts
|
|
214
|
-
function isDefaultApp(a) {
|
|
215
|
-
return !("routing" in a);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// src/config/microfrontends/utils/find-repository-root.ts
|
|
219
|
-
var import_node_fs = __toESM(require("fs"), 1);
|
|
220
|
-
var import_node_path = __toESM(require("path"), 1);
|
|
221
|
-
var GIT_DIRECTORY = ".git";
|
|
222
|
-
function hasGitDirectory(dir) {
|
|
223
|
-
const gitPath = import_node_path.default.join(dir, GIT_DIRECTORY);
|
|
224
|
-
return import_node_fs.default.existsSync(gitPath) && import_node_fs.default.statSync(gitPath).isDirectory();
|
|
225
|
-
}
|
|
226
|
-
function hasPnpmWorkspaces(dir) {
|
|
227
|
-
return import_node_fs.default.existsSync(import_node_path.default.join(dir, "pnpm-workspace.yaml"));
|
|
228
|
-
}
|
|
229
|
-
function hasPackageJson(dir) {
|
|
230
|
-
return import_node_fs.default.existsSync(import_node_path.default.join(dir, "package.json"));
|
|
231
|
-
}
|
|
232
|
-
function findRepositoryRoot(startDir) {
|
|
233
|
-
if (process.env.NX_WORKSPACE_ROOT) {
|
|
234
|
-
return process.env.NX_WORKSPACE_ROOT;
|
|
235
|
-
}
|
|
236
|
-
let currentDir = startDir || process.cwd();
|
|
237
|
-
let lastPackageJsonDir = null;
|
|
238
|
-
while (currentDir !== import_node_path.default.parse(currentDir).root) {
|
|
239
|
-
if (hasGitDirectory(currentDir) || hasPnpmWorkspaces(currentDir)) {
|
|
240
|
-
return currentDir;
|
|
241
|
-
}
|
|
242
|
-
if (hasPackageJson(currentDir)) {
|
|
243
|
-
lastPackageJsonDir = currentDir;
|
|
244
|
-
}
|
|
245
|
-
currentDir = import_node_path.default.dirname(currentDir);
|
|
246
|
-
}
|
|
247
|
-
if (lastPackageJsonDir) {
|
|
248
|
-
return lastPackageJsonDir;
|
|
249
|
-
}
|
|
250
|
-
throw new Error(
|
|
251
|
-
`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.`
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// src/config/microfrontends/utils/infer-microfrontends-location.ts
|
|
256
|
-
var import_node_path2 = require("path");
|
|
257
|
-
var import_node_fs2 = require("fs");
|
|
168
|
+
// src/config/microfrontends-config/isomorphic/index.ts
|
|
258
169
|
var import_jsonc_parser = require("jsonc-parser");
|
|
259
|
-
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
260
170
|
|
|
261
|
-
// src/config/
|
|
262
|
-
var
|
|
263
|
-
|
|
264
|
-
"microfrontends.jsonc"
|
|
265
|
-
];
|
|
266
|
-
function getPossibleConfigurationFilenames({
|
|
267
|
-
customConfigFilename
|
|
268
|
-
}) {
|
|
269
|
-
if (customConfigFilename) {
|
|
270
|
-
if (!customConfigFilename.endsWith(".json") && !customConfigFilename.endsWith(".jsonc")) {
|
|
271
|
-
throw new Error(
|
|
272
|
-
`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.`
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
return Array.from(
|
|
276
|
-
/* @__PURE__ */ new Set([customConfigFilename, ...DEFAULT_CONFIGURATION_FILENAMES])
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
return DEFAULT_CONFIGURATION_FILENAMES;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// src/config/microfrontends/utils/infer-microfrontends-location.ts
|
|
283
|
-
var configCache = {};
|
|
284
|
-
function findPackageWithMicrofrontendsConfig({
|
|
285
|
-
repositoryRoot,
|
|
286
|
-
applicationContext,
|
|
287
|
-
customConfigFilename
|
|
288
|
-
}) {
|
|
289
|
-
const applicationName = applicationContext.name;
|
|
290
|
-
try {
|
|
291
|
-
const microfrontendsJsonPaths = import_fast_glob.default.globSync(
|
|
292
|
-
`**/{${getPossibleConfigurationFilenames({ customConfigFilename }).join(",")}}`,
|
|
293
|
-
{
|
|
294
|
-
cwd: repositoryRoot,
|
|
295
|
-
absolute: true,
|
|
296
|
-
onlyFiles: true,
|
|
297
|
-
followSymbolicLinks: false,
|
|
298
|
-
ignore: ["**/node_modules/**", "**/.git/**"]
|
|
299
|
-
}
|
|
300
|
-
);
|
|
301
|
-
const matchingPaths = [];
|
|
302
|
-
for (const microfrontendsJsonPath of microfrontendsJsonPaths) {
|
|
303
|
-
try {
|
|
304
|
-
const microfrontendsJsonContent = (0, import_node_fs2.readFileSync)(
|
|
305
|
-
microfrontendsJsonPath,
|
|
306
|
-
"utf-8"
|
|
307
|
-
);
|
|
308
|
-
const microfrontendsJson = (0, import_jsonc_parser.parse)(microfrontendsJsonContent);
|
|
309
|
-
if (microfrontendsJson.applications[applicationName]) {
|
|
310
|
-
matchingPaths.push(microfrontendsJsonPath);
|
|
311
|
-
} else {
|
|
312
|
-
for (const [_, app] of Object.entries(
|
|
313
|
-
microfrontendsJson.applications
|
|
314
|
-
)) {
|
|
315
|
-
if (app.packageName === applicationName) {
|
|
316
|
-
matchingPaths.push(microfrontendsJsonPath);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
} catch (error2) {
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
if (matchingPaths.length > 1) {
|
|
324
|
-
throw new MicrofrontendError(
|
|
325
|
-
`Found multiple \`microfrontends.json\` files in the repository referencing the application "${applicationName}", but only one is allowed.
|
|
326
|
-
${matchingPaths.join("\n \u2022 ")}`,
|
|
327
|
-
{ type: "config", subtype: "inference_failed" }
|
|
328
|
-
);
|
|
329
|
-
}
|
|
330
|
-
if (matchingPaths.length === 0) {
|
|
331
|
-
let additionalErrorMessage = "";
|
|
332
|
-
if (microfrontendsJsonPaths.length > 0) {
|
|
333
|
-
if (!applicationContext.projectName) {
|
|
334
|
-
additionalErrorMessage = `
|
|
335
|
-
|
|
336
|
-
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.`;
|
|
337
|
-
} else {
|
|
338
|
-
additionalErrorMessage = `
|
|
339
|
-
|
|
340
|
-
Names of applications in \`microfrontends.json\` must match the Vercel Project name (${applicationContext.projectName}).`;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
throw new MicrofrontendError(
|
|
344
|
-
`Could not find a \`microfrontends.json\` file in the repository that contains the "${applicationName}" application.${additionalErrorMessage}
|
|
345
|
-
|
|
346
|
-
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.
|
|
347
|
-
|
|
348
|
-
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.
|
|
171
|
+
// src/config/overrides/constants.ts
|
|
172
|
+
var OVERRIDES_COOKIE_PREFIX = "vercel-micro-frontends-override";
|
|
173
|
+
var OVERRIDES_ENV_COOKIE_PREFIX = `${OVERRIDES_COOKIE_PREFIX}:env:`;
|
|
349
174
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
const [packageJsonPath] = matchingPaths;
|
|
355
|
-
return (0, import_node_path2.dirname)(packageJsonPath);
|
|
356
|
-
} catch (error2) {
|
|
357
|
-
if (error2 instanceof MicrofrontendError) {
|
|
358
|
-
throw error2;
|
|
359
|
-
}
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
function inferMicrofrontendsLocation(opts) {
|
|
364
|
-
const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}${opts.customConfigFilename ? `-${opts.customConfigFilename}` : ""}`;
|
|
365
|
-
if (configCache[cacheKey]) {
|
|
366
|
-
return configCache[cacheKey];
|
|
367
|
-
}
|
|
368
|
-
const result = findPackageWithMicrofrontendsConfig(opts);
|
|
369
|
-
if (!result) {
|
|
370
|
-
throw new MicrofrontendError(
|
|
371
|
-
`Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationContext.name}" starting in directory "${opts.repositoryRoot}".`,
|
|
372
|
-
{ type: "config", subtype: "inference_failed" }
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
configCache[cacheKey] = result;
|
|
376
|
-
return result;
|
|
175
|
+
// src/config/overrides/is-override-cookie.ts
|
|
176
|
+
function isOverrideCookie(cookie) {
|
|
177
|
+
return Boolean(cookie.name?.startsWith(OVERRIDES_COOKIE_PREFIX));
|
|
377
178
|
}
|
|
378
179
|
|
|
379
|
-
// src/config/
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
return true;
|
|
388
|
-
}
|
|
389
|
-
if (import_node_fs3.default.existsSync(import_node_path3.default.join(repositoryRoot, "vlt-workspaces.json"))) {
|
|
390
|
-
return true;
|
|
391
|
-
}
|
|
392
|
-
if (process.env.NX_WORKSPACE_ROOT === import_node_path3.default.resolve(repositoryRoot)) {
|
|
393
|
-
return true;
|
|
394
|
-
}
|
|
395
|
-
const packageJsonPath = import_node_path3.default.join(repositoryRoot, "package.json");
|
|
396
|
-
if (!import_node_fs3.default.existsSync(packageJsonPath)) {
|
|
397
|
-
return false;
|
|
398
|
-
}
|
|
399
|
-
const packageJson = JSON.parse(
|
|
400
|
-
import_node_fs3.default.readFileSync(packageJsonPath, "utf-8")
|
|
401
|
-
);
|
|
402
|
-
return packageJson.workspaces !== void 0;
|
|
403
|
-
} catch (error2) {
|
|
404
|
-
logger.error("Error determining if repository is a monorepo", error2);
|
|
405
|
-
return false;
|
|
406
|
-
}
|
|
180
|
+
// src/config/overrides/get-override-from-cookie.ts
|
|
181
|
+
function getOverrideFromCookie(cookie) {
|
|
182
|
+
if (!isOverrideCookie(cookie) || !cookie.value)
|
|
183
|
+
return;
|
|
184
|
+
return {
|
|
185
|
+
application: cookie.name.replace(OVERRIDES_ENV_COOKIE_PREFIX, ""),
|
|
186
|
+
host: cookie.value
|
|
187
|
+
};
|
|
407
188
|
}
|
|
408
189
|
|
|
409
|
-
// src/config/
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
}
|
|
422
|
-
throw new Error(
|
|
423
|
-
`The root of the package that contains the \`package.json\` file for the \`${startDir}\` directory could not be found.`
|
|
424
|
-
);
|
|
190
|
+
// src/config/overrides/parse-overrides.ts
|
|
191
|
+
function parseOverrides(cookies) {
|
|
192
|
+
const overridesConfig = { applications: {} };
|
|
193
|
+
cookies.forEach((cookie) => {
|
|
194
|
+
const override = getOverrideFromCookie(cookie);
|
|
195
|
+
if (!override)
|
|
196
|
+
return;
|
|
197
|
+
overridesConfig.applications[override.application] = {
|
|
198
|
+
environment: { host: override.host }
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
return overridesConfig;
|
|
425
202
|
}
|
|
426
203
|
|
|
427
|
-
// src/config/
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
function findConfig({
|
|
431
|
-
dir,
|
|
432
|
-
customConfigFilename
|
|
433
|
-
}) {
|
|
434
|
-
for (const filename of getPossibleConfigurationFilenames({
|
|
435
|
-
customConfigFilename
|
|
436
|
-
})) {
|
|
437
|
-
const maybeConfig = (0, import_node_path5.join)(dir, filename);
|
|
438
|
-
if (import_node_fs5.default.existsSync(maybeConfig)) {
|
|
439
|
-
return maybeConfig;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
return null;
|
|
204
|
+
// src/config/schema/utils/is-default-app.ts
|
|
205
|
+
function isDefaultApp(a) {
|
|
206
|
+
return !("routing" in a);
|
|
443
207
|
}
|
|
444
208
|
|
|
445
|
-
// src/config/microfrontends-config/isomorphic/index.ts
|
|
446
|
-
var import_jsonc_parser2 = require("jsonc-parser");
|
|
447
|
-
|
|
448
209
|
// src/config/microfrontends-config/client/index.ts
|
|
449
210
|
var import_path_to_regexp = require("path-to-regexp");
|
|
450
211
|
var regexpCache = /* @__PURE__ */ new Map();
|
|
@@ -508,41 +269,218 @@ var MicrofrontendConfigClient = class {
|
|
|
508
269
|
isEqual(other) {
|
|
509
270
|
return this === other || JSON.stringify(this.applications) === JSON.stringify(other.applications);
|
|
510
271
|
}
|
|
511
|
-
getApplicationNameForPath(path6) {
|
|
512
|
-
if (!path6.startsWith("/")) {
|
|
513
|
-
throw new Error(`Path must start with a /`);
|
|
514
|
-
}
|
|
515
|
-
if (this.pathCache[path6]) {
|
|
516
|
-
return this.pathCache[path6];
|
|
517
|
-
}
|
|
518
|
-
const pathname = new URL(path6, "https://example.com").pathname;
|
|
519
|
-
for (const [name, application] of Object.entries(this.applications)) {
|
|
520
|
-
if (application.routing) {
|
|
521
|
-
for (const group of application.routing) {
|
|
522
|
-
for (const childPath of group.paths) {
|
|
523
|
-
const regexp = getRegexp(childPath);
|
|
524
|
-
if (regexp.test(pathname)) {
|
|
525
|
-
this.pathCache[path6] = name;
|
|
526
|
-
return name;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
272
|
+
getApplicationNameForPath(path6) {
|
|
273
|
+
if (!path6.startsWith("/")) {
|
|
274
|
+
throw new Error(`Path must start with a /`);
|
|
275
|
+
}
|
|
276
|
+
if (this.pathCache[path6]) {
|
|
277
|
+
return this.pathCache[path6];
|
|
278
|
+
}
|
|
279
|
+
const pathname = new URL(path6, "https://example.com").pathname;
|
|
280
|
+
for (const [name, application] of Object.entries(this.applications)) {
|
|
281
|
+
if (application.routing) {
|
|
282
|
+
for (const group of application.routing) {
|
|
283
|
+
for (const childPath of group.paths) {
|
|
284
|
+
const regexp = getRegexp(childPath);
|
|
285
|
+
if (regexp.test(pathname)) {
|
|
286
|
+
this.pathCache[path6] = name;
|
|
287
|
+
return name;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const defaultApplication = Object.entries(this.applications).find(
|
|
294
|
+
([, application]) => application.default
|
|
295
|
+
);
|
|
296
|
+
if (!defaultApplication) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
this.pathCache[path6] = defaultApplication[0];
|
|
300
|
+
return defaultApplication[0];
|
|
301
|
+
}
|
|
302
|
+
serialize() {
|
|
303
|
+
return this.serialized;
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// src/config/microfrontends-config/utils/get-config-from-env.ts
|
|
308
|
+
function getConfigStringFromEnv() {
|
|
309
|
+
const config = process.env.MFE_CONFIG;
|
|
310
|
+
if (!config) {
|
|
311
|
+
throw new MicrofrontendError(`Missing "MFE_CONFIG" in environment.`, {
|
|
312
|
+
type: "config",
|
|
313
|
+
subtype: "not_found_in_env"
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return config;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/config/microfrontends-config/isomorphic/constants.ts
|
|
320
|
+
var DEFAULT_LOCAL_PROXY_PORT = 3024;
|
|
321
|
+
var MFE_APP_PORT_ENV = "MFE_APP_PORT";
|
|
322
|
+
var MFE_LOCAL_PROXY_PORT_ENV = "MFE_LOCAL_PROXY_PORT";
|
|
323
|
+
|
|
324
|
+
// src/config/microfrontends-config/isomorphic/utils/generate-port.ts
|
|
325
|
+
function generatePortFromName({
|
|
326
|
+
name,
|
|
327
|
+
minPort = 3e3,
|
|
328
|
+
maxPort = 8e3
|
|
329
|
+
}) {
|
|
330
|
+
if (!name) {
|
|
331
|
+
throw new Error("Name is required to generate a port");
|
|
332
|
+
}
|
|
333
|
+
let hash = 0;
|
|
334
|
+
for (let i = 0; i < name.length; i++) {
|
|
335
|
+
hash = (hash << 5) - hash + name.charCodeAt(i);
|
|
336
|
+
hash |= 0;
|
|
337
|
+
}
|
|
338
|
+
hash = Math.abs(hash);
|
|
339
|
+
const range = maxPort - minPort;
|
|
340
|
+
const port = minPort + hash % range;
|
|
341
|
+
return port;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// src/config/microfrontends-config/isomorphic/host.ts
|
|
345
|
+
var Host = class {
|
|
346
|
+
constructor(hostConfig, options) {
|
|
347
|
+
if (typeof hostConfig === "string") {
|
|
348
|
+
({
|
|
349
|
+
protocol: this.protocol,
|
|
350
|
+
host: this.host,
|
|
351
|
+
port: this.port
|
|
352
|
+
} = Host.parseUrl(hostConfig));
|
|
353
|
+
} else {
|
|
354
|
+
const { protocol = "https", host, port } = hostConfig;
|
|
355
|
+
this.protocol = protocol;
|
|
356
|
+
this.host = host;
|
|
357
|
+
this.port = port;
|
|
358
|
+
}
|
|
359
|
+
this.local = options?.isLocal;
|
|
360
|
+
}
|
|
361
|
+
static parseUrl(url, defaultProtocol = "https") {
|
|
362
|
+
let hostToParse = url;
|
|
363
|
+
if (!/^https?:\/\//.exec(hostToParse)) {
|
|
364
|
+
hostToParse = `${defaultProtocol}://${hostToParse}`;
|
|
365
|
+
}
|
|
366
|
+
const parsed = new URL(hostToParse);
|
|
367
|
+
if (!parsed.hostname) {
|
|
368
|
+
throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
|
|
369
|
+
}
|
|
370
|
+
if (parsed.hash) {
|
|
371
|
+
throw new Error(
|
|
372
|
+
Host.getMicrofrontendsError(url, "cannot have a fragment")
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
if (parsed.username || parsed.password) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
Host.getMicrofrontendsError(
|
|
378
|
+
url,
|
|
379
|
+
"cannot have authentication credentials (username and/or password)"
|
|
380
|
+
)
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
if (parsed.pathname !== "/") {
|
|
384
|
+
throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
|
|
385
|
+
}
|
|
386
|
+
if (parsed.search) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
Host.getMicrofrontendsError(url, "cannot have query parameters")
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
const protocol = parsed.protocol.slice(0, -1);
|
|
392
|
+
return {
|
|
393
|
+
protocol,
|
|
394
|
+
host: parsed.hostname,
|
|
395
|
+
port: parsed.port ? Number.parseInt(parsed.port, 10) : void 0
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
static getMicrofrontendsError(url, message) {
|
|
399
|
+
return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
|
|
400
|
+
}
|
|
401
|
+
isLocal() {
|
|
402
|
+
return this.local || this.host === "localhost" || this.host === "127.0.0.1";
|
|
403
|
+
}
|
|
404
|
+
toString() {
|
|
405
|
+
const url = this.toUrl();
|
|
406
|
+
return url.toString().replace(/\/$/, "");
|
|
407
|
+
}
|
|
408
|
+
toUrl() {
|
|
409
|
+
const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
|
|
410
|
+
return new URL(url);
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
var LocalHost = class extends Host {
|
|
414
|
+
constructor({
|
|
415
|
+
appName,
|
|
416
|
+
local
|
|
417
|
+
}) {
|
|
418
|
+
const portOverride = process.env[MFE_APP_PORT_ENV];
|
|
419
|
+
if (portOverride) {
|
|
420
|
+
const overridePort = Number.parseInt(portOverride, 10);
|
|
421
|
+
if (!Number.isNaN(overridePort) && overridePort > 0 && overridePort < 65536) {
|
|
422
|
+
super({
|
|
423
|
+
protocol: "http",
|
|
424
|
+
host: "localhost",
|
|
425
|
+
port: overridePort
|
|
426
|
+
});
|
|
427
|
+
return;
|
|
530
428
|
}
|
|
531
429
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
if (
|
|
536
|
-
|
|
430
|
+
let protocol;
|
|
431
|
+
let host;
|
|
432
|
+
let port;
|
|
433
|
+
if (typeof local === "number") {
|
|
434
|
+
port = local;
|
|
435
|
+
} else if (typeof local === "string") {
|
|
436
|
+
if (/^\d+$/.test(local)) {
|
|
437
|
+
port = Number.parseInt(local, 10);
|
|
438
|
+
} else {
|
|
439
|
+
const parsed = Host.parseUrl(local, "http");
|
|
440
|
+
protocol = parsed.protocol;
|
|
441
|
+
host = parsed.host;
|
|
442
|
+
port = parsed.port;
|
|
443
|
+
}
|
|
444
|
+
} else if (local) {
|
|
445
|
+
protocol = local.protocol;
|
|
446
|
+
host = local.host;
|
|
447
|
+
port = local.port;
|
|
537
448
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
449
|
+
super({
|
|
450
|
+
protocol: protocol ?? "http",
|
|
451
|
+
host: host ?? "localhost",
|
|
452
|
+
port: port ?? generatePortFromName({ name: appName })
|
|
453
|
+
});
|
|
543
454
|
}
|
|
544
455
|
};
|
|
545
456
|
|
|
457
|
+
// src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts
|
|
458
|
+
var import_md5 = __toESM(require("md5"), 1);
|
|
459
|
+
function hashApplicationName(name) {
|
|
460
|
+
if (!name) {
|
|
461
|
+
throw new Error("Application name is required to generate hash");
|
|
462
|
+
}
|
|
463
|
+
return (0, import_md5.default)(name).substring(0, 6).padStart(6, "0");
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts
|
|
467
|
+
var PREFIX = "vc-ap";
|
|
468
|
+
function generateAssetPrefixFromName({
|
|
469
|
+
name
|
|
470
|
+
}) {
|
|
471
|
+
if (!name) {
|
|
472
|
+
throw new Error("Name is required to generate an asset prefix");
|
|
473
|
+
}
|
|
474
|
+
return `${PREFIX}-${hashApplicationName(name)}`;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/config/microfrontends-config/isomorphic/utils/generate-automation-bypass-env-var-name.ts
|
|
478
|
+
function generateAutomationBypassEnvVarName({
|
|
479
|
+
name
|
|
480
|
+
}) {
|
|
481
|
+
return `AUTOMATION_BYPASS_${name.toUpperCase().replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
482
|
+
}
|
|
483
|
+
|
|
546
484
|
// src/config/microfrontends-config/isomorphic/validation.ts
|
|
547
485
|
var import_path_to_regexp2 = require("path-to-regexp");
|
|
548
486
|
var LIST_FORMATTER = new Intl.ListFormat("en", {
|
|
@@ -691,186 +629,38 @@ var validateAppPaths = (name, app) => {
|
|
|
691
629
|
`When \`assetPrefix\` is specified, \`/${app.assetPrefix}/:path*\` must be added the routing paths for the application. Changing the asset prefix is not a forwards and backwards compatible change, and the custom asset prefix should be added to \`paths\` and deployed before setting the \`assetPrefix\` field.`,
|
|
692
630
|
{ type: "application", subtype: "invalid_asset_prefix" }
|
|
693
631
|
);
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
};
|
|
697
|
-
var validateConfigDefaultApplication = (applicationConfigsById) => {
|
|
698
|
-
if (!applicationConfigsById) {
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
const applicationsWithoutRouting = Object.entries(
|
|
702
|
-
applicationConfigsById
|
|
703
|
-
).filter(([, app]) => isDefaultApp(app));
|
|
704
|
-
const numApplicationsWithoutRouting = applicationsWithoutRouting.reduce(
|
|
705
|
-
(acc) => {
|
|
706
|
-
return acc + 1;
|
|
707
|
-
},
|
|
708
|
-
0
|
|
709
|
-
);
|
|
710
|
-
if (numApplicationsWithoutRouting === 0) {
|
|
711
|
-
throw new MicrofrontendError(
|
|
712
|
-
"No default application found. At least one application needs to be the default by omitting routing.",
|
|
713
|
-
{ type: "config", subtype: "no_default_application" }
|
|
714
|
-
);
|
|
715
|
-
}
|
|
716
|
-
if (numApplicationsWithoutRouting > 1) {
|
|
717
|
-
const applicationNamesMissingRouting = applicationsWithoutRouting.map(
|
|
718
|
-
([name]) => name
|
|
719
|
-
);
|
|
720
|
-
throw new MicrofrontendError(
|
|
721
|
-
`All applications except for the default app must contain the "routing" field. Applications that are missing routing: ${LIST_FORMATTER.format(applicationNamesMissingRouting)}.`,
|
|
722
|
-
{ type: "config", subtype: "multiple_default_applications" }
|
|
723
|
-
);
|
|
724
|
-
}
|
|
725
|
-
};
|
|
726
|
-
|
|
727
|
-
// src/config/microfrontends-config/isomorphic/utils/hash-application-name.ts
|
|
728
|
-
var import_md5 = __toESM(require("md5"), 1);
|
|
729
|
-
function hashApplicationName(name) {
|
|
730
|
-
if (!name) {
|
|
731
|
-
throw new Error("Application name is required to generate hash");
|
|
732
|
-
}
|
|
733
|
-
return (0, import_md5.default)(name).substring(0, 6).padStart(6, "0");
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
// src/config/microfrontends-config/isomorphic/utils/generate-asset-prefix.ts
|
|
737
|
-
var PREFIX = "vc-ap";
|
|
738
|
-
function generateAssetPrefixFromName({
|
|
739
|
-
name
|
|
740
|
-
}) {
|
|
741
|
-
if (!name) {
|
|
742
|
-
throw new Error("Name is required to generate an asset prefix");
|
|
743
|
-
}
|
|
744
|
-
return `${PREFIX}-${hashApplicationName(name)}`;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// src/config/microfrontends-config/isomorphic/utils/generate-port.ts
|
|
748
|
-
function generatePortFromName({
|
|
749
|
-
name,
|
|
750
|
-
minPort = 3e3,
|
|
751
|
-
maxPort = 8e3
|
|
752
|
-
}) {
|
|
753
|
-
if (!name) {
|
|
754
|
-
throw new Error("Name is required to generate a port");
|
|
755
|
-
}
|
|
756
|
-
let hash = 0;
|
|
757
|
-
for (let i = 0; i < name.length; i++) {
|
|
758
|
-
hash = (hash << 5) - hash + name.charCodeAt(i);
|
|
759
|
-
hash |= 0;
|
|
760
|
-
}
|
|
761
|
-
hash = Math.abs(hash);
|
|
762
|
-
const range = maxPort - minPort;
|
|
763
|
-
const port = minPort + hash % range;
|
|
764
|
-
return port;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// src/config/microfrontends-config/isomorphic/host.ts
|
|
768
|
-
var Host = class {
|
|
769
|
-
constructor(hostConfig, options) {
|
|
770
|
-
if (typeof hostConfig === "string") {
|
|
771
|
-
({
|
|
772
|
-
protocol: this.protocol,
|
|
773
|
-
host: this.host,
|
|
774
|
-
port: this.port
|
|
775
|
-
} = Host.parseUrl(hostConfig));
|
|
776
|
-
} else {
|
|
777
|
-
const { protocol = "https", host, port } = hostConfig;
|
|
778
|
-
this.protocol = protocol;
|
|
779
|
-
this.host = host;
|
|
780
|
-
this.port = port;
|
|
781
|
-
}
|
|
782
|
-
this.local = options?.isLocal;
|
|
783
|
-
}
|
|
784
|
-
static parseUrl(url, defaultProtocol = "https") {
|
|
785
|
-
let hostToParse = url;
|
|
786
|
-
if (!/^https?:\/\//.exec(hostToParse)) {
|
|
787
|
-
hostToParse = `${defaultProtocol}://${hostToParse}`;
|
|
788
|
-
}
|
|
789
|
-
const parsed = new URL(hostToParse);
|
|
790
|
-
if (!parsed.hostname) {
|
|
791
|
-
throw new Error(Host.getMicrofrontendsError(url, "requires a host"));
|
|
792
|
-
}
|
|
793
|
-
if (parsed.hash) {
|
|
794
|
-
throw new Error(
|
|
795
|
-
Host.getMicrofrontendsError(url, "cannot have a fragment")
|
|
796
|
-
);
|
|
797
|
-
}
|
|
798
|
-
if (parsed.username || parsed.password) {
|
|
799
|
-
throw new Error(
|
|
800
|
-
Host.getMicrofrontendsError(
|
|
801
|
-
url,
|
|
802
|
-
"cannot have authentication credentials (username and/or password)"
|
|
803
|
-
)
|
|
804
|
-
);
|
|
805
|
-
}
|
|
806
|
-
if (parsed.pathname !== "/") {
|
|
807
|
-
throw new Error(Host.getMicrofrontendsError(url, "cannot have a path"));
|
|
808
|
-
}
|
|
809
|
-
if (parsed.search) {
|
|
810
|
-
throw new Error(
|
|
811
|
-
Host.getMicrofrontendsError(url, "cannot have query parameters")
|
|
812
|
-
);
|
|
813
|
-
}
|
|
814
|
-
const protocol = parsed.protocol.slice(0, -1);
|
|
815
|
-
return {
|
|
816
|
-
protocol,
|
|
817
|
-
host: parsed.hostname,
|
|
818
|
-
port: parsed.port ? Number.parseInt(parsed.port) : void 0
|
|
819
|
-
};
|
|
820
|
-
}
|
|
821
|
-
static getMicrofrontendsError(url, message) {
|
|
822
|
-
return `Microfrontends configuration error: the URL ${url} in your microfrontends.json ${message}.`;
|
|
823
|
-
}
|
|
824
|
-
isLocal() {
|
|
825
|
-
return this.local || this.host === "localhost" || this.host === "127.0.0.1";
|
|
826
|
-
}
|
|
827
|
-
toString() {
|
|
828
|
-
const url = this.toUrl();
|
|
829
|
-
return url.toString().replace(/\/$/, "");
|
|
830
|
-
}
|
|
831
|
-
toUrl() {
|
|
832
|
-
const url = `${this.protocol}://${this.host}${this.port ? `:${this.port}` : ""}`;
|
|
833
|
-
return new URL(url);
|
|
834
|
-
}
|
|
835
|
-
};
|
|
836
|
-
var LocalHost = class extends Host {
|
|
837
|
-
constructor({
|
|
838
|
-
appName,
|
|
839
|
-
local
|
|
840
|
-
}) {
|
|
841
|
-
let protocol;
|
|
842
|
-
let host;
|
|
843
|
-
let port;
|
|
844
|
-
if (typeof local === "number") {
|
|
845
|
-
port = local;
|
|
846
|
-
} else if (typeof local === "string") {
|
|
847
|
-
if (/^\d+$/.test(local)) {
|
|
848
|
-
port = Number.parseInt(local);
|
|
849
|
-
} else {
|
|
850
|
-
const parsed = Host.parseUrl(local, "http");
|
|
851
|
-
protocol = parsed.protocol;
|
|
852
|
-
host = parsed.host;
|
|
853
|
-
port = parsed.port;
|
|
854
|
-
}
|
|
855
|
-
} else if (local) {
|
|
856
|
-
protocol = local.protocol;
|
|
857
|
-
host = local.host;
|
|
858
|
-
port = local.port;
|
|
859
|
-
}
|
|
860
|
-
super({
|
|
861
|
-
protocol: protocol ?? "http",
|
|
862
|
-
host: host ?? "localhost",
|
|
863
|
-
port: port ?? generatePortFromName({ name: appName })
|
|
864
|
-
});
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
var validateConfigDefaultApplication = (applicationConfigsById) => {
|
|
636
|
+
if (!applicationConfigsById) {
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
const applicationsWithoutRouting = Object.entries(
|
|
640
|
+
applicationConfigsById
|
|
641
|
+
).filter(([, app]) => isDefaultApp(app));
|
|
642
|
+
const numApplicationsWithoutRouting = applicationsWithoutRouting.reduce(
|
|
643
|
+
(acc) => {
|
|
644
|
+
return acc + 1;
|
|
645
|
+
},
|
|
646
|
+
0
|
|
647
|
+
);
|
|
648
|
+
if (numApplicationsWithoutRouting === 0) {
|
|
649
|
+
throw new MicrofrontendError(
|
|
650
|
+
"No default application found. At least one application needs to be the default by omitting routing.",
|
|
651
|
+
{ type: "config", subtype: "no_default_application" }
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
if (numApplicationsWithoutRouting > 1) {
|
|
655
|
+
const applicationNamesMissingRouting = applicationsWithoutRouting.map(
|
|
656
|
+
([name]) => name
|
|
657
|
+
);
|
|
658
|
+
throw new MicrofrontendError(
|
|
659
|
+
`All applications except for the default app must contain the "routing" field. Applications that are missing routing: ${LIST_FORMATTER.format(applicationNamesMissingRouting)}.`,
|
|
660
|
+
{ type: "config", subtype: "multiple_default_applications" }
|
|
661
|
+
);
|
|
865
662
|
}
|
|
866
663
|
};
|
|
867
|
-
|
|
868
|
-
// src/config/microfrontends-config/isomorphic/utils/generate-automation-bypass-env-var-name.ts
|
|
869
|
-
function generateAutomationBypassEnvVarName({
|
|
870
|
-
name
|
|
871
|
-
}) {
|
|
872
|
-
return `AUTOMATION_BYPASS_${name.toUpperCase().replace(/[^a-zA-Z0-9]/g, "_")}`;
|
|
873
|
-
}
|
|
874
664
|
|
|
875
665
|
// src/config/microfrontends-config/isomorphic/application.ts
|
|
876
666
|
var Application = class {
|
|
@@ -952,9 +742,6 @@ var ChildApplication = class extends Application {
|
|
|
952
742
|
}
|
|
953
743
|
};
|
|
954
744
|
|
|
955
|
-
// src/config/microfrontends-config/isomorphic/constants.ts
|
|
956
|
-
var DEFAULT_LOCAL_PROXY_PORT = 3024;
|
|
957
|
-
|
|
958
745
|
// src/config/microfrontends-config/isomorphic/index.ts
|
|
959
746
|
var MicrofrontendConfigIsomorphic = class {
|
|
960
747
|
constructor({
|
|
@@ -998,7 +785,7 @@ var MicrofrontendConfigIsomorphic = class {
|
|
|
998
785
|
};
|
|
999
786
|
}
|
|
1000
787
|
static validate(config) {
|
|
1001
|
-
const c = typeof config === "string" ? (0,
|
|
788
|
+
const c = typeof config === "string" ? (0, import_jsonc_parser.parse)(config) : config;
|
|
1002
789
|
validateConfigPaths(c.applications);
|
|
1003
790
|
validateConfigDefaultApplication(c.applications);
|
|
1004
791
|
return c;
|
|
@@ -1007,7 +794,7 @@ var MicrofrontendConfigIsomorphic = class {
|
|
|
1007
794
|
cookies
|
|
1008
795
|
}) {
|
|
1009
796
|
return new MicrofrontendConfigIsomorphic({
|
|
1010
|
-
config: (0,
|
|
797
|
+
config: (0, import_jsonc_parser.parse)(getConfigStringFromEnv()),
|
|
1011
798
|
overrides: parseOverrides(cookies ?? [])
|
|
1012
799
|
});
|
|
1013
800
|
}
|
|
@@ -1073,9 +860,17 @@ var MicrofrontendConfigIsomorphic = class {
|
|
|
1073
860
|
return this.defaultApplication;
|
|
1074
861
|
}
|
|
1075
862
|
/**
|
|
1076
|
-
* Returns the configured port for the local proxy
|
|
863
|
+
* Returns the configured port for the local proxy.
|
|
864
|
+
* Can be overridden via MFE_LOCAL_PROXY_PORT environment variable.
|
|
1077
865
|
*/
|
|
1078
866
|
getLocalProxyPort() {
|
|
867
|
+
const portOverride = process.env[MFE_LOCAL_PROXY_PORT_ENV];
|
|
868
|
+
if (portOverride) {
|
|
869
|
+
const port = Number.parseInt(portOverride, 10);
|
|
870
|
+
if (!Number.isNaN(port) && port > 0 && port < 65536) {
|
|
871
|
+
return port;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
1079
874
|
return this.config.options?.localProxyPort ?? DEFAULT_LOCAL_PROXY_PORT;
|
|
1080
875
|
}
|
|
1081
876
|
toClientConfig(options) {
|
|
@@ -1098,75 +893,394 @@ var MicrofrontendConfigIsomorphic = class {
|
|
|
1098
893
|
{
|
|
1099
894
|
removeFlaggedPaths: options?.removeFlaggedPaths
|
|
1100
895
|
}
|
|
1101
|
-
);
|
|
1102
|
-
}
|
|
1103
|
-
/**
|
|
1104
|
-
* Serializes the class back to the Schema type.
|
|
1105
|
-
*
|
|
1106
|
-
* NOTE: This is used when writing the config to disk and must always match the input Schema
|
|
1107
|
-
*/
|
|
1108
|
-
toSchemaJson() {
|
|
1109
|
-
return this.serialized.config;
|
|
1110
|
-
}
|
|
1111
|
-
serialize() {
|
|
1112
|
-
return this.serialized;
|
|
1113
|
-
}
|
|
1114
|
-
};
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Serializes the class back to the Schema type.
|
|
900
|
+
*
|
|
901
|
+
* NOTE: This is used when writing the config to disk and must always match the input Schema
|
|
902
|
+
*/
|
|
903
|
+
toSchemaJson() {
|
|
904
|
+
return this.serialized.config;
|
|
905
|
+
}
|
|
906
|
+
serialize() {
|
|
907
|
+
return this.serialized;
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
// src/config/microfrontends/utils/find-config.ts
|
|
912
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
913
|
+
var import_node_path = require("path");
|
|
914
|
+
|
|
915
|
+
// src/config/microfrontends/utils/get-config-file-name.ts
|
|
916
|
+
var DEFAULT_CONFIGURATION_FILENAMES = [
|
|
917
|
+
"microfrontends.json",
|
|
918
|
+
"microfrontends.jsonc"
|
|
919
|
+
];
|
|
920
|
+
function getPossibleConfigurationFilenames({
|
|
921
|
+
customConfigFilename
|
|
922
|
+
}) {
|
|
923
|
+
if (customConfigFilename) {
|
|
924
|
+
if (!customConfigFilename.endsWith(".json") && !customConfigFilename.endsWith(".jsonc")) {
|
|
925
|
+
throw new Error(
|
|
926
|
+
`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.`
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
return Array.from(
|
|
930
|
+
/* @__PURE__ */ new Set([customConfigFilename, ...DEFAULT_CONFIGURATION_FILENAMES])
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
return DEFAULT_CONFIGURATION_FILENAMES;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// src/config/microfrontends/utils/find-config.ts
|
|
937
|
+
function findConfig({
|
|
938
|
+
dir,
|
|
939
|
+
customConfigFilename
|
|
940
|
+
}) {
|
|
941
|
+
for (const filename of getPossibleConfigurationFilenames({
|
|
942
|
+
customConfigFilename
|
|
943
|
+
})) {
|
|
944
|
+
const maybeConfig = (0, import_node_path.join)(dir, filename);
|
|
945
|
+
if (import_node_fs.default.existsSync(maybeConfig)) {
|
|
946
|
+
return maybeConfig;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
// src/config/microfrontends/utils/find-package-root.ts
|
|
953
|
+
var import_node_fs2 = __toESM(require("fs"), 1);
|
|
954
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
955
|
+
var PACKAGE_JSON = "package.json";
|
|
956
|
+
function findPackageRoot(startDir) {
|
|
957
|
+
let currentDir = startDir || process.cwd();
|
|
958
|
+
while (currentDir !== import_node_path2.default.parse(currentDir).root) {
|
|
959
|
+
const pkgJsonPath = import_node_path2.default.join(currentDir, PACKAGE_JSON);
|
|
960
|
+
if (import_node_fs2.default.existsSync(pkgJsonPath)) {
|
|
961
|
+
return currentDir;
|
|
962
|
+
}
|
|
963
|
+
currentDir = import_node_path2.default.dirname(currentDir);
|
|
964
|
+
}
|
|
965
|
+
throw new Error(
|
|
966
|
+
`The root of the package that contains the \`package.json\` file for the \`${startDir}\` directory could not be found.`
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/config/microfrontends/utils/find-repository-root.ts
|
|
971
|
+
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
972
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
973
|
+
var GIT_DIRECTORY = ".git";
|
|
974
|
+
function hasGitDirectory(dir) {
|
|
975
|
+
const gitPath = import_node_path3.default.join(dir, GIT_DIRECTORY);
|
|
976
|
+
return import_node_fs3.default.existsSync(gitPath) && import_node_fs3.default.statSync(gitPath).isDirectory();
|
|
977
|
+
}
|
|
978
|
+
function hasPnpmWorkspaces(dir) {
|
|
979
|
+
return import_node_fs3.default.existsSync(import_node_path3.default.join(dir, "pnpm-workspace.yaml"));
|
|
980
|
+
}
|
|
981
|
+
function hasPackageJson(dir) {
|
|
982
|
+
return import_node_fs3.default.existsSync(import_node_path3.default.join(dir, "package.json"));
|
|
983
|
+
}
|
|
984
|
+
function findRepositoryRoot(startDir) {
|
|
985
|
+
if (process.env.NX_WORKSPACE_ROOT) {
|
|
986
|
+
return process.env.NX_WORKSPACE_ROOT;
|
|
987
|
+
}
|
|
988
|
+
let currentDir = startDir || process.cwd();
|
|
989
|
+
let lastPackageJsonDir = null;
|
|
990
|
+
while (currentDir !== import_node_path3.default.parse(currentDir).root) {
|
|
991
|
+
if (hasGitDirectory(currentDir) || hasPnpmWorkspaces(currentDir)) {
|
|
992
|
+
return currentDir;
|
|
993
|
+
}
|
|
994
|
+
if (hasPackageJson(currentDir)) {
|
|
995
|
+
lastPackageJsonDir = currentDir;
|
|
996
|
+
}
|
|
997
|
+
currentDir = import_node_path3.default.dirname(currentDir);
|
|
998
|
+
}
|
|
999
|
+
if (lastPackageJsonDir) {
|
|
1000
|
+
return lastPackageJsonDir;
|
|
1001
|
+
}
|
|
1002
|
+
throw new Error(
|
|
1003
|
+
`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.`
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// src/config/microfrontends/utils/get-application-context.ts
|
|
1008
|
+
var import_node_fs4 = __toESM(require("fs"), 1);
|
|
1009
|
+
var import_node_path4 = __toESM(require("path"), 1);
|
|
1010
|
+
function getApplicationContext(opts) {
|
|
1011
|
+
if (opts?.appName) {
|
|
1012
|
+
logger.debug(
|
|
1013
|
+
"[MFE Config] Application name from appName parameter:",
|
|
1014
|
+
opts.appName
|
|
1015
|
+
);
|
|
1016
|
+
return { name: opts.appName };
|
|
1017
|
+
}
|
|
1018
|
+
if (process.env.VERCEL_PROJECT_NAME) {
|
|
1019
|
+
logger.debug(
|
|
1020
|
+
"[MFE Config] Application name from VERCEL_PROJECT_NAME:",
|
|
1021
|
+
process.env.VERCEL_PROJECT_NAME
|
|
1022
|
+
);
|
|
1023
|
+
return {
|
|
1024
|
+
name: process.env.VERCEL_PROJECT_NAME,
|
|
1025
|
+
projectName: process.env.VERCEL_PROJECT_NAME
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
if (process.env.NX_TASK_TARGET_PROJECT) {
|
|
1029
|
+
logger.debug(
|
|
1030
|
+
"[MFE Config] Application name from NX_TASK_TARGET_PROJECT:",
|
|
1031
|
+
process.env.NX_TASK_TARGET_PROJECT
|
|
1032
|
+
);
|
|
1033
|
+
return {
|
|
1034
|
+
name: process.env.NX_TASK_TARGET_PROJECT,
|
|
1035
|
+
packageJsonName: process.env.NX_TASK_TARGET_PROJECT
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
try {
|
|
1039
|
+
const vercelProjectJsonPath = import_node_fs4.default.readFileSync(
|
|
1040
|
+
import_node_path4.default.join(opts?.packageRoot || ".", ".vercel", "project.json"),
|
|
1041
|
+
"utf-8"
|
|
1042
|
+
);
|
|
1043
|
+
const projectJson = JSON.parse(vercelProjectJsonPath);
|
|
1044
|
+
if (projectJson.projectName) {
|
|
1045
|
+
logger.debug(
|
|
1046
|
+
"[MFE Config] Application name from .vercel/project.json:",
|
|
1047
|
+
projectJson.projectName
|
|
1048
|
+
);
|
|
1049
|
+
return {
|
|
1050
|
+
name: projectJson.projectName,
|
|
1051
|
+
projectName: projectJson.projectName
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
} catch (_) {
|
|
1055
|
+
}
|
|
1056
|
+
try {
|
|
1057
|
+
const packageJsonString = import_node_fs4.default.readFileSync(
|
|
1058
|
+
import_node_path4.default.join(opts?.packageRoot || ".", "package.json"),
|
|
1059
|
+
"utf-8"
|
|
1060
|
+
);
|
|
1061
|
+
const packageJson = JSON.parse(packageJsonString);
|
|
1062
|
+
if (!packageJson.name) {
|
|
1063
|
+
throw new MicrofrontendError(
|
|
1064
|
+
`package.json file missing required field "name"`,
|
|
1065
|
+
{
|
|
1066
|
+
type: "packageJson",
|
|
1067
|
+
subtype: "missing_field_name",
|
|
1068
|
+
source: "@vercel/microfrontends/next"
|
|
1069
|
+
}
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
logger.debug(
|
|
1073
|
+
"[MFE Config] Application name from package.json:",
|
|
1074
|
+
packageJson.name
|
|
1075
|
+
);
|
|
1076
|
+
return { name: packageJson.name, packageJsonName: packageJson.name };
|
|
1077
|
+
} catch (err) {
|
|
1078
|
+
throw MicrofrontendError.handle(err, {
|
|
1079
|
+
fileName: "package.json"
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// src/config/microfrontends/utils/infer-microfrontends-location.ts
|
|
1085
|
+
var import_node_fs5 = require("fs");
|
|
1086
|
+
var import_node_path5 = require("path");
|
|
1087
|
+
var import_fast_glob = __toESM(require("fast-glob"), 1);
|
|
1088
|
+
var import_jsonc_parser2 = require("jsonc-parser");
|
|
1089
|
+
var configCache = {};
|
|
1090
|
+
function findPackageWithMicrofrontendsConfig({
|
|
1091
|
+
repositoryRoot,
|
|
1092
|
+
applicationContext,
|
|
1093
|
+
customConfigFilename
|
|
1094
|
+
}) {
|
|
1095
|
+
const applicationName = applicationContext.name;
|
|
1096
|
+
logger.debug(
|
|
1097
|
+
"[MFE Config] Searching repository for configs containing application:",
|
|
1098
|
+
applicationName
|
|
1099
|
+
);
|
|
1100
|
+
try {
|
|
1101
|
+
const microfrontendsJsonPaths = import_fast_glob.default.globSync(
|
|
1102
|
+
`**/{${getPossibleConfigurationFilenames({ customConfigFilename }).join(",")}}`,
|
|
1103
|
+
{
|
|
1104
|
+
cwd: repositoryRoot,
|
|
1105
|
+
absolute: true,
|
|
1106
|
+
onlyFiles: true,
|
|
1107
|
+
followSymbolicLinks: false,
|
|
1108
|
+
ignore: ["**/node_modules/**", "**/.git/**"]
|
|
1109
|
+
}
|
|
1110
|
+
);
|
|
1111
|
+
logger.debug(
|
|
1112
|
+
"[MFE Config] Found",
|
|
1113
|
+
microfrontendsJsonPaths.length,
|
|
1114
|
+
"config file(s) in repository"
|
|
1115
|
+
);
|
|
1116
|
+
const matchingPaths = [];
|
|
1117
|
+
for (const microfrontendsJsonPath of microfrontendsJsonPaths) {
|
|
1118
|
+
if (doesApplicationExistInConfig(microfrontendsJsonPath, applicationName)) {
|
|
1119
|
+
matchingPaths.push(microfrontendsJsonPath);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
logger.debug(
|
|
1123
|
+
"[MFE Config] Total matching config files:",
|
|
1124
|
+
matchingPaths.length
|
|
1125
|
+
);
|
|
1126
|
+
if (matchingPaths.length > 1) {
|
|
1127
|
+
throw new MicrofrontendError(
|
|
1128
|
+
`Found multiple \`microfrontends.json\` files in the repository referencing the application "${applicationName}", but only one is allowed.
|
|
1129
|
+
${matchingPaths.join("\n \u2022 ")}`,
|
|
1130
|
+
{ type: "config", subtype: "inference_failed" }
|
|
1131
|
+
);
|
|
1132
|
+
}
|
|
1133
|
+
if (matchingPaths.length === 0) {
|
|
1134
|
+
if (repositoryRoot && doesMisplacedConfigExist(
|
|
1135
|
+
repositoryRoot,
|
|
1136
|
+
applicationName,
|
|
1137
|
+
customConfigFilename
|
|
1138
|
+
)) {
|
|
1139
|
+
logger.debug(
|
|
1140
|
+
"[MFE Config] Found misplaced config in wrong .vercel directory in repository"
|
|
1141
|
+
);
|
|
1142
|
+
const misplacedConfigPath = (0, import_node_path5.join)(
|
|
1143
|
+
repositoryRoot,
|
|
1144
|
+
".vercel",
|
|
1145
|
+
customConfigFilename || "microfrontends.json"
|
|
1146
|
+
);
|
|
1147
|
+
throw new MicrofrontendError(
|
|
1148
|
+
`Unable to automatically infer the location of the \`microfrontends.json\` file.
|
|
1149
|
+
|
|
1150
|
+
A microfrontends config was found in the \`.vercel\` directory at the repository root: ${misplacedConfigPath}
|
|
1151
|
+
However, in a monorepo, the config file should be placed in the \`.vercel\` directory in your application directory instead.
|
|
1152
|
+
|
|
1153
|
+
To fix this:
|
|
1154
|
+
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
|
|
1155
|
+
2. If manually defined, move the config file to the \`.vercel\` directory in your application
|
|
1156
|
+
3. Alternatively, set the VC_MICROFRONTENDS_CONFIG environment variable to the correct path
|
|
1157
|
+
|
|
1158
|
+
For more information, see: https://vercel.com/docs/cli/project-linking`,
|
|
1159
|
+
{ type: "config", subtype: "inference_failed" }
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
let additionalErrorMessage = "";
|
|
1163
|
+
if (microfrontendsJsonPaths.length > 0) {
|
|
1164
|
+
if (!applicationContext.projectName) {
|
|
1165
|
+
additionalErrorMessage = `
|
|
1166
|
+
|
|
1167
|
+
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.`;
|
|
1168
|
+
} else {
|
|
1169
|
+
additionalErrorMessage = `
|
|
1170
|
+
|
|
1171
|
+
Names of applications in \`microfrontends.json\` must match the Vercel Project name (${applicationContext.projectName}).`;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
throw new MicrofrontendError(
|
|
1175
|
+
`Could not find a \`microfrontends.json\` file in the repository that contains the "${applicationName}" application.${additionalErrorMessage}
|
|
1115
1176
|
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1177
|
+
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.
|
|
1178
|
+
|
|
1179
|
+
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.
|
|
1180
|
+
|
|
1181
|
+
If you suspect this is thrown in error, please reach out to the Vercel team.`,
|
|
1182
|
+
{ type: "config", subtype: "inference_failed" }
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
const [packageJsonPath] = matchingPaths;
|
|
1186
|
+
return (0, import_node_path5.dirname)(packageJsonPath);
|
|
1187
|
+
} catch (error2) {
|
|
1188
|
+
if (error2 instanceof MicrofrontendError) {
|
|
1189
|
+
throw error2;
|
|
1190
|
+
}
|
|
1191
|
+
return null;
|
|
1122
1192
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1193
|
+
}
|
|
1194
|
+
function inferMicrofrontendsLocation(opts) {
|
|
1195
|
+
const cacheKey = `${opts.repositoryRoot}-${opts.applicationContext.name}${opts.customConfigFilename ? `-${opts.customConfigFilename}` : ""}`;
|
|
1196
|
+
if (configCache[cacheKey]) {
|
|
1197
|
+
return configCache[cacheKey];
|
|
1128
1198
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1199
|
+
const result = findPackageWithMicrofrontendsConfig(opts);
|
|
1200
|
+
if (!result) {
|
|
1201
|
+
throw new MicrofrontendError(
|
|
1202
|
+
`Could not infer the location of the \`microfrontends.json\` file for application "${opts.applicationContext.name}" starting in directory "${opts.repositoryRoot}".`,
|
|
1203
|
+
{ type: "config", subtype: "inference_failed" }
|
|
1204
|
+
);
|
|
1134
1205
|
}
|
|
1206
|
+
configCache[cacheKey] = result;
|
|
1207
|
+
return result;
|
|
1208
|
+
}
|
|
1209
|
+
function existsSync(path6) {
|
|
1135
1210
|
try {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
"utf-8"
|
|
1139
|
-
);
|
|
1140
|
-
const projectJson = JSON.parse(vercelProjectJsonPath);
|
|
1141
|
-
if (projectJson.projectName) {
|
|
1142
|
-
return {
|
|
1143
|
-
name: projectJson.projectName,
|
|
1144
|
-
projectName: projectJson.projectName
|
|
1145
|
-
};
|
|
1146
|
-
}
|
|
1211
|
+
(0, import_node_fs5.statSync)(path6);
|
|
1212
|
+
return true;
|
|
1147
1213
|
} catch (_) {
|
|
1214
|
+
return false;
|
|
1148
1215
|
}
|
|
1216
|
+
}
|
|
1217
|
+
function doesMisplacedConfigExist(repositoryRoot, applicationName, customConfigFilename) {
|
|
1218
|
+
logger.debug(
|
|
1219
|
+
"[MFE Config] Looking for misplaced config in wrong .vercel directory"
|
|
1220
|
+
);
|
|
1221
|
+
const misplacedConfigPath = (0, import_node_path5.join)(
|
|
1222
|
+
repositoryRoot,
|
|
1223
|
+
".vercel",
|
|
1224
|
+
customConfigFilename || "microfrontends.json"
|
|
1225
|
+
);
|
|
1226
|
+
return existsSync(misplacedConfigPath) && doesApplicationExistInConfig(misplacedConfigPath, applicationName);
|
|
1227
|
+
}
|
|
1228
|
+
function doesApplicationExistInConfig(microfrontendsJsonPath, applicationName) {
|
|
1149
1229
|
try {
|
|
1150
|
-
const
|
|
1151
|
-
|
|
1230
|
+
const microfrontendsJsonContent = (0, import_node_fs5.readFileSync)(
|
|
1231
|
+
microfrontendsJsonPath,
|
|
1152
1232
|
"utf-8"
|
|
1153
1233
|
);
|
|
1154
|
-
const
|
|
1155
|
-
if (
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
type: "packageJson",
|
|
1160
|
-
subtype: "missing_field_name",
|
|
1161
|
-
source: "@vercel/microfrontends/next"
|
|
1162
|
-
}
|
|
1234
|
+
const microfrontendsJson = (0, import_jsonc_parser2.parse)(microfrontendsJsonContent);
|
|
1235
|
+
if (microfrontendsJson.applications[applicationName]) {
|
|
1236
|
+
logger.debug(
|
|
1237
|
+
"[MFE Config] Found application in config:",
|
|
1238
|
+
microfrontendsJsonPath
|
|
1163
1239
|
);
|
|
1240
|
+
return true;
|
|
1164
1241
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1242
|
+
for (const [_, app] of Object.entries(microfrontendsJson.applications)) {
|
|
1243
|
+
if (app.packageName === applicationName) {
|
|
1244
|
+
logger.debug(
|
|
1245
|
+
"[MFE Config] Found application via packageName in config:",
|
|
1246
|
+
microfrontendsJsonPath
|
|
1247
|
+
);
|
|
1248
|
+
return true;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
} catch (error2) {
|
|
1252
|
+
logger.debug("[MFE Config] Error checking application in config:", error2);
|
|
1253
|
+
}
|
|
1254
|
+
return false;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// src/config/microfrontends/utils/is-monorepo.ts
|
|
1258
|
+
var import_node_fs6 = __toESM(require("fs"), 1);
|
|
1259
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
1260
|
+
function isMonorepo({
|
|
1261
|
+
repositoryRoot
|
|
1262
|
+
}) {
|
|
1263
|
+
try {
|
|
1264
|
+
if (import_node_fs6.default.existsSync(import_node_path6.default.join(repositoryRoot, "pnpm-workspace.yaml"))) {
|
|
1265
|
+
return true;
|
|
1266
|
+
}
|
|
1267
|
+
if (import_node_fs6.default.existsSync(import_node_path6.default.join(repositoryRoot, "vlt-workspaces.json"))) {
|
|
1268
|
+
return true;
|
|
1269
|
+
}
|
|
1270
|
+
if (process.env.NX_WORKSPACE_ROOT === import_node_path6.default.resolve(repositoryRoot)) {
|
|
1271
|
+
return true;
|
|
1272
|
+
}
|
|
1273
|
+
const packageJsonPath = import_node_path6.default.join(repositoryRoot, "package.json");
|
|
1274
|
+
if (!import_node_fs6.default.existsSync(packageJsonPath)) {
|
|
1275
|
+
return false;
|
|
1276
|
+
}
|
|
1277
|
+
const packageJson = JSON.parse(
|
|
1278
|
+
import_node_fs6.default.readFileSync(packageJsonPath, "utf-8")
|
|
1279
|
+
);
|
|
1280
|
+
return packageJson.workspaces !== void 0;
|
|
1281
|
+
} catch (error2) {
|
|
1282
|
+
logger.error("Error determining if repository is a monorepo", error2);
|
|
1283
|
+
return false;
|
|
1170
1284
|
}
|
|
1171
1285
|
}
|
|
1172
1286
|
|
|
@@ -1183,8 +1297,8 @@ function getOutputFilePath() {
|
|
|
1183
1297
|
}
|
|
1184
1298
|
|
|
1185
1299
|
// src/config/microfrontends/server/validation.ts
|
|
1186
|
-
var import_jsonc_parser3 = require("jsonc-parser");
|
|
1187
1300
|
var import_ajv = require("ajv");
|
|
1301
|
+
var import_jsonc_parser3 = require("jsonc-parser");
|
|
1188
1302
|
|
|
1189
1303
|
// schema/schema.json
|
|
1190
1304
|
var schema_default = {
|
|
@@ -1212,9 +1326,7 @@ var schema_default = {
|
|
|
1212
1326
|
description: "Optional configuration options for the microfrontend."
|
|
1213
1327
|
}
|
|
1214
1328
|
},
|
|
1215
|
-
required: [
|
|
1216
|
-
"applications"
|
|
1217
|
-
],
|
|
1329
|
+
required: ["applications"],
|
|
1218
1330
|
additionalProperties: false,
|
|
1219
1331
|
description: "The microfrontends configuration schema. See https://vercel.com/docs/microfrontends/configuration."
|
|
1220
1332
|
},
|
|
@@ -1251,19 +1363,14 @@ var schema_default = {
|
|
|
1251
1363
|
description: "Development configuration for the default application."
|
|
1252
1364
|
}
|
|
1253
1365
|
},
|
|
1254
|
-
required: [
|
|
1255
|
-
"development"
|
|
1256
|
-
],
|
|
1366
|
+
required: ["development"],
|
|
1257
1367
|
additionalProperties: false
|
|
1258
1368
|
},
|
|
1259
1369
|
DefaultDevelopment: {
|
|
1260
1370
|
type: "object",
|
|
1261
1371
|
properties: {
|
|
1262
1372
|
local: {
|
|
1263
|
-
type: [
|
|
1264
|
-
"number",
|
|
1265
|
-
"string"
|
|
1266
|
-
],
|
|
1373
|
+
type: ["number", "string"],
|
|
1267
1374
|
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."
|
|
1268
1375
|
},
|
|
1269
1376
|
task: {
|
|
@@ -1275,9 +1382,7 @@ var schema_default = {
|
|
|
1275
1382
|
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."
|
|
1276
1383
|
}
|
|
1277
1384
|
},
|
|
1278
|
-
required: [
|
|
1279
|
-
"fallback"
|
|
1280
|
-
],
|
|
1385
|
+
required: ["fallback"],
|
|
1281
1386
|
additionalProperties: false
|
|
1282
1387
|
},
|
|
1283
1388
|
ChildApplication: {
|
|
@@ -1300,19 +1405,14 @@ var schema_default = {
|
|
|
1300
1405
|
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."
|
|
1301
1406
|
}
|
|
1302
1407
|
},
|
|
1303
|
-
required: [
|
|
1304
|
-
"routing"
|
|
1305
|
-
],
|
|
1408
|
+
required: ["routing"],
|
|
1306
1409
|
additionalProperties: false
|
|
1307
1410
|
},
|
|
1308
1411
|
ChildDevelopment: {
|
|
1309
1412
|
type: "object",
|
|
1310
1413
|
properties: {
|
|
1311
1414
|
local: {
|
|
1312
|
-
type: [
|
|
1313
|
-
"number",
|
|
1314
|
-
"string"
|
|
1315
|
-
],
|
|
1415
|
+
type: ["number", "string"],
|
|
1316
1416
|
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."
|
|
1317
1417
|
},
|
|
1318
1418
|
task: {
|
|
@@ -1352,9 +1452,7 @@ var schema_default = {
|
|
|
1352
1452
|
description: "A list of path expressions that are routed to this application. See https://vercel.com/docs/microfrontends/path-routing#supported-path-expressions."
|
|
1353
1453
|
}
|
|
1354
1454
|
},
|
|
1355
|
-
required: [
|
|
1356
|
-
"paths"
|
|
1357
|
-
],
|
|
1455
|
+
required: ["paths"],
|
|
1358
1456
|
additionalProperties: false,
|
|
1359
1457
|
description: "A group of paths that is routed to this application."
|
|
1360
1458
|
},
|
|
@@ -1533,7 +1631,13 @@ var MicrofrontendsServer = class {
|
|
|
1533
1631
|
filePath,
|
|
1534
1632
|
cookies
|
|
1535
1633
|
} = {}) {
|
|
1634
|
+
logger.debug("[MFE Config] Starting config inference", {
|
|
1635
|
+
appName,
|
|
1636
|
+
directory: directory || process.cwd(),
|
|
1637
|
+
filePath
|
|
1638
|
+
});
|
|
1536
1639
|
if (filePath) {
|
|
1640
|
+
logger.debug("[MFE Config] Using explicit filePath:", filePath);
|
|
1537
1641
|
return MicrofrontendsServer.fromFile({
|
|
1538
1642
|
filePath,
|
|
1539
1643
|
cookies
|
|
@@ -1541,16 +1645,25 @@ var MicrofrontendsServer = class {
|
|
|
1541
1645
|
}
|
|
1542
1646
|
try {
|
|
1543
1647
|
const packageRoot = findPackageRoot(directory);
|
|
1648
|
+
logger.debug("[MFE Config] Package root:", packageRoot);
|
|
1544
1649
|
const applicationContext = getApplicationContext({
|
|
1545
1650
|
appName,
|
|
1546
1651
|
packageRoot
|
|
1547
1652
|
});
|
|
1653
|
+
logger.debug("[MFE Config] Application context:", applicationContext);
|
|
1548
1654
|
const customConfigFilename = process.env.VC_MICROFRONTENDS_CONFIG_FILE_NAME;
|
|
1655
|
+
if (customConfigFilename) {
|
|
1656
|
+
logger.debug(
|
|
1657
|
+
"[MFE Config] Custom config filename from VC_MICROFRONTENDS_CONFIG_FILE_NAME:",
|
|
1658
|
+
customConfigFilename
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1549
1661
|
const maybeConfig = findConfig({
|
|
1550
1662
|
dir: packageRoot,
|
|
1551
1663
|
customConfigFilename
|
|
1552
1664
|
});
|
|
1553
1665
|
if (maybeConfig) {
|
|
1666
|
+
logger.debug("[MFE Config] Config found at package root:", maybeConfig);
|
|
1554
1667
|
return MicrofrontendsServer.fromFile({
|
|
1555
1668
|
filePath: maybeConfig,
|
|
1556
1669
|
cookies
|
|
@@ -1558,42 +1671,78 @@ var MicrofrontendsServer = class {
|
|
|
1558
1671
|
}
|
|
1559
1672
|
const repositoryRoot = findRepositoryRoot();
|
|
1560
1673
|
const isMonorepo2 = isMonorepo({ repositoryRoot });
|
|
1674
|
+
logger.debug(
|
|
1675
|
+
"[MFE Config] Repository root:",
|
|
1676
|
+
repositoryRoot,
|
|
1677
|
+
"Is monorepo:",
|
|
1678
|
+
isMonorepo2
|
|
1679
|
+
);
|
|
1561
1680
|
const configFromEnv = process.env.VC_MICROFRONTENDS_CONFIG;
|
|
1562
1681
|
if (typeof configFromEnv === "string") {
|
|
1682
|
+
logger.debug(
|
|
1683
|
+
"[MFE Config] Checking VC_MICROFRONTENDS_CONFIG:",
|
|
1684
|
+
configFromEnv
|
|
1685
|
+
);
|
|
1563
1686
|
const maybeConfigFromEnv = (0, import_node_path8.resolve)(packageRoot, configFromEnv);
|
|
1564
1687
|
if (maybeConfigFromEnv) {
|
|
1688
|
+
logger.debug(
|
|
1689
|
+
"[MFE Config] Config loaded from VC_MICROFRONTENDS_CONFIG:",
|
|
1690
|
+
maybeConfigFromEnv
|
|
1691
|
+
);
|
|
1565
1692
|
return MicrofrontendsServer.fromFile({
|
|
1566
1693
|
filePath: maybeConfigFromEnv,
|
|
1567
1694
|
cookies
|
|
1568
1695
|
});
|
|
1569
1696
|
}
|
|
1570
1697
|
} else {
|
|
1698
|
+
const vercelDir = (0, import_node_path8.join)(packageRoot, ".vercel");
|
|
1699
|
+
logger.debug(
|
|
1700
|
+
"[MFE Config] Searching for config in .vercel directory:",
|
|
1701
|
+
vercelDir
|
|
1702
|
+
);
|
|
1571
1703
|
const maybeConfigFromVercel = findConfig({
|
|
1572
|
-
dir:
|
|
1704
|
+
dir: vercelDir,
|
|
1573
1705
|
customConfigFilename
|
|
1574
1706
|
});
|
|
1575
1707
|
if (maybeConfigFromVercel) {
|
|
1708
|
+
logger.debug(
|
|
1709
|
+
"[MFE Config] Config found in .vercel directory:",
|
|
1710
|
+
maybeConfigFromVercel
|
|
1711
|
+
);
|
|
1576
1712
|
return MicrofrontendsServer.fromFile({
|
|
1577
1713
|
filePath: maybeConfigFromVercel,
|
|
1578
1714
|
cookies
|
|
1579
1715
|
});
|
|
1580
1716
|
}
|
|
1581
1717
|
if (isMonorepo2) {
|
|
1718
|
+
logger.debug(
|
|
1719
|
+
"[MFE Config] Inferring microfrontends location in monorepo for application:",
|
|
1720
|
+
applicationContext.name
|
|
1721
|
+
);
|
|
1582
1722
|
const defaultPackage = inferMicrofrontendsLocation({
|
|
1583
1723
|
repositoryRoot,
|
|
1584
1724
|
applicationContext,
|
|
1585
1725
|
customConfigFilename
|
|
1586
1726
|
});
|
|
1727
|
+
logger.debug(
|
|
1728
|
+
"[MFE Config] Inferred package location:",
|
|
1729
|
+
defaultPackage
|
|
1730
|
+
);
|
|
1587
1731
|
const maybeConfigFromDefault = findConfig({
|
|
1588
1732
|
dir: defaultPackage,
|
|
1589
1733
|
customConfigFilename
|
|
1590
1734
|
});
|
|
1591
1735
|
if (maybeConfigFromDefault) {
|
|
1736
|
+
logger.debug(
|
|
1737
|
+
"[MFE Config] Config found in inferred package:",
|
|
1738
|
+
maybeConfigFromDefault
|
|
1739
|
+
);
|
|
1592
1740
|
return MicrofrontendsServer.fromFile({
|
|
1593
1741
|
filePath: maybeConfigFromDefault,
|
|
1594
1742
|
cookies
|
|
1595
1743
|
});
|
|
1596
1744
|
}
|
|
1745
|
+
logger.debug("[MFE Config] No config found in inferred package");
|
|
1597
1746
|
}
|
|
1598
1747
|
}
|
|
1599
1748
|
throw new MicrofrontendError(
|
|
@@ -1619,8 +1768,13 @@ var MicrofrontendsServer = class {
|
|
|
1619
1768
|
cookies
|
|
1620
1769
|
}) {
|
|
1621
1770
|
try {
|
|
1771
|
+
logger.debug("[MFE Config] Reading config from file:", filePath);
|
|
1622
1772
|
const configJson = import_node_fs7.default.readFileSync(filePath, "utf-8");
|
|
1623
1773
|
const config = MicrofrontendsServer.validate(configJson);
|
|
1774
|
+
logger.debug(
|
|
1775
|
+
"[MFE Config] Config loaded with applications:",
|
|
1776
|
+
Object.keys(config.applications)
|
|
1777
|
+
);
|
|
1624
1778
|
return new MicrofrontendsServer({
|
|
1625
1779
|
config,
|
|
1626
1780
|
overrides: cookies ? parseOverrides(cookies) : void 0
|
|
@@ -1660,6 +1814,51 @@ var MicrofrontendsServer = class {
|
|
|
1660
1814
|
}
|
|
1661
1815
|
};
|
|
1662
1816
|
|
|
1817
|
+
// src/next/config/env.ts
|
|
1818
|
+
function debugEnv(env) {
|
|
1819
|
+
const indent = " ".repeat(4);
|
|
1820
|
+
const header = "env (key \u2192 val)";
|
|
1821
|
+
const separator = "\u23AF".repeat(header.length);
|
|
1822
|
+
const maxKeyLength = Math.max(...Object.keys(env).map((key) => key.length));
|
|
1823
|
+
const table = Object.keys(env).map((key, idx) => {
|
|
1824
|
+
const paddedKey = key.padEnd(maxKeyLength);
|
|
1825
|
+
return `${indent} ${idx + 1}. ${paddedKey} = ${env[key]}`;
|
|
1826
|
+
}).join("\n");
|
|
1827
|
+
logger.debug(`${indent}${header}
|
|
1828
|
+
${indent}${separator}
|
|
1829
|
+
${table}
|
|
1830
|
+
`);
|
|
1831
|
+
}
|
|
1832
|
+
function setEnvironment({
|
|
1833
|
+
app,
|
|
1834
|
+
microfrontends
|
|
1835
|
+
}) {
|
|
1836
|
+
const clientEnvs = {
|
|
1837
|
+
NEXT_PUBLIC_MFE_CURRENT_APPLICATION: app.name,
|
|
1838
|
+
NEXT_PUBLIC_MFE_CURRENT_APPLICATION_HASH: hashApplicationName(app.name),
|
|
1839
|
+
NEXT_PUBLIC_MFE_CLIENT_CONFIG: JSON.stringify(
|
|
1840
|
+
microfrontends.config.toClientConfig({
|
|
1841
|
+
removeFlaggedPaths: true
|
|
1842
|
+
}).serialize()
|
|
1843
|
+
),
|
|
1844
|
+
...app.getAssetPrefix() ? {
|
|
1845
|
+
NEXT_PUBLIC_VERCEL_FIREWALL_PATH_PREFIX: `/${app.getAssetPrefix()}`
|
|
1846
|
+
} : {},
|
|
1847
|
+
...process.env.ROUTE_OBSERVABILITY_TO_THIS_PROJECT && app.getAssetPrefix() ? {
|
|
1848
|
+
NEXT_PUBLIC_VERCEL_OBSERVABILITY_BASEPATH: `/${app.getAssetPrefix()}/_vercel`
|
|
1849
|
+
} : {}
|
|
1850
|
+
};
|
|
1851
|
+
const serverEnvs = {
|
|
1852
|
+
MFE_CURRENT_APPLICATION: app.name,
|
|
1853
|
+
MFE_CONFIG: JSON.stringify(microfrontends.config.getConfig())
|
|
1854
|
+
};
|
|
1855
|
+
const allEnvs = { ...clientEnvs, ...serverEnvs };
|
|
1856
|
+
for (const [key, value] of Object.entries(allEnvs)) {
|
|
1857
|
+
process.env[key] = value;
|
|
1858
|
+
}
|
|
1859
|
+
debugEnv(allEnvs);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1663
1862
|
// src/next/config/transforms/asset-prefix.ts
|
|
1664
1863
|
function transform(args) {
|
|
1665
1864
|
const { next, app } = args;
|
|
@@ -1764,20 +1963,6 @@ ${indent} - Automatically redirecting all requests to local microfrontends proxy
|
|
|
1764
1963
|
return { next };
|
|
1765
1964
|
}
|
|
1766
1965
|
|
|
1767
|
-
// src/next/config/transforms/transpile-packages.ts
|
|
1768
|
-
function transform5(args) {
|
|
1769
|
-
const { next } = args;
|
|
1770
|
-
if (next.transpilePackages === void 0 || !next.transpilePackages.includes("@vercel/microfrontends")) {
|
|
1771
|
-
next.transpilePackages = [
|
|
1772
|
-
...next.transpilePackages || [],
|
|
1773
|
-
"@vercel/microfrontends"
|
|
1774
|
-
];
|
|
1775
|
-
}
|
|
1776
|
-
return {
|
|
1777
|
-
next
|
|
1778
|
-
};
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
1966
|
// src/next/config/transforms/rewrites.ts
|
|
1782
1967
|
function debugRewrites(rewrites) {
|
|
1783
1968
|
const indent = " ".repeat(4);
|
|
@@ -1806,7 +1991,7 @@ function rewritesMapToArr(rewrites) {
|
|
|
1806
1991
|
];
|
|
1807
1992
|
});
|
|
1808
1993
|
}
|
|
1809
|
-
function
|
|
1994
|
+
function transform5(args) {
|
|
1810
1995
|
const { app, next } = args;
|
|
1811
1996
|
const buildBeforeFiles = () => {
|
|
1812
1997
|
const rewrites = /* @__PURE__ */ new Map();
|
|
@@ -1869,6 +2054,20 @@ function transform6(args) {
|
|
|
1869
2054
|
};
|
|
1870
2055
|
}
|
|
1871
2056
|
|
|
2057
|
+
// src/next/config/transforms/transpile-packages.ts
|
|
2058
|
+
function transform6(args) {
|
|
2059
|
+
const { next } = args;
|
|
2060
|
+
if (next.transpilePackages === void 0 || !next.transpilePackages.includes("@vercel/microfrontends")) {
|
|
2061
|
+
next.transpilePackages = [
|
|
2062
|
+
...next.transpilePackages || [],
|
|
2063
|
+
"@vercel/microfrontends"
|
|
2064
|
+
];
|
|
2065
|
+
}
|
|
2066
|
+
return {
|
|
2067
|
+
next
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
|
|
1872
2071
|
// src/next/config/transforms/webpack.ts
|
|
1873
2072
|
var import_node_fs8 = __toESM(require("fs"), 1);
|
|
1874
2073
|
var import_node_module = require("module");
|
|
@@ -1902,8 +2101,12 @@ var nextVersion = getNextJsVersion();
|
|
|
1902
2101
|
function transform7(args) {
|
|
1903
2102
|
const useDefineServer = args.opts?.preferWebpackEnvironmentPlugin ? false : semver.gte(nextVersion, "15.4.0-canary.41");
|
|
1904
2103
|
const { next, microfrontend, opts } = args;
|
|
2104
|
+
const isNext16OrHigher = semver.gte(nextVersion, "16.0.0");
|
|
2105
|
+
const hasTurbopackConfig = Boolean(next.turbopack);
|
|
2106
|
+
const turbopackConfig = isNext16OrHigher && !hasTurbopackConfig ? { turbopack: {} } : {};
|
|
1905
2107
|
const configWithWebpack = {
|
|
1906
2108
|
...next,
|
|
2109
|
+
...turbopackConfig,
|
|
1907
2110
|
...useDefineServer ? {
|
|
1908
2111
|
compiler: {
|
|
1909
2112
|
...next.compiler,
|
|
@@ -1983,56 +2186,11 @@ var transforms = {
|
|
|
1983
2186
|
buildId: transform2,
|
|
1984
2187
|
draftMode: transform3,
|
|
1985
2188
|
redirects: transform4,
|
|
1986
|
-
rewrites:
|
|
1987
|
-
transpilePackages:
|
|
2189
|
+
rewrites: transform5,
|
|
2190
|
+
transpilePackages: transform6,
|
|
1988
2191
|
webpack: transform7
|
|
1989
2192
|
};
|
|
1990
2193
|
|
|
1991
|
-
// src/next/config/env.ts
|
|
1992
|
-
function debugEnv(env) {
|
|
1993
|
-
const indent = " ".repeat(4);
|
|
1994
|
-
const header = "env (key \u2192 val)";
|
|
1995
|
-
const separator = "\u23AF".repeat(header.length);
|
|
1996
|
-
const maxKeyLength = Math.max(...Object.keys(env).map((key) => key.length));
|
|
1997
|
-
const table = Object.keys(env).map((key, idx) => {
|
|
1998
|
-
const paddedKey = key.padEnd(maxKeyLength);
|
|
1999
|
-
return `${indent} ${idx + 1}. ${paddedKey} = ${env[key]}`;
|
|
2000
|
-
}).join("\n");
|
|
2001
|
-
logger.debug(`${indent}${header}
|
|
2002
|
-
${indent}${separator}
|
|
2003
|
-
${table}
|
|
2004
|
-
`);
|
|
2005
|
-
}
|
|
2006
|
-
function setEnvironment({
|
|
2007
|
-
app,
|
|
2008
|
-
microfrontends
|
|
2009
|
-
}) {
|
|
2010
|
-
const clientEnvs = {
|
|
2011
|
-
NEXT_PUBLIC_MFE_CURRENT_APPLICATION: app.name,
|
|
2012
|
-
NEXT_PUBLIC_MFE_CURRENT_APPLICATION_HASH: hashApplicationName(app.name),
|
|
2013
|
-
NEXT_PUBLIC_MFE_CLIENT_CONFIG: JSON.stringify(
|
|
2014
|
-
microfrontends.config.toClientConfig({
|
|
2015
|
-
removeFlaggedPaths: true
|
|
2016
|
-
}).serialize()
|
|
2017
|
-
),
|
|
2018
|
-
...app.getAssetPrefix() ? {
|
|
2019
|
-
NEXT_PUBLIC_VERCEL_FIREWALL_PATH_PREFIX: `/${app.getAssetPrefix()}`
|
|
2020
|
-
} : {},
|
|
2021
|
-
...process.env.ROUTE_OBSERVABILITY_TO_THIS_PROJECT && app.getAssetPrefix() ? {
|
|
2022
|
-
NEXT_PUBLIC_VERCEL_OBSERVABILITY_BASEPATH: `/${app.getAssetPrefix()}/_vercel`
|
|
2023
|
-
} : {}
|
|
2024
|
-
};
|
|
2025
|
-
const serverEnvs = {
|
|
2026
|
-
MFE_CURRENT_APPLICATION: app.name,
|
|
2027
|
-
MFE_CONFIG: JSON.stringify(microfrontends.config.getConfig())
|
|
2028
|
-
};
|
|
2029
|
-
const allEnvs = { ...clientEnvs, ...serverEnvs };
|
|
2030
|
-
for (const [key, value] of Object.entries(allEnvs)) {
|
|
2031
|
-
process.env[key] = value;
|
|
2032
|
-
}
|
|
2033
|
-
debugEnv(allEnvs);
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
2194
|
// src/next/config/index.ts
|
|
2037
2195
|
function typedEntries(obj) {
|
|
2038
2196
|
return Object.entries(obj);
|