├── .npmrc
├── dist
└── package.json
├── .prettierrc.json
├── .github
├── fixtures
│ ├── example-with-tauri-v2
│ │ ├── .gitignore
│ │ ├── src-tauri
│ │ │ ├── build.rs
│ │ │ ├── icons
│ │ │ │ ├── 32x32.png
│ │ │ │ ├── icon.icns
│ │ │ │ ├── icon.ico
│ │ │ │ ├── icon.png
│ │ │ │ ├── 128x128.png
│ │ │ │ ├── 128x128@2x.png
│ │ │ │ ├── StoreLogo.png
│ │ │ │ ├── Square30x30Logo.png
│ │ │ │ ├── Square44x44Logo.png
│ │ │ │ ├── Square71x71Logo.png
│ │ │ │ ├── Square89x89Logo.png
│ │ │ │ ├── Square107x107Logo.png
│ │ │ │ ├── Square142x142Logo.png
│ │ │ │ ├── Square150x150Logo.png
│ │ │ │ ├── Square284x284Logo.png
│ │ │ │ └── Square310x310Logo.png
│ │ │ ├── src
│ │ │ │ ├── main.rs
│ │ │ │ └── lib.rs
│ │ │ ├── .gitignore
│ │ │ ├── Cargo.toml
│ │ │ └── tauri.conf.json
│ │ ├── dist
│ │ │ └── index.html
│ │ └── package.json
│ └── .cargo
│ │ └── config.toml
├── workflows
│ ├── covector-status.yml
│ ├── ci.yml
│ ├── covector-version-or-publish.yml
│ └── test-action.yml
└── sponsors
│ └── crabnebula.svg
├── .prettierignore
├── .changes
├── smol-toml.md
├── releaseassetnamepattern.md
├── rm-keep-universal.md
├── drop-tauri-v1.md
├── fix-workspace.md
├── remove-init-project.md
├── npm-exec.md
├── fail-if-non-draft.md
├── rename-includeupdaterjson.md
├── apptargz-version.md
├── missing-runner.md
├── overwrite-universal.md
├── mobile.md
├── includedebug.md
├── release-asset-label.md
├── upload-artifacts.md
├── readme.md
└── config.json
├── renovate.json
├── tsconfig.json
├── eslint.config.js
├── .gitignore
├── LICENSE
├── src
├── upload-workflow-artifacts.ts
├── runner.ts
├── types.d.ts
├── inputs.ts
├── upload-release-assets.ts
├── create-release.ts
├── index.ts
├── config.ts
├── upload-version-json.ts
├── build.ts
└── utils.ts
├── package.json
├── examples
├── test-build-only.yml
├── publish-to-auto-release.yml
├── publish-to-auto-release-universal-macos-app-with-signing-certificate.yml
└── publish-to-manual-release.yml
├── action.yml
├── README.md
└── CHANGELOG.md
/.npmrc:
--------------------------------------------------------------------------------
1 | auto-install-peers=true
--------------------------------------------------------------------------------
/dist/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "module"
3 | }
4 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true
3 | }
4 |
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/.gitignore:
--------------------------------------------------------------------------------
1 | dist/index.tauri.html
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist/
2 | .github/fixtures/
3 | __tests__
4 | node_modules/
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/build.rs:
--------------------------------------------------------------------------------
1 | fn main() {
2 | tauri_build::build()
3 | }
4 |
--------------------------------------------------------------------------------
/.changes/smol-toml.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: patch
3 | ---
4 |
5 | Switch from unmaintained `@iarna/toml` to `smol-toml`. No user-facing changes.
6 |
--------------------------------------------------------------------------------
/.changes/releaseassetnamepattern.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: major
3 | ---
4 |
5 | **Breaking Change**: Renamed `assetNamePattern` to `releaseAssetNamePattern`.
6 |
--------------------------------------------------------------------------------
/.changes/rm-keep-universal.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: major
3 | ---
4 |
5 | **Breaking Change**: Removed `updaterJsonKeepUniversal`. This is now always enabled.
6 |
--------------------------------------------------------------------------------
/.changes/drop-tauri-v1.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: major
3 | ---
4 |
5 | **Breaking Change**: Drop support for Tauri v1 and unstable v2 (alpha, beta, rc) versions.
6 |
--------------------------------------------------------------------------------
/.changes/fix-workspace.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: patch
3 | ---
4 |
5 | The frontend lockfile detection will now move up the file tree to fix issues in workspaces.
6 |
--------------------------------------------------------------------------------
/.changes/remove-init-project.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: major
3 | ---
4 |
5 | **Breaking Change**: Removed the feature to automatically initialize a Tauri project.
6 |
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello!
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.changes/npm-exec.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: patch
3 | ---
4 |
5 | Use `npm exec` instead of `npm run` if `@tauri-apps/cli` was detected with no `tauri` script in `package.json`.
6 |
--------------------------------------------------------------------------------
/.changes/fail-if-non-draft.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: major
3 | ---
4 |
5 | **Breaking Change**: The action will now fail if `draft: true` is set but the relevant release is not a draft.
6 |
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/32x32.png
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/icon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/icon.icns
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/icon.ico
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/icon.png
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/src/main.rs:
--------------------------------------------------------------------------------
1 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
2 |
3 | fn main() {
4 | test_app_lib::run()
5 | }
6 |
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/128x128.png
--------------------------------------------------------------------------------
/.changes/rename-includeupdaterjson.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: major
3 | ---
4 |
5 | **Breaking Change**: Renamed `includeUpdaterJson` to `uploadUpdaterJson` for consistency with other similar options.
6 |
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/128x128@2x.png
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/StoreLogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/StoreLogo.png
--------------------------------------------------------------------------------
/.changes/apptargz-version.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: major
3 | ---
4 |
5 | **Breaking Chagne**: `.app.tar.gz` & `.app.tar.gz.sig` files will now include the app version like all other bundles/installers.
6 |
--------------------------------------------------------------------------------
/.changes/missing-runner.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: patch
3 | ---
4 |
5 | Improved runner detection to prevent silent fails if runner (npm, pnpm, yarn, bun) was not installed while a lockfile was present.
6 |
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square30x30Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square30x30Logo.png
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square44x44Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square44x44Logo.png
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square71x71Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square71x71Logo.png
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square89x89Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square89x89Logo.png
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square107x107Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square107x107Logo.png
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square142x142Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square142x142Logo.png
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square150x150Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square150x150Logo.png
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square284x284Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square284x284Logo.png
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square310x310Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tauri-apps/tauri-action/HEAD/.github/fixtures/example-with-tauri-v2/src-tauri/icons/Square310x310Logo.png
--------------------------------------------------------------------------------
/.changes/overwrite-universal.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: patch
3 | ---
4 |
5 | Fiixed an issue that caused outdated signatures for macos universal builds in latest.json when re-running the action on the same release multiple times.
6 |
--------------------------------------------------------------------------------
/.changes/mobile.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: patch
3 | ---
4 |
5 | Added initial Android & iOS support. This only includes building (=== running `tauri android|ios build`), installing dependencies and uploading to stores must be done manually.
6 |
--------------------------------------------------------------------------------
/.github/fixtures/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | # Forcing examples to share a target dir without a cargo workspace.
3 | # This also fixes `tauri init` problems with the `rust-cache` action creating `src-tauri/target/`.
4 | target-dir = "./target"
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 | WixTools
5 |
6 | # These are backup files generated by rustfmt
7 | **/*.rs.bk
8 |
9 | config.json
10 | bundle.json
11 |
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/src/lib.rs:
--------------------------------------------------------------------------------
1 | #[cfg_attr(mobile, tauri::mobile_entry_point)]
2 | pub fn run() {
3 | tauri::Builder::default()
4 | .run(tauri::generate_context!())
5 | .expect("error while running tauri application");
6 | }
7 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:base"],
3 | "postUpdateOptions": ["pnpmDedupe"],
4 | "packageRules": [
5 | {
6 | "matchPackageNames": ["*"],
7 | "semanticCommitType": "chore",
8 | "minimumReleaseAge": "3 days"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.changes/includedebug.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: major
3 | ---
4 |
5 | **Breaking Change**: Remove `includeRelease` and `includeDebug`. You can switch to debug builds via `args: --debug`. To upload release _and_ debug builds, run `tauri-action` twice, preferably in a job matrix for concurrent builds.
6 |
--------------------------------------------------------------------------------
/.changes/release-asset-label.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: major
3 | ---
4 |
5 | The upload release assets will now have the original file as its `label` which will show as the filename on the GitHub Release page and will be used internally to update assets on reruns and to get the download urls for latest.json
6 |
--------------------------------------------------------------------------------
/.changes/upload-artifacts.md:
--------------------------------------------------------------------------------
1 | ---
2 | action: minor
3 | ---
4 |
5 | Added built-in variant of [`actions/upload-artifact`](https://github.com/actions/upload-artifact/) to easily upload each bundle as a seperate archive. This may be removed once https://github.com/actions/upload-artifact/issues/331 lands.
6 |
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-with-tauri",
3 | "version": "0.1.1",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "MIT"
11 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "module": "esnext",
5 | "target": "es2021",
6 | "pretty": true,
7 | "esModuleInterop": true,
8 | "resolveJsonModule": true,
9 | "moduleResolution": "node",
10 | "importHelpers": true
11 | },
12 | "include": ["./src"]
13 | }
14 |
--------------------------------------------------------------------------------
/.github/workflows/covector-status.yml:
--------------------------------------------------------------------------------
1 | name: covector status
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | covector:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - uses: actions/checkout@v6
11 | with:
12 | fetch-depth: 0
13 | - name: covector status
14 | uses: jbolda/covector/packages/action@covector-v0
15 | with:
16 | command: 'status'
17 | token: ${{ secrets.GITHUB_TOKEN }}
18 | comment: true
19 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import eslint from '@eslint/js';
2 | import eslintConfigPrettier from 'eslint-config-prettier';
3 | import tseslint from 'typescript-eslint';
4 |
5 | export default tseslint.config(
6 | {
7 | ignores: ['dist', 'eslint.config.js'],
8 | },
9 | eslint.configs.recommended,
10 | eslintConfigPrettier,
11 | ...tseslint.configs.recommendedTypeChecked,
12 | {
13 | languageOptions: {
14 | parserOptions: { project: true, tsconfigRootDir: import.meta.dirname },
15 | },
16 | },
17 | );
18 |
--------------------------------------------------------------------------------
/.changes/readme.md:
--------------------------------------------------------------------------------
1 | # Changes
2 | ##### via https://github.com/jbolda/covector
3 |
4 | As you create PRs and make changes that require a version bump, please add a new markdown file in this folder. You do not note the version *number*, but rather the type of bump that you expect: major, minor, or patch. The filename is not important, as long as it is a `.md`, but we recommend it represents the overall change for our sanity.
5 |
6 | When you select the version bump required, you do *not* need to consider depedencies. Only note the package with the actual change, and any packages that depend on that package will be bumped automatically in the process.
7 |
8 | Use the following format:
9 | ```md
10 | ---
11 | "tauri.js": patch
12 | "tauri": minor
13 | ---
14 |
15 | Change summary goes here
16 |
17 | ```
--------------------------------------------------------------------------------
/.changes/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "gitSiteUrl": "https://www.github.com/tauri-apps/tauri-action/",
3 | "packages": {
4 | "action": {
5 | "path": ".",
6 | "publish": [
7 | "git tag -a -m \"v${ pkgFile.version }\" v${ pkgFile.version }",
8 | "git tag -a -m \"v${ pkgFile.versionMajor }.${ pkgFile.versionMinor }\" v${ pkgFile.versionMajor }.${ pkgFile.versionMinor } -f",
9 | "git tag -a -m \"v${ pkgFile.versionMajor }\" v${ pkgFile.versionMajor } -f",
10 | "git push --tags -f"
11 | ],
12 | "getPublishedVersion": "git tag -l \"v${ pkgFile.version }\" | sed s/v//",
13 | "postversion": ["pnpm install --no-optional", "pnpm build"],
14 | "assets": false,
15 | "createRelease": true,
16 | "version": true
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | .DS_Store
10 |
11 | # Diagnostic reports (https://nodejs.org/api/report.html)
12 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
13 |
14 | # Dependency directories
15 | node_modules/
16 | jspm_packages/
17 |
18 | # TypeScript v1 declaration files
19 | typings/
20 |
21 | # TypeScript cache
22 | *.tsbuildinfo
23 |
24 | # Optional cache directory
25 | .npm
26 | .yarn
27 | target
28 |
29 | # Optional eslint cache
30 | .eslintcache
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 |
35 | # Output of 'npm pack'
36 | *.tgz
37 |
38 | # Yarn Integrity file
39 | .yarn-integrity
40 |
41 | # dotenv environment variables file
42 | .env
43 | .env.test
44 |
45 | # lockfiles, as a lib we don't need to lock
46 | yarn.lock
47 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request:
6 | push:
7 | branches:
8 | - dev
9 | - 'renovate/**'
10 | - '!renovate/lock-file-maintenance'
11 |
12 | jobs:
13 | CI:
14 | continue-on-error: true
15 |
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v6
19 |
20 | - name: install pnpm
21 | uses: pnpm/action-setup@v4
22 | with:
23 | version: 10.x.x
24 |
25 | - name: Setup Node
26 | uses: actions/setup-node@v6
27 | with:
28 | node-version: lts/*
29 | cache: pnpm
30 |
31 | - name: install dependencies
32 | run: pnpm install
33 |
34 | - name: check formatting
35 | run: pnpm format:check
36 |
37 | - name: run linter
38 | run: pnpm lint
39 |
40 | - name: build
41 | run: pnpm build
42 |
43 | #- name: run tests
44 | # run: pnpm test
45 |
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "test-app"
3 | version = "0.1.0"
4 | description = "A Tauri App"
5 | authors = ["you"]
6 | license = ""
7 | repository = ""
8 | edition = "2021"
9 |
10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11 |
12 | [lib]
13 | # The `_lib` suffix may seem redundant but it is necessary
14 | # to make the lib name unique and wouldn't conflict with the bin name.
15 | # This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
16 | name = "test_app_lib"
17 | crate-type = ["staticlib", "cdylib", "rlib"]
18 |
19 | [build-dependencies]
20 | tauri-build = { version = "2", features = [] }
21 |
22 | [dependencies]
23 | tauri = { version = "2", features = [] }
24 | serde = { version = "1", features = ["derive"] }
25 | serde_json = "1"
26 |
27 | [profile.dev]
28 | opt-level = 0
29 | lto = false
30 | debug = false
31 |
32 | [profile.release]
33 | opt-level = 0
34 | lto = false
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2018 GitHub, Inc. and contributors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
--------------------------------------------------------------------------------
/.github/fixtures/example-with-tauri-v2/src-tauri/tauri.conf.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.tauri.app/config/2",
3 | "productName": "TauriExample App (v2)",
4 | "version": "0.1.2",
5 | "identifier": "com.tauri.actiontest",
6 | "build": {
7 | "frontendDist": "../dist"
8 | },
9 | "app": {
10 | "windows": [
11 | {
12 | "title": "Tauri App",
13 | "width": 800,
14 | "height": 600
15 | }
16 | ],
17 | "security": {
18 | "csp": null
19 | }
20 | },
21 | "bundle": {
22 | "active": true,
23 | "createUpdaterArtifacts": true,
24 | "targets": "all",
25 | "icon": [
26 | "icons/32x32.png",
27 | "icons/128x128.png",
28 | "icons/128x128@2x.png",
29 | "icons/icon.icns",
30 | "icons/icon.ico"
31 | ],
32 | "windows": {
33 | "nsis": {
34 | "compression": "none"
35 | }
36 | },
37 | "linux": {
38 | "rpm": {
39 | "compression": {
40 | "type": "none"
41 | }
42 | }
43 | }
44 | },
45 | "plugins": {
46 | "updater": {
47 | "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK"
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/upload-workflow-artifacts.ts:
--------------------------------------------------------------------------------
1 | import { dirname } from 'node:path';
2 |
3 | import GHArtifact from '@actions/artifact';
4 | import { globbySync } from 'globby';
5 |
6 | import { retryAttempts, workflowArtifactNamePattern } from './inputs';
7 | import { getAssetName, retry } from './utils';
8 |
9 | import type { Artifact } from './types';
10 |
11 | export async function uploadWorkflowArtifacts(artifacts: Artifact[]) {
12 | for (const artifact of artifacts) {
13 | if (artifact.workflowArtifactName) {
14 | let workflowArtifactName = artifact.workflowArtifactName;
15 |
16 | workflowArtifactName = getAssetName(
17 | artifact,
18 | workflowArtifactNamePattern,
19 | );
20 |
21 | let paths = [artifact.path];
22 | if (artifact.ext === '.app') {
23 | paths = globbySync('**/*', { cwd: artifact.path, absolute: true });
24 | }
25 | console.log(
26 | "Handing it off to GitHub's uploadArtifact function. This will print a few unmanaged logs.",
27 | );
28 | await retry(
29 | () =>
30 | GHArtifact.uploadArtifact(
31 | workflowArtifactName,
32 | paths,
33 | dirname(artifact.path),
34 | {
35 | compressionLevel: artifact.ext === '.app' ? 6 : 0,
36 | },
37 | ),
38 | retryAttempts,
39 | );
40 | console.log('Workflow artifacts uploads DONE!');
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tauri-action",
3 | "version": "0.6.0",
4 | "description": "Tauri GitHub Action",
5 | "contributors": [
6 | "Tauri Programme within The Commons Conservancy"
7 | ],
8 | "license": "MIT",
9 | "main": "dist/index.js",
10 | "type": "module",
11 | "scripts": {
12 | "build": "ncc build src/index.ts -o dist",
13 | "lint": "eslint .",
14 | "format": "prettier --write src/** action.yml README.md",
15 | "format:check": "prettier --check src/** action.yml README.md"
16 | },
17 | "repository": {
18 | "type": "git",
19 | "url": "git+https://github.com/tauri-apps/tauri-action.git"
20 | },
21 | "keywords": [
22 | "actions",
23 | "tauri"
24 | ],
25 | "minimumReleaseAge": 4320,
26 | "dependencies": {
27 | "@actions/artifact": "5.0.1",
28 | "@actions/core": "2.0.1",
29 | "@actions/github": "6.0.1",
30 | "execa": "9.6.1",
31 | "find-up-simple": "1.0.1",
32 | "globby": "16.0.0",
33 | "json5": "2.2.3",
34 | "smol-toml": "1.5.2",
35 | "string-argv": "0.3.2",
36 | "tslib": "2.8.1"
37 | },
38 | "devDependencies": {
39 | "@eslint/js": "9.39.1",
40 | "@types/node": "24.10.3",
41 | "@vercel/ncc": "0.38.4",
42 | "covector": "0.12.4",
43 | "eslint": "9.39.1",
44 | "eslint-config-prettier": "10.1.8",
45 | "prettier": "3.7.4",
46 | "typescript": "5.9.3",
47 | "typescript-eslint": "8.48.0"
48 | },
49 | "engines": {
50 | "pnpm": "^10.16.0",
51 | "node": ">=20"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/.github/workflows/covector-version-or-publish.yml:
--------------------------------------------------------------------------------
1 | name: covector version or publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - dev
7 | - legacy/v0
8 | - legacy/v0.5
9 |
10 | jobs:
11 | covector:
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v6
16 | with:
17 | fetch-depth: 0
18 | - uses: actions/setup-node@v6
19 | with:
20 | node-version: lts/*
21 | registry-url: 'https://registry.npmjs.org'
22 | - name: install pnpm
23 | uses: pnpm/action-setup@v4
24 | with:
25 | version: 10.x.x
26 | - name: git config
27 | run: |
28 | git config --global user.name "${{ github.event.pusher.name }}"
29 | git config --global user.email "${{ github.event.pusher.email }}"
30 | - name: covector version-or-publish
31 | uses: jbolda/covector/packages/action@covector-v0
32 | id: covector
33 | env:
34 | NODE_AUTH_TOKEN: ${{ secrets.ORG_NPM_TOKEN }}
35 | with:
36 | token: ${{ secrets.GITHUB_TOKEN }}
37 | command: 'version-or-publish'
38 | createRelease: true
39 | recognizeContributors: true
40 |
41 | - name: Create Pull Request With Versions Bumped
42 | if: steps.covector.outputs.commandRan == 'version'
43 | uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # 7.0.9
44 | with:
45 | token: ${{ secrets.GITHUB_TOKEN }}
46 | branch: ci/release-${{ github.ref_name }}
47 | title: Apply Version Updates From Current Changes
48 | commit-message: 'apply version updates'
49 | labels: 'version updates'
50 | body: ${{ steps.covector.outputs.change }}
51 | sign-commits: true
52 |
--------------------------------------------------------------------------------
/examples/test-build-only.yml:
--------------------------------------------------------------------------------
1 | name: 'test-on-pr'
2 |
3 | on: [pull_request]
4 |
5 | # This workflow will build your tauri app without uploading it anywhere.
6 |
7 | jobs:
8 | test-tauri:
9 | strategy:
10 | fail-fast: false
11 | matrix:
12 | include:
13 | - platform: 'macos-latest' # for Arm based macs (M1 and above).
14 | args: '--target aarch64-apple-darwin'
15 | - platform: 'macos-latest' # for Intel based macs.
16 | args: '--target x86_64-apple-darwin'
17 | - platform: 'ubuntu-22.04' # for Tauri v1 you could replace this with ubuntu-20.04.
18 | args: ''
19 | - platform: 'windows-latest'
20 | args: ''
21 |
22 | runs-on: ${{ matrix.platform }}
23 | steps:
24 | - uses: actions/checkout@v4
25 |
26 | - name: setup node
27 | uses: actions/setup-node@v4
28 | with:
29 | node-version: lts/*
30 |
31 | - name: install Rust stable
32 | uses: dtolnay/rust-toolchain@stable
33 | with:
34 | # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
35 | targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
36 |
37 | - name: install dependencies (ubuntu only)
38 | if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
39 | run: |
40 | sudo apt-get update
41 | sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
42 |
43 | - name: install frontend dependencies
44 | run: yarn install # change this to npm, pnpm or bun depending on which one you use.
45 |
46 | # If tagName and releaseId are omitted tauri-action will only build the app and won't try to upload any assets.
47 | - uses: tauri-apps/tauri-action@v1
48 | env:
49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
50 | with:
51 | args: ${{ matrix.args }}
52 |
--------------------------------------------------------------------------------
/examples/publish-to-auto-release.yml:
--------------------------------------------------------------------------------
1 | name: 'publish'
2 |
3 | on:
4 | push:
5 | branches:
6 | - release
7 |
8 | # This is the example from the readme.
9 | # On each push to the `release` branch it will create or update a GitHub release, build your app, and upload the artifacts to the release.
10 |
11 | jobs:
12 | publish-tauri:
13 | permissions:
14 | contents: write
15 | strategy:
16 | fail-fast: false
17 | matrix:
18 | include:
19 | - platform: 'macos-latest' # for Arm based macs (M1 and above).
20 | args: '--target aarch64-apple-darwin'
21 | - platform: 'macos-latest' # for Intel based macs.
22 | args: '--target x86_64-apple-darwin'
23 | - platform: 'ubuntu-22.04' # for Tauri v1 you could replace this with ubuntu-20.04.
24 | args: ''
25 | - platform: 'windows-latest'
26 | args: ''
27 |
28 | runs-on: ${{ matrix.platform }}
29 | steps:
30 | - uses: actions/checkout@v4
31 |
32 | - name: setup node
33 | uses: actions/setup-node@v4
34 | with:
35 | node-version: lts/*
36 |
37 | - name: install Rust stable
38 | uses: dtolnay/rust-toolchain@stable
39 | with:
40 | # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
41 | targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
42 |
43 | - name: install dependencies (ubuntu only)
44 | if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
45 | run: |
46 | sudo apt-get update
47 | sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
48 |
49 | - name: install frontend dependencies
50 | run: yarn install # change this to npm, pnpm or bun depending on which one you use.
51 |
52 | - uses: tauri-apps/tauri-action@v1
53 | env:
54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55 | with:
56 | tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
57 | releaseName: 'App v__VERSION__'
58 | releaseBody: 'See the assets to download this version and install.'
59 | releaseDraft: true
60 | prerelease: false
61 | args: ${{ matrix.args }}
62 |
--------------------------------------------------------------------------------
/src/runner.ts:
--------------------------------------------------------------------------------
1 | import { projectPath, tauriScript } from './inputs';
2 | import {
3 | execCommand,
4 | hasDependency,
5 | hasTauriScript,
6 | retry,
7 | usesBun,
8 | usesNpm,
9 | usesPnpm,
10 | usesYarn,
11 | } from './utils';
12 |
13 | class Runner {
14 | // Could be "npm", "yarn", "pnpm", "bun", "cargo", "path/to/tauri-cli/binary" or "tauri"
15 | bin: string;
16 | // could be ["tauri"], ["run", "tauri"], ["some package.json script"], ["run", "some package.json script"] or []
17 | tauriScript: string[];
18 |
19 | constructor(bin: string, tauriScript?: string[]) {
20 | this.bin = bin;
21 | this.tauriScript = tauriScript || [];
22 | }
23 |
24 | async execTauriCommand(
25 | command: string[],
26 | commandOptions: string[],
27 | cwd?: string,
28 | env?: Record,
29 | retryAttempts: number = 0,
30 | ): Promise {
31 | const args = [...this.tauriScript, ...command];
32 |
33 | if (this.bin === 'npm' && commandOptions.length) {
34 | args.push('--');
35 | }
36 |
37 | args.push(...commandOptions);
38 |
39 | return retry(
40 | () => execCommand(this.bin, args, { cwd }, env),
41 | retryAttempts,
42 | ) as Promise;
43 | }
44 | }
45 |
46 | async function getRunner(): Promise {
47 | if (tauriScript) {
48 | console.log('`tauriScript` set. Skipping cli verification.');
49 | // FIXME: This will also split file paths with spaces.
50 | const [runnerCommand, ...runnerArgs] = tauriScript.split(' ');
51 | return new Runner(runnerCommand, runnerArgs);
52 | }
53 |
54 | if (hasDependency('@tauri-apps/cli', projectPath)) {
55 | // usesX also check if the runner executable exists.
56 | if (usesYarn(projectPath)) return new Runner('yarn', ['tauri']);
57 | if (usesPnpm(projectPath)) return new Runner('pnpm', ['tauri']);
58 | if (usesBun(projectPath)) return new Runner('bun', ['tauri']);
59 | // npm should always be available in a GitHub runner but we'll check for it anyway.
60 | if (usesNpm(projectPath))
61 | return new Runner('npm', [
62 | hasTauriScript(projectPath) ? 'run' : 'exec',
63 | 'tauri',
64 | ]);
65 | }
66 |
67 | console.warn(
68 | 'Could not detect valid `@tauri-apps/cli` installation. Proceeding to install global npm package...',
69 | );
70 |
71 | await execCommand('npm', ['install', '-g', `@tauri-apps/cli@v2`], {
72 | cwd: undefined,
73 | });
74 |
75 | return new Runner('tauri');
76 | }
77 |
78 | export { Runner, getRunner };
79 |
--------------------------------------------------------------------------------
/src/types.d.ts:
--------------------------------------------------------------------------------
1 | import { Runner } from './runner';
2 |
3 | export interface Application {
4 | tauriPath: string;
5 | runner: Runner;
6 | name: string;
7 | version: string;
8 | wixLanguage: string | string[] | { [language: string]: unknown };
9 | }
10 |
11 | export interface Asset {
12 | downloadUrl: string;
13 | assetName: string;
14 | path: string;
15 | arch: string;
16 | bundle: string;
17 | }
18 |
19 | export interface Artifact {
20 | name: string;
21 | mode: 'debug' | 'release';
22 | platform: Exclude | 'darwin';
23 | arch: string;
24 | bundle: string;
25 | ext: string;
26 | version: string;
27 | setup: '-setup' | '';
28 | _setup: '_setup' | '';
29 | // Undocumented because it's intended for internal use
30 | path: string;
31 | workflowArtifactName?: string;
32 | }
33 |
34 | export interface BuildOptions {
35 | tauriScript: string | null;
36 | rawArgs: string[] | null;
37 | parsedArgs: ParsedArgs;
38 | parsedRunnerArgs: ParsedRunnerArgs;
39 | }
40 |
41 | type ParsedArgs = {
42 | debug?: string | boolean;
43 | config?: string | boolean;
44 | target?: string | boolean;
45 | };
46 |
47 | type ParsedRunnerArgs = {
48 | profile?: string | boolean;
49 | };
50 |
51 | export interface CargoManifestBin {
52 | name: string;
53 | }
54 |
55 | export interface CargoManifest {
56 | workspace?: { package?: { version?: string; name?: string } };
57 | package: { version: string; name: string; 'default-run': string };
58 | bin: CargoManifestBin[];
59 | }
60 |
61 | export interface Info {
62 | tauriPath: string | null;
63 | name: string;
64 | // already falls back to cargo's package name in getInfo
65 | mainBinaryName: string;
66 | version: string;
67 | wixLanguage: string | string[] | { [language: string]: unknown };
68 | rpmRelease: string;
69 | unzippedSigs: boolean;
70 | }
71 |
72 | export type TargetPlatform = 'android' | 'ios' | 'macos' | 'linux' | 'windows';
73 | export interface TargetInfo {
74 | arch: string;
75 | platform: TargetPlatform;
76 | }
77 |
78 | export interface TauriConfigV2 {
79 | identifier: string;
80 | productName?: string;
81 | version?: string;
82 | mainBinaryName?: string;
83 | build?: {
84 | frontendDist?: string;
85 | beforeBuildCommand?: string;
86 | };
87 | bundle?: {
88 | createUpdaterArtifacts?: boolean | 'v1Compatible';
89 | linux?: {
90 | rpm?: {
91 | release?: string;
92 | };
93 | };
94 | windows?: {
95 | wix?: {
96 | language?: string | string[] | { [language: string]: unknown };
97 | };
98 | };
99 | };
100 | }
101 |
102 | export interface CargoConfig {
103 | build?: {
104 | target?: string;
105 | 'target-dir'?: string;
106 | };
107 | }
108 |
--------------------------------------------------------------------------------
/src/inputs.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path';
2 | import { parseArgs } from 'node:util';
3 |
4 | import * as core from '@actions/core';
5 | import { context } from '@actions/github';
6 | import stringArgv from 'string-argv';
7 |
8 | export const projectPath = resolve(
9 | process.cwd(),
10 | core.getInput('projectPath') || process.argv[2],
11 | );
12 |
13 | export const shouldUploadUpdaterJson =
14 | core.getBooleanInput('uploadUpdaterJson');
15 |
16 | export const retryAttempts = parseInt(
17 | core.getInput('retryAttempts') || '0',
18 | 10,
19 | );
20 |
21 | export const tauriScript = core.getInput('tauriScript') || undefined;
22 |
23 | export const releaseAssetNamePattern =
24 | core.getInput('releaseAssetNamePattern') || undefined;
25 |
26 | export const rawArgs = stringArgv(core.getInput('args'));
27 |
28 | const parsedArgs_ = parseArgs({
29 | args: rawArgs,
30 | strict: false,
31 | options: {
32 | target: { type: 'string', short: 't' },
33 | config: {
34 | type: 'string',
35 | short: 'c',
36 | },
37 | debug: { type: 'boolean', short: 'd' },
38 | },
39 | });
40 |
41 | const parsedRunnerArgs_ = parseArgs({
42 | args: parsedArgs_.positionals,
43 | strict: false,
44 | options: { profile: { type: 'string' } },
45 | });
46 |
47 | export const parsedArgs = parsedArgs_.values;
48 |
49 | export const parsedRunnerArgs = parsedRunnerArgs_.values;
50 |
51 | export const uploadPlainBinary = core.getBooleanInput('uploadPlainBinary');
52 |
53 | export const owner = core.getInput('owner') || context.repo.owner;
54 |
55 | export const repo = core.getInput('repo') || context.repo.repo;
56 |
57 | export const draft = core.getBooleanInput('releaseDraft');
58 |
59 | export const prerelease = core.getBooleanInput('prerelease');
60 |
61 | export const commitish = core.getInput('releaseCommitish') || context.sha;
62 |
63 | export const githubBaseUrl =
64 | core.getInput('githubBaseUrl') ||
65 | process.env.GITHUB_API_URL ||
66 | 'https://api.github.com';
67 |
68 | export const isGitea = core.getBooleanInput('isGitea');
69 |
70 | export const generateReleaseNotes = core.getBooleanInput(
71 | 'generateReleaseNotes',
72 | );
73 |
74 | export const shouldUploadWorkflowArtifacts = core.getBooleanInput(
75 | 'uploadWorkflowArtifacts',
76 | );
77 |
78 | export const workflowArtifactNamePattern =
79 | core.getInput('workflowArtifactNamePattern') || '[platform]-[arch]-[bundle]';
80 |
81 | export const uploadUpdaterSignatures = core.getBooleanInput(
82 | 'uploadUpdaterSignatures',
83 | );
84 |
85 | export const updaterJsonPreferNsis = core.getBooleanInput(
86 | 'updaterJsonPreferNsis',
87 | );
88 |
89 | export const isAndroid = core.getInput('mobile').toLowerCase() === 'android';
90 | export const isIOS = core.getInput('mobile').toLowerCase() === 'ios';
91 | export const isDebug = parsedArgs['debug'] as boolean;
92 |
--------------------------------------------------------------------------------
/src/upload-release-assets.ts:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs';
2 |
3 | import { getOctokit } from '@actions/github';
4 |
5 | import {
6 | deleteGiteaReleaseAsset,
7 | getAssetName,
8 | ghAssetName,
9 | retry,
10 | } from './utils';
11 | import {
12 | githubBaseUrl,
13 | isGitea,
14 | owner,
15 | releaseAssetNamePattern,
16 | repo,
17 | uploadUpdaterSignatures,
18 | } from './inputs';
19 |
20 | import type { Artifact } from './types';
21 |
22 | export async function uploadAssets(
23 | releaseId: number,
24 | assets: Artifact[],
25 | retryAttempts: number,
26 | ) {
27 | if (process.env.GITHUB_TOKEN === undefined) {
28 | throw new Error('GITHUB_TOKEN is required');
29 | }
30 |
31 | const github = getOctokit(process.env.GITHUB_TOKEN, {
32 | baseUrl: githubBaseUrl,
33 | });
34 |
35 | const existingAssets = (
36 | await github.rest.repos.listReleaseAssets({
37 | owner,
38 | repo,
39 | release_id: releaseId,
40 | per_page: 100,
41 | })
42 | ).data;
43 |
44 | // Determine content-length for header to upload asset
45 | const contentLength = (filePath: string) => fs.statSync(filePath).size;
46 |
47 | for (const asset of assets) {
48 | if (!uploadUpdaterSignatures && asset.ext.endsWith('.sig')) {
49 | continue;
50 | }
51 |
52 | const headers = {
53 | 'content-type': 'application/zip',
54 | 'content-length': contentLength(asset.path),
55 | };
56 |
57 | const assetName = getAssetName(asset, releaseAssetNamePattern);
58 | const assetNameGH = ghAssetName(asset, releaseAssetNamePattern);
59 |
60 | const existingAsset = existingAssets.find(
61 | (a) => a.label === assetName || a.name === assetNameGH,
62 | );
63 |
64 | if (existingAsset) {
65 | console.log(`Deleting existing ${assetName}...`);
66 | if (isGitea) {
67 | await deleteGiteaReleaseAsset(github, releaseId, existingAsset.id);
68 | } else {
69 | await github.rest.repos.deleteReleaseAsset({
70 | owner,
71 | repo,
72 | asset_id: existingAsset.id,
73 | });
74 | }
75 | }
76 |
77 | console.log(`Uploading ${assetName}...`);
78 |
79 | await retry(
80 | () =>
81 | github.rest.repos.uploadReleaseAsset({
82 | headers,
83 | name: assetName,
84 | // GitHub renames the filename so we'll also set the label which it leaves as-is.
85 | label: assetName,
86 | // https://github.com/tauri-apps/tauri-action/pull/45
87 | // @ts-expect-error error TS2322: Type 'Buffer' is not assignable to type 'string'.
88 | data: fs.createReadStream(asset.path),
89 | owner,
90 | repo,
91 | release_id: releaseId,
92 | }),
93 | retryAttempts,
94 | );
95 |
96 | console.log(`${assetName} successfully uploaded.`);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/examples/publish-to-auto-release-universal-macos-app-with-signing-certificate.yml:
--------------------------------------------------------------------------------
1 | name: 'publish'
2 |
3 | on:
4 | push:
5 | branches:
6 | - release
7 |
8 | # This is the example for publishing a Universal macOS app with a signing certificate.
9 | # On each push to the `release` branch it will create or update a GitHub release, build your app, and upload the artifacts to the release.
10 |
11 | jobs:
12 | publish-tauri:
13 | permissions:
14 | contents: write
15 | strategy:
16 | fail-fast: false
17 | matrix:
18 | include:
19 | # for Universal macOS builds (arm64 and x86_64)
20 | - platform: 'macos-latest'
21 | args: '--target universal-apple-darwin'
22 |
23 | runs-on: ${{ matrix.platform }}
24 | steps:
25 | - uses: actions/checkout@v4
26 |
27 | - name: setup node
28 | uses: actions/setup-node@v4
29 | with:
30 | node-version: lts/*
31 |
32 | - name: install Rust stable
33 | uses: dtolnay/rust-toolchain@stable
34 | with:
35 | targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
36 |
37 | - name: install frontend dependencies
38 | run: yarn install # change this to npm, pnpm or bun depending on which one you use.
39 |
40 | - name: import Apple Developer Certificate
41 | # Prevents keychain from locking automatically for 3600 seconds.
42 | env:
43 | APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
44 | APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
45 | KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
46 | run: |
47 | echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12
48 | security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
49 | security default-keychain -s build.keychain
50 | security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
51 | security set-keychain-settings -t 3600 -u build.keychain
52 | security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
53 | security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
54 | security find-identity -v -p codesigning build.keychain
55 |
56 | - name: verify certificate
57 | run: |
58 | CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application")
59 | CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
60 | echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
61 | echo "Certificate imported."
62 |
63 | - name: build and publish
64 | uses: tauri-apps/tauri-action@v1
65 | env:
66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
67 | APPLE_ID: ${{ secrets.APPLE_ID }}
68 | APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
69 | APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
70 | APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
71 | APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
72 | APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
73 | with:
74 | tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
75 | releaseName: 'App v__VERSION__'
76 | releaseBody: 'See the assets to download this version and install.'
77 | releaseDraft: true
78 | prerelease: false
79 | args: ${{ matrix.args }}
80 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | name: 'tauri-action'
2 | description: 'Build tauri binaries for MacOS, Windows and Linux'
3 | author: 'Tauri Programme within The Commons Conservancy'
4 | branding:
5 | icon: 'box'
6 | color: 'blue'
7 | inputs:
8 | releaseId:
9 | description: 'The id of the release to upload artifacts as release assets'
10 | tagName:
11 | description: 'The tag name of the release to create or upload to'
12 | releaseName:
13 | description: 'The name of the release to create'
14 | releaseBody:
15 | description: 'The body of the release to create'
16 | releaseDraft:
17 | description: 'Whether the release to create is a draft or not'
18 | default: 'false'
19 | prerelease:
20 | description: 'Whether the release to create is a prerelease or not'
21 | default: 'false'
22 | releaseCommitish:
23 | description: 'Any branch or commit SHA the Git tag is created from, unused if the Git tag already exists. Default: SHA of current commit'
24 | projectPath:
25 | description: 'Path to the root of the project that will be built. It must NOT be gitignored.'
26 | default: '.'
27 | uploadUpdaterJson:
28 | description: 'Whether to upload a static JSON file for the updater using GitHub Releases as the CDN'
29 | default: 'true'
30 | updaterJsonPreferNsis:
31 | description: 'Whether the action will use the NSIS (setup.exe) or WiX (.msi) bundles for the updater JSON if both types exist. Will default to false. May default to true for apps using tauri@v2 in the future.'
32 | default: 'false'
33 | tauriScript:
34 | description: 'The script to run to build the Tauri app'
35 | args:
36 | description: 'Arguments for the `tauri build` command'
37 | retryAttempts:
38 | description: 'The number of times to re-try building the app if the initial build fails or uploading assets if the upload fails.'
39 | owner:
40 | description: 'The account owner of the repository'
41 | repo:
42 | description: 'The name of the repository'
43 | githubBaseUrl:
44 | description: 'The base URL of the GitHub API to use. This is useful if you want to use a self-hosted GitHub instance or a GitHub Enterprise server.'
45 | isGitea:
46 | description: 'Whether to run in Gitea compatibility mode. Set this if `githubBaseUrl` targets a Gitea instance, since some API endpoints differ from GitHub'
47 | default: 'false'
48 | releaseAssetNamePattern:
49 | description: 'The naming pattern to use for the uploaded assets'
50 | uploadPlainBinary:
51 | description: 'Whether to upload the plain executable file to the GitHub Releases'
52 | default: 'false'
53 | uploadWorkflowArtifacts:
54 | description: 'Whether to upload the bundles and executables as workflow artifacts. Independent from the release configs. Affected by `uploadPlainBinary`.'
55 | default: 'false'
56 | workflowArtifactNamePattern:
57 | description: 'The naming pattern to use for uploaded workflow artifacts. Ignored if `uploadWorkflowArtifacts` is not enabled.'
58 | uploadUpdaterSignatures:
59 | description: 'Whether to upload the .sig files generated by the Tauri CLI. Does not affect the latest.json generator.'
60 | default: 'true'
61 | generateReleaseNotes:
62 | description: "Whether to use GitHub's Release Notes API to generate the release title and body. If `releaseName` is set, it will overwrite the generated title. If `releaseBody` is set, it will be pre-pended to the automatically generated notes. This action is not responsible for the generated content."
63 | default: 'false'
64 | mobile:
65 | description: 'EXPERIMENTAL - Whether to build for mobile or desktop. Can be "android" or "ios" or unset for desktop builds.'
66 | outputs:
67 | releaseId:
68 | description: 'The ID of the created release'
69 | releaseHtmlUrl:
70 | description: 'The URL users can navigate to in order to view the created release'
71 | releaseUploadUrl:
72 | description: 'The URL for uploading assets to the created release'
73 | artifactPaths:
74 | description: 'The paths of the generated artifacts'
75 | appVersion:
76 | description: 'The version of the app'
77 | runs:
78 | using: 'node24'
79 | main: 'dist/index.js'
80 |
--------------------------------------------------------------------------------
/examples/publish-to-manual-release.yml:
--------------------------------------------------------------------------------
1 | name: 'publish'
2 |
3 | on: pull_request
4 |
5 | # `tauri-action` can also upload app bundles to an existing GitHub release.
6 | # This workflow uses different actions to create and publish the release.
7 | # `tauri-action` will only build and upload the app bundles to the specified release.
8 |
9 | jobs:
10 | create-release:
11 | permissions:
12 | contents: write
13 | runs-on: ubuntu-latest
14 | outputs:
15 | release_id: ${{ steps.create-release.outputs.result }}
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 |
20 | - name: setup node
21 | uses: actions/setup-node@v4
22 | with:
23 | node-version: lts/*
24 |
25 | - name: get version
26 | run: echo "PACKAGE_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV
27 |
28 | - name: create release
29 | id: create-release
30 | uses: actions/github-script@v6
31 | with:
32 | script: |
33 | const { data } = await github.rest.repos.createRelease({
34 | owner: context.repo.owner,
35 | repo: context.repo.repo,
36 | tag_name: `app-v${process.env.PACKAGE_VERSION}`,
37 | name: `Desktop App v${process.env.PACKAGE_VERSION}`,
38 | body: 'Take a look at the assets to download and install this app.',
39 | draft: true,
40 | prerelease: false
41 | })
42 | return data.id
43 |
44 | build-tauri:
45 | needs: create-release
46 | permissions:
47 | contents: write
48 | strategy:
49 | fail-fast: false
50 | matrix:
51 | include:
52 | - platform: 'macos-latest' # for Arm based macs (M1 and above).
53 | args: '--target aarch64-apple-darwin'
54 | - platform: 'macos-latest' # for Intel based macs.
55 | args: '--target x86_64-apple-darwin'
56 | - platform: 'ubuntu-22.04' # for Tauri v1 you could replace this with ubuntu-20.04.
57 | args: ''
58 | - platform: 'windows-latest'
59 | args: ''
60 |
61 | runs-on: ${{ matrix.platform }}
62 | steps:
63 | - uses: actions/checkout@v4
64 |
65 | - name: setup node
66 | uses: actions/setup-node@v4
67 | with:
68 | node-version: lts/*
69 |
70 | - name: install Rust stable
71 | uses: dtolnay/rust-toolchain@stable
72 | with:
73 | # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
74 | targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
75 |
76 | - name: install dependencies (ubuntu only)
77 | if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
78 | run: |
79 | sudo apt-get update
80 | sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
81 |
82 | - name: install frontend dependencies
83 | run: yarn install # change this to npm, pnpm or bun depending on which one you use.
84 |
85 | - uses: tauri-apps/tauri-action@v1
86 | env:
87 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
88 | with:
89 | releaseId: ${{ needs.create-release.outputs.release_id }}
90 | args: ${{ matrix.args }}
91 |
92 | publish-release:
93 | permissions:
94 | contents: write
95 | runs-on: ubuntu-latest
96 | needs: [create-release, build-tauri]
97 |
98 | steps:
99 | - name: publish release
100 | id: publish-release
101 | uses: actions/github-script@v6
102 | env:
103 | release_id: ${{ needs.create-release.outputs.release_id }}
104 | with:
105 | script: |
106 | github.rest.repos.updateRelease({
107 | owner: context.repo.owner,
108 | repo: context.repo.repo,
109 | release_id: process.env.release_id,
110 | draft: false,
111 | prerelease: false
112 | })
113 |
--------------------------------------------------------------------------------
/src/create-release.ts:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs';
2 |
3 | import * as core from '@actions/core';
4 | import { getOctokit } from '@actions/github';
5 | import type { GitHub } from '@actions/github/lib/utils';
6 |
7 | import {
8 | commitish,
9 | draft,
10 | generateReleaseNotes,
11 | githubBaseUrl,
12 | owner,
13 | prerelease,
14 | repo,
15 | } from './inputs';
16 |
17 | interface Release {
18 | id: number;
19 | uploadUrl: string;
20 | htmlUrl: string;
21 | }
22 |
23 | interface GitHubRelease {
24 | id: number;
25 | upload_url: string;
26 | html_url: string;
27 | tag_name: string;
28 | draft: boolean;
29 | }
30 |
31 | function allReleases(
32 | github: InstanceType,
33 | ): AsyncIterableIterator<{ data: GitHubRelease[] }> {
34 | const params = { per_page: 100, owner, repo };
35 | return github.paginate.iterator(
36 | github.rest.repos.listReleases.endpoint.merge(params),
37 | );
38 | }
39 |
40 | /// Try to get release by tag. If there's none, releaseName is required to create one.
41 | export async function getOrCreateRelease(
42 | tagName: string,
43 | releaseName?: string,
44 | body?: string,
45 | ): Promise {
46 | if (process.env.GITHUB_TOKEN === undefined) {
47 | throw new Error('GITHUB_TOKEN is required');
48 | }
49 |
50 | // Get authenticated GitHub client (Ocktokit): https://github.com/actions/toolkit/tree/master/packages/github#usage
51 | const github = getOctokit(process.env.GITHUB_TOKEN, {
52 | baseUrl: githubBaseUrl,
53 | });
54 |
55 | const bodyPath = core.getInput('body_path', { required: false });
56 | let bodyFileContent: string | null = null;
57 | if (bodyPath !== '' && !!bodyPath) {
58 | try {
59 | bodyFileContent = fs.readFileSync(bodyPath, { encoding: 'utf8' });
60 | } catch (error) {
61 | // @ts-expect-error Catching errors in typescript is a headache
62 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
63 | core.setFailed(error.message);
64 | }
65 | }
66 |
67 | let release: GitHubRelease | null = null;
68 | try {
69 | // you can't get a an existing draft by tag
70 | // so we must find one in the list of all releases
71 | if (draft) {
72 | console.log(`Looking for a draft release with tag ${tagName}...`);
73 | for await (const response of allReleases(github)) {
74 | const releaseWithTag = response.data.find(
75 | (release) => release.tag_name === tagName,
76 | );
77 | if (releaseWithTag) {
78 | if (!releaseWithTag.draft) {
79 | console.warn(
80 | `Found release with tag ${tagName} but it's NOT a draft!`,
81 | );
82 | break;
83 | }
84 | release = releaseWithTag;
85 | console.log(
86 | `Found draft release with tag ${tagName} on the release list.`,
87 | );
88 | break;
89 | }
90 | }
91 | if (!release) {
92 | throw new Error('release not found');
93 | }
94 | } else {
95 | const foundRelease = await github.rest.repos.getReleaseByTag({
96 | owner,
97 | repo,
98 | tag: tagName,
99 | });
100 | release = foundRelease.data;
101 | console.log(`Found release with tag ${tagName}.`);
102 | }
103 | } catch (error) {
104 | // @ts-expect-error Catching errors in typescript is a headache
105 | if (error.status === 404 || error.message === 'release not found') {
106 | console.log(`Couldn't find release with tag ${tagName}. Creating one.`);
107 |
108 | if (!releaseName) {
109 | console.error('"releaseName" not set but required to create release.');
110 | } else {
111 | const createdRelease = await github.rest.repos.createRelease({
112 | owner,
113 | repo,
114 | tag_name: tagName,
115 | name: releaseName,
116 | body: bodyFileContent || body,
117 | draft,
118 | prerelease,
119 | target_commitish: commitish,
120 | generate_release_notes: generateReleaseNotes,
121 | });
122 |
123 | release = createdRelease.data;
124 | }
125 | } else {
126 | console.log(
127 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
128 | `⚠️ Unexpected error fetching GitHub release for tag ${tagName}: ${error}`,
129 | );
130 | throw error;
131 | }
132 | }
133 |
134 | if (!release) {
135 | throw new Error('Release not found or created.');
136 | }
137 |
138 | return {
139 | id: release.id,
140 | uploadUrl: release.upload_url,
141 | htmlUrl: release.html_url,
142 | };
143 | }
144 |
--------------------------------------------------------------------------------
/.github/workflows/test-action.yml:
--------------------------------------------------------------------------------
1 | name: 'real world tests'
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request:
6 | push:
7 | branches:
8 | - dev
9 |
10 | jobs:
11 | v2:
12 | strategy:
13 | fail-fast: false
14 | matrix:
15 | include:
16 | - platform: 'macos-latest'
17 | args: '--verbose --target universal-apple-darwin --debug'
18 | - platform: 'macos-latest'
19 | args: '--verbose --target aarch64-apple-darwin --debug'
20 | - platform: 'macos-latest'
21 | args: '--verbose --target x86_64-apple-darwin --debug'
22 | - platform: 'ubuntu-22.04'
23 | args: '--verbose --debug'
24 | - platform: 'windows-latest'
25 | args: '--verbose --debug'
26 | - platform: 'ubuntu-24.04-arm'
27 | args: '--verbose --debug'
28 | - platform: 'windows-11-arm'
29 | args: '--verbose --debug'
30 | - platform: 'ubuntu-latest'
31 | args: '--verbose --debug'
32 | mobile: 'android'
33 | # can't test without an apple account
34 | #- platform: 'macos-26'
35 | # args: '--verbose --debug'
36 | # mobile: 'ios'
37 |
38 | runs-on: ${{ matrix.platform }}
39 | steps:
40 | - uses: actions/checkout@v6
41 |
42 | # node
43 | - name: install pnpm
44 | uses: pnpm/action-setup@v4
45 | with:
46 | version: 10.x.x
47 |
48 | - name: setup node
49 | uses: actions/setup-node@v6
50 | with:
51 | node-version: lts/*
52 |
53 | - name: install example dependencies
54 | run: |
55 | echo "skipped"
56 | # disabled on desktop to test cli fallback install
57 | #cd ./.github/fixtures/example-with-tauri-v2
58 | # pnpm install
59 |
60 | # rust
61 | - name: install Rust stable (desktop)
62 | if: matrix.mobile == ''
63 | uses: dtolnay/rust-toolchain@stable
64 | with:
65 | targets: aarch64-apple-darwin,x86_64-apple-darwin
66 |
67 | - name: install Rust stable (android)
68 | if: matrix.mobile == 'android'
69 | uses: dtolnay/rust-toolchain@stable
70 | with:
71 | targets: aarch64-linux-android,armv7-linux-androideabi,i686-linux-android,x86_64-linux-android
72 |
73 | - name: install Rust stable (ios)
74 | if: matrix.mobile == 'ios'
75 | uses: dtolnay/rust-toolchain@stable
76 | with:
77 | targets: aarch64-apple-ios
78 |
79 | - name: Set up JDK
80 | if: matrix.mobile == 'android'
81 | uses: actions/setup-java@v5
82 | with:
83 | java-version: '21' # Maybe 17
84 | distribution: 'temurin'
85 |
86 | - name: Setup Android SDK
87 | if: matrix.mobile == 'android'
88 | uses: android-actions/setup-android@v3
89 |
90 | - name: Setup Android NDK
91 | if: matrix.mobile == 'android'
92 | uses: nttld/setup-ndk@v1
93 | id: setup-ndk
94 | with:
95 | ndk-version: r29
96 | link-to-sdk: true
97 |
98 | - uses: maxim-lobanov/setup-xcode@v1
99 | if: matrix.mobile == 'ios'
100 | with:
101 | xcode-version: 26.1
102 |
103 | - name: setup Apple API key
104 | if: matrix.mobile == 'ios'
105 | run: |
106 | APPLE_API_KEY_PATH="$RUNNER_TEMP/AuthKey_${{ secrets.APPLE_API_KEY_ID }}.p8"
107 | echo "${{ secrets.APPLE_API_KEY }}" > $APPLE_API_KEY_PATH
108 | echo "APPLE_API_KEY_PATH=$APPLE_API_KEY_PATH" >> $GITHUB_ENV
109 | echo "API_PRIVATE_KEYS_DIR=$RUNNER_TEMP" >> $GITHUB_ENV
110 |
111 | - uses: Swatinem/rust-cache@v2
112 | with:
113 | cache-on-failure: true
114 | workspaces: |
115 | ./.github/fixtures/example-with-tauri-v2/src-tauri -> ../../target
116 |
117 | # system
118 | - name: install dependencies (ubuntu only)
119 | if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-24.04-arm'
120 | run: |
121 | sudo apt-get update
122 | sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev
123 |
124 | - name: init mobile project
125 | if: matrix.mobile != ''
126 | run: |
127 | cd ./.github/fixtures/example-with-tauri-v2
128 | pnpm dlx @tauri-apps/cli ${{ matrix.mobile }} init
129 |
130 | - name: Preconfigured Tauri Project
131 | uses: ./
132 | env:
133 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
134 | # Updater signature is exposed here to make sure it works in PR's
135 | TAURI_SIGNING_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg==
136 | NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }}
137 | with:
138 | projectPath: ./.github/fixtures/example-with-tauri-v2
139 | tagName: ${{ !github.event.pull_request.head.repo.fork && 'example-with-tauri-v__VERSION__' || '' }}
140 | releaseName: 'Release example with preconfigured Tauri app v__VERSION__ for tauri-v2'
141 | releaseBody: 'See the assets to download this version and install.'
142 | releaseDraft: true
143 | args: ${{ matrix.args }}
144 | retryAttempts: 1
145 | uploadPlainBinary: true
146 | uploadWorkflowArtifacts: true
147 | uploadUpdaterSignatures: false
148 | mobile: ${{ matrix.mobile }}
149 | # workflowArtifactNamePattern: '[name]_${{ github.sha }}_[platform]_[arch]_[bundle]'
150 | # releaseAssetNamePattern: '[name]x[version]x[platform]x[arch]x[mode]x[setup]x[ext]'
151 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { existsSync } from 'node:fs';
2 | import { dirname, basename } from 'node:path';
3 |
4 | import * as core from '@actions/core';
5 |
6 | import { buildProject } from './build';
7 | import { getOrCreateRelease } from './create-release';
8 | import {
9 | shouldUploadUpdaterJson,
10 | isIOS,
11 | parsedArgs,
12 | retryAttempts,
13 | shouldUploadWorkflowArtifacts,
14 | } from './inputs';
15 | import { uploadAssets as uploadReleaseAssets } from './upload-release-assets';
16 | import { uploadVersionJSON } from './upload-version-json';
17 | import { uploadWorkflowArtifacts } from './upload-workflow-artifacts';
18 | import { execCommand, getInfo, getTargetInfo, retry } from './utils';
19 |
20 | import type { Artifact } from './types';
21 |
22 | async function run(): Promise {
23 | try {
24 | if (isIOS && process.platform !== 'darwin') {
25 | throw new Error('Building for iOS is only supported on macOS runners.');
26 | }
27 |
28 | // inputs that won't be changed are in ./inputs
29 | let tagName = core.getInput('tagName').replace('refs/tags/', '');
30 | let releaseId = Number(core.getInput('releaseId'));
31 | let releaseName = core.getInput('releaseName').replace('refs/tags/', '');
32 | let body = core.getInput('releaseBody');
33 |
34 | const targetPath = parsedArgs['target'] as string | undefined;
35 | const configArg = parsedArgs['config'] as string | undefined;
36 |
37 | const artifacts: Artifact[] = [];
38 |
39 | artifacts.push(...(await buildProject()));
40 |
41 | if (artifacts.length === 0) {
42 | if (releaseId || tagName || shouldUploadWorkflowArtifacts) {
43 | throw new Error('No artifacts were found.');
44 | } else {
45 | console.log(
46 | 'No artifacts were found. The action was not configured to upload artifacts, therefore this is not handled as an error.',
47 | );
48 | return;
49 | }
50 | }
51 |
52 | console.log(`Found artifacts:\n${artifacts.map((a) => a.path).join('\n')}`);
53 | core.setOutput(
54 | 'artifactPaths',
55 | JSON.stringify(artifacts.map((a) => a.path)),
56 | );
57 |
58 | const targetInfo = getTargetInfo(targetPath);
59 | const info = getInfo(targetInfo, configArg);
60 | core.setOutput('appVersion', info.version);
61 |
62 | // Since artifacts are .zip archives we can do this before the .tar.gz step below.
63 | if (shouldUploadWorkflowArtifacts) {
64 | console.log('uploadWorkflowArtifacts enabled');
65 | await uploadWorkflowArtifacts(artifacts);
66 | }
67 |
68 | // Other steps may benefit from this so we do this whether or not we want to upload it.
69 | if (targetInfo.platform === 'macos') {
70 | let i = 0;
71 | for (const artifact of artifacts) {
72 | // updater provide a .tar.gz, this will prevent duplicate and overwriting of
73 | // signed archive
74 | if (
75 | artifact.path.endsWith('.app') &&
76 | !existsSync(`${artifact.path}.tar.gz`)
77 | ) {
78 | console.log(
79 | `Packaging ${artifact.path} directory into ${artifact.path}.tar.gz`,
80 | );
81 |
82 | await execCommand('tar', [
83 | 'czf',
84 | `${artifact.path}.tar.gz`,
85 | '-C',
86 | dirname(artifact.path),
87 | basename(artifact.path),
88 | ]);
89 | artifact.path += '.tar.gz';
90 | artifact.ext += '.tar.gz';
91 | } else if (artifact.path.endsWith('.app')) {
92 | // we can't upload a directory
93 | artifacts.splice(i, 1);
94 | }
95 | i++;
96 | }
97 | }
98 |
99 | // If releaseId is set we'll use this to upload the assets to.
100 | // If tagName is set we will try to upload assets to the release associated with the given tagName.
101 | // If there's no release for that tag, we require releaseName to create a new one.
102 | if (tagName && !releaseId) {
103 | const templates = [
104 | {
105 | key: '__VERSION__',
106 | value: info.version,
107 | },
108 | ];
109 |
110 | templates.forEach((template) => {
111 | const regex = new RegExp(template.key, 'g');
112 | tagName = tagName.replace(regex, template.value);
113 | releaseName = releaseName.replace(regex, template.value);
114 | body = body.replace(regex, template.value);
115 | });
116 |
117 | const releaseData = await getOrCreateRelease(
118 | tagName,
119 | releaseName || undefined,
120 | body,
121 | );
122 | releaseId = releaseData.id;
123 | core.setOutput('releaseUploadUrl', releaseData.uploadUrl);
124 | core.setOutput('releaseId', releaseData.id.toString());
125 | core.setOutput('releaseHtmlUrl', releaseData.htmlUrl);
126 | }
127 |
128 | if (releaseId) {
129 | await uploadReleaseAssets(releaseId, artifacts, retryAttempts);
130 |
131 | if (shouldUploadUpdaterJson) {
132 | // Once we start throwing our own errors in this function we may need some custom retry logic.
133 | // We can't retry just the inner asset upload as that may upload an outdated latest.json file.
134 | await retry(
135 | () =>
136 | uploadVersionJSON(
137 | info.version,
138 | body,
139 | tagName,
140 | releaseId,
141 | artifacts,
142 | targetInfo,
143 | info.unzippedSigs,
144 | ),
145 | // since all jobs try to upload this file it tends to conflict often so we want to retry it at least once.
146 | retryAttempts === 0 ? 1 : retryAttempts,
147 | );
148 | }
149 | } else {
150 | console.log('No releaseId or tagName provided, skipping all uploads...');
151 | }
152 | } catch (error) {
153 | // @ts-expect-error Catching errors in typescript is a headache
154 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
155 | core.setFailed(error.message);
156 | }
157 | }
158 |
159 | await run();
160 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | import { existsSync, readFileSync } from 'fs';
2 | import path, { join } from 'path';
3 |
4 | import TOML from 'smol-toml';
5 | import JSON5 from 'json5';
6 |
7 | import { TargetPlatform, TauriConfigV2 } from './types';
8 |
9 | function _tryParseJsonConfig(contents: string): TauriConfigV2 | null {
10 | try {
11 | const config = JSON.parse(contents) as TauriConfigV2;
12 | return config;
13 | } catch (e) {
14 | // @ts-expect-error Catching errors in typescript is a headache
15 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
16 | const msg = e.message;
17 | console.error(
18 | `Couldn't parse --config flag as inline JSON. This error can be ignored if it's a file path. Source: "${msg}"`,
19 | );
20 | return null;
21 | }
22 | }
23 |
24 | function _tryParseJson5Config(contents: string): TauriConfigV2 | null {
25 | try {
26 | const config = JSON5.parse(contents);
27 | return config;
28 | } catch (e) {
29 | // @ts-expect-error Catching errors in typescript is a headache
30 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
31 | const msg = e.message;
32 | console.error(
33 | `Couldn't parse --config flag as inline JSON. This error can be ignored if it's a file path. Source: "${msg}"`,
34 | );
35 | return null;
36 | }
37 | }
38 |
39 | function _tryParseTomlConfig(contents: string): TauriConfigV2 | null {
40 | try {
41 | const config = TOML.parse(contents) as unknown as TauriConfigV2;
42 | return config;
43 | } catch (e) {
44 | // @ts-expect-error Catching errors in typescript is a headache
45 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
46 | const msg = e.message;
47 | console.error(
48 | `Couldn't parse --config flag as inline JSON. This error can be ignored if it's a file path. Source: "${msg}"`,
49 | );
50 | return null;
51 | }
52 | }
53 |
54 | function readPlatformConfig(
55 | tauriDir: string,
56 | platform: string,
57 | ): TauriConfigV2 | null {
58 | let path = join(tauriDir, `tauri.${platform}.conf.json`);
59 | if (existsSync(path)) {
60 | const contents = readFileSync(path).toString();
61 | const config = _tryParseJsonConfig(contents);
62 | if (config) return config;
63 | }
64 |
65 | path = join(tauriDir, `tauri.${platform}.conf.json5`);
66 | if (existsSync(path)) {
67 | const contents = readFileSync(path).toString();
68 | const config = _tryParseJson5Config(contents);
69 | if (config) return config;
70 | }
71 |
72 | path = join(tauriDir, `Tauri.${platform}.toml`);
73 | if (existsSync(path)) {
74 | const contents = readFileSync(path).toString();
75 | const config = _tryParseTomlConfig(contents);
76 | if (config) return config;
77 | }
78 |
79 | return null;
80 | }
81 |
82 | function readCustomConfig(customPath: string): TauriConfigV2 {
83 | if (!existsSync(customPath)) {
84 | throw new Error(`Provided config path \`${customPath}\` does not exist.`);
85 | }
86 |
87 | const contents = readFileSync(customPath).toString();
88 | const ext = path.extname(customPath);
89 |
90 | if (ext === '.json') {
91 | const config = _tryParseJsonConfig(contents);
92 | if (config) return config;
93 | }
94 |
95 | if (ext === '.json5') {
96 | const config = _tryParseJson5Config(contents);
97 | if (config) return config;
98 | }
99 |
100 | if (ext === '.toml') {
101 | const config = _tryParseTomlConfig(contents);
102 | if (config) return config;
103 | }
104 |
105 | throw new Error(`Couldn't parse \`${customPath}\` as ${ext.substring(1)}.`);
106 | }
107 |
108 | export class TauriConfig {
109 | // Required values
110 | identifier: string;
111 |
112 | // Optional values
113 | productName?: string;
114 | mainBinaryName?: string;
115 | version?: string;
116 | frontendDist?: string;
117 | beforeBuildCommand?: string;
118 | rpmRelease?: string;
119 | wixLanguage?: string | string[] | { [language: string]: unknown };
120 | unzippedSigs?: boolean;
121 |
122 | constructor(identifier: string) {
123 | this.identifier = identifier;
124 | }
125 |
126 | public static fromBaseConfig(tauriDir: string): TauriConfig {
127 | if (existsSync(join(tauriDir, 'tauri.conf.json'))) {
128 | const contents = readFileSync(
129 | join(tauriDir, 'tauri.conf.json'),
130 | ).toString();
131 | const config = _tryParseJsonConfig(contents);
132 | if (config) {
133 | return this.fromV2Base(config);
134 | }
135 | console.error(
136 | "Found tauri.conf.json file but couldn't parse it as JSON.",
137 | );
138 | }
139 |
140 | if (existsSync(join(tauriDir, 'tauri.conf.json5'))) {
141 | const contents = readFileSync(
142 | join(tauriDir, 'tauri.conf.json5'),
143 | ).toString();
144 | const config = _tryParseJson5Config(contents);
145 | if (config) {
146 | return this.fromV2Base(config);
147 | }
148 | console.error(
149 | "Found tauri.conf.json5 file but couldn't parse it as JSON5.",
150 | );
151 | }
152 |
153 | if (existsSync(join(tauriDir, 'Tauri.toml'))) {
154 | const contents = readFileSync(join(tauriDir, 'Tauri.toml')).toString();
155 | const config = _tryParseTomlConfig(contents);
156 | if (config) {
157 | return this.fromV2Base(config);
158 | }
159 | console.error("Found Tauri.toml file but couldn't parse it as TOML.");
160 | }
161 |
162 | throw new Error("Couldn't locate or parse tauri config.");
163 | }
164 |
165 | private static fromV2Base(config: TauriConfigV2): TauriConfig {
166 | if (!config.identifier) {
167 | throw Error('base config has no bundle identifier.');
168 | }
169 |
170 | const c = new TauriConfig(config.identifier);
171 |
172 | c.productName = config.productName;
173 | c.mainBinaryName = config.mainBinaryName;
174 | c.version = config.version;
175 | c.frontendDist = config.build?.frontendDist;
176 | c.beforeBuildCommand = config.build?.beforeBuildCommand;
177 | c.rpmRelease = config.bundle?.linux?.rpm?.release;
178 | c.wixLanguage = config.bundle?.windows?.wix?.language;
179 | c.unzippedSigs = config.bundle?.createUpdaterArtifacts === true;
180 |
181 | return c;
182 | }
183 |
184 | private mergeConfig(config: TauriConfigV2) {
185 | this.identifier = config.identifier ?? this.identifier;
186 | this.productName = config.productName ?? this.productName;
187 | this.mainBinaryName = config.mainBinaryName ?? this.mainBinaryName;
188 | this.version = config.version ?? this.version;
189 | this.frontendDist = config.build?.frontendDist ?? this.frontendDist;
190 | this.beforeBuildCommand =
191 | config.build?.beforeBuildCommand ?? this.beforeBuildCommand;
192 | this.rpmRelease = config.bundle?.linux?.rpm?.release ?? this.rpmRelease;
193 | this.wixLanguage =
194 | config.bundle?.windows?.wix?.language ?? this.wixLanguage;
195 | this.unzippedSigs =
196 | config.bundle?.createUpdaterArtifacts != null
197 | ? config.bundle?.createUpdaterArtifacts === true
198 | : this.unzippedSigs;
199 | }
200 |
201 | public mergePlatformConfig(tauriDir: string, target: TargetPlatform) {
202 | const config = readPlatformConfig(tauriDir, target);
203 |
204 | if (config) {
205 | this.mergeConfig(config);
206 | }
207 | }
208 |
209 | public mergeUserConfig(root: string, mergeConfig: string) {
210 | let config = _tryParseJsonConfig(mergeConfig);
211 |
212 | if (!config) {
213 | const configPath = path.isAbsolute(mergeConfig)
214 | ? mergeConfig
215 | : path.join(root, mergeConfig);
216 |
217 | config = readCustomConfig(configPath);
218 | }
219 |
220 | if (config) {
221 | this.mergeConfig(config);
222 | } else {
223 | console.error(`Couldn't read --config: ${mergeConfig}`);
224 | }
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/.github/sponsors/crabnebula.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/upload-version-json.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync, writeFileSync } from 'node:fs';
2 | import { basename, extname, resolve } from 'node:path';
3 |
4 | import { getOctokit } from '@actions/github';
5 |
6 | import {
7 | githubBaseUrl,
8 | isGitea,
9 | owner,
10 | releaseAssetNamePattern,
11 | repo,
12 | updaterJsonPreferNsis,
13 | } from './inputs';
14 | import { uploadAssets } from './upload-release-assets';
15 | import {
16 | createArtifact,
17 | deleteGiteaReleaseAsset,
18 | getAssetName,
19 | ghAssetName,
20 | } from './utils';
21 |
22 | import type { Artifact, TargetInfo } from './types';
23 |
24 | type Platform = {
25 | signature: string;
26 | url: string;
27 | };
28 |
29 | type VersionContent = {
30 | version: string;
31 | notes: string;
32 | pub_date: string;
33 | platforms: {
34 | [key: string]: Platform;
35 | };
36 | };
37 |
38 | export async function uploadVersionJSON(
39 | version: string,
40 | notes: string,
41 | tagName: string,
42 | releaseId: number,
43 | artifacts: Artifact[],
44 | targetInfo: TargetInfo,
45 | unzippedSig: boolean,
46 | ) {
47 | if (process.env.GITHUB_TOKEN === undefined) {
48 | throw new Error('GITHUB_TOKEN is required');
49 | }
50 |
51 | const github = getOctokit(process.env.GITHUB_TOKEN, {
52 | baseUrl: githubBaseUrl,
53 | });
54 |
55 | const versionFilename = 'latest.json';
56 | const versionFile = resolve(process.cwd(), versionFilename);
57 | const versionContent: VersionContent = {
58 | version,
59 | notes,
60 | pub_date: new Date().toISOString(),
61 | platforms: {},
62 | };
63 |
64 | const assets = await github.rest.repos.listReleaseAssets({
65 | owner,
66 | repo,
67 | release_id: releaseId,
68 | per_page: 50,
69 | });
70 | const asset = assets.data.find((e) => e.name === versionFilename);
71 |
72 | if (asset) {
73 | if (isGitea) {
74 | const info = (
75 | await github.request(
76 | 'GET /repos/{owner}/{repo}/releases/{release_id}/assets/{asset_id}',
77 | {
78 | owner,
79 | repo,
80 | release_id: releaseId,
81 | asset_id: asset.id,
82 | },
83 | )
84 | ).data as { browser_download_url: string };
85 |
86 | const data = (await github.request(`GET ${info.browser_download_url}`))
87 | .data as string;
88 |
89 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
90 | versionContent.platforms = JSON.parse(
91 | data,
92 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
93 | ).platforms;
94 | } else {
95 | const assetData = (
96 | await github.request(
97 | `GET /repos/{owner}/{repo}/releases/assets/{asset_id}`,
98 | {
99 | owner: owner,
100 | repo: repo,
101 | release_id: releaseId,
102 | asset_id: asset.id,
103 | headers: {
104 | accept: 'application/octet-stream',
105 | },
106 | },
107 | )
108 | ).data as unknown as ArrayBuffer;
109 |
110 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
111 | versionContent.platforms = JSON.parse(
112 | Buffer.from(assetData).toString(),
113 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
114 | ).platforms;
115 | }
116 | }
117 |
118 | const downloadUrls: {
119 | name: string;
120 | label: string | null;
121 | url: string;
122 | }[] = [];
123 | for (const data of assets.data) {
124 | downloadUrls.push({
125 | name: data.name,
126 | label: data.label,
127 | url: data.browser_download_url,
128 | });
129 | }
130 |
131 | // Assets matching artifacts generated by this action
132 | const filteredAssets = [];
133 | // Signature files may not be uploaded to the release so we collect them separately
134 | const signatureFiles = [];
135 | // We need to check for these so that we can non-destructively overwrite the relevant json entries with universal builds if needed
136 | let hasNativeArm;
137 | let hasNativeX64;
138 |
139 | for (const artifact of artifacts) {
140 | if (artifact.ext === '.app.tar.gz' && artifact.arch === 'universal') {
141 | const arm = { ...artifact, arch: 'aarch64' };
142 | const x64 = { ...artifact, arch: 'x86_64' };
143 |
144 | const armName = ghAssetName(arm, releaseAssetNamePattern);
145 | const armLabel = getAssetName(arm, releaseAssetNamePattern);
146 | const x64Name = ghAssetName(x64, releaseAssetNamePattern);
147 | const x64Label = getAssetName(x64, releaseAssetNamePattern);
148 |
149 | hasNativeArm = !!downloadUrls.find(
150 | (a) => a.label === armLabel || a.name === armName,
151 | );
152 | hasNativeX64 = !!downloadUrls.find(
153 | (a) => a.label === x64Label || a.name === x64Name,
154 | );
155 | }
156 |
157 | const assetLabel = getAssetName(artifact, releaseAssetNamePattern);
158 | const assetName = ghAssetName(artifact, releaseAssetNamePattern);
159 | const downloadUrl = downloadUrls.find(
160 | (a) => a.label === assetLabel || a.name === assetName,
161 | )?.url;
162 |
163 | if (artifact.ext.endsWith('.sig')) {
164 | signatureFiles.push({
165 | assetLabel,
166 | assetName,
167 | path: artifact.path,
168 | arch: artifact.arch,
169 | bundle: artifact.bundle,
170 | });
171 | } else if (downloadUrl) {
172 | filteredAssets.push({
173 | downloadUrl,
174 | assetLabel,
175 | assetName,
176 | path: artifact.path,
177 | arch: artifact.arch,
178 | bundle: artifact.bundle,
179 | });
180 | }
181 | }
182 |
183 | function signaturePriority(signaturePath: string) {
184 | if (
185 | (unzippedSig && signaturePath.endsWith('.AppImage.sig')) ||
186 | (!unzippedSig && signaturePath.endsWith('.AppImage.tar.gz.sig'))
187 | ) {
188 | return 100;
189 | }
190 | const priorities = updaterJsonPreferNsis
191 | ? unzippedSig
192 | ? ['.exe.sig', '.msi.sig']
193 | : ['.nsis.zip.sig', '.msi.zip.sig']
194 | : unzippedSig
195 | ? ['.msi.sig', '.exe.sig']
196 | : ['.msi.zip.sig', '.nsis.zip.sig'];
197 | for (const [index, extension] of priorities.entries()) {
198 | if (signaturePath.endsWith(extension)) {
199 | return 100 - index;
200 | }
201 | }
202 | return 0;
203 | }
204 | signatureFiles.sort((a, b) => {
205 | return signaturePriority(b.path) - signaturePriority(a.path);
206 | });
207 |
208 | if (!signatureFiles[0]) {
209 | console.warn(
210 | 'Signature not found for the updater JSON. Skipping upload...',
211 | );
212 | return;
213 | }
214 |
215 | for (const [idx, signatureFile] of signatureFiles.entries()) {
216 | const updaterFileLabel = basename(
217 | signatureFile.assetLabel,
218 | extname(signatureFile.assetLabel),
219 | );
220 | const updaterFileName = basename(
221 | signatureFile.assetName,
222 | extname(signatureFile.assetName),
223 | );
224 | let updaterFileDownloadUrl = filteredAssets.find(
225 | (a) =>
226 | a.assetLabel === updaterFileLabel || a.assetName === updaterFileName,
227 | )?.downloadUrl;
228 |
229 | if (!updaterFileDownloadUrl) {
230 | console.warn(
231 | `Updater asset belonging to signature file "${signatureFile.assetName}" not found.`,
232 | );
233 | continue;
234 | }
235 |
236 | // Untagged release downloads won't work after the release was published
237 | updaterFileDownloadUrl = updaterFileDownloadUrl.replace(
238 | /\/download\/(untagged-[^/]+)\//,
239 | tagName
240 | ? `/download/${encodeURIComponent(tagName)}/`
241 | : '/latest/download/',
242 | );
243 |
244 | let os = targetInfo.platform as string;
245 | if (os === 'macos') {
246 | os = 'darwin';
247 | }
248 |
249 | let arch = signatureFile.arch;
250 | arch =
251 | arch === 'amd64' || arch === 'x86_64' || arch === 'x64'
252 | ? 'x86_64'
253 | : arch === 'x86' || arch === 'i386'
254 | ? 'i686'
255 | : arch === 'arm'
256 | ? 'armv7'
257 | : arch === 'arm64'
258 | ? 'aarch64'
259 | : arch;
260 |
261 | // This is our primary updater type we use for `{os}-{arch}`
262 | if (idx === 0) {
263 | if (os === 'darwin' && arch === 'universal') {
264 | // Don't overwrite native builds unless outdated.
265 | // hasNativeArm/x64 can be false while other jobs are overwriting artifacts,
266 | // but we should still always end up with a working latest.json file.
267 | if (!versionContent.platforms['darwin-aarch64'] || !hasNativeArm) {
268 | (versionContent.platforms['darwin-aarch64'] as unknown) = {
269 | signature: readFileSync(signatureFile.path).toString(),
270 | url: updaterFileDownloadUrl,
271 | };
272 | }
273 | if (!versionContent.platforms['darwin-x86_64'] || !hasNativeX64) {
274 | (versionContent.platforms['darwin-x86_64'] as unknown) = {
275 | signature: readFileSync(signatureFile.path).toString(),
276 | url: updaterFileDownloadUrl,
277 | };
278 | }
279 | }
280 | (versionContent.platforms[`${os}-${arch}`] as unknown) = {
281 | signature: readFileSync(signatureFile.path).toString(),
282 | url: updaterFileDownloadUrl,
283 | };
284 | }
285 |
286 | // This is for the new `{os}-{arch}-{installer}` format
287 | if (os === 'darwin' && arch === 'universal') {
288 | // Don't overwrite native builds unless outdated.
289 | // hasNativeArm/x64 can be false while other jobs are overwriting artifacts,
290 | // but we should still always end up with a working latest.json file.
291 | if (!versionContent.platforms['darwin-aarch64-app'] || !hasNativeArm) {
292 | (versionContent.platforms['darwin-aarch64-app'] as unknown) = {
293 | signature: readFileSync(signatureFile.path).toString(),
294 | url: updaterFileDownloadUrl,
295 | };
296 | }
297 | if (!versionContent.platforms['darwin-x86_64-app'] || !hasNativeX64) {
298 | (versionContent.platforms['darwin-x86_64-app'] as unknown) = {
299 | signature: readFileSync(signatureFile.path).toString(),
300 | url: updaterFileDownloadUrl,
301 | };
302 | }
303 | }
304 | (versionContent.platforms[
305 | `${os}-${arch}-${signatureFile.bundle}`
306 | ] as unknown) = {
307 | signature: readFileSync(signatureFile.path).toString(),
308 | url: updaterFileDownloadUrl,
309 | };
310 | }
311 |
312 | writeFileSync(versionFile, JSON.stringify(versionContent, null, 2));
313 |
314 | if (asset) {
315 | if (isGitea) {
316 | await deleteGiteaReleaseAsset(github, releaseId, asset.id);
317 | } else {
318 | // https://docs.github.com/en/rest/releases/assets#update-a-release-asset
319 | await github.rest.repos.deleteReleaseAsset({
320 | owner,
321 | repo,
322 | release_id: releaseId,
323 | asset_id: asset.id,
324 | });
325 | }
326 | }
327 |
328 | const artifact = createArtifact({
329 | path: versionFile,
330 | name: versionFilename,
331 | platform: targetInfo.platform,
332 | arch: '',
333 | bundle: '',
334 | version,
335 | });
336 |
337 | await uploadAssets(
338 | releaseId,
339 | [artifact],
340 | // The whole step will be retried where `uploadVersionJSON` is called.
341 | // Just in case it's a quick http hickup we retry it once here as well.
342 | 1,
343 | );
344 | }
345 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tauri GitHub Action
2 |
3 | This GitHub Action builds your Tauri application as a native binary for macOS, Linux and Windows and optionally upload it to a GitHub Release.
4 |
5 | ## Example
6 |
7 | **_For more workflow examples, check out the [examples](examples) directory._**
8 |
9 | This GitHub Action has three main usages: test the build pipeline of your Tauri app, uploading Tauri artifacts to an existing release, and creating a new release with the Tauri artifacts.
10 |
11 | This example shows the most common use case for `tauri-action`. The action will build the app, create a GitHub release itself, and upload the app bundles to the newly created release.
12 |
13 | This is generally the simplest way to release your Tauri app.
14 |
15 | ```yml
16 | name: 'publish'
17 |
18 | on:
19 | push:
20 | branches:
21 | - release
22 |
23 | # This workflow will trigger on each push to the `release` branch to create or update a GitHub release, build your app, and upload the artifacts to the release.
24 |
25 | jobs:
26 | publish-tauri:
27 | permissions:
28 | contents: write
29 | strategy:
30 | fail-fast: false
31 | matrix:
32 | include:
33 | - platform: 'macos-latest' # for Arm based macs (M1 and above).
34 | args: '--target aarch64-apple-darwin'
35 | - platform: 'macos-latest' # for Intel based macs.
36 | args: '--target x86_64-apple-darwin'
37 | - platform: 'ubuntu-22.04'
38 | args: ''
39 | - platform: 'windows-latest'
40 | args: ''
41 |
42 | runs-on: ${{ matrix.platform }}
43 | steps:
44 | - uses: actions/checkout@v4
45 |
46 | - name: setup node
47 | uses: actions/setup-node@v4
48 | with:
49 | node-version: lts/*
50 |
51 | - name: install Rust stable
52 | uses: dtolnay/rust-toolchain@stable
53 | with:
54 | # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
55 | targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
56 |
57 | - name: install dependencies (ubuntu only)
58 | if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above.
59 | run: |
60 | sudo apt-get update
61 | sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
62 |
63 | - name: install frontend dependencies
64 | run: yarn install # change this to npm, pnpm or bun depending on which one you use.
65 |
66 | - uses: tauri-apps/tauri-action@v1
67 | env:
68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
69 | with:
70 | tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
71 | releaseName: 'App v__VERSION__'
72 | releaseBody: 'See the assets to download this version and install.'
73 | releaseDraft: true
74 | prerelease: false
75 | args: ${{ matrix.args }}
76 | ```
77 |
78 | ## Usage
79 |
80 | ```yml
81 | - uses: tauri-apps/tauri-action@v1
82 | env:
83 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
84 | with:
85 | # The id of the release to upload artifacts as release assets.
86 | # If set, `tagName` and `releaseName` will NOT be considered to find a release.
87 | #
88 | # default: unset
89 | releaseId: ''
90 |
91 | # The tag name of the release to upload/create or the tag of the release belonging to `releaseId`.
92 | # If this points to an existing release `releaseDraft` must match the status of that release.
93 | # If `releaseId` is set but this is not, the `latest.json` file will
94 | # point to `releases/latest/download/` instead of the tag.
95 | #
96 | # default: unset
97 | tagName: ''
98 |
99 | # The name of the release to create.
100 | # Required if `releaseId` is not set and there's no existing release for `tagName`.
101 | #
102 | # default: ""
103 | releaseName: ''
104 |
105 | # The body of the release to create.
106 | #
107 | # default: ""
108 | releaseBody: ''
109 |
110 | # Any branch or commit SHA the Git tag is created from, unused if the Git tag already exists.
111 | #
112 | # default: SHA of current commit
113 | releaseCommitish: ''
114 |
115 | # Whether the release to find or create is a draft or not.
116 | #
117 | # default: false
118 | releaseDraft: false
119 |
120 | # Whether the release to create is a prerelease or not.
121 | #
122 | # default: false
123 | prerelease: false
124 |
125 | # Whether to use GitHub's Release Notes API to generate the release title and body.
126 | # If `releaseName` is set, it will overwrite the generated title.
127 | # If `releaseBody` is set, it will be pre-pended to the automatically generated notes.
128 | # This action is not responsible for the generated content.
129 | #
130 | # default: false
131 | generateReleaseNotes: false
132 |
133 | # The account owner of the repository the release will be uploaded to.
134 | # Requires `GITHUB_TOKEN` in env and a `releaseCommitish` target if it doesn't match the current repo.
135 | #
136 | # default: owner of the current repo
137 | owner: ''
138 |
139 | # The name of the repository the release will be uploaded to.
140 | # Requires `GITHUB_TOKEN` in env and a `releaseCommitish` target if it doesn't match the current repo.
141 | #
142 | # default: name of the current repo
143 | repo: ''
144 |
145 | # The base URL of the GitHub API to use.
146 | # This is useful if you want to use a self-hosted GitHub instance or a GitHub Enterprise server.
147 | #
148 | # default: $GITHUB_API_URL or "https://api.github.com"
149 | githubBaseUrl: ''
150 |
151 | # Whether to run in Gitea compatibility mode. Set this if `githubBaseUrl` targets a Gitea instance,
152 | # since some API endpoints differ from GitHub.
153 | # Gitea support is experimental. It was implemented and tested solely by the community.
154 | #
155 | # default: false
156 | isGitea: false
157 |
158 | # The path to the root of the tauri project relative to the current working directory.
159 | # It must NOT be gitignored. Please open an issue if this causes problems.
160 | #
161 | # Relative paths provided via the `--config` flag will be resolved relative to this path.
162 | #
163 | # default: ./
164 | projectPath: ''
165 |
166 | # The number of times to re-try building the app if the initial build fails or uploading assets if the upload fails.
167 | # Some small internal steps may be re-tried regardless of this config.
168 | #
169 | # default: 0
170 | retryAttempts: 0
171 |
172 | # Whether to upload a JSON file for the updater or not (only relevant if the updater is configured).
173 | # This file assume you're using the GitHub Release as your updater endpoint.
174 | #
175 | # default: true
176 | uploadUpdaterJson: true
177 |
178 | # Whether the action will use the NSIS (setup.exe) or WiX (.msi) bundles for the updater JSON if both types exist.
179 | #
180 | # default: false (for legacy reasons)
181 | updaterJsonPreferNsis: false
182 |
183 | # The script to execute the Tauri CLI. It must not include any args or commands like `build`.
184 | # It can also be an absolute path without spaces pointing to a `tauri-cli` binary.
185 | #
186 | # default: "npm|pnpm|yarn|bun tauri" or "tauri" if the action had to install the CLI.
187 | tauriScript: ''
188 |
189 | # Additional arguments to the current tauri build command.
190 | # Relative paths in the `--config` flag will be resolved relative to `projectPath`.
191 | #
192 | # default: ""
193 | args: ''
194 |
195 | # The naming pattern to use for the uploaded assets.
196 | #
197 | # Currently available variables are:
198 | # - `[name]`: base filename / appname (Product Name)
199 | # - `[version]`: app version
200 | # - `[platform]`: target platform (OS)
201 | # - `[arch]`: target architecture - format differs per platform
202 | # - `[ext]`: file extension (`.app`, `.dmg`, `.msi`, `.exe`, `.AppImage`, `.deb`, `.rpm`, `.apk`, `.aab`, `.ipa`)
203 | # - `[mode]`: `debug` or `release` depending on the use of the `--debug` flag.
204 | # - `[setup]`: `-setup` for the NSIS installer or an empty string for all other types.
205 | # - `[_setup]`: `_setup` for the NSIS installer or an empty string for all other types.
206 | # - `[bundle]`: one of `app`, `dmg`, `msi`, `nsis`, `appimage`, `deb`, `rpm`, `apk`, `aab`, `ipa`, `bin`.
207 | #
208 | # default: If not set, the names given by Tauri's CLI are kept.
209 | releaseAssetNamePattern: ''
210 |
211 | # Whether to upload the unbundled executable binary or not. Requires Tauri v2+.
212 | # To prevent issues with Tauri's `bundle_type` value (used by e.g. the updater) this
213 | # should only be used with the `--no-bundle` flag.
214 | # ONLY ENABLE THIS IF YOU KNOW WHAT YOU'RE DOING since Tauri does NOT officially support a portable mode,
215 | # especially on platforms other than Windows where
216 | # standalone binaries for GUI applications do not exist.
217 | #
218 | # Ref: https://docs.rs/tauri-utils/latest/tauri_utils/platform/fn.bundle_type.html
219 | #
220 | # default: false
221 | uploadPlainBinary: false
222 |
223 | # Whether to upload the bundles and executables as "workflow artifacts".
224 | # Independent from the release configs.
225 | # Affected by `uploadPlainBinary`.
226 | #
227 | # Ref: https://docs.github.com/en/actions/concepts/workflows-and-actions/workflow-artifacts
228 | #
229 | # default: false
230 | uploadWorkflowArtifacts: false
231 |
232 | # The naming pattern to use for uploaded "workflow artifacts".
233 | # Ignored if `uploadWorkflowArtifacts` is not enabled.
234 | #
235 | # See `releaseAssetNamePattern` for a list of replacement variables.
236 | #
237 | # Ref: https://docs.github.com/en/actions/concepts/workflows-and-actions/workflow-artifacts
238 | #
239 | # default: "[platform]-[arch]-[bundle]"
240 | workflowArtifactNamePattern: ''
241 |
242 | # Whether to upload the .sig files generated by Tauri.
243 | # Does not affect the `latest.json` generator.
244 | #
245 | # default: true
246 | uploadUpdaterSignatures: true
247 |
248 | # EXPERIMENTAL - Whether to build for mobile or desktop.
249 | #
250 | # Effectively changes the build command from `${tauriScript} build`
251 | # to `${tauriScript} android build` / ` ${tauriScript}ios build`
252 | #
253 | # Note that you have to install system dependencies (Xcode, SDKs, etc) yourself.
254 | # Furthermore, the action does not upload the app to the App Store or Play Store.
255 | # The .apk and .ipa files can be uploaded to the release or as workflow artifcats, but
256 | # plain .ipa files are generally useless so uploading them to a release is not recommended.
257 | #
258 | # - Can be set to "android" to build for Android. This works on all runners but other
259 | # required actions typically only work on Ubuntu so we recommend Ubuntu as well.
260 | # - Can be set to "ios` to build for iOS. This only works on macOS runners.
261 | # - Any other value will be ignored.
262 | #
263 | # default: unset
264 | mobile: ''
265 | ```
266 |
267 | ## Outputs
268 |
269 | | Name | Description |
270 | | ------------------ | ------------------------------------------------------------------ |
271 | | `releaseId` | The ID of the created release |
272 | | `releaseHtmlUrl` | The URL users can navigate to in order to view the created release |
273 | | `releaseUploadUrl` | The URL for uploading assets to the created release |
274 | | `artifactPaths` | The paths of the generated artifacts |
275 | | `appVersion` | The version of the app |
276 |
277 | ## Tips and Caveats
278 |
279 | - You can run custom Tauri CLI scripts with the `tauriScript` option. So instead of running `yarn tauri ` or `npm run tauri `, we'll execute `${tauriScript} `.
280 | - Useful when you need custom build functionality when creating Tauri apps e.g. a `desktop:build` script or if you use `cargo install tauri-cli`.
281 | - `tauriScript` can also be an absolute file path pointing to a `tauri-cli` binary. The path currently cannot contain spaces.
282 |
283 | - If you want to add additional arguments to the build command, you can use the `args` option. For example, if you're setting a specific target for your build, you can specify `args: --target your-target-arch`.
284 |
285 | - When your Tauri app is not in the root of the repo, use the `projectPath` input.
286 | - Usually it will work without it, but the action will install and use a global `@tauri-apps/cli` installation instead of your project's CLI which can cause issues if you also configured `tauriScript` or if you have multiple `tauri.conf.json` files in your repo.
287 | - Additionally, relative paths provided via the `--config` flag will be resolved relative to the `projectPath` to match Tauri's behavior.
288 | - The path must NOT be gitignored. Please open an issue if this causes you problems.
289 |
290 | - If `releaseId` is set, the action will use this release to upload assets to. If `tagName` is set the action will try to find an existing release for that tag. If there's none, the action requires `releaseName` to create a new release for the specified `tagName`.
291 |
292 | - If you create the release yourself and provide a `releaseId` but do not set `tagName`, the download url for updater bundles in `latest.json` will point to `releases/latest/download/` which can cause issues if your repo contains releases that do not include updater bundles.
293 |
294 | - If you provide a `tagName` to an existing release, `releaseDraft` must be set to `true` if the existing release is a draft.
295 |
296 | - If you only want to build the app without having the action upload any assets, for example if you want to only use [`actions/upload-artifact`](https://github.com/actions/upload-artifact), simply omit `tagName`, `releaseName` and `releaseId`.
297 |
298 | - Only enable `uploadPlainBinary` if you are sure what you're doing since Tauri doesn't officially support a portable mode, especially on platforms other than Windows where standalone binaries for GUI applications basically do not exist.
299 |
300 | - Gitea support is experimental. It was implemented and tested solely by the community.
301 |
302 | - `uploadWorkflowArtifacts` will likely be removed once [actions/upload-artifact#331](https://github.com/actions/upload-artifact/issues/331) lands.
303 |
304 | - `[setup]` can be used to differenciate between the NSIS installer and the binary from `uploadPlainBinary` (both have the `.exe` extension).
305 |
306 | - `[bundle]` is likely only useful for `workflowArtifactNamePattern` and _not_ for `releaseAssetNamePattern` because of its conflict with `[ext]`.
307 |
308 | - The action's iOS and Android support is considered experimental, please report any issues or feedback you may have in this repository.
309 |
310 | ## Partners
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 | |
320 |
321 |
322 |
323 |
324 | For the complete list of sponsors please visit our [website](https://tauri.app#sponsors) and [Open Collective](https://opencollective.com/tauri).
325 |
--------------------------------------------------------------------------------
/src/build.ts:
--------------------------------------------------------------------------------
1 | import { existsSync } from 'node:fs';
2 | import { join } from 'node:path';
3 |
4 | import { getRunner } from './runner';
5 | import {
6 | isAndroid,
7 | isDebug,
8 | isIOS,
9 | parsedArgs,
10 | parsedRunnerArgs,
11 | projectPath,
12 | rawArgs,
13 | retryAttempts,
14 | uploadPlainBinary,
15 | } from './inputs';
16 | import {
17 | createArtifact,
18 | getInfo,
19 | getTargetDir,
20 | getTargetInfo,
21 | getWorkspaceDir,
22 | } from './utils';
23 |
24 | import type { Artifact } from './types';
25 |
26 | export async function buildProject(): Promise {
27 | const runner = await getRunner();
28 |
29 | const targetPath = parsedArgs['target'] as string | undefined;
30 | const configArg = parsedArgs['config'] as string | undefined;
31 | const profile = parsedRunnerArgs['profile'] as string | undefined;
32 |
33 | const targetInfo = getTargetInfo(targetPath);
34 |
35 | const info = getInfo(targetInfo, configArg);
36 |
37 | if (!info.tauriPath) {
38 | throw Error("Couldn't detect path of tauri app");
39 | }
40 |
41 | const app = {
42 | tauriPath: info.tauriPath,
43 | runner,
44 | name: info.name,
45 | mainBinaryName: info.mainBinaryName,
46 | version: info.version,
47 | wixLanguage: info.wixLanguage,
48 | rpmRelease: info.rpmRelease,
49 | };
50 |
51 | let command = ['build'];
52 | if (isAndroid) command = ['android', 'build'];
53 | if (isIOS) command = ['ios', 'build'];
54 |
55 | await runner.execTauriCommand(
56 | command,
57 | rawArgs,
58 | projectPath,
59 | targetInfo.platform === 'macos'
60 | ? {
61 | TAURI_BUNDLER_DMG_IGNORE_CI:
62 | process.env.TAURI_BUNDLER_DMG_IGNORE_CI ?? 'true',
63 | }
64 | : undefined,
65 | retryAttempts,
66 | );
67 |
68 | const workspacePath = getWorkspaceDir(app.tauriPath) ?? app.tauriPath;
69 |
70 | let artifactsPath = join(
71 | getTargetDir(workspacePath, info.tauriPath, !!targetPath),
72 | targetPath ?? '',
73 | profile ? profile : isDebug ? 'debug' : 'release',
74 | );
75 | if (isAndroid) {
76 | artifactsPath = join(info.tauriPath, 'gen/android/app/build/outputs/');
77 | }
78 | if (isIOS) {
79 | artifactsPath = join(info.tauriPath, 'gen/apple/build/');
80 | }
81 |
82 | let artifacts: Artifact[] = [];
83 |
84 | let arch = targetInfo.arch;
85 |
86 | if (targetInfo.platform === 'macos') {
87 | if (arch === 'x86_64') {
88 | arch = 'x64';
89 | } else if (arch === 'arm64') {
90 | arch = 'aarch64';
91 | }
92 |
93 | artifacts = [
94 | createArtifact({
95 | path: join(
96 | artifactsPath,
97 | `bundle/dmg/${app.name}_${app.version}_${arch}.dmg`,
98 | ),
99 | name: app.name,
100 | platform: targetInfo.platform,
101 | arch,
102 | bundle: 'dmg', // could be 'dmg' or 'app' depending on the usecase
103 | version: app.version,
104 | }),
105 | createArtifact({
106 | path: join(artifactsPath, `bundle/macos/${app.name}.app`),
107 | name: app.name,
108 | platform: targetInfo.platform,
109 | arch,
110 | bundle: 'app',
111 | version: app.version,
112 | }),
113 | createArtifact({
114 | path: join(artifactsPath, `bundle/macos/${app.name}.app.tar.gz`),
115 | name: app.name,
116 | platform: targetInfo.platform,
117 | arch,
118 | bundle: 'app',
119 | version: app.version,
120 | }),
121 | createArtifact({
122 | path: join(artifactsPath, `bundle/macos/${app.name}.app.tar.gz.sig`),
123 | name: app.name,
124 | platform: targetInfo.platform,
125 | arch,
126 | bundle: 'app',
127 | version: app.version,
128 | }),
129 | ];
130 | } else if (targetInfo.platform === 'windows') {
131 | if (arch.startsWith('i')) {
132 | arch = 'x86';
133 | } else if (arch === 'aarch64' || arch === 'arm64') {
134 | arch = 'arm64';
135 | } else {
136 | arch = 'x64';
137 | }
138 |
139 | // If multiple Wix languages are specified, multiple installers (.msi) will be made
140 | // The .zip and .sig are only generated for the first specified language
141 | let langs;
142 | if (typeof app.wixLanguage === 'string') {
143 | langs = [app.wixLanguage];
144 | } else if (Array.isArray(app.wixLanguage)) {
145 | langs = app.wixLanguage;
146 | } else {
147 | langs = Object.keys(app.wixLanguage);
148 | }
149 |
150 | const winArtifacts: Artifact[] = [];
151 |
152 | // wix v2
153 | langs.forEach((lang) => {
154 | winArtifacts.push(
155 | createArtifact({
156 | path: join(
157 | artifactsPath,
158 | `bundle/msi/${app.name}_${app.version}_${arch}_${lang}.msi`,
159 | ),
160 | name: app.name,
161 | platform: targetInfo.platform,
162 | arch,
163 | bundle: 'msi',
164 | version: app.version,
165 | }),
166 | createArtifact({
167 | path: join(
168 | artifactsPath,
169 | `bundle/msi/${app.name}_${app.version}_${arch}_${lang}.msi.sig`,
170 | ),
171 | name: app.name,
172 | platform: targetInfo.platform,
173 | arch,
174 | bundle: 'msi',
175 | version: app.version,
176 | }),
177 | createArtifact({
178 | path: join(
179 | artifactsPath,
180 | `bundle/msi/${app.name}_${app.version}_${arch}_${lang}.msi.zip`,
181 | ),
182 | name: app.name,
183 | platform: targetInfo.platform,
184 | arch,
185 | bundle: 'msi',
186 | version: app.version,
187 | }),
188 | createArtifact({
189 | path: join(
190 | artifactsPath,
191 | `bundle/msi/${app.name}_${app.version}_${arch}_${lang}.msi.zip.sig`,
192 | ),
193 | name: app.name,
194 | platform: targetInfo.platform,
195 | arch,
196 | bundle: 'msi',
197 | version: app.version,
198 | }),
199 | );
200 | });
201 |
202 | winArtifacts.push(
203 | createArtifact({
204 | path: join(
205 | artifactsPath,
206 | `bundle/nsis/${app.name}_${app.version}_${arch}-setup.exe`,
207 | ),
208 | name: app.name,
209 | platform: targetInfo.platform,
210 | arch,
211 | bundle: 'nsis',
212 | version: app.version,
213 | }),
214 | createArtifact({
215 | path: join(
216 | artifactsPath,
217 | `bundle/nsis/${app.name}_${app.version}_${arch}-setup.exe.sig`,
218 | ),
219 | name: app.name,
220 | platform: targetInfo.platform,
221 | arch,
222 | bundle: 'nsis',
223 | version: app.version,
224 | }),
225 | createArtifact({
226 | path: join(
227 | artifactsPath,
228 | `bundle/nsis/${app.name}_${app.version}_${arch}-setup.nsis.zip`,
229 | ),
230 | name: app.name,
231 | platform: targetInfo.platform,
232 | arch,
233 | bundle: 'nsis',
234 | version: app.version,
235 | }),
236 | createArtifact({
237 | path: join(
238 | artifactsPath,
239 | `bundle/nsis/${app.name}_${app.version}_${arch}-setup.nsis.zip.sig`,
240 | ),
241 | name: app.name,
242 | platform: targetInfo.platform,
243 | arch,
244 | bundle: 'nsis',
245 | version: app.version,
246 | }),
247 | );
248 |
249 | artifacts = winArtifacts;
250 | } else if (targetInfo.platform === 'linux') {
251 | const debianArch =
252 | arch === 'x64' || arch === 'x86_64'
253 | ? 'amd64'
254 | : arch === 'x32' || arch === 'i686'
255 | ? 'i386'
256 | : arch === 'arm'
257 | ? 'armhf'
258 | : arch === 'aarch64'
259 | ? 'arm64'
260 | : arch;
261 | const rpmArch =
262 | arch === 'x64' || arch === 'x86_64'
263 | ? 'x86_64'
264 | : arch === 'x32' || arch === 'x86' || arch === 'i686'
265 | ? 'i386'
266 | : arch === 'arm'
267 | ? 'armhfp'
268 | : arch === 'arm64'
269 | ? 'aarch64'
270 | : arch;
271 | const appImageArch =
272 | arch === 'x64' || arch === 'x86_64'
273 | ? 'amd64'
274 | : arch === 'x32' || arch === 'i686'
275 | ? 'i386'
276 | : arch === 'arm' // TODO: Confirm this
277 | ? 'arm'
278 | : arch === 'arm64' // TODO: This is probably a Tauri bug
279 | ? 'aarch64'
280 | : arch;
281 |
282 | artifacts = [
283 | createArtifact({
284 | path: join(
285 | artifactsPath,
286 | `bundle/deb/${app.name}_${app.version}_${debianArch}.deb`,
287 | ),
288 | name: app.name,
289 | platform: targetInfo.platform,
290 | arch: debianArch,
291 | bundle: 'deb',
292 | version: app.version,
293 | }),
294 | createArtifact({
295 | path: join(
296 | artifactsPath,
297 | `bundle/deb/${app.name}_${app.version}_${debianArch}.deb.sig`,
298 | ),
299 | name: app.name,
300 | platform: targetInfo.platform,
301 | arch: debianArch,
302 | bundle: 'deb',
303 | version: app.version,
304 | }),
305 | createArtifact({
306 | path: join(
307 | artifactsPath,
308 | `bundle/rpm/${app.name}-${app.version}-${app.rpmRelease}.${rpmArch}.rpm`,
309 | ),
310 | name: app.name,
311 | platform: targetInfo.platform,
312 | arch: rpmArch,
313 | bundle: 'rpm',
314 | version: app.version,
315 | }),
316 | createArtifact({
317 | path: join(
318 | artifactsPath,
319 | `bundle/rpm/${app.name}-${app.version}-${app.rpmRelease}.${rpmArch}.rpm.sig`,
320 | ),
321 | name: app.name,
322 | platform: targetInfo.platform,
323 | arch: rpmArch,
324 | bundle: 'rpm',
325 | version: app.version,
326 | }),
327 | createArtifact({
328 | path: join(
329 | artifactsPath,
330 | `bundle/appimage/${app.name}_${app.version}_${appImageArch}.AppImage`,
331 | ),
332 | name: app.name,
333 | platform: targetInfo.platform,
334 | arch: appImageArch,
335 | bundle: 'appimage',
336 | version: app.version,
337 | }),
338 | createArtifact({
339 | path: join(
340 | artifactsPath,
341 | `bundle/appimage/${app.name}_${app.version}_${appImageArch}.AppImage.sig`,
342 | ),
343 | name: app.name,
344 | platform: targetInfo.platform,
345 | arch: appImageArch,
346 | bundle: 'appimage',
347 | version: app.version,
348 | }),
349 | createArtifact({
350 | path: join(
351 | artifactsPath,
352 | `bundle/appimage/${app.name}_${app.version}_${appImageArch}.AppImage.tar.gz`,
353 | ),
354 | name: app.name,
355 | platform: targetInfo.platform,
356 | arch: appImageArch,
357 | bundle: 'appimage',
358 | version: app.version,
359 | }),
360 | createArtifact({
361 | path: join(
362 | artifactsPath,
363 | `bundle/appimage/${app.name}_${app.version}_${appImageArch}.AppImage.tar.gz.sig`,
364 | ),
365 | name: app.name,
366 | platform: targetInfo.platform,
367 | arch: appImageArch,
368 | bundle: 'appimage',
369 | version: app.version,
370 | }),
371 | ];
372 | } else if (targetInfo.platform === 'android') {
373 | const debug = isDebug ? 'debug' : 'release';
374 | const aabDebug = isDebug ? 'Debug' : 'Release';
375 |
376 | // TODO: detect (un)signed beforehand
377 |
378 | if (!isDebug) {
379 | // unsigned release apks
380 | artifacts.push(
381 | createArtifact({
382 | path: join(
383 | artifactsPath,
384 | `apk/universal/release/app-universal-release-unsigend.apk`,
385 | ),
386 | name: app.name,
387 | platform: targetInfo.platform,
388 | arch: 'universal',
389 | bundle: 'apk',
390 | version: app.version,
391 | }),
392 | createArtifact({
393 | path: join(
394 | artifactsPath,
395 | `apk/arm64/release/app-arm64-release-unsigend.apk`,
396 | ),
397 | name: app.name,
398 | platform: targetInfo.platform,
399 | arch: 'arm64',
400 | bundle: 'apk',
401 | version: app.version,
402 | }),
403 | createArtifact({
404 | path: join(
405 | artifactsPath,
406 | `apk/arm/release/app-arm-release-unsigend.apk`,
407 | ),
408 | name: app.name,
409 | platform: targetInfo.platform,
410 | arch: 'universal',
411 | bundle: 'apk',
412 | version: app.version,
413 | }),
414 | createArtifact({
415 | path: join(
416 | artifactsPath,
417 | `apk/x86_64/release/app-x86_64-release-unsigend.apk`,
418 | ),
419 | name: app.name,
420 | platform: targetInfo.platform,
421 | arch: 'arm',
422 | bundle: 'apk',
423 | version: app.version,
424 | }),
425 | createArtifact({
426 | path: join(
427 | artifactsPath,
428 | `apk/x86/release/app-x86-release-unsigend.apk`,
429 | ),
430 | name: app.name,
431 | platform: targetInfo.platform,
432 | arch: 'x86',
433 | bundle: 'apk',
434 | version: app.version,
435 | }),
436 | );
437 | }
438 |
439 | artifacts.push(
440 | // signed release apks and debug apks
441 | createArtifact({
442 | path: join(
443 | artifactsPath,
444 | `apk/universal/${debug}/app-universal-${debug}.apk`,
445 | ),
446 | name: app.name,
447 | platform: targetInfo.platform,
448 | arch: 'universal',
449 | bundle: 'apk',
450 | version: app.version,
451 | }),
452 | createArtifact({
453 | path: join(artifactsPath, `apk/arm64/${debug}/app-arm64-${debug}.apk`),
454 | name: app.name,
455 | platform: targetInfo.platform,
456 | arch: 'arm64',
457 | bundle: 'apk',
458 | version: app.version,
459 | }),
460 | createArtifact({
461 | path: join(artifactsPath, `apk/arm/${debug}/app-arm-${debug}.apk`),
462 | name: app.name,
463 | platform: targetInfo.platform,
464 | arch: 'universal',
465 | bundle: 'apk',
466 | version: app.version,
467 | }),
468 | createArtifact({
469 | path: join(
470 | artifactsPath,
471 | `apk/x86_64/${debug}/app-x86_64-${debug}.apk`,
472 | ),
473 | name: app.name,
474 | platform: targetInfo.platform,
475 | arch: 'arm',
476 | bundle: 'apk',
477 | version: app.version,
478 | }),
479 | createArtifact({
480 | path: join(artifactsPath, `apk/x86/${debug}/app-x86-${debug}.apk`),
481 | name: app.name,
482 | platform: targetInfo.platform,
483 | arch: 'x86',
484 | bundle: 'apk',
485 | version: app.version,
486 | }),
487 | //
488 | // aabs
489 | //
490 | createArtifact({
491 | path: join(
492 | artifactsPath,
493 | `/bundle/universal${aabDebug}/app-universal-${debug}.aab`,
494 | ),
495 | name: app.name,
496 | platform: targetInfo.platform,
497 | arch: 'universal',
498 | bundle: 'aab',
499 | version: app.version,
500 | }),
501 | createArtifact({
502 | path: join(
503 | artifactsPath,
504 | `/bundle/arm64${aabDebug}/app-arm64-${debug}.aab`,
505 | ),
506 | name: app.name,
507 | platform: targetInfo.platform,
508 | arch: 'arm64',
509 | bundle: 'aab',
510 | version: app.version,
511 | }),
512 | createArtifact({
513 | path: join(
514 | artifactsPath,
515 | `/bundle/arm${aabDebug}/app-arm-${debug}.aab`,
516 | ),
517 | name: app.name,
518 | platform: targetInfo.platform,
519 | arch: 'arm',
520 | bundle: 'aab',
521 | version: app.version,
522 | }),
523 | createArtifact({
524 | path: join(
525 | artifactsPath,
526 | `/bundle/x86_64${aabDebug}/app-x86_64-${debug}.aab`,
527 | ),
528 | name: app.name,
529 | platform: targetInfo.platform,
530 | arch: 'x86_64',
531 | bundle: 'aab',
532 | version: app.version,
533 | }),
534 | createArtifact({
535 | path: join(
536 | artifactsPath,
537 | `/bundle/x86${aabDebug}/app-x86-${debug}.aab`,
538 | ),
539 | name: app.name,
540 | platform: targetInfo.platform,
541 | arch: 'x86',
542 | bundle: 'aab',
543 | version: app.version,
544 | }),
545 | );
546 | } else if (targetInfo.platform === 'ios') {
547 | // TODO: Confirm that info.name is correct.
548 | artifacts = [
549 | createArtifact({
550 | path: join(artifactsPath, `x86_64/${app.name}.ipa`),
551 | name: app.name,
552 | platform: targetInfo.platform,
553 | arch: 'x86_64',
554 | bundle: 'ipa',
555 | version: app.version,
556 | }),
557 | createArtifact({
558 | path: join(artifactsPath, `arm64/${app.name}.ipa`),
559 | name: app.name,
560 | platform: targetInfo.platform,
561 | arch: 'arm64',
562 | bundle: 'ipa',
563 | version: app.version,
564 | }),
565 | createArtifact({
566 | path: join(artifactsPath, `arm64-sim/${app.name}.ipa`),
567 | name: app.name,
568 | platform: targetInfo.platform,
569 | arch: 'arm64-sim',
570 | bundle: 'ipa',
571 | version: app.version,
572 | }),
573 | ];
574 | } else {
575 | // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
576 | console.error(`Unhandled target platform: "${targetInfo.platform}"`);
577 | }
578 |
579 | if (uploadPlainBinary) {
580 | const ext = targetInfo.platform === 'windows' ? '.exe' : '';
581 | artifacts.push(
582 | createArtifact({
583 | path: join(artifactsPath, `${app.mainBinaryName}${ext}`),
584 | name: 'binary', // app.mainBinaryName,
585 | bundle: 'bin',
586 |
587 | platform: targetInfo.platform,
588 | arch,
589 | version: app.version,
590 | }),
591 | );
592 | }
593 |
594 | console.log(
595 | `Looking for artifacts in:\n${artifacts.map((a) => a.path).join('\n')}`,
596 | );
597 | return artifacts.filter((p) => existsSync(p.path));
598 | }
599 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { existsSync, readFileSync } from 'node:fs';
2 | import path, {
3 | basename,
4 | extname,
5 | join,
6 | normalize,
7 | resolve,
8 | sep,
9 | } from 'node:path';
10 |
11 | import TOML from 'smol-toml';
12 | import { execa, execaSync } from 'execa';
13 | import { globbySync } from 'globby';
14 |
15 | import { TauriConfig } from './config';
16 |
17 | import type {
18 | Artifact,
19 | CargoConfig,
20 | CargoManifest,
21 | Info,
22 | TargetInfo,
23 | TargetPlatform,
24 | } from './types';
25 | import { GitHub } from '@actions/github/lib/utils';
26 | import { findUpSync } from 'find-up-simple';
27 | import { isAndroid, isDebug, isIOS, owner, projectPath, repo } from './inputs';
28 |
29 | /*** constants ***/
30 | export const extensions = [
31 | '.app.tar.gz.sig',
32 | '.app.tar.gz',
33 | '.dmg',
34 | '.AppImage.tar.gz.sig',
35 | '.AppImage.tar.gz',
36 | '.AppImage.sig',
37 | '.AppImage',
38 | '.deb.sig',
39 | '.deb',
40 | '.rpm.sig',
41 | '.rpm',
42 | '.msi.zip.sig',
43 | '.msi.zip',
44 | '.msi.sig',
45 | '.msi',
46 | '.nsis.zip.sig',
47 | '.nsis.zip',
48 | '.exe.sig',
49 | '.exe',
50 | ];
51 |
52 | /*** helper functions ***/
53 | export function parseAsset(assetPath: string) {
54 | const basename = path.basename(assetPath);
55 | const exts = extensions.filter((s) => basename.includes(s));
56 | const ext = exts[0] || path.extname(assetPath);
57 | const filename = basename.replace(ext, '');
58 |
59 | let arch = '';
60 | if (ext === '.app.tar.gz.sig' || ext === '.app.tar.gz') {
61 | if (assetPath.includes('universal-apple-darwin')) {
62 | arch = 'universal';
63 | } else if (assetPath.includes('aarch64-apple-darwin')) {
64 | arch = 'aarch64';
65 | } else if (assetPath.includes('x86_64-apple-darwin')) {
66 | arch = 'x64';
67 | } else {
68 | arch = process.arch === 'arm64' ? 'aarch64' : 'x64';
69 | }
70 | }
71 |
72 | return { basename, ext, filename, arch };
73 | }
74 |
75 | export function renderNamePattern(
76 | pattern: string,
77 | replacements: Record,
78 | ) {
79 | return pattern.replace(/\[(\w+)]/g, (match, type: string) => {
80 | if (!Object.prototype.hasOwnProperty.call(replacements, type)) {
81 | return match;
82 | }
83 | const replacement = replacements[type];
84 | return replacement;
85 | });
86 | }
87 |
88 | export function getAssetName(asset: Artifact, pattern?: string) {
89 | // TODO(v1): In a future version we may want to unify the naming schemes. For now we keep using the cli output.
90 | // const DEFAULT_PATTERN = `[name]_v[version]_[platform]_[arch][ext]`;
91 | // pattern = pattern || DEFAULT_PATTERN;
92 |
93 | if (asset.name === 'latest.json') {
94 | return 'latest.json';
95 | }
96 |
97 | if (pattern) {
98 | return renderNamePattern(
99 | pattern,
100 | asset as unknown as Record,
101 | );
102 | } else {
103 | if (
104 | asset.ext !== '.app.tar.gz' &&
105 | asset.ext !== '.app.tar.gz.sig' &&
106 | asset.name !== 'binary'
107 | ) {
108 | // See TODO above, in most cases we keep the file name set by tauri's cli.
109 | return basename(asset.path);
110 | }
111 |
112 | const name = basename(asset.path, asset.ext);
113 | const arch = '_' + asset.arch;
114 | let platform = '';
115 | let version = '';
116 |
117 | if (asset.name === 'binary') {
118 | platform = '_' + asset.platform;
119 | }
120 |
121 | if (asset.ext.includes('.app.tar.gz')) {
122 | version = '_' + asset.version;
123 | }
124 |
125 | return name + platform + version + arch + asset.ext;
126 | }
127 | }
128 |
129 | export function ghAssetName(
130 | artifact: Artifact,
131 | releaseAssetNamePattern?: string,
132 | ) {
133 | return getAssetName(artifact, releaseAssetNamePattern)
134 | .trim()
135 | .replace(/[^a-zA-Z0-9_-]/g, '.')
136 | .replace(/\.\./g, '.');
137 | }
138 |
139 | export function createArtifact({
140 | path,
141 | name,
142 | platform,
143 | arch,
144 | bundle,
145 | version,
146 | }: {
147 | path: string;
148 | name: string;
149 | platform: TargetPlatform;
150 | arch: string;
151 | bundle: string;
152 | version: string;
153 | }): Artifact {
154 | const baseName = basename(path);
155 | const exts = extensions.filter((s) => baseName.includes(s));
156 | const ext = exts[0] || extname(path);
157 | let workflowArtifactName;
158 | if (
159 | name === 'binary' ||
160 | [
161 | '.app',
162 | '.dmg',
163 | '.exe',
164 | '.msi',
165 | '.deb',
166 | '.rpm',
167 | '.AppImage',
168 | '.apk',
169 | '.aab',
170 | '.ipa',
171 | ].includes(ext)
172 | ) {
173 | workflowArtifactName = `${platform}-${arch}-${bundle}`;
174 | }
175 |
176 | return {
177 | path,
178 | name,
179 | mode: isDebug ? 'debug' : 'release',
180 | platform: platform === 'macos' ? 'darwin' : platform,
181 | arch,
182 | bundle,
183 | ext,
184 | version,
185 | setup: bundle === 'nsis' ? '-setup' : '',
186 | _setup: bundle === 'nsis' ? '_setup' : '',
187 | workflowArtifactName,
188 | };
189 | }
190 |
191 | export function getPackageJson(root: string) {
192 | const packageJsonPath = join(root, 'package.json');
193 | if (existsSync(packageJsonPath)) {
194 | const packageJsonString = readFileSync(packageJsonPath).toString();
195 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return
196 | return JSON.parse(packageJsonString);
197 | }
198 | return null;
199 | }
200 |
201 | export function getTauriDir(): string | null {
202 | const tauriConfPaths = globbySync(
203 | ['**/tauri.conf.json', '**/tauri.conf.json5', '**/Tauri.toml'],
204 | {
205 | // globby v16 changes this to also look into parent dir. Monitor this closely and disable if needed.
206 | gitignore: true,
207 | cwd: projectPath,
208 | // Forcefully ignore target and node_modules dirs
209 | ignore: ['**/target', '**/node_modules'],
210 | },
211 | );
212 |
213 | if (tauriConfPaths.length === 0) {
214 | return null;
215 | }
216 |
217 | return resolve(projectPath, tauriConfPaths[0], '..');
218 | }
219 |
220 | export function getWorkspaceDir(dir: string): string | null {
221 | const rootPath = dir;
222 |
223 | while (dir.length && dir[dir.length - 1] !== sep) {
224 | const manifestPath = join(dir, 'Cargo.toml');
225 | if (existsSync(manifestPath)) {
226 | const toml = TOML.parse(readFileSync(manifestPath).toString()) as {
227 | workspace?: { members?: string[]; exclude?: string[] };
228 | };
229 | if (toml.workspace?.members) {
230 | const ignore = ['**/target', '**/node_modules'];
231 | if (toml.workspace.exclude) ignore.push(...toml.workspace.exclude);
232 |
233 | const memberPaths = globbySync(toml.workspace.members, {
234 | cwd: dir,
235 | ignore,
236 | expandDirectories: false,
237 | onlyFiles: false,
238 | });
239 |
240 | if (memberPaths.some((m) => resolve(dir, m) === rootPath)) {
241 | return dir;
242 | }
243 | }
244 | }
245 |
246 | dir = normalize(join(dir, '..'));
247 | }
248 | return null;
249 | }
250 |
251 | export function getTargetDir(
252 | workspacePath: string,
253 | tauriPath: string,
254 | targetArgSet: boolean,
255 | ): string {
256 | // The default path if no configs are set.
257 | const def = join(workspacePath, 'target');
258 |
259 | // This will hold the path of current iteration
260 | let dir = tauriPath;
261 |
262 | // hold on to target-dir cargo config while we search for build.target
263 | let targetDir;
264 | // same for build.target
265 | let targetDirExt;
266 |
267 | // The env var takes precedence over config files.
268 | if (process.env.CARGO_TARGET_DIR) {
269 | targetDir = process.env.CARGO_TARGET_DIR ?? def;
270 | }
271 |
272 | while (dir.length && dir[dir.length - 1] !== sep) {
273 | let cargoConfigPath = join(dir, '.cargo/config');
274 | if (!existsSync(cargoConfigPath)) {
275 | cargoConfigPath = join(dir, '.cargo/config.toml');
276 | }
277 | if (existsSync(cargoConfigPath)) {
278 | const cargoConfig = TOML.parse(
279 | readFileSync(cargoConfigPath).toString(),
280 | ) as CargoConfig;
281 |
282 | if (!targetDir && cargoConfig.build?.['target-dir']) {
283 | const t = cargoConfig.build['target-dir'];
284 | if (path.isAbsolute(t)) {
285 | targetDir = t;
286 | } else {
287 | targetDir = normalize(join(dir, t));
288 | }
289 | }
290 |
291 | // Even if build.target is the same as the default target it will change the output dir.
292 | // Just like tauri we only support a single string, not an array (bug?).
293 | // targetArgSet: --target overwrites the .cargo/config.toml target value so we check for that too.
294 | if (
295 | !targetArgSet &&
296 | !targetDirExt &&
297 | typeof cargoConfig.build?.target === 'string'
298 | ) {
299 | targetDirExt = cargoConfig.build.target;
300 | }
301 | }
302 |
303 | // If we got both we don't need to keep going
304 | if (targetDir && targetDirExt) break;
305 |
306 | // Prepare the path for the next iteration
307 | dir = normalize(join(dir, '..'));
308 | }
309 |
310 | if (targetDir) {
311 | return normalize(join(targetDir, targetDirExt ?? ''));
312 | }
313 |
314 | return normalize(join(def, targetDirExt ?? ''));
315 | }
316 |
317 | export function getCargoManifest(dir: string): CargoManifest {
318 | const manifestPath = join(dir, 'Cargo.toml');
319 | const cargoManifest = TOML.parse(
320 | readFileSync(manifestPath).toString(),
321 | ) as unknown as CargoManifest & {
322 | package: {
323 | version: { workspace: true } | string;
324 | name: { workspace: true } | string;
325 | };
326 | };
327 |
328 | let name = cargoManifest.package.name;
329 | let version = cargoManifest.package.version;
330 |
331 | // if the version or name is an object, it means it is a workspace package and we need to traverse up
332 | if (
333 | typeof cargoManifest.package.version == 'object' ||
334 | typeof cargoManifest.package.name == 'object'
335 | ) {
336 | const workspaceDir = getWorkspaceDir(dir);
337 | if (!workspaceDir) {
338 | throw new Error(
339 | 'Could not find workspace directory, but version and/or name specifies to use workspace package',
340 | );
341 | }
342 | const manifestPath = join(workspaceDir, 'Cargo.toml');
343 | const workspaceManifest = TOML.parse(
344 | readFileSync(manifestPath).toString(),
345 | ) as unknown as CargoManifest;
346 |
347 | if (
348 | typeof name === 'object' &&
349 | workspaceManifest?.workspace?.package?.name !== undefined
350 | ) {
351 | name = workspaceManifest.workspace.package.name;
352 | }
353 | if (
354 | typeof version === 'object' &&
355 | workspaceManifest?.workspace?.package?.version !== undefined
356 | ) {
357 | version = workspaceManifest.workspace.package.version;
358 | }
359 | }
360 |
361 | return {
362 | ...cargoManifest,
363 | package: {
364 | ...cargoManifest.package,
365 | name,
366 | version,
367 | },
368 | };
369 | }
370 |
371 | export function hasDependency(dependencyName: string, root: string): boolean {
372 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
373 | const packageJson = getPackageJson(root);
374 | // eslint-disable-next-line @typescript-eslint/no-unsafe-return
375 | return (
376 | packageJson &&
377 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
378 | (packageJson.dependencies?.[dependencyName] ||
379 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
380 | packageJson.devDependencies?.[dependencyName])
381 | );
382 | }
383 |
384 | export function hasTauriScript(root: string): boolean {
385 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
386 | const packageJson = getPackageJson(root);
387 | return (
388 | !!packageJson &&
389 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
390 | !!packageJson.scripts?.['tauri']
391 | );
392 | }
393 |
394 | export function usesNpm(cwd: string): boolean {
395 | if (findUpSync('package-lock.json', { cwd })) {
396 | if (isRunnerInstalled('npm')) {
397 | return true;
398 | } else {
399 | console.warn(
400 | "package-lock.json detected but couldn't find `npm` executable.",
401 | );
402 | }
403 | }
404 | return false;
405 | }
406 |
407 | export function usesYarn(cwd: string): boolean {
408 | if (findUpSync('yarn.lock', { cwd })) {
409 | if (isRunnerInstalled('yarn')) {
410 | return true;
411 | } else {
412 | console.warn("yarn.lock detected but couldn't find `yarn` executable.");
413 | }
414 | }
415 | return false;
416 | }
417 |
418 | export function usesPnpm(cwd: string): boolean {
419 | if (findUpSync('pnpm-lock.yaml', { cwd })) {
420 | if (isRunnerInstalled('pnpm')) {
421 | return true;
422 | } else {
423 | console.warn(
424 | "pnpm-lock.yaml detected but couldn't find `pnpm` executable.",
425 | );
426 | }
427 | }
428 | return false;
429 | }
430 |
431 | export function usesBun(cwd: string): boolean {
432 | if (findUpSync('bun.lockb', { cwd }) || findUpSync('bun.lock', { cwd })) {
433 | if (isRunnerInstalled('bun')) {
434 | return true;
435 | } else {
436 | console.warn("bun.lock(b) detected but couldn't find `bun` executable.");
437 | }
438 | }
439 | return false;
440 | }
441 |
442 | function isRunnerInstalled(runner: string) {
443 | const bin = process.platform === 'win32' ? 'where.exe' : 'which';
444 | try {
445 | return execaSync(bin, [runner]).exitCode === 0;
446 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
447 | } catch (_e) {
448 | return false;
449 | }
450 | }
451 |
452 | export async function execCommand(
453 | command: string,
454 | args: string[],
455 | { cwd }: { cwd?: string } = {},
456 | env: Record = {},
457 | ): Promise {
458 | console.log(`running ${command}`, args);
459 |
460 | const child = execa(command, args, {
461 | cwd,
462 | env: { FORCE_COLOR: '0', ...env },
463 | lines: true,
464 | stdio: 'pipe',
465 | reject: false,
466 | });
467 |
468 | child.stdout?.on('data', (data) => {
469 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
470 | process.stdout.write(data);
471 | });
472 |
473 | child.stderr?.on('data', (data) => {
474 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
475 | process.stderr.write(data);
476 | });
477 |
478 | return new Promise((resolve, reject) => {
479 | child.on('exit', (code) => {
480 | if (code && code > 0) {
481 | reject(
482 | new Error(
483 | `Command "${command} ${JSON.stringify(args)}" failed with exit code ${code}`,
484 | ),
485 | );
486 | } else {
487 | resolve();
488 | }
489 | });
490 | });
491 | }
492 |
493 | export function getInfo(targetInfo?: TargetInfo, configFlag?: string): Info {
494 | const tauriDir = getTauriDir();
495 | if (tauriDir !== null) {
496 | let name;
497 | let version;
498 | let wixLanguage: string | string[] | { [language: string]: unknown } =
499 | 'en-US';
500 | let rpmRelease = '1';
501 |
502 | const config = TauriConfig.fromBaseConfig(tauriDir);
503 |
504 | if (targetInfo) {
505 | config.mergePlatformConfig(tauriDir, targetInfo.platform);
506 | }
507 | if (configFlag) {
508 | config.mergeUserConfig(projectPath, configFlag);
509 | }
510 |
511 | name = config?.productName;
512 |
513 | if (config.version?.endsWith('.json')) {
514 | const packageJsonPath = join(tauriDir, config?.version);
515 | const contents = readFileSync(packageJsonPath).toString();
516 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
517 | version = JSON.parse(contents).version as string;
518 | } else {
519 | version = config?.version;
520 | }
521 |
522 | const cargoManifest = getCargoManifest(tauriDir);
523 | if (!(name && version)) {
524 | name = name ?? cargoManifest.package.name;
525 | version = version ?? cargoManifest.package.version;
526 | }
527 |
528 | if (!(name && version)) {
529 | console.error('Could not determine package name and version.');
530 | process.exit(1);
531 | }
532 |
533 | if (config.wixLanguage) {
534 | wixLanguage = config.wixLanguage;
535 | }
536 |
537 | if (config.rpmRelease) {
538 | rpmRelease = config.rpmRelease;
539 | }
540 |
541 | return {
542 | tauriPath: tauriDir,
543 | name,
544 | mainBinaryName: config.mainBinaryName || cargoManifest.package.name,
545 | version,
546 | wixLanguage,
547 | rpmRelease,
548 | unzippedSigs: config.unzippedSigs === true,
549 | };
550 | } else {
551 | // This should not actually happen.
552 | throw Error("Couldn't detect Tauri dir");
553 | }
554 | }
555 |
556 | export function getTargetInfo(targetPath?: string): TargetInfo {
557 | let arch: string = process.arch;
558 | let platform: TargetPlatform;
559 | if (isAndroid) {
560 | platform = 'android';
561 | } else if (isIOS) {
562 | platform = 'ios';
563 | } else if (process.platform === 'win32') {
564 | platform = 'windows';
565 | } else if (process.platform === 'darwin') {
566 | platform = 'macos';
567 | } else {
568 | platform = 'linux';
569 | }
570 |
571 | if (targetPath) {
572 | if (targetPath.includes('windows')) {
573 | platform = 'windows';
574 | } else if (targetPath.includes('darwin') || targetPath.includes('macos')) {
575 | platform = 'macos';
576 | } else if (targetPath.includes('linux')) {
577 | platform = 'linux';
578 | } else if (targetPath.includes('android')) {
579 | platform = 'android';
580 | } else if (targetPath.includes('ios')) {
581 | platform = 'ios';
582 | }
583 |
584 | if (targetPath.includes('-')) {
585 | arch = targetPath.split('-')[0];
586 | }
587 | }
588 |
589 | return { arch, platform };
590 | }
591 |
592 | /// Will run provided fn at least once plus the provided attempts on failures
593 | /// Examples
594 | /// - retry(fn, 0) = run fn once then return no matter the success status
595 | /// - retry(fn, 3) = if all tries fail, fn will be executed 4 times
596 | export async function retry(
597 | fn: () => Promise,
598 | additionalAttempts: number,
599 | ): Promise {
600 | const attempts = additionalAttempts + 1;
601 | for (let attempt = 1; attempt <= attempts; attempt++) {
602 | try {
603 | return await fn();
604 | } catch (error) {
605 | if (attempt >= attempts) throw error;
606 | console.log(
607 | `Attempt ${attempt} failed. ${attempts - attempt} tries left.`,
608 | );
609 | }
610 | }
611 | }
612 |
613 | // Helper function to delete a Gitea release asset
614 | // This is a workaround since Gitea's API is incompatible with the GitHub API
615 | export function deleteGiteaReleaseAsset(
616 | github: InstanceType,
617 | releaseId: number,
618 | assetId: number,
619 | ) {
620 | return github.request(
621 | 'DELETE /repos/{owner}/{repo}/releases/{release_id}/assets/{asset_id}',
622 | {
623 | owner,
624 | repo,
625 | release_id: releaseId,
626 | asset_id: assetId,
627 | },
628 | );
629 | }
630 |
631 | // TODO: Properly resolve the eslint issues in this file.
632 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## \[0.6.0]
4 |
5 | - [`e918a06`](https://www.github.com/tauri-apps/tauri-action/commit/e918a068fe65bde93e97fe0d4024e45dc568c536) ([#1133](https://www.github.com/tauri-apps/tauri-action/pull/1133)) Encode `tagName` option value in `latest.json` URL.
6 | - [`45acc19`](https://www.github.com/tauri-apps/tauri-action/commit/45acc1922a6fe824bcffed136e9b5d80660ed050) ([#1164](https://www.github.com/tauri-apps/tauri-action/pull/1164)) Add `generateReleaseNotes` config to use GitHub's release notes API to auto generate the release name and/or body.
7 | - [`59a1a70`](https://www.github.com/tauri-apps/tauri-action/commit/59a1a70223956f123844e5f309cf5cf82d279685) ([#1166](https://www.github.com/tauri-apps/tauri-action/pull/1166)) Fix default file name pattern to match tauri's file names again.
8 | - [`08112f7`](https://www.github.com/tauri-apps/tauri-action/commit/08112f729df63ddc8f64de706284e8518f1b1e73) ([#1106](https://www.github.com/tauri-apps/tauri-action/pull/1106)) Added experimental support for Gitea hosted instances.
9 | - [`08112f7`](https://www.github.com/tauri-apps/tauri-action/commit/08112f729df63ddc8f64de706284e8518f1b1e73) ([#1106](https://www.github.com/tauri-apps/tauri-action/pull/1106)) Added a config to set the GitHub API URL which should help users with self-hosted instances and those using GitHub Enterprise.
10 | - [`b067139`](https://www.github.com/tauri-apps/tauri-action/commit/b0671399f144ae28a851ef8c3e0540b5d5c6cd37) ([#1130](https://www.github.com/tauri-apps/tauri-action/pull/1130)) The action now uses node v24 which raises the minimum GitHub runner version to `v2.327.1`.
11 |
12 | ## \[0.5.24]
13 |
14 | - [`c5d6ac7`](https://www.github.com/tauri-apps/tauri-action/commit/c5d6ac7494763a167f5acf6e6c73664fc7360468) ([#1152](https://www.github.com/tauri-apps/tauri-action/pull/1152) by [@FabianLars](https://www.github.com/tauri-apps/tauri-action/../../FabianLars)) The `latest.json` file now contains `{os}-{arch}-{installer}` keys as well to support multiple installer formats per platform. This requires `tauri-plugin-updater` version `2.10.0` or above.
15 | - [`0085932`](https://www.github.com/tauri-apps/tauri-action/commit/0085932c0f8ac87d9757582e84c0fc6fbc0f7699) ([#1141](https://www.github.com/tauri-apps/tauri-action/pull/1141) by [@jarjk](https://www.github.com/tauri-apps/tauri-action/../../jarjk)) Added option to upload the app's binary alongside installers.
16 |
17 | ## \[0.5.23]
18 |
19 | - [`5b1138d`](https://www.github.com/tauri-apps/tauri-action/commit/5b1138d93ce221ac6bfd4f0decb41b6d87963ff4) ([#1099](https://www.github.com/tauri-apps/tauri-action/pull/1099) by [@lete114](https://www.github.com/tauri-apps/tauri-action/../../lete114)) Added the `assetNamePattern` config that allows setting a template specifying how uploaded assets will be named in the release.
20 | **BREAKING CHANGE:** The default naming scheme will now have the `-debug` suffix at the end (before the extension) on all assets.
21 | - [`fcedb49`](https://www.github.com/tauri-apps/tauri-action/commit/fcedb49796d1ab97cc5f07e8ddabcdf937b38344) ([#1125](https://www.github.com/tauri-apps/tauri-action/pull/1125) by [@ChanTsune](https://www.github.com/tauri-apps/tauri-action/../../ChanTsune)) Fixed an issue that caused the action to look for `x64` artifacts on native ARM runners.
22 |
23 | ## \[0.5.22]
24 |
25 | - [`be57b0c`](https://www.github.com/tauri-apps/tauri-action/commit/be57b0cca9e9ee2c15112c583ddb34e32157cc6a) ([#1100](https://www.github.com/tauri-apps/tauri-action/pull/1100) by [@FabianLars](https://www.github.com/tauri-apps/tauri-action/../../FabianLars)) Fixed an issue that caused the action to upload only the first artifact it found.
26 |
27 | ## \[0.5.21]
28 |
29 | - [`5666b68`](https://www.github.com/tauri-apps/tauri-action/commit/5666b68581044b3981c75a489540c3d570131ee7) ([#1030](https://www.github.com/tauri-apps/tauri-action/pull/1030) by [@997R8V10](https://www.github.com/tauri-apps/tauri-action/../../997R8V10)) `retryAttempts` is now also applied to uploads.
30 |
31 | ## \[0.5.20]
32 |
33 | - [`563aed7`](https://www.github.com/tauri-apps/tauri-action/commit/563aed7bd10e6ccaf328e73a66bb53341e88fef2) ([#1031](https://www.github.com/tauri-apps/tauri-action/pull/1031) by [@Muska-Ami](https://www.github.com/tauri-apps/tauri-action/../../Muska-Ami)) Add support for Bun's text-based lockfile.
34 |
35 | ## \[0.5.19]
36 |
37 | - [`fd1ea0d`](https://www.github.com/tauri-apps/tauri-action/commit/fd1ea0d8e31d0f8670cf14c79c076d85caf9fb53) ([#1009](https://www.github.com/tauri-apps/tauri-action/pull/1009) by [@FabianLars](https://www.github.com/tauri-apps/tauri-action/../../FabianLars)) Fixed an issue that made the action fail to find .msi packages for tauri v2 apps with numeric build numbers (`1.0.0-0`).
38 |
39 | ## \[0.5.18]
40 |
41 | - [`91c7ee8`](https://www.github.com/tauri-apps/tauri-action/commit/91c7ee83a6b312f9d9879e049242d623ae38f1f0) ([#978](https://www.github.com/tauri-apps/tauri-action/pull/978) by [@Chiichen](https://www.github.com/tauri-apps/tauri-action/../../Chiichen)) Fix [This is not building for Latest JSON and Not uploading all assets](https://github.com/tauri-apps/tauri-action/issues/975)
42 |
43 | ## \[0.5.17]
44 |
45 | - [`fb76925`](https://www.github.com/tauri-apps/tauri-action/commit/fb769255b02569d9f2e5f2e021ad3c29f241f401) ([#895](https://www.github.com/tauri-apps/tauri-action/pull/895) by [@Chiichen](https://www.github.com/tauri-apps/tauri-action/../../Chiichen)) Upload assets to the release associated with given `tagName`
46 | - [`29dbe80`](https://www.github.com/tauri-apps/tauri-action/commit/29dbe80fdda4ab77cd60f313c6e9ad692d4335e3) ([#964](https://www.github.com/tauri-apps/tauri-action/pull/964) by [@louis030195](https://www.github.com/tauri-apps/tauri-action/../../louis030195)) Add retry mechanism for failed builds to improve CI reliability
47 |
48 | ## \[0.5.16]
49 |
50 | - [`acdef25`](https://www.github.com/tauri-apps/tauri-action/commit/acdef251b8450d568e025c512753e61ef582fa8f) ([#949](https://www.github.com/tauri-apps/tauri-action/pull/949) by [@FabianLars](https://www.github.com/tauri-apps/tauri-action/../../FabianLars)) The action will now set `TAURI_BUNDLER_DMG_IGNORE_CI: true` by default on tauri cli versions 2.2.0 and above. See https://github.com/tauri-apps/tauri-action/issues/740 for context. This can be disabled by explicitly setting `TAURI_BUNDLER_DMG_IGNORE_CI: false` yourself.
51 |
52 | ## \[0.5.15]
53 |
54 | - [`f575715`](https://www.github.com/tauri-apps/tauri-action/commit/f575715ef970f081f9942c4867a8657d6d6dbfc1) ([#929](https://www.github.com/tauri-apps/tauri-action/pull/929) by [@FabianLars](https://www.github.com/tauri-apps/tauri-action/../../FabianLars)) The action will now try to check for the tauri version before installing the tauri cli fallback (if no tauri cli was found) instead of always installing the latest stable version.
55 |
56 | ## \[0.5.14]
57 |
58 | - [`9387d95`](https://www.github.com/tauri-apps/tauri-action/commit/9387d95d400af0a0c6c4199962a962e93d13cb4d) ([#908](https://www.github.com/tauri-apps/tauri-action/pull/908) by [@FabianLars](https://www.github.com/tauri-apps/tauri-action/../../FabianLars)) The action will now only use the signature file for unzipped bundles if `createUpdaterArtifacts` in tauri.conf.json is set to `true`.
59 |
60 | ## \[0.5.13]
61 |
62 | - [`3b72cab`](https://www.github.com/tauri-apps/tauri-action/commit/3b72cab93fb2fbac61fc5b91cbede2fee647dd82) ([#893](https://www.github.com/tauri-apps/tauri-action/pull/893) by [@Muska-Ami](https://www.github.com/tauri-apps/tauri-action/../../Muska-Ami)) Use Bun for the build when the `bun.locb` file is found.
63 |
64 | ## \[0.5.12]
65 |
66 | - [`93d570b`](https://www.github.com/tauri-apps/tauri-action/commit/93d570b03af965a5751e2079c1b3d264b451f300) ([#863](https://www.github.com/tauri-apps/tauri-action/pull/863) by [@tobyspark](https://www.github.com/tauri-apps/tauri-action/../../tobyspark)) Reduces memory consumption when uploading successfully built releases, by passing a file stream object rather than reading the entire file into a buffer and then passing that. Empirically, this has stopped the action from failing on GitHub's Windows runners, with apps approaching 2GB in size.
67 |
68 | ## \[0.5.11]
69 |
70 | - [`70f5023`](https://www.github.com/tauri-apps/tauri-action/commit/70f50235fd767d6357440bc26d78fe6a0fe02545) ([#873](https://www.github.com/tauri-apps/tauri-action/pull/873) by [@FabianLars](https://www.github.com/tauri-apps/tauri-action/../../FabianLars)) Support new RPM file name that was fixed in tauri-cli@2.0.0-beta.21.
71 |
72 | ## \[0.5.10]
73 |
74 | - [`f876b0d`](https://www.github.com/tauri-apps/tauri-action/commit/f876b0d6a0b9306a8030e98b0b9dda2fb231059e) ([#861](https://www.github.com/tauri-apps/tauri-action/pull/861) by [@vdemcak](https://www.github.com/tauri-apps/tauri-action/../../vdemcak)) Fixed an issue that caused the action to not generate `latest.json` due to a desync between GitHub artifacts and local variables. This was caused by incorrect normalization of artifact file names, specifically not accounting for removing special characters.
75 |
76 | ## \[0.5.9]
77 |
78 | - [`ff07d2a`](https://www.github.com/tauri-apps/tauri-action/commit/ff07d2a6ce69514dddf7cde3ba0b866dad1e07e0) ([#849](https://www.github.com/tauri-apps/tauri-action/pull/849) by [@FabianLars](https://www.github.com/tauri-apps/tauri-action/../../FabianLars)) Fixed an issue that caused the action to fail to upload some assets to existing releases if the workflow built the app for many architectures and debug + release mode.
79 |
80 | ## \[0.5.8]
81 |
82 | - [`621de48`](https://www.github.com/tauri-apps/tauri-action/commit/621de481ebf76558785277c9654f3befeaf0bd35) ([#845](https://www.github.com/tauri-apps/tauri-action/pull/845) by [@Legend-Master](https://www.github.com/tauri-apps/tauri-action/../../Legend-Master)) Fix can't find updater file name with spaces in them and can't pick up unzipped updater signatures
83 |
84 | ## \[0.5.7]
85 |
86 | - [`f8044a1`](https://www.github.com/tauri-apps/tauri-action/commit/f8044a1d9fa468d71dd285d9f17b467dc8c9b334) ([#837](https://www.github.com/tauri-apps/tauri-action/pull/837) by [@FabianLars](https://www.github.com/tauri-apps/tauri-action/../../FabianLars)) Fixed an issue that caused the action to be unable to pick up the app bundles if the `productName` contained any of these characters: `()[]{}`.
87 | - [`edd3869`](https://www.github.com/tauri-apps/tauri-action/commit/edd386979eff0987d210b7043491b7b3fef906b4) ([#766](https://www.github.com/tauri-apps/tauri-action/pull/766) by [@Legend-Master](https://www.github.com/tauri-apps/tauri-action/../../Legend-Master)) Support non zipped updater for Windows and Linux
88 |
89 | ## \[0.5.6]
90 |
91 | - [`d80ec2c`](https://www.github.com/tauri-apps/tauri-action/commit/d80ec2ce013f37a16774e5dfe5ca51d3fb12ef1a) ([#821](https://www.github.com/tauri-apps/tauri-action/pull/821) by [@pewsheen](https://www.github.com/tauri-apps/tauri-action/../../pewsheen)) Fixed an issue that the action can't find aarch64 rpm package.
92 |
93 | ## \[0.5.5]
94 |
95 | - [`10eca12`](https://www.github.com/tauri-apps/tauri-action/commit/10eca12d4b7138e3c7dca7bccaa170a8784ea3f5) ([#810](https://www.github.com/tauri-apps/tauri-action/pull/810)) The action can now detects Linux bundles with the new naming convention added in tauri cli 2.0.0-beta.19
96 |
97 | ## \[0.5.4]
98 |
99 | - [`ec3a63a`](https://www.github.com/tauri-apps/tauri-action/commit/ec3a63a669b74a6069703e9dc1c3e0bd72abad62)([#799](https://www.github.com/tauri-apps/tauri-action/pull/799)) Fixed an issue that caused the action to not detect ARM AppImages.
100 |
101 | ## \[0.5.3]
102 |
103 | - [`6c3f5cf`](https://www.github.com/tauri-apps/tauri-action/commit/6c3f5cf8dbe7410537547f57aef00238e53e931f)([#779](https://www.github.com/tauri-apps/tauri-action/pull/779)) Fixed an issue that caused `tauri-action` to not detect `build.target` in `.cargo/config.toml` if the app was part of a cargo workspace.
104 |
105 | ## \[0.5.2]
106 |
107 | - [`14e3c6c`](https://www.github.com/tauri-apps/tauri-action/commit/14e3c6c0d54349a0fe1eb576b560883744c07303)([#776](https://www.github.com/tauri-apps/tauri-action/pull/776)) Fixed an issue causing x86\_64 artifacts to be handled as aarch64 on GitHub's new M1 runners.
108 |
109 | ## \[0.5.1]
110 |
111 | - [`f2abe36`](https://www.github.com/tauri-apps/tauri-action/commit/f2abe36fa8a59765d670b75f823b2ed3e93b40ab)([#711](https://www.github.com/tauri-apps/tauri-action/pull/711)) tauri-action can now successfully build binaries even if they don't have any artifacts (`bundle.active: false`)
112 | - [`f1b5af3`](https://www.github.com/tauri-apps/tauri-action/commit/f1b5af3268dd7853edbef9a055dd9e9d051de9e1)([#724](https://www.github.com/tauri-apps/tauri-action/pull/724)) The action now correctly ignores the `[build.target]` value in `.cargo/config.toml` if the `--target` arg is set.
113 | - [`901a25d`](https://www.github.com/tauri-apps/tauri-action/commit/901a25d04f844266e79c65414f31b34d52089219)([#713](https://www.github.com/tauri-apps/tauri-action/pull/713)) Fixed an issue that caused the action to not merge user and platform configs into the base tauri config correctly.
114 |
115 | ## \[0.5.0]
116 |
117 | - [`d618a42`](https://www.github.com/tauri-apps/tauri-action/commit/d618a422b9e0fbca4fd2436be4f6368453c45a7e)([#645](https://www.github.com/tauri-apps/tauri-action/pull/645)) The action added `appVersion` parameter to facilitate easy access to the current application version in action output.
118 | - [`cb393bf`](https://www.github.com/tauri-apps/tauri-action/commit/cb393bfe3b4fb834693989499769ff8fc24ada26)([#611](https://www.github.com/tauri-apps/tauri-action/pull/611)) **Breaking:** The action no longer supports `vue-cli-plugin-tauri` since it was deprecated like `vue-cli` itself. Please migrate to `@tauri-apps/cli`.
119 | - [`b87a544`](https://www.github.com/tauri-apps/tauri-action/commit/b87a544c7a8ad25f6ff41f0a4a20b8f711056008)([#626](https://www.github.com/tauri-apps/tauri-action/pull/626)) The action now correctly handles glob patterns in the workspace.members config (example: `members = ["bin/*"]`).
120 | - [`0ae6017`](https://www.github.com/tauri-apps/tauri-action/commit/0ae60177b83b43bbaa0da921afe7262787a0441a)([#684](https://www.github.com/tauri-apps/tauri-action/pull/684)) The action now correctly handles the wix version after the build of the app in case the version includes a `+` or `-` character.
121 | - [`b862ca0`](https://www.github.com/tauri-apps/tauri-action/commit/b862ca088ab308dee6bf035f4003f1f446e11438)([#602](https://www.github.com/tauri-apps/tauri-action/pull/602)) **Breaking:** The action no longer tries to read a package.json file for the app name and version when initializing a tauri app. Use the `appName` and `appVersion` input arguments or the `--config` flag.
122 | - [`1fb5053`](https://www.github.com/tauri-apps/tauri-action/commit/1fb5053d19d6a3c1c5a2b1d39e3d5ce2bf448ca5)([#657](https://www.github.com/tauri-apps/tauri-action/pull/657)) The action now always packages the macOS `.app` bundle into a `.tar.gz` archive even if the action is not configured to upload anything.
123 | - [`27089ad`](https://www.github.com/tauri-apps/tauri-action/commit/27089ade7a1c5a985c91b23424bd5017b8148595)([#659](https://www.github.com/tauri-apps/tauri-action/pull/659)) The action now reads `build.target` from `.cargo/config` toml to get the correct `target` directory.
124 | - [`37e9ece`](https://www.github.com/tauri-apps/tauri-action/commit/37e9ece68aebd830aaa7c14c7602d700d8513f6a)([#651](https://www.github.com/tauri-apps/tauri-action/pull/651)) Add support for RPM bundle artifacts, introduced in tauri-bundler@2.0.0-alpha.14
125 | - [`81921ba`](https://www.github.com/tauri-apps/tauri-action/commit/81921ba9d3c8163235d21b262dd0c3ad3fb19029)([#702](https://www.github.com/tauri-apps/tauri-action/pull/702)) Add support for Tauri's new config structure introduced in `2.0.0-beta.0`.
126 |
127 | ## \[0.4.5]
128 |
129 | - [`2b7cd25`](https://www.github.com/tauri-apps/tauri-action/commit/2b7cd25a7d13b4d3bb90a8b0d4423686466120d4)([#598](https://www.github.com/tauri-apps/tauri-action/pull/598)) Fix path resolution for `build.target-dir` if the `.cargo` folder is not in the current working dir.
130 |
131 | ## \[0.4.4]
132 |
133 | - [`9df5eca`](https://www.github.com/tauri-apps/tauri-action/commit/9df5eca322fa3298954fb973a68c65cf2b48aebd) Fixed an issue where the distPath config was not applied after initializing the tauri project.
134 | - [`d9623e3`](https://www.github.com/tauri-apps/tauri-action/commit/d9623e36cbe6b2668d8abc98677e9113a6ace705)([#556](https://www.github.com/tauri-apps/tauri-action/pull/556)) Fixes the artifacts search path when a custom `--profile` is used.
135 | - [`802a179`](https://www.github.com/tauri-apps/tauri-action/commit/802a179bc09148fdf618276fc9f2945ac3797ed2)([#594](https://www.github.com/tauri-apps/tauri-action/pull/594)) If the action initializes the tauri project it will now clear the `beforeBuildCommand` to fix a panic when there was no `build` npm command available.
136 | - [`d00117a`](https://www.github.com/tauri-apps/tauri-action/commit/d00117a2e07c81cc900146e07064ff4b2a8782db)([#558](https://www.github.com/tauri-apps/tauri-action/pull/558)) Fixed an issue reading the app version if it relied on cargo's workspace inheritance feature.
137 |
138 | ## \[0.4.3]
139 |
140 | - [`c87af54`](https://www.github.com/tauri-apps/tauri-action/commit/c87af545bb34bb0b5d981811497ede8d99f5ebd8)([#502](https://www.github.com/tauri-apps/tauri-action/pull/502)) While looking for the tauri directory the action will now respect all gitignore files and not just the one in the root dir.
141 | - [`8e6f88e`](https://www.github.com/tauri-apps/tauri-action/commit/8e6f88e39f9947e2d20a1a23632f93b84e913acb)([#499](https://www.github.com/tauri-apps/tauri-action/pull/499)) The action now prefers release builds for the latest.json file if both, release and debug releases are enabled.
142 | - [`36a1260`](https://www.github.com/tauri-apps/tauri-action/commit/36a12601f6d5fcfbcea27f53a8bb5379327c2a19)([#490](https://www.github.com/tauri-apps/tauri-action/pull/490)) Correctly detect self-hosted macOS-arm64 runners.
143 | - [`8d5274b`](https://www.github.com/tauri-apps/tauri-action/commit/8d5274b2b3f1e03582a13e1d216003a91170b366)([#477](https://www.github.com/tauri-apps/tauri-action/pull/477)) Read config after `tauri init` command and without hardcoding the `tauri.conf.json` path, fixes action failures without error messages on repos without an existing Tauri project.
144 | - [`c87af54`](https://www.github.com/tauri-apps/tauri-action/commit/c87af545bb34bb0b5d981811497ede8d99f5ebd8)([#502](https://www.github.com/tauri-apps/tauri-action/pull/502)) While looking for the tauri directory the action will now consistently prefer files further up in the directory levels.
145 | - [`a21f29a`](https://www.github.com/tauri-apps/tauri-action/commit/a21f29abc6b81c17f754eb105763499be54a1e14)([#516](https://www.github.com/tauri-apps/tauri-action/pull/516)) Fix detection of windows arm64 bundles.
146 |
147 | ## \[0.4.2]
148 |
149 | - [`2eff2b4`](https://www.github.com/tauri-apps/tauri-action/commit/2eff2b4cc16cf4137d15f997a010f7c781c6276b)([#469](https://www.github.com/tauri-apps/tauri-action/pull/469)) Fix incorrect querying of remote repos to prevent duplicate draft releases. This was only an issue if the `owner` and `repo` configs added in v0.4.1 were set to a different repository than the one the action runs in and if `draftRelease` was set to `true`.
150 |
151 | ## \[0.4.1]
152 |
153 | - [`683dc86`](https://www.github.com/tauri-apps/tauri-action/commit/683dc8624e3ea009c0f35ddfb419a40d08718d01)([#457](https://www.github.com/tauri-apps/tauri-action/pull/457)) Add support for modifying the target repo for the release.
154 |
155 | ## \[0.4.0]
156 |
157 | - Add the paths of generated artifacts as an action output.
158 | - [40e660a](https://www.github.com/tauri-apps/tauri-action/commit/40e660a8ca7dc5e7f5f67710a0212887163c5450) add artifact paths to action output ([#343](https://www.github.com/tauri-apps/tauri-action/pull/343)) on 2022-12-15
159 | - **Breaking change**: Remove broken `configPath` argument in favor of `--config` flag.
160 | - [240732d](https://www.github.com/tauri-apps/tauri-action/commit/240732d2e73e8144d86d386f61a1a27662710a07) fix!: remove broken `configPath` option ([#428](https://www.github.com/tauri-apps/tauri-action/pull/428)) on 2023-04-30
161 | - Correctly handle `--target` option in `args` input.
162 | - [a99d0ba](https://www.github.com/tauri-apps/tauri-action/commit/a99d0bae58a558b23da95394a2a38122574b0f78) feat: Support `--target` input in `args` ([#301](https://www.github.com/tauri-apps/tauri-action/pull/301)) on 2022-10-31
163 | - [c5c0e27](https://www.github.com/tauri-apps/tauri-action/commit/c5c0e27d68a6b6fe1781c02001eaf1596bebe07b) refactor: Merge workspace into single package. ([#362](https://www.github.com/tauri-apps/tauri-action/pull/362)) on 2023-02-06
164 | - Automatically generate `latest.json` file for Tauri's updater using the GitHub release as a CDN.
165 | - [2846fa8](https://www.github.com/tauri-apps/tauri-action/commit/2846fa8fccaf00cb3d9b3433d18dd7bde8006a22) fix: Replace spaces in asset name with dots, fixes [#345](https://www.github.com/tauri-apps/tauri-action/pull/345) ([#374](https://www.github.com/tauri-apps/tauri-action/pull/374)) on 2023-02-06
166 | - Replace `_` and `.` with `-` in the product name on Linux.
167 | - [87ceccd](https://www.github.com/tauri-apps/tauri-action/commit/87ceccdc2e3b936d18cefef2ef03c96361b353ce) fix: fileAppName on Linux. Extends [#293](https://www.github.com/tauri-apps/tauri-action/pull/293) ([#310](https://www.github.com/tauri-apps/tauri-action/pull/310)) on 2022-10-08
168 | - [c5c0e27](https://www.github.com/tauri-apps/tauri-action/commit/c5c0e27d68a6b6fe1781c02001eaf1596bebe07b) refactor: Merge workspace into single package. ([#362](https://www.github.com/tauri-apps/tauri-action/pull/362)) on 2023-02-06
169 | - The action will now use `npm run tauri` instead of `npx tauri` to prevent issues in npm workspaces.
170 | - [a778402](https://www.github.com/tauri-apps/tauri-action/commit/a778402ba7c66b8a6c7c3ce0a6b9978e867936b5) fix: switch from npx to npm run, closes [#367](https://www.github.com/tauri-apps/tauri-action/pull/367) ([#387](https://www.github.com/tauri-apps/tauri-action/pull/387)) on 2023-03-08
171 | - Fixes usage with `vue-cli-plugin-tauri`.
172 | - [f7dcc97](https://www.github.com/tauri-apps/tauri-action/commit/f7dcc97c2dbce3e806c3e72c34ff08fd31dd191e) fix(core): vue-cli-plugin-tauri usage, closes [#288](https://www.github.com/tauri-apps/tauri-action/pull/288) ([#289](https://www.github.com/tauri-apps/tauri-action/pull/289)) on 2022-07-05
173 | - [c5c0e27](https://www.github.com/tauri-apps/tauri-action/commit/c5c0e27d68a6b6fe1781c02001eaf1596bebe07b) refactor: Merge workspace into single package. ([#362](https://www.github.com/tauri-apps/tauri-action/pull/362)) on 2023-02-06
174 | - Correctly handle universal macOS builds in the updater JSON file. The action will now fill out the darwin-aarch64 and darwin-x86\_64 fields with the universal builds. It will always prefer native targets for the respective fields if they exist. Additionaly there's a config to tell the updater to also include a separate darwin-universal field on top of the native fields.
175 | - [91a6560](https://www.github.com/tauri-apps/tauri-action/commit/91a6560a1665d2cdeaa2964a42b41b8b811f6b88) feat: Handle universal macos in updater json, closes [#444](https://www.github.com/tauri-apps/tauri-action/pull/444) ([#447](https://www.github.com/tauri-apps/tauri-action/pull/447)) on 2023-05-03
176 | - [fa82b53](https://www.github.com/tauri-apps/tauri-action/commit/fa82b5395accba7944d99014e1a1486b6f084ae3) fix(json): always fill out native macos fields on 2023-05-03
177 | - Add support for the NSIS bundle type introduced in Tauri v1.3. Add setting to switch between nsis and msi in the updater json file.
178 | - [0ba09ea](https://www.github.com/tauri-apps/tauri-action/commit/0ba09ea554502706c8860b6b2b433a3c42a4d559) feat: Handle nsis builds, closes [#436](https://www.github.com/tauri-apps/tauri-action/pull/436) ([#446](https://www.github.com/tauri-apps/tauri-action/pull/446)) on 2023-05-03
179 | - Automatically read platform specific tauri config files.
180 | - [4c72e78](https://www.github.com/tauri-apps/tauri-action/commit/4c72e78a8e3c6cd564986c6cec3f2a437ee9c1d1) feat: read platform specific tauri configs ([#399](https://www.github.com/tauri-apps/tauri-action/pull/399)) on 2023-03-21
181 | - Automatically read configs provided via the `-c`/`--config` argument.
182 | - [2a4a05a](https://www.github.com/tauri-apps/tauri-action/commit/2a4a05a57f182af4b91357d151349822961d45c2) feat: Read --config arg, closes [#346](https://www.github.com/tauri-apps/tauri-action/pull/346) ([#422](https://www.github.com/tauri-apps/tauri-action/pull/422)) on 2023-03-29
183 | - Add support for Tauri's toml-based config (`Tauri.toml`).
184 | - [06b006d](https://www.github.com/tauri-apps/tauri-action/commit/06b006d1096e9dfb763fea30f206284f909e01a2) feat: Add support for `Tauri.toml` config ([#375](https://www.github.com/tauri-apps/tauri-action/pull/375)) on 2023-02-22
185 | - Add `includeRelease` option to allow disabling release builds.
186 | - [5ae9606](https://www.github.com/tauri-apps/tauri-action/commit/5ae96069898c22a98adf95be6472516e102cd14d) feat: Add `includeRelease` option to allow for disabling release builds ([#365](https://www.github.com/tauri-apps/tauri-action/pull/365)) on 2023-02-06
187 |
188 | ## \[0.3.1]
189 |
190 | - Added the `bundleIdentifier` input to modify Tauri's default bundle identifier when initializing a new Tauri app.
191 | - [743a37f](https://www.github.com/tauri-apps/tauri-action/commit/743a37fd53cbdd122910b818b9bef7b7aa019134) feat(core): add bundle identifier option ([#263](https://www.github.com/tauri-apps/tauri-action/pull/263)) on 2022-05-11
192 | - Added support to loading version from JSON file in `tauri.conf.json > package > version`.
193 | - [16a8f02](https://www.github.com/tauri-apps/tauri-action/commit/16a8f02ad9b4cff2a0ed6205c7418c36f3e49fd0) build(action): rebuild after fixing version parse error ([#268](https://www.github.com/tauri-apps/tauri-action/pull/268)) on 2022-05-28
194 |
195 | ## \[0.3.0]
196 |
197 | - Delete assets from existing release, allowing running the action twice for the same version if an error happens.
198 | - [1205112](https://www.github.com/tauri-apps/tauri-action/commit/1205112d89ee510722927a791d4d460f9419c71d) fix: workflow fails whenever there's asset with same build name attached on the draft ([#208](https://www.github.com/tauri-apps/tauri-action/pull/208)) on 2022-02-20
199 | - Added support to JSON5 on `tauri.conf.json[5]`.
200 | - [b9ce5d7](https://www.github.com/tauri-apps/tauri-action/commit/b9ce5d7dc68082d21d30a60103b0ab8c5ddae3a1) feat: add JSON5 support ([#229](https://www.github.com/tauri-apps/tauri-action/pull/229)) on 2022-02-20
201 | - Update to Tauri release candidate.
202 | - [4d70258](https://www.github.com/tauri-apps/tauri-action/commit/4d7025802c5238ef60a62d33ef8c5378637948bb) fix: Change msi naming scheme for recent Tauri upgrades ([#227](https://www.github.com/tauri-apps/tauri-action/pull/227)) on 2022-02-20
203 | - Added support to Cargo workspaces.
204 | - [8e430cc](https://www.github.com/tauri-apps/tauri-action/commit/8e430cc7b0fab28f0a7768f2157933c94f8724f6) feat: cargo workspace support, closes [#196](https://www.github.com/tauri-apps/tauri-action/pull/196) ([#198](https://www.github.com/tauri-apps/tauri-action/pull/198)) on 2021-12-10
205 |
206 | ## \[0.2.0]
207 |
208 | - Removed the `preferGlobal` and `npmScript` inputs and added a `tauriScript` option.
209 | - [a1050c9](https://www.github.com/tauri-apps/tauri-action/commit/a1050c9ec8903fc5c43696da7f07dcfc89475104) refactor: add `tauriScript` input, remove `preferGlobal` and `npmScript` ([#183](https://www.github.com/tauri-apps/tauri-action/pull/183)) on 2021-11-01
210 |
211 | ## \[0.1.5]
212 |
213 | - Fix action bundle.
214 | - [a226a3d](https://www.github.com/tauri-apps/tauri-action/commit/a226a3da1dfa61b5c4cb764c250224ed5a8a52b8) fix: rebuild github action bundle ([#166](https://www.github.com/tauri-apps/tauri-action/pull/166)) on 2021-09-01
215 |
216 | ## \[0.1.4]
217 |
218 | - Fix `.app` tar being nested in folders
219 | - [2a35a8a](https://www.github.com/tauri-apps/tauri-action/commit/2a35a8a0243d33e1afc9f197601be3db187826d7) Fix `.app` tar being nested in folders ([#158](https://www.github.com/tauri-apps/tauri-action/pull/158)) on 2021-09-01
220 | - Linux: Upload AppImage updater artifacts if available.
221 | macOS: Replace `[AppName].app.tgz` to `[AppName].app.tar.gz` to align with updater artifacts.
222 | - [e7266ff](https://www.github.com/tauri-apps/tauri-action/commit/e7266fff1b42c35bfd7ff359d5c6a91ad1308dea) fix(action): Upload AppImage updater artifacts when available ([#163](https://www.github.com/tauri-apps/tauri-action/pull/163)) on 2021-08-31
223 | - Fix incorrect version being used in release names
224 | - [110a0c6](https://www.github.com/tauri-apps/tauri-action/commit/110a0c6da6de9aa85c8e3186ad642650ebc95ab0) Fix version lookup ([#160](https://www.github.com/tauri-apps/tauri-action/pull/160)) on 2021-09-01
225 |
226 | ## \[0.1.3]
227 |
228 | - Fixes execution of the `tar` command on `macOS` when the application name has spaces.
229 | - [b4b20f9](https://www.github.com/tauri-apps/tauri-action/commit/b4b20f94709829e5e974255aa8034c78e70bb5d1) fix(core): command execution ([#132](https://www.github.com/tauri-apps/tauri-action/pull/132)) on 2021-05-11
230 | - Adds `args` option to pass arguments to the tauri command.
231 | - [f564b01](https://www.github.com/tauri-apps/tauri-action/commit/f564b01e52fbf240e5e5c12577dd10625fe83580) feat: add `args` option, closes [#131](https://www.github.com/tauri-apps/tauri-action/pull/131) ([#134](https://www.github.com/tauri-apps/tauri-action/pull/134)) on 2021-05-13
232 | - Include updater artifacts if available.
233 | - [0e9704e](https://www.github.com/tauri-apps/tauri-action/commit/0e9704eb73bcadd1c6acb3a2e9a73a100465db58) Add updater artifacts when available ([#129](https://www.github.com/tauri-apps/tauri-action/pull/129)) on 2021-05-13
234 |
235 | ## \[0.1.2]
236 |
237 | - Fixes `Artifacts not found` error on Linux when the `productName` is converted to `kebab-case`.
238 | - [e6aa180](https://www.github.com/tauri-apps/tauri-action/commit/e6aa1807b6d2c80de70f78fb945e11a659037837) fix(core): product name on Linux is converted to kebab-case ([#125](https://www.github.com/tauri-apps/tauri-action/pull/125)) on 2021-04-29
239 |
240 | ## \[0.1.1]
241 |
242 | - Fixes action packaging.
243 | - [2598dd6](https://www.github.com/tauri-apps/tauri-action/commit/2598dd6f75569cc7d8af81586aaa6c9463775d80) fix(action): runtime issue: tslib not found, use `tauri-apps/tauri-action` as action path ([#119](https://www.github.com/tauri-apps/tauri-action/pull/119)) on 2021-04-28
244 | - Revert action path to `tauri-apps/tauri-action`.
245 | - [2598dd6](https://www.github.com/tauri-apps/tauri-action/commit/2598dd6f75569cc7d8af81586aaa6c9463775d80) fix(action): runtime issue: tslib not found, use `tauri-apps/tauri-action` as action path ([#119](https://www.github.com/tauri-apps/tauri-action/pull/119)) on 2021-04-28
246 |
247 | ## \[0.1.0]
248 |
249 | - Update to Tauri beta release candidate.
250 | - [b874256](https://github.com/tauri-apps/tauri-action/commit/b87425614119f70be189fddd40a403481b91a328) refactor: rewrite as yarn workspace, add cli as test tool ([#98](https://github.com/tauri-apps/tauri-action/pull/98)) on 2021-04-26
251 | - [dbbc6b4](https://github.com/tauri-apps/tauri-action/commit/dbbc6b4e604ce66a84108e7441ee1b8f38cb82fe) fix(action): test CI and fixes for usage with tauri beta-rc ([#114](https://github.com/tauri-apps/tauri-action/pull/114)) on 2021-04-28
252 |
253 | ## \[0.0.10]
254 |
255 | - If vue-cli-plugin-tauri is detected, the tauri:build command will be used.
256 | - [f043343](https://github.com/tauri-apps/tauri-action/commit/f043343ae7ada30f30f67deeacb29eb9709283c3) feat: add support for building with vue cli ([#60](https://github.com/tauri-apps/tauri-action/pull/60)) on 2021-01-30
257 |
258 | ## \[0.0.9]
259 |
260 | - Add option to elect using an existing globally installed version of Tauri.
261 | - [a45f21b](https://github.com/tauri-apps/tauri-action/commit/a45f21b1732014a0dabc488197f277ad0cef6b06) feature: add preferGlobal option ([#48](https://github.com/tauri-apps/tauri-action/pull/48)) on 2020-09-02
262 |
263 | ## \[0.0.8]
264 |
265 | - Uploaded assets break when `data` receives `fs.readFileSync(assetPath).toString()` even though types suggest it. Giving it a Buffer fixes the issue.
266 | - [cf98c66](https://github.com/tauri-apps/tauri-action/commit/cf98c661aea6841d7aff2b5f4df614b36a6f6726) fix: broken asset release upload ([#45](https://github.com/tauri-apps/tauri-action/pull/45)) on 2020-08-23
267 |
268 | ## \[0.0.7]
269 |
270 | - Updates for tauri.js 0.10.0 and tauri-core 0.8.0.
271 | - [4c37642](https://github.com/tauri-apps/tauri-action/commit/4c37642f0621ad7508f39a40a41d979b41dd6a59) fix(action) update to latest tauri.js and tauri versions on 2020-07-22
272 |
273 | ## \[0.0.6]
274 |
275 | - Fixes the includeDebug input usage.
276 | - [58d7b86](https://github.com/tauri-apps/tauri-action/commit/58d7b8650a12ffc4a11729ce93d0072e22bc4aaa) fix(action) includeDebug usage on 2020-07-12
277 | - Update @actions/github package version to v4.
278 | - [2e93aab](https://github.com/tauri-apps/tauri-action/commit/2e93aabc2a786719f0316d0677738d6a9ad06801) refactor(action) update @actions/github to v4 ([#13](https://github.com/tauri-apps/tauri-action/pull/13)) on 2020-07-12
279 |
280 | ## \[0.0.5]
281 |
282 | - Adds support to tauri listed as a dev dependency on package.json.
283 | - [a14bbef](https://github.com/tauri-apps/tauri-action/commit/a14bbefa2fd178a3a3e5621316aeda4124b91440) feat(action) add support to devDependencies' tauri on 2020-07-12
284 | - Fixes the macOS .app compression to tar when using includeDebug.
285 | - [52c88ce](https://github.com/tauri-apps/tauri-action/commit/52c88ce6cfcd8e951b027cd1aadba562c93befe7) fix(action) macOS .app compression with `includeDir`= true on 2020-07-12
286 |
287 | ## \[0.0.4]
288 |
289 | - Fixes the action build script.
290 | - [981f369](https://github.com/tauri-apps/tauri-action/commit/981f3691972cad500eaa5a2b7a1c8c30e8537c79) fix(action) build script on 2020-07-12
291 |
292 | ## \[0.0.3]
293 |
294 | - Build action on preversion so we can't forget to build when a version is updated.
295 | - [af79aee](https://github.com/tauri-apps/tauri-action/commit/af79aee2e0022f4402f619d1177e63596f8c950c) chore: build action on version ([#7](https://github.com/tauri-apps/tauri-action/pull/7)) on 2020-07-12
296 | - Adds an option to run a custom package.json script with the npmScript input.
297 | - [f91ad8d](https://github.com/tauri-apps/tauri-action/commit/f91ad8dc315e9d911f3351bead517b35b89a1e6f) feat(action) add option to run custom package.json script ([#8](https://github.com/tauri-apps/tauri-action/pull/8)) on 2020-07-12
298 | - Adds an option to include a debug build with the includeDebug (bool) input.
299 | - [a6b824c](https://github.com/tauri-apps/tauri-action/commit/a6b824c578593003332957fa899c354c40e20df5) feat(action) add option to include a debug build ([#6](https://github.com/tauri-apps/tauri-action/pull/6)) on 2020-07-12
300 |
301 | ## \[0.0.2]
302 |
303 | - Implement covector for change management and git tag creation.
304 | - [f6ce359](https://github.com/tauri-apps/tauri-action/commit/f6ce3599bee8d42f16c605e00e56c37d05847187) change file on 2020-07-11
305 |
--------------------------------------------------------------------------------