native-packages 0.8.1 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef21c207476e3d19df85ebdb463eb0ccfb020ab8a4fd8ead551a22c4c2d4de62
4
- data.tar.gz: 1648f4ffa1698dfdccea0d3693943e696b2810c69f595bf48fa01c386346808d
3
+ metadata.gz: 7991d9fd47977d5eda80c98ee460ef7c401b4db6eae41a74d285c58576bbb089
4
+ data.tar.gz: 943a2ff5686edd0cfaa7f09fd9a213d797f4a628cf5eff9331127f84840c06c8
5
5
  SHA512:
6
- metadata.gz: f3cea3ca8d3d79956a0cca4ca779955b901795b1000a4ef57cad97abebfc32721d678df435ba8a46c2e226a210fdcb92f6c7cac586f81cc416ed5e8028ee300f
7
- data.tar.gz: 2e6c2eaffc5cdab5cb3ef8ad3969b7058880cabb155f56170955f309d7b2e11701a384ac291f889cc5717d565b7073f1ec75edd51984b25554bad00e2564ec20
6
+ metadata.gz: 2ac16d8c7b639da77db270af088839ab3e6ecbb6b913086208f26dc95db69b843df11c887ad556c744f493b5fe8ea85660095b9a5eff8425821daabb7b850330
7
+ data.tar.gz: '094cba93dda2a8dbb0442d3b88d0efe5039ec28f715fdd85763e6dcdd56fde1b2a013813fbcf514930240dc874b31fcc562381c304c8e710a303e3bbe1d1e255'
data/CONTRIBUTING.md CHANGED
@@ -18,7 +18,7 @@ To check the packaged gem and standalone command:
18
18
 
19
19
  ```sh
20
20
  gem build native-packages.gemspec
21
- ruby test/gem_install.rb native-packages-0.8.1.gem
21
+ ruby test/gem_install.rb native-packages-0.10.0.gem
22
22
  ```
23
23
 
24
24
  See [Releasing the gem](docs/releasing.md) for publication and
@@ -49,6 +49,10 @@ path dependency. To test changes from a local theme checkout, use Bundler's
49
49
  temporary path configuration or a separate Gemfile, and keep that local setup
50
50
  out of the committed lockfile.
51
51
 
52
+ Social previews use the launch-film poster, configured under
53
+ `jekyll_vitepress.seo.image` in `docs/_config.yml`. Keep it a JPEG or PNG with
54
+ matching width, height, and alt text; the SVG logo is for the site itself.
55
+
52
56
  ### Writing a guide
53
57
 
54
58
  Put tutorials in `docs/_guides/` and reference pages in `docs/_reference/`.
data/README.md CHANGED
@@ -13,6 +13,8 @@ Homebrew casks.
13
13
 
14
14
  You bring the compiled app. native-packages handles packaging and release updates.
15
15
 
16
+ https://github.com/user-attachments/assets/e3157514-570d-47b3-a2e9-aca70042a138
17
+
16
18
  [Documentation](https://native-packages.dev/)
17
19
 
18
20
  ## Get started
@@ -50,10 +52,10 @@ record. Local builds work without a GitHub release or credentials.
50
52
  | --- | --- |
51
53
  | Linux | DEB, RPM, AppImage, Arch Linux packages, Alpine APK, IPK, and source RPM |
52
54
  | macOS | DMG, using your packaging script; optional Apple signing and notarization |
53
- | Windows | MSIX, or Inno Setup installers using your packaging script |
55
+ | Windows | MSIX, or MSI and Inno Setup installers using your packaging script |
54
56
 
55
- Each target needs files built for that platform. DMG and Inno builds run on
56
- macOS and Windows respectively. See [supported platforms](docs/_reference/platforms.md)
57
+ Each target needs files built for that platform. DMG builds run on macOS;
58
+ MSI and Inno builds run on Windows. See [supported platforms](docs/_reference/platforms.md)
57
59
  for prerequisites and distribution recipes.
58
60
 
59
61
  ## Guides
@@ -17,7 +17,7 @@ own compiled executable, or build the tiny example below.
17
17
  You need Ruby 3.2 or later. Install the version used by these guides:
18
18
 
19
19
  ```sh
20
- gem install native-packages --version 0.8.1
20
+ gem install native-packages --version 0.10.0
21
21
  ```
22
22
 
23
23
  For Linux packages, install **nFPM 2.47.0**, the helper native-packages uses to
@@ -79,7 +79,7 @@ Open the generated `native-packages.yaml` and replace its contents with:
79
79
  ```yaml
80
80
  schema: 1
81
81
  tool:
82
- version: '0.8.1'
82
+ version: '0.10.0'
83
83
  nfpm: '2.47.0'
84
84
 
85
85
  nfpm:
@@ -24,7 +24,7 @@ packaging:
24
24
  needs: release
25
25
  permissions:
26
26
  contents: write
27
- uses: crmne/native-packages/.github/workflows/package.yml@v0.8.1
27
+ uses: crmne/native-packages/.github/workflows/package.yml@v0.10.0
28
28
  with:
29
29
  version: ${{ github.ref_name }}
30
30
  publish: true
@@ -39,7 +39,7 @@ The configuration must include `release.repository` and a `release_asset`
39
39
  for each selected target. See [Publishing a release](publishing.md).
40
40
 
41
41
  Use the same native-packages release for the workflow and `tool.version`.
42
- For an immutable workflow reference, replace `v0.8.1` with that release's
42
+ For an immutable workflow reference, replace `v0.10.0` with that release's
43
43
  full commit SHA.
44
44
 
45
45
  ## Build without publishing
@@ -57,7 +57,7 @@ permissions:
57
57
  contents: read
58
58
  jobs:
59
59
  packaging:
60
- uses: crmne/native-packages/.github/workflows/package.yml@v0.8.1
60
+ uses: crmne/native-packages/.github/workflows/package.yml@v0.10.0
61
61
  ```
62
62
 
63
63
  Validation does not compile the app or check that future release inputs exist.
@@ -72,7 +72,7 @@ packaging:
72
72
  needs: build
73
73
  permissions:
74
74
  contents: write
75
- uses: crmne/native-packages/.github/workflows/package.yml@v0.8.1
75
+ uses: crmne/native-packages/.github/workflows/package.yml@v0.10.0
76
76
  with:
77
77
  version: '1.2.3'
78
78
  source-artifact: linux-binaries
@@ -101,7 +101,7 @@ The workflow passes the same target selection to build and publication.
101
101
  It requires every format declared for those targets. An empty selection
102
102
  means all configured targets.
103
103
 
104
- Build DMG and Inno targets in your own macOS and Windows jobs with the CLI.
104
+ Build DMG, MSI and Inno targets in your own macOS and Windows jobs with the CLI.
105
105
  See [Building across platforms](multi-platform.md) for combining their outputs
106
106
  and generating recipes after all packages are available.
107
107
 
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  title: macOS and Windows installers
3
- description: Connect your DMG or Inno Setup script to the same build and publication process as your Linux packages.
3
+ description: Connect your DMG, MSI or Inno Setup script to the same build and publication process as your Linux packages.
4
4
  nav_order: 5
5
5
  ---
6
6
 
7
7
  # macOS and Windows installers
8
8
 
9
- This guide shows how to connect an existing DMG or Inno Setup script to
9
+ This guide shows how to connect an existing DMG, MSI or Inno Setup script to
10
10
  native-packages. You supply a built app and a packaging script. The build
11
11
  command checks the app, runs your script, and records the finished installer's
12
12
  checksum.
13
13
 
14
- Build **DMGs on macOS** and **Inno installers on Windows**. These targets use
14
+ Build **DMGs on macOS** and **MSI or Inno installers on Windows**. These targets use
15
15
  your native tools and do not need an nFPM executable.
16
16
 
17
17
  ## Prepare your app
@@ -31,7 +31,7 @@ Here is a complete configuration for an ARM64 Mac app and an x86-64 Windows app:
31
31
  ```yaml
32
32
  schema: 1
33
33
  tool:
34
- version: '0.8.1'
34
+ version: '0.10.0'
35
35
  nfpm: '2.47.0'
36
36
  nfpm:
37
37
  name: hello
@@ -54,15 +54,18 @@ targets:
54
54
  windows-amd64:
55
55
  platform: windows
56
56
  arch: amd64
57
- formats: [inno]
57
+ formats: [msi]
58
58
  input:
59
59
  kind: directory
60
60
  local: dist/windows
61
61
  native:
62
- command: [ruby, packaging/setup.rb, '@PAYLOAD@', '@PACKAGE@', '@VERSION@']
63
- output: 'hello-@TAG@-windows-setup.exe'
62
+ command: [ruby, packaging/msi.rb, '@PAYLOAD@', '@PACKAGE@', '@VERSION@']
63
+ output: 'hello-@TAG@-windows-amd64.msi'
64
64
  ```
65
65
 
66
+ For an Inno Setup installer instead, use `formats: [inno]` and an `.exe`
67
+ output. A Windows target builds one installer format.
68
+
66
69
  The `nfpm` section still holds shared package metadata, and `tool.nfpm` keeps
67
70
  mixed-platform configurations consistent. Native targets use `native.command`
68
71
  to create their output.
@@ -110,6 +113,47 @@ abort "DMG verification failed" unless system("hdiutil", "verify", output)
110
113
  This places the app and an Applications shortcut inside a compressed disk
111
114
  image. Adapt the volume name and app name to your project.
112
115
 
116
+ ### A Windows Installer (MSI) project
117
+
118
+ Keep your installer's WiX source in the repository. This minimal
119
+ `packaging/hello.wxs` (WiX Toolset 5) installs everything in the payload
120
+ for all users and replaces earlier versions on upgrade:
121
+
122
+ ```xml
123
+ <Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
124
+ <Package Name="Hello" Manufacturer="Your Name" Version="$(Version)"
125
+ UpgradeCode="{00000000-0000-0000-0000-000000000000}" Scope="perMachine" Compressed="yes">
126
+ <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
127
+ <MediaTemplate EmbedCab="yes" />
128
+ <StandardDirectory Id="ProgramFiles64Folder">
129
+ <Directory Id="INSTALLFOLDER" Name="Hello" />
130
+ </StandardDirectory>
131
+ <Files Include="$(SourceDir)\**" Directory="INSTALLFOLDER" />
132
+ </Package>
133
+ </Wix>
134
+ ```
135
+
136
+ Generate your own `UpgradeCode` once and never change it: it is how Windows
137
+ recognizes an upgrade. Then have `packaging/msi.rb` build it:
138
+
139
+ ```ruby
140
+ payload, output, version = ARGV
141
+ # Windows Installer versions are numeric: 1.2.3-beta.1 installs as 1.2.3.
142
+ numeric = version.split("-").first
143
+ success = system("wix", "build", File.join(__dir__, "hello.wxs"), "-arch", "x64",
144
+ "-d", "Version=#{numeric}", "-d", "SourceDir=#{payload}", "-pdbtype", "none", "-o", output)
145
+ abort "MSI build failed" unless success
146
+ ```
147
+
148
+ `-pdbtype none` stops WiX writing a `.wixpdb` debug file next to the MSI;
149
+ the recipe may create only the declared output.
150
+
151
+ Install WiX with `dotnet tool install --global wix --version 5.0.2`. Windows
152
+ compares only the first three version fields, each at most 255.255.65535, so
153
+ release candidates of one version upgrade each other in place. Per-user
154
+ installs, shortcuts, file associations, and closing a running app are part of
155
+ your WiX project; see the [WiX documentation](https://docs.firegiant.com/wix/).
156
+
113
157
  ### An Inno Setup script
114
158
 
115
159
  Keep your app's `.iss` recipe in the repository. Have `packaging/setup.rb`
@@ -143,11 +187,11 @@ native-packages build --version 1.2.3 --target windows-amd64 --output dist/windo
143
187
  ```
144
188
 
145
189
  `doctor` checks the host and the command's executable. Your script checks its
146
- additional tools, such as the Inno compiler. `validate` and `build --dry-run`
190
+ additional tools, such as WiX or the Inno compiler. `validate` and `build --dry-run`
147
191
  do not execute it.
148
192
 
149
193
  The build checks Mach-O or PE architecture, runs the script, and checks that
150
- it produced exactly the declared DMG or EXE. Test installation, startup,
194
+ it produced exactly the declared DMG, MSI or EXE. Test installation, startup,
151
195
  upgrades, and removal separately for your app.
152
196
 
153
197
  ## Preserve the prepared input
@@ -71,7 +71,7 @@ The command downloads the inputs, verifies their published hashes, and builds
71
71
  packages in `dist/packages/1.2.3/`. It skips `before_build` hooks. Every binary
72
72
  release input needs a matching checksum.
73
73
 
74
- DMG and Inno targets use prepared local directories. Download and unpack their
74
+ DMG, MSI and Inno targets use prepared local directories. Download and unpack their
75
75
  inputs in the native build job, then use `--version`. See
76
76
  [macOS and Windows installers](native-recipes.md).
77
77
 
@@ -67,7 +67,7 @@ Package publication's separate `packaging-checksums.txt` is also unchanged.
67
67
 
68
68
  ## Attest in the application's build job
69
69
 
70
- The examples use `v0.8.1` for readability. For release workflows, replace it
70
+ The examples use `v0.10.0` for readability. For release workflows, replace it
71
71
  with that release's reviewed full commit SHA, not a mutable branch.
72
72
  After producing the final package, before uploading it, add this step to the
73
73
  job that built it:
@@ -79,7 +79,7 @@ permissions:
79
79
  attestations: write
80
80
  steps:
81
81
  # Existing checkout, build, platform signing and packaging steps go here.
82
- - uses: crmne/native-packages/.github/actions/attest@v0.8.1
82
+ - uses: crmne/native-packages/.github/actions/attest@v0.10.0
83
83
  with:
84
84
  subject-path: dist/*.tar.gz
85
85
  ```
@@ -108,7 +108,7 @@ and install Ruby 3.2 or later with Ed25519-capable OpenSSL before the signing
108
108
  step. Then call:
109
109
 
110
110
  ```yaml
111
- - uses: crmne/native-packages/.github/actions/sign-release@v0.8.1
111
+ - uses: crmne/native-packages/.github/actions/sign-release@v0.10.0
112
112
  with:
113
113
  directory: dist/release
114
114
  public-key: assets/update-public-key.hex
@@ -193,7 +193,7 @@ If several gem versions are installed, RubyGems lets you select the one required
193
193
  by the configuration:
194
194
 
195
195
  ```sh
196
- native-packages _0.8.1_ build --version 1.2.3
196
+ native-packages _0.10.0_ build --version 1.2.3
197
197
  ```
198
198
 
199
199
  Bundler also works if you prefer to manage the gem in a Gemfile.
@@ -28,7 +28,7 @@ expressions. Use the replacement values listed below.
28
28
  | Field | Purpose |
29
29
  | --- | --- |
30
30
  | `schema` | Required. Set to `1`. |
31
- | `tool.version` | Required. Exact native-packages version, such as `'0.8.1'`. |
31
+ | `tool.version` | Required. Exact native-packages version, such as `'0.10.0'`. |
32
32
  | `tool.nfpm` | Required. Set to `'2.47.0'`, including in native-only configurations. |
33
33
  | `nfpm` | Shared package metadata and installed-file mappings, or a relative YAML filename. Requires `name`. |
34
34
  | `targets` | Required. Named build targets; use `{}` for recipe-only projects. |
@@ -48,7 +48,7 @@ command needed to install and select the configured gem.
48
48
 
49
49
  `nfpm` is named after the helper that writes Linux packages and MSIX files.
50
50
  It holds the package's metadata, files, dependencies, and package-manager
51
- settings. Native DMG and Inno targets also read the shared metadata.
51
+ settings. Native DMG, MSI and Inno targets also read the shared metadata.
52
52
 
53
53
  Every target needs nonempty `maintainer`, `description`, and `license` values.
54
54
  Non-native targets need a `contents` list. The shared package name may contain
@@ -95,13 +95,14 @@ letters, digits, and hyphens, starting with a letter or digit.
95
95
  | `compiler_target` | Optional value for `@TARGET@`; otherwise it is the target ID. |
96
96
  | `before_build` | Command arguments run before copying a local input. |
97
97
  | `after_package` | Command arguments run on each package before its hash is recorded. |
98
- | `native` | Required for DMG/Inno. Contains `command` and `output`. |
98
+ | `native` | Required for DMG, MSI and Inno. Contains `command` and `output`. |
99
+ | `appimage` | Optional for AppImage targets: `update_information`, embedded with a `.zsync` file beside the AppImage. See [AppImage](platforms.md#update-information). |
99
100
 
100
101
  Formats are `deb`, `rpm`, `archlinux`, `apk`, `ipk`, `appimage`, `srpm`,
101
- `msix`, `dmg`, and `inno`. See [Supported platforms](platforms.md) for their requirements.
102
+ `msix`, `dmg`, `msi`, and `inno`. See [Supported platforms](platforms.md) for their requirements.
102
103
 
103
- Windows targets select only `[msix]` or `[inno]`; macOS targets select `[dmg]`.
104
- DMG and Inno each require a separate binary target. SRPM requires a separate
104
+ Windows targets select only `[msix]`, `[msi]` or `[inno]`; macOS targets select `[dmg]`.
105
+ DMG, MSI and Inno each require a separate binary target. SRPM requires a separate
105
106
  `kind: source` target with only `[srpm]`.
106
107
 
107
108
  ### Inputs
@@ -19,14 +19,15 @@ libraries.
19
19
  | `archlinux` | Linux | Arch-compatible files and explicit dependencies. | nFPM, `readelf` for binaries. |
20
20
  | `apk` | Linux | Suitable Alpine files, usually musl or static binaries, and dependencies. | nFPM, `readelf` for binaries. |
21
21
  | `ipk` | Linux | Files for the device/distribution baseline, dependencies, and an `abi` label. | nFPM, `readelf` for binaries. |
22
- | `appimage` | Linux | The same files as the DEB/RPM, including one desktop entry and its icon. | `mksquashfs` (squashfs-tools), `curl`, `readelf`. |
22
+ | `appimage` | Linux | The same files as the DEB/RPM, including one desktop entry and its icon. | `mksquashfs` (squashfs-tools), `curl`, `readelf`; `zsyncmake` (zsync) with update information. |
23
23
  | `srpm` | Linux source target | RPM spec and source files. | nFPM; native RPM tooling for rebuild tests. |
24
24
  | `msix` | Windows | Windows binaries, identity, application assets, and capabilities. | nFPM; Windows tools for signing and installation tests. |
25
25
  | `dmg` | macOS | Complete app and a DMG script. | macOS, `lipo`, and your script's tools, such as `hdiutil`. |
26
+ | `msi` | Windows | Complete app and a Windows Installer recipe/wrapper, such as a WiX project. | Windows and your installed MSI toolset, such as WiX. |
26
27
  | `inno` | Windows | Complete app and an Inno Setup recipe/wrapper. | Windows and your installed Inno compiler. |
27
28
 
28
29
  The supported nFPM version is **2.47.0**. It writes package files from your
29
- prepared contents. Native DMG and Inno targets call your scripts instead.
30
+ prepared contents. Native DMG, MSI and Inno targets call your scripts instead.
30
31
  Archive inputs also require `bsdtar`; release downloads require `curl`.
31
32
 
32
33
  ## Linux compatibility
@@ -83,7 +84,41 @@ DEB and RPM do, and `build.json` lists what the binary links under
83
84
  `required_libraries` with `"libraries": "host"`. It therefore runs where your
84
85
  binary's glibc floor and those libraries are met, not on every Linux system.
85
86
  Build on the oldest distribution you support, or link statically, to widen
86
- that. The AppImage carries no update information and is not signed.
87
+ that. The AppImage is not signed.
88
+
89
+ ### Update information
90
+
91
+ AppImageUpdate, AppImageLauncher, AM, Gear Lever and other updaters find a
92
+ newer AppImage through the update information embedded in it, and download
93
+ only the blocks that changed through a `.zsync` file published beside it.
94
+ Give the target an `appimage.update_information`:
95
+
96
+ ```yaml
97
+ linux-amd64:
98
+ platform: linux
99
+ arch: amd64
100
+ libc: glibc
101
+ formats: [appimage]
102
+ appimage:
103
+ update_information: gh-releases-zsync|owner|app|latest|app-*-x86_64.AppImage.zsync
104
+ ```
105
+
106
+ The string goes into the runtime's `.upd_info` section, and the build reads
107
+ it back from the finished AppImage. `zsyncmake` then writes
108
+ `NAME-VERSION-ARCH.AppImage.zsync` beside it, with the AppImage's file name
109
+ as its relative URL, and the build checks the file's name, length and SHA-1
110
+ against the AppImage. `build.json` records the `.zsync` file with its
111
+ package, and `publish --to github` attaches both. The same inputs give the
112
+ same bytes for both files.
113
+
114
+ Two forms are accepted: `gh-releases-zsync|OWNER|REPOSITORY|TAG|PATTERN`,
115
+ where `TAG` is usually `latest` (the newest stable release), and
116
+ `zsync|https://URL.zsync`. A GitHub string must name `release.repository`,
117
+ and its pattern must match the target's `.zsync` file name, so validation
118
+ fails before a release could ship an AppImage whose updaters look for a file
119
+ that is not there. Each architecture needs its own target with its own
120
+ pattern. An `after_package` hook must leave the AppImage as it is, or the
121
+ `.zsync` file no longer describes it and the build fails.
87
122
 
88
123
  Running an AppImage needs FUSE on the user's system, or
89
124
  `./App.AppImage --appimage-extract-and-run`.
@@ -105,7 +140,7 @@ project's Windows acceptance tests do. Those tests found that Windows rejected
105
140
  nFPM 2.47.0's built-in signature with `0x80096010`. An MSIX file built
106
141
  successfully is not yet evidence that Windows will accept and install it.
107
142
 
108
- ## DMG and Inno Setup
143
+ ## DMG, MSI and Inno Setup
109
144
 
110
145
  These formats run on their native host and each require a separate binary
111
146
  target with a local directory input. macOS inspection supports `amd64`,
@@ -127,7 +162,7 @@ An Arch package file and an AUR recipe are separate outputs: the former can
127
162
  be installed directly; the latter tells Arch tooling how to obtain and package
128
163
  your app.
129
164
 
130
- There is no Flatpak or Snap build/publication adapter, no NSIS/WiX adapter, and no
165
+ There is no Flatpak or Snap build/publication adapter, no NSIS adapter, and no
131
166
  hosted APT/DNF index service. Keep those workflows in your app's release
132
167
  process if you need them.
133
168
 
@@ -23,8 +23,8 @@ working on with `doctor --target ID` and `build --target ID --dry-run`.
23
23
  Install and select the version named in the error:
24
24
 
25
25
  ```sh
26
- gem install native-packages --version 0.8.1
27
- native-packages _0.8.1_ doctor
26
+ gem install native-packages --version 0.10.0
27
+ native-packages _0.10.0_ doctor
28
28
  ```
29
29
 
30
30
  `tool.version` is an exact match. In CI, use the matching released reusable
@@ -36,7 +36,7 @@ Linux and MSIX packaging require nFPM 2.47.0. Linux binary inspection needs
36
36
  `readelf`; archive extraction needs `bsdtar`. Install these using the
37
37
  [setup instructions](../_guides/getting-started.md#1-install-the-tools).
38
38
 
39
- DMG and Inno targets do not require an nFPM executable. If you only intend to
39
+ DMG, MSI and Inno targets do not require an nFPM executable. If you only intend to
40
40
  build a native target, select it explicitly so `doctor` does not check Linux
41
41
  targets from the same configuration.
42
42
 
data/docs/cli-design.md CHANGED
@@ -3,7 +3,7 @@
3
3
  The owner approved compatible shared support for RekordFlash's native alpha
4
4
  packaging on 2026-09-13. Version 0.3's [native recipe and prerelease policy](native-recipes.md)
5
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
6
+ pinned. DMG/MSI/Inno adapters coordinate existing app scripts and retain their
7
7
  signing/version decisions, without adding a compiler or plugin framework.
8
8
 
9
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.
data/docs/index.md CHANGED
@@ -28,7 +28,7 @@ features:
28
28
  details: Create DEB, RPM, AppImage, Arch, Alpine, and IPK packages from your compatible Linux builds.
29
29
  - icon: 🖥️
30
30
  title: Native installers
31
- details: Connect your macOS DMG and Windows Inno Setup scripts, or package Windows apps as MSIX.
31
+ details: Connect your macOS DMG script and your Windows MSI or Inno Setup script, or package Windows apps as MSIX.
32
32
  - icon: 🚀
33
33
  title: Release automation
34
34
  details: Build from local files or release assets. Add packaging to GitHub Actions and upload the finished packages.
@@ -39,3 +39,47 @@ features:
39
39
  title: Verified builds
40
40
  details: Check binary architecture and library requirements. Record file hashes and verify them before publishing.
41
41
  ---
42
+
43
+ <video class="hero-film" controls muted loop playsinline preload="metadata" poster="{{ '/assets/images/launch-film-poster.jpg' | relative_url }}" aria-label="native-packages in under a minute: ZapFast, then seventeen packaging files become one configuration" hidden>
44
+ <source src="{{ '/assets/videos/launch-film.mp4' | relative_url }}" type="video/mp4">
45
+ </video>
46
+
47
+ <style>
48
+ /* The theme sizes its hero for a square logo; the film is 16:9. */
49
+ .VPHero .hero-film {
50
+ display: block;
51
+ width: 100%;
52
+ max-width: 560px;
53
+ height: auto;
54
+ aspect-ratio: 16 / 9;
55
+ margin: 0 auto;
56
+ border-radius: 12px;
57
+ background: #0d0e1a;
58
+ }
59
+ .VPHero .image-container:has(.hero-film) {
60
+ width: 100%;
61
+ height: auto;
62
+ padding: 0 24px;
63
+ transform: none;
64
+ }
65
+ @media (max-width: 959px) {
66
+ .VPHero .image:has(.hero-film) {
67
+ margin: 0 0 32px;
68
+ }
69
+ }
70
+ </style>
71
+
72
+ <script>
73
+ // The theme's hero takes a picture; the film takes its place where
74
+ // scripts run, and the logo stays where they do not.
75
+ (function () {
76
+ var film = document.querySelector(".hero-film");
77
+ var slot = document.querySelector(".VPHero .image-container");
78
+ if (!film || !slot) return;
79
+ slot.replaceChildren(film);
80
+ film.hidden = false;
81
+ if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
82
+ film.play().catch(function () {});
83
+ }
84
+ })();
85
+ </script>
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.8.1 --target main --title 'native-packages 0.8.1' --notes-file release-notes.md
26
+ gh release create v0.10.0 --target main --title 'native-packages 0.10.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.
@@ -36,7 +36,7 @@ If the workflow itself needs a correction, fix it on `main` and dispatch the
36
36
  corrected release workflow against the existing published tag:
37
37
 
38
38
  ```sh
39
- gh workflow run release.yml --ref main -f tag=v0.8.1
39
+ gh workflow run release.yml --ref main -f tag=v0.10.0
40
40
  ```
41
41
 
42
42
  Validation resolves the original tag to an exact commit, requires it to be on
@@ -1,7 +1,7 @@
1
1
  # Supply the listed build outputs before running this example.
2
2
  schema: 1
3
3
  tool:
4
- version: '0.8.1'
4
+ version: '0.10.0'
5
5
  nfpm: '2.47.0'
6
6
  nfpm:
7
7
  name: example-app
@@ -1,6 +1,6 @@
1
1
  schema: 1
2
2
  tool:
3
- version: '0.8.1'
3
+ version: '0.10.0'
4
4
  nfpm: '2.47.0'
5
5
  nfpm:
6
6
  name: example-app
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "digest"
4
+
3
5
  module NativePackages
4
6
  # Builds a type 2 AppImage from the same contents nFPM packages: the files
5
7
  # are laid out in an AppDir, packed with mksquashfs and appended to a pinned
@@ -17,6 +19,13 @@ module NativePackages
17
19
  "arm7" => ["armhf", "e9060d37577b8a29914ec12d8740add24e19ff29012fb1fa0f60daf62db0688d"]
18
20
  }.freeze
19
21
  ICON_DIRECTORIES = %w[usr/share/icons usr/share/pixmaps].freeze
22
+ # The runtime's ELF section that AppImageUpdate and the other updaters
23
+ # read: a NUL-terminated string in a fixed 1024 bytes.
24
+ UPDATE_SECTION = ".upd_info"
25
+ # AppImage's update information formats, without the `bintray-zsync`
26
+ # and `pling-v1-zsync` services that no longer exist.
27
+ GITHUB_UPDATE = /\Agh-releases-zsync\|([A-Za-z0-9_.-]+)\|([A-Za-z0-9_.-]+)\|([^|\s]+)\|([^|\s]+\.zsync)\z/
28
+ URL_UPDATE = %r{\Azsync\|https://[^|\s]+\.zsync\z}
20
29
  attr_reader :root
21
30
 
22
31
  def initialize(root)
@@ -29,10 +38,81 @@ module NativePackages
29
38
 
30
39
  def self.filename(name, version, target) = "#{name}-#{version}-#{architecture(target)}.AppImage"
31
40
 
32
- def doctor(target)
41
+ # The target's update information, checked against the AppImage it
42
+ # describes: a GitHub string must name the release repository, and its
43
+ # file pattern must match this target's `.zsync` file, so a release
44
+ # never ships an AppImage whose updaters look for a file that is not
45
+ # there. Returns nil when the target has none.
46
+ def self.update_information(target, name:, version:, repository:)
47
+ settings = target["appimage"]
48
+ return nil if settings.nil?
49
+ raise Error, "appimage: expected a mapping" unless settings.is_a?(Hash)
50
+ unknown = settings.keys - %w[update_information]
51
+ raise Error, "appimage: unknown fields #{unknown.join(', ')}" unless unknown.empty?
52
+ value = settings["update_information"]
53
+ raise Error, "appimage.update_information: expected a string" unless value.is_a?(String) && !value.empty?
54
+ raise Error, "appimage.update_information: at most 1023 bytes" if value.bytesize > 1023
55
+ if (match = GITHUB_UPDATE.match(value))
56
+ owner, project, _tag, pattern = match.captures
57
+ unless "#{owner}/#{project}".casecmp?(repository.to_s)
58
+ raise Error, "appimage.update_information names #{owner}/#{project}, but release.repository is #{repository.inspect}"
59
+ end
60
+ zsync = "#{filename(name, version, target)}.zsync"
61
+ unless File.fnmatch?(pattern, zsync, File::FNM_PATHNAME)
62
+ raise Error, "appimage.update_information: #{pattern} does not match #{zsync}"
63
+ end
64
+ elsif !URL_UPDATE.match?(value)
65
+ raise Error, "appimage.update_information: use gh-releases-zsync|OWNER|REPOSITORY|TAG|FILE.zsync or zsync|https://URL.zsync"
66
+ end
67
+ value
68
+ end
69
+
70
+ # The file offset and size of the ELF section `name` in `bytes`, or nil.
71
+ # Every AppImage runtime is a little-endian ELF file, 32 or 64 bits.
72
+ def self.section(bytes, name)
73
+ raise Error, "AppImage runtime is not a little-endian ELF file" unless bytes.start_with?("\x7fELF".b) && bytes.getbyte(5) == 1
74
+ wide = { 1 => false, 2 => true }.fetch(bytes.getbyte(4)) { raise Error, "AppImage runtime has an unknown ELF class" }
75
+ offset = wide ? bytes.byteslice(0x28, 8).unpack1("Q<") : bytes.byteslice(0x20, 4).unpack1("V")
76
+ size, count, names = bytes.byteslice(wide ? 0x3A : 0x2E, 6).unpack("v3")
77
+ header = lambda do |index|
78
+ entry = bytes.byteslice(offset + (index * size), size)
79
+ raise Error, "AppImage runtime has a truncated section table" unless entry && entry.bytesize == size
80
+ wide ? [entry.unpack1("V"), *entry.byteslice(0x18, 16).unpack("Q<2")] : [entry.unpack1("V"), *entry.byteslice(0x10, 8).unpack("V2")]
81
+ end
82
+ _, strings, = header.call(names)
83
+ count.times do |index|
84
+ label, start, length = header.call(index)
85
+ found = bytes.byteslice(strings + label, name.bytesize + 1)
86
+ return [start, length] if found == "#{name}\0".b
87
+ end
88
+ nil
89
+ end
90
+
91
+ # `runtime` with `information` in its update section.
92
+ def self.embed(runtime, information)
93
+ start, length = section(runtime, UPDATE_SECTION) || raise(Error, "AppImage runtime has no #{UPDATE_SECTION} section")
94
+ raise Error, "update information exceeds the runtime's #{length}-byte #{UPDATE_SECTION} section" if information.bytesize >= length
95
+ raise Error, "AppImage runtime's #{UPDATE_SECTION} section is outside the file" if start + length > runtime.bytesize
96
+ runtime = runtime.b
97
+ runtime[start, length] = information.b + ("\0".b * (length - information.bytesize))
98
+ runtime
99
+ end
100
+
101
+ # The update information an AppImage carries, or nil when it has none.
102
+ def self.read_update_information(path)
103
+ bytes = File.binread(path)
104
+ start, length = section(bytes, UPDATE_SECTION)
105
+ return nil unless start
106
+ value = bytes.byteslice(start, length)[/\A[^\0]*/n]
107
+ value.empty? ? nil : value
108
+ end
109
+
110
+ def doctor(target, update: false)
33
111
  self.class.architecture(target)
34
- missing = %w[mksquashfs curl].reject { |tool| available?(tool) }
35
- raise Error, "install required AppImage tools: #{missing.join(', ')} (mksquashfs is in squashfs-tools)" unless missing.empty?
112
+ tools = %w[mksquashfs curl]
113
+ tools << "zsyncmake" if update
114
+ missing = tools.reject { |tool| available?(tool) }
115
+ raise Error, "install required AppImage tools: #{missing.join(', ')} (mksquashfs is in squashfs-tools, zsyncmake in zsync)" unless missing.empty?
36
116
  end
37
117
 
38
118
  def runtime(target)
@@ -46,9 +126,11 @@ module NativePackages
46
126
  path
47
127
  end
48
128
 
49
- # Returns the AppImage's path and what was put at the AppDir's root.
50
- def build(package, target, destination, name:, version:, epoch:)
51
- doctor(target)
129
+ # Returns the AppImage's path, what was put at the AppDir's root, and
130
+ # the `.zsync` file beside it when the AppImage carries update
131
+ # information (nil otherwise).
132
+ def build(package, target, destination, name:, version:, epoch:, update: nil)
133
+ doctor(target, update: !update.nil?)
52
134
  output = destination / self.class.filename(name, version, target)
53
135
  Dir.mktmpdir("native-packages-appimage-") do |directory|
54
136
  appdir = Pathname.new(directory) / "AppDir"
@@ -59,14 +141,43 @@ module NativePackages
59
141
  image = Pathname.new(directory) / "filesystem.squashfs"
60
142
  run "mksquashfs", appdir, image, "-root-owned", "-noappend", "-no-xattrs", "-no-progress", "-quiet", "-comp", "zstd",
61
143
  env: { "SOURCE_DATE_EPOCH" => epoch.to_s }
144
+ bytes = runtime(target).binread
145
+ bytes = self.class.embed(bytes, update) if update
62
146
  File.open(output, "wb") do |file|
63
- file.write(runtime(target).binread)
147
+ file.write(bytes)
64
148
  IO.copy_stream(image.to_s, file)
65
149
  end
66
150
  output.chmod(0o755)
67
151
  check(output)
68
- [output, entry.merge("runtime" => RUNTIME, "libraries" => "host")]
152
+ details = entry.merge("runtime" => RUNTIME, "libraries" => "host")
153
+ return [output, details, nil] unless update
154
+ raise Error, "AppImage lost its update information: #{output}" unless self.class.read_update_information(output) == update
155
+ [output, details.merge("update_information" => update), zsync(output, epoch)]
156
+ end
157
+ end
158
+
159
+ # Writes `<AppImage>.zsync` beside the AppImage, for updaters that fetch
160
+ # only the blocks that changed. Its URL is the AppImage's file name,
161
+ # relative to the `.zsync` file, so it works from wherever both are
162
+ # published. The AppImage's time is the build's, which zsyncmake records.
163
+ def zsync(path, epoch)
164
+ File.utime(epoch, epoch, path)
165
+ output = Pathname.new("#{path}.zsync")
166
+ run "zsyncmake", "-u", path.basename.to_s, "-o", output, path, chdir: path.dirname
167
+ check_zsync(output, path)
168
+ output
169
+ end
170
+
171
+ # Fails unless `zsync` describes the AppImage at `path` as it is now.
172
+ def check_zsync(zsync, path)
173
+ header = zsync.binread.split("\n\n".b, 2).first.to_s.force_encoding(Encoding::UTF_8).lines.to_h do |line|
174
+ key, value = line.chomp.split(": ", 2)
175
+ [key, value]
69
176
  end
177
+ expected = { "Filename" => path.basename.to_s, "URL" => path.basename.to_s, "Length" => path.size.to_s,
178
+ "SHA-1" => Digest::SHA1.file(path).hexdigest }
179
+ wrong = expected.reject { |key, value| header[key] == value }.keys
180
+ raise Error, "#{zsync.basename} does not describe #{path.basename}: #{wrong.join(', ')}" unless wrong.empty?
70
181
  end
71
182
 
72
183
  def check(path)
@@ -33,7 +33,9 @@ module NativePackages
33
33
  configuration.validate
34
34
  selected = configuration.select(ids: ids, formats: formats)
35
35
  nfpm_version if selected.values.any? { |target| !(target.fetch("formats") - Configuration::NATIVE_FORMATS - ["appimage"]).empty? }
36
- selected.each_value { |target| AppImage.new(root).doctor(target) if target.fetch("formats").include?("appimage") }
36
+ selected.each_value do |target|
37
+ AppImage.new(root).doctor(target, update: !target["appimage"].nil?) if target.fetch("formats").include?("appimage")
38
+ end
37
39
  selected.each do |id, target|
38
40
  next unless target["native"]
39
41
  metadata = configuration.target_tokens(configuration.tokens("9.8.7"), id, target, "/payload").merge("PACKAGE" => "/output/package", "FORMAT" => target.fetch("formats").first)
@@ -160,11 +162,12 @@ module NativePackages
160
162
  elsif format == "appimage"
161
163
  package = render_tree(configuration.package(target), configuration.target_tokens(info, id, target, payload))
162
164
  inspection = Inspection.new(root, configuration.data.fetch("libraries")).check(package, target, format)
163
- package_path, details = AppImage.new(root).build(package, target, destination, name: configuration.name, version: number,
164
- epoch: info.fetch("SOURCE_DATE_EPOCH"))
165
+ package_path, details, zsync = AppImage.new(root).build(package, target, destination, name: configuration.name, version: number,
166
+ epoch: info.fetch("SOURCE_DATE_EPOCH"), update: configuration.update_information(target, number))
165
167
  inspection["appimage"] = details
166
- packages = files(destination)
167
- raise Error, "AppImage build must create only its package" unless packages == [package_path]
168
+ outputs = [package_path, zsync].compact.sort
169
+ raise Error, "AppImage build must create only its package and .zsync file" unless files(destination) == outputs
170
+ packages = [package_path]
168
171
  else
169
172
  package = render_tree(configuration.package(target), configuration.target_tokens(info, id, target, payload))
170
173
  if number.include?("-") && (package.fetch("version_schema", "semver") != "semver" || %w[prerelease version_metadata].any? { |key| package.key?(key) && !package[key].to_s.empty? })
@@ -180,19 +183,23 @@ module NativePackages
180
183
  packages = files(destination)
181
184
  raise Error, "nFPM did not create exactly one #{format} package" unless packages.length == 1
182
185
  end
186
+ outputs ||= packages
183
187
  if target["after_package"]
184
188
  hook_tokens = configuration.target_tokens(info, id, target, payload).merge("PACKAGE" => packages.first.to_s, "FORMAT" => format)
185
189
  run(*render_tree(target.fetch("after_package"), hook_tokens), env: { "NATIVE_PACKAGES_TARGET" => id, "NATIVE_PACKAGES_VERSION" => number })
186
- raise Error, "after_package must preserve the package path and output set" unless files(destination) == packages
190
+ raise Error, "after_package must preserve the package path and output set" unless files(destination) == outputs
187
191
  AppImage.new(root).check(packages.first) if format == "appimage"
192
+ AppImage.new(root).check_zsync(zsync, packages.first) if format == "appimage" && zsync
188
193
  end
189
194
  if native
190
195
  native.check_output(packages.first, format)
191
196
  raise Error, "native recipes/signing must not change their input payload" unless native.digest(payload) == payload_digest
192
197
  end
193
198
  inspection["apple"] = signing.notarize(packages.first) if signing
194
- records << { "target" => id, "format" => format, "path" => packages.first.relative_path_from(staging).to_s,
199
+ record = { "target" => id, "format" => format, "path" => packages.first.relative_path_from(staging).to_s,
195
200
  "sha256" => sha256(packages.first), "validation" => inspection }
201
+ record["zsync"] = zsync.relative_path_from(staging).to_s if format == "appimage" && zsync
202
+ records << record
196
203
  end
197
204
  end
198
205
  end
@@ -381,7 +388,7 @@ module NativePackages
381
388
  end
382
389
  destinations.each do |destination|
383
390
  if destination == "github"
384
- paths = manifest.fetch("packages").map { |entry| output / entry.fetch("path") }
391
+ paths = manifest.fetch("packages").flat_map { |entry| [entry.fetch("path"), entry["zsync"]].compact.map { |path| output / path } }
385
392
  paths += manifest.fetch("files").keys.grep(/-packaging\.tar\.xz\z/).map { |path| output / path }
386
393
  names = paths.map { |path| path.basename.to_s }
387
394
  raise Error, "duplicate release asset names; give variants distinct nFPM names/releases" unless names.uniq == names
@@ -6,7 +6,7 @@ require_relative "appimage"
6
6
  module NativePackages
7
7
  class Configuration
8
8
  include Support
9
- NATIVE_FORMATS = %w[dmg inno].freeze
9
+ NATIVE_FORMATS = %w[dmg inno msi].freeze
10
10
  FORMATS = (%w[deb rpm archlinux apk ipk msix srpm appimage] + NATIVE_FORMATS).freeze
11
11
  NFPM_VERSION = "2.47.0"
12
12
  NAMES = %w[native-packages.yaml native-packages.yml].freeze
@@ -60,8 +60,8 @@ module NativePackages
60
60
  def check_prerelease(version, selected, defer_recipes: false)
61
61
  return unless version.include?("-")
62
62
  raise Error, "prerelease builds require release.prereleases: true" unless data.fetch("release")["prereleases"] == true
63
- unless selected.values.all? { |target| (target.fetch("formats") - %w[deb rpm appimage dmg inno]).empty? } && (defer_recipes || data.fetch("templates").empty?)
64
- raise Error, "prereleases support deb, rpm, appimage, dmg and inno only, without downstream recipes"
63
+ unless selected.values.all? { |target| (target.fetch("formats") - %w[deb rpm appimage dmg inno msi]).empty? } && (defer_recipes || data.fetch("templates").empty?)
64
+ raise Error, "prereleases support deb, rpm, appimage, dmg, inno and msi only, without downstream recipes"
65
65
  end
66
66
  end
67
67
 
@@ -86,7 +86,7 @@ module NativePackages
86
86
  metadata = tokens("9.8.7", epoch: 1_767_225_600)
87
87
  targets.each do |id, target|
88
88
  raise Error, "invalid target name: #{id}" unless /\A[a-z0-9][a-z0-9-]*\z/.match?(id)
89
- unknown = target.keys - %w[platform arch libc abi kind formats input nfpm before_build after_package compiler_target native]
89
+ unknown = target.keys - %w[platform arch libc abi kind formats input nfpm before_build after_package compiler_target native appimage]
90
90
  raise Error, "targets.#{id}: unknown fields #{unknown.join(', ')}" unless unknown.empty?
91
91
  formats = target.fetch("formats")
92
92
  unless formats.is_a?(Array) && !formats.empty? && formats.uniq == formats && (formats - FORMATS).empty?
@@ -97,11 +97,11 @@ module NativePackages
97
97
  kind = target.fetch("kind", "binary")
98
98
  raise Error, "targets.#{id}.kind: use binary, data or source" unless %w[binary data source].include?(kind)
99
99
  raise Error, "#{id}: MSIX requires a Windows target with only msix format" if formats.include?("msix") && (target["platform"] != "windows" || formats != ["msix"])
100
- raise Error, "#{id}: Windows targets require msix or inno" if target["platform"] == "windows" && ![["msix"], ["inno"]].include?(formats)
100
+ raise Error, "#{id}: Windows targets require msix, inno or msi" if target["platform"] == "windows" && ![["msix"], ["inno"], ["msi"]].include?(formats)
101
101
  raise Error, "#{id}: macOS targets require dmg" if target["platform"] == "macos" && formats != ["dmg"]
102
102
  native = !(formats & NATIVE_FORMATS).empty?
103
103
  if native
104
- expected = { "dmg" => "macos", "inno" => "windows" }[formats.first]
104
+ expected = { "dmg" => "macos", "inno" => "windows", "msi" => "windows" }[formats.first]
105
105
  raise Error, "#{id}: native formats require a separate binary target on their native platform" unless formats.length == 1 && expected == target["platform"] && kind == "binary"
106
106
  recipe = mapping(target.fetch("native"), "#{id}.native")
107
107
  raise Error, "#{id}.native: expected command and output" unless recipe.keys.sort == %w[command output]
@@ -110,13 +110,13 @@ module NativePackages
110
110
  raise Error, "#{id}.native.command: use command arguments including @PACKAGE@"
111
111
  end
112
112
  filename = render(recipe.fetch("output"), target_tokens(metadata, id, target, "/payload"))
113
- extension = formats == ["dmg"] ? ".dmg" : ".exe"
113
+ extension = { "dmg" => ".dmg", "inno" => ".exe", "msi" => ".msi" }.fetch(formats.first)
114
114
  unless /\A[a-zA-Z0-9][a-zA-Z0-9._+-]*\z/.match?(filename) && filename.end_with?(extension)
115
115
  raise Error, "#{id}.native.output: expected a safe #{extension} filename"
116
116
  end
117
117
  render_tree(command, target_tokens(metadata, id, target, "/payload").merge("PACKAGE" => "/output/#{filename}", "FORMAT" => formats.first))
118
118
  elsif target.key?("native")
119
- raise Error, "#{id}: native commands require a dmg or inno target"
119
+ raise Error, "#{id}: native commands require a dmg, inno or msi target"
120
120
  end
121
121
  raise Error, "#{id}: SRPM requires a separate source target" if (formats.include?("srpm") && (kind != "source" || formats != ["srpm"])) || (kind == "source" && formats != ["srpm"])
122
122
  if kind == "binary" && target["platform"] == "linux"
@@ -125,6 +125,13 @@ module NativePackages
125
125
  if formats.include?("appimage")
126
126
  raise Error, "#{id}: AppImage requires a Linux binary target" unless target["platform"] == "linux" && kind == "binary"
127
127
  AppImage.architecture(target)
128
+ elsif target.key?("appimage")
129
+ raise Error, "#{id}.appimage: only for targets that build an AppImage"
130
+ end
131
+ begin
132
+ update_information(target, "9.8.7")
133
+ rescue Error => error
134
+ raise Error, "#{id}.#{error.message}"
128
135
  end
129
136
  raise Error, "#{id}: IPK needs an explicit abi (device/distribution baseline)" if formats.include?("ipk") && target.fetch("abi", "").empty?
130
137
  input = mapping(target.fetch("input"), "#{id}.input")
@@ -162,6 +169,12 @@ module NativePackages
162
169
  true
163
170
  end
164
171
 
172
+ # The AppImage update information for `target`, checked against the
173
+ # AppImage `version` builds, or nil.
174
+ def update_information(target, version)
175
+ AppImage.update_information(target, name: name, version: version, repository: data.fetch("release")["repository"])
176
+ end
177
+
165
178
  def tokens(version, epoch: nil)
166
179
  epoch ||= Integer(ENV.fetch("SOURCE_DATE_EPOCH", Time.now.to_i.to_s))
167
180
  result = { "NAME" => name, "VERSION" => version, "TAG" => "v#{version}", "DATE" => Time.at(epoch).utc.iso8601,
@@ -76,6 +76,9 @@ module NativePackages
76
76
  next false if file.size < 512
77
77
  file.seek(-512, IO::SEEK_END)
78
78
  file.read(4) == "koly"
79
+ elsif format == "msi"
80
+ # Windows Installer databases are OLE compound files.
81
+ file.size >= 512 && file.read(8) == "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1".b
79
82
  else
80
83
  next false unless file.size >= 64 && file.read(2) == "MZ"
81
84
  file.seek(0x3c)
@@ -12,7 +12,7 @@ require "tmpdir"
12
12
  require "yaml"
13
13
 
14
14
  module NativePackages
15
- VERSION = "0.8.1"
15
+ VERSION = "0.10.0"
16
16
  class Error < StandardError; end
17
17
 
18
18
  module Support
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.8.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino