UNPKG

@backstage/cli

Version:

CLI for developing Backstage plugins and apps

608 lines (589 loc) • 20.6 kB
'use strict'; var role = require('./role-a2fb954d.cjs.js'); var fs = require('fs-extra'); var index = require('./index-3decf946.cjs.js'); var webpack = require('webpack'); var packageDetection = require('./packageDetection-a9880107.cjs.js'); require('yn'); var path = require('path'); require('react-dev-utils/FileSizeReporter'); require('react-dev-utils/formatWebpackMessages'); require('eslint-webpack-plugin'); require('fork-ts-checker-webpack-plugin'); require('html-webpack-plugin'); require('@backstage/cli-common'); require('react-dev-utils/ModuleScopePlugin'); require('run-script-webpack-plugin'); require('@manypkg/get-packages'); require('webpack-node-externals'); require('lodash/pickBy'); require('./run-168542e8.cjs.js'); require('mini-css-extract-plugin'); require('@pmmmwh/react-refresh-webpack-plugin'); var chalk = require('chalk'); require('@backstage/config'); var chokidar = require('chokidar'); var cliNode = require('@backstage/cli-node'); var uniq = require('lodash/uniq'); var openBrowser = require('react-dev-utils/openBrowser'); var WebpackDevServer = require('webpack-dev-server'); require('semver'); require('@yarnpkg/parsers'); require('@yarnpkg/lockfile'); require('minimatch'); require('./yarn-6cd89e16.cjs.js'); require('lodash/partition'); require('@backstage/config-loader'); var ctrlcWindows = require('ctrlc-windows'); var errors = require('@backstage/errors'); var debounce = require('lodash/debounce'); var url = require('url'); var spawn = require('cross-spawn'); var lint = require('./lint-50085f62.cjs.js'); var config = require('./config-77ad1feb.cjs.js'); var Lockfile = require('./Lockfile-eced6070.cjs.js'); require('commander'); require('./entryPoints-0cc55995.cjs.js'); require('./svgrTemplate-3549ea1c.cjs.js'); require('child_process'); require('util'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) 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 fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); var webpack__default = /*#__PURE__*/_interopDefaultLegacy(webpack); var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk); var uniq__default = /*#__PURE__*/_interopDefaultLegacy(uniq); var openBrowser__default = /*#__PURE__*/_interopDefaultLegacy(openBrowser); var WebpackDevServer__default = /*#__PURE__*/_interopDefaultLegacy(WebpackDevServer); var debounce__default = /*#__PURE__*/_interopDefaultLegacy(debounce); var spawn__default = /*#__PURE__*/_interopDefaultLegacy(spawn); async function serveBackend(options) { const paths = packageDetection.resolveBundlingPaths(options); const config = await packageDetection.createBackendConfig(paths, { ...options, isDev: true }); process.env.NODE_ENV = "development"; const compiler = webpack__default["default"](config, (err) => { if (err) { console.error(err); } else console.log("Build succeeded"); }); const waitForExit = async () => { for (const signal of ["SIGINT", "SIGTERM"]) { process.on(signal, () => { compiler.close(() => process.exit()); }); } return new Promise(() => { }); }; return waitForExit; } async function serveBundle(options) { var _a, _b, _c, _d; const paths = packageDetection.resolveBundlingPaths(options); const targetPkg = await fs__default["default"].readJson(paths.targetPackageJson); if (options.verifyVersions) { const lockfile = await Lockfile.Lockfile.load( index.paths.resolveTargetRoot("yarn.lock") ); const result = lockfile.analyze({ filter: lint.includedFilter, localPackages: cliNode.PackageGraph.fromPackages( await cliNode.PackageGraph.listTargetPackages() ) }); const problemPackages = [...result.newVersions, ...result.newRanges].map(({ name: name2 }) => name2).filter(lint.forbiddenDuplicatesFilter); if (problemPackages.length > 1) { console.log( chalk__default["default"].yellow( `\u26A0\uFE0F Some of the following packages may be outdated or have duplicate installations: ${uniq__default["default"](problemPackages).join(", ")} ` ) ); console.log( chalk__default["default"].yellow( `\u26A0\uFE0F This can be resolved using the following command: yarn backstage-cli versions:check --fix ` ) ); } } checkReactVersion(); const { name } = await fs__default["default"].readJson(index.paths.resolveTarget("package.json")); let webpackServer = void 0; let viteServer = void 0; let latestFrontendAppConfigs = []; const cliConfig = await config.loadCliConfig({ args: options.configPaths, fromPackage: name, withFilteredKeys: true, watch(appConfigs) { latestFrontendAppConfigs = appConfigs; webpackServer == null ? void 0 : webpackServer.invalidate(); viteServer == null ? void 0 : viteServer.restart(); } }); latestFrontendAppConfigs = cliConfig.frontendAppConfigs; const appBaseUrl = cliConfig.frontendConfig.getString("app.baseUrl"); const backendBaseUrl = cliConfig.frontendConfig.getString("backend.baseUrl"); if (appBaseUrl === backendBaseUrl) { console.log( chalk__default["default"].yellow( `\u26A0\uFE0F Conflict between app baseUrl and backend baseUrl: app.baseUrl: ${appBaseUrl} backend.baseUrl: ${backendBaseUrl} Must have unique hostname and/or ports. This can be resolved by changing app.baseUrl and backend.baseUrl to point to their respective local development ports. ` ) ); } const { frontendConfig, fullConfig } = cliConfig; const url = packageDetection.resolveBaseUrl(frontendConfig); const host = frontendConfig.getOptionalString("app.listen.host") || url.hostname; const port = frontendConfig.getOptionalNumber("app.listen.port") || Number(url.port) || (url.protocol === "https:" ? 443 : 80); const detectedModulesEntryPoint = await packageDetection.createDetectedModulesEntryPoint({ config: fullConfig, targetPath: paths.targetPath, watch() { webpackServer == null ? void 0 : webpackServer.invalidate(); viteServer == null ? void 0 : viteServer.restart(); } }); const config$1 = await packageDetection.createConfig(paths, { ...options, checksEnabled: options.checksEnabled, isDev: true, baseUrl: url, frontendConfig, getFrontendAppConfigs: () => { return latestFrontendAppConfigs; }, additionalEntryPoints: detectedModulesEntryPoint }); if (process.env.EXPERIMENTAL_VITE) { const { default: vite } = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('vite')); }); const { default: viteReact } = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('@vitejs/plugin-react')); }); const { nodePolyfills: viteNodePolyfills } = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('vite-plugin-node-polyfills')); }); const { createHtmlPlugin: viteHtml } = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('vite-plugin-html')); }); viteServer = await vite.createServer({ define: { global: "window", "process.argv": JSON.stringify(process.argv), "process.env.APP_CONFIG": JSON.stringify(cliConfig.frontendAppConfigs), // This allows for conditional imports of react-dom/client, since there's no way // to check for presence of it in source code without module resolution errors. "process.env.HAS_REACT_DOM_CLIENT": JSON.stringify(packageDetection.hasReactDomClient()) }, plugins: [ viteReact(), viteNodePolyfills(), viteHtml({ entry: paths.targetEntry, // todo(blam): we should look at contributing to thPe plugin here // to support absolute paths, but works in the interim at least. template: "public/index.html", inject: { data: { config: frontendConfig, publicPath: (_a = config$1.output) == null ? void 0 : _a.publicPath } } }) ], server: { host, port }, publicDir: paths.targetPublic, root: paths.targetPath }); } else { const compiler = webpack__default["default"](config$1); webpackServer = new WebpackDevServer__default["default"]( { hot: !process.env.CI, devMiddleware: { publicPath: (_b = config$1.output) == null ? void 0 : _b.publicPath, stats: "errors-warnings" }, static: paths.targetPublic ? { publicPath: (_c = config$1.output) == null ? void 0 : _c.publicPath, directory: paths.targetPublic } : void 0, historyApiFallback: { // Paths with dots should still use the history fallback. // See https://github.com/facebookincubator/create-react-app/issues/387. disableDotRule: true, // The index needs to be rewritten relative to the new public path, including subroutes. index: `${(_d = config$1.output) == null ? void 0 : _d.publicPath}index.html` }, https: url.protocol === "https:" ? { cert: fullConfig.getString("app.https.certificate.cert"), key: fullConfig.getString("app.https.certificate.key") } : false, host, port, proxy: targetPkg.proxy, // When the dev server is behind a proxy, the host and public hostname differ allowedHosts: [url.hostname], client: { webSocketURL: "auto://0.0.0.0:0/ws" } }, compiler ); } await (viteServer == null ? void 0 : viteServer.listen()); await new Promise(async (resolve, reject) => { if (webpackServer) { webpackServer.startCallback((err) => { if (err) { reject(err); return; } resolve(); }); } else { resolve(); } }); openBrowser__default["default"](url.href); const waitForExit = async () => { for (const signal of ["SIGINT", "SIGTERM"]) { process.on(signal, () => { webpackServer == null ? void 0 : webpackServer.close(); viteServer == null ? void 0 : viteServer.close(); process.exit(); }); } return new Promise(() => { }); }; return waitForExit; } function checkReactVersion() { try { const reactPkgPath = require.resolve("react/package.json", { paths: [index.paths.targetRoot] }); const reactPkg = require(reactPkgPath); if (reactPkg.version.startsWith("16.")) { console.log( chalk__default["default"].yellow( ` \u26A0\uFE0F \u26A0\uFE0F \u26A0\uFE0F You are using React version 16, which is deprecated for use in Backstage. \u26A0\uFE0F \u26A0\uFE0F Please upgrade to React 17 by updating your packages/app dependencies. \u26A0\uFE0F \u26A0\uFE0F \u26A0\uFE0F ` ) ); } } catch { } } var __accessCheck = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); setter ? setter.call(obj, value) : member.set(obj, value); return value; }; var __privateWrapper = (obj, member, setter, getter) => ({ set _(value) { __privateSet(obj, member, value, setter); }, get _() { return __privateGet(obj, member, getter); } }); var _generation, _methods; const requestType = "@backstage/cli/channel/request"; const responseType = "@backstage/cli/channel/response"; class IpcServer { constructor() { __privateAdd(this, _generation, 1); __privateAdd(this, _methods, /* @__PURE__ */ new Map()); } addChild(child) { var _a; const generation = __privateWrapper(this, _generation)._++; const sendMessage = (_a = child.send) == null ? void 0 : _a.bind(child); if (!sendMessage) { return; } const messageListener = (request) => { if (request.type !== requestType) { return; } const handler = __privateGet(this, _methods).get(request.method); if (!handler) { sendMessage({ type: responseType, id: request.id, error: { name: "NotFoundError", message: `No handler registered for method ${request.method}` } }); return; } Promise.resolve().then(() => handler(request.body, { generation })).then( (response) => sendMessage({ type: responseType, id: request.id, body: response }) ).catch( (error) => sendMessage({ type: responseType, id: request.id, error: errors.serializeError(error) }) ); }; child.addListener("message", messageListener); child.addListener("exit", () => { child.removeListener("message", messageListener); }); } registerMethod(method, handler) { if (__privateGet(this, _methods).has(method)) { throw new Error(`A handler is already registered for method ${method}`); } __privateGet(this, _methods).set(method, handler); } } _generation = new WeakMap(); _methods = new WeakMap(); class ServerDataStore { static bind(server) { const store = /* @__PURE__ */ new Map(); server.registerMethod( "DevDataStore.save", async (request, { generation }) => { const { key, data } = request; if (!key) { throw new Error("Key is required in DevDataStore.save"); } const item = store.get(key); if (!item) { store.set(key, { generation, data }); return { saved: true }; } if (item.generation > generation) { return { saved: false }; } store.set(key, { generation, data }); return { saved: true }; } ); server.registerMethod( "DevDataStore.load", async (request) => { const item = store.get(request.key); return { loaded: Boolean(item), data: item == null ? void 0 : item.data }; } ); } } const [nodeMajor, nodeMinor] = process.versions.node.split(".").map(Number); const supportsModuleLoaderRegister = nodeMajor >= 20 && nodeMinor >= 6; const loaderArgs = [ "--require", require.resolve("tsx/preflight"), supportsModuleLoaderRegister ? "--import" : "--loader", url.pathToFileURL(require.resolve("tsx")).toString() // Windows prefers a URL here ]; async function startBackendExperimental(options) { const envEnv = process.env; if (!envEnv.NODE_ENV) { envEnv.NODE_ENV = "development"; } const server = new IpcServer(); ServerDataStore.bind(server); let exiting = false; let child; let watcher = void 0; let shutdownPromise = void 0; const restart = debounce__default["default"](async () => { if (shutdownPromise) { return; } if (child && !child.killed && child.exitCode === null) { shutdownPromise = new Promise((resolve) => child.once("exit", resolve)); if (process.platform === "win32" && child.pid) { ctrlcWindows.ctrlc(child.pid); } else { child.kill(); } await shutdownPromise; shutdownPromise = void 0; } if (exiting) { return; } const optionArgs = new Array(); if (options.inspectEnabled) { const inspect = typeof options.inspectEnabled === "string" ? `--inspect=${options.inspectEnabled}` : "--inspect"; optionArgs.push(inspect); } else if (options.inspectBrkEnabled) { const inspect = typeof options.inspectBrkEnabled === "string" ? `--inspect-brk=${options.inspectBrkEnabled}` : "--inspect-brk"; optionArgs.push(inspect); } const userArgs = process.argv.slice(["node", "backstage-cli", "package", "start"].length).filter((arg) => !optionArgs.includes(arg)); child = spawn__default["default"]( process.execPath, [...loaderArgs, ...optionArgs, options.entry, ...userArgs], { stdio: ["ignore", "inherit", "inherit", "ipc"], env: { ...process.env, BACKSTAGE_CLI_CHANNEL: "1", ESBK_TSCONFIG_PATH: index.paths.resolveTargetRoot("tsconfig.json") }, serialization: "advanced" } ); server.addChild(child); child.on("message", (data) => { if (typeof data === "object" && (data == null ? void 0 : data.type) === "dependency") { let path$1 = data.path; if (path$1.startsWith("file:")) { path$1 = url.fileURLToPath(path$1); } if (path.isAbsolute(path$1)) { watcher == null ? void 0 : watcher.add(path$1); } } }); }, 100); restart(); watcher = chokidar.watch([], { cwd: process.cwd(), ignoreInitial: true, ignorePermissionErrors: true }).on("all", restart); process.stdin.on("data", restart); const exitPromise = new Promise((resolveExitPromise) => { async function handleSignal(signal) { exiting = true; if (child && child.exitCode === null) { await new Promise((resolve) => { child.on("close", resolve); child.kill(signal); }); } resolveExitPromise(); } process.once("SIGINT", handleSignal); process.once("SIGTERM", handleSignal); }); return () => exitPromise; } async function startBackend(options) { const hasDev = await fs__default["default"].pathExists(index.paths.resolveTarget("dev")); if (hasDev) { const waitForExit = await startBackendExperimental({ entry: "dev/index", checksEnabled: false, // not supported inspectEnabled: options.inspectEnabled, inspectBrkEnabled: options.inspectBrkEnabled }); await waitForExit(); } else if (!process.env.LEGACY_BACKEND_START) { const waitForExit = await startBackendExperimental({ entry: "src/index", checksEnabled: false, // not supported inspectEnabled: options.inspectEnabled, inspectBrkEnabled: options.inspectBrkEnabled }); await waitForExit(); } else { await fs__default["default"].remove(index.paths.resolveTarget("dist")); const waitForExit = await serveBackend({ entry: "src/index", checksEnabled: options.checksEnabled, inspectEnabled: options.inspectEnabled, inspectBrkEnabled: options.inspectBrkEnabled }); await waitForExit(); } } async function startFrontend(options) { const waitForExit = await serveBundle({ entry: options.entry, checksEnabled: options.checksEnabled, configPaths: options.configPaths, verifyVersions: options.verifyVersions }); await waitForExit(); } async function command(opts) { const role$1 = await role.findRoleFromCommand(opts); const options = { configPaths: opts.config, checksEnabled: Boolean(opts.check), inspectEnabled: opts.inspect, inspectBrkEnabled: opts.inspectBrk }; switch (role$1) { case "backend": case "backend-plugin": case "backend-plugin-module": case "node-library": return startBackend(options); case "frontend": return startFrontend({ ...options, entry: "src/index", verifyVersions: true }); case "web-library": case "frontend-plugin": case "frontend-plugin-module": return startFrontend({ entry: "dev/index", ...options }); default: throw new Error( `Start command is not supported for package role '${role$1}'` ); } } exports.command = command; //# sourceMappingURL=index-61d1a7aa.cjs.js.map