test(ci): exercise locked toolchain build consumers
This commit is contained in:
9
.github/workflows/deploy-site.yml
vendored
9
.github/workflows/deploy-site.yml
vendored
@@ -63,17 +63,12 @@ jobs:
|
||||
client-id: ${{ vars.APP_CLIENT_ID }}
|
||||
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up locked Node and npm
|
||||
id: node
|
||||
- name: Set up the locked site toolchain
|
||||
uses: ./.github/actions/setup-pm
|
||||
with:
|
||||
toolchain: node
|
||||
toolchain: all
|
||||
node-cache-dependency-path: website/package-lock.json
|
||||
|
||||
- uses: ./.github/actions/setup-pm
|
||||
with:
|
||||
cache-python: true
|
||||
|
||||
- name: Install PyYAML for skill extraction
|
||||
uses: ./.github/actions/retry
|
||||
with:
|
||||
|
||||
9
.github/workflows/docs-site-checks.yml
vendored
9
.github/workflows/docs-site-checks.yml
vendored
@@ -13,11 +13,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set up locked Node and npm
|
||||
id: node
|
||||
- name: Set up the locked site toolchain
|
||||
uses: ./.github/actions/setup-pm
|
||||
with:
|
||||
toolchain: node
|
||||
toolchain: all
|
||||
node-cache-dependency-path: website/package-lock.json
|
||||
|
||||
- name: Install website dependencies
|
||||
@@ -26,10 +25,6 @@ jobs:
|
||||
command: npm ci
|
||||
working-directory: website
|
||||
|
||||
- uses: ./.github/actions/setup-pm
|
||||
with:
|
||||
cache-python: true
|
||||
|
||||
- name: Install ascii-guard
|
||||
uses: ./.github/actions/retry
|
||||
with:
|
||||
|
||||
15
.github/workflows/e2e-desktop.yml
vendored
15
.github/workflows/e2e-desktop.yml
vendored
@@ -39,12 +39,12 @@ jobs:
|
||||
libgtk-3-0 libnotify4 libnss3 libxss1 libxtst6 \
|
||||
xdg-utils libatspi2.0-0 libdrm2 libgbm1 libasound2t64
|
||||
|
||||
# ── Node ───────────────────────────────────────────────────────────
|
||||
- name: Set up locked Node and npm
|
||||
id: node
|
||||
- name: Set up the locked desktop toolchain
|
||||
uses: ./.github/actions/setup-pm
|
||||
with:
|
||||
toolchain: node
|
||||
toolchain: all
|
||||
extras: '["all", "dev"]'
|
||||
prune-python-cache: true
|
||||
|
||||
# Full npm ci (not --ignore-scripts): electron's postinstall
|
||||
# downloads the binary we launch, and node-pty's native build is
|
||||
@@ -53,13 +53,6 @@ jobs:
|
||||
with:
|
||||
command: npm ci
|
||||
|
||||
# ── Python (for the hermes serve backend) ──────────────────────────
|
||||
- name: Set up locked Python and backend dependencies
|
||||
uses: ./.github/actions/setup-pm
|
||||
with:
|
||||
extras: '["all", "dev"]'
|
||||
prune-python-cache: true
|
||||
|
||||
# ── Build desktop app ─────────────────────────────────────────────
|
||||
# The Playwright step below runs `npm run build` before testing so
|
||||
# dist/ is always fresh — no separate build step needed here.
|
||||
|
||||
2
.github/workflows/pm-toolchain-smoke.yml
vendored
2
.github/workflows/pm-toolchain-smoke.yml
vendored
@@ -79,7 +79,7 @@ jobs:
|
||||
flags=()
|
||||
if [ "$EXPECT_WARM" = true ]; then flags+=(--offline); fi
|
||||
npm ci --workspace tests-js --include-workspace-root --include=dev --ignore-scripts --no-audit --no-fund ${flags[@]+"${flags[@]}"}
|
||||
node node_modules/vitest/vitest.mjs run --root tests-js setup-pm-post.test.mjs
|
||||
node node_modules/vitest/vitest.mjs run --root tests-js setup-pm-post.test.mjs generate-icons.test.mjs
|
||||
|
||||
- name: Run the PM and action contracts
|
||||
shell: bash
|
||||
|
||||
24
.github/workflows/pm-toolchain.yml
vendored
24
.github/workflows/pm-toolchain.yml
vendored
@@ -39,3 +39,27 @@ jobs:
|
||||
prune-python-cache: true
|
||||
cache-suffix: smoke-prune-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
- run: python -c 'import pytest; print(pytest.__version__)'
|
||||
build-consumers:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: ./.github/actions/setup-pm
|
||||
with:
|
||||
toolchain: all
|
||||
cache-suffix: smoke-consumers-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
- name: Install the docs tools into the command environment
|
||||
run: |
|
||||
uv pip install --python "$HERMES_PYTHON" ascii-guard==2.3.0 pyyaml==6.0.3 httpx==0.28.1
|
||||
python3 -c 'import yaml,httpx; print(yaml.__version__, httpx.__version__)'
|
||||
- name: Install the locked desktop and test workspaces
|
||||
run: npm ci --workspace apps/desktop --workspace tests-js --include-workspace-root --include=dev --ignore-scripts --no-audit --no-fund
|
||||
- name: Exercise the actual after-pack relocation hook
|
||||
run: node node_modules/vitest/vitest.mjs run --root tests-js after-pack-toolchain.test.mjs generate-icons.test.mjs setup-pm-post.test.mjs
|
||||
- name: Exercise the payload and icon build entrypoints
|
||||
run: |
|
||||
npm run payload --workspace apps/desktop -- --help
|
||||
node scripts/generate-icons.mjs
|
||||
node scripts/generate-icons.mjs --check
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"builder": "cross-env NODE_OPTIONS=--max-old-space-size=16384 node scripts/run-electron-builder.mjs",
|
||||
"pack": "npm run build && npm run builder -- --dir --publish never",
|
||||
"dist": "npm run build && npm run builder",
|
||||
"payload": "uv run --no-project --python 3.11 python ../../scripts/bundles/stage.py --out build/agent-payload",
|
||||
"payload": "uv run --no-project python ../../scripts/bundles/stage.py --out build/agent-payload",
|
||||
"dist:bundled": "npm run payload && cross-env HERMES_DESKTOP_VARIANT=bundled npm run dist",
|
||||
"dist:mac": "npm run build && npm run builder -- --mac",
|
||||
"dist:mac:dmg": "npm run build && npm run builder -- --mac dmg",
|
||||
|
||||
@@ -24,7 +24,7 @@ export function generateIcons(args = [], { root = repoRoot, run = spawnSync, env
|
||||
delete childEnv.PYTHONPATH
|
||||
delete childEnv.PYTHONHOME
|
||||
const result = run('uv', [
|
||||
'run', '--isolated', '--locked', '--only-group', 'icon-build', '--python', '3.14',
|
||||
'run', '--isolated', '--locked', '--only-group', 'icon-build',
|
||||
// PM copies its wheel cache into payloads. Keep build wheels outside it.
|
||||
'--cache-dir', path.join(root, '.cache', 'icon-build'),
|
||||
'python', path.join(root, 'scripts', 'generate_icons.py'), ...args
|
||||
|
||||
43
tests-js/after-pack-toolchain.test.mjs
Normal file
43
tests-js/after-pack-toolchain.test.mjs
Normal file
@@ -0,0 +1,43 @@
|
||||
import { execFileSync } from 'node:child_process'
|
||||
import { mkdirSync, mkdtempSync, readFileSync, readlinkSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { expect, test } from 'vitest'
|
||||
import afterPack from '../apps/desktop/scripts/after-pack.mjs'
|
||||
|
||||
// This is the Linux afterPack path on a real Linux host, not a fake host flag.
|
||||
// macOS adds Developer ID signing; its native release lane owns that proof.
|
||||
test.runIf(process.platform === 'linux')('afterPack uses the provisioned Python to repair actual payload links', async () => {
|
||||
const directory = mkdtempSync(path.join(tmpdir(), 'after-pack-toolchain-'))
|
||||
const payload = path.join(directory, 'resources', 'agent-payload')
|
||||
const store = path.join(payload, 'tools', 'python', 'bin')
|
||||
const venv = path.join(payload, 'venv', 'bin')
|
||||
const python = execFileSync('python3', ['-c', 'import sys; print(sys.executable)'], { encoding: 'utf8' }).trim()
|
||||
const previous = { UV_PYTHON: process.env.UV_PYTHON, UV_PYTHON_DOWNLOADS: process.env.UV_PYTHON_DOWNLOADS, PYTHONPATH: process.env.PYTHONPATH }
|
||||
const observed = path.join(directory, 'interpreter.json')
|
||||
const identity = 'import json,os,sys; print(json.dumps(os.path.realpath(sys.executable)))'
|
||||
const expected = JSON.parse(execFileSync(python, ['-c', identity], { encoding: 'utf8' }))
|
||||
try {
|
||||
// Record the interpreter that actually executes the relocation script.
|
||||
writeFileSync(path.join(directory, 'sitecustomize.py'), `import json,os,sys\nfrom pathlib import Path\nPath(${JSON.stringify(observed)}).write_text(json.dumps(os.path.realpath(sys.executable)), encoding="utf-8")\n`)
|
||||
process.env.PYTHONPATH = directory
|
||||
process.env.UV_PYTHON = python
|
||||
process.env.UV_PYTHON_DOWNLOADS = 'never'
|
||||
mkdirSync(store, { recursive: true })
|
||||
mkdirSync(venv, { recursive: true })
|
||||
writeFileSync(path.join(payload, 'manifest.json'), '{}')
|
||||
writeFileSync(path.join(store, 'python3'), 'payload interpreter link target')
|
||||
symlinkSync('/builder/tools/python/bin/python3', path.join(venv, 'python'))
|
||||
symlinkSync('python', path.join(venv, 'python3'))
|
||||
await afterPack({ electronPlatformName: process.platform, appOutDir: directory })
|
||||
expect(JSON.parse(readFileSync(observed, 'utf8'))).toBe(expected)
|
||||
expect(readlinkSync(path.join(venv, 'python'))).toBe('../../tools/python/bin/python3')
|
||||
expect(readFileSync(path.join(venv, 'python3'), 'utf8')).toBe('payload interpreter link target')
|
||||
} finally {
|
||||
for (const [key, value] of Object.entries(previous)) {
|
||||
if (value === undefined) delete process.env[key]
|
||||
else process.env[key] = value
|
||||
}
|
||||
rmSync(directory, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
@@ -5,13 +5,13 @@ import { generateIcons } from '../scripts/generate-icons.mjs'
|
||||
test('icon builds use only the locked build group outside every application venv', () => {
|
||||
const run = vi.fn(() => ({ status: 0 }))
|
||||
const root = path.resolve('icon-build-fixture')
|
||||
const env = { PATH: 'tools', VIRTUAL_ENV: 'runtime-venv', PYTHONPATH: 'payload-libraries', PYTHONHOME: 'payload-python' }
|
||||
const env = { PATH: 'tools', UV_PYTHON: 'pm-locked-python', VIRTUAL_ENV: 'runtime-venv', PYTHONPATH: 'payload-libraries', PYTHONHOME: 'payload-python' }
|
||||
expect(generateIcons(['--check'], { root, run, env })).toBe(0)
|
||||
expect(run).toHaveBeenCalledExactlyOnceWith('uv', [
|
||||
'run', '--isolated', '--locked', '--only-group', 'icon-build', '--python', '3.11',
|
||||
'run', '--isolated', '--locked', '--only-group', 'icon-build',
|
||||
'--cache-dir', path.join(root, '.cache', 'icon-build'),
|
||||
'python', path.join(root, 'scripts', 'generate_icons.py'), '--check'
|
||||
], { cwd: root, stdio: 'inherit', windowsHide: true, env: { PATH: 'tools', VIRTUAL_ENV: 'runtime-venv' } })
|
||||
], { cwd: root, stdio: 'inherit', windowsHide: true, env: { PATH: 'tools', UV_PYTHON: env.UV_PYTHON, VIRTUAL_ENV: 'runtime-venv' } })
|
||||
expect(env.PYTHONPATH).toBe('payload-libraries')
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user