feat: Initial clean release of CHM
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled

这个提交包含在:
chen
2026-11-04 11:29:39 +08:00
当前提交 301194963b
共有 8663 个文件被更改,包括 1962691 次插入 和 0 次删除

67
.github/scripts/build_windows.ps1 vendored 普通文件
查看文件

@@ -0,0 +1,67 @@
# Force the postinstall build to run sequentially and retry `yarn install` a
# few times. Parallel postinstall builds randomly crash on Windows with
# STATUS_STACK_BUFFER_OVERRUN (0xC0000409), and node-pre-gyp prebuilt sqlite3
# downloads from GitHub Releases occasionally fail. Setting the env vars here
# (rather than only on the workflow step) ensures they reach every child
# process spawned by `yarn install` -> postinstall -> gulp.
function Install-WindowsDeps {
$env:BUILD_SEQUENCIAL = '1'
$env:IS_CONTINUOUS_INTEGRATION = '1'
if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') {
# Deliberately not setting npm_config_build_from_source to force the
# sqlite3 source build: sharp treats that variable as a boolean and
# would then also build libvips from source, which fails. sqlite3
# installs with --fallback-to-build, so it compiles by itself when the
# win32-arm64 prebuilt is missing.
# Deliberately not installing the VSSetup PowerShell module here: with it
# present, node-gyp probes Visual Studio via `Get-VSSetupInstance |
# ConvertTo-Json`, whose output exceeds the default 1MB child process
# buffer on the VS 2026 image. That fails with
# ERR_CHILD_PROCESS_STDIO_MAXBUFFER and node-gyp then reports the
# install as version "undefined". Without the module it falls through to
# its own Find-VisualStudio.cs helper, which prints far less.
}
$attempts = 3
for ($i = 1; $i -le $attempts; $i++) {
yarn install
if ($LASTEXITCODE -eq 0) { return }
if ($i -eq $attempts) { exit $LASTEXITCODE }
Write-Host "yarn install failed (attempt $i/$attempts) - retrying..."
Start-Sleep -Seconds 10
}
}
# Install dependencies and run `yarn dist` for the Windows desktop app.
# Extra args are forwarded to `yarn dist` (e.g. --publish=never).
#
# Each runner builds only its own arch: cross compiling arm64 from x64 silently
# ships x64 .node files, as @electron/rebuild doesn't resolve native modules
# hoisted to the workspace root.
# https://github.com/electron-userland/electron-builder/issues/10187
function Build-WindowsApp {
param([string[]]$DistArgs = @())
Install-WindowsDeps
if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') {
# NSIS gets an `-arm64` suffix on its own, but the portable name is fixed
# and the update metadata is `latest.yml` for every Windows arch. Both
# would clobber the x64 runner's artifacts.
# https://github.com/electron-userland/electron-builder/issues/6372
$archArgs = @(
'--arm64'
'-c.portable.artifactName=${productName}Portable-arm64.${ext}'
'-c.publish.provider=github'
'-c.publish.channel=latest-win-arm64'
)
} else {
$archArgs = @('--x64', '--ia32')
}
cd packages/app-desktop
yarn dist @archArgs @DistArgs
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}

163
.github/scripts/check_pr_title.js vendored 普通文件
查看文件

@@ -0,0 +1,163 @@
// Validates the PR title and acts on the result.
//
// - Renovate is filtered out at the workflow level (job `if:`).
// - Translation-only PRs (every changed file is a .po) are skipped.
// - Users in `softCheckUsers` get a relaxed check (issue number optional)
// and only ever receive a comment, never a close.
// - Everyone else must match the strict format. Invalid titles get a
// comment and the PR is closed. We also apply a marker label so that
// we can later tell our closures apart from any other closure.
// - If the title becomes valid and the marker label is present, the PR
// is reopened and the label is removed. Closures by humans (or by
// another workflow) lack the label and are never overturned.
//
// Invoked from .github/workflows/check-pr-title.yml via actions/github-script.
// Required inputs come from `env`: PR_AUTHOR, PR_NUMBER. The title is
// fetched from the API rather than passed via env to avoid YAML expansion
// silently stripping leading whitespace from `${{ ... }}`.
module.exports = async ({ github, context, core }) => {
const softCheckUsers = ['laurent22', 'personalizedrefrigerator', 'mrjo118', 'tessus', 'CalebJohn', 'Rygaa'];
const autoClosedLabel = 'auto-closed: invalid-title';
// Product-area prefixes are kept in sync with the list recognised by
// packages/tools/git-changelog.ts so that valid titles are picked up by
// the changelog generator. Non-product prefixes (Tools, Chore, CI, Doc,
// Plugin Repo) are administrative categories for changes that do not
// ship to users and so do not appear in the changelog.
const prefix = '(Desktop|Mobile|Android|iOS|Web|Windows|Linux|macOS|Cli|Clipper|Server|Transcribe|Plugins|Api|Cloud|Tools|Chore|CI|Doc)';
const prefixList = `${prefix}(,\\s*${prefix})*`;
const strictRegex = new RegExp(`^${prefixList}: (Fixes|Resolves) #[0-9]+: .+`);
const softRegex = new RegExp(`^${prefixList}: ((Fixes|Resolves) #[0-9]+: )?.+`);
const author = process.env.PR_AUTHOR;
const prNumber = Number(process.env.PR_NUMBER);
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
const title = pr.title;
core.info(`Title (length=${title.length}): ${JSON.stringify(title)}`);
const isSoft = softCheckUsers.includes(author);
// listFiles returns up to 30 files per page; a pure translation PR is
// small, so checking the first page is enough.
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
const isTranslationOnly = files.length > 0 && files.every(f => f.filename.endsWith('.po'));
if (isTranslationOnly) {
core.info('Translation-only PR — skipping title check.');
return;
}
// Doc-only PRs do not require an issue number.
const isDocOnly = /^Doc(,\s*Doc)*:/.test(title);
const regex = isSoft || isDocOnly ? softRegex : strictRegex;
if (regex.test(title)) {
core.info('Title is valid.');
// If we previously closed this PR for an invalid title and the
// title is now valid, reopen it. We only reopen if our marker
// label is present, so closures by humans (or other workflows)
// are never overturned. A maintainer can also remove the label
// by hand to lock a PR closed regardless of future fixes.
const wasAutoClosed = pr.state === 'closed' && pr.labels.some(l => l.name === autoClosedLabel);
if (wasAutoClosed) {
try {
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
state: 'open',
});
} catch (error) {
// GitHub refuses to reopen a PR when another open PR
// already exists from the same head→base branch pair.
// In that case the contributor has already opened a
// replacement, so leave this PR closed.
if (error.status === 422) {
core.info('Cannot reopen — another PR is already open from the same branch.');
return;
}
throw error;
}
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: autoClosedLabel,
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `@${author} thanks for fixing the title — this PR has been reopened.`,
});
core.info('PR reopened after title was fixed.');
}
return;
}
// Hidden marker so we can detect a previous nag and avoid reposting on
// every `synchronize` event (push to the PR branch).
const commentMarker = '<!-- joplin-bot:invalid-pr-title -->';
const helpMessage = [
commentMarker,
`@${author} the pull request title does not match the required format.`,
'',
'Please prefix the title with the area you are targeting, then add the issue you are addressing. If the change targets several areas, separate them with commas. For example:',
'',
'- `Desktop: Resolves #123: Added new setting to change font`',
'- `Mobile, Desktop: Fixes #456: Fixed config screen error`',
'- `Mobile, Desktop, Cli: Resolves #777: Improved note search performance`',
'',
'See the [pull request template](https://github.com/laurent22/joplin/blob/dev/.github/PULL_REQUEST_TEMPLATE) for the list of valid prefixes and the full specification.',
'',
isSoft
? '_This PR has been left open — please update the title when you have a moment._'
: '_This PR has been closed automatically. Once you update the title to match the format above, the PR will be reopened automatically._',
].join('\n');
const existingComments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
});
const alreadyCommented = existingComments.some(c => c.body && c.body.includes(commentMarker));
if (!alreadyCommented) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: helpMessage,
});
} else {
core.info('Invalid-title comment already posted — not commenting again.');
}
if (!isSoft) {
// Label first so the marker is set before the close event lands.
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [autoClosedLabel],
});
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
state: 'closed',
});
core.setFailed('Pull request title does not match the required format.');
}
};

查看文件

@@ -0,0 +1,43 @@
#!/bin/bash
VERSION=$(echo "$GIT_TAG_NAME" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
echo "GIT_TAG_NAME=$GIT_TAG_NAME"
echo "VERSION=$VERSION"
echo "SERVER_TAG_PREFIX=$SERVER_TAG_PREFIX"
echo "SERVER_REPOSITORY=$SERVER_REPOSITORY"
# Check if it's a server release, otherwise exit
if [[ $GIT_TAG_NAME != $SERVER_TAG_PREFIX-* ]]; then
exit 0
fi
docker manifest inspect $SERVER_REPOSITORY:arm64-$VERSION > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Image $SERVER_REPOSITORY:arm64-$VERSION does not exist on the remote registry."
exit 0
fi
docker manifest inspect $SERVER_REPOSITORY:amd64-$VERSION > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Image $SERVER_REPOSITORY:amd64-$VERSION does not exist on the remote registry."
exit 0
fi
docker manifest create $SERVER_REPOSITORY:$VERSION \
$SERVER_REPOSITORY:arm64-$VERSION \
$SERVER_REPOSITORY:amd64-$VERSION
docker manifest annotate $SERVER_REPOSITORY:$VERSION $SERVER_REPOSITORY:arm64-$VERSION --arch arm64
docker manifest annotate $SERVER_REPOSITORY:$VERSION $SERVER_REPOSITORY:amd64-$VERSION --arch amd64
docker manifest push $SERVER_REPOSITORY:$VERSION
docker manifest create $SERVER_REPOSITORY:latest \
$SERVER_REPOSITORY:arm64-$VERSION \
$SERVER_REPOSITORY:amd64-$VERSION
docker manifest annotate $SERVER_REPOSITORY:latest $SERVER_REPOSITORY:arm64-$VERSION --arch arm64
docker manifest annotate $SERVER_REPOSITORY:latest $SERVER_REPOSITORY:amd64-$VERSION --arch amd64
docker manifest push $SERVER_REPOSITORY:latest

73
.github/scripts/repeat_changed_tests.sh vendored 可执行文件
查看文件

@@ -0,0 +1,73 @@
#!/bin/bash
# Run the test files changed by a pull request several times, to catch flaky
# tests before they are merged.
#
# Usage: repeat_changed_tests.sh <base_ref> [repeat_count]
set -u
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
ROOT_DIR="$SCRIPT_DIR/../.."
BASE_REF="${1:-}"
REPEAT_COUNT="${2:-5}"
cd "$ROOT_DIR"
# The CI checkout is shallow, so the base branch is not present locally. Always
# fetch it rather than using a local branch of the same name, which may be
# outdated.
if git fetch --no-tags --depth=1 origin "$BASE_REF" 2> /dev/null; then
BASE_REF=FETCH_HEAD
elif ! git rev-parse --verify --quiet "$BASE_REF" > /dev/null; then
echo "Could not resolve base ref \"$BASE_REF\""
exit 1
fi
# `integration-tests` holds the Playwright specs (see app-desktop/playwright.config.ts),
# which Jest cannot run. Other `.spec.*` files are Jest tests, so they are kept.
testFiles=$(git diff --name-only --diff-filter=d "$BASE_REF" HEAD | grep -E '^packages/[^/]+/.*\.(test|spec)\.(ts|tsx|js|jsx)$' | grep -v -E '^packages/[^/]+/integration-tests/' || true)
if [ -z "$testFiles" ]; then
echo "No test files were changed - skipping flaky test detection"
exit 0
fi
echo "Changed test files:"
echo "$testFiles" | sed 's/^/ /'
# Each package has its own Jest config and must be run from its own directory.
packages=$(echo "$testFiles" | sed -E 's|^packages/([^/]+)/.*|\1|' | sort -u)
for package in $packages; do
packageDir="$ROOT_DIR/packages/$package"
# Some packages have test files but no test runner configured.
if ! node -e "const s = require('$packageDir/package.json').scripts; process.exit(s && s['test-ci'] ? 0 : 1)" 2> /dev/null; then
echo "Package $package has no test-ci script - skipping"
continue
fi
# See packages/app-mobile/tools/runTestsConditionally.js
if [ "$package" == "app-mobile" ] && [ "$RUNNER_OS" == "macOS" ]; then
echo "Skipping app-mobile tests on macOS"
continue
fi
# Pass the file names without their extension: some packages run the
# TypeScript sources while others run the compiled files, and Jest matches
# these patterns against the full path either way.
packageTestNames=$(echo "$testFiles" | grep -E "^packages/$package/" | xargs -n 1 basename | sed -E 's#\.[a-z]+$##')
cd "$packageDir"
for i in $(seq 1 "$REPEAT_COUNT"); do
echo "Running $package tests - attempt $i/$REPEAT_COUNT..."
# shellcheck disable=SC2086
if ! yarn jest $packageTestNames --forceExit; then
echo "Tests failed on attempt $i/$REPEAT_COUNT in $package - they are flaky if earlier attempts passed"
exit 1
fi
done
cd "$ROOT_DIR"
done

37
.github/scripts/retry.sh vendored 可执行文件
查看文件

@@ -0,0 +1,37 @@
#!/bin/bash
# Retry a command up to a few times. Used to absorb random CI flakes (network
# failures, slow mirrors, etc).
#
# Usage: retry [attempts] [sleep_seconds] -- command [args...]
# Both attempts and sleep_seconds are optional and default to 3 and 10.
retry() {
local attempts=3
local sleep_seconds=10
if [[ "$1" =~ ^[0-9]+$ ]]; then
attempts="$1"
shift
fi
if [[ "$1" =~ ^[0-9]+$ ]]; then
sleep_seconds="$1"
shift
fi
if [ "$1" = '--' ]; then
shift
fi
local i=1
while true; do
"$@"
local result=$?
if [ $result -eq 0 ]; then
return 0
fi
if [ $i -ge $attempts ]; then
return $result
fi
echo "\`$*\` failed (attempt $i/$attempts) - retrying in ${sleep_seconds}s..."
i=$((i + 1))
sleep "$sleep_seconds"
done
}

383
.github/scripts/run_ci.sh vendored 可执行文件
查看文件

@@ -0,0 +1,383 @@
#!/bin/bash
# =============================================================================
# Setup environment variables
# =============================================================================
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
ROOT_DIR="$SCRIPT_DIR/../.."
TRANSCRIBE_TAG_PREFIX=transcribe
TRANSCRIBE_REPOSITORY=joplin/transcribe
IS_PULL_REQUEST=0
IS_DESKTOP_RELEASE=0
IS_SERVER_RELEASE=0
IS_TRANSCRIBE_RELEASE=0
IS_LINUX=0
IS_MACOS=0
# If pull requests are coming from a branch of the main repository,
# IS_PULL_REQUEST will be zero.
if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then
IS_PULL_REQUEST=1
fi
if [[ $GIT_TAG_NAME = $SERVER_TAG_PREFIX-* ]]; then
IS_SERVER_RELEASE=1
fi
if [[ $GIT_TAG_NAME = $TRANSCRIBE_TAG_PREFIX-* ]]; then
IS_TRANSCRIBE_RELEASE=1
fi
if [[ $GIT_TAG_NAME = v* ]]; then
IS_DESKTOP_RELEASE=1
fi
if [ "$RUNNER_OS" == "Linux" ]; then
IS_LINUX=1
IS_MACOS=0
else
IS_LINUX=0
IS_MACOS=1
fi
DOCKER_IMAGE_PLATFORM="linux/amd64"
# Tests can randomly fail in some cases, so only run them when not publishing
# a release
RUN_TESTS=0
if [ "$IS_SERVER_RELEASE" = 0 ] && [ "$IS_DESKTOP_RELEASE" = 0 ] && [ "$IS_TRANSCRIBE_RELEASE" = 0 ]; then
RUN_TESTS=1
fi
if [ "$RUNNER_ARCH" == "ARM64" ]; then
if [ "$IS_SERVER_RELEASE" == "0" ] && [ "$IS_TRANSCRIBE_RELEASE" == "0" ]; then
# We exit now because nothing works properly with the ARM64 architecture.
# We only proceed if building the server image.
echo "Running on ARM64 and not trying to build server image - early exit"
exit 0
fi
fi
if [ "$RUNNER_ARCH" == "ARM64" ]; then
# Canvas is only needed for tests and it doesn't build in ARM64 so remove it
RUN_TESTS=0
cd "$ROOT_DIR/packages/lib"
yarn remove canvas
cd "$ROOT_DIR"
DOCKER_IMAGE_PLATFORM="linux/arm64"
# Delete certain directories because `yarn install` will fail on ARM64.
rm -rf app-desktop
rm -rf app-mobile
fi
# =============================================================================
# Print environment
# =============================================================================
echo "RUNNER_OS=$RUNNER_OS"
echo "RUNNER_ARCH=$RUNNER_ARCH"
echo "GITHUB_WORKFLOW=$GITHUB_WORKFLOW"
echo "GITHUB_EVENT_NAME=$GITHUB_EVENT_NAME"
echo "GITHUB_REF=$GITHUB_REF"
echo "RUNNER_OS=$RUNNER_OS"
echo "GIT_TAG_NAME=$GIT_TAG_NAME"
echo "BUILD_SEQUENCIAL=$BUILD_SEQUENCIAL"
echo "SERVER_REPOSITORY=$SERVER_REPOSITORY"
echo "SERVER_TAG_PREFIX=$SERVER_TAG_PREFIX"
echo "TRANSCRIBE_TAG_PREFIX=$TRANSCRIBE_TAG_PREFIX"
echo "DOCKER_IMAGE_PLATFORM=$DOCKER_IMAGE_PLATFORM"
echo "IS_CONTINUOUS_INTEGRATION=$IS_CONTINUOUS_INTEGRATION"
echo "IS_PULL_REQUEST=$IS_PULL_REQUEST"
echo "IS_DESKTOP_RELEASE=$IS_DESKTOP_RELEASE"
echo "IS_SERVER_RELEASE=$IS_SERVER_RELEASE"
echo "IS_TRANSCRIBE_RELEASE=$IS_TRANSCRIBE_RELEASE"
echo "RUN_TESTS=$RUN_TESTS"
echo "IS_LINUX=$IS_LINUX"
echo "IS_MACOS=$IS_MACOS"
echo "Node $( node -v )"
echo "Npm $( npm -v )"
echo "Yarn $( yarn -v )"
echo "Rust $( rustc --version )"
# =============================================================================
# Install packages
# =============================================================================
cd "$ROOT_DIR"
source "$(dirname "$0")/retry.sh"
retry yarn install
testResult=$?
if [ $testResult -ne 0 ]; then
echo "Yarn installation failed. Search for 'exit code 1' in the log for more information."
exit $testResult
fi
# =============================================================================
# Run test units
# =============================================================================
if [ "$RUN_TESTS" == "1" ]; then
echo "Step: Running tests..."
# On Linux, we run the Joplin Server tests using PostgreSQL
if [ "$IS_LINUX" == "1" ]; then
echo "Running Joplin Server tests using PostgreSQL..."
# Docker Hub auth/pulls occasionally time out on CI, so retry a few
# times before giving up.
cmdResult=1
for attempt in 1 2 3; do
sudo docker compose --parallel 1 --file docker-compose.db-dev.yml up -d
cmdResult=$?
if [ $cmdResult -eq 0 ]; then
break
fi
echo "docker compose up failed (attempt $attempt). Retrying in 15s..."
sudo docker compose --file docker-compose.db-dev.yml down --remove-orphans || true
sleep 15
done
if [ $cmdResult -ne 0 ]; then
exit $cmdResult
fi
export JOPLIN_TESTS_SERVER_DB=pg
else
echo "Running Joplin Server tests using SQLite..."
fi
# Need this because we're getting this error:
#
# @joplin/lib: FATAL ERROR: Ineffective mark-compacts near heap limit
# Allocation failed - JavaScript heap out of memory
#
# https://stackoverflow.com/questions/38558989
export NODE_OPTIONS="--max-old-space-size=32768"
yarn test-ci
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
# Run the test files changed by the pull request several times, to catch
# flaky tests before they are merged.
if [ "$IS_PULL_REQUEST" == "1" ]; then
echo "Step: Checking changed tests for flakiness..."
"$SCRIPT_DIR/repeat_changed_tests.sh" "$GITHUB_BASE_REF"
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
fi
# =============================================================================
# Run linter for pull requests only. We also don't want this to make the desktop
# release randomly fail.
# =============================================================================
if [ "$RUN_TESTS" == "1" ]; then
echo "Step: Running linter..."
yarn linter-ci ./
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
yarn packageJsonLint
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
# =============================================================================
# Validate translations - this is needed as some users manually edit .po files
# (and often make mistakes) instead of using a proper tool like poedit. Doing it
# for Linux only is sufficient.
# =============================================================================
if [ "$IS_LINUX" == "1" ]; then
echo "Step: Validating translations..."
node packages/tools/validate-translation.js
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
# =============================================================================
# Check that we didn't lose any string due to gettext not being able to parse
# newly modified or added scripts. This is convenient to quickly view on GitHub
# what commit may have broken translation building.
# =============================================================================
if [ "$RUN_TESTS" == "1" ]; then
if [ "$IS_LINUX" == "1" ]; then
echo "Step: Checking for lost translation strings..."
xgettext --version
node packages/tools/build-translation.js --missing-strings-check-only
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
fi
# =============================================================================
# Check .gitignore and .ignore.eslint files - they should be updated when
# new TypeScript files are added by running `yarn updateIgnored`.
# See coding_style.md
# =============================================================================
if [ "$IS_LINUX" == "1" ]; then
echo "Step: Checking for files that should have been ignored..."
# .gitignore and .ignore.eslint can be modified during yarn install. Reset them
# so that checkIgnoredFiles works.
git restore .gitignore .ignore.eslint
node packages/tools/checkIgnoredFiles.js
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
# =============================================================================
# Check that the website still builds
# =============================================================================
if [ "$RUN_TESTS" == "1" ]; then
if [ "$IS_LINUX" == "1" ]; then
echo "Step: Check that the website still builds..."
mkdir -p ../joplin-website/docs
CROWDIN_PERSONAL_TOKEN="$CROWDIN_PERSONAL_TOKEN" yarn crowdinDownload
SKIP_SPONSOR_PROCESSING=1 yarn buildWebsite
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
fi
# =============================================================================
# Spellchecking
# =============================================================================
if [ "$IS_LINUX" == "1" ]; then
echo "Step: Spellchecking..."
yarn spellcheck --all
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi
# =============================================================================
# Find out if we should run the build or not. Electron-builder gets stuck when
# building PRs so we disable it in this case. The Linux build should provide
# enough info if the app builds or not.
# https://github.com/electron-userland/electron-builder/issues/4263
# =============================================================================
if [ "$IS_PULL_REQUEST" == "1" ]; then
if [ "$IS_MACOS" == "1" ]; then
echo "Step: Not building Electron app"
exit 0
fi
fi
# =============================================================================
# Build the Electron app or Docker image depending on the current tag.
#
# If the current tag is a desktop release tag (starts with "v", such as
# "v1.4.7"), we build and publish to GitHub. Otherwise we only build but don't
# publish to GitHub. It helps finding out any issue in pull requests and dev
# branch.
# =============================================================================
cd "$ROOT_DIR/packages/app-desktop"
# On macOS, `yarn dist` can fail randomly with `hdiutil detach ... Resource
# busy` when packaging the DMG, because another process (Spotlight mds,
# diskimages-helper, etc.) still holds the mounted image. Retry the whole
# build a couple of times to absorb the flake.
run_yarn_dist() {
local attempts=3
local i=1
while true; do
USE_HARD_LINKS=false yarn dist "$@"
local result=$?
if [ $result -eq 0 ]; then
return 0
fi
if [ "$IS_MACOS" != "1" ] || [ $i -ge $attempts ]; then
return $result
fi
echo "yarn dist failed (attempt $i/$attempts) - retrying..."
i=$((i + 1))
sleep 10
done
}
if [ "$IS_DESKTOP_RELEASE" == "1" ]; then
echo "Step: Building and publishing desktop application..."
# cd "$ROOT_DIR/packages/tools"
# node bundleDefaultPlugins.js
cd "$ROOT_DIR/packages/app-desktop"
if [ "$IS_MACOS" == "1" ]; then
# This is to fix this error:
#
# Exit code: ENOENT. spawn /usr/bin/python ENOENT
#
# Ref: https://github.com/electron-userland/electron-builder/issues/6767#issuecomment-1096589528
#
# It can be removed once we upgrade to electron-builder@23, however we
# cannot currently do this due to this error:
# https://github.com/laurent22/joplin/issues/8149
#
# electron-builder@24, however, still expects the python binary to be named
# "python" and seems to no longer respect the PYTHON_PATH environment variable.
# We work around this by aliasing python.
alias python=$(which python3)
fi
run_yarn_dist
elif [[ $IS_LINUX = 1 ]] && [ "$IS_SERVER_RELEASE" == "1" ]; then
echo "Step: Building Joplin Server Docker Image..."
cd "$ROOT_DIR"
yarn buildServerDocker --docker-file Dockerfile.server --platform $DOCKER_IMAGE_PLATFORM --tag-name $GIT_TAG_NAME --push-images --repository $SERVER_REPOSITORY
elif [[ $IS_LINUX = 1 ]] && [ "$IS_TRANSCRIBE_RELEASE" == "1" ]; then
echo "Step: Building Joplin Transcribe Docker Image..."
cd "$ROOT_DIR"
yarn buildServerDocker --docker-file Dockerfile.transcribe --platform $DOCKER_IMAGE_PLATFORM --tag-name $GIT_TAG_NAME --push-images --repository $TRANSCRIBE_REPOSITORY
else
echo "Step: Building but *not* publishing desktop application..."
if [ "$IS_MACOS" == "1" ]; then
# See above why we need to specify Python
alias python=$(which python3)
# We also want to disable signing the app in this case, because
# it randomly fails and we don't even need it
# https://www.electron.build/code-signing#how-to-disable-code-signing-during-the-build-process-on-macos
export CSC_IDENTITY_AUTO_DISCOVERY=false
npm pkg set 'build.mac.identity'=null --json
fi
run_yarn_dist --publish=never
fi