From 64442bb0833025ce94b38059e6a61cc7b296bd96 Mon Sep 17 00:00:00 2001 From: amr-crabnebula Date: Fri, 1 Dec 2023 17:37:46 +0200 Subject: [PATCH 1/5] docs: update READMEs and docs --- README.md | 99 +-- bindings/packager/nodejs/README.md | 65 ++ bindings/packager/nodejs/schema.json | 1087 +++++++++++++++++++++++ crates/config-schema-generator/build.rs | 9 +- crates/packager/README.md | 82 +- crates/packager/src/lib.rs | 70 +- crates/updater/README.md | 2 +- 7 files changed, 1273 insertions(+), 141 deletions(-) create mode 100644 bindings/packager/nodejs/README.md create mode 100644 bindings/packager/nodejs/schema.json diff --git a/README.md b/README.md index e047c9bb..c7c95466 100644 --- a/README.md +++ b/README.md @@ -2,33 +2,10 @@ cargo-packager splash -Rust executable packager, bundler and updater. A tool and library to generate installers or app bundles for your executables. +Executable packager, bundler and updater. A cli tool and library to generate installers or app bundles for your executables. It also has a compatible updater through [cargo-packager-updater](./crates/updater/). -## CLI - -### Installation - -```sh -cargo install cargo-packager --locked -``` - -### Usage - -1. Add `Packager.toml` or `packager.json` in your project or modify Cargo.toml and include - - ```toml - [package.metadata.packager] - before-packaging-command = "cargo build --release" - ``` - -2. Run the CLI - - ```sh - cargo packager --release - ``` - -### Supported packages +#### Supported packages: - macOS - DMG (.dmg) @@ -40,30 +17,65 @@ cargo install cargo-packager --locked - NSIS (.exe) - MSI using WiX Toolset (.msi) +## Rust + +### CLI + +The packager is distrubuted on crates.io as a cargo subcommand, you can install it using cargo: + +```sh +cargo install cargo-packager --locked +``` + +You then need to configure your app so the cli can recognize it. Configuration can be done in `Packager.toml` or `packager.json` in your project or modify Cargo.toml and include this snippet: + +```toml +[package.metadata.packager] +before-packaging-command = "cargo build --release" +``` + +Once, you are done configuring your app, run: + +```sh +cargo packager --release +``` + ### Configuration -By default, `cargo-packager` reads a configuration from `Packager.toml` or `packager.json` if it exists, and from `package.metadata.packager` table in `Cargo.toml`. -You can also specify a custom configuration file using the `-c/--config` cli argument. -All configuration options could be either a single config or array of configs. +By default, the packager reads its configuration from `Packager.toml` or `packager.json` if it exists, and from `package.metadata.packager` table in `Cargo.toml`. +You can also specify a custom configuration using the `-c/--config` cli argument. -For a full list of configuration options, see https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html +For a full list of configuration options, see https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html. -You could also use the schema from GitHub releases to validate your configuration or have auto completions turned on in your IDE. +You could also use the [schema](./crates/packager/schema.json) file from GitHub to validate your configuration or have auto completions in your IDE. ### Building your application before packaging -By default, `cargo-packager` doesn't build your application, it only looks for it inside the directory specified in `config.out_dir` or `--out-dir` cli arg, -However, `cargo-packager` has an option to specify a shell command to be executed before packaing your app, `beforePackagingCommand`. +By default, the packager doesn't build your application, so if your app requires a compilation step, the packager has an option to specify a shell command to be executed before packaing your app, `beforePackagingCommand`. ### Cargo profiles -By default, `cargo-packager` looks for binaries built using the `debug` profile, if your `beforePackagingCommand` builds your app using `cargo build --release`, you will also need to -run `cargo-packager` in release mode `cargo packager --release`, otherwise, if you have a custom cargo profile, you will need to specify it using `--profile` cli arg `cargo packager --profile custom-release-profile`. +By default, the packager looks for binaries built using the `debug` profile, if your `beforePackagingCommand` builds your app using `cargo build --release`, you will also need to +run the packager in release mode `cargo packager --release`, otherwise, if you have a custom cargo profile, you will need to specify it using `--profile` cli arg `cargo packager --profile custom-release-profile`. + +### Library + +This crate is also published to crates.io as a library that you can integrate into your tooling, just make sure to disable the default-feature flags. + +```sh +cargo add cargo-packager --no-default-features +``` + +#### Feature flags + +- **`cli`**: Enables the cli specifc features and dependencies. Enabled by default. +- **`tracing`**: Enables `tracing` crate integration. + +## NPM (Node.js) -For more information, checkout the available [configuration options](https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html) and for a list of available CLI -commands and arguments, run `cargo packager --help`. +Checkout the packager NPM cli [README](./bindings/packager/nodejs/README.md) -### Examples +## Examples The [`examples`](./examples/) directory contains a number of varying examples, if you want to build them all run `cargo r -p cargo-packager -- --release` in the root of this repository. Just make sure to have the tooling for each example installed on your system. You can find what tooling they require by checking the README in each example. The README also contains a command to build this example alone if you wish. @@ -77,19 +89,6 @@ Examples list (non-exhaustive): - [`slint`](./examples/slint/) - [`wails`](./examples/wails) -## Library - -This crate is also published to crates.io as a library that you can integrate into your tooling, just make sure to disable the default-feature flags. - -```sh -cargo add cargo-packager --no-default-features -``` - -#### Feature flags - -- **`cli`**: Enables the CLI specifc features and dependencies. Enabled by default. -- **`tracing`**: Enables `tracing` crate integration. - ## Licenses MIT or MIT/Apache 2.0 where applicable. diff --git a/bindings/packager/nodejs/README.md b/bindings/packager/nodejs/README.md new file mode 100644 index 00000000..341c0d03 --- /dev/null +++ b/bindings/packager/nodejs/README.md @@ -0,0 +1,65 @@ +# @crabnebula/packager + +Executable packager, bundler and updater. A cli tool and library to generate installers or app bundles for your executables. +It also has a compatible updater through [@crabnebula/updater](https://www.npmjs.com/package/@crabnebula/updater). + +#### Supported packages: + +- macOS + - DMG (.dmg) + - Bundle (.app) +- Linux + - Debian package (.deb) + - AppImage (.AppImage) +- Windows + - NSIS (.exe) + - MSI using WiX Toolset (.msi) + +## Rust + +### CLI + +The packager is distrubuted on NPM as a CLI, you can install it: + +```sh +# pnpm +pnpm add -D @crabnebula/packager +# pnpm +yarn add -D @crabnebula/packager +# npm +npm i -D @crabnebula/packager +``` + +You then need to configure your app so the CLI can recognize it. +Configuration can be done in `Packager.toml` or `packager.json` in your project or `packager` key in `packager.json` +Once, you are done configuring your app, run: + +```sh +# pnpm +pnpm packager +# pnpm +yarn packager +# npm +npx packager +``` + +### Configuration + +By default, the packager reads its configuration from `Packager.toml` or `packager.json` if it exists, and from `packager.json` keyin `packager.json`, +You can also specify a custom configuration using the `-c/--config` cli argument. + +For a full list of configuration options, see https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html. + +You could also use the [schema](./schema.json) file from GitHub to validate your configuration or have auto completions in your IDE. + +### Building your application before packaging + +By default, the packager doesn't build your application, so if your app requires a compilation step, the packager has an option to specify a shell command to be executed before packaing your app, `beforePackagingCommand`. + +### Library + +The packager is also a library that you can import and integrate into your tooling. + +## Licenses + +MIT or MIT/Apache 2.0 where applicable. diff --git a/bindings/packager/nodejs/schema.json b/bindings/packager/nodejs/schema.json new file mode 100644 index 00000000..aa83e3ca --- /dev/null +++ b/bindings/packager/nodejs/schema.json @@ -0,0 +1,1087 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config", + "description": "The packaging config.", + "type": "object", + "properties": { + "$schema": { + "description": "The JSON schema for the config.\n\nSetting this field has no effect, this just exists so we can parse the JSON correctly when it has `$schema` field set.", + "type": [ + "string", + "null" + ] + }, + "name": { + "description": "The app name, this is just an identifier that could be used to filter which app to package using `--packages` cli arg when there is multiple apps in the workspace or in the same config.\n\nThis field resembles, the `name` field in `Cargo.toml` or `package.json`\n\nIf `unset`, the CLI will try to auto-detect it from `Cargo.toml` or `package.json` otherwise, it will keep it unset.", + "type": [ + "string", + "null" + ] + }, + "enabled": { + "description": "Whether this config is enabled or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "productName": { + "description": "The package's product name, for example \"My Awesome App\".", + "default": "", + "type": "string" + }, + "version": { + "description": "The package's version.", + "default": "", + "type": "string" + }, + "binaries": { + "description": "The binaries to package.", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Binary" + } + }, + "identifier": { + "description": "The application identifier in reverse domain name notation (e.g. `com.packager.example`). This string must be unique across applications since it is used in some system configurations. This string must contain only alphanumeric characters (A–Z, a–z, and 0–9), hyphens (-), and periods (.).", + "type": [ + "string", + "null" + ], + "pattern": "^[a-zA-Z0-9-\\.]*$" + }, + "beforePackagingCommand": { + "description": "The command to run before starting to package an application.\n\nThis runs only once.", + "anyOf": [ + { + "$ref": "#/definitions/HookCommand" + }, + { + "type": "null" + } + ] + }, + "beforeEachPackageCommand": { + "description": "The command to run before packaging each format for an application.\n\nThis will run multiple times depending on the formats specifed.", + "anyOf": [ + { + "$ref": "#/definitions/HookCommand" + }, + { + "type": "null" + } + ] + }, + "logLevel": { + "description": "The logging level.", + "anyOf": [ + { + "$ref": "#/definitions/LogLevel" + }, + { + "type": "null" + } + ] + }, + "formats": { + "description": "The packaging formats to create, if not present, [`PackageFormat::platform_default`] is used.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/PackageFormat" + } + }, + "outDir": { + "description": "The directory where the [`Config::binaries`] exist and where the generated packages will be placed.", + "default": "", + "type": "string" + }, + "targetTriple": { + "description": "The target triple we are packaging for. This mainly affects [`Config::external_binaries`].\n\nDefaults to the current OS target triple.", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "The package's description.", + "type": [ + "string", + "null" + ] + }, + "longDescription": { + "description": "The app's long description.", + "type": [ + "string", + "null" + ] + }, + "homepage": { + "description": "The package's homepage.", + "type": [ + "string", + "null" + ] + }, + "authors": { + "description": "The package's authors.", + "default": null, + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "publisher": { + "description": "The app's publisher. Defaults to the second element in [`Config::identifier`](Config::identifier) string. Currently maps to the Manufacturer property of the Windows Installer.", + "type": [ + "string", + "null" + ] + }, + "licenseFile": { + "description": "A path to the license file.", + "type": [ + "string", + "null" + ] + }, + "copyright": { + "description": "The app's copyright.", + "type": [ + "string", + "null" + ] + }, + "category": { + "description": "The app's category.", + "anyOf": [ + { + "$ref": "#/definitions/AppCategory" + }, + { + "type": "null" + } + ] + }, + "icons": { + "description": "The app's icon list.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "fileAssociations": { + "description": "The file associations", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/FileAssociation" + } + }, + "resources": { + "description": "The app's resources to package. This a list of either a glob pattern, path to a file, path to a directory or an object of `src` and `target` paths. In the case of using an object, the `src` could be either a glob pattern, path to a file, path to a directory, and the `target` is a path inside the final resources folder in the installed package.\n\n## Format-specific:\n\n- **[PackageFormat::Nsis] / [PackageFormat::Wix]**: The resources are placed next to the executable in the root of the packager. - **[PackageFormat::Deb]**: The resources are placed in `usr/lib` of the package.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Resource" + } + }, + "externalBinaries": { + "description": "Paths to external binaries to add to the package.\n\nThe path specified should not include `-<.exe>` suffix, it will be auto-added when by the packager when reading these paths, so the actual binary name should have the target platform's target triple appended, as well as `.exe` for Windows.\n\nFor example, if you're packaging an external binary called `sqlite3`, the packager expects a binary named `sqlite3-x86_64-unknown-linux-gnu` on linux, and `sqlite3-x86_64-pc-windows-gnu.exe` on windows.\n\nIf you are building a universal binary for MacOS, the packager expects your external binary to also be universal, and named after the target triple, e.g. `sqlite3-universal-apple-darwin`. See ", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "windows": { + "description": "Windows-specific configuration.", + "anyOf": [ + { + "$ref": "#/definitions/WindowsConfig" + }, + { + "type": "null" + } + ] + }, + "macos": { + "description": "MacOS-specific configuration.", + "anyOf": [ + { + "$ref": "#/definitions/MacOsConfig" + }, + { + "type": "null" + } + ] + }, + "deb": { + "description": "Debian-specific configuration.", + "anyOf": [ + { + "$ref": "#/definitions/DebianConfig" + }, + { + "type": "null" + } + ] + }, + "appimage": { + "description": "AppImage configuration.", + "anyOf": [ + { + "$ref": "#/definitions/AppImageConfig" + }, + { + "type": "null" + } + ] + }, + "wix": { + "description": "WiX configuration.", + "anyOf": [ + { + "$ref": "#/definitions/WixConfig" + }, + { + "type": "null" + } + ] + }, + "nsis": { + "description": "Nsis configuration.", + "anyOf": [ + { + "$ref": "#/definitions/NsisConfig" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false, + "definitions": { + "Binary": { + "description": "A binary to package within the final package.", + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "description": "Path to the binary (without `.exe` on Windows). If it's relative, it will be resolved from [`Config::out_dir`].", + "type": "string" + }, + "main": { + "description": "Whether this is the main binary or not", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "HookCommand": { + "description": "Describes a shell command to be executed when a CLI hook is triggered.", + "anyOf": [ + { + "description": "Run the given script with the default options.", + "type": "string" + }, + { + "description": "Run the given script with custom options.", + "type": "object", + "required": [ + "script" + ], + "properties": { + "script": { + "description": "The script to execute.", + "type": "string" + }, + "dir": { + "description": "The working directory.", + "type": [ + "string", + "null" + ] + } + } + } + ] + }, + "LogLevel": { + "description": "An enum representing the available verbosity levels of the logger.", + "oneOf": [ + { + "description": "The \"error\" level.\n\nDesignates very serious errors.", + "type": "string", + "enum": [ + "error" + ] + }, + { + "description": "The \"warn\" level.\n\nDesignates hazardous situations.", + "type": "string", + "enum": [ + "warn" + ] + }, + { + "description": "The \"info\" level.\n\nDesignates useful information.", + "type": "string", + "enum": [ + "info" + ] + }, + { + "description": "The \"debug\" level.\n\nDesignates lower priority information.", + "type": "string", + "enum": [ + "debug" + ] + }, + { + "description": "The \"trace\" level.\n\nDesignates very low priority, often extremely verbose, information.", + "type": "string", + "enum": [ + "trace" + ] + } + ] + }, + "PackageFormat": { + "description": "The type of the package we're packaging.", + "oneOf": [ + { + "description": "All available package formats for the current platform.\n\nSee [`PackageFormat::platform_all`]", + "type": "string", + "enum": [ + "all" + ] + }, + { + "description": "The default list of package formats for the current platform.\n\nSee [`PackageFormat::platform_default`]", + "type": "string", + "enum": [ + "default" + ] + }, + { + "description": "The macOS application bundle (.app).", + "type": "string", + "enum": [ + "app" + ] + }, + { + "description": "The macOS DMG package (.dmg).", + "type": "string", + "enum": [ + "dmg" + ] + }, + { + "description": "The Microsoft Software Installer (.msi) through WiX Toolset.", + "type": "string", + "enum": [ + "wix" + ] + }, + { + "description": "The NSIS installer (.exe).", + "type": "string", + "enum": [ + "nsis" + ] + }, + { + "description": "The Linux Debian package (.deb).", + "type": "string", + "enum": [ + "deb" + ] + }, + { + "description": "The Linux AppImage package (.AppImage).", + "type": "string", + "enum": [ + "appimage" + ] + } + ] + }, + "AppCategory": { + "description": "The possible app categories. Corresponds to `LSApplicationCategoryType` on macOS and the GNOME desktop categories on Debian.", + "type": "string", + "enum": [ + "Business", + "DeveloperTool", + "Education", + "Entertainment", + "Finance", + "Game", + "ActionGame", + "AdventureGame", + "ArcadeGame", + "BoardGame", + "CardGame", + "CasinoGame", + "DiceGame", + "EducationalGame", + "FamilyGame", + "KidsGame", + "MusicGame", + "PuzzleGame", + "RacingGame", + "RolePlayingGame", + "SimulationGame", + "SportsGame", + "StrategyGame", + "TriviaGame", + "WordGame", + "GraphicsAndDesign", + "HealthcareAndFitness", + "Lifestyle", + "Medical", + "Music", + "News", + "Photography", + "Productivity", + "Reference", + "SocialNetworking", + "Sports", + "Travel", + "Utility", + "Video", + "Weather" + ] + }, + "FileAssociation": { + "description": "A file association configuration.", + "type": "object", + "required": [ + "extensions" + ], + "properties": { + "extensions": { + "description": "File extensions to associate with this app. e.g. 'png'", + "type": "array", + "items": { + "type": "string" + } + }, + "mimeType": { + "description": "The mime-type e.g. 'image/png' or 'text/plain'. **Linux-only**.", + "type": [ + "string", + "null" + ] + }, + "description": { + "description": "The association description. **Windows-only**. It is displayed on the `Type` column on Windows Explorer.", + "type": [ + "string", + "null" + ] + }, + "name": { + "description": "The name. Maps to `CFBundleTypeName` on macOS. Defaults to the first item in `ext`", + "type": [ + "string", + "null" + ] + }, + "role": { + "description": "The app’s role with respect to the type. Maps to `CFBundleTypeRole` on macOS. Defaults to [`BundleTypeRole::Editor`]", + "default": "editor", + "allOf": [ + { + "$ref": "#/definitions/BundleTypeRole" + } + ] + } + }, + "additionalProperties": false + }, + "BundleTypeRole": { + "description": "*macOS-only**. Corresponds to CFBundleTypeRole", + "oneOf": [ + { + "description": "CFBundleTypeRole.Editor. Files can be read and edited.", + "type": "string", + "enum": [ + "editor" + ] + }, + { + "description": "CFBundleTypeRole.Viewer. Files can be read.", + "type": "string", + "enum": [ + "viewer" + ] + }, + { + "description": "CFBundleTypeRole.Shell", + "type": "string", + "enum": [ + "shell" + ] + }, + { + "description": "CFBundleTypeRole.QLGenerator", + "type": "string", + "enum": [ + "qLGenerator" + ] + }, + { + "description": "CFBundleTypeRole.None", + "type": "string", + "enum": [ + "none" + ] + } + ] + }, + "Resource": { + "description": "A path to a resource (with optional glob pattern) or an object of `src` and `target` paths.", + "anyOf": [ + { + "description": "Supports glob patterns", + "type": "string" + }, + { + "description": "An object descriping the src file or directory and its target location in the final package.", + "type": "object", + "required": [ + "src", + "target" + ], + "properties": { + "src": { + "description": "The src file or directory, supports glob patterns.", + "type": "string" + }, + "target": { + "description": "A relative path from the root of the final package.\n\nIf `src` is a glob, this will always be treated as a directory where all globbed files will be placed under.", + "type": "string" + } + } + } + ] + }, + "WindowsConfig": { + "description": "The Windows configuration.", + "type": "object", + "properties": { + "digestAlgorithm": { + "description": "The file digest algorithm to use for creating file signatures. Required for code signing. SHA-256 is recommended.", + "type": [ + "string", + "null" + ] + }, + "certificateThumbprint": { + "description": "The SHA1 hash of the signing certificate.", + "type": [ + "string", + "null" + ] + }, + "tsp": { + "description": "Whether to use Time-Stamp Protocol (TSP, a.k.a. RFC 3161) for the timestamp server. Your code signing provider may use a TSP timestamp server, like e.g. SSL.com does. If so, enable TSP by setting to true.", + "default": false, + "type": "boolean" + }, + "timestampUrl": { + "description": "Server to use during timestamping.", + "type": [ + "string", + "null" + ] + }, + "allowDowngrades": { + "description": "Whether to validate a second app installation, blocking the user from installing an older version if set to `false`.\n\nFor instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\n\nThe default value of this flag is `true`.", + "default": true, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "MacOsConfig": { + "description": "The macOS configuration.", + "type": "object", + "properties": { + "frameworks": { + "description": "MacOS frameworks that need to be packaged with the app.\n\nEach string can either be the name of a framework (without the `.framework` extension, e.g. `\"SDL2\"`), in which case we will search for that framework in the standard install locations (`~/Library/Frameworks/`, `/Library/Frameworks/`, and `/Network/Library/Frameworks/`), or a path to a specific framework bundle (e.g. `./data/frameworks/SDL2.framework`). Note that this setting just makes cargo-packager copy the specified frameworks into the OS X app bundle (under `Foobar.app/Contents/Frameworks/`); you are still responsible for:\n\n- arranging for the compiled binary to link against those frameworks (e.g. by emitting lines like `cargo:rustc-link-lib=framework=SDL2` from your `build.rs` script)\n\n- embedding the correct rpath in your binary (e.g. by running `install_name_tool -add_rpath \"@executable_path/../Frameworks\" path/to/binary` after compiling)", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "minimumSystemVersion": { + "description": "A version string indicating the minimum MacOS version that the packaged app supports (e.g. `\"10.11\"`). If you are using this config field, you may also want have your `build.rs` script emit `cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=10.11`.", + "type": [ + "string", + "null" + ] + }, + "exceptionDomain": { + "description": "The exception domain to use on the macOS .app package.\n\nThis allows communication to the outside world e.g. a web server you're shipping.", + "type": [ + "string", + "null" + ] + }, + "signingIdentity": { + "description": "Code signing identity.", + "type": [ + "string", + "null" + ] + }, + "providerShortName": { + "description": "Provider short name for notarization.", + "type": [ + "string", + "null" + ] + }, + "entitlements": { + "description": "Path to the entitlements.plist file.", + "type": [ + "string", + "null" + ] + }, + "infoPlistPath": { + "description": "Path to the Info.plist file for the package.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, + "DebianConfig": { + "description": "The Linux debian configuration.", + "type": "object", + "properties": { + "depends": { + "description": "The list of debian dependencies.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "desktopTemplate": { + "description": "Path to a custom desktop file Handlebars template.\n\nAvailable variables: `categories`, `comment` (optional), `exec`, `icon` and `name`.\n\nDefault file contents: ```text [Desktop Entry] Categories={{categories}} {{#if comment}} Comment={{comment}} {{/if}} Exec={{exec}} Icon={{icon}} Name={{name}} Terminal=false Type=Application {{#if mime_type}} MimeType={{mime_type}} {{/if}} ```", + "type": [ + "string", + "null" + ] + }, + "files": { + "description": "List of custom files to add to the deb package. Maps a dir/file to a dir/file inside the debian package.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "AppImageConfig": { + "description": "The Linux AppImage configuration.", + "type": "object", + "properties": { + "libs": { + "description": "List of libs that exist in `/usr/lib*` to be include in the final AppImage. The libs will be searched for, using the command `find -L /usr/lib* -name `", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "bins": { + "description": "List of binary paths to include in the final AppImage. For example, if you want `xdg-open`, you'd specify `/usr/bin/xdg-open`", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "files": { + "description": "List of custom files to add to the appimage package. Maps a dir/file to a dir/file inside the appimage package.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "linuxdeployPlugins": { + "description": "A map of [`linuxdeploy`](https://github.com/linuxdeploy/linuxdeploy) plugin name and its URL to be downloaded and executed while packaing the appimage. For example, if you want to use the [`gtk`](https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh) plugin, you'd specify `gtk` as the key and its url as the value.", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "WixConfig": { + "description": "The wix format configuration", + "type": "object", + "properties": { + "languages": { + "description": "The app languages to build. See .", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/WixLanguage" + } + }, + "template": { + "description": "By default, the packager uses an internal template. This option allows you to define your own wix file.", + "type": [ + "string", + "null" + ] + }, + "mergeModules": { + "description": "List of merge modules to include in your installer. For example, if you want to include [C++ Redis merge modules]\n\n[C++ Redis merge modules]: https://wixtoolset.org/docs/v3/howtos/redistributables_and_install_checks/install_vcredist/", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "fragmentPaths": { + "description": "A list of paths to .wxs files with WiX fragments to use.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "fragments": { + "description": "List of WiX fragments as strings. This is similar to `config.wix.fragments_paths` but is a string so you can define it inline in your config.\n\n```text ```", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "componentGroupRefs": { + "description": "The ComponentGroup element ids you want to reference from the fragments.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "componentRefs": { + "description": "The Component element ids you want to reference from the fragments.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "customActionRefs": { + "description": "The CustomAction element ids you want to reference from the fragments.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "featureGroupRefs": { + "description": "The FeatureGroup element ids you want to reference from the fragments.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "featureRefs": { + "description": "The Feature element ids you want to reference from the fragments.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "mergeRefs": { + "description": "The Merge element ids you want to reference from the fragments.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "bannerPath": { + "description": "Path to a bitmap file to use as the installation user interface banner. This bitmap will appear at the top of all but the first page of the installer.\n\nThe required dimensions are 493px × 58px.", + "type": [ + "string", + "null" + ] + }, + "dialogImagePath": { + "description": "Path to a bitmap file to use on the installation user interface dialogs. It is used on the welcome and completion dialogs. The required dimensions are 493px × 312px.", + "type": [ + "string", + "null" + ] + }, + "fipsCompliant": { + "description": "Enables FIPS compliant algorithms.", + "default": false, + "type": "boolean" + } + }, + "additionalProperties": false + }, + "WixLanguage": { + "description": "A wix language.", + "anyOf": [ + { + "description": "Built-in wix language identifier.", + "type": "string" + }, + { + "description": "Custom wix language.", + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "identifier": { + "description": "Idenitifier of this language, for example `en-US`", + "type": "string" + }, + "path": { + "description": "The path to a locale (`.wxl`) file. See .", + "type": [ + "string", + "null" + ] + } + } + } + ] + }, + "NsisConfig": { + "description": "The NSIS format configuration.", + "type": "object", + "properties": { + "compression": { + "description": "Set the compression algorithm used to compress files in the installer.\n\nSee ", + "anyOf": [ + { + "$ref": "#/definitions/NsisCompression" + }, + { + "type": "null" + } + ] + }, + "template": { + "description": "A custom `.nsi` template to use.\n\nSee the default template here ", + "type": [ + "string", + "null" + ] + }, + "preinstallSection": { + "description": "Logic of an NSIS section that will be ran before the install section.\n\nSee the available libraries, dlls and global variables here \n\n### Example ```toml [package.metadata.packager.nsis] preinstall-section = \"\"\" ; Setup custom messages LangString webview2AbortError ${LANG_ENGLISH} \"Failed to install WebView2! The app can't run without it. Try restarting the installer.\" LangString webview2DownloadError ${LANG_ARABIC} \"خطأ: فشل تنزيل WebView2 - $0\"\n\nSection PreInstall ;
SectionEnd\n\nSection AnotherPreInstall ;
SectionEnd \"\"\" ```", + "type": [ + "string", + "null" + ] + }, + "headerImage": { + "description": "The path to a bitmap file to display on the header of installers pages.\n\nThe recommended dimensions are 150px x 57px.", + "type": [ + "string", + "null" + ] + }, + "sidebarImage": { + "description": "The path to a bitmap file for the Welcome page and the Finish page.\n\nThe recommended dimensions are 164px x 314px.", + "type": [ + "string", + "null" + ] + }, + "installerIcon": { + "description": "The path to an icon file used as the installer icon.", + "type": [ + "string", + "null" + ] + }, + "installMode": { + "description": "Whether the installation will be for all users or just the current user.", + "default": "currentUser", + "allOf": [ + { + "$ref": "#/definitions/NSISInstallerMode" + } + ] + }, + "languages": { + "description": "A list of installer languages. By default the OS language is used. If the OS language is not in the list of languages, the first language will be used. To allow the user to select the language, set `display_language_selector` to `true`.\n\nSee for the complete list of languages.", + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "customLanguageFiles": { + "description": "An key-value pair where the key is the language and the value is the path to a custom `.nsi` file that holds the translated text for cargo-packager's custom messages.\n\nSee for an example `.nsi` file.\n\n**Note**: the key must be a valid NSIS language and it must be added to [`NsisConfig`]languages array,", + "type": [ + "object", + "null" + ], + "additionalProperties": { + "type": "string" + } + }, + "displayLanguageSelector": { + "description": "Whether to display a language selector dialog before the installer and uninstaller windows are rendered or not. By default the OS language is selected, with a fallback to the first language in the `languages` array.", + "default": false, + "type": "boolean" + }, + "appdataPaths": { + "description": "List of paths where your app stores data. This options tells the uninstaller to provide the user with an option (disabled by default) whether they want to rmeove your app data or keep it.\n\nThe path should use a constant from in addition to `$IDENTIFIER`, `$PUBLISHER` and `$PRODUCTNAME`, for example, if you store your app data in `C:\\\\Users\\\\\\\\AppData\\\\Local\\\\\\\\` you'd need to specify ```toml [package.metadata.packager.nsis] appdata-paths = [\"$LOCALAPPDATA/$PUBLISHER/$PRODUCTNAME\"] ```", + "default": null, + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "NsisCompression": { + "description": "Compression algorithms used in the NSIS installer.\n\nSee ", + "oneOf": [ + { + "description": "ZLIB uses the deflate algorithm, it is a quick and simple method. With the default compression level it uses about 300 KB of memory.", + "type": "string", + "enum": [ + "zlib" + ] + }, + { + "description": "BZIP2 usually gives better compression ratios than ZLIB, but it is a bit slower and uses more memory. With the default compression level it uses about 4 MB of memory.", + "type": "string", + "enum": [ + "bzip2" + ] + }, + { + "description": "LZMA (default) is a new compression method that gives very good compression ratios. The decompression speed is high (10-20 MB/s on a 2 GHz CPU), the compression speed is lower. The memory size that will be used for decompression is the dictionary size plus a few KBs, the default is 8 MB.", + "type": "string", + "enum": [ + "lzma" + ] + }, + { + "description": "Disable compression.", + "type": "string", + "enum": [ + "off" + ] + } + ] + }, + "NSISInstallerMode": { + "description": "Install Modes for the NSIS installer.", + "oneOf": [ + { + "description": "Default mode for the installer.\n\nInstall the app by default in a directory that doesn't require Administrator access.\n\nInstaller metadata will be saved under the `HKCU` registry path.", + "type": "string", + "enum": [ + "currentUser" + ] + }, + { + "description": "Install the app by default in the `Program Files` folder directory requires Administrator access for the installation.\n\nInstaller metadata will be saved under the `HKLM` registry path.", + "type": "string", + "enum": [ + "perMachine" + ] + }, + { + "description": "Combines both modes and allows the user to choose at install time whether to install for the current user or per machine. Note that this mode will require Administrator access even if the user wants to install it for the current user only.\n\nInstaller metadata will be saved under the `HKLM` or `HKCU` registry path based on the user's choice.", + "type": "string", + "enum": [ + "both" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/crates/config-schema-generator/build.rs b/crates/config-schema-generator/build.rs index 5cd0734c..132c46ce 100644 --- a/crates/config-schema-generator/build.rs +++ b/crates/config-schema-generator/build.rs @@ -13,7 +13,12 @@ pub fn main() -> Result<(), Box> { let schema = schemars::schema_for!(cargo_packager::Config); let schema_str = serde_json::to_string_pretty(&schema).unwrap(); let crate_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR")?); - let mut schema_file = BufWriter::new(File::create(crate_dir.join("../packager/schema.json"))?); - write!(schema_file, "{schema_str}")?; + for path in [ + "../packager/schema.json", + "../../bindings/packager/nodejs/schema.json", + ] { + let mut schema_file = BufWriter::new(File::create(crate_dir.join(path))?); + write!(schema_file, "{schema_str}")?; + } Ok(()) } diff --git a/crates/packager/README.md b/crates/packager/README.md index 2c9e3b85..9fa477d2 100644 --- a/crates/packager/README.md +++ b/crates/packager/README.md @@ -1,30 +1,11 @@ -Rust executable packager, bundler and updater. A tool and library to generate installers or app bundles for your executables. -It also has a comptabile updater through [cargo-packager-updater](https://docs.rs/cargo-packager-updater). +# cargo-packager -## CLI +cargo-packager splash -### Installation +Executable packager, bundler and updater. A cli tool and library to generate installers or app bundles for your executables. +It also has a compatible updater through [cargo-packager-updater](https://docs.rs/cargo-packager-updater). -```sh -cargo install cargo-packager --locked -``` - -### Usage - -1. Add `Packager.toml` or `packager.json` in your project or modify Cargo.toml and include - - ```toml - [package.metadata.packager] - before-packaging-command = "cargo build --release" - ``` - -2. Run the CLI - - ```sh - cargo packager --release - ``` - -### Supported packages +#### Supported packages: - macOS - DMG (.dmg) @@ -36,43 +17,46 @@ cargo install cargo-packager --locked - NSIS (.exe) - MSI using WiX Toolset (.msi) -### Configuration +### CLI -By default, `cargo-packager` reads configuration from `Packager.toml` or `packager.json` if exists, and from `package.metadata.packager` table in `Cargo.toml`. -You can also specify a custom configuration file using `-c/--config` cli argument. -All configuration options could be either a single config or array of configs. +The packager is distrubuted on crates.io as a cargo subcommand, you can install it using cargo: -For full list of configuration options, see https://docs.rs/cargo-packager/latest/cargo-packager/struct.Config.html +```sh +cargo install cargo-packager --locked +``` -You could also use the schema from GitHub releases to validate your configuration or have auto completions in your IDE. +You then need to configure your app so the cli can recognize it. Configuration can be done in `Packager.toml` or `packager.json` in your project or modify Cargo.toml and include this snippet: -### Building your application before packaging +```toml +[package.metadata.packager] +before-packaging-command = "cargo build --release" +``` -By default, `cargo-packager` doesn't build your application, it only looks for it inside the directory specified in `config.out_dir` or `--out-dir` cli arg, -However, `cargo-packager` has an option to specify a shell command to be executed before packaing your app, `beforePackagingCommand`. +Once, you are done configuring your app, run: -### Cargo profiles +```sh +cargo packager --release +``` -By default, `cargo-packager` looks for binaries built using the `debug` profile, if your `beforePackagingCommand` builds your app using `cargo build --release`, you will also need to -run `cargo-packager` in release mode `cargo packager --release`, otherwise, if you have a custom cargo profile, you will need to specify it using `--profile` cli arg `cargo packager --profile custom-release-profile`. +### Configuration + +By default, the packager reads its configuration from `Packager.toml` or `packager.json` if it exists, and from `package.metadata.packager` table in `Cargo.toml`. +You can also specify a custom configuration using the `-c/--config` cli argument. -For more information, checkout the available [configuration options](https://docs.rs/cargo-packager/latest/cargo-packager/struct.Config.html) and for a list of available CLI -commands and arguments, run `cargo packager --help`. +For a full list of configuration options, see https://docs.rs/cargo-packager/latest/cargo_packager/config/struct.Config.html. -### Examples +You could also use the [schema](./schema.json) file from GitHub to validate your configuration or have auto completions in your IDE. -The [`examples`](../../examples/) directory contains a number of varying examples, if you want to build them all run `cargo r -p cargo-packager -- --release` in the root of this repository. Just make sure to have the tooling for each example installed on your system. You can find what tooling they require by checking the README in each example. The README also contains a command to build this example alone if you wish. +### Building your application before packaging -Examples list (non-exhaustive): +By default, the packager doesn't build your application, so if your app requires a compilation step, the packager has an option to specify a shell command to be executed before packaing your app, `beforePackagingCommand`. + +### Cargo profiles -- [`tauri`](../../examples/tauri/) -- [`wry`](../../examples/wry/) -- [`dioxus`](../../examples/dioxus/) -- [`egui`](../../examples/egui/) -- [`deno`](../../examples/deno/) -- [`slint`](../../examples/slint/) +By default, the packager looks for binaries built using the `debug` profile, if your `beforePackagingCommand` builds your app using `cargo build --release`, you will also need to +run the packager in release mode `cargo packager --release`, otherwise, if you have a custom cargo profile, you will need to specify it using `--profile` cli arg `cargo packager --profile custom-release-profile`. -## Library +### Library This crate is also published to crates.io as a library that you can integrate into your tooling, just make sure to disable the default-feature flags. @@ -82,7 +66,7 @@ cargo add cargo-packager --no-default-features #### Feature flags -- **`cli`**: Enables the CLI specifc features and dependencies. Enabled by default. +- **`cli`**: Enables the cli specifc features and dependencies. Enabled by default. - **`tracing`**: Enables `tracing` crate integration. ## Licenses diff --git a/crates/packager/src/lib.rs b/crates/packager/src/lib.rs index d80f83af..7bf2e5db 100644 --- a/crates/packager/src/lib.rs +++ b/crates/packager/src/lib.rs @@ -4,31 +4,8 @@ //! [![cargo-packager splash](https://github.com/crabnebula-dev/cargo-packager/raw/main/.github/splash.png)](https://github.com/crabnebula-dev/cargo-packager) //! -//! `cargo-packager` is a tool and library to generate installers or app bundles for your executables. -//! It also has a comptabile updater through [cargo-packager-updater](https://docs.rs/cargo-packager-updater). -//! -//! ## CLI -//! -//! ### Installation -//! -//! ```sh -//! cargo install cargo-packager --locked -//! ``` -//! -//! ### Usage -//! -//! 1. Add `Packager.toml` or `packager.json` in your project or modify Cargo.toml and include -//! -//! ```toml -//! [package.metadata.packager] -//! before-packaging-command = "cargo build --release" -//! ``` -//! -//! 2. Run the CLI -//! -//! ```sh -//! cargo packager --release -//! ``` +//! Executable packager, bundler and updater. A cli tool and library to generate installers or app bundles for your executables. +//! It also has a compatible updater through [cargo-packager-updater](https://docs.rs/cargo-packager-updater). //! //! ### Supported packages //! @@ -42,30 +19,45 @@ //! - NSIS (.exe) //! - MSI using WiX Toolset (.msi) //! +//! ## CLI +//! +//! This crate is a cargo subcommand so you can install using: +//! +//! ```sh +//! cargo install cargo-packager --locked +//! ``` +//! You then need to configure your app so the cli can recognize it. Configuration can be done in `Packager.toml` or `packager.json` in your project or modify Cargo.toml and include this snippet: +//! +//! ```toml +//! [package.metadata.packager] +//! before-packaging-command = "cargo build --release" +//! ``` +//! +//! Once, you are done configuring your app, run: +//! +//! ```sh +//! cargo packager --release +//! ``` +//! //! ### Configuration //! -//! By default, `cargo-packager` reads configuration from `Packager.toml` or `packager.json` if exists, and from `package.metadata.packager` table in `Cargo.toml`. -//! You can also specify a custom configuration file using `-c/--config` cli argument. -//! All configuration options could be either a single config or array of configs. +//! By default, the packager reads its configuration from `Packager.toml` or `packager.json` if it exists, and from `package.metadata.packager` table in `Cargo.toml`. +//! You can also specify a custom configuration using the `-c/--config` cli argument. //! -//! For full list of configuration options, see [config::Config] +//! For a full list of configuration options, see [Config]. //! -//! You could also use the schema from GitHub releases to validate your configuration or have auto completions in your IDE. +//! You could also use the [schema](./schema.json) file from GitHub to validate your configuration or have auto completions in your IDE. //! //! ### Building your application before packaging //! -//! By default, `cargo-packager` doesn't build your application, it only looks for it inside the directory specified in `config.out_dir` or `--out-dir` cli arg, -//! However, `cargo-packager` has an option to specify a shell command to be executed before packaing your app, `beforePackagingCommand`. +//! By default, the packager doesn't build your application, so if your app requires a compilation step, the packager has an option to specify a shell command to be executed before packaing your app, `beforePackagingCommand`. //! //! ### Cargo profiles //! -//! By default, `cargo-packager` looks for binaries built using the `debug` profile, if your `beforePackagingCommand` builds your app using `cargo build --release`, you will also need to -//! run `cargo-packager` in release mode `cargo packager --release`, otherwise, if you have a custom cargo profile, you will need to specify it using `--profile` cli arg `cargo packager --profile custom-release-profile`. -//! -//! For more information, checkout the available [configuration options](config::Config) and for a list of available CLI -//! commands and arguments, run `cargo packager --help`. +//! By default, the packager looks for binaries built using the `debug` profile, if your `beforePackagingCommand` builds your app using `cargo build --release`, you will also need to +//! run the packager in release mode `cargo packager --release`, otherwise, if you have a custom cargo profile, you will need to specify it using `--profile` cli arg `cargo packager --profile custom-release-profile`. //! -//! ## Library +//! ### Library //! //! This crate is also published to crates.io as a library that you can integrate into your tooling, just make sure to disable the default-feature flags. //! @@ -75,7 +67,7 @@ //! //! #### Feature flags //! -//! - **`cli`**: Enables the CLI specifc features and dependencies. Enabled by default. +//! - **`cli`**: Enables the cli specifc features and dependencies. Enabled by default. //! - **`tracing`**: Enables `tracing` crate integration. #![cfg_attr(doc_cfg, feature(doc_cfg))] diff --git a/crates/updater/README.md b/crates/updater/README.md index c74465ae..a17d84cd 100644 --- a/crates/updater/README.md +++ b/crates/updater/README.md @@ -1,6 +1,6 @@ ## cargo-packager-updater -Rust executable updater for apps installed through the packages generated by [`cargo-packager`](../packager/). +Updater for apps that was packaged by [`cargo-packager`](https://docs.rs/cargo-packager). ## Licenses From e8aec2433ebf75876b1dba1bcc9ef9734648fda1 Mon Sep 17 00:00:00 2001 From: amr-crabnebula Date: Mon, 11 Dec 2023 20:48:17 +0200 Subject: [PATCH 2/5] schma --- bindings/packager/nodejs/schema.json | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/bindings/packager/nodejs/schema.json b/bindings/packager/nodejs/schema.json index 8efae566..57170037 100644 --- a/bindings/packager/nodejs/schema.json +++ b/bindings/packager/nodejs/schema.json @@ -273,8 +273,6 @@ "type": "null" } ] -<<<<<<< HEAD -======= }, "dmg": { "description": "Dmg configuration.", @@ -286,7 +284,6 @@ "type": "null" } ] ->>>>>>> main } }, "additionalProperties": false, @@ -634,6 +631,13 @@ "description": "Whether to validate a second app installation, blocking the user from installing an older version if set to `false`.\n\nFor instance, if `1.2.1` is installed, the user won't be able to install app version `1.2.0` or `1.1.5`.\n\nThe default value of this flag is `true`.", "default": true, "type": "boolean" + }, + "signCommand": { + "description": "Specify a custom command to sign the binaries. This command needs to have a `%1` in it which is just a placeholder for the binary path, which we will detect and replace before calling the command.\n\nBy Default we use `signtool.exe` which can be found only on Windows so if you are on another platform and want to cross-compile and sign you will need to use another tool like `osslsigncode`.", + "type": [ + "string", + "null" + ] } }, "additionalProperties": false @@ -1096,8 +1100,6 @@ ] } ] -<<<<<<< HEAD -======= }, "DmgConfig": { "description": "The Apple Disk Image (.dmg) configuration.", @@ -1202,7 +1204,6 @@ } }, "additionalProperties": false ->>>>>>> main } } } \ No newline at end of file From f77fbc441ab3f977017a8dfaaa83e99979aa93a2 Mon Sep 17 00:00:00 2001 From: amr-crabnebula Date: Tue, 12 Dec 2023 10:21:22 +0200 Subject: [PATCH 3/5] finish documentation --- bindings/updater/nodejs/README.md | 129 ++++++++++++ bindings/updater/nodejs/index.d.ts | 6 +- bindings/updater/nodejs/src/from_impls.rs | 6 +- bindings/updater/nodejs/src/lib.rs | 6 +- crates/updater/README.md | 126 ++++++++++- crates/updater/src/custom_serialization.rs | 6 +- crates/updater/src/error.rs | 1 + crates/updater/src/lib.rs | 230 +++++++++++++++++++-- crates/updater/tests/app/src/main.rs | 2 +- examples/tauri/src/main.rs | 2 +- package.json | 4 - 11 files changed, 481 insertions(+), 37 deletions(-) create mode 100644 bindings/updater/nodejs/README.md diff --git a/bindings/updater/nodejs/README.md b/bindings/updater/nodejs/README.md new file mode 100644 index 00000000..f8080767 --- /dev/null +++ b/bindings/updater/nodejs/README.md @@ -0,0 +1,129 @@ +# @crabnebula/updater + +Updater for apps that was packaged by [`@crabnebula/packager`](https://www.npmjs.com/package/@crabnebula/packager). + +## Checking for an update + +you can check for an update using `checkUpdate` function which require the current version of the app and +an options object that specifies the endpoints to request updates from and the public key of the update signature. + +```js +import { checkUpdate } from "@crabnebula/updater"; + +let update = await checkUpdate("0.1.0", { + endpoints: ["http://myserver.com/updates"], + pubkey: "", +}); + +if (update !== null) { + update.downloadAndInstall(); +} else { + // there is no updates +} +``` + +## Endpoints + +Each endpoint optionally could have `{{arch}}`, `{{target}}` or `{{current_version}}` +which will be detected and replaced with the appropriate value before making a request to the endpoint. + +- `{{current_version}}`: The version of the app that is requesting the update. +- `{{target}}`: The operating system name (one of `linux`, `windows` or `macos`). +- `{{arch}}`: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`). + +for example: + +``` +https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}} +``` + +will turn into + +``` +https://releases.myapp.com/windows/x86_64/0.1.0 +``` + +if you need more data, you can set additional request headers in the options object pass to `checkUpdate` to your liking. + +## Endpoint Response + +The updater expects the endpoint to respond with 2 possible reponses: + +1. [`204 No Content`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.5) in case there is no updates available. +2. [`200 OK`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.1) and a JSON response that could be either a JSON representing all available platform updates + or if using endpoints variables (see above) or a header to attach the current updater target, + then it can just return information for the requested target. + +The JSON response is expected to have these fields set: + +- `version`: must be a valid semver, with or without a leading `v``, meaning that both `1.0.0`and`v1.0.0`are valid. +- `url`or`platforms.[target].url`: must be a valid url to the update bundle. +- `signature`or`platforms.[target].signature`: must be the content of the generated `.sig`file. The signature may change each time you run build your app so make sure to always update it. +- `format`or`platforms.[target].format`: must be one of `app`, `appimage`, `nsis`or`wix`. + +> [!NOTE] +> if using `platforms` object, each key is in the `OS-ARCH` format, where `OS` is one of `linux`, `macos` or `windows`, and `ARCH` is one of `x86_64`, `aarch64`, `i686` or `armv7`, see the example below. + +It can also contain these optional fields: + +- `notes`: Here you can add notes about the update, like release notes. +- `pub_date`: must be formatted according to [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.8) if present. + +Here is an example of the two expected JSON formats: + +- **JSON for all platforms** + + ```json + { + "version": "v1.0.0", + "notes": "Test version", + "pub_date": "2020-06-22T19:25:57Z", + "platforms": { + "darwin-x86_64": { + "signature": "Content of app.tar.gz.sig", + "url": "https://github.com/username/reponame/releases/download/v1.0.0/app-x86_64.app.tar.gz", + "format": "app" + }, + "darwin-aarch64": { + "signature": "Content of app.tar.gz.sig", + "url": "https://github.com/username/reponame/releases/download/v1.0.0/app-aarch64.app.tar.gz", + "format": "app" + }, + "linux-x86_64": { + "signature": "Content of app.AppImage.sig", + "url": "https://github.com/username/reponame/releases/download/v1.0.0/app-amd64.AppImage.tar.gz", + "format": "appimage" + }, + "windows-x86_64": { + "signature": "Content of app-setup.exe.sig or app.msi.sig, depending on the chosen format", + "url": "https://github.com/username/reponame/releases/download/v1.0.0/app-x64-setup.nsis.zip", + "format": "nsis or wix depending on the chosen format" + } + } + } + ``` + +- **JSON for one platform** + + ```json + { + "version": "0.2.0", + "pub_date": "2020-09-18T12:29:53+01:00", + "url": "https://mycompany.example.com/myapp/releases/myrelease.tar.gz", + "signature": "Content of the relevant .sig file", + "format": "app or nsis or wix or appimage depending on the release target and the chosen format", + "notes": "These are some release notes" + } + ``` + +## Update install mode on Windows + +You can specify which install mode to use on Windows using `windows.installMode` in the options object which can be one of: + +- `"Passive"`: There will be a small window with a progress bar. The update will be installed without requiring any user interaction. Generally recommended and the default mode. +- `"BasicUi"`: There will be a basic user interface shown which requires user interaction to finish the installation. +- `"Quiet"`: There will be no progress feedback to the user. With this mode the installer cannot request admin privileges by itself so it only works in user-wide installations or when your app itself already runs with admin privileges. Generally not recommended. + +## Licenses + +MIT or MIT/Apache 2.0 where applicable. diff --git a/bindings/updater/nodejs/index.d.ts b/bindings/updater/nodejs/index.d.ts index a35915cf..0a3455f0 100644 --- a/bindings/updater/nodejs/index.d.ts +++ b/bindings/updater/nodejs/index.d.ts @@ -5,14 +5,14 @@ export const enum WindowsUpdateInstallMode { /** Specifies there's a basic UI during the installation process, including a final dialog box at the end. */ - BasicUi = 0, + BasicUi = 'BasicUi', /** * The quiet mode means there's no user interaction required. * Requires admin privileges if the installer does. */ - Quiet = 1, + Quiet = 'Quiet', /** Specifies unattended mode, which means the installation only shows a progress bar. */ - Passive = 2 + Passive = 'Passive' } export interface UpdaterWindowsOptions { /** Additional arguments given to the NSIS or WiX installer. */ diff --git a/bindings/updater/nodejs/src/from_impls.rs b/bindings/updater/nodejs/src/from_impls.rs index 2e93744d..ff67a7c2 100644 --- a/bindings/updater/nodejs/src/from_impls.rs +++ b/bindings/updater/nodejs/src/from_impls.rs @@ -20,15 +20,15 @@ impl From for WindowsUpdateIns } } -impl From for UpdaterWindowsOptions { - fn from(value: cargo_packager_updater::UpdaterWindowsConfig) -> Self { +impl From for UpdaterWindowsOptions { + fn from(value: cargo_packager_updater::WindowsConfig) -> Self { Self { installer_args: value.installer_args, install_mode: value.install_mode.map(Into::into), } } } -impl From for cargo_packager_updater::UpdaterWindowsConfig { +impl From for cargo_packager_updater::WindowsConfig { fn from(value: UpdaterWindowsOptions) -> Self { Self { installer_args: value.installer_args, diff --git a/bindings/updater/nodejs/src/lib.rs b/bindings/updater/nodejs/src/lib.rs index d7534dc8..26824277 100644 --- a/bindings/updater/nodejs/src/lib.rs +++ b/bindings/updater/nodejs/src/lib.rs @@ -12,7 +12,7 @@ use napi::{ mod from_impls; -#[napi_derive::napi] +#[napi_derive::napi(string_enum)] #[derive(Default)] pub enum WindowsUpdateInstallMode { /// Specifies there's a basic UI during the installation process, including a final dialog box at the end. @@ -191,7 +191,7 @@ impl Update { let update = self.create_update()?; update - .download( + .download_extended( |c, l| { if let Some(on_chunk) = &on_chunk { on_chunk.call( @@ -227,7 +227,7 @@ impl Update { ) -> Result<()> { let update = self.create_update()?; let bytes = update - .download( + .download_extended( |c, l| { if let Some(on_chunk) = &on_chunk { on_chunk.call( diff --git a/crates/updater/README.md b/crates/updater/README.md index a17d84cd..3ab4ba7a 100644 --- a/crates/updater/README.md +++ b/crates/updater/README.md @@ -1,7 +1,131 @@ -## cargo-packager-updater +# cargo-packager-updater Updater for apps that was packaged by [`cargo-packager`](https://docs.rs/cargo-packager). +## Checking for an update + +you can check for an update using [`check_update`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/fn.check_update.html) function or construct a new [`Updater`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.Updater.html) +using [`UpdaterBuilder`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.UpdaterBuilder.html), both methods require the current version of the app and +a [`Config`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.Config.html) that specifies the endpoints to request updates from and the public key of the update signature. + +```rs +use cargo_packager_updater::{check_update, Config}; + +let config = Config { + endpoints: vec!["http://myserver.com/updates"], + pubkey: "", + ..Default::default() +}; +if let Some(update) = check_update("0.1.0", config).expect("failed while checking for update") { + update.download_and_install().expect("failed to download and install update"); +} else { + // there is no updates +} + +``` + +## Endpoints + +Each endpoint optionally could have `{{arch}}`, `{{target}}` or `{{current_version}}` +which will be detected and replaced with the appropriate value before making a request to the endpoint. + +- `{{current_version}}`: The version of the app that is requesting the update. +- `{{target}}`: The operating system name (one of `linux`, `windows` or `macos`). +- `{{arch}}`: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`). + +for example: + +``` + "https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}" +``` + +will turn into + +``` + "https://releases.myapp.com/windows/x86_64/0.1.0" +``` + +if you need more data, you can set additional request headers [`UpdaterBuilder::header`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.UpdaterBuilder.html#method.header) to your liking. + +## Endpoint Response + +The updater expects the endpoint to respond with 2 possible reponses: + +1. [`204 No Content`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.5) in case there is no updates available. +2. [`200 OK`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.1) and a JSON response that could be either a JSON representing all available platform updates + or if using endpoints variables (see above) or a header to attach the current updater target, + then it can just return information for the requested target. + +The JSON response is expected to have these fields set: + +- `version`: must be a valid semver, with or without a leading `v``, meaning that both `1.0.0`and`v1.0.0`are valid. +- `url`or`platforms.[target].url`: must be a valid url to the update bundle. +- `signature`or`platforms.[target].signature`: must be the content of the generated `.sig`file. The signature may change each time you run build your app so make sure to always update it. +- `format`or`platforms.[target].format`: must be one of `app`, `appimage`, `nsis`or`wix`. + +> [!NOTE] +> if using `platforms` object, each key is in the `OS-ARCH` format, where `OS` is one of `linux`, `macos` or `windows`, and `ARCH` is one of `x86_64`, `aarch64`, `i686` or `armv7`, see the example below. + +It can also contain these optional fields: + +- `notes`: Here you can add notes about the update, like release notes. +- `pub_date`: must be formatted according to [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.8) if present. + +Here is an example of the two expected JSON formats: + +- **JSON for all platforms** + + ```json + { + "version": "v1.0.0", + "notes": "Test version", + "pub_date": "2020-06-22T19:25:57Z", + "platforms": { + "darwin-x86_64": { + "signature": "Content of app.tar.gz.sig", + "url": "https://github.com/username/reponame/releases/download/v1.0.0/app-x86_64.app.tar.gz", + "format": "app" + }, + "darwin-aarch64": { + "signature": "Content of app.tar.gz.sig", + "url": "https://github.com/username/reponame/releases/download/v1.0.0/app-aarch64.app.tar.gz", + "format": "app" + }, + "linux-x86_64": { + "signature": "Content of app.AppImage.sig", + "url": "https://github.com/username/reponame/releases/download/v1.0.0/app-amd64.AppImage.tar.gz", + "format": "appimage" + }, + "windows-x86_64": { + "signature": "Content of app-setup.exe.sig or app.msi.sig, depending on the chosen format", + "url": "https://github.com/username/reponame/releases/download/v1.0.0/app-x64-setup.nsis.zip", + "format": "nsis or wix depending on the chosen format" + } + } + } + ``` + +- **JSON for one platform** + + ```json + { + "version": "0.2.0", + "pub_date": "2020-09-18T12:29:53+01:00", + "url": "https://mycompany.example.com/myapp/releases/myrelease.tar.gz", + "signature": "Content of the relevant .sig file", + "format": "app or nsis or wix or appimage depending on the release target and the chosen format", + "notes": "These are some release notes" + } + ``` + +## Update install mode on Windows + +You can specify which install mode to use on Windows using [`WindowsConfig::install_mode`](https://docs.rs/cargo-packager-updater/latest/cargo_packager_updater/struct.WindowsConfig.html#structfield.install_mode) which can be on of: + +- `"Passive"`: There will be a small window with a progress bar. The update will be installed without requiring any user interaction. Generally recommended and the default mode. +- `"BasicUi"`: There will be a basic user interface shown which requires user interaction to finish the installation. +- `"Quiet"`: There will be no progress feedback to the user. With this mode the installer cannot request admin privileges by itself so it only works in user-wide installations or when your app itself already runs with admin privileges. Generally not recommended. + ## Licenses MIT or MIT/Apache 2.0 where applicable. diff --git a/crates/updater/src/custom_serialization.rs b/crates/updater/src/custom_serialization.rs index 25ad0c1b..7919e8ea 100644 --- a/crates/updater/src/custom_serialization.rs +++ b/crates/updater/src/custom_serialization.rs @@ -10,7 +10,7 @@ use serde::{de::Error, Deserialize, Deserializer}; use time::OffsetDateTime; use url::Url; -use crate::{ReleaseManifestPlatform, RemoteRelease, RemoteReleaseInner, UpdateFormat}; +use crate::{ReleaseManifestPlatform, RemoteRelease, RemoteReleaseData, UpdateFormat}; fn parse_version<'de, D>(deserializer: D) -> std::result::Result where @@ -78,9 +78,9 @@ impl<'de> Deserialize<'de> for RemoteRelease { notes: release.notes, pub_date, data: if let Some(platforms) = release.platforms { - RemoteReleaseInner::Static { platforms } + RemoteReleaseData::Static { platforms } } else { - RemoteReleaseInner::Dynamic(ReleaseManifestPlatform { + RemoteReleaseData::Dynamic(ReleaseManifestPlatform { url: release.url.ok_or_else(|| { Error::custom("the `url` field was not set on the updater response") })?, diff --git a/crates/updater/src/error.rs b/crates/updater/src/error.rs index e6709272..503c21c9 100644 --- a/crates/updater/src/error.rs +++ b/crates/updater/src/error.rs @@ -68,4 +68,5 @@ pub enum Error { PersistError(#[from] tempfile::PersistError), } +/// Convenience alias for `cargo-packager-updater` crate Result type. pub type Result = std::result::Result; diff --git a/crates/updater/src/lib.rs b/crates/updater/src/lib.rs index 4d6f2315..57e7ad6e 100644 --- a/crates/updater/src/lib.rs +++ b/crates/updater/src/lib.rs @@ -3,6 +3,139 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: MIT +//! # cargo-packager-updater +//! +//! Updater for apps that was packaged by [`cargo-packager`](https://docs.rs/cargo-packager). +//! +//! ## Checking for an update +//! +//! you can check for an update using [`check_update`] function or construct a new [`Updater`] +//! using [`UpdaterBuilder`], both methods require the current version of the app and +//! a [`Config`] that specifies the endpoints to request updates from and the public key of the update signature. +//! +//! ```no_run +//! use cargo_packager_updater::{check_update, Config}; +//! +//! let config = Config { +//! endpoints: vec!["http://myserver.com/updates"], +//! pubkey: "", +//! ..Default::default() +//! }; +//! if let Some(update) = check_update("0.1.0", config).expect("failed while checking for update") { +//! update.download_and_install().expect("failed to download and install update"); +//! } else { +//! // there is no updates +//! } +//! +//! ``` +//! +//! ## Endpoints +//! +//! Each endpoint optionally could have `{{arch}}`, `{{target}}` or `{{current_version}}` +//! which will be detected and replaced with the appropriate value before making a request to the endpoint. +//! +//! - `{{current_version}}`: The version of the app that is requesting the update. +//! - `{{target}}`: The operating system name (one of `linux`, `windows` or `macos`). +//! - `{{arch}}`: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`). +//! +//! for example: +//! ```text +//! "https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}" +//! ``` +//! will turn into +//! ```text +//! "https://releases.myapp.com/windows/x86_64/0.1.0" +//! ``` +//! +//! if you need more data, you can set additional request headers [`UpdaterBuilder::header`] to your liking. +//! +//! ## Endpoint Response +//! +//! The updater expects the endpoint to respond with 2 possible reponses: +//! +//! 1. [`204 No Content`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.5) in case there is no updates available. +//! 2. [`200 OK`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.1) and a JSON response that could be either a JSON representing all available platform updates +//! or if using endpoints variables (see above) or a header to attach the current updater target, +//! then it can just return information for the requested target. +//! +//! The JSON response is expected to have these fields set: +//! +//! - `version`: must be a valid semver, with or without a leading `v``, meaning that both `1.0.0` and `v1.0.0` are valid. +//! - `url` or `platforms.[target].url`: must be a valid url to the update bundle +//! - `signature` or `platforms.[target].signature`: must be the content of the generated `.sig` file. The signature may change each time you run build your app so make sure to always update it. +//! - `format` or `platforms.[target].format`: must be one of `app`, `appimage`, `nsis` or `wix`. +//! +//!
+//!

+//! +//! Note +//!

+//! if using platforms object, each key is in the OS-ARCH format, where OS is one of linux, macos or windows, and ARCH is one of x86_64, aarch64, i686 or armv7, see the example below. +//!
+//!
+//! +//! It can also contain these optional fields: +//! - `notes`: Here you can add notes about the update, like release notes. +//! - `pub_date`: must be formatted according to [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339#section-5.8) if present. +//! +//! Here is an example of the two expected JSON formats: +//! +//! - **JSON for all platforms** +//! +//! ```json +//! { +//! "version": "v1.0.0", +//! "notes": "Test version", +//! "pub_date": "2020-06-22T19:25:57Z", +//! "platforms": { +//! "darwin-x86_64": { +//! "signature": "Content of app.tar.gz.sig", +//! "url": "https://github.com/username/reponame/releases/download/v1.0.0/app-x86_64.app.tar.gz", +//! "format": "app" +//! }, +//! "darwin-aarch64": { +//! "signature": "Content of app.tar.gz.sig", +//! "url": "https://github.com/username/reponame/releases/download/v1.0.0/app-aarch64.app.tar.gz", +//! "format": "app" +//! }, +//! "linux-x86_64": { +//! "signature": "Content of app.AppImage.sig", +//! "url": "https://github.com/username/reponame/releases/download/v1.0.0/app-amd64.AppImage.tar.gz", +//! "format": "appimage" +//! }, +//! "windows-x86_64": { +//! "signature": "Content of app-setup.exe.sig or app.msi.sig, depending on the chosen format", +//! "url": "https://github.com/username/reponame/releases/download/v1.0.0/app-x64-setup.nsis.zip", +//! "format": "nsis or wix depending on the chosen format" +//! } +//! } +//! } +//! ``` +//! +//! - **JSON for one platform** +//! +//! ```json +//! { +//! "version": "0.2.0", +//! "pub_date": "2020-09-18T12:29:53+01:00", +//! "url": "https://mycompany.example.com/myapp/releases/myrelease.tar.gz", +//! "signature": "Content of the relevant .sig file", +//! "format": "app or nsis or wix or appimage depending on the release target and the chosen format", +//! "notes": "These are some release notes" +//! } +//! ``` +//! +//! +//! ## Update install mode on Windows +//! +//! You can specify which install mode to use on Windows using [`WindowsConfig::install_mode`] which can be one of: +//! +//! - [`"Passive"`](WindowsUpdateInstallMode::Passive): There will be a small window with a progress bar. The update will be installed without requiring any user interaction. Generally recommended and the default mode. +//! - [`"BasicUi"`](WindowsUpdateInstallMode::BasicUi): There will be a basic user interface shown which requires user interaction to finish the installation. +//! - [`"Quiet"`](WindowsUpdateInstallMode::Quiet): There will be no progress feedback to the user. With this mode the installer cannot request admin privileges by itself so it only works in user-wide installations or when your app itself already runs with admin privileges. Generally not recommended. + +#![deny(missing_docs)] + use base64::Engine; use http::HeaderName; use minisign_verify::{PublicKey, Signature}; @@ -70,7 +203,7 @@ impl WindowsUpdateInstallMode { /// The updater configuration for Windows. #[derive(Debug, Default, PartialEq, Eq, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] -pub struct UpdaterWindowsConfig { +pub struct WindowsConfig { /// Additional arguments given to the NSIS or WiX installer. pub installer_args: Option>, /// The installation mode for the update on Windows. Defaults to `passive`. @@ -82,11 +215,18 @@ pub struct UpdaterWindowsConfig { #[serde(rename_all = "camelCase")] pub struct Config { /// The updater endpoints. + /// + /// Each endpoint optionally could have `{{arch}}`, `{{target}}` or `{{current_version}}` + /// which will be detected and replaced with the appropriate value before making a request to the endpoint. + /// + /// - `{{current_version}}`: The version of the app that is requesting the update. + /// - `{{target}}`: The operating system name (one of `linux`, `windows` or `macos`). + /// - `{{arch}}`: The architecture of the machine (one of `x86_64`, `i686`, `aarch64` or `armv7`). pub endpoints: Vec, /// Signature public key. pub pubkey: String, /// The Windows configuration for the updater. - pub windows: Option, + pub windows: Option, } /// Supported update format @@ -117,6 +257,7 @@ impl std::fmt::Display for UpdateFormat { } } +/// Information about a release #[derive(Debug, Deserialize, Serialize, Clone)] pub struct ReleaseManifestPlatform { /// Download URL for the platform @@ -127,11 +268,15 @@ pub struct ReleaseManifestPlatform { pub format: UpdateFormat, } +/// Information about a release data. #[derive(Debug, Deserialize, Serialize, Clone)] #[serde(untagged)] -pub enum RemoteReleaseInner { +pub enum RemoteReleaseData { + /// Dynamic release data based on the platform the update has been requested from. Dynamic(ReleaseManifestPlatform), + /// A map of release data for each platform, where the key is `-`. Static { + /// A map of release data for each platform, where the key is `-`. platforms: HashMap, }, } @@ -148,15 +293,15 @@ pub struct RemoteRelease { /// Release date. pub pub_date: Option, /// Release data. - pub data: RemoteReleaseInner, + pub data: RemoteReleaseData, } impl RemoteRelease { /// The release's download URL for the given target. pub fn download_url(&self, target: &str) -> Result<&Url> { match self.data { - RemoteReleaseInner::Dynamic(ref platform) => Ok(&platform.url), - RemoteReleaseInner::Static { ref platforms } => platforms + RemoteReleaseData::Dynamic(ref platform) => Ok(&platform.url), + RemoteReleaseData::Static { ref platforms } => platforms .get(target) .map_or(Err(Error::TargetNotFound(target.to_string())), |p| { Ok(&p.url) @@ -167,8 +312,8 @@ impl RemoteRelease { /// The release's signature for the given target. pub fn signature(&self, target: &str) -> Result<&String> { match self.data { - RemoteReleaseInner::Dynamic(ref platform) => Ok(&platform.signature), - RemoteReleaseInner::Static { ref platforms } => platforms + RemoteReleaseData::Dynamic(ref platform) => Ok(&platform.signature), + RemoteReleaseData::Static { ref platforms } => platforms .get(target) .map_or(Err(Error::TargetNotFound(target.to_string())), |platform| { Ok(&platform.signature) @@ -179,8 +324,8 @@ impl RemoteRelease { /// The release's update format for the given target. pub fn format(&self, target: &str) -> Result { match self.data { - RemoteReleaseInner::Dynamic(ref platform) => Ok(platform.format), - RemoteReleaseInner::Static { ref platforms } => platforms + RemoteReleaseData::Dynamic(ref platform) => Ok(platform.format), + RemoteReleaseData::Static { ref platforms } => platforms .get(target) .map_or(Err(Error::TargetNotFound(target.to_string())), |platform| { Ok(platform.format) @@ -189,6 +334,7 @@ impl RemoteRelease { } } +/// An [`Updater`] builder. pub struct UpdaterBuilder { current_version: Version, config: Config, @@ -200,6 +346,7 @@ pub struct UpdaterBuilder { } impl UpdaterBuilder { + /// Create a new updater builder request. pub fn new(current_version: Version, config: crate::Config) -> Self { Self { current_version, @@ -212,6 +359,7 @@ impl UpdaterBuilder { } } + /// A custom function to compare whether a new version exists or not. pub fn version_comparator bool + Send + Sync + 'static>( mut self, f: F, @@ -220,26 +368,31 @@ impl UpdaterBuilder { self } + /// Specify a public key to use when checking if the update is valid. pub fn pub_key(mut self, pub_key: impl Into) -> Self { self.config.pubkey = pub_key.into(); self } + /// Specify the target to request an update for. pub fn target(mut self, target: impl Into) -> Self { self.target.replace(target.into()); self } + /// Specify the endpoints where an update will be requested from. pub fn endpoints(mut self, endpoints: Vec) -> Self { self.config.endpoints = endpoints; self } + /// Specify the path to the current executable where the updater will try to update in the same directory. pub fn executable_path>(mut self, p: P) -> Self { self.executable_path.replace(p.as_ref().into()); self } + /// Add a header to the updater request. pub fn header(mut self, key: K, value: V) -> Result where HeaderName: TryFrom, @@ -255,11 +408,13 @@ impl UpdaterBuilder { Ok(self) } + /// Specify a timeout for the updater request. pub fn timeout(mut self, timeout: Duration) -> Self { self.timeout = Some(timeout); self } + /// Specify custom installer args on Windows. pub fn installer_args(mut self, args: I) -> Self where I: IntoIterator, @@ -277,6 +432,7 @@ impl UpdaterBuilder { self } + /// Build the updater. pub fn build(self) -> Result { if self.config.endpoints.is_empty() { return Err(Error::EmptyEndpoints); @@ -324,6 +480,7 @@ impl UpdaterBuilder { } } +/// A type that can check for updates and created by [`UpdaterBuilder`]. pub struct Updater { config: Config, current_version: Version, @@ -339,6 +496,7 @@ pub struct Updater { } impl Updater { + /// Check for an update. Returns `None` if an update was not found, otherwise it will be `Some`. pub fn check(&self) -> Result> { // we want JSON only let mut headers = self.headers.clone(); @@ -443,6 +601,7 @@ impl Updater { } } +/// Information about an update and associted methods to perform the update. #[derive(Debug, Clone)] pub struct Update { /// Config used to check for this update. @@ -475,10 +634,31 @@ impl Update { /// Downloads the updater package, verifies it then return it as bytes. /// /// Use [`Update::install`] to install it - pub fn download), D: FnOnce()>( + pub fn download(&self) -> Result> { + self.download_extended_inner( + None::)>>, + None::>, + ) + } + + /// Downloads the updater package, verifies it then return it as bytes. + /// + /// Takes two callbacks, the first will be excuted when receiveing each chunk + /// while the second will be called only once when the download finishes. + /// + /// Use [`Update::install`] to install it + pub fn download_extended), D: FnOnce()>( &self, on_chunk: C, on_download_finish: D, + ) -> Result> { + self.download_extended_inner(Some(on_chunk), Some(on_download_finish)) + } + + fn download_extended_inner), D: FnOnce()>( + &self, + on_chunk: Option, + on_download_finish: Option, ) -> Result> { // set our headers let mut headers = self.headers.clone(); @@ -501,13 +681,15 @@ impl Update { struct DownloadProgress)> { content_length: Option, inner: R, - on_chunk: C, + on_chunk: Option, } impl)> Read for DownloadProgress { fn read(&mut self, buf: &mut [u8]) -> std::io::Result { self.inner.read(buf).map(|n| { - (self.on_chunk)(n, self.content_length); + if let Some(on_chunk) = &self.on_chunk { + (on_chunk)(n, self.content_length); + } n }) } @@ -535,7 +717,9 @@ impl Update { let mut buffer = Vec::new(); let _ = std::io::copy(&mut source, &mut buffer)?; - on_download_finish(); + if let Some(on_download_finish) = on_download_finish { + on_download_finish(); + } let mut update_buffer = Cursor::new(&buffer); @@ -550,12 +734,21 @@ impl Update { } /// Downloads and installs the updater package - pub fn download_and_install), D: FnOnce()>( + pub fn download_and_install(&self) -> Result<()> { + let bytes = self.download()?; + self.install(bytes) + } + + /// Downloads and installs the updater package + /// + /// Takes two callbacks, the first will be excuted when receiveing each chunk + /// while the second will be called only once when the download finishes. + pub fn download_and_install_extended), D: FnOnce()>( &self, on_chunk: C, on_download_finish: D, ) -> Result<()> { - let bytes = self.download(on_chunk, on_download_finish)?; + let bytes = self.download_extended(on_chunk, on_download_finish)?; self.install(bytes) } @@ -820,6 +1013,7 @@ pub fn check_update(current_version: Version, config: crate::Config) -> Result Option { if let (Some(target), Some(arch)) = (get_updater_target(), get_updater_arch()) { Some(format!("{target}-{arch}")) @@ -855,7 +1049,7 @@ pub(crate) fn get_updater_arch() -> Option<&'static str> { } #[cfg(any(windows, target_os = "macos"))] -pub fn extract_path_from_executable(executable_path: &Path) -> Result { +fn extract_path_from_executable(executable_path: &Path) -> Result { // Return the path of the current executable by default // Example C:\Program Files\My App\ let extract_path = executable_path @@ -890,7 +1084,7 @@ pub fn extract_path_from_executable(executable_path: &Path) -> Result { // by our tests in the bundler // // NOTE: The buffer position is not reset. -pub fn verify_signature( +fn verify_signature( archive_reader: &mut R, release_signature: &str, pub_key: &str, diff --git a/crates/updater/tests/app/src/main.rs b/crates/updater/tests/app/src/main.rs index 590703cb..cc6fa519 100644 --- a/crates/updater/tests/app/src/main.rs +++ b/crates/updater/tests/app/src/main.rs @@ -46,7 +46,7 @@ fn main() { match updater.check() { Ok(Some(update)) => { - if let Err(e) = update.download_and_install(|_, _| {}, || {}) { + if let Err(e) = update.download_and_install() { println!("{e}"); std::process::exit(1); } diff --git a/examples/tauri/src/main.rs b/examples/tauri/src/main.rs index d0f097e0..2c8c0f16 100644 --- a/examples/tauri/src/main.rs +++ b/examples/tauri/src/main.rs @@ -67,7 +67,7 @@ fn download_update(app: AppHandle) -> Result<(), ()> { std::thread::spawn(move || { let update = app.state::(); let update_bytes = update - .download( + .download_extended( move |chunk_len, content_len| { app_1 .emit_all( diff --git a/package.json b/package.json index 177adfec..4d5928b3 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,5 @@ }, "devDependencies": { "prettier": "^3.1.0" - }, - "engines": { - "node": "^18.16.0", - "pnpm": "^8.4.0" } } From ffcba00c2c85f0fe40ce242b4022486505b7f54f Mon Sep 17 00:00:00 2001 From: amr-crabnebula Date: Tue, 12 Dec 2023 10:23:36 +0200 Subject: [PATCH 4/5] add install instructions --- bindings/packager/nodejs/README.md | 2 -- bindings/updater/nodejs/README.md | 9 +++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/bindings/packager/nodejs/README.md b/bindings/packager/nodejs/README.md index 341c0d03..b326e71f 100644 --- a/bindings/packager/nodejs/README.md +++ b/bindings/packager/nodejs/README.md @@ -15,8 +15,6 @@ It also has a compatible updater through [@crabnebula/updater](https://www.npmjs - NSIS (.exe) - MSI using WiX Toolset (.msi) -## Rust - ### CLI The packager is distrubuted on NPM as a CLI, you can install it: diff --git a/bindings/updater/nodejs/README.md b/bindings/updater/nodejs/README.md index f8080767..a90e543c 100644 --- a/bindings/updater/nodejs/README.md +++ b/bindings/updater/nodejs/README.md @@ -2,6 +2,15 @@ Updater for apps that was packaged by [`@crabnebula/packager`](https://www.npmjs.com/package/@crabnebula/packager). +```sh +# pnpm +pnpm add @crabnebula/updater +# pnpm +yarn add @crabnebula/updater +# npm +npm i @crabnebula/updater +``` + ## Checking for an update you can check for an update using `checkUpdate` function which require the current version of the app and From 15ba065575b1befd104b489341f5f7de57d39788 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 12 Dec 2023 10:55:11 +0200 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Simon Hausmann --- README.md | 2 +- bindings/packager/nodejs/README.md | 2 +- crates/packager/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c7c95466..c2921814 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ It also has a compatible updater through [cargo-packager-updater](./crates/updat ### CLI -The packager is distrubuted on crates.io as a cargo subcommand, you can install it using cargo: +The packager is distributed on crates.io as a cargo subcommand, you can install it using cargo: ```sh cargo install cargo-packager --locked diff --git a/bindings/packager/nodejs/README.md b/bindings/packager/nodejs/README.md index b326e71f..63e730b4 100644 --- a/bindings/packager/nodejs/README.md +++ b/bindings/packager/nodejs/README.md @@ -17,7 +17,7 @@ It also has a compatible updater through [@crabnebula/updater](https://www.npmjs ### CLI -The packager is distrubuted on NPM as a CLI, you can install it: +The packager is distributed on NPM as a CLI, you can install it: ```sh # pnpm diff --git a/crates/packager/README.md b/crates/packager/README.md index 9fa477d2..353fd34c 100644 --- a/crates/packager/README.md +++ b/crates/packager/README.md @@ -19,7 +19,7 @@ It also has a compatible updater through [cargo-packager-updater](https://docs.r ### CLI -The packager is distrubuted on crates.io as a cargo subcommand, you can install it using cargo: +The packager is distributed on crates.io as a cargo subcommand, you can install it using cargo: ```sh cargo install cargo-packager --locked