Merge branch 'main' into ci/self-hosted-runner-signing
This commit is contained in:
241 files changed
+7211
-5170
No files matched your search
@@ -2,6 +2,7 @@ name: Bug 反馈 / Bug Report
|
||||
description: 报告水杉输入法的错误或异常行为。Report a bug or unexpected behavior.
|
||||
title: "[Bug] "
|
||||
labels: ["bug"]
|
||||
type: Bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -18,14 +19,13 @@ body:
|
||||
description: 问题主要出现在哪个部分?Which part is primarily affected?
|
||||
multiple: true
|
||||
options:
|
||||
- TSF 前端 / MSIME-Windows
|
||||
- Server / 引擎服务 / MSIME-Server
|
||||
- 设置界面 / Settings
|
||||
- 候选窗 UI / Candidate UI
|
||||
- 词典 / MSIME-Engine/dictionary
|
||||
- 辅助码 / MSIME-Engine
|
||||
- 候选窗 / Candidate window
|
||||
- 词典 / Dictionary and Lexicon
|
||||
- 辅助码 / Help Code
|
||||
- 设置 / Settings
|
||||
- 安装器 / Installer
|
||||
- 文档 / 官网
|
||||
- 引擎服务 / Engine
|
||||
- 文档 / Docs
|
||||
- 不确定 / Not sure
|
||||
- 其他 / Other
|
||||
validations:
|
||||
@@ -33,10 +33,10 @@ body:
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 问题描述 / Description
|
||||
description: 发生了什么?期望是什么?What happened, and what did you expect?
|
||||
label: 问题与行为 / Problem & Actual behavior
|
||||
description: 发生了什么?What happened?
|
||||
placeholder: |
|
||||
简要说明问题现象与预期行为。
|
||||
简要说明问题现象与行为。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
@@ -57,13 +57,6 @@ body:
|
||||
description: 正常情况下应该怎样?What should happen instead?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: 实际行为 / Actual Behavior
|
||||
description: 实际观察到了什么?What actually happened?
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
@@ -77,7 +70,7 @@ body:
|
||||
attributes:
|
||||
label: 系统环境 / OS
|
||||
description: Windows 版本与版本号。Windows edition and version.
|
||||
placeholder: e.g. Windows 11 24H2 / Windows 10 Pro 22H2
|
||||
placeholder: e.g. Windows 11 Pro 24H2 / Windows 10 Pro 22H2
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
@@ -85,8 +78,9 @@ body:
|
||||
attributes:
|
||||
label: 输入方案 / Input Scheme
|
||||
options:
|
||||
- 小鹤双拼 / Xiaohe Shuangpin
|
||||
- 全拼 / Quanpin
|
||||
- 全拼 / Full Pinyin
|
||||
- 双拼 / Double Pinyin
|
||||
- 五笔 / Wubi
|
||||
- 其他 / Other
|
||||
- 不适用 / N/A
|
||||
validations:
|
||||
@@ -106,9 +100,9 @@ body:
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: 日志 / 截图 / 录屏(可选)
|
||||
label: 日志(可选)
|
||||
description: |
|
||||
可粘贴相关日志,或附上截图 / 录屏链接。
|
||||
可粘贴相关日志。
|
||||
Paste relevant logs, or attach screenshots / recordings.
|
||||
render: shell
|
||||
validations:
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
# First on purpose. Blank issues are disabled, so this chooser is the only path someone who found
|
||||
# a vulnerability sees, and without this row the most natural next action is to file a public bug
|
||||
# -- which is exactly what SECURITY.md forbids. Private vulnerability reporting is enabled on this
|
||||
# repository, so the advisory form below is a working channel, not just a document to read.
|
||||
- name: 安全漏洞 / Security vulnerability
|
||||
url: https://github.com/metasequoiaime/MSIME-Windows/security/advisories/new
|
||||
about: 疑似漏洞请私下上报,不要提交公开 issue。Report privately; do not open a public issue.
|
||||
- name: 组词引擎与词库问题 / Engine and dictionary
|
||||
url: https://github.com/metasequoiaime/MSIME-Engine/issues
|
||||
about: 选字顺序、词库缺词等属于共享引擎与词库,请到对应仓库反馈。Candidate order and missing words belong to the shared engine.
|
||||
- name: Telegram 群组 / Telegram Group
|
||||
url: https://t.me/msimegroup
|
||||
about: 使用问题、讨论与日常交流。Questions and discussions.
|
||||
- name: QQ 群 / QQ Group
|
||||
url: https://github.com/metasequoiaime/MSIME-Windows
|
||||
about: "QQ 群号 829919142(见仓库简介)。QQ Group: 829919142."
|
||||
url: https://github.com/metasequoiaime/MSIME-Windows#readme
|
||||
about: "QQ 群号 829919142(手动搜索群号加入)。QQ Group: 829919142."
|
||||
- name: 项目文档 / Documentation
|
||||
url: https://github.com/metasequoiaime/MSIME-Docs
|
||||
about: 水杉输入法文档仓库。Docs for Metasequoia IME.
|
||||
url: https://msime.app/docs/
|
||||
about: 水杉输入法用户文档。User documentation for Metasequoia IME.
|
||||
@@ -2,6 +2,7 @@ name: 功能建议 / Feature Request
|
||||
description: 为水杉输入法提出新功能或改进建议。Propose a new feature or improvement.
|
||||
title: "[Feat] "
|
||||
labels: ["enhancement"]
|
||||
type: Feature
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -18,14 +19,13 @@ body:
|
||||
description: 该功能主要涉及哪个部分?Which part does this mainly involve?
|
||||
multiple: true
|
||||
options:
|
||||
- TSF 前端 / MSIME-Windows
|
||||
- Server / 引擎服务 / MSIME-Server
|
||||
- 设置界面 / Settings
|
||||
- 候选窗 UI / Candidate UI
|
||||
- 词典 / MSIME-Engine/dictionary
|
||||
- 辅助码 / MSIME-Engine
|
||||
- 候选窗 / Candidate window
|
||||
- 词典 / Dictionary and Lexicon
|
||||
- 辅助码 / Help Code
|
||||
- 设置 / Settings
|
||||
- 安装器 / Installer
|
||||
- 文档 / 官网
|
||||
- 引擎服务 / Engine
|
||||
- 文档 / Docs
|
||||
- 不确定 / Not sure
|
||||
- 其他 / Other
|
||||
validations:
|
||||
@@ -33,7 +33,7 @@ body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: 要解决的问题 / Problem
|
||||
label: 问题与动机 / Problem & Motivation
|
||||
description: 当前缺少什么,或现有体验哪里不够好?What problem does this solve?
|
||||
placeholder: |
|
||||
例如:无法…… / 使用某功能时很麻烦……
|
||||
|
||||
+18
-13
@@ -4,29 +4,25 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
# A release-please pull request only rewrites these three files, and its pull_request run would
|
||||
# be created in action_required and never execute: GITHUB_TOKEN may not start workflows, so
|
||||
# anything it triggers waits for a human. Skipping it here keeps an unapprovable run out of the
|
||||
# checks list. The release workflow dispatches this workflow on the release branch instead,
|
||||
# which is the one trigger GITHUB_TOKEN is allowed to fire, so the version bump still gets a
|
||||
# full build and the checks land on the same commit.
|
||||
paths-ignore:
|
||||
- version.txt
|
||||
- CHANGELOG.md
|
||||
- .release-please-manifest.json
|
||||
workflow_dispatch:
|
||||
|
||||
merge_group:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ci-${{ github.event.pull_request.head.ref || github.ref_name }}
|
||||
group: ci-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
quality:
|
||||
uses: ./.github/workflows/quality.yml
|
||||
|
||||
lock:
|
||||
name: Product inputs
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
# The dictionary validator is compared against the engine's copy of it, so this job needs the
|
||||
# submodule's contents and not just its gitlink.
|
||||
@@ -37,6 +33,10 @@ jobs:
|
||||
run: python3 scripts/check-dictionary-contract.py
|
||||
- name: Test rejection of changed or mismatched inputs
|
||||
run: python3 -m unittest discover -s tests -p 'test_product_lock.py'
|
||||
- name: Test local environment setup in a moved checkout
|
||||
run: python3 -m unittest discover -s tests -p 'test_prepare_env.py'
|
||||
- name: Test the automatic release CI gate
|
||||
run: python3 -m unittest discover -s tests -p 'test_release_pr_ci.py'
|
||||
- name: Read the reviewed lock
|
||||
id: lock
|
||||
run: |
|
||||
@@ -46,6 +46,7 @@ jobs:
|
||||
windows:
|
||||
name: Windows ${{ matrix.name }}
|
||||
runs-on: windows-2025
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -86,7 +87,7 @@ jobs:
|
||||
shell: pwsh
|
||||
run: cmake --build windows/build --config Release --parallel
|
||||
- name: Test the wire contract on this architecture
|
||||
run: ctest --test-dir windows/build -C Release --output-on-failure
|
||||
run: ctest --no-tests=error --test-dir windows/build -C Release --output-on-failure
|
||||
|
||||
# Architecture-independent, so it runs once rather than on both matrix legs.
|
||||
- name: Check the installer language file lands where ISCC looks
|
||||
@@ -157,7 +158,7 @@ jobs:
|
||||
run: cmake --build ui/build --config Release --parallel
|
||||
- name: Test
|
||||
shell: pwsh
|
||||
run: ctest --test-dir ui/build -C Release --verbose --timeout 60
|
||||
run: ctest --no-tests=error --test-dir ui/build -C Release --verbose --timeout 60
|
||||
|
||||
pages:
|
||||
name: Settings page
|
||||
@@ -213,3 +214,7 @@ jobs:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Test full and light package contents without registering or installing
|
||||
run: ./installer/tests/package-files.ps1
|
||||
- name: Stop local packaging after failed component builds
|
||||
run: ./installer/tests/build-failures.ps1
|
||||
- name: Test local Server test entrypoints in a moved checkout
|
||||
run: ./tests/server-test-entrypoints.ps1
|
||||
@@ -0,0 +1,37 @@
|
||||
name: CodeQL
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '11 3 * * 1'
|
||||
workflow_dispatch:
|
||||
permissions:
|
||||
contents: read
|
||||
concurrency:
|
||||
group: codeql-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
analyze:
|
||||
name: CodeQL / ${{ matrix.language }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [actions, c-cpp, javascript-typescript, python]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# C/C++ source analysis complements, rather than replaces, native build CI.
|
||||
build-mode: none
|
||||
- uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
|
||||
with:
|
||||
category: '/language:${{ matrix.language }}'
|
||||
@@ -12,16 +12,21 @@ on:
|
||||
paths:
|
||||
- 'vendor/MetasequoiaImeEngine'
|
||||
|
||||
# Read-only by default, matching every other workflow here. The write scopes this job needs to merge
|
||||
# the bump are granted on the job instead, so a second job added later starts from the same floor
|
||||
# rather than inheriting write.
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
if: github.actor == 'dependabot[bot]'
|
||||
name: Classify the engine range
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
env:
|
||||
SUBMODULE: vendor/MetasequoiaImeEngine
|
||||
ENGINE: metasequoiaime/MSIME-Engine
|
||||
@@ -36,9 +41,11 @@ jobs:
|
||||
|
||||
- name: Resolve the engine range
|
||||
id: range
|
||||
env:
|
||||
BASE_REF: ${{ github.base_ref }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base=$(git rev-parse "origin/${{ github.base_ref }}:$SUBMODULE")
|
||||
base=$(git rev-parse "origin/$BASE_REF:$SUBMODULE")
|
||||
head=$(git rev-parse "HEAD:$SUBMODULE")
|
||||
echo "base=$base" >> "$GITHUB_OUTPUT"
|
||||
echo "head=$head" >> "$GITHUB_OUTPUT"
|
||||
@@ -46,17 +53,47 @@ jobs:
|
||||
|
||||
- name: Classify the changed files
|
||||
id: classify
|
||||
env:
|
||||
BASE: ${{ steps.range.outputs.base }}
|
||||
HEAD: ${{ steps.range.outputs.head }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
base='${{ steps.range.outputs.base }}'
|
||||
head='${{ steps.range.outputs.head }}'
|
||||
base="$BASE"
|
||||
head="$HEAD"
|
||||
if [ "$base" = "$head" ]; then
|
||||
echo "verdict=inert" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fail closed on anything but a plain fast-forward. The compare API answers with an empty
|
||||
# file list whenever head is already an ancestor of base, which the emptiness test below
|
||||
# would otherwise read as "nothing suspicious changed" and auto-merge — silently accepting
|
||||
# a downgrade that reverts engine source the frontend depends on.
|
||||
status=$(gh api "repos/$ENGINE/compare/$base...$head" --jq '.status')
|
||||
echo "compare status: $status"
|
||||
if [ "$status" != "ahead" ]; then
|
||||
echo "verdict=review" >> "$GITHUB_OUTPUT"
|
||||
delimiter="EOF_$(openssl rand -hex 16)"
|
||||
{
|
||||
echo "reason<<$delimiter"
|
||||
echo "引擎范围不是单纯的快进(compare status: $status),需要人工确认。"
|
||||
echo "$delimiter"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
files=$(gh api --paginate "repos/$ENGINE/compare/$base...$head" --jq '.files[].filename')
|
||||
echo "改动文件:"; echo "$files" | sed 's/^/ /'
|
||||
if [ -z "$files" ]; then
|
||||
echo "verdict=review" >> "$GITHUB_OUTPUT"
|
||||
delimiter="EOF_$(openssl rand -hex 16)"
|
||||
{
|
||||
echo "reason<<$delimiter"
|
||||
echo "compare 没有返回任何文件,无法判定这次 bump 是否安全。"
|
||||
echo "$delimiter"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Provably inert: documentation, CI wiring, dependency manifests, licence text. Everything
|
||||
# else - sources, build files, and bare paths such as a nested submodule pointer - needs a
|
||||
@@ -68,10 +105,13 @@ jobs:
|
||||
echo "verdict=inert" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "verdict=review" >> "$GITHUB_OUTPUT"
|
||||
# A random delimiter: the file names come from another repository, so a path that
|
||||
# happened to be the literal delimiter could otherwise close the block early.
|
||||
delimiter="EOF_$(openssl rand -hex 16)"
|
||||
{
|
||||
echo 'reason<<EOF'
|
||||
echo "reason<<$delimiter"
|
||||
echo "$suspicious"
|
||||
echo EOF
|
||||
echo "$delimiter"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
@@ -90,18 +130,33 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Enable auto-merge
|
||||
if: steps.classify.outputs.verdict == 'inert' && steps.lock.outputs.locked == 'yes'
|
||||
if: steps.classify.outputs.verdict == 'inert'
|
||||
env:
|
||||
BASE: ${{ steps.range.outputs.base }}
|
||||
HEAD: ${{ steps.range.outputs.head }} && steps.lock.outputs.locked == 'yes'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh pr merge --auto --squash "$PR_URL"
|
||||
gh pr comment "$PR_URL" --body "引擎 \`${{ steps.range.outputs.base }}\` → \`${{ steps.range.outputs.head }}\` 的改动全部落在文档、CI 配置和依赖清单里,没有源码、构建文件或嵌套 submodule 指针。已开启自动合并,CI 通过后自行合入。"
|
||||
body="$RUNNER_TEMP/engine-bump-comment.md"
|
||||
printf '引擎 `%s` → `%s` 的改动全部落在文档、CI 配置和依赖清单里,没有源码、构建文件或嵌套 submodule 指针。已开启自动合并,CI 通过后自行合入。\n' "$BASE" "$HEAD" > "$body"
|
||||
gh pr comment "$PR_URL" --body-file "$body"
|
||||
|
||||
# Every value below reaches the shell through the environment, never through ${{ }}
|
||||
# interpolation. REASON is a list of file names from another repository: spliced into the
|
||||
# command text, a path containing a single quote would have closed the quoting and run as a
|
||||
# command, on a runner holding a contents:write token for this repository.
|
||||
- name: Hold for review
|
||||
if: steps.classify.outputs.verdict == 'review'
|
||||
env:
|
||||
BASE: ${{ steps.range.outputs.base }}
|
||||
HEAD: ${{ steps.range.outputs.head }}
|
||||
REASON: ${{ steps.classify.outputs.reason }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
gh pr edit "$PR_URL" --add-label "needs-review" || true
|
||||
gh pr comment "$PR_URL" --body "$(printf '引擎 `%s` → `%s` 的改动里有需要人看一眼的部分,未开启自动合并:\n\n```\n%s\n```\n\n源码、`CMakeLists.txt` 或嵌套 submodule 指针的变化都可能改变行为,而前端 CI 未必覆盖得到——`helpcode_enabled()` 与 `handle_character()` 的语义变更就是这样漏过去的。' '${{ steps.range.outputs.base }}' '${{ steps.range.outputs.head }}' '${{ steps.classify.outputs.reason }}')"
|
||||
body="$RUNNER_TEMP/engine-bump-comment.md"
|
||||
printf '引擎 `%s` → `%s` 的改动里有需要人看一眼的部分,未开启自动合并:\n\n```\n%s\n```\n\n源码、`CMakeLists.txt` 或嵌套 submodule 指针的变化都可能改变行为,而前端 CI 未必覆盖得到——`helpcode_enabled()` 与 `handle_character()` 的语义变更就是这样漏过去的。\n' "$BASE" "$HEAD" "$REASON" > "$body"
|
||||
gh pr comment "$PR_URL" --body-file "$body"
|
||||
|
||||
- name: Ask for the lock to follow
|
||||
if: steps.lock.outputs.locked == 'no'
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
name: Repository quality
|
||||
# Called by CI so release-please's workflow_dispatch receives these checks too.
|
||||
on:
|
||||
workflow_call:
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
workflows:
|
||||
name: Workflow validation
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
|
||||
with:
|
||||
go-version: '1.26'
|
||||
cache: false
|
||||
- name: Install actionlint
|
||||
run: go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12
|
||||
- name: Validate workflows and embedded shell scripts
|
||||
env:
|
||||
# Ignore style suggestions, but fail on shell correctness warnings and errors.
|
||||
SHELLCHECK_OPTS: --severity=warning
|
||||
run: actionlint -color
|
||||
# Part of "Workflow validation" rather than a job of its own: it is a two-second grep, and this
|
||||
# job is already a required check, so the guard starts protecting main immediately.
|
||||
- name: Reject developer-specific paths in build inputs
|
||||
run: bash scripts/ci/check-developer-paths.sh
|
||||
# Formatting is platform independent, so it runs here on Ubuntu rather than costing a Windows
|
||||
# runner. Cheap and early: a formatting slip fails in seconds instead of after the full compile.
|
||||
- name: Check formatting
|
||||
run: scripts/format.sh --check
|
||||
dependencies:
|
||||
name: Dependency review
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
# This job must not skip. It is one of main's required status checks, and a required check that reports "skipped" blocks a merge instead of passing it. Guarding it on the pull_request event left the release pull request unmergeable by its own automation: ci.yml ignores that pull request by path and the release workflow dispatches ci.yml on the release branch instead, so this job saw workflow_dispatch and skipped, and land-release-pr.sh got "the base branch policy prohibits the merge" after everything else came back green.
|
||||
#
|
||||
# A pull_request event supplies both refs on its own. Every other trigger has to be told what to compare, and comparing the default branch against the checked-out commit is the same question the pull request asks.
|
||||
- uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4
|
||||
with:
|
||||
fail-on-severity: high
|
||||
base-ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.repository.default_branch }}
|
||||
head-ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
|
||||
@@ -1,10 +1,12 @@
|
||||
name: Release
|
||||
|
||||
# Building and publishing only ever happens on workflow_dispatch. The code signing certificate
|
||||
# has a limited number of signatures, so a release costs something real and is not something a merge
|
||||
# to main should spend on its own. The push trigger only keeps release-please's pull request current
|
||||
# and green; merging that pull request creates the draft, and dispatching this workflow against the
|
||||
# draft's tag is what builds, signs and publishes it.
|
||||
# Releasing is automatic. A push to main that carries a fix or a feat ends with a published installer, without anybody merging or dispatching anything: this workflow refreshes release-please's pull request, tests it, merges it, turns the merge into a draft release, then builds, signs and publishes that draft.
|
||||
#
|
||||
# The whole cycle runs inside the one run that the original push started. land-release-pr.sh merges with GITHUB_TOKEN, whose pushes do not start workflow runs, so the merge cannot produce a second Release run queued behind this one on the concurrency group.
|
||||
#
|
||||
# This spends a code signing signature on every releasable merge to main. The certificate allows 3000 signatures a month, so the quota is not what constrains this; what it spends is release hygiene -- see docs/product-release.md.
|
||||
#
|
||||
# workflow_dispatch stays, and is now a repair path rather than the normal one. It builds an existing draft, which is what a run that failed after creating the draft leaves behind.
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -27,38 +29,83 @@ jobs:
|
||||
prepare:
|
||||
name: Prepare release metadata
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
# actions: write lets check-release-pr.sh dispatch ci.yml on the release branch, which is the
|
||||
# only trigger GITHUB_TOKEN is allowed to fire.
|
||||
actions: write
|
||||
# Read the PR-triggered run; merging stays scoped to GITHUB_TOKEN.
|
||||
actions: read
|
||||
contents: write
|
||||
pull-requests: write
|
||||
outputs:
|
||||
# Never true on push: see the comment above the triggers. release-please still creates the
|
||||
# draft release there, it just does not get built until somebody asks for it.
|
||||
should_publish: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
tag_name: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || steps.release.outputs.tag_name }}
|
||||
version: ${{ github.event_name == 'workflow_dispatch' && steps.requested_release.outputs.version || steps.release.outputs.version }}
|
||||
target_sha: ${{ github.event_name == 'workflow_dispatch' && steps.requested_release.outputs.target_sha || steps.release.outputs.sha }}
|
||||
should_publish: ${{ github.event_name == 'workflow_dispatch' || steps.target.outputs.release_created == 'true' }}
|
||||
tag_name: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || steps.target.outputs.tag_name }}
|
||||
version: ${{ github.event_name == 'workflow_dispatch' && steps.requested_release.outputs.version || steps.target.outputs.version }}
|
||||
target_sha: ${{ github.event_name == 'workflow_dispatch' && steps.requested_release.outputs.target_sha || steps.target.outputs.target_sha }}
|
||||
steps:
|
||||
- name: Check out release automation
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Prepare release
|
||||
# Separate release metadata from PR updates to preserve release-please ordering.
|
||||
# Recover an already-merged release before calculating the next PR, matching release-please's
|
||||
# normal releases-then-pull-requests ordering.
|
||||
- name: Require the release metadata credential
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
id: release
|
||||
env:
|
||||
RELEASE_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
||||
run: |
|
||||
if [[ -z "$RELEASE_TOKEN" ]]; then
|
||||
echo '::error::RELEASE_PLEASE_TOKEN must allow contents and workflows writes to create release tags.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Recover releases from previously merged pull requests
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
id: release_pr
|
||||
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
||||
skip-github-pull-request: true
|
||||
|
||||
- name: Test the release pull request
|
||||
- name: Refresh the release pull request
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
id: release_update
|
||||
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5
|
||||
with:
|
||||
# A trusted credential starts normal PR checks without manual workflow approval.
|
||||
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
||||
skip-github-release: true
|
||||
|
||||
- name: Test and merge the release pull request
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
run: bash scripts/ci/check-release-pr.sh
|
||||
run: bash scripts/ci/land-release-pr.sh
|
||||
|
||||
- name: Create the release for the merged pull request
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
id: release
|
||||
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5
|
||||
with:
|
||||
# Creating a tag at a historical workflow-changing commit needs workflows:write.
|
||||
# Only GITHUB_TOKEN merges to main, preventing a second Release run.
|
||||
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
||||
skip-github-pull-request: true
|
||||
|
||||
- name: Resolve the release this run publishes
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
id: target
|
||||
env:
|
||||
FIRST_CREATED: ${{ steps.release_pr.outputs.release_created }}
|
||||
FIRST_TAG: ${{ steps.release_pr.outputs.tag_name }}
|
||||
FIRST_VERSION: ${{ steps.release_pr.outputs.version }}
|
||||
FIRST_SHA: ${{ steps.release_pr.outputs.sha }}
|
||||
SECOND_CREATED: ${{ steps.release.outputs.release_created }}
|
||||
SECOND_TAG: ${{ steps.release.outputs.tag_name }}
|
||||
SECOND_VERSION: ${{ steps.release.outputs.version }}
|
||||
SECOND_SHA: ${{ steps.release.outputs.sha }}
|
||||
run: bash scripts/ci/resolve-auto-release.sh
|
||||
|
||||
- name: Validate requested draft release
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
@@ -74,6 +121,7 @@ jobs:
|
||||
needs: prepare
|
||||
if: ${{ needs.prepare.outputs.should_publish == 'true' }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
outputs:
|
||||
dictionary_tag: ${{ steps.lock.outputs.dictionary_tag }}
|
||||
steps:
|
||||
@@ -264,6 +312,11 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: write
|
||||
# Build provenance. Unlike Authenticode this costs nothing and needs no certificate, and the
|
||||
# Sigstore transparency log it writes to is outside this organisation's control, so it is the
|
||||
# one integrity signal a repository admin cannot forge by swapping a release asset.
|
||||
id-token: write
|
||||
attestations: write
|
||||
env:
|
||||
DICTIONARY_TAG: ${{ needs.dependencies.outputs.dictionary_tag }}
|
||||
# download-dictionaries.sh stages MetasequoiaImeDict/out under this root, and
|
||||
@@ -391,6 +444,11 @@ jobs:
|
||||
installer/app_data/product-manifest.json
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Attest the installer's build provenance
|
||||
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
|
||||
with:
|
||||
subject-path: ${{ steps.asset.outputs.asset_path }}
|
||||
|
||||
- name: Revalidate the draft release target
|
||||
shell: bash
|
||||
env:
|
||||
@@ -411,4 +469,25 @@ jobs:
|
||||
ASSET_SHA256: ${{ steps.asset.outputs.asset_sha256 }}
|
||||
SIGNING_ENABLED: ${{ steps.signing.outputs.signing_enabled }}
|
||||
PRODUCT_MANIFEST: installer/app_data/product-manifest.json
|
||||
RELEASE_TRIGGER: ${{ github.event_name }}
|
||||
run: bash scripts/ci/publish-release.sh
|
||||
|
||||
# msime.app/update.json is what the settings page's "检查更新" reads, and MSIME-Web only
|
||||
# regenerated it from a */30 cron. Scheduled events on public repositories are delayed heavily
|
||||
# -- measured runs sat roughly two hours apart -- so the site advertised an old version for
|
||||
# hours after every release. MSIME-Web already declares a repository_dispatch trigger for
|
||||
# exactly this and nothing was ever sending it.
|
||||
#
|
||||
# This runs after publishing on purpose. If the dispatch fails the release is already out and
|
||||
# correct; what the failure means is that the website is now stale and someone has to act, so
|
||||
# it is reported as a failure rather than swallowed.
|
||||
- name: Refresh the download manifest on msime.app
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN }}
|
||||
run: |
|
||||
if [[ -z "$GH_TOKEN" ]]; then
|
||||
echo '::error::RELEASE_PLEASE_TOKEN is required to notify MSIME-Web; msime.app/update.json will stay on the previous version until its cron catches up.'
|
||||
exit 1
|
||||
fi
|
||||
gh api --method POST repos/metasequoiaime/MSIME-Web/dispatches -f event_type=update-manifest
|
||||
Reference in new issue
Block a user