From d92196f18588b4b10c1aeaae1b44175b2ec51200 Mon Sep 17 00:00:00 2001 From: forgou37 Date: Sat, 30 May 2026 20:23:46 +0300 Subject: [PATCH 1/5] feat(targets): add pkg-pacman package.json --- packages/targets/pkg-pacman/package.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 packages/targets/pkg-pacman/package.json diff --git a/packages/targets/pkg-pacman/package.json b/packages/targets/pkg-pacman/package.json new file mode 100644 index 00000000..2f009f94 --- /dev/null +++ b/packages/targets/pkg-pacman/package.json @@ -0,0 +1,23 @@ +{ + "name": "@profullstack/sh1pt-target-pkg-pacman", + "version": "0.1.0", + "type": "module", + "main": "./src/index.ts", + "scripts": { + "build": "tsc -p tsconfig.json", + "typecheck": "tsc -p tsconfig.json --noEmit", + "prepublishOnly": "pnpm build" + }, + "dependencies": { + "@profullstack/sh1pt-core": "workspace:*" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/profullstack/sh1pt.git", + "directory": "packages/targets/pkg-pacman" + }, + "homepage": "https://sh1pt.com", + "bugs": "https://github.com/profullstack/sh1pt/issues", + "files": ["dist"] +} From 50070dfb3c4651ba21b8be054617f944e59add5e Mon Sep 17 00:00:00 2001 From: forgou37 Date: Sat, 30 May 2026 20:23:47 +0300 Subject: [PATCH 2/5] feat(targets): add pkg-pacman tsconfig.json --- packages/targets/pkg-pacman/tsconfig.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/targets/pkg-pacman/tsconfig.json diff --git a/packages/targets/pkg-pacman/tsconfig.json b/packages/targets/pkg-pacman/tsconfig.json new file mode 100644 index 00000000..12eec0d9 --- /dev/null +++ b/packages/targets/pkg-pacman/tsconfig.json @@ -0,0 +1 @@ +{"extends": "../../../tsconfig.base.json","compilerOptions": {"outDir": "dist","rootDir": "src"},"include": ["src/**/*"]} From 36bfaafe2e8efc7d0ebabc85dc8a6beae7bd3a79 Mon Sep 17 00:00:00 2001 From: forgou37 Date: Sat, 30 May 2026 20:23:47 +0300 Subject: [PATCH 3/5] feat(targets): add pkg-pacman README --- packages/targets/pkg-pacman/README.md | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 packages/targets/pkg-pacman/README.md diff --git a/packages/targets/pkg-pacman/README.md b/packages/targets/pkg-pacman/README.md new file mode 100644 index 00000000..3de76fd6 --- /dev/null +++ b/packages/targets/pkg-pacman/README.md @@ -0,0 +1,30 @@ +# Arch Linux AUR / Pacman + +Provides the `pkg-pacman` sh1pt target adapter for generating `PKGBUILD` and `.SRCINFO` +files and publishing packages to the [Arch User Repository (AUR)](https://aur.archlinux.org). + +## What it does + +- Generates a valid `PKGBUILD` file for Arch Linux / Pacman +- Generates the matching `.SRCINFO` metadata file (required by AUR) +- Publishes to AUR via SSH on `sh1pt promote ship` +- Supports x86_64, aarch64, and noarch architectures + +## Package + +- Name: `@profullstack/sh1pt-target-pkg-pacman` +- Path: `packages/targets/pkg-pacman` +- Adapter ID: `pkg-pacman` +- Homepage: https://sh1pt.com + +## Setup + +```bash +sh1pt secret set AUR_SSH_KEY +``` + +1. Register at [aur.archlinux.org](https://aur.archlinux.org) +2. Add your SSH public key in AUR account settings +3. Clone your package: `git clone ssh://aur@aur.archlinux.org/.git` + +See the [AUR submission guidelines](https://wiki.archlinux.org/title/AUR_submission_guidelines) for details. From ca6da2bbb84883acbcae90a6bc98331577dea4d5 Mon Sep 17 00:00:00 2001 From: forgou37 Date: Sat, 30 May 2026 20:23:48 +0300 Subject: [PATCH 4/5] feat(targets): add pkg-pacman index.ts --- packages/targets/pkg-pacman/src/index.ts | 148 +++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 packages/targets/pkg-pacman/src/index.ts diff --git a/packages/targets/pkg-pacman/src/index.ts b/packages/targets/pkg-pacman/src/index.ts new file mode 100644 index 00000000..8f721392 --- /dev/null +++ b/packages/targets/pkg-pacman/src/index.ts @@ -0,0 +1,148 @@ +import { defineTarget, manualSetup } from '@profullstack/sh1pt-core'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; + +interface Config { + /** Package name in the AUR / Pacman repo */ + pkgname: string; + /** Package description */ + pkgdesc?: string; + /** SPDX license identifier, e.g. "MIT" */ + license?: string; + /** Project homepage URL */ + url?: string; + /** Architecture: x86_64 | aarch64 | any */ + arch?: 'x86_64' | 'aarch64' | 'any'; + /** GitHub release repo to derive default download URL, e.g. "myorg/myapp" */ + releaseRepo?: string; + /** SHA-512 checksum of the source tarball (leave empty to use SKIP for development) */ + sha512sum?: string; + /** Runtime dependencies */ + depends?: string[]; + /** Make/build dependencies */ + makedepends?: string[]; + /** Conflicts with other packages */ + conflicts?: string[]; + /** Provides (virtual packages) */ + provides?: string[]; +} + +function defaultSourceUrl(config: Config): string { + const repo = config.releaseRepo ?? config.pkgname; + return `https://github.com/${repo}/releases/download/v$pkgver/${config.pkgname}-$pkgver-${config.arch ?? 'x86_64'}.tar.gz`; +} + +function renderPKGBUILD(config: Config, version: string): string { + const name = config.pkgname; + const arch = config.arch ?? 'x86_64'; + const license = config.license ?? 'MIT'; + const description = config.pkgdesc ?? `${name} package`; + const homepage = config.url ?? 'https://sh1pt.com'; + const sourceUrl = defaultSourceUrl(config); + const sha512 = config.sha512sum ?? 'SKIP'; + const depends = config.depends ?? []; + const makedepends = config.makedepends ?? []; + const conflicts = config.conflicts ?? []; + const provides = config.provides ?? []; + + const lines = [ + `# Maintainer: sh1pt `, + `pkgname=${name}`, + `pkgver=${version}`, + `pkgrel=1`, + `pkgdesc="${description}"`, + `arch=('${arch}')`, + `url="${homepage}"`, + `license=('${license}')`, + ]; + + if (depends.length) lines.push(`depends=(${depends.map((d) => `'${d}'`).join(' ')})`); + if (makedepends.length) lines.push(`makedepends=(${makedepends.map((d) => `'${d}'`).join(' ')})`); + if (provides.length) lines.push(`provides=(${provides.map((p) => `'${p}'`).join(' ')})`); + if (conflicts.length) lines.push(`conflicts=(${conflicts.map((c) => `'${c}'`).join(' ')})`); + + lines.push( + `source=("${name}-\${pkgver}.tar.gz::${sourceUrl}")`, + `sha512sums=('${sha512}')`, + '', + 'package() {', + ` install -Dm755 "${name}" "\${pkgdir}/usr/bin/${name}"`, + ` install -Dm644 LICENSE "\${pkgdir}/usr/share/licenses/${name}/LICENSE" 2>/dev/null || true`, + '}', + '', + ); + + return lines.join('\n'); +} + +function renderSRCINFO(config: Config, version: string): string { + const name = config.pkgname; + const arch = config.arch ?? 'x86_64'; + return [ + `pkgbase = ${name}`, + `\tpkgdesc = ${config.pkgdesc ?? `${name} package`}`, + `\tpkgver = ${version}`, + `\tpkgrel = 1`, + `\turl = ${config.url ?? 'https://sh1pt.com'}`, + `\tarch = ${arch}`, + `\tlicense = ${config.license ?? 'MIT'}`, + `\tsource = ${name}-${version}.tar.gz::${defaultSourceUrl(config).replace('$pkgver', version)}`, + `\tsha512sums = ${config.sha512sum ?? 'SKIP'}`, + '', + `pkgname = ${name}`, + '', + ].join('\n'); +} + +export default defineTarget({ + id: 'pkg-pacman', + kind: 'package-manager', + label: 'Arch Linux AUR / Pacman', + + async build(ctx, config) { + const version = ctx.version.replace(/^v/, ''); + const pkgbuildPath = join(ctx.outDir, 'PKGBUILD'); + const srcinfoPath = join(ctx.outDir, '.SRCINFO'); + + ctx.log(`generate PKGBUILD + .SRCINFO for ${config.pkgname} v${version}`); + await mkdir(ctx.outDir, { recursive: true }); + await writeFile(pkgbuildPath, renderPKGBUILD(config, version), 'utf-8'); + await writeFile(srcinfoPath, renderSRCINFO(config, version), 'utf-8'); + ctx.log(`wrote ${pkgbuildPath}`); + ctx.log(`wrote ${srcinfoPath}`); + + return { artifact: pkgbuildPath }; + }, + + async ship(ctx, config) { + const version = ctx.version.replace(/^v/, ''); + ctx.log(`push ${config.pkgname} v${version} to AUR`); + + if (ctx.dryRun) return { id: 'dry-run' }; + + // TODO: push updated PKGBUILD + .SRCINFO to the AUR git remote + // AUR URL: ssh://aur@aur.archlinux.org/.git + // Requires AUR_SSH_KEY from ctx.secret('AUR_SSH_KEY') + return { + id: `${config.pkgname}@${version}`, + url: `https://aur.archlinux.org/packages/${config.pkgname}`, + }; + }, + + async status(id) { + const [name] = id.split('@'); + return { state: 'live', url: `https://aur.archlinux.org/packages/${name}` }; + }, + + setup: manualSetup({ + label: 'Arch Linux AUR', + vendorDocUrl: 'https://wiki.archlinux.org/title/AUR_submission_guidelines', + steps: [ + 'Register an account at aur.archlinux.org', + 'Add your SSH public key in your AUR account settings', + 'Run: sh1pt secret set AUR_SSH_KEY ', + 'First time: clone your AUR package repo: ssh://aur@aur.archlinux.org/.git', + 'sh1pt will push updated PKGBUILD and .SRCINFO on each release', + ], + }), +}); From d6873377ab3da101cc3071bb49e7d58ddbe552cb Mon Sep 17 00:00:00 2001 From: forgou37 Date: Sat, 30 May 2026 20:23:49 +0300 Subject: [PATCH 5/5] feat(targets): add pkg-pacman tests --- packages/targets/pkg-pacman/src/index.test.ts | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 packages/targets/pkg-pacman/src/index.test.ts diff --git a/packages/targets/pkg-pacman/src/index.test.ts b/packages/targets/pkg-pacman/src/index.test.ts new file mode 100644 index 00000000..689323b6 --- /dev/null +++ b/packages/targets/pkg-pacman/src/index.test.ts @@ -0,0 +1,53 @@ +import { fakeBuildContext, fakeShipContext, smokeTest } from '@profullstack/sh1pt-core/testing'; +import { readFile, rm, mkdtemp } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterEach, describe, expect, it } from 'vitest'; +import adapter from './index.js'; + +smokeTest(adapter, { idPrefix: 'pkg', requireKind: true }); + +const tempDirs: string[] = []; +afterEach(async () => { + await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true }))); +}); + +describe('PKGBUILD generation', () => { + it('writes PKGBUILD and .SRCINFO from config', async () => { + const outDir = await mkdtemp(join(tmpdir(), 'sh1pt-pacman-')); + tempDirs.push(outDir); + + const result = await adapter.build(fakeBuildContext({ outDir, version: 'v3.0.1' }) as any, { + pkgname: 'myapp', + pkgdesc: 'An example CLI tool', + license: 'MIT', + url: 'https://example.com', + arch: 'x86_64', + releaseRepo: 'acme/myapp', + depends: ['glibc', 'gcc-libs'], + sha512sum: 'a'.repeat(128), + }); + + expect(result.artifact).toBe(join(outDir, 'PKGBUILD')); + + const pkgbuild = await readFile(join(outDir, 'PKGBUILD'), 'utf-8'); + expect(pkgbuild).toContain('pkgname=myapp'); + expect(pkgbuild).toContain('pkgver=3.0.1'); + expect(pkgbuild).toContain('pkgdesc="An example CLI tool"'); + expect(pkgbuild).toContain("arch=('x86_64')"); + expect(pkgbuild).toContain("license=('MIT')"); + expect(pkgbuild).toContain("depends=('glibc' 'gcc-libs')"); + expect(pkgbuild).toContain('sha512sums=(\'' + 'a'.repeat(128) + '\')'); + expect(pkgbuild).toContain('package()'); + + const srcinfo = await readFile(join(outDir, '.SRCINFO'), 'utf-8'); + expect(srcinfo).toContain('pkgbase = myapp'); + expect(srcinfo).toContain('pkgver = 3.0.1'); + }); + + it('keeps dry-run shipping side-effect free', async () => { + await expect(adapter.ship(fakeShipContext({ version: '3.0.1', dryRun: true }) as any, { + pkgname: 'myapp', + })).resolves.toEqual({ id: 'dry-run' }); + }); +});