@module-federation/dts-plugin
Advanced tools
| const require_Action = require('./Action-CzhPMw2i.js'); | ||
| const require_expose_rpc = require('./expose-rpc-BNzQqY2X.js'); | ||
| let url = require("url"); | ||
| let path = require("path"); | ||
| path = require_Action.__toESM(path); | ||
| let crypto = require("crypto"); | ||
| let child_process = require("child_process"); | ||
| child_process = require_Action.__toESM(child_process); | ||
| let process$1 = require("process"); | ||
| process$1 = require_Action.__toESM(process$1); | ||
| //#region src/core/rpc/rpc-error.ts | ||
| var RpcExitError = class extends Error { | ||
| constructor(message, code, signal) { | ||
| super(message); | ||
| this.code = code; | ||
| this.signal = signal; | ||
| this.name = "RpcExitError"; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/rpc/wrap-rpc.ts | ||
| function createControlledPromise() { | ||
| let resolve = () => void 0; | ||
| let reject = () => void 0; | ||
| return { | ||
| promise: new Promise((aResolve, aReject) => { | ||
| resolve = aResolve; | ||
| reject = aReject; | ||
| }), | ||
| resolve, | ||
| reject | ||
| }; | ||
| } | ||
| function wrapRpc(childProcess, options) { | ||
| return (async (...args) => { | ||
| if (!childProcess.send) throw new Error(`Process ${childProcess.pid} doesn't have IPC channels`); | ||
| else if (!childProcess.connected) throw new Error(`Process ${childProcess.pid} doesn't have open IPC channels`); | ||
| const { id, once } = options; | ||
| const { promise: resultPromise, resolve: resolveResult, reject: rejectResult } = createControlledPromise(); | ||
| const { promise: sendPromise, resolve: resolveSend, reject: rejectSend } = createControlledPromise(); | ||
| const handleMessage = (message) => { | ||
| if (message?.id === id) { | ||
| if (message.type === require_expose_rpc.RpcGMCallTypes.RESOLVE) resolveResult(message.value); | ||
| else if (message.type === require_expose_rpc.RpcGMCallTypes.REJECT) rejectResult(message.error); | ||
| } | ||
| if (once && childProcess?.kill) childProcess.kill("SIGTERM"); | ||
| }; | ||
| const handleClose = (code, signal) => { | ||
| rejectResult(new RpcExitError(code ? `Process ${childProcess.pid} exited with code ${code}${signal ? ` [${signal}]` : ""}` : `Process ${childProcess.pid} exited${signal ? ` [${signal}]` : ""}`, code, signal)); | ||
| removeHandlers(); | ||
| }; | ||
| const removeHandlers = () => { | ||
| childProcess.off("message", handleMessage); | ||
| childProcess.off("close", handleClose); | ||
| }; | ||
| if (once) childProcess.once("message", handleMessage); | ||
| else childProcess.on("message", handleMessage); | ||
| childProcess.on("close", handleClose); | ||
| childProcess.send({ | ||
| type: require_expose_rpc.RpcGMCallTypes.CALL, | ||
| id, | ||
| args | ||
| }, (error) => { | ||
| if (error) { | ||
| rejectSend(error); | ||
| removeHandlers(); | ||
| } else resolveSend(void 0); | ||
| }); | ||
| return sendPromise.then(() => resultPromise); | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/core/rpc/rpc-worker.ts | ||
| const FEDERATION_WORKER_DATA_ENV_KEY = "VMOK_WORKER_DATA_ENV"; | ||
| function createRpcWorker(modulePath, data, memoryLimit, once) { | ||
| const options = { | ||
| env: { | ||
| ...process$1.env, | ||
| [FEDERATION_WORKER_DATA_ENV_KEY]: JSON.stringify(data || {}) | ||
| }, | ||
| stdio: [ | ||
| "inherit", | ||
| "inherit", | ||
| "inherit", | ||
| "ipc" | ||
| ], | ||
| serialization: "advanced" | ||
| }; | ||
| if (memoryLimit) options.execArgv = [`--max-old-space-size=${memoryLimit}`]; | ||
| let childProcess, remoteMethod; | ||
| const id = (0, crypto.randomUUID)(); | ||
| return { | ||
| connect(...args) { | ||
| if (childProcess && !childProcess.connected) { | ||
| childProcess.send({ | ||
| type: require_expose_rpc.RpcGMCallTypes.EXIT, | ||
| id | ||
| }); | ||
| childProcess = void 0; | ||
| remoteMethod = void 0; | ||
| } | ||
| if (!childProcess?.connected) { | ||
| childProcess = child_process.fork(modulePath, options); | ||
| remoteMethod = wrapRpc(childProcess, { | ||
| id, | ||
| once | ||
| }); | ||
| } | ||
| if (!remoteMethod) return Promise.reject(/* @__PURE__ */ new Error("Worker is not connected - cannot perform RPC.")); | ||
| return remoteMethod(...args); | ||
| }, | ||
| terminate() { | ||
| try { | ||
| if (childProcess.connected) childProcess.send({ | ||
| type: require_expose_rpc.RpcGMCallTypes.EXIT, | ||
| id | ||
| }, (err) => { | ||
| if (err) console.error("Error sending message:", err); | ||
| }); | ||
| } catch (error) { | ||
| if (error.code === "EPIPE") console.error("Pipe closed before message could be sent:", error); | ||
| else console.error("Unexpected error:", error); | ||
| } | ||
| childProcess = void 0; | ||
| remoteMethod = void 0; | ||
| }, | ||
| get connected() { | ||
| return Boolean(childProcess?.connected); | ||
| }, | ||
| get process() { | ||
| return childProcess; | ||
| }, | ||
| get id() { | ||
| return id; | ||
| } | ||
| }; | ||
| } | ||
| function getRpcWorkerData() { | ||
| return JSON.parse(process$1.env[FEDERATION_WORKER_DATA_ENV_KEY] || "{}"); | ||
| } | ||
| //#endregion | ||
| //#region src/core/rpc/index.ts | ||
| var rpc_exports = /* @__PURE__ */ require_Action.__exportAll({ | ||
| RpcExitError: () => RpcExitError, | ||
| RpcGMCallTypes: () => require_expose_rpc.RpcGMCallTypes, | ||
| createRpcWorker: () => createRpcWorker, | ||
| exposeRpc: () => require_expose_rpc.exposeRpc, | ||
| getRpcWorkerData: () => getRpcWorkerData, | ||
| wrapRpc: () => wrapRpc | ||
| }); | ||
| //#endregion | ||
| //#region src/core/lib/DtsWorker.ts | ||
| const __filename$1 = (0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href); | ||
| const __dirname$1 = path.default.dirname(__filename$1); | ||
| const __extname = path.default.extname(__filename$1); | ||
| var DtsWorker = class { | ||
| constructor(options) { | ||
| this._options = require_expose_rpc.cloneDeepOptions(options); | ||
| this.removeUnSerializationOptions(); | ||
| this.rpcWorker = createRpcWorker(path.default.resolve(__dirname$1, `./fork-generate-dts${__extname}`), {}, void 0, true); | ||
| this._res = this.rpcWorker.connect(this._options); | ||
| } | ||
| removeUnSerializationOptions() { | ||
| if (this._options.remote?.moduleFederationConfig?.manifest) delete this._options.remote?.moduleFederationConfig?.manifest; | ||
| if (this._options.host?.moduleFederationConfig?.manifest) delete this._options.host?.moduleFederationConfig?.manifest; | ||
| } | ||
| get controlledPromise() { | ||
| const ensureChildProcessExit = () => { | ||
| try { | ||
| const pid = this.rpcWorker.process?.pid; | ||
| const rootPid = process.pid; | ||
| if (pid && rootPid !== pid) process.kill(pid, 0); | ||
| } catch (error) { | ||
| if (require_expose_rpc.isDebugMode()) console.error(error); | ||
| } | ||
| }; | ||
| return Promise.resolve(this._res).then(() => { | ||
| this.exit(); | ||
| ensureChildProcessExit(); | ||
| }).catch((err) => { | ||
| if (require_expose_rpc.isDebugMode()) console.error(err); | ||
| ensureChildProcessExit(); | ||
| }); | ||
| } | ||
| exit() { | ||
| try { | ||
| this.rpcWorker?.terminate(); | ||
| } catch (err) { | ||
| if (require_expose_rpc.isDebugMode()) console.error(err); | ||
| } | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/generateTypesInChildProcess.ts | ||
| async function generateTypesInChildProcess(options) { | ||
| return new DtsWorker(options).controlledPromise; | ||
| } | ||
| //#endregion | ||
| //#region src/core/lib/consumeTypes.ts | ||
| async function consumeTypes(options) { | ||
| await new (require_expose_rpc.getDTSManagerConstructor(options.host?.implementation))(options).consumeTypes(); | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'DtsWorker', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return DtsWorker; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'consumeTypes', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return consumeTypes; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'createRpcWorker', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createRpcWorker; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'generateTypesInChildProcess', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return generateTypesInChildProcess; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'rpc_exports', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return rpc_exports; | ||
| } | ||
| }); |
| import { a as cloneDeepOptions, n as RpcGMCallTypes, o as getDTSManagerConstructor, s as isDebugMode, t as exposeRpc, x as __exportAll } from "./expose-rpc-BiwGpqZ3.mjs"; | ||
| import { fileURLToPath } from "url"; | ||
| import path from "path"; | ||
| import { randomUUID } from "crypto"; | ||
| import * as child_process from "child_process"; | ||
| import * as process$2 from "process"; | ||
| //#region src/core/rpc/rpc-error.ts | ||
| var RpcExitError = class extends Error { | ||
| constructor(message, code, signal) { | ||
| super(message); | ||
| this.code = code; | ||
| this.signal = signal; | ||
| this.name = "RpcExitError"; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/rpc/wrap-rpc.ts | ||
| function createControlledPromise() { | ||
| let resolve = () => void 0; | ||
| let reject = () => void 0; | ||
| return { | ||
| promise: new Promise((aResolve, aReject) => { | ||
| resolve = aResolve; | ||
| reject = aReject; | ||
| }), | ||
| resolve, | ||
| reject | ||
| }; | ||
| } | ||
| function wrapRpc(childProcess, options) { | ||
| return (async (...args) => { | ||
| if (!childProcess.send) throw new Error(`Process ${childProcess.pid} doesn't have IPC channels`); | ||
| else if (!childProcess.connected) throw new Error(`Process ${childProcess.pid} doesn't have open IPC channels`); | ||
| const { id, once } = options; | ||
| const { promise: resultPromise, resolve: resolveResult, reject: rejectResult } = createControlledPromise(); | ||
| const { promise: sendPromise, resolve: resolveSend, reject: rejectSend } = createControlledPromise(); | ||
| const handleMessage = (message) => { | ||
| if (message?.id === id) { | ||
| if (message.type === RpcGMCallTypes.RESOLVE) resolveResult(message.value); | ||
| else if (message.type === RpcGMCallTypes.REJECT) rejectResult(message.error); | ||
| } | ||
| if (once && childProcess?.kill) childProcess.kill("SIGTERM"); | ||
| }; | ||
| const handleClose = (code, signal) => { | ||
| rejectResult(new RpcExitError(code ? `Process ${childProcess.pid} exited with code ${code}${signal ? ` [${signal}]` : ""}` : `Process ${childProcess.pid} exited${signal ? ` [${signal}]` : ""}`, code, signal)); | ||
| removeHandlers(); | ||
| }; | ||
| const removeHandlers = () => { | ||
| childProcess.off("message", handleMessage); | ||
| childProcess.off("close", handleClose); | ||
| }; | ||
| if (once) childProcess.once("message", handleMessage); | ||
| else childProcess.on("message", handleMessage); | ||
| childProcess.on("close", handleClose); | ||
| childProcess.send({ | ||
| type: RpcGMCallTypes.CALL, | ||
| id, | ||
| args | ||
| }, (error) => { | ||
| if (error) { | ||
| rejectSend(error); | ||
| removeHandlers(); | ||
| } else resolveSend(void 0); | ||
| }); | ||
| return sendPromise.then(() => resultPromise); | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/core/rpc/rpc-worker.ts | ||
| const FEDERATION_WORKER_DATA_ENV_KEY = "VMOK_WORKER_DATA_ENV"; | ||
| function createRpcWorker(modulePath, data, memoryLimit, once) { | ||
| const options = { | ||
| env: { | ||
| ...process$2.env, | ||
| [FEDERATION_WORKER_DATA_ENV_KEY]: JSON.stringify(data || {}) | ||
| }, | ||
| stdio: [ | ||
| "inherit", | ||
| "inherit", | ||
| "inherit", | ||
| "ipc" | ||
| ], | ||
| serialization: "advanced" | ||
| }; | ||
| if (memoryLimit) options.execArgv = [`--max-old-space-size=${memoryLimit}`]; | ||
| let childProcess, remoteMethod; | ||
| const id = randomUUID(); | ||
| return { | ||
| connect(...args) { | ||
| if (childProcess && !childProcess.connected) { | ||
| childProcess.send({ | ||
| type: RpcGMCallTypes.EXIT, | ||
| id | ||
| }); | ||
| childProcess = void 0; | ||
| remoteMethod = void 0; | ||
| } | ||
| if (!childProcess?.connected) { | ||
| childProcess = child_process.fork(modulePath, options); | ||
| remoteMethod = wrapRpc(childProcess, { | ||
| id, | ||
| once | ||
| }); | ||
| } | ||
| if (!remoteMethod) return Promise.reject(/* @__PURE__ */ new Error("Worker is not connected - cannot perform RPC.")); | ||
| return remoteMethod(...args); | ||
| }, | ||
| terminate() { | ||
| try { | ||
| if (childProcess.connected) childProcess.send({ | ||
| type: RpcGMCallTypes.EXIT, | ||
| id | ||
| }, (err) => { | ||
| if (err) console.error("Error sending message:", err); | ||
| }); | ||
| } catch (error) { | ||
| if (error.code === "EPIPE") console.error("Pipe closed before message could be sent:", error); | ||
| else console.error("Unexpected error:", error); | ||
| } | ||
| childProcess = void 0; | ||
| remoteMethod = void 0; | ||
| }, | ||
| get connected() { | ||
| return Boolean(childProcess?.connected); | ||
| }, | ||
| get process() { | ||
| return childProcess; | ||
| }, | ||
| get id() { | ||
| return id; | ||
| } | ||
| }; | ||
| } | ||
| function getRpcWorkerData() { | ||
| return JSON.parse(process$2.env[FEDERATION_WORKER_DATA_ENV_KEY] || "{}"); | ||
| } | ||
| //#endregion | ||
| //#region src/core/rpc/index.ts | ||
| var rpc_exports = /* @__PURE__ */ __exportAll({ | ||
| RpcExitError: () => RpcExitError, | ||
| RpcGMCallTypes: () => RpcGMCallTypes, | ||
| createRpcWorker: () => createRpcWorker, | ||
| exposeRpc: () => exposeRpc, | ||
| getRpcWorkerData: () => getRpcWorkerData, | ||
| wrapRpc: () => wrapRpc | ||
| }); | ||
| //#endregion | ||
| //#region src/core/lib/DtsWorker.ts | ||
| const __filename = fileURLToPath(import.meta.url); | ||
| const __dirname = path.dirname(__filename); | ||
| const __extname = path.extname(__filename); | ||
| var DtsWorker = class { | ||
| constructor(options) { | ||
| this._options = cloneDeepOptions(options); | ||
| this.removeUnSerializationOptions(); | ||
| this.rpcWorker = createRpcWorker(path.resolve(__dirname, `./fork-generate-dts${__extname}`), {}, void 0, true); | ||
| this._res = this.rpcWorker.connect(this._options); | ||
| } | ||
| removeUnSerializationOptions() { | ||
| if (this._options.remote?.moduleFederationConfig?.manifest) delete this._options.remote?.moduleFederationConfig?.manifest; | ||
| if (this._options.host?.moduleFederationConfig?.manifest) delete this._options.host?.moduleFederationConfig?.manifest; | ||
| } | ||
| get controlledPromise() { | ||
| const ensureChildProcessExit = () => { | ||
| try { | ||
| const pid = this.rpcWorker.process?.pid; | ||
| const rootPid = process.pid; | ||
| if (pid && rootPid !== pid) process.kill(pid, 0); | ||
| } catch (error) { | ||
| if (isDebugMode()) console.error(error); | ||
| } | ||
| }; | ||
| return Promise.resolve(this._res).then(() => { | ||
| this.exit(); | ||
| ensureChildProcessExit(); | ||
| }).catch((err) => { | ||
| if (isDebugMode()) console.error(err); | ||
| ensureChildProcessExit(); | ||
| }); | ||
| } | ||
| exit() { | ||
| try { | ||
| this.rpcWorker?.terminate(); | ||
| } catch (err) { | ||
| if (isDebugMode()) console.error(err); | ||
| } | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/generateTypesInChildProcess.ts | ||
| async function generateTypesInChildProcess(options) { | ||
| return new DtsWorker(options).controlledPromise; | ||
| } | ||
| //#endregion | ||
| //#region src/core/lib/consumeTypes.ts | ||
| async function consumeTypes(options) { | ||
| await new (getDTSManagerConstructor(options.host?.implementation))(options).consumeTypes(); | ||
| } | ||
| //#endregion | ||
| export { createRpcWorker as a, rpc_exports as i, generateTypesInChildProcess as n, DtsWorker as r, consumeTypes as t }; |
| import { a as MF_SERVER_IDENTIFIER, n as ActionKind, o as UpdateMode, r as DEFAULT_TAR_NAME, t as Action } from "./Action-DNNg2YDh.mjs"; | ||
| import { a as getIdentifier, c as logger$1, i as getFreePort, l as LogKind, n as UpdateKind, o as getIPV4, r as fib, s as fileLog, t as Broker, u as APIKind } from "./Broker-Cmbh_XVO.mjs"; | ||
| import { createRequire } from "node:module"; | ||
| import fs, { existsSync, mkdirSync, writeFileSync } from "fs"; | ||
| import { fileURLToPath } from "url"; | ||
| import path, { dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "path"; | ||
| import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from "fs/promises"; | ||
| import { utils } from "@module-federation/managers"; | ||
| import typescript from "typescript"; | ||
| import { ENCODE_NAME_PREFIX, MANIFEST_EXT, TEMP_DIR, decodeName, getProcessEnv, inferAutoPublicPath, parseEntry } from "@module-federation/sdk"; | ||
| import ansiColors from "ansi-colors"; | ||
| import { Agent } from "undici"; | ||
| import { ThirdPartyExtractor } from "@module-federation/third-party-dts-extractor"; | ||
| import AdmZip from "adm-zip"; | ||
| import crypto from "crypto"; | ||
| import { TYPE_001, typeDescMap } from "@module-federation/error-codes"; | ||
| import { logAndReport } from "@module-federation/error-codes/node"; | ||
| import { execFile, fork } from "child_process"; | ||
| import util from "util"; | ||
| import WebSocket from "isomorphic-ws"; | ||
| import http from "http"; | ||
| import process$1 from "process"; | ||
| //#region \0rolldown/runtime.js | ||
| var __defProp = Object.defineProperty; | ||
| var __exportAll = (all, no_symbols) => { | ||
| let target = {}; | ||
| for (var name in all) { | ||
| __defProp(target, name, { | ||
| get: all[name], | ||
| enumerable: true | ||
| }); | ||
| } | ||
| if (!no_symbols) { | ||
| __defProp(target, Symbol.toStringTag, { value: "Module" }); | ||
| } | ||
| return target; | ||
| }; | ||
| var __require = /* @__PURE__ */ createRequire(import.meta.url); | ||
| //#endregion | ||
| //#region src/server/message/Action/AddPublisher.ts | ||
| var AddPublisherAction = class extends Action { | ||
| constructor(payload) { | ||
| super({ payload }, ActionKind.ADD_PUBLISHER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/AddSubscriber.ts | ||
| var AddSubscriberAction = class extends Action { | ||
| constructor(payload) { | ||
| super({ payload }, ActionKind.ADD_SUBSCRIBER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/ExitSubscriber.ts | ||
| var ExitSubscriberAction = class extends Action { | ||
| constructor(payload) { | ||
| super({ payload }, ActionKind.EXIT_SUBSCRIBER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/ExitPublisher.ts | ||
| var ExitPublisherAction = class extends Action { | ||
| constructor(payload) { | ||
| super({ payload }, ActionKind.EXIT_PUBLISHER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/NotifyWebClient.ts | ||
| var NotifyWebClientAction = class extends Action { | ||
| constructor(payload) { | ||
| super({ payload }, ActionKind.NOTIFY_WEB_CLIENT); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/UpdatePublisher.ts | ||
| var UpdatePublisherAction = class extends Action { | ||
| constructor(payload) { | ||
| super({ payload }, ActionKind.UPDATE_PUBLISHER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/broker/createBroker.ts | ||
| const __filename = fileURLToPath(import.meta.url); | ||
| const __dirname = path.dirname(__filename); | ||
| function createBroker() { | ||
| const sub = fork(path.resolve(__dirname, "./start-broker.js"), [], { | ||
| detached: true, | ||
| stdio: "ignore", | ||
| env: process.env | ||
| }); | ||
| sub.send("start"); | ||
| sub.unref(); | ||
| return sub; | ||
| } | ||
| //#endregion | ||
| //#region src/server/DevServer.ts | ||
| var ModuleFederationDevServer = class { | ||
| constructor(ctx) { | ||
| this._publishWebSocket = null; | ||
| this._subscriberWebsocketMap = {}; | ||
| this._reconnect = true; | ||
| this._reconnectTimes = 0; | ||
| this._isConnected = false; | ||
| this._isReconnecting = false; | ||
| this._updateCallback = () => Promise.resolve(void 0); | ||
| const { name, remotes, remoteTypeTarPath, updateCallback } = ctx; | ||
| this._ip = getIPV4(); | ||
| this._name = name; | ||
| this._remotes = remotes; | ||
| this._remoteTypeTarPath = remoteTypeTarPath; | ||
| this._updateCallback = updateCallback; | ||
| this._stopWhenSIGTERMOrSIGINT(); | ||
| this._handleUnexpectedExit(); | ||
| this._connectPublishToServer(); | ||
| } | ||
| _connectPublishToServer() { | ||
| if (!this._reconnect) return; | ||
| fileLog(`Publisher:${this._name} Trying to connect to ws://${this._ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}...`, MF_SERVER_IDENTIFIER, "info"); | ||
| this._publishWebSocket = new WebSocket(`ws://${this._ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}?WEB_SOCKET_CONNECT_MAGIC_ID=${Broker.WEB_SOCKET_CONNECT_MAGIC_ID}`); | ||
| this._publishWebSocket.on("open", () => { | ||
| fileLog(`Current pid: ${process.pid}, publisher:${this._name} connected to ws://${this._ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}, starting service...`, MF_SERVER_IDENTIFIER, "info"); | ||
| this._isConnected = true; | ||
| const addPublisherAction = new AddPublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| this._publishWebSocket?.send(JSON.stringify(addPublisherAction)); | ||
| this._connectSubscribers(); | ||
| }); | ||
| this._publishWebSocket.on("message", async (message) => { | ||
| try { | ||
| const parsedMessage = JSON.parse(message.toString()); | ||
| if (parsedMessage.type === "Log") { | ||
| if (parsedMessage.kind === LogKind.BrokerExitLog) { | ||
| fileLog(`Receive broker exit signal, ${this._name} service will exit...`, MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| } | ||
| } | ||
| if (parsedMessage.type === "API") { | ||
| if (parsedMessage.kind === APIKind.FETCH_TYPES) { | ||
| const { payload: { remoteInfo } } = parsedMessage; | ||
| fileLog(`${this._name} Receive broker FETCH_TYPES, payload as follows: ${JSON.stringify(remoteInfo, null, 2)}.`, MF_SERVER_IDENTIFIER, "info"); | ||
| await this.fetchDynamicRemoteTypes({ remoteInfo }); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| console.error(err); | ||
| const exitPublisher = new ExitPublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip | ||
| }); | ||
| const exitSubscriber = new ExitSubscriberAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| publishers: this._remotes.map((remote) => ({ | ||
| name: remote.name, | ||
| ip: remote.ip | ||
| })) | ||
| }); | ||
| this._publishWebSocket?.send(JSON.stringify(exitPublisher)); | ||
| this._publishWebSocket?.send(JSON.stringify(exitSubscriber)); | ||
| fileLog("Parse messages error, ModuleFederationDevServer will exit...", MF_SERVER_IDENTIFIER, "fatal"); | ||
| this._exit(); | ||
| } | ||
| }); | ||
| this._publishWebSocket.on("close", (code) => { | ||
| fileLog(`Connection closed with code ${code}.`, MF_SERVER_IDENTIFIER, "warn"); | ||
| this._publishWebSocket && this._publishWebSocket.close(); | ||
| this._publishWebSocket = null; | ||
| if (!this._reconnect) return; | ||
| const reconnectTime = fib(++this._reconnectTimes); | ||
| fileLog(`start reconnecting to server after ${reconnectTime}s.`, MF_SERVER_IDENTIFIER, "info"); | ||
| setTimeout(() => this._connectPublishToServer(), reconnectTime * 1e3); | ||
| }); | ||
| this._publishWebSocket.on("error", this._tryCreateBackgroundBroker.bind(this)); | ||
| } | ||
| _connectSubscriberToServer(remote) { | ||
| const { name, ip } = remote; | ||
| fileLog(`remote module:${name} trying to connect to ws://${ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}...`, MF_SERVER_IDENTIFIER, "info"); | ||
| const identifier = getIdentifier({ | ||
| name, | ||
| ip | ||
| }); | ||
| this._subscriberWebsocketMap[identifier] = new WebSocket(`ws://${ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}?WEB_SOCKET_CONNECT_MAGIC_ID=${Broker.WEB_SOCKET_CONNECT_MAGIC_ID}`); | ||
| this._subscriberWebsocketMap[identifier].on("open", () => { | ||
| fileLog(`Current pid: ${process.pid} remote module: ${name} connected to ws://${ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}, starting service...`, MF_SERVER_IDENTIFIER, "info"); | ||
| const addSubscriber = new AddSubscriberAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| publishers: [{ | ||
| name, | ||
| ip | ||
| }] | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].send(JSON.stringify(addSubscriber)); | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].on("message", async (message) => { | ||
| try { | ||
| const parsedMessage = JSON.parse(message.toString()); | ||
| if (parsedMessage.type === "Log") { | ||
| if (parsedMessage.kind === LogKind.BrokerExitLog) { | ||
| fileLog(`${identifier}'s Server exit, thus ${identifier} will no longer has reload ability.`, MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| } | ||
| } | ||
| if (parsedMessage.type === "API") { | ||
| if (parsedMessage.kind === APIKind.UPDATE_SUBSCRIBER) { | ||
| const { payload: { updateKind, updateSourcePaths, name: subscribeName, remoteTypeTarPath, updateMode } } = parsedMessage; | ||
| await this._updateSubscriber({ | ||
| remoteTypeTarPath, | ||
| name: subscribeName, | ||
| updateKind, | ||
| updateMode, | ||
| updateSourcePaths | ||
| }); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| console.error(err); | ||
| const exitSubscriber = new ExitSubscriberAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| publishers: [{ | ||
| name, | ||
| ip | ||
| }] | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].send(JSON.stringify(exitSubscriber)); | ||
| fileLog(`${identifier} exit, | ||
| error: ${err instanceof Error ? err.toString() : JSON.stringify(err)} | ||
| `, MF_SERVER_IDENTIFIER, "warn"); | ||
| } | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].on("close", (code) => { | ||
| fileLog(`Connection closed with code ${code}.`, MF_SERVER_IDENTIFIER, "warn"); | ||
| this._subscriberWebsocketMap[identifier]?.close(); | ||
| delete this._subscriberWebsocketMap[identifier]; | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].on("error", (err) => { | ||
| if ("code" in err && err.code === "ETIMEDOUT") fileLog(`Can not connect ${JSON.stringify(remote)}, please make sure this remote is started locally.`, MF_SERVER_IDENTIFIER, "warn"); | ||
| else console.error(err); | ||
| this._subscriberWebsocketMap[identifier]?.close(); | ||
| delete this._subscriberWebsocketMap[identifier]; | ||
| }); | ||
| } | ||
| _connectSubscribers() { | ||
| this._remotes.forEach((remote) => { | ||
| this._connectSubscriberToServer(remote); | ||
| }); | ||
| } | ||
| async _updateSubscriber(options) { | ||
| const { updateMode, updateKind, updateSourcePaths, name, remoteTypeTarPath, remoteInfo } = options; | ||
| fileLog(`[_updateSubscriber] run, options: ${JSON.stringify(options, null, 2)}`, MF_SERVER_IDENTIFIER, "warn"); | ||
| if (updateMode === UpdateMode.PASSIVE && updateSourcePaths.includes(this._name)) { | ||
| fileLog(`[_updateSubscriber] run, updateSourcePaths:${updateSourcePaths} includes ${this._name}, update ignore!`, MF_SERVER_IDENTIFIER, "warn"); | ||
| return; | ||
| } | ||
| if (updateSourcePaths.slice(-1)[0] === this._name) { | ||
| fileLog(`[_updateSubscriber] run, updateSourcePaths:${updateSourcePaths} ends is ${this._name}, update ignore!`, MF_SERVER_IDENTIFIER, "warn"); | ||
| return; | ||
| } | ||
| fileLog(`[_updateSubscriber] run, updateSourcePaths:${updateSourcePaths}, current module:${this._name}, update start...`, MF_SERVER_IDENTIFIER, "info"); | ||
| await this._updateCallback({ | ||
| name, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths, | ||
| remoteTypeTarPath, | ||
| remoteInfo | ||
| }); | ||
| const newUpdateSourcePaths = updateSourcePaths.concat(this._name); | ||
| const updatePublisher = new UpdatePublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| updateMode: UpdateMode.PASSIVE, | ||
| updateKind, | ||
| updateSourcePaths: newUpdateSourcePaths, | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| fileLog(`[_updateSubscriber] run, updateSourcePaths:${newUpdateSourcePaths}, update publisher ${this._name} start...`, MF_SERVER_IDENTIFIER, "info"); | ||
| this._publishWebSocket?.send(JSON.stringify(updatePublisher)); | ||
| } | ||
| _tryCreateBackgroundBroker(err) { | ||
| if (!((err?.code === "ECONNREFUSED" || err?.code === "ETIMEDOUT") && err.port === Broker.DEFAULT_WEB_SOCKET_PORT)) { | ||
| fileLog(`websocket error: ${err.stack}`, MF_SERVER_IDENTIFIER, "fatal"); | ||
| return; | ||
| } | ||
| fileLog(`Failed to connect to ws://${this._ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}...`, MF_SERVER_IDENTIFIER, "fatal"); | ||
| this._isReconnecting = true; | ||
| setTimeout(() => { | ||
| this._isReconnecting = false; | ||
| if (this._reconnect === false) return; | ||
| fileLog("Creating new background broker...", MF_SERVER_IDENTIFIER, "warn"); | ||
| createBroker().on("message", (message) => { | ||
| if (message === "ready") { | ||
| fileLog("background broker started.", MF_SERVER_IDENTIFIER, "info"); | ||
| this._reconnectTimes = 1; | ||
| if (process.send) process.send("ready"); | ||
| } | ||
| }); | ||
| }, Math.ceil(100 * Math.random())); | ||
| } | ||
| _stopWhenSIGTERMOrSIGINT() { | ||
| process.on("SIGTERM", () => { | ||
| fileLog(`Process(${process.pid}) SIGTERM, ModuleFederationDevServer will exit...`, MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| }); | ||
| process.on("SIGINT", () => { | ||
| fileLog(`Process(${process.pid}) SIGINT, ModuleFederationDevServer will exit...`, MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| }); | ||
| } | ||
| _handleUnexpectedExit() { | ||
| process.on("unhandledRejection", (error) => { | ||
| if (this._isReconnecting) return; | ||
| console.error("Unhandled Rejection Error: ", error); | ||
| fileLog(`Process(${process.pid}) unhandledRejection, garfishModuleServer will exit...`, MF_SERVER_IDENTIFIER, "error"); | ||
| this._exit(); | ||
| }); | ||
| process.on("uncaughtException", (error) => { | ||
| if (this._isReconnecting) return; | ||
| console.error("Unhandled Exception Error: ", error); | ||
| fileLog(`Process(${process.pid}) uncaughtException, garfishModuleServer will exit...`, MF_SERVER_IDENTIFIER, "error"); | ||
| this._exit(); | ||
| }); | ||
| } | ||
| _exit() { | ||
| this._reconnect = false; | ||
| if (this._publishWebSocket) { | ||
| const exitPublisher = new ExitPublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(exitPublisher)); | ||
| this._publishWebSocket.on("message", (message) => { | ||
| const parsedMessage = JSON.parse(message.toString()); | ||
| fileLog(`[${parsedMessage.kind}]: ${JSON.stringify(parsedMessage)}`, MF_SERVER_IDENTIFIER, "info"); | ||
| }); | ||
| } | ||
| if (this._publishWebSocket) { | ||
| this._publishWebSocket.close(); | ||
| this._publishWebSocket = null; | ||
| } | ||
| process.exit(0); | ||
| } | ||
| exit() { | ||
| this._exit(); | ||
| } | ||
| update(options) { | ||
| if (!this._publishWebSocket || !this._isConnected) return; | ||
| const { updateKind, updateMode, updateSourcePaths, clientName } = options; | ||
| fileLog(`update run, ${this._name} module update, updateKind: ${updateKind}, updateMode: ${updateMode}, updateSourcePaths: ${updateSourcePaths}`, MF_SERVER_IDENTIFIER, "info"); | ||
| if (updateKind === UpdateKind.RELOAD_PAGE) { | ||
| const notifyWebClient = new NotifyWebClientAction({ | ||
| name: clientName || this._name, | ||
| updateMode | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(notifyWebClient)); | ||
| return; | ||
| } | ||
| const updatePublisher = new UpdatePublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths: [this._name], | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(updatePublisher)); | ||
| } | ||
| async fetchDynamicRemoteTypes(options) { | ||
| const { remoteInfo, once } = options; | ||
| const updateMode = UpdateMode.PASSIVE; | ||
| const updateKind = UpdateKind.UPDATE_TYPE; | ||
| fileLog(`fetchDynamicRemoteTypes: remoteInfo: ${JSON.stringify(remoteInfo)}`, MF_SERVER_IDENTIFIER, "info"); | ||
| await this._updateCallback({ | ||
| name: this._name, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths: [], | ||
| remoteTypeTarPath: "", | ||
| remoteInfo, | ||
| once | ||
| }); | ||
| const updatePublisher = new UpdatePublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths: [this._name], | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(updatePublisher)); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/createHttpServer.ts | ||
| async function createHttpServer(options) { | ||
| const { typeTarPath } = options; | ||
| const freeport = await getFreePort(); | ||
| const server = http.createServer((req, res) => { | ||
| if ((req.url?.split("?")[0] ?? "/") === `/${DEFAULT_TAR_NAME}`) { | ||
| res.statusCode = 200; | ||
| res.setHeader("Content-Type", "application/x-gzip"); | ||
| if (req.method === "HEAD") { | ||
| res.end(); | ||
| return; | ||
| } | ||
| const stream = fs.createReadStream(typeTarPath); | ||
| stream.on("error", () => { | ||
| if (!res.headersSent) res.statusCode = 500; | ||
| res.end(); | ||
| }); | ||
| res.on("close", () => { | ||
| stream.destroy(); | ||
| }); | ||
| stream.pipe(res); | ||
| return; | ||
| } | ||
| res.statusCode = 404; | ||
| res.end(); | ||
| }); | ||
| server.listen(freeport); | ||
| return { | ||
| server, | ||
| serverAddress: `http://${getIPV4()}:${freeport}` | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/core/lib/typeScriptCompiler.ts | ||
| const STARTS_WITH_SLASH = /^\//; | ||
| const DEFINITION_FILE_EXTENSION = ".d.ts"; | ||
| const retrieveMfTypesPath = (tsConfig, remoteOptions) => normalize(tsConfig.compilerOptions.outDir.replace(remoteOptions.compiledTypesFolder, "")); | ||
| const retrieveOriginalOutDir = (tsConfig, remoteOptions) => normalize(tsConfig.compilerOptions.outDir.replace(remoteOptions.compiledTypesFolder, "").replace(remoteOptions.typesFolder, "")); | ||
| const retrieveMfAPITypesPath = (tsConfig, remoteOptions) => join(retrieveOriginalOutDir(tsConfig, remoteOptions), `${remoteOptions.typesFolder}.d.ts`); | ||
| function writeTempTsConfig(tsConfig, context, name, cwd) { | ||
| const createHash = (contents) => { | ||
| return crypto.createHash("md5").update(contents).digest("hex"); | ||
| }; | ||
| const hash = createHash(`${JSON.stringify(tsConfig)}${name}${Date.now()}`); | ||
| const tempTsConfigJsonPath = resolve(cwd ?? context, "node_modules", TEMP_DIR, `tsconfig.${hash}.json`); | ||
| mkdirSync(dirname(tempTsConfigJsonPath), { recursive: true }); | ||
| writeFileSync(tempTsConfigJsonPath, JSON.stringify(tsConfig, null, 2)); | ||
| return tempTsConfigJsonPath; | ||
| } | ||
| const removeExt = (f) => { | ||
| const vueExt = ".vue"; | ||
| const ext = extname(f); | ||
| if (ext === vueExt) return f; | ||
| const regexPattern = new RegExp(`\\${ext}$`); | ||
| return f.replace(regexPattern, ""); | ||
| }; | ||
| function getExposeKey(options) { | ||
| const { filePath, rootDir, outDir, mapExposeToEntry } = options; | ||
| return mapExposeToEntry[relative(outDir, filePath.replace(new RegExp(`\\.d.ts$`), ""))]; | ||
| } | ||
| const processTypesFile = async (options) => { | ||
| const { outDir, filePath, rootDir, cb, mapExposeToEntry, mfTypePath } = options; | ||
| if (!existsSync(filePath)) return; | ||
| if ((await stat(filePath)).isDirectory()) { | ||
| const files = await readdir(filePath); | ||
| await Promise.all(files.map((file) => processTypesFile({ | ||
| ...options, | ||
| filePath: join(filePath, file) | ||
| }))); | ||
| } else if (filePath.endsWith(".d.ts")) { | ||
| const exposeKey = getExposeKey({ | ||
| filePath, | ||
| rootDir, | ||
| outDir, | ||
| mapExposeToEntry | ||
| }); | ||
| if (exposeKey) { | ||
| const mfeTypeEntry = join(mfTypePath, `${exposeKey === "." ? "index" : exposeKey}${DEFINITION_FILE_EXTENSION}`); | ||
| const mfeTypeEntryDirectory = dirname(mfeTypeEntry); | ||
| const relativePathToOutput = relative(mfeTypeEntryDirectory, filePath).replace(DEFINITION_FILE_EXTENSION, "").replace(STARTS_WITH_SLASH, "").split(sep).join("/"); | ||
| mkdirSync(mfeTypeEntryDirectory, { recursive: true }); | ||
| await writeFile(mfeTypeEntry, `export * from './${relativePathToOutput}';\nexport { default } from './${relativePathToOutput}';`); | ||
| } | ||
| cb(await readFile(filePath, "utf8")); | ||
| } | ||
| }; | ||
| const getPMFromUserAgent = () => { | ||
| const userAgent = process.env["npm_config_user_agent"]; | ||
| if (userAgent == null) return "null"; | ||
| return userAgent.split("/")[0]; | ||
| }; | ||
| const resolvePackageManagerExecutable = () => { | ||
| switch (getPMFromUserAgent()) { | ||
| case "yarn": return "yarn"; | ||
| default: return "npx"; | ||
| } | ||
| }; | ||
| const splitCommandArgs = (value) => { | ||
| const args = []; | ||
| let current = ""; | ||
| let quote = null; | ||
| let escaped = false; | ||
| for (const char of value) { | ||
| if (escaped) { | ||
| current += char; | ||
| escaped = false; | ||
| continue; | ||
| } | ||
| if (char === "\\") { | ||
| escaped = true; | ||
| continue; | ||
| } | ||
| if (quote) { | ||
| if (char === quote) quote = null; | ||
| else current += char; | ||
| continue; | ||
| } | ||
| if (char === "\"" || char === "'") { | ||
| quote = char; | ||
| continue; | ||
| } | ||
| if (char.trim() === "") { | ||
| if (current) { | ||
| args.push(current); | ||
| current = ""; | ||
| } | ||
| continue; | ||
| } | ||
| current += char; | ||
| } | ||
| if (current) args.push(current); | ||
| return args; | ||
| }; | ||
| const formatCommandForDisplay = (executable, args) => { | ||
| const formatArg = (arg) => { | ||
| if (/[\s'"]/.test(arg)) return JSON.stringify(arg); | ||
| return arg; | ||
| }; | ||
| return [executable, ...args].map(formatArg).join(" "); | ||
| }; | ||
| const compileTs = async (mapComponentsToExpose, tsConfig, remoteOptions) => { | ||
| if (!Object.keys(mapComponentsToExpose).length) return; | ||
| const { compilerOptions } = tsConfig; | ||
| const tempTsConfigJsonPath = writeTempTsConfig(tsConfig, remoteOptions.context, remoteOptions.moduleFederationConfig.name || "mf", typeof remoteOptions.moduleFederationConfig.dts !== "boolean" ? remoteOptions.moduleFederationConfig.dts?.cwd ?? void 0 : void 0); | ||
| logger$1.debug(`tempTsConfigJsonPath: ${tempTsConfigJsonPath}`); | ||
| try { | ||
| const mfTypePath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| const thirdPartyExtractor = new ThirdPartyExtractor({ | ||
| destDir: resolve(mfTypePath, "node_modules"), | ||
| context: remoteOptions.context, | ||
| exclude: typeof remoteOptions.extractThirdParty === "object" ? remoteOptions.extractThirdParty.exclude : void 0 | ||
| }); | ||
| const execPromise = util.promisify(execFile); | ||
| const pmExecutable = resolvePackageManagerExecutable(); | ||
| const compilerArgs = splitCommandArgs(remoteOptions.compilerInstance); | ||
| const cmdArgs = [ | ||
| ...compilerArgs.length > 0 ? compilerArgs : [remoteOptions.compilerInstance], | ||
| "--project", | ||
| tempTsConfigJsonPath | ||
| ]; | ||
| const cmd = formatCommandForDisplay(pmExecutable, cmdArgs); | ||
| try { | ||
| await execPromise(pmExecutable, cmdArgs, { | ||
| cwd: typeof remoteOptions.moduleFederationConfig.dts !== "boolean" ? remoteOptions.moduleFederationConfig.dts?.cwd ?? void 0 : void 0, | ||
| shell: process.platform === "win32" | ||
| }); | ||
| } catch (err) { | ||
| if (compilerOptions.tsBuildInfoFile) try { | ||
| await rm(compilerOptions.tsBuildInfoFile); | ||
| } catch (e) {} | ||
| logAndReport(TYPE_001, typeDescMap, { cmd }, (msg) => { | ||
| throw new Error(msg); | ||
| }, void 0); | ||
| } | ||
| const mapExposeToEntry = Object.fromEntries(Object.entries(mapComponentsToExpose).map(([exposed, filename]) => { | ||
| const normalizedFileName = normalize(filename); | ||
| let relativeFileName = ""; | ||
| if (isAbsolute(normalizedFileName)) relativeFileName = relative(tsConfig.compilerOptions.rootDir, normalizedFileName); | ||
| else relativeFileName = relative(tsConfig.compilerOptions.rootDir, resolve(remoteOptions.context, normalizedFileName)); | ||
| return [removeExt(relativeFileName), exposed]; | ||
| })); | ||
| const cb = remoteOptions.extractThirdParty ? thirdPartyExtractor.collectPkgs.bind(thirdPartyExtractor) : () => void 0; | ||
| await processTypesFile({ | ||
| outDir: compilerOptions.outDir, | ||
| filePath: compilerOptions.outDir, | ||
| rootDir: compilerOptions.rootDir, | ||
| mfTypePath, | ||
| cb, | ||
| mapExposeToEntry | ||
| }); | ||
| if (remoteOptions.extractThirdParty) await thirdPartyExtractor.copyDts(); | ||
| if (remoteOptions.deleteTsConfig) await rm(tempTsConfigJsonPath); | ||
| } catch (err) { | ||
| throw err; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/archiveHandler.ts | ||
| const retrieveTypesZipPath = (mfTypesPath, remoteOptions) => join(mfTypesPath.replace(remoteOptions.typesFolder, ""), `${remoteOptions.typesFolder}.zip`); | ||
| const createTypesArchive = async (tsConfig, remoteOptions) => { | ||
| const mfTypesPath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| const zip = new AdmZip(); | ||
| zip.addLocalFolder(mfTypesPath); | ||
| return zip.writeZipPromise(retrieveTypesZipPath(mfTypesPath, remoteOptions)); | ||
| }; | ||
| const downloadErrorLogger = (destinationFolder, fileToDownload) => (reason) => { | ||
| throw { | ||
| ...reason, | ||
| message: `Network error: Unable to download federated mocks for '${destinationFolder}' from '${fileToDownload}' because '${reason.message}'` | ||
| }; | ||
| }; | ||
| const retrieveTypesArchiveDestinationPath = (hostOptions, destinationFolder) => { | ||
| return resolve(hostOptions.context, hostOptions.typesFolder, destinationFolder); | ||
| }; | ||
| const downloadTypesArchive = (hostOptions) => { | ||
| let retries = 0; | ||
| return async ([destinationFolder, fileToDownload]) => { | ||
| const destinationPath = retrieveTypesArchiveDestinationPath(hostOptions, destinationFolder); | ||
| while (retries++ < hostOptions.maxRetries) try { | ||
| const url = new URL(fileToDownload).href; | ||
| const response = await nativeFetch(url, { | ||
| responseType: "arraybuffer", | ||
| timeout: hostOptions.timeout, | ||
| family: hostOptions.family | ||
| }).catch(downloadErrorLogger(destinationFolder, url)); | ||
| if (typeof response.headers?.["content-type"] === "string" && response.headers["content-type"].includes("text/html")) throw new Error(`${url} receives invalid content-type: ${response.headers["content-type"]}`); | ||
| try { | ||
| if (hostOptions.deleteTypesFolder) await rm(destinationPath, { | ||
| recursive: true, | ||
| force: true | ||
| }); | ||
| } catch (error) { | ||
| fileLog(`Unable to remove types folder, ${error}`, "downloadTypesArchive", "error"); | ||
| } | ||
| new AdmZip(Buffer.from(response.data)).extractAllTo(destinationPath, true); | ||
| fileLog(`zip.extractAllTo success destinationPath: ${destinationPath}; url: ${url}`, "downloadTypesArchive", "info"); | ||
| return [destinationFolder, destinationPath]; | ||
| } catch (error) { | ||
| fileLog(`Error during types archive download: ${error?.message || "unknown error"}`, "downloadTypesArchive", "error"); | ||
| if (retries >= hostOptions.maxRetries) { | ||
| logger$1.error(`Failed to download types archive from "${fileToDownload}". Set FEDERATION_DEBUG=true for details.`); | ||
| if (hostOptions.abortOnError !== false) throw error; | ||
| return; | ||
| } | ||
| } | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/core/configurations/hostPlugin.ts | ||
| const defaultOptions$1 = { | ||
| typesFolder: "@mf-types", | ||
| remoteTypesFolder: "@mf-types", | ||
| deleteTypesFolder: true, | ||
| maxRetries: 3, | ||
| implementation: "", | ||
| context: process.cwd(), | ||
| abortOnError: true, | ||
| consumeAPITypes: false, | ||
| runtimePkgs: [], | ||
| remoteTypeUrls: {}, | ||
| timeout: 6e4, | ||
| typesOnBuild: false, | ||
| family: 0 | ||
| }; | ||
| const buildZipUrl = (hostOptions, url) => { | ||
| const remoteUrl = new URL(url, "file:"); | ||
| remoteUrl.pathname = `${remoteUrl.pathname.split("/").slice(0, -1).join("/")}/${hostOptions.remoteTypesFolder}.zip`; | ||
| return remoteUrl.protocol === "file:" ? remoteUrl.pathname : remoteUrl.href; | ||
| }; | ||
| const buildApiTypeUrl = (zipUrl) => { | ||
| if (!zipUrl) return; | ||
| return zipUrl.replace(".zip", ".d.ts"); | ||
| }; | ||
| const retrieveRemoteInfo = (options) => { | ||
| const { hostOptions, remoteAlias, remote } = options; | ||
| const { remoteTypeUrls } = hostOptions; | ||
| let decodedRemote = remote; | ||
| if (decodedRemote.startsWith(ENCODE_NAME_PREFIX)) decodedRemote = decodeName(decodedRemote, ENCODE_NAME_PREFIX); | ||
| const parsedInfo = parseEntry(decodedRemote, void 0, "@"); | ||
| const url = "entry" in parsedInfo ? parsedInfo.entry : parsedInfo.name === decodedRemote ? decodedRemote : ""; | ||
| let zipUrl = ""; | ||
| let apiTypeUrl = ""; | ||
| const name = parsedInfo.name || remoteAlias; | ||
| const remoteTypeUrl = typeof remoteTypeUrls === "object" && remoteTypeUrls[name]; | ||
| if (remoteTypeUrl) { | ||
| zipUrl = remoteTypeUrl.zip; | ||
| apiTypeUrl = remoteTypeUrl.api; | ||
| } | ||
| const shouldResolveTypeUrlsByConvention = Boolean(url && !url.includes(MANIFEST_EXT)); | ||
| if (!zipUrl && shouldResolveTypeUrlsByConvention) zipUrl = buildZipUrl(hostOptions, url); | ||
| if (!apiTypeUrl && zipUrl && (remoteTypeUrl || shouldResolveTypeUrlsByConvention)) apiTypeUrl = buildApiTypeUrl(zipUrl); | ||
| return { | ||
| name, | ||
| url, | ||
| zipUrl, | ||
| apiTypeUrl, | ||
| alias: remoteAlias | ||
| }; | ||
| }; | ||
| const resolveRemotes = (hostOptions) => { | ||
| const parsedOptions = utils.parseOptions(hostOptions.moduleFederationConfig.remotes || {}, (item, key) => ({ | ||
| remote: Array.isArray(item) ? item[0] : item, | ||
| key | ||
| }), (item, key) => ({ | ||
| remote: Array.isArray(item.external) ? item.external[0] : item.external, | ||
| key | ||
| })); | ||
| const remoteTypeUrls = hostOptions.remoteTypeUrls ?? {}; | ||
| if (typeof remoteTypeUrls !== "object") throw new Error("remoteTypeUrls must be consumed before resolveRemotes"); | ||
| const remoteInfos = Object.keys(remoteTypeUrls).reduce((sum, remoteName) => { | ||
| const { zip, api, alias } = remoteTypeUrls[remoteName]; | ||
| sum[alias] = { | ||
| name: remoteName, | ||
| url: "", | ||
| zipUrl: zip, | ||
| apiTypeUrl: api, | ||
| alias: alias || remoteName | ||
| }; | ||
| return sum; | ||
| }, {}); | ||
| return parsedOptions.reduce((accumulator, item) => { | ||
| const { key, remote } = item[1]; | ||
| const res = retrieveRemoteInfo({ | ||
| hostOptions, | ||
| remoteAlias: key, | ||
| remote | ||
| }); | ||
| if (accumulator[key]) { | ||
| accumulator[key] = { | ||
| ...accumulator[key], | ||
| url: res.url, | ||
| apiTypeUrl: accumulator[key].apiTypeUrl || res.apiTypeUrl | ||
| }; | ||
| return accumulator; | ||
| } | ||
| accumulator[key] = res; | ||
| return accumulator; | ||
| }, remoteInfos); | ||
| }; | ||
| const retrieveHostConfig = (options) => { | ||
| validateOptions(options); | ||
| const hostOptions = { | ||
| ...defaultOptions$1, | ||
| ...options | ||
| }; | ||
| return { | ||
| hostOptions, | ||
| mapRemotesToDownload: resolveRemotes(hostOptions) | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/core/constant.ts | ||
| const REMOTE_ALIAS_IDENTIFIER = "REMOTE_ALIAS_IDENTIFIER"; | ||
| const REMOTE_API_TYPES_FILE_NAME = "apis.d.ts"; | ||
| const HOST_API_TYPES_FILE_NAME = "index.d.ts"; | ||
| //#endregion | ||
| //#region src/core/lib/DTSManager.ts | ||
| var DTSManager = class { | ||
| constructor(options) { | ||
| this.options = cloneDeepOptions(options); | ||
| this.runtimePkgs = [ | ||
| "@module-federation/runtime", | ||
| "@module-federation/enhanced/runtime", | ||
| "@module-federation/runtime-tools" | ||
| ]; | ||
| this.loadedRemoteAPIAlias = /* @__PURE__ */ new Set(); | ||
| this.remoteAliasMap = {}; | ||
| this.extraOptions = options?.extraOptions || {}; | ||
| this.updatedRemoteInfos = {}; | ||
| } | ||
| generateAPITypes(mapComponentsToExpose) { | ||
| const exposePaths = /* @__PURE__ */ new Set(); | ||
| const packageType = Object.keys(mapComponentsToExpose).reduce((sum, exposeKey) => { | ||
| const exposePath = path.join(REMOTE_ALIAS_IDENTIFIER, exposeKey).split(path.sep).join("/"); | ||
| exposePaths.add(`'${exposePath}'`); | ||
| sum = `T extends '${exposePath}' ? typeof import('${exposePath}') :` + sum; | ||
| return sum; | ||
| }, "any;"); | ||
| return ` | ||
| export type RemoteKeys = ${[...exposePaths].join(" | ")}; | ||
| type PackageType<T> = ${packageType}`; | ||
| } | ||
| async extractRemoteTypes(options) { | ||
| const { remoteOptions, tsConfig } = options; | ||
| if (!remoteOptions.extractRemoteTypes) return; | ||
| let hasRemotes = false; | ||
| const remotes = remoteOptions.moduleFederationConfig.remotes; | ||
| if (remotes) { | ||
| if (Array.isArray(remotes)) hasRemotes = Boolean(remotes.length); | ||
| else if (typeof remotes === "object") hasRemotes = Boolean(Object.keys(remotes).length); | ||
| } | ||
| const mfTypesPath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| if (hasRemotes && this.options.host) try { | ||
| const { hostOptions } = retrieveHostConfig(this.options.host); | ||
| const remoteTypesFolder = path.resolve(hostOptions.context, hostOptions.typesFolder); | ||
| const targetDir = path.join(mfTypesPath, "node_modules"); | ||
| if (fs.existsSync(remoteTypesFolder)) { | ||
| const targetFolder = path.resolve(remoteOptions.context, targetDir); | ||
| await mkdir(targetFolder, { recursive: true }); | ||
| await cp(remoteTypesFolder, targetFolder, { | ||
| recursive: true, | ||
| force: true | ||
| }); | ||
| } | ||
| } catch (err) { | ||
| if (this.options.host?.abortOnError === false) fileLog(`Unable to copy remote types, ${err}`, "extractRemoteTypes", "error"); | ||
| else throw err; | ||
| } | ||
| } | ||
| async generateTypes() { | ||
| try { | ||
| const { options } = this; | ||
| if (!options.remote) throw new Error("options.remote is required if you want to generateTypes"); | ||
| const { remoteOptions, tsConfig, mapComponentsToExpose } = retrieveRemoteConfig(options.remote); | ||
| if (!Object.keys(mapComponentsToExpose).length) return; | ||
| if (!tsConfig.files?.length) { | ||
| logger$1.info("No type files to compile, skip"); | ||
| return; | ||
| } | ||
| if (tsConfig.compilerOptions.tsBuildInfoFile) try { | ||
| const tsBuildInfoFile = path.resolve(remoteOptions.context, tsConfig.compilerOptions.tsBuildInfoFile); | ||
| const mfTypesPath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| if (!fs.existsSync(mfTypesPath)) fs.rmSync(tsBuildInfoFile, { force: true }); | ||
| } catch (e) {} | ||
| await this.extractRemoteTypes({ | ||
| remoteOptions, | ||
| tsConfig, | ||
| mapComponentsToExpose | ||
| }); | ||
| await compileTs(mapComponentsToExpose, tsConfig, remoteOptions); | ||
| await createTypesArchive(tsConfig, remoteOptions); | ||
| let apiTypesPath = ""; | ||
| if (remoteOptions.generateAPITypes) { | ||
| const apiTypes = this.generateAPITypes(mapComponentsToExpose); | ||
| apiTypesPath = retrieveMfAPITypesPath(tsConfig, remoteOptions); | ||
| fs.writeFileSync(apiTypesPath, apiTypes); | ||
| } | ||
| try { | ||
| if (remoteOptions.deleteTypesFolder) await rm(retrieveMfTypesPath(tsConfig, remoteOptions), { | ||
| recursive: true, | ||
| force: true | ||
| }); | ||
| } catch (err) { | ||
| if (isDebugMode()) console.error(err); | ||
| } | ||
| logger$1.success("Federated types created correctly"); | ||
| } catch (error) { | ||
| if (this.options.remote?.abortOnError === false) { | ||
| if (this.options.displayErrorInTerminal) logger$1.error(error); | ||
| } else throw error; | ||
| } | ||
| } | ||
| async requestRemoteManifest(remoteInfo, hostOptions) { | ||
| try { | ||
| if (!remoteInfo.url.includes(MANIFEST_EXT)) return remoteInfo; | ||
| if (remoteInfo.zipUrl) return remoteInfo; | ||
| const url = remoteInfo.url; | ||
| const manifestJson = (await nativeFetch(url, { | ||
| timeout: hostOptions.timeout, | ||
| family: hostOptions.family | ||
| })).data; | ||
| if (!manifestJson.metaData.types.zip) throw new Error(`Can not get ${remoteInfo.name}'s types archive url!`); | ||
| const addProtocol = (u) => { | ||
| if (u.startsWith("//")) return `https:${u}`; | ||
| return u; | ||
| }; | ||
| let publicPath; | ||
| if ("publicPath" in manifestJson.metaData) publicPath = manifestJson.metaData.publicPath; | ||
| else { | ||
| const getPublicPath = new Function(manifestJson.metaData.getPublicPath); | ||
| if (manifestJson.metaData.getPublicPath.startsWith("function")) publicPath = getPublicPath()(); | ||
| else publicPath = getPublicPath(); | ||
| } | ||
| if (publicPath === "auto") publicPath = inferAutoPublicPath(remoteInfo.url); | ||
| const normalizedPublicPath = addProtocol(publicPath).endsWith("/") ? addProtocol(publicPath) : `${addProtocol(publicPath)}/`; | ||
| remoteInfo.zipUrl = new URL(manifestJson.metaData.types.zip, normalizedPublicPath).href; | ||
| if (!manifestJson.metaData.types.api) { | ||
| console.warn(`Can not get ${remoteInfo.name}'s api types url!`); | ||
| remoteInfo.apiTypeUrl = ""; | ||
| return remoteInfo; | ||
| } | ||
| remoteInfo.apiTypeUrl = new URL(manifestJson.metaData.types.api, normalizedPublicPath).href; | ||
| return remoteInfo; | ||
| } catch (_err) { | ||
| fileLog(`fetch manifest failed, ${_err}, ${remoteInfo.name} will be ignored`, "requestRemoteManifest", "error"); | ||
| return remoteInfo; | ||
| } | ||
| } | ||
| async consumeTargetRemotes(hostOptions, remoteInfo) { | ||
| if (!remoteInfo.zipUrl) throw new Error(`Can not get ${remoteInfo.name}'s types archive url!`); | ||
| return downloadTypesArchive(hostOptions)([remoteInfo.alias, remoteInfo.zipUrl]); | ||
| } | ||
| async downloadAPITypes(remoteInfo, destinationPath, hostOptions) { | ||
| const { apiTypeUrl } = remoteInfo; | ||
| if (!apiTypeUrl) return; | ||
| try { | ||
| let apiTypeFile = (await nativeFetch(apiTypeUrl, { | ||
| timeout: hostOptions.timeout, | ||
| family: hostOptions.family | ||
| })).data; | ||
| apiTypeFile = apiTypeFile.replaceAll(REMOTE_ALIAS_IDENTIFIER, remoteInfo.alias); | ||
| const filePath = path.join(destinationPath, REMOTE_API_TYPES_FILE_NAME); | ||
| fs.writeFileSync(filePath, apiTypeFile); | ||
| const existed = this.loadedRemoteAPIAlias.has(remoteInfo.alias); | ||
| this.loadedRemoteAPIAlias.add(remoteInfo.alias); | ||
| fileLog(`success`, "downloadAPITypes", "info"); | ||
| return existed; | ||
| } catch (err) { | ||
| fileLog(`Unable to download "${remoteInfo.name}" api types, ${err}`, "downloadAPITypes", "error"); | ||
| } | ||
| } | ||
| consumeAPITypes(hostOptions) { | ||
| const apiTypeFileName = path.join(hostOptions.context, hostOptions.typesFolder, HOST_API_TYPES_FILE_NAME); | ||
| try { | ||
| const existedFile = fs.readFileSync(apiTypeFileName, "utf-8"); | ||
| new ThirdPartyExtractor({ destDir: "" }).collectTypeImports(existedFile).forEach((existedImport) => { | ||
| const alias = existedImport.split("./").slice(1).join("./").replace("/apis.d.ts", ""); | ||
| this.loadedRemoteAPIAlias.add(alias); | ||
| }); | ||
| } catch (err) {} | ||
| if (!this.loadedRemoteAPIAlias.size) return; | ||
| const packageTypes = []; | ||
| const remoteKeys = []; | ||
| const importTypeStr = [...this.loadedRemoteAPIAlias].sort().map((alias, index) => { | ||
| const remoteKey = `RemoteKeys_${index}`; | ||
| const packageType = `PackageType_${index}`; | ||
| packageTypes.push(`T extends ${remoteKey} ? ${packageType}<T>`); | ||
| remoteKeys.push(remoteKey); | ||
| return `import type { PackageType as ${packageType},RemoteKeys as ${remoteKey} } from './${alias}/apis.d.ts';`; | ||
| }).join("\n"); | ||
| const remoteKeysStr = `type RemoteKeys = ${remoteKeys.join(" | ")};`; | ||
| const packageTypesStr = `type PackageType<T, Y=any> = ${[...packageTypes, "Y"].join(" :\n")} ;`; | ||
| const runtimePkgs = /* @__PURE__ */ new Set(); | ||
| [...this.runtimePkgs, ...hostOptions.runtimePkgs].forEach((pkg) => { | ||
| runtimePkgs.add(pkg); | ||
| }); | ||
| const fileStr = `${importTypeStr} | ||
| ${[...runtimePkgs].map((pkg) => { | ||
| return `declare module "${pkg}" { | ||
| ${remoteKeysStr} | ||
| ${packageTypesStr} | ||
| export function loadRemote<T extends RemoteKeys,Y>(packageName: T): Promise<PackageType<T, Y>>; | ||
| export function loadRemote<T extends string,Y>(packageName: T): Promise<PackageType<T, Y>>; | ||
| }`; | ||
| }).join("\n")} | ||
| `; | ||
| fs.writeFileSync(path.join(hostOptions.context, hostOptions.typesFolder, HOST_API_TYPES_FILE_NAME), fileStr); | ||
| } | ||
| async consumeArchiveTypes(options) { | ||
| const { hostOptions, mapRemotesToDownload } = retrieveHostConfig(options); | ||
| const downloadPromises = Object.entries(mapRemotesToDownload).map(async (item) => { | ||
| const remoteInfo = item[1]; | ||
| if (!this.remoteAliasMap[remoteInfo.alias]) { | ||
| const requiredRemoteInfo = await this.requestRemoteManifest(remoteInfo, hostOptions); | ||
| this.remoteAliasMap[remoteInfo.alias] = requiredRemoteInfo; | ||
| } | ||
| return this.consumeTargetRemotes(hostOptions, this.remoteAliasMap[remoteInfo.alias]); | ||
| }); | ||
| return { | ||
| hostOptions, | ||
| downloadPromisesResult: await Promise.allSettled(downloadPromises) | ||
| }; | ||
| } | ||
| async consumeTypes() { | ||
| try { | ||
| const { options } = this; | ||
| if (!options.host) throw new Error("options.host is required if you want to consumeTypes"); | ||
| const { mapRemotesToDownload } = retrieveHostConfig(options.host); | ||
| if (!Object.keys(mapRemotesToDownload).length) return; | ||
| const { downloadPromisesResult, hostOptions } = await this.consumeArchiveTypes(options.host); | ||
| if (hostOptions.consumeAPITypes) { | ||
| await Promise.all(downloadPromisesResult.map(async (item) => { | ||
| if (item.status === "rejected" || !item.value) return; | ||
| const [alias, destinationPath] = item.value; | ||
| const remoteInfo = this.remoteAliasMap[alias]; | ||
| if (!remoteInfo) return; | ||
| await this.downloadAPITypes(remoteInfo, destinationPath, hostOptions); | ||
| })); | ||
| this.consumeAPITypes(hostOptions); | ||
| } | ||
| logger$1.success("Federated types extraction completed"); | ||
| } catch (err) { | ||
| if (this.options.host?.abortOnError === false) fileLog(`Unable to consume federated types, ${err}`, "consumeTypes", "error"); | ||
| else throw err; | ||
| } | ||
| } | ||
| async updateTypes(options) { | ||
| try { | ||
| const { remoteName, updateMode, remoteTarPath, remoteInfo: updatedRemoteInfo, once } = options; | ||
| const hostName = this.options?.host?.moduleFederationConfig?.name; | ||
| fileLog(`options: ${JSON.stringify(options, null, 2)};\nhostName: ${hostName}`, "updateTypes", "info"); | ||
| if (updateMode === UpdateMode.POSITIVE && remoteName === hostName) { | ||
| if (!this.options.remote) return; | ||
| await this.generateTypes(); | ||
| } else { | ||
| const { remoteAliasMap } = this; | ||
| if (!this.options.host) return; | ||
| const { hostOptions, mapRemotesToDownload } = retrieveHostConfig(this.options.host); | ||
| const loadedRemoteInfo = Object.values(remoteAliasMap).find((i) => i.name === remoteName); | ||
| const consumeTypes = async (requiredRemoteInfo) => { | ||
| fileLog(`consumeTypes start`, "updateTypes", "info"); | ||
| if (!requiredRemoteInfo.zipUrl) throw new Error(`Can not get ${requiredRemoteInfo.name}'s types archive url!`); | ||
| const [_alias, destinationPath] = await this.consumeTargetRemotes(hostOptions, { | ||
| ...requiredRemoteInfo, | ||
| zipUrl: remoteTarPath || requiredRemoteInfo.zipUrl | ||
| }); | ||
| if (await this.downloadAPITypes(requiredRemoteInfo, destinationPath, hostOptions)) this.consumeAPITypes(hostOptions); | ||
| fileLog(`consumeTypes end`, "updateTypes", "info"); | ||
| }; | ||
| fileLog(`loadedRemoteInfo: ${JSON.stringify(loadedRemoteInfo, null, 2)}`, "updateTypes", "info"); | ||
| if (!loadedRemoteInfo) { | ||
| const remoteInfo = Object.values(mapRemotesToDownload).find((item) => { | ||
| return item.name === remoteName; | ||
| }); | ||
| fileLog(`remoteInfo: ${JSON.stringify(remoteInfo, null, 2)}`, "updateTypes", "info"); | ||
| if (remoteInfo) { | ||
| if (!this.remoteAliasMap[remoteInfo.alias]) { | ||
| const requiredRemoteInfo = await this.requestRemoteManifest(remoteInfo, hostOptions); | ||
| this.remoteAliasMap[remoteInfo.alias] = requiredRemoteInfo; | ||
| } | ||
| await consumeTypes(this.remoteAliasMap[remoteInfo.alias]); | ||
| } else if (updatedRemoteInfo) { | ||
| const consumeDynamicRemoteTypes = async () => { | ||
| await consumeTypes(this.updatedRemoteInfos[updatedRemoteInfo.name]); | ||
| }; | ||
| if (!this.updatedRemoteInfos[updatedRemoteInfo.name]) { | ||
| const parsedRemoteInfo = retrieveRemoteInfo({ | ||
| hostOptions, | ||
| remoteAlias: updatedRemoteInfo.alias || updatedRemoteInfo.name, | ||
| remote: updatedRemoteInfo.url | ||
| }); | ||
| fileLog(`start request manifest`, "consumeTypes", "info"); | ||
| this.updatedRemoteInfos[updatedRemoteInfo.name] = await this.requestRemoteManifest(parsedRemoteInfo, hostOptions); | ||
| fileLog(`end request manifest, this.updatedRemoteInfos[updatedRemoteInfo.name]: ${JSON.stringify(this.updatedRemoteInfos[updatedRemoteInfo.name], null, 2)}`, "updateTypes", "info"); | ||
| await consumeDynamicRemoteTypes(); | ||
| } | ||
| if (!once && this.updatedRemoteInfos[updatedRemoteInfo.name]) await consumeDynamicRemoteTypes(); | ||
| } | ||
| } else await consumeTypes(loadedRemoteInfo); | ||
| } | ||
| } catch (err) { | ||
| fileLog(`updateTypes fail, ${err}`, "updateTypes", "error"); | ||
| } | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/utils.ts | ||
| const dispatcherCache = /* @__PURE__ */ new Map(); | ||
| function getDTSManagerConstructor(implementation) { | ||
| if (implementation) { | ||
| const NewConstructor = __require(implementation); | ||
| return NewConstructor.default ? NewConstructor.default : NewConstructor; | ||
| } | ||
| return DTSManager; | ||
| } | ||
| const validateOptions = (options) => { | ||
| if (!options.moduleFederationConfig) throw new Error("moduleFederationConfig is required"); | ||
| }; | ||
| function retrieveTypesAssetsInfo(options) { | ||
| let apiTypesPath = ""; | ||
| let zipTypesPath = ""; | ||
| try { | ||
| const { tsConfig, remoteOptions, mapComponentsToExpose } = retrieveRemoteConfig(options); | ||
| if (!Object.keys(mapComponentsToExpose).length || !tsConfig.files.length) return { | ||
| apiTypesPath, | ||
| zipTypesPath, | ||
| zipName: "", | ||
| apiFileName: "" | ||
| }; | ||
| zipTypesPath = retrieveTypesZipPath(retrieveMfTypesPath(tsConfig, remoteOptions), remoteOptions); | ||
| if (remoteOptions.generateAPITypes) apiTypesPath = retrieveMfAPITypesPath(tsConfig, remoteOptions); | ||
| return { | ||
| apiTypesPath, | ||
| zipTypesPath, | ||
| zipName: path.basename(zipTypesPath), | ||
| apiFileName: path.basename(apiTypesPath) | ||
| }; | ||
| } catch (err) { | ||
| console.error(ansiColors.red(`Unable to compile federated types, ${err}`)); | ||
| return { | ||
| apiTypesPath: "", | ||
| zipTypesPath: "", | ||
| zipName: "", | ||
| apiFileName: "" | ||
| }; | ||
| } | ||
| } | ||
| function isDebugMode() { | ||
| return Boolean(process.env["FEDERATION_DEBUG"]) || process.env["NODE_ENV"] === "test"; | ||
| } | ||
| const isTSProject = (dtsOptions, context = process.cwd()) => { | ||
| if (dtsOptions === false) return false; | ||
| try { | ||
| let filepath = ""; | ||
| if (typeof dtsOptions === "object" && dtsOptions.tsConfigPath) filepath = dtsOptions.tsConfigPath; | ||
| else filepath = path.resolve(context, "./tsconfig.json"); | ||
| if (!path.isAbsolute(filepath)) filepath = path.resolve(context, filepath); | ||
| return fs.existsSync(filepath); | ||
| } catch (err) { | ||
| return false; | ||
| } | ||
| }; | ||
| function cloneDeepOptions(options) { | ||
| const excludeKeys = new Set(["manifest", "async"]); | ||
| const cache = /* @__PURE__ */ new WeakMap(); | ||
| function sanitize(val, key) { | ||
| if (key !== void 0 && excludeKeys.has(key) || typeof val === "function") return false; | ||
| if (key === "extractThirdParty" && Array.isArray(val)) return val.map(String); | ||
| if (Array.isArray(val)) { | ||
| if (cache.has(val)) return cache.get(val); | ||
| const out = []; | ||
| cache.set(val, out); | ||
| val.forEach((v, i) => out.push(sanitize(v, String(i)))); | ||
| return out; | ||
| } | ||
| if (val !== null && typeof val === "object" && Object.getPrototypeOf(val) === Object.prototype) { | ||
| const obj = val; | ||
| if (cache.has(obj)) return cache.get(obj); | ||
| const out = {}; | ||
| cache.set(obj, out); | ||
| for (const [k, v] of Object.entries(obj)) out[k] = sanitize(v, k); | ||
| return out; | ||
| } | ||
| return val; | ||
| } | ||
| return structuredClone(sanitize(options)); | ||
| } | ||
| const getEnvHeaders = () => { | ||
| const headersStr = getProcessEnv()["MF_ENV_HEADERS"]; | ||
| if (!headersStr || headersStr === "undefined") return {}; | ||
| try { | ||
| return { ...JSON.parse(headersStr) }; | ||
| } catch { | ||
| return {}; | ||
| } | ||
| }; | ||
| const createDispatcherFromFamily = (family) => { | ||
| if (!family) return void 0; | ||
| if (dispatcherCache.has(family)) return dispatcherCache.get(family); | ||
| try { | ||
| const dispatcher = new Agent({ connect: { family } }); | ||
| dispatcherCache.set(family, dispatcher); | ||
| return dispatcher; | ||
| } catch {} | ||
| }; | ||
| const toHeaderRecord = (headers) => { | ||
| const out = {}; | ||
| headers.forEach((value, key) => { | ||
| out[key.toLowerCase()] = value; | ||
| }); | ||
| return out; | ||
| }; | ||
| async function nativeFetch(url, config) { | ||
| const controller = new AbortController(); | ||
| const timeoutMs = config?.timeout ?? 6e4; | ||
| const timeoutId = setTimeout(() => controller.abort(), timeoutMs); | ||
| const headers = { | ||
| ...getEnvHeaders(), | ||
| ...config?.headers ?? {} | ||
| }; | ||
| const dispatcher = config?.dispatcher ?? createDispatcherFromFamily(config?.family); | ||
| try { | ||
| const resp = await fetch(url, { | ||
| headers, | ||
| signal: controller.signal, | ||
| ...dispatcher ? { dispatcher } : {}, | ||
| ...config?.agent ? { agent: config.agent } : {} | ||
| }); | ||
| const headerRecord = toHeaderRecord(resp.headers); | ||
| if (!resp.ok) throw new Error(`Request failed with status ${resp.status}`); | ||
| if (config?.responseType === "arraybuffer") return { | ||
| data: await resp.arrayBuffer(), | ||
| headers: headerRecord, | ||
| status: resp.status | ||
| }; | ||
| return { | ||
| data: (resp.headers.get("content-type") || "").includes("application/json") || url.endsWith(".json") ? await resp.json() : await resp.text(), | ||
| headers: headerRecord, | ||
| status: resp.status | ||
| }; | ||
| } finally { | ||
| clearTimeout(timeoutId); | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/core/configurations/remotePlugin.ts | ||
| const defaultOptions = { | ||
| tsConfigPath: "./tsconfig.json", | ||
| typesFolder: "@mf-types", | ||
| compiledTypesFolder: "compiled-types", | ||
| hostRemoteTypesFolder: "@mf-types", | ||
| deleteTypesFolder: true, | ||
| additionalFilesToCompile: [], | ||
| compilerInstance: "tsc", | ||
| compileInChildProcess: false, | ||
| implementation: "", | ||
| generateAPITypes: false, | ||
| context: process.cwd(), | ||
| abortOnError: true, | ||
| extractRemoteTypes: false, | ||
| extractThirdParty: false, | ||
| outputDir: "", | ||
| deleteTsConfig: true | ||
| }; | ||
| function getEffectiveRootDir(parsedCommandLine) { | ||
| const compilerOptions = parsedCommandLine.options; | ||
| if (compilerOptions.rootDir) return compilerOptions.rootDir; | ||
| const files = parsedCommandLine.fileNames; | ||
| if (files.length > 0) return files.map((file) => dirname(file)).reduce((commonPath, fileDir) => { | ||
| while (!fileDir.startsWith(commonPath)) commonPath = dirname(commonPath); | ||
| return commonPath; | ||
| }, files[0]); | ||
| if (parsedCommandLine.projectReferences.length) { | ||
| const relativeReferences = parsedCommandLine.projectReferences.filter((reference) => !isAbsolute(reference.originalPath ?? reference.path)); | ||
| const referencesForRoot = relativeReferences.length ? relativeReferences : parsedCommandLine.projectReferences; | ||
| return referencesForRoot.map((reference) => dirname(reference.path)).reduce((commonPath, filePath) => { | ||
| while (!filePath.startsWith(commonPath)) commonPath = dirname(commonPath); | ||
| return commonPath; | ||
| }, dirname(referencesForRoot[0].path)); | ||
| } | ||
| throw new Error("Can not get effective rootDir, please set compilerOptions.rootDir !"); | ||
| } | ||
| const getDependentFiles = (rootFiles, configContent, rootDir) => { | ||
| const dependentFiles = typescript.createProgram(rootFiles, configContent.options).getSourceFiles().map((file) => file.fileName).filter((file) => !file.endsWith(".d.ts") && file.startsWith(rootDir + "/")); | ||
| return dependentFiles.length ? dependentFiles : rootFiles; | ||
| }; | ||
| const readTsConfig = ({ tsConfigPath, typesFolder, compiledTypesFolder, context, additionalFilesToCompile, outputDir }, mapComponentsToExpose) => { | ||
| const resolvedTsConfigPath = resolve(context, tsConfigPath); | ||
| const readResult = typescript.readConfigFile(resolvedTsConfigPath, typescript.sys.readFile); | ||
| if (readResult.error) throw new Error(readResult.error.messageText.toString()); | ||
| const rawTsConfigJson = readResult.config; | ||
| const configContent = typescript.parseJsonConfigFileContent(rawTsConfigJson, typescript.sys, dirname(resolvedTsConfigPath)); | ||
| const rootDir = getEffectiveRootDir(configContent); | ||
| const outDir = resolve(context, outputDir || configContent.options.outDir || "dist", typesFolder, compiledTypesFolder); | ||
| const defaultCompilerOptions = { | ||
| rootDir, | ||
| emitDeclarationOnly: true, | ||
| noEmit: false, | ||
| declaration: true, | ||
| outDir | ||
| }; | ||
| rawTsConfigJson.compilerOptions = rawTsConfigJson.compilerOptions || {}; | ||
| rawTsConfigJson.compilerOptions = { | ||
| incremental: true, | ||
| tsBuildInfoFile: resolve(context, "node_modules/.cache/mf-types/.tsbuildinfo"), | ||
| ...rawTsConfigJson.compilerOptions, | ||
| ...defaultCompilerOptions | ||
| }; | ||
| const { paths, baseUrl, ...restCompilerOptions } = rawTsConfigJson.compilerOptions || {}; | ||
| rawTsConfigJson.compilerOptions = restCompilerOptions; | ||
| const outDirWithoutTypesFolder = resolve(context, outputDir || configContent.options.outDir || "dist"); | ||
| const excludeExtensions = [".mdx", ".md"]; | ||
| const filesToCompile = [...getDependentFiles([...Object.values(mapComponentsToExpose), ...additionalFilesToCompile].filter((filename) => !excludeExtensions.some((ext) => filename.endsWith(ext))), configContent, rootDir), ...configContent.fileNames.filter((filename) => filename.endsWith(".d.ts") && !filename.startsWith(outDirWithoutTypesFolder))]; | ||
| rawTsConfigJson.include = []; | ||
| rawTsConfigJson.files = [...new Set(filesToCompile)]; | ||
| rawTsConfigJson.exclude = []; | ||
| "references" in rawTsConfigJson && delete rawTsConfigJson.references; | ||
| rawTsConfigJson.extends = resolvedTsConfigPath; | ||
| rawTsConfigJson.compilerOptions.declarationDir = outDir; | ||
| return rawTsConfigJson; | ||
| }; | ||
| const TS_EXTENSIONS = [ | ||
| ".ts", | ||
| ".tsx", | ||
| ".vue", | ||
| ".svelte", | ||
| ".js", | ||
| ".jsx" | ||
| ]; | ||
| const resolveWithExtension = (exposedPath, context) => { | ||
| const explicitExtension = extname(exposedPath); | ||
| if (TS_EXTENSIONS.includes(explicitExtension)) return resolve(context, exposedPath); | ||
| for (const extension of TS_EXTENSIONS) { | ||
| const exposedPathWithExtension = resolve(context, `${exposedPath}${extension}`); | ||
| if (existsSync(exposedPathWithExtension)) return exposedPathWithExtension; | ||
| } | ||
| }; | ||
| const resolveExposes = (remoteOptions) => { | ||
| return utils.parseOptions(remoteOptions.moduleFederationConfig.exposes || {}, (item, key) => ({ | ||
| exposePath: Array.isArray(item) ? item[0] : item, | ||
| key | ||
| }), (item, key) => ({ | ||
| exposePath: Array.isArray(item.import) ? item.import[0] : item.import[0], | ||
| key | ||
| })).reduce((accumulator, item) => { | ||
| const { exposePath, key } = item[1]; | ||
| accumulator[key] = resolveWithExtension(exposePath, remoteOptions.context) || resolveWithExtension(join(exposePath, "index"), remoteOptions.context) || exposePath; | ||
| return accumulator; | ||
| }, {}); | ||
| }; | ||
| const retrieveRemoteConfig = (options) => { | ||
| validateOptions(options); | ||
| const remoteOptions = { | ||
| ...defaultOptions, | ||
| ...options | ||
| }; | ||
| const mapComponentsToExpose = resolveExposes(remoteOptions); | ||
| const tsConfig = readTsConfig(remoteOptions, mapComponentsToExpose); | ||
| if (tsConfig.compilerOptions.incremental && tsConfig.compilerOptions.tsBuildInfoFile && options.deleteTypesFolder !== true) remoteOptions.deleteTypesFolder = false; | ||
| return { | ||
| tsConfig, | ||
| mapComponentsToExpose, | ||
| remoteOptions | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/generateTypes.ts | ||
| async function generateTypes(options) { | ||
| return new (getDTSManagerConstructor(options.remote?.implementation))(options).generateTypes(); | ||
| } | ||
| //#endregion | ||
| //#region src/core/rpc/types.ts | ||
| let RpcGMCallTypes = /* @__PURE__ */ function(RpcGMCallTypes) { | ||
| RpcGMCallTypes["CALL"] = "mf_call"; | ||
| RpcGMCallTypes["RESOLVE"] = "mf_resolve"; | ||
| RpcGMCallTypes["REJECT"] = "mf_reject"; | ||
| RpcGMCallTypes["EXIT"] = "mf_exit"; | ||
| return RpcGMCallTypes; | ||
| }({}); | ||
| //#endregion | ||
| //#region src/core/rpc/expose-rpc.ts | ||
| function exposeRpc(fn) { | ||
| const sendMessage = (message) => new Promise((resolve, reject) => { | ||
| if (!process$1.send) reject(/* @__PURE__ */ new Error(`Process ${process$1.pid} doesn't have IPC channels`)); | ||
| else if (!process$1.connected) reject(/* @__PURE__ */ new Error(`Process ${process$1.pid} doesn't have open IPC channels`)); | ||
| else process$1.send(message, void 0, void 0, (error) => { | ||
| if (error) reject(error); | ||
| else resolve(void 0); | ||
| }); | ||
| }); | ||
| const handleMessage = async (message) => { | ||
| if (message.type === RpcGMCallTypes.CALL) { | ||
| if (!process$1.send) return; | ||
| let value, error; | ||
| try { | ||
| value = await fn(...message.args); | ||
| } catch (fnError) { | ||
| error = fnError; | ||
| } | ||
| try { | ||
| if (error) await sendMessage({ | ||
| type: RpcGMCallTypes.REJECT, | ||
| id: message.id, | ||
| error | ||
| }); | ||
| else await sendMessage({ | ||
| type: RpcGMCallTypes.RESOLVE, | ||
| id: message.id, | ||
| value | ||
| }); | ||
| } catch (sendError) { | ||
| if (error) { | ||
| if (error instanceof Error) console.error(error); | ||
| } | ||
| console.error(sendError); | ||
| } | ||
| } | ||
| }; | ||
| process$1.on("message", handleMessage); | ||
| } | ||
| //#endregion | ||
| export { retrieveMfTypesPath as _, cloneDeepOptions as a, ModuleFederationDevServer as b, isTSProject as c, DTSManager as d, HOST_API_TYPES_FILE_NAME as f, retrieveTypesZipPath as g, retrieveHostConfig as h, retrieveRemoteConfig as i, retrieveTypesAssetsInfo as l, REMOTE_API_TYPES_FILE_NAME as m, RpcGMCallTypes as n, getDTSManagerConstructor as o, REMOTE_ALIAS_IDENTIFIER as p, generateTypes as r, isDebugMode as s, exposeRpc as t, validateOptions as u, retrieveOriginalOutDir as v, __exportAll as x, createHttpServer as y }; |
| const require_Action = require('./Action-CzhPMw2i.js'); | ||
| const require_Broker = require('./Broker-CaenCqdn.js'); | ||
| let fs = require("fs"); | ||
| fs = require_Action.__toESM(fs); | ||
| let url = require("url"); | ||
| let path = require("path"); | ||
| path = require_Action.__toESM(path); | ||
| let fs_promises = require("fs/promises"); | ||
| let _module_federation_managers = require("@module-federation/managers"); | ||
| let typescript = require("typescript"); | ||
| typescript = require_Action.__toESM(typescript); | ||
| let _module_federation_sdk = require("@module-federation/sdk"); | ||
| let ansi_colors = require("ansi-colors"); | ||
| ansi_colors = require_Action.__toESM(ansi_colors); | ||
| let undici = require("undici"); | ||
| let _module_federation_third_party_dts_extractor = require("@module-federation/third-party-dts-extractor"); | ||
| let adm_zip = require("adm-zip"); | ||
| adm_zip = require_Action.__toESM(adm_zip); | ||
| let crypto = require("crypto"); | ||
| crypto = require_Action.__toESM(crypto); | ||
| let _module_federation_error_codes = require("@module-federation/error-codes"); | ||
| let _module_federation_error_codes_node = require("@module-federation/error-codes/node"); | ||
| let child_process = require("child_process"); | ||
| let util = require("util"); | ||
| util = require_Action.__toESM(util); | ||
| let isomorphic_ws = require("isomorphic-ws"); | ||
| isomorphic_ws = require_Action.__toESM(isomorphic_ws); | ||
| let http = require("http"); | ||
| http = require_Action.__toESM(http); | ||
| let process$1 = require("process"); | ||
| process$1 = require_Action.__toESM(process$1); | ||
| //#region src/server/message/Action/AddPublisher.ts | ||
| var AddPublisherAction = class extends require_Action.Action { | ||
| constructor(payload) { | ||
| super({ payload }, require_Action.ActionKind.ADD_PUBLISHER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/AddSubscriber.ts | ||
| var AddSubscriberAction = class extends require_Action.Action { | ||
| constructor(payload) { | ||
| super({ payload }, require_Action.ActionKind.ADD_SUBSCRIBER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/ExitSubscriber.ts | ||
| var ExitSubscriberAction = class extends require_Action.Action { | ||
| constructor(payload) { | ||
| super({ payload }, require_Action.ActionKind.EXIT_SUBSCRIBER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/ExitPublisher.ts | ||
| var ExitPublisherAction = class extends require_Action.Action { | ||
| constructor(payload) { | ||
| super({ payload }, require_Action.ActionKind.EXIT_PUBLISHER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/NotifyWebClient.ts | ||
| var NotifyWebClientAction = class extends require_Action.Action { | ||
| constructor(payload) { | ||
| super({ payload }, require_Action.ActionKind.NOTIFY_WEB_CLIENT); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/UpdatePublisher.ts | ||
| var UpdatePublisherAction = class extends require_Action.Action { | ||
| constructor(payload) { | ||
| super({ payload }, require_Action.ActionKind.UPDATE_PUBLISHER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/broker/createBroker.ts | ||
| const __filename$1 = (0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href); | ||
| const __dirname$1 = path.default.dirname(__filename$1); | ||
| function createBroker() { | ||
| const sub = (0, child_process.fork)(path.default.resolve(__dirname$1, "./start-broker.js"), [], { | ||
| detached: true, | ||
| stdio: "ignore", | ||
| env: process.env | ||
| }); | ||
| sub.send("start"); | ||
| sub.unref(); | ||
| return sub; | ||
| } | ||
| //#endregion | ||
| //#region src/server/DevServer.ts | ||
| var ModuleFederationDevServer = class { | ||
| constructor(ctx) { | ||
| this._publishWebSocket = null; | ||
| this._subscriberWebsocketMap = {}; | ||
| this._reconnect = true; | ||
| this._reconnectTimes = 0; | ||
| this._isConnected = false; | ||
| this._isReconnecting = false; | ||
| this._updateCallback = () => Promise.resolve(void 0); | ||
| const { name, remotes, remoteTypeTarPath, updateCallback } = ctx; | ||
| this._ip = require_Broker.getIPV4(); | ||
| this._name = name; | ||
| this._remotes = remotes; | ||
| this._remoteTypeTarPath = remoteTypeTarPath; | ||
| this._updateCallback = updateCallback; | ||
| this._stopWhenSIGTERMOrSIGINT(); | ||
| this._handleUnexpectedExit(); | ||
| this._connectPublishToServer(); | ||
| } | ||
| _connectPublishToServer() { | ||
| if (!this._reconnect) return; | ||
| require_Broker.fileLog(`Publisher:${this._name} Trying to connect to ws://${this._ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}...`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| this._publishWebSocket = new isomorphic_ws.default(`ws://${this._ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}?WEB_SOCKET_CONNECT_MAGIC_ID=${require_Broker.Broker.WEB_SOCKET_CONNECT_MAGIC_ID}`); | ||
| this._publishWebSocket.on("open", () => { | ||
| require_Broker.fileLog(`Current pid: ${process.pid}, publisher:${this._name} connected to ws://${this._ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}, starting service...`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| this._isConnected = true; | ||
| const addPublisherAction = new AddPublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| this._publishWebSocket?.send(JSON.stringify(addPublisherAction)); | ||
| this._connectSubscribers(); | ||
| }); | ||
| this._publishWebSocket.on("message", async (message) => { | ||
| try { | ||
| const parsedMessage = JSON.parse(message.toString()); | ||
| if (parsedMessage.type === "Log") { | ||
| if (parsedMessage.kind === require_Broker.LogKind.BrokerExitLog) { | ||
| require_Broker.fileLog(`Receive broker exit signal, ${this._name} service will exit...`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| } | ||
| } | ||
| if (parsedMessage.type === "API") { | ||
| if (parsedMessage.kind === require_Broker.APIKind.FETCH_TYPES) { | ||
| const { payload: { remoteInfo } } = parsedMessage; | ||
| require_Broker.fileLog(`${this._name} Receive broker FETCH_TYPES, payload as follows: ${JSON.stringify(remoteInfo, null, 2)}.`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| await this.fetchDynamicRemoteTypes({ remoteInfo }); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| console.error(err); | ||
| const exitPublisher = new ExitPublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip | ||
| }); | ||
| const exitSubscriber = new ExitSubscriberAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| publishers: this._remotes.map((remote) => ({ | ||
| name: remote.name, | ||
| ip: remote.ip | ||
| })) | ||
| }); | ||
| this._publishWebSocket?.send(JSON.stringify(exitPublisher)); | ||
| this._publishWebSocket?.send(JSON.stringify(exitSubscriber)); | ||
| require_Broker.fileLog("Parse messages error, ModuleFederationDevServer will exit...", require_Action.MF_SERVER_IDENTIFIER, "fatal"); | ||
| this._exit(); | ||
| } | ||
| }); | ||
| this._publishWebSocket.on("close", (code) => { | ||
| require_Broker.fileLog(`Connection closed with code ${code}.`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| this._publishWebSocket && this._publishWebSocket.close(); | ||
| this._publishWebSocket = null; | ||
| if (!this._reconnect) return; | ||
| const reconnectTime = require_Broker.fib(++this._reconnectTimes); | ||
| require_Broker.fileLog(`start reconnecting to server after ${reconnectTime}s.`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| setTimeout(() => this._connectPublishToServer(), reconnectTime * 1e3); | ||
| }); | ||
| this._publishWebSocket.on("error", this._tryCreateBackgroundBroker.bind(this)); | ||
| } | ||
| _connectSubscriberToServer(remote) { | ||
| const { name, ip } = remote; | ||
| require_Broker.fileLog(`remote module:${name} trying to connect to ws://${ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}...`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| const identifier = require_Broker.getIdentifier({ | ||
| name, | ||
| ip | ||
| }); | ||
| this._subscriberWebsocketMap[identifier] = new isomorphic_ws.default(`ws://${ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}?WEB_SOCKET_CONNECT_MAGIC_ID=${require_Broker.Broker.WEB_SOCKET_CONNECT_MAGIC_ID}`); | ||
| this._subscriberWebsocketMap[identifier].on("open", () => { | ||
| require_Broker.fileLog(`Current pid: ${process.pid} remote module: ${name} connected to ws://${ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}, starting service...`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| const addSubscriber = new AddSubscriberAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| publishers: [{ | ||
| name, | ||
| ip | ||
| }] | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].send(JSON.stringify(addSubscriber)); | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].on("message", async (message) => { | ||
| try { | ||
| const parsedMessage = JSON.parse(message.toString()); | ||
| if (parsedMessage.type === "Log") { | ||
| if (parsedMessage.kind === require_Broker.LogKind.BrokerExitLog) { | ||
| require_Broker.fileLog(`${identifier}'s Server exit, thus ${identifier} will no longer has reload ability.`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| } | ||
| } | ||
| if (parsedMessage.type === "API") { | ||
| if (parsedMessage.kind === require_Broker.APIKind.UPDATE_SUBSCRIBER) { | ||
| const { payload: { updateKind, updateSourcePaths, name: subscribeName, remoteTypeTarPath, updateMode } } = parsedMessage; | ||
| await this._updateSubscriber({ | ||
| remoteTypeTarPath, | ||
| name: subscribeName, | ||
| updateKind, | ||
| updateMode, | ||
| updateSourcePaths | ||
| }); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| console.error(err); | ||
| const exitSubscriber = new ExitSubscriberAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| publishers: [{ | ||
| name, | ||
| ip | ||
| }] | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].send(JSON.stringify(exitSubscriber)); | ||
| require_Broker.fileLog(`${identifier} exit, | ||
| error: ${err instanceof Error ? err.toString() : JSON.stringify(err)} | ||
| `, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| } | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].on("close", (code) => { | ||
| require_Broker.fileLog(`Connection closed with code ${code}.`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| this._subscriberWebsocketMap[identifier]?.close(); | ||
| delete this._subscriberWebsocketMap[identifier]; | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].on("error", (err) => { | ||
| if ("code" in err && err.code === "ETIMEDOUT") require_Broker.fileLog(`Can not connect ${JSON.stringify(remote)}, please make sure this remote is started locally.`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| else console.error(err); | ||
| this._subscriberWebsocketMap[identifier]?.close(); | ||
| delete this._subscriberWebsocketMap[identifier]; | ||
| }); | ||
| } | ||
| _connectSubscribers() { | ||
| this._remotes.forEach((remote) => { | ||
| this._connectSubscriberToServer(remote); | ||
| }); | ||
| } | ||
| async _updateSubscriber(options) { | ||
| const { updateMode, updateKind, updateSourcePaths, name, remoteTypeTarPath, remoteInfo } = options; | ||
| require_Broker.fileLog(`[_updateSubscriber] run, options: ${JSON.stringify(options, null, 2)}`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| if (updateMode === require_Action.UpdateMode.PASSIVE && updateSourcePaths.includes(this._name)) { | ||
| require_Broker.fileLog(`[_updateSubscriber] run, updateSourcePaths:${updateSourcePaths} includes ${this._name}, update ignore!`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| return; | ||
| } | ||
| if (updateSourcePaths.slice(-1)[0] === this._name) { | ||
| require_Broker.fileLog(`[_updateSubscriber] run, updateSourcePaths:${updateSourcePaths} ends is ${this._name}, update ignore!`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| return; | ||
| } | ||
| require_Broker.fileLog(`[_updateSubscriber] run, updateSourcePaths:${updateSourcePaths}, current module:${this._name}, update start...`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| await this._updateCallback({ | ||
| name, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths, | ||
| remoteTypeTarPath, | ||
| remoteInfo | ||
| }); | ||
| const newUpdateSourcePaths = updateSourcePaths.concat(this._name); | ||
| const updatePublisher = new UpdatePublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| updateMode: require_Action.UpdateMode.PASSIVE, | ||
| updateKind, | ||
| updateSourcePaths: newUpdateSourcePaths, | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| require_Broker.fileLog(`[_updateSubscriber] run, updateSourcePaths:${newUpdateSourcePaths}, update publisher ${this._name} start...`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| this._publishWebSocket?.send(JSON.stringify(updatePublisher)); | ||
| } | ||
| _tryCreateBackgroundBroker(err) { | ||
| if (!((err?.code === "ECONNREFUSED" || err?.code === "ETIMEDOUT") && err.port === require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT)) { | ||
| require_Broker.fileLog(`websocket error: ${err.stack}`, require_Action.MF_SERVER_IDENTIFIER, "fatal"); | ||
| return; | ||
| } | ||
| require_Broker.fileLog(`Failed to connect to ws://${this._ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}...`, require_Action.MF_SERVER_IDENTIFIER, "fatal"); | ||
| this._isReconnecting = true; | ||
| setTimeout(() => { | ||
| this._isReconnecting = false; | ||
| if (this._reconnect === false) return; | ||
| require_Broker.fileLog("Creating new background broker...", require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| createBroker().on("message", (message) => { | ||
| if (message === "ready") { | ||
| require_Broker.fileLog("background broker started.", require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| this._reconnectTimes = 1; | ||
| if (process.send) process.send("ready"); | ||
| } | ||
| }); | ||
| }, Math.ceil(100 * Math.random())); | ||
| } | ||
| _stopWhenSIGTERMOrSIGINT() { | ||
| process.on("SIGTERM", () => { | ||
| require_Broker.fileLog(`Process(${process.pid}) SIGTERM, ModuleFederationDevServer will exit...`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| }); | ||
| process.on("SIGINT", () => { | ||
| require_Broker.fileLog(`Process(${process.pid}) SIGINT, ModuleFederationDevServer will exit...`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| }); | ||
| } | ||
| _handleUnexpectedExit() { | ||
| process.on("unhandledRejection", (error) => { | ||
| if (this._isReconnecting) return; | ||
| console.error("Unhandled Rejection Error: ", error); | ||
| require_Broker.fileLog(`Process(${process.pid}) unhandledRejection, garfishModuleServer will exit...`, require_Action.MF_SERVER_IDENTIFIER, "error"); | ||
| this._exit(); | ||
| }); | ||
| process.on("uncaughtException", (error) => { | ||
| if (this._isReconnecting) return; | ||
| console.error("Unhandled Exception Error: ", error); | ||
| require_Broker.fileLog(`Process(${process.pid}) uncaughtException, garfishModuleServer will exit...`, require_Action.MF_SERVER_IDENTIFIER, "error"); | ||
| this._exit(); | ||
| }); | ||
| } | ||
| _exit() { | ||
| this._reconnect = false; | ||
| if (this._publishWebSocket) { | ||
| const exitPublisher = new ExitPublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(exitPublisher)); | ||
| this._publishWebSocket.on("message", (message) => { | ||
| const parsedMessage = JSON.parse(message.toString()); | ||
| require_Broker.fileLog(`[${parsedMessage.kind}]: ${JSON.stringify(parsedMessage)}`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| }); | ||
| } | ||
| if (this._publishWebSocket) { | ||
| this._publishWebSocket.close(); | ||
| this._publishWebSocket = null; | ||
| } | ||
| process.exit(0); | ||
| } | ||
| exit() { | ||
| this._exit(); | ||
| } | ||
| update(options) { | ||
| if (!this._publishWebSocket || !this._isConnected) return; | ||
| const { updateKind, updateMode, updateSourcePaths, clientName } = options; | ||
| require_Broker.fileLog(`update run, ${this._name} module update, updateKind: ${updateKind}, updateMode: ${updateMode}, updateSourcePaths: ${updateSourcePaths}`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| if (updateKind === require_Broker.UpdateKind.RELOAD_PAGE) { | ||
| const notifyWebClient = new NotifyWebClientAction({ | ||
| name: clientName || this._name, | ||
| updateMode | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(notifyWebClient)); | ||
| return; | ||
| } | ||
| const updatePublisher = new UpdatePublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths: [this._name], | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(updatePublisher)); | ||
| } | ||
| async fetchDynamicRemoteTypes(options) { | ||
| const { remoteInfo, once } = options; | ||
| const updateMode = require_Action.UpdateMode.PASSIVE; | ||
| const updateKind = require_Broker.UpdateKind.UPDATE_TYPE; | ||
| require_Broker.fileLog(`fetchDynamicRemoteTypes: remoteInfo: ${JSON.stringify(remoteInfo)}`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| await this._updateCallback({ | ||
| name: this._name, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths: [], | ||
| remoteTypeTarPath: "", | ||
| remoteInfo, | ||
| once | ||
| }); | ||
| const updatePublisher = new UpdatePublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths: [this._name], | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(updatePublisher)); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/createHttpServer.ts | ||
| async function createHttpServer(options) { | ||
| const { typeTarPath } = options; | ||
| const freeport = await require_Broker.getFreePort(); | ||
| const server = http.default.createServer((req, res) => { | ||
| if ((req.url?.split("?")[0] ?? "/") === `/${require_Action.DEFAULT_TAR_NAME}`) { | ||
| res.statusCode = 200; | ||
| res.setHeader("Content-Type", "application/x-gzip"); | ||
| if (req.method === "HEAD") { | ||
| res.end(); | ||
| return; | ||
| } | ||
| const stream = fs.default.createReadStream(typeTarPath); | ||
| stream.on("error", () => { | ||
| if (!res.headersSent) res.statusCode = 500; | ||
| res.end(); | ||
| }); | ||
| res.on("close", () => { | ||
| stream.destroy(); | ||
| }); | ||
| stream.pipe(res); | ||
| return; | ||
| } | ||
| res.statusCode = 404; | ||
| res.end(); | ||
| }); | ||
| server.listen(freeport); | ||
| return { | ||
| server, | ||
| serverAddress: `http://${require_Broker.getIPV4()}:${freeport}` | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/core/lib/typeScriptCompiler.ts | ||
| const STARTS_WITH_SLASH = /^\//; | ||
| const DEFINITION_FILE_EXTENSION = ".d.ts"; | ||
| const retrieveMfTypesPath = (tsConfig, remoteOptions) => (0, path.normalize)(tsConfig.compilerOptions.outDir.replace(remoteOptions.compiledTypesFolder, "")); | ||
| const retrieveOriginalOutDir = (tsConfig, remoteOptions) => (0, path.normalize)(tsConfig.compilerOptions.outDir.replace(remoteOptions.compiledTypesFolder, "").replace(remoteOptions.typesFolder, "")); | ||
| const retrieveMfAPITypesPath = (tsConfig, remoteOptions) => (0, path.join)(retrieveOriginalOutDir(tsConfig, remoteOptions), `${remoteOptions.typesFolder}.d.ts`); | ||
| function writeTempTsConfig(tsConfig, context, name, cwd) { | ||
| const createHash = (contents) => { | ||
| return crypto.default.createHash("md5").update(contents).digest("hex"); | ||
| }; | ||
| const hash = createHash(`${JSON.stringify(tsConfig)}${name}${Date.now()}`); | ||
| const tempTsConfigJsonPath = (0, path.resolve)(cwd ?? context, "node_modules", _module_federation_sdk.TEMP_DIR, `tsconfig.${hash}.json`); | ||
| (0, fs.mkdirSync)((0, path.dirname)(tempTsConfigJsonPath), { recursive: true }); | ||
| (0, fs.writeFileSync)(tempTsConfigJsonPath, JSON.stringify(tsConfig, null, 2)); | ||
| return tempTsConfigJsonPath; | ||
| } | ||
| const removeExt = (f) => { | ||
| const vueExt = ".vue"; | ||
| const ext = (0, path.extname)(f); | ||
| if (ext === vueExt) return f; | ||
| const regexPattern = new RegExp(`\\${ext}$`); | ||
| return f.replace(regexPattern, ""); | ||
| }; | ||
| function getExposeKey(options) { | ||
| const { filePath, rootDir, outDir, mapExposeToEntry } = options; | ||
| return mapExposeToEntry[(0, path.relative)(outDir, filePath.replace(new RegExp(`\\.d.ts$`), ""))]; | ||
| } | ||
| const processTypesFile = async (options) => { | ||
| const { outDir, filePath, rootDir, cb, mapExposeToEntry, mfTypePath } = options; | ||
| if (!(0, fs.existsSync)(filePath)) return; | ||
| if ((await (0, fs_promises.stat)(filePath)).isDirectory()) { | ||
| const files = await (0, fs_promises.readdir)(filePath); | ||
| await Promise.all(files.map((file) => processTypesFile({ | ||
| ...options, | ||
| filePath: (0, path.join)(filePath, file) | ||
| }))); | ||
| } else if (filePath.endsWith(".d.ts")) { | ||
| const exposeKey = getExposeKey({ | ||
| filePath, | ||
| rootDir, | ||
| outDir, | ||
| mapExposeToEntry | ||
| }); | ||
| if (exposeKey) { | ||
| const mfeTypeEntry = (0, path.join)(mfTypePath, `${exposeKey === "." ? "index" : exposeKey}${DEFINITION_FILE_EXTENSION}`); | ||
| const mfeTypeEntryDirectory = (0, path.dirname)(mfeTypeEntry); | ||
| const relativePathToOutput = (0, path.relative)(mfeTypeEntryDirectory, filePath).replace(DEFINITION_FILE_EXTENSION, "").replace(STARTS_WITH_SLASH, "").split(path.sep).join("/"); | ||
| (0, fs.mkdirSync)(mfeTypeEntryDirectory, { recursive: true }); | ||
| await (0, fs_promises.writeFile)(mfeTypeEntry, `export * from './${relativePathToOutput}';\nexport { default } from './${relativePathToOutput}';`); | ||
| } | ||
| cb(await (0, fs_promises.readFile)(filePath, "utf8")); | ||
| } | ||
| }; | ||
| const getPMFromUserAgent = () => { | ||
| const userAgent = process.env["npm_config_user_agent"]; | ||
| if (userAgent == null) return "null"; | ||
| return userAgent.split("/")[0]; | ||
| }; | ||
| const resolvePackageManagerExecutable = () => { | ||
| switch (getPMFromUserAgent()) { | ||
| case "yarn": return "yarn"; | ||
| default: return "npx"; | ||
| } | ||
| }; | ||
| const splitCommandArgs = (value) => { | ||
| const args = []; | ||
| let current = ""; | ||
| let quote = null; | ||
| let escaped = false; | ||
| for (const char of value) { | ||
| if (escaped) { | ||
| current += char; | ||
| escaped = false; | ||
| continue; | ||
| } | ||
| if (char === "\\") { | ||
| escaped = true; | ||
| continue; | ||
| } | ||
| if (quote) { | ||
| if (char === quote) quote = null; | ||
| else current += char; | ||
| continue; | ||
| } | ||
| if (char === "\"" || char === "'") { | ||
| quote = char; | ||
| continue; | ||
| } | ||
| if (char.trim() === "") { | ||
| if (current) { | ||
| args.push(current); | ||
| current = ""; | ||
| } | ||
| continue; | ||
| } | ||
| current += char; | ||
| } | ||
| if (current) args.push(current); | ||
| return args; | ||
| }; | ||
| const formatCommandForDisplay = (executable, args) => { | ||
| const formatArg = (arg) => { | ||
| if (/[\s'"]/.test(arg)) return JSON.stringify(arg); | ||
| return arg; | ||
| }; | ||
| return [executable, ...args].map(formatArg).join(" "); | ||
| }; | ||
| const compileTs = async (mapComponentsToExpose, tsConfig, remoteOptions) => { | ||
| if (!Object.keys(mapComponentsToExpose).length) return; | ||
| const { compilerOptions } = tsConfig; | ||
| const tempTsConfigJsonPath = writeTempTsConfig(tsConfig, remoteOptions.context, remoteOptions.moduleFederationConfig.name || "mf", typeof remoteOptions.moduleFederationConfig.dts !== "boolean" ? remoteOptions.moduleFederationConfig.dts?.cwd ?? void 0 : void 0); | ||
| require_Broker.logger.debug(`tempTsConfigJsonPath: ${tempTsConfigJsonPath}`); | ||
| try { | ||
| const mfTypePath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| const thirdPartyExtractor = new _module_federation_third_party_dts_extractor.ThirdPartyExtractor({ | ||
| destDir: (0, path.resolve)(mfTypePath, "node_modules"), | ||
| context: remoteOptions.context, | ||
| exclude: typeof remoteOptions.extractThirdParty === "object" ? remoteOptions.extractThirdParty.exclude : void 0 | ||
| }); | ||
| const execPromise = util.default.promisify(child_process.execFile); | ||
| const pmExecutable = resolvePackageManagerExecutable(); | ||
| const compilerArgs = splitCommandArgs(remoteOptions.compilerInstance); | ||
| const cmdArgs = [ | ||
| ...compilerArgs.length > 0 ? compilerArgs : [remoteOptions.compilerInstance], | ||
| "--project", | ||
| tempTsConfigJsonPath | ||
| ]; | ||
| const cmd = formatCommandForDisplay(pmExecutable, cmdArgs); | ||
| try { | ||
| await execPromise(pmExecutable, cmdArgs, { | ||
| cwd: typeof remoteOptions.moduleFederationConfig.dts !== "boolean" ? remoteOptions.moduleFederationConfig.dts?.cwd ?? void 0 : void 0, | ||
| shell: process.platform === "win32" | ||
| }); | ||
| } catch (err) { | ||
| if (compilerOptions.tsBuildInfoFile) try { | ||
| await (0, fs_promises.rm)(compilerOptions.tsBuildInfoFile); | ||
| } catch (e) {} | ||
| (0, _module_federation_error_codes_node.logAndReport)(_module_federation_error_codes.TYPE_001, _module_federation_error_codes.typeDescMap, { cmd }, (msg) => { | ||
| throw new Error(msg); | ||
| }, void 0); | ||
| } | ||
| const mapExposeToEntry = Object.fromEntries(Object.entries(mapComponentsToExpose).map(([exposed, filename]) => { | ||
| const normalizedFileName = (0, path.normalize)(filename); | ||
| let relativeFileName = ""; | ||
| if ((0, path.isAbsolute)(normalizedFileName)) relativeFileName = (0, path.relative)(tsConfig.compilerOptions.rootDir, normalizedFileName); | ||
| else relativeFileName = (0, path.relative)(tsConfig.compilerOptions.rootDir, (0, path.resolve)(remoteOptions.context, normalizedFileName)); | ||
| return [removeExt(relativeFileName), exposed]; | ||
| })); | ||
| const cb = remoteOptions.extractThirdParty ? thirdPartyExtractor.collectPkgs.bind(thirdPartyExtractor) : () => void 0; | ||
| await processTypesFile({ | ||
| outDir: compilerOptions.outDir, | ||
| filePath: compilerOptions.outDir, | ||
| rootDir: compilerOptions.rootDir, | ||
| mfTypePath, | ||
| cb, | ||
| mapExposeToEntry | ||
| }); | ||
| if (remoteOptions.extractThirdParty) await thirdPartyExtractor.copyDts(); | ||
| if (remoteOptions.deleteTsConfig) await (0, fs_promises.rm)(tempTsConfigJsonPath); | ||
| } catch (err) { | ||
| throw err; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/archiveHandler.ts | ||
| const retrieveTypesZipPath = (mfTypesPath, remoteOptions) => (0, path.join)(mfTypesPath.replace(remoteOptions.typesFolder, ""), `${remoteOptions.typesFolder}.zip`); | ||
| const createTypesArchive = async (tsConfig, remoteOptions) => { | ||
| const mfTypesPath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| const zip = new adm_zip.default(); | ||
| zip.addLocalFolder(mfTypesPath); | ||
| return zip.writeZipPromise(retrieveTypesZipPath(mfTypesPath, remoteOptions)); | ||
| }; | ||
| const downloadErrorLogger = (destinationFolder, fileToDownload) => (reason) => { | ||
| throw { | ||
| ...reason, | ||
| message: `Network error: Unable to download federated mocks for '${destinationFolder}' from '${fileToDownload}' because '${reason.message}'` | ||
| }; | ||
| }; | ||
| const retrieveTypesArchiveDestinationPath = (hostOptions, destinationFolder) => { | ||
| return (0, path.resolve)(hostOptions.context, hostOptions.typesFolder, destinationFolder); | ||
| }; | ||
| const downloadTypesArchive = (hostOptions) => { | ||
| let retries = 0; | ||
| return async ([destinationFolder, fileToDownload]) => { | ||
| const destinationPath = retrieveTypesArchiveDestinationPath(hostOptions, destinationFolder); | ||
| while (retries++ < hostOptions.maxRetries) try { | ||
| const url = new URL(fileToDownload).href; | ||
| const response = await nativeFetch(url, { | ||
| responseType: "arraybuffer", | ||
| timeout: hostOptions.timeout, | ||
| family: hostOptions.family | ||
| }).catch(downloadErrorLogger(destinationFolder, url)); | ||
| if (typeof response.headers?.["content-type"] === "string" && response.headers["content-type"].includes("text/html")) throw new Error(`${url} receives invalid content-type: ${response.headers["content-type"]}`); | ||
| try { | ||
| if (hostOptions.deleteTypesFolder) await (0, fs_promises.rm)(destinationPath, { | ||
| recursive: true, | ||
| force: true | ||
| }); | ||
| } catch (error) { | ||
| require_Broker.fileLog(`Unable to remove types folder, ${error}`, "downloadTypesArchive", "error"); | ||
| } | ||
| new adm_zip.default(Buffer.from(response.data)).extractAllTo(destinationPath, true); | ||
| require_Broker.fileLog(`zip.extractAllTo success destinationPath: ${destinationPath}; url: ${url}`, "downloadTypesArchive", "info"); | ||
| return [destinationFolder, destinationPath]; | ||
| } catch (error) { | ||
| require_Broker.fileLog(`Error during types archive download: ${error?.message || "unknown error"}`, "downloadTypesArchive", "error"); | ||
| if (retries >= hostOptions.maxRetries) { | ||
| require_Broker.logger.error(`Failed to download types archive from "${fileToDownload}". Set FEDERATION_DEBUG=true for details.`); | ||
| if (hostOptions.abortOnError !== false) throw error; | ||
| return; | ||
| } | ||
| } | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/core/configurations/hostPlugin.ts | ||
| const defaultOptions$1 = { | ||
| typesFolder: "@mf-types", | ||
| remoteTypesFolder: "@mf-types", | ||
| deleteTypesFolder: true, | ||
| maxRetries: 3, | ||
| implementation: "", | ||
| context: process.cwd(), | ||
| abortOnError: true, | ||
| consumeAPITypes: false, | ||
| runtimePkgs: [], | ||
| remoteTypeUrls: {}, | ||
| timeout: 6e4, | ||
| typesOnBuild: false, | ||
| family: 0 | ||
| }; | ||
| const buildZipUrl = (hostOptions, url) => { | ||
| const remoteUrl = new URL(url, "file:"); | ||
| remoteUrl.pathname = `${remoteUrl.pathname.split("/").slice(0, -1).join("/")}/${hostOptions.remoteTypesFolder}.zip`; | ||
| return remoteUrl.protocol === "file:" ? remoteUrl.pathname : remoteUrl.href; | ||
| }; | ||
| const buildApiTypeUrl = (zipUrl) => { | ||
| if (!zipUrl) return; | ||
| return zipUrl.replace(".zip", ".d.ts"); | ||
| }; | ||
| const retrieveRemoteInfo = (options) => { | ||
| const { hostOptions, remoteAlias, remote } = options; | ||
| const { remoteTypeUrls } = hostOptions; | ||
| let decodedRemote = remote; | ||
| if (decodedRemote.startsWith(_module_federation_sdk.ENCODE_NAME_PREFIX)) decodedRemote = (0, _module_federation_sdk.decodeName)(decodedRemote, _module_federation_sdk.ENCODE_NAME_PREFIX); | ||
| const parsedInfo = (0, _module_federation_sdk.parseEntry)(decodedRemote, void 0, "@"); | ||
| const url = "entry" in parsedInfo ? parsedInfo.entry : parsedInfo.name === decodedRemote ? decodedRemote : ""; | ||
| let zipUrl = ""; | ||
| let apiTypeUrl = ""; | ||
| const name = parsedInfo.name || remoteAlias; | ||
| const remoteTypeUrl = typeof remoteTypeUrls === "object" && remoteTypeUrls[name]; | ||
| if (remoteTypeUrl) { | ||
| zipUrl = remoteTypeUrl.zip; | ||
| apiTypeUrl = remoteTypeUrl.api; | ||
| } | ||
| const shouldResolveTypeUrlsByConvention = Boolean(url && !url.includes(_module_federation_sdk.MANIFEST_EXT)); | ||
| if (!zipUrl && shouldResolveTypeUrlsByConvention) zipUrl = buildZipUrl(hostOptions, url); | ||
| if (!apiTypeUrl && zipUrl && (remoteTypeUrl || shouldResolveTypeUrlsByConvention)) apiTypeUrl = buildApiTypeUrl(zipUrl); | ||
| return { | ||
| name, | ||
| url, | ||
| zipUrl, | ||
| apiTypeUrl, | ||
| alias: remoteAlias | ||
| }; | ||
| }; | ||
| const resolveRemotes = (hostOptions) => { | ||
| const parsedOptions = _module_federation_managers.utils.parseOptions(hostOptions.moduleFederationConfig.remotes || {}, (item, key) => ({ | ||
| remote: Array.isArray(item) ? item[0] : item, | ||
| key | ||
| }), (item, key) => ({ | ||
| remote: Array.isArray(item.external) ? item.external[0] : item.external, | ||
| key | ||
| })); | ||
| const remoteTypeUrls = hostOptions.remoteTypeUrls ?? {}; | ||
| if (typeof remoteTypeUrls !== "object") throw new Error("remoteTypeUrls must be consumed before resolveRemotes"); | ||
| const remoteInfos = Object.keys(remoteTypeUrls).reduce((sum, remoteName) => { | ||
| const { zip, api, alias } = remoteTypeUrls[remoteName]; | ||
| sum[alias] = { | ||
| name: remoteName, | ||
| url: "", | ||
| zipUrl: zip, | ||
| apiTypeUrl: api, | ||
| alias: alias || remoteName | ||
| }; | ||
| return sum; | ||
| }, {}); | ||
| return parsedOptions.reduce((accumulator, item) => { | ||
| const { key, remote } = item[1]; | ||
| const res = retrieveRemoteInfo({ | ||
| hostOptions, | ||
| remoteAlias: key, | ||
| remote | ||
| }); | ||
| if (accumulator[key]) { | ||
| accumulator[key] = { | ||
| ...accumulator[key], | ||
| url: res.url, | ||
| apiTypeUrl: accumulator[key].apiTypeUrl || res.apiTypeUrl | ||
| }; | ||
| return accumulator; | ||
| } | ||
| accumulator[key] = res; | ||
| return accumulator; | ||
| }, remoteInfos); | ||
| }; | ||
| const retrieveHostConfig = (options) => { | ||
| validateOptions(options); | ||
| const hostOptions = { | ||
| ...defaultOptions$1, | ||
| ...options | ||
| }; | ||
| return { | ||
| hostOptions, | ||
| mapRemotesToDownload: resolveRemotes(hostOptions) | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/core/constant.ts | ||
| const REMOTE_ALIAS_IDENTIFIER = "REMOTE_ALIAS_IDENTIFIER"; | ||
| const REMOTE_API_TYPES_FILE_NAME = "apis.d.ts"; | ||
| const HOST_API_TYPES_FILE_NAME = "index.d.ts"; | ||
| //#endregion | ||
| //#region src/core/lib/DTSManager.ts | ||
| var DTSManager = class { | ||
| constructor(options) { | ||
| this.options = cloneDeepOptions(options); | ||
| this.runtimePkgs = [ | ||
| "@module-federation/runtime", | ||
| "@module-federation/enhanced/runtime", | ||
| "@module-federation/runtime-tools" | ||
| ]; | ||
| this.loadedRemoteAPIAlias = /* @__PURE__ */ new Set(); | ||
| this.remoteAliasMap = {}; | ||
| this.extraOptions = options?.extraOptions || {}; | ||
| this.updatedRemoteInfos = {}; | ||
| } | ||
| generateAPITypes(mapComponentsToExpose) { | ||
| const exposePaths = /* @__PURE__ */ new Set(); | ||
| const packageType = Object.keys(mapComponentsToExpose).reduce((sum, exposeKey) => { | ||
| const exposePath = path.default.join(REMOTE_ALIAS_IDENTIFIER, exposeKey).split(path.default.sep).join("/"); | ||
| exposePaths.add(`'${exposePath}'`); | ||
| sum = `T extends '${exposePath}' ? typeof import('${exposePath}') :` + sum; | ||
| return sum; | ||
| }, "any;"); | ||
| return ` | ||
| export type RemoteKeys = ${[...exposePaths].join(" | ")}; | ||
| type PackageType<T> = ${packageType}`; | ||
| } | ||
| async extractRemoteTypes(options) { | ||
| const { remoteOptions, tsConfig } = options; | ||
| if (!remoteOptions.extractRemoteTypes) return; | ||
| let hasRemotes = false; | ||
| const remotes = remoteOptions.moduleFederationConfig.remotes; | ||
| if (remotes) { | ||
| if (Array.isArray(remotes)) hasRemotes = Boolean(remotes.length); | ||
| else if (typeof remotes === "object") hasRemotes = Boolean(Object.keys(remotes).length); | ||
| } | ||
| const mfTypesPath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| if (hasRemotes && this.options.host) try { | ||
| const { hostOptions } = retrieveHostConfig(this.options.host); | ||
| const remoteTypesFolder = path.default.resolve(hostOptions.context, hostOptions.typesFolder); | ||
| const targetDir = path.default.join(mfTypesPath, "node_modules"); | ||
| if (fs.default.existsSync(remoteTypesFolder)) { | ||
| const targetFolder = path.default.resolve(remoteOptions.context, targetDir); | ||
| await (0, fs_promises.mkdir)(targetFolder, { recursive: true }); | ||
| await (0, fs_promises.cp)(remoteTypesFolder, targetFolder, { | ||
| recursive: true, | ||
| force: true | ||
| }); | ||
| } | ||
| } catch (err) { | ||
| if (this.options.host?.abortOnError === false) require_Broker.fileLog(`Unable to copy remote types, ${err}`, "extractRemoteTypes", "error"); | ||
| else throw err; | ||
| } | ||
| } | ||
| async generateTypes() { | ||
| try { | ||
| const { options } = this; | ||
| if (!options.remote) throw new Error("options.remote is required if you want to generateTypes"); | ||
| const { remoteOptions, tsConfig, mapComponentsToExpose } = retrieveRemoteConfig(options.remote); | ||
| if (!Object.keys(mapComponentsToExpose).length) return; | ||
| if (!tsConfig.files?.length) { | ||
| require_Broker.logger.info("No type files to compile, skip"); | ||
| return; | ||
| } | ||
| if (tsConfig.compilerOptions.tsBuildInfoFile) try { | ||
| const tsBuildInfoFile = path.default.resolve(remoteOptions.context, tsConfig.compilerOptions.tsBuildInfoFile); | ||
| const mfTypesPath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| if (!fs.default.existsSync(mfTypesPath)) fs.default.rmSync(tsBuildInfoFile, { force: true }); | ||
| } catch (e) {} | ||
| await this.extractRemoteTypes({ | ||
| remoteOptions, | ||
| tsConfig, | ||
| mapComponentsToExpose | ||
| }); | ||
| await compileTs(mapComponentsToExpose, tsConfig, remoteOptions); | ||
| await createTypesArchive(tsConfig, remoteOptions); | ||
| let apiTypesPath = ""; | ||
| if (remoteOptions.generateAPITypes) { | ||
| const apiTypes = this.generateAPITypes(mapComponentsToExpose); | ||
| apiTypesPath = retrieveMfAPITypesPath(tsConfig, remoteOptions); | ||
| fs.default.writeFileSync(apiTypesPath, apiTypes); | ||
| } | ||
| try { | ||
| if (remoteOptions.deleteTypesFolder) await (0, fs_promises.rm)(retrieveMfTypesPath(tsConfig, remoteOptions), { | ||
| recursive: true, | ||
| force: true | ||
| }); | ||
| } catch (err) { | ||
| if (isDebugMode()) console.error(err); | ||
| } | ||
| require_Broker.logger.success("Federated types created correctly"); | ||
| } catch (error) { | ||
| if (this.options.remote?.abortOnError === false) { | ||
| if (this.options.displayErrorInTerminal) require_Broker.logger.error(error); | ||
| } else throw error; | ||
| } | ||
| } | ||
| async requestRemoteManifest(remoteInfo, hostOptions) { | ||
| try { | ||
| if (!remoteInfo.url.includes(_module_federation_sdk.MANIFEST_EXT)) return remoteInfo; | ||
| if (remoteInfo.zipUrl) return remoteInfo; | ||
| const url = remoteInfo.url; | ||
| const manifestJson = (await nativeFetch(url, { | ||
| timeout: hostOptions.timeout, | ||
| family: hostOptions.family | ||
| })).data; | ||
| if (!manifestJson.metaData.types.zip) throw new Error(`Can not get ${remoteInfo.name}'s types archive url!`); | ||
| const addProtocol = (u) => { | ||
| if (u.startsWith("//")) return `https:${u}`; | ||
| return u; | ||
| }; | ||
| let publicPath; | ||
| if ("publicPath" in manifestJson.metaData) publicPath = manifestJson.metaData.publicPath; | ||
| else { | ||
| const getPublicPath = new Function(manifestJson.metaData.getPublicPath); | ||
| if (manifestJson.metaData.getPublicPath.startsWith("function")) publicPath = getPublicPath()(); | ||
| else publicPath = getPublicPath(); | ||
| } | ||
| if (publicPath === "auto") publicPath = (0, _module_federation_sdk.inferAutoPublicPath)(remoteInfo.url); | ||
| const normalizedPublicPath = addProtocol(publicPath).endsWith("/") ? addProtocol(publicPath) : `${addProtocol(publicPath)}/`; | ||
| remoteInfo.zipUrl = new URL(manifestJson.metaData.types.zip, normalizedPublicPath).href; | ||
| if (!manifestJson.metaData.types.api) { | ||
| console.warn(`Can not get ${remoteInfo.name}'s api types url!`); | ||
| remoteInfo.apiTypeUrl = ""; | ||
| return remoteInfo; | ||
| } | ||
| remoteInfo.apiTypeUrl = new URL(manifestJson.metaData.types.api, normalizedPublicPath).href; | ||
| return remoteInfo; | ||
| } catch (_err) { | ||
| require_Broker.fileLog(`fetch manifest failed, ${_err}, ${remoteInfo.name} will be ignored`, "requestRemoteManifest", "error"); | ||
| return remoteInfo; | ||
| } | ||
| } | ||
| async consumeTargetRemotes(hostOptions, remoteInfo) { | ||
| if (!remoteInfo.zipUrl) throw new Error(`Can not get ${remoteInfo.name}'s types archive url!`); | ||
| return downloadTypesArchive(hostOptions)([remoteInfo.alias, remoteInfo.zipUrl]); | ||
| } | ||
| async downloadAPITypes(remoteInfo, destinationPath, hostOptions) { | ||
| const { apiTypeUrl } = remoteInfo; | ||
| if (!apiTypeUrl) return; | ||
| try { | ||
| let apiTypeFile = (await nativeFetch(apiTypeUrl, { | ||
| timeout: hostOptions.timeout, | ||
| family: hostOptions.family | ||
| })).data; | ||
| apiTypeFile = apiTypeFile.replaceAll(REMOTE_ALIAS_IDENTIFIER, remoteInfo.alias); | ||
| const filePath = path.default.join(destinationPath, REMOTE_API_TYPES_FILE_NAME); | ||
| fs.default.writeFileSync(filePath, apiTypeFile); | ||
| const existed = this.loadedRemoteAPIAlias.has(remoteInfo.alias); | ||
| this.loadedRemoteAPIAlias.add(remoteInfo.alias); | ||
| require_Broker.fileLog(`success`, "downloadAPITypes", "info"); | ||
| return existed; | ||
| } catch (err) { | ||
| require_Broker.fileLog(`Unable to download "${remoteInfo.name}" api types, ${err}`, "downloadAPITypes", "error"); | ||
| } | ||
| } | ||
| consumeAPITypes(hostOptions) { | ||
| const apiTypeFileName = path.default.join(hostOptions.context, hostOptions.typesFolder, HOST_API_TYPES_FILE_NAME); | ||
| try { | ||
| const existedFile = fs.default.readFileSync(apiTypeFileName, "utf-8"); | ||
| new _module_federation_third_party_dts_extractor.ThirdPartyExtractor({ destDir: "" }).collectTypeImports(existedFile).forEach((existedImport) => { | ||
| const alias = existedImport.split("./").slice(1).join("./").replace("/apis.d.ts", ""); | ||
| this.loadedRemoteAPIAlias.add(alias); | ||
| }); | ||
| } catch (err) {} | ||
| if (!this.loadedRemoteAPIAlias.size) return; | ||
| const packageTypes = []; | ||
| const remoteKeys = []; | ||
| const importTypeStr = [...this.loadedRemoteAPIAlias].sort().map((alias, index) => { | ||
| const remoteKey = `RemoteKeys_${index}`; | ||
| const packageType = `PackageType_${index}`; | ||
| packageTypes.push(`T extends ${remoteKey} ? ${packageType}<T>`); | ||
| remoteKeys.push(remoteKey); | ||
| return `import type { PackageType as ${packageType},RemoteKeys as ${remoteKey} } from './${alias}/apis.d.ts';`; | ||
| }).join("\n"); | ||
| const remoteKeysStr = `type RemoteKeys = ${remoteKeys.join(" | ")};`; | ||
| const packageTypesStr = `type PackageType<T, Y=any> = ${[...packageTypes, "Y"].join(" :\n")} ;`; | ||
| const runtimePkgs = /* @__PURE__ */ new Set(); | ||
| [...this.runtimePkgs, ...hostOptions.runtimePkgs].forEach((pkg) => { | ||
| runtimePkgs.add(pkg); | ||
| }); | ||
| const fileStr = `${importTypeStr} | ||
| ${[...runtimePkgs].map((pkg) => { | ||
| return `declare module "${pkg}" { | ||
| ${remoteKeysStr} | ||
| ${packageTypesStr} | ||
| export function loadRemote<T extends RemoteKeys,Y>(packageName: T): Promise<PackageType<T, Y>>; | ||
| export function loadRemote<T extends string,Y>(packageName: T): Promise<PackageType<T, Y>>; | ||
| }`; | ||
| }).join("\n")} | ||
| `; | ||
| fs.default.writeFileSync(path.default.join(hostOptions.context, hostOptions.typesFolder, HOST_API_TYPES_FILE_NAME), fileStr); | ||
| } | ||
| async consumeArchiveTypes(options) { | ||
| const { hostOptions, mapRemotesToDownload } = retrieveHostConfig(options); | ||
| const downloadPromises = Object.entries(mapRemotesToDownload).map(async (item) => { | ||
| const remoteInfo = item[1]; | ||
| if (!this.remoteAliasMap[remoteInfo.alias]) { | ||
| const requiredRemoteInfo = await this.requestRemoteManifest(remoteInfo, hostOptions); | ||
| this.remoteAliasMap[remoteInfo.alias] = requiredRemoteInfo; | ||
| } | ||
| return this.consumeTargetRemotes(hostOptions, this.remoteAliasMap[remoteInfo.alias]); | ||
| }); | ||
| return { | ||
| hostOptions, | ||
| downloadPromisesResult: await Promise.allSettled(downloadPromises) | ||
| }; | ||
| } | ||
| async consumeTypes() { | ||
| try { | ||
| const { options } = this; | ||
| if (!options.host) throw new Error("options.host is required if you want to consumeTypes"); | ||
| const { mapRemotesToDownload } = retrieveHostConfig(options.host); | ||
| if (!Object.keys(mapRemotesToDownload).length) return; | ||
| const { downloadPromisesResult, hostOptions } = await this.consumeArchiveTypes(options.host); | ||
| if (hostOptions.consumeAPITypes) { | ||
| await Promise.all(downloadPromisesResult.map(async (item) => { | ||
| if (item.status === "rejected" || !item.value) return; | ||
| const [alias, destinationPath] = item.value; | ||
| const remoteInfo = this.remoteAliasMap[alias]; | ||
| if (!remoteInfo) return; | ||
| await this.downloadAPITypes(remoteInfo, destinationPath, hostOptions); | ||
| })); | ||
| this.consumeAPITypes(hostOptions); | ||
| } | ||
| require_Broker.logger.success("Federated types extraction completed"); | ||
| } catch (err) { | ||
| if (this.options.host?.abortOnError === false) require_Broker.fileLog(`Unable to consume federated types, ${err}`, "consumeTypes", "error"); | ||
| else throw err; | ||
| } | ||
| } | ||
| async updateTypes(options) { | ||
| try { | ||
| const { remoteName, updateMode, remoteTarPath, remoteInfo: updatedRemoteInfo, once } = options; | ||
| const hostName = this.options?.host?.moduleFederationConfig?.name; | ||
| require_Broker.fileLog(`options: ${JSON.stringify(options, null, 2)};\nhostName: ${hostName}`, "updateTypes", "info"); | ||
| if (updateMode === require_Action.UpdateMode.POSITIVE && remoteName === hostName) { | ||
| if (!this.options.remote) return; | ||
| await this.generateTypes(); | ||
| } else { | ||
| const { remoteAliasMap } = this; | ||
| if (!this.options.host) return; | ||
| const { hostOptions, mapRemotesToDownload } = retrieveHostConfig(this.options.host); | ||
| const loadedRemoteInfo = Object.values(remoteAliasMap).find((i) => i.name === remoteName); | ||
| const consumeTypes = async (requiredRemoteInfo) => { | ||
| require_Broker.fileLog(`consumeTypes start`, "updateTypes", "info"); | ||
| if (!requiredRemoteInfo.zipUrl) throw new Error(`Can not get ${requiredRemoteInfo.name}'s types archive url!`); | ||
| const [_alias, destinationPath] = await this.consumeTargetRemotes(hostOptions, { | ||
| ...requiredRemoteInfo, | ||
| zipUrl: remoteTarPath || requiredRemoteInfo.zipUrl | ||
| }); | ||
| if (await this.downloadAPITypes(requiredRemoteInfo, destinationPath, hostOptions)) this.consumeAPITypes(hostOptions); | ||
| require_Broker.fileLog(`consumeTypes end`, "updateTypes", "info"); | ||
| }; | ||
| require_Broker.fileLog(`loadedRemoteInfo: ${JSON.stringify(loadedRemoteInfo, null, 2)}`, "updateTypes", "info"); | ||
| if (!loadedRemoteInfo) { | ||
| const remoteInfo = Object.values(mapRemotesToDownload).find((item) => { | ||
| return item.name === remoteName; | ||
| }); | ||
| require_Broker.fileLog(`remoteInfo: ${JSON.stringify(remoteInfo, null, 2)}`, "updateTypes", "info"); | ||
| if (remoteInfo) { | ||
| if (!this.remoteAliasMap[remoteInfo.alias]) { | ||
| const requiredRemoteInfo = await this.requestRemoteManifest(remoteInfo, hostOptions); | ||
| this.remoteAliasMap[remoteInfo.alias] = requiredRemoteInfo; | ||
| } | ||
| await consumeTypes(this.remoteAliasMap[remoteInfo.alias]); | ||
| } else if (updatedRemoteInfo) { | ||
| const consumeDynamicRemoteTypes = async () => { | ||
| await consumeTypes(this.updatedRemoteInfos[updatedRemoteInfo.name]); | ||
| }; | ||
| if (!this.updatedRemoteInfos[updatedRemoteInfo.name]) { | ||
| const parsedRemoteInfo = retrieveRemoteInfo({ | ||
| hostOptions, | ||
| remoteAlias: updatedRemoteInfo.alias || updatedRemoteInfo.name, | ||
| remote: updatedRemoteInfo.url | ||
| }); | ||
| require_Broker.fileLog(`start request manifest`, "consumeTypes", "info"); | ||
| this.updatedRemoteInfos[updatedRemoteInfo.name] = await this.requestRemoteManifest(parsedRemoteInfo, hostOptions); | ||
| require_Broker.fileLog(`end request manifest, this.updatedRemoteInfos[updatedRemoteInfo.name]: ${JSON.stringify(this.updatedRemoteInfos[updatedRemoteInfo.name], null, 2)}`, "updateTypes", "info"); | ||
| await consumeDynamicRemoteTypes(); | ||
| } | ||
| if (!once && this.updatedRemoteInfos[updatedRemoteInfo.name]) await consumeDynamicRemoteTypes(); | ||
| } | ||
| } else await consumeTypes(loadedRemoteInfo); | ||
| } | ||
| } catch (err) { | ||
| require_Broker.fileLog(`updateTypes fail, ${err}`, "updateTypes", "error"); | ||
| } | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/utils.ts | ||
| const dispatcherCache = /* @__PURE__ */ new Map(); | ||
| function getDTSManagerConstructor(implementation) { | ||
| if (implementation) { | ||
| const NewConstructor = require(implementation); | ||
| return NewConstructor.default ? NewConstructor.default : NewConstructor; | ||
| } | ||
| return DTSManager; | ||
| } | ||
| const validateOptions = (options) => { | ||
| if (!options.moduleFederationConfig) throw new Error("moduleFederationConfig is required"); | ||
| }; | ||
| function retrieveTypesAssetsInfo(options) { | ||
| let apiTypesPath = ""; | ||
| let zipTypesPath = ""; | ||
| try { | ||
| const { tsConfig, remoteOptions, mapComponentsToExpose } = retrieveRemoteConfig(options); | ||
| if (!Object.keys(mapComponentsToExpose).length || !tsConfig.files.length) return { | ||
| apiTypesPath, | ||
| zipTypesPath, | ||
| zipName: "", | ||
| apiFileName: "" | ||
| }; | ||
| zipTypesPath = retrieveTypesZipPath(retrieveMfTypesPath(tsConfig, remoteOptions), remoteOptions); | ||
| if (remoteOptions.generateAPITypes) apiTypesPath = retrieveMfAPITypesPath(tsConfig, remoteOptions); | ||
| return { | ||
| apiTypesPath, | ||
| zipTypesPath, | ||
| zipName: path.default.basename(zipTypesPath), | ||
| apiFileName: path.default.basename(apiTypesPath) | ||
| }; | ||
| } catch (err) { | ||
| console.error(ansi_colors.default.red(`Unable to compile federated types, ${err}`)); | ||
| return { | ||
| apiTypesPath: "", | ||
| zipTypesPath: "", | ||
| zipName: "", | ||
| apiFileName: "" | ||
| }; | ||
| } | ||
| } | ||
| function isDebugMode() { | ||
| return Boolean(process.env["FEDERATION_DEBUG"]) || process.env["NODE_ENV"] === "test"; | ||
| } | ||
| const isTSProject = (dtsOptions, context = process.cwd()) => { | ||
| if (dtsOptions === false) return false; | ||
| try { | ||
| let filepath = ""; | ||
| if (typeof dtsOptions === "object" && dtsOptions.tsConfigPath) filepath = dtsOptions.tsConfigPath; | ||
| else filepath = path.default.resolve(context, "./tsconfig.json"); | ||
| if (!path.default.isAbsolute(filepath)) filepath = path.default.resolve(context, filepath); | ||
| return fs.default.existsSync(filepath); | ||
| } catch (err) { | ||
| return false; | ||
| } | ||
| }; | ||
| function cloneDeepOptions(options) { | ||
| const excludeKeys = new Set(["manifest", "async"]); | ||
| const cache = /* @__PURE__ */ new WeakMap(); | ||
| function sanitize(val, key) { | ||
| if (key !== void 0 && excludeKeys.has(key) || typeof val === "function") return false; | ||
| if (key === "extractThirdParty" && Array.isArray(val)) return val.map(String); | ||
| if (Array.isArray(val)) { | ||
| if (cache.has(val)) return cache.get(val); | ||
| const out = []; | ||
| cache.set(val, out); | ||
| val.forEach((v, i) => out.push(sanitize(v, String(i)))); | ||
| return out; | ||
| } | ||
| if (val !== null && typeof val === "object" && Object.getPrototypeOf(val) === Object.prototype) { | ||
| const obj = val; | ||
| if (cache.has(obj)) return cache.get(obj); | ||
| const out = {}; | ||
| cache.set(obj, out); | ||
| for (const [k, v] of Object.entries(obj)) out[k] = sanitize(v, k); | ||
| return out; | ||
| } | ||
| return val; | ||
| } | ||
| return structuredClone(sanitize(options)); | ||
| } | ||
| const getEnvHeaders = () => { | ||
| const headersStr = (0, _module_federation_sdk.getProcessEnv)()["MF_ENV_HEADERS"]; | ||
| if (!headersStr || headersStr === "undefined") return {}; | ||
| try { | ||
| return { ...JSON.parse(headersStr) }; | ||
| } catch { | ||
| return {}; | ||
| } | ||
| }; | ||
| const createDispatcherFromFamily = (family) => { | ||
| if (!family) return void 0; | ||
| if (dispatcherCache.has(family)) return dispatcherCache.get(family); | ||
| try { | ||
| const dispatcher = new undici.Agent({ connect: { family } }); | ||
| dispatcherCache.set(family, dispatcher); | ||
| return dispatcher; | ||
| } catch {} | ||
| }; | ||
| const toHeaderRecord = (headers) => { | ||
| const out = {}; | ||
| headers.forEach((value, key) => { | ||
| out[key.toLowerCase()] = value; | ||
| }); | ||
| return out; | ||
| }; | ||
| async function nativeFetch(url, config) { | ||
| const controller = new AbortController(); | ||
| const timeoutMs = config?.timeout ?? 6e4; | ||
| const timeoutId = setTimeout(() => controller.abort(), timeoutMs); | ||
| const headers = { | ||
| ...getEnvHeaders(), | ||
| ...config?.headers ?? {} | ||
| }; | ||
| const dispatcher = config?.dispatcher ?? createDispatcherFromFamily(config?.family); | ||
| try { | ||
| const resp = await fetch(url, { | ||
| headers, | ||
| signal: controller.signal, | ||
| ...dispatcher ? { dispatcher } : {}, | ||
| ...config?.agent ? { agent: config.agent } : {} | ||
| }); | ||
| const headerRecord = toHeaderRecord(resp.headers); | ||
| if (!resp.ok) throw new Error(`Request failed with status ${resp.status}`); | ||
| if (config?.responseType === "arraybuffer") return { | ||
| data: await resp.arrayBuffer(), | ||
| headers: headerRecord, | ||
| status: resp.status | ||
| }; | ||
| return { | ||
| data: (resp.headers.get("content-type") || "").includes("application/json") || url.endsWith(".json") ? await resp.json() : await resp.text(), | ||
| headers: headerRecord, | ||
| status: resp.status | ||
| }; | ||
| } finally { | ||
| clearTimeout(timeoutId); | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/core/configurations/remotePlugin.ts | ||
| const defaultOptions = { | ||
| tsConfigPath: "./tsconfig.json", | ||
| typesFolder: "@mf-types", | ||
| compiledTypesFolder: "compiled-types", | ||
| hostRemoteTypesFolder: "@mf-types", | ||
| deleteTypesFolder: true, | ||
| additionalFilesToCompile: [], | ||
| compilerInstance: "tsc", | ||
| compileInChildProcess: false, | ||
| implementation: "", | ||
| generateAPITypes: false, | ||
| context: process.cwd(), | ||
| abortOnError: true, | ||
| extractRemoteTypes: false, | ||
| extractThirdParty: false, | ||
| outputDir: "", | ||
| deleteTsConfig: true | ||
| }; | ||
| function getEffectiveRootDir(parsedCommandLine) { | ||
| const compilerOptions = parsedCommandLine.options; | ||
| if (compilerOptions.rootDir) return compilerOptions.rootDir; | ||
| const files = parsedCommandLine.fileNames; | ||
| if (files.length > 0) return files.map((file) => (0, path.dirname)(file)).reduce((commonPath, fileDir) => { | ||
| while (!fileDir.startsWith(commonPath)) commonPath = (0, path.dirname)(commonPath); | ||
| return commonPath; | ||
| }, files[0]); | ||
| if (parsedCommandLine.projectReferences.length) { | ||
| const relativeReferences = parsedCommandLine.projectReferences.filter((reference) => !(0, path.isAbsolute)(reference.originalPath ?? reference.path)); | ||
| const referencesForRoot = relativeReferences.length ? relativeReferences : parsedCommandLine.projectReferences; | ||
| return referencesForRoot.map((reference) => (0, path.dirname)(reference.path)).reduce((commonPath, filePath) => { | ||
| while (!filePath.startsWith(commonPath)) commonPath = (0, path.dirname)(commonPath); | ||
| return commonPath; | ||
| }, (0, path.dirname)(referencesForRoot[0].path)); | ||
| } | ||
| throw new Error("Can not get effective rootDir, please set compilerOptions.rootDir !"); | ||
| } | ||
| const getDependentFiles = (rootFiles, configContent, rootDir) => { | ||
| const dependentFiles = typescript.default.createProgram(rootFiles, configContent.options).getSourceFiles().map((file) => file.fileName).filter((file) => !file.endsWith(".d.ts") && file.startsWith(rootDir + "/")); | ||
| return dependentFiles.length ? dependentFiles : rootFiles; | ||
| }; | ||
| const readTsConfig = ({ tsConfigPath, typesFolder, compiledTypesFolder, context, additionalFilesToCompile, outputDir }, mapComponentsToExpose) => { | ||
| const resolvedTsConfigPath = (0, path.resolve)(context, tsConfigPath); | ||
| const readResult = typescript.default.readConfigFile(resolvedTsConfigPath, typescript.default.sys.readFile); | ||
| if (readResult.error) throw new Error(readResult.error.messageText.toString()); | ||
| const rawTsConfigJson = readResult.config; | ||
| const configContent = typescript.default.parseJsonConfigFileContent(rawTsConfigJson, typescript.default.sys, (0, path.dirname)(resolvedTsConfigPath)); | ||
| const rootDir = getEffectiveRootDir(configContent); | ||
| const outDir = (0, path.resolve)(context, outputDir || configContent.options.outDir || "dist", typesFolder, compiledTypesFolder); | ||
| const defaultCompilerOptions = { | ||
| rootDir, | ||
| emitDeclarationOnly: true, | ||
| noEmit: false, | ||
| declaration: true, | ||
| outDir | ||
| }; | ||
| rawTsConfigJson.compilerOptions = rawTsConfigJson.compilerOptions || {}; | ||
| rawTsConfigJson.compilerOptions = { | ||
| incremental: true, | ||
| tsBuildInfoFile: (0, path.resolve)(context, "node_modules/.cache/mf-types/.tsbuildinfo"), | ||
| ...rawTsConfigJson.compilerOptions, | ||
| ...defaultCompilerOptions | ||
| }; | ||
| const { paths, baseUrl, ...restCompilerOptions } = rawTsConfigJson.compilerOptions || {}; | ||
| rawTsConfigJson.compilerOptions = restCompilerOptions; | ||
| const outDirWithoutTypesFolder = (0, path.resolve)(context, outputDir || configContent.options.outDir || "dist"); | ||
| const excludeExtensions = [".mdx", ".md"]; | ||
| const filesToCompile = [...getDependentFiles([...Object.values(mapComponentsToExpose), ...additionalFilesToCompile].filter((filename) => !excludeExtensions.some((ext) => filename.endsWith(ext))), configContent, rootDir), ...configContent.fileNames.filter((filename) => filename.endsWith(".d.ts") && !filename.startsWith(outDirWithoutTypesFolder))]; | ||
| rawTsConfigJson.include = []; | ||
| rawTsConfigJson.files = [...new Set(filesToCompile)]; | ||
| rawTsConfigJson.exclude = []; | ||
| "references" in rawTsConfigJson && delete rawTsConfigJson.references; | ||
| rawTsConfigJson.extends = resolvedTsConfigPath; | ||
| rawTsConfigJson.compilerOptions.declarationDir = outDir; | ||
| return rawTsConfigJson; | ||
| }; | ||
| const TS_EXTENSIONS = [ | ||
| ".ts", | ||
| ".tsx", | ||
| ".vue", | ||
| ".svelte", | ||
| ".js", | ||
| ".jsx" | ||
| ]; | ||
| const resolveWithExtension = (exposedPath, context) => { | ||
| const explicitExtension = (0, path.extname)(exposedPath); | ||
| if (TS_EXTENSIONS.includes(explicitExtension)) return (0, path.resolve)(context, exposedPath); | ||
| for (const extension of TS_EXTENSIONS) { | ||
| const exposedPathWithExtension = (0, path.resolve)(context, `${exposedPath}${extension}`); | ||
| if ((0, fs.existsSync)(exposedPathWithExtension)) return exposedPathWithExtension; | ||
| } | ||
| }; | ||
| const resolveExposes = (remoteOptions) => { | ||
| return _module_federation_managers.utils.parseOptions(remoteOptions.moduleFederationConfig.exposes || {}, (item, key) => ({ | ||
| exposePath: Array.isArray(item) ? item[0] : item, | ||
| key | ||
| }), (item, key) => ({ | ||
| exposePath: Array.isArray(item.import) ? item.import[0] : item.import[0], | ||
| key | ||
| })).reduce((accumulator, item) => { | ||
| const { exposePath, key } = item[1]; | ||
| accumulator[key] = resolveWithExtension(exposePath, remoteOptions.context) || resolveWithExtension((0, path.join)(exposePath, "index"), remoteOptions.context) || exposePath; | ||
| return accumulator; | ||
| }, {}); | ||
| }; | ||
| const retrieveRemoteConfig = (options) => { | ||
| validateOptions(options); | ||
| const remoteOptions = { | ||
| ...defaultOptions, | ||
| ...options | ||
| }; | ||
| const mapComponentsToExpose = resolveExposes(remoteOptions); | ||
| const tsConfig = readTsConfig(remoteOptions, mapComponentsToExpose); | ||
| if (tsConfig.compilerOptions.incremental && tsConfig.compilerOptions.tsBuildInfoFile && options.deleteTypesFolder !== true) remoteOptions.deleteTypesFolder = false; | ||
| return { | ||
| tsConfig, | ||
| mapComponentsToExpose, | ||
| remoteOptions | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/generateTypes.ts | ||
| async function generateTypes(options) { | ||
| return new (getDTSManagerConstructor(options.remote?.implementation))(options).generateTypes(); | ||
| } | ||
| //#endregion | ||
| //#region src/core/rpc/types.ts | ||
| let RpcGMCallTypes = /* @__PURE__ */ function(RpcGMCallTypes) { | ||
| RpcGMCallTypes["CALL"] = "mf_call"; | ||
| RpcGMCallTypes["RESOLVE"] = "mf_resolve"; | ||
| RpcGMCallTypes["REJECT"] = "mf_reject"; | ||
| RpcGMCallTypes["EXIT"] = "mf_exit"; | ||
| return RpcGMCallTypes; | ||
| }({}); | ||
| //#endregion | ||
| //#region src/core/rpc/expose-rpc.ts | ||
| function exposeRpc(fn) { | ||
| const sendMessage = (message) => new Promise((resolve, reject) => { | ||
| if (!process$1.default.send) reject(/* @__PURE__ */ new Error(`Process ${process$1.default.pid} doesn't have IPC channels`)); | ||
| else if (!process$1.default.connected) reject(/* @__PURE__ */ new Error(`Process ${process$1.default.pid} doesn't have open IPC channels`)); | ||
| else process$1.default.send(message, void 0, void 0, (error) => { | ||
| if (error) reject(error); | ||
| else resolve(void 0); | ||
| }); | ||
| }); | ||
| const handleMessage = async (message) => { | ||
| if (message.type === RpcGMCallTypes.CALL) { | ||
| if (!process$1.default.send) return; | ||
| let value, error; | ||
| try { | ||
| value = await fn(...message.args); | ||
| } catch (fnError) { | ||
| error = fnError; | ||
| } | ||
| try { | ||
| if (error) await sendMessage({ | ||
| type: RpcGMCallTypes.REJECT, | ||
| id: message.id, | ||
| error | ||
| }); | ||
| else await sendMessage({ | ||
| type: RpcGMCallTypes.RESOLVE, | ||
| id: message.id, | ||
| value | ||
| }); | ||
| } catch (sendError) { | ||
| if (error) { | ||
| if (error instanceof Error) console.error(error); | ||
| } | ||
| console.error(sendError); | ||
| } | ||
| } | ||
| }; | ||
| process$1.default.on("message", handleMessage); | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'DTSManager', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return DTSManager; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'HOST_API_TYPES_FILE_NAME', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return HOST_API_TYPES_FILE_NAME; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'ModuleFederationDevServer', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return ModuleFederationDevServer; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'REMOTE_ALIAS_IDENTIFIER', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return REMOTE_ALIAS_IDENTIFIER; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'REMOTE_API_TYPES_FILE_NAME', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return REMOTE_API_TYPES_FILE_NAME; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'RpcGMCallTypes', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return RpcGMCallTypes; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'cloneDeepOptions', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return cloneDeepOptions; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'createHttpServer', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createHttpServer; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'exposeRpc', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return exposeRpc; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'generateTypes', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return generateTypes; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'getDTSManagerConstructor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return getDTSManagerConstructor; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'isDebugMode', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return isDebugMode; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'isTSProject', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return isTSProject; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'retrieveHostConfig', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return retrieveHostConfig; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'retrieveMfTypesPath', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return retrieveMfTypesPath; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'retrieveOriginalOutDir', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return retrieveOriginalOutDir; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'retrieveRemoteConfig', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return retrieveRemoteConfig; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'retrieveTypesAssetsInfo', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return retrieveTypesAssetsInfo; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'retrieveTypesZipPath', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return retrieveTypesZipPath; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'validateOptions', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return validateOptions; | ||
| } | ||
| }); |
+10
-0
| # @module-federation/dts-plugin | ||
| ## 2.6.0 | ||
| ### Patch Changes | ||
| - 2a7f724: Fix dts-plugin expose resolution for extensionless multi-dot paths like `foo.generated` so they correctly infer supported source files such as `.ts`, `.tsx`, `.vue`, `.js`, and `.jsx`, while preserving explicit extensions and directory `index` fallback behavior. | ||
| - @module-federation/sdk@2.6.0 | ||
| - @module-federation/managers@2.6.0 | ||
| - @module-federation/third-party-dts-extractor@2.6.0 | ||
| - @module-federation/error-codes@2.6.0 | ||
| ## 2.5.1 | ||
@@ -4,0 +14,0 @@ |
+2
-2
| Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | ||
| require('./Broker-CaenCqdn.js'); | ||
| const require_expose_rpc = require('./expose-rpc-XLxmAXIW.js'); | ||
| const require_consumeTypes = require('./consumeTypes-FbF0o6AJ.js'); | ||
| const require_expose_rpc = require('./expose-rpc-BNzQqY2X.js'); | ||
| const require_consumeTypes = require('./consumeTypes-DuDkcp8N.js'); | ||
@@ -6,0 +6,0 @@ exports.DTSManager = require_expose_rpc.DTSManager; |
@@ -1,5 +0,5 @@ | ||
| import { _ as retrieveMfTypesPath, c as isTSProject, d as DTSManager, f as HOST_API_TYPES_FILE_NAME, g as retrieveTypesZipPath, h as retrieveHostConfig, i as retrieveRemoteConfig, l as retrieveTypesAssetsInfo, m as REMOTE_API_TYPES_FILE_NAME, o as getDTSManagerConstructor, p as REMOTE_ALIAS_IDENTIFIER, r as generateTypes, u as validateOptions, v as retrieveOriginalOutDir } from "./expose-rpc-ZWQxpg0y.mjs"; | ||
| import { _ as retrieveMfTypesPath, c as isTSProject, d as DTSManager, f as HOST_API_TYPES_FILE_NAME, g as retrieveTypesZipPath, h as retrieveHostConfig, i as retrieveRemoteConfig, l as retrieveTypesAssetsInfo, m as REMOTE_API_TYPES_FILE_NAME, o as getDTSManagerConstructor, p as REMOTE_ALIAS_IDENTIFIER, r as generateTypes, u as validateOptions, v as retrieveOriginalOutDir } from "./expose-rpc-BiwGpqZ3.mjs"; | ||
| import "./Broker-Cmbh_XVO.mjs"; | ||
| import { i as rpc_exports, n as generateTypesInChildProcess, r as DtsWorker, t as consumeTypes } from "./consumeTypes-COIltElB.mjs"; | ||
| import { i as rpc_exports, n as generateTypesInChildProcess, r as DtsWorker, t as consumeTypes } from "./consumeTypes-CUv-A3UY.mjs"; | ||
| export { DTSManager, DtsWorker, HOST_API_TYPES_FILE_NAME, REMOTE_ALIAS_IDENTIFIER, REMOTE_API_TYPES_FILE_NAME, consumeTypes, generateTypes, generateTypesInChildProcess, getDTSManagerConstructor, isTSProject, retrieveHostConfig, retrieveMfTypesPath, retrieveOriginalOutDir, retrieveRemoteConfig, retrieveTypesAssetsInfo, retrieveTypesZipPath, rpc_exports as rpc, validateOptions }; |
@@ -1,5 +0,5 @@ | ||
| import { _ as retrieveMfTypesPath, b as ModuleFederationDevServer, g as retrieveTypesZipPath, h as retrieveHostConfig, i as retrieveRemoteConfig, n as RpcGMCallTypes, o as getDTSManagerConstructor, t as exposeRpc, y as createHttpServer } from "./expose-rpc-ZWQxpg0y.mjs"; | ||
| import { _ as retrieveMfTypesPath, b as ModuleFederationDevServer, g as retrieveTypesZipPath, h as retrieveHostConfig, i as retrieveRemoteConfig, n as RpcGMCallTypes, o as getDTSManagerConstructor, t as exposeRpc, y as createHttpServer } from "./expose-rpc-BiwGpqZ3.mjs"; | ||
| import { o as UpdateMode, r as DEFAULT_TAR_NAME } from "./Action-DNNg2YDh.mjs"; | ||
| import { n as UpdateKind, o as getIPV4, s as fileLog } from "./Broker-Cmbh_XVO.mjs"; | ||
| import "./consumeTypes-COIltElB.mjs"; | ||
| import "./consumeTypes-CUv-A3UY.mjs"; | ||
| import "./core.mjs"; | ||
@@ -6,0 +6,0 @@ import { t as getIpFromEntry } from "./utils-CkPvDGOy.mjs"; |
@@ -1,2 +0,2 @@ | ||
| import { n as RpcGMCallTypes, r as generateTypes, t as exposeRpc } from "./expose-rpc-ZWQxpg0y.mjs"; | ||
| import { n as RpcGMCallTypes, r as generateTypes, t as exposeRpc } from "./expose-rpc-BiwGpqZ3.mjs"; | ||
| import "./Broker-Cmbh_XVO.mjs"; | ||
@@ -3,0 +3,0 @@ |
@@ -1,5 +0,5 @@ | ||
| import { a as cloneDeepOptions, c as isTSProject, l as retrieveTypesAssetsInfo, n as RpcGMCallTypes, r as generateTypes, u as validateOptions } from "./expose-rpc-ZWQxpg0y.mjs"; | ||
| import { a as cloneDeepOptions, c as isTSProject, l as retrieveTypesAssetsInfo, n as RpcGMCallTypes, r as generateTypes, u as validateOptions } from "./expose-rpc-BiwGpqZ3.mjs"; | ||
| import { s as WEB_CLIENT_OPTIONS_IDENTIFIER } from "./Action-DNNg2YDh.mjs"; | ||
| import { c as logger$1, o as getIPV4 } from "./Broker-Cmbh_XVO.mjs"; | ||
| import { a as createRpcWorker, n as generateTypesInChildProcess, t as consumeTypes } from "./consumeTypes-COIltElB.mjs"; | ||
| import { a as createRpcWorker, n as generateTypesInChildProcess, t as consumeTypes } from "./consumeTypes-CUv-A3UY.mjs"; | ||
| import "./core.mjs"; | ||
@@ -6,0 +6,0 @@ import fs from "fs"; |
| Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | ||
| const require_Action = require('./Action-CzhPMw2i.js'); | ||
| const require_Broker = require('./Broker-CaenCqdn.js'); | ||
| const require_expose_rpc = require('./expose-rpc-XLxmAXIW.js'); | ||
| require('./consumeTypes-FbF0o6AJ.js'); | ||
| const require_expose_rpc = require('./expose-rpc-BNzQqY2X.js'); | ||
| require('./consumeTypes-DuDkcp8N.js'); | ||
| require('./core.js'); | ||
@@ -7,0 +7,0 @@ const require_utils = require('./utils-7KqCZHbb.js'); |
| Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | ||
| require('./Broker-CaenCqdn.js'); | ||
| const require_expose_rpc = require('./expose-rpc-XLxmAXIW.js'); | ||
| const require_expose_rpc = require('./expose-rpc-BNzQqY2X.js'); | ||
@@ -5,0 +5,0 @@ //#region src/core/lib/forkGenerateDts.ts |
+2
-2
| Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | ||
| const require_Action = require('./Action-CzhPMw2i.js'); | ||
| const require_Broker = require('./Broker-CaenCqdn.js'); | ||
| const require_expose_rpc = require('./expose-rpc-XLxmAXIW.js'); | ||
| const require_consumeTypes = require('./consumeTypes-FbF0o6AJ.js'); | ||
| const require_expose_rpc = require('./expose-rpc-BNzQqY2X.js'); | ||
| const require_consumeTypes = require('./consumeTypes-DuDkcp8N.js'); | ||
| require('./core.js'); | ||
@@ -7,0 +7,0 @@ let fs = require("fs"); |
+7
-7
| { | ||
| "name": "@module-federation/dts-plugin", | ||
| "version": "2.5.1", | ||
| "version": "2.6.0", | ||
| "author": "hanric <hanric.zhang@gmail.com>", | ||
@@ -71,9 +71,9 @@ "main": "./dist/index.js", | ||
| "isomorphic-ws": "5.0.0", | ||
| "undici": "7.24.7", | ||
| "undici": "7.28.0", | ||
| "node-schedule": "2.1.1", | ||
| "ws": "8.21.0", | ||
| "@module-federation/error-codes": "2.5.1", | ||
| "@module-federation/managers": "2.5.1", | ||
| "@module-federation/sdk": "2.5.1", | ||
| "@module-federation/third-party-dts-extractor": "2.5.1" | ||
| "@module-federation/error-codes": "2.6.0", | ||
| "@module-federation/sdk": "2.6.0", | ||
| "@module-federation/managers": "2.6.0", | ||
| "@module-federation/third-party-dts-extractor": "2.6.0" | ||
| }, | ||
@@ -90,3 +90,3 @@ "devDependencies": { | ||
| "webpack": "^5.104.1", | ||
| "@module-federation/runtime": "2.5.1" | ||
| "@module-federation/runtime": "2.6.0" | ||
| }, | ||
@@ -93,0 +93,0 @@ "peerDependencies": { |
| const require_Action = require('./Action-CzhPMw2i.js'); | ||
| const require_expose_rpc = require('./expose-rpc-XLxmAXIW.js'); | ||
| let url = require("url"); | ||
| let path = require("path"); | ||
| path = require_Action.__toESM(path); | ||
| let crypto = require("crypto"); | ||
| let child_process = require("child_process"); | ||
| child_process = require_Action.__toESM(child_process); | ||
| let process$1 = require("process"); | ||
| process$1 = require_Action.__toESM(process$1); | ||
| //#region src/core/rpc/rpc-error.ts | ||
| var RpcExitError = class extends Error { | ||
| constructor(message, code, signal) { | ||
| super(message); | ||
| this.code = code; | ||
| this.signal = signal; | ||
| this.name = "RpcExitError"; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/rpc/wrap-rpc.ts | ||
| function createControlledPromise() { | ||
| let resolve = () => void 0; | ||
| let reject = () => void 0; | ||
| return { | ||
| promise: new Promise((aResolve, aReject) => { | ||
| resolve = aResolve; | ||
| reject = aReject; | ||
| }), | ||
| resolve, | ||
| reject | ||
| }; | ||
| } | ||
| function wrapRpc(childProcess, options) { | ||
| return (async (...args) => { | ||
| if (!childProcess.send) throw new Error(`Process ${childProcess.pid} doesn't have IPC channels`); | ||
| else if (!childProcess.connected) throw new Error(`Process ${childProcess.pid} doesn't have open IPC channels`); | ||
| const { id, once } = options; | ||
| const { promise: resultPromise, resolve: resolveResult, reject: rejectResult } = createControlledPromise(); | ||
| const { promise: sendPromise, resolve: resolveSend, reject: rejectSend } = createControlledPromise(); | ||
| const handleMessage = (message) => { | ||
| if (message?.id === id) { | ||
| if (message.type === require_expose_rpc.RpcGMCallTypes.RESOLVE) resolveResult(message.value); | ||
| else if (message.type === require_expose_rpc.RpcGMCallTypes.REJECT) rejectResult(message.error); | ||
| } | ||
| if (once && childProcess?.kill) childProcess.kill("SIGTERM"); | ||
| }; | ||
| const handleClose = (code, signal) => { | ||
| rejectResult(new RpcExitError(code ? `Process ${childProcess.pid} exited with code ${code}${signal ? ` [${signal}]` : ""}` : `Process ${childProcess.pid} exited${signal ? ` [${signal}]` : ""}`, code, signal)); | ||
| removeHandlers(); | ||
| }; | ||
| const removeHandlers = () => { | ||
| childProcess.off("message", handleMessage); | ||
| childProcess.off("close", handleClose); | ||
| }; | ||
| if (once) childProcess.once("message", handleMessage); | ||
| else childProcess.on("message", handleMessage); | ||
| childProcess.on("close", handleClose); | ||
| childProcess.send({ | ||
| type: require_expose_rpc.RpcGMCallTypes.CALL, | ||
| id, | ||
| args | ||
| }, (error) => { | ||
| if (error) { | ||
| rejectSend(error); | ||
| removeHandlers(); | ||
| } else resolveSend(void 0); | ||
| }); | ||
| return sendPromise.then(() => resultPromise); | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/core/rpc/rpc-worker.ts | ||
| const FEDERATION_WORKER_DATA_ENV_KEY = "VMOK_WORKER_DATA_ENV"; | ||
| function createRpcWorker(modulePath, data, memoryLimit, once) { | ||
| const options = { | ||
| env: { | ||
| ...process$1.env, | ||
| [FEDERATION_WORKER_DATA_ENV_KEY]: JSON.stringify(data || {}) | ||
| }, | ||
| stdio: [ | ||
| "inherit", | ||
| "inherit", | ||
| "inherit", | ||
| "ipc" | ||
| ], | ||
| serialization: "advanced" | ||
| }; | ||
| if (memoryLimit) options.execArgv = [`--max-old-space-size=${memoryLimit}`]; | ||
| let childProcess, remoteMethod; | ||
| const id = (0, crypto.randomUUID)(); | ||
| return { | ||
| connect(...args) { | ||
| if (childProcess && !childProcess.connected) { | ||
| childProcess.send({ | ||
| type: require_expose_rpc.RpcGMCallTypes.EXIT, | ||
| id | ||
| }); | ||
| childProcess = void 0; | ||
| remoteMethod = void 0; | ||
| } | ||
| if (!childProcess?.connected) { | ||
| childProcess = child_process.fork(modulePath, options); | ||
| remoteMethod = wrapRpc(childProcess, { | ||
| id, | ||
| once | ||
| }); | ||
| } | ||
| if (!remoteMethod) return Promise.reject(/* @__PURE__ */ new Error("Worker is not connected - cannot perform RPC.")); | ||
| return remoteMethod(...args); | ||
| }, | ||
| terminate() { | ||
| try { | ||
| if (childProcess.connected) childProcess.send({ | ||
| type: require_expose_rpc.RpcGMCallTypes.EXIT, | ||
| id | ||
| }, (err) => { | ||
| if (err) console.error("Error sending message:", err); | ||
| }); | ||
| } catch (error) { | ||
| if (error.code === "EPIPE") console.error("Pipe closed before message could be sent:", error); | ||
| else console.error("Unexpected error:", error); | ||
| } | ||
| childProcess = void 0; | ||
| remoteMethod = void 0; | ||
| }, | ||
| get connected() { | ||
| return Boolean(childProcess?.connected); | ||
| }, | ||
| get process() { | ||
| return childProcess; | ||
| }, | ||
| get id() { | ||
| return id; | ||
| } | ||
| }; | ||
| } | ||
| function getRpcWorkerData() { | ||
| return JSON.parse(process$1.env[FEDERATION_WORKER_DATA_ENV_KEY] || "{}"); | ||
| } | ||
| //#endregion | ||
| //#region src/core/rpc/index.ts | ||
| var rpc_exports = /* @__PURE__ */ require_Action.__exportAll({ | ||
| RpcExitError: () => RpcExitError, | ||
| RpcGMCallTypes: () => require_expose_rpc.RpcGMCallTypes, | ||
| createRpcWorker: () => createRpcWorker, | ||
| exposeRpc: () => require_expose_rpc.exposeRpc, | ||
| getRpcWorkerData: () => getRpcWorkerData, | ||
| wrapRpc: () => wrapRpc | ||
| }); | ||
| //#endregion | ||
| //#region src/core/lib/DtsWorker.ts | ||
| const __filename$1 = (0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href); | ||
| const __dirname$1 = path.default.dirname(__filename$1); | ||
| const __extname = path.default.extname(__filename$1); | ||
| var DtsWorker = class { | ||
| constructor(options) { | ||
| this._options = require_expose_rpc.cloneDeepOptions(options); | ||
| this.removeUnSerializationOptions(); | ||
| this.rpcWorker = createRpcWorker(path.default.resolve(__dirname$1, `./fork-generate-dts${__extname}`), {}, void 0, true); | ||
| this._res = this.rpcWorker.connect(this._options); | ||
| } | ||
| removeUnSerializationOptions() { | ||
| if (this._options.remote?.moduleFederationConfig?.manifest) delete this._options.remote?.moduleFederationConfig?.manifest; | ||
| if (this._options.host?.moduleFederationConfig?.manifest) delete this._options.host?.moduleFederationConfig?.manifest; | ||
| } | ||
| get controlledPromise() { | ||
| const ensureChildProcessExit = () => { | ||
| try { | ||
| const pid = this.rpcWorker.process?.pid; | ||
| const rootPid = process.pid; | ||
| if (pid && rootPid !== pid) process.kill(pid, 0); | ||
| } catch (error) { | ||
| if (require_expose_rpc.isDebugMode()) console.error(error); | ||
| } | ||
| }; | ||
| return Promise.resolve(this._res).then(() => { | ||
| this.exit(); | ||
| ensureChildProcessExit(); | ||
| }).catch((err) => { | ||
| if (require_expose_rpc.isDebugMode()) console.error(err); | ||
| ensureChildProcessExit(); | ||
| }); | ||
| } | ||
| exit() { | ||
| try { | ||
| this.rpcWorker?.terminate(); | ||
| } catch (err) { | ||
| if (require_expose_rpc.isDebugMode()) console.error(err); | ||
| } | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/generateTypesInChildProcess.ts | ||
| async function generateTypesInChildProcess(options) { | ||
| return new DtsWorker(options).controlledPromise; | ||
| } | ||
| //#endregion | ||
| //#region src/core/lib/consumeTypes.ts | ||
| async function consumeTypes(options) { | ||
| await new (require_expose_rpc.getDTSManagerConstructor(options.host?.implementation))(options).consumeTypes(); | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'DtsWorker', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return DtsWorker; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'consumeTypes', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return consumeTypes; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'createRpcWorker', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createRpcWorker; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'generateTypesInChildProcess', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return generateTypesInChildProcess; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'rpc_exports', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return rpc_exports; | ||
| } | ||
| }); |
| import { a as cloneDeepOptions, n as RpcGMCallTypes, o as getDTSManagerConstructor, s as isDebugMode, t as exposeRpc, x as __exportAll } from "./expose-rpc-ZWQxpg0y.mjs"; | ||
| import { fileURLToPath } from "url"; | ||
| import path from "path"; | ||
| import { randomUUID } from "crypto"; | ||
| import * as child_process from "child_process"; | ||
| import * as process$2 from "process"; | ||
| //#region src/core/rpc/rpc-error.ts | ||
| var RpcExitError = class extends Error { | ||
| constructor(message, code, signal) { | ||
| super(message); | ||
| this.code = code; | ||
| this.signal = signal; | ||
| this.name = "RpcExitError"; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/rpc/wrap-rpc.ts | ||
| function createControlledPromise() { | ||
| let resolve = () => void 0; | ||
| let reject = () => void 0; | ||
| return { | ||
| promise: new Promise((aResolve, aReject) => { | ||
| resolve = aResolve; | ||
| reject = aReject; | ||
| }), | ||
| resolve, | ||
| reject | ||
| }; | ||
| } | ||
| function wrapRpc(childProcess, options) { | ||
| return (async (...args) => { | ||
| if (!childProcess.send) throw new Error(`Process ${childProcess.pid} doesn't have IPC channels`); | ||
| else if (!childProcess.connected) throw new Error(`Process ${childProcess.pid} doesn't have open IPC channels`); | ||
| const { id, once } = options; | ||
| const { promise: resultPromise, resolve: resolveResult, reject: rejectResult } = createControlledPromise(); | ||
| const { promise: sendPromise, resolve: resolveSend, reject: rejectSend } = createControlledPromise(); | ||
| const handleMessage = (message) => { | ||
| if (message?.id === id) { | ||
| if (message.type === RpcGMCallTypes.RESOLVE) resolveResult(message.value); | ||
| else if (message.type === RpcGMCallTypes.REJECT) rejectResult(message.error); | ||
| } | ||
| if (once && childProcess?.kill) childProcess.kill("SIGTERM"); | ||
| }; | ||
| const handleClose = (code, signal) => { | ||
| rejectResult(new RpcExitError(code ? `Process ${childProcess.pid} exited with code ${code}${signal ? ` [${signal}]` : ""}` : `Process ${childProcess.pid} exited${signal ? ` [${signal}]` : ""}`, code, signal)); | ||
| removeHandlers(); | ||
| }; | ||
| const removeHandlers = () => { | ||
| childProcess.off("message", handleMessage); | ||
| childProcess.off("close", handleClose); | ||
| }; | ||
| if (once) childProcess.once("message", handleMessage); | ||
| else childProcess.on("message", handleMessage); | ||
| childProcess.on("close", handleClose); | ||
| childProcess.send({ | ||
| type: RpcGMCallTypes.CALL, | ||
| id, | ||
| args | ||
| }, (error) => { | ||
| if (error) { | ||
| rejectSend(error); | ||
| removeHandlers(); | ||
| } else resolveSend(void 0); | ||
| }); | ||
| return sendPromise.then(() => resultPromise); | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/core/rpc/rpc-worker.ts | ||
| const FEDERATION_WORKER_DATA_ENV_KEY = "VMOK_WORKER_DATA_ENV"; | ||
| function createRpcWorker(modulePath, data, memoryLimit, once) { | ||
| const options = { | ||
| env: { | ||
| ...process$2.env, | ||
| [FEDERATION_WORKER_DATA_ENV_KEY]: JSON.stringify(data || {}) | ||
| }, | ||
| stdio: [ | ||
| "inherit", | ||
| "inherit", | ||
| "inherit", | ||
| "ipc" | ||
| ], | ||
| serialization: "advanced" | ||
| }; | ||
| if (memoryLimit) options.execArgv = [`--max-old-space-size=${memoryLimit}`]; | ||
| let childProcess, remoteMethod; | ||
| const id = randomUUID(); | ||
| return { | ||
| connect(...args) { | ||
| if (childProcess && !childProcess.connected) { | ||
| childProcess.send({ | ||
| type: RpcGMCallTypes.EXIT, | ||
| id | ||
| }); | ||
| childProcess = void 0; | ||
| remoteMethod = void 0; | ||
| } | ||
| if (!childProcess?.connected) { | ||
| childProcess = child_process.fork(modulePath, options); | ||
| remoteMethod = wrapRpc(childProcess, { | ||
| id, | ||
| once | ||
| }); | ||
| } | ||
| if (!remoteMethod) return Promise.reject(/* @__PURE__ */ new Error("Worker is not connected - cannot perform RPC.")); | ||
| return remoteMethod(...args); | ||
| }, | ||
| terminate() { | ||
| try { | ||
| if (childProcess.connected) childProcess.send({ | ||
| type: RpcGMCallTypes.EXIT, | ||
| id | ||
| }, (err) => { | ||
| if (err) console.error("Error sending message:", err); | ||
| }); | ||
| } catch (error) { | ||
| if (error.code === "EPIPE") console.error("Pipe closed before message could be sent:", error); | ||
| else console.error("Unexpected error:", error); | ||
| } | ||
| childProcess = void 0; | ||
| remoteMethod = void 0; | ||
| }, | ||
| get connected() { | ||
| return Boolean(childProcess?.connected); | ||
| }, | ||
| get process() { | ||
| return childProcess; | ||
| }, | ||
| get id() { | ||
| return id; | ||
| } | ||
| }; | ||
| } | ||
| function getRpcWorkerData() { | ||
| return JSON.parse(process$2.env[FEDERATION_WORKER_DATA_ENV_KEY] || "{}"); | ||
| } | ||
| //#endregion | ||
| //#region src/core/rpc/index.ts | ||
| var rpc_exports = /* @__PURE__ */ __exportAll({ | ||
| RpcExitError: () => RpcExitError, | ||
| RpcGMCallTypes: () => RpcGMCallTypes, | ||
| createRpcWorker: () => createRpcWorker, | ||
| exposeRpc: () => exposeRpc, | ||
| getRpcWorkerData: () => getRpcWorkerData, | ||
| wrapRpc: () => wrapRpc | ||
| }); | ||
| //#endregion | ||
| //#region src/core/lib/DtsWorker.ts | ||
| const __filename = fileURLToPath(import.meta.url); | ||
| const __dirname = path.dirname(__filename); | ||
| const __extname = path.extname(__filename); | ||
| var DtsWorker = class { | ||
| constructor(options) { | ||
| this._options = cloneDeepOptions(options); | ||
| this.removeUnSerializationOptions(); | ||
| this.rpcWorker = createRpcWorker(path.resolve(__dirname, `./fork-generate-dts${__extname}`), {}, void 0, true); | ||
| this._res = this.rpcWorker.connect(this._options); | ||
| } | ||
| removeUnSerializationOptions() { | ||
| if (this._options.remote?.moduleFederationConfig?.manifest) delete this._options.remote?.moduleFederationConfig?.manifest; | ||
| if (this._options.host?.moduleFederationConfig?.manifest) delete this._options.host?.moduleFederationConfig?.manifest; | ||
| } | ||
| get controlledPromise() { | ||
| const ensureChildProcessExit = () => { | ||
| try { | ||
| const pid = this.rpcWorker.process?.pid; | ||
| const rootPid = process.pid; | ||
| if (pid && rootPid !== pid) process.kill(pid, 0); | ||
| } catch (error) { | ||
| if (isDebugMode()) console.error(error); | ||
| } | ||
| }; | ||
| return Promise.resolve(this._res).then(() => { | ||
| this.exit(); | ||
| ensureChildProcessExit(); | ||
| }).catch((err) => { | ||
| if (isDebugMode()) console.error(err); | ||
| ensureChildProcessExit(); | ||
| }); | ||
| } | ||
| exit() { | ||
| try { | ||
| this.rpcWorker?.terminate(); | ||
| } catch (err) { | ||
| if (isDebugMode()) console.error(err); | ||
| } | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/generateTypesInChildProcess.ts | ||
| async function generateTypesInChildProcess(options) { | ||
| return new DtsWorker(options).controlledPromise; | ||
| } | ||
| //#endregion | ||
| //#region src/core/lib/consumeTypes.ts | ||
| async function consumeTypes(options) { | ||
| await new (getDTSManagerConstructor(options.host?.implementation))(options).consumeTypes(); | ||
| } | ||
| //#endregion | ||
| export { createRpcWorker as a, rpc_exports as i, generateTypesInChildProcess as n, DtsWorker as r, consumeTypes as t }; |
| import { a as MF_SERVER_IDENTIFIER, n as ActionKind, o as UpdateMode, r as DEFAULT_TAR_NAME, t as Action } from "./Action-DNNg2YDh.mjs"; | ||
| import { a as getIdentifier, c as logger$1, i as getFreePort, l as LogKind, n as UpdateKind, o as getIPV4, r as fib, s as fileLog, t as Broker, u as APIKind } from "./Broker-Cmbh_XVO.mjs"; | ||
| import { createRequire } from "node:module"; | ||
| import fs, { existsSync, mkdirSync, writeFileSync } from "fs"; | ||
| import { fileURLToPath } from "url"; | ||
| import path, { dirname, extname, isAbsolute, join, normalize, relative, resolve, sep } from "path"; | ||
| import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from "fs/promises"; | ||
| import { utils } from "@module-federation/managers"; | ||
| import typescript from "typescript"; | ||
| import { ENCODE_NAME_PREFIX, MANIFEST_EXT, TEMP_DIR, decodeName, getProcessEnv, inferAutoPublicPath, parseEntry } from "@module-federation/sdk"; | ||
| import ansiColors from "ansi-colors"; | ||
| import { Agent } from "undici"; | ||
| import { ThirdPartyExtractor } from "@module-federation/third-party-dts-extractor"; | ||
| import AdmZip from "adm-zip"; | ||
| import crypto from "crypto"; | ||
| import { TYPE_001, typeDescMap } from "@module-federation/error-codes"; | ||
| import { logAndReport } from "@module-federation/error-codes/node"; | ||
| import { execFile, fork } from "child_process"; | ||
| import util from "util"; | ||
| import WebSocket from "isomorphic-ws"; | ||
| import http from "http"; | ||
| import process$1 from "process"; | ||
| //#region \0rolldown/runtime.js | ||
| var __defProp = Object.defineProperty; | ||
| var __exportAll = (all, no_symbols) => { | ||
| let target = {}; | ||
| for (var name in all) { | ||
| __defProp(target, name, { | ||
| get: all[name], | ||
| enumerable: true | ||
| }); | ||
| } | ||
| if (!no_symbols) { | ||
| __defProp(target, Symbol.toStringTag, { value: "Module" }); | ||
| } | ||
| return target; | ||
| }; | ||
| var __require = /* @__PURE__ */ createRequire(import.meta.url); | ||
| //#endregion | ||
| //#region src/server/message/Action/AddPublisher.ts | ||
| var AddPublisherAction = class extends Action { | ||
| constructor(payload) { | ||
| super({ payload }, ActionKind.ADD_PUBLISHER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/AddSubscriber.ts | ||
| var AddSubscriberAction = class extends Action { | ||
| constructor(payload) { | ||
| super({ payload }, ActionKind.ADD_SUBSCRIBER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/ExitSubscriber.ts | ||
| var ExitSubscriberAction = class extends Action { | ||
| constructor(payload) { | ||
| super({ payload }, ActionKind.EXIT_SUBSCRIBER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/ExitPublisher.ts | ||
| var ExitPublisherAction = class extends Action { | ||
| constructor(payload) { | ||
| super({ payload }, ActionKind.EXIT_PUBLISHER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/NotifyWebClient.ts | ||
| var NotifyWebClientAction = class extends Action { | ||
| constructor(payload) { | ||
| super({ payload }, ActionKind.NOTIFY_WEB_CLIENT); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/UpdatePublisher.ts | ||
| var UpdatePublisherAction = class extends Action { | ||
| constructor(payload) { | ||
| super({ payload }, ActionKind.UPDATE_PUBLISHER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/broker/createBroker.ts | ||
| const __filename = fileURLToPath(import.meta.url); | ||
| const __dirname = path.dirname(__filename); | ||
| function createBroker() { | ||
| const sub = fork(path.resolve(__dirname, "./start-broker.js"), [], { | ||
| detached: true, | ||
| stdio: "ignore", | ||
| env: process.env | ||
| }); | ||
| sub.send("start"); | ||
| sub.unref(); | ||
| return sub; | ||
| } | ||
| //#endregion | ||
| //#region src/server/DevServer.ts | ||
| var ModuleFederationDevServer = class { | ||
| constructor(ctx) { | ||
| this._publishWebSocket = null; | ||
| this._subscriberWebsocketMap = {}; | ||
| this._reconnect = true; | ||
| this._reconnectTimes = 0; | ||
| this._isConnected = false; | ||
| this._isReconnecting = false; | ||
| this._updateCallback = () => Promise.resolve(void 0); | ||
| const { name, remotes, remoteTypeTarPath, updateCallback } = ctx; | ||
| this._ip = getIPV4(); | ||
| this._name = name; | ||
| this._remotes = remotes; | ||
| this._remoteTypeTarPath = remoteTypeTarPath; | ||
| this._updateCallback = updateCallback; | ||
| this._stopWhenSIGTERMOrSIGINT(); | ||
| this._handleUnexpectedExit(); | ||
| this._connectPublishToServer(); | ||
| } | ||
| _connectPublishToServer() { | ||
| if (!this._reconnect) return; | ||
| fileLog(`Publisher:${this._name} Trying to connect to ws://${this._ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}...`, MF_SERVER_IDENTIFIER, "info"); | ||
| this._publishWebSocket = new WebSocket(`ws://${this._ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}?WEB_SOCKET_CONNECT_MAGIC_ID=${Broker.WEB_SOCKET_CONNECT_MAGIC_ID}`); | ||
| this._publishWebSocket.on("open", () => { | ||
| fileLog(`Current pid: ${process.pid}, publisher:${this._name} connected to ws://${this._ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}, starting service...`, MF_SERVER_IDENTIFIER, "info"); | ||
| this._isConnected = true; | ||
| const addPublisherAction = new AddPublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| this._publishWebSocket?.send(JSON.stringify(addPublisherAction)); | ||
| this._connectSubscribers(); | ||
| }); | ||
| this._publishWebSocket.on("message", async (message) => { | ||
| try { | ||
| const parsedMessage = JSON.parse(message.toString()); | ||
| if (parsedMessage.type === "Log") { | ||
| if (parsedMessage.kind === LogKind.BrokerExitLog) { | ||
| fileLog(`Receive broker exit signal, ${this._name} service will exit...`, MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| } | ||
| } | ||
| if (parsedMessage.type === "API") { | ||
| if (parsedMessage.kind === APIKind.FETCH_TYPES) { | ||
| const { payload: { remoteInfo } } = parsedMessage; | ||
| fileLog(`${this._name} Receive broker FETCH_TYPES, payload as follows: ${JSON.stringify(remoteInfo, null, 2)}.`, MF_SERVER_IDENTIFIER, "info"); | ||
| await this.fetchDynamicRemoteTypes({ remoteInfo }); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| console.error(err); | ||
| const exitPublisher = new ExitPublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip | ||
| }); | ||
| const exitSubscriber = new ExitSubscriberAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| publishers: this._remotes.map((remote) => ({ | ||
| name: remote.name, | ||
| ip: remote.ip | ||
| })) | ||
| }); | ||
| this._publishWebSocket?.send(JSON.stringify(exitPublisher)); | ||
| this._publishWebSocket?.send(JSON.stringify(exitSubscriber)); | ||
| fileLog("Parse messages error, ModuleFederationDevServer will exit...", MF_SERVER_IDENTIFIER, "fatal"); | ||
| this._exit(); | ||
| } | ||
| }); | ||
| this._publishWebSocket.on("close", (code) => { | ||
| fileLog(`Connection closed with code ${code}.`, MF_SERVER_IDENTIFIER, "warn"); | ||
| this._publishWebSocket && this._publishWebSocket.close(); | ||
| this._publishWebSocket = null; | ||
| if (!this._reconnect) return; | ||
| const reconnectTime = fib(++this._reconnectTimes); | ||
| fileLog(`start reconnecting to server after ${reconnectTime}s.`, MF_SERVER_IDENTIFIER, "info"); | ||
| setTimeout(() => this._connectPublishToServer(), reconnectTime * 1e3); | ||
| }); | ||
| this._publishWebSocket.on("error", this._tryCreateBackgroundBroker.bind(this)); | ||
| } | ||
| _connectSubscriberToServer(remote) { | ||
| const { name, ip } = remote; | ||
| fileLog(`remote module:${name} trying to connect to ws://${ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}...`, MF_SERVER_IDENTIFIER, "info"); | ||
| const identifier = getIdentifier({ | ||
| name, | ||
| ip | ||
| }); | ||
| this._subscriberWebsocketMap[identifier] = new WebSocket(`ws://${ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}?WEB_SOCKET_CONNECT_MAGIC_ID=${Broker.WEB_SOCKET_CONNECT_MAGIC_ID}`); | ||
| this._subscriberWebsocketMap[identifier].on("open", () => { | ||
| fileLog(`Current pid: ${process.pid} remote module: ${name} connected to ws://${ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}, starting service...`, MF_SERVER_IDENTIFIER, "info"); | ||
| const addSubscriber = new AddSubscriberAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| publishers: [{ | ||
| name, | ||
| ip | ||
| }] | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].send(JSON.stringify(addSubscriber)); | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].on("message", async (message) => { | ||
| try { | ||
| const parsedMessage = JSON.parse(message.toString()); | ||
| if (parsedMessage.type === "Log") { | ||
| if (parsedMessage.kind === LogKind.BrokerExitLog) { | ||
| fileLog(`${identifier}'s Server exit, thus ${identifier} will no longer has reload ability.`, MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| } | ||
| } | ||
| if (parsedMessage.type === "API") { | ||
| if (parsedMessage.kind === APIKind.UPDATE_SUBSCRIBER) { | ||
| const { payload: { updateKind, updateSourcePaths, name: subscribeName, remoteTypeTarPath, updateMode } } = parsedMessage; | ||
| await this._updateSubscriber({ | ||
| remoteTypeTarPath, | ||
| name: subscribeName, | ||
| updateKind, | ||
| updateMode, | ||
| updateSourcePaths | ||
| }); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| console.error(err); | ||
| const exitSubscriber = new ExitSubscriberAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| publishers: [{ | ||
| name, | ||
| ip | ||
| }] | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].send(JSON.stringify(exitSubscriber)); | ||
| fileLog(`${identifier} exit, | ||
| error: ${err instanceof Error ? err.toString() : JSON.stringify(err)} | ||
| `, MF_SERVER_IDENTIFIER, "warn"); | ||
| } | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].on("close", (code) => { | ||
| fileLog(`Connection closed with code ${code}.`, MF_SERVER_IDENTIFIER, "warn"); | ||
| this._subscriberWebsocketMap[identifier]?.close(); | ||
| delete this._subscriberWebsocketMap[identifier]; | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].on("error", (err) => { | ||
| if ("code" in err && err.code === "ETIMEDOUT") fileLog(`Can not connect ${JSON.stringify(remote)}, please make sure this remote is started locally.`, MF_SERVER_IDENTIFIER, "warn"); | ||
| else console.error(err); | ||
| this._subscriberWebsocketMap[identifier]?.close(); | ||
| delete this._subscriberWebsocketMap[identifier]; | ||
| }); | ||
| } | ||
| _connectSubscribers() { | ||
| this._remotes.forEach((remote) => { | ||
| this._connectSubscriberToServer(remote); | ||
| }); | ||
| } | ||
| async _updateSubscriber(options) { | ||
| const { updateMode, updateKind, updateSourcePaths, name, remoteTypeTarPath, remoteInfo } = options; | ||
| fileLog(`[_updateSubscriber] run, options: ${JSON.stringify(options, null, 2)}`, MF_SERVER_IDENTIFIER, "warn"); | ||
| if (updateMode === UpdateMode.PASSIVE && updateSourcePaths.includes(this._name)) { | ||
| fileLog(`[_updateSubscriber] run, updateSourcePaths:${updateSourcePaths} includes ${this._name}, update ignore!`, MF_SERVER_IDENTIFIER, "warn"); | ||
| return; | ||
| } | ||
| if (updateSourcePaths.slice(-1)[0] === this._name) { | ||
| fileLog(`[_updateSubscriber] run, updateSourcePaths:${updateSourcePaths} ends is ${this._name}, update ignore!`, MF_SERVER_IDENTIFIER, "warn"); | ||
| return; | ||
| } | ||
| fileLog(`[_updateSubscriber] run, updateSourcePaths:${updateSourcePaths}, current module:${this._name}, update start...`, MF_SERVER_IDENTIFIER, "info"); | ||
| await this._updateCallback({ | ||
| name, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths, | ||
| remoteTypeTarPath, | ||
| remoteInfo | ||
| }); | ||
| const newUpdateSourcePaths = updateSourcePaths.concat(this._name); | ||
| const updatePublisher = new UpdatePublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| updateMode: UpdateMode.PASSIVE, | ||
| updateKind, | ||
| updateSourcePaths: newUpdateSourcePaths, | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| fileLog(`[_updateSubscriber] run, updateSourcePaths:${newUpdateSourcePaths}, update publisher ${this._name} start...`, MF_SERVER_IDENTIFIER, "info"); | ||
| this._publishWebSocket?.send(JSON.stringify(updatePublisher)); | ||
| } | ||
| _tryCreateBackgroundBroker(err) { | ||
| if (!((err?.code === "ECONNREFUSED" || err?.code === "ETIMEDOUT") && err.port === Broker.DEFAULT_WEB_SOCKET_PORT)) { | ||
| fileLog(`websocket error: ${err.stack}`, MF_SERVER_IDENTIFIER, "fatal"); | ||
| return; | ||
| } | ||
| fileLog(`Failed to connect to ws://${this._ip}:${Broker.DEFAULT_WEB_SOCKET_PORT}...`, MF_SERVER_IDENTIFIER, "fatal"); | ||
| this._isReconnecting = true; | ||
| setTimeout(() => { | ||
| this._isReconnecting = false; | ||
| if (this._reconnect === false) return; | ||
| fileLog("Creating new background broker...", MF_SERVER_IDENTIFIER, "warn"); | ||
| createBroker().on("message", (message) => { | ||
| if (message === "ready") { | ||
| fileLog("background broker started.", MF_SERVER_IDENTIFIER, "info"); | ||
| this._reconnectTimes = 1; | ||
| if (process.send) process.send("ready"); | ||
| } | ||
| }); | ||
| }, Math.ceil(100 * Math.random())); | ||
| } | ||
| _stopWhenSIGTERMOrSIGINT() { | ||
| process.on("SIGTERM", () => { | ||
| fileLog(`Process(${process.pid}) SIGTERM, ModuleFederationDevServer will exit...`, MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| }); | ||
| process.on("SIGINT", () => { | ||
| fileLog(`Process(${process.pid}) SIGINT, ModuleFederationDevServer will exit...`, MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| }); | ||
| } | ||
| _handleUnexpectedExit() { | ||
| process.on("unhandledRejection", (error) => { | ||
| if (this._isReconnecting) return; | ||
| console.error("Unhandled Rejection Error: ", error); | ||
| fileLog(`Process(${process.pid}) unhandledRejection, garfishModuleServer will exit...`, MF_SERVER_IDENTIFIER, "error"); | ||
| this._exit(); | ||
| }); | ||
| process.on("uncaughtException", (error) => { | ||
| if (this._isReconnecting) return; | ||
| console.error("Unhandled Exception Error: ", error); | ||
| fileLog(`Process(${process.pid}) uncaughtException, garfishModuleServer will exit...`, MF_SERVER_IDENTIFIER, "error"); | ||
| this._exit(); | ||
| }); | ||
| } | ||
| _exit() { | ||
| this._reconnect = false; | ||
| if (this._publishWebSocket) { | ||
| const exitPublisher = new ExitPublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(exitPublisher)); | ||
| this._publishWebSocket.on("message", (message) => { | ||
| const parsedMessage = JSON.parse(message.toString()); | ||
| fileLog(`[${parsedMessage.kind}]: ${JSON.stringify(parsedMessage)}`, MF_SERVER_IDENTIFIER, "info"); | ||
| }); | ||
| } | ||
| if (this._publishWebSocket) { | ||
| this._publishWebSocket.close(); | ||
| this._publishWebSocket = null; | ||
| } | ||
| process.exit(0); | ||
| } | ||
| exit() { | ||
| this._exit(); | ||
| } | ||
| update(options) { | ||
| if (!this._publishWebSocket || !this._isConnected) return; | ||
| const { updateKind, updateMode, updateSourcePaths, clientName } = options; | ||
| fileLog(`update run, ${this._name} module update, updateKind: ${updateKind}, updateMode: ${updateMode}, updateSourcePaths: ${updateSourcePaths}`, MF_SERVER_IDENTIFIER, "info"); | ||
| if (updateKind === UpdateKind.RELOAD_PAGE) { | ||
| const notifyWebClient = new NotifyWebClientAction({ | ||
| name: clientName || this._name, | ||
| updateMode | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(notifyWebClient)); | ||
| return; | ||
| } | ||
| const updatePublisher = new UpdatePublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths: [this._name], | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(updatePublisher)); | ||
| } | ||
| async fetchDynamicRemoteTypes(options) { | ||
| const { remoteInfo, once } = options; | ||
| const updateMode = UpdateMode.PASSIVE; | ||
| const updateKind = UpdateKind.UPDATE_TYPE; | ||
| fileLog(`fetchDynamicRemoteTypes: remoteInfo: ${JSON.stringify(remoteInfo)}`, MF_SERVER_IDENTIFIER, "info"); | ||
| await this._updateCallback({ | ||
| name: this._name, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths: [], | ||
| remoteTypeTarPath: "", | ||
| remoteInfo, | ||
| once | ||
| }); | ||
| const updatePublisher = new UpdatePublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths: [this._name], | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(updatePublisher)); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/createHttpServer.ts | ||
| async function createHttpServer(options) { | ||
| const { typeTarPath } = options; | ||
| const freeport = await getFreePort(); | ||
| const server = http.createServer((req, res) => { | ||
| if ((req.url?.split("?")[0] ?? "/") === `/${DEFAULT_TAR_NAME}`) { | ||
| res.statusCode = 200; | ||
| res.setHeader("Content-Type", "application/x-gzip"); | ||
| if (req.method === "HEAD") { | ||
| res.end(); | ||
| return; | ||
| } | ||
| const stream = fs.createReadStream(typeTarPath); | ||
| stream.on("error", () => { | ||
| if (!res.headersSent) res.statusCode = 500; | ||
| res.end(); | ||
| }); | ||
| res.on("close", () => { | ||
| stream.destroy(); | ||
| }); | ||
| stream.pipe(res); | ||
| return; | ||
| } | ||
| res.statusCode = 404; | ||
| res.end(); | ||
| }); | ||
| server.listen(freeport); | ||
| return { | ||
| server, | ||
| serverAddress: `http://${getIPV4()}:${freeport}` | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/core/lib/typeScriptCompiler.ts | ||
| const STARTS_WITH_SLASH = /^\//; | ||
| const DEFINITION_FILE_EXTENSION = ".d.ts"; | ||
| const retrieveMfTypesPath = (tsConfig, remoteOptions) => normalize(tsConfig.compilerOptions.outDir.replace(remoteOptions.compiledTypesFolder, "")); | ||
| const retrieveOriginalOutDir = (tsConfig, remoteOptions) => normalize(tsConfig.compilerOptions.outDir.replace(remoteOptions.compiledTypesFolder, "").replace(remoteOptions.typesFolder, "")); | ||
| const retrieveMfAPITypesPath = (tsConfig, remoteOptions) => join(retrieveOriginalOutDir(tsConfig, remoteOptions), `${remoteOptions.typesFolder}.d.ts`); | ||
| function writeTempTsConfig(tsConfig, context, name, cwd) { | ||
| const createHash = (contents) => { | ||
| return crypto.createHash("md5").update(contents).digest("hex"); | ||
| }; | ||
| const hash = createHash(`${JSON.stringify(tsConfig)}${name}${Date.now()}`); | ||
| const tempTsConfigJsonPath = resolve(cwd ?? context, "node_modules", TEMP_DIR, `tsconfig.${hash}.json`); | ||
| mkdirSync(dirname(tempTsConfigJsonPath), { recursive: true }); | ||
| writeFileSync(tempTsConfigJsonPath, JSON.stringify(tsConfig, null, 2)); | ||
| return tempTsConfigJsonPath; | ||
| } | ||
| const removeExt = (f) => { | ||
| const vueExt = ".vue"; | ||
| const ext = extname(f); | ||
| if (ext === vueExt) return f; | ||
| const regexPattern = new RegExp(`\\${ext}$`); | ||
| return f.replace(regexPattern, ""); | ||
| }; | ||
| function getExposeKey(options) { | ||
| const { filePath, rootDir, outDir, mapExposeToEntry } = options; | ||
| return mapExposeToEntry[relative(outDir, filePath.replace(new RegExp(`\\.d.ts$`), ""))]; | ||
| } | ||
| const processTypesFile = async (options) => { | ||
| const { outDir, filePath, rootDir, cb, mapExposeToEntry, mfTypePath } = options; | ||
| if (!existsSync(filePath)) return; | ||
| if ((await stat(filePath)).isDirectory()) { | ||
| const files = await readdir(filePath); | ||
| await Promise.all(files.map((file) => processTypesFile({ | ||
| ...options, | ||
| filePath: join(filePath, file) | ||
| }))); | ||
| } else if (filePath.endsWith(".d.ts")) { | ||
| const exposeKey = getExposeKey({ | ||
| filePath, | ||
| rootDir, | ||
| outDir, | ||
| mapExposeToEntry | ||
| }); | ||
| if (exposeKey) { | ||
| const mfeTypeEntry = join(mfTypePath, `${exposeKey === "." ? "index" : exposeKey}${DEFINITION_FILE_EXTENSION}`); | ||
| const mfeTypeEntryDirectory = dirname(mfeTypeEntry); | ||
| const relativePathToOutput = relative(mfeTypeEntryDirectory, filePath).replace(DEFINITION_FILE_EXTENSION, "").replace(STARTS_WITH_SLASH, "").split(sep).join("/"); | ||
| mkdirSync(mfeTypeEntryDirectory, { recursive: true }); | ||
| await writeFile(mfeTypeEntry, `export * from './${relativePathToOutput}';\nexport { default } from './${relativePathToOutput}';`); | ||
| } | ||
| cb(await readFile(filePath, "utf8")); | ||
| } | ||
| }; | ||
| const getPMFromUserAgent = () => { | ||
| const userAgent = process.env["npm_config_user_agent"]; | ||
| if (userAgent == null) return "null"; | ||
| return userAgent.split("/")[0]; | ||
| }; | ||
| const resolvePackageManagerExecutable = () => { | ||
| switch (getPMFromUserAgent()) { | ||
| case "yarn": return "yarn"; | ||
| default: return "npx"; | ||
| } | ||
| }; | ||
| const splitCommandArgs = (value) => { | ||
| const args = []; | ||
| let current = ""; | ||
| let quote = null; | ||
| let escaped = false; | ||
| for (const char of value) { | ||
| if (escaped) { | ||
| current += char; | ||
| escaped = false; | ||
| continue; | ||
| } | ||
| if (char === "\\") { | ||
| escaped = true; | ||
| continue; | ||
| } | ||
| if (quote) { | ||
| if (char === quote) quote = null; | ||
| else current += char; | ||
| continue; | ||
| } | ||
| if (char === "\"" || char === "'") { | ||
| quote = char; | ||
| continue; | ||
| } | ||
| if (char.trim() === "") { | ||
| if (current) { | ||
| args.push(current); | ||
| current = ""; | ||
| } | ||
| continue; | ||
| } | ||
| current += char; | ||
| } | ||
| if (current) args.push(current); | ||
| return args; | ||
| }; | ||
| const formatCommandForDisplay = (executable, args) => { | ||
| const formatArg = (arg) => { | ||
| if (/[\s'"]/.test(arg)) return JSON.stringify(arg); | ||
| return arg; | ||
| }; | ||
| return [executable, ...args].map(formatArg).join(" "); | ||
| }; | ||
| const compileTs = async (mapComponentsToExpose, tsConfig, remoteOptions) => { | ||
| if (!Object.keys(mapComponentsToExpose).length) return; | ||
| const { compilerOptions } = tsConfig; | ||
| const tempTsConfigJsonPath = writeTempTsConfig(tsConfig, remoteOptions.context, remoteOptions.moduleFederationConfig.name || "mf", typeof remoteOptions.moduleFederationConfig.dts !== "boolean" ? remoteOptions.moduleFederationConfig.dts?.cwd ?? void 0 : void 0); | ||
| logger$1.debug(`tempTsConfigJsonPath: ${tempTsConfigJsonPath}`); | ||
| try { | ||
| const mfTypePath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| const thirdPartyExtractor = new ThirdPartyExtractor({ | ||
| destDir: resolve(mfTypePath, "node_modules"), | ||
| context: remoteOptions.context, | ||
| exclude: typeof remoteOptions.extractThirdParty === "object" ? remoteOptions.extractThirdParty.exclude : void 0 | ||
| }); | ||
| const execPromise = util.promisify(execFile); | ||
| const pmExecutable = resolvePackageManagerExecutable(); | ||
| const compilerArgs = splitCommandArgs(remoteOptions.compilerInstance); | ||
| const cmdArgs = [ | ||
| ...compilerArgs.length > 0 ? compilerArgs : [remoteOptions.compilerInstance], | ||
| "--project", | ||
| tempTsConfigJsonPath | ||
| ]; | ||
| const cmd = formatCommandForDisplay(pmExecutable, cmdArgs); | ||
| try { | ||
| await execPromise(pmExecutable, cmdArgs, { | ||
| cwd: typeof remoteOptions.moduleFederationConfig.dts !== "boolean" ? remoteOptions.moduleFederationConfig.dts?.cwd ?? void 0 : void 0, | ||
| shell: process.platform === "win32" | ||
| }); | ||
| } catch (err) { | ||
| if (compilerOptions.tsBuildInfoFile) try { | ||
| await rm(compilerOptions.tsBuildInfoFile); | ||
| } catch (e) {} | ||
| logAndReport(TYPE_001, typeDescMap, { cmd }, (msg) => { | ||
| throw new Error(msg); | ||
| }, void 0); | ||
| } | ||
| const mapExposeToEntry = Object.fromEntries(Object.entries(mapComponentsToExpose).map(([exposed, filename]) => { | ||
| const normalizedFileName = normalize(filename); | ||
| let relativeFileName = ""; | ||
| if (isAbsolute(normalizedFileName)) relativeFileName = relative(tsConfig.compilerOptions.rootDir, normalizedFileName); | ||
| else relativeFileName = relative(tsConfig.compilerOptions.rootDir, resolve(remoteOptions.context, normalizedFileName)); | ||
| return [removeExt(relativeFileName), exposed]; | ||
| })); | ||
| const cb = remoteOptions.extractThirdParty ? thirdPartyExtractor.collectPkgs.bind(thirdPartyExtractor) : () => void 0; | ||
| await processTypesFile({ | ||
| outDir: compilerOptions.outDir, | ||
| filePath: compilerOptions.outDir, | ||
| rootDir: compilerOptions.rootDir, | ||
| mfTypePath, | ||
| cb, | ||
| mapExposeToEntry | ||
| }); | ||
| if (remoteOptions.extractThirdParty) await thirdPartyExtractor.copyDts(); | ||
| if (remoteOptions.deleteTsConfig) await rm(tempTsConfigJsonPath); | ||
| } catch (err) { | ||
| throw err; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/archiveHandler.ts | ||
| const retrieveTypesZipPath = (mfTypesPath, remoteOptions) => join(mfTypesPath.replace(remoteOptions.typesFolder, ""), `${remoteOptions.typesFolder}.zip`); | ||
| const createTypesArchive = async (tsConfig, remoteOptions) => { | ||
| const mfTypesPath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| const zip = new AdmZip(); | ||
| zip.addLocalFolder(mfTypesPath); | ||
| return zip.writeZipPromise(retrieveTypesZipPath(mfTypesPath, remoteOptions)); | ||
| }; | ||
| const downloadErrorLogger = (destinationFolder, fileToDownload) => (reason) => { | ||
| throw { | ||
| ...reason, | ||
| message: `Network error: Unable to download federated mocks for '${destinationFolder}' from '${fileToDownload}' because '${reason.message}'` | ||
| }; | ||
| }; | ||
| const retrieveTypesArchiveDestinationPath = (hostOptions, destinationFolder) => { | ||
| return resolve(hostOptions.context, hostOptions.typesFolder, destinationFolder); | ||
| }; | ||
| const downloadTypesArchive = (hostOptions) => { | ||
| let retries = 0; | ||
| return async ([destinationFolder, fileToDownload]) => { | ||
| const destinationPath = retrieveTypesArchiveDestinationPath(hostOptions, destinationFolder); | ||
| while (retries++ < hostOptions.maxRetries) try { | ||
| const url = new URL(fileToDownload).href; | ||
| const response = await nativeFetch(url, { | ||
| responseType: "arraybuffer", | ||
| timeout: hostOptions.timeout, | ||
| family: hostOptions.family | ||
| }).catch(downloadErrorLogger(destinationFolder, url)); | ||
| if (typeof response.headers?.["content-type"] === "string" && response.headers["content-type"].includes("text/html")) throw new Error(`${url} receives invalid content-type: ${response.headers["content-type"]}`); | ||
| try { | ||
| if (hostOptions.deleteTypesFolder) await rm(destinationPath, { | ||
| recursive: true, | ||
| force: true | ||
| }); | ||
| } catch (error) { | ||
| fileLog(`Unable to remove types folder, ${error}`, "downloadTypesArchive", "error"); | ||
| } | ||
| new AdmZip(Buffer.from(response.data)).extractAllTo(destinationPath, true); | ||
| fileLog(`zip.extractAllTo success destinationPath: ${destinationPath}; url: ${url}`, "downloadTypesArchive", "info"); | ||
| return [destinationFolder, destinationPath]; | ||
| } catch (error) { | ||
| fileLog(`Error during types archive download: ${error?.message || "unknown error"}`, "downloadTypesArchive", "error"); | ||
| if (retries >= hostOptions.maxRetries) { | ||
| logger$1.error(`Failed to download types archive from "${fileToDownload}". Set FEDERATION_DEBUG=true for details.`); | ||
| if (hostOptions.abortOnError !== false) throw error; | ||
| return; | ||
| } | ||
| } | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/core/configurations/hostPlugin.ts | ||
| const defaultOptions$1 = { | ||
| typesFolder: "@mf-types", | ||
| remoteTypesFolder: "@mf-types", | ||
| deleteTypesFolder: true, | ||
| maxRetries: 3, | ||
| implementation: "", | ||
| context: process.cwd(), | ||
| abortOnError: true, | ||
| consumeAPITypes: false, | ||
| runtimePkgs: [], | ||
| remoteTypeUrls: {}, | ||
| timeout: 6e4, | ||
| typesOnBuild: false, | ||
| family: 0 | ||
| }; | ||
| const buildZipUrl = (hostOptions, url) => { | ||
| const remoteUrl = new URL(url, "file:"); | ||
| remoteUrl.pathname = `${remoteUrl.pathname.split("/").slice(0, -1).join("/")}/${hostOptions.remoteTypesFolder}.zip`; | ||
| return remoteUrl.protocol === "file:" ? remoteUrl.pathname : remoteUrl.href; | ||
| }; | ||
| const buildApiTypeUrl = (zipUrl) => { | ||
| if (!zipUrl) return; | ||
| return zipUrl.replace(".zip", ".d.ts"); | ||
| }; | ||
| const retrieveRemoteInfo = (options) => { | ||
| const { hostOptions, remoteAlias, remote } = options; | ||
| const { remoteTypeUrls } = hostOptions; | ||
| let decodedRemote = remote; | ||
| if (decodedRemote.startsWith(ENCODE_NAME_PREFIX)) decodedRemote = decodeName(decodedRemote, ENCODE_NAME_PREFIX); | ||
| const parsedInfo = parseEntry(decodedRemote, void 0, "@"); | ||
| const url = "entry" in parsedInfo ? parsedInfo.entry : parsedInfo.name === decodedRemote ? decodedRemote : ""; | ||
| let zipUrl = ""; | ||
| let apiTypeUrl = ""; | ||
| const name = parsedInfo.name || remoteAlias; | ||
| const remoteTypeUrl = typeof remoteTypeUrls === "object" && remoteTypeUrls[name]; | ||
| if (remoteTypeUrl) { | ||
| zipUrl = remoteTypeUrl.zip; | ||
| apiTypeUrl = remoteTypeUrl.api; | ||
| } | ||
| const shouldResolveTypeUrlsByConvention = Boolean(url && !url.includes(MANIFEST_EXT)); | ||
| if (!zipUrl && shouldResolveTypeUrlsByConvention) zipUrl = buildZipUrl(hostOptions, url); | ||
| if (!apiTypeUrl && zipUrl && (remoteTypeUrl || shouldResolveTypeUrlsByConvention)) apiTypeUrl = buildApiTypeUrl(zipUrl); | ||
| return { | ||
| name, | ||
| url, | ||
| zipUrl, | ||
| apiTypeUrl, | ||
| alias: remoteAlias | ||
| }; | ||
| }; | ||
| const resolveRemotes = (hostOptions) => { | ||
| const parsedOptions = utils.parseOptions(hostOptions.moduleFederationConfig.remotes || {}, (item, key) => ({ | ||
| remote: Array.isArray(item) ? item[0] : item, | ||
| key | ||
| }), (item, key) => ({ | ||
| remote: Array.isArray(item.external) ? item.external[0] : item.external, | ||
| key | ||
| })); | ||
| const remoteTypeUrls = hostOptions.remoteTypeUrls ?? {}; | ||
| if (typeof remoteTypeUrls !== "object") throw new Error("remoteTypeUrls must be consumed before resolveRemotes"); | ||
| const remoteInfos = Object.keys(remoteTypeUrls).reduce((sum, remoteName) => { | ||
| const { zip, api, alias } = remoteTypeUrls[remoteName]; | ||
| sum[alias] = { | ||
| name: remoteName, | ||
| url: "", | ||
| zipUrl: zip, | ||
| apiTypeUrl: api, | ||
| alias: alias || remoteName | ||
| }; | ||
| return sum; | ||
| }, {}); | ||
| return parsedOptions.reduce((accumulator, item) => { | ||
| const { key, remote } = item[1]; | ||
| const res = retrieveRemoteInfo({ | ||
| hostOptions, | ||
| remoteAlias: key, | ||
| remote | ||
| }); | ||
| if (accumulator[key]) { | ||
| accumulator[key] = { | ||
| ...accumulator[key], | ||
| url: res.url, | ||
| apiTypeUrl: accumulator[key].apiTypeUrl || res.apiTypeUrl | ||
| }; | ||
| return accumulator; | ||
| } | ||
| accumulator[key] = res; | ||
| return accumulator; | ||
| }, remoteInfos); | ||
| }; | ||
| const retrieveHostConfig = (options) => { | ||
| validateOptions(options); | ||
| const hostOptions = { | ||
| ...defaultOptions$1, | ||
| ...options | ||
| }; | ||
| return { | ||
| hostOptions, | ||
| mapRemotesToDownload: resolveRemotes(hostOptions) | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/core/constant.ts | ||
| const REMOTE_ALIAS_IDENTIFIER = "REMOTE_ALIAS_IDENTIFIER"; | ||
| const REMOTE_API_TYPES_FILE_NAME = "apis.d.ts"; | ||
| const HOST_API_TYPES_FILE_NAME = "index.d.ts"; | ||
| //#endregion | ||
| //#region src/core/lib/DTSManager.ts | ||
| var DTSManager = class { | ||
| constructor(options) { | ||
| this.options = cloneDeepOptions(options); | ||
| this.runtimePkgs = [ | ||
| "@module-federation/runtime", | ||
| "@module-federation/enhanced/runtime", | ||
| "@module-federation/runtime-tools" | ||
| ]; | ||
| this.loadedRemoteAPIAlias = /* @__PURE__ */ new Set(); | ||
| this.remoteAliasMap = {}; | ||
| this.extraOptions = options?.extraOptions || {}; | ||
| this.updatedRemoteInfos = {}; | ||
| } | ||
| generateAPITypes(mapComponentsToExpose) { | ||
| const exposePaths = /* @__PURE__ */ new Set(); | ||
| const packageType = Object.keys(mapComponentsToExpose).reduce((sum, exposeKey) => { | ||
| const exposePath = path.join(REMOTE_ALIAS_IDENTIFIER, exposeKey).split(path.sep).join("/"); | ||
| exposePaths.add(`'${exposePath}'`); | ||
| sum = `T extends '${exposePath}' ? typeof import('${exposePath}') :` + sum; | ||
| return sum; | ||
| }, "any;"); | ||
| return ` | ||
| export type RemoteKeys = ${[...exposePaths].join(" | ")}; | ||
| type PackageType<T> = ${packageType}`; | ||
| } | ||
| async extractRemoteTypes(options) { | ||
| const { remoteOptions, tsConfig } = options; | ||
| if (!remoteOptions.extractRemoteTypes) return; | ||
| let hasRemotes = false; | ||
| const remotes = remoteOptions.moduleFederationConfig.remotes; | ||
| if (remotes) { | ||
| if (Array.isArray(remotes)) hasRemotes = Boolean(remotes.length); | ||
| else if (typeof remotes === "object") hasRemotes = Boolean(Object.keys(remotes).length); | ||
| } | ||
| const mfTypesPath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| if (hasRemotes && this.options.host) try { | ||
| const { hostOptions } = retrieveHostConfig(this.options.host); | ||
| const remoteTypesFolder = path.resolve(hostOptions.context, hostOptions.typesFolder); | ||
| const targetDir = path.join(mfTypesPath, "node_modules"); | ||
| if (fs.existsSync(remoteTypesFolder)) { | ||
| const targetFolder = path.resolve(remoteOptions.context, targetDir); | ||
| await mkdir(targetFolder, { recursive: true }); | ||
| await cp(remoteTypesFolder, targetFolder, { | ||
| recursive: true, | ||
| force: true | ||
| }); | ||
| } | ||
| } catch (err) { | ||
| if (this.options.host?.abortOnError === false) fileLog(`Unable to copy remote types, ${err}`, "extractRemoteTypes", "error"); | ||
| else throw err; | ||
| } | ||
| } | ||
| async generateTypes() { | ||
| try { | ||
| const { options } = this; | ||
| if (!options.remote) throw new Error("options.remote is required if you want to generateTypes"); | ||
| const { remoteOptions, tsConfig, mapComponentsToExpose } = retrieveRemoteConfig(options.remote); | ||
| if (!Object.keys(mapComponentsToExpose).length) return; | ||
| if (!tsConfig.files?.length) { | ||
| logger$1.info("No type files to compile, skip"); | ||
| return; | ||
| } | ||
| if (tsConfig.compilerOptions.tsBuildInfoFile) try { | ||
| const tsBuildInfoFile = path.resolve(remoteOptions.context, tsConfig.compilerOptions.tsBuildInfoFile); | ||
| const mfTypesPath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| if (!fs.existsSync(mfTypesPath)) fs.rmSync(tsBuildInfoFile, { force: true }); | ||
| } catch (e) {} | ||
| await this.extractRemoteTypes({ | ||
| remoteOptions, | ||
| tsConfig, | ||
| mapComponentsToExpose | ||
| }); | ||
| await compileTs(mapComponentsToExpose, tsConfig, remoteOptions); | ||
| await createTypesArchive(tsConfig, remoteOptions); | ||
| let apiTypesPath = ""; | ||
| if (remoteOptions.generateAPITypes) { | ||
| const apiTypes = this.generateAPITypes(mapComponentsToExpose); | ||
| apiTypesPath = retrieveMfAPITypesPath(tsConfig, remoteOptions); | ||
| fs.writeFileSync(apiTypesPath, apiTypes); | ||
| } | ||
| try { | ||
| if (remoteOptions.deleteTypesFolder) await rm(retrieveMfTypesPath(tsConfig, remoteOptions), { | ||
| recursive: true, | ||
| force: true | ||
| }); | ||
| } catch (err) { | ||
| if (isDebugMode()) console.error(err); | ||
| } | ||
| logger$1.success("Federated types created correctly"); | ||
| } catch (error) { | ||
| if (this.options.remote?.abortOnError === false) { | ||
| if (this.options.displayErrorInTerminal) logger$1.error(error); | ||
| } else throw error; | ||
| } | ||
| } | ||
| async requestRemoteManifest(remoteInfo, hostOptions) { | ||
| try { | ||
| if (!remoteInfo.url.includes(MANIFEST_EXT)) return remoteInfo; | ||
| if (remoteInfo.zipUrl) return remoteInfo; | ||
| const url = remoteInfo.url; | ||
| const manifestJson = (await nativeFetch(url, { | ||
| timeout: hostOptions.timeout, | ||
| family: hostOptions.family | ||
| })).data; | ||
| if (!manifestJson.metaData.types.zip) throw new Error(`Can not get ${remoteInfo.name}'s types archive url!`); | ||
| const addProtocol = (u) => { | ||
| if (u.startsWith("//")) return `https:${u}`; | ||
| return u; | ||
| }; | ||
| let publicPath; | ||
| if ("publicPath" in manifestJson.metaData) publicPath = manifestJson.metaData.publicPath; | ||
| else { | ||
| const getPublicPath = new Function(manifestJson.metaData.getPublicPath); | ||
| if (manifestJson.metaData.getPublicPath.startsWith("function")) publicPath = getPublicPath()(); | ||
| else publicPath = getPublicPath(); | ||
| } | ||
| if (publicPath === "auto") publicPath = inferAutoPublicPath(remoteInfo.url); | ||
| const normalizedPublicPath = addProtocol(publicPath).endsWith("/") ? addProtocol(publicPath) : `${addProtocol(publicPath)}/`; | ||
| remoteInfo.zipUrl = new URL(manifestJson.metaData.types.zip, normalizedPublicPath).href; | ||
| if (!manifestJson.metaData.types.api) { | ||
| console.warn(`Can not get ${remoteInfo.name}'s api types url!`); | ||
| remoteInfo.apiTypeUrl = ""; | ||
| return remoteInfo; | ||
| } | ||
| remoteInfo.apiTypeUrl = new URL(manifestJson.metaData.types.api, normalizedPublicPath).href; | ||
| return remoteInfo; | ||
| } catch (_err) { | ||
| fileLog(`fetch manifest failed, ${_err}, ${remoteInfo.name} will be ignored`, "requestRemoteManifest", "error"); | ||
| return remoteInfo; | ||
| } | ||
| } | ||
| async consumeTargetRemotes(hostOptions, remoteInfo) { | ||
| if (!remoteInfo.zipUrl) throw new Error(`Can not get ${remoteInfo.name}'s types archive url!`); | ||
| return downloadTypesArchive(hostOptions)([remoteInfo.alias, remoteInfo.zipUrl]); | ||
| } | ||
| async downloadAPITypes(remoteInfo, destinationPath, hostOptions) { | ||
| const { apiTypeUrl } = remoteInfo; | ||
| if (!apiTypeUrl) return; | ||
| try { | ||
| let apiTypeFile = (await nativeFetch(apiTypeUrl, { | ||
| timeout: hostOptions.timeout, | ||
| family: hostOptions.family | ||
| })).data; | ||
| apiTypeFile = apiTypeFile.replaceAll(REMOTE_ALIAS_IDENTIFIER, remoteInfo.alias); | ||
| const filePath = path.join(destinationPath, REMOTE_API_TYPES_FILE_NAME); | ||
| fs.writeFileSync(filePath, apiTypeFile); | ||
| const existed = this.loadedRemoteAPIAlias.has(remoteInfo.alias); | ||
| this.loadedRemoteAPIAlias.add(remoteInfo.alias); | ||
| fileLog(`success`, "downloadAPITypes", "info"); | ||
| return existed; | ||
| } catch (err) { | ||
| fileLog(`Unable to download "${remoteInfo.name}" api types, ${err}`, "downloadAPITypes", "error"); | ||
| } | ||
| } | ||
| consumeAPITypes(hostOptions) { | ||
| const apiTypeFileName = path.join(hostOptions.context, hostOptions.typesFolder, HOST_API_TYPES_FILE_NAME); | ||
| try { | ||
| const existedFile = fs.readFileSync(apiTypeFileName, "utf-8"); | ||
| new ThirdPartyExtractor({ destDir: "" }).collectTypeImports(existedFile).forEach((existedImport) => { | ||
| const alias = existedImport.split("./").slice(1).join("./").replace("/apis.d.ts", ""); | ||
| this.loadedRemoteAPIAlias.add(alias); | ||
| }); | ||
| } catch (err) {} | ||
| if (!this.loadedRemoteAPIAlias.size) return; | ||
| const packageTypes = []; | ||
| const remoteKeys = []; | ||
| const importTypeStr = [...this.loadedRemoteAPIAlias].sort().map((alias, index) => { | ||
| const remoteKey = `RemoteKeys_${index}`; | ||
| const packageType = `PackageType_${index}`; | ||
| packageTypes.push(`T extends ${remoteKey} ? ${packageType}<T>`); | ||
| remoteKeys.push(remoteKey); | ||
| return `import type { PackageType as ${packageType},RemoteKeys as ${remoteKey} } from './${alias}/apis.d.ts';`; | ||
| }).join("\n"); | ||
| const remoteKeysStr = `type RemoteKeys = ${remoteKeys.join(" | ")};`; | ||
| const packageTypesStr = `type PackageType<T, Y=any> = ${[...packageTypes, "Y"].join(" :\n")} ;`; | ||
| const runtimePkgs = /* @__PURE__ */ new Set(); | ||
| [...this.runtimePkgs, ...hostOptions.runtimePkgs].forEach((pkg) => { | ||
| runtimePkgs.add(pkg); | ||
| }); | ||
| const fileStr = `${importTypeStr} | ||
| ${[...runtimePkgs].map((pkg) => { | ||
| return `declare module "${pkg}" { | ||
| ${remoteKeysStr} | ||
| ${packageTypesStr} | ||
| export function loadRemote<T extends RemoteKeys,Y>(packageName: T): Promise<PackageType<T, Y>>; | ||
| export function loadRemote<T extends string,Y>(packageName: T): Promise<PackageType<T, Y>>; | ||
| }`; | ||
| }).join("\n")} | ||
| `; | ||
| fs.writeFileSync(path.join(hostOptions.context, hostOptions.typesFolder, HOST_API_TYPES_FILE_NAME), fileStr); | ||
| } | ||
| async consumeArchiveTypes(options) { | ||
| const { hostOptions, mapRemotesToDownload } = retrieveHostConfig(options); | ||
| const downloadPromises = Object.entries(mapRemotesToDownload).map(async (item) => { | ||
| const remoteInfo = item[1]; | ||
| if (!this.remoteAliasMap[remoteInfo.alias]) { | ||
| const requiredRemoteInfo = await this.requestRemoteManifest(remoteInfo, hostOptions); | ||
| this.remoteAliasMap[remoteInfo.alias] = requiredRemoteInfo; | ||
| } | ||
| return this.consumeTargetRemotes(hostOptions, this.remoteAliasMap[remoteInfo.alias]); | ||
| }); | ||
| return { | ||
| hostOptions, | ||
| downloadPromisesResult: await Promise.allSettled(downloadPromises) | ||
| }; | ||
| } | ||
| async consumeTypes() { | ||
| try { | ||
| const { options } = this; | ||
| if (!options.host) throw new Error("options.host is required if you want to consumeTypes"); | ||
| const { mapRemotesToDownload } = retrieveHostConfig(options.host); | ||
| if (!Object.keys(mapRemotesToDownload).length) return; | ||
| const { downloadPromisesResult, hostOptions } = await this.consumeArchiveTypes(options.host); | ||
| if (hostOptions.consumeAPITypes) { | ||
| await Promise.all(downloadPromisesResult.map(async (item) => { | ||
| if (item.status === "rejected" || !item.value) return; | ||
| const [alias, destinationPath] = item.value; | ||
| const remoteInfo = this.remoteAliasMap[alias]; | ||
| if (!remoteInfo) return; | ||
| await this.downloadAPITypes(remoteInfo, destinationPath, hostOptions); | ||
| })); | ||
| this.consumeAPITypes(hostOptions); | ||
| } | ||
| logger$1.success("Federated types extraction completed"); | ||
| } catch (err) { | ||
| if (this.options.host?.abortOnError === false) fileLog(`Unable to consume federated types, ${err}`, "consumeTypes", "error"); | ||
| else throw err; | ||
| } | ||
| } | ||
| async updateTypes(options) { | ||
| try { | ||
| const { remoteName, updateMode, remoteTarPath, remoteInfo: updatedRemoteInfo, once } = options; | ||
| const hostName = this.options?.host?.moduleFederationConfig?.name; | ||
| fileLog(`options: ${JSON.stringify(options, null, 2)};\nhostName: ${hostName}`, "updateTypes", "info"); | ||
| if (updateMode === UpdateMode.POSITIVE && remoteName === hostName) { | ||
| if (!this.options.remote) return; | ||
| await this.generateTypes(); | ||
| } else { | ||
| const { remoteAliasMap } = this; | ||
| if (!this.options.host) return; | ||
| const { hostOptions, mapRemotesToDownload } = retrieveHostConfig(this.options.host); | ||
| const loadedRemoteInfo = Object.values(remoteAliasMap).find((i) => i.name === remoteName); | ||
| const consumeTypes = async (requiredRemoteInfo) => { | ||
| fileLog(`consumeTypes start`, "updateTypes", "info"); | ||
| if (!requiredRemoteInfo.zipUrl) throw new Error(`Can not get ${requiredRemoteInfo.name}'s types archive url!`); | ||
| const [_alias, destinationPath] = await this.consumeTargetRemotes(hostOptions, { | ||
| ...requiredRemoteInfo, | ||
| zipUrl: remoteTarPath || requiredRemoteInfo.zipUrl | ||
| }); | ||
| if (await this.downloadAPITypes(requiredRemoteInfo, destinationPath, hostOptions)) this.consumeAPITypes(hostOptions); | ||
| fileLog(`consumeTypes end`, "updateTypes", "info"); | ||
| }; | ||
| fileLog(`loadedRemoteInfo: ${JSON.stringify(loadedRemoteInfo, null, 2)}`, "updateTypes", "info"); | ||
| if (!loadedRemoteInfo) { | ||
| const remoteInfo = Object.values(mapRemotesToDownload).find((item) => { | ||
| return item.name === remoteName; | ||
| }); | ||
| fileLog(`remoteInfo: ${JSON.stringify(remoteInfo, null, 2)}`, "updateTypes", "info"); | ||
| if (remoteInfo) { | ||
| if (!this.remoteAliasMap[remoteInfo.alias]) { | ||
| const requiredRemoteInfo = await this.requestRemoteManifest(remoteInfo, hostOptions); | ||
| this.remoteAliasMap[remoteInfo.alias] = requiredRemoteInfo; | ||
| } | ||
| await consumeTypes(this.remoteAliasMap[remoteInfo.alias]); | ||
| } else if (updatedRemoteInfo) { | ||
| const consumeDynamicRemoteTypes = async () => { | ||
| await consumeTypes(this.updatedRemoteInfos[updatedRemoteInfo.name]); | ||
| }; | ||
| if (!this.updatedRemoteInfos[updatedRemoteInfo.name]) { | ||
| const parsedRemoteInfo = retrieveRemoteInfo({ | ||
| hostOptions, | ||
| remoteAlias: updatedRemoteInfo.alias || updatedRemoteInfo.name, | ||
| remote: updatedRemoteInfo.url | ||
| }); | ||
| fileLog(`start request manifest`, "consumeTypes", "info"); | ||
| this.updatedRemoteInfos[updatedRemoteInfo.name] = await this.requestRemoteManifest(parsedRemoteInfo, hostOptions); | ||
| fileLog(`end request manifest, this.updatedRemoteInfos[updatedRemoteInfo.name]: ${JSON.stringify(this.updatedRemoteInfos[updatedRemoteInfo.name], null, 2)}`, "updateTypes", "info"); | ||
| await consumeDynamicRemoteTypes(); | ||
| } | ||
| if (!once && this.updatedRemoteInfos[updatedRemoteInfo.name]) await consumeDynamicRemoteTypes(); | ||
| } | ||
| } else await consumeTypes(loadedRemoteInfo); | ||
| } | ||
| } catch (err) { | ||
| fileLog(`updateTypes fail, ${err}`, "updateTypes", "error"); | ||
| } | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/utils.ts | ||
| const dispatcherCache = /* @__PURE__ */ new Map(); | ||
| function getDTSManagerConstructor(implementation) { | ||
| if (implementation) { | ||
| const NewConstructor = __require(implementation); | ||
| return NewConstructor.default ? NewConstructor.default : NewConstructor; | ||
| } | ||
| return DTSManager; | ||
| } | ||
| const validateOptions = (options) => { | ||
| if (!options.moduleFederationConfig) throw new Error("moduleFederationConfig is required"); | ||
| }; | ||
| function retrieveTypesAssetsInfo(options) { | ||
| let apiTypesPath = ""; | ||
| let zipTypesPath = ""; | ||
| try { | ||
| const { tsConfig, remoteOptions, mapComponentsToExpose } = retrieveRemoteConfig(options); | ||
| if (!Object.keys(mapComponentsToExpose).length || !tsConfig.files.length) return { | ||
| apiTypesPath, | ||
| zipTypesPath, | ||
| zipName: "", | ||
| apiFileName: "" | ||
| }; | ||
| zipTypesPath = retrieveTypesZipPath(retrieveMfTypesPath(tsConfig, remoteOptions), remoteOptions); | ||
| if (remoteOptions.generateAPITypes) apiTypesPath = retrieveMfAPITypesPath(tsConfig, remoteOptions); | ||
| return { | ||
| apiTypesPath, | ||
| zipTypesPath, | ||
| zipName: path.basename(zipTypesPath), | ||
| apiFileName: path.basename(apiTypesPath) | ||
| }; | ||
| } catch (err) { | ||
| console.error(ansiColors.red(`Unable to compile federated types, ${err}`)); | ||
| return { | ||
| apiTypesPath: "", | ||
| zipTypesPath: "", | ||
| zipName: "", | ||
| apiFileName: "" | ||
| }; | ||
| } | ||
| } | ||
| function isDebugMode() { | ||
| return Boolean(process.env["FEDERATION_DEBUG"]) || process.env["NODE_ENV"] === "test"; | ||
| } | ||
| const isTSProject = (dtsOptions, context = process.cwd()) => { | ||
| if (dtsOptions === false) return false; | ||
| try { | ||
| let filepath = ""; | ||
| if (typeof dtsOptions === "object" && dtsOptions.tsConfigPath) filepath = dtsOptions.tsConfigPath; | ||
| else filepath = path.resolve(context, "./tsconfig.json"); | ||
| if (!path.isAbsolute(filepath)) filepath = path.resolve(context, filepath); | ||
| return fs.existsSync(filepath); | ||
| } catch (err) { | ||
| return false; | ||
| } | ||
| }; | ||
| function cloneDeepOptions(options) { | ||
| const excludeKeys = new Set(["manifest", "async"]); | ||
| const cache = /* @__PURE__ */ new WeakMap(); | ||
| function sanitize(val, key) { | ||
| if (key !== void 0 && excludeKeys.has(key) || typeof val === "function") return false; | ||
| if (key === "extractThirdParty" && Array.isArray(val)) return val.map(String); | ||
| if (Array.isArray(val)) { | ||
| if (cache.has(val)) return cache.get(val); | ||
| const out = []; | ||
| cache.set(val, out); | ||
| val.forEach((v, i) => out.push(sanitize(v, String(i)))); | ||
| return out; | ||
| } | ||
| if (val !== null && typeof val === "object" && Object.getPrototypeOf(val) === Object.prototype) { | ||
| const obj = val; | ||
| if (cache.has(obj)) return cache.get(obj); | ||
| const out = {}; | ||
| cache.set(obj, out); | ||
| for (const [k, v] of Object.entries(obj)) out[k] = sanitize(v, k); | ||
| return out; | ||
| } | ||
| return val; | ||
| } | ||
| return structuredClone(sanitize(options)); | ||
| } | ||
| const getEnvHeaders = () => { | ||
| const headersStr = getProcessEnv()["MF_ENV_HEADERS"]; | ||
| if (!headersStr || headersStr === "undefined") return {}; | ||
| try { | ||
| return { ...JSON.parse(headersStr) }; | ||
| } catch { | ||
| return {}; | ||
| } | ||
| }; | ||
| const createDispatcherFromFamily = (family) => { | ||
| if (!family) return void 0; | ||
| if (dispatcherCache.has(family)) return dispatcherCache.get(family); | ||
| try { | ||
| const dispatcher = new Agent({ connect: { family } }); | ||
| dispatcherCache.set(family, dispatcher); | ||
| return dispatcher; | ||
| } catch {} | ||
| }; | ||
| const toHeaderRecord = (headers) => { | ||
| const out = {}; | ||
| headers.forEach((value, key) => { | ||
| out[key.toLowerCase()] = value; | ||
| }); | ||
| return out; | ||
| }; | ||
| async function nativeFetch(url, config) { | ||
| const controller = new AbortController(); | ||
| const timeoutMs = config?.timeout ?? 6e4; | ||
| const timeoutId = setTimeout(() => controller.abort(), timeoutMs); | ||
| const headers = { | ||
| ...getEnvHeaders(), | ||
| ...config?.headers ?? {} | ||
| }; | ||
| const dispatcher = config?.dispatcher ?? createDispatcherFromFamily(config?.family); | ||
| try { | ||
| const resp = await fetch(url, { | ||
| headers, | ||
| signal: controller.signal, | ||
| ...dispatcher ? { dispatcher } : {}, | ||
| ...config?.agent ? { agent: config.agent } : {} | ||
| }); | ||
| const headerRecord = toHeaderRecord(resp.headers); | ||
| if (!resp.ok) throw new Error(`Request failed with status ${resp.status}`); | ||
| if (config?.responseType === "arraybuffer") return { | ||
| data: await resp.arrayBuffer(), | ||
| headers: headerRecord, | ||
| status: resp.status | ||
| }; | ||
| return { | ||
| data: (resp.headers.get("content-type") || "").includes("application/json") || url.endsWith(".json") ? await resp.json() : await resp.text(), | ||
| headers: headerRecord, | ||
| status: resp.status | ||
| }; | ||
| } finally { | ||
| clearTimeout(timeoutId); | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/core/configurations/remotePlugin.ts | ||
| const defaultOptions = { | ||
| tsConfigPath: "./tsconfig.json", | ||
| typesFolder: "@mf-types", | ||
| compiledTypesFolder: "compiled-types", | ||
| hostRemoteTypesFolder: "@mf-types", | ||
| deleteTypesFolder: true, | ||
| additionalFilesToCompile: [], | ||
| compilerInstance: "tsc", | ||
| compileInChildProcess: false, | ||
| implementation: "", | ||
| generateAPITypes: false, | ||
| context: process.cwd(), | ||
| abortOnError: true, | ||
| extractRemoteTypes: false, | ||
| extractThirdParty: false, | ||
| outputDir: "", | ||
| deleteTsConfig: true | ||
| }; | ||
| function getEffectiveRootDir(parsedCommandLine) { | ||
| const compilerOptions = parsedCommandLine.options; | ||
| if (compilerOptions.rootDir) return compilerOptions.rootDir; | ||
| const files = parsedCommandLine.fileNames; | ||
| if (files.length > 0) return files.map((file) => dirname(file)).reduce((commonPath, fileDir) => { | ||
| while (!fileDir.startsWith(commonPath)) commonPath = dirname(commonPath); | ||
| return commonPath; | ||
| }, files[0]); | ||
| if (parsedCommandLine.projectReferences.length) { | ||
| const relativeReferences = parsedCommandLine.projectReferences.filter((reference) => !isAbsolute(reference.originalPath ?? reference.path)); | ||
| const referencesForRoot = relativeReferences.length ? relativeReferences : parsedCommandLine.projectReferences; | ||
| return referencesForRoot.map((reference) => dirname(reference.path)).reduce((commonPath, filePath) => { | ||
| while (!filePath.startsWith(commonPath)) commonPath = dirname(commonPath); | ||
| return commonPath; | ||
| }, dirname(referencesForRoot[0].path)); | ||
| } | ||
| throw new Error("Can not get effective rootDir, please set compilerOptions.rootDir !"); | ||
| } | ||
| const getDependentFiles = (rootFiles, configContent, rootDir) => { | ||
| const dependentFiles = typescript.createProgram(rootFiles, configContent.options).getSourceFiles().map((file) => file.fileName).filter((file) => !file.endsWith(".d.ts") && file.startsWith(rootDir)); | ||
| return dependentFiles.length ? dependentFiles : rootFiles; | ||
| }; | ||
| const readTsConfig = ({ tsConfigPath, typesFolder, compiledTypesFolder, context, additionalFilesToCompile, outputDir }, mapComponentsToExpose) => { | ||
| const resolvedTsConfigPath = resolve(context, tsConfigPath); | ||
| const readResult = typescript.readConfigFile(resolvedTsConfigPath, typescript.sys.readFile); | ||
| if (readResult.error) throw new Error(readResult.error.messageText.toString()); | ||
| const rawTsConfigJson = readResult.config; | ||
| const configContent = typescript.parseJsonConfigFileContent(rawTsConfigJson, typescript.sys, dirname(resolvedTsConfigPath)); | ||
| const rootDir = getEffectiveRootDir(configContent); | ||
| const outDir = resolve(context, outputDir || configContent.options.outDir || "dist", typesFolder, compiledTypesFolder); | ||
| const defaultCompilerOptions = { | ||
| rootDir, | ||
| emitDeclarationOnly: true, | ||
| noEmit: false, | ||
| declaration: true, | ||
| outDir | ||
| }; | ||
| rawTsConfigJson.compilerOptions = rawTsConfigJson.compilerOptions || {}; | ||
| rawTsConfigJson.compilerOptions = { | ||
| incremental: true, | ||
| tsBuildInfoFile: resolve(context, "node_modules/.cache/mf-types/.tsbuildinfo"), | ||
| ...rawTsConfigJson.compilerOptions, | ||
| ...defaultCompilerOptions | ||
| }; | ||
| const { paths, baseUrl, ...restCompilerOptions } = rawTsConfigJson.compilerOptions || {}; | ||
| rawTsConfigJson.compilerOptions = restCompilerOptions; | ||
| const outDirWithoutTypesFolder = resolve(context, outputDir || configContent.options.outDir || "dist"); | ||
| const excludeExtensions = [".mdx", ".md"]; | ||
| const filesToCompile = [...getDependentFiles([...Object.values(mapComponentsToExpose), ...additionalFilesToCompile].filter((filename) => !excludeExtensions.some((ext) => filename.endsWith(ext))), configContent, rootDir), ...configContent.fileNames.filter((filename) => filename.endsWith(".d.ts") && !filename.startsWith(outDirWithoutTypesFolder))]; | ||
| rawTsConfigJson.include = []; | ||
| rawTsConfigJson.files = [...new Set(filesToCompile)]; | ||
| rawTsConfigJson.exclude = []; | ||
| "references" in rawTsConfigJson && delete rawTsConfigJson.references; | ||
| rawTsConfigJson.extends = resolvedTsConfigPath; | ||
| rawTsConfigJson.compilerOptions.declarationDir = outDir; | ||
| return rawTsConfigJson; | ||
| }; | ||
| const TS_EXTENSIONS = [ | ||
| "ts", | ||
| "tsx", | ||
| "vue", | ||
| "svelte" | ||
| ]; | ||
| const resolveWithExtension = (exposedPath, context) => { | ||
| if (extname(exposedPath)) return resolve(context, exposedPath); | ||
| for (const extension of TS_EXTENSIONS) { | ||
| const exposedPathWithExtension = resolve(context, `${exposedPath}.${extension}`); | ||
| if (existsSync(exposedPathWithExtension)) return exposedPathWithExtension; | ||
| } | ||
| }; | ||
| const resolveExposes = (remoteOptions) => { | ||
| return utils.parseOptions(remoteOptions.moduleFederationConfig.exposes || {}, (item, key) => ({ | ||
| exposePath: Array.isArray(item) ? item[0] : item, | ||
| key | ||
| }), (item, key) => ({ | ||
| exposePath: Array.isArray(item.import) ? item.import[0] : item.import[0], | ||
| key | ||
| })).reduce((accumulator, item) => { | ||
| const { exposePath, key } = item[1]; | ||
| accumulator[key] = resolveWithExtension(exposePath, remoteOptions.context) || resolveWithExtension(join(exposePath, "index"), remoteOptions.context) || exposePath; | ||
| return accumulator; | ||
| }, {}); | ||
| }; | ||
| const retrieveRemoteConfig = (options) => { | ||
| validateOptions(options); | ||
| const remoteOptions = { | ||
| ...defaultOptions, | ||
| ...options | ||
| }; | ||
| const mapComponentsToExpose = resolveExposes(remoteOptions); | ||
| const tsConfig = readTsConfig(remoteOptions, mapComponentsToExpose); | ||
| if (tsConfig.compilerOptions.incremental && tsConfig.compilerOptions.tsBuildInfoFile && options.deleteTypesFolder !== true) remoteOptions.deleteTypesFolder = false; | ||
| return { | ||
| tsConfig, | ||
| mapComponentsToExpose, | ||
| remoteOptions | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/generateTypes.ts | ||
| async function generateTypes(options) { | ||
| return new (getDTSManagerConstructor(options.remote?.implementation))(options).generateTypes(); | ||
| } | ||
| //#endregion | ||
| //#region src/core/rpc/types.ts | ||
| let RpcGMCallTypes = /* @__PURE__ */ function(RpcGMCallTypes) { | ||
| RpcGMCallTypes["CALL"] = "mf_call"; | ||
| RpcGMCallTypes["RESOLVE"] = "mf_resolve"; | ||
| RpcGMCallTypes["REJECT"] = "mf_reject"; | ||
| RpcGMCallTypes["EXIT"] = "mf_exit"; | ||
| return RpcGMCallTypes; | ||
| }({}); | ||
| //#endregion | ||
| //#region src/core/rpc/expose-rpc.ts | ||
| function exposeRpc(fn) { | ||
| const sendMessage = (message) => new Promise((resolve, reject) => { | ||
| if (!process$1.send) reject(/* @__PURE__ */ new Error(`Process ${process$1.pid} doesn't have IPC channels`)); | ||
| else if (!process$1.connected) reject(/* @__PURE__ */ new Error(`Process ${process$1.pid} doesn't have open IPC channels`)); | ||
| else process$1.send(message, void 0, void 0, (error) => { | ||
| if (error) reject(error); | ||
| else resolve(void 0); | ||
| }); | ||
| }); | ||
| const handleMessage = async (message) => { | ||
| if (message.type === RpcGMCallTypes.CALL) { | ||
| if (!process$1.send) return; | ||
| let value, error; | ||
| try { | ||
| value = await fn(...message.args); | ||
| } catch (fnError) { | ||
| error = fnError; | ||
| } | ||
| try { | ||
| if (error) await sendMessage({ | ||
| type: RpcGMCallTypes.REJECT, | ||
| id: message.id, | ||
| error | ||
| }); | ||
| else await sendMessage({ | ||
| type: RpcGMCallTypes.RESOLVE, | ||
| id: message.id, | ||
| value | ||
| }); | ||
| } catch (sendError) { | ||
| if (error) { | ||
| if (error instanceof Error) console.error(error); | ||
| } | ||
| console.error(sendError); | ||
| } | ||
| } | ||
| }; | ||
| process$1.on("message", handleMessage); | ||
| } | ||
| //#endregion | ||
| export { retrieveMfTypesPath as _, cloneDeepOptions as a, ModuleFederationDevServer as b, isTSProject as c, DTSManager as d, HOST_API_TYPES_FILE_NAME as f, retrieveTypesZipPath as g, retrieveHostConfig as h, retrieveRemoteConfig as i, retrieveTypesAssetsInfo as l, REMOTE_API_TYPES_FILE_NAME as m, RpcGMCallTypes as n, getDTSManagerConstructor as o, REMOTE_ALIAS_IDENTIFIER as p, generateTypes as r, isDebugMode as s, exposeRpc as t, validateOptions as u, retrieveOriginalOutDir as v, __exportAll as x, createHttpServer as y }; |
| const require_Action = require('./Action-CzhPMw2i.js'); | ||
| const require_Broker = require('./Broker-CaenCqdn.js'); | ||
| let fs = require("fs"); | ||
| fs = require_Action.__toESM(fs); | ||
| let url = require("url"); | ||
| let path = require("path"); | ||
| path = require_Action.__toESM(path); | ||
| let fs_promises = require("fs/promises"); | ||
| let _module_federation_managers = require("@module-federation/managers"); | ||
| let typescript = require("typescript"); | ||
| typescript = require_Action.__toESM(typescript); | ||
| let _module_federation_sdk = require("@module-federation/sdk"); | ||
| let ansi_colors = require("ansi-colors"); | ||
| ansi_colors = require_Action.__toESM(ansi_colors); | ||
| let undici = require("undici"); | ||
| let _module_federation_third_party_dts_extractor = require("@module-federation/third-party-dts-extractor"); | ||
| let adm_zip = require("adm-zip"); | ||
| adm_zip = require_Action.__toESM(adm_zip); | ||
| let crypto = require("crypto"); | ||
| crypto = require_Action.__toESM(crypto); | ||
| let _module_federation_error_codes = require("@module-federation/error-codes"); | ||
| let _module_federation_error_codes_node = require("@module-federation/error-codes/node"); | ||
| let child_process = require("child_process"); | ||
| let util = require("util"); | ||
| util = require_Action.__toESM(util); | ||
| let isomorphic_ws = require("isomorphic-ws"); | ||
| isomorphic_ws = require_Action.__toESM(isomorphic_ws); | ||
| let http = require("http"); | ||
| http = require_Action.__toESM(http); | ||
| let process$1 = require("process"); | ||
| process$1 = require_Action.__toESM(process$1); | ||
| //#region src/server/message/Action/AddPublisher.ts | ||
| var AddPublisherAction = class extends require_Action.Action { | ||
| constructor(payload) { | ||
| super({ payload }, require_Action.ActionKind.ADD_PUBLISHER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/AddSubscriber.ts | ||
| var AddSubscriberAction = class extends require_Action.Action { | ||
| constructor(payload) { | ||
| super({ payload }, require_Action.ActionKind.ADD_SUBSCRIBER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/ExitSubscriber.ts | ||
| var ExitSubscriberAction = class extends require_Action.Action { | ||
| constructor(payload) { | ||
| super({ payload }, require_Action.ActionKind.EXIT_SUBSCRIBER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/ExitPublisher.ts | ||
| var ExitPublisherAction = class extends require_Action.Action { | ||
| constructor(payload) { | ||
| super({ payload }, require_Action.ActionKind.EXIT_PUBLISHER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/NotifyWebClient.ts | ||
| var NotifyWebClientAction = class extends require_Action.Action { | ||
| constructor(payload) { | ||
| super({ payload }, require_Action.ActionKind.NOTIFY_WEB_CLIENT); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/message/Action/UpdatePublisher.ts | ||
| var UpdatePublisherAction = class extends require_Action.Action { | ||
| constructor(payload) { | ||
| super({ payload }, require_Action.ActionKind.UPDATE_PUBLISHER); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/broker/createBroker.ts | ||
| const __filename$1 = (0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href); | ||
| const __dirname$1 = path.default.dirname(__filename$1); | ||
| function createBroker() { | ||
| const sub = (0, child_process.fork)(path.default.resolve(__dirname$1, "./start-broker.js"), [], { | ||
| detached: true, | ||
| stdio: "ignore", | ||
| env: process.env | ||
| }); | ||
| sub.send("start"); | ||
| sub.unref(); | ||
| return sub; | ||
| } | ||
| //#endregion | ||
| //#region src/server/DevServer.ts | ||
| var ModuleFederationDevServer = class { | ||
| constructor(ctx) { | ||
| this._publishWebSocket = null; | ||
| this._subscriberWebsocketMap = {}; | ||
| this._reconnect = true; | ||
| this._reconnectTimes = 0; | ||
| this._isConnected = false; | ||
| this._isReconnecting = false; | ||
| this._updateCallback = () => Promise.resolve(void 0); | ||
| const { name, remotes, remoteTypeTarPath, updateCallback } = ctx; | ||
| this._ip = require_Broker.getIPV4(); | ||
| this._name = name; | ||
| this._remotes = remotes; | ||
| this._remoteTypeTarPath = remoteTypeTarPath; | ||
| this._updateCallback = updateCallback; | ||
| this._stopWhenSIGTERMOrSIGINT(); | ||
| this._handleUnexpectedExit(); | ||
| this._connectPublishToServer(); | ||
| } | ||
| _connectPublishToServer() { | ||
| if (!this._reconnect) return; | ||
| require_Broker.fileLog(`Publisher:${this._name} Trying to connect to ws://${this._ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}...`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| this._publishWebSocket = new isomorphic_ws.default(`ws://${this._ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}?WEB_SOCKET_CONNECT_MAGIC_ID=${require_Broker.Broker.WEB_SOCKET_CONNECT_MAGIC_ID}`); | ||
| this._publishWebSocket.on("open", () => { | ||
| require_Broker.fileLog(`Current pid: ${process.pid}, publisher:${this._name} connected to ws://${this._ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}, starting service...`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| this._isConnected = true; | ||
| const addPublisherAction = new AddPublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| this._publishWebSocket?.send(JSON.stringify(addPublisherAction)); | ||
| this._connectSubscribers(); | ||
| }); | ||
| this._publishWebSocket.on("message", async (message) => { | ||
| try { | ||
| const parsedMessage = JSON.parse(message.toString()); | ||
| if (parsedMessage.type === "Log") { | ||
| if (parsedMessage.kind === require_Broker.LogKind.BrokerExitLog) { | ||
| require_Broker.fileLog(`Receive broker exit signal, ${this._name} service will exit...`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| } | ||
| } | ||
| if (parsedMessage.type === "API") { | ||
| if (parsedMessage.kind === require_Broker.APIKind.FETCH_TYPES) { | ||
| const { payload: { remoteInfo } } = parsedMessage; | ||
| require_Broker.fileLog(`${this._name} Receive broker FETCH_TYPES, payload as follows: ${JSON.stringify(remoteInfo, null, 2)}.`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| await this.fetchDynamicRemoteTypes({ remoteInfo }); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| console.error(err); | ||
| const exitPublisher = new ExitPublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip | ||
| }); | ||
| const exitSubscriber = new ExitSubscriberAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| publishers: this._remotes.map((remote) => ({ | ||
| name: remote.name, | ||
| ip: remote.ip | ||
| })) | ||
| }); | ||
| this._publishWebSocket?.send(JSON.stringify(exitPublisher)); | ||
| this._publishWebSocket?.send(JSON.stringify(exitSubscriber)); | ||
| require_Broker.fileLog("Parse messages error, ModuleFederationDevServer will exit...", require_Action.MF_SERVER_IDENTIFIER, "fatal"); | ||
| this._exit(); | ||
| } | ||
| }); | ||
| this._publishWebSocket.on("close", (code) => { | ||
| require_Broker.fileLog(`Connection closed with code ${code}.`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| this._publishWebSocket && this._publishWebSocket.close(); | ||
| this._publishWebSocket = null; | ||
| if (!this._reconnect) return; | ||
| const reconnectTime = require_Broker.fib(++this._reconnectTimes); | ||
| require_Broker.fileLog(`start reconnecting to server after ${reconnectTime}s.`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| setTimeout(() => this._connectPublishToServer(), reconnectTime * 1e3); | ||
| }); | ||
| this._publishWebSocket.on("error", this._tryCreateBackgroundBroker.bind(this)); | ||
| } | ||
| _connectSubscriberToServer(remote) { | ||
| const { name, ip } = remote; | ||
| require_Broker.fileLog(`remote module:${name} trying to connect to ws://${ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}...`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| const identifier = require_Broker.getIdentifier({ | ||
| name, | ||
| ip | ||
| }); | ||
| this._subscriberWebsocketMap[identifier] = new isomorphic_ws.default(`ws://${ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}?WEB_SOCKET_CONNECT_MAGIC_ID=${require_Broker.Broker.WEB_SOCKET_CONNECT_MAGIC_ID}`); | ||
| this._subscriberWebsocketMap[identifier].on("open", () => { | ||
| require_Broker.fileLog(`Current pid: ${process.pid} remote module: ${name} connected to ws://${ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}, starting service...`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| const addSubscriber = new AddSubscriberAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| publishers: [{ | ||
| name, | ||
| ip | ||
| }] | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].send(JSON.stringify(addSubscriber)); | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].on("message", async (message) => { | ||
| try { | ||
| const parsedMessage = JSON.parse(message.toString()); | ||
| if (parsedMessage.type === "Log") { | ||
| if (parsedMessage.kind === require_Broker.LogKind.BrokerExitLog) { | ||
| require_Broker.fileLog(`${identifier}'s Server exit, thus ${identifier} will no longer has reload ability.`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| } | ||
| } | ||
| if (parsedMessage.type === "API") { | ||
| if (parsedMessage.kind === require_Broker.APIKind.UPDATE_SUBSCRIBER) { | ||
| const { payload: { updateKind, updateSourcePaths, name: subscribeName, remoteTypeTarPath, updateMode } } = parsedMessage; | ||
| await this._updateSubscriber({ | ||
| remoteTypeTarPath, | ||
| name: subscribeName, | ||
| updateKind, | ||
| updateMode, | ||
| updateSourcePaths | ||
| }); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| console.error(err); | ||
| const exitSubscriber = new ExitSubscriberAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| publishers: [{ | ||
| name, | ||
| ip | ||
| }] | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].send(JSON.stringify(exitSubscriber)); | ||
| require_Broker.fileLog(`${identifier} exit, | ||
| error: ${err instanceof Error ? err.toString() : JSON.stringify(err)} | ||
| `, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| } | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].on("close", (code) => { | ||
| require_Broker.fileLog(`Connection closed with code ${code}.`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| this._subscriberWebsocketMap[identifier]?.close(); | ||
| delete this._subscriberWebsocketMap[identifier]; | ||
| }); | ||
| this._subscriberWebsocketMap[identifier].on("error", (err) => { | ||
| if ("code" in err && err.code === "ETIMEDOUT") require_Broker.fileLog(`Can not connect ${JSON.stringify(remote)}, please make sure this remote is started locally.`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| else console.error(err); | ||
| this._subscriberWebsocketMap[identifier]?.close(); | ||
| delete this._subscriberWebsocketMap[identifier]; | ||
| }); | ||
| } | ||
| _connectSubscribers() { | ||
| this._remotes.forEach((remote) => { | ||
| this._connectSubscriberToServer(remote); | ||
| }); | ||
| } | ||
| async _updateSubscriber(options) { | ||
| const { updateMode, updateKind, updateSourcePaths, name, remoteTypeTarPath, remoteInfo } = options; | ||
| require_Broker.fileLog(`[_updateSubscriber] run, options: ${JSON.stringify(options, null, 2)}`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| if (updateMode === require_Action.UpdateMode.PASSIVE && updateSourcePaths.includes(this._name)) { | ||
| require_Broker.fileLog(`[_updateSubscriber] run, updateSourcePaths:${updateSourcePaths} includes ${this._name}, update ignore!`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| return; | ||
| } | ||
| if (updateSourcePaths.slice(-1)[0] === this._name) { | ||
| require_Broker.fileLog(`[_updateSubscriber] run, updateSourcePaths:${updateSourcePaths} ends is ${this._name}, update ignore!`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| return; | ||
| } | ||
| require_Broker.fileLog(`[_updateSubscriber] run, updateSourcePaths:${updateSourcePaths}, current module:${this._name}, update start...`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| await this._updateCallback({ | ||
| name, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths, | ||
| remoteTypeTarPath, | ||
| remoteInfo | ||
| }); | ||
| const newUpdateSourcePaths = updateSourcePaths.concat(this._name); | ||
| const updatePublisher = new UpdatePublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| updateMode: require_Action.UpdateMode.PASSIVE, | ||
| updateKind, | ||
| updateSourcePaths: newUpdateSourcePaths, | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| require_Broker.fileLog(`[_updateSubscriber] run, updateSourcePaths:${newUpdateSourcePaths}, update publisher ${this._name} start...`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| this._publishWebSocket?.send(JSON.stringify(updatePublisher)); | ||
| } | ||
| _tryCreateBackgroundBroker(err) { | ||
| if (!((err?.code === "ECONNREFUSED" || err?.code === "ETIMEDOUT") && err.port === require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT)) { | ||
| require_Broker.fileLog(`websocket error: ${err.stack}`, require_Action.MF_SERVER_IDENTIFIER, "fatal"); | ||
| return; | ||
| } | ||
| require_Broker.fileLog(`Failed to connect to ws://${this._ip}:${require_Broker.Broker.DEFAULT_WEB_SOCKET_PORT}...`, require_Action.MF_SERVER_IDENTIFIER, "fatal"); | ||
| this._isReconnecting = true; | ||
| setTimeout(() => { | ||
| this._isReconnecting = false; | ||
| if (this._reconnect === false) return; | ||
| require_Broker.fileLog("Creating new background broker...", require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| createBroker().on("message", (message) => { | ||
| if (message === "ready") { | ||
| require_Broker.fileLog("background broker started.", require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| this._reconnectTimes = 1; | ||
| if (process.send) process.send("ready"); | ||
| } | ||
| }); | ||
| }, Math.ceil(100 * Math.random())); | ||
| } | ||
| _stopWhenSIGTERMOrSIGINT() { | ||
| process.on("SIGTERM", () => { | ||
| require_Broker.fileLog(`Process(${process.pid}) SIGTERM, ModuleFederationDevServer will exit...`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| }); | ||
| process.on("SIGINT", () => { | ||
| require_Broker.fileLog(`Process(${process.pid}) SIGINT, ModuleFederationDevServer will exit...`, require_Action.MF_SERVER_IDENTIFIER, "warn"); | ||
| this._exit(); | ||
| }); | ||
| } | ||
| _handleUnexpectedExit() { | ||
| process.on("unhandledRejection", (error) => { | ||
| if (this._isReconnecting) return; | ||
| console.error("Unhandled Rejection Error: ", error); | ||
| require_Broker.fileLog(`Process(${process.pid}) unhandledRejection, garfishModuleServer will exit...`, require_Action.MF_SERVER_IDENTIFIER, "error"); | ||
| this._exit(); | ||
| }); | ||
| process.on("uncaughtException", (error) => { | ||
| if (this._isReconnecting) return; | ||
| console.error("Unhandled Exception Error: ", error); | ||
| require_Broker.fileLog(`Process(${process.pid}) uncaughtException, garfishModuleServer will exit...`, require_Action.MF_SERVER_IDENTIFIER, "error"); | ||
| this._exit(); | ||
| }); | ||
| } | ||
| _exit() { | ||
| this._reconnect = false; | ||
| if (this._publishWebSocket) { | ||
| const exitPublisher = new ExitPublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(exitPublisher)); | ||
| this._publishWebSocket.on("message", (message) => { | ||
| const parsedMessage = JSON.parse(message.toString()); | ||
| require_Broker.fileLog(`[${parsedMessage.kind}]: ${JSON.stringify(parsedMessage)}`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| }); | ||
| } | ||
| if (this._publishWebSocket) { | ||
| this._publishWebSocket.close(); | ||
| this._publishWebSocket = null; | ||
| } | ||
| process.exit(0); | ||
| } | ||
| exit() { | ||
| this._exit(); | ||
| } | ||
| update(options) { | ||
| if (!this._publishWebSocket || !this._isConnected) return; | ||
| const { updateKind, updateMode, updateSourcePaths, clientName } = options; | ||
| require_Broker.fileLog(`update run, ${this._name} module update, updateKind: ${updateKind}, updateMode: ${updateMode}, updateSourcePaths: ${updateSourcePaths}`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| if (updateKind === require_Broker.UpdateKind.RELOAD_PAGE) { | ||
| const notifyWebClient = new NotifyWebClientAction({ | ||
| name: clientName || this._name, | ||
| updateMode | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(notifyWebClient)); | ||
| return; | ||
| } | ||
| const updatePublisher = new UpdatePublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths: [this._name], | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(updatePublisher)); | ||
| } | ||
| async fetchDynamicRemoteTypes(options) { | ||
| const { remoteInfo, once } = options; | ||
| const updateMode = require_Action.UpdateMode.PASSIVE; | ||
| const updateKind = require_Broker.UpdateKind.UPDATE_TYPE; | ||
| require_Broker.fileLog(`fetchDynamicRemoteTypes: remoteInfo: ${JSON.stringify(remoteInfo)}`, require_Action.MF_SERVER_IDENTIFIER, "info"); | ||
| await this._updateCallback({ | ||
| name: this._name, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths: [], | ||
| remoteTypeTarPath: "", | ||
| remoteInfo, | ||
| once | ||
| }); | ||
| const updatePublisher = new UpdatePublisherAction({ | ||
| name: this._name, | ||
| ip: this._ip, | ||
| updateMode, | ||
| updateKind, | ||
| updateSourcePaths: [this._name], | ||
| remoteTypeTarPath: this._remoteTypeTarPath | ||
| }); | ||
| this._publishWebSocket.send(JSON.stringify(updatePublisher)); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/server/createHttpServer.ts | ||
| async function createHttpServer(options) { | ||
| const { typeTarPath } = options; | ||
| const freeport = await require_Broker.getFreePort(); | ||
| const server = http.default.createServer((req, res) => { | ||
| if ((req.url?.split("?")[0] ?? "/") === `/${require_Action.DEFAULT_TAR_NAME}`) { | ||
| res.statusCode = 200; | ||
| res.setHeader("Content-Type", "application/x-gzip"); | ||
| if (req.method === "HEAD") { | ||
| res.end(); | ||
| return; | ||
| } | ||
| const stream = fs.default.createReadStream(typeTarPath); | ||
| stream.on("error", () => { | ||
| if (!res.headersSent) res.statusCode = 500; | ||
| res.end(); | ||
| }); | ||
| res.on("close", () => { | ||
| stream.destroy(); | ||
| }); | ||
| stream.pipe(res); | ||
| return; | ||
| } | ||
| res.statusCode = 404; | ||
| res.end(); | ||
| }); | ||
| server.listen(freeport); | ||
| return { | ||
| server, | ||
| serverAddress: `http://${require_Broker.getIPV4()}:${freeport}` | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/core/lib/typeScriptCompiler.ts | ||
| const STARTS_WITH_SLASH = /^\//; | ||
| const DEFINITION_FILE_EXTENSION = ".d.ts"; | ||
| const retrieveMfTypesPath = (tsConfig, remoteOptions) => (0, path.normalize)(tsConfig.compilerOptions.outDir.replace(remoteOptions.compiledTypesFolder, "")); | ||
| const retrieveOriginalOutDir = (tsConfig, remoteOptions) => (0, path.normalize)(tsConfig.compilerOptions.outDir.replace(remoteOptions.compiledTypesFolder, "").replace(remoteOptions.typesFolder, "")); | ||
| const retrieveMfAPITypesPath = (tsConfig, remoteOptions) => (0, path.join)(retrieveOriginalOutDir(tsConfig, remoteOptions), `${remoteOptions.typesFolder}.d.ts`); | ||
| function writeTempTsConfig(tsConfig, context, name, cwd) { | ||
| const createHash = (contents) => { | ||
| return crypto.default.createHash("md5").update(contents).digest("hex"); | ||
| }; | ||
| const hash = createHash(`${JSON.stringify(tsConfig)}${name}${Date.now()}`); | ||
| const tempTsConfigJsonPath = (0, path.resolve)(cwd ?? context, "node_modules", _module_federation_sdk.TEMP_DIR, `tsconfig.${hash}.json`); | ||
| (0, fs.mkdirSync)((0, path.dirname)(tempTsConfigJsonPath), { recursive: true }); | ||
| (0, fs.writeFileSync)(tempTsConfigJsonPath, JSON.stringify(tsConfig, null, 2)); | ||
| return tempTsConfigJsonPath; | ||
| } | ||
| const removeExt = (f) => { | ||
| const vueExt = ".vue"; | ||
| const ext = (0, path.extname)(f); | ||
| if (ext === vueExt) return f; | ||
| const regexPattern = new RegExp(`\\${ext}$`); | ||
| return f.replace(regexPattern, ""); | ||
| }; | ||
| function getExposeKey(options) { | ||
| const { filePath, rootDir, outDir, mapExposeToEntry } = options; | ||
| return mapExposeToEntry[(0, path.relative)(outDir, filePath.replace(new RegExp(`\\.d.ts$`), ""))]; | ||
| } | ||
| const processTypesFile = async (options) => { | ||
| const { outDir, filePath, rootDir, cb, mapExposeToEntry, mfTypePath } = options; | ||
| if (!(0, fs.existsSync)(filePath)) return; | ||
| if ((await (0, fs_promises.stat)(filePath)).isDirectory()) { | ||
| const files = await (0, fs_promises.readdir)(filePath); | ||
| await Promise.all(files.map((file) => processTypesFile({ | ||
| ...options, | ||
| filePath: (0, path.join)(filePath, file) | ||
| }))); | ||
| } else if (filePath.endsWith(".d.ts")) { | ||
| const exposeKey = getExposeKey({ | ||
| filePath, | ||
| rootDir, | ||
| outDir, | ||
| mapExposeToEntry | ||
| }); | ||
| if (exposeKey) { | ||
| const mfeTypeEntry = (0, path.join)(mfTypePath, `${exposeKey === "." ? "index" : exposeKey}${DEFINITION_FILE_EXTENSION}`); | ||
| const mfeTypeEntryDirectory = (0, path.dirname)(mfeTypeEntry); | ||
| const relativePathToOutput = (0, path.relative)(mfeTypeEntryDirectory, filePath).replace(DEFINITION_FILE_EXTENSION, "").replace(STARTS_WITH_SLASH, "").split(path.sep).join("/"); | ||
| (0, fs.mkdirSync)(mfeTypeEntryDirectory, { recursive: true }); | ||
| await (0, fs_promises.writeFile)(mfeTypeEntry, `export * from './${relativePathToOutput}';\nexport { default } from './${relativePathToOutput}';`); | ||
| } | ||
| cb(await (0, fs_promises.readFile)(filePath, "utf8")); | ||
| } | ||
| }; | ||
| const getPMFromUserAgent = () => { | ||
| const userAgent = process.env["npm_config_user_agent"]; | ||
| if (userAgent == null) return "null"; | ||
| return userAgent.split("/")[0]; | ||
| }; | ||
| const resolvePackageManagerExecutable = () => { | ||
| switch (getPMFromUserAgent()) { | ||
| case "yarn": return "yarn"; | ||
| default: return "npx"; | ||
| } | ||
| }; | ||
| const splitCommandArgs = (value) => { | ||
| const args = []; | ||
| let current = ""; | ||
| let quote = null; | ||
| let escaped = false; | ||
| for (const char of value) { | ||
| if (escaped) { | ||
| current += char; | ||
| escaped = false; | ||
| continue; | ||
| } | ||
| if (char === "\\") { | ||
| escaped = true; | ||
| continue; | ||
| } | ||
| if (quote) { | ||
| if (char === quote) quote = null; | ||
| else current += char; | ||
| continue; | ||
| } | ||
| if (char === "\"" || char === "'") { | ||
| quote = char; | ||
| continue; | ||
| } | ||
| if (char.trim() === "") { | ||
| if (current) { | ||
| args.push(current); | ||
| current = ""; | ||
| } | ||
| continue; | ||
| } | ||
| current += char; | ||
| } | ||
| if (current) args.push(current); | ||
| return args; | ||
| }; | ||
| const formatCommandForDisplay = (executable, args) => { | ||
| const formatArg = (arg) => { | ||
| if (/[\s'"]/.test(arg)) return JSON.stringify(arg); | ||
| return arg; | ||
| }; | ||
| return [executable, ...args].map(formatArg).join(" "); | ||
| }; | ||
| const compileTs = async (mapComponentsToExpose, tsConfig, remoteOptions) => { | ||
| if (!Object.keys(mapComponentsToExpose).length) return; | ||
| const { compilerOptions } = tsConfig; | ||
| const tempTsConfigJsonPath = writeTempTsConfig(tsConfig, remoteOptions.context, remoteOptions.moduleFederationConfig.name || "mf", typeof remoteOptions.moduleFederationConfig.dts !== "boolean" ? remoteOptions.moduleFederationConfig.dts?.cwd ?? void 0 : void 0); | ||
| require_Broker.logger.debug(`tempTsConfigJsonPath: ${tempTsConfigJsonPath}`); | ||
| try { | ||
| const mfTypePath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| const thirdPartyExtractor = new _module_federation_third_party_dts_extractor.ThirdPartyExtractor({ | ||
| destDir: (0, path.resolve)(mfTypePath, "node_modules"), | ||
| context: remoteOptions.context, | ||
| exclude: typeof remoteOptions.extractThirdParty === "object" ? remoteOptions.extractThirdParty.exclude : void 0 | ||
| }); | ||
| const execPromise = util.default.promisify(child_process.execFile); | ||
| const pmExecutable = resolvePackageManagerExecutable(); | ||
| const compilerArgs = splitCommandArgs(remoteOptions.compilerInstance); | ||
| const cmdArgs = [ | ||
| ...compilerArgs.length > 0 ? compilerArgs : [remoteOptions.compilerInstance], | ||
| "--project", | ||
| tempTsConfigJsonPath | ||
| ]; | ||
| const cmd = formatCommandForDisplay(pmExecutable, cmdArgs); | ||
| try { | ||
| await execPromise(pmExecutable, cmdArgs, { | ||
| cwd: typeof remoteOptions.moduleFederationConfig.dts !== "boolean" ? remoteOptions.moduleFederationConfig.dts?.cwd ?? void 0 : void 0, | ||
| shell: process.platform === "win32" | ||
| }); | ||
| } catch (err) { | ||
| if (compilerOptions.tsBuildInfoFile) try { | ||
| await (0, fs_promises.rm)(compilerOptions.tsBuildInfoFile); | ||
| } catch (e) {} | ||
| (0, _module_federation_error_codes_node.logAndReport)(_module_federation_error_codes.TYPE_001, _module_federation_error_codes.typeDescMap, { cmd }, (msg) => { | ||
| throw new Error(msg); | ||
| }, void 0); | ||
| } | ||
| const mapExposeToEntry = Object.fromEntries(Object.entries(mapComponentsToExpose).map(([exposed, filename]) => { | ||
| const normalizedFileName = (0, path.normalize)(filename); | ||
| let relativeFileName = ""; | ||
| if ((0, path.isAbsolute)(normalizedFileName)) relativeFileName = (0, path.relative)(tsConfig.compilerOptions.rootDir, normalizedFileName); | ||
| else relativeFileName = (0, path.relative)(tsConfig.compilerOptions.rootDir, (0, path.resolve)(remoteOptions.context, normalizedFileName)); | ||
| return [removeExt(relativeFileName), exposed]; | ||
| })); | ||
| const cb = remoteOptions.extractThirdParty ? thirdPartyExtractor.collectPkgs.bind(thirdPartyExtractor) : () => void 0; | ||
| await processTypesFile({ | ||
| outDir: compilerOptions.outDir, | ||
| filePath: compilerOptions.outDir, | ||
| rootDir: compilerOptions.rootDir, | ||
| mfTypePath, | ||
| cb, | ||
| mapExposeToEntry | ||
| }); | ||
| if (remoteOptions.extractThirdParty) await thirdPartyExtractor.copyDts(); | ||
| if (remoteOptions.deleteTsConfig) await (0, fs_promises.rm)(tempTsConfigJsonPath); | ||
| } catch (err) { | ||
| throw err; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/archiveHandler.ts | ||
| const retrieveTypesZipPath = (mfTypesPath, remoteOptions) => (0, path.join)(mfTypesPath.replace(remoteOptions.typesFolder, ""), `${remoteOptions.typesFolder}.zip`); | ||
| const createTypesArchive = async (tsConfig, remoteOptions) => { | ||
| const mfTypesPath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| const zip = new adm_zip.default(); | ||
| zip.addLocalFolder(mfTypesPath); | ||
| return zip.writeZipPromise(retrieveTypesZipPath(mfTypesPath, remoteOptions)); | ||
| }; | ||
| const downloadErrorLogger = (destinationFolder, fileToDownload) => (reason) => { | ||
| throw { | ||
| ...reason, | ||
| message: `Network error: Unable to download federated mocks for '${destinationFolder}' from '${fileToDownload}' because '${reason.message}'` | ||
| }; | ||
| }; | ||
| const retrieveTypesArchiveDestinationPath = (hostOptions, destinationFolder) => { | ||
| return (0, path.resolve)(hostOptions.context, hostOptions.typesFolder, destinationFolder); | ||
| }; | ||
| const downloadTypesArchive = (hostOptions) => { | ||
| let retries = 0; | ||
| return async ([destinationFolder, fileToDownload]) => { | ||
| const destinationPath = retrieveTypesArchiveDestinationPath(hostOptions, destinationFolder); | ||
| while (retries++ < hostOptions.maxRetries) try { | ||
| const url = new URL(fileToDownload).href; | ||
| const response = await nativeFetch(url, { | ||
| responseType: "arraybuffer", | ||
| timeout: hostOptions.timeout, | ||
| family: hostOptions.family | ||
| }).catch(downloadErrorLogger(destinationFolder, url)); | ||
| if (typeof response.headers?.["content-type"] === "string" && response.headers["content-type"].includes("text/html")) throw new Error(`${url} receives invalid content-type: ${response.headers["content-type"]}`); | ||
| try { | ||
| if (hostOptions.deleteTypesFolder) await (0, fs_promises.rm)(destinationPath, { | ||
| recursive: true, | ||
| force: true | ||
| }); | ||
| } catch (error) { | ||
| require_Broker.fileLog(`Unable to remove types folder, ${error}`, "downloadTypesArchive", "error"); | ||
| } | ||
| new adm_zip.default(Buffer.from(response.data)).extractAllTo(destinationPath, true); | ||
| require_Broker.fileLog(`zip.extractAllTo success destinationPath: ${destinationPath}; url: ${url}`, "downloadTypesArchive", "info"); | ||
| return [destinationFolder, destinationPath]; | ||
| } catch (error) { | ||
| require_Broker.fileLog(`Error during types archive download: ${error?.message || "unknown error"}`, "downloadTypesArchive", "error"); | ||
| if (retries >= hostOptions.maxRetries) { | ||
| require_Broker.logger.error(`Failed to download types archive from "${fileToDownload}". Set FEDERATION_DEBUG=true for details.`); | ||
| if (hostOptions.abortOnError !== false) throw error; | ||
| return; | ||
| } | ||
| } | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/core/configurations/hostPlugin.ts | ||
| const defaultOptions$1 = { | ||
| typesFolder: "@mf-types", | ||
| remoteTypesFolder: "@mf-types", | ||
| deleteTypesFolder: true, | ||
| maxRetries: 3, | ||
| implementation: "", | ||
| context: process.cwd(), | ||
| abortOnError: true, | ||
| consumeAPITypes: false, | ||
| runtimePkgs: [], | ||
| remoteTypeUrls: {}, | ||
| timeout: 6e4, | ||
| typesOnBuild: false, | ||
| family: 0 | ||
| }; | ||
| const buildZipUrl = (hostOptions, url) => { | ||
| const remoteUrl = new URL(url, "file:"); | ||
| remoteUrl.pathname = `${remoteUrl.pathname.split("/").slice(0, -1).join("/")}/${hostOptions.remoteTypesFolder}.zip`; | ||
| return remoteUrl.protocol === "file:" ? remoteUrl.pathname : remoteUrl.href; | ||
| }; | ||
| const buildApiTypeUrl = (zipUrl) => { | ||
| if (!zipUrl) return; | ||
| return zipUrl.replace(".zip", ".d.ts"); | ||
| }; | ||
| const retrieveRemoteInfo = (options) => { | ||
| const { hostOptions, remoteAlias, remote } = options; | ||
| const { remoteTypeUrls } = hostOptions; | ||
| let decodedRemote = remote; | ||
| if (decodedRemote.startsWith(_module_federation_sdk.ENCODE_NAME_PREFIX)) decodedRemote = (0, _module_federation_sdk.decodeName)(decodedRemote, _module_federation_sdk.ENCODE_NAME_PREFIX); | ||
| const parsedInfo = (0, _module_federation_sdk.parseEntry)(decodedRemote, void 0, "@"); | ||
| const url = "entry" in parsedInfo ? parsedInfo.entry : parsedInfo.name === decodedRemote ? decodedRemote : ""; | ||
| let zipUrl = ""; | ||
| let apiTypeUrl = ""; | ||
| const name = parsedInfo.name || remoteAlias; | ||
| const remoteTypeUrl = typeof remoteTypeUrls === "object" && remoteTypeUrls[name]; | ||
| if (remoteTypeUrl) { | ||
| zipUrl = remoteTypeUrl.zip; | ||
| apiTypeUrl = remoteTypeUrl.api; | ||
| } | ||
| const shouldResolveTypeUrlsByConvention = Boolean(url && !url.includes(_module_federation_sdk.MANIFEST_EXT)); | ||
| if (!zipUrl && shouldResolveTypeUrlsByConvention) zipUrl = buildZipUrl(hostOptions, url); | ||
| if (!apiTypeUrl && zipUrl && (remoteTypeUrl || shouldResolveTypeUrlsByConvention)) apiTypeUrl = buildApiTypeUrl(zipUrl); | ||
| return { | ||
| name, | ||
| url, | ||
| zipUrl, | ||
| apiTypeUrl, | ||
| alias: remoteAlias | ||
| }; | ||
| }; | ||
| const resolveRemotes = (hostOptions) => { | ||
| const parsedOptions = _module_federation_managers.utils.parseOptions(hostOptions.moduleFederationConfig.remotes || {}, (item, key) => ({ | ||
| remote: Array.isArray(item) ? item[0] : item, | ||
| key | ||
| }), (item, key) => ({ | ||
| remote: Array.isArray(item.external) ? item.external[0] : item.external, | ||
| key | ||
| })); | ||
| const remoteTypeUrls = hostOptions.remoteTypeUrls ?? {}; | ||
| if (typeof remoteTypeUrls !== "object") throw new Error("remoteTypeUrls must be consumed before resolveRemotes"); | ||
| const remoteInfos = Object.keys(remoteTypeUrls).reduce((sum, remoteName) => { | ||
| const { zip, api, alias } = remoteTypeUrls[remoteName]; | ||
| sum[alias] = { | ||
| name: remoteName, | ||
| url: "", | ||
| zipUrl: zip, | ||
| apiTypeUrl: api, | ||
| alias: alias || remoteName | ||
| }; | ||
| return sum; | ||
| }, {}); | ||
| return parsedOptions.reduce((accumulator, item) => { | ||
| const { key, remote } = item[1]; | ||
| const res = retrieveRemoteInfo({ | ||
| hostOptions, | ||
| remoteAlias: key, | ||
| remote | ||
| }); | ||
| if (accumulator[key]) { | ||
| accumulator[key] = { | ||
| ...accumulator[key], | ||
| url: res.url, | ||
| apiTypeUrl: accumulator[key].apiTypeUrl || res.apiTypeUrl | ||
| }; | ||
| return accumulator; | ||
| } | ||
| accumulator[key] = res; | ||
| return accumulator; | ||
| }, remoteInfos); | ||
| }; | ||
| const retrieveHostConfig = (options) => { | ||
| validateOptions(options); | ||
| const hostOptions = { | ||
| ...defaultOptions$1, | ||
| ...options | ||
| }; | ||
| return { | ||
| hostOptions, | ||
| mapRemotesToDownload: resolveRemotes(hostOptions) | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/core/constant.ts | ||
| const REMOTE_ALIAS_IDENTIFIER = "REMOTE_ALIAS_IDENTIFIER"; | ||
| const REMOTE_API_TYPES_FILE_NAME = "apis.d.ts"; | ||
| const HOST_API_TYPES_FILE_NAME = "index.d.ts"; | ||
| //#endregion | ||
| //#region src/core/lib/DTSManager.ts | ||
| var DTSManager = class { | ||
| constructor(options) { | ||
| this.options = cloneDeepOptions(options); | ||
| this.runtimePkgs = [ | ||
| "@module-federation/runtime", | ||
| "@module-federation/enhanced/runtime", | ||
| "@module-federation/runtime-tools" | ||
| ]; | ||
| this.loadedRemoteAPIAlias = /* @__PURE__ */ new Set(); | ||
| this.remoteAliasMap = {}; | ||
| this.extraOptions = options?.extraOptions || {}; | ||
| this.updatedRemoteInfos = {}; | ||
| } | ||
| generateAPITypes(mapComponentsToExpose) { | ||
| const exposePaths = /* @__PURE__ */ new Set(); | ||
| const packageType = Object.keys(mapComponentsToExpose).reduce((sum, exposeKey) => { | ||
| const exposePath = path.default.join(REMOTE_ALIAS_IDENTIFIER, exposeKey).split(path.default.sep).join("/"); | ||
| exposePaths.add(`'${exposePath}'`); | ||
| sum = `T extends '${exposePath}' ? typeof import('${exposePath}') :` + sum; | ||
| return sum; | ||
| }, "any;"); | ||
| return ` | ||
| export type RemoteKeys = ${[...exposePaths].join(" | ")}; | ||
| type PackageType<T> = ${packageType}`; | ||
| } | ||
| async extractRemoteTypes(options) { | ||
| const { remoteOptions, tsConfig } = options; | ||
| if (!remoteOptions.extractRemoteTypes) return; | ||
| let hasRemotes = false; | ||
| const remotes = remoteOptions.moduleFederationConfig.remotes; | ||
| if (remotes) { | ||
| if (Array.isArray(remotes)) hasRemotes = Boolean(remotes.length); | ||
| else if (typeof remotes === "object") hasRemotes = Boolean(Object.keys(remotes).length); | ||
| } | ||
| const mfTypesPath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| if (hasRemotes && this.options.host) try { | ||
| const { hostOptions } = retrieveHostConfig(this.options.host); | ||
| const remoteTypesFolder = path.default.resolve(hostOptions.context, hostOptions.typesFolder); | ||
| const targetDir = path.default.join(mfTypesPath, "node_modules"); | ||
| if (fs.default.existsSync(remoteTypesFolder)) { | ||
| const targetFolder = path.default.resolve(remoteOptions.context, targetDir); | ||
| await (0, fs_promises.mkdir)(targetFolder, { recursive: true }); | ||
| await (0, fs_promises.cp)(remoteTypesFolder, targetFolder, { | ||
| recursive: true, | ||
| force: true | ||
| }); | ||
| } | ||
| } catch (err) { | ||
| if (this.options.host?.abortOnError === false) require_Broker.fileLog(`Unable to copy remote types, ${err}`, "extractRemoteTypes", "error"); | ||
| else throw err; | ||
| } | ||
| } | ||
| async generateTypes() { | ||
| try { | ||
| const { options } = this; | ||
| if (!options.remote) throw new Error("options.remote is required if you want to generateTypes"); | ||
| const { remoteOptions, tsConfig, mapComponentsToExpose } = retrieveRemoteConfig(options.remote); | ||
| if (!Object.keys(mapComponentsToExpose).length) return; | ||
| if (!tsConfig.files?.length) { | ||
| require_Broker.logger.info("No type files to compile, skip"); | ||
| return; | ||
| } | ||
| if (tsConfig.compilerOptions.tsBuildInfoFile) try { | ||
| const tsBuildInfoFile = path.default.resolve(remoteOptions.context, tsConfig.compilerOptions.tsBuildInfoFile); | ||
| const mfTypesPath = retrieveMfTypesPath(tsConfig, remoteOptions); | ||
| if (!fs.default.existsSync(mfTypesPath)) fs.default.rmSync(tsBuildInfoFile, { force: true }); | ||
| } catch (e) {} | ||
| await this.extractRemoteTypes({ | ||
| remoteOptions, | ||
| tsConfig, | ||
| mapComponentsToExpose | ||
| }); | ||
| await compileTs(mapComponentsToExpose, tsConfig, remoteOptions); | ||
| await createTypesArchive(tsConfig, remoteOptions); | ||
| let apiTypesPath = ""; | ||
| if (remoteOptions.generateAPITypes) { | ||
| const apiTypes = this.generateAPITypes(mapComponentsToExpose); | ||
| apiTypesPath = retrieveMfAPITypesPath(tsConfig, remoteOptions); | ||
| fs.default.writeFileSync(apiTypesPath, apiTypes); | ||
| } | ||
| try { | ||
| if (remoteOptions.deleteTypesFolder) await (0, fs_promises.rm)(retrieveMfTypesPath(tsConfig, remoteOptions), { | ||
| recursive: true, | ||
| force: true | ||
| }); | ||
| } catch (err) { | ||
| if (isDebugMode()) console.error(err); | ||
| } | ||
| require_Broker.logger.success("Federated types created correctly"); | ||
| } catch (error) { | ||
| if (this.options.remote?.abortOnError === false) { | ||
| if (this.options.displayErrorInTerminal) require_Broker.logger.error(error); | ||
| } else throw error; | ||
| } | ||
| } | ||
| async requestRemoteManifest(remoteInfo, hostOptions) { | ||
| try { | ||
| if (!remoteInfo.url.includes(_module_federation_sdk.MANIFEST_EXT)) return remoteInfo; | ||
| if (remoteInfo.zipUrl) return remoteInfo; | ||
| const url = remoteInfo.url; | ||
| const manifestJson = (await nativeFetch(url, { | ||
| timeout: hostOptions.timeout, | ||
| family: hostOptions.family | ||
| })).data; | ||
| if (!manifestJson.metaData.types.zip) throw new Error(`Can not get ${remoteInfo.name}'s types archive url!`); | ||
| const addProtocol = (u) => { | ||
| if (u.startsWith("//")) return `https:${u}`; | ||
| return u; | ||
| }; | ||
| let publicPath; | ||
| if ("publicPath" in manifestJson.metaData) publicPath = manifestJson.metaData.publicPath; | ||
| else { | ||
| const getPublicPath = new Function(manifestJson.metaData.getPublicPath); | ||
| if (manifestJson.metaData.getPublicPath.startsWith("function")) publicPath = getPublicPath()(); | ||
| else publicPath = getPublicPath(); | ||
| } | ||
| if (publicPath === "auto") publicPath = (0, _module_federation_sdk.inferAutoPublicPath)(remoteInfo.url); | ||
| const normalizedPublicPath = addProtocol(publicPath).endsWith("/") ? addProtocol(publicPath) : `${addProtocol(publicPath)}/`; | ||
| remoteInfo.zipUrl = new URL(manifestJson.metaData.types.zip, normalizedPublicPath).href; | ||
| if (!manifestJson.metaData.types.api) { | ||
| console.warn(`Can not get ${remoteInfo.name}'s api types url!`); | ||
| remoteInfo.apiTypeUrl = ""; | ||
| return remoteInfo; | ||
| } | ||
| remoteInfo.apiTypeUrl = new URL(manifestJson.metaData.types.api, normalizedPublicPath).href; | ||
| return remoteInfo; | ||
| } catch (_err) { | ||
| require_Broker.fileLog(`fetch manifest failed, ${_err}, ${remoteInfo.name} will be ignored`, "requestRemoteManifest", "error"); | ||
| return remoteInfo; | ||
| } | ||
| } | ||
| async consumeTargetRemotes(hostOptions, remoteInfo) { | ||
| if (!remoteInfo.zipUrl) throw new Error(`Can not get ${remoteInfo.name}'s types archive url!`); | ||
| return downloadTypesArchive(hostOptions)([remoteInfo.alias, remoteInfo.zipUrl]); | ||
| } | ||
| async downloadAPITypes(remoteInfo, destinationPath, hostOptions) { | ||
| const { apiTypeUrl } = remoteInfo; | ||
| if (!apiTypeUrl) return; | ||
| try { | ||
| let apiTypeFile = (await nativeFetch(apiTypeUrl, { | ||
| timeout: hostOptions.timeout, | ||
| family: hostOptions.family | ||
| })).data; | ||
| apiTypeFile = apiTypeFile.replaceAll(REMOTE_ALIAS_IDENTIFIER, remoteInfo.alias); | ||
| const filePath = path.default.join(destinationPath, REMOTE_API_TYPES_FILE_NAME); | ||
| fs.default.writeFileSync(filePath, apiTypeFile); | ||
| const existed = this.loadedRemoteAPIAlias.has(remoteInfo.alias); | ||
| this.loadedRemoteAPIAlias.add(remoteInfo.alias); | ||
| require_Broker.fileLog(`success`, "downloadAPITypes", "info"); | ||
| return existed; | ||
| } catch (err) { | ||
| require_Broker.fileLog(`Unable to download "${remoteInfo.name}" api types, ${err}`, "downloadAPITypes", "error"); | ||
| } | ||
| } | ||
| consumeAPITypes(hostOptions) { | ||
| const apiTypeFileName = path.default.join(hostOptions.context, hostOptions.typesFolder, HOST_API_TYPES_FILE_NAME); | ||
| try { | ||
| const existedFile = fs.default.readFileSync(apiTypeFileName, "utf-8"); | ||
| new _module_federation_third_party_dts_extractor.ThirdPartyExtractor({ destDir: "" }).collectTypeImports(existedFile).forEach((existedImport) => { | ||
| const alias = existedImport.split("./").slice(1).join("./").replace("/apis.d.ts", ""); | ||
| this.loadedRemoteAPIAlias.add(alias); | ||
| }); | ||
| } catch (err) {} | ||
| if (!this.loadedRemoteAPIAlias.size) return; | ||
| const packageTypes = []; | ||
| const remoteKeys = []; | ||
| const importTypeStr = [...this.loadedRemoteAPIAlias].sort().map((alias, index) => { | ||
| const remoteKey = `RemoteKeys_${index}`; | ||
| const packageType = `PackageType_${index}`; | ||
| packageTypes.push(`T extends ${remoteKey} ? ${packageType}<T>`); | ||
| remoteKeys.push(remoteKey); | ||
| return `import type { PackageType as ${packageType},RemoteKeys as ${remoteKey} } from './${alias}/apis.d.ts';`; | ||
| }).join("\n"); | ||
| const remoteKeysStr = `type RemoteKeys = ${remoteKeys.join(" | ")};`; | ||
| const packageTypesStr = `type PackageType<T, Y=any> = ${[...packageTypes, "Y"].join(" :\n")} ;`; | ||
| const runtimePkgs = /* @__PURE__ */ new Set(); | ||
| [...this.runtimePkgs, ...hostOptions.runtimePkgs].forEach((pkg) => { | ||
| runtimePkgs.add(pkg); | ||
| }); | ||
| const fileStr = `${importTypeStr} | ||
| ${[...runtimePkgs].map((pkg) => { | ||
| return `declare module "${pkg}" { | ||
| ${remoteKeysStr} | ||
| ${packageTypesStr} | ||
| export function loadRemote<T extends RemoteKeys,Y>(packageName: T): Promise<PackageType<T, Y>>; | ||
| export function loadRemote<T extends string,Y>(packageName: T): Promise<PackageType<T, Y>>; | ||
| }`; | ||
| }).join("\n")} | ||
| `; | ||
| fs.default.writeFileSync(path.default.join(hostOptions.context, hostOptions.typesFolder, HOST_API_TYPES_FILE_NAME), fileStr); | ||
| } | ||
| async consumeArchiveTypes(options) { | ||
| const { hostOptions, mapRemotesToDownload } = retrieveHostConfig(options); | ||
| const downloadPromises = Object.entries(mapRemotesToDownload).map(async (item) => { | ||
| const remoteInfo = item[1]; | ||
| if (!this.remoteAliasMap[remoteInfo.alias]) { | ||
| const requiredRemoteInfo = await this.requestRemoteManifest(remoteInfo, hostOptions); | ||
| this.remoteAliasMap[remoteInfo.alias] = requiredRemoteInfo; | ||
| } | ||
| return this.consumeTargetRemotes(hostOptions, this.remoteAliasMap[remoteInfo.alias]); | ||
| }); | ||
| return { | ||
| hostOptions, | ||
| downloadPromisesResult: await Promise.allSettled(downloadPromises) | ||
| }; | ||
| } | ||
| async consumeTypes() { | ||
| try { | ||
| const { options } = this; | ||
| if (!options.host) throw new Error("options.host is required if you want to consumeTypes"); | ||
| const { mapRemotesToDownload } = retrieveHostConfig(options.host); | ||
| if (!Object.keys(mapRemotesToDownload).length) return; | ||
| const { downloadPromisesResult, hostOptions } = await this.consumeArchiveTypes(options.host); | ||
| if (hostOptions.consumeAPITypes) { | ||
| await Promise.all(downloadPromisesResult.map(async (item) => { | ||
| if (item.status === "rejected" || !item.value) return; | ||
| const [alias, destinationPath] = item.value; | ||
| const remoteInfo = this.remoteAliasMap[alias]; | ||
| if (!remoteInfo) return; | ||
| await this.downloadAPITypes(remoteInfo, destinationPath, hostOptions); | ||
| })); | ||
| this.consumeAPITypes(hostOptions); | ||
| } | ||
| require_Broker.logger.success("Federated types extraction completed"); | ||
| } catch (err) { | ||
| if (this.options.host?.abortOnError === false) require_Broker.fileLog(`Unable to consume federated types, ${err}`, "consumeTypes", "error"); | ||
| else throw err; | ||
| } | ||
| } | ||
| async updateTypes(options) { | ||
| try { | ||
| const { remoteName, updateMode, remoteTarPath, remoteInfo: updatedRemoteInfo, once } = options; | ||
| const hostName = this.options?.host?.moduleFederationConfig?.name; | ||
| require_Broker.fileLog(`options: ${JSON.stringify(options, null, 2)};\nhostName: ${hostName}`, "updateTypes", "info"); | ||
| if (updateMode === require_Action.UpdateMode.POSITIVE && remoteName === hostName) { | ||
| if (!this.options.remote) return; | ||
| await this.generateTypes(); | ||
| } else { | ||
| const { remoteAliasMap } = this; | ||
| if (!this.options.host) return; | ||
| const { hostOptions, mapRemotesToDownload } = retrieveHostConfig(this.options.host); | ||
| const loadedRemoteInfo = Object.values(remoteAliasMap).find((i) => i.name === remoteName); | ||
| const consumeTypes = async (requiredRemoteInfo) => { | ||
| require_Broker.fileLog(`consumeTypes start`, "updateTypes", "info"); | ||
| if (!requiredRemoteInfo.zipUrl) throw new Error(`Can not get ${requiredRemoteInfo.name}'s types archive url!`); | ||
| const [_alias, destinationPath] = await this.consumeTargetRemotes(hostOptions, { | ||
| ...requiredRemoteInfo, | ||
| zipUrl: remoteTarPath || requiredRemoteInfo.zipUrl | ||
| }); | ||
| if (await this.downloadAPITypes(requiredRemoteInfo, destinationPath, hostOptions)) this.consumeAPITypes(hostOptions); | ||
| require_Broker.fileLog(`consumeTypes end`, "updateTypes", "info"); | ||
| }; | ||
| require_Broker.fileLog(`loadedRemoteInfo: ${JSON.stringify(loadedRemoteInfo, null, 2)}`, "updateTypes", "info"); | ||
| if (!loadedRemoteInfo) { | ||
| const remoteInfo = Object.values(mapRemotesToDownload).find((item) => { | ||
| return item.name === remoteName; | ||
| }); | ||
| require_Broker.fileLog(`remoteInfo: ${JSON.stringify(remoteInfo, null, 2)}`, "updateTypes", "info"); | ||
| if (remoteInfo) { | ||
| if (!this.remoteAliasMap[remoteInfo.alias]) { | ||
| const requiredRemoteInfo = await this.requestRemoteManifest(remoteInfo, hostOptions); | ||
| this.remoteAliasMap[remoteInfo.alias] = requiredRemoteInfo; | ||
| } | ||
| await consumeTypes(this.remoteAliasMap[remoteInfo.alias]); | ||
| } else if (updatedRemoteInfo) { | ||
| const consumeDynamicRemoteTypes = async () => { | ||
| await consumeTypes(this.updatedRemoteInfos[updatedRemoteInfo.name]); | ||
| }; | ||
| if (!this.updatedRemoteInfos[updatedRemoteInfo.name]) { | ||
| const parsedRemoteInfo = retrieveRemoteInfo({ | ||
| hostOptions, | ||
| remoteAlias: updatedRemoteInfo.alias || updatedRemoteInfo.name, | ||
| remote: updatedRemoteInfo.url | ||
| }); | ||
| require_Broker.fileLog(`start request manifest`, "consumeTypes", "info"); | ||
| this.updatedRemoteInfos[updatedRemoteInfo.name] = await this.requestRemoteManifest(parsedRemoteInfo, hostOptions); | ||
| require_Broker.fileLog(`end request manifest, this.updatedRemoteInfos[updatedRemoteInfo.name]: ${JSON.stringify(this.updatedRemoteInfos[updatedRemoteInfo.name], null, 2)}`, "updateTypes", "info"); | ||
| await consumeDynamicRemoteTypes(); | ||
| } | ||
| if (!once && this.updatedRemoteInfos[updatedRemoteInfo.name]) await consumeDynamicRemoteTypes(); | ||
| } | ||
| } else await consumeTypes(loadedRemoteInfo); | ||
| } | ||
| } catch (err) { | ||
| require_Broker.fileLog(`updateTypes fail, ${err}`, "updateTypes", "error"); | ||
| } | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/utils.ts | ||
| const dispatcherCache = /* @__PURE__ */ new Map(); | ||
| function getDTSManagerConstructor(implementation) { | ||
| if (implementation) { | ||
| const NewConstructor = require(implementation); | ||
| return NewConstructor.default ? NewConstructor.default : NewConstructor; | ||
| } | ||
| return DTSManager; | ||
| } | ||
| const validateOptions = (options) => { | ||
| if (!options.moduleFederationConfig) throw new Error("moduleFederationConfig is required"); | ||
| }; | ||
| function retrieveTypesAssetsInfo(options) { | ||
| let apiTypesPath = ""; | ||
| let zipTypesPath = ""; | ||
| try { | ||
| const { tsConfig, remoteOptions, mapComponentsToExpose } = retrieveRemoteConfig(options); | ||
| if (!Object.keys(mapComponentsToExpose).length || !tsConfig.files.length) return { | ||
| apiTypesPath, | ||
| zipTypesPath, | ||
| zipName: "", | ||
| apiFileName: "" | ||
| }; | ||
| zipTypesPath = retrieveTypesZipPath(retrieveMfTypesPath(tsConfig, remoteOptions), remoteOptions); | ||
| if (remoteOptions.generateAPITypes) apiTypesPath = retrieveMfAPITypesPath(tsConfig, remoteOptions); | ||
| return { | ||
| apiTypesPath, | ||
| zipTypesPath, | ||
| zipName: path.default.basename(zipTypesPath), | ||
| apiFileName: path.default.basename(apiTypesPath) | ||
| }; | ||
| } catch (err) { | ||
| console.error(ansi_colors.default.red(`Unable to compile federated types, ${err}`)); | ||
| return { | ||
| apiTypesPath: "", | ||
| zipTypesPath: "", | ||
| zipName: "", | ||
| apiFileName: "" | ||
| }; | ||
| } | ||
| } | ||
| function isDebugMode() { | ||
| return Boolean(process.env["FEDERATION_DEBUG"]) || process.env["NODE_ENV"] === "test"; | ||
| } | ||
| const isTSProject = (dtsOptions, context = process.cwd()) => { | ||
| if (dtsOptions === false) return false; | ||
| try { | ||
| let filepath = ""; | ||
| if (typeof dtsOptions === "object" && dtsOptions.tsConfigPath) filepath = dtsOptions.tsConfigPath; | ||
| else filepath = path.default.resolve(context, "./tsconfig.json"); | ||
| if (!path.default.isAbsolute(filepath)) filepath = path.default.resolve(context, filepath); | ||
| return fs.default.existsSync(filepath); | ||
| } catch (err) { | ||
| return false; | ||
| } | ||
| }; | ||
| function cloneDeepOptions(options) { | ||
| const excludeKeys = new Set(["manifest", "async"]); | ||
| const cache = /* @__PURE__ */ new WeakMap(); | ||
| function sanitize(val, key) { | ||
| if (key !== void 0 && excludeKeys.has(key) || typeof val === "function") return false; | ||
| if (key === "extractThirdParty" && Array.isArray(val)) return val.map(String); | ||
| if (Array.isArray(val)) { | ||
| if (cache.has(val)) return cache.get(val); | ||
| const out = []; | ||
| cache.set(val, out); | ||
| val.forEach((v, i) => out.push(sanitize(v, String(i)))); | ||
| return out; | ||
| } | ||
| if (val !== null && typeof val === "object" && Object.getPrototypeOf(val) === Object.prototype) { | ||
| const obj = val; | ||
| if (cache.has(obj)) return cache.get(obj); | ||
| const out = {}; | ||
| cache.set(obj, out); | ||
| for (const [k, v] of Object.entries(obj)) out[k] = sanitize(v, k); | ||
| return out; | ||
| } | ||
| return val; | ||
| } | ||
| return structuredClone(sanitize(options)); | ||
| } | ||
| const getEnvHeaders = () => { | ||
| const headersStr = (0, _module_federation_sdk.getProcessEnv)()["MF_ENV_HEADERS"]; | ||
| if (!headersStr || headersStr === "undefined") return {}; | ||
| try { | ||
| return { ...JSON.parse(headersStr) }; | ||
| } catch { | ||
| return {}; | ||
| } | ||
| }; | ||
| const createDispatcherFromFamily = (family) => { | ||
| if (!family) return void 0; | ||
| if (dispatcherCache.has(family)) return dispatcherCache.get(family); | ||
| try { | ||
| const dispatcher = new undici.Agent({ connect: { family } }); | ||
| dispatcherCache.set(family, dispatcher); | ||
| return dispatcher; | ||
| } catch {} | ||
| }; | ||
| const toHeaderRecord = (headers) => { | ||
| const out = {}; | ||
| headers.forEach((value, key) => { | ||
| out[key.toLowerCase()] = value; | ||
| }); | ||
| return out; | ||
| }; | ||
| async function nativeFetch(url, config) { | ||
| const controller = new AbortController(); | ||
| const timeoutMs = config?.timeout ?? 6e4; | ||
| const timeoutId = setTimeout(() => controller.abort(), timeoutMs); | ||
| const headers = { | ||
| ...getEnvHeaders(), | ||
| ...config?.headers ?? {} | ||
| }; | ||
| const dispatcher = config?.dispatcher ?? createDispatcherFromFamily(config?.family); | ||
| try { | ||
| const resp = await fetch(url, { | ||
| headers, | ||
| signal: controller.signal, | ||
| ...dispatcher ? { dispatcher } : {}, | ||
| ...config?.agent ? { agent: config.agent } : {} | ||
| }); | ||
| const headerRecord = toHeaderRecord(resp.headers); | ||
| if (!resp.ok) throw new Error(`Request failed with status ${resp.status}`); | ||
| if (config?.responseType === "arraybuffer") return { | ||
| data: await resp.arrayBuffer(), | ||
| headers: headerRecord, | ||
| status: resp.status | ||
| }; | ||
| return { | ||
| data: (resp.headers.get("content-type") || "").includes("application/json") || url.endsWith(".json") ? await resp.json() : await resp.text(), | ||
| headers: headerRecord, | ||
| status: resp.status | ||
| }; | ||
| } finally { | ||
| clearTimeout(timeoutId); | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/core/configurations/remotePlugin.ts | ||
| const defaultOptions = { | ||
| tsConfigPath: "./tsconfig.json", | ||
| typesFolder: "@mf-types", | ||
| compiledTypesFolder: "compiled-types", | ||
| hostRemoteTypesFolder: "@mf-types", | ||
| deleteTypesFolder: true, | ||
| additionalFilesToCompile: [], | ||
| compilerInstance: "tsc", | ||
| compileInChildProcess: false, | ||
| implementation: "", | ||
| generateAPITypes: false, | ||
| context: process.cwd(), | ||
| abortOnError: true, | ||
| extractRemoteTypes: false, | ||
| extractThirdParty: false, | ||
| outputDir: "", | ||
| deleteTsConfig: true | ||
| }; | ||
| function getEffectiveRootDir(parsedCommandLine) { | ||
| const compilerOptions = parsedCommandLine.options; | ||
| if (compilerOptions.rootDir) return compilerOptions.rootDir; | ||
| const files = parsedCommandLine.fileNames; | ||
| if (files.length > 0) return files.map((file) => (0, path.dirname)(file)).reduce((commonPath, fileDir) => { | ||
| while (!fileDir.startsWith(commonPath)) commonPath = (0, path.dirname)(commonPath); | ||
| return commonPath; | ||
| }, files[0]); | ||
| if (parsedCommandLine.projectReferences.length) { | ||
| const relativeReferences = parsedCommandLine.projectReferences.filter((reference) => !(0, path.isAbsolute)(reference.originalPath ?? reference.path)); | ||
| const referencesForRoot = relativeReferences.length ? relativeReferences : parsedCommandLine.projectReferences; | ||
| return referencesForRoot.map((reference) => (0, path.dirname)(reference.path)).reduce((commonPath, filePath) => { | ||
| while (!filePath.startsWith(commonPath)) commonPath = (0, path.dirname)(commonPath); | ||
| return commonPath; | ||
| }, (0, path.dirname)(referencesForRoot[0].path)); | ||
| } | ||
| throw new Error("Can not get effective rootDir, please set compilerOptions.rootDir !"); | ||
| } | ||
| const getDependentFiles = (rootFiles, configContent, rootDir) => { | ||
| const dependentFiles = typescript.default.createProgram(rootFiles, configContent.options).getSourceFiles().map((file) => file.fileName).filter((file) => !file.endsWith(".d.ts") && file.startsWith(rootDir)); | ||
| return dependentFiles.length ? dependentFiles : rootFiles; | ||
| }; | ||
| const readTsConfig = ({ tsConfigPath, typesFolder, compiledTypesFolder, context, additionalFilesToCompile, outputDir }, mapComponentsToExpose) => { | ||
| const resolvedTsConfigPath = (0, path.resolve)(context, tsConfigPath); | ||
| const readResult = typescript.default.readConfigFile(resolvedTsConfigPath, typescript.default.sys.readFile); | ||
| if (readResult.error) throw new Error(readResult.error.messageText.toString()); | ||
| const rawTsConfigJson = readResult.config; | ||
| const configContent = typescript.default.parseJsonConfigFileContent(rawTsConfigJson, typescript.default.sys, (0, path.dirname)(resolvedTsConfigPath)); | ||
| const rootDir = getEffectiveRootDir(configContent); | ||
| const outDir = (0, path.resolve)(context, outputDir || configContent.options.outDir || "dist", typesFolder, compiledTypesFolder); | ||
| const defaultCompilerOptions = { | ||
| rootDir, | ||
| emitDeclarationOnly: true, | ||
| noEmit: false, | ||
| declaration: true, | ||
| outDir | ||
| }; | ||
| rawTsConfigJson.compilerOptions = rawTsConfigJson.compilerOptions || {}; | ||
| rawTsConfigJson.compilerOptions = { | ||
| incremental: true, | ||
| tsBuildInfoFile: (0, path.resolve)(context, "node_modules/.cache/mf-types/.tsbuildinfo"), | ||
| ...rawTsConfigJson.compilerOptions, | ||
| ...defaultCompilerOptions | ||
| }; | ||
| const { paths, baseUrl, ...restCompilerOptions } = rawTsConfigJson.compilerOptions || {}; | ||
| rawTsConfigJson.compilerOptions = restCompilerOptions; | ||
| const outDirWithoutTypesFolder = (0, path.resolve)(context, outputDir || configContent.options.outDir || "dist"); | ||
| const excludeExtensions = [".mdx", ".md"]; | ||
| const filesToCompile = [...getDependentFiles([...Object.values(mapComponentsToExpose), ...additionalFilesToCompile].filter((filename) => !excludeExtensions.some((ext) => filename.endsWith(ext))), configContent, rootDir), ...configContent.fileNames.filter((filename) => filename.endsWith(".d.ts") && !filename.startsWith(outDirWithoutTypesFolder))]; | ||
| rawTsConfigJson.include = []; | ||
| rawTsConfigJson.files = [...new Set(filesToCompile)]; | ||
| rawTsConfigJson.exclude = []; | ||
| "references" in rawTsConfigJson && delete rawTsConfigJson.references; | ||
| rawTsConfigJson.extends = resolvedTsConfigPath; | ||
| rawTsConfigJson.compilerOptions.declarationDir = outDir; | ||
| return rawTsConfigJson; | ||
| }; | ||
| const TS_EXTENSIONS = [ | ||
| "ts", | ||
| "tsx", | ||
| "vue", | ||
| "svelte" | ||
| ]; | ||
| const resolveWithExtension = (exposedPath, context) => { | ||
| if ((0, path.extname)(exposedPath)) return (0, path.resolve)(context, exposedPath); | ||
| for (const extension of TS_EXTENSIONS) { | ||
| const exposedPathWithExtension = (0, path.resolve)(context, `${exposedPath}.${extension}`); | ||
| if ((0, fs.existsSync)(exposedPathWithExtension)) return exposedPathWithExtension; | ||
| } | ||
| }; | ||
| const resolveExposes = (remoteOptions) => { | ||
| return _module_federation_managers.utils.parseOptions(remoteOptions.moduleFederationConfig.exposes || {}, (item, key) => ({ | ||
| exposePath: Array.isArray(item) ? item[0] : item, | ||
| key | ||
| }), (item, key) => ({ | ||
| exposePath: Array.isArray(item.import) ? item.import[0] : item.import[0], | ||
| key | ||
| })).reduce((accumulator, item) => { | ||
| const { exposePath, key } = item[1]; | ||
| accumulator[key] = resolveWithExtension(exposePath, remoteOptions.context) || resolveWithExtension((0, path.join)(exposePath, "index"), remoteOptions.context) || exposePath; | ||
| return accumulator; | ||
| }, {}); | ||
| }; | ||
| const retrieveRemoteConfig = (options) => { | ||
| validateOptions(options); | ||
| const remoteOptions = { | ||
| ...defaultOptions, | ||
| ...options | ||
| }; | ||
| const mapComponentsToExpose = resolveExposes(remoteOptions); | ||
| const tsConfig = readTsConfig(remoteOptions, mapComponentsToExpose); | ||
| if (tsConfig.compilerOptions.incremental && tsConfig.compilerOptions.tsBuildInfoFile && options.deleteTypesFolder !== true) remoteOptions.deleteTypesFolder = false; | ||
| return { | ||
| tsConfig, | ||
| mapComponentsToExpose, | ||
| remoteOptions | ||
| }; | ||
| }; | ||
| //#endregion | ||
| //#region src/core/lib/generateTypes.ts | ||
| async function generateTypes(options) { | ||
| return new (getDTSManagerConstructor(options.remote?.implementation))(options).generateTypes(); | ||
| } | ||
| //#endregion | ||
| //#region src/core/rpc/types.ts | ||
| let RpcGMCallTypes = /* @__PURE__ */ function(RpcGMCallTypes) { | ||
| RpcGMCallTypes["CALL"] = "mf_call"; | ||
| RpcGMCallTypes["RESOLVE"] = "mf_resolve"; | ||
| RpcGMCallTypes["REJECT"] = "mf_reject"; | ||
| RpcGMCallTypes["EXIT"] = "mf_exit"; | ||
| return RpcGMCallTypes; | ||
| }({}); | ||
| //#endregion | ||
| //#region src/core/rpc/expose-rpc.ts | ||
| function exposeRpc(fn) { | ||
| const sendMessage = (message) => new Promise((resolve, reject) => { | ||
| if (!process$1.default.send) reject(/* @__PURE__ */ new Error(`Process ${process$1.default.pid} doesn't have IPC channels`)); | ||
| else if (!process$1.default.connected) reject(/* @__PURE__ */ new Error(`Process ${process$1.default.pid} doesn't have open IPC channels`)); | ||
| else process$1.default.send(message, void 0, void 0, (error) => { | ||
| if (error) reject(error); | ||
| else resolve(void 0); | ||
| }); | ||
| }); | ||
| const handleMessage = async (message) => { | ||
| if (message.type === RpcGMCallTypes.CALL) { | ||
| if (!process$1.default.send) return; | ||
| let value, error; | ||
| try { | ||
| value = await fn(...message.args); | ||
| } catch (fnError) { | ||
| error = fnError; | ||
| } | ||
| try { | ||
| if (error) await sendMessage({ | ||
| type: RpcGMCallTypes.REJECT, | ||
| id: message.id, | ||
| error | ||
| }); | ||
| else await sendMessage({ | ||
| type: RpcGMCallTypes.RESOLVE, | ||
| id: message.id, | ||
| value | ||
| }); | ||
| } catch (sendError) { | ||
| if (error) { | ||
| if (error instanceof Error) console.error(error); | ||
| } | ||
| console.error(sendError); | ||
| } | ||
| } | ||
| }; | ||
| process$1.default.on("message", handleMessage); | ||
| } | ||
| //#endregion | ||
| Object.defineProperty(exports, 'DTSManager', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return DTSManager; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'HOST_API_TYPES_FILE_NAME', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return HOST_API_TYPES_FILE_NAME; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'ModuleFederationDevServer', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return ModuleFederationDevServer; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'REMOTE_ALIAS_IDENTIFIER', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return REMOTE_ALIAS_IDENTIFIER; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'REMOTE_API_TYPES_FILE_NAME', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return REMOTE_API_TYPES_FILE_NAME; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'RpcGMCallTypes', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return RpcGMCallTypes; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'cloneDeepOptions', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return cloneDeepOptions; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'createHttpServer', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return createHttpServer; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'exposeRpc', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return exposeRpc; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'generateTypes', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return generateTypes; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'getDTSManagerConstructor', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return getDTSManagerConstructor; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'isDebugMode', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return isDebugMode; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'isTSProject', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return isTSProject; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'retrieveHostConfig', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return retrieveHostConfig; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'retrieveMfTypesPath', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return retrieveMfTypesPath; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'retrieveOriginalOutDir', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return retrieveOriginalOutDir; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'retrieveRemoteConfig', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return retrieveRemoteConfig; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'retrieveTypesAssetsInfo', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return retrieveTypesAssetsInfo; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'retrieveTypesZipPath', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return retrieveTypesZipPath; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, 'validateOptions', { | ||
| enumerable: true, | ||
| get: function () { | ||
| return validateOptions; | ||
| } | ||
| }); |
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
325163
0.2%7606
0.08%16
-5.88%+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
Updated
Updated