feat:移除了弹窗,服务器添加sls

This commit is contained in:
2026-09-08 22:39:45 +08:00
commit 6a295f9a7a
4082 changed files with 1322534 additions and 0 deletions

113
.github/ISSUE_TEMPLATE/bug-report.yml vendored Normal file
View File

@ -0,0 +1,113 @@
name: 🐛 错误报告
description: 报告 Axolotl Launcher 中可以复现的问题
title: '[错误] '
labels: [bug]
body:
- type: markdown
attributes:
value: |
感谢你帮助改进 Axolotl Launcher。请尽量完整地填写以下信息以便我们定位问题。
上传日志或截图前,请移除账号、访问令牌、设备路径等隐私信息。
- type: checkboxes
id: confirmations
attributes:
label: 提交前确认
options:
- label: 我已搜索[现有 Issues](https://github.com/Mystic-Stars/Axolotl/issues),没有发现相同问题
required: true
- label: 我正在使用最新版本的 Axolotl Launcher或已确认该问题在最新版本中仍然存在
required: true
- label: 我已从日志、截图和描述中移除账号、访问令牌等敏感信息
required: true
- type: input
id: version
attributes:
label: Axolotl Launcher 版本
description: 可在启动器设置或“关于”页面中查看,例如 1.2.3。
placeholder: 例如 1.2.3
validations:
required: true
- type: dropdown
id: operating_system
attributes:
label: 操作系统
options:
- Windows 10/11x64
- macOSApple Silicon
- macOSIntel
- Linuxx64
- 其他
validations:
required: true
- type: input
id: system_details
attributes:
label: 系统详细信息
description: 如果选择 Linux 或“其他”,请填写发行版、桌面环境或架构等信息。
placeholder: 例如 Ubuntu 24.04GNOMEx64
validations:
required: false
- type: dropdown
id: installation_source
attributes:
label: 安装来源
options:
- GitHub Releases 安装包
- 启动器内自动更新
- 软件包管理器或第三方分发渠道
- 本地开发或源码构建
- 其他
validations:
required: true
- type: textarea
id: problem
attributes:
label: 问题描述
description: 请清楚说明发生了什么,以及问题出现的频率。
placeholder: 描述实际发生的现象……
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: 复现步骤
description: 请从启动 Axolotl 开始,按顺序列出能够触发问题的操作。
placeholder: |
1. 打开……
2. 点击……
3. 选择……
4. 出现……
validations:
required: true
- type: textarea
id: expected_behavior
attributes:
label: 预期行为
description: 说明你原本期望看到的结果。
validations:
required: true
- type: textarea
id: logs
attributes:
label: 相关日志
description: 请粘贴与问题相关的日志片段,并确保其中不含敏感信息。没有日志时可以留空。
render: shell
validations:
required: false
- type: textarea
id: additional_context
attributes:
label: 截图与补充信息
description: 可直接拖入截图,并补充可能有助于定位问题的信息。
validations:
required: false

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1 @@
blank_issues_enabled: true

View File

@ -0,0 +1,52 @@
name: 💡 功能建议
description: 为 Axolotl Launcher 提议新功能或体验改进
title: '[建议] '
labels: [enhancement]
body:
- type: markdown
attributes:
value: |
感谢你为 Axolotl Launcher 提出建议。请重点描述使用场景和希望解决的问题。
- type: checkboxes
id: confirmations
attributes:
label: 提交前确认
options:
- label: 我已搜索[现有 Issues](https://github.com/Mystic-Stars/Axolotl/issues),没有发现相同或高度相似的建议
required: true
- label: 这项建议与 Axolotl Launcher 桌面应用直接相关
required: true
- type: textarea
id: problem
attributes:
label: 使用场景与现有问题
description: 请说明你在什么情况下遇到什么困难,以及该问题会带来哪些影响。
placeholder: 当我……时,目前需要……,这会导致……
validations:
required: true
- type: textarea
id: proposed_solution
attributes:
label: 期望的解决方案
description: 请描述你希望 Axolotl 如何改进,以及理想的使用流程。
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: 考虑过的替代方案
description: 如果你尝试过临时解决方法或考虑过其他设计,请在这里说明。
validations:
required: false
- type: textarea
id: additional_context
attributes:
label: 补充信息
description: 可添加示意图、截图、相关链接或其他背景信息。
validations:
required: false

View File

@ -0,0 +1,18 @@
name: Replace Gradle mirror with official distribution URL
description: Replaces the Tencent Cloud Gradle mirror URL with the official Gradle distribution URL in gradle-wrapper.properties
runs:
using: composite
steps:
- name: Replace (Linux / macOS)
if: runner.os != 'Windows'
shell: bash
run: |
sed -i.bak 's|https\\://mirrors\.cloud\.tencent\.com/gradle/|https\\://services.gradle.org/distributions/|' packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties
rm -f packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties.bak
- name: Replace (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
(Get-Content packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties) -replace 'https\\://mirrors\.cloud\.tencent\.com/gradle/', 'https\://services.gradle.org/distributions/' | Set-Content packages/app-lib/java/gradle/wrapper/gradle-wrapper.properties

BIN
.github/assets/api_cover.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
.github/assets/app_cover.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
.github/assets/codeflow-logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
.github/assets/monorepo_cover.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

BIN
.github/assets/web_cover.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -0,0 +1,86 @@
---
applyTo: '**/*.vue'
---
You are given a Nuxt/Vue single-file component (.vue). Your task is to convert every hard-coded natural-language string in the <template> into our localization system using vue-i18n with utilities from `@modrinth/ui`.
Please follow these rules precisely:
1. Identify translatable strings
- Scan the <template> for all user-visible strings (inner text, alt attributes, placeholders, button labels, etc.). Do not extract dynamic expressions (like {{ user.name }}) or HTML tags. Only extract static human-readable text.
- There may be strings within the <script> block, e.g dropdown option labels, notifications etc.
2. Create message definitions
- In the <script setup> block, import `defineMessage` or `defineMessages` from `@modrinth/ui`.
- For each extracted string, define a message with a unique `id` (use a descriptive prefix based on the component path, e.g. `auth.welcome.long-title`) and a `defaultMessage` equal to the original English string.
Example:
const messages = defineMessages({
welcomeTitle: { id: 'auth.welcome.title', defaultMessage: 'Welcome' },
welcomeDescription: { id: 'auth.welcome.description', defaultMessage: 'You're now part of the community…' },
})
3. Handle variables and ICU formats
- Replace dynamic parts with ICU placeholders: "Hello, ${user.name}!" → `{name}` and defaultMessage: 'Hello, {name}!'
- For numbers/dates/times, use ICU options (e.g., currency): `{price, number, ::currency/USD}`
- For plurals/selects, use ICU: `'{count, plural, one {# message} other {# messages}}'`
4. Rich-text messages (links/markup)
- In `defaultMessage`, wrap link/markup ranges with tags, e.g.:
"By creating an account, you agree to our <terms-link>Terms</terms-link> and <privacy-link>Privacy Policy</privacy-link>."
- Render rich-text messages with `<IntlFormatted>` from `@modrinth/ui` using named slots:
<IntlFormatted :message-id="messages.tosLabel">
<template #terms-link="{ children }">
<NuxtLink to="/terms">
<component :is="() => children" />
</NuxtLink>
</template>
<template #privacy-link="{ children }">
<NuxtLink to="/privacy">
<component :is="() => children" />
</NuxtLink>
</template>
</IntlFormatted>
- For simple emphasis: `'Welcome to <strong>Modrinth</strong>!'` with a slot:
<template #strong="{ children }">
<strong><component :is="() => children" /></strong>
</template>
- For more complex child handling, use `normalizeChildren` from `@modrinth/ui`:
<template #bold="{ children }">
<strong><component :is="() => normalizeChildren(children)" /></strong>
</template>
5. Formatting in templates
- Import and use `useVIntl()` from `@modrinth/ui`; prefer `formatMessage` for simple strings:
`const { formatMessage } = useVIntl()`
`<button>{{ formatMessage(messages.welcomeTitle) }}</button>`
- Pass variables as a second argument:
`{{ formatMessage(messages.greeting, { name: user.name }) }}`
6. Naming conventions and id stability
- Make `id`s descriptive and stable (e.g., `error.generic.default.title`). Group related messages with `defineMessages`.
7. Avoid Vue/ICU delimiter collisions
- If an ICU placeholder would end right before `}}` in a Vue template, insert a space so it becomes `} }` to avoid parsing issues.
8. Update imports and remove literals
- Ensure imports from `@modrinth/ui` are present: `defineMessage`/`defineMessages`, `useVIntl`, `IntlFormatted`, and optionally `normalizeChildren`.
- Replace all hard-coded strings with `formatMessage(...)` or `<IntlFormatted>` and remove the literals.
9. Preserve functionality
- Do not change logic, layout, reactivity, or bindings—only refactor strings into i18n.
Use existing patterns from our codebase:
- Variables/plurals: see `apps/app-frontend/src/components/home/HomeDailyChallenge.vue`
- Rich-text link tags: see `apps/website/src/pages/index.vue`
When you finish, there should be no hard-coded English strings left in the template—everything comes from `formatMessage` or `<IntlFormatted>`.

View File

@ -0,0 +1,171 @@
# MIT License
#
# Copyright (c) 2024 CARIAD SE
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
name: 'Merge Queue CI Check Skipper'
description: 'Outputs `skip-check` as `true` if this is running as part of merge queue checks and the same checks have already been executed in the PR itself.'
inputs:
secret:
description: 'Optional GitHub Secret that can access branch protection rules using the administration:read permission'
required: false
outputs:
skip-check:
description: 'Skip Check (boolean)'
value: ${{ steps.passed-checks.outputs.can-skip-checks }}
runs:
using: 'composite'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Extract PR Number and Commit ID
id: extract-pr-info
uses: actions/github-script@v7
with:
script: |
const githubRef = process.env.GITHUB_REF;
const regex = /^refs\/heads\/gh-readonly-queue\/([a-zA-Z0-9.\-_\/]+)\/pr-(\d+)-([a-f0-9]+)$/;
if (regex.test(githubRef)) {
const [, targetBranchName, prNumber, commitId] = githubRef.match(regex);
core.setOutput('targetBranchName', targetBranchName);
core.setOutput('prNumber', prNumber);
core.setOutput('commitId', commitId);
} else {
console.log(`GITHUB_REF is not a merge queue ref, setting CAN_SKIP_CHECKS to false: ${githubRef}`);
core.exportVariable('CAN_SKIP_CHECKS', 'false');
}
- name: Print PR Number and Target Commit ID
if: env.CAN_SKIP_CHECKS != 'false'
shell: bash
run: |
echo "Target Branch Name: ${{ steps.extract-pr-info.outputs.targetBranchName }}"
echo "PR Number: ${{ steps.extract-pr-info.outputs.prNumber }}"
echo "Target Commit ID: ${{ steps.extract-pr-info.outputs.commitId }}"
- name: Check if merge queue entry was enqueued as head of the queue
if: env.CAN_SKIP_CHECKS != 'false'
shell: bash
run: |
targetBranchHead=$(git rev-parse origin/${{ steps.extract-pr-info.outputs.targetBranchName }})
if [[ "$targetBranchHead" != "${{ steps.extract-pr-info.outputs.commitId }}" ]]; then
echo "'${{ steps.extract-pr-info.outputs.targetBranchName }}' branch commit ID does not match PR commit ID. This Merge Queue run was not head of the queue when it was enqueued. Setting CAN_SKIP_CHECKS to false."
echo "CAN_SKIP_CHECKS=false" >> "$GITHUB_ENV"
else
echo "This merge queue entry is targeting '${{ steps.extract-pr-info.outputs.targetBranchName }}' directly."
fi
- name: Get PR Branch
id: get-pr-branch
if: env.CAN_SKIP_CHECKS != 'false'
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
prNumber=${{ steps.extract-pr-info.outputs.prNumber }}
branchName=$(gh pr view ${prNumber} --json headRefName -q '.headRefName')
echo "prBranch=$branchName" >> "$GITHUB_OUTPUT"
- name: Print PR Branch
if: env.CAN_SKIP_CHECKS != 'false'
shell: bash
run: |
echo "PR Branch: ${{ steps.get-pr-branch.outputs.prBranch }}"
- name: Check if PR branch contains the Merge Queue target commit ID
if: env.CAN_SKIP_CHECKS != 'false'
shell: bash
run: |
# Get the branch name from previous steps
branch_name="origin/${{ steps.get-pr-branch.outputs.prBranch }}"
commit_id="${{ steps.extract-pr-info.outputs.commitId }}"
# Check if the branch history contains the commit
if git branch -r --contains "$commit_id" | grep -q "$branch_name"; then
echo "Branch '$branch_name' contains commit '$commit_id'. It is up to date with ${{ steps.extract-pr-info.outputs.targetBranchName }}."
else
echo "Branch '$branch_name' does not contain commit '$commit_id'. It is outdated. Setting CAN_SKIP_CHECKS to false."
echo "CAN_SKIP_CHECKS=false" >> "$GITHUB_ENV"
fi
- name: Compare PR Branch with Current Branch
if: env.CAN_SKIP_CHECKS != 'false'
shell: bash
run: |
if git diff --quiet "origin/${{ steps.get-pr-branch.outputs.prBranch }}"; then
echo "No differences found. PR branch is identical with this merge queue branch."
else
echo "Differences detected. PR branch has been updated after PR was added to merge queue. Setting CAN_SKIP_CHECKS to false."
echo "CAN_SKIP_CHECKS=false" >> "$GITHUB_ENV"
fi
- name: Compute/publish skip result
id: passed-checks
uses: actions/github-script@v7
env:
SECRET: ${{ inputs.secret }}
with:
github-token: ${{ inputs.secret != '' && inputs.secret || github.token }}
script: |
if (process.env.CAN_SKIP_CHECKS == "false") {
console.log("Setting CAN_SKIP_CHECKS to false");
core.setOutput("can-skip-checks", false);
return;
}
const secretProvided = !!process.env.SECRET;
if (!secretProvided) {
console.log("secret input not set, assuming all checks have passed. Ensure 'Require status checks to pass before merging' is enabled, or provide a secret with administration:read.");
core.setOutput("can-skip-checks", true);
return;
}
const { data: branchProtection } = await github.rest.repos.getBranchProtection({
owner: context.repo.owner,
repo: context.repo.repo,
branch: "${{ steps.extract-pr-info.outputs.targetBranchName }}",
});
const requiredCheckNames = branchProtection.required_status_checks.contexts;
console.log(`requiredCheckNames = ${requiredCheckNames}`);
const { data: checks } = await github.rest.checks.listForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: "refs/heads/${{ steps.get-pr-branch.outputs.prBranch }}",
});
console.log(`checks.check_runs = ${checks.check_runs.map(check => `${check.status},${check.conclusion},${check.name};`)}`);
const nonSuccessfulChecks = checks.check_runs.filter(check => check.status !== "completed" || check.conclusion !== "success");
const nonSuccessfulCheckNames = nonSuccessfulChecks.map(check => check.name);
console.log(`nonSuccessfulCheckNames = ${nonSuccessfulCheckNames}`);
const missingChecks = requiredCheckNames.filter(checkName => nonSuccessfulCheckNames.includes(checkName));
if (missingChecks.length > 0) {
console.log(`Required checks not passed, cannot skip merge queue checks. Setting CAN_SKIP_CHECKS to false. Missing checks: ${missingChecks.join(', ')}`);
core.setOutput("can-skip-checks", false);
} else {
console.log("No missing checks. Setting CAN_SKIP_CHECKS to true.");
core.setOutput("can-skip-checks", true);
}

4
.github/templates/crowdin-pr.md vendored Normal file
View File

@ -0,0 +1,4 @@
This pull request is created according to the `.github/workflows/i18n-pull.yml` file.
- 🌐 [Contribute to translations on Crowdin](https://translate.modrinth.com/)
- 🔄 [Dispatch this workflow again to update this PR](https://github.com/Modrinth/code/actions/workflows/i18n-pull.yml)

3
.github/workflows/COPYING.md vendored Normal file
View File

@ -0,0 +1,3 @@
# Copying
Modrinth's Github workflows are licensed under the MIT License, which is provided in the file [LICENSE](./LICENSE).

7
.github/workflows/LICENSE vendored Normal file
View File

@ -0,0 +1,7 @@
Copyright 2025 Rinth, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

130
.github/workflows/axolotl-ci.yml vendored Normal file
View File

@ -0,0 +1,130 @@
name: Axolotl desktop CI
on:
push:
branches: [main]
paths-ignore:
- 'apps/telemetry-dashboard/**'
- 'apps/telemetry-worker/**'
pull_request:
paths-ignore:
- 'apps/telemetry-dashboard/**'
- 'apps/telemetry-worker/**'
workflow_dispatch:
permissions:
contents: read
jobs:
guardrails:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- uses: pnpm/action-setup@008330803749db0355799c700092d9a85fd074e9 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .nvmrc
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Check immutable database migrations
env:
MIGRATION_BASE_REF: ${{ github.event.pull_request.base.sha || github.event.before }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node scripts/axolotl/check-migrations.mjs --base "${MIGRATION_BASE_REF:-HEAD^}"
node scripts/axolotl/check-migrations.mjs --release
- run: node scripts/axolotl/brand-guard.mjs
- run: node scripts/axolotl/i18n-check.mjs
- run: cargo fmt --all --check
desktop:
needs: guardrails
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
runs-on: ${{ matrix.os }}
env:
SQLX_OFFLINE: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- uses: pnpm/action-setup@008330803749db0355799c700092d9a85fd074e9 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .nvmrc
cache: pnpm
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- name: Set up Rust compiler cache
if: runner.os == 'Windows'
uses: mozilla-actions/sccache-action@2df7dbab909c49ab7d3382d05da469f3f975c2d6 # v0.0.7
- name: Check Rust compiler cache availability
id: sccache
if: runner.os == 'Windows'
continue-on-error: true
shell: bash
env:
SCCACHE_GHA_ENABLED: 'true'
run: ${SCCACHE_PATH} rustc -vV
- name: Enable Rust compiler cache
if: runner.os == 'Windows' && steps.sccache.outcome == 'success'
shell: bash
run: |
echo 'RUSTC_WRAPPER=sccache' >> "$GITHUB_ENV"
echo 'SCCACHE_GHA_ENABLED=true' >> "$GITHUB_ENV"
echo 'SCCACHE_CACHE_SIZE=4G' >> "$GITHUB_ENV"
- if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- run: pnpm install --frozen-lockfile
- name: Install Blockbench skin editor dependencies
run: npm ci
working-directory: third-party/blockbench
- uses: ./.github/actions/replace-gradle-mirror
- run: pnpm --filter @modrinth/app-frontend build
- name: Test version metadata writes
run: cargo test --package theseus writing_version_info_creates_missing_parent_directory
- run: cargo check --package theseus_gui --features updater
- name: Build modern Windows installer
if: runner.os == 'Windows'
run: pnpm --filter @modrinth/app tauri build --config tauri-release.conf.json --config tauri-modern.conf.json
- name: Verify modern Windows installer
if: runner.os == 'Windows'
shell: pwsh
run: |
$installers = @(Get-ChildItem target/release/bundle/nsis -Filter '*-setup.exe' -File)
if ($installers.Count -ne 1) {
throw "Expected one modern NSIS installer, found $($installers.Count)"
}
Write-Output "Built modern installer: $($installers[0].Name)"
- name: Build native NSIS Windows installer
if: runner.os == 'Windows'
run: pnpm --filter @modrinth/app tauri build
- name: Verify native NSIS Windows installer
if: runner.os == 'Windows'
shell: pwsh
run: |
$installers = @(Get-ChildItem target/release/bundle/nsis -Filter '*-setup.exe' -File)
if ($installers.Count -ne 1) {
throw "Expected one native NSIS installer, found $($installers.Count)"
}
Write-Output "Built native NSIS installer: $($installers[0].Name)"
- name: Show Rust compiler cache statistics
if: runner.os == 'Windows' && steps.sccache.outcome == 'success' && always()
shell: bash
run: ${SCCACHE_PATH} --show-stats
website:
needs: guardrails
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: pnpm/action-setup@008330803749db0355799c700092d9a85fd074e9 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .nvmrc
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @axolotl/website build

400
.github/workflows/axolotl-release.yml vendored Normal file
View File

@ -0,0 +1,400 @@
name: Axolotl release
on:
push:
tags:
- 'v*'
permissions:
contents: write
concurrency:
group: axolotl-release-${{ github.ref }}
cancel-in-progress: false
jobs:
preflight:
name: Release preflight
runs-on: ubuntu-24.04
env:
SQLX_OFFLINE: true
steps:
- name: Check out source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
submodules: recursive
- name: Set up pnpm
uses: pnpm/action-setup@008330803749db0355799c700092d9a85fd074e9 # v6.0.9
- name: Set up Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version-file: .nvmrc
cache: pnpm
- name: Set up Rust
uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Blockbench skin editor dependencies
run: npm ci
working-directory: third-party/blockbench
- name: Run release guardrails
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node scripts/axolotl/check-migrations.mjs --release
node scripts/axolotl/brand-guard.mjs
node scripts/axolotl/i18n-check.mjs
cargo fmt --all --check
- name: Prepare draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
release_notes="${RUNNER_TEMP}/axolotl-release-notes.md"
node scripts/axolotl/create-release-notes.mjs "${GITHUB_REF_NAME}" "$release_notes"
existing_release="$(
gh release list --limit 100 --json tagName \
--jq ".[] | select(.tagName == \"${GITHUB_REF_NAME}\") | .tagName"
)"
if [[ -n "$existing_release" ]]; then
echo "Release ${GITHUB_REF_NAME} already exists"
gh release edit "${GITHUB_REF_NAME}" --notes-file "$release_notes"
exit 0
fi
release_args=(
--draft
--notes-file "$release_notes"
--title "Axolotl Launcher ${GITHUB_REF_NAME}"
)
if [[ "${GITHUB_REF_NAME}" == *-* ]]; then
release_args+=(--prerelease)
fi
gh release create "${GITHUB_REF_NAME}" "${release_args[@]}"
build:
name: Build (${{ matrix.name }})
needs: preflight
strategy:
fail-fast: true
max-parallel: 4
matrix:
include:
- name: Linux x64
runner: ubuntu-24.04
args: --config tauri-release.conf.json --config tauri-updater.conf.json
- name: Linux arm64
runner: ubuntu-24.04-arm
args: --config tauri-release.conf.json --config tauri-updater.conf.json
- name: Windows x64
runner: windows-2025
args: --config tauri-release.conf.json --config tauri-modern.conf.json --config tauri-updater.conf.json
- name: macOS universal
runner: macos-15
args: --target universal-apple-darwin --config tauri-release.conf.json --config tauri-updater.conf.json
runs-on: ${{ matrix.runner }}
env:
SQLX_OFFLINE: true
steps:
- name: Check out source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
submodules: recursive
- name: Set up pnpm
uses: pnpm/action-setup@008330803749db0355799c700092d9a85fd074e9 # v6.0.9
- name: Set up Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version-file: .nvmrc
cache: pnpm
- name: Set up Rust
uses: dtolnay/rust-toolchain@4be7066ada62dd38de10e7b70166bc74ed198c30 # stable
- name: Set up Rust compiler cache
if: runner.os == 'Windows'
uses: mozilla-actions/sccache-action@2df7dbab909c49ab7d3382d05da469f3f975c2d6 # v0.0.7
- name: Check Rust compiler cache availability
id: sccache
if: runner.os == 'Windows'
continue-on-error: true
shell: bash
env:
SCCACHE_GHA_ENABLED: 'true'
run: ${SCCACHE_PATH} rustc -vV
- name: Enable Rust compiler cache
if: runner.os == 'Windows' && steps.sccache.outcome == 'success'
shell: bash
run: |
echo 'RUSTC_WRAPPER=sccache' >> "$GITHUB_ENV"
echo 'SCCACHE_GHA_ENABLED=true' >> "$GITHUB_ENV"
echo 'SCCACHE_CACHE_SIZE=4G' >> "$GITHUB_ENV"
- name: Add macOS universal targets
if: runner.os == 'macOS'
run: rustup target add aarch64-apple-darwin x86_64-apple-darwin
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: sudo apt-get -o Acquire::ForceIPv4=true update && sudo apt-get -o Acquire::ForceIPv4=true install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf xdg-utils
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install Blockbench skin editor dependencies
run: npm ci
working-directory: third-party/blockbench
- uses: ./.github/actions/replace-gradle-mirror
- name: Set release version
run: node scripts/axolotl/set-version.mjs "${{ github.ref_name }}"
- name: Build and upload release assets
uses: tauri-apps/tauri-action@1ddc7b49b13c3038297360482fcb3e058764d7c9 # v0.6.2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
TAURI_BUNDLER_DMG_IGNORE_CI: ${{ runner.os == 'macOS' && 'true' || '' }}
with:
projectPath: apps/app
tauriScript: pnpm tauri
args: ${{ matrix.args }}
tagName: ${{ github.ref_name }}
releaseName: Axolotl Launcher ${{ github.ref_name }}
releaseDraft: true
prerelease: ${{ contains(github.ref_name, '-') }}
generateReleaseNotes: false
includeUpdaterJson: false
updaterJsonPreferNsis: true
retryAttempts: 2
- name: Show Rust compiler cache statistics
if: runner.os == 'Windows' && steps.sccache.outcome == 'success' && always()
shell: bash
run: ${SCCACHE_PATH} --show-stats
- name: Publish Windows distribution variants
if: runner.os == 'Windows'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
shell: pwsh
run: |
$version = "${{ github.ref_name }}" -replace '^v', ''
$appName = "Axolotl Launcher"
$outDir = "target/release/bundle/nsis"
$buildDir = "target/portable-build"
$publishDir = Join-Path $env:RUNNER_TEMP "windows-assets"
New-Item -ItemType Directory -Force -Path $publishDir | Out-Null
$modernInstallers = @(Get-ChildItem $outDir -Filter '*-setup.exe' -File)
if ($modernInstallers.Count -ne 1) {
throw "Expected one modern installer, found $($modernInstallers.Count)"
}
$modernName = "Axolotl_Launcher_${version}_x64_modern-setup.exe"
$modernPath = Join-Path $publishDir $modernName
Copy-Item -Force $modernInstallers[0].FullName $modernPath
gh release upload "${{ github.ref_name }}" $modernPath --clobber
$modernSignature = "$($modernInstallers[0].FullName).sig"
if (Test-Path $modernSignature) {
$modernSignaturePath = "$modernPath.sig"
Copy-Item -Force $modernSignature $modernSignaturePath
gh release upload "${{ github.ref_name }}" $modernSignaturePath --clobber
gh release delete-asset "${{ github.ref_name }}" "$($modernInstallers[0].Name).sig" --yes
}
gh release delete-asset "${{ github.ref_name }}" $modernInstallers[0].Name --yes
pnpm --filter @modrinth/app tauri build --config tauri-release.conf.json --config tauri-native.conf.json
$nativeInstallers = @(Get-ChildItem $outDir -Filter '*-setup.exe' -File)
if ($nativeInstallers.Count -ne 1) {
throw "Expected one native NSIS installer, found $($nativeInstallers.Count)"
}
$nativeName = "Axolotl_Launcher_${version}_x64_nsis-setup.exe"
$nativePath = Join-Path $publishDir $nativeName
Copy-Item -Force $nativeInstallers[0].FullName $nativePath
gh release upload "${{ github.ref_name }}" $nativePath --clobber
$nativeSignature = "$($nativeInstallers[0].FullName).sig"
if (Test-Path $nativeSignature) {
$nativeSignaturePath = "$nativePath.sig"
Copy-Item -Force $nativeSignature $nativeSignaturePath
gh release upload "${{ github.ref_name }}" $nativeSignaturePath --clobber
}
# Reuse the binary already built by tauri-action
$appExe = "target/release/${appName}.exe"
if (Test-Path $appExe) {
New-Item -ItemType Directory -Force -Path "$buildDir\Axolotl" | Out-Null
Copy-Item -Force $appExe "$buildDir\Axolotl\"
# Create empty .Axolotl folder to trigger portable mode
New-Item -ItemType Directory -Force -Path "$buildDir\Axolotl\.Axolotl" | Out-Null
# Create portable zip (for manual download)
$zipName = "Axolotl_Launcher_${version}_x64_portable.zip"
$zipPath = Join-Path $publishDir $zipName
Compress-Archive -Path "$buildDir\Axolotl" -DestinationPath $zipPath -Force
Write-Output "Created portable zip: $zipPath"
# Upload portable zip (for manual download only)
gh release upload "${{ github.ref_name }}" "$zipPath" --clobber
} else {
throw "Binary not found at $appExe"
}
verify-and-publish:
name: Verify and publish
needs: build
runs-on: ubuntu-24.04
steps:
- name: Check out source
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
ref: main
- name: Download updater signatures
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release download "${GITHUB_REF_NAME}" --pattern '*.sig' --dir release-check/signatures
- name: Download release metadata
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
release_id="$(
gh api "repos/${GITHUB_REPOSITORY}/releases" \
--paginate \
--jq ".[] | select(.tag_name == \"${GITHUB_REF_NAME}\") | .id"
)"
if [[ -z "$release_id" ]]; then
echo "Release ${GITHUB_REF_NAME} was not found"
exit 1
fi
gh api "repos/${GITHUB_REPOSITORY}/releases/${release_id}" > release-check/release.json
- name: Generate update manifest
run: node scripts/axolotl/create-update-manifest.mjs release-check/release.json release-check/signatures "${GITHUB_REF_NAME}" release-check/latest.json
- name: Verify update manifest
run: node scripts/axolotl/verify-update-manifest.mjs release-check/latest.json "${GITHUB_REF_NAME}"
- name: Upload update manifest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release upload "${GITHUB_REF_NAME}" release-check/latest.json --clobber
- name: Publish release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [[ "${GITHUB_REF_NAME}" == *-* ]]; then
gh release edit "${GITHUB_REF_NAME}" --draft=false --prerelease
else
gh release edit "${GITHUB_REF_NAME}" --draft=false --latest
fi
- name: Refresh published GitHub release metadata
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
release_id="$(
gh api "repos/${GITHUB_REPOSITORY}/releases" \
--paginate \
--jq ".[] | select(.tag_name == \"${GITHUB_REF_NAME}\") | .id"
)"
if [[ -z "$release_id" ]]; then
echo "Published release ${GITHUB_REF_NAME} was not found"
exit 1
fi
gh api "repos/${GITHUB_REPOSITORY}/releases/${release_id}" > release-check/release.json
- name: Build Update Server artifact catalog
run: |
node scripts/axolotl/create-update-server-catalog.mjs \
release-check/release.json \
"${GITHUB_REF_NAME}" \
release-check/update-server-catalog.json
node scripts/axolotl/create-update-server-catalog.test.mjs
- name: Notify Update Server to publish release assets
env:
UPDATE_SERVER_URL: ${{ secrets.UPDATE_SERVER_URL }}
UPDATE_SERVER_WEBHOOK_SECRET: ${{ secrets.UPDATE_SERVER_WEBHOOK_SECRET }}
UPDATE_SERVER_FORCE_UPDATE: 'false'
run: |
node scripts/axolotl/publish-update-server.mjs \
release-check/update-server-catalog.json \
release-check/release.json \
"${GITHUB_REF_NAME}"
- name: Publish website download metadata
if: ${{ !contains(github.ref_name, '-') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node scripts/axolotl/create-website-release-metadata.mjs \
release-check/release.json \
apps/website/releases/latest.json
node scripts/axolotl/create-website-release-catalog.mjs \
apps/website/releases/catalog.json
git config user.name "Axolotl Release Bot"
git config user.email "release@axolotl.local"
git add apps/website/releases/latest.json apps/website/releases/catalog.json
if git diff --cached --quiet; then
echo "Website metadata is unchanged"
else
git commit -m "chore(website): update download metadata to ${GITHUB_REF_NAME} [skip ci]"
git push origin main
fi
- name: Mirror published release to CNB
env:
CNB_REPO_URL_HTTPS: ${{ secrets.CNB_REPO_URL_HTTPS }}
CNB_TOKEN: ${{ secrets.CNB_TOKEN }}
CNB_TOKEN_USER_NAME: ${{ secrets.CNB_TOKEN_USER_NAME }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
if [[ -z "${CNB_TOKEN:-}" ]]; then
echo "CNB_TOKEN is not configured"
exit 1
fi
cnb_url="${CNB_REPO_URL_HTTPS:-https://cnb.cool/axlmc/Axolotl.git}"
token_user="${CNB_TOKEN_USER_NAME:-cnb}"
auth="$(printf '%s:%s' "$token_user" "$CNB_TOKEN" | base64 -w0)"
git_cnb() {
git -c "http.extraHeader=Authorization: Basic $auth" "$@"
}
git fetch origin "refs/tags/${GITHUB_REF_NAME}:refs/tags/${GITHUB_REF_NAME}"
git_cnb push --force "$cnb_url" \
"refs/tags/${GITHUB_REF_NAME}:refs/tags/${GITHUB_REF_NAME}"
node scripts/axolotl/cnb-release.mjs finalize "${GITHUB_REF_NAME}" cnb-release/final
node scripts/axolotl/verify-update-manifest.mjs \
cnb-release/final/latest.json "${GITHUB_REF_NAME}" cnb

View File

@ -0,0 +1,22 @@
name: Cancel PR Workflows on Merge
on:
pull_request_target:
types:
- closed
permissions:
actions: write
jobs:
cancel:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # 0.12.1
with:
workflow_id: all
access_token: ${{ secrets.GITHUB_TOKEN }}
ignore_sha: true
pr_number: ${{ github.event.pull_request.number }}

123
.github/workflows/changelog-comment.yml vendored Normal file
View File

@ -0,0 +1,123 @@
name: Changelog Comment
on:
pull_request_target:
types: [opened, reopened]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to post the changelog comment on (for testing)'
required: true
type: number
permissions:
issues: write
pull-requests: read
jobs:
comment:
name: Post changelog comment
runs-on: ubuntu-latest
steps:
- name: Post or update changelog comment
uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9.0.0
with:
github-token: ${{ github.token }}
script: |
const marker = '<!-- changelog -->';
const mergedMarker = '<!-- changelog-merged -->';
const sections = ['### Added', '', '### Changed', '', '### Deprecated', '', '### Removed', '', '### Fixed', '', '### Security'].join('\n');
const productBlock = (name) => `<details>\n<summary>${name}</summary>\n\n${sections}\n\n</details>`;
const template = [
marker,
'## Pull request changelog',
'',
'<!-- Fill in the changelog under each product area this PR affects.',
' Empty sections are ignored. Leave a product collapsed/empty',
' if it doesn\'t apply. -->',
'',
productBlock('App'),
'',
productBlock('Website'),
].join('\n');
// Resolve PR number from event or workflow_dispatch input
const prNumber = context.payload.pull_request?.number
?? parseInt('${{ github.event.inputs.pr_number }}', 10);
if (!prNumber || isNaN(prNumber)) {
core.setFailed('Could not determine PR number');
return;
}
// Get PR details (need base ref for child PR detection)
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber,
});
// Check if bot comment already exists
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const existingComment = comments.find(c => c.body.includes(marker));
if (existingComment) {
core.info('Changelog comment already exists, skipping');
return;
}
// Post the template comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: template,
});
core.info(`Posted changelog comment on PR #${prNumber}`);
// Detect child PR: check if this PR's base branch is another open PR's head branch
const baseRef = pr.base.ref;
if (baseRef === 'main' || baseRef === 'prod') {
return;
}
// Look for a parent PR whose head branch matches our base branch
const { data: candidatePRs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
head: `${context.repo.owner}:${baseRef}`,
});
if (candidatePRs.length === 0) {
return;
}
const parentPR = candidatePRs[0];
core.info(`Detected parent PR #${parentPR.number} for child PR #${prNumber}`);
// Add admonition to child PR's changelog comment
const { data: childComments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const childChangelogComment = childComments.find(c => c.body.includes(marker));
if (childChangelogComment && !childChangelogComment.body.includes(mergedMarker)) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: childChangelogComment.id,
body: `${mergedMarker}\n> [!NOTE]\n> This changelog has been merged into the changelog for #${parentPR.number}\n\n${childChangelogComment.body}`,
});
}

29
.github/workflows/check-generic.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Repository checks
on:
pull_request:
push:
branches:
- main
env:
CARGO_TERM_COLOR: always
SQLX_OFFLINE: true
jobs:
typos:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: crate-ci/typos@6ac2ebd1b93eade61faf7e12688ad87a073fea59 # v1.46.0
# see <https://github.com/influxdata/datafusion-udf-wasm/pull/275>
tombi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: taiki-e/install-action@fd2f5e3d644b484055ebf4268f474c565f148f25 # v2.81.9
with:
tool: tombi@1.1.2
- run: tombi lint
- run: tombi fmt --check

26
.github/workflows/check-rust.yml vendored Normal file
View File

@ -0,0 +1,26 @@
name: Rust checks
on:
pull_request:
push:
branches:
- main
env:
CARGO_TERM_COLOR: always
SQLX_OFFLINE: true
jobs:
shear:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
submodules: recursive
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .nvmrc
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- uses: cargo-bins/cargo-binstall@dc19f1e48450eefe5a29b8da6c6b00a87d730b37 # v1.18.1
- run: cargo binstall --no-confirm cargo-shear
- run: cargo shear

View File

@ -0,0 +1,34 @@
name: Deploy telemetry dashboard
on:
push:
branches: [main]
paths:
- 'apps/telemetry-dashboard/**'
workflow_dispatch:
permissions:
contents: read
concurrency:
group: telemetry-dashboard-deploy
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: pnpm/action-setup@008330803749db0355799c700092d9a85fd074e9 # v6.0.9
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .nvmrc
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm --filter @axolotl/telemetry-dashboard build
- uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4.0.0
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
workingDirectory: apps/telemetry-dashboard
packageManager: pnpm
command: deploy

View File

@ -0,0 +1,40 @@
name: Prepare pnpm cache
on:
push:
paths:
- .github/workflows/prepare-pnpm-cache.yml
- package.json
- pnpm-lock.yaml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' && github.ref != 'refs/heads/prod' }}
jobs:
prepare:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .nvmrc
- name: Enable Corepack
run: corepack enable
- name: Get pnpm store path
id: pnpm-store
run: echo "store-path=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Cache pnpm
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ${{ steps.pnpm-store.outputs.store-path }}
key: pnpm-cache-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/pnpm-lock.yaml') }}
- name: Install dependencies
run: pnpm recursive install --frozen-lockfile

69
.github/workflows/sync-cnb-source.yml vendored Normal file
View File

@ -0,0 +1,69 @@
name: Sync source to CNB
on:
push:
branches:
- '**'
tags:
- '**'
delete:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: sync-cnb-source-${{ github.event_name == 'delete' && format('{0}/{1}', github.event.ref_type, github.event.ref) || github.ref }}
cancel-in-progress: false
jobs:
sync:
name: Sync Git ref
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Check out source
if: ${{ github.event_name != 'delete' }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
filter: blob:none
- name: Push Git ref to CNB
env:
CNB_REPO_URL_HTTPS: ${{ secrets.CNB_REPO_URL_HTTPS }}
CNB_TOKEN: ${{ secrets.CNB_TOKEN }}
CNB_TOKEN_USER_NAME: ${{ secrets.CNB_TOKEN_USER_NAME }}
DELETED_REF: ${{ github.event.ref }}
DELETED_REF_TYPE: ${{ github.event.ref_type }}
run: |
set -euo pipefail
if [[ -z "${CNB_TOKEN:-}" ]]; then
echo "CNB_TOKEN is not configured"
exit 1
fi
cnb_url="${CNB_REPO_URL_HTTPS:-https://cnb.cool/axlmc/Axolotl.git}"
token_user="${CNB_TOKEN_USER_NAME:-cnb}"
auth="$(printf '%s:%s' "$token_user" "$CNB_TOKEN" | base64 -w0)"
git_cnb() {
git -c "http.extraHeader=Authorization: Basic $auth" "$@"
}
if [[ "$GITHUB_EVENT_NAME" == "delete" ]]; then
git init
if [[ "$DELETED_REF_TYPE" == "tag" ]]; then
ref="refs/tags/$DELETED_REF"
else
ref="refs/heads/$DELETED_REF"
fi
git_cnb push "$cnb_url" --delete "$ref"
else
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
git fetch --force origin "$GITHUB_REF:$GITHUB_REF"
source_ref="$GITHUB_REF"
else
source_ref="HEAD"
fi
git_cnb push --force "$cnb_url" "$source_ref:$GITHUB_REF"
fi

View File

@ -0,0 +1,101 @@
name: Sync LobeHub models
on:
schedule:
- cron: '17 3 * * *'
workflow_dispatch:
permissions:
contents: write
pull-requests: write
concurrency:
group: sync-lobehub-models
cancel-in-progress: false
jobs:
sync:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout Axolotl Launcher
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version-file: .nvmrc
- name: Checkout LobeHub
id: upstream
run: |
set -euo pipefail
git clone --depth 1 --branch main https://github.com/lobehub/lobehub.git "$RUNNER_TEMP/lobehub"
sha="$(git -C "$RUNNER_TEMP/lobehub" rev-parse HEAD)"
echo "sha=$sha" >> "$GITHUB_OUTPUT"
- name: Generate model catalog
run: >-
node --disable-warning=ExperimentalWarning --experimental-vm-modules
scripts/axolotl/sync-lobehub-models.mjs
--upstream "$RUNNER_TEMP/lobehub"
--commit "${{ steps.upstream.outputs.sha }}"
- name: Detect changes
id: changes
run: |
if git diff --quiet -- packages/app-lib/src/api/ai.rs packages/app-lib/src/api/lobehub_text_models.json; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Close obsolete pull request
if: steps.changes.outputs.changed == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
base="${{ github.event.repository.default_branch }}"
branch="automation/sync-lobehub-models"
pr_number="$(gh pr list --state open --base "$base" --head "$branch" --json number --jq '.[0].number // empty')"
if [ -n "$pr_number" ]; then
gh pr close "$pr_number" --delete-branch --comment "Closing because the current LobeHub model catalog no longer differs from $base."
fi
- name: Push model update and create pull request
if: steps.changes.outputs.changed == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
base="${{ github.event.repository.default_branch }}"
branch="automation/sync-lobehub-models"
upstream_sha="${{ steps.upstream.outputs.sha }}"
short_sha="${upstream_sha:0:12}"
title="chore(ai): sync LobeHub models"
body="Automated synchronization of supported chat models from [LobeHub](https://github.com/lobehub/lobehub/tree/${upstream_sha}/packages/model-bank/src/aiModels) at \`${upstream_sha}\`."
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
gh auth setup-git
expected=""
if git ls-remote --exit-code --heads origin "refs/heads/$branch" >/dev/null 2>&1; then
git fetch --no-tags origin "refs/heads/$branch:refs/remotes/origin/$branch"
expected="$(git rev-parse "refs/remotes/origin/$branch")"
fi
git switch -C "$branch"
git add packages/app-lib/src/api/ai.rs packages/app-lib/src/api/lobehub_text_models.json
git commit -m "chore(ai): sync LobeHub models at $short_sha"
git push --force-with-lease="refs/heads/$branch:$expected" origin "HEAD:refs/heads/$branch"
pr_number="$(gh pr list --state open --base "$base" --head "$branch" --json number --jq '.[0].number // empty')"
if [ -n "$pr_number" ]; then
gh api --method PATCH "repos/$GITHUB_REPOSITORY/pulls/$pr_number" -f title="$title" -f body="$body" >/dev/null
else
gh pr create --base "$base" --head "$branch" --title "$title" --body "$body"
fi