From f28d0ec8c16cade74f1214a101f61848f98852d7 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:00:14 +0000 Subject: [PATCH 1/9] chore: add e18e lint plugin This also disables `no-console` since pretty much everything in this repo can run server-side, where console logs are valuable when done right. --- .oxlintrc.json | 11 +++++++++-- package.json | 1 + pnpm-lock.yaml | 22 ++++++++++++++++++++++ scripts/lint.ts | 7 ++++++- server/utils/dependency-analysis.ts | 2 +- server/utils/readme.ts | 16 ++++++++-------- test/e2e/test-utils.ts | 2 +- 7 files changed, 48 insertions(+), 13 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 7cb23eb90..d50b9d249 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,18 +1,25 @@ { "$schema": "https://unpkg.com/oxlint/configuration_schema.json", "plugins": ["unicorn", "typescript", "oxc", "vue", "vitest"], + "jsPlugins": ["@e18e/eslint-plugin"], "categories": { "correctness": "error", "suspicious": "warn", "perf": "warn" }, "rules": { - "no-console": "warn", + "no-console": "off", "no-await-in-loop": "off", "unicorn/no-array-sort": "off", "no-restricted-globals": "error", - "typescript/consistent-type-imports": "error" + "typescript/consistent-type-imports": "error", + "e18e/prefer-array-from-map": "error", + "e18e/prefer-timer-args": "error", + "e18e/prefer-date-now": "error", + "e18e/prefer-regex-test": "error", + "e18e/prefer-array-some": "error" }, + "overrides": [], "ignorePatterns": [ ".output/**", ".data/**", diff --git a/package.json b/package.json index 6dce46fbe..a2afadec3 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "vue-data-ui": "3.14.7" }, "devDependencies": { + "@e18e/eslint-plugin": "0.1.4", "@intlify/core-base": "11.2.8", "@npm/types": "2.1.0", "@playwright/test": "1.58.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e39df21b6..c464f3f31 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -201,6 +201,9 @@ importers: specifier: 3.14.7 version: 3.14.7(vue@3.5.27(typescript@5.9.3)) devDependencies: + '@e18e/eslint-plugin': + specifier: 0.1.4 + version: 0.1.4(eslint@9.39.2(jiti@2.6.1)) '@intlify/core-base': specifier: 11.2.8 version: 11.2.8 @@ -1058,6 +1061,11 @@ packages: '@dxup/unimport@0.1.2': resolution: {integrity: sha512-/B8YJGPzaYq1NbsQmwgP8EZqg40NpTw4ZB3suuI0TplbxKHeK94jeaawLmVhCv+YwUnOpiWEz9U6SeThku/8JQ==} + '@e18e/eslint-plugin@0.1.4': + resolution: {integrity: sha512-nN0zo9lIsYJynhFyN+kNVINc9pDMotKjfEr1NcRRXCFKJt89V5S4DL6ZSXN2USJb2XezkMvyLOQMZ0lZTpvFeQ==} + peerDependencies: + eslint: ^9.0.0 + '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} @@ -5565,6 +5573,9 @@ packages: engines: {node: '>=6.0'} hasBin: true + eslint-plugin-depend@1.4.0: + resolution: {integrity: sha512-MQs+m4nHSfgAO9bJDsBzqw0ofK/AOA0vfeY/6ahofqcUMLeM6/D1sTYs21fOhc17kNU/gn58YCtj20XaAssh2A==} + eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -10645,6 +10656,11 @@ snapshots: '@dxup/unimport@0.1.2': {} + '@e18e/eslint-plugin@0.1.4(eslint@9.39.2(jiti@2.6.1))': + dependencies: + eslint: 9.39.2(jiti@2.6.1) + eslint-plugin-depend: 1.4.0 + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -15639,6 +15655,12 @@ snapshots: optionalDependencies: source-map: 0.6.1 + eslint-plugin-depend@1.4.0: + dependencies: + empathic: 2.0.0 + module-replacements: 2.11.0 + semver: 7.7.3 + eslint-scope@5.1.1: dependencies: esrecurse: 4.3.0 diff --git a/scripts/lint.ts b/scripts/lint.ts index 3556a29f7..85578064e 100644 --- a/scripts/lint.ts +++ b/scripts/lint.ts @@ -28,6 +28,11 @@ function runCommand(command: string, args: string[]) { const oxlintVersion = getDependencyVersion('oxlint') const oxfmtVersion = getDependencyVersion('oxfmt') +const e18eVersion = getDependencyVersion('@e18e/eslint-plugin') -runCommand('pnpx', [`oxlint@${oxlintVersion}`]) +runCommand('pnpx', [ + `--package=@e18e/eslint-plugin@${e18eVersion}`, + `--package=oxlint@${oxlintVersion}`, + 'oxlint', +]) runCommand('pnpx', [`oxfmt@${oxfmtVersion}`, '--check']) diff --git a/server/utils/dependency-analysis.ts b/server/utils/dependency-analysis.ts index fcd109c1b..30556436b 100644 --- a/server/utils/dependency-analysis.ts +++ b/server/utils/dependency-analysis.ts @@ -179,7 +179,7 @@ export const analyzeDependencyTree = defineCachedFunction( const resolved = await resolveDependencyTree(name, version, { trackDepth: true }) // Convert to array with query info - const packages: PackageQueryInfo[] = [...resolved.values()].map(pkg => ({ + const packages: PackageQueryInfo[] = Array.from(resolved.values(), pkg => ({ name: pkg.name, version: pkg.version, depth: pkg.depth!, diff --git a/server/utils/readme.ts b/server/utils/readme.ts index 6f66d21e2..a1fcfa36d 100644 --- a/server/utils/readme.ts +++ b/server/utils/readme.ts @@ -264,6 +264,14 @@ function resolveImageUrl(url: string, packageName: string, repoInfo?: Repository return resolved } +// Helper to prefix id attributes with 'user-content-' +function prefixId(tagName: string, attribs: sanitizeHtml.Attributes) { + if (attribs.id && !attribs.id.startsWith('user-content-')) { + attribs.id = `user-content-${attribs.id}` + } + return { tagName, attribs } +} + export async function renderReadmeHtml( content: string, packageName: string, @@ -398,14 +406,6 @@ ${html} const rawHtml = marked.parse(content) as string - // Helper to prefix id attributes with 'user-content-' - const prefixId = (tagName: string, attribs: sanitizeHtml.Attributes) => { - if (attribs.id && !attribs.id.startsWith('user-content-')) { - attribs.id = `user-content-${attribs.id}` - } - return { tagName, attribs } - } - const sanitized = sanitizeHtml(rawHtml, { allowedTags: ALLOWED_TAGS, allowedAttributes: ALLOWED_ATTR, diff --git a/test/e2e/test-utils.ts b/test/e2e/test-utils.ts index 4f774e431..e454b974d 100644 --- a/test/e2e/test-utils.ts +++ b/test/e2e/test-utils.ts @@ -313,7 +313,7 @@ async function handleJsdelivrCdn(route: Route): Promise { const pathname = decodeURIComponent(url.pathname) // README file requests - return 404 (package pages work fine without README) - if (pathname.match(/readme/i)) { + if (/readme/i.test(pathname)) { await route.fulfill({ status: 404, body: 'Not found' }) return true } From 6bb7c9d2f54b3337d5b13bdfc672803ac6b6f3d0 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:26:12 +0000 Subject: [PATCH 2/9] chore: use global install because dlx doesn't do the job --- scripts/lint.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/scripts/lint.ts b/scripts/lint.ts index 85578064e..34700f8c8 100644 --- a/scripts/lint.ts +++ b/scripts/lint.ts @@ -30,9 +30,7 @@ const oxlintVersion = getDependencyVersion('oxlint') const oxfmtVersion = getDependencyVersion('oxfmt') const e18eVersion = getDependencyVersion('@e18e/eslint-plugin') -runCommand('pnpx', [ - `--package=@e18e/eslint-plugin@${e18eVersion}`, - `--package=oxlint@${oxlintVersion}`, - 'oxlint', -]) +// Peers of the lint config +runCommand('pnpm', ['i', '-g', `@e18e/eslint-plugin@${e18eVersion}`]) +runCommand('pnpx', [`--package=oxlint@${oxlintVersion}`, 'oxlint']) runCommand('pnpx', [`oxfmt@${oxfmtVersion}`, '--check']) From bee742f4f0514c2d352e4272ea1c659f14be8fd1 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:32:51 +0000 Subject: [PATCH 3/9] chore: ppffft. --- scripts/lint.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/lint.ts b/scripts/lint.ts index 34700f8c8..5aa5b6808 100644 --- a/scripts/lint.ts +++ b/scripts/lint.ts @@ -30,7 +30,7 @@ const oxlintVersion = getDependencyVersion('oxlint') const oxfmtVersion = getDependencyVersion('oxfmt') const e18eVersion = getDependencyVersion('@e18e/eslint-plugin') -// Peers of the lint config -runCommand('pnpm', ['i', '-g', `@e18e/eslint-plugin@${e18eVersion}`]) -runCommand('pnpx', [`--package=oxlint@${oxlintVersion}`, 'oxlint']) +// Install globally so we can also install peers +runCommand('pnpm', ['i', '-g', `@e18e/eslint-plugin@${e18eVersion}`, `oxlint@${oxlintVersion}`]) +runCommand('pnpm', ['exec', 'oxlint']) runCommand('pnpx', [`oxfmt@${oxfmtVersion}`, '--check']) From 70d80b15f1058d000985ff0b31b5c0d42b930076 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:39:43 +0000 Subject: [PATCH 4/9] chore: ignore in knip --- knip.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/knip.ts b/knip.ts index 493fc3154..d066b592a 100644 --- a/knip.ts +++ b/knip.ts @@ -39,6 +39,9 @@ const config: KnipConfig = { /** Some components import types from here, but installing it directly could lead to a version mismatch */ 'vue-router', + + /** Oxlint plugins don't get picked up yet */ + '@e18e/eslint-plugin', ], ignoreUnresolved: ['#components', '#oauth/config'], }, From 8f52a60ed37565e6d8f6298a111d0b6a66ead885 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:57:55 +0000 Subject: [PATCH 5/9] chore: make this sketchy file sketchier --- scripts/lint.ts | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/scripts/lint.ts b/scripts/lint.ts index 5aa5b6808..ec6b3dea7 100644 --- a/scripts/lint.ts +++ b/scripts/lint.ts @@ -4,7 +4,12 @@ * It's "stupid by design" so it could work in minimal Node.js environments. */ +import { cpSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { spawnSync } from 'node:child_process' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +const projectDir = process.cwd() function getDependencyVersion(dependencyName: string): string { const result = spawnSync('npm', ['pkg', 'get', `devDependencies.${dependencyName}`], { @@ -18,8 +23,8 @@ function getDependencyVersion(dependencyName: string): string { return JSON.parse(result.stdout) } -function runCommand(command: string, args: string[]) { - const result = spawnSync(command, args, { stdio: 'inherit' }) +function runCommand(command: string, args: string[], cwd?: string) { + const result = spawnSync(command, args, { stdio: 'inherit', cwd }) if (result.status) { throw new Error(`Command failed: ${command} ${args.join(' ')}`) @@ -30,7 +35,21 @@ const oxlintVersion = getDependencyVersion('oxlint') const oxfmtVersion = getDependencyVersion('oxfmt') const e18eVersion = getDependencyVersion('@e18e/eslint-plugin') -// Install globally so we can also install peers -runCommand('pnpm', ['i', '-g', `@e18e/eslint-plugin@${e18eVersion}`, `oxlint@${oxlintVersion}`]) -runCommand('pnpm', ['exec', 'oxlint']) +// Create a temp dir because: +// 1. oxlint seems to try to resolve plugins from the dir of the config file +// 2. pnpx/pnpm dlx doesn't have a clue about peers (plugins here), so doesn't have a way to install them +const tempDir = mkdtempSync(join(tmpdir(), 'oxlint-')) +try { + writeFileSync(join(tempDir, 'package.json'), '{"name": "temp", "version": "1.0.0"}') + cpSync('.oxlintrc.json', join(tempDir, '.oxlintrc.json')) + runCommand( + 'pnpm', + ['install', '-D', `oxlint@${oxlintVersion}`, `@e18e/eslint-plugin@${e18eVersion}`], + tempDir, + ) + runCommand('pnpm', ['exec', 'oxlint', '-c', join(tempDir, '.oxlintrc.json'), projectDir], tempDir) +} finally { + rmSync(tempDir, { recursive: true, force: true }) +} + runCommand('pnpx', [`oxfmt@${oxfmtVersion}`, '--check']) From 8cf4628e35a0ccbed7431d16a0e2b045afb8d7e1 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:16:51 +0000 Subject: [PATCH 6/9] chore: try a different approach --- .github/workflows/ci.yml | 6 +++-- scripts/lint.ts | 55 ---------------------------------------- 2 files changed, 4 insertions(+), 57 deletions(-) delete mode 100644 scripts/lint.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2468c4cc9..0bc669ebd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,10 +33,12 @@ jobs: - uses: pnpm/action-setup@1e1c8eafbd745f64b1ef30a7d7ed7965034c486c # 1e1c8eafbd745f64b1ef30a7d7ed7965034c486c name: 🟧 Install pnpm - # pnpm cache skipped deliberately as the project is not actually installed here + + - name: 📦 Install dependencies (root only, no scripts) + run: pnpm install --filter . --ignore-scripts - name: 🔠 Lint project - run: node scripts/lint.ts + run: pnpm lint types: name: 💪 Type check diff --git a/scripts/lint.ts b/scripts/lint.ts deleted file mode 100644 index ec6b3dea7..000000000 --- a/scripts/lint.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * This script runs oxlint and oxfmt in a CI environment, without the need to install the entire - * project. It reads the required version from pnpm-lock.yaml and executes the linters accordingly. - * It's "stupid by design" so it could work in minimal Node.js environments. - */ - -import { cpSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' -import { spawnSync } from 'node:child_process' -import { tmpdir } from 'node:os' -import { join } from 'node:path' - -const projectDir = process.cwd() - -function getDependencyVersion(dependencyName: string): string { - const result = spawnSync('npm', ['pkg', 'get', `devDependencies.${dependencyName}`], { - encoding: 'utf8', - }) - - if (result.status) { - throw new Error(`Command failed: pnpm info ${dependencyName} version`) - } - - return JSON.parse(result.stdout) -} - -function runCommand(command: string, args: string[], cwd?: string) { - const result = spawnSync(command, args, { stdio: 'inherit', cwd }) - - if (result.status) { - throw new Error(`Command failed: ${command} ${args.join(' ')}`) - } -} - -const oxlintVersion = getDependencyVersion('oxlint') -const oxfmtVersion = getDependencyVersion('oxfmt') -const e18eVersion = getDependencyVersion('@e18e/eslint-plugin') - -// Create a temp dir because: -// 1. oxlint seems to try to resolve plugins from the dir of the config file -// 2. pnpx/pnpm dlx doesn't have a clue about peers (plugins here), so doesn't have a way to install them -const tempDir = mkdtempSync(join(tmpdir(), 'oxlint-')) -try { - writeFileSync(join(tempDir, 'package.json'), '{"name": "temp", "version": "1.0.0"}') - cpSync('.oxlintrc.json', join(tempDir, '.oxlintrc.json')) - runCommand( - 'pnpm', - ['install', '-D', `oxlint@${oxlintVersion}`, `@e18e/eslint-plugin@${e18eVersion}`], - tempDir, - ) - runCommand('pnpm', ['exec', 'oxlint', '-c', join(tempDir, '.oxlintrc.json'), projectDir], tempDir) -} finally { - rmSync(tempDir, { recursive: true, force: true }) -} - -runCommand('pnpx', [`oxfmt@${oxfmtVersion}`, '--check']) From 49c95e9a8278852f3942bc71bfdb1e4f4bc9e297 Mon Sep 17 00:00:00 2001 From: James Garbutt <43081j@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:33:29 +0000 Subject: [PATCH 7/9] chore: enable cache --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bc669ebd..8320ecf6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,8 @@ jobs: - uses: pnpm/action-setup@1e1c8eafbd745f64b1ef30a7d7ed7965034c486c # 1e1c8eafbd745f64b1ef30a7d7ed7965034c486c name: 🟧 Install pnpm + with: + cache: true - name: 📦 Install dependencies (root only, no scripts) run: pnpm install --filter . --ignore-scripts From 83a644d1f300727ca382328e79bd8528a1abeb8c Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 5 Feb 2026 14:44:51 +0000 Subject: [PATCH 8/9] chore: update to use per-directory warning --- .oxlintrc.json | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index d50b9d249..b0bc5d6ff 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -8,7 +8,7 @@ "perf": "warn" }, "rules": { - "no-console": "off", + "no-console": "warn", "no-await-in-loop": "off", "unicorn/no-array-sort": "off", "no-restricted-globals": "error", @@ -19,7 +19,20 @@ "e18e/prefer-regex-test": "error", "e18e/prefer-array-some": "error" }, - "overrides": [], + "overrides": [ + { + "files": [ + "server/**/*", + "cli/**/*", + "scripts/**/*", + "modules/**/*", + "app/components/OgImage/*" + ], + "rules": { + "no-console": "off" + } + } + ], "ignorePatterns": [ ".output/**", ".data/**", From 7efb507432f6f83c6e7b1f0cfa0e7bd4e1989ed8 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Thu, 5 Feb 2026 14:45:30 +0000 Subject: [PATCH 9/9] fix: use more performant map --- server/api/atproto/author-profiles.get.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/server/api/atproto/author-profiles.get.ts b/server/api/atproto/author-profiles.get.ts index 6b1f656e2..f0193e1dc 100644 --- a/server/api/atproto/author-profiles.get.ts +++ b/server/api/atproto/author-profiles.get.ts @@ -49,11 +49,7 @@ export default defineCachedEventHandler( if (handles.length === 0) { return { - authors: authors.map(author => ({ - ...author, - avatar: null, - profileUrl: null, - })), + authors: authors.map(author => Object.assign(author, { avatar: null, profileUrl: null })), } } @@ -68,11 +64,14 @@ export default defineCachedEventHandler( } } - const resolvedAuthors: ResolvedAuthor[] = authors.map(author => ({ - ...author, - avatar: author.blueskyHandle ? avatarMap.get(author.blueskyHandle) || null : null, - profileUrl: author.blueskyHandle ? `https://bsky.app/profile/${author.blueskyHandle}` : null, - })) + const resolvedAuthors: ResolvedAuthor[] = authors.map(author => + Object.assign(author, { + avatar: author.blueskyHandle ? avatarMap.get(author.blueskyHandle) || null : null, + profileUrl: author.blueskyHandle + ? `https://bsky.app/profile/${author.blueskyHandle}` + : null, + }), + ) return { authors: resolvedAuthors } },