diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0d659f7 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 3 + - package-ecosystem: npm + directory: /backend + schedule: + interval: weekly + open-pull-requests-limit: 3 + - package-ecosystem: npm + directory: /frontend + schedule: + interval: weekly + open-pull-requests-limit: 3 + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 3 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..b6bfb40 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ +## Change + +Describe the problem and resulting behavior. + +## Validation + +- [ ] `npm run check` passed (or explain failures below). +- [ ] Relevant behavior tests cover the change. +- [ ] Dependency audit and secret scan passed in CI. + +Include commands/results and any manual checks that matter. + +## Risks or remaining work + +Mention API changes, deployment settings, or checks that could not run. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0ff93fd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + checks: + name: Tests and frontend build + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7 + with: + node-version: '22' + cache: npm + cache-dependency-path: | + package-lock.json + backend/package-lock.json + frontend/package-lock.json + - run: npm ci + - run: npm --prefix backend ci + - run: npm --prefix frontend ci + - run: npm run check + + dependencies: + name: Dependency audit + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + persist-credentials: false + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7 + with: + node-version: '22' + - run: npm audit --audit-level=high + - run: npm --prefix backend audit --audit-level=high + - run: npm --prefix frontend audit --audit-level=high + + secrets: + name: Secret scan + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + fetch-depth: 0 + persist-credentials: false + - name: Scan Git history with Gitleaks + run: >- + docker run --rm --network none + -e GIT_CONFIG_COUNT=1 + -e GIT_CONFIG_KEY_0=safe.directory + -e GIT_CONFIG_VALUE_0=/repo + -v "$GITHUB_WORKSPACE:/repo:ro" + ghcr.io/gitleaks/gitleaks:v8.24.2 + git /repo --redact --log-opts="--all" diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9a50dd3 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,61 @@ +# Working on DevTrack + +## Project boundaries + +DevTrack is a React/Vite dashboard backed by an Express API. Analytics are +deterministic: keep scoring, GitHub ingestion, builders, insights, and caching +in `backend/services`; keep presentation and interaction in `frontend/src`. +Preserve the response contract documented in README.md unless the task explicitly +changes it. The MVP has no persistent database. + +## Commands + +Use Node 22. Install locked dependencies with `npm ci` at the root and +`npm --prefix backend ci` / `npm --prefix frontend ci`. + +- `npm test`: offline backend tests using Node's built-in runner. +- `npm run build`: production frontend build. +- `npm run check`: tests followed by the frontend build. +- `npm --prefix backend run dev`: API development server. +- `npm --prefix frontend run dev`: frontend development server. + +On Windows PowerShell, use `npm.cmd` if execution policy blocks `npm.ps1`. + +## Agent workflow + +1. Inspect the relevant code and current Git changes before editing. Preserve + unrelated user work. Define the task's expected behavior and acceptance criteria. +2. Make focused changes. Add meaningful behavior tests for scoring, caching, API + contracts, and upstream failure handling when those behaviors change. Mock + GitHub requests; tests must not depend on live accounts or credentials. +3. Run `npm run check` before handing off code changes. Report failures and + unavailable checks honestly; do not weaken checks to obtain a passing result. +4. Report the resulting behavior, validation, and any remaining limitations. + PRs should include evidence of checks and require human review before merging. + +## Cost policy + +Keep this workflow free to operate. Do not introduce paid services, subscriptions, +larger GitHub runners, or billable usage without explicit user authorization. +The public repository uses standard GitHub-hosted runners, Node's built-in tests, +npm audit, and the standalone Gitleaks CLI. An account billing lock is separate +from these tools' pricing; do not remove security checks merely to hide that lock. + +## Access boundaries + +Use the coding tool's actual sandbox controls to limit writes to this checkout +and restrict network access to what the task needs. This file is guidance, not +an enforcement mechanism. Do not read or print real `.env` files, production +credentials, or deployment secrets. Use `.env.example` and mocked data. +Do not embed tokens in source, logs, prompts, or test fixtures. + +Production deployments, credential changes, and automatic merges require explicit +user authorization. Keep CI read-only and free of production credentials; use +`pull_request`, never `pull_request_target` to execute contributed code. + +## Parallel work + +Use multiple agents only when the user explicitly requests them. Assign each +independent task a separate branch and worktree, define ownership, and agree on +any shared API contract before implementation. Worktrees isolate changes, not +credentials or processes. Validate the combined result before merging. diff --git a/README.md b/README.md index cf30b9a..236c5ac 100644 --- a/README.md +++ b/README.md @@ -297,6 +297,16 @@ npm run dev Frontend default: http://localhost:5173 +## Development Checks and Agent Workflow + +Use Node 22 for development and CI. After installing root, backend, and frontend +dependencies with `npm ci`, run `npm run check` from the repository root. +It runs offline backend behavior tests and builds the frontend. + +See [development workflow](docs/development-workflow.md) for security checks, +PR review, and GitHub branch protection setup, and [AGENTS.md](AGENTS.md) for +project instructions used by coding agents. + ## Deployment Notes - Frontend deploy target: Vercel, root directory frontend diff --git a/backend/package-lock.json b/backend/package-lock.json index a12d94c..da4eea2 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -35,9 +35,9 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", + "version": "1.20.8", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.8.tgz", + "integrity": "sha512-JNcyFQ64OiijEkPzUBTCe+hyPXUD/3LEldGQ6iF5LR1w00mx9o7xtDWHXBY2iItjdCFGoilOLNQbH943ut7pHA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -48,7 +48,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.15.1", + "qs": "~6.16.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -58,21 +58,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -596,9 +581,9 @@ "license": "MIT" }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -606,15 +591,20 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -725,14 +715,14 @@ "license": "ISC" }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, diff --git a/backend/package.json b/backend/package.json index 387f22a..c2987d0 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,6 +4,7 @@ "description": "DevTrack backend API", "main": "server.js", "scripts": { + "test": "node --test", "start": "node server.js", "dev": "node --watch server.js" }, @@ -13,5 +14,8 @@ "dependencies": { "dotenv": "^17.4.2", "express": "^4.19.2" + }, + "overrides": { + "qs": "^6.16.0" } } diff --git a/backend/server.js b/backend/server.js index 609cc13..b7d8b76 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,4 +1,6 @@ -require('dotenv').config(); +if (require.main === module) { + require('dotenv').config(); +} const express = require('express'); const githubRoutes = require('./routes/githubRoutes'); @@ -31,6 +33,10 @@ app.use('/api/v1/health', healthRoutes); app.use(notFound); app.use(errorHandler); -app.listen(PORT, () => { - console.log(`Server running on port ${PORT}`); -}); \ No newline at end of file +if (require.main === module) { + app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); + }); +} + +module.exports = app; diff --git a/backend/test/api.test.js b/backend/test/api.test.js new file mode 100644 index 0000000..c9907e3 --- /dev/null +++ b/backend/test/api.test.js @@ -0,0 +1,60 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const http = require('node:http'); +const { once } = require('node:events'); +const app = require('../server'); + +// Use http rather than fetch for local requests so the GitHub fetch mock stays isolated. +const request = (server, path) => new Promise((resolve, reject) => { + http.get({ hostname: '127.0.0.1', port: server.address().port, path }, (response) => { + let body = ''; + response.setEncoding('utf8'); + response.on('data', (chunk) => { body += chunk; }); + response.on('end', () => resolve({ status: response.statusCode, + headers: response.headers, body: JSON.parse(body) })); + response.on('error', reject); + }).on('error', reject); +}); + +test('HTTP API preserves success, cache, health, and failure contracts', async (t) => { + const server = app.listen(0, '127.0.0.1'); + t.after(() => new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()))); + await once(server, 'listening'); + let upstreamStatus = 200; + t.mock.method(global, 'fetch', async () => upstreamStatus === 200 + ? Response.json([]) : new Response(null, { status: upstreamStatus })); + + const health = await request(server, '/api/v1/health'); + assert.equal(health.status, 200); + assert.equal(health.body.data.status, 'ok'); + + const first = await request(server, '/api/v1/github/ApiFixture'); + assert.equal(first.status, 200); + assert.equal(first.body.success, true); + assert.equal(first.headers['x-cache'], 'MISS'); + assert.equal(first.body.meta.cached, false); + assert.ok(Number.isFinite(Date.parse(first.body.meta.timestamp))); + assert.equal(first.body.data.username, 'ApiFixture'); + assert.equal(first.body.data.hireabilityScore, 0); + assert.equal(first.body.data.commitActivity.length, 12); + assert.equal(first.body.data.scoreBreakdown.length, 4); + assert.ok(Array.isArray(first.body.data.languageBreakdown)); + assert.deepEqual(Object.keys(first.body.data.insights.roleFit).sort(), ['backend', 'frontend', 'fullstack']); + + const cached = await request(server, '/api/v1/github/apifixture'); + assert.equal(cached.headers['x-cache'], 'HIT'); + assert.equal(cached.body.meta.cached, true); + assert.deepEqual(cached.body.data, first.body.data); + + for (const status of [404, 429, 502]) { + upstreamStatus = status; + const failed = await request(server, `/api/v1/github/failure${status}`); + assert.equal(failed.status, status); + assert.equal(failed.body.success, false); + assert.equal(failed.body.error.statusCode, status); + assert.equal(typeof failed.body.error.message, 'string'); + } + + assert.equal((await request(server, '/api/v1/github/%20')).status, 400); + assert.equal((await request(server, '/unknown')).status, 404); +}); diff --git a/backend/test/cache.test.js b/backend/test/cache.test.js new file mode 100644 index 0000000..50fa13f --- /dev/null +++ b/backend/test/cache.test.js @@ -0,0 +1,20 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const { getCachedAnalysis, setCachedAnalysis } = require('../services/cache/cacheService'); + +test('cache misses return null and entries expire exactly at their TTL', (t) => { + const ttlSeconds = Number(process.env.CACHE_TTL_SECONDS || 300); + const ttl = Number.isFinite(ttlSeconds) && ttlSeconds > 0 ? ttlSeconds * 1000 : 300000; + let now = 1000000; + t.mock.method(Date, 'now', () => now); + assert.equal(getCachedAnalysis('missing'), null); + const data = { username: 'cache-fixture' }; + setCachedAnalysis('cache-fixture', data); + assert.deepEqual(getCachedAnalysis('cache-fixture'), data); + now += ttl - 1; + assert.deepEqual(getCachedAnalysis('cache-fixture'), data); + now += 1; + assert.equal(getCachedAnalysis('cache-fixture'), null); + now -= ttl; + assert.equal(getCachedAnalysis('cache-fixture'), null); +}); diff --git a/backend/test/github.test.js b/backend/test/github.test.js new file mode 100644 index 0000000..12a5bc0 --- /dev/null +++ b/backend/test/github.test.js @@ -0,0 +1,75 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const { fetchUserRepositories, fetchUserEvents } = require('../services/github/githubApi'); +const { analyzeGithubProfile } = require('../services'); +const { inFlightAnalyses } = require('../services/cache/cacheService'); + +test('repositories paginate and encode usernames', async (t) => { + const urls = []; + t.mock.method(global, 'fetch', async (url) => { + urls.push(url); + return Response.json(urls.length === 1 ? Array.from({ length: 100 }, (_, id) => ({ id })) : [{ id: 100 }]); + }); + const repos = await fetchUserRepositories('a/b'); + assert.equal(repos.length, 101); + assert.match(urls[0], /a%2Fb\/repos.*page=1/); + assert.match(urls[1], /page=2/); +}); + +for (const status of [403, 429]) { + test(`GitHub ${status} becomes a rate-limit error with retry metadata`, async (t) => { + t.mock.method(global, 'fetch', async () => new Response(null, { + status, headers: { 'Retry-After': '60' } + })); + await assert.rejects(fetchUserRepositories('limited'), (error) => { + assert.equal(error.statusCode, 429); + assert.equal(error.retryAfter, '60'); + return true; + }); + }); +} + +test('missing users and upstream errors remain explicit failures', async (t) => { + for (const status of [404, 502]) { + const mock = t.mock.method(global, 'fetch', async () => new Response(null, { status })); + await assert.rejects(fetchUserRepositories('missing'), { statusCode: status }); + mock.mock.restore(); + } +}); + +test('event failures preserve previously fetched activity', async (t) => { + const events = Array.from({ length: 100 }, () => ({ created_at: new Date().toISOString() })); + let calls = 0; + t.mock.method(global, 'fetch', async () => ++calls === 1 + ? Response.json(events) : new Response(null, { status: 429 })); + assert.deepEqual(await fetchUserEvents('partial'), events); + assert.equal(calls, 2); +}); + +test('concurrent analyses share upstream calls and cache usernames without case sensitivity', async (t) => { + let calls = 0; + t.mock.method(global, 'fetch', async () => { + calls += 1; + return Response.json([]); + }); + const [first, second] = await Promise.all([ + analyzeGithubProfile('ConcurrentFixture'), analyzeGithubProfile('concurrentfixture') + ]); + assert.equal(calls, 2); + assert.equal(first.cacheHit, false); + assert.equal(second.cacheHit, true); + assert.deepEqual(first.data, second.data); + assert.equal(inFlightAnalyses.size, 0); + assert.equal((await analyzeGithubProfile('CONCURRENTFIXTURE')).cacheHit, true); + assert.equal(calls, 2); +}); + +test('failed analyses release in-flight state and can be retried', async (t) => { + let failed = true; + t.mock.method(global, 'fetch', async () => failed + ? new Response(null, { status: 502 }) : Response.json([])); + await assert.rejects(analyzeGithubProfile('retryfixture'), { statusCode: 502 }); + assert.equal(inFlightAnalyses.has('retryfixture'), false); + failed = false; + assert.equal((await analyzeGithubProfile('retryfixture')).cacheHit, false); +}); diff --git a/backend/test/scoring.test.js b/backend/test/scoring.test.js new file mode 100644 index 0000000..02c5eb0 --- /dev/null +++ b/backend/test/scoring.test.js @@ -0,0 +1,47 @@ +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const { buildHireabilityScore } = require('../services/scoring/scoringService'); + +test('a profile with no repositories or activity scores zero', () => { + const result = buildHireabilityScore( + { totalRepositories: 0, totalStars: 0, totalForks: 0 }, + { repositoriesAnalyzed: 0 }, [] + ); + assert.equal(result.hireabilityScore, 0); + assert.equal(result.scoreBreakdown.length, 4); + assert.ok(result.scoreBreakdown.every(({ score }) => score === 0)); +}); + +test('maximum signals produce 100 and respect component weights', () => { + const result = buildHireabilityScore( + { totalRepositories: 20, totalStars: 1000, totalForks: 0 }, + { repositoriesAnalyzed: 20, repositoriesWithDescription: 20, + repositoriesWithLicense: 20, recentlyUpdatedRepositories: 20 }, + Array.from({ length: 12 }, () => ({ commits: 10 })) + ); + assert.equal(result.hireabilityScore, 100); + assert.deepEqual(result.scoreBreakdown.map(({ score }) => score), [40, 30, 20, 10]); +}); + +test('long inactive gaps reduce consistency for the same activity total', () => { + const summary = { totalRepositories: 0, totalStars: 0, totalForks: 0 }; + const quality = { repositoriesAnalyzed: 0 }; + const score = (weeks) => buildHireabilityScore(summary, quality, + weeks.map((commits) => ({ commits }))); + const grouped = score([10, 10, 0, 0]); + const spread = score([10, 0, 10, 0]); + assert.ok(spread.hireabilityScore > grouped.hireabilityScore); + assert.equal(spread.scoreBreakdown[3].score, grouped.scoreBreakdown[3].score); +}); + +test('very large valid signals remain bounded and the total equals its breakdown', () => { + const result = buildHireabilityScore( + { totalRepositories: 10000, totalStars: 1000000, totalForks: 1000000 }, + { repositoriesAnalyzed: 1, repositoriesWithDescription: 1, + repositoriesWithLicense: 1, recentlyUpdatedRepositories: 1 }, + [{ commits: 1000000 }] + ); + assert.equal(result.hireabilityScore, 100); + assert.equal(result.hireabilityScore, + result.scoreBreakdown.reduce((sum, item) => sum + item.score, 0)); +}); diff --git a/docs/development-workflow.md b/docs/development-workflow.md new file mode 100644 index 0000000..4cc255a --- /dev/null +++ b/docs/development-workflow.md @@ -0,0 +1,99 @@ +# Development workflow + +Start each task with expected behavior and acceptance criteria. Implement a +focused change on a branch, run the checks, and open a PR with validation evidence. +A human reviews the resulting diff before merging. Deployment follows the +repository's hosting configuration. + +## Local checks + +Use Node 22 and install locked dependencies: + +```sh +npm ci +npm --prefix backend ci +npm --prefix frontend ci +npm run check +``` + +The backend tests are offline and use mocked GitHub responses. They cover scoring +boundaries, inactive periods, cache expiry, shared concurrent requests, failed +request cleanup, repository pagination, upstream errors, partial event data, +and HTTP response contracts. The frontend check is a production build; it does +not substitute for browser testing of interactions or visual changes. + +Audit all three lockfiles before dependency changes are merged: + +The backend overrides Express 4's older `qs` range to a patched 6.x release. +Remove the override once Express itself requests a patched version; validate +query parsing and API tests when updating it. + +```sh +npm audit --audit-level=high +npm --prefix backend audit --audit-level=high +npm --prefix frontend audit --audit-level=high +``` + +With Gitleaks installed, scan history: + +```sh +gitleaks git . --redact --log-opts="--all" +``` + +To check uncommitted file contents, use `gitleaks dir . --redact` in a clean +checkout without real `.env` files or +dependency/build directories. Do not print secrets when investigating findings. +If a real secret was committed, rotate it; removing the file alone is insufficient. + +## GitHub setup after pushing + +### Cost policy + +This workflow requires no paid subscription while DevTrack remains public. +Standard GitHub-hosted runners are free for public repositories. The tests, +npm audit, standalone Gitleaks CLI, and public-repository branch protections +do not require paid tools. Do not switch to larger runners or add subscriptions +without explicit approval. + +An account billing lock can still prevent GitHub from starting these free jobs. +Check existing subscriptions in Billing and licensing; the workflow does not +require upgrading or adding a payment method. GitHub documents downgrading to +Free as an option for locked accounts with public repositories. Existing charges +and separate subscriptions must be checked independently. + +References: [standard runners](https://docs.github.com/en/actions/reference/runners/github-hosted-runners), +[locked accounts](https://docs.github.com/en/billing/how-tos/troubleshooting/locked-account), +and [downgrading plans](https://docs.github.com/en/billing/how-tos/manage-plan-and-licenses/downgrade-plan). + +The `CI` workflow runs for PRs and pushes to main/master. It runs offline tests, +the production build, audits all lockfiles (blocking high/critical advisories), +and uses the standalone Gitleaks CLI image to scan full Git history with redacted +output. CI has read-only repository permissions and receives no production secrets. +Dependabot proposes weekly npm and Actions updates for review. + +These files do not change GitHub repository settings. Protect the default branch +by requiring a PR and the `Tests and frontend build`, `Dependency audit`, and +`Secret scan` status checks. Require the branch to be current with the base branch +and resolve review conversations before merging. + +DevTrack currently has one collaborator. Set required approvals to zero so the +owner can review the diff and checks and merge their own PR. GitHub does not allow +authors to approve their own PRs. Once another reviewer is available, consider +requiring one approval and dismissing approvals when new changes are pushed. +Enable dependency graph, secret scanning, and push protection where the repository +plan supports them. Review bypass permissions so the rules have practical effect. + +Check Vercel and Render settings separately: deployments must follow the intended +merge policy. Existing deployment configuration is not modified by this workflow. + +## Agent access and progress + +Configure access in the agent host: checkout-scoped writes, restricted network +access, mocked tests, and development-only credentials when a task truly needs them. +`AGENTS.md` documents behavior but cannot enforce a sandbox. Keep production +credentials outside the agent environment. + +For each task, record the PR, check results, elapsed time, retries, and corrections +needed during review. Use that evidence to decide whether parallel agents or a +different model actually improve accepted changes per unit of time/cost. Introduce +parallel work only on request, using independent tasks and separate worktrees. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a7142b8..0c92c9b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -116,12 +116,12 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.129.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz", - "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==", + "version": "0.153.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.153.0.tgz", + "integrity": "sha512-jSNrRIyvPVLL5E1z/tS5zLAprosGz/FuI1PWwEOz59j3E4TTTzSpqKb/RDGqJhhW0QHYISGXxq582wGx6T11Tw==", "license": "MIT", "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://github.com/sponsors/oxc-project" } }, "node_modules/@reduxjs/toolkit": { @@ -160,10 +160,26 @@ "url": "https://opencollective.com/immer" } }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.13.tgz", + "integrity": "sha512-Vu/NICrgdRvUOeaJZIyB2FP63s6fO/4ud5l8dXmxDx9yDyn+mN0y+gsvsVviSli+T361Aqo0h46EuMoobrwawA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz", - "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.13.tgz", + "integrity": "sha512-6LEoIrGy5vvCd2boNnv2soPSUZj/PRvhp8R/t9KlX+mIZxThIE/ZqxrGXxmuNX0FuvQFvLdZr7S4HrYpYBVhEQ==", "cpu": [ "arm64" ], @@ -177,9 +193,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz", - "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.13.tgz", + "integrity": "sha512-CaJSRzlQ6Q+zkDMVA9OkVxBjVoPRCDEE3v0FSJ9kIQBgRjFApA3Si4ijhbNVUji7ZJt+PfO0e46qy4nPInHUPA==", "cpu": [ "arm64" ], @@ -193,9 +209,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz", - "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.13.tgz", + "integrity": "sha512-e4r340I5hxSzJkD3YJBxgzq/rJ3wwxwdLLb2FPt1Zpyem20nrQPT11DDEEkazwxaHumH8wpglsp/Ozgj/wT6mw==", "cpu": [ "x64" ], @@ -209,9 +225,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz", - "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.13.tgz", + "integrity": "sha512-hoBWDZ4/K/mJKVMbsgUd1jfpA2DiOrJ54mpegObU9TBHBBrtX4FLIXJJQRDPZ/2Dn91GvPob+Z2hwKJzf4E5SQ==", "cpu": [ "x64" ], @@ -225,9 +241,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz", - "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.13.tgz", + "integrity": "sha512-oF9fZu3vTWnTiVAkDFxU9PJLt1nh20K9RRpuHsR7ZnE2L7XookBbDBVLojCVSLGbvtSlxeUNnjAj6DbNsKyzbg==", "cpu": [ "arm" ], @@ -241,9 +257,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz", - "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.13.tgz", + "integrity": "sha512-DTpb/+jMiqWMPgphn4ksW1zW+XLJ6dJJ9FMvnIk9I291LhCctMPhMYJrP5t9p6C9qa3KXadDdUEZHBaPh3kNxQ==", "cpu": [ "arm64" ], @@ -260,9 +276,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz", - "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.13.tgz", + "integrity": "sha512-xU+Kvv9N2A3SY35zFh1FWWNjxbadE/BKeqnkXe7vScU4GZsw757BlVmDLITZEq8DPpk6m7uoDYkK7P+x/uU5Ow==", "cpu": [ "arm64" ], @@ -279,9 +295,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz", - "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.13.tgz", + "integrity": "sha512-XMk/hnhh25oQ756Lclb1Q11h4ll0xC0Esa5w4Vzp+mAE2S3AGJgeM0SonrIgY8fHhjarb6hcoTuwoFG0VVP2ig==", "cpu": [ "ppc64" ], @@ -298,9 +314,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz", - "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.13.tgz", + "integrity": "sha512-U1BoTHYmlywE2n3HUWjzZ+8EQsKr+OwpxYuDWaubx42tUL8QSL7nOqRz/NVHanibbFP3M1Rxuprz/wNcbMKjqw==", "cpu": [ "s390x" ], @@ -317,9 +333,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz", - "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.13.tgz", + "integrity": "sha512-J4cm80Pc6wEwV5AZ42o62sIQgDk+Y7R2s646jIwuvO+4V46QB58N4vVuMPWCBaNgursXLpmUWJV//WQAkPvHBQ==", "cpu": [ "x64" ], @@ -336,9 +352,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz", - "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.13.tgz", + "integrity": "sha512-W0euZS40U4V6JkmEnVCIX9mcsL6cbXQ4X8c2w+MMOIjFg68TXUW+u3RT+2e1t1pv9mg/EP3pDtK3POXdQ7Rxyw==", "cpu": [ "x64" ], @@ -355,9 +371,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz", - "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.13.tgz", + "integrity": "sha512-do/AF/4n9MRshia3a5aXoy8xM2c6mgfT6D7H/dY6my5z+hfCIR4ab7mk4IRj++nsEQpKy39kd0DQnDq+r692tw==", "cpu": [ "arm64" ], @@ -370,28 +386,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz", - "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==", - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz", - "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.13.tgz", + "integrity": "sha512-6FtUf2cJZobutJdxnGYX8z0vtbgqU0b2v8vfBhkXb9wp2MppMFtkunE+IaOntyi01rkIQoPW2GHABtfzsC0BRQ==", "cpu": [ "arm64" ], @@ -405,9 +403,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz", - "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.13.tgz", + "integrity": "sha512-UKhFaZTeTcbKrb3iU82m17jVcPEAoqDkCN5MBqgPyZSwp/cQmib21crTS0DdH7T1B7wZRWEI89YQAOJMet5yhg==", "cpu": [ "x64" ], @@ -421,9 +419,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz", - "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "license": "MIT" }, "node_modules/@standard-schema/spec": { @@ -1371,9 +1369,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "funding": [ { "type": "github", @@ -1395,9 +1393,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", "engines": { "node": ">=12" @@ -1407,9 +1405,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.30", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz", + "integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==", "funding": [ { "type": "opencollective", @@ -1426,9 +1424,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -1537,13 +1535,13 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz", - "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.13.tgz", + "integrity": "sha512-/5apOkwWxtACcQtZ5WlrAsKS4JwXVThQPJZ3RqIruRY9UG9M8e0VlXN068xum/JRKA4aIUoJ13ghe/kGCvidiQ==", "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.129.0", - "@rolldown/pluginutils": "1.0.0" + "@oxc-project/types": "=0.153.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -1552,21 +1550,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0", - "@rolldown/binding-darwin-arm64": "1.0.0", - "@rolldown/binding-darwin-x64": "1.0.0", - "@rolldown/binding-freebsd-x64": "1.0.0", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0", - "@rolldown/binding-linux-arm64-gnu": "1.0.0", - "@rolldown/binding-linux-arm64-musl": "1.0.0", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0", - "@rolldown/binding-linux-s390x-gnu": "1.0.0", - "@rolldown/binding-linux-x64-gnu": "1.0.0", - "@rolldown/binding-linux-x64-musl": "1.0.0", - "@rolldown/binding-openharmony-arm64": "1.0.0", - "@rolldown/binding-wasm32-wasi": "1.0.0", - "@rolldown/binding-win32-arm64-msvc": "1.0.0", - "@rolldown/binding-win32-x64-msvc": "1.0.0" + "@rolldown/binding-android-arm-eabi": "1.2.13", + "@rolldown/binding-android-arm64": "1.2.13", + "@rolldown/binding-darwin-arm64": "1.2.13", + "@rolldown/binding-darwin-x64": "1.2.13", + "@rolldown/binding-freebsd-x64": "1.2.13", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.13", + "@rolldown/binding-linux-arm64-gnu": "1.2.13", + "@rolldown/binding-linux-arm64-musl": "1.2.13", + "@rolldown/binding-linux-ppc64-gnu": "1.2.13", + "@rolldown/binding-linux-s390x-gnu": "1.2.13", + "@rolldown/binding-linux-x64-gnu": "1.2.13", + "@rolldown/binding-linux-x64-musl": "1.2.13", + "@rolldown/binding-openharmony-arm64": "1.2.13", + "@rolldown/binding-win32-arm64-msvc": "1.2.13", + "@rolldown/binding-win32-x64-msvc": "1.2.13" } }, "node_modules/scheduler": { @@ -1576,9 +1574,9 @@ "license": "MIT" }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -1610,9 +1608,9 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -1663,16 +1661,16 @@ } }, "node_modules/vite": { - "version": "8.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz", - "integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz", + "integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==", "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.14", - "rolldown": "1.0.0", - "tinyglobby": "^0.2.16" + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.29", + "rolldown": "~1.2.12", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -1688,7 +1686,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.7.1", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -1738,6 +1736,267 @@ "optional": true } } + }, + "node_modules/vite/node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/vite/node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } } } } diff --git a/package-lock.json b/package-lock.json index d1d05cd..2d816bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,15 +66,15 @@ } }, "node_modules/concurrently": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.3.tgz", - "integrity": "sha512-hc3LH4UaKWd/bbyDK/IGVa4RB6PtQ3CUYwtrkzqHn+wIG3Hr5fhpRlk0L/gCa8ZE1L/Ufj50Zho69cI5w8SQBA==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-10.0.6.tgz", + "integrity": "sha512-qgICXXFp2/3nAebRLmxcINKEXM1mLK3ij/y31P02VTs/KIkddIeC05n73DxOXsXMB9LFSqNU1/na3lDlOhcbIw==", "dev": true, "license": "MIT", "dependencies": { "chalk": "5.6.2", "rxjs": "7.8.2", - "shell-quote": "1.8.4", + "shell-quote": "1.12.0", "supports-color": "10.2.2", "tree-kill": "1.2.2", "yargs": "18.0.0" @@ -141,9 +141,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", - "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.12.0.tgz", + "integrity": "sha512-PcByqNyT/38F2kDNi006HAMRJaULuBzq/FOsw3qdZvX/GA9W/jamDaRskgHjubHiftXK5sIFxLNkvrXUwcof6Q==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index c45eb41..a597fe9 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "version": "1.0.0", "description": "DevTrack monorepo", "scripts": { + "test": "npm --prefix backend test", + "build": "npm --prefix frontend run build", + "check": "npm test && npm run build", "start": "npm --prefix backend start", "dev": "npm --prefix backend run dev", "start:backend": "npm --prefix backend start",