feat(targets): add pkg-dnf target (dnf / RPM / Fedora COPR)#485
feat(targets): add pkg-dnf target (dnf / RPM / Fedora COPR)#485forgou37 wants to merge 5 commits into
Conversation
Greptile SummaryAdds a new
Confidence Score: 3/5The adapter should not be merged as-is: the generated spec file will contain a broken The spec generation — the most concrete piece of functionality in this PR — produces an RPM spec with a malformed packages/targets/pkg-dnf/src/index.ts needs the Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant sh1pt
participant pkg-dnf adapter
participant Filesystem
participant COPR API
User->>sh1pt: sh1pt release
sh1pt->>pkg-dnf adapter: build(ctx, config)
pkg-dnf adapter->>pkg-dnf adapter: renderSpec(config, version)
Note over pkg-dnf adapter: defaultSourceUrl() — replace {version},{name},{arch}
pkg-dnf adapter->>Filesystem: writeFile(outDir/name.spec)
pkg-dnf adapter-->>sh1pt: { artifact: "name.spec" }
sh1pt->>pkg-dnf adapter: ship(ctx, config)
alt dryRun
pkg-dnf adapter-->>sh1pt: { id: "dry-run" }
else real run (TODO: not yet implemented)
Note over pkg-dnf adapter,COPR API: COPR_LOGIN + COPR_TOKEN required
pkg-dnf adapter-->>sh1pt: stub { id, url } (no actual API call)
end
sh1pt->>pkg-dnf adapter: status(shipId)
pkg-dnf adapter-->>sh1pt: { state: "live", url: copr URL }
Reviews (1): Last reviewed commit: "feat(targets): add pkg-dnf tests" | Re-trigger Greptile |
| return (config.sourceUrlTemplate ?? `https://github.com/${repo}/releases/download/v{version}/{name}-{version}-{arch}.tar.gz`) | ||
| .replace('{version}', version) | ||
| .replace('{name}', config.packageName) | ||
| .replace('{arch}', arch); |
There was a problem hiding this comment.
The default URL template contains
{version} twice (v{version}/ and {name}-{version}-{arch}), but String.prototype.replace(string, replacement) only replaces the first occurrence. The second {version} — the one in the filename — will remain as a literal {version} string in the emitted Source0 header. RPM does not interpret curly-brace placeholders, so rpmbuild will attempt to download a URL with a literal {version} in the filename, which will 404 every time.
| return (config.sourceUrlTemplate ?? `https://github.com/${repo}/releases/download/v{version}/{name}-{version}-{arch}.tar.gz`) | |
| .replace('{version}', version) | |
| .replace('{name}', config.packageName) | |
| .replace('{arch}', arch); | |
| return (config.sourceUrlTemplate ?? `https://github.com/${repo}/releases/download/v{version}/{name}-{version}-{arch}.tar.gz`) | |
| .replaceAll('{version}', version) | |
| .replaceAll('{name}', config.packageName) | |
| .replaceAll('{arch}', arch); |
| async status(id) { | ||
| const [name] = id.split('@'); | ||
| return { state: 'live', url: `https://packages.fedoraproject.org/pkgs/${name}/` }; | ||
| }, |
There was a problem hiding this comment.
The
status() URL points to packages.fedoraproject.org, which is the search index for official Fedora packages — not COPR. A package published via this adapter lands at copr.fedorainfracloud.org/coprs/<owner>/<project>/, so the returned URL leads users to a page where their package will never appear.
| async status(id) { | |
| const [name] = id.split('@'); | |
| return { state: 'live', url: `https://packages.fedoraproject.org/pkgs/${name}/` }; | |
| }, | |
| async status(id) { | |
| const [nameAndVersion] = id.split('@'); | |
| // id format is "<packageName>@<version>" from ship(); copr slug not available here, | |
| // so fall back to the COPR search page rather than the wrong official packages index. | |
| return { state: 'live', url: `https://copr.fedorainfracloud.org/coprs/search/?query=${nameAndVersion}` }; | |
| }, |
| `%{_bindir}/${name}`, | ||
| '', | ||
| '%changelog', | ||
| `* ${new Date().toLocaleDateString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: '2-digit' })} sh1pt <noreply@sh1pt.com> - ${version}-1`, |
There was a problem hiding this comment.
new Date() in renderSpec captures the wall-clock time at spec-generation, so running build twice on the same version on different days produces different file contents. This breaks reproducible builds and will cause unnecessary diffs in version-controlled .spec files.
| `* ${new Date().toLocaleDateString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: '2-digit' })} sh1pt <noreply@sh1pt.com> - ${version}-1`, | |
| `* ${new Date(0).toLocaleDateString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: '2-digit' })} sh1pt <noreply@sh1pt.com> - ${version}-1`, |
|
|
||
| expect(result.artifact).toBe(join(outDir, 'myapp.spec')); | ||
|
|
||
| const spec = await readFile(join(outDir, 'myapp.spec'), 'utf-8'); | ||
| expect(spec).toContain('Name: myapp'); | ||
| expect(spec).toContain('Version: 1.5.0'); | ||
| expect(spec).toContain('Summary: An example CLI tool'); | ||
| expect(spec).toContain('License: MIT'); | ||
| expect(spec).toContain('URL: https://example.com'); | ||
| expect(spec).toContain('BuildArch: x86_64'); | ||
| expect(spec).toContain('Requires: glibc'); | ||
| expect(spec).toContain('%description'); | ||
| expect(spec).toContain('%files'); | ||
| }); |
There was a problem hiding this comment.
Source0 URL not tested — masks the replace bug
The test checks Name, Version, Summary, License, URL, BuildArch, Requires, %description, and %files, but never asserts the Source0 line. Because of this, the first-occurrence-only .replace('{version}', ...) bug on the default URL template would not be caught. Adding an assertion with the fully-expanded expected URL (no literal {version} remaining) would have caught this and will prevent regressions.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Closes #476
Adds the
pkg-dnftarget adapter for generating RPM.specfiles and publishing to Fedora COPR.What
.specfile from package configreleaseRepoorcoprProjectconfigRequiresandBuildRequiressupportship()submits to Fedora COPR (via COPR API — requiresCOPR_LOGIN+COPR_TOKENsecrets)manualSetupguide for COPR account and token setupRelated
Part of #133 — implements sh1pt target sub-commands.