-
Notifications
You must be signed in to change notification settings - Fork 309
/
deb.ts
153 lines (140 loc) · 6.13 KB
/
deb.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import {Command, Flags, Interfaces} from '@oclif/core'
import * as fs from 'fs-extra'
import {exec as execSync} from 'node:child_process'
import * as fsPromises from 'node:fs/promises'
import * as path from 'node:path'
import {promisify} from 'node:util'
import * as Tarballs from '../../tarballs'
import {debArch, debVersion, templateShortKey} from '../../upload-util'
import {uniq} from '../../util'
const exec = promisify(execSync)
const scripts = {
/* eslint-disable no-useless-escape */
bin: (config: Interfaces.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: Tarballs.BuildConfig, arch: string) => `Package: ${config.config.bin}
Version: ${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: Interfaces.Config) => `
APT::FTPArchive::Release {
Origin "${config.scopedEnvVar('AUTHOR') || config.pjson.author}";
Suite "stable";
`,
}
export default class PackDeb extends Command {
static description = 'Pack CLI into debian package.'
static flags = {
compression: Flags.option({
options: ['gzip', 'none', 'xz', 'zstd'] as const,
})({
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.',
}),
root: Flags.string({char: 'r', default: '.', description: 'Path to oclif CLI root.', required: true}),
tarball: Flags.string({
char: 't',
description: 'Optionally specify a path to a tarball already generated by NPM.',
required: false,
}),
}
async run(): Promise<void> {
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)
const {config} = buildConfig
await Tarballs.build(buildConfig, {pack: false, parallel: true, platform: 'linux', tarball: flags.tarball})
const dist = buildConfig.dist('deb')
await fs.emptyDir(dist)
const build = async (arch: Interfaces.ArchTypes) => {
this.log(`building debian / ${arch}`)
const target: {arch: Interfaces.ArchTypes; platform: 'linux'} = {arch, platform: 'linux'}
const versionedDebBase = templateShortKey('deb', {
arch: debArch(arch),
bin: config.bin,
versionShaRevision: debVersion(buildConfig),
})
const workspace = path.join(buildConfig.tmp, 'apt', versionedDebBase.replace('.deb', '.apt'))
await fs.remove(workspace)
await Promise.all([
fsPromises.mkdir(path.join(workspace, 'DEBIAN'), {recursive: true}),
fsPromises.mkdir(path.join(workspace, 'usr', 'bin'), {recursive: true}),
])
await fs.copy(buildConfig.workspace(target), path.join(workspace, 'usr', 'lib', config.dirname))
await Promise.all([
// usr/lib/oclif/bin/oclif (the executable)
fsPromises.writeFile(
path.join(workspace, 'usr', 'lib', config.dirname, 'bin', config.bin),
scripts.bin(config),
{mode: 0o755},
),
fsPromises.writeFile(path.join(workspace, 'DEBIAN', 'control'), scripts.control(buildConfig, debArch(arch))),
])
// symlink usr/bin/oclif points to usr/lib/oclif/bin/oclif
await exec(`ln -s "${path.join('..', 'lib', config.dirname, 'bin', config.bin)}" "${config.bin}"`, {
cwd: path.join(workspace, 'usr', 'bin'),
})
config.binAliases?.map((alias) =>
exec(`ln -sf "${path.join('..', 'lib', config.dirname, 'bin', config.bin)}" "${alias}"`, {
cwd: path.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}" "${path.join(dist, versionedDebBase)}"`)
this.log(`finished building debian / ${arch}`)
}
const arches = uniq(buildConfig.targets.filter((t) => t.platform === 'linux').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')
}
}
async function packForFTP(buildConfig: Tarballs.BuildConfig, config: Interfaces.Config, dist: string) {
const ftparchive = path.join(buildConfig.tmp, 'apt', 'apt-ftparchive.conf')
await fsPromises.mkdir(path.basename(ftparchive), {recursive: true})
await fs.writeFile(ftparchive, scripts.ftparchive(config))
await exec(`apt-ftparchive -c "${ftparchive}" release . > Release`, {cwd: dist})
}