fix: read plugin metadata by scanning the source, not one regex #1

Closed
Ghost wants to merge 1 commit from (deleted):fix/plugin-meta-parser into main

What breaks

getPluginMeta reads a plugin's index file with PLUGIN_META_REGEX, and that regex only matches when nothing but whitespace or comments sits between the name and description keys. Plenty of plugins put another key in that gap. When they do, the match returns null, getPluginMeta throws on rawMeta![1], and the Promise.allSettled filter in getUserplugins drops the plugin without a word.

The plugin is still installed and still runs, so it shows up in the normal Plugins list, but it is completely absent from the UserPlugins tab. The uninstall and update buttons are rendered from that same list, so neither works for it either. Reinstalling looks broken in its own way: the clone lands on disk and the review window never opens, which reads as the installer doing nothing at all.

I hit this with a userplugin that has a version key between the two, then checked how far it goes. Of the 42 repos in the userplugins org, 37 ship an index file, and 2 of those are invisible in the tab right now: the one I was installing, and amrpcstatusdisplaytype.desktop, which has tags: ["AppleMusicRichPresence"] between name and description. Vencord's own tree says the same thing about how common the shape is, for what it is worth: 29 of 170 plugins fail the regex, mostly by listing authors before description. Those never appear in this tab, so they are not affected, but they are written by the same people following the same habits.

What this changes

Metadata is now read by scanning the definePlugin object as tokens instead of matching one fixed shape. The new scanner lives in misc/pluginMeta.ts and exports a single parsePluginMeta(source). It walks top-level keys, steps over strings, template literals, regex literals, comments and nested brackets, and returns null rather than a guess whenever it loses track of where it is.

It stops as soon as both keys are found. That also keeps it out of the JSX further down the file, which matters more than it sounds: JSX text is not JavaScript, so an apostrophe in something like the artist's name opens a string literal that never closes as far as a tokenizer is concerned. Scanning the whole object without that early exit fails on real plugins.

The second change is smaller. Picking the index file used to be four ifs that each assigned on a match, so the last matching entry won and a plugin shipping both index.js and index.tsx got parsed from whichever one readdir happened to return last. It now uses explicit precedence in the order esbuild resolves a directory, so we read the file that actually ends up in the build.

Testing

I cross-checked the parser against the TypeScript compiler AST, which is the same source of truth Vencord's own generatePluginList reads. Over all 189 files under src/plugins and src/userplugins that define a plugin: 187 agree, 2 are files that only mention definePlugin in passing and both return null, 0 disagree.

On top of that, 21 hand-written cases covering regex literals containing quotes and braces, JSX tags, method shorthand, spreads, computed keys, comments that mention description, template literals with and without substitutions, and truncated or unterminated files. Plus a false-positive pass over files that define no plugin.

The index-file order was checked against esbuild 0.28.1 rather than assumed: .tsx wins over .ts, which wins over .js.

For regressions I ran the old code path and the new one side by side over every plugin repo in the userplugins org, comparing which index file gets picked and what metadata comes back. Old code lists 36 of 37, new code lists 37, nothing is lost, no name or description text differs anywhere. Same comparison over the local tree: 0 lost, 30 gained, 0 text changes. And the failure mode is unchanged for anything I could not test, since the parser returns null rather than throwing, which lands on the same Invalid plugin rejection the old code produced.

Build side, pnpm build --dev exits 0 and tsc --noEmit reports nothing new (the one error it does print is pre-existing, in components/SettingsTab.tsx, untouched here). With the built output loaded, the userplugin that started this now appears in the UserPlugins tab with its description, source link and delete button.

Notes

The parser has no dependencies and does not touch CLONE_LINK_REGEX or anything else in native.ts beyond the metadata read. If you would rather have the self-test harness in the repo than only in this description, say the word and I will add it as a file.

## What breaks `getPluginMeta` reads a plugin's index file with `PLUGIN_META_REGEX`, and that regex only matches when nothing but whitespace or comments sits between the `name` and `description` keys. Plenty of plugins put another key in that gap. When they do, the match returns null, `getPluginMeta` throws on `rawMeta![1]`, and the `Promise.allSettled` filter in `getUserplugins` drops the plugin without a word. The plugin is still installed and still runs, so it shows up in the normal Plugins list, but it is completely absent from the UserPlugins tab. The uninstall and update buttons are rendered from that same list, so neither works for it either. Reinstalling looks broken in its own way: the clone lands on disk and the review window never opens, which reads as the installer doing nothing at all. I hit this with a userplugin that has a `version` key between the two, then checked how far it goes. Of the 42 repos in the `userplugins` org, 37 ship an index file, and 2 of those are invisible in the tab right now: the one I was installing, and `amrpcstatusdisplaytype.desktop`, which has `tags: ["AppleMusicRichPresence"]` between `name` and `description`. Vencord's own tree says the same thing about how common the shape is, for what it is worth: 29 of 170 plugins fail the regex, mostly by listing `authors` before `description`. Those never appear in this tab, so they are not affected, but they are written by the same people following the same habits. ## What this changes Metadata is now read by scanning the `definePlugin` object as tokens instead of matching one fixed shape. The new scanner lives in `misc/pluginMeta.ts` and exports a single `parsePluginMeta(source)`. It walks top-level keys, steps over strings, template literals, regex literals, comments and nested brackets, and returns null rather than a guess whenever it loses track of where it is. It stops as soon as both keys are found. That also keeps it out of the JSX further down the file, which matters more than it sounds: JSX text is not JavaScript, so an apostrophe in something like `the artist's name` opens a string literal that never closes as far as a tokenizer is concerned. Scanning the whole object without that early exit fails on real plugins. The second change is smaller. Picking the index file used to be four `if`s that each assigned on a match, so the last matching entry won and a plugin shipping both `index.js` and `index.tsx` got parsed from whichever one `readdir` happened to return last. It now uses explicit precedence in the order esbuild resolves a directory, so we read the file that actually ends up in the build. ## Testing I cross-checked the parser against the TypeScript compiler AST, which is the same source of truth Vencord's own `generatePluginList` reads. Over all 189 files under `src/plugins` and `src/userplugins` that define a plugin: 187 agree, 2 are files that only mention `definePlugin` in passing and both return null, 0 disagree. On top of that, 21 hand-written cases covering regex literals containing quotes and braces, JSX tags, method shorthand, spreads, computed keys, comments that mention `description`, template literals with and without substitutions, and truncated or unterminated files. Plus a false-positive pass over files that define no plugin. The index-file order was checked against esbuild 0.28.1 rather than assumed: `.tsx` wins over `.ts`, which wins over `.js`. For regressions I ran the old code path and the new one side by side over every plugin repo in the `userplugins` org, comparing which index file gets picked and what metadata comes back. Old code lists 36 of 37, new code lists 37, nothing is lost, no name or description text differs anywhere. Same comparison over the local tree: 0 lost, 30 gained, 0 text changes. And the failure mode is unchanged for anything I could not test, since the parser returns null rather than throwing, which lands on the same `Invalid plugin` rejection the old code produced. Build side, `pnpm build --dev` exits 0 and `tsc --noEmit` reports nothing new (the one error it does print is pre-existing, in `components/SettingsTab.tsx`, untouched here). With the built output loaded, the userplugin that started this now appears in the UserPlugins tab with its description, source link and delete button. ## Notes The parser has no dependencies and does not touch `CLONE_LINK_REGEX` or anything else in `native.ts` beyond the metadata read. If you would rather have the self-test harness in the repo than only in this description, say the word and I will add it as a file.
PLUGIN_META_REGEX only matches when nothing but whitespace or comments sits
between a plugin's name and description keys. Plenty of plugins put another
key there, and when they do the match returns null, getPluginMeta throws on
rawMeta![1], and Promise.allSettled quietly drops the plugin. It never shows
up in the UserPlugins tab, and since the uninstall and update buttons are
rendered from that same list, neither works for it either. Reinstalling looks
broken in a different way: the clone lands on disk and the review window never
opens. I hit this with a userplugin that has a version key between the two,
then checked Vencord's own plugins and 29 of 170 fail the same way, mostly by
listing authors before description.

So this reads the definePlugin object as tokens instead of matching one fixed
shape. The scanner walks top level keys and steps over strings, template
literals, regex literals, comments and nested brackets, and it returns null
rather than a guess whenever it loses track. It stops as soon as both keys are
found, which also keeps it away from JSX further down: JSX text is not
JavaScript, and an apostrophe in "the artist's name" opens a string literal
that never closes.

Checked against the TypeScript compiler AST (the same thing Vencord's
generatePluginList reads) over all 189 files under src/plugins and
src/userplugins that define a plugin: 187 agree, 2 are files that only mention
definePlugin in passing and both return null, 0 disagree. Plus 21 hand written
cases covering regex literals containing quotes and braces, JSX tags, method
shorthand, spreads, computed keys, comments that mention description, template
literals with and without substitutions, and truncated or unterminated files.

Also picks the index file by explicit precedence instead of letting readdir
order decide. The old loop assigned on every match, so the last matching entry
won: a plugin shipping both index.js and index.tsx was parsed from whichever
one the filesystem happened to return last. The new order matches how esbuild
resolves a directory (verified with esbuild 0.28.1: tsx, ts, jsx, js), so we
read the file that actually ends up in the build.
Owner

AI slop

your entire account also seems to be made with as sole intent to contribute slop so I have blocked it from this git instance altogether. if you ever intent to make a good faith contribution, contact me

AI slop your entire account also seems to be made with as sole intent to contribute slop so I have blocked it from this git instance altogether. if you ever intent to make a good faith contribution, contact me
nin0 closed this pull request 2026-09-04 16:58:47 -04:00
nin0 locked as Spam and limited conversation to collaborators 2026-09-04 16:58:59 -04:00

Pull request closed

This discussion has been locked. Commenting is limited to contributors.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
userplugins/userplugininstaller!1
No description provided.