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