-
Notifications
You must be signed in to change notification settings - Fork 1
153 lines (140 loc) · 6.9 KB
/
Copy pathci.yml
File metadata and controls
153 lines (140 loc) · 6.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
node: [20, 22, 24]
include:
- os: windows-latest
node: 22
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: ${{ matrix.node }}
cache: npm
# C0 layout-engine harness (test/layoutRenderOracle.test.js,
# layoutMeasureDiff.test.js) shells out to `pdftoppm`/`pdftotext`
# (poppler) to rasterize + text-extract rendered PDFs. Installed on
# exactly ONE pinned leg — ubuntu-latest + Node 22 — which is the
# CANONICAL environment the harness's ink/pixel thresholds
# (renderOracle.js's BLANK_PAGE_MAX_INK_RATIO, EMPTY_COLUMN ink-band
# presence check) are calibrated against; see research/c0-baseline.md.
# Every other (os, node) leg has no poppler installed on purpose —
# the harness's own `hasPdftoppm()` guard (test/layout-harness/
# scaffold.js) makes those tests SKIP cleanly there instead of
# erroring, so the matrix doesn't need poppler everywhere.
- name: Install poppler-utils (canonical env for the C0 layout-harness pixel/text thresholds)
if: matrix.os == 'ubuntu-latest' && matrix.node == 22
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends poppler-utils
- run: npm ci
- run: npm test
- run: npm run build
- name: Packaged E2E — init, validate, list, build, ATS
shell: bash
run: |
npm pack
TARBALL="$PWD/$(ls hrtips-cvx-*.tgz)"
DIR="$(mktemp -d)"
cd "$DIR"
npm init -y > /dev/null
npm install --no-audit --no-fund "$TARBALL"
npx cvx init
# validate: scaffold passes strict, --json is parseable stdout
npx cvx validate --strict
npx cvx validate --json | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const r=JSON.parse(s);if(!r.ok||r.command!=='validate')process.exit(1)})"
# validate: seeded error → exit 2, findings carry file+path
cp cv-content/personal.yaml personal.bak
printf 'title: No Name\nlinkdin: typo\n' > cv-content/personal.yaml
set +e
npx cvx validate --json > findings.json; CODE=$?
set -e
test "$CODE" -eq 2
node -e "const r=require('./findings.json');if(r.ok)process.exit(1);if(!r.errors.some(e=>e.file==='personal.yaml'&&e.message.includes('name')))process.exit(1);if(!r.warnings.some(w=>w.suggestion&&w.suggestion.includes('linkedin')))process.exit(1)"
mv personal.bak cv-content/personal.yaml
# list: inventory is stable
npx cvx list --json | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const r=JSON.parse(s);const t=r.themes.map(x=>x.name);if(!['teal','coral','mono'].every(n=>t.includes(n)))process.exit(1)})"
# unknown command → exit 64
set +e
npx cvx frobnicate 2>/dev/null; CODE=$?
set -e
test "$CODE" -eq 64
# MCP: initialize handshake + tools/list against the packed tarball
cat > mcp-smoke.cjs <<'SMOKE'
const { spawn } = require("child_process");
const p = spawn(process.execPath, ["node_modules/@hrtips/cvx/bin/cvx.js", "mcp"], { stdio: ["pipe", "pipe", "inherit"] });
let buf = ""; const msgs = [];
const send = (o) => p.stdin.write(JSON.stringify(o) + "\n");
p.stdout.on("data", (d) => {
buf += d;
let i; while ((i = buf.indexOf("\n")) !== -1) {
const line = buf.slice(0, i).trim(); buf = buf.slice(i + 1);
if (line) { msgs.push(JSON.parse(line)); step(); }
}
});
function step() {
if (msgs.length === 1) {
if (msgs[0].result.serverInfo.name !== "cvx") { console.error("bad serverInfo"); process.exit(1); }
send({ jsonrpc: "2.0", method: "notifications/initialized" });
send({ jsonrpc: "2.0", id: 2, method: "tools/list" });
} else if (msgs.length === 2) {
const tools = msgs[1].result.tools.map((t) => t.name).sort().join(",");
if (tools !== "build_pdf,get_schema,init_cv,validate_cv") { console.error("bad tools:", tools); process.exit(1); }
console.log("mcp smoke ok:", tools); p.kill(); process.exit(0);
}
}
send({ jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "ci", version: "0" } } });
setTimeout(() => { console.error("mcp handshake timeout"); process.exit(1); }, 30000);
SMOKE
node mcp-smoke.cjs
npx cvx build
npx cvx build --ats
test -f bruce-wayne.pdf && test -f bruce-wayne-ats.pdf
- name: Reproducibility — two pinned builds must be byte-identical
shell: bash
run: |
DIR="$(mktemp -d)"
cd "$DIR"
npm init -y > /dev/null
npm install --no-audit --no-fund "$(ls "$GITHUB_WORKSPACE"/hrtips-cvx-*.tgz)"
npx cvx init
SOURCE_DATE_EPOCH=1700000000 npx cvx build
H1="$(node -e "console.log(require('crypto').createHash('sha256').update(require('fs').readFileSync('bruce-wayne.pdf')).digest('hex'))")"
SOURCE_DATE_EPOCH=1700000000 npx cvx build
H2="$(node -e "console.log(require('crypto').createHash('sha256').update(require('fs').readFileSync('bruce-wayne.pdf')).digest('hex'))")"
echo "run1: $H1"
echo "run2: $H2"
test "$H1" = "$H2"
# Alert-only: does the upcoming @react-pdf/renderer break rendering or
# byte-identical reproducibility? Never fails the workflow — it exists to
# surface upstream drift before a dependency bump lands.
upstream-canary:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
cache: npm
- run: npm ci
- name: Install upstream head of @react-pdf/renderer
run: npm install --no-save @react-pdf/renderer@next 2>/dev/null || npm install --no-save @react-pdf/renderer@latest
- run: npm test
- name: Reproducibility against upstream head
shell: bash
run: |
SOURCE_DATE_EPOCH=1700000000 npm run pdf
H1="$(node -e "console.log(require('crypto').createHash('sha256').update(require('fs').readFileSync('bruce-wayne.pdf')).digest('hex'))")"
SOURCE_DATE_EPOCH=1700000000 npm run pdf
H2="$(node -e "console.log(require('crypto').createHash('sha256').update(require('fs').readFileSync('bruce-wayne.pdf')).digest('hex'))")"
echo "run1: $H1"
echo "run2: $H2"
test "$H1" = "$H2"