UNPKG

@backstage/cli

Version:

CLI for developing Backstage plugins and apps

495 lines (477 loc) • 16 kB
'use strict'; var fs = require('fs-extra'); var index = require('./index-ce56dce5.cjs.js'); var webpack = require('webpack'); var paths = require('./paths-b77452cc.cjs.js'); require('yn'); var path = require('path'); require('react-dev-utils/FileSizeReporter'); require('react-dev-utils/formatWebpackMessages'); require('fork-ts-checker-webpack-plugin'); require('html-webpack-plugin'); require('react-dev-utils/ModuleScopePlugin'); require('run-script-webpack-plugin'); require('webpack-node-externals'); require('@backstage/cli-common'); require('@manypkg/get-packages'); require('mini-css-extract-plugin'); require('@pmmmwh/react-refresh-webpack-plugin'); require('./run-a0658306.cjs.js'); require('eslint-webpack-plugin'); require('lodash/pickBy'); var chalk = require('chalk'); var WebpackDevServer = require('webpack-dev-server'); var openBrowser = require('react-dev-utils/openBrowser'); var url = require('url'); var chokidar = require('chokidar'); var errors = require('@backstage/errors'); var debounce = require('lodash/debounce'); var spawn = require('cross-spawn'); var uniq = require('lodash/uniq'); var config = require('./config-6ac9124d.cjs.js'); var cliNode = require('@backstage/cli-node'); var Lockfile = require('./Lockfile-eced6070.cjs.js'); require('minimatch'); require('./yarn-6cd89e16.cjs.js'); var lint = require('./lint-ff1e8d45.cjs.js'); var role = require('./role-8f6a7da9.cjs.js'); require('commander'); require('semver'); require('./svgrTemplate-550efce6.cjs.js'); require('./entryPoints-0cc55995.cjs.js'); require('child_process'); require('util'); require('@backstage/config-loader'); require('@backstage/config'); require('@yarnpkg/parsers'); require('@yarnpkg/lockfile'); require('lodash/partition'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); var webpack__default = /*#__PURE__*/_interopDefaultLegacy(webpack); var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk); var WebpackDevServer__default = /*#__PURE__*/_interopDefaultLegacy(WebpackDevServer); var openBrowser__default = /*#__PURE__*/_interopDefaultLegacy(openBrowser); var debounce__default = /*#__PURE__*/_interopDefaultLegacy(debounce); var spawn__default = /*#__PURE__*/_interopDefaultLegacy(spawn); var uniq__default = /*#__PURE__*/_interopDefaultLegacy(uniq); async function serveBackend(options) { const paths$1 = paths.resolveBundlingPaths(options); const config = await paths.createBackendConfig(paths$1, { ...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; const url = paths.resolveBaseUrl(options.frontendConfig); const host = options.frontendConfig.getOptionalString("app.listen.host") || url.hostname; const port = options.frontendConfig.getOptionalNumber("app.listen.port") || Number(url.port) || (url.protocol === "https:" ? 443 : 80); const paths$1 = paths.resolveBundlingPaths(options); const pkgPath = paths$1.targetPackageJson; const pkg = await fs__default["default"].readJson(pkgPath); const config = await paths.createConfig(paths$1, { ...options, isDev: true, baseUrl: url }); const compiler = webpack__default["default"](config); const server = new WebpackDevServer__default["default"]( { hot: !process.env.CI, devMiddleware: { publicPath: (_a = config.output) == null ? void 0 : _a.publicPath, stats: "errors-warnings" }, static: paths$1.targetPublic ? { publicPath: (_b = config.output) == null ? void 0 : _b.publicPath, directory: paths$1.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: `${(_c = config.output) == null ? void 0 : _c.publicPath}index.html` }, https: url.protocol === "https:" ? { cert: options.fullConfig.getString("app.https.certificate.cert"), key: options.fullConfig.getString("app.https.certificate.key") } : false, host, port, proxy: pkg.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 new Promise((resolve, reject) => { server.startCallback((err) => { if (err) { reject(err); return; } openBrowser__default["default"](url.href); resolve(); }); }); const waitForExit = async () => { for (const signal of ["SIGINT", "SIGTERM"]) { process.on(signal, () => { server.close(); process.exit(); }); } return new Promise(() => { }); }; return waitForExit; } 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 loaderArgs = ["--require", require.resolve("@esbuild-kit/cjs-loader")]; 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)); child.kill(); await shutdownPromise; shutdownPromise = void 0; } if (exiting) { return; } const optionArgs = new Array(); if (options.inspectEnabled) { optionArgs.push("--inspect"); } else if (options.inspectBrkEnabled) { optionArgs.push("--inspect-brk"); } 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: ["inherit", "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([index.paths.targetDir], { cwd: process.cwd(), ignored: ["**/.*/**", "**/node_modules/**"], 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) { if (process.env.EXPERIMENTAL_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(); } } 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 { } } async function startFrontend(options) { 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")); const config$1 = await config.loadCliConfig({ args: options.configPaths, fromPackage: name, withFilteredKeys: true }); const appBaseUrl = config$1.frontendConfig.getString("app.baseUrl"); const backendBaseUrl = config$1.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 waitForExit = await serveBundle({ entry: options.entry, checksEnabled: options.checksEnabled, ...config$1 }); await waitForExit(); } async function command(opts) { const role$1 = await role.findRoleFromCommand(opts); const options = { configPaths: opts.config, checksEnabled: Boolean(opts.check), inspectEnabled: Boolean(opts.inspect), inspectBrkEnabled: Boolean(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-0ca66708.cjs.js.map