UNPKG

oclif

Version:

oclif: create your own CLI

251 lines (250 loc) 12.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.build = build; const core_1 = require("@oclif/core"); const find_yarn_workspace_root_1 = __importDefault(require("find-yarn-workspace-root")); const fs_extra_1 = require("fs-extra"); const node_child_process_1 = require("node:child_process"); const node_fs_1 = require("node:fs"); const promises_1 = require("node:fs/promises"); const node_path_1 = __importDefault(require("node:path")); const node_util_1 = require("node:util"); const log_1 = require("../log"); const upload_util_1 = require("../upload-util"); const util_1 = require("../util"); const bin_1 = require("./bin"); const node_1 = require("./node"); const exec = (0, node_util_1.promisify)(node_child_process_1.exec); const pack = async (from, to, c) => { const cwd = node_path_1.default.dirname(from); await (0, promises_1.mkdir)(node_path_1.default.dirname(to), { recursive: true }); (0, log_1.log)(`packing tarball from ${(0, util_1.prettifyPaths)(node_path_1.default.dirname(from))} to ${(0, util_1.prettifyPaths)(to)}`); const platformFlag = c.tarFlags?.[process.platform] ?? ''; if (to.endsWith('gz')) { return exec(`tar czf ${to} ${node_path_1.default.basename(from)} ${platformFlag}`, { cwd }); } await exec(`tar cfJ ${to} ${node_path_1.default.basename(from)} ${platformFlag}`, { cwd }); }; const isYarnProject = (yarnRootPath) => { const yarnLockFileName = 'yarn.lock'; const rootYarnLockFilePath = node_path_1.default.join(yarnRootPath, yarnLockFileName); return (0, node_fs_1.existsSync)(rootYarnLockFilePath); }; const copyYarnDirectory = async (relativePath, yarnRootPath, workspacePath) => { const rootYarnDirectoryPath = node_path_1.default.join(yarnRootPath, relativePath); const workspaceYarnDirectoryPath = node_path_1.default.join(workspacePath, relativePath); if ((0, node_fs_1.existsSync)(rootYarnDirectoryPath)) { // create the directory if it does not exist if (!(0, node_fs_1.existsSync)(workspaceYarnDirectoryPath)) { await (0, promises_1.mkdir)(workspaceYarnDirectoryPath, { recursive: true }); } // recursively copy all files in the directory await (0, fs_extra_1.copy)(rootYarnDirectoryPath, workspaceYarnDirectoryPath); } }; const copyCoreYarnFiles = async (yarnRootPath, workspacePath) => { // copy yarn dependencies lock file const yarnLockFileName = 'yarn.lock'; const rootYarnLockFilePath = node_path_1.default.join(yarnRootPath, yarnLockFileName); const workspaceYarnLockFilePath = node_path_1.default.join(workspacePath, yarnLockFileName); if ((0, node_fs_1.existsSync)(rootYarnLockFilePath)) { await (0, fs_extra_1.copy)(rootYarnLockFilePath, workspaceYarnLockFilePath); } // copy yarn configuration file const yarnConfigFileName = '.yarnrc.yml'; const rootYarnConfigFilePath = node_path_1.default.join(yarnRootPath, yarnConfigFileName); const workspaceYarnConfigFilePath = node_path_1.default.join(workspacePath, yarnConfigFileName); if ((0, node_fs_1.existsSync)(rootYarnConfigFilePath)) { await (0, fs_extra_1.copy)(rootYarnConfigFilePath, workspaceYarnConfigFilePath); } // copy yarn releases e.g. yarn may be installed via a local config path like "yarnPath" await copyYarnDirectory('./.yarn/releases/', yarnRootPath, workspacePath); // copy yarn plugins if they exists await copyYarnDirectory('./.yarn/plugins/', yarnRootPath, workspacePath); // copy yarn patches if they exists await copyYarnDirectory('./.yarn/patches/', yarnRootPath, workspacePath); }; async function build(c, options = {}) { (0, log_1.log)(`gathering workspace for ${c.config.bin} to ${c.workspace()}`); await extractCLI(options.tarball ?? (await packCLI(c)), c); await updatePJSON(c); await addDependencies(c); await (0, bin_1.writeBinScripts)({ baseWorkspace: c.workspace(), config: c.config, nodeOptions: c.nodeOptions, nodeVersion: c.nodeVersion, }); await pretarball(c); if (options.pruneLockfiles) { await removeLockfiles(c); } if (!c.updateConfig.s3?.host || !c.updateConfig.s3?.bucket) { core_1.ux.warn('No S3 bucket or host configured. CLI will not be able to update itself.'); } const targetsToBuild = c.targets.filter((t) => !options.platform || options.platform === t.platform); if (options.parallel) { (0, log_1.log)(`will build ${targetsToBuild.length} targets in parallel`); await Promise.all(targetsToBuild.map((t) => buildTarget(t, c, options))); } else { (0, log_1.log)(`will build ${targetsToBuild.length} targets sequentially`); for (const target of targetsToBuild) { // eslint-disable-next-line no-await-in-loop await buildTarget(target, c, options); } (0, log_1.log)(`finished building ${targetsToBuild.length} targets sequentially`); } } const isLockFile = (f) => f.endsWith('package-lock.json') || f.endsWith('yarn.lock') || f.endsWith('npm-shrinkwrap.json') || f.endsWith('oclif.lock') || f.endsWith('pnpm-lock.yaml'); /** recursively remove all lockfiles from tarball after installing dependencies */ const removeLockfiles = async (c) => { const files = await (0, promises_1.readdir)(c.workspace(), { recursive: true }); const lockfiles = files.filter((f) => isLockFile(f)).map((f) => node_path_1.default.join(c.workspace(), f)); (0, log_1.log)(`removing ${lockfiles.length} lockfiles`); await Promise.all(lockfiles.map((f) => (0, fs_extra_1.remove)(f))); }; /** runs the pretarball script from the cli being packed */ const pretarball = async (c) => { const pjson = await (0, fs_extra_1.readJSON)(node_path_1.default.join(c.workspace(), 'package.json')); if (!pjson.scripts.pretarball) return; const yarnRoot = (0, find_yarn_workspace_root_1.default)(c.root) || c.root; let script = 'npm run pretarball'; if ((0, node_fs_1.existsSync)(node_path_1.default.join(yarnRoot, 'yarn.lock'))) script = 'yarn run pretarball'; else if ((0, node_fs_1.existsSync)(node_path_1.default.join(c.root, 'pnpm-lock.yaml'))) script = 'pnpm run pretarball'; (0, log_1.log)(`running pretarball via ${script} in ${c.workspace()}`); await exec(script, { cwd: c.workspace() }); }; const updatePJSON = async (c) => { const pjsonPath = node_path_1.default.join(c.workspace(), 'package.json'); const pjson = await (0, fs_extra_1.readJSON)(pjsonPath); pjson.version = c.config.version; pjson.oclif = pjson.oclif ?? {}; pjson.oclif.update = pjson.oclif.update ?? {}; pjson.oclif.update.s3 = pjson.oclif.update.s3 ?? {}; pjson.oclif.update.s3.bucket = c.s3Config.bucket; await (0, fs_extra_1.writeJSON)(pjsonPath, pjson, { spaces: 2 }); }; const addDependencies = async (c) => { const yarnRoot = (0, find_yarn_workspace_root_1.default)(c.root) || c.root; if (isYarnProject(yarnRoot)) { await copyCoreYarnFiles(yarnRoot, c.workspace()); const { stdout } = await exec('yarn -v'); const yarnVersion = stdout.charAt(0); if (yarnVersion === '1') { await exec('yarn --no-progress --production --non-interactive', { cwd: c.workspace() }); } else if (yarnVersion === '2') { throw new Error('Yarn 2 is not supported yet. Try using Yarn 1, or Yarn 3'); } else { try { await exec('yarn workspaces focus --production', { cwd: c.workspace() }); } catch (error) { if (error instanceof Error && error.message.includes('Command not found')) { throw new Error('Missing workspace tools. Run `yarn plugin import workspace-tools`.'); } throw error; } } } else if ((0, node_fs_1.existsSync)(node_path_1.default.join(c.root, 'pnpm-lock.yaml'))) { await (0, fs_extra_1.copy)(node_path_1.default.join(c.root, 'pnpm-lock.yaml'), node_path_1.default.join(c.workspace(), 'pnpm-lock.yaml')); await exec('pnpm install --production', { cwd: c.workspace() }); } else { const lockpath = (0, node_fs_1.existsSync)(node_path_1.default.join(c.root, 'package-lock.json')) ? node_path_1.default.join(c.root, 'package-lock.json') : node_path_1.default.join(c.root, 'npm-shrinkwrap.json'); await (0, fs_extra_1.copy)(lockpath, node_path_1.default.join(c.workspace(), node_path_1.default.basename(lockpath))); await exec('npm install --production', { cwd: c.workspace() }); } }; const packCLI = async (c) => { const { stdout } = await exec('npm pack --unsafe-perm', { cwd: c.root }); return node_path_1.default.join(c.root, stdout.trim().split('\n').pop()); }; const extractCLI = async (tarball, c) => { const workspace = c.workspace(); await (0, fs_extra_1.emptyDir)(workspace); const tarballNewLocation = node_path_1.default.join(workspace, node_path_1.default.basename(tarball)); await (0, fs_extra_1.move)(tarball, tarballNewLocation); const tarCommand = `tar -xzf "${tarballNewLocation}"${process.platform === 'win32' ? ' --force-local' : ''}`; await exec(tarCommand, { cwd: workspace }); const files = await (0, promises_1.readdir)(node_path_1.default.join(workspace, 'package'), { withFileTypes: true }); await Promise.all(files.map((i) => (0, fs_extra_1.move)(node_path_1.default.join(workspace, 'package', i.name), node_path_1.default.join(workspace, i.name)))); await Promise.all([ (0, promises_1.rm)(node_path_1.default.join(workspace, 'package'), { recursive: true }), (0, promises_1.rm)(node_path_1.default.join(workspace, node_path_1.default.basename(tarball)), { recursive: true }), (0, fs_extra_1.remove)(node_path_1.default.join(workspace, 'bin', 'run.cmd')), ]); }; const buildTarget = async (target, c, options) => { const workspace = c.workspace(target); const { arch, platform } = target; const { bin, version } = c.config; const { gitSha: sha } = c; const templateShortKeyCommonOptions = { arch, bin, platform, sha, version }; const [gzLocalKey, xzLocalKey] = ['.tar.gz', '.tar.xz'].map((ext) => (0, upload_util_1.templateShortKey)('versioned', { ...templateShortKeyCommonOptions, ext })); const base = node_path_1.default.basename(gzLocalKey); (0, log_1.log)(`building target ${base}`); (0, log_1.log)('copying workspace', c.workspace(), workspace); await (0, fs_extra_1.emptyDir)(workspace); await (0, fs_extra_1.copy)(c.workspace(), workspace); await (0, node_1.fetchNodeBinary)({ arch, nodeVersion: c.nodeVersion, output: node_path_1.default.join(workspace, 'bin', 'node'), platform, tmp: node_path_1.default.join(c.config.root, 'tmp'), }); if (options.pack === false) return; if (options.parallel) { await Promise.all([ pack(workspace, c.dist(gzLocalKey), c), ...(c.xz ? [pack(workspace, c.dist(xzLocalKey), c)] : []), ]); } else { await pack(workspace, c.dist(gzLocalKey), c); if (c.xz) await pack(workspace, c.dist(xzLocalKey), c); } if (!c.updateConfig.s3?.host) return; const rollout = typeof c.updateConfig.autoupdate === 'object' && c.updateConfig.autoupdate.rollout; const gzCloudKey = `${(0, upload_util_1.commitAWSDir)(version, sha, c.updateConfig.s3)}/${gzLocalKey}`; const xzCloudKey = `${(0, upload_util_1.commitAWSDir)(version, sha, c.updateConfig.s3)}/${xzLocalKey}`; const [sha256gz, sha256xz] = await Promise.all([ (0, util_1.hash)('sha256', c.dist(gzLocalKey)), ...(c.xz ? [(0, util_1.hash)('sha256', c.dist(xzLocalKey))] : []), ]); const manifest = { baseDir: (0, upload_util_1.templateShortKey)('baseDir', { ...target, bin }), gz: c.config.s3Url(gzCloudKey), node: { compatible: c.config.pjson.engines.node, recommended: c.nodeVersion, }, rollout: rollout === false ? undefined : rollout, sha, sha256gz, sha256xz, version, xz: c.xz ? c.config.s3Url(xzCloudKey) : undefined, }; const manifestFilepath = c.dist((0, upload_util_1.templateShortKey)('manifest', templateShortKeyCommonOptions)); await (0, fs_extra_1.writeJSON)(manifestFilepath, manifest, { spaces: 2 }); };