UNPKG

npm-pkgbuild

Version:

create ArchLinux, RPM and Debian packages from npm packages

217 lines (189 loc) 5.38 kB
import { join } from "node:path"; import { readFile } from "node:fs/promises"; import { execa } from "execa"; import { ContentEntry, IteratorContentEntry } from "content-entry"; import { transform } from "content-entry-transform"; import { aggregateFifo } from "aggregate-async-iterator"; import { keyValueTransformer, equalSeparatedKeyValuePairOptions, Uint8ArraysToLines } from "key-value-transformer"; import { Packager, VERSION_FIELD, DESCRIPTION_FIELD, NAME_FIELD } from "./packager.mjs"; import { fieldProvider, copyEntries, utf8StreamOptions, quote, filterOutUnwantedDependencies, compileFields } from "../util.mjs"; const DOCKERFILE = "Dockerfile"; function* keyValueLines(key, value, options) { if (Array.isArray(value)) { value = value.join("."); } yield `LABEL ${quote(key, '"')}=${quote(value, '"')}${options.lineEnding}`; } const labelKeyValuePairs = { ...equalSeparatedKeyValuePairOptions, keyValueLines }; const dependenciesToFrom = { node: "node", "nginx-mainline": "nginx", nginx: "nginx" }; /** * docker image build */ export class DOCKER extends Packager { static get name() { return "docker"; } static get description() { return `generate container image with ${this.name}`; } static get fields() { return fields; } /** * Check for docker presence. * @param {Object} options * @param {Object} variant * @param {string} variant.arch * @returns {Promise<boolean>} true when docker executable is present */ static async prepare(options, variant) { try { await execa(this.name, ["--version"]); return true; } catch {} return false; } async create(sources, transformer, options, publishingDetails, expander) { const { properties, staging } = await this.prepare( options, publishingDetails ); async function* headLines() { let scratch = true; for (const [k, v] of Object.entries({ ...properties.from, ...Object.fromEntries( Object.entries(properties.dependencies) .filter(filterOutUnwantedDependencies()) .filter(([k, v]) => dependenciesToFrom[k]) .map(([k, v]) => [dependenciesToFrom[k], v.replace(/[>=]*/, "")]) ) })) { scratch = false; yield `FROM ${k}:${v}\n`; } if (scratch) { yield "FROM scratch\n"; } } async function* trailingLines() { yield `WORKDIR ${expander(properties.workdir)}\n`; yield "COPY . .\n"; if (properties.entrypoints) { yield `ENTRYPOINT ["node", "${ Object.values(properties.entrypoints)[0] }"]\n`; } } const fp = fieldProvider(properties, fields); transformer.push({ name: DOCKERFILE, match: entry => entry.name === DOCKERFILE, transform: async entry => new IteratorContentEntry( entry.name, undefined, keyValueTransformer(Uint8ArraysToLines(await entry.stream), fp, { ...labelKeyValuePairs, headLines, trailingLines }) ), createEntryWhenMissing: () => new ContentEntry(DOCKERFILE) }); for await (const file of copyEntries( transform(aggregateFifo(sources), transformer), staging, expander )) { if (options.verbose) { // @ts-ignore console.log(file.destination); } } if (options.verbose) { console.log(await readFile(join(staging, DOCKERFILE), utf8StreamOptions)); } let tag = `${properties.name}:${properties.version}`; let image = ""; if (!options.dry) { const docker = await execa( this.constructor.name, [ "buildx", "build", "--tag", tag, /*"--output","type=tar,dest=out.tar",*/ staging ], { cwd: staging } ); const lines = docker.stderr.split(/\n/); const wl = lines.filter(l => l.match(/writing\s+image/)); if (wl?.[0]) { image = wl[0].split(/\s+/)[3]; } if (options.verbose) { console.log(docker.stderr); console.log(docker.stdout); } } return image; } async publish(artifact, publishingDetails, logger = console.log) { try { const url = new URL(publishingDetails.url); const repoLocation = `${url.host}/${publishingDetails.username}`; const name = `${this.properties.name}:${this.properties.version}`; logger(`Publishing to ${repoLocation}`); let p1 = await execa(this.constructor.name, [ "tag", artifact, `${repoLocation}/${name}` ]); let p2 = await execa(this.constructor.name, [ "push", `${repoLocation}/${name}` ]); //console.log(`docker tag ${artifact} ${repoLocation}/${name}`); //console.log(`docker push ${repoLocation}/${name}`); } catch (e) { console.log(e, publishingDetails.url); } } } /** * @see {@link https://docs.docker.com/engine/reference/builder/} */ const fields = compileFields({ name: { ...NAME_FIELD, set: value => value.toLowerCase() }, version: { ...VERSION_FIELD }, description: { ...DESCRIPTION_FIELD }, author: { alias: "maintainer", type: "string" }, workdir: { type: "string", default: "/", mandatory: true } });