Makelore 2.0 initial clean snapshot
This commit is contained in:
79
.github/workflows/check.yml
vendored
Normal file
79
.github/workflows/check.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
# Makelore CI Workflow
|
||||
# Runs on pull requests (main/develop)
|
||||
name: Check and Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate extension bridge
|
||||
run: pnpm run ext:bridge
|
||||
|
||||
- name: Run linter
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Run typecheck
|
||||
run: pnpm run typecheck
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm run test
|
||||
|
||||
- name: Run harness checks
|
||||
run: pnpm run harness:ci
|
||||
|
||||
- name: Upload harness artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: harness-artifacts
|
||||
path: artifacts/harness
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
env:
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate extension bridge
|
||||
run: pnpm run ext:bridge
|
||||
|
||||
- name: Build
|
||||
run: pnpm run build:vite
|
||||
52
.github/workflows/comms-regression.yml
vendored
Normal file
52
.github/workflows/comms-regression.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Comms Regression
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'src/lib/api-client.ts'
|
||||
- 'src/lib/host-api.ts'
|
||||
- 'src/stores/gateway.ts'
|
||||
- 'src/stores/chat.ts'
|
||||
- 'electron/gateway/**'
|
||||
- 'electron/main/ipc-handlers.ts'
|
||||
- 'electron/utils/logger.ts'
|
||||
- 'scripts/comms/**'
|
||||
- 'tests/unit/gateway-events.test.ts'
|
||||
- '.github/workflows/comms-regression.yml'
|
||||
|
||||
jobs:
|
||||
comms-regression:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run comms replay
|
||||
run: pnpm run comms:replay
|
||||
|
||||
- name: Compare with baseline
|
||||
run: pnpm run comms:compare
|
||||
|
||||
- name: Upload comms artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: comms-regression-artifacts
|
||||
path: artifacts/comms
|
||||
70
.github/workflows/electron-e2e.yml
vendored
Normal file
70
.github/workflows/electron-e2e.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: Electron E2E
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
electron-e2e:
|
||||
name: Electron E2E (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- macos-latest
|
||||
- windows-latest
|
||||
env:
|
||||
CI: 'true'
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate extension bridge
|
||||
run: pnpm run ext:bridge
|
||||
|
||||
- name: Rebuild Electron binary
|
||||
run: pnpm rebuild electron
|
||||
|
||||
- name: Run Electron E2E on Linux
|
||||
if: runner.os == 'Linux'
|
||||
run: xvfb-run -a pnpm run test:e2e
|
||||
|
||||
- name: Run Electron E2E on macOS
|
||||
if: runner.os == 'macOS'
|
||||
run: pnpm run test:e2e
|
||||
|
||||
- name: Run Electron E2E on Windows
|
||||
if: runner.os == 'Windows'
|
||||
run: pnpm run test:e2e
|
||||
|
||||
- name: Upload Playwright artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-artifacts-${{ matrix.os }}
|
||||
path: |
|
||||
playwright-report
|
||||
test-results
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
49
.github/workflows/harness.yml
vendored
Normal file
49
.github/workflows/harness.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Harness
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'harness/**'
|
||||
- 'tests/unit/harness-specs.test.ts'
|
||||
- 'package.json'
|
||||
- 'pnpm-lock.yaml'
|
||||
- 'pnpm-workspace.yaml'
|
||||
- '.github/workflows/harness.yml'
|
||||
|
||||
jobs:
|
||||
harness:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD: '1'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run harness CI checks
|
||||
run: pnpm run harness:ci
|
||||
|
||||
- name: Upload harness artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: harness-artifacts
|
||||
path: artifacts/harness
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
89
.github/workflows/package-win-manual.yml
vendored
Normal file
89
.github/workflows/package-win-manual.yml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
name: Package Windows (Manual)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'Git ref to build (branch, tag, or SHA). Leave empty for current default ref.'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
package-windows:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.ref || github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Get pnpm store directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Download uv binaries for Windows
|
||||
run: pnpm run uv:download:win
|
||||
|
||||
- name: Build Windows package (no publish)
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: pnpm run package:win
|
||||
|
||||
- name: Upload Windows Installer (x64)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-installer-x64
|
||||
path: release/*-win-x64.exe
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload Windows Installer (arm64)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-installer-arm64
|
||||
path: release/*-win-arm64.exe
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload Windows Blockmap Files
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-blockmap
|
||||
path: release/*.blockmap
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload Windows Update Manifests
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-update-manifests
|
||||
path: |
|
||||
release/*.yml
|
||||
!release/builder-debug.yml
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
586
.github/workflows/release.yml
vendored
Normal file
586
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,586 @@
|
||||
# Makelore Release Workflow
|
||||
# Builds and publishes releases for macOS, Windows, and Linux
|
||||
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to release (e.g., 1.0.0)'
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
# Fails fast on tag pushes if package.json "version" does not match the tag.
|
||||
validate-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Assert tag matches package.json
|
||||
run: node scripts/assert-tag-matches-package.mjs
|
||||
|
||||
release:
|
||||
needs: validate-release
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: macos-latest
|
||||
platform: mac
|
||||
- os: windows-latest
|
||||
platform: win
|
||||
- os: ubuntu-latest
|
||||
platform: linux
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '24'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Download uv binaries for macOS
|
||||
if: matrix.platform == 'mac'
|
||||
run: pnpm run uv:download:mac
|
||||
|
||||
- name: Download uv binaries for Windows
|
||||
if: matrix.platform == 'win'
|
||||
run: pnpm run uv:download:win
|
||||
|
||||
- name: Download uv binaries for Linux
|
||||
if: matrix.platform == 'linux'
|
||||
run: pnpm run uv:download:linux
|
||||
|
||||
|
||||
# macOS specific steps
|
||||
- name: Free disk space (macOS)
|
||||
if: matrix.platform == 'mac'
|
||||
run: |
|
||||
echo "=== Disk usage before cleanup ==="
|
||||
df -h /
|
||||
# Remove large pre-installed toolchains not needed for Electron builds
|
||||
sudo rm -rf /usr/local/lib/android || true
|
||||
sudo rm -rf /usr/share/dotnet || true
|
||||
sudo rm -rf /usr/local/share/powershell || true
|
||||
sudo rm -rf /usr/local/share/chromium || true
|
||||
sudo rm -rf /usr/local/lib/node_modules || true
|
||||
rm -rf ~/Library/Caches/electron-builder/dmg-builder* || true
|
||||
# Homebrew cleanup
|
||||
brew cleanup --prune=all 2>/dev/null || true
|
||||
echo "=== Disk usage after cleanup ==="
|
||||
df -h /
|
||||
|
||||
# --publish never: prevent electron-builder from auto-publishing to GitHub.
|
||||
# All artifacts are collected and published atomically in the publish job.
|
||||
- name: Build macOS
|
||||
if: matrix.platform == 'mac'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.MAC_CERTS }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTS_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
run: |
|
||||
ulimit -n 65536
|
||||
echo "File descriptor limit: $(ulimit -n)"
|
||||
pnpm run package:mac
|
||||
|
||||
# Windows specific steps
|
||||
- name: Build Windows
|
||||
if: matrix.platform == 'win'
|
||||
run: pnpm run package:win
|
||||
|
||||
# Detect release channel from tag to skip code signing for alpha/beta builds
|
||||
- name: Detect Windows release channel
|
||||
if: matrix.platform == 'win'
|
||||
id: win-channel
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
|
||||
TAG="${GITHUB_REF#refs/tags/v}"
|
||||
else
|
||||
TAG="${{ github.event.inputs.version }}"
|
||||
fi
|
||||
if [[ "$TAG" =~ (alpha|beta) ]]; then
|
||||
echo "is_stable=false" >> $GITHUB_OUTPUT
|
||||
echo "Channel: prerelease ($TAG) — skipping code signing"
|
||||
else
|
||||
echo "is_stable=true" >> $GITHUB_OUTPUT
|
||||
echo "Channel: stable ($TAG) — will sign"
|
||||
fi
|
||||
|
||||
- name: Validate unsigned Windows artifacts before SignPath
|
||||
if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$unsignedExeFiles = Get-ChildItem -Path "release" -Filter *.exe -File
|
||||
if (-not $unsignedExeFiles) {
|
||||
throw "No unsigned .exe files found in release/ before SignPath upload"
|
||||
}
|
||||
$unsignedCount = $unsignedExeFiles.Count
|
||||
"UNSIGNED_EXE_COUNT=$unsignedCount" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
Write-Host "Found $unsignedCount unsigned .exe file(s):"
|
||||
$unsignedExeFiles | ForEach-Object { Write-Host " - $($_.Name)" }
|
||||
|
||||
- name: Upload unsigned Windows artifacts for SignPath
|
||||
if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true'
|
||||
id: upload-unsigned-windows-artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unsigned-win-exe-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: release/*.exe
|
||||
retention-days: 1
|
||||
|
||||
- name: Sign Windows artifacts via SignPath
|
||||
if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true'
|
||||
id: signpath-sign-windows
|
||||
uses: signpath/github-action-submit-signing-request@v2
|
||||
with:
|
||||
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||
organization-id: "78e37079-23df-4800-b41c-33312ad7c1e3"
|
||||
project-slug: "ValueCell"
|
||||
signing-policy-slug: "ValueCell-sign"
|
||||
github-artifact-id: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }}
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: release/signed
|
||||
|
||||
- name: Replace unsigned executables with signed ones
|
||||
if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true'
|
||||
shell: pwsh
|
||||
run: |
|
||||
Write-Host "SignPath GitHub artifact ID: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }}"
|
||||
$signedExeFiles = Get-ChildItem -Path "release/signed" -Filter *.exe -File -Recurse
|
||||
if (-not $signedExeFiles) {
|
||||
throw "No signed .exe files found in release/signed"
|
||||
}
|
||||
$signedCount = $signedExeFiles.Count
|
||||
if ($env:UNSIGNED_EXE_COUNT -and ($signedCount -ne [int]$env:UNSIGNED_EXE_COUNT)) {
|
||||
throw "Signed .exe count ($signedCount) does not match unsigned count ($env:UNSIGNED_EXE_COUNT)"
|
||||
}
|
||||
foreach ($file in $signedExeFiles) {
|
||||
Copy-Item -Path $file.FullName -Destination "release/$($file.Name)" -Force
|
||||
}
|
||||
$finalExeFiles = Get-ChildItem -Path "release" -Filter *.exe -File
|
||||
if ($env:UNSIGNED_EXE_COUNT -and ($finalExeFiles.Count -ne [int]$env:UNSIGNED_EXE_COUNT)) {
|
||||
throw "Final release .exe count ($($finalExeFiles.Count)) does not match unsigned count ($env:UNSIGNED_EXE_COUNT)"
|
||||
}
|
||||
Write-Host "Signed executables copied to release/ ($($finalExeFiles.Count) file(s))"
|
||||
|
||||
# Code signing changes the .exe binary, invalidating the sha512 hash that
|
||||
# electron-builder wrote into latest.yml during the initial build.
|
||||
# Recalculate the hash for each signed .exe and patch the yml files so
|
||||
# electron-updater can verify the download successfully.
|
||||
#
|
||||
# Actual latest.yml structure (from electron-builder NSIS):
|
||||
# files:
|
||||
# - url: Makelore-0.2.4-win-x64.exe ← files[] entries have url/sha512/size
|
||||
# sha512: <base64>
|
||||
# size: 430775882
|
||||
# path: Makelore-0.2.4-win-arm64.exe ← top-level has path/sha512 (no size!)
|
||||
# sha512: <base64>
|
||||
# releaseDate: '...'
|
||||
- name: Update latest.yml sha512 after code signing
|
||||
if: matrix.platform == 'win' && steps.win-channel.outputs.is_stable == 'true'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ymlFiles = Get-ChildItem -Path "release" -Filter "*.yml" -File | Where-Object { $_.Name -ne "builder-debug.yml" }
|
||||
$exeFiles = Get-ChildItem -Path "release" -Filter "*.exe" -File
|
||||
|
||||
foreach ($yml in $ymlFiles) {
|
||||
$content = Get-Content $yml.FullName -Raw
|
||||
$modified = $false
|
||||
|
||||
foreach ($exe in $exeFiles) {
|
||||
# Compute new sha512 (base64) for the signed exe
|
||||
$hash = Get-FileHash -Path $exe.FullName -Algorithm SHA512
|
||||
$hashBytes = [byte[]]::new($hash.Hash.Length / 2)
|
||||
for ($i = 0; $i -lt $hashBytes.Length; $i++) {
|
||||
$hashBytes[$i] = [Convert]::ToByte($hash.Hash.Substring($i * 2, 2), 16)
|
||||
}
|
||||
$newSha512 = [Convert]::ToBase64String($hashBytes)
|
||||
$newSize = (Get-Item $exe.FullName).Length
|
||||
$escapedName = [Regex]::Escape($exe.Name)
|
||||
|
||||
# 1) files[] entries: url: <name>\n sha512: <hash>\n size: <n>
|
||||
$urlPattern = "(?m)(url:\s*${escapedName}\s*\r?\n\s*sha512:\s*)(\S+)(\s*\r?\n\s*size:\s*)(\d+)"
|
||||
if ($content -match $urlPattern) {
|
||||
$content = $content -replace $urlPattern, "`${1}${newSha512}`${3}${newSize}"
|
||||
$modified = $true
|
||||
Write-Host "Updated $($yml.Name) files[]: $($exe.Name) sha512=$newSha512 size=$newSize"
|
||||
}
|
||||
|
||||
# 2) Top-level entry: path: <name>\nsha512: <hash>\n (no size field)
|
||||
$pathPattern = "(?m)(path:\s*${escapedName}\s*\r?\n)sha512:\s*\S+"
|
||||
if ($content -match $pathPattern) {
|
||||
$content = $content -replace $pathPattern, "`${1}sha512: ${newSha512}"
|
||||
$modified = $true
|
||||
Write-Host "Updated $($yml.Name) top-level: $($exe.Name) sha512=$newSha512"
|
||||
}
|
||||
}
|
||||
|
||||
if ($modified) {
|
||||
Set-Content -Path $yml.FullName -Value $content -NoNewline
|
||||
Write-Host "Saved updated $($yml.Name)"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "=== Final yml contents ==="
|
||||
foreach ($yml in $ymlFiles) {
|
||||
Write-Host "--- $($yml.Name) ---"
|
||||
Get-Content $yml.FullName
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# Linux specific steps
|
||||
- name: Build Linux
|
||||
if: matrix.platform == 'linux'
|
||||
run: pnpm run package:linux
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-${{ matrix.platform }}
|
||||
path: |
|
||||
release/*.dmg
|
||||
release/*.zip
|
||||
release/*.blockmap
|
||||
release/*.exe
|
||||
release/*.AppImage
|
||||
release/*.deb
|
||||
release/*.rpm
|
||||
release/*.yml
|
||||
!release/builder-debug.yml
|
||||
retention-days: 7
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Job: Publish to GitHub Releases
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
publish:
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Download release artifacts only
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: release-artifacts
|
||||
pattern: release-*
|
||||
|
||||
- name: List all downloaded artifacts
|
||||
run: |
|
||||
echo "=== All artifacts downloaded ==="
|
||||
find release-artifacts/ -type f -exec ls -lh {} \;
|
||||
echo ""
|
||||
echo "=== File tree ==="
|
||||
tree release-artifacts/ || find release-artifacts/ -print
|
||||
|
||||
- name: Remove duplicate builder-debug files
|
||||
run: |
|
||||
echo "Removing builder-debug.yml files to avoid duplicate asset upload conflicts..."
|
||||
find release-artifacts/ -name "builder-debug.yml" -delete -print || true
|
||||
|
||||
- name: Create GitHub Release (as pre-release)
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
files: |
|
||||
release-artifacts/**/*.dmg
|
||||
release-artifacts/**/*.zip
|
||||
release-artifacts/**/*.exe
|
||||
release-artifacts/**/*.AppImage
|
||||
release-artifacts/**/*.deb
|
||||
release-artifacts/**/*.rpm
|
||||
release-artifacts/**/*.yml
|
||||
draft: false
|
||||
prerelease: true
|
||||
make_latest: false
|
||||
generate_release_notes: true
|
||||
body: |
|
||||
## 🚀 Makelore ${{ github.ref_name }}
|
||||
|
||||
Makelore - AI creation workspace
|
||||
|
||||
### 📦 Downloads
|
||||
|
||||
Please select the appropriate installer for your operating system and architecture:
|
||||
|
||||
#### macOS
|
||||
- **Apple Silicon (M1/M2/M3/M4)**: `Makelore-*-mac-arm64.dmg`
|
||||
- **Intel (x64)**: `Makelore-*-mac-x64.dmg`
|
||||
|
||||
#### Windows
|
||||
- **Installer (x64)**: `Makelore-*-win-x64.exe`
|
||||
- **Installer (ARM64)**: `Makelore-*-win-arm64.exe`
|
||||
|
||||
#### Linux
|
||||
- **AppImage (x64)**: `Makelore-*-linux-x86_64.AppImage` (Universal format, recommended)
|
||||
- **AppImage (ARM64)**: `Makelore-*-linux-arm64.AppImage`
|
||||
- **Debian/Ubuntu (x64)**: `Makelore-*-linux-amd64.deb`
|
||||
- **Debian/Ubuntu (ARM64)**: `Makelore-*-linux-arm64.deb`
|
||||
- **RPM (x64)**: `Makelore-*-linux-x86_64.rpm`
|
||||
|
||||
### 📝 Release Notes
|
||||
|
||||
See the auto-generated release notes below for detailed changes.
|
||||
|
||||
### ⚠️ Installation Notes
|
||||
|
||||
- **macOS**: On first launch, you may see "cannot verify developer". Go to System Preferences → Security & Privacy to allow the app to run
|
||||
- **Windows**: SmartScreen may block the app. Click "More info" → "Run anyway" to proceed
|
||||
- **Linux AppImage**: First run `chmod +x Makelore-*.AppImage` to add execute permission. On Ubuntu 22.04 you may also need `sudo apt install libfuse2`; on Ubuntu 24.04 use `sudo apt install libfuse2t64`
|
||||
- **Linux .deb (Ubuntu 24.04)**: If installation fails due to missing dependencies, use `sudo apt install libgtk-3-0t64 libnotify4t64 libxss1t64` before installing
|
||||
|
||||
---
|
||||
|
||||
💬 Found an issue? Please submit an [Issue](https://github.com/${{ github.repository }}/issues)
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Job: Upload to Alibaba Cloud OSS
|
||||
# Uploads all release artifacts to OSS for:
|
||||
# - Official website downloads (via release-info.json)
|
||||
# - electron-updater auto-update (via {channel}-*.yml)
|
||||
#
|
||||
# Directory structure on OSS (channel-separated):
|
||||
# latest/ → stable releases (latest.yml, latest-mac.yml, …)
|
||||
# alpha/ → alpha releases (alpha.yml, alpha-mac.yml, …)
|
||||
# beta/ → beta releases (beta.yml, beta-mac.yml, …)
|
||||
# releases/vX.Y.Z/ → permanent archive, never deleted
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
upload-oss:
|
||||
needs: release
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Download release artifacts only
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: release-artifacts
|
||||
pattern: release-*
|
||||
|
||||
- name: Extract version and channel
|
||||
id: version
|
||||
run: |
|
||||
if [[ "${{ github.ref }}" == refs/tags/v* ]]; then
|
||||
VERSION="${GITHUB_REF#refs/tags/v}"
|
||||
else
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
fi
|
||||
|
||||
# Detect channel from semver prerelease tag
|
||||
# e.g. 0.1.8-alpha.0 → alpha, 1.0.0-beta.1 → beta, 1.0.0 → latest
|
||||
if [[ "$VERSION" =~ -([a-zA-Z]+) ]]; then
|
||||
CHANNEL="${BASH_REMATCH[1]}"
|
||||
else
|
||||
CHANNEL="latest"
|
||||
fi
|
||||
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "tag=v${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "channel=${CHANNEL}" >> $GITHUB_OUTPUT
|
||||
echo "Detected version: ${VERSION}, channel: ${CHANNEL}"
|
||||
|
||||
- name: Prepare upload directories
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
TAG="${{ steps.version.outputs.tag }}"
|
||||
CHANNEL="${{ steps.version.outputs.channel }}"
|
||||
|
||||
mkdir -p staging/${CHANNEL}
|
||||
mkdir -p staging/releases/${TAG}
|
||||
|
||||
# Flatten all platform artifacts into staging directories
|
||||
find release-artifacts/ -type f | while read file; do
|
||||
filename=$(basename "$file")
|
||||
cp "$file" "staging/${CHANNEL}/${filename}"
|
||||
cp "$file" "staging/releases/${TAG}/${filename}"
|
||||
done
|
||||
|
||||
echo "=== staging/${CHANNEL}/ ==="
|
||||
ls -lh staging/${CHANNEL}/
|
||||
echo ""
|
||||
echo "=== staging/releases/${TAG}/ ==="
|
||||
ls -lh staging/releases/${TAG}/
|
||||
|
||||
# Note: Do NOT rename yml files. electron-updater (generic provider) always
|
||||
# requests "latest-mac.yml", "latest.yml", etc. regardless of feed URL.
|
||||
# Channel separation is achieved by directory: /alpha/, /beta/, /latest/.
|
||||
- name: Verify yml files present
|
||||
run: |
|
||||
CHANNEL="${{ steps.version.outputs.channel }}"
|
||||
echo "=== staging/${CHANNEL}/ (update metadata) ==="
|
||||
ls -la staging/${CHANNEL}/*.yml 2>/dev/null || echo "No yml files found (check electron-builder outputs)"
|
||||
|
||||
- name: Generate release-info.json
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
CHANNEL="${{ steps.version.outputs.channel }}"
|
||||
BASE_URL="https://oss.intelli-spectrum.com/${CHANNEL}"
|
||||
|
||||
jq -n \
|
||||
--arg version "$VERSION" \
|
||||
--arg channel "$CHANNEL" \
|
||||
--arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
--arg base "$BASE_URL" \
|
||||
--arg changelog "https://github.com/${{ github.repository }}/releases/tag/v${VERSION}" \
|
||||
'{
|
||||
version: $version,
|
||||
channel: $channel,
|
||||
releaseDate: $date,
|
||||
downloads: {
|
||||
mac: {
|
||||
x64: ($base + "/Makelore-" + $version + "-mac-x64.dmg"),
|
||||
arm64: ($base + "/Makelore-" + $version + "-mac-arm64.dmg")
|
||||
},
|
||||
win: {
|
||||
x64: ($base + "/Makelore-" + $version + "-win-x64.exe"),
|
||||
arm64: ($base + "/Makelore-" + $version + "-win-arm64.exe")
|
||||
},
|
||||
linux: {
|
||||
deb_amd64: ($base + "/Makelore-" + $version + "-linux-amd64.deb"),
|
||||
deb_arm64: ($base + "/Makelore-" + $version + "-linux-arm64.deb"),
|
||||
appimage_x64: ($base + "/Makelore-" + $version + "-linux-x86_64.AppImage"),
|
||||
appimage_arm64: ($base + "/Makelore-" + $version + "-linux-arm64.AppImage"),
|
||||
rpm_x64: ($base + "/Makelore-" + $version + "-linux-x86_64.rpm")
|
||||
}
|
||||
},
|
||||
changelog: $changelog
|
||||
}' > staging/${CHANNEL}/release-info.json
|
||||
|
||||
echo "=== release-info.json ==="
|
||||
cat staging/${CHANNEL}/release-info.json
|
||||
|
||||
- name: Install and configure ossutil
|
||||
env:
|
||||
OSS_ACCESS_KEY_ID: ${{ secrets.OSS_ACCESS_KEY_ID }}
|
||||
OSS_ACCESS_KEY_SECRET: ${{ secrets.OSS_ACCESS_KEY_SECRET }}
|
||||
run: |
|
||||
curl -sL https://gosspublic.alicdn.com/ossutil/install.sh | sudo bash
|
||||
|
||||
# Write config file for non-interactive use
|
||||
cat > $HOME/.ossutilconfig << EOF
|
||||
[Credentials]
|
||||
language=EN
|
||||
endpoint=oss-cn-hangzhou.aliyuncs.com
|
||||
accessKeyID=${OSS_ACCESS_KEY_ID}
|
||||
accessKeySecret=${OSS_ACCESS_KEY_SECRET}
|
||||
EOF
|
||||
|
||||
ossutil --version
|
||||
|
||||
- name: "Upload to OSS: {channel}/ (overwrite)"
|
||||
run: |
|
||||
CHANNEL="${{ steps.version.outputs.channel }}"
|
||||
|
||||
# Only clean the current channel's directory — never touch other channels
|
||||
ossutil rm -r -f oss://valuecell-niancode/${CHANNEL}/ || true
|
||||
|
||||
# Upload all files with no-cache so clients always get the freshest version
|
||||
ossutil cp -r -f \
|
||||
--meta="Cache-Control:no-cache,no-store,must-revalidate" \
|
||||
staging/${CHANNEL}/ \
|
||||
oss://valuecell-niancode/${CHANNEL}/
|
||||
|
||||
echo "Uploaded to ${CHANNEL}/"
|
||||
|
||||
- name: "Upload to OSS: releases/vX.Y.Z/ (archive)"
|
||||
run: |
|
||||
TAG="${{ steps.version.outputs.tag }}"
|
||||
|
||||
# Upload to permanent archive (long cache, immutable)
|
||||
ossutil cp -r \
|
||||
staging/releases/${TAG}/ \
|
||||
oss://valuecell-niancode/releases/${TAG}/ \
|
||||
--meta "Cache-Control:public,max-age=31536000,immutable"
|
||||
|
||||
echo "Uploaded to releases/${TAG}/"
|
||||
|
||||
- name: Verify OSS upload
|
||||
run: |
|
||||
TAG="${{ steps.version.outputs.tag }}"
|
||||
CHANNEL="${{ steps.version.outputs.channel }}"
|
||||
|
||||
echo "=== ${CHANNEL}/ ==="
|
||||
ossutil ls oss://valuecell-niancode/${CHANNEL}/ --short
|
||||
|
||||
echo ""
|
||||
echo "=== releases/${TAG}/ ==="
|
||||
ossutil ls oss://valuecell-niancode/releases/${TAG}/ --short
|
||||
|
||||
echo ""
|
||||
echo "=== Verify release-info.json ==="
|
||||
ossutil cp oss://valuecell-niancode/${CHANNEL}/release-info.json /tmp/release-info.json -f
|
||||
jq . /tmp/release-info.json
|
||||
|
||||
echo ""
|
||||
echo "=== Verify update yml ==="
|
||||
if [ "${CHANNEL}" = "latest" ]; then
|
||||
YML_PREFIX="latest"
|
||||
else
|
||||
YML_PREFIX="${CHANNEL}"
|
||||
fi
|
||||
echo "electron-updater expects ${YML_PREFIX}-mac.yml, ${YML_PREFIX}.yml, etc. in ${CHANNEL}/:"
|
||||
ossutil ls oss://valuecell-niancode/${CHANNEL}/ --short | grep "${YML_PREFIX}.*\\.yml" || echo "(none found)"
|
||||
|
||||
echo ""
|
||||
echo "All files uploaded and verified successfully!"
|
||||
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
# Job: Finalize Release
|
||||
# Promotes the GitHub Release from pre-release to latest AFTER
|
||||
# both GitHub Release assets and OSS uploads are fully complete.
|
||||
# This ensures /releases/latest API never returns an incomplete
|
||||
# release — the website and electron-updater only see it when
|
||||
# all platform artifacts are ready.
|
||||
# ──────────────────────────────────────────────────────────────
|
||||
finalize:
|
||||
needs: [publish, upload-oss]
|
||||
runs-on: ubuntu-latest
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
||||
steps:
|
||||
- name: Promote release from pre-release to latest
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
IS_PRERELEASE_CHANNEL=false
|
||||
|
||||
if [[ "$TAG" == *"alpha"* ]] || [[ "$TAG" == *"beta"* ]]; then
|
||||
IS_PRERELEASE_CHANNEL=true
|
||||
fi
|
||||
|
||||
if [ "$IS_PRERELEASE_CHANNEL" = "true" ]; then
|
||||
echo "Tag $TAG is an alpha/beta release — keeping as pre-release."
|
||||
else
|
||||
echo "Promoting $TAG from pre-release to latest release..."
|
||||
gh release edit "$TAG" \
|
||||
--prerelease=false \
|
||||
--latest \
|
||||
--repo "${{ github.repository }}"
|
||||
echo "Release $TAG is now the latest release."
|
||||
fi
|
||||
104
.github/workflows/win-build-test.yml
vendored
Normal file
104
.github/workflows/win-build-test.yml
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
name: Windows Build Test
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version for SignPath parameter (e.g., 1.0.0-test.1)"
|
||||
required: false
|
||||
default: "dev"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
|
||||
jobs:
|
||||
windows-build-sign:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Download uv binaries for Windows
|
||||
run: pnpm run uv:download:win
|
||||
|
||||
- name: Build Windows
|
||||
run: pnpm run package:win
|
||||
|
||||
- name: Validate unsigned Windows artifacts before SignPath
|
||||
shell: pwsh
|
||||
run: |
|
||||
$unsignedExeFiles = Get-ChildItem -Path "release" -Filter *.exe -File
|
||||
if (-not $unsignedExeFiles) {
|
||||
throw "No unsigned .exe files found in release/ before SignPath upload"
|
||||
}
|
||||
$unsignedCount = $unsignedExeFiles.Count
|
||||
"UNSIGNED_EXE_COUNT=$unsignedCount" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
Write-Host "Found $unsignedCount unsigned .exe file(s):"
|
||||
$unsignedExeFiles | ForEach-Object { Write-Host " - $($_.Name)" }
|
||||
|
||||
# Required by SignPath Trusted Build: artifact must exist on GitHub first.
|
||||
- name: Upload unsigned Windows artifacts for SignPath
|
||||
id: upload-unsigned-windows-artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unsigned-win-exe-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: release/*.exe
|
||||
retention-days: 1
|
||||
|
||||
- name: Sign Windows artifacts via SignPath
|
||||
id: signpath-sign-windows
|
||||
uses: signpath/github-action-submit-signing-request@v2
|
||||
with:
|
||||
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||
organization-id: "78e37079-23df-4800-b41c-33312ad7c1e3"
|
||||
project-slug: "ValueCell"
|
||||
signing-policy-slug: "ValueCell-sign"
|
||||
github-artifact-id: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }}
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: release/signed
|
||||
|
||||
- name: Replace unsigned executables with signed ones
|
||||
shell: pwsh
|
||||
run: |
|
||||
Write-Host "SignPath GitHub artifact ID: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }}"
|
||||
$signedExeFiles = Get-ChildItem -Path "release/signed" -Filter *.exe -File -Recurse
|
||||
if (-not $signedExeFiles) {
|
||||
throw "No signed .exe files found in release/signed"
|
||||
}
|
||||
$signedCount = $signedExeFiles.Count
|
||||
if ($env:UNSIGNED_EXE_COUNT -and ($signedCount -ne [int]$env:UNSIGNED_EXE_COUNT)) {
|
||||
throw "Signed .exe count ($signedCount) does not match unsigned count ($env:UNSIGNED_EXE_COUNT)"
|
||||
}
|
||||
foreach ($file in $signedExeFiles) {
|
||||
Copy-Item -Path $file.FullName -Destination "release/$($file.Name)" -Force
|
||||
}
|
||||
$finalExeFiles = Get-ChildItem -Path "release" -Filter *.exe -File
|
||||
if ($env:UNSIGNED_EXE_COUNT -and ($finalExeFiles.Count -ne [int]$env:UNSIGNED_EXE_COUNT)) {
|
||||
throw "Final release .exe count ($($finalExeFiles.Count)) does not match unsigned count ($env:UNSIGNED_EXE_COUNT)"
|
||||
}
|
||||
Write-Host "Signed executables copied to release/ ($($finalExeFiles.Count) file(s))"
|
||||
|
||||
- name: Upload signed Windows artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: signed-win-exe-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: |
|
||||
release/*.exe
|
||||
release/latest*.yml
|
||||
retention-days: 7
|
||||
Reference in New Issue
Block a user