oclif
Version:
oclif: create your own CLI
251 lines (250 loc) • 12.7 kB
JavaScript
;
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 });
};