Skip to content

Landing page: add multilingual switcher (EN / हिंदी / தமிழ் / සිංහල) #26

Landing page: add multilingual switcher (EN / हिंदी / தமிழ் / සිංහල)

Landing page: add multilingual switcher (EN / हिंदी / தமிழ் / සිංහල) #26

Workflow file for this run

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
- 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"