native-packages 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +18 -8
- data/docs/cli-design.md +6 -0
- data/docs/configuration.md +7 -1
- data/docs/native-recipes.md +127 -0
- data/docs/platforms.md +3 -1
- data/docs/releasing.md +1 -1
- data/examples/native-packages-all-formats.yaml +1 -1
- data/examples/native-packages.yaml +1 -1
- data/lib/native_packages/build.rb +61 -18
- data/lib/native_packages/configuration.rb +40 -5
- data/lib/native_packages/native_recipe.rb +91 -0
- data/lib/native_packages/project.rb +2 -2
- data/lib/native_packages/support.rb +10 -4
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 164594265b7f47190d318ad3507446103c389206e0ab2901a54371b21c53f0ed
|
|
4
|
+
data.tar.gz: 79cfdca9089b718e2e65f4203e3ca4629542517e859992340c79229df715e68c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 587d2a749a8b5d6f380a37ca932dbb0d587bd6bee763269a5783a54d8f50bc5ab0102b32958c6eabcab79de4447b4acaa7cc8e063340a848a123b9b34d851342
|
|
7
|
+
data.tar.gz: a0935ea30ddf612535a11903912b2bfbe34ec947ac396254b0abefb0182e9a54e0551f797520916cb402208421251dfb2cc583fdf050c2e1eef1f3808ca82194
|
data/README.md
CHANGED
|
@@ -6,14 +6,11 @@ Applications keep their build scripts, installation assets and native recipes. T
|
|
|
6
6
|
|
|
7
7
|
## Install and build
|
|
8
8
|
|
|
9
|
-
Version 0.2 is being prepared for RubyGems publication. Until published, build and install the gem from this repository:
|
|
10
|
-
|
|
11
9
|
```sh
|
|
12
|
-
gem
|
|
13
|
-
gem install ./native-packages-0.2.0.gem
|
|
10
|
+
gem install native-packages
|
|
14
11
|
```
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
Install nFPM 2.47.0 separately for local use; the reusable CI workflow installs it for you. Ruby 3.2 or later is required.
|
|
17
14
|
|
|
18
15
|
From an application directory:
|
|
19
16
|
|
|
@@ -59,6 +56,19 @@ Linux binary targets declare `libc: glibc`, `musl` or `static`. The tool inspect
|
|
|
59
56
|
|
|
60
57
|
See [configuration and commands](docs/configuration.md) for tokens, release assets, hooks, version selection and publishing, and [platform coverage](docs/platforms.md) for each format's requirements.
|
|
61
58
|
|
|
59
|
+
Version 0.3 adds [native DMG and Inno recipes](docs/native-recipes.md). These run
|
|
60
|
+
the application's existing packaging commands on macOS or Windows, using the
|
|
61
|
+
same verified build manifests as Linux packages. Native recipes consume prepared
|
|
62
|
+
directories and need no nFPM installation. The gem checks Mach-O/PE architecture,
|
|
63
|
+
preserves safe internal bundle links and hashes the output after signing hooks.
|
|
64
|
+
App compilation, installer policy and signing identities stay in the application
|
|
65
|
+
repository.
|
|
66
|
+
|
|
67
|
+
Preview package versions are explicit: set `release.prereleases: true` to build
|
|
68
|
+
`1.2.3-alpha.N`, `-beta.N` or `-rc.N` for DEB/RPM/DMG/Inno. Other formats and
|
|
69
|
+
downstream recipe publication remain stable-only. Uploads require an existing
|
|
70
|
+
GitHub release already marked as a prerelease.
|
|
71
|
+
|
|
62
72
|
The [all-formats example](examples/native-packages-all-formats.yaml) shows separate Linux, OpenWrt, Windows and source inputs, including MSIX identity/assets and an SRPM source/spec layout.
|
|
63
73
|
|
|
64
74
|
## Release inputs and publication
|
|
@@ -85,14 +95,14 @@ packaging:
|
|
|
85
95
|
needs: release
|
|
86
96
|
permissions:
|
|
87
97
|
contents: write
|
|
88
|
-
uses: crmne/native-packages/.github/workflows/package.yml@v0.
|
|
98
|
+
uses: crmne/native-packages/.github/workflows/package.yml@v0.3.0
|
|
89
99
|
with:
|
|
90
100
|
version: ${{ github.ref_name }}
|
|
91
101
|
publish: true
|
|
92
102
|
secrets: inherit
|
|
93
103
|
```
|
|
94
104
|
|
|
95
|
-
|
|
105
|
+
Pin the corresponding commit SHA for an immutable workflow reference. With no version, the workflow validates configuration only. With a version, it installs the configured gem and nFPM, builds packages and uploads an Actions artifact. `publish: true` attaches packages to the existing release.
|
|
96
106
|
|
|
97
107
|
To consume an Actions artifact instead, set `source-artifact` and, if necessary, `source-directory` (default `dist`). Its files must match the local input paths in the configuration. This workflow packages on Linux, including MSIX creation from Windows binaries; native application builds and platform-specific signing jobs can use the CLI separately.
|
|
98
108
|
|
|
@@ -111,7 +121,7 @@ Migration combines `packaging/project.yml`, its nFPM definition and repository r
|
|
|
111
121
|
bundle install
|
|
112
122
|
bundle exec ruby -Ilib -e 'Dir["test/*_test.rb"].sort.each { |path| require_relative path }'
|
|
113
123
|
gem build native-packages.gemspec
|
|
114
|
-
ruby test/gem_install.rb native-packages-0.
|
|
124
|
+
ruby test/gem_install.rb native-packages-0.3.0.gem
|
|
115
125
|
```
|
|
116
126
|
|
|
117
127
|
Tests build real packages, inspect their payloads and exercise repository publication against local Git fixtures. CI additionally runs disposable Linux install/upgrade/remove checks, SRPM rebuilds and Windows MSIX acceptance. Runtime build manifests report installation as `not-tested`: CI fixture coverage is not a substitute for testing each application's packages.
|
data/docs/cli-design.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Proposal: an installable CLI with one project configuration
|
|
2
2
|
|
|
3
|
+
The owner approved compatible shared support for RekordFlash's native alpha
|
|
4
|
+
packaging on 2026-09-13. Version 0.3's [native recipe and prerelease policy](native-recipes.md)
|
|
5
|
+
extends the original stable-only policy explicitly; published 0.2 consumers stay
|
|
6
|
+
pinned. DMG/Inno adapters coordinate existing app scripts and retain their
|
|
7
|
+
signing/version decisions, without adding a compiler or plugin framework.
|
|
8
|
+
|
|
3
9
|
Status: accepted, 2026-09-13. Publishing was subsequently aligned with RubyLLM: GitHub release publication triggers the workflow, using `RUBYGEMS_AUTH_TOKEN`. The original Trusted Publishing proposal below is superseded by that decision. The v0.2 implementation follows this design. See the current [configuration reference](configuration.md) and [release setup](releasing.md) for the implemented interface and publication requirements. This document records the original design; it does not describe v0.1.0.
|
|
4
10
|
|
|
5
11
|
The proposed interface is `gem install native-packages`, `native-packages init`, and `native-packages build`. A project normally needs one `native-packages.yaml` file. The tool should expose every nFPM packager and use the application's declared inputs to decide what to build.
|
data/docs/configuration.md
CHANGED
|
@@ -19,6 +19,12 @@
|
|
|
19
19
|
|
|
20
20
|
## Targets
|
|
21
21
|
|
|
22
|
+
Version 0.3 additionally accepts macOS `dmg` and Windows `inno` targets with an
|
|
23
|
+
application-owned `native.command` and `native.output`. These consume local
|
|
24
|
+
prepared directories on their native host, with no nFPM executable required.
|
|
25
|
+
See [native recipes](native-recipes.md) for the complete contract and examples.
|
|
26
|
+
The nFPM targets below retain their existing behavior.
|
|
27
|
+
|
|
22
28
|
A target declares `platform` (`linux` or `windows`), `arch` (nFPM/Go architecture name), `formats`, and `input`. `kind` defaults to `binary`; `data` permits packages without executables, and `source` is required for a separate SRPM target. Linux binaries declare `libc` as `glibc`, `musl` or `static`. IPK also requires an `abi` label identifying the device/distribution baseline.
|
|
23
29
|
|
|
24
30
|
A target's optional `nfpm` mapping overrides the shared definition. Maps merge recursively; lists replace in full. Package name, version, platform and architecture must not conflict with their canonical declarations. Format-specific architecture overrides belong in the corresponding nFPM section. Distinct target inputs must be supplied for incompatible ABIs or platforms.
|
|
@@ -41,7 +47,7 @@ Strings can contain `@NAME@`, `@VERSION@`, `@TAG@`, `@DATE@`, `@SOURCE_DATE_EPOC
|
|
|
41
47
|
|
|
42
48
|
Additional `assets` retain the existing `file`, optional `url`, and `checksummed` fields. Local builds require `local` paths for these assets. Release builds download and hash them, checking the published checksum unless `checksummed: false` is explicitly set for a source asset. Each contributes `@KEY_FILE@`, `@KEY_URL@` and `@KEY_SHA256@` to recipe rendering. Unresolved tokens fail validation/build.
|
|
43
49
|
|
|
44
|
-
Omitting `--version` requires an exact
|
|
50
|
+
Omitting `--version` requires an exact supported release tag at HEAD. `--release` supplies the version and conflicts with a different `--version`. Versions default to stable `vMAJOR.MINOR.PATCH`. Setting `release.prereleases: true` additionally permits `-alpha.N`, `-beta.N` and `-rc.N` for DEB/RPM/DMG/Inno builds without downstream recipes; preview upload requires an existing GitHub prerelease. Native directory recipes use local `--version` mode. Build timestamps use `SOURCE_DATE_EPOCH`, otherwise the relevant Git commit timestamp, otherwise the current time for projects without Git metadata. Supply `SOURCE_DATE_EPOCH` for repeatable builds outside Git.
|
|
45
51
|
|
|
46
52
|
## Output and publication
|
|
47
53
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Native DMG and Inno recipes (0.3)
|
|
2
|
+
|
|
3
|
+
The shared build command can coordinate existing macOS and Windows packaging
|
|
4
|
+
scripts. These are native command adapters, separate from nFPM. They do not
|
|
5
|
+
generate an installer recipe, compile an app, supply signing identities or
|
|
6
|
+
automatically notarize anything. The application retains those decisions.
|
|
7
|
+
|
|
8
|
+
```yaml
|
|
9
|
+
schema: 1
|
|
10
|
+
tool:
|
|
11
|
+
version: '0.3.0'
|
|
12
|
+
nfpm: '2.47.0'
|
|
13
|
+
nfpm:
|
|
14
|
+
name: example-app
|
|
15
|
+
maintainer: Example <example@example.org>
|
|
16
|
+
description: Example application
|
|
17
|
+
license: MIT
|
|
18
|
+
release:
|
|
19
|
+
repository: example/example-app
|
|
20
|
+
prereleases: true
|
|
21
|
+
targets:
|
|
22
|
+
macos-arm64:
|
|
23
|
+
platform: macos
|
|
24
|
+
arch: arm64
|
|
25
|
+
formats: [dmg]
|
|
26
|
+
input:
|
|
27
|
+
kind: directory
|
|
28
|
+
local: Example.app
|
|
29
|
+
native:
|
|
30
|
+
command: [ruby, packaging/dmg.rb, '@PAYLOAD@', '@PACKAGE@', '@VERSION@']
|
|
31
|
+
output: 'example-app-@TAG@-macos-arm64.dmg'
|
|
32
|
+
windows-amd64:
|
|
33
|
+
platform: windows
|
|
34
|
+
arch: amd64
|
|
35
|
+
formats: [inno]
|
|
36
|
+
input:
|
|
37
|
+
kind: directory
|
|
38
|
+
local: dist/windows
|
|
39
|
+
native:
|
|
40
|
+
command: [ruby, packaging/setup.rb, '@PAYLOAD@', '@PACKAGE@', '@VERSION@']
|
|
41
|
+
output: 'example-app-@TAG@-windows-setup.exe'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Run each target on its own host using `build --version VERSION --target ID`.
|
|
45
|
+
Use the normal `aggregate` command to combine complete target results later.
|
|
46
|
+
Use the same configuration and `SOURCE_DATE_EPOCH` across jobs. No nFPM binary
|
|
47
|
+
is required when only native targets are selected; its version remains part of
|
|
48
|
+
the configuration for compatibility with mixed Linux/native builds.
|
|
49
|
+
|
|
50
|
+
`native.command` is a nonempty argument array, including `@PACKAGE@`, which
|
|
51
|
+
resolves to the exact output filename in a fresh directory. `@PAYLOAD@` is an
|
|
52
|
+
owned copy of the input directory's contents. It does not retain the original
|
|
53
|
+
directory's basename: a DMG script should copy it into its own temporary
|
|
54
|
+
`Example.app` folder before calling hdiutil. Other existing target tokens apply.
|
|
55
|
+
Commands run in the application configuration directory and receive the same
|
|
56
|
+
target/version environment as other build hooks.
|
|
57
|
+
|
|
58
|
+
DMG requires a macOS binary target; Inno requires a Windows binary target. Both
|
|
59
|
+
use one format per target and `input.kind: directory` with `input.local`.
|
|
60
|
+
Release-download mode cannot supply a directory: prepare or download/unpack
|
|
61
|
+
the app in its build job, then use local mode. `validate` and dry runs do not
|
|
62
|
+
execute commands. `doctor` checks the host and recipe executable; the recipe
|
|
63
|
+
must check its own additional tools. Mac architecture inspection also needs
|
|
64
|
+
Apple's `lipo`. Supported Mac targets are `amd64`, `arm64` and `universal` (both).
|
|
65
|
+
|
|
66
|
+
Input copying preserves file modes and internal relative symlinks, including
|
|
67
|
+
framework links. Escaping, dangling and special-file entries fail. The staged
|
|
68
|
+
tree must match the original digest and remain unchanged after packaging and
|
|
69
|
+
signing hooks. Package only this staged input; do not read another application
|
|
70
|
+
build from a global location. Recipes should preserve existing destinations,
|
|
71
|
+
clean only their own temporary files and use argument arrays for subprocesses.
|
|
72
|
+
|
|
73
|
+
The command must create exactly its declared `.dmg` or `.exe` file. The adapter
|
|
74
|
+
checks a UDIF trailer or PE container respectively. This is a container check,
|
|
75
|
+
not proof of a valid installer, signature or application. The existing
|
|
76
|
+
`after_package` hook can sign/notarize the output in place before the final hash
|
|
77
|
+
is recorded. Do not modify a package after building its manifest: signature
|
|
78
|
+
stapling changes bytes and must run inside that hook if the manifest is to verify.
|
|
79
|
+
|
|
80
|
+
Applications should invoke hdiutil's verification, platform signature checks and
|
|
81
|
+
their normal installer compiler checks in their recipes. Preserve native numeric
|
|
82
|
+
version ordering across alpha and stable releases; a SemVer suffix cannot be
|
|
83
|
+
passed directly to a numeric Windows/macOS version field. The gem does not infer
|
|
84
|
+
that policy from an application's release numbering.
|
|
85
|
+
|
|
86
|
+
## Prerelease policy
|
|
87
|
+
|
|
88
|
+
`release.prereleases: true` explicitly enables `MAJOR.MINOR.PATCH-alpha.N`,
|
|
89
|
+
`-beta.N` and `-rc.N`, with a positive sequence number and no build suffix.
|
|
90
|
+
Default/legacy commands continue to require stable versions. Preview builds
|
|
91
|
+
support DEB, RPM, DMG and Inno only and cannot contain downstream recipes.
|
|
92
|
+
nFPM's normal SemVer conversion produces, for example, `1.2.3~alpha.1` in DEB/RPM;
|
|
93
|
+
overriding that prerelease or disabling conversion is rejected. Other package
|
|
94
|
+
formats need their own reviewed version policy before being enabled.
|
|
95
|
+
|
|
96
|
+
Preview publication only attaches to an existing GitHub release marked as a
|
|
97
|
+
prerelease. It never creates a release or changes one from stable to prerelease.
|
|
98
|
+
All configured targets must be aggregated and their hashes must verify first.
|
|
99
|
+
This opt-in cannot affect applications still pinned to the published 0.2.0 gem.
|
|
100
|
+
|
|
101
|
+
## Acceptance scope
|
|
102
|
+
|
|
103
|
+
`ruby -Ilib test/native_acceptance.rb NEW_DIRECTORY` builds a small real native
|
|
104
|
+
executable and packages alpha 1, alpha 2 and stable versions. It installs into an
|
|
105
|
+
owned disposable directory, checks the executable/version and model fixture,
|
|
106
|
+
rolls back to alpha 1 and removes the installation. Mac images are attached
|
|
107
|
+
read-only and detached; Windows uses a unique per-user fixture identity and
|
|
108
|
+
uninstaller. On Windows, run with a C compiler environment and set
|
|
109
|
+
`NATIVE_PACKAGES_ISCC` to the actual Inno compiler path.
|
|
110
|
+
|
|
111
|
+
These are gem fixtures, not application installation evidence. They do not
|
|
112
|
+
exercise a DJ library, USB export, licensing, notarization or OS-store acceptance.
|
|
113
|
+
The generated application build manifest continues to say `installation:
|
|
114
|
+
not-tested`; do not turn that into a success claim merely because gem CI passed.
|
|
115
|
+
|
|
116
|
+
The [2026-09-13 native record](acceptance/0.3.0-native.json) records successful
|
|
117
|
+
DMG lifecycle checks on an ARM64 Mac and Inno lifecycle checks on Windows x64.
|
|
118
|
+
Both covered alpha 1 → alpha 2 → stable → alpha 1 and final removal, preserving
|
|
119
|
+
the fixture model and exact executable bytes. The native Mac run exposed the
|
|
120
|
+
SSH-locale command-output bug, fixed before the successful run. Inno Setup
|
|
121
|
+
6.7.3's official installer was verified against its SHA-256 and Pyrsys B.V.
|
|
122
|
+
signature; fixtures compiled with MSVC 14.51. Linux passed 49 tests and 287
|
|
123
|
+
assertions, including all existing package formats; the isolated gem install
|
|
124
|
+
and build check passed. These local results precede hosted CI for this version.
|
|
125
|
+
|
|
126
|
+
References: [nFPM version conversion](https://nfpm.goreleaser.com/docs/configuration/),
|
|
127
|
+
[Inno command-line compiler](https://jrsoftware.org/ishelp/topic_compilercmdline.htm).
|
data/docs/platforms.md
CHANGED
|
@@ -15,7 +15,9 @@ All seven nFPM 2.47.0 packagers are exposed by the v0.2 CLI. Each target explici
|
|
|
15
15
|
| Homebrew | Hash/render supplied formulae or casks and publish a tap | macOS bundles, signing/notarization, native recipe logic |
|
|
16
16
|
| Nixpkgs/Gentoo/Void and similar repositories | Template rendering and configured Git/PR/MR publication | Native recipes, dependency hashes, distro checks and review |
|
|
17
17
|
| Flatpak | No build/publication adapter yet | Use flatpak-builder and native manifests/runtime/sandbox settings |
|
|
18
|
-
|
|
|
18
|
+
| Inno Setup | Native command adapter, PE inspection, copied input and final output hashes | Inno recipe/compiler, application version mapping, signing and installation tests |
|
|
19
|
+
| macOS DMG | Native command adapter, Mach-O inspection, copied bundle and final output hashes | Bundle, hdiutil recipe, signing/notarization, update/rollback testing |
|
|
20
|
+
| Other Windows installers | No adapter for NSIS/WiX yet | Existing native tools and WinGet/Scoop integrations |
|
|
19
21
|
|
|
20
22
|
The IPK acceptance fixture targets OpenWrt 24.10.8. OpenWrt 25.12 switched to APK, so IPK is not the package format for every OpenWrt release. [OpenWrt 25.12 release notes](https://openwrt.org/releases/25.12/notes-25.12.0)
|
|
21
23
|
|
data/docs/releasing.md
CHANGED
|
@@ -23,7 +23,7 @@ Update the gemspec, `NativePackages::VERSION`, example/version references and ch
|
|
|
23
23
|
Create a GitHub release for that commit:
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
gh release create v0.
|
|
26
|
+
gh release create v0.3.0 --target main --title 'native-packages 0.3.0' --notes-file release-notes.md
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Adding `--draft` permits review before publication and does not trigger the publisher. The tag must match the exact gem version and point to a commit on `main`. Set `--prerelease` only for a prerelease gem version.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "configuration"
|
|
4
4
|
require_relative "inspection"
|
|
5
|
+
require_relative "native_recipe"
|
|
5
6
|
|
|
6
7
|
module NativePackages
|
|
7
8
|
class Build
|
|
@@ -29,7 +30,12 @@ module NativePackages
|
|
|
29
30
|
def doctor(ids: [], formats: [], release: false)
|
|
30
31
|
configuration.validate
|
|
31
32
|
selected = configuration.select(ids: ids, formats: formats)
|
|
32
|
-
nfpm_version
|
|
33
|
+
nfpm_version if selected.values.any? { |target| (target.fetch("formats") & Configuration::NATIVE_FORMATS).empty? }
|
|
34
|
+
selected.each do |id, target|
|
|
35
|
+
next unless target["native"]
|
|
36
|
+
metadata = configuration.target_tokens(configuration.tokens("9.8.7"), id, target, "/payload").merge("PACKAGE" => "/output/package", "FORMAT" => target.fetch("formats").first)
|
|
37
|
+
NativeRecipe.new(root).doctor(target, render_tree(target.fetch("native").fetch("command"), metadata))
|
|
38
|
+
end
|
|
33
39
|
required = []
|
|
34
40
|
required << "readelf" if selected.values.any? { |target| target["platform"] == "linux" && target.fetch("kind", "binary") == "binary" }
|
|
35
41
|
required << "bsdtar" if selected.values.any? { |target| target.fetch("input").fetch("kind", "archive") == "archive" }
|
|
@@ -44,12 +50,12 @@ module NativePackages
|
|
|
44
50
|
end
|
|
45
51
|
|
|
46
52
|
def version(value, release)
|
|
47
|
-
if release && value &&
|
|
53
|
+
if release && value && configuration.package_version(value) != configuration.package_version(release)
|
|
48
54
|
raise Error, "--release and --version disagree"
|
|
49
55
|
end
|
|
50
56
|
value ||= release
|
|
51
57
|
value ||= capture("git", "describe", "--exact-match", "--tags", "HEAD")
|
|
52
|
-
|
|
58
|
+
configuration.package_version(value)
|
|
53
59
|
rescue Error => error
|
|
54
60
|
raise error if value
|
|
55
61
|
raise Error, "no exact release tag at HEAD; supply --version"
|
|
@@ -71,10 +77,14 @@ module NativePackages
|
|
|
71
77
|
configuration.validate
|
|
72
78
|
selected = configuration.select(ids: ids, formats: formats)
|
|
73
79
|
number = version(value, release)
|
|
80
|
+
configuration.check_prerelease(number, selected)
|
|
81
|
+
if release && selected.values.any? { |target| target["native"] }
|
|
82
|
+
raise Error, "native recipes consume local prepared directories; use --version with native build artifacts"
|
|
83
|
+
end
|
|
74
84
|
output = Pathname.new(output || root / "dist/packages" / number).expand_path(root)
|
|
75
85
|
if dry_run
|
|
76
86
|
puts JSON.pretty_generate("version" => number, "mode" => release ? "release" : "local", "output" => output.to_s,
|
|
77
|
-
"targets" => selected.transform_values { |target| target.slice("formats", "platform", "arch", "input", "before_build", "after_package") })
|
|
87
|
+
"targets" => selected.transform_values { |target| target.slice("formats", "platform", "arch", "input", "before_build", "after_package", "native") })
|
|
78
88
|
return
|
|
79
89
|
end
|
|
80
90
|
raise Error, "output exists: #{output}; choose a fresh --output" if output.exist?
|
|
@@ -98,28 +108,54 @@ module NativePackages
|
|
|
98
108
|
if source.directory? && (output.to_s == source.expand_path.to_s || output.to_s.start_with?(source.expand_path.to_s + File::SEPARATOR))
|
|
99
109
|
raise Error, "package output must be outside the input directory"
|
|
100
110
|
end
|
|
101
|
-
|
|
111
|
+
native = target["native"] && NativeRecipe.new(root)
|
|
112
|
+
digest = native ? native.digest(source) : input_digest(source)
|
|
113
|
+
sources << { "target" => id, "path" => source.to_s, "sha256" => digest }
|
|
102
114
|
Dir.mktmpdir("native-packages-input-") do |directory|
|
|
103
115
|
payload = Pathname.new(directory) / "payload"
|
|
104
|
-
|
|
116
|
+
if native
|
|
117
|
+
FileUtils.cp_r(source, payload, preserve: true)
|
|
118
|
+
raise Error, "native input changed while staging" unless native.digest(payload) == digest
|
|
119
|
+
else
|
|
120
|
+
copy_input(source, payload, input.fetch("kind", "archive"))
|
|
121
|
+
end
|
|
105
122
|
target.fetch("formats").each do |format|
|
|
106
|
-
package = render_tree(configuration.package(target), configuration.target_tokens(info, id, target, payload))
|
|
107
|
-
package.merge!("name" => configuration.name, "version" => number, "arch" => target.fetch("arch"),
|
|
108
|
-
"platform" => target.fetch("platform"), "mtime" => info.fetch("DATE"))
|
|
109
|
-
inspection = Inspection.new(root, configuration.data.fetch("libraries")).check(package, target, format)
|
|
110
|
-
config_path = Pathname.new(directory) / "nfpm.yaml"
|
|
111
|
-
write(config_path, YAML.dump(package))
|
|
112
123
|
destination = staging / "packages" / id / format
|
|
113
124
|
destination.mkpath
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
125
|
+
if native
|
|
126
|
+
inspection = native.inspect(payload, target)
|
|
127
|
+
recipe = target.fetch("native")
|
|
128
|
+
tokens = configuration.target_tokens(info, id, target, payload)
|
|
129
|
+
package_path = destination / render(recipe.fetch("output"), tokens)
|
|
130
|
+
run(*render_tree(recipe.fetch("command"), tokens.merge("PACKAGE" => package_path.to_s, "FORMAT" => format)),
|
|
131
|
+
env: { "SOURCE_DATE_EPOCH" => info.fetch("SOURCE_DATE_EPOCH").to_s, "NATIVE_PACKAGES_TARGET" => id, "NATIVE_PACKAGES_VERSION" => number })
|
|
132
|
+
packages = files(destination)
|
|
133
|
+
raise Error, "native recipe must create only its declared output" unless packages == [package_path]
|
|
134
|
+
native.check_output(package_path, format)
|
|
135
|
+
else
|
|
136
|
+
package = render_tree(configuration.package(target), configuration.target_tokens(info, id, target, payload))
|
|
137
|
+
if number.include?("-") && (package.fetch("version_schema", "semver") != "semver" || %w[prerelease version_metadata].any? { |key| package.key?(key) && !package[key].to_s.empty? })
|
|
138
|
+
raise Error, "prerelease package versions must use unmodified nFPM semver conversion"
|
|
139
|
+
end
|
|
140
|
+
package.merge!("name" => configuration.name, "version" => number, "arch" => target.fetch("arch"),
|
|
141
|
+
"platform" => target.fetch("platform"), "mtime" => info.fetch("DATE"))
|
|
142
|
+
inspection = Inspection.new(root, configuration.data.fetch("libraries")).check(package, target, format)
|
|
143
|
+
config_path = Pathname.new(directory) / "nfpm.yaml"
|
|
144
|
+
write(config_path, YAML.dump(package))
|
|
145
|
+
run "nfpm", "package", "--config", config_path, "--packager", format, "--target", destination,
|
|
146
|
+
env: { "SOURCE_DATE_EPOCH" => info.fetch("SOURCE_DATE_EPOCH").to_s }
|
|
147
|
+
packages = files(destination)
|
|
148
|
+
raise Error, "nFPM did not create exactly one #{format} package" unless packages.length == 1
|
|
149
|
+
end
|
|
118
150
|
if target["after_package"]
|
|
119
151
|
signing = configuration.target_tokens(info, id, target, payload).merge("PACKAGE" => packages.first.to_s, "FORMAT" => format)
|
|
120
152
|
run(*render_tree(target.fetch("after_package"), signing), env: { "NATIVE_PACKAGES_TARGET" => id, "NATIVE_PACKAGES_VERSION" => number })
|
|
121
153
|
raise Error, "after_package must preserve the package path and output set" unless files(destination) == packages
|
|
122
154
|
end
|
|
155
|
+
if native
|
|
156
|
+
native.check_output(packages.first, format)
|
|
157
|
+
raise Error, "native recipes/signing must not change their input payload" unless native.digest(payload) == digest
|
|
158
|
+
end
|
|
123
159
|
records << { "target" => id, "format" => format, "path" => packages.first.relative_path_from(staging).to_s,
|
|
124
160
|
"sha256" => sha256(packages.first), "validation" => inspection }
|
|
125
161
|
end
|
|
@@ -127,7 +163,7 @@ module NativePackages
|
|
|
127
163
|
end
|
|
128
164
|
recipes = staging / "recipes"
|
|
129
165
|
project.generate(recipes, info)
|
|
130
|
-
project.check(recipes)
|
|
166
|
+
project.check(recipes, prerelease: number.include?("-"))
|
|
131
167
|
if !configuration.data.fetch("templates").empty?
|
|
132
168
|
project.recipe_archive(recipes, staging, name: configuration.name, version: number, epoch: info.fetch("SOURCE_DATE_EPOCH"))
|
|
133
169
|
(staging / "packaging-checksums.txt").delete
|
|
@@ -217,7 +253,9 @@ module NativePackages
|
|
|
217
253
|
manifest = JSON.parse((output / "build.json").read)
|
|
218
254
|
raise Error, "unsupported build manifest" unless manifest.fetch("schema") == 1
|
|
219
255
|
raise Error, "build belongs to another project/configuration" unless manifest.fetch("name") == configuration.name && manifest.fetch("configuration") == configuration.digest
|
|
220
|
-
|
|
256
|
+
number = configuration.package_version(manifest.fetch("version"))
|
|
257
|
+
selected = configuration.select(ids: manifest.fetch("targets").keys)
|
|
258
|
+
configuration.check_prerelease(number, selected)
|
|
221
259
|
expected = configuration.targets.transform_values { |target| target.fetch("formats") }
|
|
222
260
|
raise Error, "incomplete target set; aggregate all configured targets before publishing" if complete && manifest.fetch("targets") != expected
|
|
223
261
|
expected_pairs = manifest.fetch("targets").flat_map { |id, formats| formats.map { |format| [id, format] } }.sort
|
|
@@ -242,6 +280,11 @@ module NativePackages
|
|
|
242
280
|
manifest = verify(output)
|
|
243
281
|
raise Error, "supply at least one --to destination" if destinations.empty?
|
|
244
282
|
destinations.each { |name| project.repositories.select(name) unless name == "github" }
|
|
283
|
+
if manifest.fetch("version").include?("-")
|
|
284
|
+
raise Error, "prereleases can only attach to an existing GitHub prerelease" unless destinations == ["github"]
|
|
285
|
+
marked = capture("gh", "release", "view", "v#{manifest.fetch('version')}", "--repo", project.repository, "--json", "isPrerelease", "--jq", ".isPrerelease")
|
|
286
|
+
raise Error, "destination release must already be marked prerelease" unless marked == "true"
|
|
287
|
+
end
|
|
245
288
|
destinations.each do |destination|
|
|
246
289
|
if destination == "github"
|
|
247
290
|
paths = manifest.fetch("packages").map { |entry| output / entry.fetch("path") }
|
|
@@ -5,7 +5,8 @@ require_relative "project"
|
|
|
5
5
|
module NativePackages
|
|
6
6
|
class Configuration
|
|
7
7
|
include Support
|
|
8
|
-
|
|
8
|
+
NATIVE_FORMATS = %w[dmg inno].freeze
|
|
9
|
+
FORMATS = (%w[deb rpm archlinux apk ipk msix srpm] + NATIVE_FORMATS).freeze
|
|
9
10
|
NFPM_VERSION = "2.47.0"
|
|
10
11
|
NAMES = %w[native-packages.yaml native-packages.yml].freeze
|
|
11
12
|
KEYS = %w[schema tool nfpm targets release assets templates repositories revisions libraries version_file version_section].freeze
|
|
@@ -53,6 +54,15 @@ module NativePackages
|
|
|
53
54
|
def targets = data.fetch("targets")
|
|
54
55
|
def digest = OpenSSL::Digest::SHA256.hexdigest(JSON.generate(data))
|
|
55
56
|
def package(target) = merge(data.fetch("nfpm"), target.fetch("nfpm", {}))
|
|
57
|
+
def package_version(value) = version_arg(value, prerelease: data.fetch("release").fetch("prereleases", false))
|
|
58
|
+
|
|
59
|
+
def check_prerelease(version, selected)
|
|
60
|
+
return unless version.include?("-")
|
|
61
|
+
raise Error, "prerelease builds require release.prereleases: true" unless data.fetch("release")["prereleases"] == true
|
|
62
|
+
unless selected.values.all? { |target| (target.fetch("formats") - %w[deb rpm dmg inno]).empty? } && data.fetch("templates").empty?
|
|
63
|
+
raise Error, "prereleases support deb, rpm, dmg and inno only, without downstream recipes"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
56
66
|
|
|
57
67
|
def project
|
|
58
68
|
config = { "version" => 1, "name" => name, "repository" => data.fetch("release").fetch("repository", ""),
|
|
@@ -69,21 +79,44 @@ module NativePackages
|
|
|
69
79
|
raise Error, "configuration needs native-packages #{wanted}; run gem install native-packages -v #{wanted}, then native-packages _#{wanted}_ COMMAND"
|
|
70
80
|
end
|
|
71
81
|
raise Error, "unsupported nFPM version; use #{NFPM_VERSION}" unless data.fetch("tool").fetch("nfpm") == NFPM_VERSION
|
|
82
|
+
if data.fetch("release").key?("prereleases") && ![true, false].include?(data.fetch("release")["prereleases"])
|
|
83
|
+
raise Error, "release.prereleases must be true or false"
|
|
84
|
+
end
|
|
72
85
|
metadata = tokens("9.8.7", epoch: 1_767_225_600)
|
|
73
86
|
targets.each do |id, target|
|
|
74
87
|
raise Error, "invalid target name: #{id}" unless /\A[a-z0-9][a-z0-9-]*\z/.match?(id)
|
|
75
|
-
unknown = target.keys - %w[platform arch libc abi kind formats input nfpm before_build after_package compiler_target]
|
|
88
|
+
unknown = target.keys - %w[platform arch libc abi kind formats input nfpm before_build after_package compiler_target native]
|
|
76
89
|
raise Error, "targets.#{id}: unknown fields #{unknown.join(', ')}" unless unknown.empty?
|
|
77
90
|
formats = target.fetch("formats")
|
|
78
91
|
unless formats.is_a?(Array) && !formats.empty? && formats.uniq == formats && (formats - FORMATS).empty?
|
|
79
92
|
raise Error, "targets.#{id}.formats: choose from #{FORMATS.join(', ')}"
|
|
80
93
|
end
|
|
81
|
-
raise Error, "targets.#{id}.platform: use linux or
|
|
94
|
+
raise Error, "targets.#{id}.platform: use linux, windows or macos" unless %w[linux windows macos].include?(target.fetch("platform"))
|
|
82
95
|
raise Error, "targets.#{id}.arch: expected an architecture" unless /\A[a-zA-Z0-9_+-]+\z/.match?(target.fetch("arch"))
|
|
83
96
|
kind = target.fetch("kind", "binary")
|
|
84
97
|
raise Error, "targets.#{id}.kind: use binary, data or source" unless %w[binary data source].include?(kind)
|
|
85
98
|
raise Error, "#{id}: MSIX requires a Windows target with only msix format" if formats.include?("msix") && (target["platform"] != "windows" || formats != ["msix"])
|
|
86
|
-
raise Error, "#{id}: Windows targets require msix" if target["platform"] == "windows" &&
|
|
99
|
+
raise Error, "#{id}: Windows targets require msix or inno" if target["platform"] == "windows" && ![["msix"], ["inno"]].include?(formats)
|
|
100
|
+
raise Error, "#{id}: macOS targets require dmg" if target["platform"] == "macos" && formats != ["dmg"]
|
|
101
|
+
native = !(formats & NATIVE_FORMATS).empty?
|
|
102
|
+
if native
|
|
103
|
+
expected = { "dmg" => "macos", "inno" => "windows" }[formats.first]
|
|
104
|
+
raise Error, "#{id}: native formats require a separate binary target on their native platform" unless formats.length == 1 && expected == target["platform"] && kind == "binary"
|
|
105
|
+
recipe = mapping(target.fetch("native"), "#{id}.native")
|
|
106
|
+
raise Error, "#{id}.native: expected command and output" unless recipe.keys.sort == %w[command output]
|
|
107
|
+
command = recipe["command"]
|
|
108
|
+
unless command.is_a?(Array) && !command.empty? && command.all? { |part| part.is_a?(String) } && command.any? { |part| part.include?("@PACKAGE@") }
|
|
109
|
+
raise Error, "#{id}.native.command: use command arguments including @PACKAGE@"
|
|
110
|
+
end
|
|
111
|
+
filename = render(recipe.fetch("output"), target_tokens(metadata, id, target, "/payload"))
|
|
112
|
+
extension = formats == ["dmg"] ? ".dmg" : ".exe"
|
|
113
|
+
unless /\A[a-zA-Z0-9][a-zA-Z0-9._+-]*\z/.match?(filename) && filename.end_with?(extension)
|
|
114
|
+
raise Error, "#{id}.native.output: expected a safe #{extension} filename"
|
|
115
|
+
end
|
|
116
|
+
render_tree(command, target_tokens(metadata, id, target, "/payload").merge("PACKAGE" => "/output/#{filename}", "FORMAT" => formats.first))
|
|
117
|
+
elsif target.key?("native")
|
|
118
|
+
raise Error, "#{id}: native commands require a dmg or inno target"
|
|
119
|
+
end
|
|
87
120
|
raise Error, "#{id}: SRPM requires a separate source target" if (formats.include?("srpm") && (kind != "source" || formats != ["srpm"])) || (kind == "source" && formats != ["srpm"])
|
|
88
121
|
if kind == "binary" && target["platform"] == "linux"
|
|
89
122
|
raise Error, "#{id}.libc: declare glibc, musl or static" unless %w[glibc musl static].include?(target["libc"])
|
|
@@ -92,12 +125,14 @@ module NativePackages
|
|
|
92
125
|
input = mapping(target.fetch("input"), "#{id}.input")
|
|
93
126
|
raise Error, "#{id}.input: declare local or release_asset" unless input["local"] || input["release_asset"]
|
|
94
127
|
raise Error, "#{id}.input.kind: use file, directory or archive" unless %w[file directory archive].include?(input.fetch("kind", "archive"))
|
|
128
|
+
raise Error, "#{id}: native packaging requires an input directory" if native && input.fetch("kind", "archive") != "directory"
|
|
129
|
+
raise Error, "#{id}: native packaging requires input.local" if native && !input["local"]
|
|
95
130
|
%w[before_build after_package].each do |key|
|
|
96
131
|
hook = target[key]
|
|
97
132
|
raise Error, "#{id}.#{key}: use a nonempty array of command arguments" if hook && (!hook.is_a?(Array) || hook.empty? || !hook.all? { |part| part.is_a?(String) })
|
|
98
133
|
end
|
|
99
134
|
rendered = render_tree(package(target), target_tokens(metadata, id, target, "/payload"))
|
|
100
|
-
raise Error, "#{id}: nfpm.contents must be an array" unless rendered["contents"].is_a?(Array)
|
|
135
|
+
raise Error, "#{id}: nfpm.contents must be an array" unless native || rendered["contents"].is_a?(Array)
|
|
101
136
|
%w[maintainer description license].each do |key|
|
|
102
137
|
raise Error, "#{id}: fill in nfpm.#{key}" unless rendered[key].is_a?(String) && !rendered[key].strip.empty?
|
|
103
138
|
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "find"
|
|
4
|
+
|
|
5
|
+
module NativePackages
|
|
6
|
+
# Coordinate application-owned native recipes. The gem owns the input copy,
|
|
7
|
+
# output boundary and manifest; applications retain installer/signing policy.
|
|
8
|
+
class NativeRecipe
|
|
9
|
+
include Support
|
|
10
|
+
MACHO = ["feedface", "feedfacf", "cefaedfe", "cffaedfe", "cafebabe", "bebafeca", "cafebabf", "bfbafeca"].freeze
|
|
11
|
+
attr_reader :root
|
|
12
|
+
|
|
13
|
+
def initialize(root)
|
|
14
|
+
@root = root
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.host
|
|
18
|
+
return "windows" if Gem.win_platform?
|
|
19
|
+
RUBY_PLATFORM.include?("darwin") ? "macos" : "linux"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def doctor(target, command)
|
|
23
|
+
raise Error, "#{target.fetch('platform')} packaging must run on that native host" unless self.class.host == target.fetch("platform")
|
|
24
|
+
required = [command.first]
|
|
25
|
+
required << "lipo" if target["platform"] == "macos"
|
|
26
|
+
missing = required.reject { |tool| available?(tool) || (Pathname.new(tool).absolute? && File.file?(tool) && File.executable?(tool)) }
|
|
27
|
+
raise Error, "install required native tools: #{missing.join(', ')}" unless missing.empty?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def tree(path)
|
|
31
|
+
path = Pathname.new(path)
|
|
32
|
+
raise Error, "native input must be a real directory" unless path.directory? && !path.symlink?
|
|
33
|
+
boundary = path.realpath.to_s + File::SEPARATOR
|
|
34
|
+
Find.find(path.to_s).sort.map do |name|
|
|
35
|
+
item = Pathname.new(name)
|
|
36
|
+
stat = item.lstat
|
|
37
|
+
relative = item.relative_path_from(path).to_s
|
|
38
|
+
if stat.symlink?
|
|
39
|
+
link = item.readlink
|
|
40
|
+
unless !link.absolute? && item.realpath.to_s.start_with?(boundary)
|
|
41
|
+
raise Error, "native input symlink leaves the payload: #{relative}"
|
|
42
|
+
end
|
|
43
|
+
[relative, "symlink", link.to_s]
|
|
44
|
+
elsif stat.directory?
|
|
45
|
+
[relative, "directory", stat.mode & 0o777]
|
|
46
|
+
elsif stat.file?
|
|
47
|
+
[relative, "file", stat.mode & 0o777, sha256(item)]
|
|
48
|
+
else
|
|
49
|
+
raise Error, "unsupported native input entry: #{relative}"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
rescue Errno::ENOENT, Errno::ELOOP => error
|
|
53
|
+
raise Error, "invalid native input link or missing entry: #{error.message}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def digest(path) = OpenSSL::Digest::SHA256.hexdigest(JSON.generate(tree(path)))
|
|
57
|
+
|
|
58
|
+
def inspect(payload, target)
|
|
59
|
+
paths = tree(payload).filter_map { |relative, kind, *| payload / relative if kind == "file" }
|
|
60
|
+
return Inspection.new(root).pe(paths, target) if target["platform"] == "windows"
|
|
61
|
+
expected = { "amd64" => "x86_64", "arm64" => "arm64", "universal" => "arm64 x86_64" }[target.fetch("arch")]
|
|
62
|
+
raise Error, "Mach-O inspection supports amd64, arm64 and universal" unless expected
|
|
63
|
+
binaries = paths.select { |path| MACHO.include?(path.binread(4).unpack1("H*")) }
|
|
64
|
+
raise Error, "macOS target contains no Mach-O binaries" if binaries.empty?
|
|
65
|
+
binaries.each do |path|
|
|
66
|
+
arches = capture("lipo", "-archs", path).split.sort
|
|
67
|
+
raise Error, "wrong Mach-O architecture: #{path}" unless (expected.split - arches).empty?
|
|
68
|
+
end
|
|
69
|
+
{ "kind" => "macho", "binaries" => binaries.length, "architecture" => target.fetch("arch"), "dependencies" => "explicit", "installation" => "not-tested" }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def check_output(path, format)
|
|
73
|
+
raise Error, "native recipe must create a regular package file" unless path.file? && !path.symlink?
|
|
74
|
+
valid = File.open(path, "rb") do |file|
|
|
75
|
+
if format == "dmg"
|
|
76
|
+
next false if file.size < 512
|
|
77
|
+
file.seek(-512, IO::SEEK_END)
|
|
78
|
+
file.read(4) == "koly"
|
|
79
|
+
else
|
|
80
|
+
next false unless file.size >= 64 && file.read(2) == "MZ"
|
|
81
|
+
file.seek(0x3c)
|
|
82
|
+
offset = file.read(4).unpack1("L<")
|
|
83
|
+
next false unless offset >= 64 && offset + 24 <= file.size
|
|
84
|
+
file.seek(offset)
|
|
85
|
+
file.read(4) == "PE\0\0"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
raise Error, "native recipe output is not a #{format} container" unless valid
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -103,11 +103,11 @@ module NativePackages
|
|
|
103
103
|
write(output / "release.json", JSON.pretty_generate(metadata.sort.to_h) + "\n")
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
-
def check(output)
|
|
106
|
+
def check(output, prerelease: false)
|
|
107
107
|
output = Pathname.new(output).expand_path
|
|
108
108
|
metadata = JSON.parse((output / "release.json").read)
|
|
109
109
|
raise Error, "prepared recipes belong to another project" unless metadata.fetch("NAME") == package_name
|
|
110
|
-
version_arg(metadata.fetch("VERSION"))
|
|
110
|
+
version_arg(metadata.fetch("VERSION"), prerelease: prerelease)
|
|
111
111
|
Dir.mktmpdir("native-packages-check-") do |temporary|
|
|
112
112
|
expected = Pathname.new(temporary)
|
|
113
113
|
generate(expected, metadata)
|
|
@@ -12,7 +12,7 @@ require "tmpdir"
|
|
|
12
12
|
require "yaml"
|
|
13
13
|
|
|
14
14
|
module NativePackages
|
|
15
|
-
VERSION = "0.
|
|
15
|
+
VERSION = "0.3.0"
|
|
16
16
|
class Error < StandardError; end
|
|
17
17
|
|
|
18
18
|
module Support
|
|
@@ -24,7 +24,9 @@ module NativePackages
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def capture(*arguments, env: {}, chdir: root)
|
|
27
|
-
|
|
27
|
+
# Native tools can emit UTF-8 progress even when an SSH session declares
|
|
28
|
+
# US-ASCII. Preserve those bytes; locale-dependent strip would raise.
|
|
29
|
+
output, error, status = Open3.capture3(env, *arguments.map(&:to_s), chdir: chdir.to_s, binmode: true)
|
|
28
30
|
raise Error, "#{arguments.first} failed: #{error.strip}" unless status.success?
|
|
29
31
|
output.strip
|
|
30
32
|
end
|
|
@@ -36,9 +38,13 @@ module NativePackages
|
|
|
36
38
|
end
|
|
37
39
|
end
|
|
38
40
|
|
|
39
|
-
def version_arg(value)
|
|
41
|
+
def version_arg(value, prerelease: false)
|
|
40
42
|
version = value.delete_prefix("v")
|
|
41
|
-
|
|
43
|
+
stable = /\A(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)\z/.match?(version)
|
|
44
|
+
preview = prerelease && /\A(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)-(alpha|beta|rc)\.[1-9]\d*\z/.match?(version)
|
|
45
|
+
unless stable || preview
|
|
46
|
+
raise Error, prerelease ? "expected 1.2.3 or 1.2.3-alpha.N, -beta.N or -rc.N" : "expected a stable version such as 1.2.3"
|
|
47
|
+
end
|
|
42
48
|
version
|
|
43
49
|
end
|
|
44
50
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: native-packages
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Carmine Paolino
|
|
@@ -21,6 +21,7 @@ files:
|
|
|
21
21
|
- docs/cli-design.md
|
|
22
22
|
- docs/configuration.md
|
|
23
23
|
- docs/legacy.md
|
|
24
|
+
- docs/native-recipes.md
|
|
24
25
|
- docs/platforms.md
|
|
25
26
|
- docs/releasing.md
|
|
26
27
|
- examples/native-packages-all-formats.yaml
|
|
@@ -35,6 +36,7 @@ files:
|
|
|
35
36
|
- lib/native_packages/cli.rb
|
|
36
37
|
- lib/native_packages/configuration.rb
|
|
37
38
|
- lib/native_packages/inspection.rb
|
|
39
|
+
- lib/native_packages/native_recipe.rb
|
|
38
40
|
- lib/native_packages/project.rb
|
|
39
41
|
- lib/native_packages/repositories.rb
|
|
40
42
|
- lib/native_packages/scaffold.rb
|