UNPKG

@red-hat-developer-hub/cli

Version:
735 lines (727 loc) 28.2 kB
'use strict'; var getPackages = require('@manypkg/get-packages'); var parsers = require('@yarnpkg/parsers'); var chalk = require('chalk'); var fs = require('fs-extra'); var semver = require('semver'); var child_process = require('child_process'); var node_module = require('node:module'); var path = require('path'); var productionPack = require('../../lib/packager/productionPack.cjs.js'); var paths = require('../../lib/paths.cjs.js'); var tasks = require('../../lib/tasks.cjs.js'); var Lockfile = require('../../lib/versioning/Lockfile.cjs.js'); var backendUtils = require('./backend-utils.cjs.js'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } function _interopNamespaceCompat(e) { if (e && typeof e === 'object' && 'default' in e) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var chalk__default = /*#__PURE__*/_interopDefaultCompat(chalk); var fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs); var semver__namespace = /*#__PURE__*/_interopNamespaceCompat(semver); var path__namespace = /*#__PURE__*/_interopNamespaceCompat(path); async function backend(opts) { const targetRelativePath = "dist-dynamic"; const target = path__namespace.join(paths.paths.targetDir, targetRelativePath); const yarn = "yarn"; const yarnVersion = child_process.execSync(`${yarn} --version`).toString().trim(); const pkgContent = await fs__namespace.readFile( paths.paths.resolveTarget("package.json"), "utf8" ); const pkg = JSON.parse(pkgContent); if (pkg.bundled) { throw new Error( `Packages exported as dynamic backend plugins should not have the ${chalk__default.default.cyan( "bundled" )} field set to ${chalk__default.default.cyan("true")}` ); } const derivedPackageName = `${pkg.name}-dynamic`; const packagesToEmbed = opts.embedPackage || []; const allowNative = opts.allowNativePackage || []; const suppressNative = opts.suppressNativePackage || []; const ignoreVersionCheck = opts.ignoreVersionCheck || []; const monoRepoPackages = await getPackages.getPackages(paths.paths.targetDir); const embeddedResolvedPackages = await searchEmbedded( pkg, packagesToEmbed, monoRepoPackages, node_module.createRequire(path__namespace.join(paths.paths.targetDir, "package.json")), [] ); const embeddedPackages = embeddedResolvedPackages.map((e) => e.packageName); const unusedEmbeddedPackages = packagesToEmbed.filter( (e) => !embeddedPackages.includes(e) ); if (unusedEmbeddedPackages.length > 0) { tasks.Task.log( chalk__default.default.yellow( `Some embedded packages are not part of the plugin transitive dependencies:${chalk__default.default.cyan( ["", ...unusedEmbeddedPackages].join("\n- ") )}` ) ); } const stringOrRegexp = (s) => s.startsWith("/") && s.endsWith("/") ? new RegExp(s.slice(1, -1)) : s; const moveToPeerDependencies = [ /@backstage\//, ...(opts.sharedPackage || []).filter((p) => !p.startsWith("!")).map(stringOrRegexp) ]; const dontMoveToPeerDependencies = (opts.sharedPackage || []).filter((p) => p.startsWith("!")).map((p) => p.slice(1)).map(stringOrRegexp); dontMoveToPeerDependencies.push(...embeddedPackages); const sharedPackagesRules = { include: moveToPeerDependencies, exclude: dontMoveToPeerDependencies }; if (opts.clean) { await fs__namespace.remove(target); } await fs__namespace.mkdirs(target); await fs__namespace.writeFile( path__namespace.join(target, ".gitignore"), ` * ${opts.trackDynamicManifestAndLockFile ? ` !package.json !yarn.lock ` : ""}` ); if (suppressNative.length > 0) { for (const toSuppress of suppressNative) { await fs__namespace.mkdirs(path__namespace.join(target, "embedded", toSuppress)); await fs__namespace.writeFile( path__namespace.join(target, "embedded", toSuppress, "package.json"), JSON.stringify( { name: toSuppress, main: "index.js" }, void 0, 2 ) ); await fs__namespace.writeFile( path__namespace.join(target, "embedded", toSuppress, "index.js"), ` throw new Error( 'The package "${toSuppress}" has been marked as a native module and removed from this dynamic plugin package "${derivedPackageName}", as native modules are not currently supported by dynamic plugins' );` ); } } const embeddedPeerDependencies = {}; for (const embedded of embeddedResolvedPackages) { const embeddedDestRelativeDir = embeddedPackageRelativePath(embedded); const embeddedDestDir = path__namespace.join(target, embeddedDestRelativeDir); if (!embedded.alreadyPacked) { if (opts.build) { tasks.Task.log( `Building embedded package ${chalk__default.default.cyan( embedded.packageName )} in ${chalk__default.default.cyan(embedded.dir)}` ); await tasks.Task.forCommand(`${yarn} build`, { cwd: embedded.dir, optional: false }); } tasks.Task.log( `Packing embedded package ${chalk__default.default.cyan( embedded.packageName )} in ${chalk__default.default.cyan(embedded.dir)} to ${chalk__default.default.cyan( embeddedDestRelativeDir )}` ); await productionPack.productionPack({ packageDir: embedded.dir, targetDir: embeddedDestDir }); } else { tasks.Task.log( `Copying embedded package ${chalk__default.default.cyan( embedded.packageName )} from ${chalk__default.default.cyan(embedded.dir)} to ${chalk__default.default.cyan( embeddedDestRelativeDir )}` ); fs__namespace.rmSync(embeddedDestDir, { force: true, recursive: true }); fs__namespace.cpSync(embedded.dir, embeddedDestDir, { recursive: true }); } if (fs__namespace.pathExistsSync(path__namespace.join(embeddedDestDir, "node_modules"))) { fs__namespace.rmSync(path__namespace.join(embeddedDestDir, "node_modules"), { force: true, recursive: true }); } tasks.Task.log( `Customizing embedded package ${chalk__default.default.cyan( embedded.packageName )} for dynamic loading` ); await customizeForDynamicUse({ embedded: embeddedResolvedPackages, isYarnV1: yarnVersion.startsWith("1."), monoRepoPackages, sharedPackages: sharedPackagesRules, overridding: { private: true, version: `${embedded.version}+embedded` }, after: (embeddedPkg) => { if (embeddedPkg.peerDependencies) { Object.entries(embeddedPkg.peerDependencies).forEach( ([name, version]) => { backendUtils.addToDependenciesForModule({ dependency: { name, version }, dependencies: embeddedPeerDependencies, ignoreVersionCheck, module: embeddedPkg.name }); } ); } } })(path__namespace.join(embeddedDestDir, "package.json")); } const embeddedDependenciesResolutions = embeddedResolvedPackages.reduce( (resolutions, embeddedPkg) => ({ ...resolutions, ...{ [embeddedPkg.packageName]: `file:./${embeddedPackageRelativePath(embeddedPkg)}` } }), {} ); if (opts.build) { tasks.Task.log(`Building main package`); await tasks.Task.forCommand(`${yarn} build`, { cwd: paths.paths.targetDir, optional: false }); } tasks.Task.log( `Packing main package to ${chalk__default.default.cyan( path__namespace.join(targetRelativePath, "package.json") )}` ); await productionPack.productionPack({ packageDir: "", targetDir: target }); if (fs__namespace.pathExistsSync(path__namespace.join(target, "dist-dynamic"))) { fs__namespace.rmSync(path__namespace.join(target, "dist-dynamic"), { force: true, recursive: true }); } tasks.Task.log( `Customizing main package in ${chalk__default.default.cyan( path__namespace.join(targetRelativePath, "package.json") )} for dynamic loading` ); await customizeForDynamicUse({ embedded: embeddedResolvedPackages, isYarnV1: yarnVersion.startsWith("1."), monoRepoPackages, sharedPackages: sharedPackagesRules, overridding: { name: derivedPackageName, bundleDependencies: true, // We remove scripts, because they do not make sense for this derived package. // They even bring errors, especially the pre-pack and post-pack ones: // we want to be able to use npm pack on this derived package to distribute it as a dynamic plugin, // and obviously this should not trigger the backstage pre-pack or post-pack actions // which are related to the packaging of the original static package. scripts: {} }, additionalResolutions: { ...embeddedDependenciesResolutions, ...suppressNative.map((nativePkg) => ({ [nativePkg]: path__namespace.join("file:./embedded", nativePkg) })).reduce((prev, curr) => ({ ...prev, ...curr }), {}) }, after(mainPkg) { if (Object.keys(embeddedPeerDependencies).length === 0) { return; } tasks.Task.log( `Hoisting peer dependencies of embedded packages to the main package` ); const mainPeerDependencies = mainPkg.peerDependencies || {}; backendUtils.addToMainDependencies( embeddedPeerDependencies, mainPeerDependencies, ignoreVersionCheck ); if (Object.keys(mainPeerDependencies).length > 0) { mainPkg.peerDependencies = mainPeerDependencies; } } })(path__namespace.resolve(target, "package.json")); const yarnLock = path__namespace.resolve(target, "yarn.lock"); const yarnLockExists = await fs__namespace.pathExists(yarnLock); if (!yarnLockExists) { let staticPluginYarnLock; if (await fs__namespace.pathExists(path__namespace.join(paths.paths.targetDir, "yarn.lock"))) { staticPluginYarnLock = path__namespace.join(paths.paths.targetDir, "yarn.lock"); } else if (await fs__namespace.pathExists(path__namespace.join(paths.paths.targetRoot, "yarn.lock"))) { staticPluginYarnLock = path__namespace.join(paths.paths.targetRoot, "yarn.lock"); } if (!staticPluginYarnLock) { throw new Error( `Could not find the static plugin ${chalk__default.default.cyan( "yarn.lock" )} file in either the local folder or the monorepo root (${chalk__default.default.cyan( paths.paths.targetRoot )})` ); } await fs__namespace.copyFile(staticPluginYarnLock, yarnLock); if (!opts.install) { tasks.Task.log( chalk__default.default.yellow( `Last export step (${chalk__default.default.cyan( "yarn install" )} has been disabled: the dynamic plugin package ${chalk__default.default.cyan( "yarn.lock" )} file will be inconsistent until ${chalk__default.default.cyan( "yarn install" )} is run manually` ) ); } } if (opts.install) { tasks.Task.log(`Installing private dependencies of the main package`); const logFile = "yarn-install.log"; const redirect = `> ${logFile}`; const yarnInstall = yarnVersion.startsWith("1.") ? `${yarn} install --production${yarnLockExists ? " --frozen-lockfile" : ""} ${redirect}` : `${yarn} install${yarnLockExists ? " --immutable" : " --no-immutable"} ${redirect}`; await tasks.Task.forCommand(yarnInstall, { cwd: target, optional: false }); await fs__namespace.remove(paths.paths.resolveTarget(targetRelativePath, ".yarn")); tasks.Task.log(`Validating private dependencies`); const lockFile = await Lockfile.Lockfile.load(yarnLock); const sharedPackagesInPrivateDeps = []; for (const key of lockFile.keys()) { const entry = lockFile.get(key); if (!entry) { continue; } if (`${pkg.name}-dynamic` === key) { continue; } if (embeddedPackages.includes(key)) { continue; } if (isPackageShared(key, sharedPackagesRules)) { sharedPackagesInPrivateDeps.push(key); } } if (sharedPackagesInPrivateDeps.length > 0) { const dynamicPkgContent = await fs__namespace.readFile( path__namespace.resolve(target, "package.json"), "utf8" ); const dynamicPkg = JSON.parse(dynamicPkgContent); const lockfileContents = await fs__namespace.readFile(yarnLock, "utf8"); let data; try { data = parsers.parseSyml(lockfileContents); } catch (err) { throw new Error(`Failed parsing ${chalk__default.default.cyan(yarnLock)}: ${err}`); } const packagesToProbablyEmbed = []; for (const dep in dynamicPkg.dependencies || []) { if (!Object.prototype.hasOwnProperty.call(dynamicPkg.dependencies, dep)) { continue; } const matchingEntry = Object.entries(data).find(([q, _]) => { return q.startsWith(`${dep}@`) && (q.includes(`@${dynamicPkg.dependencies[dep]}`) || q.includes(`@npm:${dynamicPkg.dependencies[dep]}`)); }); if (matchingEntry) { const yarnEntry = matchingEntry[1]; if (yarnEntry.dependencies) { if (Object.keys(yarnEntry.dependencies).some((d) => { return isPackageShared(d, sharedPackagesRules); })) { packagesToProbablyEmbed.push(dep); } } } } throw new Error( `Following shared package(s) should not be part of the plugin private dependencies:${chalk__default.default.cyan( ["", ...sharedPackagesInPrivateDeps].join("\n- ") )} Either unshare them with the ${chalk__default.default.cyan( "--shared-package !<package>" )} option, or use the ${chalk__default.default.cyan( "--embed-package" )} to embed the following packages which use shared dependencies:${chalk__default.default.cyan( ["", ...packagesToProbablyEmbed].join("\n- ") )}` ); } const nativePackages = []; for await (const nativePkg of backendUtils.gatherNativeModules(target)) { if (!allowNative.includes(nativePkg)) { nativePackages.push(nativePkg); } } if (nativePackages.length > 0) { throw new Error( `Dynamic plugins do not support native plugins. the following native modules have been transitively detected:${chalk__default.default.cyan( ["", ...nativePackages].join("\n- ") )}` ); } tasks.Task.log(`Validating plugin entry points`); const validateEntryPointsError = validatePluginEntryPoints(target); if (validateEntryPointsError) { throw new Error(validateEntryPointsError); } await fs__namespace.remove(paths.paths.resolveTarget(targetRelativePath, logFile)); } return target; } async function searchEmbedded(pkg, packagesToEmbed, monoRepoPackages, req, alreadyResolved) { const embedded = [...packagesToEmbed]; let regex = void 0; switch (pkg.backstage?.role) { case "backend-plugin": regex = /-backend$/; break; case "backend-plugin-module": regex = /-backend-module-.+$/; break; case "node-library": regex = /-node$/; break; } if (regex) { const commonPackage = pkg.name.replace(regex, "-common"); if (commonPackage !== pkg.name && !alreadyResolved.find((r) => r.packageName === commonPackage)) { embedded.push(commonPackage); } const nodePackage = pkg.name.replace(regex, "-node"); if (nodePackage !== pkg.name && !alreadyResolved.find((r) => r.packageName === nodePackage)) { embedded.push(nodePackage); } } const resolved = []; if (pkg.dependencies) { for (const dep in pkg.dependencies || []) { if (!Object.prototype.hasOwnProperty.call(pkg.dependencies, dep)) { continue; } if (embedded.includes(dep)) { const dependencyVersion = pkg.dependencies[dep]; const relatedMonoRepoPackages = monoRepoPackages.packages.filter( (p) => p.packageJson.name === dep ); if (relatedMonoRepoPackages.length > 1) { throw new Error( `Two packages named '${dep}' exist in the monorepo structure: this is not supported.` ); } if (relatedMonoRepoPackages.length === 0 && dependencyVersion.startsWith("workspace:")) { throw new Error( `No package named '${dep}' exist in the monorepo structure.` ); } let resolvedPackage; let resolvedPackageDir; let alreadyPacked = false; if (relatedMonoRepoPackages.length === 1) { const monoRepoPackage = relatedMonoRepoPackages[0]; let isResolved = false; if (dependencyVersion.startsWith("workspace:")) { isResolved = checkWorkspacePackageVersion(dependencyVersion, { dir: monoRepoPackage.dir, version: monoRepoPackage.packageJson.version }); } else if (semver__namespace.satisfies( monoRepoPackage.packageJson.version, dependencyVersion )) { isResolved = true; } if (!isResolved) { throw new Error( `Monorepo package named '${dep}' at '${monoRepoPackage.dir}' doesn't satisfy dependency version requirement in parent package '${pkg.name}'.` ); } resolvedPackage = JSON.parse( await fs__namespace.readFile( paths.paths.resolveTarget( path__namespace.join(monoRepoPackage.dir, "package.json") ), "utf8" ) ); resolvedPackageDir = monoRepoPackage.dir; } else { const resolvedPackageJson = req.resolve( path__namespace.join(dep, "package.json") ); resolvedPackageDir = path__namespace.dirname(resolvedPackageJson); resolvedPackage = JSON.parse( await fs__namespace.readFile(resolvedPackageJson, "utf8") ); if (!semver__namespace.satisfies(resolvedPackage.version, dependencyVersion)) { throw new Error( `Resolved package named '${dep}' at '${resolvedPackageDir}' doesn't satisfy dependency version requirement in parent package '${pkg.name}': '${resolvedPackage.version}', '${dependencyVersion}'.` ); } alreadyPacked = !resolvedPackage.main?.endsWith(".ts"); } if (resolvedPackage.bundled) { throw new Error( 'Packages embedded inside dynamic backend plugins should not have the "bundled" field set to true' ); } if (![...alreadyResolved, ...resolved].find( (p) => p.dir === resolvedPackageDir )) { resolved.push({ dir: resolvedPackageDir, packageName: resolvedPackage.name, version: resolvedPackage.version ?? "0.0.0", parentPackageName: pkg.name, alreadyPacked }); resolved.push( ...await searchEmbedded( resolvedPackage, embedded, monoRepoPackages, node_module.createRequire(path__namespace.join(resolvedPackageDir, "package.json")), [...alreadyResolved, ...resolved] ) ); } } } } return resolved; } function checkWorkspacePackageVersion(requiredVersionSpec, pkg) { const versionDetail = requiredVersionSpec.replace(/^workspace:/, ""); return pkg.dir === versionDetail || versionDetail === "*" || versionDetail === "~" || versionDetail === "^" || semver__namespace.satisfies(pkg.version, versionDetail); } function customizeForDynamicUse(options) { return async (dynamicPkgPath) => { const dynamicPkgContent = await fs__namespace.readFile(dynamicPkgPath, "utf8"); const pkgToCustomize = JSON.parse( dynamicPkgContent ); for (const field in options.overridding || {}) { if (!Object.prototype.hasOwnProperty.call(options.overridding, field)) { continue; } pkgToCustomize[field] = options.overridding[field]; } pkgToCustomize.files = pkgToCustomize.files?.filter( (f) => !f.startsWith("dist-dynamic/") ); if (pkgToCustomize.dependencies) { for (const dep in pkgToCustomize.dependencies) { if (!Object.prototype.hasOwnProperty.call( pkgToCustomize.dependencies, dep )) { continue; } const dependencyVersionSpec = pkgToCustomize.dependencies[dep]; if (dependencyVersionSpec.startsWith("workspace:")) { let resolvedVersion; const rangeSpecifier = dependencyVersionSpec.replace( /^workspace:/, "" ); const embeddedDep = options.embedded.find( (e) => e.packageName === dep && checkWorkspacePackageVersion(dependencyVersionSpec, e) ); if (embeddedDep) { resolvedVersion = embeddedDep.version; } else if (options.monoRepoPackages) { const relatedMonoRepoPackages = options.monoRepoPackages.packages.filter( (p) => p.packageJson.name === dep ); if (relatedMonoRepoPackages.length > 1) { throw new Error( `Two packages named ${chalk__default.default.cyan( dep )} exist in the monorepo structure: this is not supported.` ); } if (relatedMonoRepoPackages.length === 1 && checkWorkspacePackageVersion(dependencyVersionSpec, { dir: relatedMonoRepoPackages[0].dir, version: relatedMonoRepoPackages[0].packageJson.version })) { resolvedVersion = rangeSpecifier === "^" || rangeSpecifier === "~" ? rangeSpecifier + relatedMonoRepoPackages[0].packageJson.version : relatedMonoRepoPackages[0].packageJson.version; } } if (!resolvedVersion) { throw new Error( `Workspace dependency ${chalk__default.default.cyan(dep)} of package ${chalk__default.default.cyan( pkgToCustomize.name )} doesn't exist in the monorepo structure: maybe you should embed it ?` ); } pkgToCustomize.dependencies[dep] = resolvedVersion; } if (isPackageShared(dep, options.sharedPackages)) { tasks.Task.log(` moving ${chalk__default.default.cyan(dep)} to peerDependencies`); pkgToCustomize.peerDependencies ||= {}; pkgToCustomize.peerDependencies[dep] = pkgToCustomize.dependencies[dep]; delete pkgToCustomize.dependencies[dep]; continue; } if (options.isYarnV1) { const embeddedDep = options.embedded.find( (e) => e.packageName === dep && checkWorkspacePackageVersion(dependencyVersionSpec, e) ); if (embeddedDep) { pkgToCustomize.dependencies[dep] = `file:./${embeddedPackageRelativePath(embeddedDep)}`; } } } } pkgToCustomize.devDependencies = {}; const overrides = pkgToCustomize.overrides || {}; pkgToCustomize.overrides = { // The following lines are a workaround for the fact that the @aws-sdk/util-utf8-browser package // is not compatible with the NPM 9+, so that `npm pack` would not grab the Javascript files. // This package has been deprecated in favor of @smithy/util-utf8. // // See https://github.com/aws/aws-sdk-js-v3/issues/5305. "@aws-sdk/util-utf8-browser": { "@smithy/util-utf8": "^2.0.0" }, ...overrides, ...options.additionalOverrides || {} }; const resolutions = pkgToCustomize.resolutions || {}; pkgToCustomize.resolutions = { // The following lines are a workaround for the fact that the @aws-sdk/util-utf8-browser package // is not compatible with the NPM 9+, so that `npm pack` would not grab the Javascript files. // This package has been deprecated in favor of @smithy/util-utf8. // // See https://github.com/aws/aws-sdk-js-v3/issues/5305. "@aws-sdk/util-utf8-browser": "npm:@smithy/util-utf8@~2", ...resolutions, ...options.additionalResolutions || {} }; if (options.after) { options.after(pkgToCustomize); } await fs__namespace.writeJson(dynamicPkgPath, pkgToCustomize, { encoding: "utf8", spaces: 2 }); }; } function isPackageShared(pkgName, rules) { function test(str, expr) { if (typeof expr === "string") { return str === expr; } return expr.test(str); } if ((rules?.exclude || []).some((dontMove) => test(pkgName, dontMove))) { return false; } if ((rules?.include || []).some((move) => test(pkgName, move))) { return true; } return false; } function validatePluginEntryPoints(target) { const dynamicPluginRequire = node_module.createRequire(`${target}/package.json`); function requireModule(modulePath) { try { return dynamicPluginRequire(modulePath); } catch (e) { if (e?.code !== "ERR_UNSUPPORTED_DIR_IMPORT" && e?.name !== SyntaxError.name || dynamicPluginRequire.extensions[".ts"] !== void 0) { throw e; } } tasks.Task.log( ` adding typescript extension support to enable entry point validation` ); let nodeTransform; try { nodeTransform = dynamicPluginRequire.resolve( "@backstage/cli/config/nodeTransform.cjs" ); } catch (e) { tasks.Task.log( ` => unable to find '@backstage/cli/config/nodeTransform.cjs' in the plugin context` ); } if (nodeTransform) { dynamicPluginRequire(nodeTransform); } else { tasks.Task.log( ` => searching for 'ts-node' (legacy mode before backage 1.24.0)` ); let tsNode; try { tsNode = dynamicPluginRequire.resolve("ts-node"); } catch (e) { tasks.Task.log(` => unable to find 'ts-node' in the plugin context`); } if (tsNode) { dynamicPluginRequire(tsNode).register({ transpileOnly: true, project: path__namespace.resolve(paths.paths.targetRoot, "tsconfig.json"), compilerOptions: { module: "CommonJS" } }); } } return dynamicPluginRequire(modulePath); } try { const pluginModule = requireModule(target); const alphaPackage = path__namespace.resolve(target, "alpha"); const pluginAlphaModule = fs__namespace.pathExistsSync(alphaPackage) ? requireModule(alphaPackage) : void 0; if (![pluginAlphaModule, pluginModule].filter((m) => m !== void 0).some(backendUtils.isValidPluginModule)) { return `Backend plugin is not valid for dynamic loading: it should either export a ${chalk__default.default.cyan( "BackendFeature" )} or ${chalk__default.default.cyan( "BackendFeatureFactory" )} as default export, or export a ${chalk__default.default.cyan( "const dynamicPluginInstaller: BackendDynamicPluginInstaller" )} field as dynamic loading entrypoint`; } } catch (e) { return `Unable to validate plugin entry points: ${e}`; } return ""; } function embeddedPackageRelativePath(p) { return path__namespace.join( "embedded", p.packageName.replace(/^@/, "").replace(/\//, "-") ); } exports.backend = backend; exports.customizeForDynamicUse = customizeForDynamicUse; //# sourceMappingURL=backend.cjs.js.map