Landing page: add multilingual switcher (EN / हिंदी / தமிழ் / සිංහල) #26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |