UNPKG

oclif

Version:

oclif: create your own CLI

195 lines (194 loc) 9.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const core_1 = require("@oclif/core"); const fs = __importStar(require("fs-extra")); const node_child_process_1 = require("node:child_process"); const fsPromises = __importStar(require("node:fs/promises")); const node_path_1 = __importDefault(require("node:path")); const node_util_1 = require("node:util"); const semver_1 = require("semver"); const Tarballs = __importStar(require("../../tarballs")); const upload_util_1 = require("../../upload-util"); const util_1 = require("../../util"); const exec = (0, node_util_1.promisify)(node_child_process_1.exec); const scripts = { /* eslint-disable no-useless-escape */ bin: (config) => `#!/usr/bin/env bash set -e echoerr() { echo "$@" 1>&2; } get_script_dir () { SOURCE="\${BASH_SOURCE[0]}" # While \$SOURCE is a symlink, resolve it while [ -h "\$SOURCE" ]; do DIR="\$( cd -P "\$( dirname "\$SOURCE" )" && pwd )" SOURCE="\$( readlink "\$SOURCE" )" # If \$SOURCE was a relative symlink (so no "/" as prefix, need to resolve it relative to the symlink base directory [[ \$SOURCE != /* ]] && SOURCE="\$DIR/\$SOURCE" done DIR="\$( cd -P "\$( dirname "\$SOURCE" )" && pwd )" echo "\$DIR" } DIR=\$(get_script_dir) export ${config.scopedEnvVarKey('UPDATE_INSTRUCTIONS')}="update with \\"sudo apt update && sudo apt install ${config.bin}\\"" \$DIR/node \$DIR/run "\$@" `, /* eslint-enable no-useless-escape */ control: (config, arch) => `Package: ${config.config.bin} Version: ${(0, upload_util_1.debVersion)(config)} Section: main Priority: standard Architecture: ${arch} Maintainer: ${config.config.scopedEnvVar('AUTHOR') || config.config.pjson.author} Description: ${config.config.pjson.description} Aliases: ${config.config.binAliases?.join(', ')} `, ftparchive: (config) => ` APT::FTPArchive::Release { Origin "${config.scopedEnvVar('AUTHOR') || config.pjson.author}"; Suite "stable"; `, }; class PackDeb extends core_1.Command { static description = 'Add a pretarball script to your package.json if you need to run any scripts before the tarball is created.'; static flags = { compression: core_1.Flags.option({ options: ['gzip', 'none', 'xz', 'zstd'], })({ char: 'z', description: 'For more details see the `-Zcompress-type` section at https://man7.org/linux/man-pages/man1/dpkg-deb.1.html', summary: 'Override the default compression used by dpkg-deb.', }), 'prune-lockfiles': core_1.Flags.boolean({ description: 'remove lockfiles in the tarball.', exclusive: ['tarball'] }), root: core_1.Flags.string({ char: 'r', default: '.', description: 'Path to oclif CLI root.', required: true }), sha: core_1.Flags.string({ description: '7-digit short git commit SHA (defaults to current checked out commit).', required: false, }), tarball: core_1.Flags.string({ char: 't', description: 'Optionally specify a path to a tarball already generated by NPM.', exclusive: ['prune-lockfiles'], required: false, }), }; static summary = 'Pack CLI into debian package.'; async run() { if (process.platform !== 'linux') throw new Error('debian packing must be run on linux'); const { flags } = await this.parse(PackDeb); const buildConfig = await Tarballs.buildConfig(flags.root, { sha: flags?.sha }); const { config } = buildConfig; await Tarballs.build(buildConfig, { pack: false, parallel: true, platform: 'linux', pruneLockfiles: flags['prune-lockfiles'], tarball: flags.tarball, }); const dist = buildConfig.dist('deb'); await fs.emptyDir(dist); const build = async (arch) => { this.log(`building debian / ${arch}`); const target = { arch, platform: 'linux' }; const versionedDebBase = (0, upload_util_1.templateShortKey)('deb', { arch: (0, upload_util_1.debArch)(arch), bin: config.bin, versionShaRevision: (0, upload_util_1.debVersion)(buildConfig), }); const workspace = node_path_1.default.join(buildConfig.tmp, 'apt', versionedDebBase.replace('.deb', '.apt')); await fs.remove(workspace); await Promise.all([ fsPromises.mkdir(node_path_1.default.join(workspace, 'DEBIAN'), { recursive: true }), fsPromises.mkdir(node_path_1.default.join(workspace, 'usr', 'bin'), { recursive: true }), ]); await fs.copy(buildConfig.workspace(target), node_path_1.default.join(workspace, 'usr', 'lib', config.dirname)); await Promise.all([ // usr/lib/oclif/bin/oclif (the executable) fsPromises.writeFile(node_path_1.default.join(workspace, 'usr', 'lib', config.dirname, 'bin', config.bin), scripts.bin(config), { mode: 0o755 }), fsPromises.writeFile(node_path_1.default.join(workspace, 'DEBIAN', 'control'), scripts.control(buildConfig, (0, upload_util_1.debArch)(arch))), ]); // symlink usr/bin/oclif points to usr/lib/oclif/bin/oclif await exec(`ln -s "${node_path_1.default.join('..', 'lib', config.dirname, 'bin', config.bin)}" "${config.bin}"`, { cwd: node_path_1.default.join(workspace, 'usr', 'bin'), }); config.binAliases?.map((alias) => exec(`ln -sf "${node_path_1.default.join('..', 'lib', config.dirname, 'bin', config.bin)}" "${alias}"`, { cwd: node_path_1.default.join(workspace, 'usr', 'bin'), })); await exec(`sudo chown -R root "${workspace}"`); await exec(`sudo chgrp -R root "${workspace}"`); const dpkgDeb = flags.compression ? `dpkg-deb --build "-Z${flags.compression}"` : 'dpkg-deb --build'; await exec(`${dpkgDeb} "${workspace}" "${node_path_1.default.join(dist, versionedDebBase)}"`); this.log(`finished building debian / ${arch}`); }; const arches = (0, util_1.uniq)(buildConfig.targets .filter((t) => t.platform === 'linux') .filter((t) => { // Skip 32-bit Arm for Node.js 24+ if (t.arch === 'arm' && (0, semver_1.gt)(buildConfig.nodeVersion, '24.0.0')) { return false; } return true; }) .map((t) => t.arch)); await Promise.all(arches.map((a) => build(a))); await exec('apt-ftparchive packages . > Packages', { cwd: dist }); this.log('debian packages created'); await Promise.all([ exec('gzip -c Packages > Packages.gz', { cwd: dist }), exec('bzip2 -k Packages', { cwd: dist }), exec('xz -k Packages', { cwd: dist }), packForFTP(buildConfig, config, dist), ]); this.log('debian packages archived'); const gpgKey = config.scopedEnvVar('DEB_KEY'); if (gpgKey) { this.log('adding gpg signatures to Release'); await exec(`gpg --digest-algo SHA512 --clearsign -u ${gpgKey} -o InRelease Release`, { cwd: dist }); await exec(`gpg --digest-algo SHA512 -abs -u ${gpgKey} -o Release.gpg Release`, { cwd: dist }); } this.log('debian packing complete'); } } exports.default = PackDeb; async function packForFTP(buildConfig, config, dist) { const ftparchive = node_path_1.default.join(buildConfig.tmp, 'apt', 'apt-ftparchive.conf'); await fsPromises.mkdir(node_path_1.default.basename(ftparchive), { recursive: true }); await fs.writeFile(ftparchive, scripts.ftparchive(config)); await exec(`apt-ftparchive -c "${ftparchive}" release . > Release`, { cwd: dist }); }