From 6e8fe2b558cded52f67bbbd66b3a6c0c27ea9f50 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sat, 25 Jul 2026 21:26:40 +0000 Subject: [PATCH] refactor: wire most site and users admin views --- CLAUDE.md | 36 +- backend/api/authentication.ts | 83 +- backend/api/blocks.ts | 194 ++ backend/api/groups.ts | 100 +- backend/api/index.ts | 4 + backend/api/mail.ts | 144 + backend/api/schemas/block.ts | 42 + backend/api/schemas/mail.ts | 68 + backend/api/schemas/site.ts | 104 +- backend/api/schemas/user.ts | 67 +- backend/api/sites.ts | 244 +- backend/api/users.ts | 559 +++- backend/index.ts | 14 +- backend/locales/en.json | 34 + backend/models/blocks.ts | 191 ++ backend/models/groups.ts | 32 + backend/models/index.ts | 2 + backend/models/sites.ts | 92 +- backend/models/users.ts | 397 ++- blocks/block-index/component.js | 11 + blocks/block-media-player/component.js | 11 + blocks/rollup.config.mjs | 69 + frontend/.oxlintrc.json | 3 +- frontend/.vscode/extensions.json | 14 - frontend/package-lock.json | 2664 ++++++----------- frontend/package.json | 4 + frontend/src/boot/temporal.js | 14 + .../EditorMarkdownConfigOverlay.vue | 105 +- frontend/src/components/GroupCreateDialog.vue | 35 +- frontend/src/components/GroupDeleteDialog.vue | 36 +- frontend/src/components/GroupEditOverlay.vue | 213 +- .../src/components/SiteActivateDialog.vue | 4 +- .../src/components/UserChangePwdDialog.vue | 46 +- frontend/src/components/UserCreateDialog.vue | 85 +- frontend/src/components/UserDefaultsMenu.vue | 70 +- frontend/src/components/UserEditOverlay.vue | 120 +- frontend/src/components/UserSearchDialog.vue | 208 ++ frontend/src/main.js | 4 + frontend/src/pages/AdminBlocks.vue | 108 +- frontend/src/pages/AdminEditors.vue | 100 +- frontend/src/pages/AdminGeneral.vue | 125 +- frontend/src/pages/AdminGroups.vue | 20 +- frontend/src/pages/AdminLocale.vue | 131 +- frontend/src/pages/AdminLogin.vue | 178 +- frontend/src/pages/AdminMail.vue | 151 +- frontend/src/pages/AdminSites.vue | 1 - frontend/src/pages/AdminTheme.vue | 122 +- frontend/src/pages/AdminUsers.vue | 71 +- frontend/vite.config.js | 20 +- 49 files changed, 4123 insertions(+), 3027 deletions(-) create mode 100644 backend/api/blocks.ts create mode 100644 backend/api/mail.ts create mode 100644 backend/api/schemas/block.ts create mode 100644 backend/api/schemas/mail.ts create mode 100644 backend/models/blocks.ts delete mode 100644 frontend/.vscode/extensions.json create mode 100644 frontend/src/boot/temporal.js create mode 100644 frontend/src/components/UserSearchDialog.vue diff --git a/CLAUDE.md b/CLAUDE.md index 1f71bd737..996d4dc96 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,7 +73,8 @@ Quasar app on plain Vite (not Quasar CLI). `src/main.js` wires it up manually: r - `src/boot/` — one-time app initializers: `api.js` (creates the `ky` client with JWT refresh, exposed as the `API_CLIENT` global), `components.js` (global components), `eventbus.js` (`EVENT_BUS` global, - mitt), `externals.js`, `i18n.js`, `monaco.js`. + mitt), `externals.js`, `i18n.js`, `monaco.js`, `temporal.js` (conditionally polyfills `Temporal`, + awaited before anything else in `main.js`). - `src/router/` — `index.js` (router factory) and `routes.js` (the full route table; page components are lazily imported). - `src/layouts/` — `MainLayout`, `AdminLayout`, `AuthLayout`, `ProfileLayout`. @@ -210,12 +211,31 @@ the standard-style space before parens (`function initializeRouter ()`); new and be oxfmt-formatted, but don't reformat untouched files as drive-by changes. Each workspace has its own `.oxlintrc.json` — the backend declares the `WIKI` global and node env; -the frontend adds the `vue` plugin and the `API_CLIENT` / `EVENT_BUS` globals. Only the `correctness` -category is an error. +the frontend adds the `vue` plugin and the `API_CLIENT` / `EVENT_BUS` / `Temporal` globals. Only the +`correctness` category is an error. Both tools handle `.ts` with no extra configuration, and the backend's oxlint config already enables the `typescript` plugin. oxlint does not type-check — run `npm run typecheck` for that. +### Utilities and dates + +These apply to **every workspace**, `frontend/` included — not just the backend. + +- **Use `es-toolkit`, not `lodash-es`.** Installed in both `backend/` and `frontend/`. +- **Use the native `Temporal` API, not luxon.** See [Backend patterns](#backend-patterns) for the + Temporal gotchas worth knowing; they apply on the frontend too. +- **luxon and lodash-es are being removed entirely.** The migration is gradual: when you touch a file + that imports either one, convert that file's usages as part of the same change — but don't sweep + through untouched files as a drive-by. Once the last usage is gone, both dependencies get dropped. +- Prefer real es-toolkit subpath exports (`es-toolkit/object`, `es-toolkit/array`, + `es-toolkit/predicate`) over `es-toolkit/compat`. Two lodash helpers are compat-only and have direct + equivalents: `defaultsDeep(source, defaults)` → `toMerged(defaults, source)` (note the argument + order flips) and `toSafeInteger(x)` → `Number.parseInt(x, 10)`. +- On the frontend `Temporal` is a global, declared in `.oxlintrc.json`. `src/boot/temporal.js` + dynamically imports `temporal-polyfill` for browsers without native support (Safari, as of + mid-2026) and is awaited first in `main.js`. The polyfill is a lazy chunk (~21 kB gzipped) that + browsers with native `Temporal` never download. + ### Backend patterns - **The `WIKI` global.** Set up in `index.ts`, typed in `types/global.d.ts`, available everywhere @@ -236,10 +256,8 @@ the `typescript` plugin. oxlint does not type-check — run `npm run typecheck` failures into `{ ok, error, statusCode, message }` JSON. - **Schema changes**: edit `db/schema.ts`, then `npm run db-generate` and commit the generated migration. Never hand-edit an existing migration. -- Prefer **es-toolkit** over lodash on the backend. -- **Dates use the native `Temporal` API**, not luxon (which is no longer a backend dependency — - `frontend/` still uses it). `Temporal` is a global in Node 26 and is typed by the TS 7 lib, so it - needs no import. Three things to know: +- **Dates use the native `Temporal` API**, not luxon (no longer a backend dependency). `Temporal` is a + global in Node 26 and is typed by the TS 7 lib, so it needs no import. Four things to know: - `Temporal.Instant` accepts **exact time units only** — `add({ days: 1 })` throws. Since these are all UTC instants, use `{ hours: 24 }`. - Temporal types have no `valueOf`, so `a < b` **throws**. Compare with @@ -260,7 +278,9 @@ the `typescript` plugin. oxlint does not type-check — run `npm run typecheck` config, so no import needed) — e.g. `await API_CLIENT.get('sites').json()`. It handles the `/_api` prefix and JWT refresh. - Cross-component messaging uses the `EVENT_BUS` global (mitt). -- State lives in Pinia option stores; `lodash-es` is the utility library here. +- State lives in Pinia option stores. For utilities and dates use `es-toolkit` and `Temporal` — see + [Utilities and dates](#utilities-and-dates); the `lodash-es` and `luxon` still present in older + files are on their way out. ### GraphQL is being removed diff --git a/backend/api/authentication.ts b/backend/api/authentication.ts index f8e1b663e..94f0c14b7 100644 --- a/backend/api/authentication.ts +++ b/backend/api/authentication.ts @@ -12,6 +12,8 @@ async function routes(app: FastifyInstance) { { schema: { summary: 'List all site authentication strategies', + description: + 'Ordered by the position configured for the site. `activeStrategy` holds the per-instance settings, nested under it `strategy` holds the module definition.', tags: ['Authentication'], params: { type: 'object', @@ -20,7 +22,8 @@ async function routes(app: FastifyInstance) { type: 'string', format: 'uuid' } - } + }, + required: ['siteId'] }, querystring: { type: 'object', @@ -30,6 +33,61 @@ async function routes(app: FastifyInstance) { default: false } } + }, + response: { + 200: { + description: 'List of site authentication strategies', + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'uuid' + }, + order: { + type: 'integer' + }, + isVisible: { + type: 'boolean' + }, + activeStrategy: { + type: 'object', + properties: { + displayName: { + type: 'string' + }, + registration: { + type: 'boolean' + }, + strategy: { + type: 'object', + properties: { + key: { + type: 'string' + }, + title: { + type: 'string' + }, + icon: { + type: 'string' + }, + color: { + type: 'string' + }, + useForm: { + type: 'boolean' + }, + usernameType: { + type: 'string' + } + } + } + } + } + } + } + } } } }, @@ -39,19 +97,28 @@ async function routes(app: FastifyInstance) { return reply.badRequest('Invalid Site ID') } const activeStrategies = await WIKI.models.authentication.getStrategies({ enabledOnly: true }) + // -> A site created before it had strategies configured has no list at all + const configuredStrategies = site.config.authStrategies ?? [] const siteStrategies = activeStrategies .map((str: any) => { const authModule = WIKI.data.authentication.find((m: any) => m.key === str.module) - const siteStr = site.config.authStrategies.find((s: any) => s.id === str.id) || {} + const siteStr = configuredStrategies.find((s: any) => s.id === str.id) || {} return { id: str.id, - displayName: str.displayName, - useForm: authModule.useForm, - usernameType: authModule.usernameType, - color: authModule.color, - icon: authModule.icon, order: siteStr.order ?? 0, - isVisible: siteStr.isVisible ?? false + isVisible: siteStr.isVisible ?? false, + activeStrategy: { + displayName: str.displayName, + registration: str.registration, + strategy: { + key: authModule?.key ?? str.module, + title: authModule?.title ?? str.module, + icon: authModule?.icon ?? '', + color: authModule?.color ?? 'primary', + useForm: authModule?.useForm ?? false, + usernameType: authModule?.usernameType ?? 'email' + } + } } }) .sort((a: any, b: any) => a.order - b.order) diff --git a/backend/api/blocks.ts b/backend/api/blocks.ts new file mode 100644 index 000000000..54179e5bb --- /dev/null +++ b/backend/api/blocks.ts @@ -0,0 +1,194 @@ +import type { FastifyInstance } from 'fastify' + +/** + * Blocks API Routes + */ +async function routes(app: FastifyInstance) { + /** + * LIST SITE BLOCKS + */ + app.get<{ Params: { siteId: string } }>( + '/sites/:siteId/blocks', + { + config: { + permissions: ['read:sites', 'manage:sites'] + }, + schema: { + summary: 'List the blocks available to a site', + description: + 'Built-in blocks are registered from the compiled block manifest, so the list reflects what is actually installed.', + tags: ['Blocks'], + params: { + type: 'object', + properties: { + siteId: { + type: 'string', + format: 'uuid' + } + }, + required: ['siteId'] + }, + response: { + 200: { + description: 'List of site blocks', + type: 'array', + items: { $ref: 'Block#' } + } + } + } + }, + async (req, reply) => { + const site = await WIKI.models.sites.getSiteById({ id: req.params.siteId }) + if (!site) { + return reply.notFound('Site does not exist.') + } + return WIKI.models.blocks.getSiteBlocks(req.params.siteId) + } + ) + + /** + * SET SITE BLOCKS STATE + */ + app.put<{ + Params: { siteId: string } + Body: { states: { id: string; isEnabled: boolean }[] } + }>( + '/sites/:siteId/blocks', + { + config: { + permissions: ['manage:sites'] + }, + schema: { + summary: 'Enable or disable site blocks', + description: 'Only the blocks listed are affected; any others keep their current state.', + tags: ['Blocks'], + params: { + type: 'object', + properties: { + siteId: { + type: 'string', + format: 'uuid' + } + }, + required: ['siteId'] + }, + body: { + type: 'object', + required: ['states'], + properties: { + states: { + type: 'array', + items: { + type: 'object', + required: ['id', 'isEnabled'], + properties: { + id: { + type: 'string', + format: 'uuid' + }, + isEnabled: { + type: 'boolean' + } + } + } + } + } + }, + response: { + 200: { + description: 'Blocks state updated successfully', + type: 'object', + properties: { + ok: { + type: 'boolean' + }, + message: { + type: 'string' + }, + updated: { + type: 'integer', + description: + 'How many block rows were written. A block already in the requested state still counts.' + } + } + } + } + } + }, + async (req, reply) => { + const site = await WIKI.models.sites.getSiteById({ id: req.params.siteId }) + if (!site) { + return reply.notFound('Site does not exist.') + } + + try { + const updated = await WIKI.models.blocks.setBlocksState(req.params.siteId, req.body.states) + return { + ok: true, + message: 'Blocks state updated successfully.', + updated + } + } catch (err: any) { + WIKI.logger.warn(err) + return reply.internalServerError() + } + } + ) + + /** + * DELETE CUSTOM BLOCK + */ + app.delete<{ Params: { siteId: string; blockId: string } }>( + '/sites/:siteId/blocks/:blockId', + { + config: { + permissions: ['manage:sites'] + }, + schema: { + summary: 'Delete a custom block', + description: + 'Only custom blocks can be deleted. Built-in blocks are registered from disk and would reappear on the next sync.', + tags: ['Blocks'], + params: { + type: 'object', + properties: { + siteId: { + type: 'string', + format: 'uuid' + }, + blockId: { + type: 'string', + format: 'uuid' + } + }, + required: ['siteId', 'blockId'] + }, + response: { + 204: { + description: 'Block deleted successfully' + } + } + } + }, + async (req, reply) => { + const site = await WIKI.models.sites.getSiteById({ id: req.params.siteId }) + if (!site) { + return reply.notFound('Site does not exist.') + } + + const siteBlocks = await WIKI.models.blocks.getSiteBlocks(req.params.siteId) + const block = siteBlocks.find((b) => b.id === req.params.blockId) + if (!block) { + return reply.notFound('Block does not exist.') + } + if (!block.isCustom) { + return reply.conflict('Cannot delete a built-in block.') + } + + await WIKI.models.blocks.deleteCustomBlock(req.params.siteId, req.params.blockId) + return reply.code(204).send() + } + ) +} + +export default routes diff --git a/backend/api/groups.ts b/backend/api/groups.ts index b30ed302b..e199ea9af 100644 --- a/backend/api/groups.ts +++ b/backend/api/groups.ts @@ -41,6 +41,71 @@ async function routes(app: FastifyInstance) { } ) + /** + * CREATE GROUP + */ + app.post<{ Body: { name: string } }>( + '/', + { + config: { + permissions: ['write:groups', 'manage:groups'] + }, + schema: { + summary: 'Create a new group', + description: + 'Creates a non-system group, seeded with the same starting permissions and default rule as the built-in `Users` group.', + tags: ['Groups'], + body: { + type: 'object', + required: ['name'], + properties: { + name: { + type: 'string', + minLength: 1, + maxLength: 255 + } + }, + examples: [{ name: 'Editors' }] + }, + response: { + 200: { + description: 'Group created successfully', + type: 'object', + properties: { + ok: { + type: 'boolean' + }, + message: { + type: 'string' + }, + id: { + type: 'string', + format: 'uuid' + } + } + } + } + } + }, + async (req, reply) => { + if (!/^[^<>"]+$/.test(req.body.name)) { + throw new CustomError('groupCreateInvalidName', 'Invalid Group Name') + } + + try { + const id = await WIKI.models.groups.createGroup(req.body.name) + return { + ok: true, + message: 'Group created successfully.', + id + } + } catch (err: any) { + WIKI.logger.warn(err) + return reply.internalServerError() + } + } + ) + /** * GET SINGLE GROUP */ @@ -192,12 +257,18 @@ async function routes(app: FastifyInstance) { } // -> The root administrators group must keep its permissions, or the instance becomes - // unmanageable with no way to grant `manage:system` back. + // unmanageable with no way to grant `manage:system` back. Resending the current set is + // allowed, so that a client editing other fields can still submit the whole group. if (patch.permissions && group.id === WIKI.config.auth.rootAdminGroupId) { - throw new CustomError( - 'groupUpdateRootAdminPermissions', - 'Cannot modify the permissions of the root administrators group.' - ) + const isUnchanged = + patch.permissions.length === group.permissions.length && + patch.permissions.every((p) => group.permissions.includes(p)) + if (!isUnchanged) { + throw new CustomError( + 'groupUpdateRootAdminPermissions', + 'Cannot modify the permissions of the root administrators group.' + ) + } } // -> Rule IDs must be unique within the group, as they address the rule client-side @@ -355,6 +426,8 @@ async function routes(app: FastifyInstance) { }, schema: { summary: 'Assign a user to a group', + description: + 'System users (the guest account) cannot be assigned: their group membership is fixed at install time.', tags: ['Groups'], params: { type: 'object', @@ -391,9 +464,15 @@ async function routes(app: FastifyInstance) { if (!group) { return reply.notFound('Group does not exist.') } - if (!(await WIKI.models.users.getById(req.params.userId))) { + const user = await WIKI.models.users.getById(req.params.userId) + if (!user) { return reply.notFound('User does not exist.') } + // -> The guest account is the only system user, and it must stay in the guests group alone: + // its permissions are what anonymous visitors get. + if (user.isSystem) { + return reply.conflict('Cannot assign a system user to a group.') + } const assigned = await WIKI.models.groups.assignUserToGroup(group.id, req.params.userId) if (!assigned) { @@ -419,7 +498,7 @@ async function routes(app: FastifyInstance) { schema: { summary: 'Unassign a user from a group', description: - 'Removes the user from the group. The last remaining user cannot be removed from the root administrators group.', + 'Removes the user from the group. The last remaining user cannot be removed from the root administrators group, and system users (the guest account) cannot be unassigned at all.', tags: ['Groups'], params: { type: 'object', @@ -451,6 +530,13 @@ async function routes(app: FastifyInstance) { return reply.notFound('User is not assigned to this group.') } + // -> Removing the guest account from the guests group would strip anonymous visitors of the + // permissions that group carries, with no way to put it back + const user = await WIKI.models.users.getById(req.params.userId) + if (user?.isSystem) { + return reply.conflict('Cannot unassign a system user from a group.') + } + // -> Emptying the root administrators group would lock everyone out of system management if (group.id === WIKI.config.auth.rootAdminGroupId) { if ((await WIKI.models.groups.countUsersInGroup(group.id)) <= 1) { diff --git a/backend/api/index.ts b/backend/api/index.ts index 3c2a8525b..5775dc714 100644 --- a/backend/api/index.ts +++ b/backend/api/index.ts @@ -5,14 +5,18 @@ import type { FastifyInstance } from 'fastify' */ async function routes(app: FastifyInstance) { // Register schemas + await import('./schemas/block.ts').then((m) => m.registerSchemas(app)) await import('./schemas/group.ts').then((m) => m.registerSchemas(app)) + await import('./schemas/mail.ts').then((m) => m.registerSchemas(app)) await import('./schemas/site.ts').then((m) => m.registerSchemas(app)) await import('./schemas/user.ts').then((m) => m.registerSchemas(app)) // Register routes app.register(import('./authentication.ts')) + app.register(import('./blocks.ts')) app.register(import('./groups.ts'), { prefix: '/groups' }) app.register(import('./locales.ts'), { prefix: '/locales' }) + app.register(import('./mail.ts'), { prefix: '/mail' }) app.register(import('./pages.ts')) app.register(import('./sites.ts'), { prefix: '/sites' }) app.register(import('./system.ts'), { prefix: '/system' }) diff --git a/backend/api/mail.ts b/backend/api/mail.ts new file mode 100644 index 000000000..1d646e54e --- /dev/null +++ b/backend/api/mail.ts @@ -0,0 +1,144 @@ +import type { FastifyInstance } from 'fastify' + +/** + * Placeholder sent to the client in place of the stored SMTP password. Sending it back unchanged + * leaves the stored password alone. + */ +const PASSWORD_MASK = '********' + +/** + * Mail settings, stored as the `mail` key of the settings table. + */ +const MAIL_CONFIG_KEYS = [ + 'senderName', + 'senderEmail', + 'defaultBaseURL', + 'host', + 'port', + 'name', + 'secure', + 'verifySSL', + 'user', + 'pass', + 'useDKIM', + 'dkimDomainName', + 'dkimKeySelector', + 'dkimPrivateKey' +] as const + +/** + * Mail API Routes + */ +async function routes(app: FastifyInstance) { + /** + * GET MAIL CONFIG + */ + app.get( + '/config', + { + config: { + permissions: ['manage:system'] + }, + schema: { + summary: 'Get mail configuration', + tags: ['Mail'], + response: { + 200: { + description: 'Mail configuration', + type: 'object', + $ref: 'MailConfig#' + } + } + } + }, + async () => { + return { + ...WIKI.config.mail, + pass: WIKI.config.mail?.pass?.length > 0 ? PASSWORD_MASK : '' + } + } + ) + + /** + * UPDATE MAIL CONFIG + */ + app.put<{ + Body: { + senderName?: string + senderEmail?: string + defaultBaseURL?: string + host?: string + port?: number + name?: string + secure?: boolean + verifySSL?: boolean + user?: string + pass?: string + useDKIM?: boolean + dkimDomainName?: string + dkimKeySelector?: string + dkimPrivateKey?: string + } + }>( + '/config', + { + config: { + permissions: ['manage:system'] + }, + schema: { + summary: 'Update mail configuration', + tags: ['Mail'], + body: { + $ref: 'MailConfig#' + }, + response: { + 200: { + description: 'Mail configuration updated successfully', + type: 'object', + properties: { + ok: { + type: 'boolean' + }, + message: { + type: 'string' + } + } + } + } + } + }, + async (req, reply) => { + const patch: Record = {} + for (const key of MAIL_CONFIG_KEYS) { + if (req.body[key] !== undefined) { + patch[key] = req.body[key] + } + } + + // -> Base URLs are used to build links in emails, always without a trailing slash + if (typeof patch.defaultBaseURL === 'string') { + patch.defaultBaseURL = patch.defaultBaseURL.replace(/\/+$/, '') + } + + // -> The client only ever receives a masked password, so an unchanged one must not be stored + if (patch.pass === PASSWORD_MASK) { + delete patch.pass + } + + const previousConfig = WIKI.config.mail + WIKI.config.mail = { ...previousConfig, ...patch } + + if (!(await WIKI.configSvc.saveToDb(['mail']))) { + WIKI.config.mail = previousConfig + return reply.internalServerError('Failed to save mail configuration.') + } + + return { + ok: true, + message: 'Mail configuration updated successfully.' + } + } + ) +} + +export default routes diff --git a/backend/api/schemas/block.ts b/backend/api/schemas/block.ts new file mode 100644 index 000000000..3c40958bb --- /dev/null +++ b/backend/api/schemas/block.ts @@ -0,0 +1,42 @@ +import type { FastifyInstance } from 'fastify' + +export async function registerSchemas(app: FastifyInstance): Promise { + /** + * BLOCK + */ + app.addSchema({ + $id: 'Block', + type: 'object', + properties: { + id: { + type: 'string', + format: 'uuid' + }, + block: { + type: 'string', + description: 'Element suffix — the block renders as ``.' + }, + name: { + type: 'string' + }, + description: { + type: 'string' + }, + icon: { + type: 'string', + description: 'Blueprint icon name, resolved as `/_assets/icons/ultraviolet-{icon}.svg`.' + }, + isEnabled: { + type: 'boolean' + }, + isCustom: { + type: 'boolean', + description: 'False for blocks registered from the compiled block manifest.' + }, + config: { + type: 'object', + additionalProperties: true + } + } + }) +} diff --git a/backend/api/schemas/mail.ts b/backend/api/schemas/mail.ts new file mode 100644 index 000000000..15b89ddd9 --- /dev/null +++ b/backend/api/schemas/mail.ts @@ -0,0 +1,68 @@ +import type { FastifyInstance } from 'fastify' + +export async function registerSchemas(app: FastifyInstance): Promise { + /** + * MAIL CONFIG + */ + app.addSchema({ + $id: 'MailConfig', + type: 'object', + properties: { + senderName: { + type: 'string', + maxLength: 255 + }, + senderEmail: { + type: 'string', + maxLength: 255 + }, + defaultBaseURL: { + type: 'string', + maxLength: 255 + }, + host: { + type: 'string', + maxLength: 255 + }, + port: { + type: 'integer', + minimum: 1, + maximum: 65535 + }, + name: { + type: 'string', + maxLength: 255 + }, + secure: { + type: 'boolean' + }, + verifySSL: { + type: 'boolean' + }, + user: { + type: 'string', + maxLength: 255 + }, + pass: { + type: 'string', + description: + 'Returned masked as `********` when a password is stored. Send the masked value back unchanged to keep the stored password.', + maxLength: 255 + }, + useDKIM: { + type: 'boolean' + }, + dkimDomainName: { + type: 'string', + maxLength: 255 + }, + dkimKeySelector: { + type: 'string', + maxLength: 255 + }, + dkimPrivateKey: { + type: 'string' + } + } + }) +} diff --git a/backend/api/schemas/site.ts b/backend/api/schemas/site.ts index dbd6e687d..567cf679c 100644 --- a/backend/api/schemas/site.ts +++ b/backend/api/schemas/site.ts @@ -84,11 +84,27 @@ export async function registerSchemas(app: FastifyInstance): Promise { profile: { type: 'boolean' }, + reasonForChange: { + type: 'string', + enum: ['off', 'optional', 'required'] + }, search: { type: 'boolean' } } }, + uploads: { + type: 'object', + properties: { + conflictBehavior: { + type: 'string', + enum: ['overwrite', 'reject', 'new'] + }, + normalizeFilename: { + type: 'boolean' + } + } + }, logoUrl: { type: 'string' }, @@ -109,6 +125,53 @@ export async function registerSchemas(app: FastifyInstance): Promise { } } }, + auth: { + type: 'object', + description: 'Login experience for this site. Redirects can be overridden per group.', + properties: { + autoLogin: { + type: 'boolean' + }, + bypassUnauthorized: { + type: 'boolean' + }, + hideLocal: { + type: 'boolean' + }, + loginRedirect: { + type: 'string', + maxLength: 255 + }, + welcomeRedirect: { + type: 'string', + maxLength: 255 + }, + logoutRedirect: { + type: 'string', + maxLength: 255 + } + } + }, + authStrategies: { + type: 'array', + description: 'Which authentication strategies this site offers, in display order.', + items: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'uuid' + }, + order: { + type: 'integer', + minimum: 0 + }, + isVisible: { + type: 'boolean' + } + } + } + }, locales: { type: 'object', properties: { @@ -120,6 +183,9 @@ export async function registerSchemas(app: FastifyInstance): Promise { items: { type: 'string' } + }, + forcePrefix: { + type: 'boolean' } } }, @@ -145,15 +211,44 @@ export async function registerSchemas(app: FastifyInstance): Promise { }, editors: { type: 'object', + description: + 'Per-editor state. `config` is free-form and specific to each editor implementation.', properties: { asciidoc: { - type: 'boolean' + type: 'object', + properties: { + isActive: { + type: 'boolean' + }, + config: { + type: 'object', + additionalProperties: true + } + } }, markdown: { - type: 'boolean' + type: 'object', + properties: { + isActive: { + type: 'boolean' + }, + config: { + type: 'object', + additionalProperties: true + } + } }, wysiwyg: { - type: 'boolean' + type: 'object', + properties: { + isActive: { + type: 'boolean' + }, + config: { + type: 'object', + additionalProperties: true + } + } } } }, @@ -165,7 +260,8 @@ export async function registerSchemas(app: FastifyInstance): Promise { }, codeBlocksTheme: { type: 'string', - format: 'hexcolor' + description: 'Name of a highlight.js stylesheet, e.g. `github-dark`.', + maxLength: 255 }, colorPrimary: { type: 'string', diff --git a/backend/api/schemas/user.ts b/backend/api/schemas/user.ts index 0f2677334..d7af73b84 100644 --- a/backend/api/schemas/user.ts +++ b/backend/api/schemas/user.ts @@ -56,6 +56,30 @@ export async function registerSchemas(app: FastifyInstance): Promise { } }) + /** + * USER DEFAULTS - Instance-wide defaults applied to new users + */ + app.addSchema({ + $id: 'UserDefaults', + type: 'object', + properties: { + timezone: { + type: 'string', + description: 'IANA time zone name, e.g. `America/New_York`.', + maxLength: 255 + }, + dateFormat: { + type: 'string', + description: 'Empty string means the locale default.', + enum: ['', 'DD/MM/YYYY', 'DD.MM.YYYY', 'MM/DD/YYYY', 'YYYY-MM-DD', 'YYYY/MM/DD'] + }, + timeFormat: { + type: 'string', + enum: ['12h', '24h'] + } + } + }) + /** * USER - All fields */ @@ -77,10 +101,47 @@ export async function registerSchemas(app: FastifyInstance): Promise { additionalProperties: true }, auth: { - type: 'string' + type: 'array', + description: + 'Authentication providers linked to this user. Secrets are never included — `config.isPasswordSet` and `config.tfaIsActive` report their state instead.', + items: { + type: 'object', + properties: { + authId: { + type: 'string', + format: 'uuid' + }, + authName: { + type: 'string' + }, + strategyKey: { + type: 'string' + }, + strategyIcon: { + type: 'string' + }, + config: { + type: 'object', + additionalProperties: true + } + } + } }, - passkeys: { - type: 'string' + groups: { + type: 'array', + description: 'Groups this user belongs to.', + items: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'uuid' + }, + name: { + type: 'string' + } + } + } } } } diff --git a/backend/api/sites.ts b/backend/api/sites.ts index c3011ec90..5229905d2 100644 --- a/backend/api/sites.ts +++ b/backend/api/sites.ts @@ -2,6 +2,32 @@ import { validate as uuidValidate } from 'uuid' import { CustomError } from '../helpers/common.ts' import type { FastifyInstance } from 'fastify' +/** + * Site properties stored in the `config` JSONB column rather than as their own table column. + * Anything listed here is merged into the existing config on update. + */ +const SITE_CONFIG_KEYS = [ + 'title', + 'description', + 'company', + 'contentLicense', + 'footerExtra', + 'pageExtensions', + 'pageCasing', + 'logoText', + 'sitemap', + 'discoverable', + 'auth', + 'authStrategies', + 'defaults', + 'editors', + 'features', + 'locales', + 'robots', + 'theme', + 'uploads' +] as const + /** * Sites API Routes */ @@ -30,12 +56,7 @@ async function routes(app: FastifyInstance) { ...s.config, id: s.id, hostname: s.hostname, - isEnabled: s.isEnabled, - editors: { - asciidoc: s.config.editors?.asciidoc?.isActive ?? false, - markdown: s.config.editors?.markdown?.isActive ?? false, - wysiwyg: s.config.editors?.wysiwyg?.isActive ?? false - } + isEnabled: s.isEnabled })) } ) @@ -101,12 +122,7 @@ async function routes(app: FastifyInstance) { ...site.config, id: site.id, hostname: site.hostname, - isEnabled: site.isEnabled, - editors: { - asciidoc: site.config.editors?.asciidoc?.isActive ?? false, - markdown: site.config.editors?.markdown?.isActive ?? false, - wysiwyg: site.config.editors?.wysiwyg?.isActive ?? false - } + isEnabled: site.isEnabled } } else { return reply.notFound('Site does not exist.') @@ -219,7 +235,29 @@ async function routes(app: FastifyInstance) { */ app.put<{ Params: { siteId: string } - Body: { isEnabled?: boolean; hostname?: string; title?: string } + Body: { + isEnabled?: boolean + hostname?: string + title?: string + description?: string + company?: string + contentLicense?: string + footerExtra?: string + pageExtensions?: string[] + pageCasing?: boolean + logoText?: boolean + sitemap?: boolean + discoverable?: boolean + auth?: Record + authStrategies?: Array<{ id: string; order?: number; isVisible?: boolean }> + defaults?: Record + editors?: Record }> + features?: Record + locales?: { primary?: string; active?: string[]; forcePrefix?: boolean } + robots?: Record + theme?: Record + uploads?: Record + } }>( '/:siteId', { @@ -229,6 +267,16 @@ async function routes(app: FastifyInstance) { schema: { summary: 'Update a site', tags: ['Sites'], + params: { + type: 'object', + properties: { + siteId: { + type: 'string', + format: 'uuid' + } + }, + required: ['siteId'] + }, body: { type: 'object', properties: { @@ -245,6 +293,64 @@ async function routes(app: FastifyInstance) { type: 'string', minLength: 1, maxLength: 255 + }, + description: { + type: 'string' + }, + company: { + type: 'string' + }, + contentLicense: { + type: 'string' + }, + footerExtra: { + type: 'string' + }, + pageExtensions: { + type: 'array', + items: { + type: 'string', + pattern: '^[a-z0-9]+$' + } + }, + pageCasing: { + type: 'boolean' + }, + logoText: { + type: 'boolean' + }, + sitemap: { + type: 'boolean' + }, + discoverable: { + type: 'boolean' + }, + auth: { + $ref: 'Site#/properties/auth' + }, + authStrategies: { + $ref: 'Site#/properties/authStrategies' + }, + defaults: { + $ref: 'Site#/properties/defaults' + }, + editors: { + $ref: 'Site#/properties/editors' + }, + features: { + $ref: 'Site#/properties/features' + }, + locales: { + $ref: 'Site#/properties/locales' + }, + robots: { + $ref: 'Site#/properties/robots' + }, + theme: { + $ref: 'Site#/properties/theme' + }, + uploads: { + $ref: 'Site#/properties/uploads' } }, examples: [ @@ -253,11 +359,111 @@ async function routes(app: FastifyInstance) { title: 'My Wiki Site' } ] + }, + response: { + 200: { + description: 'Site updated successfully', + type: 'object', + properties: { + ok: { + type: 'boolean' + }, + message: { + type: 'string' + } + } + } } } }, - async () => { - return { hello: 'world' } + async (req, reply) => { + // -> Validate inputs + if (req.body.title !== undefined && !/^[^<>"]+$/.test(req.body.title)) { + throw new CustomError('siteUpdateInvalidTitle', 'Invalid Site Title') + } + + const site = await WIKI.models.sites.getSiteById({ id: req.params.siteId }) + if (!site) { + return reply.notFound('Site does not exist.') + } + + // -> Check for duplicate hostname + if ( + req.body.hostname !== undefined && + req.body.hostname !== site.hostname && + !(await WIKI.models.sites.isHostnameUnique(req.body.hostname)) + ) { + if (req.body.hostname === '*') { + throw new CustomError( + 'siteUpdateDuplicateCatchAll', + 'A site with a catch-all hostname already exists! Cannot have 2 catch-all hostnames.' + ) + } else { + throw new CustomError( + 'siteUpdateDuplicateHostname', + 'A site with a this hostname already exists! Cannot have duplicate hostnames.' + ) + } + } + + // -> Validate locales against the installed ones, and against what the site ends up with once + // the patch is merged, so that a partial update cannot leave the primary locale inactive + if (req.body.locales) { + const installedCodes = (await WIKI.models.locales.getLocales()).map((lc: any) => lc.code) + const active = req.body.locales.active ?? site.config.locales?.active ?? [] + const primary = req.body.locales.primary ?? site.config.locales?.primary + + if (active.length < 1) { + throw new CustomError( + 'siteUpdateNoActiveLocale', + 'At least one active locale is required.' + ) + } + const unknownCodes = [...active, primary].filter( + (code) => code && !installedCodes.includes(code) + ) + if (unknownCodes.length > 0) { + throw new CustomError( + 'siteUpdateUnknownLocale', + `Locale is not installed: ${[...new Set(unknownCodes)].join(', ')}` + ) + } + if (!active.includes(primary)) { + throw new CustomError( + 'siteUpdatePrimaryLocaleNotActive', + 'The primary locale must be one of the active locales.' + ) + } + } + + // -> Split the patch between real columns and the config JSONB blob + const config: Record = {} + for (const key of SITE_CONFIG_KEYS) { + if (req.body[key] !== undefined) { + config[key] = req.body[key] + } + } + + // -> Keep the legacy `features.ratings` flag in sync with the ratings mode + if (config.features?.ratingsMode !== undefined) { + config.features.ratings = config.features.ratingsMode !== 'off' + } + + // -> Update site + try { + await WIKI.models.sites.updateSite(req.params.siteId, { + hostname: req.body.hostname, + isEnabled: req.body.isEnabled, + ...(Object.keys(config).length < 1 ? {} : { config }) + }) + return { + ok: true, + message: 'Site updated successfully.' + } + } catch (err: any) { + WIKI.logger.warn(err) + return reply.internalServerError() + } } ) @@ -300,6 +506,14 @@ async function routes(app: FastifyInstance) { reply.badRequest('Site does not exist.') } } catch (err: any) { + // -> Pages, assets, navigation, tags and the page tree all reference the site without a + // cascade, so a site still holding content cannot be removed. That is a conflict to + // report, not a server fault. + if (err.cause?.code === '23503' || err.code === '23503') { + return reply.conflict( + 'Cannot delete a site that still holds content. Delete its pages and assets first.' + ) + } reply.send(err) } } diff --git a/backend/api/users.ts b/backend/api/users.ts index 5c01763a2..4472f915c 100644 --- a/backend/api/users.ts +++ b/backend/api/users.ts @@ -1,10 +1,25 @@ +import { CustomError } from '../helpers/common.ts' import type { FastifyInstance } from 'fastify' +import type { UserPatch } from '../models/users.ts' + +interface UserUpdateBody { + name?: string + email?: string + isActive?: boolean + isVerified?: boolean + meta?: Record + prefs?: Record + groups?: string[] + auth?: Record +} /** * Users API Routes */ async function routes(app: FastifyInstance) { - app.get<{ Querystring: { page?: number; limit?: number } }>( + app.get<{ + Querystring: { page?: number; limit?: number; filter?: string; assignableToGroupId?: string } + }>( '/', { config: { @@ -16,6 +31,17 @@ async function routes(app: FastifyInstance) { querystring: { type: 'object', properties: { + filter: { + type: 'string', + description: 'Matched against the user name and email, case-insensitively.', + maxLength: 255 + }, + assignableToGroupId: { + type: 'string', + format: 'uuid', + description: + 'Keep only the users that may be assigned to this group, i.e. omit its current members and any system user. Intended for pickers offering users to assign.' + }, page: { type: 'integer', minimum: 1, default: 1 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 20 } } @@ -37,8 +63,16 @@ async function routes(app: FastifyInstance) { } } }, - async () => { - return { hello: 'world' } + async (req) => { + const page = req.query.page ?? 1 + const limit = req.query.limit ?? 20 + const { total, users } = await WIKI.models.users.getUsers({ + filter: req.query.filter ?? '', + assignableToGroupId: req.query.assignableToGroupId ?? '', + page, + limit + }) + return { page, limit, total, users } } ) @@ -66,6 +100,104 @@ async function routes(app: FastifyInstance) { } ) + /** + * GET USER DEFAULTS + * + * Instance-wide, not per-site: stored as the `userDefaults` key of the settings table. + */ + app.get( + '/defaults', + { + config: { + permissions: ['read:users', 'manage:users'] + }, + schema: { + summary: 'Get the defaults applied to new users', + tags: ['Users'], + response: { + 200: { + description: 'User defaults', + type: 'object', + $ref: 'UserDefaults#' + } + } + } + }, + async () => { + return WIKI.config.userDefaults + } + ) + + /** + * UPDATE USER DEFAULTS + */ + app.put<{ Body: { timezone?: string; dateFormat?: string; timeFormat?: string } }>( + '/defaults', + { + config: { + permissions: ['manage:users'] + }, + schema: { + summary: 'Update the defaults applied to new users', + description: + 'These are instance-wide, not per-site. Existing users keep their own preferences.', + tags: ['Users'], + body: { + $ref: 'UserDefaults#' + }, + response: { + 200: { + description: 'User defaults updated successfully', + type: 'object', + properties: { + ok: { + type: 'boolean' + }, + message: { + type: 'string' + } + } + } + } + } + }, + async (req, reply) => { + // -> A bad time zone would break every date the affected users see, and the list of valid + // zones is only known at runtime, so it cannot be expressed as a schema enum + if (req.body.timezone !== undefined) { + if (!Intl.supportedValuesOf('timeZone').includes(req.body.timezone)) { + throw new CustomError( + 'userDefaultsInvalidTimezone', + `Not a recognized IANA time zone: ${req.body.timezone}` + ) + } + } + + const patch: Record = {} + for (const key of ['timezone', 'dateFormat', 'timeFormat'] as const) { + if (req.body[key] !== undefined) { + patch[key] = req.body[key] + } + } + if (Object.keys(patch).length < 1) { + throw new CustomError('userDefaultsEmpty', 'No user defaults provided to update.') + } + + const previousDefaults = WIKI.config.userDefaults + WIKI.config.userDefaults = { ...previousDefaults, ...patch } + + if (!(await WIKI.configSvc.saveToDb(['userDefaults']))) { + WIKI.config.userDefaults = previousDefaults + return reply.internalServerError('Failed to save user defaults.') + } + + return { + ok: true, + message: 'User defaults updated successfully.' + } + } + ) + app.get<{ Params: { userId: string } }>( '/:userId', { @@ -74,6 +206,8 @@ async function routes(app: FastifyInstance) { }, schema: { summary: 'Get user info', + description: + 'Returns the user with its group membership and linked authentication providers.', tags: ['Users'], params: { type: 'object', @@ -82,7 +216,8 @@ async function routes(app: FastifyInstance) { type: 'string', format: 'uuid' } - } + }, + required: ['userId'] }, response: { 200: { @@ -93,12 +228,29 @@ async function routes(app: FastifyInstance) { } } }, - async () => { - return { hello: 'world' } + async (req, reply) => { + const user = await WIKI.models.users.getUserDetail(req.params.userId) + if (!user) { + return reply.notFound('User does not exist.') + } + return user } ) - app.post( + /** + * CREATE USER + */ + app.post<{ + Body: { + name: string + email: string + password: string + groups?: string[] + mustChangePassword?: boolean + sendWelcomeEmail?: boolean + sendWelcomeEmailFromSiteId?: string + } + }>( '/', { config: { @@ -106,15 +258,116 @@ async function routes(app: FastifyInstance) { }, schema: { summary: 'Create a new user', - tags: ['Users'] + description: + 'Creates a user authenticated against the local strategy. `sendWelcomeEmail` is accepted but not yet supported, as the server has no mail transport.', + tags: ['Users'], + body: { + type: 'object', + required: ['name', 'email', 'password'], + properties: { + name: { + type: 'string', + minLength: 1, + maxLength: 255 + }, + email: { + type: 'string', + format: 'email', + maxLength: 255 + }, + password: { + type: 'string', + minLength: 8, + maxLength: 255 + }, + groups: { + type: 'array', + items: { + type: 'string', + format: 'uuid' + } + }, + mustChangePassword: { + type: 'boolean', + default: false + }, + sendWelcomeEmail: { + type: 'boolean', + default: false + }, + sendWelcomeEmailFromSiteId: { + type: 'string', + format: 'uuid' + } + }, + examples: [ + { + name: 'Jane Doe', + email: 'jane@example.com', + password: 'a-long-password', + groups: [] + } + ] + }, + response: { + 200: { + description: 'User created successfully', + type: 'object', + properties: { + ok: { + type: 'boolean' + }, + message: { + type: 'string' + }, + id: { + type: 'string', + format: 'uuid' + } + } + } + } } }, - async () => { - return { hello: 'world' } + async (req, reply) => { + if (!/^[^<>"]+$/.test(req.body.name)) { + throw new CustomError('userCreateInvalidName', 'Invalid User Name') + } + if (await WIKI.models.users.getByEmail(req.body.email.toLowerCase())) { + throw new CustomError('userCreateDuplicateEmail', 'A user with this email already exists.') + } + // -> There is no mail transport yet, so accepting this flag would silently drop the request + if (req.body.sendWelcomeEmail) { + throw new CustomError( + 'userCreateWelcomeEmailUnavailable', + 'Sending a welcome email is not supported yet, as mail delivery is not implemented.' + ) + } + + try { + const id = await WIKI.models.users.createUser({ + name: req.body.name, + email: req.body.email, + password: req.body.password, + groups: req.body.groups ?? [], + mustChangePassword: req.body.mustChangePassword ?? false + }) + return { + ok: true, + message: 'User created successfully.', + id + } + } catch (err: any) { + WIKI.logger.warn(err) + return reply.internalServerError() + } } ) - app.put<{ Params: { userId: string } }>( + /** + * UPDATE USER + */ + app.put<{ Params: { userId: string }; Body: UserUpdateBody }>( '/:userId', { config: { @@ -122,11 +375,237 @@ async function routes(app: FastifyInstance) { }, schema: { summary: 'Update a user', - tags: ['Users'] + description: + 'Updates any subset of the user fields. Omitted fields are left unchanged. Passing `groups` replaces the group membership entirely — except for system users (the guest account), whose membership is fixed.', + tags: ['Users'], + params: { + type: 'object', + properties: { + userId: { + type: 'string', + format: 'uuid' + } + }, + required: ['userId'] + }, + body: { + type: 'object', + properties: { + name: { + type: 'string', + minLength: 1, + maxLength: 255 + }, + email: { + type: 'string', + format: 'email', + maxLength: 255 + }, + isActive: { + type: 'boolean' + }, + isVerified: { + type: 'boolean' + }, + meta: { + type: 'object', + additionalProperties: true + }, + prefs: { + type: 'object', + additionalProperties: true + }, + groups: { + type: 'array', + items: { + type: 'string', + format: 'uuid' + } + }, + auth: { + type: 'object', + description: + 'Local-strategy flags: `mustChangePwd`, `restrictLogin`, `tfaRequired`. Secrets cannot be set here — use the password endpoint.', + properties: { + mustChangePwd: { + type: 'boolean' + }, + restrictLogin: { + type: 'boolean' + }, + tfaRequired: { + type: 'boolean' + } + } + } + } + }, + response: { + 200: { + description: 'User updated successfully', + type: 'object', + properties: { + ok: { + type: 'boolean' + }, + message: { + type: 'string' + } + } + } + } } }, - async () => { - return { hello: 'world' } + async (req, reply) => { + const user = await WIKI.models.users.getById(req.params.userId) + if (!user) { + return reply.notFound('User does not exist.') + } + + // -> Collect only the fields actually provided + const patch: UserPatch = {} + for (const key of ['name', 'email', 'isActive', 'isVerified', 'meta', 'prefs'] as const) { + if (req.body[key] !== undefined) { + ;(patch as Record)[key] = req.body[key] + } + } + + if ( + Object.keys(patch).length < 1 && + req.body.groups === undefined && + req.body.auth === undefined + ) { + throw new CustomError('userUpdateEmpty', 'No user fields provided to update.') + } + + // -> Email is unique, so a clash needs a clearer answer than a constraint violation + if (patch.email && patch.email.toLowerCase() !== user.email.toLowerCase()) { + if (await WIKI.models.users.getByEmail(patch.email.toLowerCase())) { + throw new CustomError( + 'userUpdateDuplicateEmail', + 'A user with this email already exists.' + ) + } + } + + // -> Group membership is replaced wholesale here, which would otherwise be a way around the + // guards on the groups endpoint. + if (req.body.groups !== undefined) { + // -> The guest account must stay in the guests group and nowhere else. Resending the + // membership unchanged is allowed, so that saving another field is not blocked. + if (user.isSystem) { + const current = await WIKI.models.users.getUserGroupIds(req.params.userId) + const requested = req.body.groups + const unchanged = + current.length === requested.length && current.every((id) => requested.includes(id)) + if (!unchanged) { + return reply.conflict('Cannot change the group membership of a system user.') + } + } + + const rootAdminGroupId = WIKI.config.auth.rootAdminGroupId + const wasRootAdmin = await WIKI.models.groups.isUserInGroup( + rootAdminGroupId, + req.params.userId + ) + if (wasRootAdmin && !req.body.groups.includes(rootAdminGroupId)) { + if ((await WIKI.models.groups.countUsersInGroup(rootAdminGroupId)) <= 1) { + return reply.conflict('Cannot remove the last user from the root administrators group.') + } + } + } + + try { + if (Object.keys(patch).length > 0) { + await WIKI.models.users.updateUser(req.params.userId, patch) + } + if (req.body.groups !== undefined) { + await WIKI.models.users.setUserGroups(req.params.userId, req.body.groups) + } + if (req.body.auth !== undefined) { + await WIKI.models.users.setUserAuthFlags(req.params.userId, req.body.auth) + } + return { + ok: true, + message: 'User updated successfully.' + } + } catch (err: any) { + WIKI.logger.warn(err) + return reply.internalServerError() + } + } + ) + + /** + * SET USER PASSWORD + */ + app.put<{ + Params: { userId: string } + Body: { newPassword: string; mustChangePassword?: boolean } + }>( + '/:userId/password', + { + config: { + permissions: ['manage:users'] + }, + schema: { + summary: "Set a user's password", + description: 'Replaces the local-strategy password. Other linked providers are untouched.', + tags: ['Users'], + params: { + type: 'object', + properties: { + userId: { + type: 'string', + format: 'uuid' + } + }, + required: ['userId'] + }, + body: { + type: 'object', + required: ['newPassword'], + properties: { + newPassword: { + type: 'string', + minLength: 8, + maxLength: 255 + }, + mustChangePassword: { + type: 'boolean', + default: false + } + } + }, + response: { + 200: { + description: 'Password updated successfully', + type: 'object', + properties: { + ok: { + type: 'boolean' + }, + message: { + type: 'string' + } + } + } + } + } + }, + async (req, reply) => { + const updated = await WIKI.models.users.setUserPassword({ + id: req.params.userId, + newPassword: req.body.newPassword, + mustChangePassword: req.body.mustChangePassword ?? false + }) + if (!updated) { + return reply.notFound('User does not exist.') + } + return { + ok: true, + message: 'User password updated successfully.' + } } ) @@ -138,11 +617,57 @@ async function routes(app: FastifyInstance) { }, schema: { summary: 'Delete a user', - tags: ['Users'] + description: + 'System users cannot be deleted, nor can the last user of the root administrators group.', + tags: ['Users'], + params: { + type: 'object', + properties: { + userId: { + type: 'string', + format: 'uuid' + } + }, + required: ['userId'] + }, + response: { + 204: { + description: 'User deleted successfully' + } + } } }, - async () => { - return { hello: 'world' } + async (req, reply) => { + const user = await WIKI.models.users.getById(req.params.userId) + if (!user) { + return reply.notFound('User does not exist.') + } + if (user.isSystem) { + return reply.conflict('Cannot delete a system user.') + } + + // -> Emptying the root administrators group would lock everyone out of system management + const rootAdminGroupId = WIKI.config.auth.rootAdminGroupId + if (await WIKI.models.groups.isUserInGroup(rootAdminGroupId, user.id)) { + if ((await WIKI.models.groups.countUsersInGroup(rootAdminGroupId)) <= 1) { + return reply.conflict('Cannot delete the last user of the root administrators group.') + } + } + + try { + await WIKI.models.users.deleteUser(user.id) + return reply.code(204).send() + } catch (err: any) { + // -> Pages and assets reference users without a cascade, so a user who authored content + // cannot be removed. That is a conflict to report, not a server fault. + if (err.cause?.code === '23503' || err.code === '23503') { + return reply.conflict( + 'Cannot delete a user who still owns pages or assets. Reassign them first.' + ) + } + WIKI.logger.warn(err) + return reply.internalServerError() + } } ) } diff --git a/backend/index.ts b/backend/index.ts index 7dc74dab0..f05540cf2 100644 --- a/backend/index.ts +++ b/backend/index.ts @@ -141,6 +141,10 @@ async function postBoot() { await WIKI.models.locales.reloadCache() await WIKI.models.sites.reloadCache() + // -> Must follow the sites cache: every site gets a row per installed block + await WIKI.models.blocks.refreshFromDisk() + await WIKI.models.blocks.syncAllSites() + await WIKI.dbManager.subscribeToNotifications() await WIKI.scheduler.start() } @@ -174,11 +178,13 @@ async function initHTTPServer() { // its `Plugin` type is not importable to state this more precisely.) plugins: [[ajvFormats.default, {}] as any], onCreate: (ajv: any) => { + // -> Accepts the shorthand, alpha and full forms a color picker can produce: + // #RGB, #RGBA, #RRGGBB and #RRGGBBAA ajv.addFormat('hexcolor', (data: unknown) => { - // FIXME: pre-existing bug — this is inverted: strings have no `.test()` method, it - // belongs to RegExp. Any value reaching this format validator throws a TypeError. - // Preserved as-is; the fix is `/#[a-fA-F0-9]{6}/.test(data)`. - return typeof data === 'string' && (data as any).test(/#[a-fA-F0-9]{6}/) + return ( + typeof data === 'string' && + /^#(?:[a-fA-F0-9]{3,4}|[a-fA-F0-9]{6}|[a-fA-F0-9]{8})$/.test(data) + ) }) } }, diff --git a/backend/locales/en.json b/backend/locales/en.json index 09d146155..3d7e0d343 100644 --- a/backend/locales/en.json +++ b/backend/locales/en.json @@ -112,9 +112,15 @@ "admin.auth.vendor": "Vendor", "admin.auth.vendorWebsite": "Website", "admin.blocks.add": "Add Block", + "admin.blocks.addUnavailable": "Adding custom blocks is not implemented yet.", "admin.blocks.builtin": "Built-in", "admin.blocks.custom": "Custom", + "admin.blocks.delete": "Delete Block", + "admin.blocks.deleteConfirm": "Are you sure you want to delete the custom block {blockName}?", + "admin.blocks.deleteSuccess": "Block was deleted successfully.", "admin.blocks.isEnabled": "Enabled", + "admin.blocks.loadFailed": "Failed to load blocks.", + "admin.blocks.saveFailed": "Failed to save the blocks state.", "admin.blocks.saveSuccess": "Blocks state saved successfully.", "admin.blocks.subtitle": "Manage dynamic components available for use inside pages.", "admin.blocks.title": "Content Blocks", @@ -308,8 +314,12 @@ "admin.general.uploads": "Uploads", "admin.general.urlHandling": "URL Handling", "admin.groups.assignUser": "Assign User", + "admin.groups.assignUserFailed": "Failed to assign {userName} to this group.", + "admin.groups.assignUserSuccess": "User was assigned to the group successfully. | {count} users were assigned to the group successfully.", + "admin.groups.assignUserTitle": "Assign Users to Group", "admin.groups.authBehaviors": "Authentication Behaviors", "admin.groups.create": "New Group", + "admin.groups.createInvalidData": "Cannot create group: invalid data.", "admin.groups.createSuccess": "Group created successfully.", "admin.groups.delete": "Delete Group", "admin.groups.deleteConfirm": "Are you sure you want delete group {groupName}? Any user currently assigned to this group will be unassigned from it.", @@ -330,6 +340,8 @@ "admin.groups.info": "Group Info", "admin.groups.name": "Group Name", "admin.groups.nameHint": "Name of the group", + "admin.groups.nameInvalidChars": "Group Name contains invalid characters.", + "admin.groups.nameMissing": "A group name is required.", "admin.groups.overview": "Overview", "admin.groups.permissions": "Permissions", "admin.groups.redirectOnFirstLogin": "First-time Login Redirect", @@ -343,20 +355,26 @@ "admin.groups.ruleDeny": "Deny", "admin.groups.ruleForceAllow": "Force Allow", "admin.groups.ruleLocales": "Locale(s)", + "admin.groups.ruleMatch": "Match", "admin.groups.ruleMatchEnd": "Path Ends With...", "admin.groups.ruleMatchExact": "Path Is Exactly...", "admin.groups.ruleMatchRegex": "Path Matches Regex...", "admin.groups.ruleMatchStart": "Path Starts With...", "admin.groups.ruleMatchTag": "Has Any Tag...", "admin.groups.ruleMatchTagAll": "Has All Tags...", + "admin.groups.rulePath": "Path", "admin.groups.ruleSites": "Site(s)", "admin.groups.ruleUntitled": "Untitled Rule", "admin.groups.rules": "Rules", "admin.groups.rulesNone": "This group doesn't have any rules yet.", + "admin.groups.saveSuccess": "Group saved successfully.", "admin.groups.selectedLocales": "Any Locale | {n} locale only | {count} locales selected", "admin.groups.selectedSites": "Any Site | 1 site selected | {count} sites selected", "admin.groups.subtitle": "Manage user groups and permissions", "admin.groups.title": "Groups", + "admin.groups.unassignUser": "Unassign User", + "admin.groups.unassignUserConfirm": "Are you sure you want to unassign {userName} from this group?", + "admin.groups.unassignUserSuccess": "User was unassigned from the group successfully.", "admin.groups.userCount": "User Count", "admin.groups.users": "Users", "admin.groups.usersCount": "0 user | 1 user | {count} users", @@ -388,6 +406,7 @@ "admin.locale.downloadTitle": "Download Locale", "admin.locale.forcePrefix": "Force Locale Prefix", "admin.locale.forcePrefixHint": "Paths without a locale code will always be redirected to the primary locale.", + "admin.locale.loadFailed": "Failed to fetch locale settings.", "admin.locale.name": "Name", "admin.locale.namespaces.hint": "Enables multiple language versions of the same page.", "admin.locale.namespaces.label": "Multilingual Namespaces", @@ -398,6 +417,7 @@ "admin.locale.primary": "Primary Locale", "admin.locale.primaryHint": "The locale to use as default / fallback for this site.", "admin.locale.rtl": "RTL", + "admin.locale.saveSuccess": "Locale settings saved successfully.", "admin.locale.settings": "Locale Settings", "admin.locale.sideload": "Sideload Locale Package", "admin.locale.sideloadHelp": "If you are not connected to the internet or cannot download locale files using the method above, you can instead sideload packages manually by uploading them below.", @@ -407,15 +427,19 @@ "admin.login.background": "Background Image", "admin.login.backgroundHint": "Specify an image to use as the login background. PNG and JPG are supported, 1920x1080 recommended. Leave empty for default.", "admin.login.bgUploadSuccess": "Login background image uploaded successfully.", + "admin.login.bgUploadUnavailable": "Uploading a background image is not implemented yet.", "admin.login.bypassScreen": "Bypass Login Screen", "admin.login.bypassScreenHint": "Should the user be redirected automatically to the first authentication provider. Has no effect if the first provider is a username/password provider type.", "admin.login.bypassUnauthorized": "Bypass Unauthorized Screen", "admin.login.bypassUnauthorizedHint": "Always redirect the user to the login screen instead of showing an unauthorized error page when the user is not logged in.", "admin.login.experience": "User Experience", + "admin.login.loadFailed": "Failed to load login configuration.", "admin.login.loginRedirect": "Login Redirect", "admin.login.loginRedirectHint": "Optionally redirect the user to a specific page when he/she logins (except if first time login which is defined below). This can be overridden at the group level.", + "admin.login.loginRedirectInvalidChars": "Login Redirect contains invalid characters.", "admin.login.logoutRedirect": "Logout Redirect", "admin.login.logoutRedirectHint": "Optionally redirect the user to a specific page when he/she logouts. This can be overridden at the group level.", + "admin.login.logoutRedirectInvalidChars": "Logout Redirect contains invalid characters.", "admin.login.providers": "Login Providers", "admin.login.providersVisbleWarning": "Note that you can always temporarily show all hidden providers by adding ?all=1 to the url. This is useful to login as local admin while hiding it from normal users.", "admin.login.saveSuccess": "Login configuration saved successfully.", @@ -423,6 +447,7 @@ "admin.login.title": "Login", "admin.login.welcomeRedirect": "First-time Login Redirect", "admin.login.welcomeRedirectHint": "Optionally redirect the user to a specific page when he/she login for the first time. This can be overridden at the group level.", + "admin.login.welcomeRedirectInvalidChars": "First-time Login Redirect contains invalid characters.", "admin.mail.configuration": "Configuration", "admin.mail.defaultBaseURL": "Default Base URL", "admin.mail.dkim": "DKIM (optional)", @@ -437,6 +462,7 @@ "admin.mail.dkimUseHint": "Should DKIM be used when sending emails.", "admin.mail.saveSuccess": "Configuration saved successfully.", "admin.mail.sendTestSuccess": "A test email was sent successfully.", + "admin.mail.sendTestUnavailable": "Sending emails is not implemented yet, so no test email can be sent.", "admin.mail.sender": "Sender", "admin.mail.senderEmail": "Sender Email", "admin.mail.senderName": "Sender Name", @@ -634,6 +660,7 @@ "admin.sites.refreshSuccess": "List of sites refreshed successfully.", "admin.sites.subtitle": "Manage your wiki sites", "admin.sites.title": "Sites", + "admin.sites.updateSuccess": "Site updated successfully.", "admin.ssl.currentState": "Current State", "admin.ssl.domain": "Domain", "admin.ssl.domainHint": "Enter the fully qualified domain pointing to your wiki. (e.g. wiki.example.com)", @@ -956,6 +983,7 @@ "admin.users.groupAssignNotice": "Note that you cannot assign users to the Administrators or Guests groups from this panel.", "admin.users.groupSelected": "Assign to {group}", "admin.users.groups": "Groups", + "admin.users.groupsLoadFailed": "Failed to load groups.", "admin.users.groupsMissing": "You must assign the user to at least 1 group.", "admin.users.groupsSelected": "Assign to {count} groups", "admin.users.id": "ID", @@ -969,6 +997,7 @@ "admin.users.lastUpdated": "Last Updated", "admin.users.linkedAccounts": "Linked Accounts", "admin.users.linkedProviders": "Linked Providers", + "admin.users.loadFailed": "Failed to load users.", "admin.users.loading": "Loading User...", "admin.users.location": "Location", "admin.users.locationHint": "The city / country of the user or the office location.", @@ -1009,12 +1038,17 @@ "admin.users.pwdStrengthWeak": "Weak", "admin.users.refreshSuccess": "Users refreshed successfully.", "admin.users.saveSuccess": "User saved successfully.", + "admin.users.searchNoResults": "No user matches your search.", + "admin.users.searchUsers": "Search by name or email...", "admin.users.selectGroup": "Select Group...", + "admin.users.selectUsers": "Select Users", + "admin.users.selectedCount": "{count} selected", "admin.users.sendWelcomeEmail": "Send Welcome Email", "admin.users.sendWelcomeEmailAltHint": "An email will be sent to the user with link(s) to the wiki(s) the user has read access to.", "admin.users.sendWelcomeEmailFromSiteId": "Site to use for the Welcome Email", "admin.users.sendWelcomeEmailHint": "An email will be sent to the user with his login details.", "admin.users.subtitle": "Manage Users", + "admin.users.systemUser": "System User", "admin.users.tfa": "Two Factor Authentication (2FA)", "admin.users.tfaInvalidate": "Invalidate 2FA", "admin.users.tfaInvalidateConfirm": "Are you sure you want to invalidate the user current 2FA configuration? This action cannot be undone.", diff --git a/backend/models/blocks.ts b/backend/models/blocks.ts new file mode 100644 index 000000000..ea9d4a54f --- /dev/null +++ b/backend/models/blocks.ts @@ -0,0 +1,191 @@ +import { readFile } from 'node:fs/promises' +import path from 'node:path' +import { and, eq, inArray } from 'drizzle-orm' +import { blocks as blocksTable, sites as sitesTable } from '../db/schema.ts' + +/** A block as declared by its component's `static definition`. */ +export interface BlockDefinition { + block: string + name: string + description: string + icon: string +} + +/** A block row as exposed by the API. */ +export interface SiteBlock { + id: string + block: string + name: string + description: string + icon: string + isEnabled: boolean + isCustom: boolean + config: Record +} + +const blockSelection = { + id: blocksTable.id, + block: blocksTable.block, + name: blocksTable.name, + description: blocksTable.description, + icon: blocksTable.icon, + isEnabled: blocksTable.isEnabled, + isCustom: blocksTable.isCustom, + config: blocksTable.config +} + +/** + * Blocks model + * + * Built-in blocks live in the `blocks/` workspace, one directory per block. Their metadata is + * declared as a `static definition` on each Lit component and collected into + * `blocks/compiled/blocks.manifest.json` by the rollup build, which is what this model reads — + * the components themselves cannot be imported outside a browser. + */ +class Blocks { + /** Definitions read from the compiled manifest, refreshed by `refreshFromDisk()`. */ + definitions: BlockDefinition[] = [] + + /** + * Load the built-in block definitions from the compiled manifest. + * + * A missing manifest is not fatal: it just means `blocks` has not been built yet, in which case + * only custom blocks are available. + */ + async refreshFromDisk(): Promise { + const manifestPath = path.join(WIKI.ROOTPATH, 'blocks/compiled/blocks.manifest.json') + try { + const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) + if (!Array.isArray(manifest)) { + throw new TypeError('Manifest is not an array.') + } + this.definitions = manifest + WIKI.logger.info(`Found ${this.definitions.length} blocks [ OK ]`) + } catch (err: any) { + this.definitions = [] + WIKI.logger.warn( + `Could not read the blocks manifest at ${manifestPath} — run "npm run build" in blocks/. [ SKIPPED ]` + ) + WIKI.logger.warn(err.message) + } + } + + /** + * Register any built-in block missing from a site, and drop rows for built-ins that no longer + * exist on disk. Existing rows are updated in place so that `isEnabled` and `config` survive. + * + * Custom blocks are never touched — they have no on-disk counterpart to compare against. + */ + async syncSite(siteId: string): Promise { + const existing = await WIKI.db + .select({ id: blocksTable.id, block: blocksTable.block }) + .from(blocksTable) + .where(and(eq(blocksTable.siteId, siteId), eq(blocksTable.isCustom, false))) + const existingKeys = existing.map((b: any) => b.block) + const definedKeys = this.definitions.map((d) => d.block) + + for (const definition of this.definitions) { + if (existingKeys.includes(definition.block)) { + // -> Metadata may have changed on disk; state and config belong to the site + await WIKI.db + .update(blocksTable) + .set({ + name: definition.name, + description: definition.description, + icon: definition.icon + }) + .where(and(eq(blocksTable.siteId, siteId), eq(blocksTable.block, definition.block))) + } else { + await WIKI.db.insert(blocksTable).values({ + siteId, + block: definition.block, + name: definition.name, + description: definition.description, + icon: definition.icon, + isEnabled: true, + isCustom: false, + config: {} + }) + } + } + + // -> A built-in that has been removed from disk should not linger in the admin list + const orphaned = existingKeys.filter((key: string) => !definedKeys.includes(key)) + if (orphaned.length > 0) { + await WIKI.db + .delete(blocksTable) + .where( + and( + eq(blocksTable.siteId, siteId), + eq(blocksTable.isCustom, false), + inArray(blocksTable.block, orphaned) + ) + ) + } + } + + /** + * Register the built-in blocks for every site. Called at boot, after the sites cache is loaded. + */ + async syncAllSites(): Promise { + WIKI.logger.info('Registering blocks for all sites...') + const sites = await WIKI.db.select({ id: sitesTable.id }).from(sitesTable) + for (const site of sites) { + await WIKI.models.blocks.syncSite(site.id) + } + WIKI.logger.info(`Registered blocks for ${sites.length} sites [ OK ]`) + } + + /** + * Fetch the blocks available to a site, built-in first, then by name + */ + async getSiteBlocks(siteId: string): Promise { + const results = await WIKI.db + .select(blockSelection) + .from(blocksTable) + .where(eq(blocksTable.siteId, siteId)) + .orderBy(blocksTable.isCustom, blocksTable.name) + return results as SiteBlock[] + } + + /** + * Enable or disable blocks in bulk. + * + * @param states Block IDs with their desired state + * @returns The number of block rows written — a block already in the requested state still counts + */ + async setBlocksState( + siteId: string, + states: { id: string; isEnabled: boolean }[] + ): Promise { + let changed = 0 + for (const isEnabled of [true, false]) { + const ids = states.filter((s) => s.isEnabled === isEnabled).map((s) => s.id) + if (ids.length < 1) { + continue + } + const result = await WIKI.db + .update(blocksTable) + .set({ isEnabled }) + .where(and(eq(blocksTable.siteId, siteId), inArray(blocksTable.id, ids))) + changed += result.rowCount ?? 0 + } + return changed + } + + /** + * Delete a custom block. Built-in blocks are rejected, since the next sync would recreate them. + * + * @returns Whether a block was deleted + */ + async deleteCustomBlock(siteId: string, id: string): Promise { + const result = await WIKI.db + .delete(blocksTable) + .where( + and(eq(blocksTable.siteId, siteId), eq(blocksTable.id, id), eq(blocksTable.isCustom, true)) + ) + return (result.rowCount ?? 0) > 0 + } +} + +export const blocks = new Blocks() diff --git a/backend/models/groups.ts b/backend/models/groups.ts index 8c87670a4..e3aeae70a 100644 --- a/backend/models/groups.ts +++ b/backend/models/groups.ts @@ -148,6 +148,38 @@ class Groups { ]) } + /** + * Create a new (non-system) group, seeded with the same starting permissions and default rule as + * the `Users` group. + * + * @param name Group name + * @returns The new group's ID + */ + async createGroup(name: string): Promise { + const startingPermissions = ['read:pages', 'read:assets', 'read:comments'] + const result = await WIKI.db + .insert(groupsTable) + .values({ + name, + permissions: startingPermissions, + rules: [ + { + id: uuid(), + name: 'Default Rule', + roles: startingPermissions, + match: 'START', + mode: 'ALLOW', + path: '', + locales: [], + sites: [] + } + ], + isSystem: false + }) + .returning({ id: groupsTable.id }) + return result[0].id + } + /** * Fetch all groups, ordered by name */ diff --git a/backend/models/index.ts b/backend/models/index.ts index 942b477f2..05e907bb2 100644 --- a/backend/models/index.ts +++ b/backend/models/index.ts @@ -1,4 +1,5 @@ import { authentication } from './authentication.ts' +import { blocks } from './blocks.ts' import { groups } from './groups.ts' import { jobs } from './jobs.ts' import { locales } from './locales.ts' @@ -9,6 +10,7 @@ import { users } from './users.ts' export default { authentication, + blocks, groups, jobs, locales, diff --git a/backend/models/sites.ts b/backend/models/sites.ts index 0f85ad20f..6077df113 100644 --- a/backend/models/sites.ts +++ b/backend/models/sites.ts @@ -1,6 +1,6 @@ -import { toMerged } from 'es-toolkit/object' +import { mergeWith, toMerged } from 'es-toolkit/object' import { keyBy } from 'es-toolkit/array' -import { sites as sitesTable } from '../db/schema.ts' +import { blocks as blocksTable, sites as sitesTable } from '../db/schema.ts' import { eq } from 'drizzle-orm' import type { SystemIds } from './types.ts' @@ -84,6 +84,7 @@ class Sites { comments: false, contributions: false, profile: true, + reasonForChange: 'required', search: true }, logoUrl: '', @@ -93,9 +94,20 @@ class Sites { index: true, follow: true }, + // -> Local authentication is the only strategy guaranteed to exist at this point + authStrategies: [{ id: WIKI.data.systemIds.localAuthId, order: 0, isVisible: true }], + auth: { + autoLogin: false, + bypassUnauthorized: false, + hideLocal: false, + loginRedirect: '/', + welcomeRedirect: '/', + logoutRedirect: '/' + }, locales: { primary: 'en', - active: ['en'] + active: ['en'], + forcePrefix: false }, assets: { logo: false, @@ -190,20 +202,71 @@ class Sites { // } // }) + // -> Site lookups by id / hostname are served from cache, which must know about the new site + await WIKI.models.sites.reloadCache() + + // -> Otherwise the new site would have no blocks until the next restart + await WIKI.models.blocks.syncSite(newSite.id) + return newSite } - async updateSite(id: string, patch: Record) { - // FIXME: pre-existing bug — `WIKI.db.sites.query()` is leftover Objection.js API that does not - // exist on a Drizzle instance, so this method always throws. Needs rewriting as a Drizzle - // `update(sitesTable).set(patch).where(eq(sitesTable.id, id))`. - return (WIKI.db as any).sites.query().findById(id).patch(patch) + async updateSite( + id: string, + patch: { hostname?: string; isEnabled?: boolean; config?: Record } + ): Promise { + const values: Partial = {} + if (patch.hostname !== undefined) { + values.hostname = patch.hostname + } + if (patch.isEnabled !== undefined) { + values.isEnabled = patch.isEnabled + } + if (patch.config) { + // -> Config is a JSONB blob, so it must be read and merged rather than partially assigned. + // Arrays are replaced rather than merged index-wise, otherwise removing an entry (e.g. a page + // extension) would leave the original value in place. + const current = await WIKI.db + .select({ config: sitesTable.config }) + .from(sitesTable) + .where(eq(sitesTable.id, id)) + if (current.length < 1) { + return false + } + values.config = mergeWith( + current[0].config as Record, + patch.config, + (_targetValue, sourceValue) => (Array.isArray(sourceValue) ? sourceValue : undefined) + ) + } + if (Object.keys(values).length < 1) { + return false + } + + const updatedResult = await WIKI.db.update(sitesTable).set(values).where(eq(sitesTable.id, id)) + if ((updatedResult.rowCount ?? 0) < 1) { + return false + } + + await WIKI.models.sites.reloadCache() + return true } async deleteSite(id: string): Promise { // await WIKI.db.storage.query().delete().where('siteId', id) + + // -> Block rows are registration metadata derived from disk, and their FK has no cascade, so + // they would otherwise block the delete. Content tables (pages, assets, ...) deliberately + // still do — see the conflict handling in the route. + await WIKI.db.delete(blocksTable).where(eq(blocksTable.siteId, id)) + const deletedResult = await WIKI.db.delete(sitesTable).where(eq(sitesTable.id, id)) - return Boolean((deletedResult.rowCount ?? 0) > 0) + if ((deletedResult.rowCount ?? 0) < 1) { + return false + } + + await WIKI.models.sites.reloadCache() + return true } async countSites() { @@ -249,9 +312,18 @@ class Sites { follow: true }, authStrategies: [{ id: ids.authModuleId, order: 0, isVisible: true }], + auth: { + autoLogin: false, + bypassUnauthorized: false, + hideLocal: false, + loginRedirect: '/', + welcomeRedirect: '/', + logoutRedirect: '/' + }, locales: { primary: 'en', - active: ['en'] + active: ['en'], + forcePrefix: false }, assets: { logo: false, diff --git a/backend/models/users.ts b/backend/models/users.ts index ed913df26..05de9718b 100644 --- a/backend/models/users.ts +++ b/backend/models/users.ts @@ -1,10 +1,83 @@ import bcrypt from 'bcryptjs' -import { userGroups, users as usersTable, userKeys } from '../db/schema.ts' -import { eq } from 'drizzle-orm' +import { + authentication as authenticationTable, + groups as groupsTable, + sessions as sessionsTable, + userGroups, + users as usersTable, + userKeys +} from '../db/schema.ts' +import { and, count, eq, ilike, inArray, notExists, or, sql } from 'drizzle-orm' import { nanoid } from 'nanoid' import { flatten, uniq } from 'es-toolkit/array' import type { SystemIds } from './types.ts' +/** The essential user fields, mirroring the `UserCore` API schema. */ +export interface UserCore { + id: string + name: string + email: string + hasAvatar: boolean + isSystem: boolean + isActive: boolean + isVerified: boolean + createdAt: Date + updatedAt: Date + lastLoginAt: Date | null +} + +/** One page of users, with the total matching the filter rather than the page size. */ +export interface UserPage { + total: number + users: UserCore[] +} + +/** + * An authentication provider linked to a user, as exposed by the API. Secrets held in the stored + * `auth` blob (the password hash, the TFA secret) are never included — `isPasswordSet` and + * `tfaIsActive` report their state instead. + */ +export interface UserAuthProvider { + authId: string + authName: string + strategyKey: string + strategyIcon: string + config: Record +} + +/** The subset of user fields that may be modified. `isSystem` is deliberately absent. */ +export interface UserPatch { + name?: string + email?: string + isActive?: boolean + isVerified?: boolean + meta?: Record + prefs?: Record +} + +/** + * Escape the LIKE wildcards `%` and `_` (and the escape character itself) so that a user-supplied + * filter is matched literally. Values are still parameterized by the driver — this is about a `%` + * in the filter silently matching everything, not about injection. + */ +function escapeLikePattern(value: string): string { + return value.replaceAll('\\', '\\\\').replaceAll('%', '\\%').replaceAll('_', '\\_') +} + +/** Selection shared by the list / detail queries. Never includes `auth` or `passkeys`. */ +const userSelection = { + id: usersTable.id, + name: usersTable.name, + email: usersTable.email, + hasAvatar: usersTable.hasAvatar, + isSystem: usersTable.isSystem, + isActive: usersTable.isActive, + isVerified: usersTable.isVerified, + createdAt: usersTable.createdAt, + updatedAt: usersTable.updatedAt, + lastLoginAt: usersTable.lastLoginAt +} + export interface LoginOptions { siteId: string strategyId: string @@ -35,6 +108,326 @@ class Users { return res?.[0] ?? null } + /** + * Fetch a page of users, optionally filtered by name or email + * + * @param filter Matched literally against name and email, case-insensitively + * @param assignableToGroupId Keep only the users that may be assigned to this group + * @returns The page of users plus the total number matching the filter + */ + async getUsers({ + filter = '', + assignableToGroupId = '', + page = 1, + limit = 20 + }: { + filter?: string + assignableToGroupId?: string + page?: number + limit?: number + } = {}): Promise { + const conditions = [] + if (filter) { + const pattern = `%${escapeLikePattern(filter)}%` + conditions.push(or(ilike(usersTable.name, pattern), ilike(usersTable.email, pattern))!) + } + if (assignableToGroupId) { + // -> Members of the group have nothing left to assign, and system users (the guest account) + // have a fixed membership that `POST /groups/:id/users/:id` refuses to change + conditions.push(eq(usersTable.isSystem, false)) + conditions.push( + notExists( + WIKI.db + .select({ exists: sql`1` }) + .from(userGroups) + .where( + and(eq(userGroups.userId, usersTable.id), eq(userGroups.groupId, assignableToGroupId)) + ) + ) + ) + } + const where = conditions.length > 0 ? and(...conditions) : undefined + + const totals = await WIKI.db.select({ total: count() }).from(usersTable).where(where) + const users = await WIKI.db + .select(userSelection) + .from(usersTable) + .where(where) + .orderBy(usersTable.name) + .limit(limit) + .offset((page - 1) * limit) + + return { + total: totals[0]?.total ?? 0, + users + } + } + + /** + * Fetch a single user with the groups it belongs to and the authentication providers linked to it. + * + * The stored `auth` blob is keyed by strategy ID and holds secrets, so it is reshaped into a list + * of providers carrying only state (`isPasswordSet`, `tfaIsActive`) — never the password hash or + * the TFA secret. + * + * @param id User ID + * @returns The user, or null if no such user exists + */ + async getUserDetail(id: string) { + const results = await WIKI.db.select().from(usersTable).where(eq(usersTable.id, id)).limit(1) + const user = results[0] + if (!user) { + return null + } + + const groups = await WIKI.db + .select({ id: groupsTable.id, name: groupsTable.name }) + .from(userGroups) + .innerJoin(groupsTable, eq(groupsTable.id, userGroups.groupId)) + .where(eq(userGroups.userId, id)) + .orderBy(groupsTable.name) + + const strategies = await WIKI.db.select().from(authenticationTable) + const auth: UserAuthProvider[] = [] + for (const [strategyId, rawConfig] of Object.entries( + (user.auth ?? {}) as Record + )) { + const strategy = strategies.find((s: any) => s.id === strategyId) + const definition = WIKI.data.authentication?.find((d: any) => d.key === strategy?.module) + const { password, tfaSecret, ...config } = rawConfig ?? {} + auth.push({ + authId: strategyId, + authName: strategy?.displayName || definition?.title || strategy?.module || 'Unknown', + strategyKey: strategy?.module ?? 'unknown', + strategyIcon: definition?.icon ?? '', + config: { + ...config, + isPasswordSet: Boolean(password), + tfaIsActive: Boolean(tfaSecret) + } + }) + } + + return { + id: user.id, + name: user.name, + email: user.email, + hasAvatar: user.hasAvatar, + isSystem: user.isSystem, + isActive: user.isActive, + isVerified: user.isVerified, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + lastLoginAt: user.lastLoginAt, + meta: user.meta, + prefs: user.prefs, + auth, + groups + } + } + + /** + * Create a new user, authenticated against the local strategy. + * + * @returns The new user's ID + */ + async createUser({ + name, + email, + password, + groups = [], + mustChangePassword = false, + isVerified = true + }: { + name: string + email: string + password: string + groups?: string[] + mustChangePassword?: boolean + /** + * Defaults to true: an administrator creating the account vouches for the address, and login + * rejects unverified users with `ERR_USER_NOT_VERIFIED` — which no email can currently clear. + */ + isVerified?: boolean + }): Promise { + const localStrategyId = WIKI.data.systemIds.localAuthId + const result = await WIKI.db + .insert(usersTable) + .values({ + email: email.toLowerCase(), + name, + auth: { + [localStrategyId]: { + password: await bcrypt.hash(password, 12), + mustChangePwd: mustChangePassword, + restrictLogin: false, + tfaIsActive: false, + tfaRequired: false, + tfaSecret: '' + } + }, + isSystem: false, + isActive: true, + isVerified, + meta: { + location: '', + jobTitle: '', + pronouns: '' + }, + prefs: { + // -> Seeded from the instance-wide user defaults, which an administrator can change + timezone: WIKI.config.userDefaults?.timezone ?? 'America/New_York', + dateFormat: WIKI.config.userDefaults?.dateFormat ?? 'YYYY-MM-DD', + timeFormat: WIKI.config.userDefaults?.timeFormat ?? '12h', + appearance: 'site', + cvd: 'none' + } + }) + .returning({ id: usersTable.id }) + + const userId = result[0].id + if (groups.length > 0) { + await this.setUserGroups(userId, groups) + } + return userId + } + + /** + * Update a user's own fields. Group membership is handled by `setUserGroups()`. + * + * @param patch Fields to change — must not be empty + * @returns Whether a user was updated + */ + async updateUser(id: string, patch: UserPatch): Promise { + const values: Record = { ...patch, updatedAt: sql`now()` } + if (typeof values.email === 'string') { + values.email = values.email.toLowerCase() + } + const result = await WIKI.db.update(usersTable).set(values).where(eq(usersTable.id, id)) + return (result.rowCount ?? 0) > 0 + } + + /** + * The IDs of the groups a user belongs to + */ + async getUserGroupIds(userId: string): Promise { + const rows = await WIKI.db + .select({ groupId: userGroups.groupId }) + .from(userGroups) + .where(eq(userGroups.userId, userId)) + return rows.map((r: any) => r.groupId) + } + + /** + * Replace a user's group membership with exactly the given groups. + * + * Unknown group IDs are ignored rather than failing the whole update, so that a stale client does + * not block an otherwise valid save. + */ + async setUserGroups(userId: string, groupIds: string[]): Promise { + const wanted = + groupIds.length > 0 + ? await WIKI.db + .select({ id: groupsTable.id }) + .from(groupsTable) + .where(inArray(groupsTable.id, groupIds)) + : [] + const wantedIds = wanted.map((g: any) => g.id) + + await WIKI.db.delete(userGroups).where(eq(userGroups.userId, userId)) + if (wantedIds.length > 0) { + await WIKI.db + .insert(userGroups) + .values(wantedIds.map((groupId: string) => ({ userId, groupId }))) + } + } + + /** + * Update the local-strategy behaviour flags for a user, leaving secrets and any other linked + * provider untouched. + * + * @param flags Any of `mustChangePwd`, `restrictLogin`, `tfaRequired` + * @returns False if the user does not exist + */ + async setUserAuthFlags(id: string, flags: Record): Promise { + const user = await this.getById(id) + if (!user) { + return false + } + + const localStrategyId = WIKI.data.systemIds.localAuthId + const auth = (user.auth ?? {}) as Record + const current = auth[localStrategyId] + if (!current) { + // -> The user does not use local authentication, so there are no local flags to set + return false + } + + for (const key of ['mustChangePwd', 'restrictLogin', 'tfaRequired'] as const) { + if (flags[key] !== undefined) { + current[key] = Boolean(flags[key]) + } + } + auth[localStrategyId] = current + + await WIKI.db + .update(usersTable) + .set({ auth, updatedAt: sql`now()` }) + .where(eq(usersTable.id, id)) + return true + } + + /** + * Set a user's local-strategy password, leaving any other linked provider untouched. + * + * @returns False if the user does not exist + */ + async setUserPassword({ + id, + newPassword, + mustChangePassword = false + }: { + id: string + newPassword: string + mustChangePassword?: boolean + }): Promise { + const user = await this.getById(id) + if (!user) { + return false + } + + const localStrategyId = WIKI.data.systemIds.localAuthId + const auth = (user.auth ?? {}) as Record + auth[localStrategyId] = { + ...auth[localStrategyId], + password: await bcrypt.hash(newPassword, 12), + mustChangePwd: mustChangePassword + } + + await WIKI.db + .update(usersTable) + .set({ auth, updatedAt: sql`now()` }) + .where(eq(usersTable.id, id)) + return true + } + + /** + * Delete a user. + * + * Group assignments cascade, but sessions and keys do not — they are login artifacts, so they are + * cleared here rather than blocking the delete. References from authored content (pages, assets) + * have no cascade either and will make this throw, which is deliberate: the delete is refused + * rather than silently orphaning content. + * + * @returns Whether a user was deleted + */ + async deleteUser(id: string): Promise { + await WIKI.db.delete(userKeys).where(eq(userKeys.userId, id)) + await WIKI.db.delete(sessionsTable).where(eq(sessionsTable.userId, id)) + const result = await WIKI.db.delete(usersTable).where(eq(usersTable.id, id)) + return (result.rowCount ?? 0) > 0 + } + async init(ids: SystemIds): Promise { WIKI.logger.info('Inserting default users...') diff --git a/blocks/block-index/component.js b/blocks/block-index/component.js index 5ea178145..8a866024f 100644 --- a/blocks/block-index/component.js +++ b/blocks/block-index/component.js @@ -5,6 +5,17 @@ import treeQuery from './tree.graphql' * Block Index */ export class BlockIndexElement extends LitElement { + /** + * Metadata for the admin area. Collected at build time into `compiled/blocks.manifest.json`, + * which the server reads to register the block. Values must be plain literals. + */ + static definition = { + block: 'index', + name: 'Index', + description: 'Displays a list of pages contained in a folder.', + icon: 'index' + } + static get styles() { return css` :host { diff --git a/blocks/block-media-player/component.js b/blocks/block-media-player/component.js index cff1cb13d..6dfd342cb 100644 --- a/blocks/block-media-player/component.js +++ b/blocks/block-media-player/component.js @@ -4,6 +4,17 @@ import { LitElement, html, css } from 'lit' * Block Media Player */ export class BlockMediaPlayerElement extends LitElement { + /** + * Metadata for the admin area. Collected at build time into `compiled/blocks.manifest.json`, + * which the server reads to register the block. Values must be plain literals. + */ + static definition = { + block: 'media-player', + name: 'Media Player', + description: 'Plays an audio or video file inline.', + icon: 'widescreen' + } + static get styles() { return css` :host { diff --git a/blocks/rollup.config.mjs b/blocks/rollup.config.mjs index f3007b820..29d9aabcb 100644 --- a/blocks/rollup.config.mjs +++ b/blocks/rollup.config.mjs @@ -5,6 +5,74 @@ import graphql from '@rollup/plugin-graphql' import * as glob from 'glob' +/** + * Turn an ESTree literal node into a plain JS value. + * + * Only literals, arrays and objects of literals are supported — a block definition is metadata, so + * anything computed is a mistake worth failing the build over. + */ +function literalToValue (node, blockDir) { + switch (node.type) { + case 'Literal': + return node.value + case 'ArrayExpression': + return node.elements.map(el => literalToValue(el, blockDir)) + case 'ObjectExpression': + return Object.fromEntries(node.properties.map(prop => [ + prop.key.name ?? prop.key.value, + literalToValue(prop.value, blockDir) + ])) + default: + throw new Error(`${blockDir}: "static definition" must contain only plain literals, got ${node.type}.`) + } +} + +/** + * Collects each block's `static definition` into `compiled/blocks.manifest.json`. + * + * The definitions are read from the AST rather than by importing the modules, since a component + * registers itself with `customElements` on load and so cannot be imported outside a browser. + */ +function blocksManifest () { + const definitions = new Map() + return { + name: 'blocks-manifest', + buildStart () { + definitions.clear() + }, + transform (code, id) { + if (!id.endsWith('/component.js')) { + return null + } + const blockDir = id.split('/').at(-2) + const ast = this.parse(code) + for (const node of ast.body) { + const classNode = node.type === 'ExportNamedDeclaration' ? node.declaration : node + if (classNode?.type !== 'ClassDeclaration') { + continue + } + const definitionNode = classNode.body.body.find(member => + member.type === 'PropertyDefinition' && member.static && member.key.name === 'definition' + ) + if (definitionNode) { + definitions.set(blockDir, literalToValue(definitionNode.value, blockDir)) + } + } + if (!definitions.has(blockDir)) { + this.warn(`${blockDir} has no "static definition" — it will not appear in the admin area.`) + } + return null + }, + generateBundle () { + this.emitFile({ + type: 'asset', + fileName: 'blocks.manifest.json', + source: JSON.stringify([...definitions.values()], null, 2) + '\n' + }) + } + } +} + export default { input: Object.fromEntries( glob.sync('@(block-*)/component.js', { @@ -28,6 +96,7 @@ export default { } }, plugins: [ + blocksManifest(), resolve(), graphql(), terser({ diff --git a/frontend/.oxlintrc.json b/frontend/.oxlintrc.json index 30af6a897..9126b1d90 100644 --- a/frontend/.oxlintrc.json +++ b/frontend/.oxlintrc.json @@ -13,7 +13,8 @@ "process": "readonly", "chrome": "readonly", "API_CLIENT": "readonly", - "EVENT_BUS": "readonly" + "EVENT_BUS": "readonly", + "Temporal": "readonly" }, "ignorePatterns": [ "dist/**", diff --git a/frontend/.vscode/extensions.json b/frontend/.vscode/extensions.json deleted file mode 100644 index 10affd458..000000000 --- a/frontend/.vscode/extensions.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "recommendations": [ - "editorconfig.editorconfig", - "johnsoncodehk.volar", - "wayou.vscode-todo-highlight" - ], - "unwantedRecommendations": [ - "octref.vetur", - "hookyqr.beautify", - "dbaeumer.jshint", - "ms-vscode.vscode-typescript-tslint-plugin", - "dbaeumer.vscode-eslint" - ] -} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4566fa21d..fc5a39d68 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,6 +19,7 @@ "codemirror": "5.65.11", "codemirror-asciidoc": "1.0.4", "dependency-graph": "1.0.0", + "es-toolkit": "1.50.0", "filesize": "11.0.17", "filesize-parser": "1.5.1", "fuse.js": "7.4.2", @@ -63,6 +64,7 @@ "sortablejs": "1.15.7", "sortablejs-vue3": "1.3.0", "tabulator-tables": "6.4.0", + "temporal-polyfill": "1.0.1", "tippy.js": "6.3.7", "twemoji": "14.0.2", "typescript": "6.0.3", @@ -80,6 +82,7 @@ "@quasar/app-vite": "2.6.2", "@quasar/vite-plugin": "1.12.0", "@types/lodash": "4.17.24", + "@vitejs/plugin-vue": "6.0.8", "@vue/language-plugin-pug": "3.3.5", "autoprefixer": "10.5.0", "browserlist": "latest", @@ -87,11 +90,11 @@ "oxfmt": "0.54.0", "oxlint": "1.69.0", "sass": "1.101.0", + "vite": "8.1.5", "vite-plugin-vue-devtools": "8.1.2" }, "engines": { - "node": ">= 18.0", - "npm": ">= 6.13.4" + "node": ">= 26.0" } }, "node_modules/@babel/code-frame": { @@ -603,22 +606,20 @@ "license": "BSD-2-Clause" }, "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.1", + "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "license": "MIT", "optional": true, "dependencies": { @@ -626,10 +627,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "license": "MIT", "optional": true, "dependencies": { @@ -637,13 +637,12 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -654,13 +653,12 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -671,13 +669,12 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -688,13 +685,12 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -705,13 +701,12 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -722,13 +717,12 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -739,13 +733,12 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -756,13 +749,12 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -773,13 +765,12 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -790,13 +781,12 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -807,13 +797,12 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -824,13 +813,12 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -841,13 +829,12 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -858,13 +845,12 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -875,13 +861,12 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -892,13 +877,12 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -909,13 +893,12 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -926,13 +909,12 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -943,13 +925,12 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -960,13 +941,12 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -977,13 +957,12 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -994,13 +973,12 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1011,13 +989,12 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1028,13 +1005,12 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1045,13 +1021,12 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1062,13 +1037,12 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1728,6 +1702,490 @@ } } }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@intlify/bundle-utils/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, "node_modules/@intlify/core-base": { "version": "11.4.5", "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.4.5.tgz", @@ -2108,14 +2566,13 @@ "license": "Apache-2.0" }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz", - "integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==", - "dev": true, + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.2" + "@tybys/wasm-util": "^0.10.3" }, "funding": { "type": "github", @@ -2178,10 +2635,10 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.133.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", - "dev": true, + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", + "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" @@ -3418,448 +3875,6 @@ } } }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@quasar/app-vite/node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@quasar/app-vite/node_modules/confbox": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", @@ -3867,164 +3882,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@quasar/app-vite/node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" - } - }, - "node_modules/@quasar/app-vite/node_modules/open": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", - "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-browser": "^5.4.0", - "define-lazy-prop": "^3.0.0", - "is-in-ssh": "^1.0.0", - "is-inside-container": "^1.0.0", - "powershell-utils": "^0.1.0", - "wsl-utils": "^0.3.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@quasar/app-vite/node_modules/vite": { - "version": "8.0.16", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "1.0.3", - "tinyglobby": "^0.2.17" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/@quasar/app-vite/node_modules/wsl-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", - "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0", - "powershell-utils": "^0.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@quasar/extras": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@quasar/extras/-/extras-2.0.1.tgz", @@ -4091,13 +3948,12 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4108,13 +3964,12 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4125,13 +3980,12 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4142,13 +3996,12 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4159,13 +4012,12 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4176,13 +4028,15 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", "cpu": [ "arm64" ], - "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4193,13 +4047,15 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", "cpu": [ "arm64" ], - "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -4210,13 +4066,15 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", "cpu": [ "ppc64" ], - "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4227,13 +4085,15 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", "cpu": [ "s390x" ], - "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4244,13 +4104,15 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", "cpu": [ "x64" ], - "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -4261,13 +4123,15 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", "cpu": [ "x64" ], - "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -4278,13 +4142,12 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4295,32 +4158,30 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", "cpu": [ "wasm32" ], - "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4331,13 +4192,12 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4351,7 +4211,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@rollup/pluginutils": { @@ -4377,356 +4237,6 @@ } } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true - }, "node_modules/@simplewebauthn/browser": { "version": "13.3.0", "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.3.0.tgz", @@ -4817,10 +4327,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", - "dev": true, + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "license": "MIT", "optional": true, "dependencies": { @@ -4878,10 +4387,10 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "devOptional": true, + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, "license": "MIT" }, "node_modules/@types/express": { @@ -5243,9 +4752,9 @@ } }, "node_modules/@vitejs/plugin-vue": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", - "integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==", + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz", + "integrity": "sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==", "dev": true, "license": "MIT", "dependencies": { @@ -5611,9 +5120,9 @@ } }, "node_modules/ansis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", - "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", + "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", "dev": true, "license": "ISC", "engines": { @@ -6911,11 +6420,22 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz", + "integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks", + "tests/types" + ] + }, "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "devOptional": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -6925,32 +6445,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, "node_modules/escalade": { @@ -9449,9 +8969,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "funding": [ { "type": "github", @@ -9559,6 +9079,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/ohash": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", @@ -9590,19 +9124,21 @@ } }, "node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", "dev": true, "license": "MIT", "dependencies": { - "default-browser": "^5.2.1", + "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9897,9 +9433,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { "node": ">=12" @@ -9998,9 +9534,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", "funding": [ { "type": "opencollective", @@ -10017,7 +9553,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -10605,13 +10141,13 @@ "license": "MIT" }, "node_modules/rolldown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", - "dev": true, + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", + "devOptional": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.133.0", + "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -10621,67 +10157,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.3", - "@rolldown/binding-darwin-arm64": "1.0.3", - "@rolldown/binding-darwin-x64": "1.0.3", - "@rolldown/binding-freebsd-x64": "1.0.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", - "@rolldown/binding-linux-arm64-gnu": "1.0.3", - "@rolldown/binding-linux-arm64-musl": "1.0.3", - "@rolldown/binding-linux-ppc64-gnu": "1.0.3", - "@rolldown/binding-linux-s390x-gnu": "1.0.3", - "@rolldown/binding-linux-x64-gnu": "1.0.3", - "@rolldown/binding-linux-x64-musl": "1.0.3", - "@rolldown/binding-openharmony-arm64": "1.0.3", - "@rolldown/binding-wasm32-wasi": "1.0.3", - "@rolldown/binding-win32-arm64-msvc": "1.0.3", - "@rolldown/binding-win32-x64-msvc": "1.0.3" - } - }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "devOptional": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" } }, "node_modules/rollup-plugin-visualizer": { @@ -10715,27 +10205,6 @@ } } }, - "node_modules/rollup-plugin-visualizer/node_modules/open": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", - "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "default-browser": "^5.4.0", - "define-lazy-prop": "^3.0.0", - "is-in-ssh": "^1.0.0", - "is-inside-container": "^1.0.0", - "powershell-utils": "^0.1.0", - "wsl-utils": "^0.3.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/rollup-plugin-visualizer/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -10746,23 +10215,6 @@ "node": ">= 12" } }, - "node_modules/rollup-plugin-visualizer/node_modules/wsl-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", - "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0", - "powershell-utils": "^0.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/rope-sequence": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", @@ -11830,6 +11282,28 @@ "streamx": "^2.15.0" } }, + "node_modules/temporal-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-1.0.1.tgz", + "integrity": "sha512-N2SoI9olnW7BUsU8RosDphZQl9s+WJ8O7PoJMFCr/e5/1rFkVI4GNOWaSeySG+UoP04foPYsnLWbJmbXOiShZg==", + "license": "MIT", + "dependencies": { + "temporal-spec": "1.0.0", + "temporal-utils": "1.0.1" + } + }, + "node_modules/temporal-spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-1.0.0.tgz", + "integrity": "sha512-00Ahj1e1ifaERTMOIIGpOCdOo9IEk2m6GGSMedsn9a2SIsGLdOTbmME1Htv6IM82b6VHrzSUTIVc7YHy6hdhFQ==", + "license": "Apache-2.0" + }, + "node_modules/temporal-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/temporal-utils/-/temporal-utils-1.0.1.tgz", + "integrity": "sha512-HAixuesxFQIUaQk3ptX2jhfO/FsOkgVkDDMawvp6n/fkB1q6BKfs3lURw9I+pK/2e2e/q/vrLrxmeqauBoyGMQ==", + "license": "MIT" + }, "node_modules/terser": { "version": "5.46.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", @@ -12301,19 +11775,17 @@ } }, "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -12329,9 +11801,10 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", - "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", @@ -12344,15 +11817,18 @@ "@types/node": { "optional": true }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, "jiti": { "optional": true }, "less": { "optional": true }, - "lightningcss": { - "optional": true - }, "sass": { "optional": true }, @@ -12377,51 +11853,61 @@ } }, "node_modules/vite-dev-rpc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-1.1.0.tgz", - "integrity": "sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vite-dev-rpc/-/vite-dev-rpc-2.0.0.tgz", + "integrity": "sha512-yKwbTwdHKSD2k/aGqyWpPHepo45OQc8lH3/6IfT4ZqeKE26ooKvi4WIEKzqWav8v+9Is8u1k8q54hvOmqASazA==", "dev": true, "license": "MIT", "dependencies": { - "birpc": "^2.4.0", - "vite-hot-client": "^2.1.0" + "birpc": "^4.0.0", + "vite-hot-client": "^2.2.0" }, "funding": { "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0" + "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0 || ^8.0.0" + } + }, + "node_modules/vite-dev-rpc/node_modules/birpc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-4.0.0.tgz", + "integrity": "sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" } }, "node_modules/vite-hot-client": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.1.0.tgz", - "integrity": "sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-2.2.0.tgz", + "integrity": "sha512-76Zs9zrHbH7M7wqeyooGQKdX+yg0pQ0xuQ1PbFp4z5a0Lzn2e5IPFoCswnmqZ4GiwqB4Jo3WcDAMO9jARTJl8w==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 || ^8.0.0" } }, "node_modules/vite-plugin-inspect": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.3.3.tgz", - "integrity": "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==", + "version": "11.4.1", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-11.4.1.tgz", + "integrity": "sha512-ShOFe2PURXGvRS5OrgmOLZOCwDTD7dEBVt0tMpFPKb9AsvqXKCRGM8QgKrUbRbJYFXScHvDPpGRd28rYidC0tA==", "dev": true, "license": "MIT", "dependencies": { - "ansis": "^4.1.0", - "debug": "^4.4.1", + "ansis": "^4.3.0", "error-stack-parser-es": "^1.0.5", + "obug": "^2.1.1", "ohash": "^2.0.11", - "open": "^10.2.0", - "perfect-debounce": "^2.0.0", - "sirv": "^3.0.1", - "unplugin-utils": "^0.3.0", - "vite-dev-rpc": "^1.1.0" + "open": "^11.0.0", + "perfect-debounce": "^2.1.0", + "sirv": "^3.0.2", + "unplugin-utils": "^0.3.1", + "vite-dev-rpc": "^2.0.0" }, "engines": { "node": ">=14" @@ -12430,7 +11916,7 @@ "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "vite": "^6.0.0 || ^7.0.0-0" + "vite": "^6.0.0 || ^7.0.0-0 || ^8.0.0-0" }, "peerDependenciesMeta": { "@nuxt/kit": { @@ -12480,491 +11966,6 @@ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", - "devOptional": true, - "hasInstallScript": true, - "license": "MIT", - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" - } - }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", @@ -13390,16 +12391,17 @@ } }, "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.1.tgz", + "integrity": "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==", "dev": true, "license": "MIT", "dependencies": { - "is-wsl": "^3.1.0" + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" diff --git a/frontend/package.json b/frontend/package.json index cd7a62ef6..165de6db8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,6 +24,7 @@ "codemirror": "5.65.11", "codemirror-asciidoc": "1.0.4", "dependency-graph": "1.0.0", + "es-toolkit": "1.50.0", "filesize": "11.0.17", "filesize-parser": "1.5.1", "fuse.js": "7.4.2", @@ -68,6 +69,7 @@ "sortablejs": "1.15.7", "sortablejs-vue3": "1.3.0", "tabulator-tables": "6.4.0", + "temporal-polyfill": "1.0.1", "tippy.js": "6.3.7", "twemoji": "14.0.2", "typescript": "6.0.3", @@ -85,6 +87,7 @@ "@quasar/app-vite": "2.6.2", "@quasar/vite-plugin": "1.12.0", "@types/lodash": "4.17.24", + "@vitejs/plugin-vue": "6.0.8", "@vue/language-plugin-pug": "3.3.5", "autoprefixer": "10.5.0", "browserlist": "latest", @@ -92,6 +95,7 @@ "oxfmt": "0.54.0", "oxlint": "1.69.0", "sass": "1.101.0", + "vite": "8.1.5", "vite-plugin-vue-devtools": "8.1.2" }, "engines": { diff --git a/frontend/src/boot/temporal.js b/frontend/src/boot/temporal.js new file mode 100644 index 000000000..0e6c4b246 --- /dev/null +++ b/frontend/src/boot/temporal.js @@ -0,0 +1,14 @@ +/** + * Installs a `Temporal` polyfill on browsers that don't implement it natively yet (Safari, as of + * mid-2026). The import is dynamic and guarded, so browsers with native support never download it. + * + * Must run before anything that touches `Temporal` — it is awaited first in `main.js`. + */ +export async function initializeTemporal () { + if (typeof globalThis.Temporal !== 'undefined') { + return + } + + // -> Patches globalThis.Temporal, Intl.DateTimeFormat and Date.prototype.toTemporalInstant + await import('temporal-polyfill/global') +} diff --git a/frontend/src/components/EditorMarkdownConfigOverlay.vue b/frontend/src/components/EditorMarkdownConfigOverlay.vue index 320d6ede0..2015d623e 100644 --- a/frontend/src/components/EditorMarkdownConfigOverlay.vue +++ b/frontend/src/components/EditorMarkdownConfigOverlay.vue @@ -257,7 +257,7 @@ import { useI18n } from 'vue-i18n' import { useQuasar } from 'quasar' import { onMounted, reactive } from 'vue' -import { cloneDeep } from 'lodash-es' +import { toMerged } from 'es-toolkit/object' import { useAdminStore } from '@/stores/admin' import { useEditorStore } from '@/stores/editor' @@ -279,22 +279,30 @@ const { t } = useI18n() // DATA -const state = reactive({ - config: { - allowHTML: false, - linkify: false, - lineBreaks: false, +/** + * Fallbacks for options a site may not have stored yet, so that every control renders with a + * defined value. Must mirror the markdown defaults used by the backend when creating a site. + */ +function defaultConfig () { + return { + allowHTML: true, + linkify: true, + lineBreaks: true, typographer: false, quotes: 'english', - underline: false, + underline: true, tabWidth: 2, latexEngine: 'katex', - multimdTable: false, + multimdTable: true, plantuml: false, - plantumlServerUrl: 'https://', + plantumlServerUrl: 'https://www.plantuml.com/plantuml/', kroki: false, - krokiServerUrl: 'https://' - }, + krokiServerUrl: 'https://kroki.io' + } +} + +const state = reactive({ + config: defaultConfig(), loading: 0 }) @@ -328,28 +336,11 @@ async function load () { state.loading++ $q.loading.show() try { - const resp = await APOLLO_CLIENT.query({ - query: ` - query getEditorsState ( - $siteId: UUID! - ) { - siteById ( - id: $siteId - ) { - id - editors { - markdown { - config - } - } - } - }`, - variables: { - siteId: adminStore.currentSiteId - }, - fetchPolicy: 'network-only' - }) - state.config = cloneDeep(resp?.data?.siteById?.editors?.markdown?.config) + const resp = await API_CLIENT.get(`sites/${adminStore.currentSiteId}?strict=true`).json() + if (!resp?.editors?.markdown?.config) { + throw new Error('Failed to fetch markdown editor configuration.') + } + state.config = toMerged(defaultConfig(), resp.editors.markdown.config) } catch (err) { $q.notify({ type: 'negative', @@ -363,43 +354,23 @@ async function load () { async function save () { state.loading++ try { - const respRaw = await APOLLO_CLIENT.mutate({ - mutation: ` - mutation saveEditorState ( - $id: UUID! - $patch: SiteUpdateInput! - ) { - updateSite ( - id: $id, - patch: $patch - ) { - operation { - succeeded - slug - message - } - } - } - `, - variables: { - id: adminStore.currentSiteId, - patch: { - editors: { - markdown: { config: state.config } - } + // -> Only `config` is sent, so the editor's active state is left untouched by the merge + const resp = await API_CLIENT.put(`sites/${adminStore.currentSiteId}`, { + json: { + editors: { + markdown: { config: state.config } } } - }) - if (respRaw?.data?.updateSite?.operation?.succeeded) { - $q.notify({ - type: 'positive', - message: t('admin.editors.markdown.saveSuccess') - }) - editorStore.$patch({ configIsLoaded: false }) - close() - } else { - throw new Error(respRaw?.data?.updateSite?.operation?.message || 'An unexpected error occured.') + }).json() + if (!resp?.ok) { + throw new Error(t(`admin.editors.markdown.${resp?.error}`, resp?.message || 'An unexpected error occured.')) } + $q.notify({ + type: 'positive', + message: t('admin.editors.markdown.saveSuccess') + }) + editorStore.$patch({ configIsLoaded: false }) + close() } catch (err) { $q.notify({ type: 'negative', diff --git a/frontend/src/components/GroupCreateDialog.vue b/frontend/src/components/GroupCreateDialog.vue index 6d9f52429..f7b9ff287 100644 --- a/frontend/src/components/GroupCreateDialog.vue +++ b/frontend/src/components/GroupCreateDialog.vue @@ -86,34 +86,19 @@ async function create () { if (!isFormValid) { throw new Error(t('admin.groups.createInvalidData')) } - const resp = await APOLLO_CLIENT.mutate({ - mutation: ` - mutation createGroup ( - $name: String! - ) { - createGroup( - name: $name - ) { - operation { - succeeded - message - } - } - } - `, - variables: { + const resp = await API_CLIENT.post('groups', { + json: { name: state.groupName } - }) - if (resp?.data?.createGroup?.operation?.succeeded) { - $q.notify({ - type: 'positive', - message: t('admin.groups.createSuccess') - }) - onDialogOK() - } else { - throw new Error(resp?.data?.createGroup?.operation?.message || 'An unexpected error occured.') + }).json() + if (!resp?.ok) { + throw new Error(t(`admin.groups.${resp?.error}`, resp?.message || 'An unexpected error occured.')) } + $q.notify({ + type: 'positive', + message: t('admin.groups.createSuccess') + }) + onDialogOK() } catch (err) { $q.notify({ type: 'negative', diff --git a/frontend/src/components/GroupDeleteDialog.vue b/frontend/src/components/GroupDeleteDialog.vue index c13e73e00..1f013b128 100644 --- a/frontend/src/components/GroupDeleteDialog.vue +++ b/frontend/src/components/GroupDeleteDialog.vue @@ -62,34 +62,22 @@ const { t } = useI18n() async function confirm () { try { - const resp = await APOLLO_CLIENT.mutate({ - mutation: ` - mutation deleteGroup ($id: UUID!) { - deleteGroup(id: $id) { - operation { - succeeded - message - } - } - } - `, - variables: { - id: props.group.id - } - }) - if (resp?.data?.deleteGroup?.operation?.succeeded) { - $q.notify({ - type: 'positive', - message: t('admin.groups.deleteSuccess') - }) - onDialogOK() - } else { - throw new Error(resp?.data?.deleteGroup?.operation?.message || 'An unexpected error occured.') + const resp = await API_CLIENT.delete(`groups/${props.group.id}`) + if (!resp?.ok) { + throw new Error((await resp.json())?.message || 'An unexpected error occured.') } + $q.notify({ + type: 'positive', + message: t('admin.groups.deleteSuccess') + }) + onDialogOK() } catch (err) { + // -> ky throws for statuses above 400 (e.g. 409 for a system group), where the reason the API + // gave is in the response body rather than in the error message + const apiMessage = await err.response?.json().then(b => b?.message).catch(() => null) $q.notify({ type: 'negative', - message: err.message + message: apiMessage || err.message }) } } diff --git a/frontend/src/components/GroupEditOverlay.vue b/frontend/src/components/GroupEditOverlay.vue index 9b004d0ce..27ee5f6f8 100644 --- a/frontend/src/components/GroupEditOverlay.vue +++ b/frontend/src/components/GroupEditOverlay.vue @@ -30,6 +30,8 @@ q-layout(view='hHh lpR fFf', container) text-color='white' :label='t(`common.actions.save`)' icon='las la-check' + :loading='state.isLoading' + @click='save' ) q-drawer.bg-dark-6(:model-value='true', :width='250', dark) q-list(padding, v-show='!state.isLoading') @@ -483,13 +485,17 @@ q-layout(view='hHh lpR fFf', container) :label='t(`common.actions.edit`)' no-caps ) + //- Hidden for system users: the guest account's membership is fixed, and the API + //- refuses to change it either way q-btn.acrylic-btn( v-if='!props.row.isSystem' flat icon='las la-user-minus' color='accent' + :aria-label='t(`admin.groups.unassignUser`)' @click='unassignUser(props.row)' ) + q-tooltip(anchor='center left' self='center right') {{ t('admin.groups.unassignUser') }} .flex.flex-center.q-mt-md(v-if='usersTotalPages > 1') q-pagination( @@ -503,9 +509,7 @@ q-layout(view='hHh lpR fFf', container) + + diff --git a/frontend/src/main.js b/frontend/src/main.js index dfdb4750a..1fa855d67 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.js @@ -7,6 +7,7 @@ import { initializeComponents } from './boot/components' import { initializeEventBus } from './boot/eventbus' import { initializeExternals } from './boot/externals' import { initializeI18n } from './boot/i18n' +import { initializeTemporal } from './boot/temporal' import quasarIconSet from 'quasar/icon-set/mdi-v7' // Import icon libraries @@ -20,6 +21,9 @@ import './css/app.scss' import RootApp from './App.vue' +// Must come first: everything below may use Temporal, directly or indirectly. +await initializeTemporal() + const router = initializeRouter() const store = initializeStore(router) diff --git a/frontend/src/pages/AdminBlocks.vue b/frontend/src/pages/AdminBlocks.vue index 64c046786..8d9f558f8 100644 --- a/frontend/src/pages/AdminBlocks.vue +++ b/frontend/src/pages/AdminBlocks.vue @@ -79,16 +79,15 @@ q-page.admin-flags unchecked-icon='las la-times' :label='t(`admin.blocks.isEnabled`)' :aria-label='t(`admin.blocks.isEnabled`)' - disable )