UNPKG

@backstage/cli

Version:

CLI for developing Backstage plugins and apps

701 lines (688 loc) • 24.4 kB
'use strict'; 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