From 8c57c4ad7c8cef42036ef516b25fc0beefd9b379 Mon Sep 17 00:00:00 2001 From: heznpc Date: Wed, 1 Jul 2026 15:31:49 +0900 Subject: [PATCH] ci: harden profilekit release surface --- .cursor/rules/repo-context.mdc | 10 + .github/copilot-instructions.md | 8 + .github/workflows/ci.yml | 51 ++ .github/workflows/claude-security-review.yml | 38 ++ .github/workflows/codeql.yml | 31 + .github/workflows/maintenance.yml | 76 +++ .github/workflows/stale.yml | 29 + AGENTS.md | 36 ++ CLAUDE.md | 7 + README.md | 6 +- claude-security-guidance.md | 25 + package-lock.json | 630 +++++++++++++++++++ package.json | 5 +- src/endpoints/catalog.js | 2 +- tests/catalog.test.js | 2 +- tests/endpoint-headers.test.js | 2 +- 16 files changed, 951 insertions(+), 7 deletions(-) create mode 100644 .cursor/rules/repo-context.mdc create mode 100644 .github/copilot-instructions.md create mode 100644 .github/workflows/claude-security-review.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/maintenance.yml create mode 100644 .github/workflows/stale.yml create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 claude-security-guidance.md diff --git a/.cursor/rules/repo-context.mdc b/.cursor/rules/repo-context.mdc new file mode 100644 index 0000000..35e640c --- /dev/null +++ b/.cursor/rules/repo-context.mdc @@ -0,0 +1,10 @@ +--- +description: Shared repo context +alwaysApply: true +--- + +Read `AGENTS.md` first. Treat it as the source of truth for this repo's +commands, invariants, public naming, release constraints, and first-user path. + +This file is a thin client adapter for Cursor. Do not add independent strategy, +roadmap, package-name, release, or baseline-scope rules here. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..0c6e602 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,8 @@ +# Agent context + +Read `AGENTS.md` first. Treat it as the source of truth for this repo's +commands, invariants, public naming, release constraints, and first-user path. + +This file is a thin client adapter for GitHub Copilot and VS Code. Do not add +independent strategy, roadmap, package-name, release, or baseline-scope rules +here. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9234d34..e149a7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,12 +5,48 @@ on: branches: [main] pull_request: branches: [main] + workflow_call: # Least-privilege: this workflow only reads the repo and runs tests. permissions: contents: read jobs: + static-checks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: secret-scanning with gitleaks + env: + GITLEAKS_VERSION: 8.30.1 + GITLEAKS_SHA256: 551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb + run: | + set -euo pipefail + curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" -o /tmp/gitleaks.tgz + echo "${GITLEAKS_SHA256} /tmp/gitleaks.tgz" | sha256sum -c - + tar xz -C /tmp -f /tmp/gitleaks.tgz + /tmp/gitleaks detect --source . --verbose --redact + + - uses: actions/setup-node@v6 + with: + node-version: '24' + cache: npm + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Check licenses + run: npm exec -- license-checker-evergreen --failOn "GPL-2.0;GPL-3.0;AGPL-3.0" + + - name: Security audit + run: npm audit --audit-level=high + + - name: Syntax check + run: npm run check + test: runs-on: ubuntu-latest strategy: @@ -89,3 +125,18 @@ jobs: docker logs pkit docker rm -f pkit + + ci: + needs: [static-checks, test, docker] + if: always() + runs-on: ubuntu-latest + steps: + - name: Aggregate result + run: | + if [ "${{ needs.static-checks.result }}" != "success" ] || \ + [ "${{ needs.test.result }}" != "success" ] || \ + [ "${{ needs.docker.result }}" != "success" ]; then + echo "::error::CI failed (static-checks=${{ needs.static-checks.result }}, test=${{ needs.test.result }}, docker=${{ needs.docker.result }})" + exit 1 + fi + echo "All checks passed." diff --git a/.github/workflows/claude-security-review.yml b/.github/workflows/claude-security-review.yml new file mode 100644 index 0000000..8aca72c --- /dev/null +++ b/.github/workflows/claude-security-review.yml @@ -0,0 +1,38 @@ +name: AI Security Review + +on: + pull_request: + +permissions: + pull-requests: write + contents: read + +jobs: + ai-review: + runs-on: ubuntu-latest + steps: + - name: Check CLAUDE_API_KEY presence + id: gate + env: + API_KEY: ${{ secrets.CLAUDE_API_KEY }} + run: | + set -euo pipefail + if [ -z "${API_KEY:-}" ]; then + echo "::notice::CLAUDE_API_KEY secret is not set; skipping AI security review. Set the secret in repo settings to enable." + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - uses: actions/checkout@v6 + if: steps.gate.outputs.skip == 'false' + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + fetch-depth: 2 + + - name: AI security review + if: steps.gate.outputs.skip == 'false' + uses: anthropics/claude-code-security-review@0c6a49f1fa56a1d472575da86a94dbc1edb78eda + with: + comment-pr: true + claude-api-key: ${{ secrets.CLAUDE_API_KEY }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..0facf6a --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,31 @@ +# CodeQL advanced setup keeps the static-analysis policy visible in the repo. +name: CodeQL + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + - cron: '0 6 * * 1' + +permissions: + contents: read + security-events: write + +jobs: + analyze: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: javascript-typescript + + - name: Autobuild + uses: github/codeql-action/autobuild@v4 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/maintenance.yml b/.github/workflows/maintenance.yml new file mode 100644 index 0000000..8c791ea --- /dev/null +++ b/.github/workflows/maintenance.yml @@ -0,0 +1,76 @@ +name: Maintenance + +on: + schedule: + - cron: '0 0 * * 1' + workflow_dispatch: + +permissions: + contents: read + issues: write + +jobs: + ci: + uses: ./.github/workflows/ci.yml + + open-issue-on-failure: + needs: ci + if: failure() + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v9 + with: + script: | + const existing = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: 'maintenance', + }); + if (existing.data.length > 0) return; + const run = await github.rest.actions.getWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.runId, + }); + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: 'Maintenance CI failed', + body: `The scheduled maintenance CI check failed.\n\n**Run:** ${run.data.html_url}`, + labels: ['maintenance'], + }); + + close-issue-on-success: + needs: ci + if: success() + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v9 + with: + script: | + const open = await github.rest.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + labels: 'maintenance', + }); + const run = await github.rest.actions.getWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.runId, + }); + for (const issue of open.data) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `Maintenance CI is green again.\n\n**Run:** ${run.data.html_url}`, + }); + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'closed', + }); + } diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..dd701e3 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,29 @@ +name: Stale Issues + +on: + schedule: + - cron: '0 0 * * *' + workflow_dispatch: + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v10 + with: + days-before-stale: 30 + days-before-close: 7 + stale-issue-label: stale + stale-pr-label: stale + stale-issue-message: > + This issue has been inactive for 30 days and will be closed in 7 days + unless there is new activity. + stale-pr-message: > + This PR has been inactive for 30 days and will be closed in 7 days + unless there is new activity. + exempt-issue-labels: pinned,bug,help wanted + exempt-pr-labels: pinned,work in progress diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..4f6d8f5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,36 @@ +# ProfileKit + +Composable SVG card endpoints for GitHub profiles, READMEs, blogs, and personal +sites. Vanilla Node, no runtime dependencies, Vercel API routes plus Docker +self-hosting. + +## Run this repo + +```bash +npm run check +npm test +docker build -t profilekit:local . +docker run --rm -p 3000:3000 profilekit:local +``` + +## Structure + +``` +api/ + health.js -> health endpoint + divider.js -> SVG divider endpoint +src/ + cards/ -> card renderers + core/ -> shared SVG/query helpers +server.js -> local/self-host HTTP adapter +tests/ -> node:test coverage for endpoints and renderers +``` + +## Invariants + +- Keep endpoints deterministic: same query string means same SVG. +- Treat every query parameter as untrusted input. Clamp numbers, whitelist + enum-like strings, and escape text before embedding in SVG. +- Do not add user ranking, scoring, ratings, or analytics capture. +- Do not introduce a database or server-side persistence for card rendering. +- Docker and Vercel paths must stay behaviorally equivalent. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5838d3c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,7 @@ +# Agent context + +Read `AGENTS.md` first. Treat it as the source of truth for this repo's +commands, invariants, public naming, release constraints, and first-user path. + +This file is a thin client adapter. Do not add independent strategy, roadmap, +package-name, release, or baseline-scope rules here. diff --git a/README.md b/README.md index 81c04b8..4b4f4e9 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ A community gallery for sharing single-card presets and adopting others' designs ## About this project -**Currently implemented.** 28 SVG card endpoints (`/api/*`), 17 built-in themes plus gist-hosted custom palettes via `theme_url=`, five bundled variable fonts, `/api/stack` composition with namespaced child IDs, a live playground at [profilekit.vercel.app](https://profilekit.vercel.app), and an MCP wrapper at [`@heznpc/profilekit-mcp`](https://www.npmjs.com/package/@heznpc/profilekit-mcp). Two deployment paths: **Vercel functions** (primary, `api/[endpoint].js`) and an **optional self-hosted Docker** image (`Dockerfile` + `server.js`) running the same handlers. Zero runtime dependencies, 30-minute CDN cache on the hosted instance. +**Currently implemented.** 28 SVG card endpoints (`/api/*`), 17 built-in themes plus gist-hosted custom palettes via `theme_url=`, five bundled variable fonts, `/api/stack` composition with namespaced child IDs, a live playground at [profilekit.vercel.app](https://profilekit.vercel.app), and an MCP wrapper at [`profilekit-mcp`](https://www.npmjs.com/package/profilekit-mcp). Two deployment paths: **Vercel functions** (primary, `api/[endpoint].js`) and an **optional self-hosted Docker** image (`Dockerfile` + `server.js`) running the same handlers. Zero runtime dependencies, 30-minute CDN cache on the hosted instance. **Planned.** A single-card preset gallery at `/gallery` — adopt someone else's design URL as a starting point, then tweak parameters in the editor. Cross-agent preset compile (one preset → Claude Code, Cursor, Codex CLI configs). @@ -182,7 +182,7 @@ The GitHub-profile-card space has two long-running projects ProfileKit is most o | Configuration | Query string + optional `theme_url=` gist for palettes | Query string + per-theme presets | YAML in `.github/workflows/` | | Runtime deps | Zero (Node 22 `node:test`, `node:fetch`) | Several | Action toolchain + Docker image | | Cards beyond GitHub stats | Hero / section / divider / now / timeline / tags / toc / typing / wave / terminal / neon / glitch / matrix / snake / equalizer / heartbeat / constellation / radar / quote / posts (devto, medium, rss) | GitHub stats, languages, pin, gists | Mostly GitHub stats; plugin set is the largest of the three | -| MCP integration | First-class — [`@heznpc/profilekit-mcp`](https://www.npmjs.com/package/@heznpc/profilekit-mcp) lets Claude / Cursor / Codex CLI build cards as a tool call | None | None | +| MCP integration | First-class — [`profilekit-mcp`](https://www.npmjs.com/package/profilekit-mcp) lets Claude / Cursor / Codex CLI build cards as a tool call | None | None | | Composition into one image | `/api/stack?cards=hero,section,now,…` | Not native | The whole point of metrics is a single composed image | **When ProfileKit fits well**: you want the same card definition usable in a GitHub README *and* a dev.to bio *and* an MCP tool call, you don't want a GitHub Action committing to your repo, and "no ranking, composable presentation" sounds right for your profile. @@ -709,7 +709,7 @@ The Docker path is purely additive — the Vercel path keeps working unchanged. ## Roadmap -- **Now** — 28 card endpoints, 17 themes, playground at [profilekit.vercel.app](https://profilekit.vercel.app), MCP server at [`@heznpc/profilekit-mcp`](https://www.npmjs.com/package/@heznpc/profilekit-mcp), curated picks in the Templates tab. +- **Now** — 28 card endpoints, 17 themes, playground at [profilekit.vercel.app](https://profilekit.vercel.app), MCP server at [`profilekit-mcp`](https://www.npmjs.com/package/profilekit-mcp), curated picks in the Templates tab. - **Next — Gallery.** Single-card presets shareable by URL or registered in a browseable index at `profilekit.vercel.app/gallery`. Adopting a preset opens it in the editor with the original params pre-filled so you tweak from a starting point instead of a blank canvas. Editing UX targets a Sims 3 Create-A-Style / Naver-blog-editor feel — direct manipulation on the preview, not just a form. **Explicit non-goals**: no ratings, no rankings, no remix lineage, no leaderboards. - **Parallel** — Cross-agent compile (one preset definition → Claude Code, Cursor, Codex CLI configs). Lives as a feature, not a roadmap tier. diff --git a/claude-security-guidance.md b/claude-security-guidance.md new file mode 100644 index 0000000..b8cf57e --- /dev/null +++ b/claude-security-guidance.md @@ -0,0 +1,25 @@ +# Security guidance + +This file is read by Anthropic's Claude Code Security Guidance Plugin as an +in-session guard while Claude writes code. It complements the +`claude-code-security-review` GitHub Action and the repo-level +`audit_security` check. + +## Universal rules + +- Never log secrets, tokens, cookies, or raw request headers. +- Never use `eval`, `Function()`, or shell execution for request data. +- Validate and clamp all query parameters before rendering SVG. +- Escape user-visible text before embedding it in SVG or HTML. +- Keep `.env*` files out of git. Use examples with placeholder values only. +- Use OIDC trusted publishing where publishing is added; do not add long-lived + registry tokens to workflows. + +## ProfileKit rules + +- SVG endpoints must be deterministic and side-effect free. +- Do not add persistence, analytics capture, tracking pixels, or ranking data + to card rendering. +- Maintain parity between Vercel API routes and the Docker/self-host server. +- Cap width, height, text length, color strings, and list sizes so malformed + query strings cannot produce excessive SVG output. diff --git a/package-lock.json b/package-lock.json index 92ec4dd..e2cb4f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,9 +8,639 @@ "name": "profilekit", "version": "1.0.0", "license": "MIT", + "devDependencies": { + "license-checker-evergreen": "^6.3.1" + }, "engines": { "node": ">=22 <25" } + }, + "node_modules/abbrev": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", + "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/debuglog": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/license-checker-evergreen": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/license-checker-evergreen/-/license-checker-evergreen-6.3.1.tgz", + "integrity": "sha512-EK/gqHg2JqA7U/by8TYsK+hTtY51fipNWsmot1g1c/4Mq2/U2FCv7RnJGD5NALbV1Lwg9J5Cn5MlRl6cCXbSsA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "chalk": "^5.6.2", + "debug": "^4.4.3", + "lodash.clonedeep": "4.5.0", + "mkdirp": "3.0.1", + "nopt": "8.1.0", + "read-installed": "~4.0.3", + "semver": "^7.7.3", + "spdx-correct": "3.2.0", + "spdx-expression-parse": "4.0.0", + "spdx-satisfies": "6.0.0", + "treeify": "1.1.0" + }, + "bin": { + "license-checker-evergreen": "dist/bin/license-checker-evergreen.js" + }, + "engines": { + "node": ">=18", + "npm": ">=8" + } + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", + "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true, + "license": "ISC" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/read-installed": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", + "integrity": "sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.2" + } + }, + "node_modules/read-installed/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-package-json": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz", + "integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==", + "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.1", + "json-parse-even-better-errors": "^2.3.0", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" + } + }, + "node_modules/readdir-scoped-modules": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "*" + } + }, + "node_modules/spdx-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", + "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-find-index": "^1.0.2", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, + "node_modules/spdx-compare/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/spdx-ranges": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", + "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", + "dev": true, + "license": "(MIT AND CC-BY-3.0)" + }, + "node_modules/spdx-satisfies": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-6.0.0.tgz", + "integrity": "sha512-oOWQocnRbFVtBnBITfFgzjhnOklHossTvI+6C1hB2slvp3HgTsfru5wuo8HY2rQpwSm5JuIhNzIuqOfR5IuojQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-compare": "^1.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, + "node_modules/spdx-satisfies/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/util-extend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", + "integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==", + "dev": true, + "license": "MIT" + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" } } } diff --git a/package.json b/package.json index f67396c..c18e0bf 100644 --- a/package.json +++ b/package.json @@ -12,5 +12,8 @@ "smoke:server": "node scripts/server-smoke.js", "check": "node --check server.js && find api src scripts -name '*.js' -exec node --check {} +" }, - "license": "MIT" + "license": "MIT", + "devDependencies": { + "license-checker-evergreen": "^6.3.1" + } } diff --git a/src/endpoints/catalog.js b/src/endpoints/catalog.js index 80e9670..195bb33 100644 --- a/src/endpoints/catalog.js +++ b/src/endpoints/catalog.js @@ -1,7 +1,7 @@ const { themes } = require("../common/themes"); const { cacheHeaders } = require("../common/utils"); -// Card catalog — consumed by @heznpc/profilekit-mcp and any external tool +// Card catalog — consumed by profilekit-mcp and any external tool // that wants to discover available endpoints + their common parameters // without scraping the README. The schema is intentionally shallow: full // parameter types live in the endpoint handlers; this answers the higher diff --git a/tests/catalog.test.js b/tests/catalog.test.js index 9459952..3d37f95 100644 --- a/tests/catalog.test.js +++ b/tests/catalog.test.js @@ -7,7 +7,7 @@ const { } = require("../src/endpoints/catalog"); // /api/catalog is the machine-readable discovery surface consumed by -// @heznpc/profilekit-mcp and any client that doesn't scrape README. The +// profilekit-mcp and any client that doesn't scrape README. The // README promise of "theme_url on every card endpoint" has to be reflected // here too — otherwise discovery clients silently disagree with the docs. diff --git a/tests/endpoint-headers.test.js b/tests/endpoint-headers.test.js index 382d951..717b987 100644 --- a/tests/endpoint-headers.test.js +++ b/tests/endpoint-headers.test.js @@ -87,7 +87,7 @@ test("/api/health emits no-store cache so probes always observe current state", }); test("/api/catalog emits JSON + cached", async () => { - // Discovery endpoint consumed by @heznpc/profilekit-mcp. A regression + // Discovery endpoint consumed by profilekit-mcp. A regression // to text/plain would break the wrapper's JSON.parse, and missing // Cache-Control would hammer the function on every MCP discover call. const handler = require("../src/endpoints/catalog");