From 0ca230db7cd60735add48899192b5bd57009fe3b Mon Sep 17 00:00:00 2001 From: Sven Reiser Date: Sat, 20 Jun 2026 21:14:06 +0200 Subject: [PATCH 1/3] ci: add Biome + Vitest + git-hook quality gates --- .github/workflows/ci.yml | 34 ++ .husky/pre-commit | 1 + .husky/pre-push | 6 + biome.json | 42 ++ package-lock.json | 819 +++++++++++++++++++++++++++++++++------ package.json | 15 +- 6 files changed, 788 insertions(+), 129 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .husky/pre-commit create mode 100644 .husky/pre-push create mode 100644 biome.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..174dd66 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + packages: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + # Installs @mind-studio/* from GitHub Packages via the committed .npmrc. + # GITHUB_TOKEN can read public packages in the same MIND-Studio org; if a + # cross-repo read is ever refused, set a NODE_AUTH_TOKEN secret to an org + # PAT with read:packages. + - run: npm ci + env: + NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN || secrets.GITHUB_TOKEN }} + # Lint + format gate (Biome). Fails on errors; warnings are the + # documented legacy baseline tracked in biome.json. + - run: npx biome ci . + - run: npx tsc --noEmit + - run: npm run test + - run: npm run build + env: + NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 0000000..3885023 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,6 @@ +# Fast confidence gate before code leaves this machine — mirrors CI's +# correctness checks. The slow `next build` is left to CI. +# Bypass in a pinch with `git push --no-verify`. +npm run lint || exit 1 +npm run typecheck || exit 1 +npm run test || exit 1 diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..9bc1cf3 --- /dev/null +++ b/biome.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.5.0/schema.json", + "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, + "files": { + "ignoreUnknown": true, + "includes": [ + "**", + "!**/public", + "!**/scripts", + "!**/next-env.d.ts", + "!**/package-lock.json", + "!**/*.css" + ] + }, + "formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2, "lineWidth": 100 }, + "linter": { + "enabled": true, + "domains": { "next": "recommended", "react": "recommended" }, + "rules": { + "recommended": true, + "correctness": { "useExhaustiveDependencies": "warn" }, + "security": { "noDangerouslySetInnerHtml": "warn" }, + "suspicious": { + "noImplicitAnyLet": "warn", + "useIterableCallbackReturn": "warn", + "noArrayIndexKey": "warn", + "noCommentText": "warn", + "noControlCharactersInRegex": "warn", + "noAssignInExpressions": "warn" + }, + "a11y": { + "useButtonType": "warn", + "noLabelWithoutControl": "warn", + "noStaticElementInteractions": "warn", + "noSvgWithoutTitle": "warn", + "noAutofocus": "warn" + } + } + }, + "javascript": { "formatter": { "quoteStyle": "double" } }, + "assist": { "enabled": true, "actions": { "source": { "organizeImports": "on" } } } +} diff --git a/package-lock.json b/package-lock.json index 3162046..aff86f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,16 +37,19 @@ "yjs": "^13.6.20" }, "devDependencies": { + "@biomejs/biome": "^2.5.0", "@tailwindcss/postcss": "^4", "@types/better-sqlite3": "^7.6.13", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "@types/ws": "^8.5.13", + "husky": "^9.1.7", + "lint-staged": "^17.0.7", "tailwindcss": "^4", "tsx": "^4.22.3", "typescript": "^5", - "vitest": "^4.1.7", + "vitest": "^4.1.9", "ws": "^8.18.0" } }, @@ -144,6 +147,169 @@ "buffer": "^6.0.3" } }, + "node_modules/@biomejs/biome": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.5.0.tgz", + "integrity": "sha512-4kURkd9hAPrdDM3C9n82ycYgx8hvQcW6MjKTEejruj8rK0N8P3OPpdy8BvI8kt3KWY4ycF5XtDOrktetEfhfuw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.5.0", + "@biomejs/cli-darwin-x64": "2.5.0", + "@biomejs/cli-linux-arm64": "2.5.0", + "@biomejs/cli-linux-arm64-musl": "2.5.0", + "@biomejs/cli-linux-x64": "2.5.0", + "@biomejs/cli-linux-x64-musl": "2.5.0", + "@biomejs/cli-win32-arm64": "2.5.0", + "@biomejs/cli-win32-x64": "2.5.0" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.5.0.tgz", + "integrity": "sha512-Mn3Fwi3SA5fgmfCPqmzpWF2DLZnms3BVAhM088nTnGrTZmHS3wwIjcoZPqpXeNgd3DrrLH6xp8vTLIBuJoZiXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.5.0.tgz", + "integrity": "sha512-rg3VPL5P8mYro6pqlXYXuJWph21slVp3SZtAqWSrkZs40d2gTzYmHF8E/X1iTID25btmNKltNDJ926sqVBp7DQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.5.0.tgz", + "integrity": "sha512-tl+LW8fdD96/xdeWtWwc82LIOc5CoY7N2AsogLTp5R4ECErYt+8Jl/N68ezN9vzSiqPTxw6vjcihoLPYKZHrlw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.5.0.tgz", + "integrity": "sha512-vQdM4oSGaf7ZNeGO9w5+Y8SBtyser9M6znxYbm7Ec8wInxJu1WiKxFYZW5Auj2d80bcVvefuGGRxoFOE0eee8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.5.0.tgz", + "integrity": "sha512-zpEGf4RQbFEh8Vt7OmavLyyOzRbtcE9osCqrS1kfvt8jDvxwhKXLSf7n0ebr/ov0RJ9ssP+lhs6C8a9WwFvrQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.5.0.tgz", + "integrity": "sha512-+9hIcMngJ+yGUahXqZuZ8CoWKJE9SAZsFsM3QDvXpNsLbXZ9lqVzgBhOk/jTSYkOA0GLP9eu3teukqpLUojHMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.5.0.tgz", + "integrity": "sha512-jB0wAvTLI4itx5VidqVUejPQFhRUxiZ9l9FvZ26D5fl6t3qme+ZB4PD3bTSeL1vZ8NI2Rx/zj6H9zcESuGHKGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.5.0.tgz", + "integrity": "sha512-VT/lF+GId+67j8aDfLkxdxNoVApsPSTbyAtB3jJq0IWTrY77WXfbPfpngxq0bA6JCEv/7k8C9qWjDRKRznDlyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@date-fns/tz": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.5.0.tgz", @@ -1417,14 +1583,14 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", + "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.2" }, "funding": { "type": "github", @@ -1570,9 +1736,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.132.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", - "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "version": "0.133.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", + "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", "dev": true, "license": "MIT", "funding": { @@ -3158,9 +3324,9 @@ "license": "MIT" }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", - "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", + "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", "cpu": [ "arm64" ], @@ -3175,9 +3341,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", - "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", + "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "cpu": [ "arm64" ], @@ -3192,9 +3358,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", - "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", + "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", "cpu": [ "x64" ], @@ -3209,9 +3375,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", - "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", + "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", "cpu": [ "x64" ], @@ -3226,9 +3392,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", - "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", + "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", "cpu": [ "arm" ], @@ -3243,9 +3409,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", - "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", + "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", "cpu": [ "arm64" ], @@ -3260,9 +3426,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", - "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", + "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", "cpu": [ "arm64" ], @@ -3277,9 +3443,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", - "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", + "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", "cpu": [ "ppc64" ], @@ -3294,9 +3460,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", - "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", + "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", "cpu": [ "s390x" ], @@ -3311,9 +3477,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", - "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", + "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", "cpu": [ "x64" ], @@ -3328,9 +3494,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", - "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", + "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", "cpu": [ "x64" ], @@ -3345,9 +3511,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", - "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", + "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", "cpu": [ "arm64" ], @@ -3362,9 +3528,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", - "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", + "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", "cpu": [ "wasm32" ], @@ -3381,9 +3547,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", - "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", + "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", "cpu": [ "arm64" ], @@ -3398,9 +3564,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", - "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", + "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", "cpu": [ "x64" ], @@ -4497,16 +4663,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", - "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", + "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.7", - "@vitest/utils": "4.1.7", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -4515,13 +4681,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", - "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", + "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.7", + "@vitest/spy": "4.1.9", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -4542,9 +4708,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz", - "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", + "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", "dev": true, "license": "MIT", "dependencies": { @@ -4555,13 +4721,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz", - "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", + "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.7", + "@vitest/utils": "4.1.9", "pathe": "^2.0.3" }, "funding": { @@ -4569,14 +4735,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz", - "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", + "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.7", - "@vitest/utils": "4.1.7", + "@vitest/pretty-format": "4.1.9", + "@vitest/utils": "4.1.9", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -4585,9 +4751,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz", - "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", + "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", "dev": true, "license": "MIT", "funding": { @@ -4595,13 +4761,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz", - "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", + "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.7", + "@vitest/pretty-format": "4.1.9", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -4621,6 +4787,48 @@ "node": ">=6.5" } }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -4831,6 +5039,39 @@ "url": "https://polar.sh/cva" } }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -5092,6 +5333,13 @@ "embla-carousel": "8.6.0" } }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -5127,6 +5375,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-module-lexer": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", @@ -5301,6 +5562,19 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -5332,6 +5606,22 @@ "node": ">=6.0.0" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -5393,6 +5683,22 @@ "node": ">=12" } }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isomorphic.js": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", @@ -5787,6 +6093,121 @@ "uc.micro": "^2.0.0" } }, + "node_modules/lint-staged": { + "version": "17.0.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.0.7.tgz", + "integrity": "sha512-JrSobt+tW3rH8IOMi8tDZd3foorM5yPEkLD/V2NxobgHrFfHWGee4MOLVuZeScgxftEwbHrPHIFA/ZL+nUJeuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "listr2": "^10.2.1", + "picomatch": "^4.0.4", + "string-argv": "^0.3.2", + "tinyexec": "^1.2.4" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=22.22.1" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + }, + "optionalDependencies": { + "yaml": "^2.9.0" + } + }, + "node_modules/listr2": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-10.2.1.tgz", + "integrity": "sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.2.0", + "eventemitter3": "^5.0.4", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^10.0.0" + }, + "engines": { + "node": ">=22.13.0" + } + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -5869,6 +6290,19 @@ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "license": "MIT" }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mimic-response": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", @@ -6058,6 +6492,22 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/openid-client": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", @@ -6748,14 +7198,38 @@ "integrity": "sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==", "license": "MIT" }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rolldown": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", - "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", + "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.132.0", + "@oxc-project/types": "=0.133.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6765,21 +7239,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.2", - "@rolldown/binding-darwin-arm64": "1.0.2", - "@rolldown/binding-darwin-x64": "1.0.2", - "@rolldown/binding-freebsd-x64": "1.0.2", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", - "@rolldown/binding-linux-arm64-gnu": "1.0.2", - "@rolldown/binding-linux-arm64-musl": "1.0.2", - "@rolldown/binding-linux-ppc64-gnu": "1.0.2", - "@rolldown/binding-linux-s390x-gnu": "1.0.2", - "@rolldown/binding-linux-x64-gnu": "1.0.2", - "@rolldown/binding-linux-x64-musl": "1.0.2", - "@rolldown/binding-openharmony-arm64": "1.0.2", - "@rolldown/binding-wasm32-wasi": "1.0.2", - "@rolldown/binding-win32-arm64-msvc": "1.0.2", - "@rolldown/binding-win32-x64-msvc": "1.0.2" + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" } }, "node_modules/rope-sequence": { @@ -6884,6 +7358,19 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/simple-concat": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", @@ -6929,6 +7416,23 @@ "simple-concat": "^1.0.0" } }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/sonner": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", @@ -6971,6 +7475,49 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -7089,9 +7636,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.3.tgz", - "integrity": "sha512-g62dB+w1/OEFnPvmX0yd/HnetYITOL+1nJW7kitOycOeAvmbWC/nu0fwmmQ/kupNojqExzyC/T++pST/jRJ2mQ==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "dev": true, "license": "MIT", "engines": { @@ -7099,9 +7646,9 @@ } }, "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==", "dev": true, "license": "MIT", "dependencies": { @@ -7344,17 +7891,17 @@ } }, "node_modules/vite": { - "version": "8.0.14", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", - "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", + "version": "8.0.16", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", + "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", - "rolldown": "1.0.2", - "tinyglobby": "^0.2.16" + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -7422,19 +7969,19 @@ } }, "node_modules/vitest": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz", - "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", + "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.7", - "@vitest/mocker": "4.1.7", - "@vitest/pretty-format": "4.1.7", - "@vitest/runner": "4.1.7", - "@vitest/snapshot": "4.1.7", - "@vitest/spy": "4.1.7", - "@vitest/utils": "4.1.7", + "@vitest/expect": "4.1.9", + "@vitest/mocker": "4.1.9", + "@vitest/pretty-format": "4.1.9", + "@vitest/runner": "4.1.9", + "@vitest/snapshot": "4.1.9", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -7462,12 +8009,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.7", - "@vitest/browser-preview": "4.1.7", - "@vitest/browser-webdriverio": "4.1.7", - "@vitest/coverage-istanbul": "4.1.7", - "@vitest/coverage-v8": "4.1.7", - "@vitest/ui": "4.1.7", + "@vitest/browser-playwright": "4.1.9", + "@vitest/browser-preview": "4.1.9", + "@vitest/browser-webdriverio": "4.1.9", + "@vitest/coverage-istanbul": "4.1.9", + "@vitest/coverage-v8": "4.1.9", + "@vitest/ui": "4.1.9", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -7534,6 +8081,24 @@ "node": ">=8" } }, + "node_modules/wrap-ansi": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-10.0.0.tgz", + "integrity": "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "string-width": "^8.2.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index f4764bb..59a707e 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,13 @@ "tracker:check": "tsx scripts/tracker-build.ts --check", "calendar:build": "tsx scripts/calendar-build.ts", "calendar:check": "tsx scripts/calendar-build.ts --check", - "test": "vitest run", + "test": "vitest run --passWithNoTests", "test:watch": "vitest", + "typecheck": "tsc --noEmit", + "lint": "biome check .", + "lint:fix": "biome check --write .", + "format": "biome format --write .", + "prepare": "husky", "seed:demo": "tsx scripts/seed-demo.ts", "seed:tracker": "tsx scripts/seed-tracker-demo.ts", "seed:profiles": "tsx scripts/seed-profiles.ts", @@ -55,18 +60,24 @@ "yjs": "^13.6.20" }, "devDependencies": { + "@biomejs/biome": "^2.5.0", "@tailwindcss/postcss": "^4", "@types/better-sqlite3": "^7.6.13", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "@types/ws": "^8.5.13", + "husky": "^9.1.7", + "lint-staged": "^17.0.7", "tailwindcss": "^4", "tsx": "^4.22.3", "typescript": "^5", - "vitest": "^4.1.7", + "vitest": "^4.1.9", "ws": "^8.18.0" }, + "lint-staged": { + "*.{js,jsx,ts,tsx,json,jsonc}": "biome check --write --no-errors-on-unmatched" + }, "overrides": { "postcss": "^8.5.10" } From 840c421a8f4cfda4fcbccca668a5857e3efc85ef Mon Sep 17 00:00:00 2001 From: Sven Reiser Date: Sat, 20 Jun 2026 21:14:17 +0200 Subject: [PATCH 2/3] style: apply Biome formatting across app source --- infra/css/config.json | 2 +- relay/server.ts | 31 +-- src/app/api/admin/reconcile/route.ts | 9 +- src/app/api/agent-runs/[id]/log/route.ts | 4 +- src/app/api/agents/dispatch/route.ts | 23 +- src/app/api/agents/route.ts | 6 +- src/app/api/auth/callback/route.ts | 6 +- src/app/api/auth/login/route.ts | 26 +-- src/app/api/auth/start/route.ts | 19 +- .../api/git/[owner]/[repo]/[...path]/route.ts | 21 +- .../api/git/internal/post-receive/route.ts | 45 +--- src/app/api/health/route.ts | 4 +- src/app/api/identities/[webId]/route.ts | 7 +- src/app/api/metrics/route.ts | 7 +- .../npm/[owner]/[repo]/[...path]/route.ts | 54 ++--- .../api/profile/ai/keys/[provider]/route.ts | 12 +- src/app/api/profile/ai/pref/route.ts | 13 +- src/app/api/profile/ai/route.ts | 12 +- .../[owner]/[repo]/files/[...path]/route.ts | 46 ++-- .../[owner]/[repo]/inbox/[id]/accept/route.ts | 10 +- .../repos/[owner]/[repo]/inbox/[id]/route.ts | 4 +- .../api/repos/[owner]/[repo]/inbox/route.ts | 4 +- .../issues/[number]/agent-runs/route.ts | 4 +- .../[repo]/issues/[number]/comments/route.ts | 23 +- .../[owner]/[repo]/issues/[number]/route.ts | 27 ++- .../[owner]/[repo]/issues/propose/route.ts | 11 +- .../api/repos/[owner]/[repo]/issues/route.ts | 26 +-- .../[owner]/[repo]/members/[webid]/route.ts | 4 +- .../api/repos/[owner]/[repo]/members/route.ts | 14 +- .../repos/[owner]/[repo]/mind-epics/route.ts | 6 +- .../repos/[owner]/[repo]/mind-issues/route.ts | 35 +-- .../repos/[owner]/[repo]/packages/route.ts | 4 +- .../api/repos/[owner]/[repo]/pages/route.ts | 26 +-- .../[repo]/pulls/[number]/close/route.ts | 4 +- .../[repo]/pulls/[number]/merge/route.ts | 20 +- .../[repo]/pulls/[number]/preview/route.ts | 14 +- .../[owner]/[repo]/pulls/[number]/route.ts | 2 +- .../api/repos/[owner]/[repo]/pulls/route.ts | 41 +--- src/app/api/repos/[owner]/[repo]/route.ts | 41 ++-- .../api/repos/[owner]/[repo]/runs/route.ts | 33 +-- .../repos/[owner]/[repo]/tokens/[id]/route.ts | 2 +- .../api/repos/[owner]/[repo]/tokens/route.ts | 12 +- src/app/api/repos/route.ts | 50 ++--- src/app/api/signup/route.ts | 43 +--- src/app/connect/page.tsx | 45 ++-- src/app/how-it-works/page.tsx | 179 +++++++-------- src/app/identities/identity-row.tsx | 25 +-- src/app/identities/page.tsx | 19 +- src/app/layout.tsx | 46 ++-- src/app/login/login-page.tsx | 40 +--- src/app/not-found.tsx | 10 +- src/app/page.tsx | 130 ++++------- src/app/people/[owner]/page.tsx | 6 +- src/app/people/page.tsx | 34 +-- src/app/people/profile-view.tsx | 124 +++-------- src/app/profile/ai-providers/manage.tsx | 106 ++++----- src/app/profile/ai-providers/page.tsx | 20 +- src/app/profile/page.tsx | 47 ++-- src/app/profile/profile-settings.tsx | 8 +- .../[owner]/[repo]/blob/[...path]/page.tsx | 20 +- .../[repo]/issues/[number]/agent-run-card.tsx | 49 ++-- .../[repo]/issues/[number]/comment-form.tsx | 25 +-- .../[repo]/issues/[number]/issue-actions.tsx | 18 +- .../[owner]/[repo]/issues/[number]/page.tsx | 38 ++-- .../draft/[draftId]/collaborative-draft.tsx | 75 +++---- .../issues/draft/[draftId]/draft-loader.tsx | 4 +- .../[repo]/issues/draft/[draftId]/page.tsx | 20 +- .../[repo]/issues/new/new-issue-form.tsx | 15 +- .../repos/[owner]/[repo]/issues/new/page.tsx | 14 +- src/app/repos/[owner]/[repo]/issues/page.tsx | 72 ++---- .../[owner]/[repo]/issues/propose/page.tsx | 34 ++- .../[repo]/issues/propose/propose-form.tsx | 15 +- src/app/repos/[owner]/[repo]/nav-tabs.tsx | 12 +- .../repos/[owner]/[repo]/packages/page.tsx | 33 +-- src/app/repos/[owner]/[repo]/page.tsx | 210 ++++++------------ .../repos/[owner]/[repo]/proposals/page.tsx | 34 ++- .../[repo]/proposals/proposal-actions.tsx | 29 +-- .../[owner]/[repo]/pulls/[number]/page.tsx | 65 ++---- .../[repo]/pulls/[number]/pr-preview-card.tsx | 24 +- .../[repo]/pulls/[number]/pull-actions.tsx | 20 +- src/app/repos/[owner]/[repo]/pulls/page.tsx | 87 ++------ src/app/repos/[owner]/[repo]/repo-tabs.tsx | 25 +-- src/app/repos/[owner]/[repo]/rerun-button.tsx | 28 +-- .../repos/[owner]/[repo]/runs/[id]/page.tsx | 24 +- src/app/repos/[owner]/[repo]/runs/page.tsx | 30 +-- .../repos/[owner]/[repo]/settings/page.tsx | 79 +++---- .../[repo]/settings/settings-forms.tsx | 47 +--- .../repos/[owner]/[repo]/token-manager.tsx | 48 ++-- .../[owner]/[repo]/tree/[[...path]]/page.tsx | 36 +-- src/app/repos/_components/repo-list.tsx | 95 ++------ src/app/repos/new/new-repo-form.tsx | 35 +-- src/app/repos/new/page.tsx | 21 +- src/app/repos/page.tsx | 14 +- src/app/signup/signup-form.tsx | 8 +- src/app/v2/[[...path]]/route.ts | 29 +-- src/components/auth-cta-server.tsx | 4 +- src/components/auth-cta.tsx | 11 +- src/components/branch-picker.tsx | 8 +- src/components/copy-button.tsx | 2 +- src/components/diff-view.tsx | 13 +- src/components/feedback-launcher.tsx | 7 +- src/components/main-nav.tsx | 2 +- src/components/matrix-rain.tsx | 90 ++++++-- src/components/relative-time.tsx | 7 +- src/components/sign-in-wall.tsx | 11 +- src/components/theme-shell.tsx | 9 +- src/components/theme-toggle.tsx | 7 +- src/lib/agents/bootstrap.ts | 15 +- src/lib/agents/dispatch.ts | 32 +-- src/lib/agents/drivers/coder.ts | 103 +++------ src/lib/agents/drivers/codex.ts | 64 ++---- src/lib/agents/drivers/echo.ts | 3 +- src/lib/agents/drivers/openrouter.ts | 12 +- src/lib/agents/registry.ts | 4 +- src/lib/ai-providers/providers.ts | 31 +-- src/lib/ai-providers/store.ts | 46 ++-- src/lib/auth/csrf-client.ts | 5 +- src/lib/auth/session.ts | 13 +- src/lib/bootstrap.ts | 2 +- src/lib/collab/config.ts | 13 +- src/lib/collab/draft-doc.ts | 11 +- src/lib/env.ts | 30 +-- src/lib/git/backend.ts | 36 +-- src/lib/git/diff.ts | Bin 4258 -> 4195 bytes src/lib/git/http-cgi.ts | 19 +- src/lib/git/merge.ts | 20 +- src/lib/git/objects.ts | 38 +--- src/lib/http/json.ts | 5 +- src/lib/ledger/client.ts | 6 +- src/lib/ledger/policy.ts | 4 +- src/lib/metrics.ts | 23 +- src/lib/packages/auth.ts | 5 +- src/lib/packages/content-store.ts | 6 +- src/lib/packages/repo-store.ts | 2 +- src/lib/packages/store.ts | 33 +-- src/lib/pages/preview.ts | 39 +--- src/lib/pages/publisher.ts | 72 ++---- src/lib/pages/reconciler.ts | 22 +- src/lib/rate-limit.ts | 15 +- src/lib/registry/activity.ts | 14 +- src/lib/registry/agent-runs.ts | 39 ++-- src/lib/registry/db.ts | 18 +- src/lib/registry/identities.ts | 37 +-- src/lib/registry/issue-projection.ts | 9 +- src/lib/registry/issues.ts | 43 ++-- src/lib/registry/pulls.ts | 57 ++--- src/lib/registry/quotas.ts | 44 +--- src/lib/registry/repos.ts | 60 ++--- src/lib/registry/runs.ts | 39 +--- src/lib/registry/tokens.ts | 6 +- src/lib/registry/users.ts | 12 +- src/lib/solid/auth.ts | 25 +-- src/lib/solid/containers.ts | 40 ++-- src/lib/solid/css-account.ts | 85 ++----- src/lib/solid/fetch-for-owner.ts | 16 +- src/lib/solid/inbox.ts | 26 +-- src/lib/solid/issues.ts | 14 +- src/lib/solid/members.ts | 64 ++---- src/lib/solid/oidc-server.ts | 24 +- src/lib/solid/profile.ts | 17 +- src/lib/solid/pulls.ts | 14 +- src/lib/solid/repo-metadata.ts | 34 +-- src/lib/solid/tracker-pod.ts | 30 +-- src/lib/tracker/author.ts | 37 +-- src/lib/tracker/build.ts | 38 +++- src/lib/tracker/model.ts | 9 +- src/lib/tracker/parse.ts | 52 ++--- src/lib/tracker/read.ts | 16 +- src/lib/tracker/source.ts | 6 +- src/lib/workflows/parse.ts | 12 +- src/lib/workflows/runner.ts | 28 +-- src/proxy.ts | 7 +- tests/create-epic.test.ts | 2 +- tests/fetch-for-owner.test.ts | 10 +- tests/identity-storage-restart.test.ts | 15 +- tests/inbox-acl.test.ts | 2 +- tests/inbox-roundtrip.test.ts | 14 +- tests/issue-projection.test.ts | 18 +- tests/ledger-gate.test.ts | 2 +- tests/members-roundtrip.test.ts | 10 +- tests/merge-empty-target.test.ts | 30 +-- tests/oidc-refresh-normalize.test.ts | 10 +- tests/oidc-session-cache.test.ts | 38 +--- tests/packages-content-store.test.ts | 8 +- tests/packages-npm.test.ts | 16 +- tests/packages-oci-blob-cap.test.ts | 20 +- tests/packages-oci.test.ts | 11 +- tests/packages-store.test.ts | 11 +- tests/parse-porcelain.test.ts | 30 +-- tests/path-traversal.test.ts | 7 +- tests/publisher-walk.test.ts | 4 +- tests/pulls-roundtrip.test.ts | 23 +- tests/push-tokens.test.ts | 7 +- tests/quotas.test.ts | 10 +- tests/service-secret-auth.test.ts | 2 +- tests/tracker-parse.test.ts | 8 +- tests/tracker-pod.test.ts | 16 +- tsconfig.json | 14 +- vitest.config.ts | 2 +- 199 files changed, 1742 insertions(+), 3548 deletions(-) diff --git a/infra/css/config.json b/infra/css/config.json index 0947366..a67e4b2 100644 --- a/infra/css/config.json +++ b/infra/css/config.json @@ -10,7 +10,7 @@ " - PodResourcesGenerator.templateFolder -> seed NEW pods from the Mind pod template (Welcome README)", "All filePaths point at locations populated by the docker-compose bind mount (./infra/css -> /css-host)." ], - "import": [ "css:config/file.json" ], + "import": ["css:config/file.json"], "@graph": [ { "@type": "Override", diff --git a/relay/server.ts b/relay/server.ts index 42cf647..aa62747 100644 --- a/relay/server.ts +++ b/relay/server.ts @@ -31,12 +31,12 @@ */ import http from "node:http"; -import { WebSocketServer, WebSocket } from "ws"; -import * as Y from "yjs"; -import * as syncProtocol from "y-protocols/sync"; -import * as awarenessProtocol from "y-protocols/awareness"; -import * as encoding from "lib0/encoding"; import * as decoding from "lib0/decoding"; +import * as encoding from "lib0/encoding"; +import { WebSocket, WebSocketServer } from "ws"; +import * as awarenessProtocol from "y-protocols/awareness"; +import * as syncProtocol from "y-protocols/sync"; +import * as Y from "yjs"; const PORT = Number(process.env.RELAY_PORT ?? 3012); const HOST = process.env.RELAY_HOST ?? "0.0.0.0"; @@ -84,15 +84,7 @@ function getRoom(name: string): Room { // the reference server, which also lets a peer's own state round-trip. awareness.on( "update", - ({ - added, - updated, - removed, - }: { - added: number[]; - updated: number[]; - removed: number[]; - }) => { + ({ added, updated, removed }: { added: number[]; updated: number[]; removed: number[] }) => { const changed = added.concat(updated, removed); const encoder = encoding.createEncoder(); encoding.writeVarUint(encoder, MESSAGE_AWARENESS); @@ -235,11 +227,7 @@ function setupConnection(conn: WebSocket, roomName: string) { function closeConn() { const owned = room.conns.get(conn); if (owned && owned.size > 0) { - awarenessProtocol.removeAwarenessStates( - room.awareness, - Array.from(owned), - "relay-cleanup", - ); + awarenessProtocol.removeAwarenessStates(room.awareness, Array.from(owned), "relay-cleanup"); } room.conns.delete(conn); room.awareness.off("update", trackAwareness); @@ -270,10 +258,7 @@ function setupConnection(conn: WebSocket, roomName: string) { encoding.writeVarUint(awEncoder, MESSAGE_AWARENESS); encoding.writeVarUint8Array( awEncoder, - awarenessProtocol.encodeAwarenessUpdate( - room.awareness, - Array.from(states.keys()), - ), + awarenessProtocol.encodeAwarenessUpdate(room.awareness, Array.from(states.keys())), ); send(conn, encoding.toUint8Array(awEncoder)); } diff --git a/src/app/api/admin/reconcile/route.ts b/src/app/api/admin/reconcile/route.ts index 4f6823d..b9a26db 100644 --- a/src/app/api/admin/reconcile/route.ts +++ b/src/app/api/admin/reconcile/route.ts @@ -1,7 +1,7 @@ -import { NextResponse } from "next/server"; import { headers } from "next/headers"; -import { reconcilePages } from "@/lib/pages/reconciler"; +import { NextResponse } from "next/server"; import { getEnv } from "@/lib/env"; +import { reconcilePages } from "@/lib/pages/reconciler"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -24,10 +24,7 @@ export async function POST() { const hdrs = await headers(); const presented = (hdrs.get("authorization") ?? "").replace(/^Bearer\s+/i, ""); if (!presented || presented !== env.adminToken) { - return NextResponse.json( - { error: "invalid admin token" }, - { status: 401 }, - ); + return NextResponse.json({ error: "invalid admin token" }, { status: 401 }); } const outcomes = await reconcilePages(); return NextResponse.json({ outcomes }); diff --git a/src/app/api/agent-runs/[id]/log/route.ts b/src/app/api/agent-runs/[id]/log/route.ts index f0f21a2..eb3d363 100644 --- a/src/app/api/agent-runs/[id]/log/route.ts +++ b/src/app/api/agent-runs/[id]/log/route.ts @@ -1,8 +1,8 @@ -import { NextResponse } from "next/server"; import * as fs from "node:fs/promises"; import * as path from "node:path"; -import { getAgentRun } from "@/lib/registry/agent-runs"; +import { NextResponse } from "next/server"; import { AGENT_LOGS_DIR } from "@/lib/agents/dispatch"; +import { getAgentRun } from "@/lib/registry/agent-runs"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/agents/dispatch/route.ts b/src/app/api/agents/dispatch/route.ts index bb2632b..48b48b7 100644 --- a/src/app/api/agents/dispatch/route.ts +++ b/src/app/api/agents/dispatch/route.ts @@ -3,13 +3,10 @@ import { ensureAgentsBootstrap } from "@/lib/agents/bootstrap"; import { dispatch } from "@/lib/agents/dispatch"; import { getDriver } from "@/lib/agents/registry"; import type { AgentEvent } from "@/lib/agents/types"; -import { getRepo } from "@/lib/registry/repos"; import { requireOwner } from "@/lib/auth/session"; -import { rateLimit, RATE_LIMITS } from "@/lib/rate-limit"; -import { - assertCanDispatchRun, - QuotaExceededError, -} from "@/lib/registry/quotas"; +import { RATE_LIMITS, rateLimit } from "@/lib/rate-limit"; +import { assertCanDispatchRun, QuotaExceededError } from "@/lib/registry/quotas"; +import { getRepo } from "@/lib/registry/repos"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -54,10 +51,7 @@ export async function POST(req: Request) { const d = (body as Record).driver; if (d !== undefined && d !== null) { if (typeof d !== "string" || !getDriver(d)) { - return NextResponse.json( - { error: `unknown driver ${JSON.stringify(d)}` }, - { status: 400 }, - ); + return NextResponse.json({ error: `unknown driver ${JSON.stringify(d)}` }, { status: 400 }); } driverOverride = d; } @@ -98,9 +92,7 @@ export async function POST(req: Request) { return NextResponse.json({ event: event.value, outcomes }); } -function parseEvent( - raw: unknown, -): { value: AgentEvent } | { error: string } { +function parseEvent(raw: unknown): { value: AgentEvent } | { error: string } { if (typeof raw !== "object" || raw === null) { return { error: "body must be an object" }; } @@ -137,10 +129,7 @@ function parseEvent( }; } if (type === "issue.commented") { - if ( - typeof o.issueNumber !== "number" || - typeof o.commentId !== "number" - ) { + if (typeof o.issueNumber !== "number" || typeof o.commentId !== "number") { return { error: "issueNumber and commentId are required for issue.commented", }; diff --git a/src/app/api/agents/route.ts b/src/app/api/agents/route.ts index 4254a5a..4276385 100644 --- a/src/app/api/agents/route.ts +++ b/src/app/api/agents/route.ts @@ -1,10 +1,6 @@ import { NextResponse } from "next/server"; import { ensureAgentsBootstrap } from "@/lib/agents/bootstrap"; -import { - getDefaultDriverName, - listDrivers, - listRoles, -} from "@/lib/agents/registry"; +import { getDefaultDriverName, listDrivers, listRoles } from "@/lib/agents/registry"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/auth/callback/route.ts b/src/app/api/auth/callback/route.ts index c802174..53b87c0 100644 --- a/src/app/api/auth/callback/route.ts +++ b/src/app/api/auth/callback/route.ts @@ -1,9 +1,9 @@ -import { NextResponse } from "next/server"; import { cookies } from "next/headers"; -import { completeAuthFlow } from "@/lib/solid/oidc-server"; +import { NextResponse } from "next/server"; import { issueSession } from "@/lib/auth/session"; import { getEnv } from "@/lib/env"; -import { log, clip, scrubWebId } from "@/lib/log"; +import { clip, log, scrubWebId } from "@/lib/log"; +import { completeAuthFlow } from "@/lib/solid/oidc-server"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index 1e2f538..4ab68b5 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -1,11 +1,11 @@ -import { NextResponse } from "next/server"; import { headers } from "next/headers"; -import { startAuthFlow, completeAuthFlow } from "@/lib/solid/oidc-server"; -import { runPasswordLoginOidcFlow, CssApiError } from "@/lib/solid/css-account"; +import { NextResponse } from "next/server"; import { issueSession } from "@/lib/auth/session"; import { getEnv } from "@/lib/env"; -import { rateLimit, RATE_LIMITS } from "@/lib/rate-limit"; -import { log, clip, scrubWebId } from "@/lib/log"; +import { clip, log, scrubWebId } from "@/lib/log"; +import { RATE_LIMITS, rateLimit } from "@/lib/rate-limit"; +import { CssApiError, runPasswordLoginOidcFlow } from "@/lib/solid/css-account"; +import { completeAuthFlow, startAuthFlow } from "@/lib/solid/oidc-server"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -42,11 +42,7 @@ export async function POST(req: Request) { const origin = hdrs.get("origin") ?? ""; const secFetchSite = hdrs.get("sec-fetch-site") ?? ""; const ownOrigin = env.bridgePublicUrl.replace(/\/$/, ""); - if ( - origin !== ownOrigin && - secFetchSite !== "same-origin" && - secFetchSite !== "same-site" - ) { + if (origin !== ownOrigin && secFetchSite !== "same-origin" && secFetchSite !== "same-site") { return NextResponse.json( { error: "cross-origin login is not allowed", code: "CSRF" }, { status: 403 }, @@ -62,18 +58,12 @@ export async function POST(req: Request) { const email = typeof body.email === "string" ? body.email.trim() : ""; const password = typeof body.password === "string" ? body.password : ""; if (!email || !email.includes("@") || !password) { - return NextResponse.json( - { error: "email and password are required" }, - { status: 400 }, - ); + return NextResponse.json({ error: "email and password are required" }, { status: 400 }); } const issuerRaw = typeof body.oidcIssuer === "string" ? body.oidcIssuer : env.podBaseUrl; const oidcIssuer = issuerRaw.endsWith("/") ? issuerRaw : `${issuerRaw}/`; if (!/^https?:\/\//.test(oidcIssuer)) { - return NextResponse.json( - { error: "oidcIssuer must be an http(s) URL" }, - { status: 400 }, - ); + return NextResponse.json({ error: "oidcIssuer must be an http(s) URL" }, { status: 400 }); } // Start the OIDC dance — produces a redirect URL + a session in diff --git a/src/app/api/auth/start/route.ts b/src/app/api/auth/start/route.ts index 0a9e830..50e24c0 100644 --- a/src/app/api/auth/start/route.ts +++ b/src/app/api/auth/start/route.ts @@ -1,10 +1,10 @@ -import { NextResponse } from "next/server"; import { cookies, headers } from "next/headers"; -import { startAuthFlow } from "@/lib/solid/oidc-server"; +import { NextResponse } from "next/server"; import { getEnv } from "@/lib/env"; +import { clip, log } from "@/lib/log"; +import { RATE_LIMITS, rateLimit } from "@/lib/rate-limit"; +import { startAuthFlow } from "@/lib/solid/oidc-server"; import { fetchProfile } from "@/lib/solid/profile"; -import { rateLimit, RATE_LIMITS } from "@/lib/rate-limit"; -import { log, clip } from "@/lib/log"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -29,11 +29,7 @@ export async function POST(req: Request) { const origin = hdrs.get("origin") ?? ""; const secFetchSite = hdrs.get("sec-fetch-site") ?? ""; const ownOrigin = stripTrailingSlash(env.bridgePublicUrl); - if ( - origin !== ownOrigin && - secFetchSite !== "same-origin" && - secFetchSite !== "same-site" - ) { + if (origin !== ownOrigin && secFetchSite !== "same-origin" && secFetchSite !== "same-site") { return NextResponse.json( { error: "cross-origin auth start is not allowed", code: "CSRF" }, { status: 403 }, @@ -63,10 +59,7 @@ export async function POST(req: Request) { webIdHint = (body as Record).webId as string; } if (typeof oidcIssuer !== "string" || !/^https?:\/\//.test(oidcIssuer)) { - return NextResponse.json( - { error: "oidcIssuer (URL) is required" }, - { status: 400 }, - ); + return NextResponse.json({ error: "oidcIssuer (URL) is required" }, { status: 400 }); } const normalised = oidcIssuer.endsWith("/") ? oidcIssuer : `${oidcIssuer}/`; diff --git a/src/app/api/git/[owner]/[repo]/[...path]/route.ts b/src/app/api/git/[owner]/[repo]/[...path]/route.ts index 4b6b332..eb35e37 100644 --- a/src/app/api/git/[owner]/[repo]/[...path]/route.ts +++ b/src/app/api/git/[owner]/[repo]/[...path]/route.ts @@ -1,15 +1,11 @@ import { NextResponse } from "next/server"; -import { getRepo, validateName, RegistryError } from "@/lib/registry/repos"; -import { runGitHttpBackend } from "@/lib/git/http-cgi"; import { getRepoDiskBytes } from "@/lib/git/backend"; -import { verifyPushToken } from "@/lib/registry/tokens"; -import { - isLockedOut, - recordFailure, - RATE_LIMITS, -} from "@/lib/rate-limit"; -import { QUOTAS } from "@/lib/registry/quotas"; +import { runGitHttpBackend } from "@/lib/git/http-cgi"; import { Metrics } from "@/lib/metrics"; +import { isLockedOut, RATE_LIMITS, recordFailure } from "@/lib/rate-limit"; +import { QUOTAS } from "@/lib/registry/quotas"; +import { getRepo, RegistryError, validateName } from "@/lib/registry/repos"; +import { verifyPushToken } from "@/lib/registry/tokens"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -27,9 +23,7 @@ async function handle(req: Request, { params }: Params): Promise { // Git clients append `.git` to the repo name in the remote URL. Strip it // for the registry lookup; pass the suffixed form to git http-backend // (which expects a `*.git` directory under GIT_PROJECT_ROOT). - const repoName = repoSegment.endsWith(".git") - ? repoSegment.slice(0, -4) - : repoSegment; + const repoName = repoSegment.endsWith(".git") ? repoSegment.slice(0, -4) : repoSegment; try { validateName(owner, "owner"); @@ -56,8 +50,7 @@ async function handle(req: Request, { params }: Params): Promise { lastSegment === "git-receive-pack" || (lastSegment === "refs" && service === "git-receive-pack"); const isPull = - lastSegment === "git-upload-pack" || - (lastSegment === "refs" && service === "git-upload-pack"); + lastSegment === "git-upload-pack" || (lastSegment === "refs" && service === "git-upload-pack"); const requiresAuth = isPush || (isPull && repo.visibility === "private"); if (requiresAuth) { diff --git a/src/app/api/git/internal/post-receive/route.ts b/src/app/api/git/internal/post-receive/route.ts index 3313689..ac49070 100644 --- a/src/app/api/git/internal/post-receive/route.ts +++ b/src/app/api/git/internal/post-receive/route.ts @@ -1,21 +1,14 @@ -import { NextResponse } from "next/server"; import { createHmac, timingSafeEqual } from "node:crypto"; import { headers } from "next/headers"; -import { getRepo, getPagesConfig, validateName } from "@/lib/registry/repos"; -import { publishPages } from "@/lib/pages/publisher"; -import { runWorkflow } from "@/lib/workflows/runner"; +import { NextResponse } from "next/server"; +import { getEnv } from "@/lib/env"; import { repoPath } from "@/lib/git/backend"; -import { - mirrorTrackerFromGit, - readPodTracker, -} from "@/lib/solid/tracker-pod"; +import { log, newCorrelationId, withCorrelationId } from "@/lib/log"; +import { publishPages } from "@/lib/pages/publisher"; import { projectTrackerToRegistry } from "@/lib/registry/issue-projection"; -import { getEnv } from "@/lib/env"; -import { - log, - newCorrelationId, - withCorrelationId, -} from "@/lib/log"; +import { getPagesConfig, getRepo, validateName } from "@/lib/registry/repos"; +import { mirrorTrackerFromGit, readPodTracker } from "@/lib/solid/tracker-pod"; +import { runWorkflow } from "@/lib/workflows/runner"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -46,10 +39,7 @@ export async function POST(req: Request) { const headerSig = hdrs.get("x-bridge-hmac") ?? ""; if (!verifyHmac(raw, headerSig, env.hookSecret)) { - return NextResponse.json( - { error: "invalid or missing X-Bridge-Hmac" }, - { status: 401 }, - ); + return NextResponse.json({ error: "invalid or missing X-Bridge-Hmac" }, { status: 401 }); } let body: unknown; @@ -59,19 +49,9 @@ export async function POST(req: Request) { return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); } - const { owner, repo: name, ref, newRev } = (body ?? {}) as Record< - string, - unknown - >; - if ( - typeof owner !== "string" || - typeof name !== "string" || - typeof ref !== "string" - ) { - return NextResponse.json( - { error: "owner, repo, ref required" }, - { status: 400 }, - ); + const { owner, repo: name, ref, newRev } = (body ?? {}) as Record; + if (typeof owner !== "string" || typeof name !== "string" || typeof ref !== "string") { + return NextResponse.json({ error: "owner, repo, ref required" }, { status: 400 }); } try { @@ -89,8 +69,7 @@ export async function POST(req: Request) { // refs/heads/main → main const branch = ref.replace(/^refs\/heads\//, ""); - const branchMatches = - pages?.enabled === true && pages.sourceBranch === branch; + const branchMatches = pages?.enabled === true && pages.sourceBranch === branch; let scheduled: "workflow" | "legacy-pages" | "skipped" = "skipped"; diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts index ad59470..9b243b2 100644 --- a/src/app/api/health/route.ts +++ b/src/app/api/health/route.ts @@ -1,7 +1,7 @@ -import { NextResponse } from "next/server"; import { spawn } from "node:child_process"; -import { getDb } from "@/lib/registry/db"; +import { NextResponse } from "next/server"; import { getEnv } from "@/lib/env"; +import { getDb } from "@/lib/registry/db"; import { version } from "../../../../package.json"; export const runtime = "nodejs"; diff --git a/src/app/api/identities/[webId]/route.ts b/src/app/api/identities/[webId]/route.ts index ac8458f..04a6b42 100644 --- a/src/app/api/identities/[webId]/route.ts +++ b/src/app/api/identities/[webId]/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from "next/server"; +import { clearSession, requireOwner } from "@/lib/auth/session"; import { deleteIdentity, getIdentityByWebId } from "@/lib/registry/identities"; import { clearCachedSession } from "@/lib/solid/oidc-server"; -import { requireOwner, clearSession } from "@/lib/auth/session"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -34,10 +34,7 @@ export async function DELETE(_req: Request, { params }: Params) { // pod writes off a still-valid access token (MC-176). clearCachedSession(decoded); if (!ok) { - return NextResponse.json( - { error: "identity not found" }, - { status: 404 }, - ); + return NextResponse.json({ error: "identity not found" }, { status: 404 }); } // Disconnecting the identity invalidates the session that was issued // off that OIDC flow, so drop the cookie too — the next request will diff --git a/src/app/api/metrics/route.ts b/src/app/api/metrics/route.ts index cddf2d0..b754bd5 100644 --- a/src/app/api/metrics/route.ts +++ b/src/app/api/metrics/route.ts @@ -1,5 +1,5 @@ -import { NextResponse } from "next/server"; import { headers } from "next/headers"; +import { NextResponse } from "next/server"; import { renderExposition } from "@/lib/metrics"; export const runtime = "nodejs"; @@ -23,10 +23,7 @@ export async function GET() { const hdrs = await headers(); const presented = (hdrs.get("authorization") ?? "").replace(/^Bearer\s+/i, ""); if (!presented || presented !== expected) { - return NextResponse.json( - { error: "invalid metrics token" }, - { status: 401 }, - ); + return NextResponse.json({ error: "invalid metrics token" }, { status: 401 }); } return new NextResponse(renderExposition(), { status: 200, diff --git a/src/app/api/packages/npm/[owner]/[repo]/[...path]/route.ts b/src/app/api/packages/npm/[owner]/[repo]/[...path]/route.ts index 0970343..208f9fd 100644 --- a/src/app/api/packages/npm/[owner]/[repo]/[...path]/route.ts +++ b/src/app/api/packages/npm/[owner]/[repo]/[...path]/route.ts @@ -1,27 +1,27 @@ import { NextResponse } from "next/server"; -import { getRepo, validateName, RegistryError } from "@/lib/registry/repos"; -import { - upsertPackageVersion, - listVersions, - validatePackageName, - validateVersion, - PackageError, -} from "@/lib/packages/store"; +import { getEnv } from "@/lib/env"; +import { jsonResponse } from "@/lib/http/json"; +import { authenticatePackagePush } from "@/lib/packages/auth"; import { - parseNpmPublish, buildPackument, findVersionByFilename, - NpmPublishError, type NpmPublishBody, + NpmPublishError, type NpmVersionMeta, + parseNpmPublish, } from "@/lib/packages/npm"; -import { authenticatePackagePush } from "@/lib/packages/auth"; import { getRepoContentStore } from "@/lib/packages/repo-store"; +import { + listVersions, + PackageError, + upsertPackageVersion, + validatePackageName, + validateVersion, +} from "@/lib/packages/store"; import { assertCanStorePackage, QuotaExceededError } from "@/lib/registry/quotas"; -import { jsonResponse } from "@/lib/http/json"; -import { getEnv } from "@/lib/env"; -import { OwnerFetchUnavailableError } from "@/lib/solid/fetch-for-owner"; import type { Repo } from "@/lib/registry/repos"; +import { getRepo, RegistryError, validateName } from "@/lib/registry/repos"; +import { OwnerFetchUnavailableError } from "@/lib/solid/fetch-for-owner"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -79,10 +79,7 @@ export async function PUT(req: Request, { params }: Params) { handle = await getRepoContentStore(repo, "write"); } catch (e) { if (e instanceof OwnerFetchUnavailableError) { - return NextResponse.json( - { error: e.message, code: "OWNER_UNAVAILABLE" }, - { status: 503 }, - ); + return NextResponse.json({ error: e.message, code: "OWNER_UNAVAILABLE" }, { status: 503 }); } throw e; } @@ -146,11 +143,7 @@ export async function GET(req: Request, { params }: Params) { return jsonResponse(buildPackument(pkg, rows, base)); } -async function serveTarball( - repo: Repo, - pkg: string, - filename: string, -): Promise { +async function serveTarball(repo: Repo, pkg: string, filename: string): Promise { const rows = listVersions(repo.id, "npm", pkg); const record = findVersionByFilename(rows, filename); if (!record) { @@ -190,16 +183,13 @@ function decodeSegment(s: string): string { } function unauthorized(owner: string, name: string): NextResponse { - return new NextResponse( - JSON.stringify({ error: "missing or invalid push token" }), - { - status: 401, - headers: { - "Content-Type": "application/json", - "WWW-Authenticate": `Basic realm="${owner}/${name}"`, - }, + return new NextResponse(JSON.stringify({ error: "missing or invalid push token" }), { + status: 401, + headers: { + "Content-Type": "application/json", + "WWW-Authenticate": `Basic realm="${owner}/${name}"`, }, - ); + }); } function quota(e: QuotaExceededError): NextResponse { diff --git a/src/app/api/profile/ai/keys/[provider]/route.ts b/src/app/api/profile/ai/keys/[provider]/route.ts index 766232e..eb2e251 100644 --- a/src/app/api/profile/ai/keys/[provider]/route.ts +++ b/src/app/api/profile/ai/keys/[provider]/route.ts @@ -1,10 +1,7 @@ import { NextResponse } from "next/server"; -import { requireSession } from "@/lib/auth/session"; -import { - setUserApiKey, - deleteUserApiKey, -} from "@/lib/ai-providers/store"; import { isProviderName } from "@/lib/ai-providers/providers"; +import { deleteUserApiKey, setUserApiKey } from "@/lib/ai-providers/store"; +import { requireSession } from "@/lib/auth/session"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -41,10 +38,7 @@ export async function POST(req: Request, { params }: Params) { // wrong-shape keys will just fail at the model call and the user will // see the upstream error. if (apiKey.length > 1000) { - return NextResponse.json( - { error: "apiKey too long" }, - { status: 400 }, - ); + return NextResponse.json({ error: "apiKey too long" }, { status: 400 }); } const saved = setUserApiKey(auth.webId, provider, apiKey); diff --git a/src/app/api/profile/ai/pref/route.ts b/src/app/api/profile/ai/pref/route.ts index 1a2c7dc..5164337 100644 --- a/src/app/api/profile/ai/pref/route.ts +++ b/src/app/api/profile/ai/pref/route.ts @@ -1,11 +1,7 @@ import { NextResponse } from "next/server"; -import { requireSession } from "@/lib/auth/session"; -import { - getUserAiPref, - setUserAiPref, - getDecryptedApiKey, -} from "@/lib/ai-providers/store"; import { isProviderName } from "@/lib/ai-providers/providers"; +import { getDecryptedApiKey, getUserAiPref, setUserAiPref } from "@/lib/ai-providers/store"; +import { requireSession } from "@/lib/auth/session"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -41,10 +37,7 @@ export async function PUT(req: Request) { ); } if (typeof model !== "string" || model.trim().length === 0) { - return NextResponse.json( - { error: "model must be a non-empty string" }, - { status: 400 }, - ); + return NextResponse.json({ error: "model must be a non-empty string" }, { status: 400 }); } // Owner must have an API key configured for the provider they pick; // otherwise the resolver would just fall back to env at run time, diff --git a/src/app/api/profile/ai/route.ts b/src/app/api/profile/ai/route.ts index adc3ad6..92bddec 100644 --- a/src/app/api/profile/ai/route.ts +++ b/src/app/api/profile/ai/route.ts @@ -1,12 +1,12 @@ import { NextResponse } from "next/server"; -import { requireSession } from "@/lib/auth/session"; +import { PROVIDERS } from "@/lib/ai-providers/providers"; import { - listConfiguredProviders, getUserAiPref, + listConfiguredProviders, resolveCoderConfigSummary, } from "@/lib/ai-providers/store"; -import { PROVIDERS } from "@/lib/ai-providers/providers"; -import { ledgerEnabled, getBalance } from "@/lib/ledger/client"; +import { requireSession } from "@/lib/auth/session"; +import { getBalance, ledgerEnabled } from "@/lib/ledger/client"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -31,9 +31,7 @@ export async function GET() { // bridge-default key. null when the ledger is off or the user is on their own // key — the builder then renders the existing source-based copy unchanged. const freeBalance = - summary.source === "env-fallback" && ledgerEnabled() - ? await getBalance(auth.webId) - : null; + summary.source === "env-fallback" && ledgerEnabled() ? await getBalance(auth.webId) : null; return NextResponse.json({ providers: listConfiguredProviders(auth.webId), diff --git a/src/app/api/repos/[owner]/[repo]/files/[...path]/route.ts b/src/app/api/repos/[owner]/[repo]/files/[...path]/route.ts index e5ba198..bbb06bc 100644 --- a/src/app/api/repos/[owner]/[repo]/files/[...path]/route.ts +++ b/src/app/api/repos/[owner]/[repo]/files/[...path]/route.ts @@ -1,20 +1,18 @@ import { NextResponse } from "next/server"; -import { getRepo } from "@/lib/registry/repos"; +import { requireOwner } from "@/lib/auth/session"; +import { authenticatePackagePush } from "@/lib/packages/auth"; +import { getRepoContentStore } from "@/lib/packages/repo-store"; import { - upsertPackageVersion, getPackageVersion, + PackageError, + upsertPackageVersion, validatePackageName, validateVersion, - PackageError, } from "@/lib/packages/store"; -import { authenticatePackagePush } from "@/lib/packages/auth"; -import { getRepoContentStore } from "@/lib/packages/repo-store"; -import { assertCanStorePackage, QuotaExceededError } from "@/lib/registry/quotas"; -import { requireOwner } from "@/lib/auth/session"; import { mimeForPath } from "@/lib/pages/mime"; -import { - OwnerFetchUnavailableError, -} from "@/lib/solid/fetch-for-owner"; +import { assertCanStorePackage, QuotaExceededError } from "@/lib/registry/quotas"; +import { getRepo } from "@/lib/registry/repos"; +import { OwnerFetchUnavailableError } from "@/lib/solid/fetch-for-owner"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -78,10 +76,7 @@ export async function PUT(req: Request, { params }: Params) { storeHandle = await getRepoContentStore(repo, "write"); } catch (e) { if (e instanceof OwnerFetchUnavailableError) { - return NextResponse.json( - { error: e.message, code: "OWNER_UNAVAILABLE" }, - { status: 503 }, - ); + return NextResponse.json({ error: e.message, code: "OWNER_UNAVAILABLE" }, { status: 503 }); } throw e; } @@ -89,8 +84,7 @@ export async function PUT(req: Request, { params }: Params) { try { const blob = await store.save(bytes); const contentType = - req.headers.get("content-type")?.split(";")[0]?.trim() || - mimeForPath(filename); + req.headers.get("content-type")?.split(";")[0]?.trim() || mimeForPath(filename); upsertPackageVersion({ repoId: repo.id, type: "file", @@ -107,10 +101,7 @@ export async function PUT(req: Request, { params }: Params) { ); } catch (e) { if (e instanceof OwnerFetchUnavailableError) { - return NextResponse.json( - { error: e.message, code: "OWNER_UNAVAILABLE" }, - { status: 503 }, - ); + return NextResponse.json({ error: e.message, code: "OWNER_UNAVAILABLE" }, { status: 503 }); } throw e; } finally { @@ -162,16 +153,13 @@ export async function GET(req: Request, { params }: Params) { } function unauthorized(owner: string, name: string): NextResponse { - return new NextResponse( - JSON.stringify({ error: "missing or invalid push token" }), - { - status: 401, - headers: { - "Content-Type": "application/json", - "WWW-Authenticate": `Basic realm="${owner}/${name}"`, - }, + return new NextResponse(JSON.stringify({ error: "missing or invalid push token" }), { + status: 401, + headers: { + "Content-Type": "application/json", + "WWW-Authenticate": `Basic realm="${owner}/${name}"`, }, - ); + }); } function quota(e: QuotaExceededError): NextResponse { diff --git a/src/app/api/repos/[owner]/[repo]/inbox/[id]/accept/route.ts b/src/app/api/repos/[owner]/[repo]/inbox/[id]/accept/route.ts index ef3e506..c9821f9 100644 --- a/src/app/api/repos/[owner]/[repo]/inbox/[id]/accept/route.ts +++ b/src/app/api/repos/[owner]/[repo]/inbox/[id]/accept/route.ts @@ -1,10 +1,10 @@ import { NextResponse } from "next/server"; -import { getRepo } from "@/lib/registry/repos"; import { requireOwner } from "@/lib/auth/session"; import { repoPath } from "@/lib/git/backend"; -import { createMindIssue, IssueAuthorError } from "@/lib/tracker/author"; -import { getProposal, deleteProposal, type Proposal } from "@/lib/solid/inbox"; +import { getRepo } from "@/lib/registry/repos"; import { OwnerFetchUnavailableError } from "@/lib/solid/fetch-for-owner"; +import { deleteProposal, getProposal, type Proposal } from "@/lib/solid/inbox"; +import { createMindIssue, IssueAuthorError } from "@/lib/tracker/author"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -22,9 +22,7 @@ function provenanceFooter(p: Proposal): string { : p.contact ? `${p.contact} (unverified)` : "an anonymous visitor"; - const when = p.createdAt - ? new Date(p.createdAt).toISOString().slice(0, 10) - : "an unknown date"; + const when = p.createdAt ? new Date(p.createdAt).toISOString().slice(0, 10) : "an unknown date"; return `\n\n---\n_Proposed via the pod inbox by ${who} on ${when}; accepted by the repo owner._`; } diff --git a/src/app/api/repos/[owner]/[repo]/inbox/[id]/route.ts b/src/app/api/repos/[owner]/[repo]/inbox/[id]/route.ts index 093ea0d..8c60a23 100644 --- a/src/app/api/repos/[owner]/[repo]/inbox/[id]/route.ts +++ b/src/app/api/repos/[owner]/[repo]/inbox/[id]/route.ts @@ -1,8 +1,8 @@ import { NextResponse } from "next/server"; -import { getRepo } from "@/lib/registry/repos"; import { requireOwner } from "@/lib/auth/session"; -import { deleteProposal } from "@/lib/solid/inbox"; +import { getRepo } from "@/lib/registry/repos"; import { OwnerFetchUnavailableError } from "@/lib/solid/fetch-for-owner"; +import { deleteProposal } from "@/lib/solid/inbox"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/repos/[owner]/[repo]/inbox/route.ts b/src/app/api/repos/[owner]/[repo]/inbox/route.ts index 98e7a32..3590942 100644 --- a/src/app/api/repos/[owner]/[repo]/inbox/route.ts +++ b/src/app/api/repos/[owner]/[repo]/inbox/route.ts @@ -1,9 +1,9 @@ import { NextResponse } from "next/server"; -import { getRepo } from "@/lib/registry/repos"; import { requireOwner } from "@/lib/auth/session"; import { jsonResponse } from "@/lib/http/json"; -import { listProposals } from "@/lib/solid/inbox"; +import { getRepo } from "@/lib/registry/repos"; import { OwnerFetchUnavailableError } from "@/lib/solid/fetch-for-owner"; +import { listProposals } from "@/lib/solid/inbox"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/repos/[owner]/[repo]/issues/[number]/agent-runs/route.ts b/src/app/api/repos/[owner]/[repo]/issues/[number]/agent-runs/route.ts index a4ebed4..7ffeaa3 100644 --- a/src/app/api/repos/[owner]/[repo]/issues/[number]/agent-runs/route.ts +++ b/src/app/api/repos/[owner]/[repo]/issues/[number]/agent-runs/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from "next/server"; -import { getRepo } from "@/lib/registry/repos"; -import { getIssueByNumber } from "@/lib/registry/issues"; import { listAgentRunsForIssue } from "@/lib/registry/agent-runs"; +import { getIssueByNumber } from "@/lib/registry/issues"; +import { getRepo } from "@/lib/registry/repos"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/repos/[owner]/[repo]/issues/[number]/comments/route.ts b/src/app/api/repos/[owner]/[repo]/issues/[number]/comments/route.ts index f3aef8e..383cdbb 100644 --- a/src/app/api/repos/[owner]/[repo]/issues/[number]/comments/route.ts +++ b/src/app/api/repos/[owner]/[repo]/issues/[number]/comments/route.ts @@ -1,15 +1,15 @@ import { NextResponse } from "next/server"; -import { getRepo, RegistryError } from "@/lib/registry/repos"; +import { ensureAgentsBootstrap } from "@/lib/agents/bootstrap"; +import { dispatch } from "@/lib/agents/dispatch"; +import { requireOwner } from "@/lib/auth/session"; import { addComment, getIssueByNumber, listComments, setCommentPodUrl, } from "@/lib/registry/issues"; +import { getRepo, RegistryError } from "@/lib/registry/repos"; import { commentUrl, writeCommentToPod } from "@/lib/solid/issues"; -import { ensureAgentsBootstrap } from "@/lib/agents/bootstrap"; -import { dispatch } from "@/lib/agents/dispatch"; -import { requireOwner } from "@/lib/auth/session"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -63,15 +63,9 @@ export async function POST(req: Request, { params }: Params) { } catch { return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); } - const { body: commentBody, authorWebId } = (body ?? {}) as Record< - string, - unknown - >; + const { body: commentBody, authorWebId } = (body ?? {}) as Record; if (typeof commentBody !== "string" || commentBody.trim().length === 0) { - return NextResponse.json( - { error: "body is required and must be non-empty" }, - { status: 400 }, - ); + return NextResponse.json({ error: "body is required and must be non-empty" }, { status: 400 }); } // The session establishes who is authoring this comment. A bodysupplied // authorWebId that disagrees with the session would let any @@ -104,10 +98,7 @@ export async function POST(req: Request, { params }: Params) { comment.podUrl = canonical; writeCommentToPod(repo, issue.number, comment).catch((err) => { - console.warn( - `[comments.POST] writeCommentToPod for ${owner}/${name}#${number} failed:`, - err, - ); + console.warn(`[comments.POST] writeCommentToPod for ${owner}/${name}#${number} failed:`, err); }); // Re-fire the coder on the new comment so the conversation keeps diff --git a/src/app/api/repos/[owner]/[repo]/issues/[number]/route.ts b/src/app/api/repos/[owner]/[repo]/issues/[number]/route.ts index 4a3b316..b8992a0 100644 --- a/src/app/api/repos/[owner]/[repo]/issues/[number]/route.ts +++ b/src/app/api/repos/[owner]/[repo]/issues/[number]/route.ts @@ -1,14 +1,14 @@ import { NextResponse } from "next/server"; -import { getRepo, RegistryError } from "@/lib/registry/repos"; +import { requireOwner } from "@/lib/auth/session"; import { getIssueByNumber, - listComments, - updateIssue, type IssuePriority, type IssueStatus, + listComments, + updateIssue, } from "@/lib/registry/issues"; +import { getRepo, RegistryError } from "@/lib/registry/repos"; import { writeIssueToPod } from "@/lib/solid/issues"; -import { requireOwner } from "@/lib/auth/session"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -63,15 +63,17 @@ export async function PATCH(req: Request, { params }: Params) { return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); } - const { status, priority, labels, title, body: issueBody } = - (body ?? {}) as Record; + const { + status, + priority, + labels, + title, + body: issueBody, + } = (body ?? {}) as Record; try { const updated = updateIssue(issue.id, { - status: - status === "open" || status === "closed" - ? (status as IssueStatus) - : undefined, + status: status === "open" || status === "closed" ? (status as IssueStatus) : undefined, priority: priority === "low" || priority === "normal" || priority === "high" ? (priority as IssuePriority) @@ -82,10 +84,7 @@ export async function PATCH(req: Request, { params }: Params) { }); writeIssueToPod(repo, updated).catch((err) => { - console.warn( - `[issues.PATCH] writeIssueToPod for ${owner}/${name}#${number} failed:`, - err, - ); + console.warn(`[issues.PATCH] writeIssueToPod for ${owner}/${name}#${number} failed:`, err); }); return NextResponse.json({ issue: updated }); diff --git a/src/app/api/repos/[owner]/[repo]/issues/propose/route.ts b/src/app/api/repos/[owner]/[repo]/issues/propose/route.ts index a1134bf..5f99d7c 100644 --- a/src/app/api/repos/[owner]/[repo]/issues/propose/route.ts +++ b/src/app/api/repos/[owner]/[repo]/issues/propose/route.ts @@ -1,9 +1,9 @@ import { NextResponse } from "next/server"; -import { getRepo } from "@/lib/registry/repos"; import { readSession } from "@/lib/auth/session"; -import { rateLimit, RATE_LIMITS } from "@/lib/rate-limit"; -import { postProposal } from "@/lib/solid/inbox"; +import { RATE_LIMITS, rateLimit } from "@/lib/rate-limit"; +import { getRepo } from "@/lib/registry/repos"; import { OwnerFetchUnavailableError } from "@/lib/solid/fetch-for-owner"; +import { postProposal } from "@/lib/solid/inbox"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -57,10 +57,7 @@ export async function POST(req: Request, { params }: Params) { return NextResponse.json({ error: "title is required" }, { status: 400 }); } if (title.length > MAX_TITLE) { - return NextResponse.json( - { error: `title must be ≤ ${MAX_TITLE} characters` }, - { status: 400 }, - ); + return NextResponse.json({ error: `title must be ≤ ${MAX_TITLE} characters` }, { status: 400 }); } if (typeof proposalBody === "string" && proposalBody.length > MAX_BODY) { return NextResponse.json( diff --git a/src/app/api/repos/[owner]/[repo]/issues/route.ts b/src/app/api/repos/[owner]/[repo]/issues/route.ts index 5366f9a..dd960d1 100644 --- a/src/app/api/repos/[owner]/[repo]/issues/route.ts +++ b/src/app/api/repos/[owner]/[repo]/issues/route.ts @@ -1,18 +1,18 @@ import { NextResponse } from "next/server"; -import { getRepo, RegistryError } from "@/lib/registry/repos"; +import { ensureAgentsBootstrap } from "@/lib/agents/bootstrap"; +import { dispatch } from "@/lib/agents/dispatch"; +import { requireOwner } from "@/lib/auth/session"; +import { jsonResponse } from "@/lib/http/json"; +import { RATE_LIMITS, rateLimit } from "@/lib/rate-limit"; import { createIssue, - listIssues, - setIssuePodUrl, type IssuePriority, type IssueStatus, + listIssues, + setIssuePodUrl, } from "@/lib/registry/issues"; +import { getRepo, RegistryError } from "@/lib/registry/repos"; import { issueUrl, writeIssueToPod } from "@/lib/solid/issues"; -import { ensureAgentsBootstrap } from "@/lib/agents/bootstrap"; -import { dispatch } from "@/lib/agents/dispatch"; -import { requireOwner } from "@/lib/auth/session"; -import { rateLimit, RATE_LIMITS } from "@/lib/rate-limit"; -import { jsonResponse } from "@/lib/http/json"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -28,10 +28,7 @@ export async function GET(req: Request, { params }: Params) { const url = new URL(req.url); const statusParam = url.searchParams.get("status") ?? "open"; if (!["open", "closed", "all"].includes(statusParam)) { - return NextResponse.json( - { error: "status must be open|closed|all" }, - { status: 400 }, - ); + return NextResponse.json({ error: "status must be open|closed|all" }, { status: 400 }); } const issues = listIssues(repo.id, { status: statusParam as IssueStatus | "all", @@ -66,10 +63,7 @@ export async function POST(req: Request, { params }: Params) { } = (body ?? {}) as Record; if (typeof title !== "string") { - return NextResponse.json( - { error: "title is required" }, - { status: 400 }, - ); + return NextResponse.json({ error: "title is required" }, { status: 400 }); } // The session establishes who is authoring this issue. Any // bodysupplied authorWebId that disagrees with the session is rejected diff --git a/src/app/api/repos/[owner]/[repo]/members/[webid]/route.ts b/src/app/api/repos/[owner]/[repo]/members/[webid]/route.ts index a69daf4..090354b 100644 --- a/src/app/api/repos/[owner]/[repo]/members/[webid]/route.ts +++ b/src/app/api/repos/[owner]/[repo]/members/[webid]/route.ts @@ -1,9 +1,9 @@ import { NextResponse } from "next/server"; -import { getRepo } from "@/lib/registry/repos"; import { requireMember } from "@/lib/auth/session"; import { jsonResponse } from "@/lib/http/json"; -import { removeMember } from "@/lib/solid/members"; +import { getRepo } from "@/lib/registry/repos"; import { OwnerFetchUnavailableError } from "@/lib/solid/fetch-for-owner"; +import { removeMember } from "@/lib/solid/members"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/repos/[owner]/[repo]/members/route.ts b/src/app/api/repos/[owner]/[repo]/members/route.ts index 4435d9f..88d79ca 100644 --- a/src/app/api/repos/[owner]/[repo]/members/route.ts +++ b/src/app/api/repos/[owner]/[repo]/members/route.ts @@ -1,9 +1,9 @@ import { NextResponse } from "next/server"; -import { getRepo } from "@/lib/registry/repos"; import { requireMember } from "@/lib/auth/session"; import { jsonResponse } from "@/lib/http/json"; -import { addMember, readMembers, isMemberRole } from "@/lib/solid/members"; +import { getRepo } from "@/lib/registry/repos"; import { OwnerFetchUnavailableError } from "@/lib/solid/fetch-for-owner"; +import { addMember, isMemberRole, readMembers } from "@/lib/solid/members"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -61,16 +61,10 @@ export async function POST(req: Request, { params }: Params) { } const { webId, role } = (body ?? {}) as Record; if (typeof webId !== "string" || !webId.startsWith("http")) { - return NextResponse.json( - { error: "webId must be an http(s) WebID" }, - { status: 400 }, - ); + return NextResponse.json({ error: "webId must be an http(s) WebID" }, { status: 400 }); } if (typeof role !== "string" || !isMemberRole(role)) { - return NextResponse.json( - { error: "role must be reader|writer|admin" }, - { status: 400 }, - ); + return NextResponse.json({ error: "role must be reader|writer|admin" }, { status: 400 }); } if (webId === repo.ownerWebId) { return NextResponse.json( diff --git a/src/app/api/repos/[owner]/[repo]/mind-epics/route.ts b/src/app/api/repos/[owner]/[repo]/mind-epics/route.ts index 094cfb2..be5bbd0 100644 --- a/src/app/api/repos/[owner]/[repo]/mind-epics/route.ts +++ b/src/app/api/repos/[owner]/[repo]/mind-epics/route.ts @@ -1,9 +1,9 @@ import { NextResponse } from "next/server"; -import { getRepo } from "@/lib/registry/repos"; +import { requireOwner } from "@/lib/auth/session"; import { repoPath } from "@/lib/git/backend"; +import { RATE_LIMITS, rateLimit } from "@/lib/rate-limit"; +import { getRepo } from "@/lib/registry/repos"; import { createMindEpic, IssueAuthorError } from "@/lib/tracker/author"; -import { requireOwner } from "@/lib/auth/session"; -import { rateLimit, RATE_LIMITS } from "@/lib/rate-limit"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/repos/[owner]/[repo]/mind-issues/route.ts b/src/app/api/repos/[owner]/[repo]/mind-issues/route.ts index b42c508..205b4ec 100644 --- a/src/app/api/repos/[owner]/[repo]/mind-issues/route.ts +++ b/src/app/api/repos/[owner]/[repo]/mind-issues/route.ts @@ -1,9 +1,9 @@ import { NextResponse } from "next/server"; -import { getRepo } from "@/lib/registry/repos"; +import { requireOwner } from "@/lib/auth/session"; import { repoPath } from "@/lib/git/backend"; +import { RATE_LIMITS, rateLimit } from "@/lib/rate-limit"; +import { getRepo } from "@/lib/registry/repos"; import { createMindIssue, IssueAuthorError } from "@/lib/tracker/author"; -import { requireOwner } from "@/lib/auth/session"; -import { rateLimit, RATE_LIMITS } from "@/lib/rate-limit"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -35,13 +35,7 @@ export async function POST(req: Request, { params }: Params) { } catch { return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); } - const { - title, - type, - epic, - priority, - body: issueBody, - } = (body ?? {}) as Record; + const { title, type, epic, priority, body: issueBody } = (body ?? {}) as Record; if (typeof title !== "string" || !title.trim()) { return NextResponse.json({ error: "title is required" }, { status: 400 }); @@ -51,19 +45,14 @@ export async function POST(req: Request, { params }: Params) { } try { - const result = await createMindIssue( - repoPath(repo.owner, repo.name), - owner, - name, - { - title, - type, - epicSlug: typeof epic === "string" ? epic : null, - priority: typeof priority === "string" ? priority : undefined, - body: typeof issueBody === "string" ? issueBody : undefined, - authorWebId: auth.webId, - }, - ); + const result = await createMindIssue(repoPath(repo.owner, repo.name), owner, name, { + title, + type, + epicSlug: typeof epic === "string" ? epic : null, + priority: typeof priority === "string" ? priority : undefined, + body: typeof issueBody === "string" ? issueBody : undefined, + authorWebId: auth.webId, + }); return NextResponse.json({ issue: result }, { status: 201 }); } catch (e) { if (e instanceof IssueAuthorError) { diff --git a/src/app/api/repos/[owner]/[repo]/packages/route.ts b/src/app/api/repos/[owner]/[repo]/packages/route.ts index 8cda981..500ffa0 100644 --- a/src/app/api/repos/[owner]/[repo]/packages/route.ts +++ b/src/app/api/repos/[owner]/[repo]/packages/route.ts @@ -1,8 +1,8 @@ import { NextResponse } from "next/server"; -import { getRepo } from "@/lib/registry/repos"; -import { listPackages, type PackageType } from "@/lib/packages/store"; import { requireOwner } from "@/lib/auth/session"; import { jsonResponse } from "@/lib/http/json"; +import { listPackages, type PackageType } from "@/lib/packages/store"; +import { getRepo } from "@/lib/registry/repos"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/repos/[owner]/[repo]/pages/route.ts b/src/app/api/repos/[owner]/[repo]/pages/route.ts index db7dff6..ec32a59 100644 --- a/src/app/api/repos/[owner]/[repo]/pages/route.ts +++ b/src/app/api/repos/[owner]/[repo]/pages/route.ts @@ -1,12 +1,7 @@ import { NextResponse } from "next/server"; -import { - getRepo, - getPagesConfig, - RegistryError, - updatePagesConfig, -} from "@/lib/registry/repos"; -import { writeRepoMetadata } from "@/lib/solid/repo-metadata"; import { requireOwner } from "@/lib/auth/session"; +import { getPagesConfig, getRepo, RegistryError, updatePagesConfig } from "@/lib/registry/repos"; +import { writeRepoMetadata } from "@/lib/solid/repo-metadata"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -37,23 +32,20 @@ export async function PUT(req: Request, { params }: Params) { } catch { return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); } - const { enabled, sourceBranch, sourcePath, targetContainer } = - (body ?? {}) as Record; + const { enabled, sourceBranch, sourcePath, targetContainer } = (body ?? {}) as Record< + string, + unknown + >; try { const updated = updatePagesConfig(repo.id, { enabled: typeof enabled === "boolean" ? enabled : undefined, - sourceBranch: - typeof sourceBranch === "string" ? sourceBranch : undefined, + sourceBranch: typeof sourceBranch === "string" ? sourceBranch : undefined, sourcePath: typeof sourcePath === "string" ? sourcePath : undefined, - targetContainer: - typeof targetContainer === "string" ? targetContainer : undefined, + targetContainer: typeof targetContainer === "string" ? targetContainer : undefined, }); writeRepoMetadata(repo, updated).catch((err) => { - console.warn( - `[pages.PUT] writeRepoMetadata for ${owner}/${name} failed:`, - err, - ); + console.warn(`[pages.PUT] writeRepoMetadata for ${owner}/${name} failed:`, err); }); return NextResponse.json({ pages: updated }); } catch (e) { diff --git a/src/app/api/repos/[owner]/[repo]/pulls/[number]/close/route.ts b/src/app/api/repos/[owner]/[repo]/pulls/[number]/close/route.ts index 97c1107..db06264 100644 --- a/src/app/api/repos/[owner]/[repo]/pulls/[number]/close/route.ts +++ b/src/app/api/repos/[owner]/[repo]/pulls/[number]/close/route.ts @@ -1,8 +1,8 @@ import { NextResponse } from "next/server"; -import { getRepo, RegistryError } from "@/lib/registry/repos"; -import { getPullRequest, closePullRequest } from "@/lib/registry/pulls"; import { requireOwner } from "@/lib/auth/session"; import { deletePreview } from "@/lib/pages/preview"; +import { closePullRequest, getPullRequest } from "@/lib/registry/pulls"; +import { getRepo, RegistryError } from "@/lib/registry/repos"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/repos/[owner]/[repo]/pulls/[number]/merge/route.ts b/src/app/api/repos/[owner]/[repo]/pulls/[number]/merge/route.ts index d85246a..0381932 100644 --- a/src/app/api/repos/[owner]/[repo]/pulls/[number]/merge/route.ts +++ b/src/app/api/repos/[owner]/[repo]/pulls/[number]/merge/route.ts @@ -1,16 +1,13 @@ import { NextResponse } from "next/server"; -import { getRepo } from "@/lib/registry/repos"; -import { - getPullRequest, - markPullRequestMerged, -} from "@/lib/registry/pulls"; -import { mergeBranches } from "@/lib/git/merge"; +import { requireOwner } from "@/lib/auth/session"; import { repoPath } from "@/lib/git/backend"; -import { displayNameForWebId } from "@/lib/solid/web-id"; +import { mergeBranches } from "@/lib/git/merge"; +import { deletePreview } from "@/lib/pages/preview"; import { getIssueById, updateIssue } from "@/lib/registry/issues"; +import { getPullRequest, markPullRequestMerged } from "@/lib/registry/pulls"; +import { getRepo } from "@/lib/registry/repos"; import { writeIssueToPod } from "@/lib/solid/issues"; -import { requireOwner } from "@/lib/auth/session"; -import { deletePreview } from "@/lib/pages/preview"; +import { displayNameForWebId } from "@/lib/solid/web-id"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -36,10 +33,7 @@ export async function POST(_req: Request, { params }: Params) { return NextResponse.json({ error: "pull not found" }, { status: 404 }); } if (pull.status !== "open") { - return NextResponse.json( - { error: `pull is ${pull.status}, cannot merge` }, - { status: 409 }, - ); + return NextResponse.json({ error: `pull is ${pull.status}, cannot merge` }, { status: 409 }); } // Attribute the merge to the repo owner (the human in the loop). diff --git a/src/app/api/repos/[owner]/[repo]/pulls/[number]/preview/route.ts b/src/app/api/repos/[owner]/[repo]/pulls/[number]/preview/route.ts index b6a3aff..e70dc38 100644 --- a/src/app/api/repos/[owner]/[repo]/pulls/[number]/preview/route.ts +++ b/src/app/api/repos/[owner]/[repo]/pulls/[number]/preview/route.ts @@ -1,16 +1,15 @@ -import { NextResponse } from "next/server"; import { readFile } from "node:fs/promises"; import * as path from "node:path"; -import { getRepo } from "@/lib/registry/repos"; -import { getPullRequest, updatePullPreview } from "@/lib/registry/pulls"; +import { NextResponse } from "next/server"; import { requireOwner, requireSession } from "@/lib/auth/session"; import { buildAndPublishPreview } from "@/lib/pages/preview"; +import { getPullRequest, updatePullPreview } from "@/lib/registry/pulls"; +import { getRepo } from "@/lib/registry/repos"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; -const AGENT_LOGS_DIR = - process.env.AGENT_LOGS_DIR ?? path.join(process.cwd(), ".agent-logs"); +const AGENT_LOGS_DIR = process.env.AGENT_LOGS_DIR ?? path.join(process.cwd(), ".agent-logs"); type Params = { params: Promise<{ owner: string; repo: string; number: string }>; @@ -71,10 +70,7 @@ export async function GET(_req: Request, { params }: Params) { const { pull } = r; let log = ""; if (pull.previewLogPath) { - log = await readFile( - path.join(AGENT_LOGS_DIR, pull.previewLogPath), - "utf-8", - ).catch(() => ""); + log = await readFile(path.join(AGENT_LOGS_DIR, pull.previewLogPath), "utf-8").catch(() => ""); } return NextResponse.json({ status: pull.previewStatus, diff --git a/src/app/api/repos/[owner]/[repo]/pulls/[number]/route.ts b/src/app/api/repos/[owner]/[repo]/pulls/[number]/route.ts index e661b14..9d8cef3 100644 --- a/src/app/api/repos/[owner]/[repo]/pulls/[number]/route.ts +++ b/src/app/api/repos/[owner]/[repo]/pulls/[number]/route.ts @@ -1,6 +1,6 @@ import { NextResponse } from "next/server"; -import { getRepo } from "@/lib/registry/repos"; import { getPullRequest } from "@/lib/registry/pulls"; +import { getRepo } from "@/lib/registry/repos"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/repos/[owner]/[repo]/pulls/route.ts b/src/app/api/repos/[owner]/[repo]/pulls/route.ts index 2b413d9..c121797 100644 --- a/src/app/api/repos/[owner]/[repo]/pulls/route.ts +++ b/src/app/api/repos/[owner]/[repo]/pulls/route.ts @@ -1,9 +1,9 @@ import { NextResponse } from "next/server"; -import { getRepo, RegistryError } from "@/lib/registry/repos"; -import { listPullRequests, upsertPullRequest } from "@/lib/registry/pulls"; -import { listBranches } from "@/lib/git/objects"; -import { repoPath } from "@/lib/git/backend"; import { requireOwner } from "@/lib/auth/session"; +import { repoPath } from "@/lib/git/backend"; +import { listBranches } from "@/lib/git/objects"; +import { listPullRequests, upsertPullRequest } from "@/lib/registry/pulls"; +import { getRepo, RegistryError } from "@/lib/registry/repos"; import { writePullToPod } from "@/lib/solid/pulls"; export const runtime = "nodejs"; @@ -20,15 +20,9 @@ export async function GET(req: Request, { params }: Params) { const url = new URL(req.url); const statusParam = url.searchParams.get("status") ?? "open"; if (!["open", "merged", "closed", "all"].includes(statusParam)) { - return NextResponse.json( - { error: "status must be open|merged|closed|all" }, - { status: 400 }, - ); + return NextResponse.json({ error: "status must be open|merged|closed|all" }, { status: 400 }); } - const pulls = listPullRequests( - repo.id, - statusParam as "open" | "merged" | "closed" | "all", - ); + const pulls = listPullRequests(repo.id, statusParam as "open" | "merged" | "closed" | "all"); return NextResponse.json({ pulls }); } @@ -59,15 +53,10 @@ export async function POST(req: Request, { params }: Params) { return NextResponse.json({ error: "title required" }, { status: 400 }); } if (typeof sourceBranch !== "string" || !sourceBranch.trim()) { - return NextResponse.json( - { error: "sourceBranch required" }, - { status: 400 }, - ); + return NextResponse.json({ error: "sourceBranch required" }, { status: 400 }); } const target = - typeof targetBranch === "string" && targetBranch.trim() - ? targetBranch - : repo.defaultBranch; + typeof targetBranch === "string" && targetBranch.trim() ? targetBranch : repo.defaultBranch; if (sourceBranch === target) { return NextResponse.json( { error: "sourceBranch and targetBranch must differ" }, @@ -89,10 +78,7 @@ export async function POST(req: Request, { params }: Params) { ); } if (!targetSha) { - return NextResponse.json( - { error: `target branch '${target}' not found` }, - { status: 400 }, - ); + return NextResponse.json({ error: `target branch '${target}' not found` }, { status: 400 }); } try { @@ -104,9 +90,7 @@ export async function POST(req: Request, { params }: Params) { targetBranch: target, sourceSha, authorWebId: - typeof authorWebId === "string" && authorWebId === auth.webId - ? authorWebId - : auth.webId, + typeof authorWebId === "string" && authorWebId === auth.webId ? authorWebId : auth.webId, issueId: typeof issueId === "number" ? issueId : null, }); @@ -114,10 +98,7 @@ export async function POST(req: Request, { params }: Params) { // (mirrors how issues are written). A pod hiccup must never block the // PR open, so we fire-and-forget and only log on failure (#142). writePullToPod(repo, pull).catch((err) => { - console.warn( - `[pulls.POST] writePullToPod for ${owner}/${name}#${pull.number} failed:`, - err, - ); + console.warn(`[pulls.POST] writePullToPod for ${owner}/${name}#${pull.number} failed:`, err); }); return NextResponse.json({ pull }, { status: 201 }); diff --git a/src/app/api/repos/[owner]/[repo]/route.ts b/src/app/api/repos/[owner]/[repo]/route.ts index 5cf67f2..2131dad 100644 --- a/src/app/api/repos/[owner]/[repo]/route.ts +++ b/src/app/api/repos/[owner]/[repo]/route.ts @@ -1,14 +1,14 @@ import { NextResponse } from "next/server"; +import { requireOwner } from "@/lib/auth/session"; +import { deleteBareRepo } from "@/lib/git/backend"; import { - getRepo, + deleteRepoById, getPagesConfig, + getRepo, RegistryError, updateRepo, - deleteRepoById, } from "@/lib/registry/repos"; import { writeRepoMetadata } from "@/lib/solid/repo-metadata"; -import { requireOwner } from "@/lib/auth/session"; -import { deleteBareRepo } from "@/lib/git/backend"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -48,33 +48,25 @@ export async function PATCH(req: Request, { params }: Params) { } catch { return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); } - const { visibility, defaultBranch, proposalsEnabled, collabEnabled } = - (body ?? {}) as Record; + const { visibility, defaultBranch, proposalsEnabled, collabEnabled } = (body ?? {}) as Record< + string, + unknown + >; try { const updated = updateRepo(owner, name, { - visibility: - visibility === "public" || visibility === "private" - ? visibility - : undefined, - defaultBranch: - typeof defaultBranch === "string" ? defaultBranch : undefined, - proposalsEnabled: - typeof proposalsEnabled === "boolean" ? proposalsEnabled : undefined, - collabEnabled: - typeof collabEnabled === "boolean" ? collabEnabled : undefined, + visibility: visibility === "public" || visibility === "private" ? visibility : undefined, + defaultBranch: typeof defaultBranch === "string" ? defaultBranch : undefined, + proposalsEnabled: typeof proposalsEnabled === "boolean" ? proposalsEnabled : undefined, + collabEnabled: typeof collabEnabled === "boolean" ? collabEnabled : undefined, }); writeRepoMetadata(updated, getPagesConfig(updated.id)).catch((err) => { - console.warn( - `[repos.PATCH] writeRepoMetadata for ${owner}/${name} failed:`, - err, - ); + console.warn(`[repos.PATCH] writeRepoMetadata for ${owner}/${name} failed:`, err); }); return NextResponse.json({ repo: updated }); } catch (e) { if (e instanceof RegistryError) { - const status = - e.code === "NOT_FOUND" ? 404 : e.code === "INVALID_NAME" ? 400 : 400; + const status = e.code === "NOT_FOUND" ? 404 : e.code === "INVALID_NAME" ? 400 : 400; return NextResponse.json({ error: e.message }, { status }); } throw e; @@ -128,10 +120,7 @@ export async function DELETE(req: Request, { params }: Params) { try { await deleteBareRepo(owner, name); } catch (err) { - console.warn( - `[repos.DELETE] failed to remove bare repo for ${owner}/${name}:`, - err, - ); + console.warn(`[repos.DELETE] failed to remove bare repo for ${owner}/${name}:`, err); } return NextResponse.json({ ok: true }); diff --git a/src/app/api/repos/[owner]/[repo]/runs/route.ts b/src/app/api/repos/[owner]/[repo]/runs/route.ts index fcda092..71bbfe1 100644 --- a/src/app/api/repos/[owner]/[repo]/runs/route.ts +++ b/src/app/api/repos/[owner]/[repo]/runs/route.ts @@ -1,15 +1,12 @@ -import { NextResponse } from "next/server"; import { spawn } from "node:child_process"; +import { NextResponse } from "next/server"; +import { requireOwner } from "@/lib/auth/session"; +import { repoPath } from "@/lib/git/backend"; +import { jsonResponse } from "@/lib/http/json"; +import { assertCanDispatchRun, QuotaExceededError } from "@/lib/registry/quotas"; import { getRepo } from "@/lib/registry/repos"; import { listRunsForRepo } from "@/lib/registry/runs"; -import { repoPath } from "@/lib/git/backend"; import { runWorkflow } from "@/lib/workflows/runner"; -import { requireOwner } from "@/lib/auth/session"; -import { jsonResponse } from "@/lib/http/json"; -import { - assertCanDispatchRun, - QuotaExceededError, -} from "@/lib/registry/quotas"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -67,8 +64,7 @@ export async function POST(req: Request, { params }: Params) { } catch { /* empty body is fine — default to repo's default branch */ } - const requestedBranch = - (body as Record)?.branch; + const requestedBranch = (body as Record)?.branch; const branch = typeof requestedBranch === "string" && requestedBranch.length > 0 ? requestedBranch @@ -89,15 +85,10 @@ export async function POST(req: Request, { params }: Params) { // Fire-and-forget; surface the run id so the client can navigate to it. // We don't `await` the runner — manual triggers are explicitly async to // mirror the push-driven path. - const runPromise = runWorkflow({ repoId: repo.id, ref, branch }).catch( - (err) => { - console.error( - `[manual-run] failed for ${owner}/${name}@${branch}:`, - err, - ); - return null; - }, - ); + const runPromise = runWorkflow({ repoId: repo.id, ref, branch }).catch((err) => { + console.error(`[manual-run] failed for ${owner}/${name}@${branch}:`, err); + return null; + }); // We DO await briefly here so we can return the run id of the row the // runner just inserted. The runner inserts the row before doing any @@ -132,8 +123,6 @@ function resolveRef(bareRepoPath: string, ref: string): Promise { let stdout = ""; child.stdout.on("data", (d: Buffer) => (stdout += d.toString())); child.on("error", () => resolve(null)); - child.on("close", (code) => - resolve(code === 0 ? stdout.trim() : null), - ); + child.on("close", (code) => resolve(code === 0 ? stdout.trim() : null)); }); } diff --git a/src/app/api/repos/[owner]/[repo]/tokens/[id]/route.ts b/src/app/api/repos/[owner]/[repo]/tokens/[id]/route.ts index f5d744c..2fd2c37 100644 --- a/src/app/api/repos/[owner]/[repo]/tokens/[id]/route.ts +++ b/src/app/api/repos/[owner]/[repo]/tokens/[id]/route.ts @@ -1,7 +1,7 @@ import { NextResponse } from "next/server"; +import { requireOwner } from "@/lib/auth/session"; import { getRepo } from "@/lib/registry/repos"; import { revokePushToken } from "@/lib/registry/tokens"; -import { requireOwner } from "@/lib/auth/session"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; diff --git a/src/app/api/repos/[owner]/[repo]/tokens/route.ts b/src/app/api/repos/[owner]/[repo]/tokens/route.ts index ba95b2b..a51dd6e 100644 --- a/src/app/api/repos/[owner]/[repo]/tokens/route.ts +++ b/src/app/api/repos/[owner]/[repo]/tokens/route.ts @@ -1,12 +1,9 @@ import { NextResponse } from "next/server"; +import { requireOwner } from "@/lib/auth/session"; +import { RATE_LIMITS, rateLimit } from "@/lib/rate-limit"; +import { assertCanMintToken, QuotaExceededError } from "@/lib/registry/quotas"; import { getRepo } from "@/lib/registry/repos"; import { createPushToken, listPushTokens } from "@/lib/registry/tokens"; -import { requireOwner } from "@/lib/auth/session"; -import { rateLimit, RATE_LIMITS } from "@/lib/rate-limit"; -import { - assertCanMintToken, - QuotaExceededError, -} from "@/lib/registry/quotas"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -63,8 +60,7 @@ export async function POST(req: Request, { params }: Params) { return NextResponse.json( { ...created, - hint: - "Use this token as the HTTP-Basic password (any username) when pushing or, for private repos, when cloning. The plaintext is shown ONCE — store it now.", + hint: "Use this token as the HTTP-Basic password (any username) when pushing or, for private repos, when cloning. The plaintext is shown ONCE — store it now.", }, { status: 201 }, ); diff --git a/src/app/api/repos/route.ts b/src/app/api/repos/route.ts index e9a1cd3..0bc0b22 100644 --- a/src/app/api/repos/route.ts +++ b/src/app/api/repos/route.ts @@ -1,4 +1,10 @@ import { NextResponse } from "next/server"; +import { requireSession } from "@/lib/auth/session"; +import { getEnv } from "@/lib/env"; +import { createBareRepo } from "@/lib/git/backend"; +import { jsonResponse } from "@/lib/http/json"; +import { RATE_LIMITS, rateLimit } from "@/lib/rate-limit"; +import { assertCanCreateRepo, QuotaExceededError } from "@/lib/registry/quotas"; import { createRepo, deleteRepoById, @@ -6,17 +12,8 @@ import { listRepos, RegistryError, } from "@/lib/registry/repos"; -import { createBareRepo } from "@/lib/git/backend"; -import { writeRepoMetadata } from "@/lib/solid/repo-metadata"; -import { requireSession } from "@/lib/auth/session"; -import { getEnv } from "@/lib/env"; import { verifyPodRootForWebId } from "@/lib/solid/profile"; -import { rateLimit, RATE_LIMITS } from "@/lib/rate-limit"; -import { jsonResponse } from "@/lib/http/json"; -import { - assertCanCreateRepo, - QuotaExceededError, -} from "@/lib/registry/quotas"; +import { writeRepoMetadata } from "@/lib/solid/repo-metadata"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -38,20 +35,11 @@ export async function POST(req: Request) { return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); } - const { - owner, - name, - ownerWebId, - ownerPodRoot, - defaultBranch, - visibility, - } = (body ?? {}) as Record; + const { owner, name, ownerWebId, ownerPodRoot, defaultBranch, visibility } = (body ?? + {}) as Record; if (typeof owner !== "string" || typeof name !== "string") { - return NextResponse.json( - { error: "owner and name are required strings" }, - { status: 400 }, - ); + return NextResponse.json({ error: "owner and name are required strings" }, { status: 400 }); } if (typeof ownerWebId !== "string" || typeof ownerPodRoot !== "string") { return NextResponse.json( @@ -116,12 +104,8 @@ export async function POST(req: Request) { name, ownerWebId, ownerPodRoot, - defaultBranch: - typeof defaultBranch === "string" ? defaultBranch : undefined, - visibility: - visibility === "private" || visibility === "public" - ? visibility - : undefined, + defaultBranch: typeof defaultBranch === "string" ? defaultBranch : undefined, + visibility: visibility === "private" || visibility === "public" ? visibility : undefined, }); } catch (e) { if (e instanceof RegistryError) { @@ -139,10 +123,7 @@ export async function POST(req: Request) { } catch (e) { deleteRepoById(repo.id); console.error("[repos.POST] failed to create bare repo:", e); - return NextResponse.json( - { error: "failed to create bare git repository" }, - { status: 500 }, - ); + return NextResponse.json({ error: "failed to create bare git repository" }, { status: 500 }); } // Best-effort: publish a Turtle description of the repo into the owner's @@ -150,10 +131,7 @@ export async function POST(req: Request) { // the repo as Linked Data. Failure here does not fail repo creation — // the pod might be temporarily offline. writeRepoMetadata(repo, getPagesConfig(repo.id)).catch((err) => { - console.warn( - `[repos.POST] writeRepoMetadata for ${repo.owner}/${repo.name} failed:`, - err, - ); + console.warn(`[repos.POST] writeRepoMetadata for ${repo.owner}/${repo.name} failed:`, err); }); const env = getEnv(); diff --git a/src/app/api/signup/route.ts b/src/app/api/signup/route.ts index d15622e..b2f341d 100644 --- a/src/app/api/signup/route.ts +++ b/src/app/api/signup/route.ts @@ -1,10 +1,10 @@ -import { NextResponse } from "next/server"; import { headers } from "next/headers"; +import { NextResponse } from "next/server"; import { getEnv } from "@/lib/env"; -import { createUser } from "@/lib/registry/users"; +import { clip, log } from "@/lib/log"; +import { RATE_LIMITS, rateLimit } from "@/lib/rate-limit"; import { RegistryError, validateName } from "@/lib/registry/repos"; -import { rateLimit, RATE_LIMITS } from "@/lib/rate-limit"; -import { log, clip } from "@/lib/log"; +import { createUser } from "@/lib/registry/users"; export const runtime = "nodejs"; export const dynamic = "force-dynamic"; @@ -41,11 +41,7 @@ export async function POST(req: Request) { const origin = hdrs.get("origin") ?? ""; const secFetchSite = hdrs.get("sec-fetch-site") ?? ""; const ownOrigin = env.bridgePublicUrl.replace(/\/$/, ""); - if ( - origin !== ownOrigin && - secFetchSite !== "same-origin" && - secFetchSite !== "same-site" - ) { + if (origin !== ownOrigin && secFetchSite !== "same-origin" && secFetchSite !== "same-site") { return NextResponse.json( { error: "cross-origin signup is not allowed", code: "CSRF" }, { status: 403 }, @@ -63,27 +59,17 @@ export async function POST(req: Request) { } const { email, password, podName } = (body ?? {}) as Record; - if ( - typeof email !== "string" || - typeof password !== "string" || - typeof podName !== "string" - ) { + if (typeof email !== "string" || typeof password !== "string" || typeof podName !== "string") { return NextResponse.json( { error: "email, password, podName are required strings" }, { status: 400 }, ); } if (!email.includes("@")) { - return NextResponse.json( - { error: "email must look like an email address" }, - { status: 400 }, - ); + return NextResponse.json({ error: "email must look like an email address" }, { status: 400 }); } if (password.length < 8) { - return NextResponse.json( - { error: "password must be at least 8 characters" }, - { status: 400 }, - ); + return NextResponse.json({ error: "password must be at least 8 characters" }, { status: 400 }); } try { validateName(podName, "owner"); @@ -94,9 +80,7 @@ export async function POST(req: Request) { throw e; } - const podBase = env.podBaseUrl.endsWith("/") - ? env.podBaseUrl - : env.podBaseUrl + "/"; + const podBase = env.podBaseUrl.endsWith("/") ? env.podBaseUrl : env.podBaseUrl + "/"; // CSS v7 dropped the single-POST `/idp/register/` endpoint that earlier // CSS shipped. The v7 path is a three-step account-API dance: @@ -232,9 +216,7 @@ export async function POST(req: Request) { } function extractSetCookie(res: Response, name: string): string | null { - const getSetCookie = ( - res.headers as unknown as { getSetCookie?: () => string[] } - ).getSetCookie; + const getSetCookie = (res.headers as unknown as { getSetCookie?: () => string[] }).getSetCookie; if (typeof getSetCookie !== "function") return null; const lines = getSetCookie.call(res.headers); for (const line of lines) { @@ -266,10 +248,7 @@ function pickStringPath(obj: unknown, path: string[]): string | null { return typeof cur === "string" ? cur : null; } -function stringField( - obj: Record | null, - key: string, -): string | null { +function stringField(obj: Record | null, key: string): string | null { if (!obj) return null; const v = obj[key]; return typeof v === "string" ? v : null; diff --git a/src/app/connect/page.tsx b/src/app/connect/page.tsx index 4c95a82..159398d 100644 --- a/src/app/connect/page.tsx +++ b/src/app/connect/page.tsx @@ -33,10 +33,9 @@ export default function ConnectPage() { Connect a pod.

- Mind Codespaces needs permission to write into your pod (your published - site, and a Turtle description of each repo). You authorize that here - via your pod's own OIDC issuer — the bridge never sees your - password, only a token your pod issues to it. + Mind Codespaces needs permission to write into your pod (your published site, and a Turtle + description of each repo). You authorize that here via your pod's own OIDC issuer — the + bridge never sees your password, only a token your pod issues to it.


@@ -49,15 +48,12 @@ export default function ConnectPage() { > 01 — authorize

-

+

Point us at your issuer.

- Most pods publish their OIDC issuer at the pod's root URL. - Pick a preset or paste your own. + Most pods publish their OIDC issuer at the pod's root URL. Pick a preset or paste + your own.

@@ -72,10 +68,7 @@ export default function ConnectPage() { > What this means

-

+

A token, not a key.

@@ -114,20 +107,14 @@ export default function ConnectPage() {
  • · - - your-pod/codespaces/* - {" "} - - — repo metadata - + your-pod/codespaces/*{" "} + — repo metadata
  • · - - your-pod/public/sites/* - {" "} + your-pod/public/sites/*{" "} — when Mind Pages is enabled @@ -141,8 +128,7 @@ export default function ConnectPage() { title="A refresh token lives on disk." body={ <> - We persist it in{" "} - .registry-data/ so a restart + We persist it in .registry-data/ so a restart doesn't bounce you out. It never leaves this machine. } @@ -175,15 +161,10 @@ function TrustItem({ {kicker}
    -

    +

    {title}

    -
    - {body} -
    +
    {body}
  • ); diff --git a/src/app/how-it-works/page.tsx b/src/app/how-it-works/page.tsx index 529c992..76ff389 100644 --- a/src/app/how-it-works/page.tsx +++ b/src/app/how-it-works/page.tsx @@ -16,23 +16,20 @@ export default function HowItWorksPage() { className="mt-4 text-[11px] uppercase tracking-[0.22em] text-[color:var(--ink-faint)]" style={{ fontFamily: "var(--font-mono-src)" }} > - Your code · your context{" "} - · your pod. + Your code · your context · your pod.

    - Mind Codespaces gives you Git collaboration where{" "} - everything that matters — your code, your issues, your AI - memory, your published sites — lives inside your own Solid Pod. The - bridge in the middle just translates protocols; it doesn't own - your project. + Mind Codespaces gives you Git collaboration where everything that matters — your + code, your issues, your AI memory, your published sites — lives inside your own Solid Pod. + The bridge in the middle just translates protocols; it doesn't own your project.


    - A pod is a small piece of storage you own. With Mind Codespaces, four - kinds of things go inside it — and nothing else needs to leave. + A pod is a small piece of storage you own. With Mind Codespaces, four kinds of things go + inside it — and nothing else needs to leave.

    @@ -41,20 +38,15 @@ export default function HowItWorksPage() {

    - The flow has three beats. There's some plumbing — a small bridge - translates Git Smart HTTP into pod writes — but you only ever talk to - two things: your terminal and your pod. + The flow has three beats. There's some plumbing — a small bridge translates Git Smart + HTTP into pod writes — but you only ever talk to two things: your terminal and your pod.

      - +

      - The coder agent runs against the same pod the user owns, with the - permissions the user granted. It reads what you let it read, writes - where you let it write, and the conversation history — the thing AI - tools usually keep in their own database — lands back in the pod - alongside the code. If you revoke access, the agent stops. If you - move pods, the memory comes with you. + The coder agent runs against the same pod the user owns, with the permissions the user + granted. It reads what you let it read, writes where you let it write, and the + conversation history — the thing AI tools usually keep in their own database — lands back + in the pod alongside the code. If you revoke access, the agent stops. If you move pods, + the memory comes with you.

        - issue body, recent commits, files the prompt names — nothing else - until you say so. + issue body, recent commits, files the prompt names — nothing else until you say so. - a branch in your repo, a pull request, screenshots and - comments in your pod. + a branch in your repo, a pull request, screenshots and comments in{" "} + your pod. - the conversation, but the memory lives in the pod, not in an - opaque vendor database. + the conversation, but the memory lives in the pod, not in an opaque vendor database. - the moment you revoke its permission — the bridge is a delegate, - not an owner. + the moment you revoke its permission — the bridge is a delegate, not an owner.
    @@ -103,8 +91,8 @@ export default function HowItWorksPage() {

    - The bridge can be replaced and your work survives, because nothing - irreplaceable lives in the bridge. + The bridge can be replaced and your work survives, because nothing irreplaceable lives in + the bridge.

    -

    +

    {title}

    {children}
    @@ -203,8 +182,7 @@ function PodCapsule() { className="relative rounded-[2rem] border p-8 sm:p-10" style={{ borderColor: "var(--accent)", - background: - "color-mix(in srgb, var(--accent) 6%, var(--paper-soft))", + background: "color-mix(in srgb, var(--accent) 6%, var(--paper-soft))", boxShadow: "0 0 0 1px color-mix(in srgb, var(--accent) 20%, transparent), 0 20px 60px -30px color-mix(in srgb, var(--accent) 40%, transparent)", }} @@ -240,17 +218,13 @@ function PodCapsule() {
    {it.icon}
    -

    +

    {it.label}

    + @@ -284,7 +267,16 @@ function CodeGlyph() { function IssueGlyph() { return ( - + @@ -294,7 +286,16 @@ function IssueGlyph() { function BrainGlyph() { return ( - + @@ -306,7 +307,16 @@ function BrainGlyph() { function PagesGlyph() { return ( - + @@ -344,10 +354,7 @@ function PushFlow() { > - + {/* Laptop node */} @@ -493,15 +500,7 @@ function PushFlow() { ); } -function FlowStep({ - n, - title, - body, -}: { - n: number; - title: string; - body: string; -}) { +function FlowStep({ n, title, body }: { n: number; title: string; body: string }) { return (

  • -

    +

    0{n}

    -

    +

    {title}

    Scenario

    -

    +

    {heading}

    -

    - {outcome} -

    -
      +

      {outcome}

      +
        {kept.map((item) => ( -
      • +
      • kept {item}
      • ))} {lost.map((item) => ( -
      • +
      • lost {item}
      • diff --git a/src/app/identities/identity-row.tsx b/src/app/identities/identity-row.tsx index 42792f0..026f4c8 100644 --- a/src/app/identities/identity-row.tsx +++ b/src/app/identities/identity-row.tsx @@ -1,11 +1,11 @@ "use client"; +import { Button } from "@mind-studio/ui"; import Link from "next/link"; import { useState } from "react"; -import { Button } from "@mind-studio/ui"; -import type { Identity } from "@/lib/registry/identities"; -import { formatAbsoluteIso, formatRelativeTime } from "@/lib/format"; import { authedFetch } from "@/lib/auth/csrf-client"; +import { formatAbsoluteIso, formatRelativeTime } from "@/lib/format"; +import type { Identity } from "@/lib/registry/identities"; export type OwnedRepo = { id: number; @@ -32,10 +32,9 @@ export function IdentityRow({ setBusy(true); setError(null); try { - const res = await authedFetch( - `/api/identities/${encodeURIComponent(identity.webId)}`, - { method: "DELETE" }, - ); + const res = await authedFetch(`/api/identities/${encodeURIComponent(identity.webId)}`, { + method: "DELETE", + }); if (!res.ok) { const body = await res.json().catch(() => ({})); throw new Error(body.error ?? `request failed (${res.status})`); @@ -166,9 +165,7 @@ export function IdentityRow({
  • - {error ? ( -

    {error}

    - ) : null} + {error ?

    {error}

    : null} ); } @@ -188,13 +185,7 @@ function DisconnectControl({ }) { if (!confirming) { return ( - ); diff --git a/src/app/identities/page.tsx b/src/app/identities/page.tsx index 02ba4a5..d4739ba 100644 --- a/src/app/identities/page.tsx +++ b/src/app/identities/page.tsx @@ -1,5 +1,5 @@ import Link from "next/link"; -import { listIdentities, type Identity } from "@/lib/registry/identities"; +import { type Identity, listIdentities } from "@/lib/registry/identities"; import { listRepos, type Repo } from "@/lib/registry/repos"; import { IdentityRow } from "./identity-row"; @@ -38,9 +38,8 @@ export default function IdentitiesPage() { ) : null}

    - WebIDs that have authorized this bridge. Their refresh tokens live - in .registry-data/ — disconnect to - revoke. + WebIDs that have authorized this bridge. Their refresh tokens live in{" "} + .registry-data/ — disconnect to revoke.


    @@ -77,17 +76,13 @@ function EmptyState() { > // no identities connected

    -

    +

    Running on seeded credentials.

    - The publisher is signing writes with a shared dev account - (alice@mind.local). Authorize - a real pod to swap in a delegated refresh token — repos owned by - that WebID will publish under your own identity. + The publisher is signing writes with a shared dev account ( + alice@mind.local). Authorize a real pod to swap in a delegated + refresh token — repos owned by that WebID will publish under your own identity.

    BEFORE first paint to dodge the @@ -89,10 +89,7 @@ async function Masthead() {
    - + {session ? ( Codespaces

    - A prototype bridge between Git and Solid Pods. Git stays Git; the pod - owns identity, metadata, and the published site. + A prototype bridge between Git and Solid Pods. Git stays Git; the pod owns identity, + metadata, and the published site.

    - Sibling of Mind Market · No third-party - analytics · No cookies set beyond strictly necessary + Sibling of Mind Market · No third-party analytics · No + cookies set beyond strictly necessary

    diff --git a/src/app/login/login-page.tsx b/src/app/login/login-page.tsx index 959731e..030bf3d 100644 --- a/src/app/login/login-page.tsx +++ b/src/app/login/login-page.tsx @@ -1,8 +1,8 @@ "use client"; -import { useCallback, useState } from "react"; -import { useRouter } from "next/navigation"; import { Button, Input } from "@mind-studio/ui"; +import { useRouter } from "next/navigation"; +import { useCallback, useState } from "react"; type Tab = "login" | "register"; @@ -63,11 +63,7 @@ export function LoginPage({ }} role="tablist" > - setTab("login")} - label="Sign in" - /> + setTab("login")} label="Sign in" /> setTab("register")} @@ -84,10 +80,7 @@ export function LoginPage({ issuerPresets={issuerPresets} /> ) : ( - setTab("login")} - /> + setTab("login")} /> )} @@ -219,10 +212,7 @@ function LoginPanel({ {advancedOpen ? (
    - +
    ) : null} @@ -276,10 +266,7 @@ function RegisterPanel({ error?: string; }; if (!loginRes.ok) { - setError( - loginData.error ?? - "account created, but auto sign-in failed — try signing in", - ); + setError(loginData.error ?? "account created, but auto sign-in failed — try signing in"); setBusy(false); return; } @@ -440,9 +427,7 @@ function ExternalPodPanel({ className="rounded-full border px-2.5 py-0.5 text-[11px] disabled:opacity-50" style={{ fontFamily: "var(--font-mono-src)", - borderColor: active - ? "var(--accent)" - : "var(--ink-trace)", + borderColor: active ? "var(--accent)" : "var(--ink-trace)", background: active ? "var(--accent-soft)" : "transparent", color: active ? "var(--accent-deep)" : "var(--ink-soft)", }} @@ -469,8 +454,7 @@ function ExternalPodPanel({

    // - external pods log you in at their own URL — the bridge never sees - that password. + external pods log you in at their own URL — the bridge never sees that password.

    ); @@ -480,13 +464,7 @@ function ExternalPodPanel({ /* Bits */ /* -------------------------------------------------------------------- */ -function FieldLabel({ - name, - children, -}: { - name: string; - children: React.ReactNode; -}) { +function FieldLabel({ name, children }: { name: string; children: React.ReactNode }) { return (