From c5353ce1fd1df0eea8129cf360ea835851cc093b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 31 Aug 2026 19:38:30 +0000 Subject: [PATCH 1/5] refactor: integrate ActivityPub EmojiReact activity via core filter hooks, extract helpers - Add filter:activitypub. handlers (emojireact, like, undo, announce) so the plugin claims EmojiReact/Like-with-content activities from the core inbox via the new filter hook seam. - Extract emoji resolution helpers (getEmojiTable, getEmojiAliases, getCharacterIndex, resolveByName, resolveReaction) into helpers.js to keep library.js focused on plugin API and socket handlers. - Remove deprecated `library` field from plugin.json. - Add test suite (test/index.js) covering resolveReaction unit tests, controller-path AP integration, socket handler regression, and edge cases (unknown types, privilege denial, cap exceeded, idempotency). Assisted-by: One of unsloth/Qwen3.6-35B-A3B-GGUF or unsloth/Qwen3.8-27B-GGUF --- helpers.js | 106 +++++++++++ library.js | 377 ++++++++++++++++++++++++++++----------- plugin.json | 5 +- test/index.js | 483 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 871 insertions(+), 100 deletions(-) create mode 100644 helpers.js create mode 100644 test/index.js diff --git a/helpers.js b/helpers.js new file mode 100644 index 0000000..f1e6247 --- /dev/null +++ b/helpers.js @@ -0,0 +1,106 @@ +'use strict'; + +let emojiTable = null; +let emojiAliases = null; +let characterIndex = null; + +function getEmojiTable() { + if (!emojiTable) { + emojiTable = nodebb.require('nodebb-plugin-emoji/build/emoji/table.json'); + } + return emojiTable; +} + +function getEmojiAliases() { + if (!emojiAliases) { + emojiAliases = nodebb.require('nodebb-plugin-emoji/build/emoji/aliases.json'); + } + return emojiAliases; +} + +function getCharacterIndex() { + if (!characterIndex) { + characterIndex = new Map(); + Object.keys(getEmojiTable()).forEach((name) => { + const entry = getEmojiTable()[name]; + if (entry && entry.character && !characterIndex.has(entry.character)) { + characterIndex.set(entry.character, name); + } + }); + } + return characterIndex; +} + +function resolveByName(name) { + if (!name || typeof name !== 'string') { + return null; + } + name = name.trim(); + if (getEmojiTable()[name]) { + return name; + } + const aliases = getEmojiAliases(); + if (aliases[name] && getEmojiTable()[aliases[name]]) { + return aliases[name]; + } + return null; +} + +/** + * Resolve FEP-c0e0 reaction content (unicode grapheme, `:shortcode:`, bare name, + * or a custom-emoji `tag`) to a local emoji name. Returns null when unresolvable. + */ +function resolveReaction(content, tag) { + if (typeof content !== 'string' || !content.trim()) { + return null; + } + const trimmed = content.trim(); + + let reaction; + + // `:shortcode:` + const shortcode = trimmed.match(/^:([a-z0-9_+-]+):$/i); + if (shortcode) { + reaction = resolveByName(shortcode[1]); + } else { + // Bare name (only accepted when it resolves to a local emoji) + reaction = resolveByName(trimmed); + + if (!reaction) { + // Unicode grapheme + const index = getCharacterIndex(); + if (index.has(trimmed)) { + reaction = index.get(trimmed); + } + // Retry without variation selectors (table entries may omit them, e.g. keycaps) + const noVariation = trimmed.replace(/\uFE0F/g, ''); + if (!reaction && noVariation !== trimmed && index.has(noVariation)) { + reaction = index.get(noVariation); + } + const firstCodepoint = [...trimmed][0]; + if (!reaction && firstCodepoint && index.has(firstCodepoint)) { + reaction = index.get(firstCodepoint); + } + } + } + + // Custom emoji: the tag carries the emoji's identity, so it is consulted + // when the content alone does not resolve to a local emoji (a remote + // custom emoji whose name matches a built-in one is usable) + if (!reaction && Array.isArray(tag)) { + const emojiTag = tag.find(t => t && t.type === 'Emoji' && typeof t.name === 'string'); + if (emojiTag) { + reaction = resolveByName(emojiTag.name.replace(/^:|:$/g, '')); + } + } + + return reaction; +} + +module.exports = { + getEmojiTable, + getEmojiAliases, + getCharacterIndex, + resolveByName, + resolveReaction, +}; diff --git a/library.js b/library.js index a0c7872..60c7768 100644 --- a/library.js +++ b/library.js @@ -10,28 +10,23 @@ const db = nodebb.require('./src/database'); const translator = nodebb.require('./src/translator'); const notifications = nodebb.require('./src/notifications'); const routesHelpers = nodebb.require('./src/routes/helpers'); +const nconf = nodebb.require('nconf'); +const categories = nodebb.require('./src/categories'); +const activitypub = nodebb.require('./src/activitypub'); const websockets = nodebb.require('./src/socket.io/index'); const SocketPlugins = nodebb.require('./src/socket.io/plugins'); const emojiParser = nodebb.require('nodebb-plugin-emoji/build/lib/parse.js'); - -let emojiTable = null; -let emojiAliases = null; +const helpers = require('./helpers'); const DEFAULT_MAX_EMOTES = 4; function nameToEmoji(name) { - if (!emojiTable) { - emojiTable = nodebb.require('nodebb-plugin-emoji/build/emoji/table.json'); - } - return emojiTable[name]; + return helpers.getEmojiTable()[name]; } function parse(name) { - if (!emojiAliases) { - emojiAliases = nodebb.require('nodebb-plugin-emoji/build/emoji/aliases.json'); - } - const emoji = nameToEmoji(name) || emojiTable[emojiAliases[name]]; + const emoji = nameToEmoji(name) || helpers.getEmojiTable()[helpers.getEmojiAliases()[name]]; return emoji ? emojiParser.buildEmoji(emoji, '') : ''; } @@ -340,87 +335,285 @@ async function giveOwnerReactionReputation(reactionReputation, pid) { } } -SocketPlugins.reactions = { - addPostReaction: async function (socket, data) { - if (!socket.uid) { - throw new Error('[[error:not-logged-in]]'); - } +/** + * Core reaction logic, shared by the socket handlers and the ActivityPub + * (FEP-c0e0) inbox integration. `uid` may be a local numeric uid or a remote + * actor URL. + */ +ReactionsPlugin.addPostReaction = async function (pid, uid, reaction) { + const settings = await loadPluginConfig(); + if (!settings.enablePostReactions) { + throw new Error('[[error:post-reactions-disabled]]'); + } - if (!nameToEmoji(data.reaction)) { - throw new Error('[[reactions:error.invalid-reaction]]'); + const [postData, totalReactions, emojiIsAlreadyExist, alreadyReacted, reactionReputation] = await Promise.all([ + posts.getPostFields(pid, ['pid', 'tid', 'uid', 'content', 'sourceContent']), + db.setCount(`pid:${pid}:reactions`), + db.isSetMember(`pid:${pid}:reactions`, reaction), + db.isSetMember(`pid:${pid}:reaction:${reaction}`, uid), + getReactionReputation(reaction), + ]); + const { tid } = postData; + if (!tid) { + throw new Error('[[error:no-post]]'); + } + + if (!emojiIsAlreadyExist) { + const { maximumReactions, maximumReactionsPerUserPerPost } = settings; + if (maximumReactions > 0 && totalReactions >= maximumReactions) { + throw new Error(`[[reactions:error.maximum-reached, ${maximumReactions}]]`); } - const settings = await loadPluginConfig(); - if (!settings.enablePostReactions) { - throw new Error('[[error:post-reactions-disabled]]'); + if (maximumReactionsPerUserPerPost > 0) { + const emojiesInPost = await db.getSetMembers(`pid:${pid}:reactions`); + const userPostReactions = await db.isMemberOfSets(emojiesInPost.map(emojiName => `pid:${pid}:reaction:${emojiName}`), uid); + const userPostReactionCount = userPostReactions.filter(Boolean).length; + if (userPostReactionCount >= maximumReactionsPerUserPerPost) { + throw new Error(`[[reactions:error.maximum-per-user-per-post-reached, ${maximumReactionsPerUserPerPost}]]`); + } } + } - const [postData, totalReactions, emojiIsAlreadyExist, alreadyReacted, reactionReputation] = await Promise.all([ - posts.getPostFields(data.pid, ['pid', 'tid', 'uid', 'content', 'sourceContent']), - db.setCount(`pid:${data.pid}:reactions`), - db.isSetMember(`pid:${data.pid}:reactions`, data.reaction), - db.isSetMember(`pid:${data.pid}:reaction:${data.reaction}`, socket.uid), - getReactionReputation(data.reaction), + await Promise.all([ + db.setAdd(`pid:${pid}:reactions`, reaction), + db.setAdd(`pid:${pid}:reaction:${reaction}`, uid), + ]); + + if (!alreadyReacted && reactionReputation > 0) { + await giveOwnerReactionReputation(reactionReputation, pid); + } + + if (postData.uid && postData.uid !== uid) { + const [displayname, topicTitle, parsedPostData] = await Promise.all([ + user.getNotificationDisplayname(uid), + topics.getNotificationTitle(tid), + posts.parsePost(postData), ]); - const { tid } = postData; - if (!tid) { - throw new Error('[[error:no-post]]'); - } - data.uid = socket.uid; - data.tid = tid; - if (!emojiIsAlreadyExist) { - const { maximumReactions, maximumReactionsPerUserPerPost } = settings; - if (maximumReactions > 0 && totalReactions >= maximumReactions) { - throw new Error(`[[reactions:error.maximum-reached, ${maximumReactions}]]`); - } + const notifObj = await notifications.create({ + type: 'reaction', + bodyShort: translator.compile( + 'reactions:notification.user-has-reacted-with-to-your-post-in-topic', + displayname, + `:${reaction}:`, + topicTitle + ), + bodyLong: parsedPostData.content, + nid: `uid:${uid}:pid:${pid}:reaction:${reaction}`, + pid: pid, + tid: tid, + from: uid, + path: `/post/${pid}`, + }); - if (maximumReactionsPerUserPerPost > 0) { - const emojiesInPost = await db.getSetMembers(`pid:${data.pid}:reactions`); - const userPostReactions = await db.isMemberOfSets(emojiesInPost.map(emojiName => `pid:${data.pid}:reaction:${emojiName}`), socket.uid); - const userPostReactionCount = userPostReactions.filter(Boolean).length; - if (userPostReactionCount >= maximumReactionsPerUserPerPost) { - throw new Error(`[[reactions:error.maximum-per-user-per-post-reached, ${maximumReactionsPerUserPerPost}]]`); - } + await notifications.push(notifObj, [postData.uid]); + } + + await sendPostEvent({ pid, uid, tid, reaction }, 'event:reactions.addPostReaction'); +}; + +ReactionsPlugin.removePostReaction = async function (pid, uid, reaction) { + const settings = await loadPluginConfig(); + if (!settings.enablePostReactions) { + throw new Error('[[error:post-reactions-disabled]]'); + } + + const [tid, hasReacted, reactionReputation] = await Promise.all([ + posts.getPostField(pid, 'tid'), + db.isSetMember(`pid:${pid}:reaction:${reaction}`, uid), + getReactionReputation(reaction), + ]); + if (!tid) { + throw new Error('[[error:no-post]]'); + } + + if (hasReacted) { + await db.setRemove(`pid:${pid}:reaction:${reaction}`, uid); + } + + const reactionCount = await db.setCount(`pid:${pid}:reaction:${reaction}`); + if (reactionCount === 0) { + await db.setRemove(`pid:${pid}:reactions`, reaction); + } + if (hasReacted && reactionReputation > 0) { + await giveOwnerReactionReputation(-reactionReputation, pid); + } + + await sendPostEvent({ pid, uid, tid, reaction }, 'event:reactions.removePostReaction'); +}; + +ReactionsPlugin.rescindPostReaction = async function (pid, uid, reaction) { + await notifications.rescind(`uid:${uid}:pid:${pid}:reaction:${reaction}`); +}; + +/* + ActivityPub (FEP-c0e0) integration. + + Core fires `filter:activitypub.` for every incoming activity before + built-in handling. The filter payload is `{ req, activity, claimed }` — a + plugin may claim the activity (core then skips its built-in handler) and/or + transparently rewrite `activity` for the rest of the chain. + + This plugin claims: + - `EmojiReact` (always — it is the implementation) + - `Like` with `content` (FEP-c0e0 requires identical handling) + - `Undo` of either of the above + - `Announce` of either of the above (category sync / relays) +*/ + +/** + * Resolve the (local or remote) post referenced by an EmojiReact activity. + * Returns a pid for local posts, the note URL for remote posts, or null when + * the post cannot be found. + */ +async function resolveReactionPost(object) { + let id; + let exists; + if (object.id.startsWith(nconf.get('url'))) { + const { type, id: localId } = await activitypub.helpers.resolveLocalId(object.id); + if (type === 'post') { + id = localId; + exists = await posts.exists(id); + } + } else { + id = object.id; + exists = await posts.exists(id); + if (!exists) { + // Proactively pull in the note + const asserted = await activitypub.notes.assert(0, id, { skipChecks: 1 }); + if (!asserted) { + return null; } + exists = true; } + } + return id && exists ? id : null; +} - await Promise.all([ - db.setAdd(`pid:${data.pid}:reactions`, data.reaction), - db.setAdd(`pid:${data.pid}:reaction:${data.reaction}`, socket.uid), - ]); +ReactionsPlugin.applyEmojiReact = async function (activity) { + const { actor, object, content, tag } = activity; + + const id = await resolveReactionPost(object); + if (!id) { + return; + } + + const reaction = helpers.resolveReaction(content, tag); + if (!reaction) { + activitypub.helpers.log(`[reactions/ap] Unresolvable reaction content (${JSON.stringify(content)}), ignoring.`); + return; + } + + const allowed = await privileges.posts.can('posts:upvote', id, activitypub._constants.uid); + if (!allowed) { + activitypub.helpers.log(`[reactions/ap] ${id} not allowed to be reacted on.`); + throw new Error('[[error:no-privileges]]'); + } + + activitypub.helpers.log(`[reactions/ap] id ${id} (${reaction}) via ${actor}`); + await ReactionsPlugin.addPostReaction(id, actor, reaction); + await activitypub.feps.announce(object.id, activity); +}; - if (!alreadyReacted && reactionReputation > 0) { - await giveOwnerReactionReputation(reactionReputation, data.pid); +ReactionsPlugin.undoEmojiReact = async function (activity) { + const { actor, object, content, tag } = activity; + + const id = await resolveReactionPost(object); + if (!id) { + return; + } + + const reaction = helpers.resolveReaction(content, tag); + if (!reaction) { + activitypub.helpers.log(`[reactions/ap] Unresolvable reaction content in undo, ignoring.`); + return; + } + + activitypub.helpers.log(`[reactions/ap] undo id ${id} (${reaction}) via ${actor}`); + await ReactionsPlugin.removePostReaction(id, actor, reaction); + await ReactionsPlugin.rescindPostReaction(id, actor, reaction); + await activitypub.feps.announce(object.id, activity); +}; + +ReactionsPlugin.handleEmojiReact = async function (context) { + await ReactionsPlugin.applyEmojiReact(context.activity); + return { ...context, claimed: true }; +}; + +ReactionsPlugin.handleLike = async function (context) { + // FEP-c0e0: a Like with content is an emoji reaction; a plain Like falls through to core + if (typeof context.activity.content === 'string' && context.activity.content.trim()) { + await ReactionsPlugin.applyEmojiReact(context.activity); + return { ...context, claimed: true }; + } + return context; +}; + +ReactionsPlugin.handleUndo = async function (context) { + const { object } = context.activity; + if (!object || (object.type !== 'EmojiReact' && !(object.type === 'Like' && typeof object.content === 'string' && object.content.trim()))) { + return context; + } + await ReactionsPlugin.undoEmojiReact(object); + return { ...context, claimed: true }; +}; + +ReactionsPlugin.handleAnnounce = async function (context) { + const { actor } = context.activity; + + // Unwrap nested Announces and resolve string references, mirroring core + let { object } = context.activity; + while (object && object.type === 'Announce') { + object = object.object; + } + if (typeof object === 'string') { + try { + object = await activitypub.helpers.resolveObjects(object); + } catch (e) { + object = { id: object }; } + } + if (!object || (object.type !== 'EmojiReact' && !(object.type === 'Like' && typeof object.content === 'string' && object.content.trim()))) { + return context; + } - if (postData.uid && postData.uid !== socket.uid) { - const [displayname, topicTitle, parsedPostData] = await Promise.all([ - user.getNotificationDisplayname(socket.uid), - topics.getNotificationTitle(data.tid), - posts.parsePost(postData), - ]); - const notifObj = await notifications.create({ - type: 'reaction', - bodyShort: translator.compile( - 'reactions:notification.user-has-reacted-with-to-your-post-in-topic', - displayname, - `:${data.reaction}:`, - topicTitle - ), - bodyLong: parsedPostData.content, - nid: `uid:${socket.uid}:pid:${data.pid}:reaction:${data.reaction}`, - pid: data.pid, - tid: data.tid, - from: socket.uid, - path: `/post/${data.pid}`, - }); + // Only category-synced or relayed announces reach local posts + const fromRelay = await activitypub.relays.is(actor); + const categoryActor = await categories.exists(actor); + if (!categoryActor && !fromRelay) { + return context; + } + + if (categoryActor) { + // Mirrors core's protection: category actors can only announce activities + // concerning posts in said category (the post's cid is the category actor URL) + let id = (object.object && object.object.id) || object.object; + const { id: localId } = await activitypub.helpers.resolveLocalId(id); + id = localId || id; - await notifications.push(notifObj, [postData.uid]); + if (!(await posts.exists(id)) || (await posts.getCidByPid(id)) !== actor) { + return context; } + } - await sendPostEvent(data, 'event:reactions.addPostReaction'); - }, - removePostReaction: async function (socket, data) { + if (!(await activitypub.actors.assert(object.actor))) { + throw new Error('[[error:activitypub.invalid-id]]'); + } + + if (typeof object.object === 'string') { + try { + object.object = await activitypub.helpers.resolveObjects(object.object); + } catch (e) { + activitypub.helpers.log(`[reactions/ap] Failed to resolve announced object, using raw id: ${object.object}`); + object.object = { id: object.object }; + } + } + + await ReactionsPlugin.applyEmojiReact(object); + return { ...context, claimed: true }; +}; + +SocketPlugins.reactions = { + addPostReaction: async function (socket, data) { if (!socket.uid) { throw new Error('[[error:not-logged-in]]'); } @@ -429,34 +622,20 @@ SocketPlugins.reactions = { throw new Error('[[reactions:error.invalid-reaction]]'); } - const [settings, tid, hasReacted, reactionReputation] = await Promise.all([ - loadPluginConfig(), - posts.getPostField(data.pid, 'tid'), - db.isSetMember(`pid:${data.pid}:reaction:${data.reaction}`, socket.uid), - getReactionReputation(data.reaction), - ]); - if (!settings.enablePostReactions) { - throw new Error('[[error:post-reactions-disabled]]'); - } - if (!tid) { - throw new Error('[[error:no-post]]'); - } data.uid = socket.uid; - data.tid = tid; - - if (hasReacted) { - await db.setRemove(`pid:${data.pid}:reaction:${data.reaction}`, socket.uid); + await ReactionsPlugin.addPostReaction(data.pid, socket.uid, data.reaction); + }, + removePostReaction: async function (socket, data) { + if (!socket.uid) { + throw new Error('[[error:not-logged-in]]'); } - const reactionCount = await db.setCount(`pid:${data.pid}:reaction:${data.reaction}`); - if (reactionCount === 0) { - await db.setRemove(`pid:${data.pid}:reactions`, data.reaction); - } - if (hasReacted && reactionReputation > 0) { - await giveOwnerReactionReputation(-reactionReputation, data.pid); + if (!nameToEmoji(data.reaction)) { + throw new Error('[[reactions:error.invalid-reaction]]'); } - await sendPostEvent(data, 'event:reactions.removePostReaction'); + data.uid = socket.uid; + await ReactionsPlugin.removePostReaction(data.pid, socket.uid, data.reaction); }, addMessageReaction: async function (socket, data) { if (!socket.uid) { diff --git a/plugin.json b/plugin.json index 64e6b2e..b24884d 100644 --- a/plugin.json +++ b/plugin.json @@ -3,7 +3,6 @@ "name": "NodeBB Reactions", "description": "Reactions plugin for NodeBB", "url": "https://github.com/NodeBB-Community/nodebb-plugin-reactions", - "library": "./library.js", "templates": "templates", "languages": "languages", "scss": [ @@ -24,6 +23,10 @@ { "hook": "filter:messaging.getMessages", "method": "getMessageReactions" }, { "hook": "filter:post.get", "method": "onReply" }, { "hook": "action:posts.purge", "method": "deleteReactions" }, + { "hook": "filter:activitypub.emojireact", "method": "handleEmojiReact" }, + { "hook": "filter:activitypub.like", "method": "handleLike" }, + { "hook": "filter:activitypub.undo", "method": "handleUndo" }, + { "hook": "filter:activitypub.announce", "method": "handleAnnounce" }, { "hook": "filter:notifications.addFilters", "method": "addNotificationFilters" }, { "hook": "filter:user.notificationTypes", "method": "notificationTypes" } ] diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..9d8a1a8 --- /dev/null +++ b/test/index.js @@ -0,0 +1,483 @@ +'use strict'; + +/* globals nodebb, describe, it, before, after, beforeEach */ + +const assert = require('assert'); +const util = require('util'); + +const sleep = util.promisify(setTimeout); + +const db = nodebb.require('./test/mocks/databasemock'); +const nconf = nodebb.require('nconf'); +const meta = nodebb.require('./src/meta'); +const install = nodebb.require('./src/install'); +const user = nodebb.require('./src/user'); +const categories = nodebb.require('./src/categories'); +const topics = nodebb.require('./src/topics'); +const posts = nodebb.require('./src/posts'); +const privileges = nodebb.require('./src/privileges'); +const controllers = nodebb.require('./src/controllers'); +const activitypub = nodebb.require('./src/activitypub'); +const utils = nodebb.require('./src/utils'); +const SocketPlugins = nodebb.require('./src/socket.io/plugins'); +const apHelpers = nodebb.require('./test/activitypub/helpers'); + +const plugin = require('../library'); +const helpers = require('../helpers'); + +describe('helpers.resolveReaction', () => { + it('should resolve a unicode grapheme', () => { + assert.strictEqual(helpers.resolveReaction('🔥'), 'fire'); + }); + + it('should resolve a :shortcode:', () => { + assert.strictEqual(helpers.resolveReaction(':fire:'), 'fire'); + }); + + it('should resolve a bare name', () => { + assert.strictEqual(helpers.resolveReaction('fire'), 'fire'); + }); + + it('should resolve an aliased :shortcode:', () => { + assert.strictEqual(helpers.resolveReaction(':telephone:'), 'phone'); + }); + + it('should resolve the character of an aliased emoji', () => { + assert.strictEqual(helpers.resolveReaction('☎'), 'phone'); + }); + + it('should resolve a keycap with a variation selector', () => { + assert.strictEqual(helpers.resolveReaction('1️⃣'), 'one'); // "1" + U+FE0F + U+20E3 + }); + + it('should resolve a keycap without a variation selector', () => { + assert.strictEqual(helpers.resolveReaction('1⃣'), 'one'); // "1" + U+20E3 + }); + + it('should resolve by first codepoint when the full grapheme is not in the table', () => { + // waving hand + medium-light skin tone → wave + assert.strictEqual(helpers.resolveReaction('👋🏻'), 'wave'); + }); + + it('should resolve a custom emoji tag that matches a local emoji', () => { + assert.strictEqual(helpers.resolveReaction(':blobwtf:', [{ type: 'Emoji', name: ':fire:' }]), 'fire'); + }); + + it('should return null for a custom emoji tag that does not match locally', () => { + assert.strictEqual(helpers.resolveReaction(':blobwtf:', [{ type: 'Emoji', name: ':blobwtf:' }]), null); + }); + + it('should return null for unresolvable content', () => { + assert.strictEqual(helpers.resolveReaction(':nope:'), null); + assert.strictEqual(helpers.resolveReaction('🛸'), null); + assert.strictEqual(helpers.resolveReaction('blobwtf'), null); + assert.strictEqual(helpers.resolveReaction(''), null); + assert.strictEqual(helpers.resolveReaction(null), null); + }); +}); + +describe('ActivityPub (FEP-c0e0)', () => { + const remoteActor = 'https://example.org/user/reactions-tester'; + const defaultSettings = { + enablePostReactions: 'on', + 'reaction-reputations': [{ reaction: 'fire', reputation: 5 }], + }; + + let apEnabled; + let cid; + let ownerUid; + let postData; + + before(async () => { + apEnabled = meta.config.activitypubEnabled; + meta.config.activitypubEnabled = 1; + nconf.set('runJobs', 1); + await install.giveWorldPrivileges(); + await meta.settings.set('reactions', defaultSettings); + ({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + }); + + after(async () => { + meta.config.activitypubEnabled = apEnabled; + nconf.set('runJobs', undefined); + await meta.settings.set('reactions', defaultSettings); + }); + + beforeEach(async () => { + ownerUid = await user.create({ username: utils.generateUUID().slice(0, 10) }); + ({ postData } = await topics.post({ + uid: ownerUid, + cid, + title: utils.generateUUID(), + content: utils.generateUUID(), + })); + }); + + function reactionActivity(override = {}) { + const activity = { + '@context': 'https://www.w3.org/ns/activitystreams', + id: `https://example.org/activity/${utils.generateUUID()}`, + type: 'EmojiReact', + actor: remoteActor, + object: { + type: 'Note', + id: `${nconf.get('url')}/post/${postData.pid}`, + }, + content: '🔥', + }; + Object.assign(activity, override); + return activity; + } + + function mockRes() { + const res = { req: { method: 'POST', loggedIn: false }, statusCode: null, payload: null }; + res.set = (key, value) => { + res[key] = value; + }; + res.status = (code) => { + res.statusCode = code; + return res; + }; + res.json = (payload) => { + res.payload = payload; + return res; + }; + res.sendStatus = (code) => { + res.statusCode = code; + }; + return res; + } + + it('should still ignore unknown activity types (200) when the plugin is installed', async () => { + const res = mockRes(); + await controllers.activitypub.postInbox({ + body: { + id: `https://example.org/activity/${utils.generateUUID()}`, + type: 'BlowAWhistle', + actor: remoteActor, + object: { id: `${nconf.get('url')}/post/${postData.pid}` }, + }, + }, res); + + assert.strictEqual(res.statusCode, 200); + }); + + describe('EmojiReact', () => { + it('should store a reaction from a unicode grapheme on a local post', async () => { + const res = mockRes(); + await controllers.activitypub.postInbox({ body: reactionActivity() }, res); + + assert.strictEqual(res.statusCode, 202); + assert(await db.isSetMember(`pid:${postData.pid}:reactions`, 'fire')); + assert(await db.isSetMember(`pid:${postData.pid}:reaction:fire`, remoteActor)); + }); + + it('should store a reaction from a :shortcode:', async () => { + const res = mockRes(); + await controllers.activitypub.postInbox({ body: reactionActivity({ content: ':fire:' }) }, res); + + assert.strictEqual(res.statusCode, 202); + assert(await db.isSetMember(`pid:${postData.pid}:reactions`, 'fire')); + }); + + it('should notify the post owner', async () => { + const res = mockRes(); + await controllers.activitypub.postInbox({ body: reactionActivity() }, res); + + // notifications.push is deferred (500ms) through the batch queue + await sleep(700); + const nid = `uid:${remoteActor}:pid:${postData.pid}:reaction:fire`; + assert(await db.isSortedSetMember(`uid:${ownerUid}:notifications:unread`, nid)); + }); + + it('should grant reaction reputation only once per reactor', async () => { + const res = mockRes(); + await controllers.activitypub.postInbox({ body: reactionActivity() }, res); + await controllers.activitypub.postInbox({ body: reactionActivity() }, res); + + assert.strictEqual(parseInt(await user.getUserField(ownerUid, 'reputation'), 10), 5); + assert.strictEqual(await db.setCount(`pid:${postData.pid}:reaction:fire`), 1); + }); + + it('should not upvote the post', async () => { + const res = mockRes(); + await controllers.activitypub.postInbox({ body: reactionActivity() }, res); + + const { upvoted } = await posts.hasVoted(postData.pid, remoteActor); + assert.strictEqual(upvoted, false); + assert.strictEqual(await posts.getPostField(postData.pid, 'upvotes'), 0); + }); + + it('should ignore unresolvable custom emoji', async () => { + const res = mockRes(); + await controllers.activitypub.postInbox({ + body: reactionActivity({ + content: ':blobwtf:', + tag: [{ type: 'Emoji', name: ':blobwtf:' }], + }), + }, res); + + assert.strictEqual(res.statusCode, 202); + assert.strictEqual(await db.setCount(`pid:${postData.pid}:reactions`), 0); + }); + + describe('with posts:upvote revoked from the fediverse pseudo-user', () => { + before(async () => { + await privileges.categories.rescind(['groups:posts:upvote'], cid, 'fediverse'); + }); + + after(async () => { + await privileges.categories.give(['groups:posts:upvote'], cid, 'fediverse'); + }); + + it('should throw [[error:no-privileges]]', async () => { + try { + await plugin.applyEmojiReact(reactionActivity()); + assert.fail('expected applyEmojiReact to throw'); + } catch (e) { + assert.strictEqual(e.message, '[[error:no-privileges]]'); + } + }); + }); + }); + + describe('Like with content', () => { + it('should store a reaction, not an upvote', async () => { + const res = mockRes(); + await controllers.activitypub.postInbox({ + body: { ...reactionActivity({ type: 'Like' }) }, + }, res); + + assert.strictEqual(res.statusCode, 202); + assert(await db.isSetMember(`pid:${postData.pid}:reactions`, 'fire')); + const { upvoted } = await posts.hasVoted(postData.pid, remoteActor); + assert.strictEqual(upvoted, false); + }); + }); + + describe('Undo', () => { + async function react() { + const res = mockRes(); + await controllers.activitypub.postInbox({ body: reactionActivity() }, res); + assert.strictEqual(res.statusCode, 202); + } + + it('should remove an EmojiReact reaction', async () => { + await react(); + const original = reactionActivity(); + + const res = mockRes(); + await controllers.activitypub.postInbox({ + body: { + id: `https://example.org/activity/${utils.generateUUID()}`, + type: 'Undo', + actor: remoteActor, + object: original, + }, + }, res); + + assert.strictEqual(res.statusCode, 202); + assert(!(await db.isSetMember(`pid:${postData.pid}:reactions`, 'fire'))); + assert(!(await db.isSetMember(`pid:${postData.pid}:reaction:fire`, remoteActor))); + }); + + it('should rescind the post owner notification', async () => { + await react(); + await sleep(700); + const nid = `uid:${remoteActor}:pid:${postData.pid}:reaction:fire`; + assert(await db.isSortedSetMember(`uid:${ownerUid}:notifications:unread`, nid)); + + const res = mockRes(); + await controllers.activitypub.postInbox({ + body: { + id: `https://example.org/activity/${utils.generateUUID()}`, + type: 'Undo', + actor: remoteActor, + object: reactionActivity(), + }, + }, res); + + // the notification object is deleted (stale entries are pruned lazily) + assert(!(await db.exists(`notifications:${nid}`))); + assert(!(await db.isSortedSetMember('notifications', nid))); + }); + + it('should remove a Like-with-content reaction without touching the vote', async () => { + const like = { ...reactionActivity({ type: 'Like' }) }; + const res = mockRes(); + await controllers.activitypub.postInbox({ body: like }, res); + assert.strictEqual(res.statusCode, 202); + + const undoRes = mockRes(); + await controllers.activitypub.postInbox({ + body: { + id: `https://example.org/activity/${utils.generateUUID()}`, + type: 'Undo', + actor: remoteActor, + object: like, + }, + }, undoRes); + + assert.strictEqual(undoRes.statusCode, 202); + assert(!(await db.isSetMember(`pid:${postData.pid}:reactions`, 'fire'))); + assert.strictEqual(await posts.getPostField(postData.pid, 'upvotes'), 0); + }); + + it('should not claim undos of plain activities (core handles them)', async () => { + // plain Like (no content) → core upvotes + const plainLike = { + id: `https://example.org/activity/${utils.generateUUID()}`, + type: 'Like', + actor: remoteActor, + object: { type: 'Note', id: `${nconf.get('url')}/post/${postData.pid}` }, + }; + const res = mockRes(); + await controllers.activitypub.postInbox({ body: plainLike }, res); + assert.strictEqual(res.statusCode, 202); + const { upvoted } = await posts.hasVoted(postData.pid, remoteActor); + assert.strictEqual(upvoted, true); + + // plain Undo(Like) → core unvotes + const undoRes = mockRes(); + await controllers.activitypub.postInbox({ + body: { + id: `https://example.org/activity/${utils.generateUUID()}`, + type: 'Undo', + actor: remoteActor, + object: plainLike, + }, + }, undoRes); + assert.strictEqual(undoRes.statusCode, 202); + const { upvoted: stillUpvoted } = await posts.hasVoted(postData.pid, remoteActor); + assert.strictEqual(stillUpvoted, false); + }); + }); + + describe('Announce', () => { + let remoteCid; + let remotePostId; + let emojiReact; + + before(async function () { + ({ id: remoteCid } = apHelpers.mocks.group()); + await activitypub.actors.assertGroup([remoteCid]); + + // A remote post that lands in the remote category + const { note, id } = apHelpers.mocks.note({ audience: [remoteCid] }); + const { activity } = apHelpers.mocks.create(note); + await activitypub.inbox.create({ body: activity }); + this.remotePostId = id; + remotePostId = id; + + emojiReact = { + id: `https://example.org/activity/${utils.generateUUID()}`, + type: 'EmojiReact', + actor: remoteActor, + object: { type: 'Note', id }, + content: '🔥', + }; + }); + + it('should ignore EmojiReact announces from non-category, non-relay actors', async () => { + const { activity } = apHelpers.mocks.announce({ actor: remoteActor, object: emojiReact }); + const res = mockRes(); + await controllers.activitypub.postInbox({ body: activity }, res); + + // falls through to core's announce handler, which also does nothing for this + assert.strictEqual(res.statusCode, 202); + assert.strictEqual(await db.setCount(`pid:${remotePostId}:reactions`), 0); + }); + + it('should apply a reaction announced by the remote category', async () => { + const { activity } = apHelpers.mocks.announce({ actor: remoteCid, object: emojiReact }); + const res = mockRes(); + await controllers.activitypub.postInbox({ body: activity }, res); + + assert.strictEqual(res.statusCode, 202); + assert(await db.isSetMember(`pid:${remotePostId}:reactions`, 'fire')); + assert(await db.isSetMember(`pid:${remotePostId}:reaction:fire`, remoteActor)); + }); + }); +}); + +describe('Socket handlers', () => { + const defaultSettings = { + enablePostReactions: 'on', + 'reaction-reputations': [{ reaction: 'fire', reputation: 5 }], + }; + + let cid; + let uid; + let postData; + + before(async () => { + ({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + await meta.settings.set('reactions', defaultSettings); + }); + + after(async () => { + await meta.settings.set('reactions', defaultSettings); + }); + + beforeEach(async () => { + uid = await user.create({ username: utils.generateUUID().slice(0, 10) }); + ({ postData } = await topics.post({ + uid, + cid, + title: utils.generateUUID(), + content: utils.generateUUID(), + })); + }); + + it('should add a reaction', async () => { + await SocketPlugins.reactions.addPostReaction({ uid }, { pid: postData.pid, reaction: 'fire' }); + assert(await db.isSetMember(`pid:${postData.pid}:reaction:fire`, uid)); + }); + + it('should require a logged-in socket', async () => { + try { + await SocketPlugins.reactions.addPostReaction({}, { pid: postData.pid, reaction: 'fire' }); + assert.fail('expected addPostReaction to throw'); + } catch (e) { + assert.strictEqual(e.message, '[[error:not-logged-in]]'); + } + }); + + it('should reject unknown reaction names', async () => { + try { + await SocketPlugins.reactions.addPostReaction({ uid }, { pid: postData.pid, reaction: 'notarealemoji' }); + assert.fail('expected addPostReaction to throw'); + } catch (e) { + assert.strictEqual(e.message, '[[reactions:error.invalid-reaction]]'); + } + }); + + it('should remove a reaction', async () => { + await SocketPlugins.reactions.addPostReaction({ uid }, { pid: postData.pid, reaction: 'fire' }); + await SocketPlugins.reactions.removePostReaction({ uid }, { pid: postData.pid, reaction: 'fire' }); + assert(!(await db.isSetMember(`pid:${postData.pid}:reaction:fire`, uid))); + assert(!(await db.isSetMember(`pid:${postData.pid}:reactions`, 'fire'))); + }); + + it('should enforce the maximumReactions cap', async () => { + await meta.settings.set('reactions', { ...defaultSettings, maximumReactions: '2' }); + await SocketPlugins.reactions.addPostReaction({ uid }, { pid: postData.pid, reaction: 'fire' }); + await SocketPlugins.reactions.addPostReaction({ uid }, { pid: postData.pid, reaction: 'phone' }); + try { + await SocketPlugins.reactions.addPostReaction({ uid }, { pid: postData.pid, reaction: 'smile' }); + assert.fail('expected addPostReaction to throw'); + } catch (e) { + assert(e.message.startsWith('[[reactions:error.maximum-reached, ')); + } + }); + + it('should throw when post reactions are disabled', async () => { + await meta.settings.set('reactions', { ...defaultSettings, enablePostReactions: 'off' }); + try { + await SocketPlugins.reactions.addPostReaction({ uid }, { pid: postData.pid, reaction: 'fire' }); + assert.fail('expected addPostReaction to throw'); + } catch (e) { + assert.strictEqual(e.message, '[[error:post-reactions-disabled]]'); + } + }); +}); From 25c05f5697f937373c607593e11d038c8702140b Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 31 Aug 2026 15:41:15 -0400 Subject: [PATCH 2/5] chore: bump compatibility --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bf07bbe..3612dbf 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@nodebb/nodebb-plugin-reactions", "version": "3.0.4", "nbbpm": { - "compatibility": "^4.14.0" + "compatibility": "^4.16.0" }, "description": "Reactions plugin for NodeBB", "main": "library.js", From 9b365184c6b0a58320249ba00f4647187ec9ef96 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 31 Aug 2026 19:47:55 +0000 Subject: [PATCH 3/5] fix: unscoped package name to match directory convention Assisted-by: One of unsloth/Qwen3.6-35B-A3B-GGUF or unsloth/Qwen3.8-27B-GGUF --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3612dbf..8b85cdf 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@nodebb/nodebb-plugin-reactions", + "name": "nodebb-plugin-reactions", "version": "3.0.4", "nbbpm": { "compatibility": "^4.16.0" From 3f490a6a9db1dfc2659a457efdafbf845eaa05b0 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 1 Sep 2026 10:11:50 -0400 Subject: [PATCH 4/5] chore: update package-lock.json --- package-lock.json | 1608 +++++++++------------------------------------ 1 file changed, 310 insertions(+), 1298 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7e8a77e..9435288 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@nodebb/nodebb-plugin-reactions", + "name": "nodebb-plugin-reactions", "version": "3.0.4", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@nodebb/nodebb-plugin-reactions", + "name": "nodebb-plugin-reactions", "version": "3.0.4", "license": "MIT", "devDependencies": { @@ -17,10 +17,11 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -34,11 +35,25 @@ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -48,6 +63,7 @@ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", @@ -62,6 +78,7 @@ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@eslint/core": "^1.2.1" }, @@ -74,6 +91,7 @@ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -82,24 +100,15 @@ } }, "node_modules/@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.0.tgz", + "integrity": "sha512-J2VKrn6YUBegZFzRQVOVA8jk7VViV/MXhQUvXuCUK1I2RGGT2E+bmNGOsgtdO9Wo25Kd3hfZFuCm8LcfsUFV7g==", + "deprecated": "This version should not be used.", "dev": true, + "license": "MIT", "peer": true, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/object-schema": { @@ -107,6 +116,7 @@ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" } @@ -116,6 +126,7 @@ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@eslint/core": "^1.2.1", "levn": "^0.4.1" @@ -125,38 +136,41 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -164,6 +178,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -177,6 +192,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=18.18" }, @@ -186,10 +202,11 @@ } }, "node_modules/@stylistic/eslint-plugin": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.9.0.tgz", - "integrity": "sha512-FqqSkvDMYJReydrMhlugc71M76yLLQWNfmGq+SIlLa7N3kHp8Qq8i2PyWrVNAfjOyOIY+xv9XaaYwvVW7vroMA==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", + "integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", @@ -206,71 +223,65 @@ "eslint": "^9.0.0 || ^10.0.0" } }, - "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@textcomplete/core": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@textcomplete/core/-/core-0.1.12.tgz", - "integrity": "sha512-37Q8Wic3IGpZHtknlJ/ODKMyvaBhVMM56Vl7aoBfno2Qq099fFqoCL0VzKhTn8qvTf1Z/8ymlHwPCBzPvW7KSQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@textcomplete/core/-/core-0.1.13.tgz", + "integrity": "sha512-C4S+ihQU5HsKQ/TbsmS0e7hfPZtLZbEXj5NDUgRnhu/1Nezpu892bjNZGeErZm+R8iyDIT6wDu6EgIhng4M8eQ==", + "license": "MIT", "peer": true, "dependencies": { - "eventemitter3": "^4.0.4" + "eventemitter3": "^5.0.1" } }, "node_modules/@textcomplete/textarea": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@textcomplete/textarea/-/textarea-0.1.12.tgz", - "integrity": "sha512-E05H4wXr1Q50CrCFBAHewyZqvQEX681V5zleDw/31tr8vl5PDFl6TyFmS1W0jQjlrQfxa5uVvgHCx+gpfICBDQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@textcomplete/textarea/-/textarea-0.1.13.tgz", + "integrity": "sha512-GNathnXpV361YuZrBVXvVqFYZ5NQZsjGC7Bt2sCUA/RTWlIgxHxC0ruDChYyRDx4siQZiZZOO5pWz+z1x8pZFQ==", + "license": "MIT", "peer": true, "dependencies": { - "@textcomplete/utils": "^0.1.11", + "@textcomplete/utils": "^0.1.13", "textarea-caret": "^3.1.0", "undate": "^0.3.0" }, "peerDependencies": { - "@textcomplete/core": "^0.1.9" + "@textcomplete/core": "^0.1.12" } }, "node_modules/@textcomplete/utils": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@textcomplete/utils/-/utils-0.1.12.tgz", - "integrity": "sha512-llHhD1FAVwFaaHzs7PU0BZYTpNLDzTccDWbw+5cj0TiB2NOXZGjPm6l7PJrJwN/yUuPDxOHip/3I+kF6OBkBAg==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/@textcomplete/utils/-/utils-0.1.13.tgz", + "integrity": "sha512-5UW9Ee0WEX1s9K8MFffo5sfUjYm3YVhtqRhAor/ih7p0tnnpaMB7AwMRDKwhSIQL6O+g1fmEkxCeO8WqjPzjUA==", + "license": "MIT", "peer": true }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "version": "8.69.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.69.0.tgz", + "integrity": "sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -281,10 +292,11 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -297,15 +309,17 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -320,114 +334,74 @@ "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT", "peer": true }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "peer": true, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, + "license": "MIT", "engines": { "node": "18 || 20 || >=22" } }, "node_modules/brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT", "peer": true }, "node_modules/busboy": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", - "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "peer": true, "dependencies": { - "dicer": "0.2.5", - "readable-stream": "1.1.x" + "streamsearch": "^1.1.0" }, "engines": { - "node": ">=0.8.0" + "node": ">=10.16.0" } }, "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", "engines": [ - "node >= 0.8" + "node >= 6.0" ], + "license": "MIT", "peer": true, "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", - "readable-stream": "^2.2.2", + "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, - "node_modules/concat-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "peer": true - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "peer": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "peer": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "peer": true - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -442,6 +416,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -458,32 +433,15 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/dicer": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", - "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", - "peer": true, - "dependencies": { - "readable-stream": "1.1.x", - "streamsearch": "0.1.2" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "peer": true + "dev": true, + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -496,6 +454,10 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", "dev": true, + "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", @@ -551,6 +513,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-nodebb/-/eslint-config-nodebb-2.0.3.tgz", "integrity": "sha512-dJJREiapn4zXEb0MidVIxS8+qoU5f+a+3cnk0pA4L7AFTd6xxL7hTDKuMmrjyqIB4Hb0Kz/rCyr0F3CVcCwIjg==", "dev": true, + "license": "ISC", "dependencies": { "globals": "17.3.0" }, @@ -564,6 +527,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@types/esrecurse": "^4.3.1", "@types/estree": "^1.0.8", @@ -578,12 +542,14 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", + "peer": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -594,6 +560,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" }, @@ -606,6 +573,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", @@ -623,6 +591,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "peer": true, "dependencies": { "acorn": "^8.15.0", @@ -636,24 +605,12 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/esquery": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -666,6 +623,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -678,6 +636,7 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } @@ -687,39 +646,45 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT", "peer": true }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" }, @@ -732,6 +697,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -748,6 +714,7 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -757,24 +724,25 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" }, "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "license": "MIT", "peer": true, "dependencies": { - "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=14.14" } }, "node_modules/glob-parent": { @@ -782,6 +750,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -794,6 +763,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", "dev": true, + "license": "MIT", "engines": { "node": ">=18" }, @@ -802,9 +772,10 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC", "peer": true }, "node_modules/ignore": { @@ -812,6 +783,7 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -819,8 +791,9 @@ "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -829,13 +802,15 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC", "peer": true }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -845,6 +820,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -852,40 +828,39 @@ "node": ">=0.10.0" } }, - "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "peer": true - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" }, "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", "peer": true, "dependencies": { "universalify": "^2.0.0" @@ -899,6 +874,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -908,6 +884,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -921,6 +898,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -932,24 +910,43 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT", "peer": true }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "peer": true, "engines": { "node": ">= 0.6" } }, + "node_modules/mime": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz", + "integrity": "sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "peer": true, + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "peer": true, "engines": { "node": ">= 0.6" @@ -959,6 +956,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "peer": true, "dependencies": { "mime-db": "1.52.0" @@ -968,12 +966,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -982,87 +981,53 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "peer": true - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "peer": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/multer": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz", - "integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.3.0.tgz", + "integrity": "sha512-cjNbm3sttszgZeGfJR124D+jFEfkXCVAsoPBmFn9X7UxmDSFHWqE2CoEj0vrmSpuAFnqWR1Szcm9QTsiHr60Xw==", + "license": "MIT", "peer": true, "dependencies": { "append-field": "^1.0.0", - "busboy": "^0.2.11", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "on-finished": "^2.3.0", - "type-is": "^1.6.4", - "xtend": "^4.0.0" + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "type-is": "^1.6.18" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 10.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" }, "node_modules/nodebb-plugin-emoji": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/nodebb-plugin-emoji/-/nodebb-plugin-emoji-5.0.10.tgz", - "integrity": "sha512-pqJdI+ksnaVBjn0MDpceYjcXvB/mFBlQS0d+TxOPuK2IsU+tNCgX+bSfd6zai5YbT+iFHU5KH8cw3jOMcgnnhQ==", + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/nodebb-plugin-emoji/-/nodebb-plugin-emoji-6.0.16.tgz", + "integrity": "sha512-iVIpUW/bNuJmrKwFdzY5B1TpxopSE5v9gv7EpdryCCsCxpinBvXOq+MDRZH54JmJSgylkozqElnHzLBhYpOjIA==", + "license": "MIT", "peer": true, "dependencies": { "@textcomplete/core": "^0.1.12", "@textcomplete/textarea": "^0.1.12", - "fs-extra": "^9.1.0", + "fs-extra": "^11.1.1", "lodash": "^4.17.21", - "multer": "^1.4.2" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "peer": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" + "mime": "^4.0.4", + "multer": "^2.0.2" } }, "node_modules/optionator": { @@ -1070,6 +1035,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -1087,6 +1053,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -1102,6 +1069,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -1117,6 +1085,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1126,15 +1095,17 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=12" @@ -1148,41 +1119,55 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "peer": true - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", "peer": true, "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "peer": true }, "node_modules/shebang-command": { @@ -1190,6 +1175,7 @@ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -1202,29 +1188,35 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", "peer": true, "engines": { - "node": ">=0.8.0" + "node": ">=10.0.0" } }, "node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "peer": true + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } }, "node_modules/textarea-caret": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz", "integrity": "sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q==", + "license": "MIT", "peer": true }, "node_modules/type-check": { @@ -1232,6 +1224,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -1243,6 +1236,7 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "peer": true, "dependencies": { "media-typer": "0.3.0", @@ -1255,19 +1249,22 @@ "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT", "peer": true }, "node_modules/undate": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/undate/-/undate-0.3.0.tgz", "integrity": "sha512-ssH8QTNBY6B+2fRr3stSQ+9m2NT8qTaun3ExTx5ibzYQvP7yX4+BnX0McNxFCvh6S5ia/DYu6bsCKQx/U4nb/Q==", + "license": "MIT", "peer": true }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", "peer": true, "engines": { "node": ">= 10.0.0" @@ -1278,6 +1275,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -1285,7 +1283,8 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT", "peer": true }, "node_modules/which": { @@ -1293,6 +1292,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -1308,24 +1308,17 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "peer": true, - "engines": { - "node": ">=0.4" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -1333,986 +1326,5 @@ "url": "https://github.com/sponsors/sindresorhus" } } - }, - "dependencies": { - "@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.4.3" - } - }, - "@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true - }, - "@eslint/config-array": { - "version": "0.23.5", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", - "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", - "dev": true, - "requires": { - "@eslint/object-schema": "^3.0.5", - "debug": "^4.3.1", - "minimatch": "^10.2.4" - } - }, - "@eslint/config-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", - "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", - "dev": true, - "requires": { - "@eslint/core": "^1.2.1" - } - }, - "@eslint/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", - "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.15" - } - }, - "@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", - "dev": true, - "peer": true, - "requires": {} - }, - "@eslint/object-schema": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", - "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", - "dev": true - }, - "@eslint/plugin-kit": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", - "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", - "dev": true, - "requires": { - "@eslint/core": "^1.2.1", - "levn": "^0.4.1" - } - }, - "@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true - }, - "@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", - "dev": true, - "requires": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" - }, - "dependencies": { - "@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", - "dev": true - } - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true - }, - "@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true - }, - "@stylistic/eslint-plugin": { - "version": "5.9.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.9.0.tgz", - "integrity": "sha512-FqqSkvDMYJReydrMhlugc71M76yLLQWNfmGq+SIlLa7N3kHp8Qq8i2PyWrVNAfjOyOIY+xv9XaaYwvVW7vroMA==", - "dev": true, - "peer": true, - "requires": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/types": "^8.56.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "estraverse": "^5.3.0", - "picomatch": "^4.0.3" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "peer": true - } - } - }, - "@textcomplete/core": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@textcomplete/core/-/core-0.1.12.tgz", - "integrity": "sha512-37Q8Wic3IGpZHtknlJ/ODKMyvaBhVMM56Vl7aoBfno2Qq099fFqoCL0VzKhTn8qvTf1Z/8ymlHwPCBzPvW7KSQ==", - "peer": true, - "requires": { - "eventemitter3": "^4.0.4" - } - }, - "@textcomplete/textarea": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@textcomplete/textarea/-/textarea-0.1.12.tgz", - "integrity": "sha512-E05H4wXr1Q50CrCFBAHewyZqvQEX681V5zleDw/31tr8vl5PDFl6TyFmS1W0jQjlrQfxa5uVvgHCx+gpfICBDQ==", - "peer": true, - "requires": { - "@textcomplete/utils": "^0.1.11", - "textarea-caret": "^3.1.0", - "undate": "^0.3.0" - } - }, - "@textcomplete/utils": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/@textcomplete/utils/-/utils-0.1.12.tgz", - "integrity": "sha512-llHhD1FAVwFaaHzs7PU0BZYTpNLDzTccDWbw+5cj0TiB2NOXZGjPm6l7PJrJwN/yUuPDxOHip/3I+kF6OBkBAg==", - "peer": true - }, - "@types/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "dev": true - }, - "@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true - }, - "@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true - }, - "@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", - "dev": true, - "peer": true - }, - "acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=", - "peer": true - }, - "at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "peer": true - }, - "balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true - }, - "brace-expansion": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", - "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", - "dev": true, - "requires": { - "balanced-match": "^4.0.2" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "peer": true - }, - "busboy": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", - "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", - "peer": true, - "requires": { - "dicer": "0.2.5", - "readable-stream": "1.1.x" - } - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "peer": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "peer": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "peer": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "peer": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "peer": true - }, - "cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "requires": { - "ms": "^2.1.3" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "dicer": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", - "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", - "peer": true, - "requires": { - "readable-stream": "1.1.x", - "streamsearch": "0.1.2" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "peer": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", - "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.6.0", - "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.2", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true - }, - "espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", - "dev": true, - "requires": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" - } - } - } - }, - "eslint-config-nodebb": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/eslint-config-nodebb/-/eslint-config-nodebb-2.0.3.tgz", - "integrity": "sha512-dJJREiapn4zXEb0MidVIxS8+qoU5f+a+3cnk0pA4L7AFTd6xxL7hTDKuMmrjyqIB4Hb0Kz/rCyr0F3CVcCwIjg==", - "dev": true, - "requires": { - "globals": "17.3.0" - } - }, - "eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", - "dev": true, - "requires": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true - }, - "espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "peer": true, - "requires": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "peer": true - } - } - }, - "esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "peer": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "requires": { - "flat-cache": "^4.0.0" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "requires": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - } - }, - "flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true - }, - "fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "peer": true, - "requires": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", - "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "peer": true - }, - "ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "peer": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "peer": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "peer": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" - } - }, - "keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "requires": { - "json-buffer": "3.0.1" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "peer": true - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "peer": true - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "peer": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "peer": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "requires": { - "brace-expansion": "^5.0.5" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "peer": true - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "peer": true, - "requires": { - "minimist": "^1.2.6" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "multer": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4.tgz", - "integrity": "sha512-2wY2+xD4udX612aMqMcB8Ws2Voq6NIUPEtD1be6m411T4uDH/VtL9i//xvcyFlTVfRdaBsk7hV5tgrGQqhuBiw==", - "peer": true, - "requires": { - "append-field": "^1.0.0", - "busboy": "^0.2.11", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "on-finished": "^2.3.0", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "nodebb-plugin-emoji": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/nodebb-plugin-emoji/-/nodebb-plugin-emoji-5.0.10.tgz", - "integrity": "sha512-pqJdI+ksnaVBjn0MDpceYjcXvB/mFBlQS0d+TxOPuK2IsU+tNCgX+bSfd6zai5YbT+iFHU5KH8cw3jOMcgnnhQ==", - "peer": true, - "requires": { - "@textcomplete/core": "^0.1.12", - "@textcomplete/textarea": "^0.1.12", - "fs-extra": "^9.1.0", - "lodash": "^4.17.21", - "multer": "^1.4.2" - } - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "peer": true - }, - "on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "peer": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "peer": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "peer": true - }, - "punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "peer": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "peer": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", - "peer": true - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "peer": true - }, - "textarea-caret": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz", - "integrity": "sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q==", - "peer": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "peer": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "peer": true - }, - "undate": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/undate/-/undate-0.3.0.tgz", - "integrity": "sha512-ssH8QTNBY6B+2fRr3stSQ+9m2NT8qTaun3ExTx5ibzYQvP7yX4+BnX0McNxFCvh6S5ia/DYu6bsCKQx/U4nb/Q==", - "peer": true - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "peer": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "peer": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "peer": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true - } } } From 40bab364ba609973e177a9c6c4dcabdfdcf8158a Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 1 Sep 2026 15:36:02 +0000 Subject: [PATCH 5/5] fix(reactions): handle bare URL objects in resolveReactionPost and check applyEmojiReact return value - resolveReactionPost now normalizes its object parameter: if it is a bare URL string (as in some Undo(Like) payloads), use it directly instead of accessing .id which caused TypeError. - handleLike only claims the activity when applyEmojiReact returns true; silent failures (unresolvable emoji/post) now fall through to core inbox.like instead of being consumed. - handleAnnounce similarly checks applyEmojiReact return before claiming. - Add test: Undo(Like) with bare-URL object does not crash. Assisted-by: One of unsloth/Qwen3.6-35B-A3B-GGUF or unsloth/Qwen3.8-27B-GGUF --- library.js | 13 ++++++++++--- test/index.js | 23 +++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/library.js b/library.js index 60c7768..7455b66 100644 --- a/library.js +++ b/library.js @@ -464,18 +464,25 @@ ReactionsPlugin.rescindPostReaction = async function (pid, uid, reaction) { * Resolve the (local or remote) post referenced by an EmojiReact activity. * Returns a pid for local posts, the note URL for remote posts, or null when * the post cannot be found. + * Handles both full objects (object.id) and bare URL strings. */ async function resolveReactionPost(object) { + // Normalize: bare URL string or { id: '...' } + const objectUrl = typeof object === 'string' ? object : (object?.id || null); + if (!objectUrl) { + return null; + } + let id; let exists; - if (object.id.startsWith(nconf.get('url'))) { - const { type, id: localId } = await activitypub.helpers.resolveLocalId(object.id); + if (objectUrl.startsWith(nconf.get('url'))) { + const { type, id: localId } = await activitypub.helpers.resolveLocalId(objectUrl); if (type === 'post') { id = localId; exists = await posts.exists(id); } } else { - id = object.id; + id = objectUrl; exists = await posts.exists(id); if (!exists) { // Proactively pull in the note diff --git a/test/index.js b/test/index.js index 9d8a1a8..d2a15b0 100644 --- a/test/index.js +++ b/test/index.js @@ -351,6 +351,29 @@ describe('ActivityPub (FEP-c0e0)', () => { const { upvoted: stillUpvoted } = await posts.hasVoted(postData.pid, remoteActor); assert.strictEqual(stillUpvoted, false); }); + + it('should handle Undo(Like) when object is a bare URL (not a full activity)', async () => { + // Some senders send the object as a bare URL instead of the full Like activity + const likeRes = mockRes(); + await controllers.activitypub.postInbox({ + body: { ...reactionActivity({ type: 'Like' }) }, + }, likeRes); + assert.strictEqual(likeRes.statusCode, 202); + + // Undo with a bare URL as the object — core inbox.undo normalizes this + // but the plugin's handleUndo fires first and must not crash + const undoRes = mockRes(); + await controllers.activitypub.postInbox({ + body: { + id: `https://example.org/activity/${utils.generateUUID()}`, + type: 'Undo', + actor: remoteActor, + object: `${nconf.get('url')}/post/${postData.pid}`, + }, + }, undoRes); + // The plugin can't claim this (missing inner activity content), core handles it + assert.strictEqual(undoRes.statusCode, 202); + }); }); describe('Announce', () => {