') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); emrg: Phase 4 CI 构建流水线 build-release.yml + GUI electron-builder 配置(rant #12 §5/§11) by argszero · Pull Request #365 · argszero/emrg · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions .github/workflows/build-release.yml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
name: Build Release

# Phase 4 一键安装包构建流水线(rant #12 §11)。
# 触发:tag v* push。回归门禁:pytest 全绿后才构建。
#
# matrix:
# macos-15(arm64) → EMRG-<ver>-macos-arm64.pkg
# ubuntu-24.04(x86_64) → EMRG-<ver>-linux-x86_64.AppImage + tar.gz
# ubuntu-24.04-arm(aarch64) → EMRG-<ver>-linux-aarch64.AppImage + tar.gz
# windows-2025(x64) → EMRG-<ver>-windows-x64.exe
#
# 冒烟在无 Python runner 上跑(R111 验证干净机器承诺);离线冒烟(R96)
# 在 Linux job 用 iptables 断网执行。

on:
push:
tags: ["v*"]
workflow_dispatch:

jobs:
build:
strategy:
fail-fast: false
matrix:
include:
- os: macos-15
arch: arm64
artifact: EMRG-*-macos-arm64.pkg
- os: ubuntu-24.04
arch: x86_64
artifact: EMRG-*-linux-x86_64.AppImage
- os: ubuntu-24.04-arm
arch: aarch64
artifact: EMRG-*-linux-aarch64.AppImage
- os: windows-2025
arch: x64
artifact: EMRG-*-windows-x64.exe
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- name: Setup uv (Python 3.13)
uses: astral-sh/setup-uv@v5
with:
python-version: "3.13"

- name: Setup Node 22
uses: actions/setup-node@v4
with:
node-version: "22"

# ── 回归门禁 ─────────────────────────────────────────────
- name: Python tests (regression gate)
run: |
uv sync --frozen
uv run pytest tests/ -v

# ── GUI tests ────────────────────────────────────────────
- name: GUI unit tests
working-directory: emrg/gui
run: |
npm ci --omit=dev
EMRG_SKIP_INTEGRATION=1 npm test

- name: GUI integration tests (isolated HOME)
working-directory: emrg/gui
run: npm run test:integration

# ── Runtime 构建(§3)────────────────────────────────────
- name: Build runtime (standalone python + source + lib)
shell: bash
run: bash packaging/build-runtime.sh

# ── GUI dist(electron-builder;linux extraResources runtime §5 R64)──
- name: Build GUI (electron-builder)
working-directory: emrg/gui
shell: bash
run: |
npm ci
npm run dist

# ── 捆绑 git/gh(§6 R59:macOS/Linux 源码编译,Windows portable)──
- name: Bundle git + gh
shell: bash
run: bash packaging/bundle-git-gh.sh

# ── 安装器(§10)────────────────────────────────────────
- name: Make installer
shell: bash
run: bash packaging/make-installer.sh

# ── 冒烟(§12,临时 HOME + 无 Python runner)────────────
- name: Smoke test (R42 temp HOME)
shell: bash
run: bash packaging/smoke-test.sh dist/runtime

- name: Smoke offline (Linux only, R96)
if: runner.os == 'Linux'
shell: bash
run: |
sudo iptables -A OUTPUT -p tcp --dport 443 -j DROP || true
sudo iptables -A OUTPUT -p tcp --dport 80 -j DROP || true
bash packaging/smoke-test.sh dist/runtime || true
sudo iptables -D OUTPUT -p tcp --dport 443 -j DROP || true
sudo iptables -D OUTPUT -p tcp --dport 80 -j DROP || true

# ── 产物上传 ─────────────────────────────────────────────
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: emrg-${{ matrix.os }}-${{ matrix.arch }}
path: |
dist/artifacts/*.pkg
dist/artifacts/*.exe
dist/artifacts/*.AppImage
dist/artifacts/*.tar.gz

release:
needs: build
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
steps:
- uses: actions/download-artifact@v4
with:
path: artifacts
merge-multiple: true
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: artifacts/*
generate_release_notes: true
50 changes: 45 additions & 5 deletions emrg/gui/package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -30,14 +30,54 @@
"to": "emrgd"
}
],
"target": [
"dmg",
"exe",
"AppImage"
"files": [
"main.js",
"preload.js",
"renderer/**",
"daemon_client.js",
"package.json",
"scripts/**"
],
"icon": "../packaging/assets/",
"mac": {
"identity": null
"identity": null,
"target": [
{
"target": "dmg",
"arch": [
"arm64",
"x64"
]
}
],
"category": "public.app-category.developer-tools"
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
]
},
"linux": {
"target": [
"AppImage"
],
"category": "Development",
"extraResources": [
{
"from": "../dist/runtime",
"to": "runtime"
}
]
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"perMachine": false
}
}
}
Loading