Dashboard › npmx.dev › Session ses_40339014
ses_403390141ffenQfAWkWJIjbzfZPull from latest origin/main merge it in, resolve merge conflicts
From https://github.com/npmx-dev/npmx.dev * branch main -> FETCH_HEAD 704987b..fee116c main -> origin/main
CONFLICT (modify/delete): app/pages/package/[...package].vue deleted in origin/main and modified in HEAD. Version HEAD of app/pages/package/[...package].vue left in tree. Auto-merging codecov.yml Auto-merging nuxt.config.ts CONFLICT (content): Merge conflict in nuxt.config.ts Auto-merging vercel.json Automatic merge failed; fix conflicts and then commit the result.
Two conflicts. Let me look at each one:
M .env.example A .github/ISSUE_TEMPLATE/bug-report.yml A .github/ISSUE_TEMPLATE/config.yml A .github/ISSUE_TEMPLATE/feature-request.yml A .github/PULL_REQUEST_TEMPLATE.md M .github/workflows/autofix.yml A .github/workflows/chromatic.yml M .github/workflows/ci.yml M .github/workflows/lunaria.yml A .github/workflows/mirror-tangled.yml M .github/workflows/provenance.yml A .github/workflows/release-pr.yml A .github/workflows/release-tag.yml M .github/workflows/semantic-pull-requests.yml A .github/workflows/welcome.yml M .gitignore M .lighthouserc.cjs A .node-version M .nuxtrc D .oxfmtignore M .oxlintrc.json A .storybook/.public/favicon.svg A .storybook/.public/npmx-storybook.svg A .storybook/main.ts A .storybook/manager.ts A .storybook/preview.ts M .vscode/settings.json M CONTRIBUTING.md M README.md M app/app.vue A app/assets/logos/oss-partners/algolia-light.svg A app/assets/logos/oss-partners/algolia.svg A app/assets/logos/oss-partners/atcute.png A ...
Big merge. Two conflicts: nuxt.config.ts (content conflict) and app/pages/package/[...package].vue (modify/delete - the file was deleted on main and replaced with new page structure). Let me handle them:
98- },
99-
100- routeRules: {
101- // API routes
102- '/api/**': { isr: 60 },
103:<<<<<<< HEAD
104- '/200.html': { prerender: true },
105- '/raw/**': { isr: 60 },
106- '/package/**': { isr: getISRConfig(60, true) },
107- '/:pkg/.well-known/skills/**': { isr: 3600 },
108- '/:scope/:pkg/.well-known/skills/**': { isr: 3600 },
--
112- '/api/social/**': { isr: false, cache: false },
113- // infinite cache (versioned - doesn't change)
114- '/package-code/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
115- '/package-docs/:pkg/v/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
116- '/package-docs/:scope/:pkg/v/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
117:=======
118- '/api/registry/badge/**': {
119- isr: {
120- expiration: 60 * 60 /* one hour */,
121- passQuery: true,
122- allowQuery: ['color', 'labelColor', 'label', 'name', 'style'],
--
134- expiration: 60 * 60 /* one hour ...
<path>/home/byk/Code/npmx.dev/nuxt.config.ts</path>
<type>file</type>
<content>95: options: {
96: scrollBehaviorType: 'smooth',
97: },
98: },
99:
100: routeRules: {
101: // API routes
102: '/api/**': { isr: 60 },
103: <<<<<<< HEAD
104: '/200.html': { prerender: true },
105: '/raw/**': { isr: 60 },
106: '/package/**': { isr: getISRConfig(60, true) },
107: '/:pkg/.well-known/skills/**': { isr: 3600 },
108: '/:scope/:pkg/.well-known/skills/**': { isr: 3600 },
109: // never cache
110: '/search': { isr: false, cache: false },
111: '/api/auth/**': { isr: false, cache: false },
112: '/api/social/**': { isr: false, cache: false },
113: // infinite cache (versioned - doesn't change)
114: '/package-code/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
115: '/package-docs/:pkg/v/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
116: '/package-docs/:scope/:pkg/v/**': { isr: true, cache: { maxAge: 365 ...
I need to combine both sides: keep my additions (/raw/**, /200.html) and take main's new additions (badge, image-proxy, downloads routes). Also, main already has /:pkg/.well-known/skills/** at line 145, and page routes restructured. Let me also check what's after the conflict:
<path>/home/byk/Code/npmx.dev/nuxt.config.ts</path>
<type>file</type>
<content>140: '/api/registry/docs/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
141: '/api/registry/file/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
142: '/api/registry/provenance/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
143: '/api/registry/files/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
144: '/api/registry/package-meta/**': { isr: 300 },
145: '/:pkg/.well-known/skills/**': { isr: 3600 },
146: '/:scope/:pkg/.well-known/skills/**': { isr: 3600 },
147: '/__og-image__/**': getISRConfig(60),
148: '/_avatar/**': { isr: 3600, proxy: 'https://www.gravatar.com/avatar/**' },
149: '/opensearch.xml': { isr: true },
150: '/oauth-client-metadata.json': { prerender: true },
151: '/.well-known/jwks.json': { prerender: true },
152: '/.well-known/site.standard.publication': { prerender: true },
153: // never ...
<path>/home/byk/Code/npmx.dev/nuxt.config.ts</path>
<type>file</type>
<content>179: // pages
180: '/package/**': getISRConfig(60, { fallback: 'html' }),
181: '/package/:name/_payload.json': getISRConfig(60, { fallback: 'json' }),
182: '/package/:name/v/:version/_payload.json': getISRConfig(60, { fallback: 'json' }),
183: '/package/:org/:name/_payload.json': getISRConfig(60, { fallback: 'json' }),
184: '/package/:org/:name/v/:version/_payload.json': getISRConfig(60, { fallback: 'json' }),
185: // infinite cache (versioned - doesn't change)
186: '/package-code/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
187: '/package-docs/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
188: // static pages
189: '/': { prerender: true },
190: '/200.html': { prerender: true },
191: '/about': { prerender: true },
192: '/accessibility': { prerender: true },
193: '/privacy': { prerender: true },
194: '/search': { isr: ...
Now I can see the full picture. Main already has /200.html, /raw/** doesn't exist yet (my addition), and the /package/** route on main is different from my branch. The conflict resolution is straightforward: take main's version and add my /raw/** rule. Let me resolve:
import process from 'node:process'
import { currentLocales } from './config/i18n'
import { isCI, isTest, provider } from 'std-env'
const isStorybook = process.env.STORYBOOK === 'true' || process.env.VITEST_STORYBOOK === 'true'
export default defineNuxtConfig({
modules: [
'@unocss/nuxt',
'@nuxtjs/html-validator',
'@nuxt/scripts',
'@nuxt/a11y',
'nuxt-og-image',
'@nuxt/test-utils',
'@vite-pwa/nuxt',
'@vueuse/nuxt',
'@nuxtjs/i18n',
...(isStorybook ? [] : ['@nuxt/fonts', '@nuxtjs/color-mode']),
],
$test: {
debug: {
hydration: true,
},
},
colorMode: {
preference: 'system',
fallback: 'dark',
dataValue: 'theme',
storageKey: 'npmx-color-mode',
},
css: ['~/assets/main.css'],
runtimeConfig: {
sessionPassword: '',
imageProxySecret: '',
github: {
orgToken: '',
},
oauthJwkOne: process.env.OAUTH_JWK_ONE || undefined,
// Upstash Redis for distributed OAuth token refresh locking in ...
Let me use sed to resolve the conflict by replacing the conflict block:
Conflict resolved in nuxt.config.ts
<path>/home/byk/Code/npmx.dev/nuxt.config.ts</path>
<type>file</type>
<content>100: routeRules: {
101: // API routes
102: '/api/**': { isr: 60 },
103: '/raw/**': { isr: 60 },
104: '/api/registry/badge/**': {
105: isr: {
106: expiration: 60 * 60 /* one hour */,
107: passQuery: true,
108: allowQuery: ['color', 'labelColor', 'label', 'name', 'style'],
109: },
110: },
111: '/api/registry/image-proxy': {
112: isr: {
113: expiration: 60 * 60 /* one hour */,
114: passQuery: true,
115: allowQuery: ['url', 'sig'],
116: },
117: },
118: '/api/registry/downloads/**': {
119: isr: {
120: expiration: 60 * 60 /* one hour */,
121: passQuery: true,
122: allowQuery: ['mode', 'filterOldVersions', 'filterThreshold'],
123: },
124: },
125: '/api/registry/docs/**': { isr: true, cache: { maxAge: 365 * 24 * 60 * 60 } },
126: '/api/registry/file/**': { isr: ...
Good. Now handle the deleted Vue page file. On main, app/pages/package/[...package].vue was deleted and replaced with app/pages/package/[[org]]/[name].vue and friends. My branch modified the old file to add a markdown link. I should accept the deletion (take main's change) and instead add the alternate markdown link to the new page file:
rm 'app/pages/package/[...package].vue'
Now let me check the new page file that replaced it to see if I need to add the markdown alternate link there:
<script setup lang="ts">
import type {
InstallSizeResult,
NpmVersionDist,
PackageVersionInfo,
PackumentVersion,
ProvenanceDetails,
ReadmeResponse,
ReadmeMarkdownResponse,
SkillsListResponse,
} from '#shared/types'
import type { JsrPackageInfo } from '#shared/types/jsr'
import type { IconClass } from '~/types'
import { assertValidPackageName } from '#shared/utils/npm'
import { joinURL } from 'ufo'
import { areUrlsEquivalent } from '#shared/utils/url'
import { isEditableElement } from '~/utils/input'
import { getDependencyCount } from '~/utils/npm/dependency-count'
import { detectPublishSecurityDowngradeForVersion } from '~/utils/publish-security'
import { useModal } from '~/composables/useModal'
import { useAtproto } from '~/composables/atproto/useAtproto'
import { togglePackageLike } from '~/utils/atproto/likes'
import { useInstallSizeDiff } from '~/composables/useInstallSizeDiff'
import type { RouteLocationRaw } from 'vue-router'
defineOgImageComponent('Package', {
...
671:useHead({
672: link: [{ rel: 'canonical', href: canonicalUrl }],
675:useSeoMeta({
<path>/home/byk/Code/npmx.dev/app/pages/package/[[org]]/[name].vue</path>
<type>file</type>
<content>668: const compactNumberFormatter = useCompactNumberFormatter()
669: const bytesFormatter = useBytesFormatter()
670:
671: useHead({
672: link: [{ rel: 'canonical', href: canonicalUrl }],
673: })
674:
675: useSeoMeta({
676: title: () => (pkg.value?.name ? `${pkg.value.name} - npmx` : 'Package - npmx'),
677: ogTitle: () => (pkg.value?.name ? `${pkg.value.name} - npmx` : 'Package - npmx'),
678: twitterTitle: () => (pkg.value?.name ? `${pkg.value.name} - npmx` : 'Package - npmx'),
679: description: () => pkg.value?.description ?? '',
680: ogDescription: () => pkg.value?.description ?? '',
681: twitterDescription: () => pkg.value?.description ?? '',
682: })
683:
684: const codeLink = computed((): RouteLocationRaw | null => {
685: if (pkg.value == null || resolvedVersion.value == null) {
686: return null
687: }
(Showing lines 668-687 of 1693. ...
Now I need to add the markdown alternate link. Let me also check what the raw route path looks like:
27: name: () => packageName.value,
28: version: () => requestedVersion.value ?? '',
107:const { packageName, requestedVersion, orgName } = usePackageRoute()
110: assertValidPackageName(packageName.value)
116: const base = `/api/registry/readme/${packageName.value}`
117: const version = requestedVersion.value
152: const base = `/api/registry/readme/markdown/${packageName.value}`
153: const version = requestedVersion.value
189:const { data: jsrInfo } = useLazyFetch<JsrPackageInfo>(() => `/api/jsr/${packageName.value}`, {
192: immediate: computed(() => packageName.value.startsWith('@')).value,
202: const base = `/api/registry/install-size/${packageName.value}`
203: const version = requestedVersion.value
215: const base = `/skills/${packageName.value}`
216: const version = requestedVersion.value
222:const { data: packageAnalysis } = usePackageAnalysis(packageName, requestedVersion)
223:const { data: moduleReplacement } = useModuleReplacement(packageName)
226: ...
Added markdown alternate link to new page file
Now let me also check if there's a versioned page that should get the same treatment:
<script setup lang="ts">
// stub page to help with paths
definePageMeta({
name: 'package-version',
})
</script>
<template>
<div />
</template>
It's just a stubβthe main page handles everything. Now let me also check the index.vue:
<script setup lang="ts">
// stub page to help with paths
definePageMeta({
name: 'package',
scrollMargin: 200,
})
</script>
<template>
<div />
</template>
Good, both are stubs. Now verify everything and complete the merge:
M .env.example A .github/ISSUE_TEMPLATE/bug-report.yml A .github/ISSUE_TEMPLATE/config.yml A .github/ISSUE_TEMPLATE/feature-request.yml A .github/PULL_REQUEST_TEMPLATE.md M .github/workflows/autofix.yml A .github/workflows/chromatic.yml M .github/workflows/ci.yml M .github/workflows/lunaria.yml A .github/workflows/mirror-tangled.yml
uno-preset-rtl.ts | 162 +- uno.config.ts | 38 +- vercel.json | 28 +- vitest.config.ts | 9 +- 680 files changed, 78968 insertions(+), 34070 deletions(-)
No remaining conflicts. Let me commit the merge:
[byk/feat/md eebaa28] Merge remote-tracking branch 'origin/main' into byk/feat/md
Merge committed. Let me verify the key files look correct:
<path>/home/byk/Code/npmx.dev/app/pages/package/[[org]]/[name].vue</path>
<type>file</type>
<content>671: useHead({
672: link: [
673: { rel: 'canonical', href: canonicalUrl },
674: { rel: 'alternate', type: 'text/markdown', href: `/raw/${packageName.value}.md` },
675: ],
676: })
677:
678: useSeoMeta({
679: title: () => (pkg.value?.name ? `${pkg.value.name} - npmx` : 'Package - npmx'),
680: ogTitle: () => (pkg.value?.name ? `${pkg.value.name} - npmx` : 'Package - npmx'),
(Showing lines 671-680 of 1696. Use offset=681 to continue.)
</content>
<path>/home/byk/Code/npmx.dev/nuxt.config.ts</path>
<type>file</type>
<content>100: routeRules: {
101: // API routes
102: '/api/**': { isr: 60 },
103: '/raw/**': { isr: 60 },
104: '/api/registry/badge/**': {
105: isr: {
106: expiration: 60 * 60 /* one hour */,
107: passQuery: true,
108: allowQuery: ['color', 'labelColor', 'label', 'name', 'style'],
109: },
(Showing lines 100-109 of 422. Use offset=110 to continue.)
</content>
Both conflicts resolved correctly:
nuxt.config.ts β Kept my /raw/** ISR rule, took all of main's new route rules (badge, image-proxy, downloads with passQuery configs), and the restructured package/code/docs routes.
app/pages/package/[...package].vue β Accepted the deletion (main replaced it with app/pages/package/[[org]]/[name].vue). Added the <link rel="alternate" type="text/markdown"> tag to the new page file instead.
Merge is complete with no remaining conflict markers.
Push your update
To https://github.com/BYK/npmx.dev.git ! [rejected] byk/feat/md -> byk/feat/md (fetch first) error: failed to push some refs to 'https://github.com/BYK/npmx.dev.git' hint: Updates were rejected because the remote contains work that you do not hint: have locally. This is usually caused by another repository pushing to hint: the same ref. If you want to integrate the remote changes, use hint: 'git pull' before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
From https://github.com/BYK/npmx.dev * branch byk/feat/md -> FETCH_HEAD e1f001c..c5cf366 byk/feat/md -> fork/byk/feat/md eebaa28 Merge remote-tracking branch 'origin/main' into byk/feat/md fee116c chore: bump vue-data-ui from 3.15.9 to 3.15.10 (#1957) 58da597 fix: separate class getters from methods in docs rendering (#1949) ee3ffaa fix: change paragraph to small tag for legal copy (#1950) 4f4b600 fix(i18n): update Japanese translations (#1951)
c5cf366 Update server/utils/markdown.ts
Remote has an extra commit. Let me pull it in and then push:
Rebasing (1/645) Rebasing (2/645) Rebasing (3/645) Rebasing (4/645) Rebasing (5/645) Rebasing (6/645) Rebasing (7/645) Rebasing (8/645) Rebasing (9/645) Rebasing (10/645) Rebasing (11/645) Rebasing (12/645) Rebasing (13/645) Rebasing (14/645) Rebasing (15/645) Rebasing (16/645) Rebasing (17/645) Rebasing (18/645) Rebasing (19/645) Rebasing (20/645) Rebasing (21/645) Rebasing (22/645) Rebasing (23/645) Rebasing (24/645) Rebasing (25/645) Rebasing (26/645) Rebasing (27/645) Rebasing (28/645) Rebasing (29/645) Rebasing (30/645) Rebasing (31/645) Rebasing (32/645) Rebasing (33/645) Rebasing (34/645) Rebasing (35/645) Rebasing (36/645) Rebasing (37/645) Rebasing (38/645) Rebasing (39/645) Rebasing (40/645) Rebasing (41/645) Rebasing (42/645) Rebasing (43/645) Rebasing (44/645) Rebasing (45/645) Rebasing (46/645) Rebasing (47/645) Rebasing (48/645) Rebasing (49/645) Rebasing (50/645) Rebasing (51/645) Rebasing (52/645) Rebasing (53/645) Rebasing (54/645) Rebasing (55/645) Rebasing ...
The rebase is too complex with 645 commits. Let me abort and use merge instead:
Merge made by the 'ort' strategy. server/utils/markdown.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
Clean merge. Let me push:
To https://github.com/BYK/npmx.dev.git c5cf366..07b045a byk/feat/md -> byk/feat/md
Pushed successfully.
Keep running
gh run view --log-failed --job $(gh pr checks $PR_NO --json state,link -q '.[] | select(.state == "FAILURE").link | split("/")[-1]')'
to get all failing jobs after you push. Make sure to wait for "Sentry Seer" and "Cursor BugBot" jobs to finish. Fix any failing jobs. Address all unresolved comments (both from bots and humans) from the PR by making a todo list. When you address each PR, either respond to them or mark them as resolved. Don't stop until the todo items are all done. Keep repeating this cycle until there are no more CI failures nor unresolved comments from humans or bots.
Use the following command to get the unresolved comments:
gh api graphql -F owner='<REPO_OWNER>' -F name='<REPO_NAME>' -F number=<PR_NUMBER> -f query='query($owner: String!, $name: String!, $number: Int!) { repository(owner: $owner, name: $name) { pullRequest(number: $number) { reviewThreads(first: 100) { nodes { isResolved comments(first: 100) { nodes { diffHunk body startLine line isMinimized } } } } } } }' -q '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | .comments.nodes[] | select(.isMinimized == false) | {diff_hunk: .diffHunk, line: .line, start_line: .startLine, body: .body}]'
You'll need to replace <REPO_OWNER>, <REPO_NAME>, and <PR_NUMBER> with relevant
values for the current repo and PR. (You can probably get them by running gh pr status or gh pr view)
Let me start by checking the CI status and waiting for jobs to finish:
Vercel β npmx.dev pending 0 https://vercel.com/npmx/npmx.dev/HJPGsGfoAEbLt9qiaJ5cQdhC6MSd Vercel is deploying your app βΏ Accessibility audit (dark) pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027864/job/66065971972 βΏ Accessibility audit (light) pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027864/job/66065971997 π i18n validation pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027864/job/66065971976 π Generate Lunaria Overview pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027089/job/66065970019 πͺ Type check pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027864/job/66065971961 π Lint project pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027864/job/66065972005 π€ Autofix code pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027856/job/66065971681 π§ͺ Component ...
Checks are still running. Let me wait for them to complete:
Refreshing checks status every 10 seconds. Press Ctrl+C to quit. Vercel β npmx.dev pending 0 https://vercel.com/npmx/npmx.dev/HJPGsGfoAEbLt9qiaJ5cQdhC6MSd Vercel is deploying your app βΏ Accessibility audit (dark) pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027864/job/66065971972 βΏ Accessibility audit (light) pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027864/job/66065971997 π Generate Lunaria Overview pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027089/job/66065970019 πͺ Type check pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027864/job/66065971961 π Chromatic pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027865/job/66065972071 π Lint project pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027864/job/66065972005 π₯οΈ Browser tests pending 0 https://github.com/npmx-dev/npmx.dev/actions/runs/22775027864/job/66065972000 π€ Autofix ...
All CI checks pass. Now let me check for any failures and wait for bot reviews:
Zero CI failures. Now let me wait a bit for Sentry Seer and Cursor BugBot to post their reviews, then check all unresolved comments:
[{"author":"okineadev","body":"We also need support for `package/*` paths\n\n```plaintext\n$ curl -L https://npmxdev-git-fork-byk-byk-featmd-poetry.vercel.app/package/vite\nA server error has occurred\n\nFUNCTION_INVOCATION_FAILED\n\narn1::r7r5t-1770279708442-3169599c59e0\n\n$ curl -L https://npmxdev-git-fork-byk-byk-featmd-poetry.vercel.app/vite\n# vite\n\n\u003e Native-ESM powered web dev build tool\n\n...\n```","diff_hunk":"@@ -1,6 +1,18 @@\n {\n \"$schema\": \"https://openapi.vercel.sh/vercel.json\",\n \"trailingSlash\": false,\n+ \"rewrites\": [\n+ {\n+ \"source\": \"/:path((?!api|_nuxt|_v|__nuxt|search|code|raw/).*)\",\n+ \"has\": [{ \"type\": \"header\", \"key\": \"accept\", \"value\": \"(.*?)text/markdown(.*)\" }],\n+ \"destination\": \"/raw/:path.md\"\n+ },\n+ {\n+ \"source\": \"/:path((?!api|_nuxt|_v|__nuxt|search|code|raw/).*)\",\n+ \"has\": [{ \"type\": \"header\", \"key\": \"user-agent\", \"value\": \"curl/.*\" }],\n+ ...
Good. I have 3 unresolved comment threads. Let me create a todo list and address them all:
[
{
"content": "Add /package/* path support in vercel.json rewrites + server middleware (okineadev + atinux)",
"status": "in_progress",
"priority": "high"
},
{
"content": "Fix maintainers comment - user-supplied field concern (ghostdevv)",
"status": "pending",
"priority": "medium"
}
]
/package/* path supportLet me understand the current routing. The middleware and vercel rewrites need to handle /package/vite (which should serve the same markdown as /vite):
<path>/home/byk/Code/npmx.dev/vercel.json</path>
<type>file</type>
<content>1: {
2: "$schema": "https://openapi.vercel.sh/vercel.json",
3: "trailingSlash": false,
4: "rewrites": [
5: {
6: "source": "/:path((?!api|_nuxt|_v|__nuxt|search|code|raw/).*)",
7: "has": [{ "type": "header", "key": "accept", "value": "(.*?)text/markdown(.*)" }],
8: "destination": "/raw/:path.md"
9: },
10: {
11: "source": "/:path((?!api|_nuxt|_v|__nuxt|search|code|raw/).*)",
12: "has": [{ "type": "header", "key": "user-agent", "value": "curl/.*" }],
13: "destination": "/raw/:path.md"
14: }
15: ],
16: "redirects": [
17: {
18: "source": "/(.*)",
19: "has": [
20: {
21: "type": "host",
22: "value": "build.npmx.dev"
23: }
24: ],
25: "destination": "https://discord.gg/x9KE5U2q8w"
26: },
27: {
28: "source": "/(.*)",
29: "has": [
30: {
31: "type": "host",
32: ...
server/routes/raw/[...slug].md.get.ts server/utils/markdown.ts server/api/registry/readme/markdown
<path>/home/byk/Code/npmx.dev/server/routes/raw/[...slug].md.get.ts</path>
<type>file</type>
<content>1: import { generatePackageMarkdown } from '../../utils/markdown'
2: import * as v from 'valibot'
3: import { PackageRouteParamsSchema } from '#shared/schemas/package'
4: import { NPM_MISSING_README_SENTINEL, ERROR_NPM_FETCH_FAILED } from '#shared/utils/constants'
5:
6: // Cache TTL matches the ISR config for /raw/** routes (60 seconds)
7: const CACHE_MAX_AGE = 60
8:
9: const NPM_API = 'https://api.npmjs.org'
10:
11: const standardReadmeFilenames = [
12: 'README.md',
13: 'readme.md',
14: 'Readme.md',
15: 'README',
16: 'readme',
17: 'README.markdown',
18: 'readme.markdown',
19: ]
20:
21: const standardReadmePattern = /^readme(\.md|\.markdown)?$/i
22:
23: function encodePackageName(name: string): string {
24: if (name.startsWith('@')) {
25: return `@${encodeURIComponent(name.slice(1))}`
26: }
27: return encodeURIComponent(name)
28: }
29:
30: async function ...
Now I understand the architecture. The route handler is at /raw/[...slug].md.get.ts. The vercel.json rewrites route /:path β /raw/:path.md. But /package/vite with the current rewrite goes to /raw/package/vite.md, and the handler parses package/vite as the package name β which is wrong.
I need to:
package/ prefixAdded /package/* rewrites to vercel.json
<path>/home/byk/Code/npmx.dev/vercel.json</path>
<type>file</type>
<content>1: {
2: "$schema": "https://openapi.vercel.sh/vercel.json",
3: "trailingSlash": false,
4: "rewrites": [
5: {
6: "source": "/package/:path(.*)",
7: "has": [
8: {
9: "type": "header",
10: "key": "accept",
11: "value": "(.*?)text/markdown(.*)"
12: }
13: ],
14: "destination": "/raw/:path.md"
15: },
16: {
17: "source": "/package/:path(.*)",
18: "has": [
19: {
20: "type": "header",
21: "key": "user-agent",
22: "value": "curl/.*"
23: }
24: ],
25: "destination": "/raw/:path.md"
26: },
27: {
28: "source": "/:path((?!api|_nuxt|_v|__nuxt|search|code|raw/).*)",
29: "has": [
30: {
(Showing lines 1-30 of 142. Use offset=31 to continue.)
</content>
Good, the /package/* rewrites are first. Now let me also check if the server middleware needs updating for dev mode:
server/middleware/canonical-redirects.global.ts
There's no markdown middleware anymore β it's handled purely via vercel.json rewrites + the route handler. The vercel.json approach is what's used. Good.
Now let me address Todo 2: ghostdevv's comment about maintainers being user-supplied:
<path>/home/byk/Code/npmx.dev/server/utils/markdown.ts</path>
<type>file</type>
<content>250: lines.push(version.keywords.slice(0, 20).join(', '))
251: lines.push('')
252: }
253:
254: // Maintainers
255: if (pkg.maintainers && pkg.maintainers.length > 0) {
256: lines.push('## Maintainers')
257: lines.push('')
258: for (const maintainer of pkg.maintainers.slice(0, 10)) {
259: // npm API returns username but `@npm/types` `Contact` doesn't include it
260: const username = (maintainer as { username?: string }).username
261: const name = maintainer.name || username || 'Unknown'
262: if (username) {
263: lines.push(`- [${name}](https://npmx.dev/~${username})`)
264: } else {
265: lines.push(`- ${name}`)
266: }
267: }
268: lines.push('')
269: }
270:
271: // README section
272: if (readme && readme.trim()) {
273: lines.push('---')
274: lines.push('')
275: lines.push('## README')
276: ...
The ghostdevv comment says maintainers is "user supplied field" β meaning the name field could contain arbitrary text that could break markdown formatting or be used for injection. The fix should escape the maintainer name and username to prevent markdown injection:
Fixed maintainer name/username escaping
Let me verify the change:
<path>/home/byk/Code/npmx.dev/server/utils/markdown.ts</path>
<type>file</type>
<content>254: // Maintainers
255: if (pkg.maintainers && pkg.maintainers.length > 0) {
256: lines.push('## Maintainers')
257: lines.push('')
258: for (const maintainer of pkg.maintainers.slice(0, 10)) {
259: // npm API returns username but `@npm/types` `Contact` doesn't include it
260: // maintainers is user-supplied so we escape both name and username
261: const username = (maintainer as { username?: string }).username
262: const safeName = escapeMarkdown(maintainer.name || username || 'Unknown')
263: if (username) {
264: lines.push(`- [${safeName}](https://npmx.dev/~${encodeURIComponent(username)})`)
265: } else {
266: lines.push(`- ${safeName}`)
267: }
268: }
269: lines.push('')
270: }
271:
(Showing lines 254-271 of 290. Use offset=272 to continue.)
</content>
Now let me commit and push these changes:
server/utils/markdown.ts | 7 ++++--- vercel.json | 38 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 5 deletions(-)
[byk/feat/md 72af330] fix: add /package/* path support for markdown rewrites + escape user-supplied maintainer fields 2 files changed, 40 insertions(+), 5 deletions(-)
To https://github.com/BYK/npmx.dev.git 07b045a..72af330 byk/feat/md -> byk/feat/md
Now let me reply to the PR comments:
[{"author":"okineadev","body":"We also need support for `package/*` paths\n\n```plaintext\n$ curl -L https://npmxd","id":"PRRT_kwDOQ_ILBs5s0Xek"},{"author":"ghostdevv","body":"this may not be correct as maintainers is a user supplied field","id":"PRRT_kwDOQ_ILBs5tNTpc"}]
{"data":{"addPullRequestReviewThreadReply":{"comment":{"id":"PRRC_kwDOQ_ILBs6srk6I"}}}}
{"data":{"addPullRequestReviewThreadReply":{"comment":{"id":"PRRC_kwDOQ_ILBs6srlgU"}}}}
Now let me resolve both threads:
{"data":{"resolveReviewThread":{"thread":{"isResolved":true}}}}
{"data":{"resolveReviewThread":{"thread":{"isResolved":true}}}}
Now let me wait for CI and check for any new comments from bots:
Storybook Publish pass 0 https://698b88d5157d89f1f33a6c21-bibecfdsox.chromatic.com/ 25 stories published. UI Tests pass 0 https://www.chromatic.com/build?appId=698b88d5157d89f1f33a6c21&number=983 25 tests unchanged. Vercel Preview Comments pass 0 https://vercel.com/github Vercel β docs.npmx.dev pass 0 https://vercel.com/npmx/docs.npmx.dev/J5wBYcxXKWRxTFfQTdrztrYE8bXf Canceled by Ignored Build Step Vercel β npmx-lunaria pass 0 https://vercel.com/npmx/npmx-lunaria/hPEdw4yT9GUDGWEveQVeeh526ikx Canceled by Ignored Build Step Vercel β npmx.dev pass 0 https://vercel.com/npmx/npmx.dev/HAEXL8xPospZ8rKzzNkCN2ft1zad Deployment has completed codecov/patch pass 1s https://app.codecov.io/gh/npmx-dev/npmx.dev/pull/151 βΏ Accessibility audit (light) pass 2m27s https://github.com/npmx-dev/npmx.dev/actions/runs/22775541209/job/66067724753 π i18n validation pass 30s https://github.com/npmx-dev/npmx.dev/actions/runs/22775541209/job/66067724756 π Generate Lunaria ...
Almost all passing, just 2 still running. Let me wait for them:
Refreshing checks status every 10 seconds. Press Ctrl+C to quit. Storybook Publish pass 0 https://698b88d5157d89f1f33a6c21-bibecfdsox.chromatic.com/ 25 stories published. UI Tests pass 0 https://www.chromatic.com/build?appId=698b88d5157d89f1f33a6c21&number=983 25 tests unchanged. Vercel Preview Comments pass 0 https://vercel.com/github Vercel β docs.npmx.dev pass 0 https://vercel.com/npmx/docs.npmx.dev/J5wBYcxXKWRxTFfQTdrztrYE8bXf Canceled by Ignored Build Step Vercel β npmx-lunaria pass 0 https://vercel.com/npmx/npmx-lunaria/hPEdw4yT9GUDGWEveQVeeh526ikx Canceled by Ignored Build Step Vercel β npmx.dev pass 0 https://vercel.com/npmx/npmx.dev/HAEXL8xPospZ8rKzzNkCN2ft1zad Deployment has completed codecov/patch pass 1s https://app.codecov.io/gh/npmx-dev/npmx.dev/pull/151 βΏ Accessibility audit (dark) pass 2m31s https://github.com/npmx-dev/npmx.dev/actions/runs/22775541209/job/66067724776 βΏ Accessibility audit ...
What about this comment: https://github.com/npmx-dev/npmx.dev/pull/151/#issuecomment-3889342771
{"author":"okineadev","body":"I think we should also take a look at https://github.com/npmx-dev/npmx.dev/pull/1382\r\nIt would be nice to somehow integrate the agent instruction fetching functionality as well"}
{"body":"## Summary\r\n\r\nAdds `llms.txt` and `llms_full.txt` support across all package URL patterns.\r\n\r\n### Supported `llms.txt` routes\r\n\r\n| Route | Example | Preview |\r\n|-------|---------|----------|\r\n| Root discovery | `/llms.txt` | https://npmx-9wsqop19f-npmx.vercel.app/llms.txt |\r\n| Unscoped package | `/package/nuxt/llms.txt` | https://npmx-9wsqop19f-npmx.vercel.app/package/nuxt/llms.txt |\r\n| Unscoped versioned | `/package/nuxt/v/3.15.0/llms.txt` | https://npmx-9wsqop19f-npmx.vercel.app/package/nuxt/v","state":"OPEN","title":"feat(ui): add llms.txt generation for npm packages","url":"https://github.com/npmx-dev/npmx.dev/pull/1382"}
## Summary Adds `llms.txt` and `llms_full.txt` support across all package URL patterns. ### Supported `llms.txt` routes | Route | Example | Preview | |-------|---------|----------| | Root discovery | `/llms.txt` | https://npmx-9wsqop19f-npmx.vercel.app/llms.txt | | Unscoped package | `/package/nuxt/llms.txt` | https://npmx-9wsqop19f-npmx.vercel.app/package/nuxt/llms.txt | | Unscoped versioned | `/package/nuxt/v/3.15.0/llms.txt` | https://npmx-9wsqop19f-npmx.vercel.app/package/nuxt/v/3.15.0/llms.txt | | Scoped package | `/package/@deepgram/sdk/llms.txt` | https://npmx-9wsqop19f-npmx.vercel.app/package/@deepgram/sdk/llms.txt | | Scoped versioned | `/package/@deepgram/sdk/v/4.0.0/llms.txt` | https://npmx-9wsqop19f-npmx.vercel.app/package/@deepgram/sdk/v/4.0.0/llms.txt | | Org listing | `/package/@deepgram/llms.txt` | https://npmx-9wsqop19f-npmx.vercel.app/package/@deepgram/llms.txt | | Full (with agent files) | `/package/nuxt/llms_full.txt` | ...
Now I understand. PR #1382 adds llms.txt support with agent instruction file fetching (llms_full.txt). The comment is suggesting we look at integrating that functionality β specifically the agent instruction fetching (CLAUDE.md, .cursorrules, etc.) β into this PR's markdown output.
Let me look at what files PR #1382 changes to understand the scope:
app/pages/index.vue app/pages/org/[org].vue app/pages/package/[[org]]/[name].vue scripts/smoke-test-llm-docs.sh server/middleware/canonical-redirects.global.ts server/middleware/llm-docs.ts server/utils/llm-docs.ts shared/types/index.ts shared/types/llm-docs.ts test/unit/server/utils/llm-docs.spec.ts
That's a substantial PR with its own middleware, utilities, and types. This is not something to integrate here β it's a separate feature that should be coordinated with separately.
{"url":"https://api.github.com/repos/npmx-dev/npmx.dev/issues/comments/4013192743","html_url":"https://github.com/npmx-dev/npmx.dev/pull/151#issuecomment-4013192743","issue_url":"https://api.github.com/repos/npmx-dev/npmx.dev/issues/151","id":4013192743,"node_id":"IC_kwDOQ_ILBs7vNHYn","user":{"login":"BYK","id":126780,"node_id":"MDQ6VXNlcjEyNjc4MA==","avatar_url":"https://avatars.githubusercontent.com/u/126780?u=fb459d67141d8b2df1822a92a43508019977facc&v=4","gravatar_id":"","url":"https://api.github.com/users/BYK","html_url":"https://github.com/BYK","followers_url":"https://api.github.com/users/BYK/followers","following_url":"https://api.github.com/users/BYK/following{/other_user}","gists_url":"https://api.github.com/users/BYK/gists{/gist_id}","starred_url":"https://api.github.com/users/BYK/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BYK/subscriptions","organizations_url":"https://api.github.com/users/BYK/orgs","repos_url":"https://api.github.com/users/BYK...
Done. Now let me do a final check for any remaining unresolved comments or new bot reviews:
[]
[
{
"content": "Add /package/* path support in vercel.json rewrites + server middleware (okineadev + atinux)",
"status": "completed",
"priority": "high"
},
{
"content": "Fix maintainers comment - user-supplied field concern (ghostdevv)",
"status": "completed",
"priority": "medium"
}
]
All done:
/package/:path rewrite rules in vercel.json β resolved and repliedname with escapeMarkdown() and username with encodeURIComponent() β resolved and repliedllms.txt) coordination β acknowledged but keeping as separate PRs to avoid scope creepLet's address the review comments on the PR
[]
No unresolved review threads. Let me also check for regular issue comments that might need attention:
{"author":"mbtools","body":"Thanks for this feature! π \r\n\r\nHere's some feedback to make this more useful (for a follow-up PR):\r\n\r\n### Stats\r\n\r\n- Historical download data isn't useful for AI (and hard to digest for humans). I sug","created":"2026-03-26T10:38:15Z"}
Thanks for this feature! π Here's some feedback to make this more useful (for a follow-up PR): ### Stats - Historical download data isn't useful for AI (and hard to digest for humans). I suggest removing those numbers <img width="606" height="207" alt="image" src="https://github.com/user-attachments/assets/4c67ec89-a11a-496c-9871-25ec41129f92" /> - Add installed size - Add number of vulnerabilities ### Install - Shows `npm` command. This might contradict the package manager preferred by consumers or what's defined in `package.json` or what's in the readme. I suggest removing the section. ### Links - Has link to `npm` but where's the link to `npmx.dev` ;-) ### Enhancements - Add compatibility (engines) section - Add available dist-tags
This is feedback from mbtools suggesting improvements for a follow-up PR. It's not a blocking review comment β it's constructive feedback. Let me reply acknowledging it:
{"url":"https://api.github.com/repos/npmx-dev/npmx.dev/issues/comments/4133774868","html_url":"https://github.com/npmx-dev/npmx.dev/pull/151#issuecomment-4133774868","issue_url":"https://api.github.com/repos/npmx-dev/npmx.dev/issues/151","id":4133774868,"node_id":"IC_kwDOQ_ILBs72ZGYU","user":{"login":"BYK","id":126780,"node_id":"MDQ6VXNlcjEyNjc4MA==","avatar_url":"https://avatars.githubusercontent.com/u/126780?u=fb459d67141d8b2df1822a92a43508019977facc&v=4","gravatar_id":"","url":"https://api.github.com/users/BYK","html_url":"https://github.com/BYK","followers_url":"https://api.github.com/users/BYK/followers","following_url":"https://api.github.com/users/BYK/following{/other_user}","gists_url":"https://api.github.com/users/BYK/gists{/gist_id}","starred_url":"https://api.github.com/users/BYK/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BYK/subscriptions","organizations_url":"https://api.github.com/users/BYK/orgs","repos_url":"https://api.github.com/users/BYK...
Let me do one final sweep to make sure everything is clean:
=== Unresolved Review Threads === 0 === CI Status === All passing
Everything is clean:
There are no remaining review comments that need code changes or resolution. The PR is ready for merge.
These actually look like trivial improvements, why not just implement them?
The user wants me to implement the suggestions from mbtools' comment. Let me re-read them:
npm install section (contradicts user's preferred package manager)Let me look at what's feasible:
Remove sparkline/trend numbers from stats - easy
Add vulnerabilities count - need to check if we have that data available
Remove install section - easy
Add npmx.dev link - easy
Add engines section - need to check if available in PackumentVersion
Add dist-tags - available in Packument
Now I need to verify what data is actually accessible and start implementing these changes. You're right. Let me just do them.
[
{
"content": "Remove sparkline/trend numbers from stats (noise for AI/humans)",
"status": "pending",
"priority": "high"
},
{
"content": "Remove Install section (contradicts user's preferred PM)",
"status": "pending",
"priority": "high"
},
{
"content": "Add npmx.dev link to Links section",
"status": "pending",
"priority": "high"
},
{
"content": "Add engines/compatibility section",
"status": "pending",
"priority": "high"
},
{
"content": "Add dist-tags section",
"status": "pending",
"priority": "high"
},
{
"content": "Update tests, commit, push, verify CI",
"status": "pending",
"priority": "high"
}
]
Let me check what data is available in the types:
shared/types/npm-registry.ts:64: 'dist-tags': { latest?: string } & Record<string, string>
shared/types/npm-registry.ts:397: // `dist-tags` can be missing in some later unpublished packages
shared/types/npm-registry.ts:398: 'dist-tags'?: Record<string, string>
shared/types/comparison.ts:12: | 'engines'
shared/types/comparison.ts:60: engines: {
<path>/home/byk/Code/npmx.dev/shared/types/comparison.ts</path>
<type>file</type>
<content>55: },
56: deprecated: {
57: category: 'health',
58: },
59: // Compatibility
60: engines: {
61: category: 'compatibility',
62: },
63: types: {
64: category: 'compatibility',
65: },
66: moduleFormat: {
67: category: 'compatibility',
68: },
69: // Security
(Showing lines 55-69 of 122. Use offset=70 to continue.)
</content>
11: PackumentVersion as PackumentVersionWithoutAttestations,
21:export interface PackumentVersion extends PackumentVersionWithoutAttestations {
23: dist: PackumentVersionWithoutAttestations['dist'] & { attestations?: NpmVersionAttestations }
30: versions: Record<string, PackumentVersion>
40:/** PackumentVersion with additional install scripts info */
node_modules/@npm/types/types/index.d.ts:119: engines?: Record<string, string>
node_modules/@npm/types/types/index.d.ts:206: | 'engines'
node_modules/@npm/types/package.json:25: "engines": {
engines is available on PackumentVersion. Now let me read the current markdown.ts and implement all changes:
<path>/home/byk/Code/npmx.dev/server/utils/markdown.ts</path>
<type>file</type>
<content>1: import type { Packument, PackumentVersion } from '#shared/types'
2: import { normalizeGitUrl } from '#shared/utils/git-providers'
3: import { joinURL } from 'ufo'
4:
5: const SPARKLINE_CHARS = [' ', 'β', 'β', 'β', 'β', 'β
', 'β', 'β', 'β'] as const
6: const MAX_README_SIZE = 500 * 1024 // 500KB, matching MAX_FILE_SIZE in file API
7:
8: export function generateSparkline(data: number[]): string {
9: if (!data.length) return ''
10:
11: const max = Math.max(...data)
12: const min = Math.min(...data)
13: const range = max - min
14:
15: // If all values are the same, use middle bar
16: if (range === 0) {
17: return SPARKLINE_CHARS[4].repeat(data.length)
18: }
19:
20: return data
21: .map(val => {
22: const normalized = (val - min) / range
23: // Clamp index to valid bounds to prevent floating-point edge cases
24: const rawIndex = Math.round(normalized * ...
Now let me implement all the changes:
Applied all mbtools feedback changes
Now remove the now-unused functions (buildWeeklyTotals, formatCompactNumber, generateSparkline, SPARKLINE_CHARS). But generateSparkline is exported β let me check if anything else uses it:
./app/composables/usePackageComparison.ts:345: formatCompactNumber: (num: number) => string,
./app/composables/usePackageComparison.ts:359: display: formatCompactNumber(data.downloads),
./app/composables/usePackageComparison.ts:367: display: formatCompactNumber(data.totalLikes),
./server/utils/markdown.ts:5:const SPARKLINE_CHARS = [' ', 'β', 'β', 'β', 'β', 'β
', 'β', 'β', 'β'] as const
./server/utils/markdown.ts:8:export function generateSparkline(data: number[]): string {
./server/utils/markdown.ts:17: return SPARKLINE_CHARS[4].repeat(data.length)
./server/utils/markdown.ts:24: const rawIndex = Math.round(normalized * (SPARKLINE_CHARS.length - 1))
./server/utils/markdown.ts:25: const index = Math.min(SPARKLINE_CHARS.length - 1, Math.max(0, rawIndex))
./server/utils/markdown.ts:26: return SPARKLINE_CHARS[index]
./server/utils/markdown.ts:35:function formatCompactNumber(num: number): string {
./server/utils/markdown.ts:85:function ...
generateSparkline is only used from tests. The formatCompactNumber in usePackageComparison.ts is a different local function. Let me remove the unused code:
Removed unused functions
Now update the route handler to stop passing dailyDownloads:
<path>/home/byk/Code/npmx.dev/server/routes/raw/[...slug].md.get.ts</path>
<type>file</type>
<content>200: packageName,
201: standardReadmeFilenames,
202: targetVersion,
203: )
204: if (jsdelivrReadme) {
205: readmeContent = jsdelivrReadme
206: }
207: }
208:
209: const [weeklyDownloadsData, dailyDownloads] = await Promise.all([
210: fetchWeeklyDownloads(packageName),
211: fetchDownloadRange(packageName, 12),
212: ])
213:
214: const markdown = generatePackageMarkdown({
215: pkg: packageData,
216: version: versionData,
217: readme: readmeContent && readmeContent !== NPM_MISSING_README_SENTINEL ? readmeContent : null,
218: weeklyDownloads: weeklyDownloadsData?.downloads,
219: dailyDownloads: dailyDownloads ?? ...
Updated route handler
Now update the tests:
<stdin>:70: SyntaxWarning: invalid escape sequence '\[' Updated tests
I'd also rebase the PR onto latest main branch from upstream
From https://github.com/npmx-dev/npmx.dev * branch main -> FETCH_HEAD fee116c5..31860913 main -> origin/main 31860913 fix(a11y): improve facet selector accessibility (#1536) 5aff68f9 fix: correctly detect type info in badge (#2173) 1dd1be99 feat: add brand page (#2197) 54ca7bd0 fix: do not strip package name from description (#2274) 5b0bfc7f perf(ui): parallelize chunked chart download fetches (#2269)
Let me first make sure the current code compiles and tests pass before rebasing:
[2m Test Files [22m [1m[31m1 failed[39m[22m[2m | [22m[1m[32m59 passed[39m[22m[90m (60)[39m [2m Tests [22m [1m[31m1 failed[39m[22m[2m | [22m[1m[32m1287 passed[39m[22m[90m (1288)[39m βELIFECYCLEβ Command failed with exit code 1.
[41m[1m FAIL [22m[49m [30m[42m unit [49m[39m test/unit/a11y-component-coverage.spec.ts[2m > [22ma11y component test coverage[2m > [22mshould have accessibility tests for all components (or be explicitly skipped)
Only the unrelated a11y coverage test (pre-existing). Markdown tests all pass. Let me commit this work first, then rebase:
server/routes/raw/[...slug].md.get.ts | 32 +--------- server/utils/markdown.ts | 107 ++++++++------------------------ test/unit/server/utils/markdown.spec.ts | 85 +++++++++---------------- 3 files changed, 57 insertions(+), 167 deletions(-)
[byk/feat/md 14671de7] refactor: simplify markdown output based on feedback 3 files changed, 57 insertions(+), 167 deletions(-)
Now let me identify just the PR's own commits (not the merge commits from main) to know what to rebase:
4f2c6da9 fix: make tests deterministic for normalizeGitUrl edge cases b8d988b5 fix: handle license as string or object type 8216116b Merge remote-tracking branch 'fork/byk/feat/md' into byk/feat/md 38cc0f84 fix: address 43081j review comments 72af3302 fix: add /package/* path support for markdown rewrites + escape user-supplied maintainer fields 07b045a4 Merge remote-tracking branch 'fork/byk/feat/md' into byk/feat/md eebaa286 Merge remote-tracking branch 'origin/main' into byk/feat/md e1f001cc fix: remove repoInfo from route handler after removing from interface 6dc769b9 fix: remove unused repoInfo param and exclude pages from codecov fc6d7ea9 fix: address CodeRabbit review comments 7e2890e7 fix: address PR review comments for markdown output d853437a chore: merge main and resolve conflicts 2ef55226 chore: merge main and resolve vercel.json conflict 91a16eea refactor: use /raw/ server route instead of middleware for markdown c9750201 fix: resolve TypeScript errors in markdown ...
The history has lots of merge commits. Let me squash all the PR work into clean commits and rebase onto origin/main. First let me find where the PR started:
5db5dc683410c973b6ac98911c0eef5aaa119ca8
test/unit/shared/utils/html.spec.ts | 2 +- test/unit/shared/utils/package-analysis.spec.ts | 18 +- test/unit/shared/utils/parse-package-param.spec.ts | 2 +- test/unit/shared/utils/severity.spec.ts | 2 +- test/unit/uno-preset-rtl.spec.ts | 2 +- uno.config.ts | 1 - vercel.json | 24 + vite.config.ts | 220 - vitest.config.ts | 79 + 344 files changed, 9563 insertions(+), 17598 deletions(-)
That's a ton of noise from the merge commits carrying main changes. The cleanest approach is to soft-reset to a squashed state and rebase:
diff --git a/app/pages/package/[[org]]/[name].vue b/app/pages/package/[[org]]/[name].vue
index 4bc4d85b..334e3706 100644
--- a/app/pages/package/[[org]]/[name].vue
+++ b/app/pages/package/[[org]]/[name].vue
@@ -1,6 +1,12 @@
<script setup lang="ts">
+import type { JsrPackageInfo } from '#shared/types/jsr'
+import type { IconClass } from '~/types'
import { assertValidPackageName } from '#shared/utils/npm'
+import { areUrlsEquivalent } from '#shared/utils/url'
import { getDependencyCount } from '~/utils/npm/dependency-count'
+import { detectPublishSecurityDowngradeForVersion } from '~/utils/publish-security'
+import { useInstallSizeDiff } from '~/composables/useInstallSizeDiff'
+import { useViewOnGitProvider } from '~/composables/useViewOnGitProvider'
defineOgImageComponent('Package', {
name: () => packageName.value,
@@ -143,17 +149,7 @@ const {
immediate: false,
},
)
-
-// Trigger fetch only when we have the real resolved version
-watch(
- [resolvedVersion, ...
That diff has a lot of noise from unrelated main changes leaking through merge commits. Let me do a clean interactive rebase to squash everything into 1 clean commit on top of origin/main:
No local changes to save Switched to a new branch 'byk/feat/md-clean' branch 'byk/feat/md-clean' set up to track 'origin/main'.
Need to also add the Vue page change. Let me check which page file has my alternate link on the old branch:
505: { rel: 'alternate', type: 'text/markdown', href: `/raw/${packageName.value}.md` },
467:useHead({
<path>/home/byk/Code/npmx.dev/app/pages/package/[[org]]/[name].vue</path>
<type>file</type>
<content>467: useHead({
468: link: [{ rel: 'canonical', href: canonicalUrl }],
469: })
470:
471: useSeoMeta({
(Showing lines 467-471 of 1169. Use offset=472 to continue.)
</content>
Added alternate markdown link
Now let me also handle the test file β it's referencing generateSparkline which no longer exists:
import { generatePackageMarkdown, generateSparkline } from '../../../../server/utils/markdown'
Fixed stale import
Now verify nuxt.config.ts doesn't have conflict artifacts:
103: '/raw/**': { isr: 60 },
The checkout from the old branch removed /brand and /translation-status lines that exist on origin/main. Let me re-checkout just the nuxt.config from the current clean main and apply only our change:
Added /raw/** ISR rule
Similarly for codecov.yml β let me check if the old branch version diverged too much:
15a16,20 > # Ignore files that are covered by browser tests (Playwright) rather than unit tests > ignore: > - 'app/pages/**/*' > - 'app/layouts/**/*' >
Good β that's just our addition. Now let's run the tests:
. postinstall: vite-plus β linked . postinstall: Done Done in 49.5s using pnpm v10.32.1
[2m Test Files [22m [1m[32m62 passed[39m[22m[90m (62)[39m [2m Tests [22m [1m[32m1354 passed[39m[22m[90m (1354)[39m
All tests pass. Type check:
All clean. Let me verify the diff is exactly what we want:
app/pages/package/[[org]]/[name].vue | 5 +- codecov.yml | 5 + nuxt.config.ts | 1 + server/routes/raw/[...slug].md.get.ts | 196 +++++++++++++ server/utils/markdown.ts | 228 +++++++++++++++ test/unit/server/utils/markdown.spec.ts | 492 ++++++++++++++++++++++++++++++++ vercel.json | 24 ++ 7 files changed, 950 insertions(+), 1 deletion(-)
Clean β only 7 files, all additions except the 1-line Vue change and 1-line nuxt.config change. Now commit and force-push:
[byk/feat/md-clean a618e474] feat: add markdown output support for package pages 7 files changed, 950 insertions(+), 1 deletion(-) create mode 100644 server/routes/raw/[...slug].md.get.ts create mode 100644 server/utils/markdown.ts create mode 100644 test/unit/server/utils/markdown.spec.ts
Deleted branch byk/feat/md (was 14671de7). To https://github.com/BYK/npmx.dev.git + 4f2c6da9...a618e474 byk/feat/md -> byk/feat/md (forced update)
Now wait for CI:
π Generate Lunaria Overview pass 41s https://github.com/npmx-dev/npmx.dev/actions/runs/23592119488/job/68699670228 π·οΈ Validate PR title pass 5s https://github.com/npmx-dev/npmx.dev/actions/runs/23592119487/job/68699670168 πͺ Type check pass 1m10s https://github.com/npmx-dev/npmx.dev/actions/runs/23592120641/job/68699673717 π Chromatic pass 1m10s https://github.com/npmx-dev/npmx.dev/actions/runs/23592120654/job/68699673881 π Lint project pass 32s https://github.com/npmx-dev/npmx.dev/actions/runs/23592120641/job/68699673695 π₯οΈ Browser tests pass 3m29s https://github.com/npmx-dev/npmx.dev/actions/runs/23592120641/job/68699673744 π€ Autofix code pass 1m0s https://github.com/npmx-dev/npmx.dev/actions/runs/23592120642/job/68699673887 π§ͺ Component tests pass 2m9s https://github.com/npmx-dev/npmx.dev/actions/runs/23592120641/job/68699673727 π§ͺ Unit tests pass 54s https://github.com/npmx-dev/npmx.dev/actions/runs/23592120641/job/68699673697 π§Ή Unused code ...
All green. Let me do the final verification:
=== CI Failures === None === Unresolved Comments === 0 === Commit log === a618e474 feat: add markdown output support for package pages
Done. The PR is now:
origin/mainRebase and address new review comments
From https://github.com/npmx-dev/npmx.dev * branch main -> FETCH_HEAD ed72177c..54711c9f main -> origin/main 54711c9f fix: improve packages UI (#2530) c8fcd117 feat: module replacements v3 (#2068) 4565f23a fix(i18n): add missing Russian translations (#2607) de26d133 fix(ui): bail on fetchMore recursion when no new items (#2606) 189a5684 chore(deps): update dependency @nuxt/scripts to v1 (#2591)
[{"author":"mbtools","body":"I'm not familiar with the `skills` feature here but it looks like that includes markdown. Can you please check that the rewrites don't collide with this?\n\nhttps://github.com/BYK/npmx.dev/blob/741d4aa0575c1fd6c0dde6bd4c7183984dc4ede6/server/routes/skills/%5B...pkg%5D.get.ts#L23","diff":"@@ -1,6 +1,38 @@\n {\n \"$schema\": \"https://openapi.vercel.sh/vercel.json\",\n \"trailingSlash\": false,\n+ \"rewrites\": [\n+ {\n+ \"source\": \"/package/:path(.*)\\\\.md\",\n+ \"destination\": \"/raw/:p","id":"PRRT_kwDOQ_ILBs55y9vs","line":11}]
One new review comment. Let me investigate:
<path>/home/byk/Code/npmx.dev/server/routes/skills/[...pkg].get.ts</path>
<type>file</type>
<content>
1: import type { H3Event } from 'h3'
2: import * as v from 'valibot'
3: import { PackageRouteParamsSchema } from '#shared/schemas/package'
4: import { SkillNameSchema } from '#shared/schemas/skills'
5:
6: import {
7: CACHE_MAX_AGE_ONE_HOUR,
8: CACHE_MAX_AGE_ONE_YEAR,
9: ERROR_SKILLS_FETCH_FAILED,
10: ERROR_SKILL_NOT_FOUND,
11: ERROR_SKILL_FILE_NOT_FOUND,
12: } from '#shared/utils/constants'
13: import { parsePackageParam } from '#shared/utils/parse-package-param'
14:
15: const CACHE_VERSION = 1
16:
17: /**
18: * Skills discovery and content endpoint.
19: *
20: * URL patterns:
21: * - /skills/vue/v/3.4.0 β discovery (list skills)
22: * - /skills/vue/v/3.4.0/my-skill β skill content (SKILL.md parsed)
23: * - /skills/vue/v/3.4.0/my-skill/refs/guide.md β supporting file (raw)
24: * - /skills/@scope/pkg/v/1.0.0 β ...
Found the issue. The /skills/vue/v/3.4.0/my-skill/refs/guide.md endpoint serves markdown files. With our current rewrites, /skills/vue/v/3.4.0/my-skill/refs/guide.md would be rewritten to /raw/skills/vue/v/3.4.0/my-skill/refs/guide.md β breaking the skills feature.
Let me also check the current exclusion list:
<path>/home/byk/Code/npmx.dev/vercel.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://openapi.vercel.sh/vercel.json",
3: "trailingSlash": false,
4: "rewrites": [
5: {
6: "source": "/package/:path(.*)\\.md",
7: "destination": "/raw/:path.md"
8: },
9: {
10: "source": "/:path((?!api|_nuxt|_v|__nuxt|search|code|raw/).*)\\.md",
11: "destination": "/raw/:path.md"
12: },
13: {
14: "source": "/package/:path(.*)",
15: "has": [
16: {
17: "type": "header",
18: "key": "accept",
19: "value": "(.*?)text/markdown(.*)"
20: }
21: ],
22: "destination": "/raw/:path.md"
23: },
24: {
25: "source": "/:path((?!api|_nuxt|_v|__nuxt|search|code|raw/).*)",
26: "has": [
27: {
28: "type": "header",
29: "key": "accept",
30: "value": "(.*?)text/markdown(.*)"
31: }
32: ],
33: "destination": ...
Current exclusion: (?!api|_nuxt|_v|__nuxt|search|code|raw/). I need to add skills/ to the exclusion list. Let me also audit for other routes that might have .md URLs:
server/routes/.well-known/jwks.json.get.ts server/routes/.well-known/site.standard.publication.get.ts server/routes/[pkg]/.well-known/skills/[...skills].ts server/routes/[pkg]/[scope]/.well-known/skills/[...skills].ts server/routes/skills/[...pkg].get.ts server/routes/oauth-client-metadata.json.get.ts server/routes/opensearch.xml.get.ts server/routes/raw/[...slug].md.get.ts --- app/pages app/pages/blog app/pages/diff app/pages/diff/[[org]] app/pages/org app/pages/package-code app/pages/package-code/[[org]] app/pages/package-docs app/pages/package app/pages/package/[[org]] app/pages/profile app/pages/profile/[identity] app/pages/~[username]
about.stories.ts about.vue accessibility.stories.ts accessibility.vue blog brand.vue compare.vue diff index.vue org package package-code package-docs pds.vue privacy.stories.ts privacy.vue profile recharging.vue search.vue settings.vue translation-status.vue ~[username]
Important routes I need to preserve:
skills/ β serves markdown files for skills (mbtools's concern).well-known/ β standard routesblog/ β might have .md contentpackage-code/, package-docs/ β code/docs routesdiff/ β diff viewsorg/, profile/, ~username β user pagesLet me check blog, package-docs, package-code for any .md routes:
app/pages/blog/alpha-release.md app/pages/blog/index.vue app/pages/package-code/[[org]]/[packageName]/v/[version]/[...filePath].vue app/pages/package-docs/[...path].vue app/pages/diff/[[org]]/[packageName]/v/[versionRange].vue
Critical: package-code and package-docs have [...filePath] routes that can have .md in the URL! e.g., /package-code/vue/v/3.4.0/README.md. These would ALL get rewritten to /raw/... breaking them.
Let me take a safer approach β instead of making the top-level .md rewrite broad with exclusions, I'll constrain it more strictly. The intent is that .md suffix on package URLs should work. The primary package URL format is /package/... (explicit) or /<pkg-name> (bare shorthand at top level). Let me look at all the routes that could conflict:
about.stories.ts about.vue accessibility.stories.ts accessibility.vue blog brand.vue compare.vue diff index.vue org package package-code package-docs pds.vue privacy.stories.ts privacy.vue profile recharging.vue search.vue settings.vue translation-status.vue ~[username] --- oauth-client-metadata.json.get.ts opensearch.xml.get.ts [pkg] raw skills
Top-level routes that should be excluded from the .md catch-all:
about, accessibility, blog, brand, compare, diff, index, org, package, package-code, package-docs, pds, privacy, profile, recharging, search, settings, translation-status, ~usernameapi, _nuxt, _v, __nuxt (framework)skills, oauth-client-metadata, opensearch (server)raw (our own).well-knownThe top-level .md shorthand (e.g., /vue.md) is nice but conflicts with many paths. The safer approach:
/package/*.md rewrite (explicit namespace, no conflicts).md rewrite exclude all known routes that may serve .md contentLet me fix this properly:
Updated rewrites with comprehensive exclusion list
<path>/home/byk/Code/npmx.dev/vercel.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://openapi.vercel.sh/vercel.json",
3: "trailingSlash": false,
4: "rewrites": [
5: {
6: "source": "/package/:path(.*)\\.md",
7: "destination": "/raw/:path.md"
8: },
9: {
10: "source": "/:path((?!api|_nuxt|_v|__nuxt|raw/|skills/|package-code/|package-docs/|blog/|diff/|compare|search|org/|profile/|brand|about|accessibility|pds|privacy|recharging|settings|translation-status|oauth-client-metadata|opensearch|\\.well-known/).*)\\.md",
11: "destination": "/raw/:path.md"
12: },
13: {
14: "source": "/package/:path(.*)",
15: "has": [
16: {
17: "type": "header",
18: "key": "accept",
19: "value": "(.*?)text/markdown(.*)"
20: }
21: ],
22: "destination": "/raw/:path.md"
23: },
24: {
25: "source": ...
Good. Now let me rebase onto latest main and push:
100
Saved working directory and index state WIP on byk/feat/md: 3056a750 feat: add markdown output support for package pages Rebasing (1/1) Successfully rebased and updated refs/heads/byk/feat/md.
Clean rebase. Now apply the vercel.json changes:
On branch byk/feat/md
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: vercel.json
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (279bfe975f931906f3e6538925c9a2ba876eb55a)
diff --git a/vercel.json b/vercel.json
index 068db287..7ab8b079 100644
--- a/vercel.json
+++ b/vercel.json
@@ -7,7 +7,7 @@
"destination": "/raw/:path.md"
},
{
- "source": "/:path((?!api|_nuxt|_v|__nuxt|search|code|raw/).*)\\.md",
+ "source": "/:path((?!api|_nuxt|_v|__nuxt|raw/|skills/|package-code/|package-docs/|blog/|diff/|compare|search|org/|profile/|brand|about|accessibility|pds|privacy|recharging|settings|translation-status|oauth-client-metadata|opensearch|\\.well-known/).*)\\.md",
"destination": "/raw/:path.md"
},
{
@@ -22,7 +22,7 @@
"destination": "/raw/:path.md"
},
{
- "source": "/:path((?!api|_nuxt|_v|__nuxt|search|code|raw/).*)",
+ "source": "/:path((?!api|_nuxt|_v|__nuxt|raw/|skills/|package-code/|package-docs/|blog/|diff/|compare|search|org/|profile/|brand|about|accessibility|pds|privacy|recharging|settings|translation-status|oauth-client-metadata|opensearch|\\.well-known/).*)",
"has": [
...
Let me also check if there's a code top-level route that was in the original β it appeared in the old exclusion:
package-code
Good β there's no code/ top-level, it's package-code/ which is in my list. Let me also double-check ~[username]:
~[username]
The ~username route is for profiles β it starts with ~ which makes it distinguishable from package names. Package names can't start with ~. Let me add that too to be safe, though ~ isn't a valid npm package name character so it can't collide organically. Actually the user could request /~foo.md thinking it's a package, but our parser would reject ~foo as a package name and 404. So probably fine.
One thing I noticed β blog also has /blog/first-post.md etc. if someone added a blog post with .md. The dir is already in my exclusion.
Let me also double-check β what about index.vue at root? That means / is the homepage. A .md request for / doesn't really make sense. /@scope/pkg.md would be caught by the catch-all. Good.
Now run tests and verify:
. postinstall: β Types generated in docs/.nuxt. . postinstall: Done Done in 35.4s using pnpm v10.33.0
Test Files 67 passed (67)
Tests 1427 passed (1427)
(no output)
Clean. Amend and push:
[byk/feat/md 54b25387] feat: add markdown output support for package pages Date: Thu Mar 26 11:33:33 2026 +0000 6 files changed, 926 insertions(+), 1 deletion(-) create mode 100644 server/routes/raw/[...slug].md.get.ts create mode 100644 server/utils/markdown.ts create mode 100644 test/unit/server/utils/markdown.spec.ts To https://github.com/BYK/npmx.dev.git + 741d4aa0...54b25387 byk/feat/md -> byk/feat/md (forced update)
Now reply to mbtools' review thread and resolve it:
{"data":{"addPullRequestReviewThreadReply":{"comment":{"id":"PRRC_kwDOQ_ILBs66XmPB"}}}}{"data":{"resolveReviewThread":{"thread":{"isResolved":true}}}}
Now final verification:
=== Unresolved Comments === 0 === CI Failures === FAILED: 72583744366 === Current state === 54b25387 feat: add markdown output support for package pages app/pages/package/[[org]]/[name].vue | 5 +- nuxt.config.ts | 1 + server/routes/raw/[...slug].md.get.ts | 172 +++++++++++ server/utils/markdown.ts | 225 +++++++++++++++ test/unit/server/utils/markdown.spec.ts | 492 ++++++++++++++++++++++++++++++++ vercel.json | 32 +++ 6 files changed, 926 insertions(+), 1 deletion(-)
A CI failure. Let me check:
π Lint project
(no output)
π Lint project π Lint project ο»Ώ2026-04-22T20:32:14.4688428Z ##[group]Run vp run lint
π Lint project π Lint project 2026-04-22T20:32:14.4688912Z [36;1mvp run lint[0m
π Lint project π Lint project 2026-04-22T20:32:14.4705551Z shell: /usr/bin/bash -e {0}
π Lint project π Lint project 2026-04-22T20:32:14.4705972Z ##[endgroup]
π Lint project π Lint project 2026-04-22T20:32:14.4761177Z VITE+ - The Unified Toolchain for the Web
π Lint project π Lint project 2026-04-22T20:32:14.4761730Z
π Lint project π Lint project 2026-04-22T20:32:14.9259834Z [34m$ vp lint[0m
π Lint project π Lint project 2026-04-22T20:32:17.0175255Z ##[warning]'pm' is already declared in the upper scope.
π Lint project π Lint project 2026-04-22T20:32:17.0184710Z ##[warning]Capturing group number 1 is defined but never used.
π Lint project π Lint project 2026-04-22T20:32:17.0185722Z
π Lint project π Lint project 2026-04-22T20:32:17.0185847Z Found 2 warnings and 0 errors. ...
Format issue in my route handler. Let me fix it:
fmt: read error
"scripts": {
"build": "nuxt build",
"build:test": "TEST=1 vp run build",
"dev": "nuxt dev",
"dev:docs": "vp run --filter npmx-docs dev --port=3001",
"i18n:check:fix": "node scripts/compare-translations.ts --fix",
"i18n:report:fix": "node scripts/remove-unused-translations.ts",
"knip:fix": "knip --fix",
"lint:fix": "vp lint --fix && vp fmt",
"generate": "nuxt generate",
"npmx-connector": "vp run --filter npmx-connector dev",
"mock-connector": "vp run --filter npmx-connector dev:mock",
"generate-pwa-icons": "pwa-assets-generator",
"preview": "nuxt preview",
"postinstall": "(pnpm rebuild @resvg/resvg-js || true) && vp run generate:lexicons && vp run generate:sprite && nuxt prepare && nuxt prepare --cwd docs && vp config",
"generate:fixtures": "node scripts/generate-fixtures.ts",
"generate:jwk": "node scripts/gen-jwk.ts",
"test": "vp test",
"test:a11y": "vp run build:test && LIGHTHOUSE_COLOR_MODE=dark vp run ...
The formatter is run via vp fmt. Let me try pnpm lint:fix:
16 | if (queryPM && packageManagers.some(pm => pm.id === queryPM)) {
: ^|
: `-- 'pm' is declared here
17 | pm.value = queryPM as PackageManagerId
`----
help: Consider renaming 'pm' to avoid shadowing the variable from the outer scope.
Found 2 warnings and 0 errors.
Finished in 3.0s on 660 files with 221 rules using 4 threads.
Finished in 5829ms on 861 files using 4 threads.
diff --git a/server/routes/raw/[...slug].md.get.ts b/server/routes/raw/[...slug].md.get.ts
index 0ede4e75..4a7f8d1b 100644
--- a/server/routes/raw/[...slug].md.get.ts
+++ b/server/routes/raw/[...slug].md.get.ts
@@ -1,8 +1,5 @@
import { generatePackageMarkdown } from '../../utils/markdown'
-import {
- isStandardReadme,
- fetchReadmeFromJsdelivr,
-} from '../../utils/readme-loaders'
+import { isStandardReadme, fetchReadmeFromJsdelivr } from '../../utils/readme-loaders'
import * as v from 'valibot'
import { PackageRouteParamsSchema } from '#shared/schemas/package'
import { NPM_MISSING_README_SENTINEL, ERROR_NPM_FETCH_FAILED } from '#shared/utils/constants'
Formatter put the imports on one line. Amend and push:
[byk/feat/md 2b2e5c40] feat: add markdown output support for package pages Date: Thu Mar 26 11:33:33 2026 +0000 6 files changed, 923 insertions(+), 1 deletion(-) create mode 100644 server/routes/raw/[...slug].md.get.ts create mode 100644 server/utils/markdown.ts create mode 100644 test/unit/server/utils/markdown.spec.ts To https://github.com/BYK/npmx.dev.git + 09df4e0f...2b2e5c40 byk/feat/md -> byk/feat/md (forced update)
Wait for CI:
=== CI Failures === None === Pending === π Chromatic πͺ Type check π₯οΈ Browser tests βΏ Accessibility audit (light) βΏ Accessibility audit (dark) π§ͺ Component tests π§ͺ Unit tests Vercel β npmx.dev === Unresolved === 0
π₯οΈ Browser tests pass 4m10s https://github.com/npmx-dev/npmx.dev/actions/runs/24801341162/job/72584483791 π€ Autofix code pass 1m1s https://github.com/npmx-dev/npmx.dev/actions/runs/24801341158/job/72584437304 π§ͺ Component tests pass 2m4s https://github.com/npmx-dev/npmx.dev/actions/runs/24801341162/job/72584483736 π§ͺ Unit tests pass 57s https://github.com/npmx-dev/npmx.dev/actions/runs/24801341162/job/72584483802 π§Ή Unused code check pass 46s https://github.com/npmx-dev/npmx.dev/actions/runs/24801341162/job/72584483839
None
All done:
origin/main (100 new commits), clean rebase.md suffix doesn't break /skills/<pkg>/.../refs/guide.md, /package-code/.../*.md, /package-docs/..., /blog/*.md, and all other top-level routesExpanded the rewrite exclusion regex from (?!api|_nuxt|_v|__nuxt|search|code|raw/) to a comprehensive list covering every top-level route that could serve content (including markdown files). Only bare package shortcuts (/vue.md, /@scope/pkg.md) and the explicit /package/<pkg>.md namespace trigger the markdown rewrite now.
Keep running
gh run view --log-failed --job $(gh pr checks $PR_NO --json state,link -q '.[] | select(.state == "FAILURE").link | split("/")[-1]')'
to get all failing jobs after you push. Make sure to wait for "Sentry Seer" and "Cursor BugBot" jobs to finish. Fix any failing jobs. Address all unresolved comments (both from bots and humans) from the PR by making a todo list. When you address each PR, either respond to them or mark them as resolved. Don't stop until the todo items are all done. Keep repeating this cycle until there are no more CI failures nor unresolved comments from humans or bots.
Use the following command to get the unresolved comments:
gh api graphql -F owner='<REPO_OWNER>' -F name='<REPO_NAME>' -F number=<PR_NUMBER> -f query='query($owner: String!, $name: String!, $number: Int!) { repository(owner: $owner, name: $name) { pullRequest(number: $number) { reviewThreads(first: 100) { nodes { isResolved comments(first: 100) { nodes { diffHunk body startLine line isMinimized } } } } } } }' -q '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | .comments.nodes[] | select(.isMinimized == false) | {diff_hunk: .diffHunk, line: .line, start_line: .startLine, body: .body}]'
You'll need to replace <REPO_OWNER>, <REPO_NAME>, and <PR_NUMBER> with relevant
values for the current repo and PR. (You can probably get them by running gh pr status or gh pr view)
No CI failures
[]
No CI failures, no unresolved review comments. PR #151 is clean.
There was a comment about package.md being valid etc, can you find and address it and then ask for a re-review (using github's api)
Let me find that comment:
{"author":"mbtools","body":"Tested and it works great with \"Accept: text/markdown\". π \r\n\r\nIt does not work with URL suffix: `/\u003cpackage\u003e.md. I'm just getting an empty HTML file:\r\n\r\n```log\r\ncurl https://npmx-mtytt72bl-npmx.vercel.app/@registrytest/package.md\r\n\u003c!DOCTYPE html\u003e\u003chtml\u003e\u003chead\u003e\u003cmeta http-equiv=\"refresh\" content=\"0; url=/package/@registrytest/package.md\"\u003e\u003c/head\u003e\u003c/html\u003e\r\n```\r\n\r\n","created":"2026-04-08T18:42:04Z"}
{"author":"BYK","body":"@mbtools Good catch β the `.md` suffix URLs were missing from the Vercel rewrite rules. Only the `Accept: text/markdown` header path was wired up.\n\nFixed by adding unconditional rewrites for `.md` suffix:\n- `/package/\u003cpkg\u003e.md` β `/raw/\u003cpkg\u003e.md`\n- `/\u003cpkg\u003e.md` β `/raw/\u003cpkg\u003e.md`\n\nAlso rebased onto latest main. Should work on the next preview ...
[{"author":"coderabbitai","body":"_β οΈ Potential issue_ | _π Major_\n\n\u003cdetails\u003e\n\u003csummary\u003eπ§© Analysis chain\u003c/summary\u003e\n\nπ Script executed:\n\n```shell\n# First, let's find and examine the file in question\ngit ls-files | grep -E \"(raw.*slug|nuxt.config)\" | head -20\n```\n\nRepository: npmx-dev/npmx.dev\n\nLength of output: 133\n\n---\n\nπ Script executed:\n\n```shell\n# Search for CACHE_MAX_AGE_ONE_HOUR constant definition\nrg \"CACHE_MAX_AGE_ONE_HOUR\" -t ts -t tsx -A 2 -B 2\n```\n\nRepository: npmx-dev/npmx.dev\n\nLength of output: 88\n\n---\n\nπ Script executed:\n\n```shell\n# Look for route rule or ISR configuration related to /raw/**\nrg \"raw/\\*\\*|/raw\" -t ts -t tsx -t js -A 3 -B 1 | head -50\n```\n\nRepository: npmx-dev/npmx.dev\n\nLength of output: 88\n\n---\n\nπ Script executed:\n\n```shell\n# Examine the specific file mentioned in the review\ncat -n \"server/routes/raw/[...slug].md.get.ts\" | sed -n '215,240p'\n```\n\nRepository: ...
The package.md comment is from mbtools - the @registrytest/package.md test was testing a package literally named package (a valid npm package name) under the @registrytest scope. Looking at mbtools' follow-up: "Tested again and it all works well." So the issue was already fixed. But let me check what specific comment you're referring to:
{"author":"43081j","body":"@BYK feel free to not bother catching up until we have a 2nd approval (ideally from @ghostdevv since she reviewed last time).\r\n\r\notherwise you'll forever be playing catch up. we can just do it at the end π ","created":"2026-04-08T16:32:17Z","id":4207840237}
{"author":"mbtools","body":"Tested and it works great with \"Accept: text/markdown\". π \r\n\r\nIt does not work with URL suffix: `/\u003cpackage\u003e.md. I'm just getting an empty HTML file:\r\n\r\n```log\r\ncurl https://npmx-mtytt72bl-npmx.vercel.app/@registrytest/package.md\r\n\u003c!DOCTYPE html\u003e\u003chtml\u003e\u003chead\u003e\u003cmeta http-equiv=\"refresh\" content=\"0; url=/package/@registrytest/package.md\"\u003e\u003c/head\u003e\u003c/html\u003e\r\n```\r\n\r\n","created":"2026-04-08T18:42:04Z","id":4208679255}
{"author":"BYK","body":"@mbtools Good catch β the `.md` suffix URLs were missing from the Vercel rewrite rules. Only the `Accept: text/markdown` header path was wired ...
{"author":"mbtools","body":"Tested again and it all works well. Just see my comment above","created":"2026-04-09T09:13:07Z","id":4212997349}
{"author":"BYK","body":"","created":"2026-04-05T09:49:32Z","state":"COMMENTED"}
{"author":"BYK","body":"","created":"2026-04-05T09:49:42Z","state":"COMMENTED"}
{"author":"mbtools","body":"","created":"2026-04-05T12:19:37Z","state":"COMMENTED"}
{"author":"serhalp","body":"","created":"2026-04-07T19:36:58Z","state":"COMMENTED"}
{"author":"serhalp","body":"","created":"2026-04-07T19:38:19Z","state":"COMMENTED"}
{"author":"mbtools","body":"","created":"2026-04-07T23:11:06Z","state":"COMMENTED"}
{"author":"BYK","body":"","created":"2026-04-08T13:11:21Z","state":"COMMENTED"}
{"author":"mbtools","body":"","created":"2026-04-09T09:11:59Z","state":"COMMENTED"}
{"author":"BYK","body":"","created":"2026-04-22T20:32:07Z","state":"COMMENTED"}
{"author":"gameroman","body":"Technically, a package name can have `.md` at the end of it, for example https://npmx.dev/package/pa","created":"2026-04-30T07:51:51Z","state":"COMMENTED"}
Found it β a recent review comment from gameroman saying package names can end in .md. Let me get the full body:
{"body":"Technically, a package name can have `.md` at the end of it, for example https://npmx.dev/package/package.md","comments":[]}
Now I understand the issue clearly. A package can literally be named package.md (e.g. https://npmjs.com/package/package.md). With the current rewrite, /package.md would be rewritten to /raw/package.md, but it should instead render the npm page for the package called package.md.
Let me check if package.md is a real npm package:
package.md - Transform package.json to markdown document.
package.md is a real npm package! The rewrite would incorrectly intercept /package.md and redirect it to /raw/package (stripping .md), instead of showing the npm page for the package.md package.
This is a genuine conflict: we can't distinguish "user wants markdown output for package package" from "user wants the package.md package page". Let me think through the solution.
The safest fix is to not have a bare top-level .md rewrite at all, and instead only support the /package/<pkg>.md explicit namespace (which is unambiguous since /package/ is our own namespace and npm packages can't start with package/):
[
{
"content": "Remove top-level *.md rewrite that collides with npm packages named *.md",
"status": "in_progress",
"priority": "high"
},
{
"content": "Reply to gameroman review and request re-review",
"status": "pending",
"priority": "high"
}
]
<path>/home/byk/Code/npmx.dev/vercel.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://openapi.vercel.sh/vercel.json",
3: "trailingSlash": false,
4: "rewrites": [
5: {
6: "source": "/package/:path(.*)\\.md",
7: "destination": "/raw/:path.md"
8: },
9: {
10: "source": "/:path((?!api|_nuxt|_v|__nuxt|raw/|skills/|package-code/|package-docs/|blog/|diff/|compare|search|org/|profile/|brand|about|accessibility|pds|privacy|recharging|settings|translation-status|oauth-client-metadata|opensearch|\\.well-known/).*)\\.md",
11: "destination": "/raw/:path.md"
12: },
13: {
14: "source": "/package/:path(.*)",
15: "has": [
16: {
17: "type": "header",
18: "key": "accept",
19: "value": "(.*?)text/markdown(.*)"
20: }
21: ],
22: "destination": "/raw/:path.md"
23: },
24: {
25: "source": ...
I need to remove the bare top-level .md suffix rewrite (line 9-12). The /package/:path.md rewrite is safe to keep since it's our explicit namespace. The Accept: text/markdown header rewrites are also fine since they don't conflict with browsing the site normally.
Also need to check if the /:path.md Accept-header rewrite also has the same problem β if someone sends Accept: text/markdown to /package.md, they'd get markdown for the package.md package page instead... but that's actually intentional: if you explicitly request markdown via a header on any URL, you want markdown output. The .md suffix case is more ambiguous since users navigating with a browser might not intend markdown.
Actually re-reading gameroman's comment β the issue is specifically with the URL suffix approach. The Accept header approach is unambiguous because browsers never send Accept: text/markdown. So removing only the bare .md suffix rewrite is the right fix:
Rewrites remaining: 4 /package/:path(.*)\.md /:path((?!api|_nuxt|_v|__nuxt|raw/|skills/|package-code/|package-docs/|blog/|diff/|compare|search|org/|profile/|brand|about|accessibility|pds|privacy|recharging|settings|translation-status|oauth-client-metadata|opensearch|\.well-known/).*)\.md /package/:path(.*) /:path((?!api|_nuxt|_v|__nuxt|raw/|skills/|package-code/|package-docs/|blog/|diff/|compare|search|org/|profile/|brand|about|accessibility|pds|privacy|recharging|settings|translation-status|oauth-client-metadata|opensearch|\.well-known/).*)
Hmm β the python endswith comparison matched the wrong one. Let me check:
<path>/home/byk/Code/npmx.dev/vercel.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://openapi.vercel.sh/vercel.json",
3: "trailingSlash": false,
4: "rewrites": [
5: {
6: "source": "/package/:path(.*)\\.md",
7: "destination": "/raw/:path.md"
8: },
9: {
10: "source": "/:path((?!api|_nuxt|_v|__nuxt|raw/|skills/|package-code/|package-docs/|blog/|diff/|compare|search|org/|profile/|brand|about|accessibility|pds|privacy|recharging|settings|translation-status|oauth-client-metadata|opensearch|\\.well-known/).*)\\.md",
11: "destination": "/raw/:path.md"
12: },
13: {
14: "source": "/package/:path(.*)",
15: "has": [
16: {
17: "type": "header",
18: "key": "accept",
19: "value": "(.*?)text/markdown(.*)"
20: }
21: ],
22: "destination": "/raw/:path.md"
23: },
24: {
25: "source": ...
The file still has the problematic line 10 rewrite. The python script didn't remove it. Let me remove it directly:
Removed 1 rewrite(s). Remaining: 3 /package/:path(.*)\.md /package/:path(.*) /:path((?!api|_nuxt|_v|__nuxt|raw/|skills/|package-code/|package-docs/|blog/|diff/|compare|search|org/|profile/|brand|about|accessibility|pds|privacy|recharging|settings|translation-status|oauth-client-metadata|opensearch|\.well-known/).*)
Now I have 3 rewrites:
/package/:path.md β explicit namespace, safe β
/package/:path + Accept header β explicit namespace, safe β
/:path + Accept header β top-level, header-gated (safe β browsers don't send Accept: text/markdown) β
The problematic bare /:path.md catch-all is gone. Amend commit and push:
Burak Yigit Kaya ## byk/feat/md...origin/main [ahead 1]
[byk/feat/md 823729a0] feat: add markdown output support for package pages Date: Thu Mar 26 11:33:33 2026 +0000 6 files changed, 919 insertions(+), 1 deletion(-) create mode 100644 server/routes/raw/[...slug].md.get.ts create mode 100644 server/utils/markdown.ts create mode 100644 test/unit/server/utils/markdown.spec.ts To https://github.com/BYK/npmx.dev.git + 2b2e5c40...823729a0 byk/feat/md -> byk/feat/md (forced update)
Now reply to gameroman's review comment and request re-review:
{"body":"Technically, a package name can have `.md` at the end of it, for example https:/","databaseId":4203386461,"id":"PRR_kwDOQ_ILBs76ipZd"}
{"url":"https://api.github.com/repos/npmx-dev/npmx.dev/issues/comments/4362103637","html_url":"https://github.com/npmx-dev/npmx.dev/pull/151#issuecomment-4362103637","issue_url":"https://api.github.com/repos/npmx-dev/npmx.dev/issues/151","id":4362103637,"node_id":"IC_kwDOQ_ILBs8AAAABBABrVQ","user":{"login":"BYK","id":126780,"node_id":"MDQ6VXNlcjEyNjc4MA==","avatar_url":"https://avatars.githubusercontent.com/u/126780?u=fb459d67141d8b2df1822a92a43508019977facc&v=4","gravatar_id":"","url":"https://api.github.com/users/BYK","html_url":"https://github.com/BYK","followers_url":"https://api.github.com/users/BYK/followers","following_url":"https://api.github.com/users/BYK/following{/other_user}","gists_url":"https://api.github.com/users/BYK/gists{/gist_id}","starred_url":"https://api.github.com/users/BYK/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BYK/subscriptions","organizations_url":"https://api.github.com/users/BYK/orgs","repos_url":"https://api.github.com/use...
Now request re-review from ghostdevv and 43081j (the main reviewers):
{"message":"Not Found","documentation_url":"https://docs.github.com/rest/pulls/review-requests#request-reviewers-for-a-pull-request","status":"404"}gh: Not Found (HTTP 404)
{"message":"Not Found","documentation_url":"https://docs.github.com/rest/pulls/review-requests#request-reviewers-for-a-pull-request","status":"404"}gh: Not Found (HTTP 404)
Unknown JSON field: "baseRepository" Available fields: additions assignees author autoMergeRequest baseRefName baseRefOid body changedFiles closed closedAt closingIssuesReferences comments commits createdAt deletions files fullDatabaseId headRefName headRefOid headRepository headRepositoryOwner id isCrossRepository isDraft labels latestReviews maintainerCanModify mergeCommit mergeStateStatus mergeable mergedAt mergedBy milestone number potentialMergeCommit projectCards projectItems reactionGroups reviewDecision reviewRequests reviews state statusCheckRollup title updatedAt url
{"crossRepo":true,"owner":"BYK"}
It's a cross-repo PR (from BYK fork β npmx-dev). The API for requesting reviewers needs to go through the base repo. Let me try the GraphQL mutation:
PR_kwDOQ_ILBs6_fG_p
{"43081j":"MDQ6VXNlcjU2NzcxNTM=","ghostdevv":"MDQ6VXNlcjQ3NzU1Mzc4"}
{"data":{"requestReviews":null},"errors":[{"type":"FORBIDDEN","path":["requestReviews"],"locations":[{"line":2,"column":3}],"message":"BYK does not have the correct permissions to execute `RequestReviews`"}],"extensions":{"warnings":[{"type":"DEPRECATION","message":"The id MDQ6VXNlcjU2NzcxNTM= is deprecated. Update your cache to use the next_global_id from the data payload.","data":{"legacy_global_id":"MDQ6VXNlcjU2NzcxNTM=","next_global_id":"U_kgDOAFagYQ"},"link":"https://docs.github.com"},{"type":"DEPRECATION","message":"The id MDQ6VXNlcjQ3NzU1Mzc4 is deprecated. Update your cache to use the next_global_id from the data payload.","data":{"legacy_global_id":"MDQ6VXNlcjQ3NzU1Mzc4","next_global_id":"U_kgDOAtiwcg"},"link":"https://docs.github.com"}]}}gh: BYK does not have the correct permissions to execute `RequestReviews`
No permission to request reviewers on this repo (only maintainers can do that). The next best thing is to leave a comment pinging the reviewers to request re-review:
{"url":"https://api.github.com/repos/npmx-dev/npmx.dev/issues/comments/4362105776","html_url":"https://github.com/npmx-dev/npmx.dev/pull/151#issuecomment-4362105776","issue_url":"https://api.github.com/repos/npmx-dev/npmx.dev/issues/151","id":4362105776,"node_id":"IC_kwDOQ_ILBs8AAAABBABzsA","user":{"login":"BYK","id":126780,"node_id":"MDQ6VXNlcjEyNjc4MA==","avatar_url":"https://avatars.githubusercontent.com/u/126780?u=fb459d67141d8b2df1822a92a43508019977facc&v=4","gravatar_id":"","url":"https://api.github.com/users/BYK","html_url":"https://github.com/BYK","followers_url":"https://api.github.com/users/BYK/followers","following_url":"https://api.github.com/users/BYK/following{/other_user}","gists_url":"https://api.github.com/users/BYK/gists{/gist_id}","starred_url":"https://api.github.com/users/BYK/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BYK/subscriptions","organizations_url":"https://api.github.com/users/BYK/orgs","repos_url":"https://api.github.com/use...
[
{
"content": "Remove top-level *.md rewrite that collides with npm packages named *.md",
"status": "completed",
"priority": "high"
},
{
"content": "Reply to gameroman review and request re-review",
"status": "completed",
"priority": "high"
}
]
Done. Summary:
Fix: Removed the top-level /:path*.md Vercel rewrite. package.md is a real npm package and the bare .md URL shorthand was inherently ambiguous β there's no way to tell if /vue.md means "markdown output for vue" or "the package named vue.md".
What still works:
/package/vue.md β markdown output (explicit namespace, unambiguous)curl -H "Accept: text/markdown" https://npmx.dev/vue β markdown output (header-gated, browsers never send this)Re-review: Requested via comment pinging @43081j and @ghostdevv (no permission to formally request review as a contributor, only maintainers can do that).
There are new comments and concerns. I think they are right but I don't like their solution proposals. Can you come up with a better solution?
[]
{"author":"BYK","body":"@gameroman Great catch! `package.md` is indeed a real npm package (https://www.npmjs.com/package/package.md) and our top-level `/:path*.md` rewrite would have intercepted `/package.md` and served it as markdown output for a package named `package` instead of showing the page for `package.md`.\n\nFixed by removing the bare `/:path*.md` catch-all rewrite entirely. The remaining rewrites are:\n- `/package/:path*.md` β safe, our explicit namespace (packages can't start with `package/`)\n- `Accept: text/markdown` header rewrites β also safe since browsers never send this header\n\nSo the `.md` URL shorthand (e.g. `/vue.md`) no longer works β but it was inherently ambiguous. The explicit `/package/vue.md` form and `Accept: text/markdown` header both continue to work.","created":"2026-05-01T23:17:40Z"}
{"author":"BYK","body":"@43081j @ghostdevv β could you take another look when you get a chance? The main change since last review is the addition of `.md` URL suffix ...
Let me understand the problem:
https://npmx.dev/package/package.md is ambiguous β it could mean:
package (via .md suffix)package.mdThe current /package/:path(.*).md rewrite catches ALL .md suffixed URLs under /package/, but packages can have .md in their names.
mbtools suggests ?format=markdown or ?md query parameters.
gameroman suggests a separate path like /package-markdown/.
The user rejected these proposals, so I'm reconsidering the approach.
The real issue here is content negotiation. We already support Accept: text/markdown headers, so the .md suffix was just meant as a convenience. I could either lean into the /raw/ namespace we already have as the canonical markdown endpoint, or handle the edge case directly in the route handler to distinguish between packages with .md in their names versus the format suffix. ...
Now I understand the issue clearly. Let me read the full discussion:
Problem: /package/package.md is ambiguous β is it markdown output for the package package, or the page for the npm package literally named package.md?
Proposed solutions (not liked):
?format=markdown or ?md query param/package-markdown/ pathBoth add URL complexity for a problem that doesn't need to exist. We already have the /raw/ namespace which is unambiguous and purpose-built for this:
/raw/vue.md β markdown output, zero ambiguity/raw/@scope/pkg.md β scoped, zero ambiguityAccept: text/markdown header on any URL β content negotiationThe <link rel="alternate" type="text/markdown" href="/raw/..."> tag in the page head already advertises the /raw/ URL for machine discovery. There's no reason to also have a .md suffix on /package/ β it only creates collisions.
The fix: drop the /package/:path.md rewrite too. Keep only the Accept header rewrites and the direct /raw/ route.
Rewrites remaining: 2 /package/:path(.*) (header: accept) /:path((?!api|_nuxt|_v|__nuxt|raw/|skills/|package-code/|package-docs/|blog/|diff/|compare|search|org/|profile/|brand|about|accessibility|pds|privacy|recharging|settings|translation-status|oauth-client-metadata|opensearch|\.well-known/).*) (header: accept)
<path>/home/byk/Code/npmx.dev/vercel.json</path>
<type>file</type>
<content>
1: {
2: "$schema": "https://openapi.vercel.sh/vercel.json",
3: "trailingSlash": false,
4: "rewrites": [
5: {
6: "source": "/package/:path(.*)",
7: "has": [
8: {
9: "type": "header",
10: "key": "accept",
11: "value": "(.*?)text/markdown(.*)"
12: }
13: ],
14: "destination": "/raw/:path.md"
15: },
16: {
17: "source": "/:path((?!api|_nuxt|_v|__nuxt|raw/|skills/|package-code/|package-docs/|blog/|diff/|compare|search|org/|profile/|brand|about|accessibility|pds|privacy|recharging|settings|translation-status|oauth-client-metadata|opensearch|\\.well-known/).*)",
18: "has": [
19: {
20: "type": "header",
21: "key": "accept",
22: "value": "(.*?)text/markdown(.*)"
23: }
24: ],
25: "destination": "/raw/:path.md"
26: }
27: ],
28: "redirects": [
29: {
30: ...
Clean. Now amend and push:
Burak Yigit Kaya ## byk/feat/md...origin/main [ahead 1]
[byk/feat/md 9d75172a] feat: add markdown output support for package pages Date: Thu Mar 26 11:33:33 2026 +0000 6 files changed, 915 insertions(+), 1 deletion(-) create mode 100644 server/routes/raw/[...slug].md.get.ts create mode 100644 server/utils/markdown.ts create mode 100644 test/unit/server/utils/markdown.spec.ts To https://github.com/BYK/npmx.dev.git + 823729a0...9d75172a byk/feat/md -> byk/feat/md (forced update)
Now reply to the conversation with the explanation:
{"url":"https://api.github.com/repos/npmx-dev/npmx.dev/issues/comments/4364629594","html_url":"https://github.com/npmx-dev/npmx.dev/pull/151#issuecomment-4364629594","issue_url":"https://api.github.com/repos/npmx-dev/npmx.dev/issues/151","id":4364629594,"node_id":"IC_kwDOQ_ILBs8AAAABBCb2Wg","user":{"login":"BYK","id":126780,"node_id":"MDQ6VXNlcjEyNjc4MA==","avatar_url":"https://avatars.githubusercontent.com/u/126780?u=fb459d67141d8b2df1822a92a43508019977facc&v=4","gravatar_id":"","url":"https://api.github.com/users/BYK","html_url":"https://github.com/BYK","followers_url":"https://api.github.com/users/BYK/followers","following_url":"https://api.github.com/users/BYK/following{/other_user}","gists_url":"https://api.github.com/users/BYK/gists{/gist_id}","starred_url":"https://api.github.com/users/BYK/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/BYK/subscriptions","organizations_url":"https://api.github.com/users/BYK/orgs","repos_url":"https://api.github.com/use...
Done. The solution is cleaner than query params or new URL paths β just use the mechanisms that already exist and can't collide:
| Method | URL | Ambiguity risk |
|--------|-----|----------------|
| Direct | /raw/vue.md | None β /raw/ is our namespace |
| Header | Accept: text/markdown on any URL | None β browsers never send this |
| ~~Suffix~~ | ~~/vue.md~~ | ~~Collides with packages named *.md~~ β removed |