fix: read tagName if releaseId is provided. fixes incorrect download urls (#332)

* chore: add prettier

* fix: get tag from release if tagName is empty

* use tagName input and fall back to 'latest'

* revert tagname request

* fix link on 'latest' fallback

* starting to feel stupid

* oof

* add note about tagName in readme
This commit is contained in:
Fabian-Lars
2022-12-05 01:39:28 +01:00
committed by GitHub
parent e60a38e362
commit dddfe2eddb
19 changed files with 573 additions and 548 deletions

View File

@@ -2,7 +2,7 @@ name: covector version or publish
on: on:
push: push:
branches: branches:
- dev - dev
jobs: jobs:
covector: covector:
@@ -36,7 +36,6 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
branch: release/version-updates branch: release/version-updates
title: Apply Version Updates From Current Changes title: Apply Version Updates From Current Changes
commit-message: "apply version updates" commit-message: 'apply version updates'
labels: "version updates" labels: 'version updates'
body: ${{ steps.covector.outputs.change }} body: ${{ steps.covector.outputs.change }}

View File

@@ -1,4 +1,4 @@
name: "test-action" name: 'test-action'
on: on:
workflow_dispatch: workflow_dispatch:
@@ -11,46 +11,46 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: setup node - name: setup node
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 14 node-version: 14
- name: install Rust stable - name: install Rust stable
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: stable
- name: install dependencies (ubuntu only) - name: install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-latest' if: matrix.platform == 'ubuntu-latest'
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf sudo apt-get install -y libgtk-3-dev webkit2gtk-4.0 libappindicator3-dev librsvg2-dev patchelf
- name: install example dependencies - name: install example dependencies
run: yarn run: yarn
working-directory: ./packages/action/__fixtures__/example working-directory: ./packages/action/__fixtures__/example
- uses: ./ - uses: ./
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Updater signature is exposed here to make sure it works in PR's # Updater signature is exposed here to make sure it works in PR's
TAURI_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg== TAURI_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg==
with: with:
projectPath: ./packages/action/__fixtures__/example-with-tauri projectPath: ./packages/action/__fixtures__/example-with-tauri
tagName: example-with-tauri-v__VERSION__ tagName: example-with-tauri-v__VERSION__
releaseName: "Release example with preconfigured Tauri app v__VERSION__" releaseName: 'Release example with preconfigured Tauri app v__VERSION__'
releaseBody: "See the assets to download this version and install." releaseBody: 'See the assets to download this version and install.'
releaseDraft: true releaseDraft: true
prerelease: false prerelease: false
- uses: ./ - uses: ./
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Updater signature is exposed here to make sure it works in PR's # Updater signature is exposed here to make sure it works in PR's
TAURI_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg== TAURI_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5YTBGV3JiTy9lRDZVd3NkL0RoQ1htZmExNDd3RmJaNmRMT1ZGVjczWTBKZ0FBQkFBQUFBQUFBQUFBQUlBQUFBQWdMekUzVkE4K0tWQ1hjeGt1Vkx2QnRUR3pzQjVuV0ZpM2czWXNkRm9hVUxrVnB6TUN3K1NheHJMREhQbUVWVFZRK3NIL1VsMDBHNW5ET1EzQno0UStSb21nRW4vZlpTaXIwZFh5ZmRlL1lSN0dKcHdyOUVPclVvdzFhVkxDVnZrbHM2T1o4Tk1NWEU9Cg==
with: with:
projectPath: ./packages/action/__fixtures__/example projectPath: ./packages/action/__fixtures__/example
bundleIdentifier: com.tauri.actiontest bundleIdentifier: com.tauri.actiontest
# iconPath: ./icon.png # iconPath: ./icon.png
tagName: example-v__VERSION__ tagName: example-v__VERSION__
releaseName: "Release example app v__VERSION__" releaseName: 'Release example app v__VERSION__'
releaseBody: "See the assets to download this version and install." releaseBody: 'See the assets to download this version and install.'
releaseDraft: true releaseDraft: true
prerelease: false prerelease: false

4
.prettierignore Normal file
View File

@@ -0,0 +1,4 @@
dist/
__fixtures__/
__tests__
node_modules/

3
.prettierrc.json Normal file
View File

@@ -0,0 +1,3 @@
{
"singleQuote": true
}

132
README.md
View File

@@ -10,7 +10,7 @@ This GitHub Action has three main usages: test the build pipeline of your Tauri
## Testing the Build ## Testing the Build
```yml ```yml
name: "test-on-pr" name: 'test-on-pr'
on: [pull_request] on: [pull_request]
jobs: jobs:
@@ -22,29 +22,29 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: setup node - name: setup node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 16
- name: install Rust stable - name: install Rust stable
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: install dependencies (ubuntu only) - name: install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-20.04' if: matrix.platform == 'ubuntu-20.04'
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: install app dependencies and build it - name: install app dependencies and build it
run: yarn && yarn build run: yarn && yarn build
- uses: tauri-apps/tauri-action@v0 - uses: tauri-apps/tauri-action@v0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
``` ```
## Creating a release and uploading the Tauri bundles ## Creating a release and uploading the Tauri bundles
```yml ```yml
name: "publish" name: 'publish'
on: on:
push: push:
branches: branches:
@@ -59,29 +59,29 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: setup node - name: setup node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 16
- name: install Rust stable - name: install Rust stable
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: install dependencies (ubuntu only) - name: install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-20.04' if: matrix.platform == 'ubuntu-20.04'
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: install app dependencies and build it - name: install app dependencies and build it
run: yarn && yarn build run: yarn && yarn build
- uses: tauri-apps/tauri-action@v0 - uses: tauri-apps/tauri-action@v0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version
releaseName: "App v__VERSION__" releaseName: 'App v__VERSION__'
releaseBody: "See the assets to download this version and install." releaseBody: 'See the assets to download this version and install.'
releaseDraft: true releaseDraft: true
prerelease: false prerelease: false
``` ```
## Uploading the artifacts to a release ## Uploading the artifacts to a release
@@ -119,7 +119,7 @@ jobs:
draft: true, draft: true,
prerelease: false prerelease: false
}) })
return data.id return data.id
build-tauri: build-tauri:
@@ -131,30 +131,30 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: setup node - name: setup node
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: 16
- name: install Rust stable - name: install Rust stable
uses: dtolnay/rust-toolchain@stable uses: dtolnay/rust-toolchain@stable
- name: install dependencies (ubuntu only) - name: install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-20.04' if: matrix.platform == 'ubuntu-20.04'
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: install app dependencies and build it - name: install app dependencies and build it
run: yarn && yarn build run: yarn && yarn build
- uses: tauri-apps/tauri-action@v0 - uses: tauri-apps/tauri-action@v0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
releaseId: ${{ needs.create-release.outputs.release_id }} releaseId: ${{ needs.create-release.outputs.release_id }}
publish-release: publish-release:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
needs: [ create-release, build-tauri ] needs: [create-release, build-tauri]
steps: steps:
- name: publish release - name: publish release
id: publish-release id: publish-release
@@ -180,7 +180,7 @@ jobs:
| `configPath` | false | Path to the tauri.conf.json file if you want a configuration different from the default one | string | tauri.conf.json | | `configPath` | false | Path to the tauri.conf.json file if you want a configuration different from the default one | string | tauri.conf.json |
| `distPath` | false | Path to the distributable folder with your index.html and JS/CSS | string | | | `distPath` | false | Path to the distributable folder with your index.html and JS/CSS | string | |
| `releaseId` | false | The id of the release to upload artifacts as release assets | string | | | `releaseId` | false | The id of the release to upload artifacts as release assets | string | |
| `tagName` | false | The tag name of the release to create | string | | | `tagName` | false | The tag name of the release to create or the tag of the release belonging to `releaseId` | string | |
| `releaseName` | false | The name of the release to create | string | | | `releaseName` | false | The name of the release to create | string | |
| `releaseBody` | false | The body of the release to create | string | | | `releaseBody` | false | The body of the release to create | string | |
| `releaseDraft` | false | Whether the release to create is a draft or not | bool | false | | `releaseDraft` | false | Whether the release to create is a draft or not | bool | false |
@@ -191,7 +191,6 @@ jobs:
| `tauriScript` | false | the script to execute the Tauri CLI | string | `yarn\|npx tauri` | | `tauriScript` | false | the script to execute the Tauri CLI | string | `yarn\|npx tauri` |
| `args` | false | Additional arguments to the current build command | string | | | `args` | false | Additional arguments to the current build command | string | |
## Outputs ## Outputs
| Name | Description | | Name | Description |
@@ -208,3 +207,4 @@ jobs:
- Useful when you need custom build functionality when creating Tauri apps e.g. a `desktop:build` script. - Useful when you need custom build functionality when creating Tauri apps e.g. a `desktop:build` script.
- 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`. - 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`.
- When your app isn't on the root of the repo, use the `projectPath` input. - When your app isn't on the root of the repo, use the `projectPath` input.
- 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/<bundle>` which can cause issues if your repo contains releases that do not include updater bundles.

View File

@@ -6,6 +6,7 @@
"packages/action" "packages/action"
], ],
"devDependencies": { "devDependencies": {
"covector": "0.7.3" "covector": "0.7.3",
"prettier": "^2.8.0"
} }
} }

File diff suppressed because one or more lines are too long

View File

@@ -1,28 +1,28 @@
import * as core from '@actions/core' import * as core from '@actions/core';
import { getOctokit, context } from '@actions/github' import { getOctokit, context } from '@actions/github';
import { GitHub } from '@actions/github/lib/utils' import { GitHub } from '@actions/github/lib/utils';
import fs from 'fs' import fs from 'fs';
interface Release { interface Release {
id: number id: number;
uploadUrl: string uploadUrl: string;
htmlUrl: string htmlUrl: string;
} }
interface GitHubRelease { interface GitHubRelease {
id: number id: number;
upload_url: string upload_url: string;
html_url: string html_url: string;
tag_name: string tag_name: string;
} }
function allReleases( function allReleases(
github: InstanceType<typeof GitHub> github: InstanceType<typeof GitHub>
): AsyncIterableIterator<{ data: GitHubRelease[] }> { ): AsyncIterableIterator<{ data: GitHubRelease[] }> {
const params = { per_page: 100, ...context.repo } const params = { per_page: 100, ...context.repo };
return github.paginate.iterator( return github.paginate.iterator(
github.rest.repos.listReleases.endpoint.merge(params) github.rest.repos.listReleases.endpoint.merge(params)
) );
} }
export default async function createRelease( export default async function createRelease(
@@ -34,58 +34,58 @@ export default async function createRelease(
prerelease = true prerelease = true
): Promise<Release> { ): Promise<Release> {
if (process.env.GITHUB_TOKEN === undefined) { if (process.env.GITHUB_TOKEN === undefined) {
throw new Error('GITHUB_TOKEN is required') throw new Error('GITHUB_TOKEN is required');
} }
// Get authenticated GitHub client (Ocktokit): https://github.com/actions/toolkit/tree/master/packages/github#usage // Get authenticated GitHub client (Ocktokit): https://github.com/actions/toolkit/tree/master/packages/github#usage
const github = getOctokit(process.env.GITHUB_TOKEN) const github = getOctokit(process.env.GITHUB_TOKEN);
// Get owner and repo from context of payload that triggered the action // Get owner and repo from context of payload that triggered the action
const { owner, repo } = context.repo const { owner, repo } = context.repo;
const bodyPath = core.getInput('body_path', { required: false }) const bodyPath = core.getInput('body_path', { required: false });
let bodyFileContent = null let bodyFileContent = null;
if (bodyPath !== '' && !!bodyPath) { if (bodyPath !== '' && !!bodyPath) {
try { try {
bodyFileContent = fs.readFileSync(bodyPath, { encoding: 'utf8' }) bodyFileContent = fs.readFileSync(bodyPath, { encoding: 'utf8' });
} catch (error) { } catch (error) {
core.setFailed(error.message) core.setFailed(error.message);
} }
} }
let release: GitHubRelease | null = null let release: GitHubRelease | null = null;
try { try {
// you can't get a an existing draft by tag // you can't get a an existing draft by tag
// so we must find one in the list of all releases // so we must find one in the list of all releases
if (draft) { if (draft) {
console.log(`Looking for a draft release with tag ${tagName}...`) console.log(`Looking for a draft release with tag ${tagName}...`);
for await (const response of allReleases(github)) { for await (const response of allReleases(github)) {
let releaseWithTag = response.data.find( let releaseWithTag = response.data.find(
(release) => release.tag_name === tagName (release) => release.tag_name === tagName
) );
if (releaseWithTag) { if (releaseWithTag) {
release = releaseWithTag release = releaseWithTag;
console.log( console.log(
`Found draft release with tag ${tagName} on the release list.` `Found draft release with tag ${tagName} on the release list.`
) );
break break;
} }
} }
if (!release) { if (!release) {
throw new Error('release not found') throw new Error('release not found');
} }
} else { } else {
const foundRelease = await github.rest.repos.getReleaseByTag({ const foundRelease = await github.rest.repos.getReleaseByTag({
owner, owner,
repo, repo,
tag: tagName tag: tagName,
}) });
release = foundRelease.data release = foundRelease.data;
console.log(`Found release with tag ${tagName}.`) console.log(`Found release with tag ${tagName}.`);
} }
} catch (error) { } catch (error) {
if (error.status === 404 || error.message === 'release not found') { if (error.status === 404 || error.message === 'release not found') {
console.log(`Couldn't find release with tag ${tagName}. Creating one.`) console.log(`Couldn't find release with tag ${tagName}. Creating one.`);
const createdRelease = await github.rest.repos.createRelease({ const createdRelease = await github.rest.repos.createRelease({
owner, owner,
repo, repo,
@@ -94,25 +94,25 @@ export default async function createRelease(
body: bodyFileContent || body, body: bodyFileContent || body,
draft, draft,
prerelease, prerelease,
target_commitish: commitish || context.sha target_commitish: commitish || context.sha,
}) });
release = createdRelease.data release = createdRelease.data;
} else { } else {
console.log( console.log(
`⚠️ Unexpected error fetching GitHub release for tag ${tagName}: ${error}` `⚠️ Unexpected error fetching GitHub release for tag ${tagName}: ${error}`
) );
throw error throw error;
} }
} }
if (!release) { if (!release) {
throw new Error('Release not found or created.') throw new Error('Release not found or created.');
} }
return { return {
id: release.id, id: release.id,
uploadUrl: release.upload_url, uploadUrl: release.upload_url,
htmlUrl: release.html_url htmlUrl: release.html_url,
} };
} }

View File

@@ -1,47 +1,50 @@
import { platform } from 'os' import { platform } from 'os';
import * as core from '@actions/core' import * as core from '@actions/core';
import { join, resolve, dirname, basename } from 'path' import { join, resolve, dirname, basename } from 'path';
import { existsSync } from 'fs' import { existsSync } from 'fs';
import uploadReleaseAssets from './upload-release-assets' import uploadReleaseAssets from './upload-release-assets';
import uploadVersionJSON from './upload-version-json' import uploadVersionJSON from './upload-version-json';
import createRelease from './create-release' import createRelease from './create-release';
import { import {
getPackageJson, getPackageJson,
buildProject, buildProject,
getInfo, getInfo,
execCommand execCommand,
} from '@tauri-apps/action-core' } from '@tauri-apps/action-core';
import type { BuildOptions } from '@tauri-apps/action-core' import type { BuildOptions } from '@tauri-apps/action-core';
import stringArgv from 'string-argv' import stringArgv from 'string-argv';
async function run(): Promise<void> { async function run(): Promise<void> {
try { try {
const projectPath = resolve( const projectPath = resolve(
process.cwd(), process.cwd(),
core.getInput('projectPath') || process.argv[2] core.getInput('projectPath') || process.argv[2]
) );
const configPath = join( const configPath = join(
projectPath, projectPath,
core.getInput('configPath') || 'tauri.conf.json' core.getInput('configPath') || 'tauri.conf.json'
) );
const distPath = core.getInput('distPath') const distPath = core.getInput('distPath');
const iconPath = core.getInput('iconPath') const iconPath = core.getInput('iconPath');
const includeDebug = core.getBooleanInput('includeDebug') const includeDebug = core.getBooleanInput('includeDebug');
const tauriScript = core.getInput('tauriScript') const tauriScript = core.getInput('tauriScript');
const args = stringArgv(core.getInput('args')) const args = stringArgv(core.getInput('args'));
const bundleIdentifier = core.getInput('bundleIdentifier') const bundleIdentifier = core.getInput('bundleIdentifier');
let tagName = core.getInput('tagName').replace('refs/tags/', '') let tagName = core.getInput('tagName').replace('refs/tags/', '');
let releaseName = core.getInput('releaseName').replace('refs/tags/', '') let releaseId = Number(core.getInput('releaseId'));
let body = core.getInput('releaseBody') let releaseName = core.getInput('releaseName').replace('refs/tags/', '');
const draft = core.getBooleanInput('releaseDraft') let body = core.getInput('releaseBody');
const prerelease = core.getBooleanInput('prerelease') const draft = core.getBooleanInput('releaseDraft');
const commitish = core.getInput('releaseCommitish') || null const prerelease = core.getBooleanInput('prerelease');
const commitish = core.getInput('releaseCommitish') || null;
if (Boolean(tagName) !== Boolean(releaseName)) { if (!releaseId) {
throw new Error( if (Boolean(tagName) !== Boolean(releaseName)) {
'`tag` is required along with `releaseName` when creating a release.' throw new Error(
) '`tagName` is required along with `releaseName` when creating a release.'
);
}
} }
const options: BuildOptions = { const options: BuildOptions = {
@@ -50,37 +53,36 @@ async function run(): Promise<void> {
iconPath, iconPath,
tauriScript, tauriScript,
args, args,
bundleIdentifier bundleIdentifier,
} };
const info = getInfo(projectPath) const info = getInfo(projectPath);
const artifacts = await buildProject(projectPath, false, options) const artifacts = await buildProject(projectPath, false, options);
if (includeDebug) { if (includeDebug) {
const debugArtifacts = await buildProject(projectPath, true, options) const debugArtifacts = await buildProject(projectPath, true, options);
artifacts.push(...debugArtifacts) artifacts.push(...debugArtifacts);
} }
if (artifacts.length === 0) { if (artifacts.length === 0) {
throw new Error('No artifacts were found.') throw new Error('No artifacts were found.');
} }
console.log(`Found artifacts:\n${artifacts.map((a) => a.path).join('\n')}`) console.log(`Found artifacts:\n${artifacts.map((a) => a.path).join('\n')}`);
let releaseId: number if (tagName && !releaseId) {
if (tagName) { const packageJson = getPackageJson(projectPath);
const packageJson = getPackageJson(projectPath)
const templates = [ const templates = [
{ {
key: '__VERSION__', key: '__VERSION__',
value: info.version || packageJson.version value: info.version || packageJson.version,
} },
] ];
templates.forEach((template) => { templates.forEach((template) => {
const regex = new RegExp(template.key, 'g') const regex = new RegExp(template.key, 'g');
tagName = tagName.replace(regex, template.value) tagName = tagName.replace(regex, template.value);
releaseName = releaseName.replace(regex, template.value) releaseName = releaseName.replace(regex, template.value);
body = body.replace(regex, template.value) body = body.replace(regex, template.value);
}) });
const releaseData = await createRelease( const releaseData = await createRelease(
tagName, tagName,
@@ -89,18 +91,16 @@ async function run(): Promise<void> {
commitish || undefined, commitish || undefined,
draft, draft,
prerelease prerelease
) );
releaseId = releaseData.id releaseId = releaseData.id;
core.setOutput('releaseUploadUrl', releaseData.uploadUrl) core.setOutput('releaseUploadUrl', releaseData.uploadUrl);
core.setOutput('releaseId', releaseData.id.toString()) core.setOutput('releaseId', releaseData.id.toString());
core.setOutput('releaseHtmlUrl', releaseData.htmlUrl) core.setOutput('releaseHtmlUrl', releaseData.htmlUrl);
} else {
releaseId = Number(core.getInput('releaseId') || 0)
} }
if (releaseId) { if (releaseId) {
if (platform() === 'darwin') { if (platform() === 'darwin') {
let i = 0 let i = 0;
for (const artifact of artifacts) { for (const artifact of artifacts) {
// updater provide a .tar.gz, this will prevent duplicate and overwriting of // updater provide a .tar.gz, this will prevent duplicate and overwriting of
// signed archive // signed archive
@@ -113,28 +113,28 @@ async function run(): Promise<void> {
`${artifact.path}.tar.gz`, `${artifact.path}.tar.gz`,
'-C', '-C',
dirname(artifact.path), dirname(artifact.path),
basename(artifact.path) basename(artifact.path),
]) ]);
artifact.path += '.tar.gz' artifact.path += '.tar.gz';
} else if (artifact.path.endsWith('.app')) { } else if (artifact.path.endsWith('.app')) {
// we can't upload a directory // we can't upload a directory
artifacts.splice(i, 1) artifacts.splice(i, 1);
} }
i++ i++;
} }
} }
await uploadReleaseAssets(releaseId, artifacts) await uploadReleaseAssets(releaseId, artifacts);
await uploadVersionJSON({ await uploadVersionJSON({
version: info.version, version: info.version,
notes: body, notes: body,
tagName, tagName,
releaseId, releaseId,
artifacts artifacts,
}) });
} }
} catch (error) { } catch (error) {
core.setFailed(error.message) core.setFailed(error.message);
} }
} }
run() run();

View File

@@ -1,49 +1,49 @@
import { getOctokit, context } from '@actions/github' import { getOctokit, context } from '@actions/github';
import { Artifact } from '@tauri-apps/action-core' import { Artifact } from '@tauri-apps/action-core';
import fs from 'fs' import fs from 'fs';
import { getAssetName } from './utils' import { getAssetName } from './utils';
export default async function uploadAssets( export default async function uploadAssets(
releaseId: number, releaseId: number,
assets: Artifact[] assets: Artifact[]
) { ) {
if (process.env.GITHUB_TOKEN === undefined) { if (process.env.GITHUB_TOKEN === undefined) {
throw new Error('GITHUB_TOKEN is required') throw new Error('GITHUB_TOKEN is required');
} }
const github = getOctokit(process.env.GITHUB_TOKEN) const github = getOctokit(process.env.GITHUB_TOKEN);
const existingAssets = ( const existingAssets = (
await github.rest.repos.listReleaseAssets({ await github.rest.repos.listReleaseAssets({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
release_id: releaseId, release_id: releaseId,
per_page: 50 per_page: 50,
}) })
).data ).data;
// Determine content-length for header to upload asset // Determine content-length for header to upload asset
const contentLength = (filePath: string) => fs.statSync(filePath).size const contentLength = (filePath: string) => fs.statSync(filePath).size;
for (const asset of assets) { for (const asset of assets) {
const headers = { const headers = {
'content-type': 'application/zip', 'content-type': 'application/zip',
'content-length': contentLength(asset.path) 'content-length': contentLength(asset.path),
} };
const assetName = getAssetName(asset.path) const assetName = getAssetName(asset.path);
const existingAsset = existingAssets.find((a) => a.name === assetName) const existingAsset = existingAssets.find((a) => a.name === assetName);
if (existingAsset) { if (existingAsset) {
console.log(`Deleting existing ${assetName}...`) console.log(`Deleting existing ${assetName}...`);
await github.rest.repos.deleteReleaseAsset({ await github.rest.repos.deleteReleaseAsset({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
asset_id: existingAsset.id asset_id: existingAsset.id,
}) });
} }
console.log(`Uploading ${assetName}...`) console.log(`Uploading ${assetName}...`);
await github.rest.repos.uploadReleaseAsset({ await github.rest.repos.uploadReleaseAsset({
headers, headers,
@@ -53,7 +53,7 @@ export default async function uploadAssets(
data: fs.readFileSync(asset.path), data: fs.readFileSync(asset.path),
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
release_id: releaseId release_id: releaseId,
}) });
} }
} }

View File

@@ -1,47 +1,47 @@
import { getOctokit, context } from '@actions/github' import { getOctokit, context } from '@actions/github';
import { resolve } from 'path' import { resolve } from 'path';
import { readFileSync, writeFileSync } from 'fs' import { readFileSync, writeFileSync } from 'fs';
import uploadAssets from './upload-release-assets' import uploadAssets from './upload-release-assets';
import fetch from 'node-fetch' import fetch from 'node-fetch';
import { arch, platform } from 'os' import { arch, platform } from 'os';
import { getAssetName } from './utils' import { getAssetName } from './utils';
import { Artifact } from '@tauri-apps/action-core' import { Artifact } from '@tauri-apps/action-core';
export default async function uploadVersionJSON({ export default async function uploadVersionJSON({
version, version,
notes, notes,
tagName, tagName,
releaseId, releaseId,
artifacts artifacts,
}: { }: {
version: string version: string;
notes: string notes: string;
tagName: string tagName: string;
releaseId: number releaseId: number;
artifacts: Artifact[] artifacts: Artifact[];
}) { }) {
if (process.env.GITHUB_TOKEN === undefined) { if (process.env.GITHUB_TOKEN === undefined) {
throw new Error('GITHUB_TOKEN is required') throw new Error('GITHUB_TOKEN is required');
} }
const github = getOctokit(process.env.GITHUB_TOKEN) const github = getOctokit(process.env.GITHUB_TOKEN);
const versionFilename = 'latest.json' const versionFilename = 'latest.json';
const versionFile = resolve(process.cwd(), versionFilename) const versionFile = resolve(process.cwd(), versionFilename);
const versionContent = { const versionContent = {
version, version,
notes, notes,
pub_date: new Date().toISOString(), pub_date: new Date().toISOString(),
platforms: {} platforms: {},
} };
const assets = await github.rest.repos.listReleaseAssets({ const assets = await github.rest.repos.listReleaseAssets({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
release_id: releaseId, release_id: releaseId,
per_page: 50 per_page: 50,
}) });
const asset = assets.data.find((e) => e.name === versionFilename) const asset = assets.data.find((e) => e.name === versionFilename);
if (asset) { if (asset) {
const assetData = ( const assetData = (
@@ -52,44 +52,47 @@ export default async function uploadVersionJSON({
repo: context.repo.repo, repo: context.repo.repo,
asset_id: asset.id, asset_id: asset.id,
headers: { headers: {
accept: 'application/octet-stream' accept: 'application/octet-stream',
} },
} }
) )
).data as any as ArrayBuffer ).data as any as ArrayBuffer;
versionContent.platforms = JSON.parse( versionContent.platforms = JSON.parse(
Buffer.from(assetData).toString() Buffer.from(assetData).toString()
).platforms ).platforms;
// https://docs.github.com/en/rest/releases/assets#update-a-release-asset // https://docs.github.com/en/rest/releases/assets#update-a-release-asset
await github.rest.repos.deleteReleaseAsset({ await github.rest.repos.deleteReleaseAsset({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
release_id: releaseId, release_id: releaseId,
asset_id: asset.id asset_id: asset.id,
}) });
} }
const sigFile = artifacts.find((s) => s.path.endsWith('.sig')) const sigFile = artifacts.find((s) => s.path.endsWith('.sig'));
const assetNames = new Set(artifacts.map((p) => getAssetName(p.path))) const assetNames = new Set(artifacts.map((p) => getAssetName(p.path)));
let downloadUrl = assets.data let downloadUrl = assets.data
.filter((e) => assetNames.has(e.name)) .filter((e) => assetNames.has(e.name))
.find( .find(
(s) => s.name.endsWith('.tar.gz') || s.name.endsWith('.zip') (s) => s.name.endsWith('.tar.gz') || s.name.endsWith('.zip')
)?.browser_download_url )?.browser_download_url;
// Untagged release downloads won't work after the release was published // Untagged release downloads won't work after the release was published
downloadUrl = downloadUrl?.replace( downloadUrl = downloadUrl?.replace(
/\/download\/(untagged-[^\/]+)\//, /\/download\/(untagged-[^\/]+)\//,
`/download/${tagName}/` tagName ? `/download/${tagName}/` : '/latest/download/'
) );
let os = platform() as string let os = platform() as string;
if (os === 'win32') os = 'windows' if (os === 'win32') {
os = 'windows';
}
if (downloadUrl && sigFile) { if (downloadUrl && sigFile) {
let arch = sigFile.arch let arch = sigFile.arch;
arch = arch =
arch === 'amd64' || arch === 'x86_64' || arch === 'x64' arch === 'amd64' || arch === 'x86_64' || arch === 'x64'
? 'x86_64' ? 'x86_64'
: arch === 'x86' || arch === 'i386' : arch === 'x86' || arch === 'i386'
@@ -98,24 +101,24 @@ export default async function uploadVersionJSON({
? 'armv7' ? 'armv7'
: arch === 'arm64' : arch === 'arm64'
? 'aarch64' ? 'aarch64'
: arch : arch;
// https://github.com/tauri-apps/tauri/blob/fd125f76d768099dc3d4b2d4114349ffc31ffac9/core/tauri/src/updater/core.rs#L856 // https://github.com/tauri-apps/tauri/blob/fd125f76d768099dc3d4b2d4114349ffc31ffac9/core/tauri/src/updater/core.rs#L856
versionContent.platforms[`${os}-${arch}`] = { versionContent.platforms[`${os}-${arch}`] = {
signature: readFileSync(sigFile.path).toString(), signature: readFileSync(sigFile.path).toString(),
url: downloadUrl url: downloadUrl,
} };
} else { } else {
const missing = downloadUrl const missing = downloadUrl
? 'Signature' ? 'Signature'
: sigFile : sigFile
? 'Asset' ? 'Asset'
: 'Asset and signature' : 'Asset and signature';
console.warn(`${missing} not found for the updater JSON.`) console.warn(`${missing} not found for the updater JSON.`);
} }
writeFileSync(versionFile, JSON.stringify(versionContent, null, 2)) writeFileSync(versionFile, JSON.stringify(versionContent, null, 2));
console.log(`Uploading ${versionFile}...`) console.log(`Uploading ${versionFile}...`);
await uploadAssets(releaseId, [{ path: versionFile, arch: '' }]) await uploadAssets(releaseId, [{ path: versionFile, arch: '' }]);
} }

View File

@@ -10,25 +10,25 @@ const extensions = [
'.deb', '.deb',
'.msi.zip.sig', '.msi.zip.sig',
'.msi.zip', '.msi.zip',
'.msi' '.msi',
] ];
export function getAssetName(assetPath: string) { export function getAssetName(assetPath: string) {
const basename = path.basename(assetPath) const basename = path.basename(assetPath);
const exts = extensions.filter((s) => basename.includes(s)) const exts = extensions.filter((s) => basename.includes(s));
const ext = exts[0] || path.extname(assetPath) const ext = exts[0] || path.extname(assetPath);
const filename = basename.replace(ext, '') const filename = basename.replace(ext, '');
let arch = '' let arch = '';
if (ext === '.app.tar.gz.sig' || ext === '.app.tar.gz') { if (ext === '.app.tar.gz.sig' || ext === '.app.tar.gz') {
arch = assetPath.includes('universal-apple-darwin') arch = assetPath.includes('universal-apple-darwin')
? '_universal' ? '_universal'
: assetPath.includes('aarch64-apple-darwin') : assetPath.includes('aarch64-apple-darwin')
? '_aarch64' ? '_aarch64'
: '_x64' : '_x64';
} }
return assetPath.includes(`${path.sep}debug${path.sep}`) return assetPath.includes(`${path.sep}debug${path.sep}`)
? `${filename}-debug${arch}${ext}` ? `${filename}-debug${arch}${ext}`
: `${filename}${arch}${ext}` : `${filename}${arch}${ext}`;
} }

View File

@@ -1,2 +1,2 @@
import { run } from '../dist/index.js' import { run } from '../dist/index.js';
run() run();

View File

@@ -1,43 +1,49 @@
import { join, resolve } from 'path' import { join, resolve } from 'path';
import { existsSync } from 'fs' import { existsSync } from 'fs';
import { buildProject } from '@tauri-apps/action-core' import { buildProject } from '@tauri-apps/action-core';
import type { BuildOptions } from '@tauri-apps/action-core' import type { BuildOptions } from '@tauri-apps/action-core';
import parseArgs from 'minimist' import parseArgs from 'minimist';
export async function run(): Promise<void> { export async function run(): Promise<void> {
const argv = parseArgs(process.argv.slice(2), { const argv = parseArgs(process.argv.slice(2), {
string: ['project-path', 'config-path', 'dist-path', 'icon-path', 'tauri-script'], string: [
'project-path',
'config-path',
'dist-path',
'icon-path',
'tauri-script',
],
boolean: ['global-tauri', 'include-debug'], boolean: ['global-tauri', 'include-debug'],
default: { default: {
'config-path': 'tauri.conf.json', 'config-path': 'tauri.conf.json',
'project-path': '', 'project-path': '',
} },
}) });
const projectPath = resolve(process.cwd(), argv['project-path']) const projectPath = resolve(process.cwd(), argv['project-path']);
const configPath = join(projectPath, argv['config-path']) const configPath = join(projectPath, argv['config-path']);
const distPath = argv['dist-path'] const distPath = argv['dist-path'];
const iconPath = argv['icon-path'] const iconPath = argv['icon-path'];
const includeDebug = argv['include-debug'] const includeDebug = argv['include-debug'];
const tauriScript = argv['tauri-script'] const tauriScript = argv['tauri-script'];
const args = argv._ const args = argv._;
const options: BuildOptions = { const options: BuildOptions = {
configPath: existsSync(configPath) ? configPath : null, configPath: existsSync(configPath) ? configPath : null,
distPath, distPath,
iconPath, iconPath,
tauriScript, tauriScript,
args args,
} };
const artifacts = await buildProject(projectPath, false, options) const artifacts = await buildProject(projectPath, false, options);
if (includeDebug) { if (includeDebug) {
const debugArtifacts = await buildProject(projectPath, true, options) const debugArtifacts = await buildProject(projectPath, true, options);
artifacts.push(...debugArtifacts) artifacts.push(...debugArtifacts);
} }
if (artifacts.length === 0) { if (artifacts.length === 0) {
throw new Error('No artifacts were found.') throw new Error('No artifacts were found.');
} }
console.log(`Artifacts: ${artifacts}.`) console.log(`Artifacts: ${artifacts}.`);
} }

View File

@@ -1,26 +1,25 @@
import typescript from "@rollup/plugin-typescript"; import typescript from '@rollup/plugin-typescript';
import pkg from "./package.json"; import pkg from './package.json';
export default { export default {
treeshake: true, treeshake: true,
perf: true, perf: true,
input: { index: "index.ts" }, input: { index: 'index.ts' },
output: { output: {
dir: "dist", dir: 'dist',
format: "esm", format: 'esm',
entryFileNames: "[name].js", entryFileNames: '[name].js',
exports: "named", exports: 'named',
}, },
plugins: [typescript({ plugins: [
allowSyntheticDefaultImports: true typescript({
})], allowSyntheticDefaultImports: true,
external: [ }),
...Object.keys(pkg.dependencies || {}),
"path",
], ],
external: [...Object.keys(pkg.dependencies || {}), 'path'],
watch: { watch: {
chokidar: true, chokidar: true,
include: "src/**", include: 'src/**',
exclude: "node_modules/**", exclude: 'node_modules/**',
}, },
}; };

View File

@@ -1 +1 @@
declare module 'glob-gitignore' declare module 'glob-gitignore';

View File

@@ -1,96 +1,95 @@
import { platform } from 'os' import { platform } from 'os';
import { readFileSync, existsSync, copyFileSync, writeFileSync } from 'fs' import { readFileSync, existsSync, copyFileSync, writeFileSync } from 'fs';
import { execa } from 'execa' import { execa } from 'execa';
import { parse as parseToml } from '@iarna/toml' import { parse as parseToml } from '@iarna/toml';
import { join, resolve, normalize, sep } from 'path' import { join, resolve, normalize, sep } from 'path';
import { sync as globSync } from 'glob-gitignore' import { sync as globSync } from 'glob-gitignore';
import ignore from 'ignore' import ignore from 'ignore';
import JSON5 from 'json5' import JSON5 from 'json5';
export function getPackageJson(root: string): any { export function getPackageJson(root: string): any {
const packageJsonPath = join(root, 'package.json') const packageJsonPath = join(root, 'package.json');
if (existsSync(packageJsonPath)) { if (existsSync(packageJsonPath)) {
const packageJsonString = readFileSync(packageJsonPath).toString() const packageJsonString = readFileSync(packageJsonPath).toString();
const packageJson = JSON.parse(packageJsonString) const packageJson = JSON.parse(packageJsonString);
return packageJson return packageJson;
} }
return null return null;
} }
function getTauriDir(root: string): string | null { function getTauriDir(root: string): string | null {
const ignoreRules = ignore() const ignoreRules = ignore();
const gitignorePath = join(root, '.gitignore') const gitignorePath = join(root, '.gitignore');
if (existsSync(gitignorePath)) { if (existsSync(gitignorePath)) {
ignoreRules.add(readFileSync(gitignorePath).toString()) ignoreRules.add(readFileSync(gitignorePath).toString());
} else { } else {
ignoreRules.add('node_modules').add('target') ignoreRules.add('node_modules').add('target');
} }
const paths = globSync('**/tauri.conf.json', { const paths = globSync('**/tauri.conf.json', {
cwd: root, cwd: root,
ignore: ignoreRules, ignore: ignoreRules,
}) });
const tauriConfPath = paths[0] const tauriConfPath = paths[0];
return tauriConfPath ? resolve(root, tauriConfPath, '..') : null return tauriConfPath ? resolve(root, tauriConfPath, '..') : null;
} }
function getWorkspaceDir(dir: string): string | null { function getWorkspaceDir(dir: string): string | null {
const rootPath = dir const rootPath = dir;
while (dir.length && dir[dir.length - 1] !== sep) { while (dir.length && dir[dir.length - 1] !== sep) {
const manifestPath = join(dir, 'Cargo.toml') const manifestPath = join(dir, 'Cargo.toml');
if (existsSync(manifestPath)) { if (existsSync(manifestPath)) {
const toml = parseToml(readFileSync(manifestPath).toString()) const toml = parseToml(readFileSync(manifestPath).toString());
// @ts-expect-error // @ts-expect-error
if (toml.workspace && toml.workspace.members) { if (toml.workspace?.members) {
// @ts-expect-error // @ts-expect-error
const members: string[] = toml.workspace.members const members: string[] = toml.workspace.members;
if (members.some((m) => resolve(dir, m) === rootPath)) { if (members.some((m) => resolve(dir, m) === rootPath)) {
return dir return dir;
} }
} }
} }
dir = normalize(join(dir, '..')) dir = normalize(join(dir, '..'));
} }
return null return null;
} }
function getTargetDir(crateDir: string): string { function getTargetDir(crateDir: string): string {
const def = join(crateDir, 'target') const def = join(crateDir, 'target');
if ('CARGO_TARGET_DIR' in process.env) { if ('CARGO_TARGET_DIR' in process.env) {
return process.env.CARGO_TARGET_DIR ?? def return process.env.CARGO_TARGET_DIR ?? def;
} }
let dir = crateDir let dir = crateDir;
while (dir.length && dir[dir.length - 1] !== sep) { while (dir.length && dir[dir.length - 1] !== sep) {
let cargoConfigPath = join(dir, '.cargo/config') let cargoConfigPath = join(dir, '.cargo/config');
if (!existsSync(cargoConfigPath)) { if (!existsSync(cargoConfigPath)) {
cargoConfigPath = join(dir, '.cargo/config.toml') cargoConfigPath = join(dir, '.cargo/config.toml');
} }
if (existsSync(cargoConfigPath)) { if (existsSync(cargoConfigPath)) {
const cargoConfig = parseToml(readFileSync(cargoConfigPath).toString()) const cargoConfig = parseToml(readFileSync(cargoConfigPath).toString());
// @ts-ignore // @ts-ignore
if (cargoConfig.build && cargoConfig.build['target-dir']) { if (cargoConfig.build?.['target-dir']) {
// @ts-ignore // @ts-ignore
return cargoConfig.build['target-dir'] return cargoConfig.build['target-dir'];
} }
} }
dir = normalize(join(dir, '..')) dir = normalize(join(dir, '..'));
} }
return def return def;
} }
function hasDependency(dependencyName: string, root: string): boolean { function hasDependency(dependencyName: string, root: string): boolean {
const packageJson = getPackageJson(root) const packageJson = getPackageJson(root);
return ( return (
packageJson && packageJson &&
((packageJson.dependencies && packageJson.dependencies[dependencyName]) || (packageJson.dependencies?.[dependencyName] ||
(packageJson.devDependencies && packageJson.devDependencies?.[dependencyName])
packageJson.devDependencies[dependencyName])) );
)
} }
function usesYarn(root: string): boolean { function usesYarn(root: string): boolean {
return existsSync(join(root, 'yarn.lock')) return existsSync(join(root, 'yarn.lock'));
} }
export function execCommand( export function execCommand(
@@ -98,134 +97,135 @@ export function execCommand(
args: string[], args: string[],
{ cwd }: { cwd?: string } = {} { cwd }: { cwd?: string } = {}
): Promise<void> { ): Promise<void> {
console.log(`running ${command}`, args) console.log(`running ${command}`, args);
return execa(command, args, { return execa(command, args, {
cwd, cwd,
stdio: 'inherit', stdio: 'inherit',
env: { FORCE_COLOR: '0' }, env: { FORCE_COLOR: '0' },
}).then() }).then();
} }
interface CargoManifestBin { interface CargoManifestBin {
name: string name: string;
} }
interface CargoManifest { interface CargoManifest {
package: { version: string; name: string; 'default-run': string } package: { version: string; name: string; 'default-run': string };
bin: CargoManifestBin[] bin: CargoManifestBin[];
} }
interface TauriConfig { interface TauriConfig {
package?: { package?: {
productName?: string productName?: string;
version?: string version?: string;
} };
tauri?: { tauri?: {
bundle?: { bundle?: {
identifier: string identifier: string;
windows?: { windows?: {
wix?: { wix?: {
language?: string | string[] | { [language: string]: unknown } language?: string | string[] | { [language: string]: unknown };
} };
} };
} };
} };
} }
interface Application { interface Application {
tauriPath: string tauriPath: string;
runner: Runner runner: Runner;
name: string name: string;
version: string version: string;
wixLanguage: string | string[] | { [language: string]: unknown } wixLanguage: string | string[] | { [language: string]: unknown };
} }
export interface BuildOptions { export interface BuildOptions {
configPath: string | null configPath: string | null;
distPath: string | null distPath: string | null;
iconPath: string | null iconPath: string | null;
tauriScript: string | null tauriScript: string | null;
args: string[] | null args: string[] | null;
bundleIdentifier: string | null bundleIdentifier: string | null;
} }
export interface Runner { export interface Runner {
runnerCommand: string runnerCommand: string;
runnerArgs: string[] runnerArgs: string[];
} }
interface Info { interface Info {
tauriPath: string | null tauriPath: string | null;
name: string name: string;
version: string version: string;
wixLanguage: string | string[] | { [language: string]: unknown } wixLanguage: string | string[] | { [language: string]: unknown };
} }
export interface Artifact { export interface Artifact {
path: string path: string;
arch: string arch: string;
} }
function _getJson5Config(contents: string): TauriConfig | null { function _getJson5Config(contents: string): TauriConfig | null {
try { try {
const config = JSON5.parse(contents) as TauriConfig const config = JSON5.parse(contents) as TauriConfig;
return config return config;
} catch (e) { } catch (e) {
return null return null;
} }
} }
function getConfig(path: string): TauriConfig { function getConfig(path: string): TauriConfig {
const contents = readFileSync(path).toString() const contents = readFileSync(path).toString();
try { try {
const config = JSON.parse(contents) as TauriConfig const config = JSON.parse(contents) as TauriConfig;
return config return config;
} catch (e) { } catch (e) {
let json5Conf = _getJson5Config(contents) let json5Conf = _getJson5Config(contents);
if (json5Conf === null) { if (json5Conf === null) {
json5Conf = _getJson5Config( json5Conf = _getJson5Config(
readFileSync(join(path, '..', 'tauri.conf.json5')).toString() readFileSync(join(path, '..', 'tauri.conf.json5')).toString()
) );
} }
if (json5Conf) { if (json5Conf) {
return json5Conf return json5Conf;
} }
throw e throw e;
} }
} }
export function getInfo(root: string): Info { export function getInfo(root: string): Info {
const tauriDir = getTauriDir(root) const tauriDir = getTauriDir(root);
if (tauriDir !== null) { if (tauriDir !== null) {
const configPath = join(tauriDir, 'tauri.conf.json') const configPath = join(tauriDir, 'tauri.conf.json');
let name let name;
let version let version;
let wixLanguage: string | string[] | { [language: string]: unknown } = 'en-US' let wixLanguage: string | string[] | { [language: string]: unknown } =
const config = getConfig(configPath) 'en-US';
const config = getConfig(configPath);
if (config.package) { if (config.package) {
name = config.package.productName name = config.package.productName;
version = config.package.version version = config.package.version;
if (config.package.version?.endsWith('.json')) { if (config.package.version?.endsWith('.json')) {
const packageJsonPath = join(tauriDir, config.package.version) const packageJsonPath = join(tauriDir, config.package.version);
const contents = readFileSync(packageJsonPath).toString() const contents = readFileSync(packageJsonPath).toString();
version = JSON.parse(contents).version version = JSON.parse(contents).version;
} }
} }
if (!(name && version)) { if (!(name && version)) {
const manifestPath = join(tauriDir, 'Cargo.toml') const manifestPath = join(tauriDir, 'Cargo.toml');
const cargoManifest = parseToml( const cargoManifest = parseToml(
readFileSync(manifestPath).toString() readFileSync(manifestPath).toString()
) as any as CargoManifest ) as any as CargoManifest;
name = name || cargoManifest.package.name name = name || cargoManifest.package.name;
version = version || cargoManifest.package.version version = version || cargoManifest.package.version;
} }
if (config.tauri?.bundle?.windows?.wix?.language) { if (config.tauri?.bundle?.windows?.wix?.language) {
wixLanguage = config.tauri.bundle.windows.wix.language wixLanguage = config.tauri.bundle.windows.wix.language;
} }
if (!(name && version)) { if (!(name && version)) {
console.error('Could not determine package name and version') console.error('Could not determine package name and version');
process.exit(1) process.exit(1);
} }
return { return {
@@ -233,31 +233,38 @@ export function getInfo(root: string): Info {
name, name,
version, version,
wixLanguage, wixLanguage,
} };
} else { } else {
const packageJson = getPackageJson(root) const packageJson = getPackageJson(root);
const appName = packageJson const appName = packageJson
? (packageJson.displayName || packageJson.name).replace(/ /g, '-') ? (packageJson.displayName || packageJson.name).replace(/ /g, '-')
: 'app' : 'app';
const version = packageJson ? packageJson.version : '0.1.0' const version = packageJson ? packageJson.version : '0.1.0';
return { return {
tauriPath: null, tauriPath: null,
name: appName, name: appName,
version, version,
wixLanguage: 'en-US', wixLanguage: 'en-US',
} };
} }
} }
export async function buildProject( export async function buildProject(
root: string, root: string,
debug: boolean, debug: boolean,
{ configPath, distPath, iconPath, tauriScript, args, bundleIdentifier }: BuildOptions {
configPath,
distPath,
iconPath,
tauriScript,
args,
bundleIdentifier,
}: BuildOptions
): Promise<Artifact[]> { ): Promise<Artifact[]> {
return new Promise<Runner>((resolve, reject) => { return new Promise<Runner>((resolve, reject) => {
if (tauriScript) { if (tauriScript) {
const [runnerCommand, ...runnerArgs] = tauriScript.split(' ') const [runnerCommand, ...runnerArgs] = tauriScript.split(' ');
resolve({ runnerCommand, runnerArgs }) resolve({ runnerCommand, runnerArgs });
} else if ( } else if (
hasDependency('@tauri-apps/cli', root) || hasDependency('@tauri-apps/cli', root) ||
hasDependency('vue-cli-plugin-tauri', root) hasDependency('vue-cli-plugin-tauri', root)
@@ -266,19 +273,19 @@ export async function buildProject(
usesYarn(root) usesYarn(root)
? { runnerCommand: 'yarn', runnerArgs: ['tauri'] } ? { runnerCommand: 'yarn', runnerArgs: ['tauri'] }
: { runnerCommand: 'npx', runnerArgs: ['tauri'] } : { runnerCommand: 'npx', runnerArgs: ['tauri'] }
) );
} else { } else {
execCommand('npm', ['install', '-g', '@tauri-apps/cli'], { execCommand('npm', ['install', '-g', '@tauri-apps/cli'], {
cwd: undefined, cwd: undefined,
}) })
.then(() => { .then(() => {
resolve({ runnerCommand: 'tauri', runnerArgs: [] }) resolve({ runnerCommand: 'tauri', runnerArgs: [] });
}) })
.catch(reject) .catch(reject);
} }
}) })
.then((runner: Runner) => { .then((runner: Runner) => {
const info = getInfo(root) const info = getInfo(root);
if (info.tauriPath) { if (info.tauriPath) {
return { return {
tauriPath: info.tauriPath, tauriPath: info.tauriPath,
@@ -286,9 +293,9 @@ export async function buildProject(
name: info.name, name: info.name,
version: info.version, version: info.version,
wixLanguage: info.wixLanguage, wixLanguage: info.wixLanguage,
} };
} else { } else {
const packageJson = getPackageJson(root) const packageJson = getPackageJson(root);
return execCommand( return execCommand(
runner.runnerCommand, runner.runnerCommand,
[...runner.runnerArgs, 'init', '--ci', '--app-name', info.name], [...runner.runnerArgs, 'init', '--ci', '--app-name', info.name],
@@ -296,43 +303,43 @@ export async function buildProject(
cwd: root, cwd: root,
} }
).then(() => { ).then(() => {
const tauriPath = getTauriDir(root) const tauriPath = getTauriDir(root);
if (tauriPath === null) { if (tauriPath === null) {
console.error('Failed to resolve Tauri path') console.error('Failed to resolve Tauri path');
process.exit(1) process.exit(1);
} }
const configPath = join(tauriPath, 'tauri.conf.json') const configPath = join(tauriPath, 'tauri.conf.json');
const config = getConfig(configPath) const config = getConfig(configPath);
console.log( console.log(
`Replacing tauri.conf.json config - package.version=${info.version}` `Replacing tauri.conf.json config - package.version=${info.version}`
) );
const pkgConfig = { const pkgConfig = {
...config.package, ...config.package,
version: info.version, version: info.version,
} };
if (packageJson?.productName) { if (packageJson?.productName) {
console.log( console.log(
`Replacing tauri.conf.json config - package.productName=${packageJson.productName}` `Replacing tauri.conf.json config - package.productName=${packageJson.productName}`
) );
pkgConfig.productName = packageJson.productName pkgConfig.productName = packageJson.productName;
} }
config.package = pkgConfig config.package = pkgConfig;
if (bundleIdentifier) { if (bundleIdentifier) {
console.log( console.log(
`Replacing tauri.conf.json config - tauri.bundle.identifier=${bundleIdentifier}` `Replacing tauri.conf.json config - tauri.bundle.identifier=${bundleIdentifier}`
) );
config.tauri = { config.tauri = {
...config.tauri, ...config.tauri,
bundle: { bundle: {
...config.tauri?.bundle, ...config.tauri?.bundle,
identifier: bundleIdentifier identifier: bundleIdentifier,
} },
} };
} }
writeFileSync(configPath, JSON.stringify(config, null, 2)) writeFileSync(configPath, JSON.stringify(config, null, 2));
const app = { const app = {
tauriPath, tauriPath,
@@ -340,7 +347,7 @@ export async function buildProject(
name: info.name, name: info.name,
version: info.version, version: info.version,
wixLanguage: info.wixLanguage, wixLanguage: info.wixLanguage,
} };
if (iconPath) { if (iconPath) {
return execCommand( return execCommand(
runner.runnerCommand, runner.runnerCommand,
@@ -348,76 +355,78 @@ export async function buildProject(
{ {
cwd: root, cwd: root,
} }
).then(() => app) ).then(() => app);
} }
return app return app;
}) });
} }
}) })
.then((app: Application) => { .then((app: Application) => {
const tauriConfPath = join(app.tauriPath, 'tauri.conf.json') const tauriConfPath = join(app.tauriPath, 'tauri.conf.json');
if (configPath !== null) { if (configPath !== null) {
copyFileSync(configPath, tauriConfPath) copyFileSync(configPath, tauriConfPath);
} }
if (distPath) { if (distPath) {
const tauriConf = JSON.parse(readFileSync(tauriConfPath).toString()) const tauriConf = JSON.parse(readFileSync(tauriConfPath).toString());
tauriConf.build.distDir = distPath tauriConf.build.distDir = distPath;
writeFileSync(tauriConfPath, JSON.stringify(tauriConf)) writeFileSync(tauriConfPath, JSON.stringify(tauriConf));
} }
const tauriArgs = debug ? ['--debug', ...(args ?? [])] : args ?? [] const tauriArgs = debug ? ['--debug', ...(args ?? [])] : args ?? [];
let buildCommand let buildCommand;
let buildArgs: string[] = [] let buildArgs: string[] = [];
if (hasDependency('vue-cli-plugin-tauri', root)) { if (hasDependency('vue-cli-plugin-tauri', root)) {
if (usesYarn(root)) { if (usesYarn(root)) {
buildCommand = 'yarn' buildCommand = 'yarn';
buildArgs = ['tauri:build'] buildArgs = ['tauri:build'];
} else { } else {
buildCommand = 'npm' buildCommand = 'npm';
buildArgs = ['run', 'tauri:build'] buildArgs = ['run', 'tauri:build'];
} }
} else { } else {
buildCommand = app.runner.runnerCommand buildCommand = app.runner.runnerCommand;
buildArgs = [...app.runner.runnerArgs, 'build'] buildArgs = [...app.runner.runnerArgs, 'build'];
} }
return execCommand(buildCommand, [...buildArgs, ...tauriArgs], { return execCommand(buildCommand, [...buildArgs, ...tauriArgs], {
cwd: root, cwd: root,
}) })
.then(() => { .then(() => {
let fileAppName = app.name let fileAppName = app.name;
// on Linux, the app product name is converted to kebab-case // on Linux, the app product name is converted to kebab-case
if (!['darwin', 'win32'].includes(platform())) { if (!['darwin', 'win32'].includes(platform())) {
fileAppName = fileAppName fileAppName = fileAppName
.replace(/([a-z0-9])([A-Z])/g, '$1-$2') .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
.replace(/([A-Z])([A-Z])(?=[a-z])/g, '$1-$2') .replace(/([A-Z])([A-Z])(?=[a-z])/g, '$1-$2')
.replace(/[ _.]/g, '-') .replace(/[ _.]/g, '-')
.toLowerCase() .toLowerCase();
} }
const cratePath = getWorkspaceDir(app.tauriPath) ?? app.tauriPath const cratePath = getWorkspaceDir(app.tauriPath) ?? app.tauriPath;
const found = [...tauriArgs].findIndex( const found = [...tauriArgs].findIndex(
(e) => e === '-t' || e === '--target' (e) => e === '-t' || e === '--target'
) );
const targetPath = found >= 0 ? [...tauriArgs][found + 1] : '' const targetPath = found >= 0 ? [...tauriArgs][found + 1] : '';
const artifactsPath = join( const artifactsPath = join(
getTargetDir(cratePath), getTargetDir(cratePath),
targetPath, targetPath,
debug ? 'debug' : 'release' debug ? 'debug' : 'release'
) );
let arch = let arch =
targetPath.search('-') >= 0 targetPath.search('-') >= 0
? targetPath.split('-')[0] ? targetPath.split('-')[0]
: process.arch : process.arch;
if (platform() === 'darwin') { if (platform() === 'darwin') {
if (arch === "x86_64") arch = "x64" if (arch === 'x86_64') {
arch = 'x64';
}
return [ return [
join( join(
@@ -426,60 +435,60 @@ export async function buildProject(
), ),
join(artifactsPath, `bundle/macos/${fileAppName}.app`), join(artifactsPath, `bundle/macos/${fileAppName}.app`),
join(artifactsPath, `bundle/macos/${fileAppName}.app.tar.gz`), join(artifactsPath, `bundle/macos/${fileAppName}.app.tar.gz`),
join(artifactsPath, `bundle/macos/${fileAppName}.app.tar.gz.sig`) join(artifactsPath, `bundle/macos/${fileAppName}.app.tar.gz.sig`),
].map(path => ({ path, arch })) ].map((path) => ({ path, arch }));
} else if (platform() === 'win32') { } else if (platform() === 'win32') {
arch = arch.startsWith('i') ? 'x86' : 'x64' arch = arch.startsWith('i') ? 'x86' : 'x64';
// If multiple Wix languages are specified, multiple installers (.msi) will be made // If multiple Wix languages are specified, multiple installers (.msi) will be made
// The .zip and .sig are only generated for the first specified language // The .zip and .sig are only generated for the first specified language
let langs let langs;
if (typeof app.wixLanguage === 'string') { if (typeof app.wixLanguage === 'string') {
langs = [app.wixLanguage] langs = [app.wixLanguage];
} else if (Array.isArray(app.wixLanguage)) { } else if (Array.isArray(app.wixLanguage)) {
langs = app.wixLanguage langs = app.wixLanguage;
} else { } else {
langs = Object.keys(app.wixLanguage) langs = Object.keys(app.wixLanguage);
} }
const artifacts: string[] = [] const artifacts: string[] = [];
langs.forEach((lang) => { langs.forEach((lang) => {
artifacts.push( artifacts.push(
join( join(
artifactsPath, artifactsPath,
`bundle/msi/${fileAppName}_${app.version}_${arch}_${lang}.msi` `bundle/msi/${fileAppName}_${app.version}_${arch}_${lang}.msi`
) )
) );
artifacts.push( artifacts.push(
join( join(
artifactsPath, artifactsPath,
`bundle/msi/${fileAppName}_${app.version}_${arch}_${lang}.msi.zip` `bundle/msi/${fileAppName}_${app.version}_${arch}_${lang}.msi.zip`
) )
) );
artifacts.push( artifacts.push(
join( join(
artifactsPath, artifactsPath,
`bundle/msi/${fileAppName}_${app.version}_${arch}_${lang}.msi.zip.sig` `bundle/msi/${fileAppName}_${app.version}_${arch}_${lang}.msi.zip.sig`
) )
) );
}) });
return artifacts.map(path => ({ path, arch })) return artifacts.map((path) => ({ path, arch }));
} else { } else {
const debianArch = const debianArch =
arch === 'x64' || arch === 'x86_64' arch === 'x64' || arch === 'x86_64'
? 'amd64' ? 'amd64'
: arch === 'x32' || arch === 'i686' : arch === 'x32' || arch === 'i686'
? 'i386' ? 'i386'
: arch === 'arm' : arch === 'arm'
? 'armhf' ? 'armhf'
: arch === 'aarch64' : arch === 'aarch64'
? 'arm64' ? 'arm64'
: arch : arch;
const appImageArch = const appImageArch =
arch === 'x64' || arch === 'x86_64' arch === 'x64' || arch === 'x86_64'
? 'amd64' ? 'amd64'
: arch === 'x32' || arch === 'i686' : arch === 'x32' || arch === 'i686'
? 'i386' ? 'i386'
: arch : arch;
return [ return [
{ {
@@ -487,35 +496,39 @@ export async function buildProject(
artifactsPath, artifactsPath,
`bundle/deb/${fileAppName}_${app.version}_${debianArch}.deb` `bundle/deb/${fileAppName}_${app.version}_${debianArch}.deb`
), ),
arch: debianArch arch: debianArch,
}, },
{ {
path: join( path: join(
artifactsPath, artifactsPath,
`bundle/appimage/${fileAppName}_${app.version}_${appImageArch}.AppImage` `bundle/appimage/${fileAppName}_${app.version}_${appImageArch}.AppImage`
), ),
arch: appImageArch arch: appImageArch,
}, },
{ {
path: join( path: join(
artifactsPath, artifactsPath,
`bundle/appimage/${fileAppName}_${app.version}_${appImageArch}.AppImage.tar.gz` `bundle/appimage/${fileAppName}_${app.version}_${appImageArch}.AppImage.tar.gz`
), ),
arch: appImageArch arch: appImageArch,
}, },
{ {
path: join( path: join(
artifactsPath, artifactsPath,
`bundle/appimage/${fileAppName}_${app.version}_${appImageArch}.AppImage.tar.gz.sig` `bundle/appimage/${fileAppName}_${app.version}_${appImageArch}.AppImage.tar.gz.sig`
), ),
arch: appImageArch arch: appImageArch,
} },
] ];
} }
}) })
.then((artifacts) => { .then((artifacts) => {
console.log(`Expected artifacts paths:\n${artifacts.map(a => a.path).join('\n')}`) console.log(
return artifacts.filter((p) => existsSync(p.path)) `Expected artifacts paths:\n${artifacts
}) .map((a) => a.path)
}) .join('\n')}`
);
return artifacts.filter((p) => existsSync(p.path));
});
});
} }

View File

@@ -1,26 +1,25 @@
import typescript from "@rollup/plugin-typescript"; import typescript from '@rollup/plugin-typescript';
import pkg from "./package.json"; import pkg from './package.json';
export default { export default {
treeshake: true, treeshake: true,
perf: true, perf: true,
input: { index: "index.ts" }, input: { index: 'index.ts' },
output: { output: {
dir: "dist", dir: 'dist',
format: "esm", format: 'esm',
entryFileNames: "[name].js", entryFileNames: '[name].js',
exports: "named", exports: 'named',
}, },
plugins: [typescript({ plugins: [
tsconfig: './tsconfig.json' typescript({
})], tsconfig: './tsconfig.json',
external: [ }),
...Object.keys(pkg.dependencies || {}),
"path",
], ],
external: [...Object.keys(pkg.dependencies || {}), 'path'],
watch: { watch: {
chokidar: true, chokidar: true,
include: "src/**", include: 'src/**',
exclude: "node_modules/**", exclude: 'node_modules/**',
}, },
}; };

View File

@@ -1,5 +1,3 @@
{ {
"extends": [ "extends": ["config:base"]
"config:base"
]
} }