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