diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..41a8ca3 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,47 @@ +name: CI + +on: [push, pull_request] + +jobs: + lint: + name: 'Lint' + runs-on: ubuntu-latest + permissions: + checks: write + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 21 + cache: 'npm' + - name: Install Node Dependencies + run: npm ci + + # Linting + - name: Create ESLint Report + run: node_modules/.bin/eslint --output-file eslint_report.json --format json . + continue-on-error: true + - name: Annotate Code Linting Results + uses: ataylorme/eslint-annotate-action@v3 + continue-on-error: true + with: + report-json: 'eslint_report.json' + - name: Run ESLint + run: node_modules/.bin/eslint . + + build: + name: 'Build' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 21 + cache: 'npm' + - name: Install Node Dependencies + run: npm ci + # Build + - name: Build + run: npm run build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cdb5e6c --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# dev +.yarn/ +!.yarn/releases +.vscode/* +!.vscode/launch.json +!.vscode/*.code-snippets +.idea/workspace.xml +.idea/usage.statistics.xml +.idea/shelf + +# deps +node_modules/ + +# env +.env +.env.production + +# logs +logs/ +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# misc +.DS_Store + + +/config/*.* +!/config/custom-environment-variables.json +!/config/example.json +/static/app.css +_*.ts +/.idea +/public/ + +/dev/*.manifest.json +/build/ \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..bb76965 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,17 @@ +{ + "trailingComma": "none", + "bracketSpacing": true, + "quoteProps": "consistent", + "tabWidth": 4, + "semi": false, + "singleQuote": true, + "printWidth": 1000, + "overrides": [ + { + "files": ["**/*.ts"], + "options": { + "printWidth": 180 + } + } + ] +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..a603835 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2023 FRC Team 1540 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/MAINTAINING.md b/MAINTAINING.md new file mode 100644 index 0000000..173fe2c --- /dev/null +++ b/MAINTAINING.md @@ -0,0 +1,55 @@ +# Maintaining + +## Adding Members + +Visit `/admin/members/` and enter data in the bottom row. Slack IDs will automatically populate by the email + +## Adjusting Seasons + +Set the `start_date` field to the date you want to start tracking current hour information from, typically around kickoff or the end of summer. Any hour submissions after this point will be counted towards totals. It can be in any format accepted by the [Javascript Date constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date) + +## Refreshing Veracross photos + +Open [the student directory](https://portals.veracross.com/catlin/student/directory/1) and paste the following into the browser console. You may need to disable CSP to run this script. + +```js +function loadNext() { + console.log('loading more pages...') + const elem = document.querySelector('.DirectoryEntries_LoadMoreEntriesButton') + if (elem) { + elem.click() + setTimeout(loadNext, 1000) + } else { + const people = document.querySelectorAll('.directory-Entry_Header') + const data = {} + people.forEach((person) => { + const email = person.querySelector('a')?.innerHTML?.trim() + const photo = person.querySelector('.directory-Entry_PersonPhoto--full')?.src + if (email && photo) { + data[email] = photo + } else { + console.log('missing data for', person) + } + }) + fetch('https://cluck.team1540.org/api/members/fallback_photos', { + method: 'POST', + headers: { 'X-Api-Key': 'YOUR-API-KEY' }, + body: JSON.stringify(data) + }) + .then((resp) => resp.text()) + .then(console.log) + } +} + +loadNext() +``` + +## Adding Member Fields + +Additional fields not used by CLUCK can be added to the spreadsheet directly. See the [registered column](https://docs.google.com/spreadsheets/d/1p18eJW29CzLn-zZKBKm-OOM6BtR-oLlrZVfNJtNPl9A/edit?gid=568325748#gid=568325748&range=B2:B46) and ['extra' sheet](https://docs.google.com/spreadsheets/d/1p18eJW29CzLn-zZKBKm-OOM6BtR-oLlrZVfNJtNPl9A/edit?gid=2140052736#gid=2140052736) in the template spreadsheet for an example + +- Hours & Certs row 3 represents the table that the column is found in +- Hours & Certs row 4 is the column name +- Hours & Certs row 5 is automatically calculated to be the column index + +Make sure to update [the db model](prisma/schema.prisma), [the spreadsheet mapping](src/spreadsheet/index.ts), and the [member dashboard](src/views/admin_members) if adding new fields to CLUCK. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7bc0fed --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# CLUCK + +CLUCK is a team management tool designed by and for FRC Team 1540. It manages hour tracking and certifications, integrating with Slack and Google Sheets. + +## Installation + +See [SETUP.md](SETUP.md) for installation instructions. + +## Feature Showcase + +| Label | Image | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Spreadsheet | [![Spreadsheet](https://github.com/user-attachments/assets/1cbaee67-7ab1-4e45-9774-339cd8300b24)](https://docs.google.com/spreadsheets/d/1p18eJW29CzLn-zZKBKm-OOM6BtR-oLlrZVfNJtNPl9A/edit?gid=568325748#gid=568325748) | +| Slack Time Logging | ![Slack](https://github.com/user-attachments/assets/80a61a51-43b1-4673-a483-513a408e6726) | +| Slack User Info | ![Slack](https://github.com/user-attachments/assets/df06e2c2-933e-4472-a1c3-cf830798bffa) | +| Member Dashboard | ![Dashboard](https://github.com/user-attachments/assets/3e1229ec-ecdf-44c8-9ab9-4ed958d4e78b) | +| Certifications Dashboard | ![Certifications](https://github.com/user-attachments/assets/cdd3dbf4-bbce-4a3b-9463-8fe612d294e5) | diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..c27bdcd --- /dev/null +++ b/SETUP.md @@ -0,0 +1,55 @@ +# Setup + +## Config + +Set the `start_date` field to the date you want to start tracking current hour information from. This should be the start of the current season, and is used to calculate which hour logs count towards totals. It can be in any format accepted by the [Javascript Date constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date) + +Make a copy of the `./config/example.json` file as `./config/yourenvlabel.json` and adjust the fields as needed. You will need to set the environment variable `NODE_ENV='yourenvlabel'` when running to load the correct config file + +Create a `.env` file in the base folder, and in it set the following DATABASE_URL variable for Prisma + +```bash +DATABASE_URL="postgres://user@host/db" +``` + +## Database + +Once you have a postgres database running, run the following to apply the Prisma schema + +```bash +npx prisma migrate deploy +``` + +Then, to populate the database with dummy data (for development), run `dev/seed_db.ts`. This will remove existing database records, do not use in production + +```bash +tsx dev/seed_db.ts +``` + +## Slack + +If you plan to run this in a workspace with other instances, you'll need to generate a manifest with different slash command names. First, set the `slack.app.command_prefix` field in your config file, then run the following to create the customized manifest: + +```bash +tsx dev/generate_manifest.ts +``` + +If your instance is the only one, you can use `dev/manifest.json` directly +You'll need to [create a Slack application](https://api.slack.com/apps), which you should do from the app manifest you're using. + +- After you complete the setup flow, click "Install to Workspace" +- `slack.app.signing_secret` can be found in Basic Information > App Credentials > Signing Secret +- `slack.app.app_token` should be generated in Basic Information > App-Level Tokens with scope `connections:write` +- `slack.app.bot_token` can be found in OAuth & Permissions > Bot User OAuth Token + +The slack user token needs to be authorized by an administrator, and requires the scopes shown in `dev/user_manifest.json`. You can either add these to the main app's manifest (if the main app will be authorized by a slack administrator) or create a separate app for the user token. The user token is used to set profile fields and usergroups by selected departments. +`slack.app.user_token` can be left as an empty string if desired to disable the profile field and department usergroup functionality, otherwise it will appear next to where the bot token was after reauthorization. + +Make sure to also set the appropriate user and channel ids + +## Spreadsheet + +CLUCK is designed to work with a Google Sheets spreadsheet as a frontend. You can make a copy of the [template spreadsheet](https://docs.google.com/spreadsheets/d/1p18eJW29CzLn-zZKBKm-OOM6BtR-oLlrZVfNJtNPl9A/copy) and set the `google.sheet.id` field in your config file to the id in the URL. +Then, to create the credentials, follow [Google's instructions to create a service account](https://developers.google.com/workspace/guides/create-credentials#service-account), and download the `credentials.json` file. Set the `google.account.private_key` and `google.account.client_email` entries in your config file to the matching keys in `credentials.json`. + +You'll need to share the spreadsheet with the email address in `client_email` as an editor to allow the bot to access it. diff --git a/config/example.json b/config/example.json new file mode 100644 index 0000000..4a8ee03 --- /dev/null +++ b/config/example.json @@ -0,0 +1,37 @@ +{ + "slack": { + "app": { + "signing_secret": "XXX", + "bot_token": "xoxb-XXX", + "app_token": "xapp-X-XXX", + "user_token": "xoxp-XXX", + "command_prefix": "" + }, + "profile": { + "department": "XAAAAA", + "certs": "XAAAA" + }, + "channels": { + "celebration": "CXXXX", + "void": "CXXX", + "approval": "DXXX", + "certification_approval": "DXXX" + }, + "groups": { + "students": "SXXX" + }, + "users": { + "admins": ["UXXX"] + } + }, + "google": { + "sheet_id": "", + "account": { + "private_key": "", + "client_email": "" + } + }, + "start_date": "2024-06-01", + "port": 3000, + "default_photo": "/static/img/default_member.webp" +} diff --git a/dev/generate_manifest.ts b/dev/generate_manifest.ts new file mode 100644 index 0000000..a7db9ec --- /dev/null +++ b/dev/generate_manifest.ts @@ -0,0 +1,14 @@ +import prod_manifest from './manifest.json' +import fs from 'fs/promises' +import config from '~config' + +const dev_manifest = structuredClone(prod_manifest) +const prefix = config.slack.app.command_prefix +if (prefix) { + dev_manifest.features.slash_commands.forEach((cmd) => { + cmd.command = '/' + prefix + '_' + cmd.command.slice(1) + }) + dev_manifest.display_information.name += ` (${prefix})` + dev_manifest.features.bot_user.display_name += ` (${prefix})` +} +await fs.writeFile(`./dev/${prefix}.manifest.json`, JSON.stringify(dev_manifest, null, 4)) diff --git a/dev/manifest.json b/dev/manifest.json new file mode 100644 index 0000000..efc3bc6 --- /dev/null +++ b/dev/manifest.json @@ -0,0 +1,84 @@ +{ + "display_information": { + "name": "CLUCK", + "description": "A Bot for Logging Robotics Hours", + "background_color": "#2b323d" + }, + "features": { + "app_home": { + "home_tab_enabled": true, + "messages_tab_enabled": true, + "messages_tab_read_only_enabled": false + }, + "bot_user": { + "display_name": "Time Sheet", + "always_online": true + }, + "shortcuts": [ + { + "name": "Log Hours", + "type": "global", + "callback_id": "log_time", + "description": "Opens interactive hours logging popup" + } + ], + "slash_commands": [ + { + "command": "/log", + "description": "Log Hours and Minutes", + "usage_hint": "[hours]h [minutes]m [activity]", + "should_escape": false + }, + { + "command": "/graph", + "description": "Graphs hours for members", + "usage_hint": "[@user]...", + "should_escape": true + }, + { + "command": "/certify", + "description": "[Manager Only] Give a user a certification", + "should_escape": true + }, + { + "command": "/clearlogin", + "description": "Logs you out, giving no hours", + "should_escape": false + }, + { + "command": "/voidtime", + "description": "[Copresident Only]", + "usage_hint": "", + "should_escape": true + }, + { + "command": "/loggedin", + "description": "Shows who is currently signed in", + "should_escape": false + }, + { + "command": "/hours", + "description": "Shows your current hour information", + "should_escape": false + }, + { + "command": "/departments", + "description": "Allows you to manage your department associations", + "should_escape": false + } + ] + }, + "oauth_config": { + "scopes": { + "bot": ["app_mentions:read", "channels:history", "channels:join", "channels:read", "chat:write", "chat:write.public", "commands", "files:write", "groups:read", "im:history", "im:read", "im:write", "mpim:read", "usergroups:read", "users:read", "users.profile:read", "users:read.email"] + } + }, + "settings": { + "event_subscriptions": { + "bot_events": ["app_home_opened", "app_mention"] + }, + "org_deploy_enabled": false, + "socket_mode_enabled": true, + "token_rotation_enabled": false + } +} diff --git a/dev/seed_accounts.ts b/dev/seed_accounts.ts new file mode 100644 index 0000000..aea32d3 --- /dev/null +++ b/dev/seed_accounts.ts @@ -0,0 +1,19 @@ +import { PrismaClient } from '@prisma/client' +import { createUser } from '~lib/auth' +import logger from '~lib/logger' + +const prisma = new PrismaClient() + +async function main() { + await createUser('zach', 'oneringtorulethemall', true, true) +} + +await main() + .then(async () => { + await prisma.$disconnect() + }) + .catch(async (e) => { + logger.error(e) + await prisma.$disconnect() + process.exit(1) + }) diff --git a/dev/seed_db.ts b/dev/seed_db.ts new file mode 100644 index 0000000..299a898 --- /dev/null +++ b/dev/seed_db.ts @@ -0,0 +1,226 @@ +import { enum_MeetingAttendances_state, Prisma, PrismaClient } from '@prisma/client' +import { faker } from '@faker-js/faker' +import { toTitleCase } from '~lib/util' +import logger from '~lib/logger' + +const prisma = new PrismaClient() + +async function main() { + await seedDepartments() + await seedCerts() + await seedMembers(25) + + await seedDepartmentAssociations() + await seedMemberCerts() + await seedLabHours(600) + await seedMeetings(6) +} + +async function seedMembers(count: number) { + await prisma.member.deleteMany({ where: { slack_id: null } }) + const members: Prisma.MemberCreateManyInput[] = [] + for (let i = 0; i < count; i++) { + const first = faker.person.firstName() + const last = faker.person.lastName() + members.push({ + email: last.toLowerCase() + first.toLowerCase().slice(0, 2) + '@domain.edu', + first_name: first, + full_name: first + ' ' + last, + use_slack_photo: false, + fallback_photo: faker.image.urlPicsumPhotos({ width: 100, height: 100 }) + }) + } + await prisma.member.createMany({ data: members }) +} +const cert_departments: [string, string][] = [ + ['fab', 'fab'], + ['design', 'dsn'], + ['strategy', 'strat'], + ['media', 'media'], + ['robot software', 'rsw'], + ['controls', 'ctrls'], + ['pneumatics', 'pnu'], + ['app software', 'asw'], + ['outreach', 'outrch'], + ['awards', 'award'], + ['executive', 'exec'] +] + +const standalone_certs: [string, string][] = [ + ['safety captain', '_safety'], + ['copresident', '_copres'] +] + +async function seedDepartments() { + await prisma.department.deleteMany() + const departments: Prisma.DepartmentCreateManyInput[] = [] + cert_departments.forEach(([name, shortname]) => { + departments.push({ + id: shortname, + name: toTitleCase(name) + }) + }) + await prisma.department.createMany({ data: departments }) +} + +async function seedDepartmentAssociations() { + const departments = await prisma.department.findMany() + const members = await prisma.member.findMany() + const input: Prisma.DepartmentAssociationCreateManyInput[] = [] + members.forEach((member) => { + if (member.slack_id == null) { + const dept = departments[Math.floor(Math.random() * departments.length)] + input.push({ + member_id: member.email, + department_id: dept.id + }) + const dept2 = departments[Math.floor(Math.random() * departments.length)] + input.push({ + member_id: member.email, + department_id: dept2.id + }) + } + }) + await prisma.departmentAssociation.createMany({ data: input }) +} + +async function seedCerts() { + await prisma.cert.deleteMany() + const certs: Prisma.CertCreateManyInput[] = [] + cert_departments.forEach(([name, shortname]) => { + for (let i = 1; i <= 3; i++) { + certs.push({ + id: shortname.toUpperCase() + '_' + i, + label: toTitleCase(name) + ' ' + i, + replaces: i > 1 ? shortname.toUpperCase() + '_' + (i - 1) : null, + department: shortname + }) + } + certs.push({ + id: shortname.toUpperCase() + '_MGR', + label: toTitleCase(name) + ' Manager', + isManager: true, + department: shortname + }) + }) + standalone_certs.forEach(([name, shortname]) => { + certs.push({ + id: shortname.toUpperCase(), + label: toTitleCase(name) + }) + }) + await prisma.cert.createMany({ data: certs }) +} + +async function seedLabHours(count: number) { + await prisma.hourLog.deleteMany({ where: { type: 'lab' } }) + const members = await prisma.member.findMany() + const randomEmail = () => { + const i = Math.round(Math.random() * (members.length - 1)) + return members[i].email + } + const hourlogs: Prisma.HourLogCreateManyInput[] = [] + for (let i = 0; i < count; i++) { + const time_in = faker.date.recent({ days: 15 }) + const duration = faker.number.float({ min: 0.2, max: 4, fractionDigits: 2 }) + hourlogs.push({ + member_id: randomEmail(), + time_in: faker.date.recent({ days: 15 }), + time_out: new Date(time_in.getTime() + duration * 60 * 60 * 1000), + type: 'lab', + state: 'complete', + duration: new Prisma.Decimal(duration) + }) + } + await prisma.hourLog.createMany({ data: hourlogs }) +} + +async function seedMemberCerts() { + await prisma.memberCert.deleteMany() + const members = await prisma.member.findMany() + const input: Prisma.MemberCertCreateManyInput[] = [] + members.forEach((member) => { + cert_departments.forEach((dept) => { + let level = null + switch (Math.round(Math.random() * 14)) { + case 0: + case 1: + case 2: + level = 1 + break + case 3: + case 4: + level = 2 + break + case 5: + level = 3 + } + if (level == 3 && Math.random() < 0.5) { + input.push({ + member_id: member.email, + cert_id: dept[1].toUpperCase() + '_MGR', + announced: true + }) + } + if (level != null) { + input.push({ + member_id: member.email, + cert_id: dept[1].toUpperCase() + '_' + level, + announced: true + }) + } + }) + }) + await prisma.memberCert.createMany({ data: input }) +} + +const MEETING_SPACING = 1000 * 60 * 60 * 24 * 7 +async function seedMeetings(count: number) { + await prisma.meetings.deleteMany() + await prisma.meetingAttendanceEntry.deleteMany() + + const newMeetings: Prisma.MeetingsCreateManyInput[] = [] + let meetingDate = Date.now() - count * MEETING_SPACING + for (let i = 0; i < count; i++) { + newMeetings.push({ + date: new Date(meetingDate), + mandatory: true + }) + meetingDate += MEETING_SPACING + } + const meetings = await prisma.meetings.createManyAndReturn({ + select: { id: true }, + data: newMeetings + }) + + const newMeetingAttendance: Prisma.MeetingAttendanceEntryCreateManyInput[] = [] + const members = await prisma.member.findMany({ select: { email: true } }) + meetings.forEach((meeting, i) => { + if (i + 1 == count) { + return // don't fill for last meeting + } + members.forEach((member) => { + const rand = Math.random() + let state: enum_MeetingAttendances_state = enum_MeetingAttendances_state.present + if (rand < 0.3) state = enum_MeetingAttendances_state.absent + if (rand < 0.05) state = enum_MeetingAttendances_state.no_credit + + newMeetingAttendance.push({ + state: state, + meeting_id: meeting.id, + member_id: member.email + }) + }) + }) + await prisma.meetingAttendanceEntry.createMany({ data: newMeetingAttendance }) +} + +await main() + .then(async () => { + await prisma.$disconnect() + }) + .catch(async (e) => { + logger.error(e) + await prisma.$disconnect() + process.exit(1) + }) diff --git a/dev/user_manifest.json b/dev/user_manifest.json new file mode 100644 index 0000000..63271fc --- /dev/null +++ b/dev/user_manifest.json @@ -0,0 +1,17 @@ +{ + "display_information": { + "name": "CLUCK User Updater", + "description": "A Slack app that updates user profiles with the latest information from the CLUCK database.", + "background_color": "#2b323d" + }, + "oauth_config": { + "scopes": { + "user": ["usergroups:write", "users.profile:read", "users.profile:write"] + } + }, + "settings": { + "org_deploy_enabled": false, + "socket_mode_enabled": false, + "token_rotation_enabled": false + } +} diff --git a/esbuild-backend.ts b/esbuild-backend.ts new file mode 100644 index 0000000..5bdb738 --- /dev/null +++ b/esbuild-backend.ts @@ -0,0 +1,12 @@ +import esbuild from 'esbuild' + +await esbuild.build({ + entryPoints: ['src/index.ts'], + tsconfig: 'tsconfig.json', + bundle: true, + packages: 'external', + platform: 'node', + format: 'esm', + sourcemap: 'linked', + outfile: 'build/index.js' +}) diff --git a/esbuild-frontend.ts b/esbuild-frontend.ts new file mode 100644 index 0000000..a407cda --- /dev/null +++ b/esbuild-frontend.ts @@ -0,0 +1,71 @@ +import htmlPlugin from '@chialab/esbuild-plugin-html' +import { sassPlugin } from 'esbuild-sass-plugin' +import esbuild from 'esbuild' +import fs from 'fs/promises' +import path from 'path' +import logger from '~lib/logger' + +fs.stat('./public').catch(async () => { + await fs.mkdir('./public') +}) + +const views: Record = { + grid: '/grid/', + admin_members: '/admin/members/', + admin_certs: '/admin/certs/', + admin_departments: '/admin/departments/', + admin_meetings: '/admin/meetings/' +} + +const contexts: esbuild.BuildContext[] = [] +for (const id in views) { + contexts.push( + await esbuild.context({ + entryPoints: [path.join('src/views', id, 'index.html')], + outdir: path.join('public/', views[id]), + assetNames: `assets/[name]`, + chunkNames: `assets/[name]`, + plugins: [ + sassPlugin(), + htmlPlugin({ minifyOptions: { minifySvg: false } }), + { + name: 'rebuild-notify', + setup(build) { + build.onEnd((result) => { + logger.info(`${id} ended with ${result.errors.length} errors`) + // HERE: somehow restart the server from here, e.g., by sending a signal that you trap and react to inside the server. + }) + } + } + ], + bundle: true, + minify: true, + sourcemap: 'inline', + external: ['/static/*'] + }) + ) + logger.info('Loaded build context for ' + id) +} +const watch = process.argv.includes('--watch') +// const endPromise = Promise.all( +// contexts.map(async (context) => { +// if (watch) { +// logger.info('adding watch listener') +// await context.watch({}) +// } else { +// await context.rebuild() +// await context.dispose() +// } +// }) +// ) +if (watch) { + logger.info('Watching...') + for (const context of contexts) { + await context.watch({}) + } + logger.info('Done...') +} else { + logger.info('Building...') + await Promise.all(contexts.map((ctx) => ctx.rebuild().then(ctx.dispose))) + logger.info('Build complete') +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..7247179 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,22 @@ +// @ts-check + +import eslint from '@eslint/js' +import tseslint from 'typescript-eslint' +import globals from 'globals' + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + { files: ['src/**/*.ts'] }, + { + ignores: ['public/', '**/moses.js', '**/_*.ts', 'build/'] + }, + { files: ['static/js/**.js'], languageOptions: { globals: globals.browser } }, + { + rules: { + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': 'warn', + 'prefer-const': 'warn' + } + } +) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..2dc2c79 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8652 @@ +{ + "name": "cluck2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cluck2", + "dependencies": { + "@googleapis/sheets": "^9.0.0", + "@hono/node-server": "^1.12.0", + "@prisma/client": "^5.17.0", + "@slack/bolt": "^4.0.0-rc.1", + "@slack/web-api": "^7", + "ag-grid-community": "^32.0.2", + "async-lock": "^1.4.1", + "config": "^3.3.12", + "core-js-pure": "^3.37.1", + "google-auth-library": "^9.11.0", + "hono": "^4.5.0", + "pino": "^9.3.1", + "pino-pretty": "^11.2.1", + "quickchart-js": "^3.1.3", + "reflect-metadata": "^0.2.2", + "slack-block-builder": "^2.8.0", + "socket.io": "^4.7.5", + "socket.io-client": "^4.7.5", + "ws": "^8.18.0" + }, + "devDependencies": { + "@chialab/esbuild-plugin-html": "^0.18.2", + "@eslint/js": "^9.6.0", + "@faker-js/faker": "^8.4.1", + "@types/async-lock": "^1.4.2", + "@types/config": "^3.3.4", + "@types/core-js": "^2.5.8", + "@types/eslint__js": "^8.42.3", + "@types/node": "^20.14.11", + "@types/validator": "^13.12.0", + "@types/ws": "^8.5.11", + "autoprefixer": "^10.4.19", + "esbuild": "^0.21.5", + "esbuild-sass-plugin": "^3.3.1", + "eslint": "^9.7.0", + "eslint-formatter-markdown": "^1.0.4", + "htmlnano": "^2.1.1", + "npm-run-all": "^4.1.5", + "pre-commit": "^1.2.2", + "prettier": "^3.3.3", + "prisma": "^5.17.0", + "tailwindcss": "^3.4.6", + "tsx": "^4.16.2", + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.0-alpha.41", + "vite-tsconfig-paths": "^4.3.2", + "vitest": "^2.0.3" + }, + "engines": { + "node": ">=21.0.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz", + "integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==", + "dev": true, + "peer": true + }, + "node_modules/@chialab/esbuild-plugin-html": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/@chialab/esbuild-plugin-html/-/esbuild-plugin-html-0.18.2.tgz", + "integrity": "sha512-8dBqIZIL1tkLYyMko+HY8b80QoDOIGlydbIQO8ALLv4e59UO0MGQjFG3RCD0V5sMbpjeNrJ3MCKz29wGL16Wrg==", + "dev": true, + "dependencies": { + "@chialab/esbuild-rna": "^0.18.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "htmlnano": "^2.0.0" + }, + "peerDependenciesMeta": { + "htmlnano": { + "optional": true + } + } + }, + "node_modules/@chialab/esbuild-rna": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/@chialab/esbuild-rna/-/esbuild-rna-0.18.2.tgz", + "integrity": "sha512-ckzskez7bxstVQ4c5cxbx0DRP2teldzrcSGQl2KPh1VJGdO2ZmRrb6vNkBBD5K3dx9tgTyvskWp4dV+Fbg07Ag==", + "dev": true, + "dependencies": { + "@chialab/estransform": "^0.18.0", + "@chialab/node-resolve": "^0.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@chialab/estransform": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/@chialab/estransform/-/estransform-0.18.1.tgz", + "integrity": "sha512-W/WmjpQL2hndD0/XfR0FcPBAUj+aLNeoAVehOjV/Q9bSnioz0GVSAXXhzp59S33ZynxJBBfn8DNiMTVNJmk4Aw==", + "dev": true, + "dependencies": { + "@parcel/source-map": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@chialab/node-resolve": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@chialab/node-resolve/-/node-resolve-0.18.0.tgz", + "integrity": "sha512-eV1m70Qn9pLY9xwFmZ2FlcOzwiaUywsJ7NB/ud8VB7DouvCQtIHkQ3Om7uPX0ojXGEG1LCyO96kZkvbNTxNu0Q==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", + "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.7.0.tgz", + "integrity": "sha512-ChuWDQenef8OSFnvuxv0TCVxEwmu3+hPNKvM9B34qpM0rDRbjL8t5QkQeHHeAfsKQjuH9wS82WeCi1J/owatng==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@faker-js/faker": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", + "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, + "node_modules/@googleapis/sheets": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@googleapis/sheets/-/sheets-9.0.0.tgz", + "integrity": "sha512-CKoczlGSjWlPyous7nX626b/Bj9BK9DiVxaMwzWK8bkm1qk7VrSg/074y+XFQ+QPyyNyOIWB9tOFm99/PbEhXw==", + "dependencies": { + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@hono/node-server": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.12.0.tgz", + "integrity": "sha512-e6oHjNiErRxsZRZBmc2KucuvY3btlO/XPncIpP2X75bRdTilF9GLjm3NHvKKunpJbbJJj31/FoPTksTf8djAVw==", + "engines": { + "node": ">=18.14.1" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@parcel/source-map": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@parcel/source-map/-/source-map-2.1.1.tgz", + "integrity": "sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==", + "dev": true, + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": "^12.18.3 || >=14" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@prisma/client": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.17.0.tgz", + "integrity": "sha512-N2tnyKayT0Zf7mHjwEyE8iG7FwTmXDHFZ1GnNhQp0pJUObsuel4ZZ1XwfuAYkq5mRIiC/Kot0kt0tGCfLJ70Jw==", + "hasInstallScript": true, + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.17.0.tgz", + "integrity": "sha512-l7+AteR3P8FXiYyo496zkuoiJ5r9jLQEdUuxIxNCN1ud8rdbH3GTxm+f+dCyaSv9l9WY+29L9czaVRXz9mULfg==", + "devOptional": true + }, + "node_modules/@prisma/engines": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.17.0.tgz", + "integrity": "sha512-+r+Nf+JP210Jur+/X8SIPLtz+uW9YA4QO5IXA+KcSOBe/shT47bCcRMTYCbOESw3FFYFTwe7vU6KTWHKPiwvtg==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/debug": "5.17.0", + "@prisma/engines-version": "5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053", + "@prisma/fetch-engine": "5.17.0", + "@prisma/get-platform": "5.17.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053.tgz", + "integrity": "sha512-tUuxZZysZDcrk5oaNOdrBnnkoTtmNQPkzINFDjz7eG6vcs9AVDmA/F6K5Plsb2aQc/l5M2EnFqn3htng9FA4hg==", + "devOptional": true + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.17.0.tgz", + "integrity": "sha512-ESxiOaHuC488ilLPnrv/tM2KrPhQB5TRris/IeIV4ZvUuKeaicCl4Xj/JCQeG9IlxqOgf1cCg5h5vAzlewN91Q==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "5.17.0", + "@prisma/engines-version": "5.17.0-31.393aa359c9ad4a4bb28630fb5613f9c281cde053", + "@prisma/get-platform": "5.17.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.17.0.tgz", + "integrity": "sha512-UlDgbRozCP1rfJ5Tlkf3Cnftb6srGrEQ4Nm3og+1Se2gWmCZ0hmPIi+tQikGDUVLlvOWx3Gyi9LzgRP+HTXV9w==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "5.17.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.1.tgz", + "integrity": "sha512-lncuC4aHicncmbORnx+dUaAgzee9cm/PbIqgWz1PpXuwc+sa1Ct83tnqUDy/GFKleLiN7ZIeytM6KJ4cAn1SxA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.1.tgz", + "integrity": "sha512-F/tkdw0WSs4ojqz5Ovrw5r9odqzFjb5LIgHdHZG65dFI1lWTWRVy32KDJLKRISHgJvqUeUhdIvy43fX41znyDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.1.tgz", + "integrity": "sha512-vk+ma8iC1ebje/ahpxpnrfVQJibTMyHdWpOGZ3JpQ7Mgn/3QNHmPq7YwjZbIE7km73dH5M1e6MRRsnEBW7v5CQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.1.tgz", + "integrity": "sha512-IgpzXKauRe1Tafcej9STjSSuG0Ghu/xGYH+qG6JwsAUxXrnkvNHcq/NL6nz1+jzvWAnQkuAJ4uIwGB48K9OCGA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.1.tgz", + "integrity": "sha512-P9bSiAUnSSM7EmyRK+e5wgpqai86QOSv8BwvkGjLwYuOpaeomiZWifEos517CwbG+aZl1T4clSE1YqqH2JRs+g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.1.tgz", + "integrity": "sha512-5RnjpACoxtS+aWOI1dURKno11d7krfpGDEn19jI8BuWmSBbUC4ytIADfROM1FZrFhQPSoP+KEa3NlEScznBTyQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.1.tgz", + "integrity": "sha512-8mwmGD668m8WaGbthrEYZ9CBmPug2QPGWxhJxh/vCgBjro5o96gL04WLlg5BA233OCWLqERy4YUzX3bJGXaJgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.1.tgz", + "integrity": "sha512-dJX9u4r4bqInMGOAQoGYdwDP8lQiisWb9et+T84l2WXk41yEej8v2iGKodmdKimT8cTAYt0jFb+UEBxnPkbXEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.1.tgz", + "integrity": "sha512-V72cXdTl4EI0x6FNmho4D502sy7ed+LuVW6Ym8aI6DRQ9hQZdp5sj0a2usYOlqvFBNKQnLQGwmYnujo2HvjCxQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.1.tgz", + "integrity": "sha512-f+pJih7sxoKmbjghrM2RkWo2WHUW8UbfxIQiWo5yeCaCM0TveMEuAzKJte4QskBp1TIinpnRcxkquY+4WuY/tg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.1.tgz", + "integrity": "sha512-qb1hMMT3Fr/Qz1OKovCuUM11MUNLUuHeBC2DPPAWUYYUAOFWaxInaTwTQmc7Fl5La7DShTEpmYwgdt2hG+4TEg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.1.tgz", + "integrity": "sha512-7O5u/p6oKUFYjRbZkL2FLbwsyoJAjyeXHCU3O4ndvzg2OFO2GinFPSJFGbiwFDaCFc+k7gs9CF243PwdPQFh5g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.1.tgz", + "integrity": "sha512-pDLkYITdYrH/9Cv/Vlj8HppDuLMDUBmgsM0+N+xLtFd18aXgM9Nyqupb/Uw+HeidhfYg2lD6CXvz6CjoVOaKjQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.1.tgz", + "integrity": "sha512-W2ZNI323O/8pJdBGil1oCauuCzmVd9lDmWBBqxYZcOqWD6aWqJtVBQ1dFrF4dYpZPks6F+xCZHfzG5hYlSHZ6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.1.tgz", + "integrity": "sha512-ELfEX1/+eGZYMaCIbK4jqLxO1gyTSOIlZr6pbC4SRYFaSIDVKOnZNMdoZ+ON0mrFDp4+H5MhwNC1H/AhE3zQLg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.1.tgz", + "integrity": "sha512-yjk2MAkQmoaPYCSu35RLJ62+dz358nE83VfTePJRp8CG7aMg25mEJYpXFiD+NcevhX8LxD5OP5tktPXnXN7GDw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@slack/bolt": { + "version": "4.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@slack/bolt/-/bolt-4.0.0-rc.1.tgz", + "integrity": "sha512-s5cp/nR1uIQzS+IZu8MtBl1VKRRgG79GtnQzUfCzKvMyZWxcS87LQXRuajJ2QwOUwvICg7X8T30ubhbgMipDpg==", + "dependencies": { + "@slack/logger": "^4.0.0", + "@slack/oauth": "^2.6.2", + "@slack/socket-mode": "2.0.0-rc.2", + "@slack/types": "^2.11.0", + "@slack/web-api": "^7", + "@types/express": "^4.16.1", + "@types/promise.allsettled": "^1.0.3", + "@types/tsscmp": "^1.0.0", + "axios": "^1.6.0", + "express": "^4.16.4", + "path-to-regexp": "^6.2.1", + "please-upgrade-node": "^3.2.0", + "promise.allsettled": "^1.0.2", + "raw-body": "^2.3.3", + "tsscmp": "^1.0.6" + }, + "engines": { + "node": ">=12.13.0", + "npm": ">=6.12.0" + } + }, + "node_modules/@slack/logger": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz", + "integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==", + "dependencies": { + "@types/node": ">=18.0.0" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/oauth": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@slack/oauth/-/oauth-2.6.2.tgz", + "integrity": "sha512-2R3MyB/R63hTRXzk5J6wcui59TBxXzhk+Uh2/Xu3Wp3O4pXg/BNucQhP/DQbL/ScVhLvFtMXirLrKi0Yo5gIVw==", + "dependencies": { + "@slack/logger": "^3.0.0", + "@slack/web-api": "^6.11.2", + "@types/jsonwebtoken": "^8.3.7", + "@types/node": ">=12", + "jsonwebtoken": "^9.0.0", + "lodash.isstring": "^4.0.1" + }, + "engines": { + "node": ">=12.13.0", + "npm": ">=6.12.0" + } + }, + "node_modules/@slack/oauth/node_modules/@slack/logger": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-3.0.0.tgz", + "integrity": "sha512-DTuBFbqu4gGfajREEMrkq5jBhcnskinhr4+AnfJEk48zhVeEv3XnUKGIX98B74kxhYsIMfApGGySTn7V3b5yBA==", + "dependencies": { + "@types/node": ">=12.0.0" + }, + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/oauth/node_modules/@slack/web-api": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-6.12.0.tgz", + "integrity": "sha512-RPw6F8rWfGveGkZEJ4+4jUin5iazxRK2q3FpQDz/FvdgzC3nZmPyLx8WRzc6nh0w3MBjEbphNnp2VZksfhpBIQ==", + "dependencies": { + "@slack/logger": "^3.0.0", + "@slack/types": "^2.11.0", + "@types/is-stream": "^1.1.0", + "@types/node": ">=12.0.0", + "axios": "^1.6.5", + "eventemitter3": "^3.1.0", + "form-data": "^2.5.0", + "is-electron": "2.2.2", + "is-stream": "^1.1.0", + "p-queue": "^6.6.1", + "p-retry": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/oauth/node_modules/eventemitter3": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", + "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" + }, + "node_modules/@slack/oauth/node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/@slack/oauth/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@slack/socket-mode": { + "version": "2.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@slack/socket-mode/-/socket-mode-2.0.0-rc.2.tgz", + "integrity": "sha512-VKUHXObuYBo2YmR4Wh+Ec/IllfOPkjyWMqn9rMHOs+8koat/s4+IvEHuG5TJw6TVbHSuFuoOXaFuXo+/XId3fQ==", + "dependencies": { + "@slack/logger": "^4", + "@slack/web-api": "^7.0.1", + "@types/node": ">=18", + "@types/ws": "^8", + "eventemitter3": "^5", + "finity": "^0.5.4", + "ws": "^8" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/types": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.12.0.tgz", + "integrity": "sha512-yFewzUomYZ2BYaGJidPuIgjoYj5wqPDmi7DLSaGIkf+rCi4YZ2Z3DaiYIbz7qb/PL2NmamWjCvB7e9ArI5HkKg==", + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/web-api": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.3.1.tgz", + "integrity": "sha512-n2KfnlqjaPJ5y98nU5Nn0UwTIxafLwQSQIOLpsQXKGYDF24S/ap5Ebv+ifVMtY+vIJBqj1q2+l3W9bpFHeiJ2A==", + "dependencies": { + "@slack/logger": "^4.0.0", + "@slack/types": "^2.9.0", + "@types/node": ">=18.0.0", + "@types/retry": "0.12.0", + "axios": "^1.6.5", + "eventemitter3": "^5.0.1", + "form-data": "^4.0.0", + "is-electron": "2.2.2", + "is-stream": "^2", + "p-queue": "^6", + "p-retry": "^4", + "retry": "^0.13.1" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/@types/async-lock": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/async-lock/-/async-lock-1.4.2.tgz", + "integrity": "sha512-HlZ6Dcr205BmNhwkdXqrg2vkFMN2PluI7Lgr8In3B3wE5PiQHhjRqtW/lGdVU9gw+sM0JcIDx2AN+cW8oSWIcw==", + "dev": true + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/config": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@types/config/-/config-3.3.4.tgz", + "integrity": "sha512-qFiTLnWy+TdPSMIXFHP+87lFXFRM4SXjRS+CSB66+56TrpLNw003y1sh7DGaaC1NGesxgKoT5FDy6dyA1Xju/g==", + "dev": true + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/core-js": { + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/@types/core-js/-/core-js-2.5.8.tgz", + "integrity": "sha512-VgnAj6tIAhJhZdJ8/IpxdatM8G4OD3VWGlp6xIxUGENZlpbob9Ty4VVdC1FIEp0aK6DBscDDjyzy5FB60TuNqg==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint__js": { + "version": "8.42.3", + "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", + "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", + "dev": true, + "dependencies": { + "@types/eslint": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" + }, + "node_modules/@types/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@types/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/jsonwebtoken": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz", + "integrity": "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" + }, + "node_modules/@types/node": { + "version": "20.14.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz", + "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/promise.allsettled": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/promise.allsettled/-/promise.allsettled-1.0.6.tgz", + "integrity": "sha512-wA0UT0HeT2fGHzIFV9kWpYz5mdoyLxKrTgMdZQM++5h6pYAFH73HXcQhefg24nD1yivUFEn5KU+EF4b+CXJ4Wg==" + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/tsscmp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/tsscmp/-/tsscmp-1.0.2.tgz", + "integrity": "sha512-cy7BRSU8GYYgxjcx0Py+8lo5MthuDhlyu076KUcYzVNXL23luYgRHkMG2fIFEc6neckeh/ntP82mw+U4QjZq+g==" + }, + "node_modules/@types/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==", + "dev": true + }, + "node_modules/@types/ws": { + "version": "8.5.11", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.11.tgz", + "integrity": "sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.0.0-alpha.46", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.0-alpha.46.tgz", + "integrity": "sha512-qT5Zds2YwQntRiZfOYAXXy6Ezi1XsPKZ5z99yAFkexte/LcPNBEhdT2hdqwnVv2XR3STSSow4Kj2pfFVHzVhHA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.0.0-alpha.46", + "@typescript-eslint/type-utils": "8.0.0-alpha.46", + "@typescript-eslint/utils": "8.0.0-alpha.46", + "@typescript-eslint/visitor-keys": "8.0.0-alpha.46", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.0.0-alpha.46", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.0-alpha.46.tgz", + "integrity": "sha512-XXD1zsC4OEGbyQsp4OzydvOsrXb6GN9+NuAXTjl4ASU1OGKNg+OVLJuVdu6k2mtuZv0zBr+j8nOJ99z7lt4/iA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.0.0-alpha.46", + "@typescript-eslint/types": "8.0.0-alpha.46", + "@typescript-eslint/typescript-estree": "8.0.0-alpha.46", + "@typescript-eslint/visitor-keys": "8.0.0-alpha.46", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.0.0-alpha.46", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.0-alpha.46.tgz", + "integrity": "sha512-mUXSpB78vwagK4++AtAkOCTaXIUwKb8DwrIQzxj55l6h8dIMtax/efwEMP0rATL6n6oy+QsG1qGHABZBmk8tqg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.0.0-alpha.46", + "@typescript-eslint/visitor-keys": "8.0.0-alpha.46" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.0.0-alpha.46", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.0-alpha.46.tgz", + "integrity": "sha512-JQQkrFEViCbQ/pR0ZVbtbXY5okwGc1I6egEo/hCmWiML80lHLe2TQgS6NJ6mZ4noTo0k1YDwh7HGSmn+gQCWtA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.0.0-alpha.46", + "@typescript-eslint/utils": "8.0.0-alpha.46", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.0.0-alpha.46", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.0-alpha.46.tgz", + "integrity": "sha512-NvvbUafjBeownwQYZH0UOMJdwbzWaPErGflbLCtjeHxMF+zSeqkW3tsXkHJtujZ/J1OfyDfxybSLB0rHroBVxg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.0.0-alpha.46", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.0-alpha.46.tgz", + "integrity": "sha512-SCsZ6D8OwUpX8IYcmiIgRb2/AB7qW5EQSZmjfrNPglzCBH/r389EGw6+6p2D+j7fKYJlREEA22mVQztbbUKtZw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.0.0-alpha.46", + "@typescript-eslint/visitor-keys": "8.0.0-alpha.46", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.0.0-alpha.46", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0-alpha.46.tgz", + "integrity": "sha512-dVh+7pv+9lF4cdhr3ZYjJIp04J4ZjKslkyr1VuA9g5nG5s8ENoznCnYsA3wXGu+pkJCIINb2VDFbz6O03c6Jgw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.0.0-alpha.46", + "@typescript-eslint/types": "8.0.0-alpha.46", + "@typescript-eslint/typescript-estree": "8.0.0-alpha.46" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.0.0-alpha.46", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.0-alpha.46.tgz", + "integrity": "sha512-/foiKGq6SKu/HoLkCHsxjnvu1CsC6DTOA3lHJsb/ObTteIFZhh7ek7YAiChmyfZNclAdhjiLYDNp71ezBp2VGQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.0.0-alpha.46", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vitest/expect": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.3.tgz", + "integrity": "sha512-X6AepoOYePM0lDNUPsGXTxgXZAl3EXd0GYe/MZyVE4HzkUqyUVC6S3PrY5mClDJ6/7/7vALLMV3+xD/Ko60Hqg==", + "dev": true, + "dependencies": { + "@vitest/spy": "2.0.3", + "@vitest/utils": "2.0.3", + "chai": "^5.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.3.tgz", + "integrity": "sha512-URM4GLsB2xD37nnTyvf6kfObFafxmycCL8un3OC9gaCs5cti2u+5rJdIflZ2fUJUen4NbvF6jCufwViAFLvz1g==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.3.tgz", + "integrity": "sha512-EmSP4mcjYhAcuBWwqgpjR3FYVeiA4ROzRunqKltWjBfLNs1tnMLtF+qtgd5ClTwkDP6/DGlKJTNa6WxNK0bNYQ==", + "dev": true, + "dependencies": { + "@vitest/utils": "2.0.3", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.3.tgz", + "integrity": "sha512-6OyA6v65Oe3tTzoSuRPcU6kh9m+mPL1vQ2jDlPdn9IQoUxl8rXhBnfICNOC+vwxWY684Vt5UPgtcA2aPFBb6wg==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.0.3", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.3.tgz", + "integrity": "sha512-sfqyAw/ypOXlaj4S+w8689qKM1OyPOqnonqOc9T91DsoHbfN5mU7FdifWWv3MtQFf0lEUstEwR9L/q/M390C+A==", + "dev": true, + "dependencies": { + "tinyspy": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-c/UdELMuHitQbbc/EVctlBaxoYAwQPQdSNwv7z/vHyBKy2edYZaFgptE27BRueZB7eW8po+cllotMNTDpL3HWg==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "2.0.3", + "estree-walker": "^3.0.3", + "loupe": "^3.1.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ag-charts-types": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/ag-charts-types/-/ag-charts-types-10.0.2.tgz", + "integrity": "sha512-Nxo5slHOXlaeg0gRIsVnovAosQzzlYfWJtdDy0Aq/VvpJru/PJ+5i2c9aCyEhgRxhBjImsoegwkgRj7gNOWV6Q==" + }, + "node_modules/ag-grid-community": { + "version": "32.0.2", + "resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-32.0.2.tgz", + "integrity": "sha512-vLJJUjnsG9hNK41GNuW2EHu1W264kxA/poOpcX4kmyrjU5Uzvelsbj3HdKAO9POV28iqyRdKGYfAWdn8QzA7KA==", + "dependencies": { + "ag-charts-types": "10.0.2" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.map": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/array.prototype.map/-/array.prototype.map-1.0.7.tgz", + "integrity": "sha512-XpcFfLoBEAhezrrNw1V+yLXkE7M6uR7xJEsxbG6c/V9v043qurwVJB9r9UTnoSioFDoz1i1VOydpWGmJpfVZbg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-array-method-boxes-properly": "^1.0.0", + "es-object-atoms": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001640", + "electron-to-chromium": "^1.4.820", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-builder": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/buffer-builder/-/buffer-builder-0.2.0.tgz", + "integrity": "sha512-7VPMEPuYznPSoR21NE1zvd2Xna6c/CloiZCfcMXR1Jny6PjX0N4Nsa38zcBFo/FMK+BlA+FLKbJCQ0i2yxp+Xg==", + "dev": true, + "peer": true + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001642", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz", + "integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/config": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/config/-/config-3.3.12.tgz", + "integrity": "sha512-Vmx389R/QVM3foxqBzXO8t2tUikYZP64Q6vQxGrsMpREeJc/aWRnPRERXWsYzOHAumx/AOoILWe6nU3ZJL+6Sw==", + "dependencies": { + "json5": "^2.2.3" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-js-pure": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.37.1.tgz", + "integrity": "sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dir-glob/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.831", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.831.tgz", + "integrity": "sha512-6cKy9msoQGWhBF30n6y5ck80BdunKdwMRugM1lr74DMMXhjDRxSwMvrD1ncPP+n1PjF+KKmZZydvEF29skNZ1Q==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", + "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/esbuild-sass-plugin": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/esbuild-sass-plugin/-/esbuild-sass-plugin-3.3.1.tgz", + "integrity": "sha512-SnO1ls+d52n6j8gRRpjexXI8MsHEaumS0IdDHaYM29Y6gakzZYMls6i9ql9+AWMSQk/eryndmUpXEgT34QrX1A==", + "dev": true, + "dependencies": { + "resolve": "^1.22.8", + "safe-identifier": "^0.4.2", + "sass": "^1.71.1" + }, + "peerDependencies": { + "esbuild": ">=0.20.1", + "sass-embedded": "^1.71.1" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.7.0.tgz", + "integrity": "sha512-FzJ9D/0nGiCGBf8UXO/IGLTgLVzIxze1zpfA8Ton2mjLovXdAPlYDv+MQDcqj3TmrhAGYfOpz9RfR+ent0AgAw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.17.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.7.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/eslint-formatter-markdown": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/eslint-formatter-markdown/-/eslint-formatter-markdown-1.0.4.tgz", + "integrity": "sha512-0QI3QIfhJejbUugu2XBhG8EGKcAmfeGKOYck9fuFpWWNbHn88tNWvXpShvxa85nVzW9Ha86dlYP+XGePRI4EtA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.4" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/finity": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/finity/-/finity-0.5.4.tgz", + "integrity": "sha512-3l+5/1tuw616Lgb0QBimxfdd2TqaDGpfCBpfX6EqtFmqUV3FtQnVEX4Aa62DagYEqnsTIjZcTfbq9msDbXYgyA==" + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.0.tgz", + "integrity": "sha512-DSrkyMTfAnAm4ks9Go20QGOcXEyW/NmZhvTYBU2rb4afBB393WIMQPWPEDMl/k8xqiNN9HYq2zao3oWXsdl2Tg==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", + "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, + "node_modules/google-auth-library": { + "version": "9.11.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.11.0.tgz", + "integrity": "sha512-epX3ww/mNnhl6tL45EQ/oixsY8JLEgUFoT4A5E/5iAR4esld9Kqv6IJGk7EmGuOgDvaarwF95hU2+v7Irql9lw==", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, + "node_modules/hono": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.5.0.tgz", + "integrity": "sha512-ZbezypZfn4odyApjCCv+Fw5OgweBqRLA/EsMyc4FUknFvBJcBIKhHy4sqmD1rWpBc/3wUlaQ6tqOPjk36R1ckg==", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/htmlnano": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/htmlnano/-/htmlnano-2.1.1.tgz", + "integrity": "sha512-kAERyg/LuNZYmdqgCdYvugyLWNFAm8MWXpQMz1pLpetmCbFwoMxvkSoaAMlFrOC4OKTWI4KlZGT/RsNxg4ghOw==", + "dev": true, + "dependencies": { + "cosmiconfig": "^9.0.0", + "posthtml": "^0.16.5", + "timsort": "^0.3.0" + }, + "peerDependencies": { + "cssnano": "^7.0.0", + "postcss": "^8.3.11", + "purgecss": "^6.0.0", + "relateurl": "^0.2.7", + "srcset": "5.0.1", + "svgo": "^3.0.2", + "terser": "^5.10.0", + "uncss": "^0.17.3" + }, + "peerDependenciesMeta": { + "cssnano": { + "optional": true + }, + "postcss": { + "optional": true + }, + "purgecss": { + "optional": true + }, + "relateurl": { + "optional": true + }, + "srcset": { + "optional": true + }, + "svgo": { + "optional": true + }, + "terser": { + "optional": true + }, + "uncss": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", + "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", + "dev": true + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/iterate-iterator": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.2.tgz", + "integrity": "sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/iterate-value": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/iterate-value/-/iterate-value-1.0.2.tgz", + "integrity": "sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ==", + "dependencies": { + "es-get-iterator": "^1.0.2", + "iterate-iterator": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/javascript-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", + "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==" + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, + "node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-releases": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz", + "integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/npm-run-all/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/npm-run-all/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-to-regexp": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", + "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" + }, + "node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pino": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.3.1.tgz", + "integrity": "sha512-afSfrq/hUiW/MFmQcLEwV9Zh8Ry6MrMTOyBU53o/fc0gEl+1OZ/Fks/xQCM2nOC0C/OfDtQMnT2d8c3kpcfSzA==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.2.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.2.1.tgz", + "integrity": "sha512-O05NuD9tkRasFRWVaF/uHLOvoRDFD7tb5VMertr78rbsYFjYp48Vg3477EshVAF5eZaEw+OpDl/tu+B0R5o+7g==", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz", + "integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/posthtml": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.6.tgz", + "integrity": "sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==", + "dev": true, + "dependencies": { + "posthtml-parser": "^0.11.0", + "posthtml-render": "^3.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/posthtml-parser": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", + "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", + "dev": true, + "dependencies": { + "htmlparser2": "^7.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/posthtml-render": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", + "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", + "dev": true, + "dependencies": { + "is-json": "^2.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/pre-commit": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/pre-commit/-/pre-commit-1.2.2.tgz", + "integrity": "sha512-qokTiqxD6GjODy5ETAIgzsRgnBWWQHQH2ghy86PU7mIn/wuWeTwF3otyNQZxWBwVn8XNr8Tdzj/QfUXpH+gRZA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "spawn-sync": "^1.0.15", + "which": "1.2.x" + } + }, + "node_modules/pre-commit/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/pre-commit/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/pre-commit/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pre-commit/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pre-commit/node_modules/which": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", + "integrity": "sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prisma": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.17.0.tgz", + "integrity": "sha512-m4UWkN5lBE6yevqeOxEvmepnL5cNPEjzMw2IqDB59AcEV6w7D8vGljDLd1gPFH+W6gUxw9x7/RmN5dCS/WTPxA==", + "devOptional": true, + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "5.17.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" + }, + "node_modules/promise.allsettled": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/promise.allsettled/-/promise.allsettled-1.0.7.tgz", + "integrity": "sha512-hezvKvQQmsFkOdrZfYxUxkyxl8mgFQeT259Ajj9PXdbg9VzBCWrItOev72JyWxkCD5VSSqAeHmlN3tWx4DlmsA==", + "dependencies": { + "array.prototype.map": "^1.0.5", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "iterate-value": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/quickchart-js": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/quickchart-js/-/quickchart-js-3.1.3.tgz", + "integrity": "sha512-QzPUXBA/UntYBbOMITtMz7B426fes1XFmmjmjA070jXeMWhyhDojJf2aSZPsekj35ywfJhWjY6TKf3S0/XxyAg==", + "dependencies": { + "cross-fetch": "^3.1.5", + "javascript-stringify": "^2.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.1.tgz", + "integrity": "sha512-Elx2UT8lzxxOXMpy5HWQGZqkrQOtrVDDa/bm9l10+U4rQnVzbL/LgZ4NOM1MPIDyHk69W4InuYDF5dzRh4Kw1A==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.1", + "@rollup/rollup-android-arm64": "4.18.1", + "@rollup/rollup-darwin-arm64": "4.18.1", + "@rollup/rollup-darwin-x64": "4.18.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.1", + "@rollup/rollup-linux-arm-musleabihf": "4.18.1", + "@rollup/rollup-linux-arm64-gnu": "4.18.1", + "@rollup/rollup-linux-arm64-musl": "4.18.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.1", + "@rollup/rollup-linux-riscv64-gnu": "4.18.1", + "@rollup/rollup-linux-s390x-gnu": "4.18.1", + "@rollup/rollup-linux-x64-gnu": "4.18.1", + "@rollup/rollup-linux-x64-musl": "4.18.1", + "@rollup/rollup-win32-arm64-msvc": "4.18.1", + "@rollup/rollup-win32-ia32-msvc": "4.18.1", + "@rollup/rollup-win32-x64-msvc": "4.18.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-identifier": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz", + "integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==", + "dev": true + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sass": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", + "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.77.8.tgz", + "integrity": "sha512-WGXA6jcaoBo5Uhw0HX/s6z/sl3zyYQ7ZOnLOJzqwpctFcFmU4L07zn51e2VSkXXFpQZFAdMZNqOGz/7h/fvcRA==", + "dev": true, + "peer": true, + "dependencies": { + "@bufbuild/protobuf": "^1.0.0", + "buffer-builder": "^0.2.0", + "immutable": "^4.0.0", + "rxjs": "^7.4.0", + "supports-color": "^8.1.1", + "varint": "^6.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "optionalDependencies": { + "sass-embedded-android-arm": "1.77.8", + "sass-embedded-android-arm64": "1.77.8", + "sass-embedded-android-ia32": "1.77.8", + "sass-embedded-android-x64": "1.77.8", + "sass-embedded-darwin-arm64": "1.77.8", + "sass-embedded-darwin-x64": "1.77.8", + "sass-embedded-linux-arm": "1.77.8", + "sass-embedded-linux-arm64": "1.77.8", + "sass-embedded-linux-ia32": "1.77.8", + "sass-embedded-linux-musl-arm": "1.77.8", + "sass-embedded-linux-musl-arm64": "1.77.8", + "sass-embedded-linux-musl-ia32": "1.77.8", + "sass-embedded-linux-musl-x64": "1.77.8", + "sass-embedded-linux-x64": "1.77.8", + "sass-embedded-win32-arm64": "1.77.8", + "sass-embedded-win32-ia32": "1.77.8", + "sass-embedded-win32-x64": "1.77.8" + } + }, + "node_modules/sass-embedded-android-arm": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm/-/sass-embedded-android-arm-1.77.8.tgz", + "integrity": "sha512-GpGL7xZ7V1XpFbnflib/NWbM0euRzineK0iwoo31/ntWKAXGj03iHhGzkSiOwWSFcXgsJJi3eRA5BTmBvK5Q+w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "bin": { + "sass": "dart-sass/sass" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-arm64": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.77.8.tgz", + "integrity": "sha512-EmWHLbEx0Zo/f/lTFzMeH2Du+/I4RmSRlEnERSUKQWVp3aBSO04QDvdxfFezgQ+2Yt/ub9WMqBpma9P/8MPsLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "bin": { + "sass": "dart-sass/sass" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-ia32": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-android-ia32/-/sass-embedded-android-ia32-1.77.8.tgz", + "integrity": "sha512-+GjfJ3lDezPi4dUUyjQBxlNKXNa+XVWsExtGvVNkv1uKyaOxULJhubVo2G6QTJJU0esJdfeXf5Ca5/J0ph7+7w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "bin": { + "sass": "dart-sass/sass" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-android-x64": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-android-x64/-/sass-embedded-android-x64-1.77.8.tgz", + "integrity": "sha512-YZbFDzGe5NhaMCygShqkeCWtzjhkWxGVunc7ULR97wmxYPQLPeVyx7XFQZc84Aj0lKAJBJS4qRZeqphMqZEJsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "peer": true, + "bin": { + "sass": "dart-sass/sass" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-arm64": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.77.8.tgz", + "integrity": "sha512-aifgeVRNE+i43toIkDFFJc/aPLMo0PJ5s5hKb52U+oNdiJE36n65n2L8F/8z3zZRvCa6eYtFY2b7f1QXR3B0LA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "bin": { + "sass": "dart-sass/sass" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-darwin-x64": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.77.8.tgz", + "integrity": "sha512-/VWZQtcWIOek60Zj6Sxk6HebXA1Qyyt3sD8o5qwbTgZnKitB1iEBuNunyGoAgMNeUz2PRd6rVki6hvbas9hQ6w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "bin": { + "sass": "dart-sass/sass" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.77.8.tgz", + "integrity": "sha512-2edZMB6jf0whx3T0zlgH+p131kOEmWp+I4wnKj7ZMUeokiY4Up05d10hSvb0Q63lOrSjFAWu6P5/pcYUUx8arQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "bin": { + "sass": "dart-sass/sass" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-arm64": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.77.8.tgz", + "integrity": "sha512-6iIOIZtBFa2YfMsHqOb3qake3C9d/zlKxjooKKnTSo+6g6z+CLTzMXe1bOfayb7yxeenElmFoK1k54kWD/40+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "bin": { + "sass": "dart-sass/sass" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-ia32": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-ia32/-/sass-embedded-linux-ia32-1.77.8.tgz", + "integrity": "sha512-63GsFFHWN5yRLTWiSef32TM/XmjhCBx1DFhoqxmj+Yc6L9Z1h0lDHjjwdG6Sp5XTz5EmsaFKjpDgnQTP9hJX3Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "bin": { + "sass": "dart-sass/sass" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.77.8.tgz", + "integrity": "sha512-nFkhSl3uu9btubm+JBW7uRglNVJ8W8dGfzVqh3fyQJKS1oyBC3vT3VOtfbT9YivXk28wXscSHpqXZwY7bUuopA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-arm64": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.77.8.tgz", + "integrity": "sha512-j8cgQxNWecYK+aH8ESFsyam/Q6G+9gg8eJegiRVpA9x8yk3ykfHC7UdQWwUcF22ZcuY4zegrjJx8k+thsgsOVA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-ia32": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-ia32/-/sass-embedded-linux-musl-ia32-1.77.8.tgz", + "integrity": "sha512-oWveMe+8TFlP8WBWPna/+Ec5TV0CE+PxEutyi0ltSruBds2zxRq9dPVOqrpPcDN9QUx50vNZC0Afgch0aQEd0g==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-musl-x64": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.77.8.tgz", + "integrity": "sha512-2NtRpMXHeFo9kaYxuZ+Ewwo39CE7BTS2JDfXkTjZTZqd8H+8KC53eBh516YQnn2oiqxSiKxm7a6pxbxGZGwXOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-linux-x64": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.77.8.tgz", + "integrity": "sha512-ND5qZLWUCpOn7LJfOf0gLSZUWhNIysY+7NZK1Ctq+pM6tpJky3JM5I1jSMplNxv5H3o8p80n0gSm+fcjsEFfjQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "bin": { + "sass": "dart-sass/sass" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-arm64": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.77.8.tgz", + "integrity": "sha512-7L8zT6xzEvTYj86MvUWnbkWYCNQP+74HvruLILmiPPE+TCgOjgdi750709BtppVJGGZSs40ZuN6mi/YQyGtwXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "bin": { + "sass": "dart-sass/sass.bat" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-ia32": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-ia32/-/sass-embedded-win32-ia32-1.77.8.tgz", + "integrity": "sha512-7Buh+4bP0WyYn6XPbthkIa3M2vtcR8QIsFVg3JElVlr+8Ng19jqe0t0SwggDgbMX6AdQZC+Wj4F1BprZSok42A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "bin": { + "sass": "dart-sass/sass.bat" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded-win32-x64": { + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.77.8.tgz", + "integrity": "sha512-rZmLIx4/LLQm+4GW39sRJW0MIlDqmyV0fkRzTmhFP5i/wVC7cuj8TUubPHw18rv2rkHFfBZKZJTCkPjCS5Z+SA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "bin": { + "sass": "dart-sass/sass.bat" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-embedded/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==" + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slack-block-builder": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/slack-block-builder/-/slack-block-builder-2.8.0.tgz", + "integrity": "sha512-iisM+j99iKRuQFVfdWo0FiszDAl3r8Snq704oZH6C0RbDqvoVQStiptt6Y7kc6RX/5hSAqTqjhgvZ/di8cvaIA==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sonic-boom": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz", + "integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", + "dev": true + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", + "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", + "dev": true + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", + "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.6.tgz", + "integrity": "sha512-1uRHzPB+Vzu57ocybfZ4jh5Q3SdlH7XW23J5sQoM9LhE9eIOlzxer/3XPSsycvih3rboRsvt0QCmzSrqyOYUIA==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==", + "dev": true + }, + "node_modules/tinybench": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz", + "integrity": "sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==", + "dev": true + }, + "node_modules/tinypool": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.0.tgz", + "integrity": "sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.0.tgz", + "integrity": "sha512-q5nmENpTHgiPVd1cJDDc9cVoYN5x4vCvwT3FMilvKPKneCBZAxn2YWQjDF0UMcE9k0Cay1gBiDfTMU0g+mPMQA==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/tsconfck": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.1.tgz", + "integrity": "sha512-00eoI6WY57SvZEVjm13stEVE90VkEdJAFGgpFLTsZbJyW/LwFQ7uQxJHWpZ2hzSWgCPKc9AnBnNP+0X7o3hAmQ==", + "dev": true, + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "peer": true + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/tsx": { + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.16.2.tgz", + "integrity": "sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==", + "dev": true, + "dependencies": { + "esbuild": "~0.21.5", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.0.0-alpha.46", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.0.0-alpha.46.tgz", + "integrity": "sha512-S3T/Taq3V2G1iZaT+VDOln+twi4k5iNIUJTIeoF12HEfImGXhTy1jarMxSipdwjHA/DhhCgOTJhjlzt3QiG+sw==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.0.0-alpha.46", + "@typescript-eslint/parser": "8.0.0-alpha.46", + "@typescript-eslint/utils": "8.0.0-alpha.46" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "name": "uri-js-replace", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.0.tgz", + "integrity": "sha512-bXmav/bdI0EwnetCfw5ZYOzRWmetZyqEcBZCLkFQ8GSrlvNBkXX1Nq7m++h1WOcSFm9sPiRS4OWNwbvBaGsXoQ==", + "dev": true + }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/varint": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", + "integrity": "sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==", + "dev": true, + "peer": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.4.tgz", + "integrity": "sha512-Cw+7zL3ZG9/NZBB8C+8QbQZmR54GwqIz+WMI4b3JgdYJvX+ny9AjJXqkGQlDXSXRP9rP0B4tbciRMOVEKulVOA==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.39", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.0.3.tgz", + "integrity": "sha512-14jzwMx7XTcMB+9BhGQyoEAmSl0eOr3nrnn+Z12WNERtOvLN+d2scbRUvyni05rT3997Bg+rZb47NyP4IQPKXg==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.5", + "pathe": "^1.1.2", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz", + "integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.0.3.tgz", + "integrity": "sha512-o3HRvU93q6qZK4rI2JrhKyZMMuxg/JRt30E6qeQs6ueaiz5hr1cPj+Sk2kATgQzMMqsa2DiNI0TIK++1ULx8Jw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@vitest/expect": "2.0.3", + "@vitest/pretty-format": "^2.0.3", + "@vitest/runner": "2.0.3", + "@vitest/snapshot": "2.0.3", + "@vitest/spy": "2.0.3", + "@vitest/utils": "2.0.3", + "chai": "^5.1.1", + "debug": "^4.3.5", + "execa": "^8.0.1", + "magic-string": "^0.30.10", + "pathe": "^1.1.2", + "std-env": "^3.7.0", + "tinybench": "^2.8.0", + "tinypool": "^1.0.0", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.0.3", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.0.3", + "@vitest/ui": "2.0.3", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2688f7d --- /dev/null +++ b/package.json @@ -0,0 +1,79 @@ +{ + "name": "cluck2", + "type": "module", + "scripts": { + "dev": "npm-run-all -c --parallel watch:**", + "dev:frontend": "npm-run-all -c --parallel serve build:css watch:frontend", + "watch:backend": "tsx watch src/index.ts --tsconfig ./tsconfig.json", + "watch:css": "tailwindcss -i ./src/app.css -o static/app.css --watch", + "watch:frontend": "tsx esbuild-frontend.ts --watch", + "build:css": "tailwindcss -i ./src/app.css -o static/app.css --minify", + "build:frontend": "tsx esbuild-frontend.ts", + "build:backend": "tsx esbuild-backend.ts", + "build": "npm-run-all -c --parallel build:**", + "prod": "NODE_ENV=prod node --enable-source-maps build/index.js", + "lint:style": "prettier --check .", + "lint:code": "eslint . -c eslint.config.js", + "lint": "npm-run-all -l -c --parallel lint:**", + "format": "prettier --write ." + }, + "dependencies": { + "@googleapis/sheets": "^9.0.0", + "@hono/node-server": "^1.12.0", + "@prisma/client": "^5.17.0", + "@slack/bolt": "^4.0.0-rc.1", + "@slack/web-api": "^7", + "ag-grid-community": "^32.0.2", + "async-lock": "^1.4.1", + "config": "^3.3.12", + "core-js-pure": "^3.37.1", + "google-auth-library": "^9.11.0", + "hono": "^4.5.0", + "pino": "^9.3.1", + "pino-pretty": "^11.2.1", + "quickchart-js": "^3.1.3", + "reflect-metadata": "^0.2.2", + "slack-block-builder": "^2.8.0", + "socket.io": "^4.7.5", + "socket.io-client": "^4.7.5", + "ws": "^8.18.0" + }, + "devDependencies": { + "@chialab/esbuild-plugin-html": "^0.18.2", + "@eslint/js": "^9.6.0", + "@faker-js/faker": "^8.4.1", + "@types/async-lock": "^1.4.2", + "@types/config": "^3.3.4", + "@types/core-js": "^2.5.8", + "@types/eslint__js": "^8.42.3", + "@types/node": "^20.14.11", + "@types/validator": "^13.12.0", + "@types/ws": "^8.5.11", + "autoprefixer": "^10.4.19", + "esbuild": "^0.21.5", + "esbuild-sass-plugin": "^3.3.1", + "eslint": "^9.7.0", + "eslint-formatter-markdown": "^1.0.4", + "htmlnano": "^2.1.1", + "npm-run-all": "^4.1.5", + "pre-commit": "^1.2.2", + "prettier": "^3.3.3", + "prisma": "^5.17.0", + "tailwindcss": "^3.4.6", + "tsx": "^4.16.2", + "typescript": "^5.5.3", + "typescript-eslint": "^8.0.0-alpha.41", + "vite-tsconfig-paths": "^4.3.2", + "vitest": "^2.0.3" + }, + "engines": { + "node": ">=21.0.0" + }, + "overrides": { + "uri-js": "npm:uri-js-replace" + }, + "pre-commit": [ + "lint:code", + "format" + ] +} diff --git a/prisma/migrations/0_init/migration.sql b/prisma/migrations/0_init/migration.sql new file mode 100644 index 0000000..9664d2c --- /dev/null +++ b/prisma/migrations/0_init/migration.sql @@ -0,0 +1,144 @@ +-- CreateEnum +CREATE TYPE "attendance_state" AS ENUM ('present', 'absent', 'no_credit'); + +-- CreateEnum +CREATE TYPE "enum_HourLogs_state" AS ENUM ('complete', 'pending', 'cancelled'); + +-- CreateEnum +CREATE TYPE "enum_HourLogs_type" AS ENUM ('lab', 'external', 'summer', 'event'); + +-- CreateEnum +CREATE TYPE "enum_MeetingAttendances_state" AS ENUM ('present', 'absent', 'no_credit'); + +-- CreateEnum +CREATE TYPE "enum_Members_slack_leaderboard_type" AS ENUM ('weekly', 'department'); + +-- CreateEnum +CREATE TYPE "enum_Members_team" AS ENUM ('primary', 'junior'); + +-- CreateEnum +CREATE TYPE "hours_category" AS ENUM ('lab', 'external', 'summer', 'event'); + +-- CreateEnum +CREATE TYPE "hours_state" AS ENUM ('complete', 'in_progress', 'pending', 'voided'); + +-- CreateEnum +CREATE TYPE "team_option" AS ENUM ('primary', 'junior'); + +-- CreateTable +CREATE TABLE "Accounts" ( + "id" VARCHAR(255) NOT NULL, + "password" CHAR(64) NOT NULL, + "api_key" CHAR(36) NOT NULL, + "write_access" BOOLEAN NOT NULL, + "admin_access" BOOLEAN NOT NULL, + "createdAt" TIMESTAMPTZ(6) NOT NULL, + "updatedAt" TIMESTAMPTZ(6) NOT NULL, + + CONSTRAINT "Accounts_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Certs" ( + "id" VARCHAR(15) NOT NULL, + "label" VARCHAR(100) NOT NULL, + "department" VARCHAR(50) NOT NULL, + "level" SMALLINT NOT NULL, + "createdAt" TIMESTAMPTZ(6) NOT NULL, + "updatedAt" TIMESTAMPTZ(6) NOT NULL, + + CONSTRAINT "Certs_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "HourLogs" ( + "id" SERIAL NOT NULL, + "member_id" VARCHAR(50) NOT NULL, + "time_in" TIMESTAMPTZ(6) NOT NULL, + "time_out" TIMESTAMPTZ(6), + "duration" DECIMAL(6,3), + "type" "enum_HourLogs_type" NOT NULL, + "state" "enum_HourLogs_state" NOT NULL, + "message" VARCHAR(2000), + "slack_ts" DECIMAL(16,6), + "createdAt" TIMESTAMPTZ(6) NOT NULL, + "updatedAt" TIMESTAMPTZ(6) NOT NULL, + + CONSTRAINT "HourLogs_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "MeetingAttendances" ( + "id" SERIAL NOT NULL, + "state" "enum_MeetingAttendances_state" NOT NULL, + "meeting_id" SMALLINT NOT NULL, + "member_id" VARCHAR(50) NOT NULL, + "createdAt" TIMESTAMPTZ(6) NOT NULL, + "updatedAt" TIMESTAMPTZ(6) NOT NULL, + + CONSTRAINT "MeetingAttendances_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Meetings" ( + "id" SMALLSERIAL NOT NULL, + "date" DATE NOT NULL, + "mandatory" BOOLEAN NOT NULL, + "createdAt" TIMESTAMPTZ(6) NOT NULL, + "updatedAt" TIMESTAMPTZ(6) NOT NULL, + + CONSTRAINT "Meetings_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Members" ( + "email" VARCHAR(50) NOT NULL, + "first_name" VARCHAR(50) NOT NULL, + "full_name" VARCHAR(100) NOT NULL, + "team" "enum_Members_team" NOT NULL, + "grade" SMALLINT NOT NULL, + "years" SMALLINT NOT NULL, + "use_slack_photo" BOOLEAN NOT NULL, + "slack_id" VARCHAR(15), + "slack_photo" VARCHAR(255), + "slack_photo_small" VARCHAR(255), + "slack_leaderboard_type" "enum_Members_slack_leaderboard_type", + "slack_department" VARCHAR(50), + "fallback_photo" VARCHAR(255), + "cert_ids" VARCHAR(15)[], + "createdAt" TIMESTAMPTZ(6) NOT NULL, + "updatedAt" TIMESTAMPTZ(6) NOT NULL, + + CONSTRAINT "Members_pkey" PRIMARY KEY ("email") +); + +-- CreateIndex +CREATE INDEX "accounts_api_key" ON "Accounts"("api_key"); + +-- CreateIndex +CREATE INDEX "hour_logs_member_id" ON "HourLogs"("member_id"); + +-- CreateIndex +CREATE INDEX "hour_logs_state" ON "HourLogs"("state"); + +-- CreateIndex +CREATE INDEX "hour_logs_type" ON "HourLogs"("type"); + +-- CreateIndex +CREATE INDEX "meeting_attendances_meeting_id" ON "MeetingAttendances"("meeting_id"); + +-- CreateIndex +CREATE INDEX "meeting_attendances_member_id" ON "MeetingAttendances"("member_id"); + +-- CreateIndex +CREATE INDEX "members_full_name" ON "Members"("full_name"); + +-- AddForeignKey +ALTER TABLE "HourLogs" ADD CONSTRAINT "HourLogs_member_id_fkey" FOREIGN KEY ("member_id") REFERENCES "Members"("email") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MeetingAttendances" ADD CONSTRAINT "MeetingAttendances_meeting_id_fkey" FOREIGN KEY ("meeting_id") REFERENCES "Meetings"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MeetingAttendances" ADD CONSTRAINT "MeetingAttendances_member_id_fkey" FOREIGN KEY ("member_id") REFERENCES "Members"("email") ON DELETE CASCADE ON UPDATE CASCADE; + diff --git a/prisma/migrations/20240619235612_rename_members/migration.sql b/prisma/migrations/20240619235612_rename_members/migration.sql new file mode 100644 index 0000000..1f5e6e4 --- /dev/null +++ b/prisma/migrations/20240619235612_rename_members/migration.sql @@ -0,0 +1,29 @@ +-- AlterTable +ALTER TABLE "Accounts" ALTER COLUMN "createdAt" SET DEFAULT CURRENT_TIMESTAMP; + +-- AlterTable +ALTER TABLE "Certs" ALTER COLUMN "createdAt" SET DEFAULT CURRENT_TIMESTAMP; + +-- AlterTable +ALTER TABLE "HourLogs" ALTER COLUMN "createdAt" SET DEFAULT CURRENT_TIMESTAMP; + +-- AlterTable +ALTER TABLE "MeetingAttendances" ALTER COLUMN "createdAt" SET DEFAULT CURRENT_TIMESTAMP; + +-- AlterTable +ALTER TABLE "Meetings" ALTER COLUMN "createdAt" SET DEFAULT CURRENT_TIMESTAMP; + +-- AlterTable +ALTER TABLE "Members" ALTER COLUMN "createdAt" SET DEFAULT CURRENT_TIMESTAMP; + +-- DropEnum +DROP TYPE "attendance_state"; + +-- DropEnum +DROP TYPE "hours_category"; + +-- DropEnum +DROP TYPE "hours_state"; + +-- DropEnum +DROP TYPE "team_option"; diff --git a/prisma/migrations/20240620002558_add_membercerts/migration.sql b/prisma/migrations/20240620002558_add_membercerts/migration.sql new file mode 100644 index 0000000..7960f0e --- /dev/null +++ b/prisma/migrations/20240620002558_add_membercerts/migration.sql @@ -0,0 +1,22 @@ +-- CreateTable +CREATE TABLE "MemberCerts" ( + "id" SERIAL NOT NULL, + "cert_id" TEXT NOT NULL, + "member_id" TEXT NOT NULL, + "createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMPTZ(6) NOT NULL, + + CONSTRAINT "MemberCerts_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "membercerts_memberid" ON "MemberCerts"("member_id"); + +-- CreateIndex +CREATE INDEX "membercerts_certid" ON "MemberCerts"("cert_id"); + +-- AddForeignKey +ALTER TABLE "MemberCerts" ADD CONSTRAINT "MemberCerts_member_id_fkey" FOREIGN KEY ("member_id") REFERENCES "Members"("email") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MemberCerts" ADD CONSTRAINT "MemberCerts_cert_id_fkey" FOREIGN KEY ("cert_id") REFERENCES "Certs"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20240620041707_add_fallback_photos/migration.sql b/prisma/migrations/20240620041707_add_fallback_photos/migration.sql new file mode 100644 index 0000000..a0cc9bc --- /dev/null +++ b/prisma/migrations/20240620041707_add_fallback_photos/migration.sql @@ -0,0 +1,7 @@ +-- CreateTable +CREATE TABLE "FallbackPhotos" ( + "email" TEXT NOT NULL, + "url" TEXT NOT NULL, + + CONSTRAINT "FallbackPhotos_pkey" PRIMARY KEY ("email") +); diff --git a/prisma/migrations/20240622163642_flexible_leaderboards/migration.sql b/prisma/migrations/20240622163642_flexible_leaderboards/migration.sql new file mode 100644 index 0000000..46323af --- /dev/null +++ b/prisma/migrations/20240622163642_flexible_leaderboards/migration.sql @@ -0,0 +1,12 @@ +/* + Warnings: + + - The `slack_leaderboard_type` column on the `Members` table would be dropped and recreated. This will lead to data loss if there is data in the column. + +*/ +-- AlterTable +ALTER TABLE "Members" DROP COLUMN "slack_leaderboard_type", +ADD COLUMN "slack_leaderboard_type" VARCHAR(50); + +-- DropEnum +DROP TYPE "enum_Members_slack_leaderboard_type"; diff --git a/prisma/migrations/20240622170516_slack_ts_string/migration.sql b/prisma/migrations/20240622170516_slack_ts_string/migration.sql new file mode 100644 index 0000000..182212e --- /dev/null +++ b/prisma/migrations/20240622170516_slack_ts_string/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "HourLogs" ALTER COLUMN "slack_ts" SET DATA TYPE VARCHAR; diff --git a/prisma/migrations/20240708155515_remove_cert_id_array/migration.sql b/prisma/migrations/20240708155515_remove_cert_id_array/migration.sql new file mode 100644 index 0000000..c882a4a --- /dev/null +++ b/prisma/migrations/20240708155515_remove_cert_id_array/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to drop the column `cert_ids` on the `Members` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Members" DROP COLUMN "cert_ids"; diff --git a/prisma/migrations/20240710202052_add_slack_id_index/migration.sql b/prisma/migrations/20240710202052_add_slack_id_index/migration.sql new file mode 100644 index 0000000..c177775 --- /dev/null +++ b/prisma/migrations/20240710202052_add_slack_id_index/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - A unique constraint covering the columns `[slack_id]` on the table `Members` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "Members_slack_id_key" ON "Members"("slack_id"); + +-- CreateIndex +CREATE INDEX "members_slack_id" ON "Members"("slack_id"); diff --git a/prisma/migrations/20240712015813_use_compound_ids/migration.sql b/prisma/migrations/20240712015813_use_compound_ids/migration.sql new file mode 100644 index 0000000..5b99396 --- /dev/null +++ b/prisma/migrations/20240712015813_use_compound_ids/migration.sql @@ -0,0 +1,25 @@ +/* + Warnings: + + - The primary key for the `MeetingAttendances` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to drop the column `id` on the `MeetingAttendances` table. All the data in the column will be lost. + - The primary key for the `MemberCerts` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to drop the column `id` on the `MemberCerts` table. All the data in the column will be lost. + - A unique constraint covering the columns `[slack_id]` on the table `Accounts` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "Accounts" ADD COLUMN "slack_id" BOOLEAN; + +-- AlterTable +ALTER TABLE "MeetingAttendances" DROP CONSTRAINT "MeetingAttendances_pkey", +DROP COLUMN "id", +ADD CONSTRAINT "MeetingAttendances_pkey" PRIMARY KEY ("meeting_id", "member_id"); + +-- AlterTable +ALTER TABLE "MemberCerts" DROP CONSTRAINT "MemberCerts_pkey", +DROP COLUMN "id", +ADD CONSTRAINT "MemberCerts_pkey" PRIMARY KEY ("cert_id", "member_id"); + +-- CreateIndex +CREATE UNIQUE INDEX "Accounts_slack_id_key" ON "Accounts"("slack_id"); diff --git a/prisma/migrations/20240712015938_store_announced_state_on_membercert/migration.sql b/prisma/migrations/20240712015938_store_announced_state_on_membercert/migration.sql new file mode 100644 index 0000000..5f6a256 --- /dev/null +++ b/prisma/migrations/20240712015938_store_announced_state_on_membercert/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "MemberCerts" ADD COLUMN "announced" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/migrations/20240712170110_improve_cert_progression/migration.sql b/prisma/migrations/20240712170110_improve_cert_progression/migration.sql new file mode 100644 index 0000000..2cc6b16 --- /dev/null +++ b/prisma/migrations/20240712170110_improve_cert_progression/migration.sql @@ -0,0 +1,19 @@ +/* + Warnings: + + - You are about to drop the column `department` on the `Certs` table. All the data in the column will be lost. + - You are about to drop the column `level` on the `Certs` table. All the data in the column will be lost. + - A unique constraint covering the columns `[replaces]` on the table `Certs` will be added. If there are existing duplicate values, this will fail. + +*/ +-- AlterTable +ALTER TABLE "Certs" DROP COLUMN "department", +DROP COLUMN "level", +ADD COLUMN "isManager" BOOLEAN NOT NULL DEFAULT false, +ADD COLUMN "replaces" VARCHAR(50); + +-- CreateIndex +CREATE UNIQUE INDEX "Certs_replaces_key" ON "Certs"("replaces"); + +-- AddForeignKey +ALTER TABLE "Certs" ADD CONSTRAINT "Certs_replaces_fkey" FOREIGN KEY ("replaces") REFERENCES "Certs"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20240713151327_add_managed_by_certs/migration.sql b/prisma/migrations/20240713151327_add_managed_by_certs/migration.sql new file mode 100644 index 0000000..c17e63b --- /dev/null +++ b/prisma/migrations/20240713151327_add_managed_by_certs/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Certs" ADD COLUMN "managerCert" VARCHAR(15); + +-- AddForeignKey +ALTER TABLE "Certs" ADD CONSTRAINT "Certs_managerCert_fkey" FOREIGN KEY ("managerCert") REFERENCES "Certs"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20240713153546_more_membercerts_metadata/migration.sql b/prisma/migrations/20240713153546_more_membercerts_metadata/migration.sql new file mode 100644 index 0000000..9968742 --- /dev/null +++ b/prisma/migrations/20240713153546_more_membercerts_metadata/migration.sql @@ -0,0 +1,15 @@ +/* + Warnings: + + - Added the required column `state` to the `MemberCerts` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "enum_MemberCerts_state" AS ENUM ('pending', 'approved'); + +-- AlterTable +ALTER TABLE "MemberCerts" ADD COLUMN "given_by" TEXT, +ADD COLUMN "state" "enum_MemberCerts_state" NOT NULL; + +-- AddForeignKey +ALTER TABLE "MemberCerts" ADD CONSTRAINT "MemberCerts_given_by_fkey" FOREIGN KEY ("given_by") REFERENCES "Members"("email") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20240713153758_adjust_membercert_states/migration.sql b/prisma/migrations/20240713153758_adjust_membercert_states/migration.sql new file mode 100644 index 0000000..8d3d4c9 --- /dev/null +++ b/prisma/migrations/20240713153758_adjust_membercert_states/migration.sql @@ -0,0 +1,14 @@ +/* + Warnings: + + - The values [pending] on the enum `enum_MemberCerts_state` will be removed. If these variants are still used in the database, this will fail. + +*/ +-- AlterEnum +BEGIN; +CREATE TYPE "enum_MemberCerts_state_new" AS ENUM ('pending_create', 'pending_delete', 'approved'); +ALTER TABLE "MemberCerts" ALTER COLUMN "state" TYPE "enum_MemberCerts_state_new" USING ("state"::text::"enum_MemberCerts_state_new"); +ALTER TYPE "enum_MemberCerts_state" RENAME TO "enum_MemberCerts_state_old"; +ALTER TYPE "enum_MemberCerts_state_new" RENAME TO "enum_MemberCerts_state"; +DROP TYPE "enum_MemberCerts_state_old"; +COMMIT; diff --git a/prisma/migrations/20240713154958_use_separate_table_for_cert_requests/migration.sql b/prisma/migrations/20240713154958_use_separate_table_for_cert_requests/migration.sql new file mode 100644 index 0000000..12d504e --- /dev/null +++ b/prisma/migrations/20240713154958_use_separate_table_for_cert_requests/migration.sql @@ -0,0 +1,47 @@ +/* + Warnings: + + - You are about to drop the column `given_by` on the `MemberCerts` table. All the data in the column will be lost. + - You are about to drop the column `state` on the `MemberCerts` table. All the data in the column will be lost. + +*/ +-- CreateEnum +CREATE TYPE "enum_MemberCertsRequest_state" AS ENUM ('pending', 'approved'); + +-- DropForeignKey +ALTER TABLE "MemberCerts" DROP CONSTRAINT "MemberCerts_given_by_fkey"; + +-- AlterTable +ALTER TABLE "MemberCerts" DROP COLUMN "given_by", +DROP COLUMN "state"; + +-- DropEnum +DROP TYPE "enum_MemberCerts_state"; + +-- CreateTable +CREATE TABLE "MemberCertRequests" ( + "id" SERIAL NOT NULL, + "cert_id" TEXT NOT NULL, + "member_id" TEXT NOT NULL, + "requester_id" TEXT NOT NULL, + "state" "enum_MemberCertsRequest_state" NOT NULL, + "createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMPTZ(6) NOT NULL, + + CONSTRAINT "MemberCertRequests_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "membercertrequests_memberid" ON "MemberCertRequests"("member_id"); + +-- CreateIndex +CREATE INDEX "membercertrequests_certid" ON "MemberCertRequests"("cert_id"); + +-- AddForeignKey +ALTER TABLE "MemberCertRequests" ADD CONSTRAINT "MemberCertRequests_requester_id_fkey" FOREIGN KEY ("requester_id") REFERENCES "Members"("email") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MemberCertRequests" ADD CONSTRAINT "MemberCertRequests_member_id_fkey" FOREIGN KEY ("member_id") REFERENCES "Members"("email") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "MemberCertRequests" ADD CONSTRAINT "MemberCertRequests_cert_id_fkey" FOREIGN KEY ("cert_id") REFERENCES "Certs"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20240713163637_store_slack_ts_membercert_request/migration.sql b/prisma/migrations/20240713163637_store_slack_ts_membercert_request/migration.sql new file mode 100644 index 0000000..f7b5331 --- /dev/null +++ b/prisma/migrations/20240713163637_store_slack_ts_membercert_request/migration.sql @@ -0,0 +1,5 @@ +-- AlterEnum +ALTER TYPE "enum_MemberCertsRequest_state" ADD VALUE 'rejected'; + +-- AlterTable +ALTER TABLE "MemberCertRequests" ADD COLUMN "slack_ts" TEXT; diff --git a/prisma/migrations/20240715044728_support_adhoc_fields/migration.sql b/prisma/migrations/20240715044728_support_adhoc_fields/migration.sql new file mode 100644 index 0000000..02efc37 --- /dev/null +++ b/prisma/migrations/20240715044728_support_adhoc_fields/migration.sql @@ -0,0 +1,29 @@ +-- CreateEnum +CREATE TYPE "enum_AdditionalMemberFields_type" AS ENUM ('string', 'boolean'); + +-- CreateTable +CREATE TABLE "AdditionalMemberField" ( + "id" SERIAL NOT NULL, + "type" "enum_AdditionalMemberFields_type" NOT NULL, + "label" VARCHAR NOT NULL, + + CONSTRAINT "AdditionalMemberField_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AdditionalMemberData" ( + "member_id" VARCHAR NOT NULL, + "field_id" INTEGER NOT NULL, + "value" VARCHAR NOT NULL, + + CONSTRAINT "AdditionalMemberData_pkey" PRIMARY KEY ("member_id","field_id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "AdditionalMemberData_member_id_field_id_key" ON "AdditionalMemberData"("member_id", "field_id"); + +-- AddForeignKey +ALTER TABLE "AdditionalMemberData" ADD CONSTRAINT "AdditionalMemberData_member_id_fkey" FOREIGN KEY ("member_id") REFERENCES "Members"("email") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AdditionalMemberData" ADD CONSTRAINT "AdditionalMemberData_field_id_fkey" FOREIGN KEY ("field_id") REFERENCES "AdditionalMemberField"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/20240717032624_remove_adhoc_fields/migration.sql b/prisma/migrations/20240717032624_remove_adhoc_fields/migration.sql new file mode 100644 index 0000000..2c42f99 --- /dev/null +++ b/prisma/migrations/20240717032624_remove_adhoc_fields/migration.sql @@ -0,0 +1,21 @@ +/* + Warnings: + + - You are about to drop the `AdditionalMemberData` table. If the table is not empty, all the data it contains will be lost. + - You are about to drop the `AdditionalMemberField` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "AdditionalMemberData" DROP CONSTRAINT "AdditionalMemberData_field_id_fkey"; + +-- DropForeignKey +ALTER TABLE "AdditionalMemberData" DROP CONSTRAINT "AdditionalMemberData_member_id_fkey"; + +-- DropTable +DROP TABLE "AdditionalMemberData"; + +-- DropTable +DROP TABLE "AdditionalMemberField"; + +-- DropEnum +DROP TYPE "enum_AdditionalMemberFields_type"; diff --git a/prisma/migrations/20240717032948_store_first_registration_status/migration.sql b/prisma/migrations/20240717032948_store_first_registration_status/migration.sql new file mode 100644 index 0000000..db7e20e --- /dev/null +++ b/prisma/migrations/20240717032948_store_first_registration_status/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - You are about to drop the column `slack_department` on the `Members` table. All the data in the column will be lost. + - You are about to drop the column `slack_leaderboard_type` on the `Members` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Members" DROP COLUMN "slack_department", +DROP COLUMN "slack_leaderboard_type", +ADD COLUMN "frc_registered" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/migrations/20240717164928_remove_frc_registered/migration.sql b/prisma/migrations/20240717164928_remove_frc_registered/migration.sql new file mode 100644 index 0000000..9baeb62 --- /dev/null +++ b/prisma/migrations/20240717164928_remove_frc_registered/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - You are about to drop the column `frc_registered` on the `Members` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Members" DROP COLUMN "frc_registered"; diff --git a/prisma/migrations/20240718213204_use_department_associations/migration.sql b/prisma/migrations/20240718213204_use_department_associations/migration.sql new file mode 100644 index 0000000..3685f9c --- /dev/null +++ b/prisma/migrations/20240718213204_use_department_associations/migration.sql @@ -0,0 +1,41 @@ +/* + Warnings: + + - You are about to drop the column `managerCert` on the `Certs` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "Certs" DROP CONSTRAINT "Certs_managerCert_fkey"; + +-- AlterTable +ALTER TABLE "Certs" DROP COLUMN "managerCert", +ADD COLUMN "department" VARCHAR; + +-- CreateTable +CREATE TABLE "Departments" ( + "id" VARCHAR(50) NOT NULL, + "name" VARCHAR(100) NOT NULL, + "slack_group" VARCHAR(50), + "createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMPTZ(6) NOT NULL, + + CONSTRAINT "Departments_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "DepartmentAssociations" ( + "department_id" TEXT NOT NULL, + "member_id" TEXT NOT NULL, + "createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "DepartmentAssociations_pkey" PRIMARY KEY ("department_id","member_id") +); + +-- AddForeignKey +ALTER TABLE "DepartmentAssociations" ADD CONSTRAINT "DepartmentAssociations_department_id_fkey" FOREIGN KEY ("department_id") REFERENCES "Departments"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "DepartmentAssociations" ADD CONSTRAINT "DepartmentAssociations_member_id_fkey" FOREIGN KEY ("member_id") REFERENCES "Members"("email") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Certs" ADD CONSTRAINT "Certs_department_fkey" FOREIGN KEY ("department") REFERENCES "Departments"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/migrations/20240719215100_hourlog_response_field/migration.sql b/prisma/migrations/20240719215100_hourlog_response_field/migration.sql new file mode 100644 index 0000000..82e5ef0 --- /dev/null +++ b/prisma/migrations/20240719215100_hourlog_response_field/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "HourLogs" ADD COLUMN "response" VARCHAR(2000); diff --git a/prisma/migrations/20240731023907_store_member_activity/migration.sql b/prisma/migrations/20240731023907_store_member_activity/migration.sql new file mode 100644 index 0000000..745f047 --- /dev/null +++ b/prisma/migrations/20240731023907_store_member_activity/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Members" ADD COLUMN "active" BOOLEAN NOT NULL DEFAULT true; diff --git a/prisma/migrations/20240801235303_remove_extra_member_info/migration.sql b/prisma/migrations/20240801235303_remove_extra_member_info/migration.sql new file mode 100644 index 0000000..4c0e76b --- /dev/null +++ b/prisma/migrations/20240801235303_remove_extra_member_info/migration.sql @@ -0,0 +1,15 @@ +/* + Warnings: + + - You are about to drop the column `grade` on the `Members` table. All the data in the column will be lost. + - You are about to drop the column `team` on the `Members` table. All the data in the column will be lost. + - You are about to drop the column `years` on the `Members` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Members" DROP COLUMN "grade", +DROP COLUMN "team", +DROP COLUMN "years"; + +-- DropEnum +DROP TYPE "enum_Members_team"; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..f33a980 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,205 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Account { + id String @id @db.VarChar(255) + password String @db.Char(64) + api_key String @db.Char(36) + write_access Boolean + admin_access Boolean + slack_id Boolean? @unique + createdAt DateTime @default(now()) @db.Timestamptz(6) + updatedAt DateTime @updatedAt @db.Timestamptz(6) + + @@index([api_key], map: "accounts_api_key") + @@map("Accounts") +} + +model Department { + id String @id @db.VarChar(50) + name String @db.VarChar(100) + slack_group String? @db.VarChar(50) + createdAt DateTime @default(now()) @db.Timestamptz(6) + updatedAt DateTime @updatedAt @db.Timestamptz(6) + Certs Cert[] + + Members DepartmentAssociation[] + + @@map("Departments") +} + +model DepartmentAssociation { + department_id String + member_id String + createdAt DateTime @default(now()) @db.Timestamptz(6) + + Department Department @relation(fields: [department_id], references: [id], onDelete: Cascade) + Member Member @relation(fields: [member_id], references: [email], onDelete: Cascade) + + @@id([department_id, member_id]) + @@map("DepartmentAssociations") +} + +model Cert { + id String @id @db.VarChar(15) + label String @db.VarChar(100) + isManager Boolean @default(false) + department String? @db.VarChar() + replaces String? @unique @db.VarChar(50) + createdAt DateTime @default(now()) @db.Timestamptz(6) + updatedAt DateTime @updatedAt @db.Timestamptz(6) + ReplacedByCert Cert? @relation("CertProgression") + ReplacesCert Cert? @relation("CertProgression", fields: [replaces], references: [id], onDelete: SetNull) + Department Department? @relation(fields: [department], references: [id], onDelete: SetNull) + Instances MemberCert[] + Requests MemberCertRequest[] + + @@map("Certs") +} + +/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments +model HourLog { + id Int @id @default(autoincrement()) + member_id String @db.VarChar(50) + time_in DateTime @db.Timestamptz(6) + time_out DateTime? @db.Timestamptz(6) + duration Decimal? @db.Decimal(6, 3) + type enum_HourLogs_type + state enum_HourLogs_state + message String? @db.VarChar(2000) + response String? @db.VarChar(2000) + slack_ts String? @db.VarChar() + createdAt DateTime @default(now()) @db.Timestamptz(6) + updatedAt DateTime @updatedAt @db.Timestamptz(6) + Member Member @relation(fields: [member_id], references: [email], onDelete: Cascade) + + @@index([member_id], map: "hour_logs_member_id") + @@index([state], map: "hour_logs_state") + @@index([type], map: "hour_logs_type") + @@map("HourLogs") +} + +model MeetingAttendanceEntry { + state enum_MeetingAttendances_state + meeting_id Int @db.SmallInt + member_id String @db.VarChar(50) + createdAt DateTime @default(now()) @db.Timestamptz(6) + updatedAt DateTime @updatedAt @db.Timestamptz(6) + Meeting Meetings @relation(fields: [meeting_id], references: [id], onDelete: Cascade) + Member Member @relation(fields: [member_id], references: [email], onDelete: Cascade) + + @@id([meeting_id, member_id]) + @@index([meeting_id], map: "meeting_attendances_meeting_id") + @@index([member_id], map: "meeting_attendances_member_id") + @@map("MeetingAttendances") +} + +model Meetings { + id Int @id @default(autoincrement()) @db.SmallInt + date DateTime @db.Date + mandatory Boolean + createdAt DateTime @default(now()) @db.Timestamptz(6) + updatedAt DateTime @updatedAt @db.Timestamptz(6) + Attendances MeetingAttendanceEntry[] + + @@map("Meetings") +} + +model Member { + email String @id @db.VarChar(50) + first_name String @db.VarChar(50) + full_name String @db.VarChar(100) + use_slack_photo Boolean + slack_id String? @unique @db.VarChar(15) + slack_photo String? @db.VarChar(255) + slack_photo_small String? @db.VarChar(255) + fallback_photo String? @db.VarChar(255) + active Boolean @default(true) + + createdAt DateTime @default(now()) @db.Timestamptz(6) + updatedAt DateTime @updatedAt @db.Timestamptz(6) + + HourLogs HourLog[] + MeetingAttendances MeetingAttendanceEntry[] + MemberCerts MemberCert[] + MemberCertRequests MemberCertRequest[] @relation("MemberCertRecipient") + MemberCertsRequested MemberCertRequest[] @relation("MemberCertRequester") + Departments DepartmentAssociation[] + + @@index([slack_id], map: "members_slack_id") + @@index([full_name], map: "members_full_name") + @@map("Members") +} + +model MemberCert { + cert_id String + member_id String + announced Boolean @default(false) + createdAt DateTime @default(now()) @db.Timestamptz(6) + updatedAt DateTime @updatedAt @db.Timestamptz(6) + + Member Member @relation(fields: [member_id], references: [email], onDelete: Cascade) + Cert Cert @relation(fields: [cert_id], references: [id], onDelete: Cascade) + + @@id([cert_id, member_id]) + @@index([member_id], map: "membercerts_memberid") + @@index([cert_id], map: "membercerts_certid") + @@map("MemberCerts") +} + +model MemberCertRequest { + id Int @id @default(autoincrement()) + cert_id String + member_id String + requester_id String + slack_ts String? + state enum_MemberCertsRequest_state + createdAt DateTime @default(now()) @db.Timestamptz(6) + updatedAt DateTime @updatedAt @db.Timestamptz(6) + + Requester Member @relation("MemberCertRequester", fields: [requester_id], references: [email], onDelete: Cascade) + Member Member @relation("MemberCertRecipient", fields: [member_id], references: [email], onDelete: Cascade) + Cert Cert @relation(fields: [cert_id], references: [id], onDelete: Cascade) + + @@index([member_id], map: "membercertrequests_memberid") + @@index([cert_id], map: "membercertrequests_certid") + @@map("MemberCertRequests") +} + +model FallbackPhoto { + email String @id + url String + + @@map("FallbackPhotos") +} + +enum enum_HourLogs_state { + complete + pending + cancelled +} + +enum enum_HourLogs_type { + lab + external + summer + event +} + +enum enum_MeetingAttendances_state { + present + absent + no_credit +} + +enum enum_MemberCertsRequest_state { + pending + approved + rejected +} diff --git a/src/app.css b/src/app.css new file mode 100644 index 0000000..ae29a3a --- /dev/null +++ b/src/app.css @@ -0,0 +1,39 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +.vertical-text-parent { + display: flex; + justify-content: end; + align-items: center; + writing-mode: vertical-rl; +} + +.switch-toggle { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + max-width: fit-content; + height: 100%; + margin-left: 0.25rem; + margin-right: 0.25rem; +} + +.switch-toggle input { + position: absolute; + opacity: 0; +} +.switch-toggle input + label { + padding: 7px; + float: left; + cursor: pointer; + background: #2dd4bf; + transition-duration: 200ms; + transition-property: background-color; +} + +.switch-toggle input:checked + label { + background: #0d9488; + box-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.2); +} diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..5fc5931 --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,13 @@ +import {} from 'hono' + +declare module 'hono' { + interface ContextRenderer { + (content: string | Promise, props?: { title?: string; js?: string; body_class?: string }): Response + } + interface ContextVariableMap { + auth_write: boolean + auth_read: boolean + auth_admin: boolean + user: string + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..b4bef13 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,55 @@ +import type { Server as HttpServer } from 'http' + +import { serve } from '@hono/node-server' +import { serveStatic } from '@hono/node-server/serve-static' +import { Hono } from 'hono' +import { appendTrailingSlash } from 'hono/trailing-slash' +import { compress } from 'hono/compress' +import { secureHeaders } from 'hono/secure-headers' + +import { renderer } from '~lib/renderer' + +import admin_router from '~routes/admin' +import api_router from '~routes/api' +import account_router from '~routes/auth' + +import { requireAdminLogin, requireReadLogin } from '~lib/auth' +import logger from '~lib/logger' +import { startWS } from '~lib/sockets' +import { scheduleTasks } from '~tasks' +import config from '~config' +import { startSlack } from '~slack' + +const app = new Hono() + +// Don't interfere with socket.io +app.use('/ws/*', async (c, next) => {}) + +app.use(renderer) + +app.use(compress()) +app.use(appendTrailingSlash()) +app.use(secureHeaders()) +app.use(async (c, next) => { + logger.debug(`${c.req.method} ${c.req.url}`) + await next() +}) + +app.use('/admin/*', requireAdminLogin) +app.use('/grid/', requireReadLogin) +app.get('/grid', (c) => c.redirect('/grid/', 301)) + +app.route('/admin/', admin_router) +app.route('/api', api_router) +app.route('/auth', account_router) + +app.use('/static/*', serveStatic({ root: './' })) +app.use('/*', serveStatic({ root: './public' })) + +const server = serve({ fetch: app.fetch, port: config.port }, async (info) => { + logger.info(`Server is running: http://${info.address}:${info.port}`) + + await startSlack() + scheduleTasks() + startWS(server as HttpServer) +}) diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..9997f06 --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,109 @@ +import { Context, MiddlewareHandler } from 'hono' +import { BlankEnv } from 'hono/types' +import logger from '~lib/logger' +import { getCookie, setCookie } from 'hono/cookie' +import prisma from '~lib/prisma' +import crypto from 'crypto' +import { Account } from '@prisma/client' + +type KeyAuth = { read: boolean; write: boolean; admin: boolean } +type KeyValidation = { id: string } & KeyAuth +type LoginValidation = ({ valid: true; key: string } & KeyAuth) | { valid: false } + +export async function createUser(id: string, password: string, write_access: boolean, admin_access: boolean): Promise { + const hashedPassword = crypto.createHash('sha256').update(password).digest('hex') + logger.info('Creating user ' + id) + return prisma.account.create({ + data: { + id, + password: hashedPassword, + write_access, + admin_access, + api_key: crypto.randomBytes(20).toString('hex') + } + }) +} +export async function validateLogin(id: string, password: string): Promise { + const acc = await prisma.account.findUnique({ where: { id } }) + if (!acc) { + return { valid: false } + } + const hashedPassword = crypto.createHash('sha256').update(password).digest('hex') + if (hashedPassword != acc.password) { + return { valid: false } + } + return { valid: true, key: acc.api_key, read: true, write: acc.write_access, admin: acc.admin_access } +} + +export async function validateApiKey(key?: string): Promise { + if (!key) { + return { id: 'none', read: false, write: false, admin: false } + } + const acc = await prisma.account.findFirst({ where: { api_key: key }, select: { id: true, write_access: true, admin_access: true } }) + if (!acc) { + return { id: 'none', read: false, write: false, admin: false } + } + return { id: acc.id, read: true, write: acc.write_access, admin: acc.admin_access } +} +export function setAuthMsg(c: Context, msg: string) { + setCookie(c, 'cluck_msg', msg, { path: '/auth/login' }) +} +export function consumeAuthMsg(c: Context): string { + setAuthMsg(c, '') + return getCookie(c, 'cluck_msg') ?? '' +} +const validateAuth = async (c: Context) => { + const api_key = c.req.header('X-Api-Key') ?? getCookie(c, 'cluck_auth') + const permissions = await validateApiKey(api_key) + logger.info(`${c.req.method} ${c.req.url} from ${permissions.id} [write=${permissions.write}]`) + c.set('auth_read', permissions.read) + c.set('auth_write', permissions.write) + c.set('auth_admin', permissions.admin) + c.set('user', permissions.id) +} + +export const requireReadLogin: MiddlewareHandler = async (c, next) => { + await validateAuth(c) + if (c.get('auth_read')) { + await next() + } else { + return c.redirect(`/auth/login?redirectTo=${c.req.path}`, 302) + } +} +export const requireReadAPI: MiddlewareHandler = async (c, next) => { + await validateAuth(c) + if (c.get('auth_read')) { + await next() + } else { + c.status(401) + return c.text('Invalid key') + } +} + +export const requireWriteLogin: MiddlewareHandler = async (c, next) => { + await validateAuth(c) + if (c.get('auth_write')) { + await next() + } else { + return c.redirect(`/auth/login?redirectTo=${c.req.path}&level=write`, 302) + } +} + +export const requireWriteAPI: MiddlewareHandler = async (c, next) => { + await validateAuth(c) + if (c.get('auth_write')) { + await next() + } else { + c.status(403) + return c.text(c.get('auth_read') ? 'Insufficient permissions' : 'Invalid key') + } +} + +export const requireAdminLogin: MiddlewareHandler = async (c, next) => { + await validateAuth(c) + if (c.get('auth_admin')) { + await next() + } else { + return c.redirect(`/auth/login?redirectTo=${c.req.path}&level=admin`, 302) + } +} diff --git a/src/lib/cert_operations.ts b/src/lib/cert_operations.ts new file mode 100644 index 0000000..a4a1f74 --- /dev/null +++ b/src/lib/cert_operations.ts @@ -0,0 +1,80 @@ +import { Prisma } from '@prisma/client' +import prisma from '~lib/prisma' +import { slack_client } from '~slack' +import { getCertRequestMessage } from '~slack/blocks/certify' + +enum CertOperationsError { + CERT_NOT_FOUND = 'This cert cannot be found', + CERT_NOT_MANAGED = "This cert can't be given by managers", + USER_NOT_MANAGER = 'You are not a manager for this cert', + USER_NOT_FOUND = 'User not found' +} + +export async function canGiveCert(user: Prisma.MemberWhereUniqueInput, cert: { department: string | null }) { + if (cert.department == null) { + return { success: false, error: CertOperationsError.CERT_NOT_MANAGED } + } + const managecert = await prisma.memberCert.findFirst({ + where: { + Member: user, + Cert: { department: cert.department, isManager: true } + } + }) + + if (managecert == null) { + return { success: false, error: CertOperationsError.USER_NOT_MANAGER } + } + return { success: true } +} + +export async function createCertRequest(giver: Prisma.MemberWhereUniqueInput, recipient_slack_ids: string[], cert_id: Prisma.CertWhereUniqueInput) { + const cert = await prisma.cert.findUnique({ + where: cert_id, + select: { id: true, department: true, replaces: true, label: true } + }) + if (!cert) { + return { success: false, error: CertOperationsError.CERT_NOT_FOUND } + } + const canGive = await canGiveCert(giver, cert) + if (!canGive.success) { + return canGive + } + + const giving_member = await prisma.member.findUnique({ where: giver }) + if (!giving_member) { + return { success: false, error: CertOperationsError.USER_NOT_FOUND } + } + const recipients = await prisma.member.findMany({ + select: { email: true, MemberCerts: { where: { cert_id: cert.id }, select: { cert_id: true } } }, + where: { slack_id: { in: recipient_slack_ids } } + }) + if (recipients.length != recipient_slack_ids.length) { + return { success: false, error: CertOperationsError.USER_NOT_FOUND } + } + setTimeout(async () => { + // Do in separate event loop to avoid blocking the request + const resp = await prisma.memberCertRequest.createManyAndReturn({ + data: recipients + .filter((r) => r.MemberCerts.length == 0) // Only request certs for members who don't already have it + .map((member) => ({ + requester_id: giving_member.email, + member_id: member.email, + cert_id: cert.id, + state: 'pending' + })), + select: { + id: true, + slack_ts: true, + Cert: true, + Member: true, + Requester: true, + state: true + } + }) + for (const r of resp) { + const msg = await slack_client.chat.postMessage(getCertRequestMessage(r)) + await prisma.memberCertRequest.update({ where: { id: r.id }, data: { slack_ts: msg.ts } }) + } + }) + return { success: true } +} diff --git a/src/lib/config.ts b/src/lib/config.ts new file mode 100644 index 0000000..2118259 --- /dev/null +++ b/src/lib/config.ts @@ -0,0 +1,10 @@ +import type template_config from '../../config/example.json' +import config_lib from 'config' + +type Config = typeof template_config +process.env.NODE_CONFIG_STRICT_MODE = 'TRUE' + +const current_config = config_lib.util.loadFileConfigs('./config') as Config +export default current_config + +export const season_start_date = new Date(current_config.start_date!) diff --git a/src/lib/hour_operations.ts b/src/lib/hour_operations.ts new file mode 100644 index 0000000..0b5134c --- /dev/null +++ b/src/lib/hour_operations.ts @@ -0,0 +1,147 @@ +import prisma from '~lib/prisma' +import { emitCluckChange } from '~lib/sockets' +import { enum_HourLogs_type, Prisma } from '@prisma/client' +import { season_start_date } from '~config' + +export enum HourError { + NOT_SIGNED_IN +} + +export async function completeHourLog(email: string, isVoid: boolean): Promise<{ success: true } | { success: false; error: HourError }> { + const log = await prisma.hourLog.findFirst({ where: { state: 'pending', type: 'lab', member_id: email } }) + if (!log) { + return { success: false, error: HourError.NOT_SIGNED_IN } + } + + const now = new Date() + const duration = (now.getTime() - log.time_in.getTime()) / 1000 / 60 / 60 + + await prisma.hourLog.update({ + where: { id: log.id }, + data: { + time_out: new Date(), + state: 'complete', + duration: new Prisma.Decimal(isVoid ? 0 : duration) + } + }) + emitCluckChange({ email, logging_in: false }) + return { success: true } +} + +export async function getValidHours(user: Prisma.MemberWhereUniqueInput) { + const member = await prisma.member.findUnique({ + where: user, + include: { + HourLogs: { + where: { + state: 'complete', + time_in: { + gte: season_start_date + } + }, + select: { duration: true, type: true, message: true } + } + } + }) + if (member == null) { + return + } + return member.HourLogs +} + +export async function calculateHours(user: Prisma.MemberWhereUniqueInput) { + if (user.email == null) { + const member = await prisma.member.findUnique({ where: user }) + if (member == null) { + return + } + user.email = member.email + } + const sums = await prisma.hourLog.groupBy({ by: 'type', _sum: { duration: true }, where: { state: 'complete', member_id: user.email, time_out: { gte: season_start_date } } }) + const out: Record = { event: 0, external: 0, lab: 0, summer: 0, total: 0, qualifying: 0 } + sums.forEach((sum) => { + out[sum.type] = sum._sum.duration!.toNumber() + out.total += out[sum.type] + }) + out.qualifying = out.lab + out.external + return out +} +export async function calculateAllHours() { + const out: Record> = {} + const totals = await prisma.hourLog.groupBy({ + by: ['member_id', 'type'], + _sum: { duration: true }, + where: { state: 'complete', time_in: { gte: season_start_date } } + }) + totals.forEach((total) => { + out[total.member_id] ??= { event: 0, external: 0, lab: 0, summer: 0, total: 0, qualifying: 0 } + out[total.member_id][total.type] = total._sum.duration!.toNumber() + out[total.member_id].total += out[total.member_id][total.type] + out[total.member_id].qualifying = out[total.member_id].lab + out[total.member_id].external + }) + return out +} +export async function getWeeklyHours(): Promise> { + const logs = await prisma.hourLog.groupBy({ + by: ['member_id'], + where: { + time_in: { + gte: new Date(new Date().getTime() - 1000 * 60 * 60 * 24 * 7) + } + }, + _sum: { + duration: true + } + }) + return Object.fromEntries(logs.map((l) => [l.member_id, l._sum.duration?.toNumber() ?? 0])) +} + +export async function getMeetings(): Promise> { + const meetings = await prisma.member.findMany({ + select: { + email: true, + _count: { + select: { + MeetingAttendances: { + where: { + state: 'present', + Meeting: { + date: { + gte: season_start_date + } + } + } + } + } + } + } + }) + return Object.fromEntries(meetings.map((m) => [m.email, m._count.MeetingAttendances])) +} + +export async function getMeetingsMissed(): Promise> { + const meetings = await prisma.member.findMany({ + select: { + email: true, + _count: { + select: { + MeetingAttendances: { + where: { + state: { + not: 'present' + }, + Meeting: { + mandatory: true, + date: { + gte: season_start_date + } + } + } + } + } + } + } + }) + + return Object.fromEntries(meetings.map((m) => [m.email, m._count.MeetingAttendances])) +} diff --git a/src/lib/logger.ts b/src/lib/logger.ts new file mode 100644 index 0000000..f0eee0f --- /dev/null +++ b/src/lib/logger.ts @@ -0,0 +1,68 @@ +import { Logger as BoltLogger, LogLevel as BoltLogLevel } from '@slack/logger' +import pino, { LevelWithSilentOrString, LogFn } from 'pino' + +const transport = pino.transport({ + targets: [ + { + level: 'debug', + target: 'pino-pretty', + options: { + colorize: true, + levelFirst: false, + ignore: 'pid,hostname' + } + } + ] +}) + +const logger = pino({ level: 'trace' }, transport) + +const pinoToBoltLevel: Record = { + fatal: BoltLogLevel.ERROR, + error: BoltLogLevel.ERROR, + warn: BoltLogLevel.WARN, + info: BoltLogLevel.INFO, + debug: BoltLogLevel.DEBUG, + trace: BoltLogLevel.DEBUG, + + silent: BoltLogLevel.ERROR +} + +export const createBoltLogger = (): BoltLogger => ({ + setLevel(level) { + return + }, + getLevel() { + return pinoToBoltLevel[logger.level] + }, + setName(name: string) { + return + }, + debug(...msgs) { + logSlack(logger.trace.bind(logger), msgs) + }, + info(...msgs) { + logSlack(logger.info.bind(logger), msgs) + }, + warn(...msgs) { + logSlack(logger.warn.bind(logger), msgs) + }, + error(...msgs) { + logSlack(logger.error.bind(logger), msgs) + } +}) + +function logSlack(logFn: LogFn, msgs: unknown[]) { + if (msgs.length === 0) { + return + } else if (msgs.length == 1) { + logFn({ name: 'slack' }, msgs[0] as string) + } else { + try { + logFn({ name: 'slack', ...(msgs[0] as object) }, msgs.slice(1).join(', ')) + } catch { + logFn({ name: 'slack' }, msgs.join(', ')) + } + } +} +export default logger diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts new file mode 100644 index 0000000..46a8192 --- /dev/null +++ b/src/lib/prisma.ts @@ -0,0 +1,45 @@ +import { Member, PrismaClient } from '@prisma/client' +import config from '~config' +import { getMemberPhoto } from '~lib/util' +import logger from '~lib/logger' + +export const prisma = new PrismaClient({ + log: [ + { + emit: 'event', + level: 'query' + }, + { + emit: 'event', + level: 'info' + }, + { + emit: 'event', + level: 'warn' + }, + { + emit: 'event', + level: 'error' + } + ] +}) +export default prisma + +export function getMemberPhotoOrDefault(member: Pick, small: boolean = false): string { + return getMemberPhoto(member, small) ?? config.default_photo +} + +if (process.env.NODE_ENV != 'prod') { + prisma.$on('query', (e) => { + logger.trace({ name: 'prisma' }, e.query) + }) + prisma.$on('info', (e) => { + logger.trace({ name: 'prisma' }, e.message) + }) + prisma.$on('warn', (e) => { + logger.warn({ name: 'prisma' }, e.message) + }) + prisma.$on('error', (e) => { + logger.error({ name: 'prisma' }, e.message) + }) +} diff --git a/src/lib/renderer.tsx b/src/lib/renderer.tsx new file mode 100644 index 0000000..3cfbfa3 --- /dev/null +++ b/src/lib/renderer.tsx @@ -0,0 +1,15 @@ +import { jsxRenderer } from 'hono/jsx-renderer' + +export const renderer = jsxRenderer(({ children, title, js, body_class }) => { + return ( + + + {title} + + + {js && } + + {children} + + ) +}) diff --git a/src/lib/sockets.ts b/src/lib/sockets.ts new file mode 100644 index 0000000..6795e6b --- /dev/null +++ b/src/lib/sockets.ts @@ -0,0 +1,27 @@ +import { Server as SocketIOServer } from 'socket.io' +import { Server as HttpServer } from 'http' + +import { WSCluckChange } from '~types' +import logger from '~lib/logger' + +let io: SocketIOServer | null = null +export function startWS(server: HttpServer) { + if (io != null) { + return + } + io = new SocketIOServer(server, { + path: '/ws' + }) + logger.info('Websocket server started') + + io.on('connection', (socket) => { + socket.emit('hello', 'world') + socket.on('hello', (data) => { + socket.broadcast.emit('hello', data) + }) + }) +} + +export function emitCluckChange(data: WSCluckChange) { + io!.emit('cluck_change', data) +} diff --git a/src/lib/util.ts b/src/lib/util.ts new file mode 100644 index 0000000..ebe4b04 --- /dev/null +++ b/src/lib/util.ts @@ -0,0 +1,58 @@ +import type { Member } from '@prisma/client' + +export function getMemberPhoto(member: Pick, small: boolean = false): string | null { + if (member.use_slack_photo) { + return (small ? member.slack_photo_small : member.slack_photo) ?? member.fallback_photo + } else { + return member.fallback_photo + } +} + +export function safeParseInt(value: unknown): number | undefined { + if (value == null) { + return undefined + } + if (typeof value === 'string') { + const num = parseInt(value) + return isNaN(num) ? undefined : num + } + if (typeof value === 'number') { + return value + } +} + +export function safeParseFloat(value: unknown): number | undefined { + if (value == null) { + return undefined + } + if (typeof value === 'string') { + const num = parseFloat(value) + return isNaN(num) ? undefined : num + } + if (typeof value === 'number') { + return value + } +} + +const english_ordinal_rules = new Intl.PluralRules('en', { type: 'ordinal' }) +const suffixes = { + zero: '', // Unused for english locale + many: '', // Unused for english locale + one: 'st', + two: 'nd', + few: 'rd', + other: 'th' +} +export function ordinal(number: number): string { + const category = english_ordinal_rules.select(number) + const suffix = suffixes[category] + return number + suffix +} + +export function toTitleCase(str: string): string { + return str.replace(/\w\S*/g, (text) => text.charAt(0).toUpperCase() + text.substring(1).toLowerCase()) +} + +export function sortCertLabels(a: string, b: string): number { + return a[a.length - 1].localeCompare(b[b.length - 1]) +} diff --git a/src/routes/admin/attendance.tsx b/src/routes/admin/attendance.tsx new file mode 100644 index 0000000..ef75afd --- /dev/null +++ b/src/routes/admin/attendance.tsx @@ -0,0 +1,171 @@ +import { Hono } from 'hono' +import { safeParseInt } from '~lib/util' +import prisma, { getMemberPhotoOrDefault } from '~lib/prisma' +import { enum_MeetingAttendances_state } from '@prisma/client' +import { season_start_date } from '~config' + +export const router = new Hono().basePath('/attendance/') + +const attendanceSVGs = { + absent: ( + + + + ), + no_credit: ( + + + + ), + present: ( + + + + ) +} +router + .get('/', async (c) => { + const meetings = await prisma.meetings.findMany({ orderBy: [{ date: 'desc' }, { id: 'desc' }], where: { date: { gte: season_start_date } } }) + const colcount = meetings.length + 4 + const rows = ( + await prisma.member.findMany({ + orderBy: { full_name: 'asc' }, + select: { + email: true, + full_name: true, + use_slack_photo: true, + slack_id: true, + slack_photo: true, + slack_photo_small: true, + fallback_photo: true, + MeetingAttendances: { + where: { Meeting: { date: { gte: season_start_date } } } + } + }, + where: { + active: true + } + }) + ).map((member) => { + const memberMeetings = new Map(member.MeetingAttendances.map((attendance) => [attendance.meeting_id, attendance.state])) + return ( +
+
+
+ {member.slack_id +
{member.email}
+
+
{member.MeetingAttendances.filter((m) => m.state == 'present').length}
+
+ {[{ id: -1 }, ...meetings].map((meeting) => ( +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ ))} +
+ ) + }) + const now = new Date() + const todayDate = now.getFullYear() + '-' + (now.getMonth() + 1).toString().padStart(2, '0') + '-' + now.getDate().toString().padStart(2, '0') + return c.render( + <> + {meetings.map((meeting) => ( +
+ ))} +
+
+
+
+
+
Student
+
+
+ +
+
+ {meetings.map((meeting) => ( +
+
{meeting.date.toLocaleDateString('en-us', { day: 'numeric', month: 'short', timeZone: 'UTC' })}
+
+ ))} +
+
+
+ {rows} +
+
+
+ +
+ {meetings.map((meeting) => ( +
+ +
+ ))} +
+
+
+ + ) + }) + .post('/:id', async (c) => { + console.log('handling') + const data = await c.req.parseBody() + + const meeting_id_str = c.req.param('id') + let meeting_id = undefined + if (meeting_id_str == 'new') { + const datestr = data.date + 'T00:00:00.000Z' // set to midnight in local time + const { id } = await prisma.meetings.create({ data: { date: new Date(datestr), mandatory: true } }) + meeting_id = id + } else { + meeting_id = safeParseInt(meeting_id_str) + } + if (meeting_id) { + const members = await prisma.member.findMany({ select: { email: true }, where: { active: true } }) + await prisma.$transaction([ + prisma.meetingAttendanceEntry.deleteMany({ + where: { + meeting_id + } + }), + prisma.meetingAttendanceEntry.createMany({ + data: members.map((m) => ({ + member_id: m.email, + meeting_id, + state: (data[m.email] ?? 'absent') as enum_MeetingAttendances_state + })), + skipDuplicates: true + }) + ]) + } + return c.redirect('/admin/attendance/', 302) + }) + .post('/delete/:id', async (c) => { + const meeting_id = safeParseInt(c.req.param('id')) + if (meeting_id) { + await prisma.meetings.delete({ where: { id: meeting_id } }) + } else { + c.text('Invalid meeting ID', 400) + } + return c.redirect('/admin/attendance/', 302) + }) diff --git a/src/routes/admin/index.tsx b/src/routes/admin/index.tsx new file mode 100644 index 0000000..ddad26a --- /dev/null +++ b/src/routes/admin/index.tsx @@ -0,0 +1,39 @@ +import { Hono } from 'hono' +import { router as membercert_route } from './membercerts' +import { router as meeting_route } from './attendance' +import { toTitleCase } from '~lib/util' + +export const router = new Hono() + +router.route('/', membercert_route) +router.route('/', meeting_route) + +const pages = { + members: ['Members', 'Add new members and sync slack accounts'], + certs: ['Cert Setup', 'Create and edit cert names and levels'], + membercerts: ['Cert Assignments', 'Manage certifications for team members'], + attendance: ['Attendance', 'Record and view meeting attendance'], + meetings: ['Meetings', 'Adjust meeting dates and information'], + departments: ['Departments', 'Create and edit departments'] +} + +router.get('/', (c) => { + return c.render( +
+
+

Admin

+

Welcome to the admin page, {toTitleCase(c.get('user'))}

+
+
+ {Object.entries(pages).map(([path, [name, desc]], i) => ( + + {name} +

{desc}

+
+ ))} +
+
+ ) +}) + +export default router diff --git a/src/routes/admin/membercerts.tsx b/src/routes/admin/membercerts.tsx new file mode 100644 index 0000000..0a7a3dc --- /dev/null +++ b/src/routes/admin/membercerts.tsx @@ -0,0 +1,79 @@ +import { Hono } from 'hono' +import prisma from '~lib/prisma' +import { scheduleCertAnnouncement } from '~tasks/certs' + +export const router = new Hono().basePath('/membercerts') + +export const cert_colors = [ + 'bg-red-200 border-red-500 accent-red-400', // + 'bg-yellow-200 border-yellow-500 accent-yellow-400', + 'bg-green-200 border-green-500 accent-green-400', + 'bg-blue-200 border-blue-500 accent-blue-400', + 'bg-purple-200 border-purple-500 accent-purple-400', + 'bg-pink-200 border-pink-500 accent-pink-400' +] +export const getColor = (i: number) => cert_colors[i % cert_colors.length] +router + .get('/', async (c) => { + const certs = await prisma.cert.findMany({ orderBy: [{ department: 'asc' }, { id: 'asc' }], select: { id: true, label: true, department: true, isManager: true } }) + const colcount = certs.length + 1 + const rows = (await prisma.member.findMany({ where: { active: true }, orderBy: { full_name: 'asc' }, select: { email: true, full_name: true, MemberCerts: { select: { cert_id: true } } } })).map((member, rowI) => { + let colorI = -1 + const certSet = new Set(member.MemberCerts.map((cert) => cert.cert_id)) + return ( +
+
+

{member.full_name}

+
+ {certs.map((cert, i) => { + const isFirst = i == 0 || cert.department !== certs[i - 1].department + const isLast = i >= certs.length - 1 || cert.department !== certs[i + 1].department + if (isFirst) { + colorI++ + } + return ( +
+ +
+ ) + })} +
+ ) + }) + let colorI = -1 + return c.render( + <> +
+
+
+
+ {certs.map((cert, i) => { + const isFirst = i == 0 || cert.department !== certs[i - 1].department + const isLast = i >= certs.length - 1 || cert.department !== certs[i + 1].department + if (isFirst) { + colorI++ + } + return ( +
+
{cert.label}
+
+ ) + })} +
+ {rows} +
+
+ , + { js: 'js/membercerts' } + ) + }) + .post(async (c) => { + const { email, cert, value }: { email: string; cert: string; value: boolean } = await c.req.json() + if (value) { + await prisma.memberCert.create({ data: { member_id: email, cert_id: cert } }) + scheduleCertAnnouncement() + } else { + await prisma.memberCert.delete({ where: { cert_id_member_id: { cert_id: cert, member_id: email } } }) + } + return c.text('OK', 200) + }) diff --git a/src/routes/api/admin.ts b/src/routes/api/admin.ts new file mode 100644 index 0000000..87ae73d --- /dev/null +++ b/src/routes/api/admin.ts @@ -0,0 +1,127 @@ +import { Cert, Meetings, Member, Prisma } from '@prisma/client' +import { Hono } from 'hono' +import prisma from '~lib/prisma' +import logger from '~lib/logger' +import { safeParseInt } from '~lib/util' +import { season_start_date } from '~config' + +export const router = new Hono() + +router + .get('/admin/members', async (c) => { + return c.json( + await prisma.member.findMany({ + orderBy: { full_name: 'asc' }, + where: { active: true } + }) + ) + }) + .post(async (c) => { + const data = (await c.req.json()) as Partial + const email = data.email?.trim() + const full_name = data.full_name?.trim() + if (email == null || full_name == null) { + return c.json({ success: false }) + } + const fallback_photo = await prisma.fallbackPhoto.findUnique({ where: { email: data.email as string } }) + const record: Prisma.MemberCreateInput = { + email, + full_name, + use_slack_photo: data.use_slack_photo || false, + first_name: full_name.split(' ')[0], + fallback_photo: fallback_photo?.url + } + return c.json({ + data: await prisma.member.create({ data: record }), + success: true + }) + }) + .put(async (c) => { + const data = (await c.req.json()) as Partial & { id?: string } + const id = data.id + delete data['id'] + return c.json(await prisma.member.update({ data, where: { email: id } })) + }) + +router + .get('/admin/departments', async (c) => { + return c.json(await prisma.department.findMany()) + }) + .put(async (c) => { + const data = (await c.req.json()) as Partial & { id: string } + try { + return c.json(await prisma.department.update({ data, where: { id: data.id } })) + } catch (err) { + logger.warn({ msg: 'Error on PUT /admin/departments', err, data }) + return c.json({ error: err }, 400) + } + }) + .post(async (c) => { + const data = (await c.req.json()) as Prisma.DepartmentCreateInput + if (data.id == null || data.name == null) { + return c.json({ error: 'Invalid data' }, 400) + } + data.slack_group = null // Will be created automatically + try { + return c.json({ + data: await prisma.department.create({ data }), + success: true + }) + } catch (err) { + logger.warn({ msg: 'Error on POST /admin/departments', err, data }) + return c.json({ error: err }, 400) + } + }) + +router + .get('/admin/certs', async (c) => { + return c.json(await prisma.cert.findMany()) + }) + .put(async (c) => { + const data = (await c.req.json()) as Partial + try { + return c.json(await prisma.cert.update({ data, where: { id: data.id } })) + } catch (err) { + logger.warn({ msg: 'Error on PUT /admin/certs', err, data }) + return c.json({ error: err }, 400) + } + }) + .post(async (c) => { + const data = (await c.req.json()) as Prisma.CertCreateInput + + if (data.id == null || data.label == null || data.isManager == null) { + return c.json({ error: 'Invalid data' }, 400) + } + try { + return c.json({ + data: await prisma.cert.create({ data }), + success: true + }) + } catch (err) { + logger.warn({ msg: 'Error on POST /admin/certs', err, data }) + return c.json({ error: err }, 400) + } + }) + +router + .get('/admin/meetings', async (c) => { + return c.json(await prisma.meetings.findMany({ orderBy: { date: 'asc' }, where: { date: { gte: season_start_date } } })) + }) + .put(async (c) => { + const data = (await c.req.json()) as Partial + try { + return c.json(await prisma.meetings.update({ data, where: { id: data.id } })) + } catch (err) { + logger.warn({ msg: 'Error on PUT /admin/meetings', err, data }) + return c.json({ error: err }, 400) + } + }) + .delete(async (c) => { + const data = (await c.req.json()) as { id: unknown } + try { + return c.json(await prisma.meetings.delete({ where: { id: safeParseInt(data.id) } })) + } catch (err) { + logger.warn({ msg: 'Error on DELETE /admin/meetings', err, data }) + return c.json({ error: err }, 400) + } + }) diff --git a/src/routes/api/index.ts b/src/routes/api/index.ts new file mode 100644 index 0000000..07009cb --- /dev/null +++ b/src/routes/api/index.ts @@ -0,0 +1,186 @@ +import { Context, Hono } from 'hono' +import { syncSlackMembers } from '~tasks/slack' +import { APIClockExternalRespondRequest, APIClockExternalSubmitRequest, APIClockLabRequest, APIClockResponse, APIMember } from '~types' +import logger from '~lib/logger' +import { requireReadAPI, requireWriteAPI } from '~lib/auth' +import { emitCluckChange } from '~lib/sockets' +import prisma, { getMemberPhotoOrDefault } from '~lib/prisma' +import { cors } from 'hono/cors' +import { completeHourLog } from '~lib/hour_operations' +import { router as admin_api_router } from './admin' + +const router = new Hono() + +router.route('/', admin_api_router) +router.get('/members', requireReadAPI, async (c) => { + const members = await prisma.member.findMany({ + select: { + email: true, + first_name: true, + full_name: true, + use_slack_photo: true, + slack_photo: true, + slack_photo_small: true, + fallback_photo: true + }, + where: { + active: true + }, + orderBy: { full_name: 'asc' } + }) + const resp: APIMember[] = members.map((member) => ({ + email: member.email, + first_name: member.first_name, + full_name: member.full_name, + photo: getMemberPhotoOrDefault(member, false), + photo_small: getMemberPhotoOrDefault(member, true) + })) + return c.json(resp) +}) + +router.get('/members/refresh', requireReadAPI, async (c) => { + await syncSlackMembers() + return c.redirect('/api/members', 302) +}) + +router.use('/members/fallback_photos', cors({ origin: ['https://portals.veracross.com'] })) +router.post('/members/fallback_photos', requireWriteAPI, async (c) => { + logger.info('Updating fallback photos') + const body: Record = await c.req.json() + await prisma.fallbackPhoto.deleteMany({}) + const { count } = await prisma.fallbackPhoto.createMany({ data: Object.entries(body).map(([k, v]) => ({ email: k.toLowerCase(), url: v })) }) + const members = await prisma.member.findMany({ select: { email: true } }) + for (const member of members) { + await prisma.member.update({ where: { email: member.email }, data: { fallback_photo: body[member.email] } }) + } + return c.text(`Updated ${count} fallback photos`) +}) +function clockJson(c: Context, payload: APIClockResponse) { + return c.json(payload) +} + +router + .post('/clock/lab', requireWriteAPI, async (c) => { + const { email, action }: APIClockLabRequest = await c.req.json() + const member = await prisma.member.findUnique({ where: { email }, select: { email: true } }) + if (member == null) { + logger.warn('ignoring login for unknown user ' + email) + c.status(400) + return clockJson(c, { success: false, error: 'member unknown' }) + } + try { + const log = await prisma.hourLog.findFirst({ where: { state: 'pending', type: 'lab', member_id: email } }) + if (log) { + if (action == 'in') { + logger.warn('ignoring duplicate login for ' + email) + return clockJson(c, { success: false, error: 'member already logged in', log_id: log.id }) + } + if (action == 'out') { + await completeHourLog(email, false) + } else if (action == 'void') { + await completeHourLog(email, true) + } + + return clockJson(c, { success: true, log_id: log.id }) + } else if (action == 'in') { + const newLog = await prisma.hourLog.create({ + data: { + member_id: email, + time_in: new Date(), + type: 'lab', + state: 'pending' + } + }) + emitCluckChange({ email, logging_in: true }) + return clockJson(c, { success: true, log_id: newLog.id }) + } else { + c.status(400) + return clockJson(c, { success: false, error: 'member not signed in' }) + } + } catch (e) { + logger.error(e) + c.status(500) + return clockJson(c, { success: false, error: 'unknown' }) + } + }) + .get(async (c) => { + const records = await prisma.hourLog.findMany({ + where: { state: 'pending', type: 'lab' }, + select: { id: true, member_id: true, time_in: true } + }) + return c.json(records.map(({ id, member_id, time_in }) => ({ id, time_in, email: member_id }))) + }) + +router.get('/clock/external', requireReadAPI, async (c) => { + const records = await prisma.hourLog.findMany({ + where: { state: 'pending', type: 'external' }, + select: { id: true, member_id: true, time_in: true, duration: true, slack_ts: true, message: true } + }) + return c.json(records.map(({ id, member_id, time_in }) => ({ id, time_in, email: member_id }))) +}) + +router.post('/clock/external/submit', requireWriteAPI, async (c) => { + const { email, message, hours }: APIClockExternalSubmitRequest = await c.req.json() + const member = await prisma.member.findUnique({ where: { email }, select: { email: true } }) + if (member == null) { + logger.warn('ignoring external submission for unknown user ' + email) + c.status(400) + return c.json({ success: false, error: 'member unknown' }) + } + try { + const newLog = await prisma.hourLog.create({ + data: { + member_id: email, + time_in: new Date(), + duration: hours, + message, + type: 'external', + state: 'pending' + } + }) + return clockJson(c, { success: true, log_id: newLog.id }) + } catch (e) { + logger.error(e) + c.status(500) + return clockJson(c, { success: false, error: 'unknown' }) + } +}) + +router.post('/clock/external/respond', requireWriteAPI, async (c) => { + const { id, action, category }: APIClockExternalRespondRequest = await c.req.json() + const log = await prisma.hourLog.findUnique({ where: { id } }) + if (log == null) { + logger.warn('Ignoring confirmation for unknown hour request ' + id) + c.status(400) + return clockJson(c, { success: false, error: 'request unknown' }) + } + if (log.state != 'pending') { + logger.warn('Received confirmation for completed hour request ' + id + '. Updating anyway...') + } + try { + if (action == 'approve') { + await prisma.hourLog.update({ + where: { id: log.id }, + data: { + time_out: new Date(), + state: 'complete', + type: category + } + }) + } else { + await prisma.hourLog.update({ + where: { id: log.id }, + data: { + time_out: new Date(), + state: 'cancelled' + } + }) + } + return clockJson(c, { success: true, log_id: log.id }) + } catch (e) { + logger.error(e) + c.status(500) + return clockJson(c, { success: false, error: 'unknown' }) + } +}) +export default router diff --git a/src/routes/auth.tsx b/src/routes/auth.tsx new file mode 100644 index 0000000..b89b112 --- /dev/null +++ b/src/routes/auth.tsx @@ -0,0 +1,61 @@ +import { Hono } from 'hono' +import { validateLogin } from '~lib/auth' +import { setCookie } from 'hono/cookie' +import { consumeAuthMsg, setAuthMsg } from '~lib/auth' + +const router = new Hono() + +router + .get('/login', (c) => { + return c.render( +
+
+

Sign in {c.req.query('level') ? `- ${c.req.query('level')}` : ``}

+
+
+ + +
+
+ + +
+ +
+

{consumeAuthMsg(c)}

+
+
, + { body_class: 'bg-gray-100' } + ) + }) + .post(async (c) => { + const { id, password } = await c.req.parseBody() + const destination = c.req.query('redirectTo') ?? '/' + const level = c.req.query('level')?.toLowerCase() ?? 'read' + + if (typeof id !== 'string' || typeof password !== 'string') { + setAuthMsg(c, 'Missing username or password') + return c.redirect(c.req.url, 302) + } + const auth = await validateLogin(id, password) + if (!auth.valid) { + setAuthMsg(c, 'Invalid username or password') + return c.redirect(c.req.url, 302) + } + if (level == 'admin' && !auth.admin) { + setAuthMsg(c, 'This page requires an admin account') + return c.redirect(c.req.path, 302) + } + if (level == 'write' && !auth.write) { + setAuthMsg(c, 'This page requires a write account') + return c.redirect(c.req.path, 302) + } + setCookie(c, 'cluck_auth', auth.key, { expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), httpOnly: true, secure: true }) + return c.redirect(destination, 302) + }) + +export default router diff --git a/src/slack/blocks/admin/hour_submission.ts b/src/slack/blocks/admin/hour_submission.ts new file mode 100644 index 0000000..626031e --- /dev/null +++ b/src/slack/blocks/admin/hour_submission.ts @@ -0,0 +1,62 @@ +import { BlockBuilder, Blocks, Elements, Message } from 'slack-block-builder' +import { formatDuration, sanitizeCodeblock } from '~slack/lib/messages' +import { ActionIDs } from '~slack/handlers' +import { enum_HourLogs_type } from '@prisma/client' +import { toTitleCase } from '~lib/util' + +export const getSubmissionContextBlock = ({ request_id, state, type }: { request_id: string; state: 'pending' | 'approved' | 'rejected'; type?: enum_HourLogs_type }) => { + switch (state) { + case 'pending': + return Blocks.Context().elements(`${request_id} | ⏳ Submitted ${new Date().toLocaleString()}`) + case 'approved': + return Blocks.Context().elements(`${request_id} | ✅ Approved ${new Date().toLocaleString()} | ${toTitleCase(type ?? 'external')}`) + case 'rejected': + return Blocks.Context().elements(`${request_id} | ❌ Rejected ${new Date().toLocaleString()}`) + } +} + +export type HourSubmissionBlocksInput = { + request_id: string + slack_id: string + hours: number + activity: string + response?: string | null + state: 'pending' | 'approved' | 'rejected' + type?: enum_HourLogs_type +} +export function getHourSubmissionBlocks(v: HourSubmissionBlocksInput) { + const blocks: BlockBuilder[] = [] + blocks.push( + Blocks.Header().text('Time Submission'), + Blocks.Section().text(`>>>*<@${v.slack_id}>* submitted *${formatDuration(v.hours)}* for activity\n\`\`\`${sanitizeCodeblock(v.activity)}\`\`\``) + ) + if (v.response) { + blocks.push(Blocks.Section().text(`>>>*Response:*\n\`\`\`${sanitizeCodeblock(v.response)}\`\`\``)) + } + if (v.state == 'pending') { + blocks.push( + Blocks.Actions().elements( + Elements.Button().primary().text('Accept').actionId(ActionIDs.ACCEPT).value(v.request_id), + Elements.Button().danger().text('Reject').actionId(ActionIDs.REJECT).value(v.request_id), + Elements.Button().text('☀️').actionId(ActionIDs.ACCEPT_SUMMER).value(v.request_id), + Elements.Button().text('📆').actionId(ActionIDs.ACCEPT_EVENT).value(v.request_id), + Elements.Button().text('🔨').actionId(ActionIDs.ACCEPT_LAB).value(v.request_id), + Elements.Button().text('Accept w/ Message').actionId(ActionIDs.ACCEPT_WITH_MSG).value(v.request_id) + ) + ) + } + blocks.push(getSubmissionContextBlock(v)) + + return blocks +} + +export function getHourSubmissionMessage(v: HourSubmissionBlocksInput) { + //prettier-ignore + const msg = Message() + .text(`<@${v.slack_id}> submitted ${formatDuration(v.hours)} for ${v.activity}`) + .blocks( + getHourSubmissionBlocks(v), + Blocks.Divider() + ) + return msg.buildToObject() +} diff --git a/src/slack/blocks/admin/onboarding.ts b/src/slack/blocks/admin/onboarding.ts new file mode 100644 index 0000000..ea97482 --- /dev/null +++ b/src/slack/blocks/admin/onboarding.ts @@ -0,0 +1,23 @@ +import { Blocks, Modal } from 'slack-block-builder' +import { ViewIDs } from '~slack/handlers' +import { slack_client } from '~slack' +import config from '~config' +import prisma from '~lib/prisma' + +export async function getOnboardingModal() { + const dbMembers = await prisma.member.findMany({ where: { slack_id: { not: null } } }) + const dbMemberSet = new Set(dbMembers.map((member) => member.slack_id!)) + + const slackMembers = (await slack_client.usergroups.users.list({ usergroup: config.slack.groups.students })).users! + const newMembers = slackMembers.filter((member) => !dbMemberSet.has(member)) + + const modal = Modal().title('Onboarding').callbackId(ViewIDs.MODAL_ONBOARDING).submit('Add') + modal.blocks( + Blocks.Section().text('The following members will be added to the database from '), + Blocks.Divider(), + Blocks.Section().text(newMembers.map((member) => `<@${member}>`).join('\n')) + ) + modal.privateMetaData(newMembers.join(',')) + + return modal.buildToObject() +} diff --git a/src/slack/blocks/admin/pending_requests.ts b/src/slack/blocks/admin/pending_requests.ts new file mode 100644 index 0000000..54dff2e --- /dev/null +++ b/src/slack/blocks/admin/pending_requests.ts @@ -0,0 +1,51 @@ +import prisma from '~lib/prisma' +import { slack_client } from '~slack' +import config from '~config' +import { formatDuration } from '~slack/lib/messages' +import { Blocks, Elements, Message } from 'slack-block-builder' +import logger from '~lib/logger' +import { HourSubmissionBlocksInput } from '~slack/blocks/admin/hour_submission' + +export async function getPendingHourSubmissionData(): Promise { + const pendingRequests = await prisma.hourLog.findMany({ + where: { type: 'external', state: 'pending' }, + select: { id: true, duration: true, message: true, slack_ts: true, Member: { select: { slack_id: true } } } + }) + return pendingRequests.map((v) => ({ request_id: v.id.toString(), slack_id: v.Member.slack_id!, hours: v.duration!.toNumber(), activity: v.message!, state: 'pending' })) +} + +export async function getPendingRequestMessage({ team_id, app_id }: { team_id: string; app_id: string }) { + const pendingRequests = await prisma.hourLog.findMany({ + where: { type: 'external', state: 'pending' }, + select: { id: true, duration: true, message: true, slack_ts: true, Member: { select: { slack_id: true } } } + }) + + const output = Message() + .channel(config.slack.channels.approval) + .text(`⏳ ${pendingRequests.length} Pending Time Requests`) + .blocks(Blocks.Header().text(`⏳ ${pendingRequests.length} Pending Time Requests`), Blocks.Divider()) + + for (const log of pendingRequests) { + const permalink = await slack_client.chat + .getPermalink({ + channel: config.slack.channels.approval, + message_ts: log.slack_ts! + }) + .catch(() => null) + if (!permalink) { + logger.warn({ id: log.id }, 'Could not find slack message for log') + continue + } + output.blocks( + Blocks.Section().text(`*<@${log.Member.slack_id}>* - ${formatDuration(log.duration!.toNumber())}`), + Blocks.Context().elements(`${log.id} | Submitted ${new Date().toLocaleString()}`), + Blocks.Divider() + ) + } + output.blocks( + Blocks.Actions().elements(Elements.Button().text('Open App Home').url(`slack://app?team=${team_id}&id=${app_id}&tab=home`).actionId('jump_url')), + Blocks.Divider() + ) + + return output.buildToObject() +} diff --git a/src/slack/blocks/admin/respond.ts b/src/slack/blocks/admin/respond.ts new file mode 100644 index 0000000..65209df --- /dev/null +++ b/src/slack/blocks/admin/respond.ts @@ -0,0 +1,50 @@ +import { formatDuration, sanitizeCodeblock } from '~slack/lib/messages' +import { ViewIDs } from '~slack/handlers' +import { Bits, Blocks, Elements, Modal } from 'slack-block-builder' + +export function getRespondMessageModal( + action: 'Accept' | 'Reject', + request: { + id: number + duration: number + activity: string + first_name: string + } +) { + // prettier-ignore + const modal = Modal() + .privateMetaData(request.id.toString()) + .callbackId(action == 'Accept' ? ViewIDs.MODAL_ACCEPT : ViewIDs.MODAL_REJECT) + .title(`${action} Time Request`) + .submit(`${action}`) + .close('Cancel') + .blocks( + Blocks.Section() + .text(`_*${request.first_name}*_ submitted *${formatDuration(request.duration)}* for activity\n\n>_\`\`\`${sanitizeCodeblock(request.activity)}\`\`\`_`), + Blocks.Divider(), + Blocks.Input() + .blockId('message') + .element(Elements.TextInput().multiline().actionId('input')) + .label('Message') + ) + if (action == 'Accept') { + modal.blocks( + Blocks.Input() + .blockId('type_selector') + .label('Category') + .element( + Elements.StaticSelect() + .actionId('selector') + .placeholder('Select a category...') + .initialOption(Bits.Option().text('External').value('external')) + .options( + Bits.Option().text('External').value('external'), + Bits.Option().text('Summer ☀️').value('summer'), + Bits.Option().text('Event').value('event'), + Bits.Option().text('Lab').value('lab') + ) + ) + ) + } + return modal +} diff --git a/src/slack/blocks/app_home.ts b/src/slack/blocks/app_home.ts new file mode 100644 index 0000000..f40f0cb --- /dev/null +++ b/src/slack/blocks/app_home.ts @@ -0,0 +1,61 @@ +import { getPendingHourSubmissionData } from '~slack/blocks/admin/pending_requests' +import { getHourSubmissionBlocks } from '~slack/blocks/admin/hour_submission' +import { ActionIDs } from '~slack/handlers' +import { getUserHourSummaryBlocks } from '~slack/blocks/member/user_hours' +import { getUserCertBlocks } from '~slack/blocks/member/user_certs' +import { Bits, Blocks, Elements, HomeTab } from 'slack-block-builder' +import config from '~config' +import { getCertRequestBlocks } from '~slack/blocks/certify' +import prisma from '~lib/prisma' +import { getTaskKeys } from '~tasks' + +export async function getAppHome(user_id: string) { + const homeTab = HomeTab() + if (config.slack.users.admins.includes(user_id)) { + const pending_requests = await getPendingHourSubmissionData() + const pending_certs = await prisma.memberCertRequest.findMany({ where: { state: 'pending' }, include: { Member: true, Cert: true, Requester: true } }) + + homeTab.blocks( + Blocks.Header().text('Admin Dashboard'), + Blocks.Section() + .text('Manual Tasks') + .accessory( + Elements.OverflowMenu() + .actionId(ActionIDs.RUN_TASK) + .options(getTaskKeys().map((key) => Bits.Option().text(key).value(key))) + ), + Blocks.Actions().elements( + Elements.Button().text('Open Onboarding').actionId(ActionIDs.OPEN_ONBOARDING_MODAL), + Elements.Button().text('Send Pending Requests').actionId(ActionIDs.SEND_PENDING_REQUESTS) + ) + ) + homeTab.blocks(Blocks.Header().text('Pending Hour Submissions')) + if (pending_requests.length > 0) { + homeTab.blocks(pending_requests.flatMap((req) => [...getHourSubmissionBlocks(req), Blocks.Divider()])) + } else { + homeTab.blocks(Blocks.Section().text('None')) + homeTab.blocks(Blocks.Divider()) + } + homeTab.blocks(Blocks.Header().text('Pending Certifications')) + if (pending_certs.length > 0) { + homeTab.blocks(pending_certs.flatMap((req) => [...getCertRequestBlocks(req).blocks, Blocks.Divider()])) + } else { + homeTab.blocks(Blocks.Section().text('None')) + homeTab.blocks(Blocks.Divider()) + } + homeTab.blocks(Blocks.Context().elements('Last updated ' + new Date().toLocaleTimeString())) + } else { + homeTab.blocks( + Blocks.Actions().elements( + Elements.Button().text('Log Hours').actionId(ActionIDs.OPEN_LOG_MODAL), + Elements.Button().text('Show Info').actionId(ActionIDs.OPEN_USERINFO_MODAL) + ), + await getUserHourSummaryBlocks({ slack_id: user_id }), + Blocks.Divider(), + await getUserCertBlocks({ slack_id: user_id }), + Blocks.Divider(), + Blocks.Context().elements('Last updated ' + new Date().toLocaleTimeString()) + ) + } + return homeTab.buildToObject() +} diff --git a/src/slack/blocks/certify.ts b/src/slack/blocks/certify.ts new file mode 100644 index 0000000..e0443a8 --- /dev/null +++ b/src/slack/blocks/certify.ts @@ -0,0 +1,112 @@ +import { Prisma } from '@prisma/client' +import { Bits, BlockBuilder, Blocks, Elements, Message, Modal, OptionGroupBuilder } from 'slack-block-builder' +import { ActionIDs, ViewIDs } from '~slack/handlers' +import prisma from '~lib/prisma' +import config from '~config' + +export async function getCertifyModal(user: Prisma.MemberWhereUniqueInput) { + const manager = await prisma.member.findUnique({ + where: user, + select: { + MemberCerts: { + where: { Cert: { isManager: true } }, + select: { + Cert: { + select: { + id: true, + Department: { select: { name: true, id: true, Certs: { select: { id: true, label: true } } } } + } + } + } + } + } + }) + if (!manager) { + return Modal().title(':(').blocks(Blocks.Header().text('No member found')).buildToObject() + } + const managedDepartments = manager.MemberCerts.map((c) => c.Cert.Department) + if (managedDepartments.length == 0) { + return Modal().title(':(').blocks(Blocks.Header().text('Must be a manager')).buildToObject() + } + + const optionGroups: OptionGroupBuilder[] = managedDepartments + .filter((d) => d != null) + .map((d) => { + return Bits.OptionGroup() + .label(d.name) + .options(d.Certs.map((c) => Bits.Option().text(c.label).value(c.id))) + }) + + return Modal() + .title('Certify') + .callbackId(ViewIDs.MODAL_CERTIFY) + .blocks( + Blocks.Input().label('Member(s)').blockId('users').element(Elements.UserMultiSelect().actionId('users').placeholder('Select the members to certify')), + Blocks.Input() + .label('Certification') + .blockId('cert') + .element(Elements.StaticSelect().actionId('cert').placeholder('Select the certification to give').optionGroups(optionGroups)) + ) + .submit('Submit') + .close('Cancel') + .buildToObject() +} + +export function getCertRequestBlocks(r: { + id: number + state: 'pending' | 'approved' | 'rejected' + Requester: { slack_id: string | null } + Member: { full_name: string; slack_id: string | null; slack_photo_small: string | null; fallback_photo: string | null } + Cert: { label: string } +}): { blocks: BlockBuilder[]; text: string } { + let text: string + let footer: string + const blocks: BlockBuilder[] = [] + switch (r.state) { + case 'approved': + text = `Approved \`${r.Cert.label}\` cert for <@${r.Member.slack_id}>` + footer = '✅ Approved' + break + case 'rejected': + text = `Rejected \`${r.Cert.label}\` cert for <@${r.Member.slack_id}>` + footer = '❌ Rejected' + break + default: + text = `\`${r.Cert.label}\` cert requested for <@${r.Member.slack_id}> by <@${r.Requester.slack_id}>` + footer = '⏳ Submitted' + } + blocks.push(Blocks.Section().text(text)) + + if (r.state == 'pending') { + blocks.push( + Blocks.Actions().elements( + Elements.Button().primary().text('Approve').actionId(ActionIDs.CERT_APPROVE).value(r.id.toString()), + Elements.Button().danger().text('Reject').actionId(ActionIDs.CERT_REJECT).value(r.id.toString()) + ) + ) + } + blocks.push( + Blocks.Context().elements( + Elements.Img() + .altText(r.Member.full_name) + .imageUrl(r.Member.slack_photo_small ?? r.Member.fallback_photo ?? ''), + `${r.id} | ${footer} ${new Date().toLocaleString()}` + ) + ) + return { blocks, text } +} + +export function getCertRequestMessage(r: { + id: number + state: 'pending' | 'approved' | 'rejected' + slack_ts: string | null + Requester: { slack_id: string | null } + Member: { full_name: string; slack_id: string | null; slack_photo_small: string | null; fallback_photo: string | null } + Cert: { label: string } +}) { + const msg = Message().channel(config.slack.channels.certification_approval).ts(r.slack_ts!) + + const { blocks, text } = getCertRequestBlocks(r) + msg.text(text).blocks(blocks) + return msg.buildToObject() +} diff --git a/src/slack/blocks/member/log.ts b/src/slack/blocks/member/log.ts new file mode 100644 index 0000000..c604ebc --- /dev/null +++ b/src/slack/blocks/member/log.ts @@ -0,0 +1,25 @@ +import { Blocks, Elements, Modal } from 'slack-block-builder' +import { ViewIDs } from '~slack/handlers' + +//prettier-ignore +export const getLogModal =() => Modal() + .title('Log External Hours') + .callbackId(ViewIDs.MODAL_LOG) + .submit('Log') + .close('Cancel') + .blocks( + Blocks.Input() + .blockId('hours') + .element(Elements.TextInput() + .actionId('hours') + .placeholder('2h15m') + ) + .label('🕘 Hours Spent'), + Blocks.Input() + .blockId('task') + .element(Elements.TextInput() + .actionId('task') + .placeholder('Write error messaging for the slack time bot #METAAAAA!!!') + .multiline()) + .label('🧗 Activity') + ).buildToObject() diff --git a/src/slack/blocks/member/user_certs.ts b/src/slack/blocks/member/user_certs.ts new file mode 100644 index 0000000..a679a3b --- /dev/null +++ b/src/slack/blocks/member/user_certs.ts @@ -0,0 +1,45 @@ +import { Blocks, ViewBlockBuilder } from 'slack-block-builder' +import { Prisma } from '@prisma/client' +import { UndefinableArray } from 'slack-block-builder/dist/internal' +import prisma from '~lib/prisma' +import { sortCertLabels } from '~lib/util' + +export async function getUserCertBlocks(user: Prisma.MemberWhereUniqueInput): Promise> { + const member = await prisma.member.findUnique({ + where: user, + select: { + MemberCerts: { + select: { + Cert: { + select: { label: true, isManager: true } + } + } + } + } + }) + if (!member) { + return [Blocks.Header().text('No member found')] + } + const certs: string[] = [] + const managerCerts: string[] = [] + member.MemberCerts.forEach((mc) => { + if (mc.Cert.isManager) { + managerCerts.push(mc.Cert.label) + } else { + certs.push(mc.Cert.label) + } + }) + const output = [] + if (certs.length > 0) { + output.push(Blocks.Header().text('🎓 Your Certifications')) + output.push(Blocks.Section().text(certs.sort(sortCertLabels).join('\n'))) + } + if (managerCerts.length > 0) { + if (certs.length > 0) { + output.push(Blocks.Divider()) + } + output.push(Blocks.Header().text(':office_worker: Your Management Roles')) + output.push(Blocks.Section().text(managerCerts.join('\n'))) + } + return output +} diff --git a/src/slack/blocks/member/user_departments.ts b/src/slack/blocks/member/user_departments.ts new file mode 100644 index 0000000..b89ac72 --- /dev/null +++ b/src/slack/blocks/member/user_departments.ts @@ -0,0 +1,36 @@ +import prisma from '~lib/prisma' +import { Prisma } from '@prisma/client' +import { Bits, BlockBuilder, Blocks, Elements, OptionBuilder } from 'slack-block-builder' + +export async function getDepartmentPicker(user: Prisma.MemberWhereUniqueInput): Promise { + const member = await prisma.member.findUnique({ + where: user, + select: { + Departments: { + select: { + department_id: true + } + } + } + }) + + const departments = await prisma.department.findMany() + + if (!member) { + return [Blocks.Header().text('No member found')] + } + const optionMap: Record = {} + departments.forEach((d) => { + optionMap[d.id] = Bits.Option().text(d.name).value(d.id) + }) + const initialOptions = member.Departments.map((d) => optionMap[d.department_id]) + const options = Object.values(optionMap) + + return [ + Blocks.Input() + .label('Select your departments') + .blockId('department') + .optional() + .element(Elements.StaticMultiSelect().actionId('department').options(options).initialOptions(initialOptions)) + ] +} diff --git a/src/slack/blocks/member/user_hours.ts b/src/slack/blocks/member/user_hours.ts new file mode 100644 index 0000000..0a8786e --- /dev/null +++ b/src/slack/blocks/member/user_hours.ts @@ -0,0 +1,56 @@ +import { Blocks, Elements, ViewBlockBuilder } from 'slack-block-builder' +import { calculateHours } from '~lib/hour_operations' +import { Prisma } from '@prisma/client' +import { UndefinableArray } from 'slack-block-builder/dist/internal' +import { ActionIDs } from '~slack/handlers' +import prisma from '~lib/prisma' + +export async function getUserHourSummaryBlocks(user: Prisma.MemberWhereUniqueInput): Promise> { + const hours = await calculateHours(user) + if (!hours) { + return [Blocks.Header().text('No hours found')] + } + return [ + Blocks.Header().text('⏳ Your Hours'), + Blocks.Actions().elements(Elements.Button().text('Show Pending').actionId(ActionIDs.SHOW_OWN_PENDING_REQUESTS)), + Blocks.Section().fields('*Category*', '*Hours*'), + Blocks.Divider(), + Blocks.Section().fields('Lab', hours.lab.toFixed(1)), + Blocks.Divider(), + Blocks.Section().fields('External', hours.external.toFixed(1)), + Blocks.Divider(), + Blocks.Section().fields('Event', hours.event.toFixed(1)), + Blocks.Divider(), + Blocks.Section().fields('Summer', hours.summer.toFixed(1)), + Blocks.Divider(), + Blocks.Section().fields('*Total*', '*' + hours.total.toFixed(1) + '*'), + Blocks.Divider(), + Blocks.Section().fields('Qualifying', hours.qualifying.toFixed(1)), + Blocks.Context().elements('excludes events and summer hours') + ] +} + +export async function getUserPendingRequestBlocks(user: Prisma.MemberWhereUniqueInput): Promise { + const hours = await prisma.hourLog.findMany({ + where: { + Member: user, + type: { not: 'lab' }, + state: 'pending' + }, + select: { + id: true, + time_in: true, + duration: true, + message: true + }, + orderBy: { time_in: 'asc' } + }) + if (hours.length == 0) { + return [Blocks.Header().text('No pending hours found')] + } + return hours.flatMap((item) => [ + Blocks.Divider(), + Blocks.Section({ text: `*${item.duration!.toNumber()} hours*\n\`\`\`${item.message}\n\`\`\`` }), + Blocks.Context().elements(`${item.id} | Submitted ${item.time_in.toLocaleString()}`) + ]) +} diff --git a/src/slack/blocks/member/user_info.ts b/src/slack/blocks/member/user_info.ts new file mode 100644 index 0000000..eecc048 --- /dev/null +++ b/src/slack/blocks/member/user_info.ts @@ -0,0 +1,30 @@ +import { Member, Prisma } from '@prisma/client' +import { Blocks, Elements, Modal } from 'slack-block-builder' +import prisma, { getMemberPhotoOrDefault } from '~lib/prisma' + +export async function getUserDataModal(input: Prisma.MemberWhereUniqueInput) { + const member = await prisma.member.findUnique({ where: input }) + if (member == null) { + return Modal().title('Who are you?') + } + + const createFieldSection = (label: string, field: T, converter: (v: Member[T]) => string) => { + return [Blocks.Section().fields('*' + label + '*', converter(member[field])), Blocks.Divider()] + } + // prettier-ignore + return Modal() + .title('Your Information') + .blocks( + Blocks.Section() + .text(' ') + .accessory(Elements.Img().imageUrl(getMemberPhotoOrDefault(member)).altText('Profile Picture')), + Blocks.Divider(), + ...createFieldSection('Join Date', 'createdAt', v => v.toLocaleDateString()), + ...createFieldSection('Email', 'email', v => v!), + ...createFieldSection('Slack ID', 'slack_id', v => v!), + ...createFieldSection('First Name', 'first_name', v => v!), + ...createFieldSection('Full Name', 'full_name', v => v!), + ...createFieldSection('Slack Photo Approved', 'use_slack_photo', v => v ? 'Yes' : 'No'), + Blocks.Context().elements('Last modified ' + member.updatedAt.toLocaleString()) + ) +} diff --git a/src/slack/blocks/responses.ts b/src/slack/blocks/responses.ts new file mode 100644 index 0000000..9b44e8a --- /dev/null +++ b/src/slack/blocks/responses.ts @@ -0,0 +1,62 @@ +import { Blocks, Message } from 'slack-block-builder' +import { formatDuration, sanitizeCodeblock } from '~slack/lib/messages' +import { getSubmissionContextBlock } from '~slack/blocks/admin/hour_submission' +import { enum_HourLogs_type } from '@prisma/client' + +export default { + tooFewHours() { + return Message() + .text(':warning: I just blocked your submission of ZERO hours. Please submit hours in the form: `/log 2h15m write error messaging for the slack time bot #METAAAAA!!!`') + .buildToObject() + }, + submissionLogged() { + return Message().text('Your submission has been logged').buildToObject() + }, + noActivitySpecified() { + return Message() + .text( + ':warning: I just blocked your submission with no activity. Please submit hours in the form: `/log 2h15m write error messaging for the slack time bot #METAAAAA!!!`' + ) + .buildToObject() + }, + submissionLoggedDM(v: { id: number; hours: number; activity: string }) { + return Message() + .text(`You submitted ${formatDuration(v.hours)}`) + .blocks( + Blocks.Section().text( + `:clock2: You submitted *${formatDuration(v.hours)}* :clock7:\n>>>:person_climbing: *Activity:*\n\`\`\`${sanitizeCodeblock(v.activity)}\`\`\`` + ), + getSubmissionContextBlock({ request_id: v.id.toString(), state: 'pending' }) + ) + .buildToObject() + }, + submissionRespondedDM(action: 'approve' | 'reject', v: { slack_id: string; hours: number; activity: string; message: string; request_id: number; type?: enum_HourLogs_type }) { + if (action === 'approve') { + return this.submissionAcceptedDM(v) + } else { + return this.submissionRejectedDM(v) + } + }, + + submissionAcceptedDM(v: { slack_id: string; hours: number; activity: string; message?: string | null; request_id: number; type?: enum_HourLogs_type }) { + let msg = `:white_check_mark: *<@${v.slack_id}>* accepted *${formatDuration(v.hours)}* :white_check_mark:\n>>>:person_climbing: *Activity:*\n\`\`\`${sanitizeCodeblock(v.activity)}\`\`\`` + if (v.message) { + msg += `\n:loudspeaker: *Message:*\n\`\`\`${sanitizeCodeblock(v.message)}\`\`\`` + } + return Message() + .text(`<@${v.slack_id}> accepted ${formatDuration(v.hours)}`) + .blocks(Blocks.Section().text(msg), getSubmissionContextBlock({ request_id: v.request_id.toString(), state: 'approved', type: v.type })) + .buildToObject() + }, + submissionRejectedDM(v: { slack_id: string; hours: number; activity: string; message: string; request_id: number }) { + return Message() + .text(`<@${v.slack_id}> rejected ${formatDuration(v.hours)}`) + .blocks( + Blocks.Section().text( + `:x: *<@${v.slack_id}>* rejected *${formatDuration(v.hours)}* :x:\n>>>:person_climbing: *Activity:*\n\`\`\`${sanitizeCodeblock(v.activity)}\`\`\`\n:loudspeaker: *Message:*\n\`\`\`${sanitizeCodeblock(v.message)}\`\`\`` + ), + getSubmissionContextBlock({ request_id: v.request_id.toString(), state: 'rejected' }) + ) + .buildToObject() + } +} diff --git a/src/slack/handlers/actions/certify.ts b/src/slack/handlers/actions/certify.ts new file mode 100644 index 0000000..bb7f77c --- /dev/null +++ b/src/slack/handlers/actions/certify.ts @@ -0,0 +1,115 @@ +import { getCertifyModal, getCertRequestMessage } from '~slack/blocks/certify' +import { createCertRequest } from '~lib/cert_operations' +import { ActionMiddleware, CommandMiddleware, ViewMiddleware } from '~slack/lib/types' +import prisma from '~lib/prisma' +import { safeParseInt } from '~lib/util' +import { scheduleCertAnnouncement } from '~tasks/certs' +import { slack_client } from '~slack' +import { getAppHome } from '~slack/blocks/app_home' + +export const handleCertifyCommand: CommandMiddleware = async ({ command, ack, client }) => { + await ack() + + await client.views.open({ + view: await getCertifyModal({ slack_id: command.user_id }), + trigger_id: command.trigger_id + }) +} + +export const handleSubmitCertifyModal: ViewMiddleware = async ({ ack, body, view }) => { + // Get the hours and task from the modal + const members = view.state.values.users.users.selected_users + const cert = view.state.values.cert.cert.selected_option?.value + if (members == null || members.length == 0 || cert == null) { + await ack({ + response_action: 'errors', + errors: { users: 'Please select a user', cert: 'Please select a certification' } + }) + return + } else { + const { success, error } = await createCertRequest({ slack_id: body.user.id }, members, { id: cert }) + if (!success) { + await ack({ response_action: 'errors', errors: { users: error ?? 'Unknown error' } }) + } else { + await ack() + } + } +} + +export const handleCertReject: ActionMiddleware = async ({ ack, action, client, body }) => { + await ack() + const cert_req_id = safeParseInt(action.value) + if (cert_req_id == null) { + return + } + + const req = await prisma.memberCertRequest.update({ + where: { id: cert_req_id }, + data: { state: 'rejected' }, + select: { + id: true, + slack_ts: true, + Cert: true, + Member: true, + Requester: true, + state: true + } + }) + + await client.chat.update(getCertRequestMessage(req)) + await slack_client.views.publish({ + user_id: body.user.id, + view: await getAppHome(body.user.id) + }) +} + +export const handleCertApprove: ActionMiddleware = async ({ ack, action, client, body }) => { + await ack() + const cert_req_id = safeParseInt(action.value) + if (cert_req_id == null) { + return + } + + const req = await prisma.memberCertRequest.update({ + where: { id: cert_req_id }, + data: { state: 'approved' }, + select: { + id: true, + slack_ts: true, + Cert: true, + Member: true, + Requester: true, + state: true + } + }) + if (!req) { + return + } + await prisma.memberCert.create({ + data: { + cert_id: req.Cert.id, + member_id: req.Member.email, + announced: false + } + }) + if (req.Cert.replaces) { + try { + await prisma.memberCert.delete({ + where: { + cert_id_member_id: { + cert_id: req.Cert.replaces, + member_id: req.Member.email + } + } + }) + } catch { + // If the cert doesn't exist, that's fine + } + } + await client.chat.update(getCertRequestMessage(req)) + scheduleCertAnnouncement() + await slack_client.views.publish({ + user_id: body.user.id, + view: await getAppHome(body.user.id) + }) +} diff --git a/src/slack/handlers/actions/departments.ts b/src/slack/handlers/actions/departments.ts new file mode 100644 index 0000000..4465d7b --- /dev/null +++ b/src/slack/handlers/actions/departments.ts @@ -0,0 +1,51 @@ +import { Modal } from 'slack-block-builder' +import { CommandMiddleware, ViewMiddleware } from '~slack/lib/types' +import { getDepartmentPicker } from '~slack/blocks/member/user_departments' +import { ViewIDs } from '~slack/handlers' +import prisma from '~lib/prisma' +import logger from '~lib/logger' +import { setProfileAttribute } from '~slack/lib/profile' +import { formatList } from '~slack/lib/messages' +import { scheduleUpdateSlackUsergroups } from '~tasks/slack_groups' + +export const handleDepartmentsCommand: CommandMiddleware = async ({ ack, body, client }) => { + await ack() + await client.views.open({ + trigger_id: body.trigger_id, + view: Modal() + .title('Departments') + .submit('Save') + .callbackId(ViewIDs.MODAL_DEPARTMENTS) + .privateMetaData(body.channel_id) + .blocks(await getDepartmentPicker({ slack_id: body.user_id })) + .buildToObject() + }) +} + +export const handleSubmitDepartmentsModal: ViewMiddleware = async ({ ack, body, client }) => { + // Save the user's departments + await ack() + const options = body.view.state.values.department.department.selected_options! + const department_ids = options.map((o) => o.value) + const member = await prisma.member.findUnique({ where: { slack_id: body.user.id } }) + if (!member) { + logger.warn(`User ${body.user.id} not found in database`) + return + } + await prisma.departmentAssociation.deleteMany({ where: { member_id: member.email, department_id: { notIn: department_ids } } }) + await prisma.departmentAssociation.createMany({ + data: department_ids.map((id) => ({ member_id: member.email, department_id: id })), + skipDuplicates: true + }) + const depts = await prisma.department.findMany({ where: { id: { in: department_ids } }, select: { slack_group: true, name: true } }) + await setProfileAttribute(body.user.id, 'department', formatList(depts.map((d) => d.name)) || 'None') + + await client.chat + .postEphemeral({ + user: body.user.id, + channel: body.view.private_metadata, + text: 'Successfully updated your departments: ' + formatList(depts.map((d) => d.name)) || 'None' + }) + .catch(() => {}) + scheduleUpdateSlackUsergroups() +} diff --git a/src/slack/handlers/actions/graph.ts b/src/slack/handlers/actions/graph.ts new file mode 100644 index 0000000..92047d7 --- /dev/null +++ b/src/slack/handlers/actions/graph.ts @@ -0,0 +1,52 @@ +import { Blocks, Message } from 'slack-block-builder' +import { createHourChartForTeam, createHourChartForUsers } from '~slack/lib/chart' +import { formatList } from '~slack/lib/messages' +import { CommandMiddleware } from '~slack/lib/types' +import prisma from '~lib/prisma' + +export const handleGraphCommand: CommandMiddleware = async ({ command, ack, respond }) => { + await ack() + const text = command.text.trim() + + const resp = { text: '', url: '', title: 'Hours Graph' } + await respond({ response_type: 'ephemeral', text: 'Generating graph...' }) + if (text == 'all') { + const { url, success } = await createHourChartForTeam() + resp.text = ':chart_with_upwards_trend: <@' + command.user_id + '> generated a graph for the team' + resp.title = success ? 'Hours Graph' : 'You were hourless to stop me from making this pun' + resp.url = url + } else { + const users: Set = new Set() + const user_matches = text.matchAll(/<@(\w+)\|\w.+?>/g) + const group_matches = text.matchAll(//g) + for (const user of user_matches) { + users.add(user[1]) + } + for (const group of group_matches) { + const associations = await prisma.departmentAssociation.findMany({ + where: { Department: { slack_group: group[1] } }, + select: { Member: { select: { slack_id: true } } } + }) + associations.forEach((a) => { + if (a.Member.slack_id) { + users.add(a.Member.slack_id) + } + }) + } + + if (users.size == 0) { + users.add(command.user_id) + } + + const { url, success } = await createHourChartForUsers([...users]) + resp.url = url + resp.title = success ? 'Hours Graph' : 'You were hourless to stop me from making this pun' + resp.text = ':chart_with_upwards_trend: <@' + command.user_id + '> generated a graph for ' + formatList([...users].map((u) => `<@${u}>`)) + } + const msg = Message() + .text(resp.text) + .blocks(Blocks.Section().text(resp.text), Blocks.Image().title(resp.title).imageUrl(resp.url).altText('Hours Graph')) + .inChannel() + .buildToObject() + await respond(msg) +} diff --git a/src/slack/handlers/actions/hours.ts b/src/slack/handlers/actions/hours.ts new file mode 100644 index 0000000..526d8b8 --- /dev/null +++ b/src/slack/handlers/actions/hours.ts @@ -0,0 +1,34 @@ +import { Blocks, Modal } from 'slack-block-builder' +import { getUserHourSummaryBlocks, getUserPendingRequestBlocks } from '~slack/blocks/member/user_hours' +import { ActionMiddleware, CommandMiddleware } from '~slack/lib/types' + +export const handleShowHoursCommand: CommandMiddleware = async ({ command, ack, client }) => { + await ack() + + await client.views.open({ + view: Modal() + .title('Hours') + .blocks(await getUserHourSummaryBlocks({ slack_id: command.user_id }), Blocks.Divider(), Blocks.Context().elements('Last updated ' + new Date().toLocaleTimeString())) + .buildToObject(), + trigger_id: command.trigger_id + }) +} + +export const handleShowPendingHours: ActionMiddleware = async ({ body, ack, client }) => { + await ack() + const modal = Modal() + .title('Hours') + .blocks(await getUserPendingRequestBlocks({ slack_id: body.user.id })) + .buildToObject() + if (body.view?.type == 'modal') { + await client.views.update({ + view: modal, + view_id: body.view.id + }) + } else { + await client.views.open({ + view: modal, + trigger_id: body.trigger_id + }) + } +} diff --git a/src/slack/handlers/actions/hours_response.ts b/src/slack/handlers/actions/hours_response.ts new file mode 100644 index 0000000..5f14f81 --- /dev/null +++ b/src/slack/handlers/actions/hours_response.ts @@ -0,0 +1,100 @@ +import type { ActionMiddleware, ViewMiddleware } from '~slack/lib/types' +import { getRespondMessageModal } from '~slack/blocks/admin/respond' +import prisma from '~lib/prisma' +import { safeParseInt } from '~lib/util' +import { enum_HourLogs_type } from '@prisma/client' +import { handleHoursResponse } from '~slack/lib/hours_submission' +import type { AllMiddlewareArgs, SlackViewMiddlewareArgs, ViewSubmitAction } from '@slack/bolt' +import { getPendingRequestMessage } from '~slack/blocks/admin/pending_requests' +import config from '~config' + +export const handleHoursAcceptWithMessageButton: ActionMiddleware = async ({ ack, body, action, client, logger }) => { + await ack() + const requestInfo = await prisma.hourLog.findUnique({ + where: { id: safeParseInt(action.value) }, + select: { id: true, message: true, duration: true, Member: { select: { first_name: true } } } + }) + if (!requestInfo) { + logger.error({ action, name: 'handleAcceptWithMessageButton' }, 'Could not find request info') + return + } + try { + await client.views.open({ + trigger_id: body.trigger_id, + view: getRespondMessageModal('Accept', { + id: requestInfo.id, + activity: requestInfo.message!, + duration: requestInfo.duration!.toNumber(), + first_name: requestInfo.Member.first_name + }).buildToObject() + }) + } catch (err) { + logger.error({ err, info: requestInfo }, 'Failed to handle accept button') + } +} + +export const handleSubmitHoursAcceptModal: ViewMiddleware = async ({ ack, body, view }) => { + await ack() + await handleHoursResponse({ + action: 'approve', + request_id: safeParseInt(view.private_metadata)!, + actor_slack_id: body.user.id, + type: view.state.values.type_selector.selector.selected_option?.value as enum_HourLogs_type, + response: view.state.values.message.input.value as string + }) +} + +export function createHoursAcceptButtonHandler(type: enum_HourLogs_type): ActionMiddleware { + return async ({ ack, action, body }) => { + await ack() + await handleHoursResponse({ action: 'approve', request_id: safeParseInt(action.value)!, actor_slack_id: body.user.id, type, response: null }) + } +} + +export const handleHoursRejectButton: ActionMiddleware = async ({ ack, body, action, client, logger }) => { + await ack() + const requestInfo = await prisma.hourLog.findUnique({ + where: { id: safeParseInt(action.value) }, + select: { id: true, message: true, duration: true, Member: { select: { first_name: true } } } + }) + if (!requestInfo) { + logger.error('Could not find request info') + return + } + try { + await client.views.open({ + trigger_id: body.trigger_id, + view: getRespondMessageModal('Reject', { + id: requestInfo.id, + activity: requestInfo.message!, + duration: requestInfo.duration!.toNumber(), + first_name: requestInfo.Member.first_name + }).buildToObject() + }) + } catch (err) { + logger.error('Failed to handle reject button:\n' + err) + } +} + +export async function handleSubmitHoursRejectModal({ respond, ack, body, view, logger }: SlackViewMiddlewareArgs & AllMiddlewareArgs) { + await ack() + + const response = body.view.state.values?.message?.input?.value ?? 'Unknown' + + const { error } = await handleHoursResponse({ action: 'reject', request_id: safeParseInt(view.private_metadata)!, actor_slack_id: body.user.id, response }) + if (error) { + logger.error({ error }, 'Failed to handle reject modal') + await respond({ response_type: 'ephemeral', text: error.toString() }) + return + } +} + +export const handleSendPendingRequestsButton: ActionMiddleware = async ({ ack, client, body }) => { + await ack() + + const msg = await getPendingRequestMessage({ team_id: body.team!.id, app_id: body.api_app_id }) + await client.chat.postMessage({ + ...msg, + channel: config.slack.channels.approval + }) +} diff --git a/src/slack/handlers/actions/log.test.ts b/src/slack/handlers/actions/log.test.ts new file mode 100644 index 0000000..35b9c2c --- /dev/null +++ b/src/slack/handlers/actions/log.test.ts @@ -0,0 +1,19 @@ +import { expect, test, vi } from 'vitest' + +import { parseArgs } from '~slack/handlers/actions/log' + +vi.mock(import('~slack')) + +test('parses /log arguments', () => { + expect(parseArgs('3h30m lots of working').activity).toBe('lots of working') + expect(parseArgs('3h30m lots of working').hours).toBeCloseTo(3.5, 3) + expect(parseArgs('3h 30m lots of working').activity).toBe('lots of working') + expect(parseArgs('3h 30m lots of working').hours).toBeCloseTo(3.5, 3) + expect(parseArgs('45m lots of working').hours).toBeCloseTo(0.75, 3) + expect(parseArgs('1h45m lots of working').hours).toBeCloseTo(1.75, 3) + expect(parseArgs('30hm lots of working').hours).toBe(0) + expect(parseArgs('30mh 3h lots of working').hours).toBe(0) + expect(parseArgs('30h 3h lots of working').activity).toBe('3h lots of working') + expect(parseArgs('30h 3h lots of working').hours).toBe(30) + expect(parseArgs('lots of working').hours).toBe(0) +}) diff --git a/src/slack/handlers/actions/log.ts b/src/slack/handlers/actions/log.ts new file mode 100644 index 0000000..fa58c4d --- /dev/null +++ b/src/slack/handlers/actions/log.ts @@ -0,0 +1,85 @@ +import { getLogModal } from '~slack/blocks/member/log' +import { safeParseFloat } from '~lib/util' +import { handleHoursRequest } from '~slack/lib/hours_submission' +import responses from '~slack/blocks/responses' +import type { ActionMiddleware, CommandMiddleware, ShortcutMiddleware, ViewMiddleware } from '~slack/lib/types' + +export function parseArgs(text: string): { hours: number; activity: string | undefined } { + const timeRegex = /^(?:([\d.]+)h)? ?(?:([\d.]+)m)? (.+)$/ + if (!timeRegex.test(text)) { + return { hours: 0, activity: undefined } + } + const m = timeRegex.exec(text)! + const msg = m[3] + const hours = safeParseFloat(m[1]) ?? 0 + const minutes = safeParseFloat(m[2]) ?? 0 + + return { + hours: hours + minutes / 60, + activity: msg + } +} + +export const handleLogCommand: CommandMiddleware = async ({ command, logger, ack, client }) => { + if (command.text.trim().length === 0) { + await ack() + await client.views.open({ + view: getLogModal(), + trigger_id: command.trigger_id + }) + } else { + const { hours, activity } = parseArgs(command.text.trim()) + if (activity == '' || activity == undefined) { + await ack({ ...responses.noActivitySpecified(), response_type: 'ephemeral' }) + return + } + try { + if (hours < 0.1) { + await ack({ ...responses.tooFewHours(), response_type: 'ephemeral' }) + } else { + await ack({ ...responses.submissionLogged(), response_type: 'ephemeral' }) + await handleHoursRequest(command.user_id, hours, activity) + } + } catch (err) { + logger.error('Failed to complete log command:\n' + err) + } + } +} + +export const handleLogShortcut: ShortcutMiddleware = async ({ shortcut, ack, client }) => { + await ack() + + await client.views.open({ + view: getLogModal(), + trigger_id: shortcut.trigger_id + }) +} + +export const handleOpenLogModal: ActionMiddleware = async ({ body, ack, client }) => { + await ack() + + await client.views.open({ + view: getLogModal(), + trigger_id: body.trigger_id + }) +} + +export const handleSubmitLogModal: ViewMiddleware = async ({ ack, body, view }) => { + // Get the hours and task from the modal + let hours = safeParseFloat(view.state.values.hours.hours.value) ?? parseArgs(view.state.values.hours.hours.value ?? '').hours + const activity = view.state.values.task.task.value + + // Ensure the time values are valid + hours = isNaN(hours) ? 0 : hours + if (hours < 0.1) { + await ack({ response_action: 'errors', errors: { hours: 'Please enter a valid duration' } }) + return + } + if (activity?.trim() == '' || activity == undefined) { + await ack({ response_action: 'errors', errors: { task: 'Please enter an activity' } }) + return + } + + await ack() + await handleHoursRequest(body.user.id, hours, activity) +} diff --git a/src/slack/handlers/actions/loggedin.ts b/src/slack/handlers/actions/loggedin.ts new file mode 100644 index 0000000..ffbb082 --- /dev/null +++ b/src/slack/handlers/actions/loggedin.ts @@ -0,0 +1,19 @@ +import prisma from '~lib/prisma' +import { CommandMiddleware } from '~slack/lib/types' + +export const handleGetLoggedInCommand: CommandMiddleware = async ({ logger, ack, respond }) => { + await ack() + try { + const users = await prisma.hourLog.findMany({ + where: { state: 'pending', type: 'lab' }, + include: { Member: { select: { full_name: true } } } + }) + const names = users.map((u) => u.Member.full_name) + + await respond({ response_type: 'ephemeral', text: users.length > 0 ? `*Currently Logged In:*\n${names.join('\n')}` : 'Nobody is logged in' }) + } catch (e) { + logger.error(e) + await respond({ response_type: 'ephemeral', text: `Could not get logged in users: ${e}` }) + return + } +} diff --git a/src/slack/handlers/actions/logout.ts b/src/slack/handlers/actions/logout.ts new file mode 100644 index 0000000..67bb085 --- /dev/null +++ b/src/slack/handlers/actions/logout.ts @@ -0,0 +1,25 @@ +import prisma from '~lib/prisma' +import { completeHourLog, HourError } from '~lib/hour_operations' +import { CommandMiddleware } from '~slack/lib/types' + +export const handleLogoutCommand: CommandMiddleware = async ({ command, logger, ack, respond }) => { + await ack() + const member = await prisma.member.findUnique({ where: { slack_id: command.user_id } }) + if (member) { + try { + const result = await completeHourLog(member.email, true) + if (result.success) { + await respond({ response_type: 'ephemeral', text: `Successfully cleared, you are no longer signed in` }) + } else if (result.error == HourError.NOT_SIGNED_IN) { + await respond({ response_type: 'ephemeral', text: 'You are not signed in' }) + } + } catch (e) { + logger.error(e) + await respond({ response_type: 'ephemeral', text: `Could not void hours: ${e}` }) + return + } + } else { + await respond({ response_type: 'ephemeral', text: `I don't know who you are` }) + logger.error(`Could not find user ${command.user_id}`) + } +} diff --git a/src/slack/handlers/actions/run_task.ts b/src/slack/handlers/actions/run_task.ts new file mode 100644 index 0000000..140613c --- /dev/null +++ b/src/slack/handlers/actions/run_task.ts @@ -0,0 +1,28 @@ +import { OverflowAction } from '@slack/bolt' +import { Blocks, Modal } from 'slack-block-builder' +import { ActionMiddleware } from '~slack/lib/types' +import { runTask } from '~tasks' + +export const handleRunTask: ActionMiddleware = async ({ ack, body, client, payload }) => { + await ack() + const task = payload.selected_option.value + runTask(task) + .catch((err) => { + client.views.open({ + trigger_id: body.trigger_id, + view: Modal() + .title('Task Failed') + .blocks(Blocks.Section().text(`Failed to run task '${task}':\n \`\`\`\n${err}\n\`\`\``)) + .buildToObject() + }) + }) + .then(() => { + client.views.open({ + trigger_id: body.trigger_id, + view: Modal() + .title('Task Completed') + .blocks(Blocks.Section().text(`Task '${task}' completed successfully`)) + .buildToObject() + }) + }) +} diff --git a/src/slack/handlers/actions/void.ts b/src/slack/handlers/actions/void.ts new file mode 100644 index 0000000..9e24a33 --- /dev/null +++ b/src/slack/handlers/actions/void.ts @@ -0,0 +1,58 @@ +import config from '~config' +import { completeHourLog, HourError } from '~lib/hour_operations' +import prisma from '~lib/prisma' +import { CommandMiddleware } from '~slack/lib/types' + +export const handleVoidCommand: CommandMiddleware = async ({ command, logger, ack, respond, client }) => { + const void_channel_members = (await client.conversations.members({ channel: config.slack.channels.void })).members! + + if (!void_channel_members.includes(command.user_id)) { + await ack({ response_type: 'ephemeral', text: 'Must be a copresident to run this command' }) + return + } + + try { + const target_match = command.text.match(/<@([\w\d]+)\|.+>/) + if (target_match == null) { + await ack({ + response_type: 'ephemeral', + text: `Please provide the user in the form of a mention (like <@${command.user_id}>)` + }) + return + } + const target_id = target_match![1] + + if (void_channel_members.includes(target_id)) { + await ack({ + response_type: 'ephemeral', + text: `Target must not be in <#${config.slack.channels.void}>!` + }) + return + } + await ack() + const target_member = await prisma.member.findFirst({ where: { slack_id: target_id } }) + if (target_member == null) { + await respond({ response_type: 'ephemeral', text: `Could not find user with id '${target_id}'` }) + return + } + const status = await completeHourLog(target_member.email, true) + if (status.success) { + await respond({ response_type: 'ephemeral', text: `Successfully voided hours for <@${target_id}>` }) + await client.chat.postMessage({ + channel: config.slack.channels.void, + text: `<@${command.user_id}> has voided hours for <@${target_id}>` + }) + } else if (status.error == HourError.NOT_SIGNED_IN) { + await respond({ response_type: 'ephemeral', text: `<@${target_id}> is not logged in` }) + } else { + await respond({ + response_type: 'ephemeral', + text: `Could not void hours for $<@${target_id}>: ${status.error}` + }) + } + } catch (e) { + logger.error(e) + await respond({ response_type: 'ephemeral', text: `Could not parse arguments: ${e}` }) + return + } +} diff --git a/src/slack/handlers/index.ts b/src/slack/handlers/index.ts new file mode 100644 index 0000000..ea4aa6e --- /dev/null +++ b/src/slack/handlers/index.ts @@ -0,0 +1,117 @@ +import type { App } from '@slack/bolt' +import { handleAppHomeOpened } from './views/app_home' +import { handleLogCommand, handleLogShortcut, handleOpenLogModal, handleSubmitLogModal } from './actions/log' +import { handleLogoutCommand } from './actions/logout' +import { handleOpenUserInfoModal } from './views/userinfo' +import { handleVoidCommand } from './actions/void' +import { handleGetLoggedInCommand } from './actions/loggedin' +import config from '~config' +import { handleGraphCommand } from './actions/graph' +import { handleShowHoursCommand, handleShowPendingHours } from './actions/hours' +import { handleCertApprove, handleCertifyCommand, handleCertReject, handleSubmitCertifyModal } from './actions/certify' +import { handleDepartmentsCommand, handleSubmitDepartmentsModal } from './actions/departments' +import { handleOpenOnboardingModal, handleSubmitOnboardingModal } from './views/onboarding' +import { + createHoursAcceptButtonHandler, + handleHoursAcceptWithMessageButton, + handleHoursRejectButton, + handleSendPendingRequestsButton, + handleSubmitHoursAcceptModal, + handleSubmitHoursRejectModal +} from './actions/hours_response' +import { handleRunTask } from '~slack/handlers/actions/run_task' + +export enum ActionIDs { + ACCEPT = 'accept', + ACCEPT_SUMMER = 'accept_summer', + ACCEPT_EVENT = 'accept_event', + ACCEPT_LAB = 'accept_lab', + ACCEPT_WITH_MSG = 'accept_msg', + REJECT = 'reject', + OPEN_USERINFO_MODAL = 'open_settings_modal', + OPEN_LOG_MODAL = 'open_log_modal', + SHOW_OWN_PENDING_REQUESTS = 'show_own_pending_requests', + OPEN_ONBOARDING_MODAL = 'open_onboarding_modal', + SEND_PENDING_REQUESTS = 'send_pending_requests', + CERT_APPROVE = 'cert_approve', + CERT_REJECT = 'cert_reject', + RUN_TASK = 'run_task' +} + +export enum ViewIDs { + MODAL_REJECT = 'reject_modal', + MODAL_ACCEPT = 'accept_modal', + MODAL_LOG = 'time_submission', + MODAL_CERTIFY = 'certify_modal', + MODAL_DEPARTMENTS = 'departments_modal', + MODAL_ONBOARDING = 'onboarding_modal' +} + +export function registerSlackHandlers(app: App) { + // Commands and Shortcuts + let cmd_prefix = '/' + if (config.slack.app.command_prefix) { + cmd_prefix += config.slack.app.command_prefix + '_' + } + app.command(cmd_prefix + 'log', handleLogCommand) + app.command(cmd_prefix + 'graph', handleGraphCommand) + app.command(cmd_prefix + 'clearlogin', handleLogoutCommand) + app.command(cmd_prefix + 'voidtime', handleVoidCommand) + app.command(cmd_prefix + 'loggedin', handleGetLoggedInCommand) + app.command(cmd_prefix + 'hours', handleShowHoursCommand) + app.command(cmd_prefix + 'certify', handleCertifyCommand) + app.command(cmd_prefix + 'departments', handleDepartmentsCommand) + app.shortcut('log_hours', handleLogShortcut) + + // Buttons + app.action(ActionIDs.ACCEPT, createHoursAcceptButtonHandler('external')) + app.action(ActionIDs.ACCEPT_SUMMER, createHoursAcceptButtonHandler('summer')) + app.action(ActionIDs.ACCEPT_EVENT, createHoursAcceptButtonHandler('event')) + app.action(ActionIDs.ACCEPT_LAB, createHoursAcceptButtonHandler('lab')) + app.action(ActionIDs.ACCEPT_WITH_MSG, handleHoursAcceptWithMessageButton) + app.action(ActionIDs.REJECT, handleHoursRejectButton) + app.action(ActionIDs.OPEN_USERINFO_MODAL, handleOpenUserInfoModal) + app.action(ActionIDs.OPEN_LOG_MODAL, handleOpenLogModal) + app.action(ActionIDs.CERT_REJECT, handleCertReject) + app.action(ActionIDs.CERT_APPROVE, handleCertApprove) + app.action(ActionIDs.SHOW_OWN_PENDING_REQUESTS, handleShowPendingHours) + app.action(ActionIDs.SEND_PENDING_REQUESTS, handleSendPendingRequestsButton) + app.action(ActionIDs.OPEN_ONBOARDING_MODAL, handleOpenOnboardingModal) + app.action(ActionIDs.RUN_TASK, handleRunTask) + app.action('jump_url', async ({ ack }) => { + await ack() + }) + + // Modal Submission + app.view(ViewIDs.MODAL_REJECT, handleSubmitHoursRejectModal) + app.view(ViewIDs.MODAL_ACCEPT, handleSubmitHoursAcceptModal) + app.view(ViewIDs.MODAL_LOG, handleSubmitLogModal) + app.view(ViewIDs.MODAL_CERTIFY, handleSubmitCertifyModal) + app.view(ViewIDs.MODAL_DEPARTMENTS, handleSubmitDepartmentsModal) + app.view(ViewIDs.MODAL_ONBOARDING, handleSubmitOnboardingModal) + // Events + app.event('app_home_opened', handleAppHomeOpened) + app.action(/./, async ({ body, logger, action }) => { + const details: Record = { + type: body?.type, + user: body?.user?.id + } + if ('value' in action) { + details.value = action.value + } + if ('action_id' in action) { + details.action_id = action.action_id + } + logger.debug(details, 'Slack action triggered') + }) + + app.command(/./, async ({ body, logger, command }) => { + const details: Record = { + type: body?.type, + user: body?.user?.id, + command: command?.command, + text: command?.text + } + logger.debug(details, 'Slack command triggered') + }) +} diff --git a/src/slack/handlers/views/app_home.ts b/src/slack/handlers/views/app_home.ts new file mode 100644 index 0000000..79f7f07 --- /dev/null +++ b/src/slack/handlers/views/app_home.ts @@ -0,0 +1,12 @@ +import { EventMiddleware } from '~slack/lib/types' +import { getAppHome } from '~slack/blocks/app_home' + +export const handleAppHomeOpened: EventMiddleware<'app_home_opened'> = async ({ body, event, client }) => { + // Don't update when the messages tab is opened + if (body.event.tab == 'home') { + await client.views.publish({ + user_id: event.user, + view: await getAppHome(event.user) + }) + } +} diff --git a/src/slack/handlers/views/onboarding.ts b/src/slack/handlers/views/onboarding.ts new file mode 100644 index 0000000..c8f6535 --- /dev/null +++ b/src/slack/handlers/views/onboarding.ts @@ -0,0 +1,35 @@ +import type { ActionMiddleware, ViewMiddleware } from '~slack/lib/types' +import { getOnboardingModal } from '~slack/blocks/admin/onboarding' +import { slack_client } from '~slack' +import prisma from '~lib/prisma' + +export const handleOpenOnboardingModal: ActionMiddleware = async ({ body, ack, client }) => { + await ack() + + await client.views.open({ + view: await getOnboardingModal(), + trigger_id: body.trigger_id + }) +} + +export const handleSubmitOnboardingModal: ViewMiddleware = async ({ ack, body, view }) => { + await ack() + const members = new Set(view.private_metadata.split(',')) + const user_list = await slack_client.users.list({}) + const members_to_add = user_list.members?.filter((m) => members.has(m.id!)) ?? [] + const fallback_photos = await prisma.fallbackPhoto.findMany() + const fallback_photo_map = new Map(fallback_photos.map((fp) => [fp.email, fp.url])) + await prisma.member.createMany({ + data: members_to_add.map((m) => ({ + email: m.profile!.email!, + slack_id: m.id, + slack_photo: m.profile?.image_original, + slack_photo_small: m.profile?.image_192, + first_name: m.profile!.real_name_normalized!.split(' ')[0] ?? m.profile!.real_name_normalized, + full_name: m.profile!.real_name_normalized!, + fallback_photo: fallback_photo_map.get(m.profile!.email!), + use_slack_photo: false, + active: true + })) + }) +} diff --git a/src/slack/handlers/views/userinfo.ts b/src/slack/handlers/views/userinfo.ts new file mode 100644 index 0000000..b5c7506 --- /dev/null +++ b/src/slack/handlers/views/userinfo.ts @@ -0,0 +1,14 @@ +import type { ActionMiddleware } from '~slack/lib/types' +import { getUserDataModal } from '~slack/blocks/member/user_info' + +export const handleOpenUserInfoModal: ActionMiddleware = async ({ ack, client, body, logger }) => { + await ack() + try { + await client.views.open({ + trigger_id: body.trigger_id, + view: (await getUserDataModal({ slack_id: body.user.id })).buildToObject() + }) + } catch (err) { + logger.error('Failed to handle open settings modal:\n' + err) + } +} diff --git a/src/slack/index.ts b/src/slack/index.ts new file mode 100644 index 0000000..a57a619 --- /dev/null +++ b/src/slack/index.ts @@ -0,0 +1,20 @@ +import bolt from '@slack/bolt' +import config from '~config' +import logger, { createBoltLogger } from '~lib/logger' +import { registerSlackHandlers } from '~slack/handlers' +// Initialize Slack App +const slack_app = new bolt.App({ + token: config.slack.app.bot_token, + signingSecret: config.slack.app.signing_secret, + socketMode: true, + appToken: config.slack.app.app_token, + logger: createBoltLogger() +}) + +export const slack_client = slack_app.client + +export async function startSlack() { + await slack_app.start() + registerSlackHandlers(slack_app) + logger.info('Slack started') +} diff --git a/src/slack/lib/chart.ts b/src/slack/lib/chart.ts new file mode 100644 index 0000000..5d31240 --- /dev/null +++ b/src/slack/lib/chart.ts @@ -0,0 +1,132 @@ +import { Prisma } from '@prisma/client' +import QuickChart from 'quickchart-js' +import prisma from '~lib/prisma' +import { season_start_date } from '~config' + +class HourAggregator { + private readonly group_size: number + private readonly values: number[] + + constructor( + private start: Date, + end: Date + ) { + const diff = end.getTime() - start.getTime() + this.group_size = diff / 100 + this.values = new Array(100).fill(0) + } + + add(date: Date, value: number) { + const index = Math.floor((date.getTime() - this.start.getTime()) / this.group_size) + this.values[index] += value + } + + getCumulative() { + let running = 0 + return this.values.map((dayVal) => { + running += dayVal + return Math.round(running * 10) / 10 + }) + } + getLabels() { + return this.values.map((_, i) => { + return i % 3 == 0 ? new Date(this.start.getTime() + i * this.group_size).toLocaleDateString('en-us', { month: 'short', day: 'numeric' }) : '' + }) + } +} + +export async function createHourChartForUsers(userIds: string[]) { + const hourLogs = await prisma.hourLog.findMany({ + where: { + Member: { + slack_id: { + in: userIds + } + } + }, + select: { + time_in: true, + duration: true + }, + orderBy: { + time_in: 'asc' + } + }) + return await createHourChart(hourLogs) +} + +export async function createHourChartForTeam() { + const hourLogs = await prisma.hourLog.findMany({ + select: { + time_in: true, + duration: true + }, + orderBy: { + time_in: 'asc' + }, + where: { + time_in: { gte: season_start_date } + } + }) + return await createHourChart(hourLogs) +} + +async function createHourChart(hourLogs: { time_in: Date; duration: Prisma.Decimal | null }[]): Promise<{ + url: string + success: boolean +}> { + if (hourLogs.length < 2) { + return { url: 'https://picsum.photos/id/22/1000/600.jpg', success: false } + } + const aggregator = new HourAggregator(hourLogs[0].time_in, hourLogs[hourLogs.length - 1].time_in) + hourLogs.forEach((log) => aggregator.add(log.time_in, log.duration!.toNumber())) + + const myChart = new QuickChart() + myChart.setWidth(1000) + myChart.setHeight(600) + myChart.setConfig({ + type: 'line', + options: { + title: { + display: true, + text: 'Hours' + }, + legend: { + display: false + }, + scales: { + xAxis: { + title: { + display: true, + text: 'Date' + } + }, + yAxes: [ + { + title: { + display: true, + text: 'Hours' + }, + ticks: { + beginAtZero: true, + display: false + } + } + ] + } + }, + data: { + labels: aggregator.getLabels(), + datasets: [ + { + label: 'Hours', + fill: true, + lineTension: 0.3, + data: aggregator.getCumulative(), + pointRadius: 0 + } + ] + } + }) + return { url: await myChart.getShortUrl(), success: true } +} diff --git a/src/slack/lib/hours_submission.ts b/src/slack/lib/hours_submission.ts new file mode 100644 index 0000000..edfa908 --- /dev/null +++ b/src/slack/lib/hours_submission.ts @@ -0,0 +1,114 @@ +import prisma from '~lib/prisma' +import { enum_HourLogs_type, Prisma } from '@prisma/client' +import config from '~config' +import { slack_client } from '~slack' +import { getHourSubmissionMessage } from '~slack/blocks/admin/hour_submission' +import responses from '~slack/blocks/responses' +import logger from '~lib/logger' +import { getAppHome } from '~slack/blocks/app_home' +import { SlackMessageDto } from 'slack-block-builder' + +export async function handleHoursRequest(slack_id: string, hours: number, activity: string) { + const request: Prisma.HourLogCreateInput = { + time_in: new Date(), + type: 'external', + state: 'pending', + duration: new Prisma.Decimal(hours), + message: activity, + Member: { + connect: { + slack_id + } + } + } + + const entry = await prisma.hourLog.create({ data: request }) + + // Send request message to approvers + const message = getHourSubmissionMessage({ slack_id, activity, hours, request_id: entry.id.toString(), state: 'pending' }) + const msg = await slack_client.chat.postMessage({ channel: config.slack.channels.approval, text: message.text, blocks: message.blocks }) + + await slack_client.chat.postMessage({ + ...responses.submissionLoggedDM({ hours, activity, id: entry.id }), + channel: slack_id + }) + + await prisma.hourLog.update({ where: { id: entry.id }, data: { slack_ts: msg.ts } }) +} + +export async function handleHoursResponse({ + action, + request_id, + type, + response, + actor_slack_id +}: { + action: 'approve' | 'reject' + request_id: number + actor_slack_id: string + type?: enum_HourLogs_type + response: string | null +}): Promise<{ error?: unknown }> { + const log = await prisma.hourLog.update({ + where: { id: request_id }, + data: { + state: action == 'approve' ? 'complete' : 'cancelled', + time_out: new Date(), + type, + response + }, + include: { Member: { select: { slack_id: true } } } + }) + if (!log) { + logger.error({ request_id }, 'Could not find request info') + return { error: 'Could not find request info' } + } + try { + const message = getHourSubmissionMessage({ + slack_id: log.Member.slack_id!, + activity: log.message!, + hours: log.duration!.toNumber(), + request_id: request_id.toString(), + state: action == 'approve' ? 'approved' : 'rejected', + response, + type + }) + await slack_client.chat.update({ + channel: config.slack.channels.approval, + ts: log.slack_ts!, + text: message.text, + blocks: message.blocks + }) + let dm: SlackMessageDto + if (action == 'approve') { + dm = responses.submissionAcceptedDM({ + slack_id: actor_slack_id, + hours: log.duration!.toNumber(), + activity: log.message!, + message: log.response, + request_id, + type + }) + } else { + dm = responses.submissionRejectedDM({ + slack_id: actor_slack_id, + hours: log.duration!.toNumber(), + activity: log.message!, + message: log.response!, + request_id + }) + } + await slack_client.chat.postMessage({ + ...dm, + channel: log.Member.slack_id! + }) + await slack_client.views.publish({ + user_id: actor_slack_id, + view: await getAppHome(actor_slack_id) + }) + return {} + } catch (err) { + logger.error({ err, log }, 'Failed to handle accept modal') + return { error: err } + } +} diff --git a/src/slack/lib/messages.ts b/src/slack/lib/messages.ts new file mode 100644 index 0000000..7a3d089 --- /dev/null +++ b/src/slack/lib/messages.ts @@ -0,0 +1,33 @@ +export function sanitizeCodeblock(activity: string): string { + return activity.replace('`', "'") +} + +export function formatDuration(hrs: number, mins?: number): string { + if (typeof mins === 'undefined') { + const mins_cached = hrs * 60 + hrs = Math.floor(Math.abs(mins_cached) / 60) * Math.sign(mins_cached) + mins = Math.round(mins_cached % 60) + } + const hours = hrs === 1 ? '1 hour' : `${hrs} hours` + const minutes = mins === 1 ? '1 minute' : `${mins} minutes` + + if (hrs === 0) { + return minutes + } else if (mins === 0) { + return hours + } else { + return `${hours} and ${minutes}` + } +} + +export function formatList(names: string[]): string { + if (names.length === 0) { + return '' + } else if (names.length === 1) { + return names[0] + } else if (names.length === 2) { + return `${names[0]} and ${names[1]}` + } else { + return `${names.slice(0, names.length - 1).join(', ')}, and ${names[names.length - 1]}` + } +} diff --git a/src/slack/lib/profile.ts b/src/slack/lib/profile.ts new file mode 100644 index 0000000..88e00b8 --- /dev/null +++ b/src/slack/lib/profile.ts @@ -0,0 +1,27 @@ +import config from '~config' +import logger from '~lib/logger' +import { WebClient } from '@slack/web-api' + +const token = config.slack.app.user_token +export const profile_client: WebClient | null = token ? new WebClient(token) : null + +export async function setProfileAttribute(user: string, field: keyof typeof config.slack.profile, value: string): Promise { + if (!token) { + return false + } + try { + logger.debug(`Setting slack ${field} for ${user} to '${value}'`) + const resp = await profile_client!.users.profile.set({ + user: user, + name: config.slack.profile[field], + value + }) + if (!resp.ok) { + logger.error(resp) + } + return resp.ok + } catch (e) { + logger.error(e) + return false + } +} diff --git a/src/slack/lib/types.ts b/src/slack/lib/types.ts new file mode 100644 index 0000000..473b285 --- /dev/null +++ b/src/slack/lib/types.ts @@ -0,0 +1,20 @@ +import type { + BlockAction, + BlockElementAction, + ButtonAction, + Middleware, + SlackActionMiddlewareArgs, + SlackCommandMiddlewareArgs, + SlackEventMiddlewareArgs, + SlackShortcut, + SlackShortcutMiddlewareArgs, + SlackViewMiddlewareArgs, + ViewSubmitAction +} from '@slack/bolt' +import { StringIndexed } from '@slack/bolt/dist/types/helpers' + +export type CommandMiddleware = Middleware +export type ShortcutMiddleware = Middleware, StringIndexed> +export type ActionMiddleware = Middleware>, StringIndexed> +export type ViewMiddleware = Middleware, StringIndexed> +export type EventMiddleware = Middleware, StringIndexed> diff --git a/src/spreadsheet/index.ts b/src/spreadsheet/index.ts new file mode 100644 index 0000000..ebcd2f0 --- /dev/null +++ b/src/spreadsheet/index.ts @@ -0,0 +1,181 @@ +import { JWT } from 'google-auth-library' +import { sheets, sheets_v4 } from '@googleapis/sheets' +import config from '~config' +import prisma from '~lib/prisma' +import { getMemberPhoto } from '~lib/util' +import { calculateAllHours, getMeetings, getMeetingsMissed, getWeeklyHours } from '~lib/hour_operations' +import logger from '~lib/logger' + +/** + * Load or request or authorization to call APIs. + * + */ +export async function authorize() { + const auth = new JWT({ + email: config.google.account.client_email, + key: config.google.account.private_key, + scopes: ['https://www.googleapis.com/auth/spreadsheets'] + }) + return sheets({ version: 'v4', auth: auth }) +} + +const client = await authorize() + +function max50Hours(hours: number) { + return hours >= 50 ? '50+' : hours +} + +export async function updateSheet() { + const members = await prisma.member.findMany({ orderBy: { full_name: 'asc' }, where: { active: true } }) + const certs = await prisma.memberCert.findMany({ orderBy: { cert_id: 'asc' }, include: { Cert: { select: { label: true } } } }) + const loggedin = await prisma.hourLog.findMany({ where: { state: 'pending', type: 'lab' } }) + const certMap: Record = {} + certs.map((c) => { + certMap[c.member_id] ??= [] + certMap[c.member_id].push(c.Cert.label) + }) + + const loggedInMap: Set = new Set() + loggedin.forEach((l) => { + loggedInMap.add(l.member_id) + }) + + const meetingsMissed = await getMeetingsMissed() + const meetings = await getMeetings() + const weeklyHours = await getWeeklyHours() + const allHours = await calculateAllHours() + const headers = [ + 'Name', + 'LoggedIn', + 'Meetings', + 'MeetingsMissed', + 'LabHours', + 'ExternalHours', + 'EventHours', + 'SummerHours', + 'QualifyingHours', + 'TotalHours', + 'WeeklyHours', + 'Photo', + 'Certifications', + 'FirstRegistered' + ] as const + const columns = Object.fromEntries(headers.map((h, i) => [h, i])) as Record<(typeof headers)[number], number> + const rows: (string | number)[][] = [] + rows.push(headers as unknown as string[]) + + let hourReqMet = 0 + for (const m of members) { + const hours = allHours[m.email] ?? { event: 0, external: 0, lab: 0, summer: 0, total: 0, qualifying: 0 } + const row = new Array(headers.length).fill('') + row[columns.Name] = m.full_name + row[columns.LoggedIn] = loggedInMap.has(m.email) + row[columns.Meetings] = meetings[m.email] ?? 0 + row[columns.MeetingsMissed] = meetingsMissed[m.email] ?? 0 + row[columns.LabHours] = max50Hours(hours.lab) + row[columns.ExternalHours] = hours.external + row[columns.EventHours] = hours.event + row[columns.SummerHours] = hours.summer + row[columns.QualifyingHours] = max50Hours(hours.qualifying) + row[columns.TotalHours] = max50Hours(hours.total) + row[columns.WeeklyHours] = weeklyHours[m.email] ?? 0 + row[columns.Photo] = getMemberPhoto(m, true) ?? '' + row[columns.Certifications] = certMap[m.email]?.join(', ') + rows.push(row) + + if (hours.qualifying >= 50) { + hourReqMet++ + } + } + await client.spreadsheets.values.update({ + spreadsheetId: config.google.sheet_id, + range: "'Data'!A1:" + rows.length, + valueInputOption: 'RAW', + requestBody: { + values: rows + } + }) + const res = await client.spreadsheets.get({ + spreadsheetId: config.google.sheet_id, + includeGridData: false + }) + const data_sheet = res.data.sheets?.find((sheet) => sheet.properties?.title === 'Data') + const home_sheet = res.data.sheets?.find((sheet) => sheet.properties?.title === 'Hours & Certs') + const data_id = data_sheet?.properties?.sheetId + const home_id = home_sheet?.properties?.sheetId + + if (!data_id || !home_id) { + logger.warn('Could not find sheet ids') + return + } + const updateRequests: sheets_v4.Schema$Request[] = [] + + updateRequests.push({ + updateSheetProperties: { + properties: { + sheetId: data_id, + gridProperties: { + rowCount: rows.length, + columnCount: headers.length + } + }, + fields: 'gridProperties' + } + }) + + updateRequests.push({ + autoResizeDimensions: { + dimensions: { + sheetId: data_id, + dimension: 'COLUMNS' + } + } + }) + + updateRequests.push({ + updateCells: { + range: { + sheetId: home_id, + startRowIndex: 0, + endRowIndex: 1, + startColumnIndex: 0, + endColumnIndex: 1 + }, + rows: [ + { values: [{ note: 'Last Updated ' + new Date().toLocaleString('en-us', { day: 'numeric', month: 'long', hour12: true, hour: 'numeric', minute: '2-digit' }) }] } + ], + fields: 'note' + } + }) + + const sheetColorGreen = hourReqMet / members.length + const sheetColorRed = 1 - sheetColorGreen + const sheetColorBase = 0.3 + updateRequests.push({ + updateSheetProperties: { + properties: { + sheetId: home_id, + gridProperties: { + rowCount: rows.length + 15 // Buffer + }, + tabColorStyle: { + rgbColor: { + red: Math.min(sheetColorRed + sheetColorBase, 1), + green: Math.min(sheetColorGreen + sheetColorBase, 1), + blue: 0 + } + } + }, + fields: 'gridProperties(rowCount),tabColorStyle' + } + }) + + if (updateRequests.length > 0) { + await client.spreadsheets.batchUpdate({ + spreadsheetId: config.google.sheet_id, + requestBody: { + requests: updateRequests + } + }) + } +} diff --git a/src/tasks/certs.ts b/src/tasks/certs.ts new file mode 100644 index 0000000..c38418b --- /dev/null +++ b/src/tasks/certs.ts @@ -0,0 +1,61 @@ +import { setProfileAttribute } from '~slack/lib/profile' +import prisma from '~lib/prisma' +import { slack_client } from '~slack' +import config from '~config' +import { ordinal, sortCertLabels } from '~lib/util' +import logger from '~lib/logger' + +const congratsMessages = [ + 'Hey! Congrats @ for you new {} Cert!', // prettier don't make this one line + 'Awww, @ just got their # cert: {}! They hatch so fast... [;', + '@. {}. Well done.', + 'Bawk bawk, bawk bawk @ bawk {} bawk, bawk SKREEEEE', + 'Friends! @ has earned a {}. May we all feast and be merry. :shallow_pan_of_food: ', + 'Congrats to @ on getting a {} certification!', + '@ just earned a {}. Did you know: Software is the bread and butter of robotics.', + 'Cluck-tastic! @ has just achieved a {} Cert. Egg-cellent job!' +] +let announcementTimeout: NodeJS.Timeout | null = null + +export function scheduleCertAnnouncement() { + if (announcementTimeout) { + announcementTimeout.refresh() + } else { + announcementTimeout = setTimeout(announceNewCerts, 1000 * 15) // Certs often get changed in bursts, don't run for each change + } +} +export async function announceNewCerts() { + const toAnnounce = await prisma.member.findMany({ + where: { MemberCerts: { some: { announced: false } } }, + select: { + slack_id: true, + first_name: true, + MemberCerts: { + select: { announced: true, Cert: { select: { label: true } } } + } + } + }) + logger.debug({ toAnnounce }, 'Announcing new certs') + for (const member of toAnnounce) { + for (const cert of member.MemberCerts) { + if (cert.announced) { + continue + } + let message = congratsMessages[Math.floor(Math.random() * congratsMessages.length)] // get random message + message = message.replace('@', member.slack_id == null ? `*${member.first_name}*` : `<@${member.slack_id}>`) // set user mention + message = message.replace('{}', `*${cert.Cert.label}*`) // set cert name in *bold* + message = message.replace('#', ordinal(member.MemberCerts.length)) // set cert name in *bold* + await slack_client.chat.postMessage({ channel: config.slack.channels.celebration, text: message }) + } + if (member.slack_id) { + await setProfileAttribute( + member.slack_id, + 'certs', + member.MemberCerts.map((cert) => cert.Cert.label) + .sort(sortCertLabels) + .join(', ') + ) + } + } + await prisma.memberCert.updateMany({ where: { announced: false }, data: { announced: true } }) +} diff --git a/src/tasks/index.ts b/src/tasks/index.ts new file mode 100644 index 0000000..5dd0dd9 --- /dev/null +++ b/src/tasks/index.ts @@ -0,0 +1,57 @@ +import logger from '~lib/logger' +import { updateSlackUsergroups } from '~tasks/slack_groups' +import { syncSlackMembers } from '~tasks/slack' +import { announceNewCerts } from '~tasks/certs' +import { updateSheet } from '~spreadsheet' + +type TaskFunc = (reason: string) => Promise +type Func = (() => void) | (() => Promise) + +const tasks: Record = {} + +function scheduleTask(task: Func, interval_seconds: number, runOnInit: boolean, offset_seconds: number): TaskFunc { + const label = 'task/' + task.name + const cb = async (reason: string) => { + try { + await task() + } catch (e) { + logger.error({ name: label, error: e?.toString() }, 'Error running task for ' + reason) + throw e + } + logger.info({ name: label }, 'Task ran successfully') + return + } + if (runOnInit) { + setTimeout(() => { + cb('initial run') + }) + } + setTimeout(() => { + setInterval(() => { + cb('scheduled run') + }, interval_seconds * 1000) + }, offset_seconds * 1000) + return cb +} + +export function scheduleTasks() { + // Offset is to combat Slack's rate limits + const isProd = process.env.NODE_ENV === 'prod' + + tasks['Sync Sheet'] = scheduleTask(updateSheet, 60 * 30, isProd, 0) // Update spreadsheet every half-hour + tasks['Sync Members'] = scheduleTask(syncSlackMembers, 60 * 60, isProd, 0) // Update slack members every hour, can also be run manually on admin dashboard + tasks['Announce Certs'] = scheduleTask(announceNewCerts, 60 * 60, isProd, 60) // Just in case the cert announcement isn't automatically run on changes + tasks['Sync Departments'] = scheduleTask(updateSlackUsergroups, 60 * 60, isProd, 120) +} + +export async function runTask(key: string) { + if (key in tasks) { + await tasks[key]('manual run') + } else { + logger.warn('No task found for key: ' + key) + } +} + +export function getTaskKeys(): string[] { + return Object.keys(tasks) +} diff --git a/src/tasks/slack.ts b/src/tasks/slack.ts new file mode 100644 index 0000000..38871e7 --- /dev/null +++ b/src/tasks/slack.ts @@ -0,0 +1,49 @@ +import logger from '~lib/logger' + +import AsyncLock from 'async-lock' +import prisma from '~lib/prisma' +import { slack_client } from '~slack' +import { Member as SlackMember } from '@slack/web-api/dist/types/response/UsersListResponse' +import config from '~config' + +const lock = new AsyncLock({ maxExecutionTime: 3000, maxPending: 0 }) + +export async function syncSlackMembers() { + if (lock.isBusy()) { + return + } + await lock + .acquire('slack_sync', async () => { + logger.info('Starting slack sync...') + const db_members = await prisma.member.findMany() + const slack_members = (await slack_client.users.list({})).members ?? [] + const slack_members_lookup: Record = {} + slack_members.forEach((member) => { + if (member.profile?.email) { + slack_members_lookup[member.profile.email.toLowerCase().trim()] = member + } + }) + let updated = 0 + const students_group = await slack_client.usergroups.users.list({ usergroup: config.slack.groups.students }) + const students_set = new Set(students_group.users ?? []) + for (const member of db_members) { + const slack_member = slack_members_lookup[member.email] + if (slack_member != null) { + const display_name = ((slack_member.profile?.display_name?.length ?? 0) > 0 ? slack_member.profile?.display_name : slack_member.name) ?? member.full_name + await prisma.member.update({ + where: { email: member.email }, + data: { + slack_id: slack_member.id, + slack_photo: slack_member.profile?.image_original, + slack_photo_small: slack_member.profile?.image_192, + first_name: display_name.split(' ')[0].trim(), + active: students_set.has(slack_member.id!) && !slack_member?.deleted + } + }) + updated++ + } + } + logger.info(`Found ${updated} members on slack`) + }) + .catch((err: Error) => logger.warn(err.message)) +} diff --git a/src/tasks/slack_groups.ts b/src/tasks/slack_groups.ts new file mode 100644 index 0000000..5b033c9 --- /dev/null +++ b/src/tasks/slack_groups.ts @@ -0,0 +1,77 @@ +import prisma from '~lib/prisma' +import logger from '~lib/logger' +import { profile_client } from '~slack/lib/profile' +import { slack_client } from '~slack' + +let timeout: NodeJS.Timeout + +export function scheduleUpdateSlackUsergroups() { + if (timeout) { + timeout.refresh() + } else { + timeout = setTimeout(updateSlackUsergroups, 1000 * 30) + } +} +export async function updateSlackUsergroups() { + if (profile_client == null) { + return + } + const usergroups_list = await slack_client.usergroups.list({ include_disabled: true }) + const usergroups = new Map(usergroups_list.usergroups!.map((g) => [g.id!, g])) + const departments = await prisma.department.findMany({ include: { Members: { select: { Member: { select: { slack_id: true } } } } } }) + for (const department of departments) { + const handle = department.name.toLowerCase().replace(' ', '-') + '-dept' + const existing = usergroups.get(department.slack_group ?? '') + if (department.slack_group == null || existing == null) { + const resp = await profile_client.usergroups + .create({ + name: department.name, + handle + }) + .catch((e) => { + return { usergroup: null, error: e } + }) + if (resp.error != null) { + logger.error({ error: resp.error, department: department.id, handle: department.name.toLowerCase().replace(' ', '-') }, 'Could not create usergroup') + continue + } else { + await prisma.department.update({ + where: { id: department.id }, + data: { + slack_group: resp.usergroup!.id + } + }) + department.slack_group = resp.usergroup!.id! + } + } else { + if (existing.name != department.name || existing.handle != handle) { + await profile_client.usergroups.update({ + usergroup: department.slack_group, + name: department.name, + handle + }) + } + } + + const members = department.Members.filter((m) => m.Member.slack_id != null).map((m) => m.Member.slack_id!) + if (members.length == 0 && existing != null) { + // If it's already disabled, don't disable it again + if (!existing.date_delete) { + await profile_client.usergroups.disable({ + usergroup: department.slack_group + }) + } + } + if (members.length > 0) { + if (existing?.date_delete) { + await profile_client.usergroups.enable({ + usergroup: department.slack_group + }) + } + await profile_client.usergroups.users.update({ + usergroup: department.slack_group, + users: members.join(',') + }) + } + } +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..41617a2 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,55 @@ +export type HourCategory = 'lab' | 'external' | 'summer' | 'event' // must also change enum constraint when modifying + +export type APIMember = { + email: string + first_name: string + full_name: string + photo: string + photo_small: string +} + +export type APIClockLabRequest = { + action: 'in' | 'out' | 'void' + email: string +} + +export type APIClockExternalSubmitRequest = { + email: string + message: string + hours: number +} + +export type APIClockExternalRespondRequest = { + id: number + action: 'approve' | 'deny' + category: HourCategory +} +export type APIClockResponse = { success: false; error: string; log_id?: number } | { success: true; log_id: number } + +type APIMembersResponse = APIMember[] +export type APILoggedIn = { id: string; email: string; time_in: string } + +interface APIMethod { + req: unknown + resp: unknown +} +interface APIRoute { + POST?: APIMethod + GET?: APIMethod + PUT?: APIMethod +} + +export interface APIRoutes extends Record { + '/clock/lab': { + POST: { req: APIClockLabRequest; resp: APIClockResponse } + GET: { req: null; resp: APILoggedIn[] } + } + '/members': { + GET: { req: null; resp: APIMembersResponse } + } + '/members/refresh': { + GET: { req: null; resp: APIMembersResponse } + } +} + +export type WSCluckChange = { email: string; logging_in: boolean } diff --git a/src/views/admin_certs/index.html b/src/views/admin_certs/index.html new file mode 100644 index 0000000..817abb1 --- /dev/null +++ b/src/views/admin_certs/index.html @@ -0,0 +1,15 @@ + + + + Certs + + + + + +
+
+
+ + + diff --git a/src/views/admin_certs/index.ts b/src/views/admin_certs/index.ts new file mode 100644 index 0000000..b2f7da3 --- /dev/null +++ b/src/views/admin_certs/index.ts @@ -0,0 +1,131 @@ +import Prisma from '@prisma/client' +import * as ag from 'ag-grid-community' +import 'ag-grid-community/styles/ag-grid.min.css' +import 'ag-grid-community/styles/ag-theme-quartz.min.css' + +async function main() { + const departmentLookup = new Map() + + class ButtonComponent implements ag.ICellRendererComp { + private eGui!: HTMLElement + private eButton!: HTMLElement + + getGui(): HTMLElement { + return this.eGui + } + destroy?(): void { + this.eGui.remove() + } + refresh(): boolean { + return true + } + // ... + init(params: ag.ICellRendererParams) { + // create the cell + this.eGui = document.createElement('div') + this.eGui.className = 'w-full h-full flex items-center justify-center' + if (params.data.createdAt == null) { + this.eButton = document.createElement('button') + this.eButton.className = + 'block border-gray-400 text-sm text-gray-50 px-2 py-1 w-full rounded-md border bg-transparent hover:bg-gray-500 transition-colors duration-300' + this.eButton.innerText = 'Add' + this.eButton.addEventListener('click', async () => { + const bottomRow = gridApi.getPinnedBottomRow(0)! + const res = await fetch('/api/admin/certs', { method: 'POST', body: JSON.stringify(bottomRow.data) }) + if (res.ok) { + const response_data = await res.json() + gridApi.applyTransaction({ add: [response_data.data] }) + const row = gridApi.getRowNode(response_data.data.id) + row?.setSelected(true) + bottomRow.setData({ id: '', label: '', isManager: false, department: null, replaces: null }) + gridApi.getColumnDef('replaces')?.cellEditorParams?.values?.push(response_data.data.id) + } else { + alert('Invalid data') + } + }) + this.eGui.appendChild(this.eButton) + } + } + // ... + } + + const colDefs: ag.ColDef[] = [ + { + editable: false, + headerName: '', + cellRenderer: ButtonComponent, + initialWidth: 100 + }, + { + field: 'id', + editable: true, + headerName: 'Cert ID', + sort: 'asc' + }, + { + field: 'label', + editable: true, + cellEditor: 'agTextCellEditor', + headerName: 'Label' + }, + { + field: 'isManager', + editable: true, + headerName: 'Manager?' + }, + { + colId: 'department', + field: 'department', + editable: true, + cellEditor: 'agSelectCellEditor', + headerName: 'Department', + cellEditorParams: { + values: [] + } + }, + { + colId: 'replaces', + field: 'replaces', + editable: true, + cellEditor: 'agSelectCellEditor', + cellEditorParams: { values: [] }, + headerName: 'Replaces' + } + ] + // Grid Options: Contains all of the Data Grid configurations + const gridOptions: ag.GridOptions = { + getRowId: (params) => params.data.id, + + // Column Definitions: Defines the columns to be displayed. + columnDefs: colDefs, + pinnedBottomRowData: [{ id: '', label: '', isManager: false, department: null, replaces: null }], + + defaultColDef: { + valueFormatter: (params) => + params.node?.isRowPinned() && (params.value == null || params.value == '') ? (params.colDef.headerName ?? params.colDef.field!) + '...' : params.value + }, + getRowStyle: ({ node }) => (node?.isRowPinned() ? { 'font-weight': '300', 'color': '#a0a0a0', 'font-style': 'italic' } : undefined), + async onCellValueChanged(event) { + if (event.rowPinned) { + return + } + const res = await fetch('/api/admin/certs', { method: 'PUT', body: JSON.stringify(event.data) }) + const cert = await res.json() + if (cert.error) { + event.node.setDataValue(event.column.getColDef().field!, event.oldValue) + } + gridApi.applyTransaction({ update: [cert] }) + } + } + // Your Javascript code to create the Data Grid + const gridApi = ag.createGrid(document.querySelector('#mygrid')!, gridOptions) + fetch('/api/admin/certs').then(async (resp) => { + const deps_resp = await fetch('/api/admin/departments') + const deps: Prisma.Department[] = await deps_resp.json() + const certs: Prisma.Cert[] = await resp.json() + gridApi.setGridOption('rowData', certs) + gridApi.getColumnDef('department')!.cellEditorParams!.values = [null, ...deps.map((c) => c.id)] + gridApi.getColumnDef('replaces')!.cellEditorParams!.values = [null, ...certs.filter((c) => !c.isManager).map((c) => c.id)] + }) +} +main() diff --git a/src/views/admin_departments/index.html b/src/views/admin_departments/index.html new file mode 100644 index 0000000..817abb1 --- /dev/null +++ b/src/views/admin_departments/index.html @@ -0,0 +1,15 @@ + + + + Certs + + + + + +
+
+
+ + + diff --git a/src/views/admin_departments/index.ts b/src/views/admin_departments/index.ts new file mode 100644 index 0000000..dfd4295 --- /dev/null +++ b/src/views/admin_departments/index.ts @@ -0,0 +1,106 @@ +import Prisma from '@prisma/client' +import * as ag from 'ag-grid-community' +import 'ag-grid-community/styles/ag-grid.min.css' +import 'ag-grid-community/styles/ag-theme-quartz.min.css' + +async function main() { + class ButtonComponent implements ag.ICellRendererComp { + private eGui!: HTMLElement + private eButton!: HTMLElement + + getGui(): HTMLElement { + return this.eGui + } + destroy?(): void { + this.eGui.remove() + } + refresh(): boolean { + return true + } + // ... + init(params: ag.ICellRendererParams) { + // create the cell + this.eGui = document.createElement('div') + this.eGui.className = 'w-full h-full flex items-center justify-center' + if (params.data.createdAt == null) { + this.eButton = document.createElement('button') + this.eButton.className = + 'block border-gray-400 text-sm text-gray-50 px-2 py-1 w-full rounded-md border bg-transparent hover:bg-gray-500 transition-colors duration-300' + this.eButton.innerText = 'Add' + this.eButton.addEventListener('click', async () => { + const bottomRow = gridApi.getPinnedBottomRow(0)! + const res = await fetch('/api/admin/departments', { method: 'POST', body: JSON.stringify(bottomRow.data) }) + if (res.ok) { + const response_data = await res.json() + gridApi.applyTransaction({ add: [response_data.data] }) + const row = gridApi.getRowNode(response_data.data.id) + row?.setSelected(true) + bottomRow.setData({ id: '', name: '', slack_group: '' }) + } else { + alert('Invalid data') + } + }) + this.eGui.appendChild(this.eButton) + } + } + // ... + } + + const colDefs: ag.ColDef[] = [ + { + editable: false, + headerName: '', + cellRenderer: ButtonComponent, + initialWidth: 100 + }, + { + field: 'id', + editable: false, + headerName: 'Department ID', + sort: 'asc' + }, + { + field: 'name', + editable: true, + cellEditor: 'agTextCellEditor', + headerName: 'Label' + }, + { + field: 'slack_group', + editable: true, + headerName: 'Slack Group ID' + } + ] + // Grid Options: Contains all of the Data Grid configurations + const gridOptions: ag.GridOptions = { + getRowId: (params) => params.data.id, + + // Column Definitions: Defines the columns to be displayed. + columnDefs: colDefs, + pinnedBottomRowData: [{ id: '', name: '', slack_group: '' }], + + defaultColDef: { + valueFormatter: (params) => + params.node?.isRowPinned() && (params.value == null || params.value == '') ? (params.colDef.headerName ?? params.colDef.field!) + '...' : params.value + }, + getRowStyle: ({ node }) => (node?.isRowPinned() ? { 'font-weight': '300', 'color': '#a0a0a0', 'font-style': 'italic' } : undefined), + async onCellValueChanged(event) { + if (event.rowPinned) { + return + } + const res = await fetch('/api/admin/departments', { method: 'PUT', body: JSON.stringify(event.data) }) + const cert = await res.json() + if (cert.error) { + event.node.setDataValue(event.column.getColDef().field!, event.oldValue) + } + gridApi.applyTransaction({ update: [cert] }) + } + } + // Your Javascript code to create the Data Grid + const gridApi = ag.createGrid(document.querySelector('#mygrid')!, gridOptions) + fetch('/api/admin/departments').then(async (resp) => { + const deps: Prisma.Department[] = await resp.json() + gridApi.setGridOption('rowData', deps) + }) +} +main() diff --git a/src/views/admin_meetings/index.html b/src/views/admin_meetings/index.html new file mode 100644 index 0000000..817abb1 --- /dev/null +++ b/src/views/admin_meetings/index.html @@ -0,0 +1,15 @@ + + + + Certs + + + + + +
+
+
+ + + diff --git a/src/views/admin_meetings/index.ts b/src/views/admin_meetings/index.ts new file mode 100644 index 0000000..545f822 --- /dev/null +++ b/src/views/admin_meetings/index.ts @@ -0,0 +1,110 @@ +import Prisma from '@prisma/client' +import * as ag from 'ag-grid-community' +import 'ag-grid-community/styles/ag-grid.min.css' +import 'ag-grid-community/styles/ag-theme-quartz.min.css' + +async function main() { + class ButtonComponent implements ag.ICellRendererComp { + private eGui!: HTMLElement + private eButton!: HTMLElement + + getGui(): HTMLElement { + return this.eGui + } + destroy?(): void { + this.eGui.remove() + } + refresh(): boolean { + return true + } + // ... + init(params: ag.ICellRendererParams) { + // create the cell + const rowID = params.data.id + console.log(rowID) + this.eGui = document.createElement('div') + this.eGui.className = 'w-full h-full flex items-center justify-center' + this.eButton = document.createElement('button') + this.eButton.className = 'block border-red-400 text-sm text-red-50 px-2 py-1 w-full rounded-md border bg-transparent hover:bg-red-500 transition-colors duration-300' + this.eButton.innerText = 'Delete' + this.eButton.addEventListener('click', async () => { + if (!confirm('Are you sure you want to delete this meeting?')) { + return + } + const res = await fetch('/api/admin/meetings', { method: 'DELETE', body: JSON.stringify(gridApi.getRowNode(rowID)?.data) }) + if (res.ok) { + const nullDate = new Date(0) + gridApi.applyTransaction({ + remove: [ + { + id: rowID, + date: nullDate, + mandatory: false, + createdAt: nullDate, + updatedAt: nullDate + } + ] + }) + } else { + alert(res) + } + }) + this.eGui.appendChild(this.eButton) + } + // ... + } + const colDefs: ag.ColDef[] = [ + { + field: 'id', + editable: false, + headerName: 'ID', + initialWidth: 50, + cellStyle: { color: '#a0a0a0' } + }, + { + field: 'date', + editable: true, + cellEditor: 'agDateStringCellEditor', + headerName: 'Date', + sort: 'asc', + initialSort: 'asc', + valueParser: (params) => new Date(params.newValue), + valueFormatter: (params) => new Date(params.value).toLocaleDateString('en-US', { timeZone: 'UTC' }) + }, + { + field: 'mandatory', + editable: true, + headerName: 'Is Mandatory?' + }, + { + headerName: '', + sortable: false, + cellRenderer: ButtonComponent + } + ] + // Grid Options: Contains all of the Data Grid configurations + const gridOptions: ag.GridOptions = { + getRowId: (params) => params.data.id.toString(), + // Column Definitions: Defines the columns to be displayed. + columnDefs: colDefs, + getRowStyle: ({ node }) => (node?.isRowPinned() ? { 'font-weight': '300', 'color': '#a0a0a0', 'font-style': 'italic' } : undefined), + async onCellValueChanged(event) { + if (event.rowPinned) { + return + } + const res = await fetch('/api/admin/meetings', { method: 'PUT', body: JSON.stringify(event.data) }) + const cert = await res.json() + if (cert.error) { + event.node.setDataValue(event.column.getColDef().field!, event.oldValue) + } + gridApi.applyTransaction({ update: [cert] }) + } + } + // Your Javascript code to create the Data Grid + const gridApi = ag.createGrid(document.querySelector('#mygrid')!, gridOptions) + fetch('/api/admin/meetings').then(async (resp) => { + const meetings: Prisma.Meetings[] = await resp.json() + gridApi.setGridOption('rowData', meetings) + }) +} +main() diff --git a/src/views/admin_members/grid.ts b/src/views/admin_members/grid.ts new file mode 100644 index 0000000..5e0268c --- /dev/null +++ b/src/views/admin_members/grid.ts @@ -0,0 +1,89 @@ +import Prisma from '@prisma/client' +import * as ag from 'ag-grid-community' +import { getMemberPhoto } from '~lib/util' + +export function getColumns(params: { photo_column_formatter?: unknown }) { + const out: ag.ColDef[] = [ + { + field: 'email', + editable: true + }, + { + field: 'full_name', + editable: true, + cellEditor: 'agTextCellEditor', + headerName: 'Full Name', + sort: 'asc' + }, + { + field: 'use_slack_photo', + editable: true, + headerName: 'Slack Photo Approved', + initialWidth: 100 + } + ] + if (params.photo_column_formatter) { + out.unshift({ + headerName: '', + valueGetter: (params) => getMemberPhoto(params.data as never) ?? '', + editable: false, + cellRenderer: params.photo_column_formatter, + initialWidth: 100 + }) + } + + return out +} + +export class ProfilePhotoComponent implements ag.ICellRendererComp { + private eGui!: HTMLElement + private eImage!: HTMLImageElement + private eBlank!: HTMLDivElement + + getGui(): HTMLElement { + return this.eGui + } + destroy?(): void { + this.eGui.remove() + } + refresh(params: ag.ICellRendererParams): boolean { + this.eImage.src = params.value + if (params.value) { + this.eBlank.style.display = 'none' + this.eImage.style.display = 'block' + } else { + this.eImage.style.display = 'none' + this.eBlank.style.display = 'block' + } + + return true + } + // ... + init(params: ag.ICellRendererParams) { + // create the cell + + this.eGui = document.createElement('div') + this.eGui.className = 'h-full w-full overflow-hidden' + + const childClass = 'rounded-sm mx-auto object-contain h-full aspect-square border-[3px] ' + (params.data.slack_id != null ? 'border-green-500' : 'border-red-500') + this.eImage = document.createElement('img') + + this.eImage.src = params.value + this.eImage.alt = 'Profile Photo' + this.eImage.className = childClass + + this.eBlank = document.createElement('div') + this.eBlank.className = childClass + + if (params.value) { + this.eBlank.style.display = 'none' + this.eImage.style.display = 'block' + } else { + this.eImage.style.display = 'none' + this.eBlank.style.display = 'block' + } + this.eGui.appendChild(this.eImage) + this.eGui.appendChild(this.eBlank) + } + // ... +} diff --git a/src/views/admin_members/index.html b/src/views/admin_members/index.html new file mode 100644 index 0000000..1c7deb3 --- /dev/null +++ b/src/views/admin_members/index.html @@ -0,0 +1,19 @@ + + + + Members + + + + + +
+
+
+
+ +
+
+ + + diff --git a/src/views/admin_members/index.ts b/src/views/admin_members/index.ts new file mode 100644 index 0000000..74a775a --- /dev/null +++ b/src/views/admin_members/index.ts @@ -0,0 +1,43 @@ +import Prisma from '@prisma/client' +import * as ag from 'ag-grid-community' +import 'ag-grid-community/styles/ag-grid.min.css' +import 'ag-grid-community/styles/ag-theme-quartz.min.css' +import { getColumns, ProfilePhotoComponent } from '~views/admin_members/grid' +import { initNewMemberTable } from '~views/admin_members/new_member' + +async function main() { + const gridOptions: ag.GridOptions = { + getRowId: (params) => params.data.email, + + columnDefs: getColumns({ photo_column_formatter: ProfilePhotoComponent }), + + async onCellValueChanged(event) { + const payload: Prisma.Member & { id?: string } = event.data + if (event.column.getColDef().field == 'email') { + payload.id = event.oldValue + } else { + payload.id = event.data.email + } + const res = await fetch('/api/admin/members', { method: 'PUT', body: JSON.stringify(payload) }) + const member = await res.json() + gridApi.applyTransaction({ update: [member] }) + } + } + + const gridApi = ag.createGrid(document.querySelector('#mygrid')!, gridOptions) + + fetch('/api/admin/members').then(async (resp) => { + gridApi.setGridOption('rowData', await resp.json()) + }) + + document.getElementById('btn-sync-slack')?.addEventListener('click', async () => { + await fetch('/api/members/refresh', { method: 'GET' }) + fetch('/api/admin/members').then(async (resp) => { + gridApi.setGridOption('rowData', await resp.json()) + }) + }) + + await initNewMemberTable(gridApi) +} + +main() diff --git a/src/views/admin_members/new_member.ts b/src/views/admin_members/new_member.ts new file mode 100644 index 0000000..9c9083f --- /dev/null +++ b/src/views/admin_members/new_member.ts @@ -0,0 +1,60 @@ +import Prisma from '@prisma/client' +import * as ag from 'ag-grid-community' +import { GridApi } from 'ag-grid-community' +import 'ag-grid-community/styles/ag-grid.min.css' +import 'ag-grid-community/styles/ag-theme-quartz.min.css' +import { toTitleCase } from '~lib/util' +import { getColumns } from '~views/admin_members/grid' + +const getDefaultRow = () => ({ use_slack_photo: false }) as never +export async function initNewMemberTable(mainTable: GridApi) { + class ButtonComponent implements ag.ICellRendererComp { + private eGui!: HTMLElement + private eButton!: HTMLElement + + getGui(): HTMLElement { + return this.eGui + } + destroy?(): void { + this.eGui.remove() + } + refresh(): boolean { + return true + } + + init() { + this.eGui = document.createElement('div') + this.eGui.className = 'w-full h-full flex items-center justify-center' + this.eButton = document.createElement('button') + this.eButton.className = 'block border-gray-400 text-sm text-gray-50 px-2 py-1 w-full rounded-md border bg-transparent hover:bg-gray-500 transition-colors duration-300' + this.eButton.innerText = 'Add' + this.eButton.addEventListener('click', async () => { + const bottomRow = grid.getDisplayedRowAtIndex(0)! + const res = await fetch('/api/admin/members', { method: 'POST', body: JSON.stringify(bottomRow.data) }) + const response_data = await res.json() + if (response_data.success) { + mainTable.applyTransaction({ add: [response_data.data] }) + mainTable.getRowNode(response_data.data.email)?.setSelected(true) + bottomRow.setData(getDefaultRow()) + } else { + alert('Invalid data') + } + }) + this.eGui.appendChild(this.eButton) + } + } + + const gridOptions: ag.GridOptions = { + columnDefs: getColumns({ photo_column_formatter: ButtonComponent }), + rowData: [getDefaultRow()], + headerHeight: 0, + rowHeight: 50, + autoSizePadding: 0, + defaultColDef: { + valueFormatter: (params) => (params.value == null || params.value == '' ? (params.colDef.headerName ?? toTitleCase(params.colDef.field!)) + '...' : params.value) + }, + getRowStyle: () => ({ 'font-weight': '300', 'color': '#a0a0a0', 'font-style': 'italic' }) + } + + const grid = ag.createGrid(document.querySelector('#new-member-grid')!, gridOptions) +} diff --git a/src/views/grid/clockapi.ts b/src/views/grid/clockapi.ts new file mode 100644 index 0000000..466d747 --- /dev/null +++ b/src/views/grid/clockapi.ts @@ -0,0 +1,39 @@ +/* exported clock cluckedIn ping refreshMemberList getApiKey checkAuth*/ + +import type { APIClockLabRequest, APIMember, APILoggedIn } from '~types' +import { apiFetch } from '~views/util' +import { getClockMode } from '~views/grid/style' + +export async function clock(email: string, clockingIn: boolean): Promise { + const outMode = getClockMode() == 'normal' ? 'out' : 'void' + const body: APIClockLabRequest = { + email: email, + action: clockingIn ? 'in' : outMode + } + const res = await apiFetch('/clock/lab', 'POST', body) + return res?.success ?? false +} + +export async function getLoggedIn(): Promise { + const res = await apiFetch('/clock/lab', 'GET', null) + if (!res) { + throw new Error('error loading data') + } + return res +} + +export async function refreshMemberList(): Promise { + const res = await apiFetch('/members/refresh', 'GET', null) + if (!res) { + throw new Error('error loading data') + } + return res +} + +export async function getMemberList(): Promise { + const res = await apiFetch('/members', 'GET', null) + if (!res) { + throw new Error('error loading data') + } + return res +} diff --git a/src/views/grid/gestures.ts b/src/views/grid/gestures.ts new file mode 100644 index 0000000..dcdbce4 --- /dev/null +++ b/src/views/grid/gestures.ts @@ -0,0 +1,56 @@ +/* eslint @typescript-eslint/no-explicit-any: 0 */ +// USES MOSES LIBRARY: https://github.com/ifrost/moses +import { openFullscreen } from '../util' +import { redrawRows, refreshMemberListAndRerun } from '.' + +declare const moses: any + +// create collection of predefined patterns +const mosesPatterns = moses.model.MosesPatterns.create() + +// choose patterns from the collection +// var patterns = [mosesPatterns.V, mosesPatterns.CIRCLE, mosesPatterns.DASH, mosesPatterns.SQUARE, mosesPatterns.SEVEN, mosesPatterns.Z]; +const patterns = [mosesPatterns.CIRCLE, mosesPatterns.SQUARE, mosesPatterns.V] + +// create a sampler +const div = document.body +const sampler = moses.sampler.DistanceSampler.create(div, 5) + +// create a recogniser +export function registerGestures() { + const recogniser = moses.recogniser.DefaultRecogniser.create() + + // register selected patterns + patterns.forEach(function (pattern) { + recogniser.register(pattern) + }) + // display the result + recogniser.on('recognised', async function (data: any) { + window.gestureDetected = true + if (data.bestMatch.value > 0.4) { + switch (data.bestMatch.pattern.name) { + case mosesPatterns.CIRCLE.name: + refreshMemberListAndRerun() + break + case mosesPatterns.SQUARE.name: + refreshMemberListAndRerun() + break + case mosesPatterns.V.name: + openFullscreen() + redrawRows() + break + } + setTimeout(() => { + window.gestureDetected = false + }, 50) + } else { + window.gestureDetected = false + } + }) + + // assign sampler to the recogniser + recogniser.sampler = sampler + + // activate the sampler + sampler.activate() +} diff --git a/src/views/grid/index.html b/src/views/grid/index.html new file mode 100644 index 0000000..aeab4e3 --- /dev/null +++ b/src/views/grid/index.html @@ -0,0 +1,15 @@ + + + + Cluck Grid + + + + + +
NOT CONNECTED TO CLUCK API!
+
+ + + + diff --git a/src/views/grid/index.ts b/src/views/grid/index.ts new file mode 100644 index 0000000..5cb8ea9 --- /dev/null +++ b/src/views/grid/index.ts @@ -0,0 +1,146 @@ +import { APIMember, WSCluckChange } from '~types' +import { openFullscreen } from '../util' +import { clock, getLoggedIn, getMemberList, refreshMemberList } from './clockapi' +import { registerGestures } from './gestures' +import { applyRandomStyles, isButtonLoggedIn, setButtonLoggedIn } from './style' +import socket_io from 'socket.io-client' + +declare global { + interface Window { + skipAuth: boolean + gestureDetected: boolean + openFullscreen: () => void + } +} +window.openFullscreen = openFullscreen +let members: APIMember[] + +export async function buildGrid() { + // document.getElementById('button-grid')!.style.display = 'none' + redrawRows() + + // Make member buttons + document.getElementById('button-grid')!.replaceChildren() + const memberButtons = members.map((member) => { + // Init button + const memberButton = document.createElement('div') + memberButton.classList.add('memberButton') + memberButton.id = member.email + + // Set click toggle + memberButton.onclick = async (click) => { + if (window.gestureDetected) { + // Avoid registering clicks when swiping + return + } + let button = click.target as HTMLDivElement + if (button.classList.contains('buttonText')) { + button = button.parentElement as HTMLDivElement + } + + // Cluck API Call + const ok = await clock(button.id, !isButtonLoggedIn(button)) + if (!ok) { + await refreshLoggedIn() + } + } + + // Add name text + const text = document.createElement('div') + text.classList.add('buttonText') + text.innerHTML = member.first_name + + // Randomize mix and match text styles + applyRandomStyles(text) + + // Do other adding and styling things + memberButton.appendChild(text) + memberButton.style.setProperty('background-image', `url(${member.photo})`) + return memberButton + }) + document.getElementById('button-grid')!.replaceChildren(...memberButtons) + setTimeout(refreshLoggedIn) +} + +;(async () => { + members = await getMemberList() + await buildGrid() + registerGestures() + addEventListener('resize', redrawRows) +})() + +async function refreshLoggedIn() { + const membersIn = new Set() + const noconnect = document.getElementById('noconnect')! + try { + const loggedIn = await getLoggedIn() + loggedIn.forEach((member) => membersIn.add(member.email)) + noconnect.style.setProperty('visibility', 'hidden') + } catch (err) { + console.warn(err) + noconnect.style.setProperty('visibility', 'visible') + return + } + + // Update buttons + const buttons = document.getElementsByClassName('memberButton') as HTMLCollectionOf + for (const button of buttons) { + setButtonLoggedIn(button, membersIn.has(button.id)) + } +} + +export function redrawRows() { + // Compute number of rows and columns, and cell size + const n = members.length + const x = document.documentElement.clientWidth + const y = document.documentElement.clientHeight + const ratio = x / y + const ncolsFloat = Math.sqrt(n * ratio) + const nrowsFloat = n / ncolsFloat + + // Find best option filling the whole height + let nrows1 = Math.ceil(nrowsFloat) + let ncols1 = Math.ceil(n / nrows1) + while (nrows1 * ratio < ncols1) { + nrows1++ + ncols1 = Math.ceil(n / nrows1) + } + const cellSize1 = y / nrows1 + + // Find best option filling the whole width + let ncols2 = Math.ceil(ncolsFloat) + let nrows2 = Math.ceil(n / ncols2) + while (ncols2 < nrows2 * ratio) { + ncols2++ + nrows2 = Math.ceil(n / ncols2) + } + const cellSize2 = x / ncols2 + + // Find the best values + let nrows, ncols + if (cellSize1 < cellSize2) { + nrows = nrows2 + ncols = ncols2 + } else { + nrows = nrows1 + ncols = ncols1 + } + + document.documentElement.style.setProperty('--width', ncols.toString()) + document.documentElement.style.setProperty('--height', nrows.toString()) +} + +export async function refreshMemberListAndRerun() { + members = await refreshMemberList() + await buildGrid() +} + +setInterval(refreshMemberListAndRerun, 60 * 60 * 1000) + +const socket = socket_io({ path: '/ws' }) +socket.on('cluck_change', (data: WSCluckChange) => { + const element = document.getElementById(data.email) + if (element) { + setButtonLoggedIn(element as HTMLDivElement, data.logging_in) + } +}) diff --git a/src/views/grid/style.scss b/src/views/grid/style.scss new file mode 100644 index 0000000..ac80eb9 --- /dev/null +++ b/src/views/grid/style.scss @@ -0,0 +1,162 @@ +/* Fonts */ +@font-face { + font-family: basics-serif; + src: url(/static/font/basics_serif.ttf); +} + +@font-face { + font-family: cocogoose; + src: url(/static/font/cocogoose.ttf); +} + +@font-face { + font-family: gilroy; + src: url(/static/font/gilroy-bold.otf); +} + +@font-face { + font-family: tcm; + src: url(/static/font/tcm.ttf); +} + +html { + overflow: hidden; + touch-action: none; +} + +body { + background-image: url('/static/img/bg-grid.png'); + background-size: cover; + background-repeat: no-repeat; + overscroll-behavior: none; +} + +.blurredBody { + background-image: url('/static/img/bg-grid-blurred.png'); + background-size: cover; + background-repeat: no-repeat; + height: 100vh; + width: 100vw; + overflow: hidden; +} + +.noconnect { + position: absolute; + background-color: red; + font-size: 400%; + color: white; + height: 60%; + width: 100%; + text-align: center; + z-index: 100; + left: 0%; + vertical-align: middle; + line-height: 800%; + top: 15%; + visibility: hidden; +} + +:root { + --width: 10; + --height: 4; +} + +.button-grid { + height: 97vh; + margin: 10px; + display: grid; + grid-template-columns: repeat(var(--width), 1fr); + grid-template-rows: repeat(var(--height), 1fr); +} + +/* .button-in { + filter: grayscale(90%); + box-shadow: inset 0 0 0 1000px rgba(255, 255, 255, 0.2); +} */ + +/* filter: grayscale(0%); +box-shadow: inset 0 0 0 1000px rgba(0, 255, 85, 0.2); */ + +.memberButton { + margin: 4px; + + background-size: cover; + background-position-y: top; + background-position-x: center; + background-repeat: no-repeat; + + border-radius: 20px; + text-align: center; + position: relative; + overflow: hidden; + transition-duration: 75ms; +} + +.memberButton { + &[data-loggedin='false'] { + box-shadow: + inset 0 0 0 1000px rgba(255, 255, 255, 0.4), + 0 0 10px rgba(255, 0, 0, 0.5); + filter: grayscale(100%); + } + &[data-loggedin='true'] { + filter: grayscale(0%); + box-shadow: + inset 0 0 0 1000px rgba(255, 255, 255, 0), + 0 0 15px 7px rgb(0, 255, 136); + } +} + +body[data-gridstyle='void'] { + .memberButton { + .buttonText { + box-shadow: inset 0 0 0 1000px rgba(230, 0, 0, 0.6); + } + &[data-loggedin='false'] { + transition-duration: 1s; + } + &[data-loggedin='true'] { + filter: grayscale(0%); + box-shadow: + inset 0 0 0 1000px rgba(255, 255, 255, 0), + 0 0 15px 7px rgb(255, 0, 0); + } + } +} + +.buttonText { + user-select: none; + box-shadow: inset 0 0 0 1000px rgba(70, 255, 169, 0.6); + border-radius: 14px; + font-size: calc(14vw / var(--width)); + padding: 3px; + font-family: 'tcm', sans-serif; + position: absolute; + color: white; + + left: 0; + right: 0; + display: inline-block; + + &.labelTop { + top: 0; + vertical-align: top; + } + &.labelBottom { + bottom: 0; + vertical-align: bottom; + } + + &.labelLeft { + // left + right: auto; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + &.labelRight { + // right + left: auto; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } +} diff --git a/src/views/grid/style.ts b/src/views/grid/style.ts new file mode 100644 index 0000000..8634886 --- /dev/null +++ b/src/views/grid/style.ts @@ -0,0 +1,33 @@ +type GridStyle = 'void' | 'normal' +const gridstyle = new URL(document.URL).searchParams.get('void') != null ? 'void' : 'normal' +document.body.setAttribute('data-gridstyle', gridstyle) + +export function getClockMode(): GridStyle { + return gridstyle +} + +export function isButtonLoggedIn(element: HTMLDivElement): boolean { + return element.getAttribute('data-loggedin') == 'true' +} + +export function setButtonLoggedIn(element: HTMLDivElement, state: boolean) { + return element.setAttribute('data-loggedin', state.toString()) +} + +if (gridstyle == 'void') { + setInterval(() => { + Array.from(document.querySelectorAll('.memberButton') as NodeListOf).forEach((b, index) => { + b.style.transform = isButtonLoggedIn(b) ? `rotate(${Math.sin((Date.now() + ((index * 100000) % 234234)) / 1000) * 10}deg)` : 'rotate(0)' + }) + }, 50) +} + +export function applyRandomStyles(e: HTMLDivElement) { + e.classList.add(randomChoice('labelLeft', 'labelRight', 'labelCenter')) + e.classList.add(randomChoice('labelTop', 'labelBottom')) + e.style.fontFamily = randomChoice('gilroy', 'cocogoose', 'tcm') +} + +function randomChoice(...options: T[]): T { + return options[Math.floor(Math.random() * options.length)] +} diff --git a/src/views/util.ts b/src/views/util.ts new file mode 100644 index 0000000..d9cc432 --- /dev/null +++ b/src/views/util.ts @@ -0,0 +1,38 @@ +import { APIRoutes } from '~types' + +export function openFullscreen() { + const elem = document.documentElement + if (elem.requestFullscreen) { + elem.requestFullscreen() // @ts-expect-error nonstandard feature + } else if (elem.webkitRequestFullscreen) { + // @ts-expect-error nonstandard feature + elem.webkitRequestFullscreen() // @ts-expect-error nonstandard feature + } else if (elem.msRequestFullscreen) { + // @ts-expect-error nonstandard feature + elem.msRequestFullscreen() + } +} + +type Req = T extends { req: unknown } ? T['req'] : never +type Res = T extends { resp: unknown } ? T['resp'] : never + +export async function apiFetch( + endpoint: Route, + method: Method, + body: Req +): Promise | undefined> { + const response = await fetch('/api' + endpoint, { + method: method, + body: body == null ? undefined : JSON.stringify(body), + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + }) + if (!response.ok) { + console.error(method, endpoint, response.status, response.statusText) + return undefined + } + console.log(method, endpoint, response.status, response.statusText) + return (await response.json()) as Res +} diff --git a/static/font/basics_serif.ttf b/static/font/basics_serif.ttf new file mode 100644 index 0000000..a0a34de Binary files /dev/null and b/static/font/basics_serif.ttf differ diff --git a/static/font/cocogoose.ttf b/static/font/cocogoose.ttf new file mode 100644 index 0000000..faf500c Binary files /dev/null and b/static/font/cocogoose.ttf differ diff --git a/static/font/fishface.ttf b/static/font/fishface.ttf new file mode 100644 index 0000000..406713a Binary files /dev/null and b/static/font/fishface.ttf differ diff --git a/static/font/gilroy-bold.otf b/static/font/gilroy-bold.otf new file mode 100644 index 0000000..7413e3d Binary files /dev/null and b/static/font/gilroy-bold.otf differ diff --git a/static/font/tcm.ttf b/static/font/tcm.ttf new file mode 100644 index 0000000..a4e3547 Binary files /dev/null and b/static/font/tcm.ttf differ diff --git a/static/img/bg-grid-blurred.png b/static/img/bg-grid-blurred.png new file mode 100644 index 0000000..4e0fa92 Binary files /dev/null and b/static/img/bg-grid-blurred.png differ diff --git a/static/img/bg-grid.png b/static/img/bg-grid.png new file mode 100644 index 0000000..2666655 Binary files /dev/null and b/static/img/bg-grid.png differ diff --git a/static/img/clay.png b/static/img/clay.png new file mode 100644 index 0000000..41b473b Binary files /dev/null and b/static/img/clay.png differ diff --git a/static/img/default_member.old.jpg b/static/img/default_member.old.jpg new file mode 100644 index 0000000..7dfa29a Binary files /dev/null and b/static/img/default_member.old.jpg differ diff --git a/static/img/default_member.png b/static/img/default_member.png new file mode 100644 index 0000000..74d3c94 Binary files /dev/null and b/static/img/default_member.png differ diff --git a/static/img/default_member.webp b/static/img/default_member.webp new file mode 100644 index 0000000..6064c22 Binary files /dev/null and b/static/img/default_member.webp differ diff --git a/static/img/favicon.svg b/static/img/favicon.svg new file mode 100644 index 0000000..62a8e26 --- /dev/null +++ b/static/img/favicon.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/static/img/trimet-logo.png b/static/img/trimet-logo.png new file mode 100644 index 0000000..155e001 Binary files /dev/null and b/static/img/trimet-logo.png differ diff --git a/static/js/membercerts.js b/static/js/membercerts.js new file mode 100644 index 0000000..ae5cee8 --- /dev/null +++ b/static/js/membercerts.js @@ -0,0 +1,22 @@ +window.addEventListener('load', () => { + const elements = document.getElementsByClassName('cert-checkbox') + for (const checkbox of elements) { + const [email, cert] = checkbox.getAttribute('name').split('::') + checkbox.addEventListener('change', async () => { + const response = await fetch(document.location.href, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + email: email, + cert: cert, + value: checkbox['checked'] + }) + }) + if (!response.ok) { + alert('Failed to update cert status') + } + }) + } +}) diff --git a/static/js/moses.js b/static/js/moses.js new file mode 100644 index 0000000..98fc3a4 --- /dev/null +++ b/static/js/moses.js @@ -0,0 +1,3035 @@ +/** + * @license almond 0.3.1 Copyright (c) 2011-2014, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/jrburke/almond for details + */ + +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ + +;(function (e, t) { + typeof define == 'function' && define.amd ? define(t) : (e.moses = t()) +})(this, function () { + var e, t, n + ;(function (r) { + function v(e, t) { + return h.call(e, t) + } + function m(e, t) { + var n, + r, + i, + s, + o, + u, + a, + f, + c, + h, + p, + v = t && t.split('/'), + m = l.map, + g = (m && m['*']) || {} + if (e && e.charAt(0) === '.') + if (t) { + ;(e = e.split('/')), (o = e.length - 1), l.nodeIdCompat && d.test(e[o]) && (e[o] = e[o].replace(d, '')), (e = v.slice(0, v.length - 1).concat(e)) + for (c = 0; c < e.length; c += 1) { + p = e[c] + if (p === '.') e.splice(c, 1), (c -= 1) + else if (p === '..') { + if (c === 1 && (e[2] === '..' || e[0] === '..')) break + c > 0 && (e.splice(c - 1, 2), (c -= 2)) + } + } + e = e.join('/') + } else e.indexOf('./') === 0 && (e = e.substring(2)) + if ((v || g) && m) { + n = e.split('/') + for (c = n.length; c > 0; c -= 1) { + r = n.slice(0, c).join('/') + if (v) + for (h = v.length; h > 0; h -= 1) { + i = m[v.slice(0, h).join('/')] + if (i) { + i = i[r] + if (i) { + ;(s = i), (u = c) + break + } + } + } + if (s) break + !a && g && g[r] && ((a = g[r]), (f = c)) + } + !s && a && ((s = a), (u = f)), s && (n.splice(0, u, s), (e = n.join('/'))) + } + return e + } + function g(e, t) { + return function () { + var n = p.call(arguments, 0) + return typeof n[0] != 'string' && n.length === 1 && n.push(null), s.apply(r, n.concat([e, t])) + } + } + function y(e) { + return function (t) { + return m(t, e) + } + } + function b(e) { + return function (t) { + a[e] = t + } + } + function w(e) { + if (v(f, e)) { + var t = f[e] + delete f[e], (c[e] = !0), i.apply(r, t) + } + if (!v(a, e) && !v(c, e)) throw new Error('No ' + e) + return a[e] + } + function E(e) { + var t, + n = e ? e.indexOf('!') : -1 + return n > -1 && ((t = e.substring(0, n)), (e = e.substring(n + 1, e.length))), [t, e] + } + function S(e) { + return function () { + return (l && l.config && l.config[e]) || {} + } + } + var i, + s, + o, + u, + a = {}, + f = {}, + l = {}, + c = {}, + h = Object.prototype.hasOwnProperty, + p = [].slice, + d = /\.js$/ + ;(o = function (e, t) { + var n, + r = E(e), + i = r[0] + return (e = r[1]), i && ((i = m(i, t)), (n = w(i))), i ? (n && n.normalize ? (e = n.normalize(e, y(t))) : (e = m(e, t))) : ((e = m(e, t)), (r = E(e)), (i = r[0]), (e = r[1]), i && (n = w(i))), { f: i ? i + '!' + e : e, n: e, pr: i, p: n } + }), + (u = { + require: function (e) { + return g(e) + }, + exports: function (e) { + var t = a[e] + return typeof t != 'undefined' ? t : (a[e] = {}) + }, + module: function (e) { + return { id: e, uri: '', exports: a[e], config: S(e) } + } + }), + (i = function (e, t, n, i) { + var s, + l, + h, + p, + d, + m = [], + y = typeof n, + E + i = i || e + if (y === 'undefined' || y === 'function') { + t = !t.length && n.length ? ['require', 'exports', 'module'] : t + for (d = 0; d < t.length; d += 1) { + ;(p = o(t[d], i)), (l = p.f) + if (l === 'require') m[d] = u.require(e) + else if (l === 'exports') (m[d] = u.exports(e)), (E = !0) + else if (l === 'module') s = m[d] = u.module(e) + else if (v(a, l) || v(f, l) || v(c, l)) m[d] = w(l) + else { + if (!p.p) throw new Error(e + ' missing ' + l) + p.p.load(p.n, g(i, !0), b(l), {}), (m[d] = a[l]) + } + } + h = n ? n.apply(a[e], m) : undefined + if (e) + if (s && s.exports !== r && s.exports !== a[e]) a[e] = s.exports + else if (h !== r || !E) a[e] = h + } else e && (a[e] = n) + }), + (e = + t = + s = + function (e, t, n, a, f) { + if (typeof e == 'string') return u[e] ? u[e](t) : w(o(e, t).f) + if (!e.splice) { + ;(l = e), l.deps && s(l.deps, l.callback) + if (!t) return + t.splice ? ((e = t), (t = n), (n = null)) : (e = r) + } + return ( + (t = t || function () {}), + typeof n == 'function' && ((n = a), (a = f)), + a + ? i(r, e, t, n) + : setTimeout(function () { + i(r, e, t, n) + }, 4), + s + ) + }), + (s.config = function (e) { + return s(e) + }), + (e._defined = a), + (n = function (e, t, n) { + if (typeof e != 'string') throw new Error('See almond README: incorrect module build, no module name') + t.splice || ((n = t), (t = [])), !v(a, e) && !v(f, e) && (f[e] = [e, t, n]) + }), + (n.amd = { jQuery: !0 }) + })(), + n('../node_modules/almond/almond', function () {}) + var r = r || {} + return ( + !(function () { + function e() { + ;(T = !0), + clearTimeout(N), + (N = setTimeout(function () { + T = !1 + }, 700)) + } + function t(e, t) { + for (; e; ) { + if (e.contains(t)) return e + e = e.parentNode + } + return null + } + function n(e, n, r) { + for (var i = t(e, n), s = e, o = []; s && s !== i; ) v(s, 'pointerenter') && o.push(s), (s = s.parentNode) + for (; o.length > 0; ) r(o.pop()) + } + function r(e, n, r) { + for (var i = t(e, n), s = e; s && s !== i; ) v(s, 'pointerleave') && r(s), (s = s.parentNode) + } + function i(e, t) { + ;['pointerdown', 'pointermove', 'pointerup', 'pointerover', 'pointerout'].forEach(function (n) { + window.addEventListener(e(n), function (e) { + !T && m(e.target, n) && t(e, n, !0) + }) + }), + void 0 === window['on' + e('pointerenter').toLowerCase()] && + window.addEventListener(e('pointerover'), function (e) { + if (!T) { + var r = m(e.target, 'pointerenter') + r && + r !== window && + (r.contains(e.relatedTarget) || + n(r, e.relatedTarget, function (n) { + t(e, 'pointerenter', !1, n, e.relatedTarget) + })) + } + }), + void 0 === window['on' + e('pointerleave').toLowerCase()] && + window.addEventListener(e('pointerout'), function (e) { + if (!T) { + var n = m(e.target, 'pointerleave') + n && + n !== window && + (n.contains(e.relatedTarget) || + r(n, e.relatedTarget, function (n) { + t(e, 'pointerleave', !1, n, e.relatedTarget) + })) + } + }) + } + if (!window.PointerEvent) { + Array.prototype.indexOf || + (Array.prototype.indexOf = function (e) { + var t = Object(this), + n = t.length >>> 0 + if (0 === n) return -1 + var r = 0 + if ((arguments.length > 0 && ((r = Number(arguments[1])), r !== r ? (r = 0) : 0 !== r && 1 / 0 !== r && r !== -1 / 0 && (r = (r > 0 || -1) * Math.floor(Math.abs(r)))), r >= n)) return -1 + for (var i = r >= 0 ? r : Math.max(n - Math.abs(r), 0); n > i; i++) if (i in t && t[i] === e) return i + return -1 + }), + Array.prototype.forEach || + (Array.prototype.forEach = function (e, t) { + if (!(this && e instanceof Function)) throw new TypeError() + for (var n = 0; n < this.length; n++) e.call(t, this[n], n, this) + }), + String.prototype.trim || + (String.prototype.trim = function () { + return this.replace(/^\s+|\s+$/, '') + }) + var s = ['pointerdown', 'pointerup', 'pointermove', 'pointerover', 'pointerout', 'pointercancel', 'pointerenter', 'pointerleave'], + o = ['PointerDown', 'PointerUp', 'PointerMove', 'PointerOver', 'PointerOut', 'PointerCancel', 'PointerEnter', 'PointerLeave'], + u = 'touch', + a = 'pen', + f = 'mouse', + l = {}, + c = function (e) { + for (; e && !e.handjs_forcePreventDefault; ) e = e.parentNode + return !!e || window.handjs_forcePreventDefault + }, + h = function (e, t, n, r, i) { + var s + if ( + (document.createEvent ? ((s = document.createEvent('MouseEvents')), s.initMouseEvent(t, n, !0, window, 1, e.screenX, e.screenY, e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, i || e.relatedTarget)) : ((s = document.createEventObject()), (s.screenX = e.screenX), (s.screenY = e.screenY), (s.clientX = e.clientX), (s.clientY = e.clientY), (s.ctrlKey = e.ctrlKey), (s.altKey = e.altKey), (s.shiftKey = e.shiftKey), (s.metaKey = e.metaKey), (s.button = e.button), (s.relatedTarget = i || e.relatedTarget)), + void 0 === s.offsetX && + (void 0 !== e.offsetX + ? (Object && void 0 !== Object.defineProperty && (Object.defineProperty(s, 'offsetX', { writable: !0 }), Object.defineProperty(s, 'offsetY', { writable: !0 })), (s.offsetX = e.offsetX), (s.offsetY = e.offsetY)) + : Object && void 0 !== Object.defineProperty + ? (Object.defineProperty(s, 'offsetX', { + get: function () { + return this.currentTarget && this.currentTarget.offsetLeft ? e.clientX - this.currentTarget.offsetLeft : e.clientX + } + }), + Object.defineProperty(s, 'offsetY', { + get: function () { + return this.currentTarget && this.currentTarget.offsetTop ? e.clientY - this.currentTarget.offsetTop : e.clientY + } + })) + : void 0 !== e.layerX && ((s.offsetX = e.layerX - e.currentTarget.offsetLeft), (s.offsetY = e.layerY - e.currentTarget.offsetTop))), + (s.isPrimary = void 0 !== e.isPrimary ? e.isPrimary : !0), + e.pressure) + ) + s.pressure = e.pressure + else { + var o = 0 + void 0 !== e.which ? (o = e.which) : void 0 !== e.button && (o = e.button), (s.pressure = 0 === o ? 0 : 0.5) + } + if ( + ((s.rotation = e.rotation ? e.rotation : 0), + (s.hwTimestamp = e.hwTimestamp ? e.hwTimestamp : 0), + (s.tiltX = e.tiltX ? e.tiltX : 0), + (s.tiltY = e.tiltY ? e.tiltY : 0), + (s.height = e.height ? e.height : 0), + (s.width = e.width ? e.width : 0), + (s.preventDefault = function () { + void 0 !== e.preventDefault && e.preventDefault() + }), + void 0 !== s.stopPropagation) + ) { + var l = s.stopPropagation + s.stopPropagation = function () { + void 0 !== e.stopPropagation && e.stopPropagation(), l.call(this) + } + } + switch (((s.pointerId = e.pointerId), (s.pointerType = e.pointerType), s.pointerType)) { + case 2: + s.pointerType = u + break + case 3: + s.pointerType = a + break + case 4: + s.pointerType = f + } + r ? r.dispatchEvent(s) : e.target ? e.target.dispatchEvent(s) : e.srcElement.fireEvent('on' + y(t), s) + }, + p = function (e, t, n, r, i) { + ;(e.pointerId = 1), (e.pointerType = f), h(e, t, n, r, i) + }, + d = function (e, t, n, r, i, s) { + var o = t.identifier + 2 + ;(t.pointerId = o), + (t.pointerType = u), + (t.currentTarget = n), + void 0 !== r.preventDefault && + (t.preventDefault = function () { + r.preventDefault() + }), + h(t, e, i, n, s) + }, + v = function (e, t) { + return e.__handjsGlobalRegisteredEvents && e.__handjsGlobalRegisteredEvents[t] + }, + m = function (e, t) { + for (; e && !v(e, t); ) e = e.parentNode + return e ? e : v(window, t) ? window : void 0 + }, + g = function (e, t, n, r, i, s) { + m(n, e) && d(e, t, n, r, i, s) + }, + y = function (e) { + return e.toLowerCase().replace('pointer', 'mouse') + }, + b = function (e, t) { + var n = s.indexOf(t), + r = e + o[n] + return r + }, + w = function (e, t, n, r) { + if ((void 0 === e.__handjsRegisteredEvents && (e.__handjsRegisteredEvents = []), r)) { + if (void 0 !== e.__handjsRegisteredEvents[t]) return e.__handjsRegisteredEvents[t]++, void 0 + ;(e.__handjsRegisteredEvents[t] = 1), e.addEventListener(t, n, !1) + } else { + if (-1 !== e.__handjsRegisteredEvents.indexOf(t) && (e.__handjsRegisteredEvents[t]--, 0 !== e.__handjsRegisteredEvents[t])) return + e.removeEventListener(t, n), (e.__handjsRegisteredEvents[t] = 0) + } + }, + E = function (e, t, n) { + if ((e.__handjsGlobalRegisteredEvents || (e.__handjsGlobalRegisteredEvents = []), n)) { + if (void 0 !== e.__handjsGlobalRegisteredEvents[t]) return e.__handjsGlobalRegisteredEvents[t]++, void 0 + e.__handjsGlobalRegisteredEvents[t] = 1 + } else void 0 !== e.__handjsGlobalRegisteredEvents[t] && (e.__handjsGlobalRegisteredEvents[t]--, e.__handjsGlobalRegisteredEvents[t] < 0 && (e.__handjsGlobalRegisteredEvents[t] = 0)) + var r, i + switch ( + (window.MSPointerEvent + ? ((r = function (e) { + return b('MS', e) + }), + (i = h)) + : ((r = y), (i = p)), + t) + ) { + case 'pointerenter': + case 'pointerleave': + var s = r(t) + void 0 !== e['on' + s.toLowerCase()] && + w( + e, + s, + function (e) { + i(e, t) + }, + n + ) + } + }, + S = function (e) { + var t = e.prototype ? e.prototype.addEventListener : e.addEventListener, + n = function (e, n, r) { + ;-1 !== s.indexOf(e) && E(this, e, !0), void 0 === t ? this.attachEvent('on' + y(e), n) : t.call(this, e, n, r) + } + e.prototype ? (e.prototype.addEventListener = n) : (e.addEventListener = n) + }, + x = function (e) { + var t = e.prototype ? e.prototype.removeEventListener : e.removeEventListener, + n = function (e, n, r) { + ;-1 !== s.indexOf(e) && E(this, e, !1), void 0 === t ? this.detachEvent(y(e), n) : t.call(this, e, n, r) + } + e.prototype ? (e.prototype.removeEventListener = n) : (e.removeEventListener = n) + } + S(window), S(window.HTMLElement || window.Element), S(document), navigator.isCocoonJS || (S(HTMLBodyElement), S(HTMLDivElement), S(HTMLImageElement), S(HTMLUListElement), S(HTMLAnchorElement), S(HTMLLIElement), S(HTMLTableElement), window.HTMLSpanElement && S(HTMLSpanElement)), window.HTMLCanvasElement && S(HTMLCanvasElement), !navigator.isCocoonJS && window.SVGElement && S(SVGElement), x(window), x(window.HTMLElement || window.Element), x(document), navigator.isCocoonJS || (x(HTMLBodyElement), x(HTMLDivElement), x(HTMLImageElement), x(HTMLUListElement), x(HTMLAnchorElement), x(HTMLLIElement), x(HTMLTableElement), window.HTMLSpanElement && x(HTMLSpanElement)), window.HTMLCanvasElement && x(HTMLCanvasElement), !navigator.isCocoonJS && window.SVGElement && x(SVGElement) + var T = !1, + N = -1 + !(function () { + window.MSPointerEvent + ? i(function (e) { + return b('MS', e) + }, h) + : (i(y, p), + void 0 !== window.ontouchstart && + (window.addEventListener('touchstart', function (t) { + for (var r = 0; r < t.changedTouches.length; ++r) { + var i = t.changedTouches[r] + ;(l[i.identifier] = i.target), + g('pointerover', i, i.target, t, !0), + n(i.target, null, function (e) { + d('pointerenter', i, e, t, !1) + }), + g('pointerdown', i, i.target, t, !0) + } + e() + }), + window.addEventListener('touchend', function (t) { + for (var n = 0; n < t.changedTouches.length; ++n) { + var i = t.changedTouches[n], + s = l[i.identifier] + g('pointerup', i, s, t, !0), + g('pointerout', i, s, t, !0), + r(s, null, function (e) { + d('pointerleave', i, e, t, !1) + }) + } + e() + }), + window.addEventListener('touchmove', function (t) { + for (var i = 0; i < t.changedTouches.length; ++i) { + var s = t.changedTouches[i], + o = document.elementFromPoint(s.clientX, s.clientY), + u = l[s.identifier] + if ((u && c(u) === !0 && t.preventDefault(), g('pointermove', s, u, t, !0), !navigator.isCocoonJS)) { + var o = document.elementFromPoint(s.clientX, s.clientY) + if (u === o) continue + u && + (g('pointerout', s, u, t, !0, o), + u.contains(o) || + r(u, o, function (e) { + d('pointerleave', s, e, t, !1, o) + })), + o && + (g('pointerover', s, o, t, !0, u), + o.contains(u) || + n(o, u, function (e) { + d('pointerenter', s, e, t, !1, u) + })), + (l[s.identifier] = o) + } + } + e() + }), + window.addEventListener('touchcancel', function (e) { + for (var t = 0; t < e.changedTouches.length; ++t) { + var n = e.changedTouches[t] + g('pointercancel', n, l[n.identifier], e, !0) + } + }))) + })(), + void 0 === navigator.pointerEnabled && ((navigator.pointerEnabled = !0), navigator.msPointerEnabled && (navigator.maxTouchPoints = navigator.msMaxTouchPoints)) + } + })(), + (function () { + window.PointerEvent || + (document.styleSheets && + document.addEventListener && + document.addEventListener( + 'DOMContentLoaded', + function () { + if (void 0 === document.body.style.touchAction) { + var e = new RegExp('.+?{.*?}', 'm'), + t = new RegExp('.+?{', 'm'), + n = function (n) { + var r = e.exec(n) + if (r) { + var i = r[0] + n = n.replace(i, '').trim() + var s = t.exec(i)[0].replace('{', '').trim() + if (-1 !== i.replace(/\s/g, '').indexOf('touch-action:none')) + for (var o = document.querySelectorAll(s), u = 0; u < o.length; u++) { + var a = o[u] + void 0 !== a.style.msTouchAction ? (a.style.msTouchAction = 'none') : (a.handjs_forcePreventDefault = !0) + } + return n + } + }, + r = function (e) { + if (window.setImmediate) e && setImmediate(r, n(e)) + else for (; e; ) e = n(e) + } + try { + for (var i = 0; i < document.styleSheets.length; i++) { + var s = document.styleSheets[i] + if (null != s.href) { + var o = new XMLHttpRequest() + o.open('get', s.href), o.send() + var u = o.responseText.replace(/(\n|\r)/g, '') + r(u) + } + } + } catch (a) {} + for (var f = document.getElementsByTagName('style'), i = 0; i < f.length; i++) { + var l = f[i], + c = l.innerHTML.replace(/(\n|\r)/g, '').trim() + r(c) + } + } + }, + !1 + )) + })(), + n('../node_modules/handjs/hand.min', function () {}), + (function (e) { + n('p', [], function () { + return function () { + return ( + (function n(e, r, i) { + function s(u, a) { + if (!r[u]) { + if (!e[u]) { + var f = typeof t == 'function' && t + if (!a && f) return f(u, !0) + if (o) return o(u, !0) + throw new Error("Cannot find module '" + u + "'") + } + var l = (r[u] = { exports: {} }) + e[u][0].call( + l.exports, + function (t) { + var n = e[u][1][t] + return s(n ? n : t) + }, + l, + l.exports, + n, + e, + r, + i + ) + } + return r[u].exports + } + var o = typeof t == 'function' && t + for (var u = 0; u < i.length; u++) s(i[u]) + return s + })( + { + 1: [ + function (e, t, n) { + ;(function (n, r, i, s, o, u, a, f, l) { + var c = e('./js/protoplast'), + h = e('./js/aop'), + p = e('./js/dispatcher'), + d = e('./js/di'), + v = e('./js/component'), + m = e('./js/utils'), + g = e('./js/constructors'), + y = { extend: c.extend.bind(c), create: c.create.bind(c), Dispatcher: p, Aop: h, Context: d, Component: v, constructors: g, utils: m } + ;(r.Protoplast = y), (t.exports = y) + }).call(this, e('IrXUsu'), typeof self != 'undefined' ? self : typeof window != 'undefined' ? window : {}, e('buffer').Buffer, arguments[3], arguments[4], arguments[5], arguments[6], '/fake_e1eb630e.js', '/') + }, + { './js/aop': 2, './js/component': 3, './js/constructors': 4, './js/di': 5, './js/dispatcher': 6, './js/protoplast': 7, './js/utils': 8, 'IrXUsu': 12, 'buffer': 9 } + ], + 2: [ + function (e, t, n) { + ;(function (e, n, r, i, s, o, u, a, f) { + function l(e, t, n) { + var r = e[t] + if (!e[t]) throw Error("Can't create aspect for method " + t + '. Method does not exist.') + e[t] = function () { + n.before && n.before.apply(this, arguments) + var e = r.apply(this, arguments) + return n.after && (e = n.after.call(this, e, arguments)), e + } + } + var c = function (e) { + return { + aop: function (t, n) { + return ( + t instanceof Array || (t = [t]), + t.forEach(function (t) { + l(e, t, n) + }, this), + this + ) + } + } + } + t.exports = c + }).call(this, e('IrXUsu'), typeof self != 'undefined' ? self : typeof window != 'undefined' ? window : {}, e('buffer').Buffer, arguments[3], arguments[4], arguments[5], arguments[6], '/js/aop.js', '/js') + }, + { IrXUsu: 12, buffer: 9 } + ], + 3: [ + function (e, t, n) { + ;(function (n, r, i, s, o, u, a, f, l) { + var c = e('./protoplast'), + h = c.extend({ + $create: function () { + ;(this._children = []), (this.root = document.createElement(this.tag || 'div')) + }, + __fastinject__: { + get: function () { + return this.___fastinject___ + }, + set: function (e) { + ;(this.___fastinject___ = e), + this._children.forEach(function (e) { + this.__fastinject__(e), (e.__fastinject__ = this.__fastinject__) + }, this) + } + }, + init: { inject_init: !0, value: function () {} }, + destroy: function () { + this._children.concat().forEach(function (e) { + this.remove(e) + }, this) + }, + add: function (e) { + if (!e) throw new Error('Child component cannot be null') + if (!e.root) throw new Error('Child component should have root property') + this._children.push(e), this.__fastinject__ && this.__fastinject__(e), this.root.appendChild(e.root) + }, + remove: function (e) { + var t = this._children.indexOf(e) + t !== -1 && (this._children.splice(t, 1), this.root.removeChild(e.root), e.destroy()) + } + }) + ;(h.Root = function (e, t) { + var n = h.create() + return (n.root = e), t && t.register(n), n + }), + (t.exports = h) + }).call(this, e('IrXUsu'), typeof self != 'undefined' ? self : typeof window != 'undefined' ? window : {}, e('buffer').Buffer, arguments[3], arguments[4], arguments[5], arguments[6], '/js/component.js', '/js') + }, + { './protoplast': 7, 'IrXUsu': 12, 'buffer': 9 } + ], + 4: [ + function (e, t, n) { + ;(function (n, r, i, s, o, u, a, f, l) { + var c = e('./utils'), + h = { + uniqueId: function () { + this.$id = c.uniqueId(this.$meta.$prefix) + }, + autobind: function () { + for (var e in this) typeof this[e] == 'function' && (this[e] = this[e].bind(this)) + } + } + t.exports = h + }).call(this, e('IrXUsu'), typeof self != 'undefined' ? self : typeof window != 'undefined' ? window : {}, e('buffer').Buffer, arguments[3], arguments[4], arguments[5], arguments[6], '/js/constructors.js', '/js') + }, + { './utils': 8, 'IrXUsu': 12, 'buffer': 9 } + ], + 5: [ + function (e, t, n) { + ;(function (n, r, i, s, o, u, a, f, l) { + var c = e('./protoplast'), + h = e('./dispatcher'), + p = c.extend({ + $create: function () { + var e = this + ;(this._objects = { + pub: function (t, n) { + e._dispatcher.dispatch(t, n) + }, + sub: function (t) { + var n = this + return { + add: function (r) { + e._dispatcher.on(t, r, n) + }, + remove: function (r) { + e._dispatcher.off(t, r, n) + } + } + } + }), + (this._unknows = []), + (this._dispatcher = h.create()) + }, + _objects: null, + register: function (e, t) { + arguments.length == 1 ? ((t = e), this._unknows.push(t)) : (this._objects[e] = t), + (t.__fastinject__ = function (e) { + this.register(e), this.process(e) + }.bind(this)), + t.$meta && t.$meta.inject && this.inject(t, t.$meta.inject) + }, + process: function (e) { + e.$meta && + e.$meta.inject_init && + Object.keys(e.$meta.inject_init).forEach(function (t) { + e[t]() + }, this), + e.$meta && + e.$meta.sub && + Object.keys(e.$meta.sub).forEach(function (t) { + this._objects.sub.call(e, e.$meta.sub[t]).add(e[t]) + }, this) + }, + inject: function (e, t) { + var n = this, + r + for (var i in t) + t.hasOwnProperty(i) && + ((r = t[i]), + (function (t) { + Object.defineProperty(e, i, { + get: function () { + return n._objects[t] + } + }) + })(r)) + }, + build: function () { + Object.keys(this._objects).forEach(function (e) { + var t = this._objects[e] + this.process(t) + }, this), + this._unknows.forEach(function (e) { + this.process(e) + }, this) + } + }) + t.exports = p + }).call(this, e('IrXUsu'), typeof self != 'undefined' ? self : typeof window != 'undefined' ? window : {}, e('buffer').Buffer, arguments[3], arguments[4], arguments[5], arguments[6], '/js/di.js', '/js') + }, + { './dispatcher': 6, './protoplast': 7, 'IrXUsu': 12, 'buffer': 9 } + ], + 6: [ + function (e, t, n) { + ;(function (n, r, i, s, o, u, a, f, l) { + var c = e('./protoplast'), + h = c.extend({ + dispatch: function (e, t) { + ;(this._topics = this._topics || {}), + (this._topics[e] || []).forEach(function (e) { + e.handler.call(e.context, t) + }) + }, + on: function (e, t, n) { + if (!t) throw new Error('Handler is required for event ' + e) + ;(this._topics = this._topics || {}), (this._topics[e] = this._topics[e] || []), this._topics[e].push({ handler: t, context: n }) + }, + off: function (e, t, n) { + ;(this._topics = this._topics || {}), + (this._topics[e] = this._topics[e].filter(function (e) { + return t ? e.handler !== t : e.context !== n + })) + } + }) + t.exports = h + }).call(this, e('IrXUsu'), typeof self != 'undefined' ? self : typeof window != 'undefined' ? window : {}, e('buffer').Buffer, arguments[3], arguments[4], arguments[5], arguments[6], '/js/dispatcher.js', '/js') + }, + { './protoplast': 7, 'IrXUsu': 12, 'buffer': 9 } + ], + 7: [ + function (e, t, n) { + ;(function (n, r, i, s, o, u, a, f, l) { + var c = e('./utils'), + h = { + $meta: {}, + create: function () { + return c.createObject(this, arguments) + } + } + ;(h.extend = function (e, t) { + var n = Object.create(this), + r, + i, + s + e instanceof Array || ((t = e), (e = [])), (t = t || {}), (e = e || []), (r = t.$meta || {}), delete t.$meta, t.$create !== undefined && ((r.$constructors = r.$constructors || []), r.$constructors.push(t.$create), delete t.$create), (n = c.mixin(n, e)) + for (var o in t) { + s = !1 + if (Object.prototype.toString.call(t[o]) !== '[object Object]') (s = !0), (i = { value: t[o], writable: !0, enumerable: !0 }) + else { + i = t[o] + for (var u in i) ['value', 'get', 'set', 'writable', 'enumerable'].indexOf(u) === -1 ? ((r[u] = r[u] || {}), (r[u][o] = i[u]), delete i[u]) : (s = !0) + } + s && Object.defineProperty(n, o, i) + } + return (n.$meta = c.merge(r, this.$meta)), (n.$super = this), c.processPrototype(n), n + }), + (t.exports = h) + }).call(this, e('IrXUsu'), typeof self != 'undefined' ? self : typeof window != 'undefined' ? window : {}, e('buffer').Buffer, arguments[3], arguments[4], arguments[5], arguments[6], '/js/protoplast.js', '/js') + }, + { './utils': 8, 'IrXUsu': 12, 'buffer': 9 } + ], + 8: [ + function (e, t, n) { + ;(function (e, n, r, i, s, o, u, a, f) { + function c(e) { + var t = ++l + return (e || '') + t + } + function h(e, t) { + var n = Object.create(e) + return ( + n.$meta.$constructors && + n.$meta.$constructors.forEach(function (e) { + e.apply(n, t) + }), + n + ) + } + function p(e) { + e.$meta.$processors && + e.$meta.$processors.forEach(function (t) { + t(e) + }) + } + function d(e, t) { + for (var n in t) t.hasOwnProperty(n) && (t[n] instanceof Array ? (e[n] = t[n].concat(e[n] || [])) : ['number', 'boolean', 'string'].indexOf(typeof t[n]) !== -1 ? e.hasOwnProperty(n) || (e[n] = t[n]) : ((e[n] = e[n] || {}), d(e[n], t[n]))) + return e + } + function v(e, t) { + for (var n in t) n.substr(0, 2) !== '__' && (e[n] = t[n]) + return e + } + function m(e, t) { + return ( + t.forEach(function (t) { + v(e, t) + }), + e + ) + } + var l = 0 + t.exports = { createObject: h, processPrototype: p, merge: d, mixin: m, uniqueId: c } + }).call(this, e('IrXUsu'), typeof self != 'undefined' ? self : typeof window != 'undefined' ? window : {}, e('buffer').Buffer, arguments[3], arguments[4], arguments[5], arguments[6], '/js/utils.js', '/js') + }, + { IrXUsu: 12, buffer: 9 } + ], + 9: [ + function (e, t, n) { + ;(function (t, r, i, s, o, u, a, f, l) { + function i(e, t, n) { + if (this instanceof i) { + var r = typeof e + if (t === 'base64' && r === 'string') { + e = j(e) + while (e.length % 4 !== 0) e += '=' + } + var s + if (r === 'number') s = q(e) + else if (r === 'string') s = i.byteLength(e, t) + else { + if (r !== 'object') throw new Error('First argument needs to be a number, array or string.') + s = q(e.length) + } + var o + i._useTypedArrays ? (o = i._augment(new Uint8Array(s))) : ((o = this), (o.length = s), (o._isBuffer = !0)) + var u + if (i._useTypedArrays && typeof e.byteLength == 'number') o._set(e) + else if (U(e)) for (u = 0; u < s; u++) i.isBuffer(e) ? (o[u] = e.readUInt8(u)) : (o[u] = e[u]) + else if (r === 'string') o.write(e, 0, t) + else if (r === 'number' && !i._useTypedArrays && !n) for (u = 0; u < s; u++) o[u] = 0 + return o + } + return new i(e, t, n) + } + function p(e, t, n, r) { + n = Number(n) || 0 + var s = e.length - n + r ? ((r = Number(r)), r > s && (r = s)) : (r = s) + var o = t.length + Z(o % 2 === 0, 'Invalid hex string'), r > o / 2 && (r = o / 2) + for (var u = 0; u < r; u++) { + var a = parseInt(t.substr(u * 2, 2), 16) + Z(!isNaN(a), 'Invalid hex string'), (e[n + u] = a) + } + return (i._charsWritten = u * 2), u + } + function d(e, t, n, r) { + var s = (i._charsWritten = J(W(t), e, n, r)) + return s + } + function v(e, t, n, r) { + var s = (i._charsWritten = J(X(t), e, n, r)) + return s + } + function m(e, t, n, r) { + return v(e, t, n, r) + } + function g(e, t, n, r) { + var s = (i._charsWritten = J($(t), e, n, r)) + return s + } + function y(e, t, n, r) { + var s = (i._charsWritten = J(V(t), e, n, r)) + return s + } + function b(e, t, n) { + return t === 0 && n === e.length ? c.fromByteArray(e) : c.fromByteArray(e.slice(t, n)) + } + function w(e, t, n) { + var r = '', + i = '' + n = Math.min(e.length, n) + for (var s = t; s < n; s++) e[s] <= 127 ? ((r += K(i) + String.fromCharCode(e[s])), (i = '')) : (i += '%' + e[s].toString(16)) + return r + K(i) + } + function E(e, t, n) { + var r = '' + n = Math.min(e.length, n) + for (var i = t; i < n; i++) r += String.fromCharCode(e[i]) + return r + } + function S(e, t, n) { + return E(e, t, n) + } + function x(e, t, n) { + var r = e.length + if (!t || t < 0) t = 0 + if (!n || n < 0 || n > r) n = r + var i = '' + for (var s = t; s < n; s++) i += z(e[s]) + return i + } + function T(e, t, n) { + var r = e.slice(t, n), + i = '' + for (var s = 0; s < r.length; s += 2) i += String.fromCharCode(r[s] + r[s + 1] * 256) + return i + } + function N(e, t, n, r) { + r || (Z(typeof n == 'boolean', 'missing or invalid endian'), Z(t !== undefined && t !== null, 'missing offset'), Z(t + 1 < e.length, 'Trying to read beyond buffer length')) + var i = e.length + if (t >= i) return + var s + return n ? ((s = e[t]), t + 1 < i && (s |= e[t + 1] << 8)) : ((s = e[t] << 8), t + 1 < i && (s |= e[t + 1])), s + } + function C(e, t, n, r) { + r || (Z(typeof n == 'boolean', 'missing or invalid endian'), Z(t !== undefined && t !== null, 'missing offset'), Z(t + 3 < e.length, 'Trying to read beyond buffer length')) + var i = e.length + if (t >= i) return + var s + return n ? (t + 2 < i && (s = e[t + 2] << 16), t + 1 < i && (s |= e[t + 1] << 8), (s |= e[t]), t + 3 < i && (s += (e[t + 3] << 24) >>> 0)) : (t + 1 < i && (s = e[t + 1] << 16), t + 2 < i && (s |= e[t + 2] << 8), t + 3 < i && (s |= e[t + 3]), (s += (e[t] << 24) >>> 0)), s + } + function k(e, t, n, r) { + r || (Z(typeof n == 'boolean', 'missing or invalid endian'), Z(t !== undefined && t !== null, 'missing offset'), Z(t + 1 < e.length, 'Trying to read beyond buffer length')) + var i = e.length + if (t >= i) return + var s = N(e, t, n, !0), + o = s & 32768 + return o ? (65535 - s + 1) * -1 : s + } + function L(e, t, n, r) { + r || (Z(typeof n == 'boolean', 'missing or invalid endian'), Z(t !== undefined && t !== null, 'missing offset'), Z(t + 3 < e.length, 'Trying to read beyond buffer length')) + var i = e.length + if (t >= i) return + var s = C(e, t, n, !0), + o = s & 2147483648 + return o ? (4294967295 - s + 1) * -1 : s + } + function A(e, t, n, r) { + return r || (Z(typeof n == 'boolean', 'missing or invalid endian'), Z(t + 3 < e.length, 'Trying to read beyond buffer length')), h.read(e, t, n, 23, 4) + } + function O(e, t, n, r) { + return r || (Z(typeof n == 'boolean', 'missing or invalid endian'), Z(t + 7 < e.length, 'Trying to read beyond buffer length')), h.read(e, t, n, 52, 8) + } + function M(e, t, n, r, i) { + i || (Z(t !== undefined && t !== null, 'missing value'), Z(typeof r == 'boolean', 'missing or invalid endian'), Z(n !== undefined && n !== null, 'missing offset'), Z(n + 1 < e.length, 'trying to write beyond buffer length'), Q(t, 65535)) + var s = e.length + if (n >= s) return + for (var o = 0, u = Math.min(s - n, 2); o < u; o++) e[n + o] = (t & (255 << (8 * (r ? o : 1 - o)))) >>> ((r ? o : 1 - o) * 8) + } + function _(e, t, n, r, i) { + i || (Z(t !== undefined && t !== null, 'missing value'), Z(typeof r == 'boolean', 'missing or invalid endian'), Z(n !== undefined && n !== null, 'missing offset'), Z(n + 3 < e.length, 'trying to write beyond buffer length'), Q(t, 4294967295)) + var s = e.length + if (n >= s) return + for (var o = 0, u = Math.min(s - n, 4); o < u; o++) e[n + o] = (t >>> ((r ? o : 3 - o) * 8)) & 255 + } + function D(e, t, n, r, i) { + i || (Z(t !== undefined && t !== null, 'missing value'), Z(typeof r == 'boolean', 'missing or invalid endian'), Z(n !== undefined && n !== null, 'missing offset'), Z(n + 1 < e.length, 'Trying to write beyond buffer length'), G(t, 32767, -32768)) + var s = e.length + if (n >= s) return + t >= 0 ? M(e, t, n, r, i) : M(e, 65535 + t + 1, n, r, i) + } + function P(e, t, n, r, i) { + i || (Z(t !== undefined && t !== null, 'missing value'), Z(typeof r == 'boolean', 'missing or invalid endian'), Z(n !== undefined && n !== null, 'missing offset'), Z(n + 3 < e.length, 'Trying to write beyond buffer length'), G(t, 2147483647, -2147483648)) + var s = e.length + if (n >= s) return + t >= 0 ? _(e, t, n, r, i) : _(e, 4294967295 + t + 1, n, r, i) + } + function H(e, t, n, r, i) { + i || (Z(t !== undefined && t !== null, 'missing value'), Z(typeof r == 'boolean', 'missing or invalid endian'), Z(n !== undefined && n !== null, 'missing offset'), Z(n + 3 < e.length, 'Trying to write beyond buffer length'), Y(t, 3.4028234663852886e38, -3.4028234663852886e38)) + var s = e.length + if (n >= s) return + h.write(e, t, n, r, 23, 4) + } + function B(e, t, n, r, i) { + i || (Z(t !== undefined && t !== null, 'missing value'), Z(typeof r == 'boolean', 'missing or invalid endian'), Z(n !== undefined && n !== null, 'missing offset'), Z(n + 7 < e.length, 'Trying to write beyond buffer length'), Y(t, 1.7976931348623157e308, -1.7976931348623157e308)) + var s = e.length + if (n >= s) return + h.write(e, t, n, r, 52, 8) + } + function j(e) { + return e.trim ? e.trim() : e.replace(/^\s+|\s+$/g, '') + } + function I(e, t, n) { + return typeof e != 'number' ? n : ((e = ~~e), e >= t ? t : e >= 0 ? e : ((e += t), e >= 0 ? e : 0)) + } + function q(e) { + return (e = ~~Math.ceil(+e)), e < 0 ? 0 : e + } + function R(e) { + return ( + Array.isArray || + function (e) { + return Object.prototype.toString.call(e) === '[object Array]' + } + )(e) + } + function U(e) { + return R(e) || i.isBuffer(e) || (e && typeof e == 'object' && typeof e.length == 'number') + } + function z(e) { + return e < 16 ? '0' + e.toString(16) : e.toString(16) + } + function W(e) { + var t = [] + for (var n = 0; n < e.length; n++) { + var r = e.charCodeAt(n) + if (r <= 127) t.push(e.charCodeAt(n)) + else { + var i = n + r >= 55296 && r <= 57343 && n++ + var s = encodeURIComponent(e.slice(i, n + 1)) + .substr(1) + .split('%') + for (var o = 0; o < s.length; o++) t.push(parseInt(s[o], 16)) + } + } + return t + } + function X(e) { + var t = [] + for (var n = 0; n < e.length; n++) t.push(e.charCodeAt(n) & 255) + return t + } + function V(e) { + var t, + n, + r, + i = [] + for (var s = 0; s < e.length; s++) (t = e.charCodeAt(s)), (n = t >> 8), (r = t % 256), i.push(r), i.push(n) + return i + } + function $(e) { + return c.toByteArray(e) + } + function J(e, t, n, r) { + var i + for (var s = 0; s < r; s++) { + if (s + n >= t.length || s >= e.length) break + t[s + n] = e[s] + } + return s + } + function K(e) { + try { + return decodeURIComponent(e) + } catch (t) { + return String.fromCharCode(65533) + } + } + function Q(e, t) { + Z(typeof e == 'number', 'cannot write a non-number as a number'), Z(e >= 0, 'specified a negative value for writing an unsigned value'), Z(e <= t, 'value is larger than maximum value for type'), Z(Math.floor(e) === e, 'value has a fractional component') + } + function G(e, t, n) { + Z(typeof e == 'number', 'cannot write a non-number as a number'), Z(e <= t, 'value larger than maximum allowed value'), Z(e >= n, 'value smaller than minimum allowed value'), Z(Math.floor(e) === e, 'value has a fractional component') + } + function Y(e, t, n) { + Z(typeof e == 'number', 'cannot write a non-number as a number'), Z(e <= t, 'value larger than maximum allowed value'), Z(e >= n, 'value smaller than minimum allowed value') + } + function Z(e, t) { + if (!e) throw new Error(t || 'Failed assertion') + } + var c = e('base64-js'), + h = e('ieee754') + ;(n.Buffer = i), + (n.SlowBuffer = i), + (n.INSPECT_MAX_BYTES = 50), + (i.poolSize = 8192), + (i._useTypedArrays = (function () { + try { + var e = new ArrayBuffer(0), + t = new Uint8Array(e) + return ( + (t.foo = function () { + return 42 + }), + 42 === t.foo() && typeof t.subarray == 'function' + ) + } catch (n) { + return !1 + } + })()), + (i.isEncoding = function (e) { + switch (String(e).toLowerCase()) { + case 'hex': + case 'utf8': + case 'utf-8': + case 'ascii': + case 'binary': + case 'base64': + case 'raw': + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + return !0 + default: + return !1 + } + }), + (i.isBuffer = function (e) { + return e !== null && e !== undefined && !!e._isBuffer + }), + (i.byteLength = function (e, t) { + var n + e += '' + switch (t || 'utf8') { + case 'hex': + n = e.length / 2 + break + case 'utf8': + case 'utf-8': + n = W(e).length + break + case 'ascii': + case 'binary': + case 'raw': + n = e.length + break + case 'base64': + n = $(e).length + break + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + n = e.length * 2 + break + default: + throw new Error('Unknown encoding') + } + return n + }), + (i.concat = function (e, t) { + Z(R(e), 'Usage: Buffer.concat(list, [totalLength])\nlist should be an Array.') + if (e.length === 0) return new i(0) + if (e.length === 1) return e[0] + var n + if (typeof t != 'number') { + t = 0 + for (n = 0; n < e.length; n++) t += e[n].length + } + var r = new i(t), + s = 0 + for (n = 0; n < e.length; n++) { + var o = e[n] + o.copy(r, s), (s += o.length) + } + return r + }), + (i.prototype.write = function (e, t, n, r) { + if (isFinite(t)) isFinite(n) || ((r = n), (n = undefined)) + else { + var i = r + ;(r = t), (t = n), (n = i) + } + t = Number(t) || 0 + var s = this.length - t + n ? ((n = Number(n)), n > s && (n = s)) : (n = s), (r = String(r || 'utf8').toLowerCase()) + var o + switch (r) { + case 'hex': + o = p(this, e, t, n) + break + case 'utf8': + case 'utf-8': + o = d(this, e, t, n) + break + case 'ascii': + o = v(this, e, t, n) + break + case 'binary': + o = m(this, e, t, n) + break + case 'base64': + o = g(this, e, t, n) + break + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + o = y(this, e, t, n) + break + default: + throw new Error('Unknown encoding') + } + return o + }), + (i.prototype.toString = function (e, t, n) { + var r = this + ;(e = String(e || 'utf8').toLowerCase()), (t = Number(t) || 0), (n = n !== undefined ? Number(n) : (n = r.length)) + if (n === t) return '' + var i + switch (e) { + case 'hex': + i = x(r, t, n) + break + case 'utf8': + case 'utf-8': + i = w(r, t, n) + break + case 'ascii': + i = E(r, t, n) + break + case 'binary': + i = S(r, t, n) + break + case 'base64': + i = b(r, t, n) + break + case 'ucs2': + case 'ucs-2': + case 'utf16le': + case 'utf-16le': + i = T(r, t, n) + break + default: + throw new Error('Unknown encoding') + } + return i + }), + (i.prototype.toJSON = function () { + return { type: 'Buffer', data: Array.prototype.slice.call(this._arr || this, 0) } + }), + (i.prototype.copy = function (e, t, n, r) { + var s = this + n || (n = 0), !r && r !== 0 && (r = this.length), t || (t = 0) + if (r === n) return + if (e.length === 0 || s.length === 0) return + Z(r >= n, 'sourceEnd < sourceStart'), Z(t >= 0 && t < e.length, 'targetStart out of bounds'), Z(n >= 0 && n < s.length, 'sourceStart out of bounds'), Z(r >= 0 && r <= s.length, 'sourceEnd out of bounds'), r > this.length && (r = this.length), e.length - t < r - n && (r = e.length - t + n) + var o = r - n + if (o < 100 || !i._useTypedArrays) for (var u = 0; u < o; u++) e[u + t] = this[u + n] + else e._set(this.subarray(n, n + o), t) + }), + (i.prototype.slice = function (e, t) { + var n = this.length + ;(e = I(e, n, 0)), (t = I(t, n, n)) + if (i._useTypedArrays) return i._augment(this.subarray(e, t)) + var r = t - e, + s = new i(r, undefined, !0) + for (var o = 0; o < r; o++) s[o] = this[o + e] + return s + }), + (i.prototype.get = function (e) { + return console.log('.get() is deprecated. Access using array indexes instead.'), this.readUInt8(e) + }), + (i.prototype.set = function (e, t) { + return console.log('.set() is deprecated. Access using array indexes instead.'), this.writeUInt8(e, t) + }), + (i.prototype.readUInt8 = function (e, t) { + t || (Z(e !== undefined && e !== null, 'missing offset'), Z(e < this.length, 'Trying to read beyond buffer length')) + if (e >= this.length) return + return this[e] + }), + (i.prototype.readUInt16LE = function (e, t) { + return N(this, e, !0, t) + }), + (i.prototype.readUInt16BE = function (e, t) { + return N(this, e, !1, t) + }), + (i.prototype.readUInt32LE = function (e, t) { + return C(this, e, !0, t) + }), + (i.prototype.readUInt32BE = function (e, t) { + return C(this, e, !1, t) + }), + (i.prototype.readInt8 = function (e, t) { + t || (Z(e !== undefined && e !== null, 'missing offset'), Z(e < this.length, 'Trying to read beyond buffer length')) + if (e >= this.length) return + var n = this[e] & 128 + return n ? (255 - this[e] + 1) * -1 : this[e] + }), + (i.prototype.readInt16LE = function (e, t) { + return k(this, e, !0, t) + }), + (i.prototype.readInt16BE = function (e, t) { + return k(this, e, !1, t) + }), + (i.prototype.readInt32LE = function (e, t) { + return L(this, e, !0, t) + }), + (i.prototype.readInt32BE = function (e, t) { + return L(this, e, !1, t) + }), + (i.prototype.readFloatLE = function (e, t) { + return A(this, e, !0, t) + }), + (i.prototype.readFloatBE = function (e, t) { + return A(this, e, !1, t) + }), + (i.prototype.readDoubleLE = function (e, t) { + return O(this, e, !0, t) + }), + (i.prototype.readDoubleBE = function (e, t) { + return O(this, e, !1, t) + }), + (i.prototype.writeUInt8 = function (e, t, n) { + n || (Z(e !== undefined && e !== null, 'missing value'), Z(t !== undefined && t !== null, 'missing offset'), Z(t < this.length, 'trying to write beyond buffer length'), Q(e, 255)) + if (t >= this.length) return + this[t] = e + }), + (i.prototype.writeUInt16LE = function (e, t, n) { + M(this, e, t, !0, n) + }), + (i.prototype.writeUInt16BE = function (e, t, n) { + M(this, e, t, !1, n) + }), + (i.prototype.writeUInt32LE = function (e, t, n) { + _(this, e, t, !0, n) + }), + (i.prototype.writeUInt32BE = function (e, t, n) { + _(this, e, t, !1, n) + }), + (i.prototype.writeInt8 = function (e, t, n) { + n || (Z(e !== undefined && e !== null, 'missing value'), Z(t !== undefined && t !== null, 'missing offset'), Z(t < this.length, 'Trying to write beyond buffer length'), G(e, 127, -128)) + if (t >= this.length) return + e >= 0 ? this.writeUInt8(e, t, n) : this.writeUInt8(255 + e + 1, t, n) + }), + (i.prototype.writeInt16LE = function (e, t, n) { + D(this, e, t, !0, n) + }), + (i.prototype.writeInt16BE = function (e, t, n) { + D(this, e, t, !1, n) + }), + (i.prototype.writeInt32LE = function (e, t, n) { + P(this, e, t, !0, n) + }), + (i.prototype.writeInt32BE = function (e, t, n) { + P(this, e, t, !1, n) + }), + (i.prototype.writeFloatLE = function (e, t, n) { + H(this, e, t, !0, n) + }), + (i.prototype.writeFloatBE = function (e, t, n) { + H(this, e, t, !1, n) + }), + (i.prototype.writeDoubleLE = function (e, t, n) { + B(this, e, t, !0, n) + }), + (i.prototype.writeDoubleBE = function (e, t, n) { + B(this, e, t, !1, n) + }), + (i.prototype.fill = function (e, t, n) { + e || (e = 0), t || (t = 0), n || (n = this.length), typeof e == 'string' && (e = e.charCodeAt(0)), Z(typeof e == 'number' && !isNaN(e), 'value is not a number'), Z(n >= t, 'end < start') + if (n === t) return + if (this.length === 0) return + Z(t >= 0 && t < this.length, 'start out of bounds'), Z(n >= 0 && n <= this.length, 'end out of bounds') + for (var r = t; r < n; r++) this[r] = e + }), + (i.prototype.inspect = function () { + var e = [], + t = this.length + for (var r = 0; r < t; r++) { + e[r] = z(this[r]) + if (r === n.INSPECT_MAX_BYTES) { + e[r + 1] = '...' + break + } + } + return '' + }), + (i.prototype.toArrayBuffer = function () { + if (typeof Uint8Array != 'undefined') { + if (i._useTypedArrays) return new i(this).buffer + var e = new Uint8Array(this.length) + for (var t = 0, n = e.length; t < n; t += 1) e[t] = this[t] + return e.buffer + } + throw new Error('Buffer.toArrayBuffer not supported in this browser') + }) + var F = i.prototype + i._augment = function (e) { + return ( + (e._isBuffer = !0), + (e._get = e.get), + (e._set = e.set), + (e.get = F.get), + (e.set = F.set), + (e.write = F.write), + (e.toString = F.toString), + (e.toLocaleString = F.toString), + (e.toJSON = F.toJSON), + (e.copy = F.copy), + (e.slice = F.slice), + (e.readUInt8 = F.readUInt8), + (e.readUInt16LE = F.readUInt16LE), + (e.readUInt16BE = F.readUInt16BE), + (e.readUInt32LE = F.readUInt32LE), + (e.readUInt32BE = F.readUInt32BE), + (e.readInt8 = F.readInt8), + (e.readInt16LE = F.readInt16LE), + (e.readInt16BE = F.readInt16BE), + (e.readInt32LE = F.readInt32LE), + (e.readInt32BE = F.readInt32BE), + (e.readFloatLE = F.readFloatLE), + (e.readFloatBE = F.readFloatBE), + (e.readDoubleLE = F.readDoubleLE), + (e.readDoubleBE = F.readDoubleBE), + (e.writeUInt8 = F.writeUInt8), + (e.writeUInt16LE = F.writeUInt16LE), + (e.writeUInt16BE = F.writeUInt16BE), + (e.writeUInt32LE = F.writeUInt32LE), + (e.writeUInt32BE = F.writeUInt32BE), + (e.writeInt8 = F.writeInt8), + (e.writeInt16LE = F.writeInt16LE), + (e.writeInt16BE = F.writeInt16BE), + (e.writeInt32LE = F.writeInt32LE), + (e.writeInt32BE = F.writeInt32BE), + (e.writeFloatLE = F.writeFloatLE), + (e.writeFloatBE = F.writeFloatBE), + (e.writeDoubleLE = F.writeDoubleLE), + (e.writeDoubleBE = F.writeDoubleBE), + (e.fill = F.fill), + (e.inspect = F.inspect), + (e.toArrayBuffer = F.toArrayBuffer), + e + ) + } + }).call(this, e('IrXUsu'), typeof self != 'undefined' ? self : typeof window != 'undefined' ? window : {}, e('buffer').Buffer, arguments[3], arguments[4], arguments[5], arguments[6], '/node_modules/gulp-browserify/node_modules/browserify/node_modules/buffer/index.js', '/node_modules/gulp-browserify/node_modules/browserify/node_modules/buffer') + }, + { 'IrXUsu': 12, 'base64-js': 10, 'buffer': 9, 'ieee754': 11 } + ], + 10: [ + function (e, t, n) { + ;(function (e, t, r, i, s, o, u, a, f) { + var l = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + ;(function (e) { + 'use strict' + function f(e) { + var t = e.charCodeAt(0) + if (t === n || t === u) return 62 + if (t === r || t === a) return 63 + if (t < i) return -1 + if (t < i + 10) return t - i + 26 + 26 + if (t < o + 26) return t - o + if (t < s + 26) return t - s + 26 + } + function c(e) { + function c(e) { + u[l++] = e + } + var n, r, i, s, o, u + if (e.length % 4 > 0) throw new Error('Invalid string. Length must be a multiple of 4') + var a = e.length + ;(o = '=' === e.charAt(a - 2) ? 2 : '=' === e.charAt(a - 1) ? 1 : 0), (u = new t((e.length * 3) / 4 - o)), (i = o > 0 ? e.length - 4 : e.length) + var l = 0 + for (n = 0, r = 0; n < i; n += 4, r += 3) (s = (f(e.charAt(n)) << 18) | (f(e.charAt(n + 1)) << 12) | (f(e.charAt(n + 2)) << 6) | f(e.charAt(n + 3))), c((s & 16711680) >> 16), c((s & 65280) >> 8), c(s & 255) + return o === 2 ? ((s = (f(e.charAt(n)) << 2) | (f(e.charAt(n + 1)) >> 4)), c(s & 255)) : o === 1 && ((s = (f(e.charAt(n)) << 10) | (f(e.charAt(n + 1)) << 4) | (f(e.charAt(n + 2)) >> 2)), c((s >> 8) & 255), c(s & 255)), u + } + function h(e) { + function o(e) { + return l.charAt(e) + } + function u(e) { + return o((e >> 18) & 63) + o((e >> 12) & 63) + o((e >> 6) & 63) + o(e & 63) + } + var t, + n = e.length % 3, + r = '', + i, + s + for (t = 0, s = e.length - n; t < s; t += 3) (i = (e[t] << 16) + (e[t + 1] << 8) + e[t + 2]), (r += u(i)) + switch (n) { + case 1: + ;(i = e[e.length - 1]), (r += o(i >> 2)), (r += o((i << 4) & 63)), (r += '==') + break + case 2: + ;(i = (e[e.length - 2] << 8) + e[e.length - 1]), (r += o(i >> 10)), (r += o((i >> 4) & 63)), (r += o((i << 2) & 63)), (r += '=') + } + return r + } + var t = typeof Uint8Array != 'undefined' ? Uint8Array : Array, + n = '+'.charCodeAt(0), + r = '/'.charCodeAt(0), + i = '0'.charCodeAt(0), + s = 'a'.charCodeAt(0), + o = 'A'.charCodeAt(0), + u = '-'.charCodeAt(0), + a = '_'.charCodeAt(0) + ;(e.toByteArray = c), (e.fromByteArray = h) + })(typeof n == 'undefined' ? (this.base64js = {}) : n) + }).call(this, e('IrXUsu'), typeof self != 'undefined' ? self : typeof window != 'undefined' ? window : {}, e('buffer').Buffer, arguments[3], arguments[4], arguments[5], arguments[6], '/node_modules/gulp-browserify/node_modules/browserify/node_modules/buffer/node_modules/base64-js/lib/b64.js', '/node_modules/gulp-browserify/node_modules/browserify/node_modules/buffer/node_modules/base64-js/lib') + }, + { IrXUsu: 12, buffer: 9 } + ], + 11: [ + function (e, t, n) { + ;(function (e, t, r, i, s, o, u, a, f) { + ;(n.read = function (e, t, n, r, i) { + var s, + o, + u = i * 8 - r - 1, + a = (1 << u) - 1, + f = a >> 1, + l = -7, + c = n ? i - 1 : 0, + h = n ? -1 : 1, + p = e[t + c] + ;(c += h), (s = p & ((1 << -l) - 1)), (p >>= -l), (l += u) + for (; l > 0; s = s * 256 + e[t + c], c += h, l -= 8); + ;(o = s & ((1 << -l) - 1)), (s >>= -l), (l += r) + for (; l > 0; o = o * 256 + e[t + c], c += h, l -= 8); + if (s === 0) s = 1 - f + else { + if (s === a) return o ? NaN : (p ? -1 : 1) * Infinity + ;(o += Math.pow(2, r)), (s -= f) + } + return (p ? -1 : 1) * o * Math.pow(2, s - r) + }), + (n.write = function (e, t, n, r, i, s) { + var o, + u, + a, + f = s * 8 - i - 1, + l = (1 << f) - 1, + c = l >> 1, + h = i === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0, + p = r ? 0 : s - 1, + d = r ? 1 : -1, + v = t < 0 || (t === 0 && 1 / t < 0) ? 1 : 0 + ;(t = Math.abs(t)), isNaN(t) || t === Infinity ? ((u = isNaN(t) ? 1 : 0), (o = l)) : ((o = Math.floor(Math.log(t) / Math.LN2)), t * (a = Math.pow(2, -o)) < 1 && (o--, (a *= 2)), o + c >= 1 ? (t += h / a) : (t += h * Math.pow(2, 1 - c)), t * a >= 2 && (o++, (a /= 2)), o + c >= l ? ((u = 0), (o = l)) : o + c >= 1 ? ((u = (t * a - 1) * Math.pow(2, i)), (o += c)) : ((u = t * Math.pow(2, c - 1) * Math.pow(2, i)), (o = 0))) + for (; i >= 8; e[n + p] = u & 255, p += d, u /= 256, i -= 8); + ;(o = (o << i) | u), (f += i) + for (; f > 0; e[n + p] = o & 255, p += d, o /= 256, f -= 8); + e[n + p - d] |= v * 128 + }) + }).call(this, e('IrXUsu'), typeof self != 'undefined' ? self : typeof window != 'undefined' ? window : {}, e('buffer').Buffer, arguments[3], arguments[4], arguments[5], arguments[6], '/node_modules/gulp-browserify/node_modules/browserify/node_modules/buffer/node_modules/ieee754/index.js', '/node_modules/gulp-browserify/node_modules/browserify/node_modules/buffer/node_modules/ieee754') + }, + { IrXUsu: 12, buffer: 9 } + ], + 12: [ + function (e, t, n) { + ;(function (e, n, r, i, s, o, u, a, f) { + function l() {} + var e = (t.exports = {}) + ;(e.nextTick = (function () { + var e = typeof window != 'undefined' && window.setImmediate, + t = typeof window != 'undefined' && window.postMessage && window.addEventListener + if (e) + return function (e) { + return window.setImmediate(e) + } + if (t) { + var n = [] + return ( + window.addEventListener( + 'message', + function (e) { + var t = e.source + if ((t === window || t === null) && e.data === 'process-tick') { + e.stopPropagation() + if (n.length > 0) { + var r = n.shift() + r() + } + } + }, + !0 + ), + function (t) { + n.push(t), window.postMessage('process-tick', '*') + } + ) + } + return function (t) { + setTimeout(t, 0) + } + })()), + (e.title = 'browser'), + (e.browser = !0), + (e.env = {}), + (e.argv = []), + (e.on = l), + (e.addListener = l), + (e.once = l), + (e.off = l), + (e.removeListener = l), + (e.removeAllListeners = l), + (e.emit = l), + (e.binding = function (e) { + throw new Error('process.binding is not supported') + }), + (e.cwd = function () { + return '/' + }), + (e.chdir = function (e) { + throw new Error('process.chdir is not supported') + }) + }).call(this, e('IrXUsu'), typeof self != 'undefined' ? self : typeof window != 'undefined' ? window : {}, e('buffer').Buffer, arguments[3], arguments[4], arguments[5], arguments[6], '/node_modules/gulp-browserify/node_modules/browserify/node_modules/process/browser.js', '/node_modules/gulp-browserify/node_modules/browserify/node_modules/process') + }, + { IrXUsu: 12, buffer: 9 } + ] + }, + {}, + [1] + ), + (e.Protoplast = Protoplast) + ) + }.apply(e, arguments) + }) + })(this), + n('model/recognition-data', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + allMatches: null, + bestMatch: null, + $create: function (e) { + ;(this.allMatches = e), + (this.bestMatch = null), + e.forEach(function (e) { + this.bestMatch != null ? (this.bestMatch = e.value > this.bestMatch.value ? e : this.bestMatch) : (this.bestMatch = e) + }, this) + } + }) + return n + }), + n('../js/recogniser/default-recogniser', ['require', 'p', 'model/recognition-data'], function (e) { + var t = e('p'), + n = e('model/recognition-data'), + r = t.extend([t.Dispatcher], { + _sampler: null, + patterns: null, + sampler: { + set: function (e) { + this._sampler !== null && this._removeSampler(), (this._sampler = e), this._initSampler() + } + }, + $create: function () { + this._clear() + }, + register: function (e) { + if (this.patterns[e.name]) throw new Error('Pattern with name ' + e.name + ' is already registered!') + this.patterns[e.name] = e + }, + _initSampler: function () { + ;(this.__onSamplingFinished = this._onSamplingFinished.bind(this)), this._sampler.on('finished', this.__onSamplingFinished) + }, + _removeSampler: function () { + this._sampler.off('finished', this.__onSamplingFinished), (this._sampler = null) + }, + _onSamplingFinished: function (e) { + this.dispatch('recognised', this._recognise(e)) + }, + _recognise: function (e) { + var t = [] + for (var r in this.patterns) t.push(this.patterns[r].algorithm.match(this.patterns[r], e)) + return n.create(t) + }, + _clear: function () { + this.patterns = {} + } + }) + return r + }), + n('../js/model/directions', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ UP: 1, RIGHT_UP: 2, RIGHT: 3, RIGHT_DOWN: 4, DOWN: 5, LEFT_DOWN: 6, LEFT: 7, LEFT_UP: 8, NO_MOVE: 0 }) + return n + }), + n('../js/model/match', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + value: null, + pattern: null, + recognised: !1, + $create: function (e, t, n) { + ;(this.pattern = e), (this.value = t), (this.recognised = n) + } + }) + return n + }), + n('model/directions', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ UP: 1, RIGHT_UP: 2, RIGHT: 3, RIGHT_DOWN: 4, DOWN: 5, LEFT_DOWN: 6, LEFT: 7, LEFT_UP: 8, NO_MOVE: 0 }) + return n + }), + n('util/math', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + distance: function (e, t) { + var n = e.x - t.x, + r = e.y - t.y + return Math.sqrt(n * n + r * r) + }, + threePointsAngle: function (e, t, n) { + var r = e.subtract(t), + i = n.subtract(t), + s = r.x * i.x + r.y * i.y, + o = s / (r.length * i.length) + return Math.acos(o) + } + }) + return n + }), + n('model/point', ['require', 'p', 'util/math'], function (e) { + var t = e('p'), + n = e('util/math'), + r = t.extend({ + x: null, + y: null, + $create: function (e, t) { + ;(this.x = e), (this.y = t) + }, + subtract: function (e) { + return r.create(this.x - e.x, this.y - e.y) + }, + length: { + get: function () { + return n.distance(this, r.create(0, 0)) + } + }, + toString: function () { + return '(' + this.x + ',' + this.y + ')' + } + }) + return r + }), + n('util/direction', ['require', 'p', 'model/directions', 'model/point'], function (e) { + var t = e('p'), + n = e('model/directions'), + r = e('model/point'), + i = t.extend({ + twoPointsDirection: function (e, t) { + var i = t.subtract(e) + if (e.x === t.x && e.y === t.y) return 0 + var s = r.create(0, -1), + o = i.x * s.x + i.y * s.y, + u = o / (i.length * s.length), + a = this._eight(Math.acos(u)) + if (i.x < 0) + switch (a) { + case 0: + return n.UP + case 1: + case 2: + return n.LEFT_UP + case 3: + case 4: + return n.LEFT + case 5: + case 6: + return n.LEFT_DOWN + case 7: + return n.DOWN + } + else + switch (a) { + case 0: + return n.UP + case 1: + case 2: + return n.RIGHT_UP + case 3: + case 4: + return n.RIGHT + case 5: + case 6: + return n.RIGHT_DOWN + case 7: + return n.DOWN + } + }, + PI8: Math.PI / 8, + _eight: function (e) { + var t = e / this.PI8 + return t === 8 ? 7 : Math.floor(t) + } + }) + return i + }), + n('algorithm/moses-fit', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + _reducedPatternData: null, + _reducedSamplingData: null, + $create: function (e, t) { + this._mosesFit(e, t) + }, + getReducedPatternData: function () { + return this._reducedPatternData + }, + getReducedSamplingData: function () { + return this._reducedSamplingData + }, + _mosesFit: function (e, t) { + var n + e.length >= t.length ? ((n = (e.length - 1) / (t.length - 1)), (this._reducedPatternData = this._reduceList(e, n == Infinity ? (n = t.length) : n)), (this._reducedSamplingData = t)) : ((n = (t.length - 1) / (e.length - 1)), (this._reducedPatternData = e), (this._reducedSamplingData = this._reduceList(t, n == Infinity ? (n = e.length) : n))) + }, + _reduceList: function (e, t) { + var n = [], + r = 0, + i = 0 + while (i < e.length - 1) n.push(e[i]), (r += t), (i = Math.round(r)) + return n.push(e[e.length - 1]), n + } + }) + return n + }), + n('model/match', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + value: null, + pattern: null, + recognised: !1, + $create: function (e, t, n) { + ;(this.pattern = e), (this.value = t), (this.recognised = n) + } + }) + return n + }), + n('algorithm/default-moses-algorithm', ['require', 'p', 'util/direction', 'algorithm/moses-fit', 'model/match'], function (e) { + var t = e('p'), + n = e('util/direction'), + r = e('algorithm/moses-fit'), + i = e('model/match'), + s = t.extend({ + _threshold: null, + _minSamplerPoints: null, + $create: function (e, t) { + ;(this._threshold = e || 0.5), (this._minSamplerPoints = t || 5) + }, + match: function (e, t) { + var n = this._matchingValue(e.data, t), + r = n >= this._threshold && t.length >= this._minSamplerPoints + return i.create(e, n, r) + }, + _matchingValue: function (e, t) { + var n = r.create(e.slice(), t.slice()), + i = this._preparePatternData(n.getReducedPatternData()), + s = this._prepareSamplingData(n.getReducedSamplingData()), + o = this._pointsToDirections(i), + u = this._pointsToDirections(s), + a = this._calculateMosesSimilarity(o, u) + return a + }, + _preparePatternData: function (e) { + return e + }, + _prepareSamplingData: function (e) { + return e + }, + _pointsToDirections: function (e) { + var t = [] + for (var r = 0; r < e.length - 1; r++) t.push(n.twoPointsDirection(e[r], e[r + 1])) + return t + }, + _calculateMosesSimilarity: function (e, t) { + var n = e.length, + r = 0 + for (var i = 0; i < n; i++) { + var s = e[i], + o = t[i] + if (s == o) r++ + else { + var u = Math.abs(s - o) + r += u == 7 || u == 1 ? 0.5 : 0 + } + } + return r / n + } + }) + return s + }), + n('util/array', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + compare: function (e, t) { + if (e.length !== t.length) return !1 + for (var n = 0; n < e.length; n++) if (e[n] !== t[n]) return !1 + return !0 + } + }) + return n + }), + n('model/polyline', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + $create: function () { + ;(this.segments = []), (this.vertices = []), (this.closed = !1) + } + }) + return n + }), + n('model/segment', ['require', 'p', 'util/direction'], function (e) { + var t = e('p'), + n = e('util/direction'), + r = t.extend({ + $create: function (e, t) { + ;(this.start = e), (this.end = t) + }, + vector: { + get: function () { + return this.end.subtract(this.start) + } + }, + direction: { + get: function () { + return n.twoPointsDirection(this.start, this.end) + } + } + }) + return r + }), + n('util/segment', ['require', 'p', 'model/segment'], function (e) { + var t = e('p'), + n = e('model/segment'), + r = t.extend({ + merge: function (e, t, n) {}, + mergeWithPrev: function (e, t) { + var n + return ( + (e = e.concat()), + t === 0 + ? ((n = this.mergeSegments(e[0], e[1])), (e = [n].concat(e.slice(2)))) + : ((n = this.mergeSegments(e[t - 1], e[t])), + (e = e + .slice(0, t - 1) + .concat([n]) + .concat(e.slice(t + 1)))), + e + ) + }, + mergeSegments: function (e, t) { + return n.create(e.start, t.end) + } + }) + return r + }), + n('algorithm/polygonal-line-algorithm', ['require', 'p', 'util/array', 'util/direction', 'model/point', 'model/polyline', 'model/segment', 'util/segment', 'model/match', 'util/math'], function (e) { + var t = e('p'), + n = e('util/array'), + r = e('util/direction'), + i = e('model/point'), + s = e('model/polyline'), + o = e('model/segment'), + u = e('util/segment'), + a = e('model/match'), + f = e('util/math'), + l = t.extend({ + $create: function (e) { + ;(e = e || {}), (this.tolerance = e.tolerance || Math.PI / 12), (this.minLength = e.minLength || 30), (this.closedTolerance = e.closedTolerance || 30) + }, + match: function (e, t) { + var n = a.create(e, 0, !1) + n.polyline = s.create() + if (t.length < 3) return n + var r = this._getJointIndicies(t) + return ( + (n.polyline.segments = this._getSegments(r, t)), + (n.polyline.segments = this._smoothSegments(n.polyline.segments)), + (n.polyline.segments = this._mergeShortSegments(n.polyline.segments)), + (n.polyline.segments = this._smoothSegments(n.polyline.segments)), + (n.polyline.vertices = [n.polyline.segments[0].start].concat( + n.polyline.segments.map(function (e) { + return e.end + }) + )), + (n.polyline.closed = f.distance(n.polyline.vertices[0], n.polyline.vertices[n.polyline.vertices.length - 1]) < this.closedTolerance), + (n.recognised = this._recognise(e.data || {}, n.polyline)), + (n.value = n.recognised ? 1 : 0), + n + ) + }, + _recognise: function (e, t) { + var r = !0, + i, + s + return ( + e.segments !== undefined && (r = e.segments === t.segments.length), + r && + e.directions !== undefined && + ((s = t.segments.map(function (e) { + return e.direction + })), + (i = e.directions.some(function (e) { + return n.compare(e, s) + })), + (r = i)), + r && e.closed !== undefined && (r = t.closed === e.closed), + r && e.test !== undefined && (r = e.test(t)), + r + ) + }, + _mergeShortSegments: function (e) { + var t = e.concat(), + n + while (t.length > 1 && (n = this._indexOfShortSegment(t)) !== -1) t = u.mergeWithPrev(t, n) + return t + }, + _indexOfShortSegment: function (e) { + var t = -1 + return ( + e.forEach(function (e, n) { + e.vector.length < this.minLength && (t = n) + }, this), + t + ) + }, + _smoothSegments: function (e) { + var t = this._segmentsToPoints(e), + n = this._getJointIndicies(t), + r = this._getSegments(n, t) + return r + }, + _segmentsToPoints: function (e) { + var t = [] + return ( + e.length && + ((t = [e[0].start]), + e.forEach(function (e) { + t.push(e.end) + })), + t + ) + }, + _getSegments: function (e, t) { + var n = [], + r, + i, + s + for (var u = 1; u < e.length; u++) (i = t[e[u - 1]]), (s = t[e[u]]), (r = o.create(i, s)), n.push(r) + return n + }, + _getJointIndicies: function (e) { + var t = this._getAngles(e), + n = this._anglesToJointIndicies(t) + return n + }, + _getSegmentAngles: function (e) { + var t = [] + for (var n = 1; n < e.length; n++) t.push(f.threePointsAngle(e[n - 1].start, e[n - 1].end, e[n].end)) + return t + }, + _getAngles: function (e) { + var t = [] + for (var n = 2; n < e.length; n++) t.push(f.threePointsAngle(e[n - 2], e[n - 1], e[n])) + return t + }, + _anglesToJointIndicies: function (e) { + var t = e.map(function (e) { + return !this._isStraight(e) + }, this) + t = [!0].concat(t).concat(!0) + var n = t.reduce(function (e, t, n) { + return t && e.push(n), e + }, []) + return n + }, + _isStraight: function (e) { + return Math.abs(Math.PI - e) <= this.tolerance + } + }) + return l + }), + n('algorithm/shifted-points-moses-algorithm', ['require', 'algorithm/default-moses-algorithm'], function (e) { + var t = e('algorithm/default-moses-algorithm'), + n = t.extend({ + _preparePatternData: function (e) { + return this._shiftData(e) + }, + _prepareSamplingData: function (e) { + return this._shiftData(e) + }, + _shiftData: function (e) { + return this._shiftRight(e, this._upperLeftPointIndex(e)), e + }, + _shiftRight: function (e, t) { + for (var n = 0; n < t; n++) { + var r = e.shift() + e.push(r) + } + }, + _upperLeftPointIndex: function (e) { + var t = Infinity, + n = Infinity, + r = -1 + for (var i = 0; i < e.length; i++) { + var s = e[i] + if (s.y < n || (s.y == n && s.x < t)) (t = s.x), (n = s.y), (r = i) + } + return r + } + }) + return n + }), + n('algorithm/straight-line-algorithm', ['require', 'p', 'model/point', 'model/match'], function (e) { + var t = e('p'), + n = e('model/point'), + r = e('model/match'), + i = t.extend({ + $create: function (e) { + this._tolerance = e || 10 + }, + match: function (e, t) { + var n = t.map(function (e) { + return e.x + }), + i = t.map(function (e) { + return e.y + }), + s = this._calculateMovement(n), + o = this._calculateMovement(i), + u = this._pointsInDirection(n, s), + a = this._pointsInDirection(i, o), + f = u + a, + l = n.length + i.length, + c = f / l, + h = r.create(e, c, c > 0.8) + return (h.vertical = s === 0), (h.horizontal = o === 0), h + }, + _delta: function (e) { + var t = Math.min.apply(null, e), + n = Math.max.apply(null, e) + return n - t + }, + _calculateMovement: function (e) { + var t = this._delta(e) + return Math.abs(t) <= this._tolerance ? 0 : e[0] - e[e.length - 1] > 0 ? -1 : 1 + }, + _pointsInDirection: function (e, t) { + var n = 1 + return t + ? (e.reduce( + function (e, r, i) { + return i === 0 ? n++ : t === 1 ? (n += r > e ? 1 : r === e ? 0.5 : 0) : t === -1 && (n += r < e ? 1 : r === e ? 0.5 : 0), r + }.bind(this) + ), + n) + : e.length + } + }) + return i + }), + n('algorithm/reversed-moses-algorithm', ['require', 'algorithm/default-moses-algorithm'], function (e) { + var t = e('algorithm/default-moses-algorithm'), + n = t.extend({ + _matchingValue: function (e, n) { + var r = e.slice() + r.reverse() + var i = t._matchingValue.call(this, e, n), + s = t._matchingValue.call(this, r, n) + return i > s ? i : s + } + }) + return n + }), + n('model/pattern', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + name: null, + data: null, + algorithm: null, + $create: function (e, t, n) { + ;(this.name = e), (this.data = t), (this.algorithm = n) + } + }) + return n + }), + n('algorithm/pattern-collection-algorithm', ['require', 'p', 'model/match'], function (e) { + var t = e('p'), + n = e('model/match'), + r = t.extend({ + match: function (e, t) { + var r, i + return ( + e.patterns.forEach(function (e) { + ;(i = e.algorithm.match(e, t)), r != null ? (r = i.value > r.value ? i : r) : (r = i) + }), + n.create(e, r.value, r.recognised) + ) + } + }) + return r + }), + n('model/pattern-collection', ['require', 'p', 'algorithm/pattern-collection-algorithm'], function (e) { + var t = e('p'), + n = e('algorithm/pattern-collection-algorithm'), + r = t.extend({ + patterns: null, + $create: function (e) { + this.name = e + var t = Array.prototype.slice.call(arguments).slice(1) + ;(this.patterns = t), (this.algorithm = n.create()) + } + }) + return r + }), + n('model/pattern-factory', ['require', 'p', 'model/point', 'model/pattern'], function (e) { + var t = e('p'), + n = e('model/point'), + r = e('model/pattern'), + i = t.extend({ + fromFlatArray: function (e, t, i) { + if (t.length % 2 != 0) throw Error('You must provide even number of coordinates!') + var s = [] + for (var o = 0; o < t.length; o += 2) s.push(n.create(t[o], t[o + 1])) + return r.create(e, s, i) + } + }) + return i + }), + n('../js/model/moses-patterns', ['require', 'p', 'model/directions', 'algorithm/default-moses-algorithm', 'algorithm/polygonal-line-algorithm', 'algorithm/shifted-points-moses-algorithm', 'algorithm/straight-line-algorithm', 'algorithm/reversed-moses-algorithm', 'model/pattern', 'model/pattern-collection', 'model/pattern-factory'], function (e) { + var t = e('p'), + n = e('model/directions'), + r = e('algorithm/default-moses-algorithm'), + i = e('algorithm/polygonal-line-algorithm'), + s = e('algorithm/shifted-points-moses-algorithm'), + o = e('algorithm/straight-line-algorithm'), + u = e('algorithm/reversed-moses-algorithm'), + a = e('model/pattern'), + f = e('model/pattern-collection'), + l = e('model/pattern-factory'), + c = t.extend({ + $create: function () { + ;(this.CIRCLE = f.create('Circle', this.CIRCLE_CLOCKWISE, this.CIRCLE_COUNTER_CLOCKWISE)), (this.SQUARE = f.create('Square', this.LEFT_TOP_SQUARE, this.RIGHT_TOP_SQUARE, this.LEFT_BOTTOM_SQUARE, this.RIGHT_BOTTOM_SQUARE)) + }, + CIRCLE_CLOCKWISE: { value: l.fromFlatArray('Circle (clockwise)', [0, -100, 17, -98.5, 34, -94, 50, -86.6, 64.2, -76.6, 76.6, -64.2, 86.6, -50, 94, -34, 98.5, -17.7, 100, 0, 98.5, 17.7, 94, 34, 86.6, 50, 76.6, 64.2, 64.2, 76.6, 50, 86.6, 34, 94, 17, 98.5, 0, 100, -17, 98.5, -34, 94, -50, 86.6, -64.2, 76.6, -76.6, 64.2, -86.6, 50, -94, 34, -98.5, 17.7, -100, 0, -98.5, -17.7, -94, -34, -86.6, -50, -76.6, -64.2, -64.2, -76.6, -50, -86.6, -34, -94, -17, -98.5, 0, -100], s.create(0.6, 10)) }, + CIRCLE_COUNTER_CLOCKWISE: { value: l.fromFlatArray('Circle (counter clockwise)', [0, -100, -17, -98.5, -34, -94, -50, -86.6, -64.2, -76.6, -76.6, -64.2, -86.6, -50, -94, -34, -98.5, -17.7, -100, 0, -98.5, 17.7, -94, 34, -86.6, 50, -76.6, 64.2, -64.2, 76.6, -50, 86.6, -34, 94, -17, 98.5, 0, 100, 17, 98.5, 34, 94, 50, 86.6, 64.2, 76.6, 76.6, 64.2, 86.6, 50, 94, 34, 98.5, 17.7, 100, 0, 98.5, -17.7, 94, -34, 86.6, -50, 76.6, -64.2, 64.2, -76.6, 50, -86.6, 34, -94, 17, -98.5, 0, -100], s.create(0.6, 10)) }, + LEFT_TOP_SQUARE: { value: l.fromFlatArray('Square (from left top corner)', [0, 0, 10, 0, 20, 0, 30, 0, 40, 0, 50, 0, 60, 0, 70, 0, 80, 0, 90, 0, 90, 10, 90, 20, 90, 30, 90, 40, 90, 50, 90, 60, 90, 70, 90, 80, 90, 90, 80, 90, 70, 90, 60, 90, 50, 90, 40, 90, 30, 90, 20, 90, 10, 90, 0, 90, 0, 80, 0, 70, 0, 60, 0, 50, 0, 40, 0, 30, 0, 20, 0, 10, 0, 0], u.create(0.7, 4)) }, + RIGHT_TOP_SQUARE: { value: l.fromFlatArray('Square (from right top corner)', [90, 0, 90, 10, 90, 20, 90, 30, 90, 40, 90, 50, 90, 60, 90, 70, 90, 80, 90, 90, 80, 90, 70, 90, 60, 90, 50, 90, 40, 90, 30, 90, 20, 90, 10, 90, 0, 90, 0, 80, 0, 70, 0, 60, 0, 50, 0, 40, 0, 30, 0, 20, 0, 10, 0, 0, 10, 0, 20, 0, 30, 0, 40, 0, 50, 0, 60, 0, 70, 0, 80, 0, 90, 0], u.create(0.7, 4)) }, + LEFT_BOTTOM_SQUARE: { value: l.fromFlatArray('Square (from left bottom corner)', [0, 90, 0, 80, 0, 70, 0, 60, 0, 50, 0, 40, 0, 30, 0, 20, 0, 10, 0, 0, 10, 0, 20, 0, 30, 0, 40, 0, 50, 0, 60, 0, 70, 0, 80, 0, 90, 0, 90, 10, 90, 20, 90, 30, 90, 40, 90, 50, 90, 60, 90, 70, 90, 80, 90, 90, 80, 90, 70, 90, 60, 90, 50, 90, 40, 90, 30, 90, 20, 90, 10, 90, 0, 90], u.create(0.7, 4)) }, + RIGHT_BOTTOM_SQUARE: { value: l.fromFlatArray('Square (from right bottom corner)', [90, 90, 80, 90, 70, 90, 60, 90, 50, 90, 40, 90, 30, 90, 20, 90, 10, 90, 0, 90, 0, 80, 0, 70, 0, 60, 0, 50, 0, 40, 0, 30, 0, 20, 0, 10, 0, 0, 10, 0, 20, 0, 30, 0, 40, 0, 50, 0, 60, 0, 70, 0, 80, 0, 90, 0, 90, 10, 90, 20, 90, 30, 90, 40, 90, 50, 90, 60, 90, 70, 90, 80, 90, 90], u.create(0.7, 4)) }, + V: { value: l.fromFlatArray('V', [-50, -100, -45, -90, -40, -80, -35, -70, -30, -60, -25, -50, -20, -40, -15, -30, -10, -20, -5, -10, 0, 0, 5, -10, 10, -20, 15, -30, 20, -40, 25, -50, 30, -60, 35, -70, 40, -80, 45, -90, 50, -100], r.create(0.6, 4)) }, + DASH: { value: l.fromFlatArray('Chevron', [-50, 100, -45, 90, -40, 80, -35, 70, -30, 60, -25, 50, -20, 40, -15, 30, -10, 20, -5, 10, 0, 0, 5, 10, 10, 20, 15, 30, 20, 40, 25, 50, 30, 60, 35, 70, 40, 80, 45, 90, 50, 100], r.create(0.6, 4)) }, + SEVEN: { value: l.fromFlatArray('Seven', [0, 0, 10, 0, 20, 0, 30, 0, 40, 0, 50, 0, 60, 0, 70, 0, 80, 0, 90, 0, 100, 0, 90, 10, 80, 20, 70, 30, 60, 40, 50, 50, 40, 60, 30, 70, 20, 80, 10, 90, 0, 100], r.create(0.7, 2)) }, + Z: { value: l.fromFlatArray('Z', [0, 0, 10, 0, 20, 0, 30, 0, 40, 0, 50, 0, 60, 0, 70, 0, 80, 0, 90, 0, 100, 0, 90, 10, 80, 20, 70, 30, 60, 40, 50, 50, 40, 60, 30, 70, 20, 80, 10, 90, 0, 100, 10, 100, 20, 100, 30, 100, 40, 100, 50, 100, 60, 100, 70, 100, 80, 100, 90, 100], r.create(0.65, 4)) }, + LINE_UP_DOWN: { value: l.fromFlatArray('Up-down', [0, 0, 0, 10, 0, 20, 0, 30, 0, 40, 0, 50, 0, 60, 0, 70, 0, 80, 0, 90, 0, 100], r.create(0.7, 2)) }, + LINE_DOWN_UP: { value: l.fromFlatArray('Down-up', [0, 0, 0, -10, 0, -20, 0, -30, 0, -40, 0, -50, 0, -60, 0, -70, 0, -80, 0, -90, 0, -100], r.create(0.7, 2)) }, + LINE_LEFT_RIGHT: { value: l.fromFlatArray('Left-right', [0, 0, 10, 0, 20, 0, 30, 0, 40, 0, 50, 0, 60, 0, 70, 0, 80, 0, 90, 0, 100, 0], r.create(0.7, 2)) }, + LINE_RIGHT_LEFT: { value: l.fromFlatArray('Right-left', [0, 0, -10, 0, -20, 0, -30, 0, -40, 0, -50, 0, -60, 0, -70, 0, -80, 0, -90, 0, -100, 0], r.create(0.7, 2)) }, + STRAIGHT_LINE: { value: a.create('Straight line', [], o.create()) }, + POLYGONAL: { + value: { + LINE: a.create('Straight line', { segments: 1 }, i.create()), + Z: a.create('Z', { segments: 3, closed: !1, directions: [[n.RIGHT, n.LEFT_DOWN, n.RIGHT]] }, i.create()), + TRIANGLE: a.create('Triangle', { segments: 3, closed: !0 }, i.create()), + TWO_LINES: a.create('Two lines', { segments: 2, closed: !1 }, i.create()), + DOUBLE_LINE: a.create('Double line', { segments: 2, closed: !0 }, i.create()), + RECTANGLE: a.create( + 'Rectangle', + { + segments: 4, + closed: !0, + directions: [ + [n.RIGHT, n.DOWN, n.LEFT, n.UP], + [n.DOWN, n.LEFT, n.UP, n.RIGHT], + [n.LEFT, n.UP, n.RIGHT, n.DOWN], + [n.UP, n.RIGHT, n.DOWN, n.LEFT], + [n.DOWN, n.RIGHT, n.UP, n.LEFT], + [n.RIGHT, n.UP, n.LEFT, n.DOWN], + [n.UP, n.LEFT, n.DOWN, n.RIGHT], + [n.LEFT, n.DOWN, n.RIGHT, n.UP] + ] + }, + i.create() + ) + } + } + }) + return c + }), + n('../js/model/pattern-collection', ['require', 'p', 'algorithm/pattern-collection-algorithm'], function (e) { + var t = e('p'), + n = e('algorithm/pattern-collection-algorithm'), + r = t.extend({ + patterns: null, + $create: function (e) { + this.name = e + var t = Array.prototype.slice.call(arguments).slice(1) + ;(this.patterns = t), (this.algorithm = n.create()) + } + }) + return r + }), + n('../js/model/pattern-factory', ['require', 'p', 'model/point', 'model/pattern'], function (e) { + var t = e('p'), + n = e('model/point'), + r = e('model/pattern'), + i = t.extend({ + fromFlatArray: function (e, t, i) { + if (t.length % 2 != 0) throw Error('You must provide even number of coordinates!') + var s = [] + for (var o = 0; o < t.length; o += 2) s.push(n.create(t[o], t[o + 1])) + return r.create(e, s, i) + } + }) + return i + }), + n('../js/model/pattern', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + name: null, + data: null, + algorithm: null, + $create: function (e, t, n) { + ;(this.name = e), (this.data = t), (this.algorithm = n) + } + }) + return n + }), + n('../js/model/point', ['require', 'p', 'util/math'], function (e) { + var t = e('p'), + n = e('util/math'), + r = t.extend({ + x: null, + y: null, + $create: function (e, t) { + ;(this.x = e), (this.y = t) + }, + subtract: function (e) { + return r.create(this.x - e.x, this.y - e.y) + }, + length: { + get: function () { + return n.distance(this, r.create(0, 0)) + } + }, + toString: function () { + return '(' + this.x + ',' + this.y + ')' + } + }) + return r + }), + n('../js/model/polyline', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + $create: function () { + ;(this.segments = []), (this.vertices = []), (this.closed = !1) + } + }) + return n + }), + n('../js/model/recognition-data', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + allMatches: null, + bestMatch: null, + $create: function (e) { + ;(this.allMatches = e), + (this.bestMatch = null), + e.forEach(function (e) { + this.bestMatch != null ? (this.bestMatch = e.value > this.bestMatch.value ? e : this.bestMatch) : (this.bestMatch = e) + }, this) + } + }) + return n + }), + n('../js/model/segment', ['require', 'p', 'util/direction'], function (e) { + var t = e('p'), + n = e('util/direction'), + r = t.extend({ + $create: function (e, t) { + ;(this.start = e), (this.end = t) + }, + vector: { + get: function () { + return this.end.subtract(this.start) + } + }, + direction: { + get: function () { + return n.twoPointsDirection(this.start, this.end) + } + } + }) + return r + }), + n('sampler/sampler', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend([t.Dispatcher], { + _data: null, + getData: function () { + return this._data + }, + activate: function () { + ;(this._data = []), this.dispatch('activated', this._data) + }, + deactivate: function () { + ;(this._data = []), this.dispatch('deactivated', this._data) + }, + _dispatchSampled: function () { + this.dispatch('sampled', this._data) + }, + _dispatchStarted: function () { + this.dispatch('started', this._data) + }, + _dispatchFinished: function () { + this.dispatch('finished', this._data) + } + }) + return n + }), + n('sampler/dom-sampler', ['require', 'sampler/sampler', 'model/point'], function (e) { + var t = e('sampler/sampler'), + n = e('model/point'), + r = t.extend({ + _element: null, + _pointerId: null, + $create: function (e) { + this._element = e + }, + activate: function () { + ;(this.__startScreening = this._startScreening.bind(this)), this._element.addEventListener('pointerdown', this.__startScreening), t.activate.call(this) + }, + deactivate: function () { + this._element.removeEventListener('pointerdown', this.__startScreening), t.deactivate.call(this) + }, + _mousePosition: function (e) { + return n.create(e.pageX - this._element.offsetLeft, e.pageY - this._element.offsetTop) + }, + _addMousePosition: function (e) { + this._data.push(this._mousePosition(e)) + }, + _continueScreening: function (e) { + this._addMousePosition(e), this._dispatchSampled() + }, + getLastPosition: function () { + return this._data.length > 0 ? this._data[this._data.length - 1] : null + }, + _startScreening: function (e) { + this._pointerId || ((this._pointerId = e.pointerId), (this._data = []), this._addMousePosition(e), (this.__continueScreening = this._continueScreening.bind(this)), this._element.addEventListener('pointermove', this.__continueScreening), (this.__endScreening = this._endScreening.bind(this)), this._element.addEventListener('pointerup', this.__endScreening), this._dispatchStarted()) + }, + _deactivateScreening: function () { + this._element.removeEventListener('pointermove', this.__continueScreening), this._element.removeEventListener('pointerup', this.__endScreening), (this._pointerId = null) + }, + _endScreening: function () { + this._deactivateScreening(), this._dispatchFinished() + } + }) + return r + }), + n('../js/sampler/distance-sampler', ['require', 'sampler/dom-sampler', 'util/math'], function (e) { + var t = e('sampler/dom-sampler'), + n = e('util/math'), + r = t.extend({ + distance: null, + $create: function (e, t) { + this.distance = t + }, + _continueScreening: function (e) { + var r = this.getLastPosition(), + i = this._mousePosition(e) + r !== null && n.distance(r, i) >= this.distance && t._continueScreening.call(this, e) + } + }) + return r + }), + n('../js/sampler/sampler', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend([t.Dispatcher], { + _data: null, + getData: function () { + return this._data + }, + activate: function () { + ;(this._data = []), this.dispatch('activated', this._data) + }, + deactivate: function () { + ;(this._data = []), this.dispatch('deactivated', this._data) + }, + _dispatchSampled: function () { + this.dispatch('sampled', this._data) + }, + _dispatchStarted: function () { + this.dispatch('started', this._data) + }, + _dispatchFinished: function () { + this.dispatch('finished', this._data) + } + }) + return n + }), + n('../js/sampler/time-sampler', ['require', 'sampler/dom-sampler'], function (e) { + var t = e('sampler/dom-sampler'), + n = t.extend({ + interval: null, + _timer: null, + _lastEvent: null, + $create: function (e, t) { + this.interval = t || 100 + }, + _startScreening: function (e) { + t._startScreening.call(this, e), (this._lastEvent = null), (this._timer = setInterval(this._sample.bind(this), this.interval)) + }, + _continueScreening: function (e) { + this._lastEvent = e + }, + _sample: function () { + this._lastEvent && (this._addMousePosition(this._lastEvent), this._dispatchSampled()) + }, + _endScreening: function (e) { + t._endScreening.call(this, e), clearInterval(this._timer) + } + }) + return n + }), + n('../js/sampler/dom-sampler', ['require', 'sampler/sampler', 'model/point'], function (e) { + var t = e('sampler/sampler'), + n = e('model/point'), + r = t.extend({ + _element: null, + _pointerId: null, + $create: function (e) { + this._element = e + }, + activate: function () { + ;(this.__startScreening = this._startScreening.bind(this)), this._element.addEventListener('pointerdown', this.__startScreening), t.activate.call(this) + }, + deactivate: function () { + this._element.removeEventListener('pointerdown', this.__startScreening), t.deactivate.call(this) + }, + _mousePosition: function (e) { + return n.create(e.pageX - this._element.offsetLeft, e.pageY - this._element.offsetTop) + }, + _addMousePosition: function (e) { + this._data.push(this._mousePosition(e)) + }, + _continueScreening: function (e) { + this._addMousePosition(e), this._dispatchSampled() + }, + getLastPosition: function () { + return this._data.length > 0 ? this._data[this._data.length - 1] : null + }, + _startScreening: function (e) { + this._pointerId || ((this._pointerId = e.pointerId), (this._data = []), this._addMousePosition(e), (this.__continueScreening = this._continueScreening.bind(this)), this._element.addEventListener('pointermove', this.__continueScreening), (this.__endScreening = this._endScreening.bind(this)), this._element.addEventListener('pointerup', this.__endScreening), this._dispatchStarted()) + }, + _deactivateScreening: function () { + this._element.removeEventListener('pointermove', this.__continueScreening), this._element.removeEventListener('pointerup', this.__endScreening), (this._pointerId = null) + }, + _endScreening: function () { + this._deactivateScreening(), this._dispatchFinished() + } + }) + return r + }), + n('../js/algorithm/default-moses-algorithm', ['require', 'p', 'util/direction', 'algorithm/moses-fit', 'model/match'], function (e) { + var t = e('p'), + n = e('util/direction'), + r = e('algorithm/moses-fit'), + i = e('model/match'), + s = t.extend({ + _threshold: null, + _minSamplerPoints: null, + $create: function (e, t) { + ;(this._threshold = e || 0.5), (this._minSamplerPoints = t || 5) + }, + match: function (e, t) { + var n = this._matchingValue(e.data, t), + r = n >= this._threshold && t.length >= this._minSamplerPoints + return i.create(e, n, r) + }, + _matchingValue: function (e, t) { + var n = r.create(e.slice(), t.slice()), + i = this._preparePatternData(n.getReducedPatternData()), + s = this._prepareSamplingData(n.getReducedSamplingData()), + o = this._pointsToDirections(i), + u = this._pointsToDirections(s), + a = this._calculateMosesSimilarity(o, u) + return a + }, + _preparePatternData: function (e) { + return e + }, + _prepareSamplingData: function (e) { + return e + }, + _pointsToDirections: function (e) { + var t = [] + for (var r = 0; r < e.length - 1; r++) t.push(n.twoPointsDirection(e[r], e[r + 1])) + return t + }, + _calculateMosesSimilarity: function (e, t) { + var n = e.length, + r = 0 + for (var i = 0; i < n; i++) { + var s = e[i], + o = t[i] + if (s == o) r++ + else { + var u = Math.abs(s - o) + r += u == 7 || u == 1 ? 0.5 : 0 + } + } + return r / n + } + }) + return s + }), + n('../js/algorithm/moses-fit', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + _reducedPatternData: null, + _reducedSamplingData: null, + $create: function (e, t) { + this._mosesFit(e, t) + }, + getReducedPatternData: function () { + return this._reducedPatternData + }, + getReducedSamplingData: function () { + return this._reducedSamplingData + }, + _mosesFit: function (e, t) { + var n + e.length >= t.length ? ((n = (e.length - 1) / (t.length - 1)), (this._reducedPatternData = this._reduceList(e, n == Infinity ? (n = t.length) : n)), (this._reducedSamplingData = t)) : ((n = (t.length - 1) / (e.length - 1)), (this._reducedPatternData = e), (this._reducedSamplingData = this._reduceList(t, n == Infinity ? (n = e.length) : n))) + }, + _reduceList: function (e, t) { + var n = [], + r = 0, + i = 0 + while (i < e.length - 1) n.push(e[i]), (r += t), (i = Math.round(r)) + return n.push(e[e.length - 1]), n + } + }) + return n + }), + n('../js/algorithm/pattern-collection-algorithm', ['require', 'p', 'model/match'], function (e) { + var t = e('p'), + n = e('model/match'), + r = t.extend({ + match: function (e, t) { + var r, i + return ( + e.patterns.forEach(function (e) { + ;(i = e.algorithm.match(e, t)), r != null ? (r = i.value > r.value ? i : r) : (r = i) + }), + n.create(e, r.value, r.recognised) + ) + } + }) + return r + }), + n('../js/algorithm/reversed-moses-algorithm', ['require', 'algorithm/default-moses-algorithm'], function (e) { + var t = e('algorithm/default-moses-algorithm'), + n = t.extend({ + _matchingValue: function (e, n) { + var r = e.slice() + r.reverse() + var i = t._matchingValue.call(this, e, n), + s = t._matchingValue.call(this, r, n) + return i > s ? i : s + } + }) + return n + }), + n('../js/algorithm/polygonal-line-algorithm', ['require', 'p', 'util/array', 'util/direction', 'model/point', 'model/polyline', 'model/segment', 'util/segment', 'model/match', 'util/math'], function (e) { + var t = e('p'), + n = e('util/array'), + r = e('util/direction'), + i = e('model/point'), + s = e('model/polyline'), + o = e('model/segment'), + u = e('util/segment'), + a = e('model/match'), + f = e('util/math'), + l = t.extend({ + $create: function (e) { + ;(e = e || {}), (this.tolerance = e.tolerance || Math.PI / 12), (this.minLength = e.minLength || 30), (this.closedTolerance = e.closedTolerance || 30) + }, + match: function (e, t) { + var n = a.create(e, 0, !1) + n.polyline = s.create() + if (t.length < 3) return n + var r = this._getJointIndicies(t) + return ( + (n.polyline.segments = this._getSegments(r, t)), + (n.polyline.segments = this._smoothSegments(n.polyline.segments)), + (n.polyline.segments = this._mergeShortSegments(n.polyline.segments)), + (n.polyline.segments = this._smoothSegments(n.polyline.segments)), + (n.polyline.vertices = [n.polyline.segments[0].start].concat( + n.polyline.segments.map(function (e) { + return e.end + }) + )), + (n.polyline.closed = f.distance(n.polyline.vertices[0], n.polyline.vertices[n.polyline.vertices.length - 1]) < this.closedTolerance), + (n.recognised = this._recognise(e.data || {}, n.polyline)), + (n.value = n.recognised ? 1 : 0), + n + ) + }, + _recognise: function (e, t) { + var r = !0, + i, + s + return ( + e.segments !== undefined && (r = e.segments === t.segments.length), + r && + e.directions !== undefined && + ((s = t.segments.map(function (e) { + return e.direction + })), + (i = e.directions.some(function (e) { + return n.compare(e, s) + })), + (r = i)), + r && e.closed !== undefined && (r = t.closed === e.closed), + r && e.test !== undefined && (r = e.test(t)), + r + ) + }, + _mergeShortSegments: function (e) { + var t = e.concat(), + n + while (t.length > 1 && (n = this._indexOfShortSegment(t)) !== -1) t = u.mergeWithPrev(t, n) + return t + }, + _indexOfShortSegment: function (e) { + var t = -1 + return ( + e.forEach(function (e, n) { + e.vector.length < this.minLength && (t = n) + }, this), + t + ) + }, + _smoothSegments: function (e) { + var t = this._segmentsToPoints(e), + n = this._getJointIndicies(t), + r = this._getSegments(n, t) + return r + }, + _segmentsToPoints: function (e) { + var t = [] + return ( + e.length && + ((t = [e[0].start]), + e.forEach(function (e) { + t.push(e.end) + })), + t + ) + }, + _getSegments: function (e, t) { + var n = [], + r, + i, + s + for (var u = 1; u < e.length; u++) (i = t[e[u - 1]]), (s = t[e[u]]), (r = o.create(i, s)), n.push(r) + return n + }, + _getJointIndicies: function (e) { + var t = this._getAngles(e), + n = this._anglesToJointIndicies(t) + return n + }, + _getSegmentAngles: function (e) { + var t = [] + for (var n = 1; n < e.length; n++) t.push(f.threePointsAngle(e[n - 1].start, e[n - 1].end, e[n].end)) + return t + }, + _getAngles: function (e) { + var t = [] + for (var n = 2; n < e.length; n++) t.push(f.threePointsAngle(e[n - 2], e[n - 1], e[n])) + return t + }, + _anglesToJointIndicies: function (e) { + var t = e.map(function (e) { + return !this._isStraight(e) + }, this) + t = [!0].concat(t).concat(!0) + var n = t.reduce(function (e, t, n) { + return t && e.push(n), e + }, []) + return n + }, + _isStraight: function (e) { + return Math.abs(Math.PI - e) <= this.tolerance + } + }) + return l + }), + n('../js/algorithm/shifted-points-moses-algorithm', ['require', 'algorithm/default-moses-algorithm'], function (e) { + var t = e('algorithm/default-moses-algorithm'), + n = t.extend({ + _preparePatternData: function (e) { + return this._shiftData(e) + }, + _prepareSamplingData: function (e) { + return this._shiftData(e) + }, + _shiftData: function (e) { + return this._shiftRight(e, this._upperLeftPointIndex(e)), e + }, + _shiftRight: function (e, t) { + for (var n = 0; n < t; n++) { + var r = e.shift() + e.push(r) + } + }, + _upperLeftPointIndex: function (e) { + var t = Infinity, + n = Infinity, + r = -1 + for (var i = 0; i < e.length; i++) { + var s = e[i] + if (s.y < n || (s.y == n && s.x < t)) (t = s.x), (n = s.y), (r = i) + } + return r + } + }) + return n + }), + n('../js/algorithm/straight-line-algorithm', ['require', 'p', 'model/point', 'model/match'], function (e) { + var t = e('p'), + n = e('model/point'), + r = e('model/match'), + i = t.extend({ + $create: function (e) { + this._tolerance = e || 10 + }, + match: function (e, t) { + var n = t.map(function (e) { + return e.x + }), + i = t.map(function (e) { + return e.y + }), + s = this._calculateMovement(n), + o = this._calculateMovement(i), + u = this._pointsInDirection(n, s), + a = this._pointsInDirection(i, o), + f = u + a, + l = n.length + i.length, + c = f / l, + h = r.create(e, c, c > 0.8) + return (h.vertical = s === 0), (h.horizontal = o === 0), h + }, + _delta: function (e) { + var t = Math.min.apply(null, e), + n = Math.max.apply(null, e) + return n - t + }, + _calculateMovement: function (e) { + var t = this._delta(e) + return Math.abs(t) <= this._tolerance ? 0 : e[0] - e[e.length - 1] > 0 ? -1 : 1 + }, + _pointsInDirection: function (e, t) { + var n = 1 + return t + ? (e.reduce( + function (e, r, i) { + return i === 0 ? n++ : t === 1 ? (n += r > e ? 1 : r === e ? 0.5 : 0) : t === -1 && (n += r < e ? 1 : r === e ? 0.5 : 0), r + }.bind(this) + ), + n) + : e.length + } + }) + return i + }), + n('../js/util/array', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + compare: function (e, t) { + if (e.length !== t.length) return !1 + for (var n = 0; n < e.length; n++) if (e[n] !== t[n]) return !1 + return !0 + } + }) + return n + }), + n('../js/util/math', ['require', 'p'], function (e) { + var t = e('p'), + n = t.extend({ + distance: function (e, t) { + var n = e.x - t.x, + r = e.y - t.y + return Math.sqrt(n * n + r * r) + }, + threePointsAngle: function (e, t, n) { + var r = e.subtract(t), + i = n.subtract(t), + s = r.x * i.x + r.y * i.y, + o = s / (r.length * i.length) + return Math.acos(o) + } + }) + return n + }), + n('../js/util/segment', ['require', 'p', 'model/segment'], function (e) { + var t = e('p'), + n = e('model/segment'), + r = t.extend({ + merge: function (e, t, n) {}, + mergeWithPrev: function (e, t) { + var n + return ( + (e = e.concat()), + t === 0 + ? ((n = this.mergeSegments(e[0], e[1])), (e = [n].concat(e.slice(2)))) + : ((n = this.mergeSegments(e[t - 1], e[t])), + (e = e + .slice(0, t - 1) + .concat([n]) + .concat(e.slice(t + 1)))), + e + ) + }, + mergeSegments: function (e, t) { + return n.create(e.start, t.end) + } + }) + return r + }), + n('../js/util/direction', ['require', 'p', 'model/directions', 'model/point'], function (e) { + var t = e('p'), + n = e('model/directions'), + r = e('model/point'), + i = t.extend({ + twoPointsDirection: function (e, t) { + var i = t.subtract(e) + if (e.x === t.x && e.y === t.y) return 0 + var s = r.create(0, -1), + o = i.x * s.x + i.y * s.y, + u = o / (i.length * s.length), + a = this._eight(Math.acos(u)) + if (i.x < 0) + switch (a) { + case 0: + return n.UP + case 1: + case 2: + return n.LEFT_UP + case 3: + case 4: + return n.LEFT + case 5: + case 6: + return n.LEFT_DOWN + case 7: + return n.DOWN + } + else + switch (a) { + case 0: + return n.UP + case 1: + case 2: + return n.RIGHT_UP + case 3: + case 4: + return n.RIGHT + case 5: + case 6: + return n.RIGHT_DOWN + case 7: + return n.DOWN + } + }, + PI8: Math.PI / 8, + _eight: function (e) { + var t = e / this.PI8 + return t === 8 ? 7 : Math.floor(t) + } + }) + return i + }), + n('../build/main', ['require', '../node_modules/handjs/hand.min', '../js/recogniser/default-recogniser', '../js/model/directions', '../js/model/match', '../js/model/moses-patterns', '../js/model/pattern-collection', '../js/model/pattern-factory', '../js/model/pattern', '../js/model/point', '../js/model/polyline', '../js/model/recognition-data', '../js/model/segment', '../js/sampler/distance-sampler', '../js/sampler/sampler', '../js/sampler/time-sampler', '../js/sampler/dom-sampler', '../js/algorithm/default-moses-algorithm', '../js/algorithm/moses-fit', '../js/algorithm/pattern-collection-algorithm', '../js/algorithm/reversed-moses-algorithm', '../js/algorithm/polygonal-line-algorithm', '../js/algorithm/shifted-points-moses-algorithm', '../js/algorithm/straight-line-algorithm', '../js/util/array', '../js/util/math', '../js/util/segment', '../js/util/direction'], function (e) { + var t = e('../node_modules/handjs/hand.min'), + n = e('../js/recogniser/default-recogniser'), + r = e('../js/model/directions'), + i = e('../js/model/match'), + s = e('../js/model/moses-patterns'), + o = e('../js/model/pattern-collection'), + u = e('../js/model/pattern-factory'), + a = e('../js/model/pattern'), + f = e('../js/model/point'), + l = e('../js/model/polyline'), + c = e('../js/model/recognition-data'), + h = e('../js/model/segment'), + p = e('../js/sampler/distance-sampler'), + d = e('../js/sampler/sampler'), + v = e('../js/sampler/time-sampler'), + m = e('../js/sampler/dom-sampler'), + g = e('../js/algorithm/default-moses-algorithm'), + y = e('../js/algorithm/moses-fit'), + b = e('../js/algorithm/pattern-collection-algorithm'), + w = e('../js/algorithm/reversed-moses-algorithm'), + E = e('../js/algorithm/polygonal-line-algorithm'), + S = e('../js/algorithm/shifted-points-moses-algorithm'), + x = e('../js/algorithm/straight-line-algorithm'), + T = e('../js/util/array'), + N = e('../js/util/math'), + h = e('../js/util/segment'), + C = e('../js/util/direction') + return { recogniser: { DefaultRecogniser: n }, model: { Directions: r, Match: i, MosesPatterns: s, PatternCollection: o, PatternFactory: u, Pattern: a, Point: f, Polyline: l, RecognitionData: c, Segment: h }, sampler: { DistanceSampler: p, Sampler: d, TimeSampler: v, DomSampler: m }, algorithm: { DefaultMosesAlgorithm: g, MosesFit: y, PatternCollectionAlgorithm: b, ReversedMosesAlgorithm: w, PolygonalLineAlgorithm: E, ShiftedPointsMosesAlgorithm: S, StraightLineAlgorithm: x }, util: { Array: T, Math: N, Segment: h, Direction: C } } + }), + t('../build/main') + ) +}) diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..bb98c42 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{ts,tsx,html}'], + theme: { + extend: {} + }, + plugins: [] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..71dbd70 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es2017", + "module": "nodenext", + "moduleResolution": "Bundler", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + + "types": ["node", "vite/client"], + "jsx": "react-jsx", + "jsxImportSource": "hono/jsx", + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "paths": { + "~config": ["./lib/config.ts"], + "~types": ["./types/index.ts"], + "~*": ["./*"] + }, + "baseUrl": "./src" + } +} diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..7770732 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,5 @@ +import tsconfigPaths from 'vite-tsconfig-paths' + +export default { + plugins: [tsconfigPaths()] +}