Skip to content

Commit

Permalink
Merge branch 'main' of github.com-crabnebula-dev:crabnebula-dev/cargo…
Browse files Browse the repository at this point in the history
…-packager
  • Loading branch information
amr-crabnebula committed Dec 12, 2023
2 parents 506a294 + c28b918 commit 6cffaf6
Show file tree
Hide file tree
Showing 17 changed files with 681 additions and 178 deletions.
99 changes: 49 additions & 50 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,10 @@

<img src=".github/splash.png" alt="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)
Expand All @@ -40,30 +17,65 @@ cargo install cargo-packager --locked
- NSIS (.exe)
- MSI using WiX Toolset (.msi)

## Rust

### CLI

The packager is distributed 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.

Expand All @@ -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.
63 changes: 63 additions & 0 deletions bindings/packager/nodejs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# @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)

### CLI

The packager is distributed 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.
7 changes: 7 additions & 0 deletions bindings/packager/nodejs/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -631,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
Expand Down
138 changes: 138 additions & 0 deletions bindings/updater/nodejs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# @crabnebula/updater

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
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: "<pubkey here>",
});

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.
6 changes: 3 additions & 3 deletions bindings/updater/nodejs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
6 changes: 3 additions & 3 deletions bindings/updater/nodejs/src/from_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ impl From<cargo_packager_updater::WindowsUpdateInstallMode> for WindowsUpdateIns
}
}

impl From<cargo_packager_updater::UpdaterWindowsConfig> for UpdaterWindowsOptions {
fn from(value: cargo_packager_updater::UpdaterWindowsConfig) -> Self {
impl From<cargo_packager_updater::WindowsConfig> 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<UpdaterWindowsOptions> for cargo_packager_updater::UpdaterWindowsConfig {
impl From<UpdaterWindowsOptions> for cargo_packager_updater::WindowsConfig {
fn from(value: UpdaterWindowsOptions) -> Self {
Self {
installer_args: value.installer_args,
Expand Down
Loading

0 comments on commit 6cffaf6

Please sign in to comment.