npm-pkgbuild
Version:
create ArchLinux, RPM and Debian packages from npm packages
293 lines (250 loc) • 7.68 kB
JavaScript
import { join } from "node:path";
import { createWriteStream } from "node:fs";
import { readFile, writeFile } from "node:fs/promises";
import { execa } from "execa";
import { ContentEntry, IteratorContentEntry } from "content-entry";
import { transform } from "content-entry-transform";
import {
keyValueTransformer,
equalSeparatedKeyValuePairOptions,
Uint8ArraysToLines
} from "key-value-transformer";
import { aggregateFifo } from "aggregate-async-iterator";
import {
Packager,
VERSION_FIELD,
DESCRIPTION_FIELD,
NAME_FIELD
} from "./packager.mjs";
import {
copyEntries,
fieldProvider,
quote,
utf8StreamOptions,
compileFields,
normalizeExpression
} from "../util.mjs";
function* keyValueLines(key, value, options) {
yield `${keyPrefix(key)}${options.keyValueSeparator}${
Array.isArray(value)
? "(" + value.map(v => quote(v)).join(" ") + ")"
: quote(value)
}${options.lineEnding}`;
}
/**
* @type {Object} KeyValueTransformOptions
* Options to describe key value pair separated by an equal sign '='
*/
export const pkgKeyValuePairOptions = {
...equalSeparatedKeyValuePairOptions,
extractKeyValue: line => {
const m = line.match(/^(#\s*)?(\w+)=\s*\((.*)\)|(.*)/);
if (m) {
return [m[2], m[3] ? m[3].split(/\s*,\s*/) : m[4]];
}
},
keyValueLines
};
function keyPrefix(key) {
const f = fields[key];
return f?.prefix ? f.prefix + key : key;
}
const PKGBUILD = "PKGBUILD";
let _ext = ".pkg.tar.xz";
let _prepared;
let _architecture = "aarch64";
export class ARCH extends Packager {
static get name() {
return "arch";
}
static get description() {
return "generate Arch-Linux package";
}
static get fileNameExtension() {
return _ext;
}
static get fields() {
return fields;
}
static async prepare(options = {}, variant = {}) {
if (_prepared === undefined) {
try {
await execa("makepkg", ["-V"]);
const cfg = await readFile("/etc/makepkg.conf", utf8StreamOptions);
function getValue(key) {
const v = process.env[key];
if (v !== undefined) {
return v;
}
const i = cfg.indexOf(`${key}=`);
if (i > 0) {
const m = cfg
.substring(i)
.split(/\n/)[0]
.match(/=['"]([^'"]+)['"]/);
return m?.[1];
}
}
_ext = getValue("PKGEXT");
_architecture = getValue("CARCH");
_prepared = true;
} catch (e) {
_prepared = false;
}
}
return (
_prepared &&
(variant.arch === undefined || variant.arch === _architecture)
);
}
get packageFileName() {
const p = this.properties;
return `${p.name}-${p.version}-${p.release}-${p.arch}${this.fileNameExtension}`;
}
dependencyExpression(name, expression) {
return quote(`${this.packageName(name)}${normalizeExpression(expression)}`);
}
async create(sources, transformer, publishingDetails, options, expander) {
const { properties, staging, destination } = await this.prepare(options);
if (properties.source) {
properties.md5sums = ["SKIP"];
}
if (properties.hooks) {
properties.install = `${properties.name}.install`;
const out = createWriteStream(
join(staging, properties.install),
utf8StreamOptions
);
for await (const hook of this.hookContent()) {
out.write(`${hook.name}() {\n`);
out.write(hook.string);
out.write(`\n}\n`);
}
out.end();
}
const self = this;
async function* trailingLines() {
yield `
package() {
depends=(${self.makeDepends(properties.dependencies).join(" ")})
if [ "$(ls -A $srcdir)" ]
then
cp -rp $srcdir/* "$pkgdir"
### CHOWN ###
fi
}
`;
}
if (properties.backup?.[0] === "/") {
properties.backup = properties.backup.replace(/\//, "");
}
const fp = fieldProvider(properties, fields);
transformer.push({
name: PKGBUILD,
match: entry => entry.name === PKGBUILD,
transform: async entry =>
new IteratorContentEntry(
"../" + entry.name,
undefined,
keyValueTransformer(Uint8ArraysToLines(await entry.stream), fp, {
...pkgKeyValuePairOptions,
trailingLines
})
),
createEntryWhenMissing: () => new ContentEntry(PKGBUILD)
});
const ownership = [];
for await (const file of copyEntries(
transform(aggregateFifo(sources), transformer),
join(staging, "src"),
expander
)) {
if (file.owner || file.group) {
ownership.push(file);
}
if (options.verbose) {
console.log(file.destination);
}
}
if (ownership.length) {
const pkgbuild = join(staging, PKGBUILD);
let content = await readFile(pkgbuild, utf8StreamOptions);
const markerPos = content.indexOf("### CHOWN ###");
content =
content.substring(0, markerPos) +
ownership
.map(
f =>
` chown ${[f.owner || "", f.group || ""].join(":")} \"$pkgdir/${
f.destination
}\"`
)
.join("\n") +
content.substring(markerPos + 14);
await writeFile(pkgbuild, content, utf8StreamOptions);
}
if (options.verbose) {
console.log(await readFile(join(staging, PKGBUILD), utf8StreamOptions));
/*console.log("***", staging, "***");
const ls = await execa("ls", ["-lR"], { cwd: staging });
console.log(ls.stdout);
console.log("*** end ***");*/
}
if (!options.dry) {
let PACKAGER;
if (properties.contributors?.length > 0) {
PACKAGER = person(properties.contributors);
}
const makepkg = await execa("makepkg", ["--noprogressbar", "-c", "-f", "-e"], {
cwd: staging,
env: { PKGDEST: destination, PACKAGER }
});
if (options.verbose) {
console.log(makepkg.stdout);
}
}
return join(destination, this.packageFileName);
}
}
function person(contributors) {
return contributors[0].name + " " + contributors[0].email;
}
const default_attribute = { type: "string" };
const default_array_attribute = { type: "string[]" };
/**
* well known package properties
* https://www.archlinux.org/pacman/PKGBUILD.5.html
*/
const fields = compileFields({
Maintainer: { alias: "maintainer", type: "string[]", prefix: "# " },
packager: { alias: "maintainer", type: "string[]" },
pkgname: { ...NAME_FIELD, type: "string[]" },
pkgver: { ...VERSION_FIELD },
pkgrel: { alias: "release", type: "integer", default: 1, mandatory: true },
epoch: { type: "integer", default: 0 },
pkgdesc: { ...DESCRIPTION_FIELD },
url: { alias: "homepage", type: "string" },
license: { type: "string[]", mandatory: true },
install: { type: "string" },
changelog: { type: "string" },
source: default_array_attribute,
validpgpkeys: default_array_attribute,
noextract: default_attribute,
cksums: default_array_attribute,
md5sums: default_array_attribute,
sha1sums: default_array_attribute,
sha256sums: default_array_attribute,
sha384sums: default_array_attribute,
sha512sums: default_array_attribute,
groups: default_array_attribute,
arch: { ...default_array_attribute, default: ["any"], mandatory: true },
backup: default_array_attribute,
depends: default_array_attribute,
makedepends: default_attribute,
checkdepends: default_attribute,
optdepends: default_attribute,
conflicts: default_attribute,
provides: default_attribute,
replaces: default_attribute,
options: default_attribute
});