@backstage/cli
Version:
CLI for developing Backstage plugins and apps
608 lines (589 loc) • 20.6 kB
JavaScript
;
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