mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-07-23 12:50:59 +02:00
refactor: harden tests
This commit is contained in:
+105
-15
@@ -8,6 +8,19 @@ on:
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
secrets:
|
||||
TAURI_WEBDRIVER_TOKEN:
|
||||
description: Read-only token for the test-driver repository
|
||||
required: false
|
||||
WAYFERN_TEST_TOKEN:
|
||||
description: Token used by the full Wayfern integration suite
|
||||
required: false
|
||||
RESIDENTIAL_PROXY_URL_ONE_SOCKS:
|
||||
description: SOCKS5 residential proxy used by the network suite
|
||||
required: false
|
||||
RESIDENTIAL_PROXY_URL_ONE_HTTP:
|
||||
description: HTTP residential proxy used by the network suite
|
||||
required: false
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
suite:
|
||||
@@ -20,6 +33,7 @@ on:
|
||||
- smoke
|
||||
- ui
|
||||
- entities
|
||||
- network
|
||||
- integrations
|
||||
- sync
|
||||
- browser
|
||||
@@ -51,13 +65,15 @@ jobs:
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
path: donutbrowser
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout private cross-platform WebDriver
|
||||
- name: Checkout test driver
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
repository: ${{ vars.TAURI_WEBDRIVER_REPOSITORY || 'zhom/tauri-cross-platform-webdriver' }}
|
||||
repository: ${{ vars.TAURI_WEBDRIVER_REPOSITORY }}
|
||||
token: ${{ secrets.TAURI_WEBDRIVER_TOKEN || github.token }}
|
||||
path: tauri-cross-platform-webdriver
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
@@ -81,6 +97,7 @@ jobs:
|
||||
with:
|
||||
workspaces: |
|
||||
donutbrowser/src-tauri -> target
|
||||
donutbrowser/e2e/app -> target
|
||||
tauri-cross-platform-webdriver -> target
|
||||
|
||||
- name: Install Tauri dependencies
|
||||
@@ -95,8 +112,6 @@ jobs:
|
||||
|
||||
- name: Run isolated smoke suite
|
||||
working-directory: donutbrowser
|
||||
env:
|
||||
DONUT_E2E_KEEP_ARTIFACTS: "1"
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${RUNNER_OS}" == "Linux" ]]; then
|
||||
@@ -106,17 +121,17 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload E2E diagnostics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: app-e2e-smoke-${{ matrix.os }}
|
||||
path: ${{ runner.temp }}/donut-e2e-*
|
||||
path: ${{ runner.temp }}/donut-e2e-*/diagnostics/**
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
|
||||
full:
|
||||
name: Full UI, sync, encryption, and Wayfern
|
||||
if: ${{ inputs.smoke_only != true }}
|
||||
if: ${{ inputs.smoke_only != true && inputs.suite != 'network' }}
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 40
|
||||
|
||||
@@ -125,13 +140,15 @@ jobs:
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
path: donutbrowser
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout private cross-platform WebDriver
|
||||
- name: Checkout test driver
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
repository: ${{ vars.TAURI_WEBDRIVER_REPOSITORY || 'zhom/tauri-cross-platform-webdriver' }}
|
||||
repository: ${{ vars.TAURI_WEBDRIVER_REPOSITORY }}
|
||||
token: ${{ secrets.TAURI_WEBDRIVER_TOKEN || github.token }}
|
||||
path: tauri-cross-platform-webdriver
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
@@ -155,6 +172,7 @@ jobs:
|
||||
with:
|
||||
workspaces: |
|
||||
donutbrowser/src-tauri -> target
|
||||
donutbrowser/e2e/app -> target
|
||||
tauri-cross-platform-webdriver -> target
|
||||
|
||||
- name: Install app dependencies
|
||||
@@ -164,16 +182,88 @@ jobs:
|
||||
- name: Run full isolated app suite
|
||||
working-directory: donutbrowser
|
||||
env:
|
||||
DONUT_E2E_KEEP_ARTIFACTS: "1"
|
||||
DONUT_E2E_SUITE: ${{ inputs.suite || 'full' }}
|
||||
DONUT_E2E_SKIP_NETWORK_TEST: "1"
|
||||
WAYFERN_TEST_TOKEN: ${{ secrets.WAYFERN_TEST_TOKEN }}
|
||||
run: node e2e/run.mjs --suite=${DONUT_E2E_SUITE}
|
||||
run: node e2e/run.mjs "--suite=${DONUT_E2E_SUITE}"
|
||||
|
||||
- name: Upload E2E diagnostics
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: app-e2e-full-macos
|
||||
path: ${{ runner.temp }}/donut-e2e-*
|
||||
path: ${{ runner.temp }}/donut-e2e-*/diagnostics/**
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
|
||||
network:
|
||||
name: Real proxy and local WireGuard browser traffic
|
||||
if: ${{ inputs.smoke_only != true && (inputs.suite == '' || inputs.suite == 'full' || inputs.suite == 'network') }}
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
- name: Checkout Donut Browser
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
path: donutbrowser
|
||||
persist-credentials: false
|
||||
|
||||
- name: Checkout test driver
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
repository: ${{ vars.TAURI_WEBDRIVER_REPOSITORY }}
|
||||
token: ${{ secrets.TAURI_WEBDRIVER_TOKEN || github.token }}
|
||||
path: tauri-cross-platform-webdriver
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||
with:
|
||||
run_install: false
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version-file: donutbrowser/.node-version
|
||||
cache: pnpm
|
||||
cache-dependency-path: donutbrowser/pnpm-lock.yaml
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # master
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Cache Rust dependencies
|
||||
uses: swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
workspaces: |
|
||||
donutbrowser/src-tauri -> target
|
||||
donutbrowser/e2e/app -> target
|
||||
tauri-cross-platform-webdriver -> target
|
||||
|
||||
- name: Install Tauri dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev patchelf libxdo-dev xvfb openvpn
|
||||
|
||||
- name: Install app dependencies
|
||||
working-directory: donutbrowser
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run isolated real-network suite
|
||||
working-directory: donutbrowser
|
||||
env:
|
||||
WAYFERN_TEST_TOKEN: ${{ secrets.WAYFERN_TEST_TOKEN }}
|
||||
RESIDENTIAL_PROXY_URL_ONE_SOCKS: ${{ secrets.RESIDENTIAL_PROXY_URL_ONE_SOCKS }}
|
||||
RESIDENTIAL_PROXY_URL_ONE_HTTP: ${{ secrets.RESIDENTIAL_PROXY_URL_ONE_HTTP }}
|
||||
run: xvfb-run -a pnpm e2e:network
|
||||
|
||||
- name: Upload E2E diagnostics
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: app-e2e-network-linux
|
||||
path: ${{ runner.temp }}/donut-e2e-*/diagnostics/**
|
||||
if-no-files-found: ignore
|
||||
retention-days: 7
|
||||
|
||||
@@ -2,6 +2,12 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
checkout_ref:
|
||||
description: Optional commit to check out instead of the triggering ref
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
@@ -32,6 +38,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
||||
with:
|
||||
ref: ${{ inputs.checkout_ref }}
|
||||
|
||||
- name: Set up pnpm package manager
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 #v6.0.9
|
||||
|
||||
@@ -30,7 +30,8 @@ jobs:
|
||||
name: Lint JavaScript/TypeScript
|
||||
if: github.repository == 'zhom/donutbrowser' && github.actor == 'dependabot[bot]'
|
||||
uses: ./.github/workflows/lint-js.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
checkout_ref: ${{ github.event.pull_request.head.sha }}
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -38,7 +39,8 @@ jobs:
|
||||
name: Lint Rust
|
||||
if: github.repository == 'zhom/donutbrowser' && github.actor == 'dependabot[bot]'
|
||||
uses: ./.github/workflows/lint-rs.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
checkout_ref: ${{ github.event.pull_request.head.sha }}
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -46,7 +48,8 @@ jobs:
|
||||
name: CodeQL
|
||||
if: github.repository == 'zhom/donutbrowser' && github.actor == 'dependabot[bot]'
|
||||
uses: ./.github/workflows/codeql.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
checkout_ref: ${{ github.event.pull_request.head.sha }}
|
||||
permissions:
|
||||
security-events: write
|
||||
contents: read
|
||||
@@ -57,7 +60,8 @@ jobs:
|
||||
name: Spell Check
|
||||
if: github.repository == 'zhom/donutbrowser' && github.actor == 'dependabot[bot]'
|
||||
uses: ./.github/workflows/spellcheck.yml
|
||||
secrets: inherit
|
||||
with:
|
||||
checkout_ref: ${{ github.event.pull_request.head.sha }}
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
||||
@@ -11,6 +11,11 @@ on:
|
||||
description: "Docker tag (e.g., v1.0.0)"
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME:
|
||||
required: true
|
||||
DOCKERHUB_TOKEN:
|
||||
required: true
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@@ -43,23 +48,29 @@ jobs:
|
||||
|
||||
- name: Determine tags
|
||||
id: tags
|
||||
env:
|
||||
INPUT_TAG: ${{ inputs.tag }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
COMMIT_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
TAGS=""
|
||||
INPUT_TAG="${{ inputs.tag }}"
|
||||
|
||||
if [ -n "$INPUT_TAG" ]; then
|
||||
# Called from release workflow or manual dispatch
|
||||
if [[ ! "$INPUT_TAG" =~ ^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$ ]]; then
|
||||
echo "Invalid Docker tag" >&2
|
||||
exit 1
|
||||
fi
|
||||
TAGS="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${INPUT_TAG}"
|
||||
TAGS="${TAGS},${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest"
|
||||
elif [ "${{ github.event_name }}" = "push" ]; then
|
||||
elif [ "$EVENT_NAME" = "push" ]; then
|
||||
# Push to main (nightly): tag with nightly and commit SHA
|
||||
SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7)
|
||||
SHORT_SHA=${COMMIT_SHA:0:7}
|
||||
TAGS="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly"
|
||||
TAGS="${TAGS},${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:nightly-${SHORT_SHA}"
|
||||
fi
|
||||
|
||||
echo "tags=${TAGS}" >> "$GITHUB_OUTPUT"
|
||||
echo "Tags: ${TAGS}"
|
||||
printf 'tags=%s\n' "$TAGS" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a #v7.3.0
|
||||
|
||||
@@ -29,8 +29,8 @@ jobs:
|
||||
ISSUE_TITLE: ${{ github.event.issue.title }}
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
run: |
|
||||
printf '%s' "$ISSUE_TITLE" > /tmp/issue-title.txt
|
||||
printf '%s' "${ISSUE_BODY:-}" > /tmp/issue-body.txt
|
||||
printf '%s' "$ISSUE_TITLE" | node scripts/redact-sensitive-text.mjs > /tmp/issue-title.txt
|
||||
printf '%s' "${ISSUE_BODY:-}" | node scripts/redact-sensitive-text.mjs --issue-body > /tmp/issue-body.txt
|
||||
|
||||
- name: Build prompt
|
||||
run: |
|
||||
@@ -92,11 +92,9 @@ jobs:
|
||||
sed -E 's/^```(json)?$//; s/```$//' /tmp/raw.txt > /tmp/result.json
|
||||
if ! jq -e . /tmp/result.json >/dev/null 2>&1; then
|
||||
echo "::warning::Model returned non-JSON; treating as compliant"
|
||||
cat /tmp/raw.txt
|
||||
echo '{"is_compliant": true, "non_compliance_reasons": []}' > /tmp/result.json
|
||||
fi
|
||||
echo "Result:"
|
||||
cat /tmp/result.json
|
||||
echo "Compliance response validated"
|
||||
|
||||
- name: Build comment
|
||||
id: build
|
||||
|
||||
@@ -14,7 +14,6 @@ permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
id-token: write
|
||||
|
||||
env:
|
||||
# Single source of truth for the model used by both triage and composer.
|
||||
@@ -49,8 +48,9 @@ jobs:
|
||||
env:
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
run: |
|
||||
node <<'EOF'
|
||||
const fs = require('node:fs');
|
||||
node --input-type=module <<'EOF'
|
||||
import fs from 'node:fs';
|
||||
import { redactIssueBody, redactSensitiveText } from './scripts/redact-sensitive-text.mjs';
|
||||
const body = process.env.ISSUE_BODY || '';
|
||||
// GitHub issue templates render fields as `### Heading\nValue` blocks.
|
||||
// Split on `###` at line start to recover them.
|
||||
@@ -61,29 +61,27 @@ jobs:
|
||||
if (nl < 0) continue;
|
||||
const heading = section.slice(0, nl).trim();
|
||||
const value = section.slice(nl + 1).trim();
|
||||
fields[heading] = value === '_No response_' ? '' : value;
|
||||
const normalized = value === '_No response_' ? '' : value;
|
||||
fields[heading] = heading === 'Error logs or screenshots'
|
||||
? '[omitted from automated processing]'
|
||||
: redactSensitiveText(normalized);
|
||||
}
|
||||
fs.writeFileSync('/tmp/issue-fields.json', JSON.stringify(fields, null, 2));
|
||||
// Convenience extractions for the prompt — empty string if missing.
|
||||
const get = (k) => fields[k] || '';
|
||||
fs.writeFileSync('/tmp/issue-os.txt', get('Operating System'));
|
||||
fs.writeFileSync('/tmp/issue-version.txt', get('Donut Browser version'));
|
||||
fs.writeFileSync('/tmp/issue-wayfern-version.txt', get('Wayfern version'));
|
||||
fs.writeFileSync('/tmp/issue-repro.txt', get('Steps to reproduce'));
|
||||
fs.writeFileSync('/tmp/issue-logs.txt', get('Error logs or screenshots'));
|
||||
fs.writeFileSync('/tmp/issue-what.txt', get('What happened?') || get('What do you want?'));
|
||||
fs.writeFileSync('/tmp/issue-body.txt', redactIssueBody(body));
|
||||
EOF
|
||||
echo "Parsed fields:"
|
||||
cat /tmp/issue-fields.json
|
||||
|
||||
- name: Build repo context
|
||||
env:
|
||||
ISSUE_TITLE: ${{ github.event.issue.title }}
|
||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||
run: |
|
||||
cp CLAUDE.md /tmp/repo-context.txt
|
||||
printf '%s' "$ISSUE_TITLE" > /tmp/issue-title.txt
|
||||
printf '%s' "${ISSUE_BODY:-}" > /tmp/issue-body.txt
|
||||
printf '%s' "$ISSUE_TITLE" | node scripts/redact-sensitive-text.mjs > /tmp/issue-title.txt
|
||||
|
||||
# List all source files for the AI to choose from
|
||||
find . -type f \( -name "*.rs" -o -name "*.ts" -o -name "*.tsx" \) \
|
||||
@@ -199,7 +197,7 @@ jobs:
|
||||
Return ONLY valid JSON. No preamble, no code fences. Schema:
|
||||
{
|
||||
"language": "en" or ISO 639-1 code,
|
||||
"classification": one of ["bug-in-scope", "bug-template-violation", "feature-request", "fork-request", "regression", "ai-generated-junk", "question", "other"],
|
||||
"classification": one of ["bug-in-scope", "bug-template-violation", "feature-request", "fork-request", "regression", "automated-content", "question", "other"],
|
||||
"operating_system": "macos" | "windows" | "linux" | "unknown",
|
||||
"is_paid_feature": true | false,
|
||||
"user_followed_template": true | false,
|
||||
@@ -211,11 +209,11 @@ jobs:
|
||||
|
||||
Classification guidance:
|
||||
- "bug-template-violation": missing or filled-in nonsense for required template fields.
|
||||
- "ai-generated-junk": cites fabricated "official docs" (context7, deepwiki, non-donutbrowser URLs) or has the polished AI-spam shape (long, structured, fabricated certainty).
|
||||
- "automated-content": cites fabricated "official docs" (context7, deepwiki, non-donutbrowser URLs) or has a highly structured automated-submission pattern with fabricated certainty.
|
||||
- "fork-request": asks for support of CloverLabsAI/VulpineOS/etc. forks.
|
||||
- "regression": user names a prior version that worked.
|
||||
|
||||
File selection: pick files that an experienced reviewer would actually look at to act on this issue. If the issue is a fork-request or junk, set files_to_read to []. Otherwise pick concrete files relevant to the symptoms.
|
||||
File selection: pick files that an experienced reviewer would actually look at to act on this issue. For a fork request or automated-content classification, set files_to_read to []. Otherwise pick concrete files relevant to the symptoms.
|
||||
TRIAGE_TAIL
|
||||
} > /tmp/triage-system.txt
|
||||
wc -c /tmp/triage-system.txt
|
||||
@@ -238,7 +236,7 @@ jobs:
|
||||
messages: [
|
||||
{ role: "system", content: $system_prompt },
|
||||
{ role: "user",
|
||||
content: ("Issue title: " + $title + "\n\nBody:\n" + $body + "\n\nParsed template fields:\n" + $fields + "\n\nAll source files:\n" + $files) }
|
||||
content: ("Issue title: " + $title + "\n\nSanitized body:\n" + $body + "\n\nSanitized template fields:\n" + $fields + "\n\nAll source files:\n" + $files) }
|
||||
]
|
||||
}')
|
||||
|
||||
@@ -249,14 +247,12 @@ jobs:
|
||||
|
||||
jq -r '.choices[0].message.content // empty' <<< "$RESPONSE" > /tmp/triage-raw.txt
|
||||
|
||||
# Strip ```json fences if the model couldn't help itself.
|
||||
# Normalize optional markdown fences before parsing.
|
||||
sed -E 's/^```(json)?$//; s/```$//' /tmp/triage-raw.txt > /tmp/triage.json
|
||||
|
||||
# Validate; if the model returned junk, fall back to a minimal stub so the
|
||||
# composer still gets called and produces SOMETHING.
|
||||
# Fall back to a safe classification when the response is not JSON.
|
||||
if ! jq -e . /tmp/triage.json >/dev/null 2>&1; then
|
||||
echo "::warning::Triage returned non-JSON; using fallback classification"
|
||||
cat /tmp/triage-raw.txt
|
||||
jq -n '{
|
||||
language: "en",
|
||||
classification: "bug-in-scope",
|
||||
@@ -270,17 +266,16 @@ jobs:
|
||||
}' > /tmp/triage.json
|
||||
fi
|
||||
|
||||
echo "Triage result:"
|
||||
cat /tmp/triage.json
|
||||
echo "Triage response validated"
|
||||
|
||||
- name: Read files chosen by triage
|
||||
run: |
|
||||
: > /tmp/file-context.txt
|
||||
# files_to_read may be empty (e.g. fork-request or junk) — that's fine.
|
||||
# An empty file list is valid for classifications that need no source context.
|
||||
jq -r '.files_to_read[]? // empty' /tmp/triage.json | while IFS= read -r filepath; do
|
||||
filepath=$(echo "$filepath" | xargs)
|
||||
[ -z "$filepath" ] && continue
|
||||
# Reject paths that escape the repo or look fishy
|
||||
# Reject paths that escape the repository.
|
||||
case "$filepath" in
|
||||
/*|*..*|*$'\n'*) continue ;;
|
||||
esac
|
||||
@@ -331,7 +326,7 @@ jobs:
|
||||
The triage classification (`triage.classification`) determines the response shape:
|
||||
|
||||
- `bug-in-scope`: ask for what is missing using the user's reported OS log path. Be concrete about how to obtain logs.
|
||||
- `bug-template-violation` or `ai-generated-junk`: politely ask the user to refile using the bug-report template (the Operating System, Donut Browser version, Wayfern version, Steps to reproduce, Error logs sections). If they cited "documentation" from any non-`donutbrowser.com`/non-`github.com/zhom` URL (e.g. context7, deepwiki), gently note that those are AI-generated third-party summaries and the only authoritative sources are this repo and donutbrowser.com.
|
||||
- `bug-template-violation` or `automated-content`: politely ask the user to refile using the bug-report template (the Operating System, Donut Browser version, Wayfern version, Steps to reproduce, Error logs sections). If they cited "documentation" from any non-`donutbrowser.com`/non-`github.com/zhom` URL (e.g. context7, deepwiki), gently note that those are AI-generated third-party summaries and the only authoritative sources are this repo and donutbrowser.com.
|
||||
- `feature-request`: one neutral sentence acknowledging, then ask only what is genuinely needed (concrete use case, whether a workaround would suffice). Do NOT validate.
|
||||
- `fork-request`: one neutral sentence acknowledging the request. Note that this would substantially increase support burden and the maintainer evaluates such requests on a case-by-case basis. Ask whether the alternative fork supports all platforms the user uses (macOS / Windows / Linux). No "clear enhancement" language.
|
||||
- `regression`: do NOT call known/expected. Ask which exact previous version was the last working one, what changed in the user's environment between then and now, and the specific delta in symptoms.
|
||||
@@ -408,8 +403,8 @@ jobs:
|
||||
+ "Title: " + $title
|
||||
+ "\nAuthor: " + $author
|
||||
+ "\n\n## Triage result\n" + $triage
|
||||
+ "\n\n## Parsed template fields\n" + $fields
|
||||
+ "\n\n## Raw issue body\n" + $body
|
||||
+ "\n\n## Sanitized template fields\n" + $fields
|
||||
+ "\n\n## Sanitized issue body\n" + $body
|
||||
+ "\n\n## Source files (selected by triage)\n" + $files) }
|
||||
]
|
||||
}')
|
||||
@@ -423,8 +418,6 @@ jobs:
|
||||
|
||||
if [ ! -s /tmp/ai-comment.txt ]; then
|
||||
echo "::error::Composer returned empty response"
|
||||
echo "Raw response:"
|
||||
echo "$RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -484,8 +477,9 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
HEAD_REPOSITORY: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
run: |
|
||||
gh api "/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" \
|
||||
gh api --paginate "/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files?per_page=100" \
|
||||
--jq '.[] | "- \(.filename) (\(.status)) +\(.additions)/-\(.deletions)"' \
|
||||
> /tmp/pr-files.txt
|
||||
|
||||
@@ -499,14 +493,27 @@ jobs:
|
||||
cp CLAUDE.md /tmp/repo-context.txt
|
||||
|
||||
: > /tmp/related-file-contents.txt
|
||||
gh api "/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files" --jq '.[].filename' | while IFS= read -r filepath; do
|
||||
if [ -f "$filepath" ] && file --mime "$filepath" | grep -q "text/"; then
|
||||
echo "=== $filepath (full file) ===" >> /tmp/related-file-contents.txt
|
||||
cat "$filepath" >> /tmp/related-file-contents.txt
|
||||
echo "" >> /tmp/related-file-contents.txt
|
||||
gh api --paginate "/repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/files?per_page=100" \
|
||||
--jq '.[] | select(.status != "removed") | [.filename, .sha] | @tsv' |
|
||||
while IFS=$'\t' read -r filepath blob_sha; do
|
||||
case "$filepath" in
|
||||
/*|*..*|*$'\n'*) continue ;;
|
||||
esac
|
||||
blob_file=$(mktemp)
|
||||
if gh api "/repos/$HEAD_REPOSITORY/git/blobs/$blob_sha" --jq .content \
|
||||
| tr -d '\n' | base64 --decode > "$blob_file" 2>/dev/null \
|
||||
&& file --mime "$blob_file" | grep -q "text/"; then
|
||||
echo "=== $filepath (head revision) ===" >> /tmp/related-file-contents.txt
|
||||
cat "$blob_file" >> /tmp/related-file-contents.txt
|
||||
echo "" >> /tmp/related-file-contents.txt
|
||||
fi
|
||||
rm -f "$blob_file"
|
||||
done
|
||||
head -c 100000 /tmp/related-file-contents.txt > /tmp/pr-file-context.txt
|
||||
node scripts/redact-sensitive-text.mjs < /tmp/pr-diff.txt > /tmp/pr-diff.safe.txt
|
||||
mv /tmp/pr-diff.safe.txt /tmp/pr-diff.txt
|
||||
node scripts/redact-sensitive-text.mjs < /tmp/pr-file-context.txt > /tmp/pr-file-context.safe.txt
|
||||
mv /tmp/pr-file-context.safe.txt /tmp/pr-file-context.txt
|
||||
|
||||
- name: Analyze PR with AI
|
||||
env:
|
||||
@@ -525,8 +532,8 @@ jobs:
|
||||
GREETING='This is a first-time contributor. Start your comment with: "Thanks for your first PR!"'
|
||||
fi
|
||||
|
||||
printf '%s' "$PR_TITLE" > /tmp/pr-title.txt
|
||||
printf '%s' "${PR_BODY:-}" > /tmp/pr-body.txt
|
||||
printf '%s' "$PR_TITLE" | node scripts/redact-sensitive-text.mjs > /tmp/pr-title.txt
|
||||
printf '%s' "${PR_BODY:-}" | node scripts/redact-sensitive-text.mjs > /tmp/pr-body.txt
|
||||
printf '%s' "$PR_AUTHOR" > /tmp/pr-author.txt
|
||||
printf '%s' "$PR_BASE" > /tmp/pr-base.txt
|
||||
printf '%s' "$PR_HEAD" > /tmp/pr-head.txt
|
||||
@@ -550,7 +557,7 @@ jobs:
|
||||
messages: [
|
||||
{
|
||||
role: "system",
|
||||
content: ("You are a code review bot for Donut Browser, an open-source anti-detect browser (Tauri desktop app: Rust backend + Next.js frontend).\n\nProject guidelines and structure:\n" + $repo_context + "\n\nContributing guidelines:\n" + $contributing + "\n\nYou have access to the full changed files and the diff. Use them to give a substantive review.\n\nReview this PR and produce a single comment. Format:\n\n1. One sentence summarizing what this PR does and whether the approach is sound.\n2. **Code review** - Specific observations about the actual code changes. Mention file names and what you see in the diff. Look for:\n - Bugs or logic errors in the changed code\n - Security issues (SQL injection, path traversal, XSS, command injection)\n - Missing error handling or edge cases\n - Breaking changes to existing APIs or behavior\n - If UI text was added/changed, check if all 7 translation files (en, es, fr, ja, pt, ru, zh) in src/i18n/locales/ were updated\n - If Tauri commands were added/removed, the unused-commands test in lib.rs needs updating\n3. **Suggestions** - Concrete improvements if any. Skip if the PR looks good.\n\nRules:\n- Be substantive. Review the actual diff, not just the description.\n- Do NOT nitpick formatting or style — the project has automated linting (biome + clippy + rustfmt).\n- Do NOT just summarize the PR description back to the user — they wrote it, they know what it says.\n- If the PR is good, say so briefly.\n- Never exceed 20 lines.")
|
||||
content: ("You are a code review bot for Donut Browser, an open-source anti-detect browser (Tauri desktop app: Rust backend + Next.js frontend).\n\nProject guidelines and structure:\n" + $repo_context + "\n\nContributing guidelines:\n" + $contributing + "\n\nYou have access to sanitized head-revision contents for changed files and a sanitized diff. Use them to give a substantive review.\n\nReview this PR and produce a single comment. Format:\n\n1. One sentence summarizing what this PR does and whether the approach is sound.\n2. **Code review** - Specific observations about the actual code changes. Mention file names and what you see in the diff. Look for:\n - Bugs or logic errors in the changed code\n - Security issues (SQL injection, path traversal, XSS, command injection)\n - Missing error handling or edge cases\n - Breaking changes to existing APIs or behavior\n - If UI text was added or changed, verify the key exists in every JSON file under src/i18n/locales/\n - If Tauri commands were added or removed, verify e2e/coverage-map.mjs is updated exactly once per command\n3. **Suggestions** - Concrete improvements if any. Skip if the PR looks good.\n\nRules:\n- Be substantive. Review the actual diff, not just the description.\n- Do NOT nitpick formatting or style — the project has automated linting (biome + clippy + rustfmt).\n- Do NOT just summarize the PR description back to the user — they wrote it, they know what it says.\n- If the PR is good, say so briefly.\n- Never exceed 20 lines.")
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
@@ -577,8 +584,6 @@ jobs:
|
||||
|
||||
if [ ! -s /tmp/ai-comment.txt ]; then
|
||||
echo "::error::AI response was empty"
|
||||
echo "Raw response:"
|
||||
echo "$RESPONSE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -593,6 +598,9 @@ jobs:
|
||||
if: |
|
||||
github.repository == 'zhom/donutbrowser' &&
|
||||
(github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') &&
|
||||
(github.event.comment.author_association == 'OWNER' ||
|
||||
github.event.comment.author_association == 'MEMBER' ||
|
||||
github.event.comment.author_association == 'COLLABORATOR') &&
|
||||
(contains(github.event.comment.body, ' /oc') ||
|
||||
startsWith(github.event.comment.body, '/oc') ||
|
||||
contains(github.event.comment.body, ' /opencode') ||
|
||||
|
||||
@@ -4,6 +4,12 @@ name: Lint Node.js
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
checkout_ref:
|
||||
description: Optional commit to check out instead of the triggering ref
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -35,6 +41,8 @@ jobs:
|
||||
|
||||
- name: Checkout repository code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
||||
with:
|
||||
ref: ${{ inputs.checkout_ref }}
|
||||
|
||||
- name: Set up pnpm package manager
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 #v6.0.9
|
||||
|
||||
@@ -4,6 +4,12 @@ name: Lint Rust
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
checkout_ref:
|
||||
description: Optional commit to check out instead of the triggering ref
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
@@ -42,6 +48,8 @@ jobs:
|
||||
|
||||
- name: Checkout repository code
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
||||
with:
|
||||
ref: ${{ inputs.checkout_ref }}
|
||||
|
||||
- name: Set up pnpm package manager
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 #v6.0.9
|
||||
|
||||
@@ -15,14 +15,12 @@ jobs:
|
||||
lint-js:
|
||||
name: Lint JavaScript/TypeScript
|
||||
uses: ./.github/workflows/lint-js.yml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
lint-rust:
|
||||
name: Lint Rust
|
||||
uses: ./.github/workflows/lint-rs.yml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -41,14 +39,15 @@ jobs:
|
||||
sync-e2e:
|
||||
name: Sync E2E Tests
|
||||
uses: ./.github/workflows/sync-e2e.yml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
app-e2e:
|
||||
name: Cross-platform App E2E
|
||||
uses: ./.github/workflows/app-e2e.yml
|
||||
secrets: inherit
|
||||
secrets:
|
||||
TAURI_WEBDRIVER_TOKEN: ${{ secrets.TAURI_WEBDRIVER_TOKEN }}
|
||||
WAYFERN_TEST_TOKEN: ${{ secrets.WAYFERN_TEST_TOKEN }}
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
|
||||
@@ -31,17 +31,24 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
INPUT_TAG: ${{ inputs.tag }}
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
WORKFLOW_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
if [[ -n "${INPUT_TAG:-}" ]]; then
|
||||
echo "tag=${INPUT_TAG}" >> "$GITHUB_OUTPUT"
|
||||
elif [[ "${{ github.event_name }}" == "workflow_run" ]]; then
|
||||
TAG="$INPUT_TAG"
|
||||
elif [[ "$EVENT_NAME" == "workflow_run" ]]; then
|
||||
# The Release workflow is triggered by a tag push (v*),
|
||||
# so head_branch is the tag name
|
||||
echo "tag=${{ github.event.workflow_run.head_branch }}" >> "$GITHUB_OUTPUT"
|
||||
TAG="$WORKFLOW_HEAD_BRANCH"
|
||||
else
|
||||
TAG=$(gh release view --repo "${{ github.repository }}" --json tagName -q .tagName)
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
TAG=$(gh release view --repo "$REPOSITORY" --json tagName -q .tagName)
|
||||
fi
|
||||
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Invalid release tag" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf 'tag=%s\n' "$TAG" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Install tools
|
||||
run: |
|
||||
@@ -59,19 +66,12 @@ jobs:
|
||||
R2_ENDPOINT_URL: ${{ secrets.R2_ENDPOINT_URL }}
|
||||
R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }}
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_TAG: ${{ steps.tag.outputs.tag }}
|
||||
run: |
|
||||
# GitHub injects secrets verbatim. If a value was pasted with
|
||||
# surrounding quotes or a trailing newline — the local .env wraps all
|
||||
# four R2_* values in double quotes — it reaches the script malformed:
|
||||
# e.g. an endpoint of https://"host" yields
|
||||
# `Could not connect to the endpoint URL`, and a quoted key yields
|
||||
# `Unauthorized`. The local run is unaffected because publish-repo.sh
|
||||
# sources .env through bash, which strips the quotes; CI has no .env,
|
||||
# so strip here. No-op when the secrets are already clean. The script
|
||||
# itself is intentionally left untouched.
|
||||
# Normalize accidental quotes and whitespace in configured secrets.
|
||||
strip() { printf '%s' "$1" | tr -d '\r\n' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'\$/\1/"; }
|
||||
export R2_ACCESS_KEY_ID="$(strip "$R2_ACCESS_KEY_ID")"
|
||||
export R2_SECRET_ACCESS_KEY="$(strip "$R2_SECRET_ACCESS_KEY")"
|
||||
export R2_ENDPOINT_URL="$(strip "$R2_ENDPOINT_URL")"
|
||||
export R2_BUCKET_NAME="$(strip "$R2_BUCKET_NAME")"
|
||||
bash scripts/publish-repo.sh "${{ steps.tag.outputs.tag }}"
|
||||
bash scripts/publish-repo.sh "$RELEASE_TAG"
|
||||
|
||||
@@ -37,7 +37,6 @@ jobs:
|
||||
if: github.repository == 'zhom/donutbrowser'
|
||||
name: Lint JavaScript/TypeScript
|
||||
uses: ./.github/workflows/lint-js.yml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -45,7 +44,6 @@ jobs:
|
||||
if: github.repository == 'zhom/donutbrowser'
|
||||
name: Lint Rust
|
||||
uses: ./.github/workflows/lint-rs.yml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -53,7 +51,6 @@ jobs:
|
||||
if: github.repository == 'zhom/donutbrowser'
|
||||
name: CodeQL
|
||||
uses: ./.github/workflows/codeql.yml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
security-events: write
|
||||
contents: read
|
||||
@@ -64,7 +61,6 @@ jobs:
|
||||
if: github.repository == 'zhom/donutbrowser'
|
||||
name: Spell Check
|
||||
uses: ./.github/workflows/spellcheck.yml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -218,6 +214,7 @@ jobs:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
# tauri-action invokes `pnpm tauri build`, which runs
|
||||
# `beforeBuildCommand` from tauri.conf.json. That rebuilds the
|
||||
# frontend in its own subprocess, so the env var MUST be forwarded
|
||||
@@ -559,7 +556,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger Cloudflare Pages deployment
|
||||
run: curl -fsSL -X POST "${{ secrets.CLOUDFLARE_WEB_DEPLOYMENT_HOOK }}"
|
||||
env:
|
||||
DEPLOYMENT_HOOK: ${{ secrets.CLOUDFLARE_WEB_DEPLOYMENT_HOOK }}
|
||||
run: curl -fsSL -X POST "$DEPLOYMENT_HOOK"
|
||||
|
||||
docker:
|
||||
if: github.repository == 'zhom/donutbrowser'
|
||||
@@ -567,7 +566,9 @@ jobs:
|
||||
uses: ./.github/workflows/docker-sync.yml
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
secrets: inherit
|
||||
secrets:
|
||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
update-flake:
|
||||
if: github.repository == 'zhom/donutbrowser'
|
||||
|
||||
@@ -44,7 +44,6 @@ jobs:
|
||||
if: github.repository == 'zhom/donutbrowser'
|
||||
name: Lint JavaScript/TypeScript
|
||||
uses: ./.github/workflows/lint-js.yml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -52,7 +51,6 @@ jobs:
|
||||
if: github.repository == 'zhom/donutbrowser'
|
||||
name: Lint Rust
|
||||
uses: ./.github/workflows/lint-rs.yml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -60,7 +58,6 @@ jobs:
|
||||
if: github.repository == 'zhom/donutbrowser'
|
||||
name: CodeQL
|
||||
uses: ./.github/workflows/codeql.yml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
security-events: write
|
||||
contents: read
|
||||
@@ -71,7 +68,6 @@ jobs:
|
||||
if: github.repository == 'zhom/donutbrowser'
|
||||
name: Spell Check
|
||||
uses: ./.github/workflows/spellcheck.yml
|
||||
secrets: inherit
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -241,6 +237,7 @@ jobs:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
TARGET: ${{ matrix.target }}
|
||||
# tauri-action's inner `pnpm tauri build` re-runs beforeBuildCommand
|
||||
# which rebuilds dist/ in a subprocess. The env var must be here too.
|
||||
NEXT_PUBLIC_TURNSTILE: ${{ secrets.NEXT_PUBLIC_TURNSTILE }}
|
||||
@@ -419,7 +416,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Trigger Cloudflare Pages deployment
|
||||
run: curl -fsSL -X POST "${{ secrets.CLOUDFLARE_WEB_DEPLOYMENT_HOOK }}"
|
||||
env:
|
||||
DEPLOYMENT_HOOK: ${{ secrets.CLOUDFLARE_WEB_DEPLOYMENT_HOOK }}
|
||||
run: curl -fsSL -X POST "$DEPLOYMENT_HOOK"
|
||||
|
||||
notify-discord:
|
||||
if: github.repository == 'zhom/donutbrowser'
|
||||
|
||||
@@ -5,6 +5,12 @@ permissions:
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
checkout_ref:
|
||||
description: Optional commit to check out instead of the triggering ref
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
@@ -22,5 +28,7 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout Actions Repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 #v7.0.0
|
||||
with:
|
||||
ref: ${{ inputs.checkout_ref }}
|
||||
- name: Spell Check Repo
|
||||
uses: crate-ci/typos@bee27e3a4fd1ea2111cf90ab89cd076c870fce14 #v1.48.0
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.0
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 #v6.0.9
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: "pnpm"
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.0
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Start MinIO
|
||||
run: |
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v7
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: "pnpm"
|
||||
|
||||
Reference in New Issue
Block a user