From f620a2eddfa5b084d60e3224a78d03038945d751 Mon Sep 17 00:00:00 2001 From: Alexander-Sorrell-IT <284331358+Alexander-Sorrell-IT@users.noreply.github.com> Date: Fri, 29 May 2026 14:31:08 -0500 Subject: [PATCH 1/2] feat(targets): add pkg-dnf target (dnf repo / Fedora COPR) Adds the dnf/RPM distribution target for Fedora/RHEL-like systems. - build() generates a real RPM .spec (valid version normalization) + a dnf .repo file (self-hosted or Fedora COPR baseurl). - ship() is dry-run-safe and surfaces the exact rpmbuild + (copr-cli build | createrepo_c + GPG-sign) commands; live publish throws not-implemented rather than returning a false success. - Register pkg-dnf in cli adapter-registry; TARGETS.md row added. - manualSetup covers both COPR (hosted) and self-hosted GPG-signed repo paths. --- TARGETS.md | 1 + packages/cli/src/adapter-registry.ts | 2 +- packages/targets/pkg-dnf/README.md | 43 ++++++ packages/targets/pkg-dnf/package.json | 17 +++ packages/targets/pkg-dnf/src/index.test.ts | 73 ++++++++++ packages/targets/pkg-dnf/src/index.ts | 153 +++++++++++++++++++++ packages/targets/pkg-dnf/tsconfig.json | 5 + pnpm-lock.yaml | 6 + 8 files changed, 299 insertions(+), 1 deletion(-) create mode 100644 packages/targets/pkg-dnf/README.md create mode 100644 packages/targets/pkg-dnf/package.json create mode 100644 packages/targets/pkg-dnf/src/index.test.ts create mode 100644 packages/targets/pkg-dnf/src/index.ts create mode 100644 packages/targets/pkg-dnf/tsconfig.json diff --git a/TARGETS.md b/TARGETS.md index 2b7f76d1..5bb1d238 100644 --- a/TARGETS.md +++ b/TARGETS.md @@ -20,6 +20,7 @@ Each row is a **target adapter** — one plugin in `packages/targets/*`. Status | `pkg-winget` | Microsoft winget | 🚧 | | `pkg-scoop` | Scoop bucket | 🚧 | | `pkg-apt` | apt repo / PPA | 🚧 | +| `pkg-dnf` | dnf repo / Fedora COPR | ✅ | | `pkg-snap` | Snapcraft | 🚧 | | `pkg-flatpak` | Flathub | ✅ | | `pkg-aur` | Arch User Repo | 🚧 | diff --git a/packages/cli/src/adapter-registry.ts b/packages/cli/src/adapter-registry.ts index 6f78ff5d..d121e47d 100644 --- a/packages/cli/src/adapter-registry.ts +++ b/packages/cli/src/adapter-registry.ts @@ -162,7 +162,7 @@ export const CATEGORIES: readonly AdapterCategory[] = [ id: 'targets', pkgPrefix: '@profullstack/sh1pt-target', description: 'Distribution targets — stores, registries, CDNs, deploy platforms', - adapters: ['browser-chrome', 'browser-edge', 'browser-firefox', 'browser-safari', 'chat-discord', 'chat-signal', 'chat-slack', 'chat-telegram', 'chat-whatsapp', 'console-steam', 'deploy-coolify', 'deploy-denodeploy', 'deploy-firebase', 'deploy-fly', 'deploy-lambda', 'deploy-netlify', 'deploy-railway', 'deploy-render', 'deploy-vercel', 'deploy-workers', 'desktop-linux', 'desktop-mac', 'desktop-steamos', 'desktop-win', 'exe-dev', 'mobile-android', 'mobile-expo', 'mobile-ios', 'payment-adyen', 'payment-coinpay', 'payment-paypal', 'payment-square', 'payment-stripe', 'pkg-apt', 'pkg-aube', 'pkg-aur', 'pkg-cdn', 'pkg-deno', 'pkg-docker', 'pkg-fdroid', 'pkg-flatpak', 'pkg-ghpackages', 'pkg-homebrew', 'pkg-jsr', 'pkg-nix', 'pkg-npm', 'pkg-perry', 'pkg-scoop', 'pkg-snap', 'pkg-winget', 'plugin-jetbrains', 'plugin-vscode', 'qa-geisterhand', 'sdk-pypi', 'tv-androidtv', 'tv-firetv', 'tv-roku', 'tv-tvos', 'tv-webos', 'web-static', 'xr-meta-quest', 'xr-pico', 'xr-sidequest', 'xr-steamvr', 'xr-visionos', 'xr-webxr'], + adapters: ['browser-chrome', 'browser-edge', 'browser-firefox', 'browser-safari', 'chat-discord', 'chat-signal', 'chat-slack', 'chat-telegram', 'chat-whatsapp', 'console-steam', 'deploy-coolify', 'deploy-denodeploy', 'deploy-firebase', 'deploy-fly', 'deploy-lambda', 'deploy-netlify', 'deploy-railway', 'deploy-render', 'deploy-vercel', 'deploy-workers', 'desktop-linux', 'desktop-mac', 'desktop-steamos', 'desktop-win', 'exe-dev', 'mobile-android', 'mobile-expo', 'mobile-ios', 'payment-adyen', 'payment-coinpay', 'payment-paypal', 'payment-square', 'payment-stripe', 'pkg-apt', 'pkg-aube', 'pkg-aur', 'pkg-cdn', 'pkg-deno', 'pkg-dnf', 'pkg-docker', 'pkg-fdroid', 'pkg-flatpak', 'pkg-ghpackages', 'pkg-homebrew', 'pkg-jsr', 'pkg-nix', 'pkg-npm', 'pkg-perry', 'pkg-scoop', 'pkg-snap', 'pkg-winget', 'plugin-jetbrains', 'plugin-vscode', 'qa-geisterhand', 'sdk-pypi', 'tv-androidtv', 'tv-firetv', 'tv-roku', 'tv-tvos', 'tv-webos', 'web-static', 'xr-meta-quest', 'xr-pico', 'xr-sidequest', 'xr-steamvr', 'xr-visionos', 'xr-webxr'], }, { id: 'vcs', diff --git a/packages/targets/pkg-dnf/README.md b/packages/targets/pkg-dnf/README.md new file mode 100644 index 00000000..b4ec4a82 --- /dev/null +++ b/packages/targets/pkg-dnf/README.md @@ -0,0 +1,43 @@ +# dnf repo / Fedora COPR + +Provides the dnf repo / Fedora COPR sh1pt target adapter for build, publish, or deployment workflows. + +## What it does + +- Registers a target surface that sh1pt can build, publish, or deploy to. +- Provides adapter metadata and lifecycle hooks used by the CLI. +- Includes setup guidance for required credentials or provider configuration. +- Implements build behavior used by sh1pt target workflows. + +## Package + +- Name: `@profullstack/sh1pt-target-pkg-dnf` +- Path: `packages/targets/pkg-dnf` +- Adapter ID: `pkg-dnf` +- Homepage: https://sh1pt.com + +## Scripts + +- `build`: `tsc -p tsconfig.json` +- `prepublishOnly`: `pnpm build` +- `typecheck`: `tsc -p tsconfig.json --noEmit` + +## Usage + +```bash +pnpm add @profullstack/sh1pt-target-pkg-dnf +``` + +## Development + +```bash +pnpm --filter @profullstack/sh1pt-target-pkg-dnf typecheck +``` + +Run tests from the repository root when this module includes a test file: + +```bash +pnpm vitest run packages/targets/pkg-dnf/src/index.test.ts +``` + + diff --git a/packages/targets/pkg-dnf/package.json b/packages/targets/pkg-dnf/package.json new file mode 100644 index 00000000..d4023e21 --- /dev/null +++ b/packages/targets/pkg-dnf/package.json @@ -0,0 +1,17 @@ +{ + "name": "@profullstack/sh1pt-target-pkg-dnf", + "version": "0.1.15", + "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-dnf" }, + "homepage": "https://sh1pt.com", + "bugs": "https://github.com/profullstack/sh1pt/issues", + "files": ["dist"] +} diff --git a/packages/targets/pkg-dnf/src/index.test.ts b/packages/targets/pkg-dnf/src/index.test.ts new file mode 100644 index 00000000..c8d0ccbe --- /dev/null +++ b/packages/targets/pkg-dnf/src/index.test.ts @@ -0,0 +1,73 @@ +import { fakeBuildContext, fakeShipContext, smokeTest } from '@profullstack/sh1pt-core/testing'; +import { mkdtemp, readFile, rm } 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('dnf / RPM spec generation', () => { + it('writes an RPM spec and a self-hosted .repo file', async () => { + const outDir = await mkdtemp(join(tmpdir(), 'sh1pt-dnf-')); + tempDirs.push(outDir); + + const result = await adapter.build(fakeBuildContext({ outDir, version: 'v1.5.0-rc1' }) as any, { + packageName: 'myapp', + summary: 'My App', + license: 'Apache-2.0', + url: 'https://example.com/myapp', + requires: ['glibc', 'openssl'], + }); + + const specPath = join(outDir, 'rpm', 'myapp.spec'); + expect(result.artifact).toBe(specPath); + + const spec = await readFile(specPath, 'utf-8'); + expect(spec).toContain('Name: myapp'); + // 'v' stripped and '-' replaced so the RPM version is valid. + expect(spec).toContain('Version: 1.5.0.rc1'); + expect(spec).toContain('Release: 1%{?dist}'); + expect(spec).toContain('License: Apache-2.0'); + expect(spec).toContain('Requires: glibc'); + expect(spec).toContain('Requires: openssl'); + expect(spec).toContain('%description'); + expect(spec).toContain('%changelog'); + + const repo = await readFile(join(outDir, 'rpm', 'myapp.repo'), 'utf-8'); + expect(repo).toContain('[myapp]'); + expect(repo).toContain('gpgcheck=1'); + expect(repo).toContain('dnf.sh1pt.com'); + }); + + it('targets Fedora COPR when coprProject is set', async () => { + const outDir = await mkdtemp(join(tmpdir(), 'sh1pt-dnf-')); + tempDirs.push(outDir); + + const result = await adapter.build(fakeBuildContext({ outDir, version: '2.0.0' }) as any, { + packageName: 'myapp', + coprProject: 'acme/myapp', + }); + + const repo = await readFile(join(outDir, 'rpm', 'myapp.repo'), 'utf-8'); + expect(repo).toContain('download.copr.fedorainfracloud.org/results/acme/myapp'); + + // COPR publish path uses copr-cli with the documented `status ` form. + expect(result.meta?.commands).toContain('copr-cli status '); + expect((result.meta?.commands as string[])?.some((c: string) => c.startsWith('copr-cli build acme/myapp'))).toBe(true); + }); + + it('keeps dry-run shipping side-effect free and surfaces build commands', async () => { + const ship = await adapter.ship(fakeShipContext({ version: '1.5.0', dryRun: true }) as any, { + packageName: 'myapp', + }); + expect(ship.id).toBe('dry-run'); + expect((ship.meta?.commands as string[])?.some((c: string) => c.startsWith('rpmbuild'))).toBe(true); + }); +}); diff --git a/packages/targets/pkg-dnf/src/index.ts b/packages/targets/pkg-dnf/src/index.ts new file mode 100644 index 00000000..3ed98053 --- /dev/null +++ b/packages/targets/pkg-dnf/src/index.ts @@ -0,0 +1,153 @@ +import { defineTarget, manualSetup } from '@profullstack/sh1pt-core'; +import { mkdir, writeFile } from 'node:fs/promises'; +import { dirname, join } from 'node:path'; + +type RpmArch = 'x86_64' | 'aarch64' | 'noarch'; + +interface Config { + packageName: string; // e.g. "myapp" + summary?: string; + description?: string; + license?: string; // SPDX, e.g. "MIT" + url?: string; // homepage + release?: string; // RPM release field, default "1" + arch?: RpmArch[]; // default ["x86_64"] + requires?: string[]; // runtime Requires + sourceUrl?: string; // Source0 tarball URL + coprProject?: string; // "owner/project" — publish via Fedora COPR + repoBaseUrl?: string; // self-hosted dnf repo base URL (for the .repo file) +} + +// RPM versions may not contain '-' (it separates version from release). +function rpmVersion(version: string): string { + return version.replace(/^v/, '').replace(/-/g, '.'); +} + +function arches(config: Config): RpmArch[] { + return config.arch ?? ['x86_64']; +} + +function renderSpec(ctx: { version: string }, config: Config): string { + const version = rpmVersion(ctx.version); + const release = config.release ?? '1'; + const summary = config.summary ?? `Release package for ${config.packageName}`; + const license = config.license ?? 'MIT'; + const buildArch = arches(config).includes('noarch') ? 'noarch' : undefined; + const lines = [ + `Name: ${config.packageName}`, + `Version: ${version}`, + `Release: ${release}%{?dist}`, + `Summary: ${summary}`, + `License: ${license}`, + ]; + if (config.url) lines.push(`URL: ${config.url}`); + if (config.sourceUrl) lines.push(`Source0: ${config.sourceUrl}`); + if (buildArch) lines.push(`BuildArch: ${buildArch}`); + for (const req of config.requires ?? []) lines.push(`Requires: ${req}`); + lines.push(''); + lines.push('%description'); + lines.push(config.description ?? `${summary}. Generated by sh1pt for dnf/RPM publishing.`); + lines.push(''); + lines.push('%prep'); + lines.push('%autosetup -n %{name}-%{version}'); + lines.push(''); + lines.push('%build'); + lines.push('# TODO: project build steps (make, cargo, go build, …)'); + lines.push(''); + lines.push('%install'); + lines.push('# TODO: install built artifacts into %{buildroot}'); + lines.push(''); + lines.push('%files'); + lines.push('# TODO: list packaged files'); + lines.push(''); + lines.push('%changelog'); + lines.push(`* Release ${version}-${release} - sh1pt `); + lines.push(`- Automated release ${version}`); + lines.push(''); + return lines.join('\n'); +} + +function renderRepoFile(config: Config): string { + const baseUrl = config.coprProject + ? `https://download.copr.fedorainfracloud.org/results/${config.coprProject}/fedora-$releasever-$basearch/` + : (config.repoBaseUrl ?? `https://dnf.sh1pt.com/${config.packageName}/fedora-$releasever-$basearch/`); + return [ + `[${config.packageName}]`, + `name=${config.packageName}`, + `baseurl=${baseUrl}`, + 'enabled=1', + 'gpgcheck=1', + `gpgkey=${config.repoBaseUrl ?? `https://dnf.sh1pt.com/${config.packageName}`}/RPM-GPG-KEY-${config.packageName}`, + '', + ].join('\n'); +} + +function publishCommands(ctx: { version: string }, config: Config): string[] { + const specFile = `${config.packageName}.spec`; + if (config.coprProject) { + return [ + `rpmbuild -bs ${specFile}`, + `copr-cli build ${config.coprProject} ~/rpmbuild/SRPMS/${config.packageName}-${rpmVersion(ctx.version)}-${config.release ?? '1'}*.src.rpm`, + `copr-cli status `, + ]; + } + return [ + `rpmbuild -bb ${specFile}`, + `createrepo_c `, + `gpg --detach-sign --armor /repodata/repomd.xml`, + `rsync / ${config.repoBaseUrl ?? 'dnf.sh1pt.com:/var/www/dnf'}/`, + ]; +} + +export default defineTarget({ + id: 'pkg-dnf', + kind: 'package-manager', + label: 'dnf repo / Fedora COPR', + async build(ctx, config) { + const specPath = join(ctx.outDir, 'rpm', `${config.packageName}.spec`); + const repoPath = join(ctx.outDir, 'rpm', `${config.packageName}.repo`); + ctx.log(`generate RPM spec for ${config.packageName} v${ctx.version} [${arches(config).join(', ')}]`); + await mkdir(dirname(specPath), { recursive: true }); + await Promise.all([ + writeFile(specPath, renderSpec(ctx, config), 'utf-8'), + writeFile(repoPath, renderRepoFile(config), 'utf-8'), + ]); + return { + artifact: specPath, + meta: { specFile: specPath, repoFile: repoPath, commands: publishCommands(ctx, config) }, + }; + }, + async ship(ctx, config) { + const via = config.coprProject ? `COPR ${config.coprProject}` : (config.repoBaseUrl ?? 'dnf.sh1pt.com'); + ctx.log(`publish ${config.packageName}@${ctx.version} to ${via}`); + if (ctx.dryRun) return { id: 'dry-run', meta: { commands: publishCommands(ctx, config) } }; + if (config.coprProject) { + if (!ctx.secret('COPR_API_TOKEN')) { + throw new Error('COPR_API_TOKEN not in vault — run: sh1pt secret set COPR_API_TOKEN '); + } + } else if (!ctx.secret('DNF_GPG_KEY')) { + throw new Error('DNF_GPG_KEY not in vault — run: sh1pt secret set DNF_GPG_KEY "$(gpg --export-secret-keys --armor )"'); + } + // Live publish (rpmbuild + copr-cli build | createrepo_c + GPG-sign + upload) + // is not implemented yet — fail loudly rather than report a false success. + throw new Error( + `pkg-dnf live publish for ${config.packageName} is not implemented yet — ` + + `use dryRun to preview the ${config.coprProject ? 'rpmbuild + copr-cli' : 'rpmbuild + createrepo_c'} commands.`, + ); + }, + async status(id) { + const name = id.split('@')[0] ?? id; + return { state: 'in-review', url: `https://dnf.sh1pt.com/${name}/` }; + }, + setup: manualSetup({ + label: 'dnf repo / Fedora COPR', + vendorDocUrl: 'https://rpm-packaging-guide.github.io/', + steps: [ + 'COPR (hosted): create an account at https://copr.fedorainfracloud.org and a project.', + 'COPR: copy the API token from https://copr.fedorainfracloud.org/api/ and run: sh1pt secret set COPR_API_TOKEN ', + 'Self-hosted: generate a signing key (gpg --full-generate-key) and run: sh1pt secret set DNF_GPG_KEY "$(gpg --export-secret-keys --armor )"', + 'Self-hosted: run: sh1pt secret set DNF_GPG_PASSPHRASE ', + 'Build needs rpmbuild + createrepo_c (or copr-cli) installed on a Fedora/RHEL-like host.', + ], + }), +}); diff --git a/packages/targets/pkg-dnf/tsconfig.json b/packages/targets/pkg-dnf/tsconfig.json new file mode 100644 index 00000000..cf441478 --- /dev/null +++ b/packages/targets/pkg-dnf/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { "outDir": "dist", "rootDir": "src" }, + "include": ["src/**/*"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d5378866..62b68a78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1832,6 +1832,12 @@ importers: specifier: workspace:* version: link:../../core + packages/targets/pkg-dnf: + dependencies: + '@profullstack/sh1pt-core': + specifier: workspace:* + version: link:../../core + packages/targets/pkg-docker: dependencies: '@profullstack/sh1pt-core': From 7066f9cb9e9dc3ece42a7f88ee6cdab02bfb1cf9 Mon Sep 17 00:00:00 2001 From: Alexander-Sorrell-IT <284331358+Alexander-Sorrell-IT@users.noreply.github.com> Date: Fri, 29 May 2026 16:10:34 -0500 Subject: [PATCH 2/2] fix(pkg-dnf): valid RPM %changelog date, correct COPR gpgkey, honest live ship MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses Greptile + adversarial review: - %changelog now emits a valid RPM header '* Wed May 29 2026 sh1pt <…> - ver-rel' (rpmbuild rejected the prior freeform line). - COPR .repo gpgkey now points at the COPR project's pubkey.gpg, not the self-hosted fallback (GPG verify would have failed). - ship() throws 'not implemented' immediately (no misleading secret-check first). - Tests assert the changelog format, COPR gpgkey, and the live-throw. --- packages/targets/pkg-dnf/src/index.test.ts | 8 ++++++++ packages/targets/pkg-dnf/src/index.ts | 24 ++++++++++++++-------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/packages/targets/pkg-dnf/src/index.test.ts b/packages/targets/pkg-dnf/src/index.test.ts index c8d0ccbe..2136b625 100644 --- a/packages/targets/pkg-dnf/src/index.test.ts +++ b/packages/targets/pkg-dnf/src/index.test.ts @@ -39,6 +39,8 @@ describe('dnf / RPM spec generation', () => { expect(spec).toContain('Requires: openssl'); expect(spec).toContain('%description'); expect(spec).toContain('%changelog'); + // %changelog header must be valid RPM form: "* Wed May 29 2026 Name - ver-rel" + expect(spec).toMatch(/^\* (Sun|Mon|Tue|Wed|Thu|Fri|Sat) [A-Z][a-z]{2} \d{2} \d{4} sh1pt - .+-.+$/m); const repo = await readFile(join(outDir, 'rpm', 'myapp.repo'), 'utf-8'); expect(repo).toContain('[myapp]'); @@ -57,6 +59,8 @@ describe('dnf / RPM spec generation', () => { const repo = await readFile(join(outDir, 'rpm', 'myapp.repo'), 'utf-8'); expect(repo).toContain('download.copr.fedorainfracloud.org/results/acme/myapp'); + // gpgkey must point at the COPR project's pubkey, not the self-hosted fallback. + expect(repo).toContain('gpgkey=https://download.copr.fedorainfracloud.org/results/acme/myapp/pubkey.gpg'); // COPR publish path uses copr-cli with the documented `status ` form. expect(result.meta?.commands).toContain('copr-cli status '); @@ -69,5 +73,9 @@ describe('dnf / RPM spec generation', () => { }); expect(ship.id).toBe('dry-run'); expect((ship.meta?.commands as string[])?.some((c: string) => c.startsWith('rpmbuild'))).toBe(true); + + await expect(adapter.ship(fakeShipContext({ version: '1.5.0', dryRun: false }) as any, { + packageName: 'myapp', + })).rejects.toThrow(/not implemented/i); }); }); diff --git a/packages/targets/pkg-dnf/src/index.ts b/packages/targets/pkg-dnf/src/index.ts index 3ed98053..6e2e28ee 100644 --- a/packages/targets/pkg-dnf/src/index.ts +++ b/packages/targets/pkg-dnf/src/index.ts @@ -27,6 +27,15 @@ function arches(config: Config): RpmArch[] { return config.arch ?? ['x86_64']; } +const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; +const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +// RPM %changelog requires the exact header form: "* Wed May 29 2026 Name - ver-rel". +// Built locale-independently so the .spec is accepted by rpmbuild. +function rpmChangelogDate(d: Date): string { + return `${WEEKDAYS[d.getUTCDay()]} ${MONTHS[d.getUTCMonth()]} ${String(d.getUTCDate()).padStart(2, '0')} ${d.getUTCFullYear()}`; +} + function renderSpec(ctx: { version: string }, config: Config): string { const version = rpmVersion(ctx.version); const release = config.release ?? '1'; @@ -61,7 +70,7 @@ function renderSpec(ctx: { version: string }, config: Config): string { lines.push('# TODO: list packaged files'); lines.push(''); lines.push('%changelog'); - lines.push(`* Release ${version}-${release} - sh1pt `); + lines.push(`* ${rpmChangelogDate(new Date())} sh1pt - ${version}-${release}`); lines.push(`- Automated release ${version}`); lines.push(''); return lines.join('\n'); @@ -77,7 +86,9 @@ function renderRepoFile(config: Config): string { `baseurl=${baseUrl}`, 'enabled=1', 'gpgcheck=1', - `gpgkey=${config.repoBaseUrl ?? `https://dnf.sh1pt.com/${config.packageName}`}/RPM-GPG-KEY-${config.packageName}`, + `gpgkey=${config.coprProject + ? `https://download.copr.fedorainfracloud.org/results/${config.coprProject}/pubkey.gpg` + : `${config.repoBaseUrl ?? `https://dnf.sh1pt.com/${config.packageName}`}/RPM-GPG-KEY-${config.packageName}`}`, '', ].join('\n'); } @@ -121,15 +132,10 @@ export default defineTarget({ const via = config.coprProject ? `COPR ${config.coprProject}` : (config.repoBaseUrl ?? 'dnf.sh1pt.com'); ctx.log(`publish ${config.packageName}@${ctx.version} to ${via}`); if (ctx.dryRun) return { id: 'dry-run', meta: { commands: publishCommands(ctx, config) } }; - if (config.coprProject) { - if (!ctx.secret('COPR_API_TOKEN')) { - throw new Error('COPR_API_TOKEN not in vault — run: sh1pt secret set COPR_API_TOKEN '); - } - } else if (!ctx.secret('DNF_GPG_KEY')) { - throw new Error('DNF_GPG_KEY not in vault — run: sh1pt secret set DNF_GPG_KEY "$(gpg --export-secret-keys --armor )"'); - } // Live publish (rpmbuild + copr-cli build | createrepo_c + GPG-sign + upload) // is not implemented yet — fail loudly rather than report a false success. + // (Required secrets are documented in setup(); not checked here since the + // path is unimplemented.) throw new Error( `pkg-dnf live publish for ${config.packageName} is not implemented yet — ` + `use dryRun to preview the ${config.coprProject ? 'rpmbuild + copr-cli' : 'rpmbuild + createrepo_c'} commands.`,