@backstage/cli
Version:
CLI for developing Backstage plugins and apps
701 lines (688 loc) • 24.4 kB
JavaScript
var chalk = require('chalk');
var fs = require('fs-extra');
var path = require('path');
var os = require('os');
var tar = require('tar');
var partition = require('lodash/partition');
var index = require('./index-d2845aa8.cjs.js');
var run = require('./run-eac5f3ab.cjs.js');
var PackageGraph = require('./PackageGraph-84e587f4.cjs.js');
var rollup = require('rollup');
var commonjs = require('@rollup/plugin-commonjs');
var resolve = require('@rollup/plugin-node-resolve');
var postcss = require('rollup-plugin-postcss');
var esbuild = require('rollup-plugin-esbuild');
var svgr = require('@svgr/rollup');
var dts = require('rollup-plugin-dts');
var json = require('@rollup/plugin-json');
var yaml = require('@rollup/plugin-yaml');
var rollupPluginutils = require('rollup-pluginutils');
var svgrTemplate = require('./svgrTemplate-d1dad6d3.cjs.js');
var entryPoints = require('./entryPoints-e81a0dba.cjs.js');
var parallel = require('./parallel-4af834f6.cjs.js');
var packageRoles = require('./packageRoles-54e27ede.cjs.js');
var productionPack = require('./productionPack-2038bfcf.cjs.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk);
var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
var tar__default = /*#__PURE__*/_interopDefaultLegacy(tar);
var partition__default = /*#__PURE__*/_interopDefaultLegacy(partition);
var commonjs__default = /*#__PURE__*/_interopDefaultLegacy(commonjs);
var resolve__default = /*#__PURE__*/_interopDefaultLegacy(resolve);
var postcss__default = /*#__PURE__*/_interopDefaultLegacy(postcss);
var esbuild__default = /*#__PURE__*/_interopDefaultLegacy(esbuild);
var svgr__default = /*#__PURE__*/_interopDefaultLegacy(svgr);
var dts__default = /*#__PURE__*/_interopDefaultLegacy(dts);
var json__default = /*#__PURE__*/_interopDefaultLegacy(json);
var yaml__default = /*#__PURE__*/_interopDefaultLegacy(yaml);
function forwardFileImports(options) {
const filter = rollupPluginutils.createFilter(options.include, options.exclude);
const exportedFiles = /* @__PURE__ */ new Set();
const generatedFor = /* @__PURE__ */ new Set();
return {
name: "forward-file-imports",
async generateBundle(outputOptions, bundle, isWrite) {
if (!isWrite) {
return;
}
const dir = outputOptions.dir || path.dirname(outputOptions.file);
if (generatedFor.has(dir)) {
return;
}
for (const output of Object.values(bundle)) {
if (output.type !== "chunk") {
continue;
}
const chunk = output;
if (!chunk.facadeModuleId) {
continue;
}
generatedFor.add(dir);
const srcRoot = path.dirname(chunk.facadeModuleId);
await Promise.all(
Array.from(exportedFiles).map(async (exportedFile) => {
const outputPath = path.relative(srcRoot, exportedFile);
const targetFile = path.resolve(dir, outputPath);
await fs__default["default"].ensureDir(path.dirname(targetFile));
await fs__default["default"].copyFile(exportedFile, targetFile);
})
);
return;
}
},
options(inputOptions) {
const origExternal = inputOptions.external;
const external = (id, importer, isResolved) => {
if (typeof origExternal === "function" && origExternal(id, importer, isResolved)) {
return true;
}
if (Array.isArray(origExternal) && origExternal.includes(id)) {
return true;
}
if (!filter(id)) {
return false;
}
if (!importer) {
throw new Error(`Unknown importer of file module ${id}`);
}
const fullId = isResolved ? id : path.resolve(path.dirname(importer), id);
exportedFiles.add(fullId);
return true;
};
return { ...inputOptions, external };
}
};
}
var Output = /* @__PURE__ */ ((Output2) => {
Output2[Output2["esm"] = 0] = "esm";
Output2[Output2["cjs"] = 1] = "cjs";
Output2[Output2["types"] = 2] = "types";
return Output2;
})(Output || {});
const SCRIPT_EXTS = [".js", ".jsx", ".ts", ".tsx"];
function isFileImport(source) {
if (source.startsWith(".")) {
return true;
}
if (source.startsWith("/")) {
return true;
}
if (source.match(/[a-z]:/i)) {
return true;
}
return false;
}
async function makeRollupConfigs(options) {
var _a;
const configs = new Array();
const targetDir = (_a = options.targetDir) != null ? _a : index.paths.targetDir;
let targetPkg = options.packageJson;
if (!targetPkg) {
const packagePath = path.resolve(targetDir, "package.json");
targetPkg = await fs__default["default"].readJson(packagePath);
}
const onwarn = ({ code, message }) => {
if (code === "EMPTY_BUNDLE") {
return;
}
if (options.logPrefix) {
console.log(options.logPrefix + message);
} else {
console.log(message);
}
};
const distDir = path.resolve(targetDir, "dist");
const entryPoints$1 = entryPoints.readEntryPoints(targetPkg);
for (const { path: path$1, name, ext } of entryPoints$1) {
if (!SCRIPT_EXTS.includes(ext)) {
continue;
}
if (options.outputs.has(Output.cjs) || options.outputs.has(Output.esm)) {
const output = new Array();
const mainFields = ["module", "main"];
if (options.outputs.has(Output.cjs)) {
output.push({
dir: distDir,
entryFileNames: `${name}.cjs.js`,
chunkFileNames: `cjs/${name}/[name]-[hash].cjs.js`,
format: "commonjs",
sourcemap: true
});
}
if (options.outputs.has(Output.esm)) {
output.push({
dir: distDir,
entryFileNames: `${name}.esm.js`,
chunkFileNames: `esm/${name}/[name]-[hash].esm.js`,
format: "module",
sourcemap: true
});
mainFields.unshift("browser");
}
configs.push({
input: path.resolve(targetDir, path$1),
output,
onwarn,
preserveEntrySignatures: "strict",
// All module imports are always marked as external
external: (source, importer, isResolved) => Boolean(importer && !isResolved && !isFileImport(source)),
plugins: [
resolve__default["default"]({ mainFields }),
commonjs__default["default"]({
include: /node_modules/,
exclude: [/\/[^/]+\.(?:stories|test)\.[^/]+$/]
}),
postcss__default["default"](),
forwardFileImports({
exclude: /\.icon\.svg$/,
include: [
/\.svg$/,
/\.png$/,
/\.gif$/,
/\.jpg$/,
/\.jpeg$/,
/\.eot$/,
/\.woff$/,
/\.woff2$/,
/\.ttf$/
]
}),
json__default["default"](),
yaml__default["default"](),
svgr__default["default"]({
include: /\.icon\.svg$/,
template: svgrTemplate.svgrTemplate
}),
esbuild__default["default"]({
target: "es2019",
minify: options.minify
})
]
});
}
if (options.outputs.has(Output.types) && !options.useApiExtractor) {
const typesInput = index.paths.resolveTargetRoot(
"dist-types",
path.relative(index.paths.targetRoot, targetDir),
path$1.replace(/\.ts$/, ".d.ts")
);
const declarationsExist = await fs__default["default"].pathExists(typesInput);
if (!declarationsExist) {
const declarationPath = path.relative(targetDir, typesInput);
throw new Error(
`No declaration files found at ${declarationPath}, be sure to run ${chalk__default["default"].bgRed.white(
"yarn tsc"
)} to generate .d.ts files before packaging`
);
}
configs.push({
input: typesInput,
output: {
file: path.resolve(distDir, `${name}.d.ts`),
format: "es"
},
external: [
/\.css$/,
/\.scss$/,
/\.sass$/,
/\.svg$/,
/\.eot$/,
/\.woff$/,
/\.woff2$/,
/\.ttf$/
],
onwarn,
plugins: [dts__default["default"]()]
});
}
}
return configs;
}
async function buildTypeDefinitionsWorker(workerData, sendMessage) {
try {
require("@microsoft/api-extractor");
} catch (error) {
throw new Error(
"Failed to resolve @microsoft/api-extractor, it must best installed as a dependency of your project in order to use experimental type builds"
);
}
const { dirname } = require("path");
const { entryPoints, workerConfigs, typescriptCompilerFolder } = workerData;
const apiExtractor = require("@microsoft/api-extractor");
const { Extractor, ExtractorConfig, CompilerState } = apiExtractor;
const {
PackageJsonLookup
// eslint-disable-next-line @backstage/no-undeclared-imports
} = require("@rushstack/node-core-library/lib/PackageJsonLookup");
const old = PackageJsonLookup.prototype.tryGetPackageJsonFilePathFor;
PackageJsonLookup.prototype.tryGetPackageJsonFilePathFor = function tryGetPackageJsonFilePathForPatch(path) {
if (path.includes("@material-ui") && !dirname(path).endsWith("@material-ui")) {
return void 0;
}
return old.call(this, path);
};
let compilerState;
for (const { extractorOptions, targetTypesDir } of workerConfigs) {
const extractorConfig = ExtractorConfig.prepare(extractorOptions);
if (!compilerState) {
compilerState = CompilerState.create(extractorConfig, {
additionalEntryPoints: entryPoints
});
}
const extractorResult = Extractor.invoke(extractorConfig, {
compilerState,
localBuild: false,
typescriptCompilerFolder,
showVerboseMessages: false,
showDiagnostics: false,
messageCallback: (message) => {
message.handled = true;
sendMessage({ message, targetTypesDir });
}
});
if (!extractorResult.succeeded) {
throw new Error(
`Type definition build completed with ${extractorResult.errorCount} errors and ${extractorResult.warningCount} warnings`
);
}
}
}
const ignoredMessages = /* @__PURE__ */ new Set(["tsdoc-undefined-tag", "ae-forgotten-export"]);
async function buildTypeDefinitions(targetDirs = [index.paths.targetDir]) {
const packageDirs = targetDirs.map(
(dir) => path.relative(index.paths.targetRoot, dir)
);
const entryPoints = await Promise.all(
packageDirs.map(async (dir) => {
const entryPoint = index.paths.resolveTargetRoot(
"dist-types",
dir,
"src/index.d.ts"
);
const declarationsExist = await fs__default["default"].pathExists(entryPoint);
if (!declarationsExist) {
throw new Error(
`No declaration files found at ${entryPoint}, be sure to run ${chalk__default["default"].bgRed.white(
"yarn tsc"
)} to generate .d.ts files before packaging`
);
}
return entryPoint;
})
);
const workerConfigs = packageDirs.map((packageDir) => {
const targetDir = index.paths.resolveTargetRoot(packageDir);
const targetTypesDir = index.paths.resolveTargetRoot("dist-types", packageDir);
const extractorOptions = {
configObject: {
mainEntryPointFilePath: path.resolve(targetTypesDir, "src/index.d.ts"),
bundledPackages: [],
compiler: {
skipLibCheck: true,
tsconfigFilePath: index.paths.resolveTargetRoot("tsconfig.json")
},
dtsRollup: {
enabled: true,
untrimmedFilePath: path.resolve(targetDir, "dist/index.alpha.d.ts"),
betaTrimmedFilePath: path.resolve(targetDir, "dist/index.beta.d.ts"),
publicTrimmedFilePath: path.resolve(targetDir, "dist/index.d.ts")
},
newlineKind: "lf",
projectFolder: targetDir
},
configObjectFullPath: targetDir,
packageJsonFullPath: path.resolve(targetDir, "package.json")
};
return { extractorOptions, targetTypesDir };
});
const typescriptDir = index.paths.resolveTargetRoot("node_modules/typescript");
const hasTypescript = await fs__default["default"].pathExists(typescriptDir);
const typescriptCompilerFolder = hasTypescript ? typescriptDir : void 0;
await parallel.runWorkerThreads({
threadCount: 1,
workerData: {
entryPoints,
workerConfigs,
typescriptCompilerFolder
},
worker: buildTypeDefinitionsWorker,
onMessage: ({
message,
targetTypesDir
}) => {
if (ignoredMessages.has(message.messageId)) {
return;
}
let text = `${message.text} (${message.messageId})`;
if (message.sourceFilePath) {
text += " at ";
text += path.relative(targetTypesDir, message.sourceFilePath);
if (message.sourceFileLine) {
text += `:${message.sourceFileLine}`;
if (message.sourceFileColumn) {
text += `:${message.sourceFileColumn}`;
}
}
}
if (message.logLevel === "error") {
console.error(chalk__default["default"].red(`Error: ${text}`));
} else if (message.logLevel === "warning" || message.category === "Extractor") {
console.warn(`Warning: ${text}`);
} else {
console.log(text);
}
}
});
}
function formatErrorMessage(error) {
var _a;
let msg = "";
if (error.code === "PLUGIN_ERROR") {
if (error.plugin === "esbuild") {
msg += `${error.message}`;
if ((_a = error.errors) == null ? void 0 : _a.length) {
msg += `
`;
for (const { text, location } of error.errors) {
const { line, column } = location;
const path$1 = path.relative(index.paths.targetDir, error.id);
const loc = chalk__default["default"].cyan(`${path$1}:${line}:${column}`);
if (text === 'Unexpected "<"' && error.id.endsWith(".js")) {
msg += `${loc}: ${text}, JavaScript files with JSX should use a .jsx extension`;
} else {
msg += `${loc}: ${text}`;
}
}
}
} else {
msg += `(plugin ${error.plugin}) ${error}
`;
}
} else {
if (error.loc) {
const file = `${index.paths.resolveTarget(error.loc.file || error.id)}`;
const pos = `${error.loc.line}:${error.loc.column}`;
msg += `${file} [${pos}]
`;
} else if (error.id) {
msg += `${index.paths.resolveTarget(error.id)}
`;
}
msg += `${error}
`;
if (error.url) {
msg += `${chalk__default["default"].cyan(error.url)}
`;
}
if (error.frame) {
msg += `${chalk__default["default"].dim(error.frame)}
`;
}
}
return msg;
}
async function rollupBuild(config) {
try {
const bundle = await rollup.rollup(config);
if (config.output) {
for (const output of [config.output].flat()) {
await bundle.generate(output);
await bundle.write(output);
}
}
} catch (error) {
throw new Error(formatErrorMessage(error));
}
}
const buildPackage = async (options) => {
try {
const { resolutions } = await fs__default["default"].readJson(
index.paths.resolveTargetRoot("package.json")
);
if (resolutions == null ? void 0 : resolutions.esbuild) {
console.warn(
chalk__default["default"].red(
'Your root package.json contains a "resolutions" entry for "esbuild". This was included in older @backstage/create-app templates in order to work around build issues that have since been fixed. Please remove the entry and run `yarn install`'
)
);
}
} catch {
}
const rollupConfigs = await makeRollupConfigs(options);
await fs__default["default"].remove(index.paths.resolveTarget("dist"));
const buildTasks = rollupConfigs.map(rollupBuild);
if (options.outputs.has(Output.types) && options.useApiExtractor) {
buildTasks.push(buildTypeDefinitions());
}
await Promise.all(buildTasks);
};
const buildPackages = async (options) => {
if (options.some((opt) => !opt.targetDir)) {
throw new Error("targetDir must be set for all build options");
}
const rollupConfigs = await Promise.all(options.map(makeRollupConfigs));
await Promise.all(
options.map(({ targetDir }) => fs__default["default"].remove(path.resolve(targetDir, "dist")))
);
const buildTasks = rollupConfigs.flat().map((opts) => () => rollupBuild(opts));
const typeDefinitionTargetDirs = options.filter(
({ outputs, useApiExtractor }) => outputs.has(Output.types) && useApiExtractor
).map((_) => _.targetDir);
if (typeDefinitionTargetDirs.length > 0) {
buildTasks.unshift(() => buildTypeDefinitions(typeDefinitionTargetDirs));
}
await parallel.runParallelWorkers({
items: buildTasks,
worker: async (task) => task()
});
};
function getOutputsForRole(role) {
const outputs = /* @__PURE__ */ new Set();
for (const output of packageRoles.getRoleInfo(role).output) {
if (output === "cjs") {
outputs.add(Output.cjs);
}
if (output === "esm") {
outputs.add(Output.esm);
}
if (output === "types") {
outputs.add(Output.types);
}
}
return outputs;
}
const UNSAFE_PACKAGES = [
...Object.keys(index.dependencies),
...Object.keys(index.devDependencies)
];
function prefixLogFunc(prefix, out) {
return (data) => {
for (const line of data.toString("utf8").split(/\r?\n/)) {
process[out].write(`${prefix} ${line}
`);
}
};
}
async function createDistWorkspace(packageNames, options = {}) {
var _a, _b, _c, _d, _e;
const targetDir = (_a = options.targetDir) != null ? _a : await fs__default["default"].mkdtemp(path.resolve(os.tmpdir(), "dist-workspace"));
const packages = await PackageGraph.PackageGraph.listTargetPackages();
const packageGraph = PackageGraph.PackageGraph.fromPackages(packages);
const targetNames = packageGraph.collectPackageNames(packageNames, (node) => {
if (node.packageJson.bundled) {
return void 0;
}
return node.publishedLocalDependencies.keys();
});
const targets = Array.from(targetNames).map((name) => packageGraph.get(name));
if (options.buildDependencies) {
const exclude = (_b = options.buildExcludes) != null ? _b : [];
const toBuild = new Set(
targets.map((_) => _.name).filter((name) => !exclude.includes(name))
);
const standardBuilds = new Array();
const customBuild = new Array();
for (const pkg of packages) {
if (!toBuild.has(pkg.packageJson.name)) {
continue;
}
const role = (_c = pkg.packageJson.backstage) == null ? void 0 : _c.role;
if (!role) {
console.warn(
`Building ${pkg.packageJson.name} separately because it has no role`
);
customBuild.push({ dir: pkg.dir, name: pkg.packageJson.name });
continue;
}
const buildScript = (_d = pkg.packageJson.scripts) == null ? void 0 : _d.build;
if (!buildScript) {
customBuild.push({ dir: pkg.dir, name: pkg.packageJson.name });
continue;
}
if (!buildScript.startsWith("backstage-cli package build")) {
console.warn(
`Building ${pkg.packageJson.name} separately because it has a custom build script, '${buildScript}'`
);
customBuild.push({ dir: pkg.dir, name: pkg.packageJson.name });
continue;
}
if (packageRoles.getRoleInfo(role).output.includes("bundle")) {
console.warn(
`Building ${pkg.packageJson.name} separately because it is a bundled package`
);
customBuild.push({ dir: pkg.dir, name: pkg.packageJson.name });
continue;
}
const outputs = getOutputsForRole(role);
outputs.delete(Output.types);
if (outputs.size > 0) {
standardBuilds.push({
targetDir: pkg.dir,
packageJson: pkg.packageJson,
outputs,
logPrefix: `${chalk__default["default"].cyan(path.relative(index.paths.targetRoot, pkg.dir))}: `,
// No need to detect these for the backend builds, we assume no minification or types
minify: false,
useApiExtractor: false
});
}
}
await buildPackages(standardBuilds);
if (customBuild.length > 0) {
await parallel.runParallelWorkers({
items: customBuild,
worker: async ({ name, dir }) => {
await run.run("yarn", ["run", "build"], {
cwd: dir,
stdoutLogFunc: prefixLogFunc(`${name}: `, "stdout"),
stderrLogFunc: prefixLogFunc(`${name}: `, "stderr")
});
}
});
}
}
await moveToDistWorkspace(targetDir, targets);
const files = (_e = options.files) != null ? _e : ["yarn.lock", "package.json"];
for (const file of files) {
const src = typeof file === "string" ? file : file.src;
const dest = typeof file === "string" ? file : file.dest;
await fs__default["default"].copy(index.paths.resolveTargetRoot(src), path.resolve(targetDir, dest));
}
if (options.skeleton) {
const skeletonFiles = targets.map((target) => {
const dir = path.relative(index.paths.targetRoot, target.dir);
return path.join(dir, "package.json");
}).sort();
await tar__default["default"].create(
{
file: path.resolve(targetDir, options.skeleton),
cwd: targetDir,
portable: true,
noMtime: true,
gzip: options.skeleton.endsWith(".gz")
},
skeletonFiles
);
}
return targetDir;
}
const FAST_PACK_SCRIPTS = [
void 0,
"backstage-cli prepack",
"backstage-cli package prepack"
];
async function moveToDistWorkspace(workspaceDir, localPackages) {
const [fastPackPackages, slowPackPackages] = partition__default["default"](
localPackages,
(pkg) => {
var _a;
return FAST_PACK_SCRIPTS.includes((_a = pkg.packageJson.scripts) == null ? void 0 : _a.prepack);
}
);
await Promise.all(
fastPackPackages.map(async (target) => {
console.log(`Moving ${target.name} into dist workspace`);
const outputDir = path.relative(index.paths.targetRoot, target.dir);
const absoluteOutputPath = path.resolve(workspaceDir, outputDir);
await productionPack.productionPack({
packageDir: target.dir,
targetDir: absoluteOutputPath
});
})
);
async function pack(target, archive) {
var _a, _b;
console.log(`Repacking ${target.name} into dist workspace`);
const archivePath = path.resolve(workspaceDir, archive);
await run.run("yarn", ["pack", "--filename", archivePath], {
cwd: target.dir
});
if ((_b = (_a = target.packageJson) == null ? void 0 : _a.scripts) == null ? void 0 : _b.postpack) {
await run.run("yarn", ["postpack"], { cwd: target.dir });
}
const outputDir = path.relative(index.paths.targetRoot, target.dir);
const absoluteOutputPath = path.resolve(workspaceDir, outputDir);
await fs__default["default"].ensureDir(absoluteOutputPath);
await tar__default["default"].extract({
file: archivePath,
cwd: absoluteOutputPath,
strip: 1
});
await fs__default["default"].remove(archivePath);
if (target.packageJson.bundled) {
const pkgJson = await fs__default["default"].readJson(
path.resolve(absoluteOutputPath, "package.json")
);
delete pkgJson.dependencies;
delete pkgJson.devDependencies;
delete pkgJson.peerDependencies;
delete pkgJson.optionalDependencies;
await fs__default["default"].writeJson(
path.resolve(absoluteOutputPath, "package.json"),
pkgJson,
{
spaces: 2
}
);
}
}
const [unsafePackages, safePackages] = partition__default["default"](
slowPackPackages,
(p) => UNSAFE_PACKAGES.includes(p.name)
);
for (const target of unsafePackages) {
await pack(target, `temp-package.tgz`);
}
await Promise.all(
safePackages.map(
async (target, index) => pack(target, `temp-package-${index}.tgz`)
)
);
}
exports.Output = Output;
exports.buildPackage = buildPackage;
exports.buildPackages = buildPackages;
exports.createDistWorkspace = createDistWorkspace;
exports.getOutputsForRole = getOutputsForRole;
//# sourceMappingURL=createDistWorkspace-4f496e3c.cjs.js.map
;