├── .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 | 320 | 321 | 322 |
316 | 317 | CrabNebula 318 | 319 |
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 | --------------------------------------------------------------------------------