From 83cdb092cdc671c8933f854bd6c331db77694d6d Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 15 May 2026 19:17:21 +0000 Subject: [PATCH] 7 --- backend/package.json | 1 + .../20260515000000-create-subscriptions.js | 183 +++ .../20260515020000-create-billing-settings.js | 70 + backend/src/db/models/billing_settings.js | 46 + backend/src/db/models/subscription_plans.js | 88 ++ backend/src/db/models/subscriptions.js | 73 + backend/src/db/models/users.js | 9 +- .../20260515010000-subscription-plans.js | 88 ++ backend/src/index.js | 4 + backend/src/routes/billing.js | 73 + backend/src/routes/billingWebhook.js | 20 + backend/src/services/billing.js | 857 ++++++++++ backend/src/services/workspace.js | 351 ++++- backend/yarn.lock | 1341 +++++++++++++++- .../src/components/AdminEntity/PageKit.tsx | 240 +++ .../components/Agents/AgentFormSections.tsx | 222 +++ frontend/src/components/Agents/CardAgents.tsx | 341 ++-- .../src/components/Agents/TableAgents.tsx | 18 +- frontend/src/components/AsideMenu.tsx | 16 +- frontend/src/components/AsideMenuLayer.tsx | 19 +- .../components/Billing/PricingPlanCard.tsx | 122 ++ frontend/src/components/DevModeBadge.tsx | 150 -- .../src/components/ListActionsPopover.tsx | 68 +- .../components/Workspace/WorkspaceShell.tsx | 573 ++++++- frontend/src/css/_app.css | 11 + frontend/src/hooks/useDevCompilationStatus.ts | 44 - frontend/src/layouts/Authenticated.tsx | 45 +- frontend/src/lib/billingPlans.ts | 128 ++ frontend/src/menuAside.ts | 5 + frontend/src/pages/_app.tsx | 2 - frontend/src/pages/agents/agents-edit.tsx | 837 ++-------- frontend/src/pages/agents/agents-list.tsx | 5 +- frontend/src/pages/agents/agents-new.tsx | 653 ++------ frontend/src/pages/agents/agents-view.tsx | 932 ++++------- .../pages/attachments/attachments-edit.tsx | 979 ++++-------- .../pages/attachments/attachments-view.tsx | 581 +++---- frontend/src/pages/billing.tsx | 274 ++++ .../conversations/conversations-edit.tsx | 981 ++++-------- .../conversations/conversations-view.tsx | 1184 +++++++------- frontend/src/pages/index.tsx | 228 ++- frontend/src/pages/messages/messages-edit.tsx | 1391 ++++++----------- frontend/src/pages/messages/messages-view.tsx | 1093 ++++--------- .../pages/permissions/permissions-edit.tsx | 280 ++-- .../pages/permissions/permissions-view.tsx | 228 +-- frontend/src/pages/roles/roles-edit.tsx | 383 ++--- frontend/src/pages/roles/roles-view.tsx | 445 ++---- frontend/src/pages/settings.tsx | 46 + frontend/src/pages/stripe-settings.tsx | 306 ++++ .../pages/usage_events/usage_events-edit.tsx | 1391 ++++------------- .../pages/usage_events/usage_events-view.tsx | 780 +++------ frontend/src/pages/users/users-edit.tsx | 955 ++++------- frontend/src/pages/users/users-new.tsx | 728 +++------ frontend/src/pages/users/users-view.tsx | 1052 ++++--------- 53 files changed, 10242 insertions(+), 10698 deletions(-) create mode 100644 backend/src/db/migrations/20260515000000-create-subscriptions.js create mode 100644 backend/src/db/migrations/20260515020000-create-billing-settings.js create mode 100644 backend/src/db/models/billing_settings.js create mode 100644 backend/src/db/models/subscription_plans.js create mode 100644 backend/src/db/models/subscriptions.js create mode 100644 backend/src/db/seeders/20260515010000-subscription-plans.js create mode 100644 backend/src/routes/billing.js create mode 100644 backend/src/routes/billingWebhook.js create mode 100644 backend/src/services/billing.js create mode 100644 frontend/src/components/AdminEntity/PageKit.tsx create mode 100644 frontend/src/components/Agents/AgentFormSections.tsx create mode 100644 frontend/src/components/Billing/PricingPlanCard.tsx delete mode 100644 frontend/src/components/DevModeBadge.tsx delete mode 100644 frontend/src/hooks/useDevCompilationStatus.ts create mode 100644 frontend/src/lib/billingPlans.ts create mode 100644 frontend/src/pages/billing.tsx create mode 100644 frontend/src/pages/stripe-settings.tsx diff --git a/backend/package.json b/backend/package.json index cd1cbcc..f35285f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -36,6 +36,7 @@ "sequelize": "6.35.2", "sequelize-json-schema": "^2.1.1", "sqlite": "4.0.15", + "stripe": "^22.1.1", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.0", "tedious": "^18.2.4" diff --git a/backend/src/db/migrations/20260515000000-create-subscriptions.js b/backend/src/db/migrations/20260515000000-create-subscriptions.js new file mode 100644 index 0000000..a1a80f8 --- /dev/null +++ b/backend/src/db/migrations/20260515000000-create-subscriptions.js @@ -0,0 +1,183 @@ +module.exports = { + async up(queryInterface, Sequelize) { + const transaction = await queryInterface.sequelize.transaction(); + + try { + await queryInterface.createTable( + 'subscription_plans', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + name: { + type: Sequelize.DataTypes.TEXT, + }, + slug: { + type: Sequelize.DataTypes.TEXT, + allowNull: false, + unique: true, + }, + description: { + type: Sequelize.DataTypes.TEXT, + }, + price_monthly: { + type: Sequelize.DataTypes.INTEGER, + allowNull: false, + defaultValue: 0, + }, + currency: { + type: Sequelize.DataTypes.TEXT, + allowNull: false, + defaultValue: 'usd', + }, + stripe_product_id: { + type: Sequelize.DataTypes.TEXT, + }, + stripe_price_id: { + type: Sequelize.DataTypes.TEXT, + }, + message_limit: { + type: Sequelize.DataTypes.INTEGER, + }, + agent_limit: { + type: Sequelize.DataTypes.INTEGER, + }, + is_featured: { + type: Sequelize.DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + is_active: { + type: Sequelize.DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + }, + features_json: { + type: Sequelize.DataTypes.TEXT, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await queryInterface.createTable( + 'subscriptions', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + userId: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + planId: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'subscription_plans', + }, + }, + status: { + type: Sequelize.DataTypes.ENUM, + values: ['trialing', 'active', 'canceled', 'past_due'], + allowNull: false, + defaultValue: 'active', + }, + stripe_customer_id: { + type: Sequelize.DataTypes.TEXT, + }, + stripe_subscription_id: { + type: Sequelize.DataTypes.TEXT, + }, + current_period_start: { + type: Sequelize.DataTypes.DATE, + }, + current_period_end: { + type: Sequelize.DataTypes.DATE, + }, + cancel_at_period_end: { + type: Sequelize.DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + }, + + async down(queryInterface) { + const transaction = await queryInterface.sequelize.transaction(); + + try { + await queryInterface.dropTable('subscriptions', { transaction }); + await queryInterface.dropTable('subscription_plans', { transaction }); + + if (queryInterface.sequelize.getDialect() === 'postgres') { + await queryInterface.sequelize.query('DROP TYPE IF EXISTS "enum_subscriptions_status";', { + transaction, + }); + } + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + }, +}; diff --git a/backend/src/db/migrations/20260515020000-create-billing-settings.js b/backend/src/db/migrations/20260515020000-create-billing-settings.js new file mode 100644 index 0000000..32605cb --- /dev/null +++ b/backend/src/db/migrations/20260515020000-create-billing-settings.js @@ -0,0 +1,70 @@ +module.exports = { + async up(queryInterface, Sequelize) { + const transaction = await queryInterface.sequelize.transaction(); + + try { + await queryInterface.createTable( + 'billing_settings', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + key: { + type: Sequelize.DataTypes.TEXT, + allowNull: false, + unique: true, + defaultValue: 'default', + }, + stripe_secret_key: { + type: Sequelize.DataTypes.TEXT, + }, + stripe_webhook_secret: { + type: Sequelize.DataTypes.TEXT, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + }, + + async down(queryInterface) { + const transaction = await queryInterface.sequelize.transaction(); + + try { + await queryInterface.dropTable('billing_settings', { transaction }); + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + }, +}; diff --git a/backend/src/db/models/billing_settings.js b/backend/src/db/models/billing_settings.js new file mode 100644 index 0000000..67cd290 --- /dev/null +++ b/backend/src/db/models/billing_settings.js @@ -0,0 +1,46 @@ +module.exports = function (sequelize, DataTypes) { + const billing_settings = sequelize.define( + 'billing_settings', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + key: { + type: DataTypes.TEXT, + allowNull: false, + unique: true, + defaultValue: 'default', + }, + stripe_secret_key: { + type: DataTypes.TEXT, + }, + stripe_webhook_secret: { + type: DataTypes.TEXT, + }, + importHash: { + type: DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + billing_settings.associate = (db) => { + db.billing_settings.belongsTo(db.users, { + as: 'createdBy', + }); + + db.billing_settings.belongsTo(db.users, { + as: 'updatedBy', + }); + }; + + return billing_settings; +}; diff --git a/backend/src/db/models/subscription_plans.js b/backend/src/db/models/subscription_plans.js new file mode 100644 index 0000000..37441b0 --- /dev/null +++ b/backend/src/db/models/subscription_plans.js @@ -0,0 +1,88 @@ +module.exports = function (sequelize, DataTypes) { + const subscription_plans = sequelize.define( + 'subscription_plans', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + name: { + type: DataTypes.TEXT, + }, + slug: { + type: DataTypes.TEXT, + allowNull: false, + unique: true, + }, + description: { + type: DataTypes.TEXT, + }, + price_monthly: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0, + }, + currency: { + type: DataTypes.TEXT, + allowNull: false, + defaultValue: 'usd', + }, + stripe_product_id: { + type: DataTypes.TEXT, + }, + stripe_price_id: { + type: DataTypes.TEXT, + }, + message_limit: { + type: DataTypes.INTEGER, + }, + agent_limit: { + type: DataTypes.INTEGER, + }, + is_featured: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + is_active: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: true, + }, + features_json: { + type: DataTypes.TEXT, + }, + importHash: { + type: DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + subscription_plans.associate = (db) => { + db.subscription_plans.hasMany(db.subscriptions, { + as: 'subscriptions_plan', + foreignKey: { + name: 'planId', + }, + constraints: false, + }); + + db.subscription_plans.belongsTo(db.users, { + as: 'createdBy', + }); + + db.subscription_plans.belongsTo(db.users, { + as: 'updatedBy', + }); + }; + + return subscription_plans; +}; diff --git a/backend/src/db/models/subscriptions.js b/backend/src/db/models/subscriptions.js new file mode 100644 index 0000000..4e82c67 --- /dev/null +++ b/backend/src/db/models/subscriptions.js @@ -0,0 +1,73 @@ +module.exports = function (sequelize, DataTypes) { + const subscriptions = sequelize.define( + 'subscriptions', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + status: { + type: DataTypes.ENUM, + values: ['trialing', 'active', 'canceled', 'past_due'], + allowNull: false, + defaultValue: 'active', + }, + stripe_customer_id: { + type: DataTypes.TEXT, + }, + stripe_subscription_id: { + type: DataTypes.TEXT, + }, + current_period_start: { + type: DataTypes.DATE, + }, + current_period_end: { + type: DataTypes.DATE, + }, + cancel_at_period_end: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + importHash: { + type: DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + subscriptions.associate = (db) => { + db.subscriptions.belongsTo(db.users, { + as: 'user', + foreignKey: { + name: 'userId', + }, + constraints: false, + }); + + db.subscriptions.belongsTo(db.subscription_plans, { + as: 'plan', + foreignKey: { + name: 'planId', + }, + constraints: false, + }); + + db.subscriptions.belongsTo(db.users, { + as: 'createdBy', + }); + + db.subscriptions.belongsTo(db.users, { + as: 'updatedBy', + }); + }; + + return subscriptions; +}; diff --git a/backend/src/db/models/users.js b/backend/src/db/models/users.js index 11ef0d1..3d82ea2 100644 --- a/backend/src/db/models/users.js +++ b/backend/src/db/models/users.js @@ -172,6 +172,14 @@ provider: { constraints: false, }); + db.users.hasMany(db.subscriptions, { + as: 'subscriptions_user', + foreignKey: { + name: 'userId', + }, + constraints: false, + }); + //end loop @@ -252,4 +260,3 @@ function trimStringFields(users) { return users; } - diff --git a/backend/src/db/seeders/20260515010000-subscription-plans.js b/backend/src/db/seeders/20260515010000-subscription-plans.js new file mode 100644 index 0000000..4e75f96 --- /dev/null +++ b/backend/src/db/seeders/20260515010000-subscription-plans.js @@ -0,0 +1,88 @@ +const db = require('../models'); + +const PLAN_DATA = [ + { + name: 'Starter', + slug: 'starter', + description: 'For solo builders who want a clean AI workspace and the basics of billing.', + price_monthly: 19, + currency: 'usd', + stripe_product_id: 'prod_mock_starter', + stripe_price_id: 'price_mock_starter_monthly', + message_limit: 300, + agent_limit: 3, + is_featured: false, + is_active: true, + features_json: JSON.stringify([ + '300 AI messages every month', + '3 custom agents', + 'Conversation history and export', + ]), + }, + { + name: 'Pro', + slug: 'pro', + description: 'For power users who need more throughput, better limits, and a polished workspace.', + price_monthly: 49, + currency: 'usd', + stripe_product_id: 'prod_mock_pro', + stripe_price_id: 'price_mock_pro_monthly', + message_limit: 1500, + agent_limit: 10, + is_featured: true, + is_active: true, + features_json: JSON.stringify([ + '1,500 AI messages every month', + '10 custom agents', + 'Priority billing support', + ]), + }, + { + name: 'Team', + slug: 'team', + description: 'For heavier shared use with room for more agents, more messages, and longer workflows.', + price_monthly: 149, + currency: 'usd', + stripe_product_id: 'prod_mock_team', + stripe_price_id: 'price_mock_team_monthly', + message_limit: 5000, + agent_limit: 30, + is_featured: false, + is_active: true, + features_json: JSON.stringify([ + '5,000 AI messages every month', + '30 custom agents', + 'Shared workspace billing controls', + ]), + }, +]; + +module.exports = { + async up() { + const adminUser = await db.users.findOne({ + order: [['createdAt', 'ASC']], + }); + + for (const plan of PLAN_DATA) { + const [record] = await db.subscription_plans.findOrCreate({ + where: { + slug: plan.slug, + }, + defaults: { + ...plan, + createdById: adminUser ? adminUser.id : null, + updatedById: adminUser ? adminUser.id : null, + }, + }); + + await record.update({ + ...plan, + updatedById: adminUser ? adminUser.id : null, + }); + } + }, + + async down(queryInterface) { + await queryInterface.bulkDelete('subscription_plans', null, {}); + }, +}; diff --git a/backend/src/index.js b/backend/src/index.js index 30d9f39..adc5715 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -36,6 +36,8 @@ const attachmentsRoutes = require('./routes/attachments'); const usage_eventsRoutes = require('./routes/usage_events'); const workspaceRoutes = require('./routes/workspace'); +const billingRoutes = require('./routes/billing'); +const billingWebhookRoutes = require('./routes/billingWebhook'); const getBaseUrl = (url) => { @@ -87,6 +89,7 @@ app.use('/api-docs', function (req, res, next) { app.use(cors({origin: true})); require('./auth/auth'); +app.use('/api/billing/webhook', bodyParser.raw({ type: 'application/json' }), billingWebhookRoutes); app.use(bodyParser.json()); app.use('/api/auth', authRoutes); @@ -111,6 +114,7 @@ app.use('/api/attachments', passport.authenticate('jwt', {session: false}), atta app.use('/api/usage_events', passport.authenticate('jwt', {session: false}), usage_eventsRoutes); app.use('/api/workspace', passport.authenticate('jwt', { session: false }), workspaceRoutes); +app.use('/api/billing', passport.authenticate('jwt', { session: false }), billingRoutes); app.use( '/api/openai', diff --git a/backend/src/routes/billing.js b/backend/src/routes/billing.js new file mode 100644 index 0000000..44b386c --- /dev/null +++ b/backend/src/routes/billing.js @@ -0,0 +1,73 @@ +const express = require('express'); + +const BillingService = require('../services/billing'); +const wrapAsync = require('../helpers').wrapAsync; + +const router = express.Router(); + +router.get( + '/plans', + wrapAsync(async (req, res) => { + const plans = await BillingService.listPlans(); + res.status(200).send({ plans }); + }), +); + +router.get( + '/current', + wrapAsync(async (req, res) => { + const subscription = await BillingService.getCurrentSubscription(req.currentUser); + res.status(200).send({ subscription }); + }), +); + +router.get( + '/stripe-settings', + wrapAsync(async (req, res) => { + const settings = await BillingService.getStripeSettings(req.currentUser, req.get('host')); + res.status(200).send({ settings }); + }), +); + +router.put( + '/stripe-settings', + wrapAsync(async (req, res) => { + const settings = await BillingService.updateStripeSettings( + req.body.data, + req.currentUser, + req.get('host'), + ); + res.status(200).send({ settings }); + }), +); + +router.post( + '/subscribe', + wrapAsync(async (req, res) => { + const checkout = await BillingService.subscribe(req.body.planId, req.currentUser); + res.status(200).send(checkout); + }), +); + +router.get( + '/checkout-session', + wrapAsync(async (req, res) => { + const subscription = await BillingService.confirmCheckoutSession( + req.query.sessionId, + req.currentUser, + ); + res.status(200).send({ subscription }); + }), +); + +router.post( + '/cancel', + wrapAsync(async (req, res) => { + const subscription = await BillingService.cancel(req.currentUser); + res.status(200).send({ subscription }); + }), +); + +router.use('/', require('../helpers').commonErrorHandler); + +module.exports = router; diff --git a/backend/src/routes/billingWebhook.js b/backend/src/routes/billingWebhook.js new file mode 100644 index 0000000..cdbed54 --- /dev/null +++ b/backend/src/routes/billingWebhook.js @@ -0,0 +1,20 @@ +const express = require('express'); +const BillingService = require('../services/billing'); +const wrapAsync = require('../helpers').wrapAsync; + +const router = express.Router(); + +router.post( + '/', + wrapAsync(async (req, res) => { + const result = await BillingService.handleWebhook( + req.body, + req.headers['stripe-signature'], + ); + res.status(200).send(result); + }), +); + +router.use('/', require('../helpers').commonErrorHandler); + +module.exports = router; diff --git a/backend/src/services/billing.js b/backend/src/services/billing.js new file mode 100644 index 0000000..3534518 --- /dev/null +++ b/backend/src/services/billing.js @@ -0,0 +1,857 @@ +const Stripe = require('stripe'); +const db = require('../db/models'); +const config = require('../config'); +const ForbiddenError = require('./notifications/errors/forbidden'); + +const ACTIVE_STATUSES = ['trialing', 'active', 'past_due']; +const Op = db.Sequelize.Op; +const STRIPE_WEBHOOK_EVENTS = [ + 'checkout.session.completed', + 'customer.subscription.created', + 'customer.subscription.updated', + 'customer.subscription.deleted', +]; + +function parseFeatures(featuresJson) { + if (!featuresJson) { + return []; + } + + const parsed = JSON.parse(featuresJson); + + if (Array.isArray(parsed)) { + return parsed; + } + + return []; +} + +function createHttpError(message, code) { + const error = new Error(message); + error.code = code; + return error; +} + +function maskSecret(value) { + if (!value) { + return ''; + } + + if (value.length <= 8) { + return value; + } + + return `${value.slice(0, 4)}••••${value.slice(-4)}`; +} + +async function findStripeSettingsRecord() { + return db.billing_settings.findOne({ + where: { + key: 'default', + }, + }); +} + +async function findOrCreateStripeSettingsRecord(currentUserId) { + const existingRecord = await findStripeSettingsRecord(); + + if (existingRecord) { + return existingRecord; + } + + return db.billing_settings.create({ + createdById: currentUserId || null, + key: 'default', + updatedById: currentUserId || null, + }); +} + +async function getStripeSecretKeyValue() { + const settings = await findStripeSettingsRecord(); + + if (settings && settings.stripe_secret_key) { + return settings.stripe_secret_key; + } + + return process.env.STRIPE_SECRET_KEY || ''; +} + +async function getStripeWebhookSecretValue() { + const settings = await findStripeSettingsRecord(); + + if (settings && settings.stripe_webhook_secret) { + return settings.stripe_webhook_secret; + } + + return process.env.STRIPE_WEBHOOK_SECRET || ''; +} + +async function getStripeClient() { + const secretKey = await getStripeSecretKeyValue(); + + if (!secretKey) { + throw createHttpError('Stripe is not configured. Set STRIPE_SECRET_KEY.', 500); + } + + return new Stripe(secretKey); +} + +function getFrontendBaseUrl() { + if (process.env.FRONTEND_APP_URL) { + return process.env.FRONTEND_APP_URL.replace(/\/$/, ''); + } + + if (process.env.NEXT_PUBLIC_BACK_API) { + return process.env.NEXT_PUBLIC_BACK_API.replace(/\/api\/?$/, ''); + } + + if (process.env.FULL_DOMAIN) { + return `https://${process.env.FULL_DOMAIN.replace(/\/$/, '')}`; + } + + if (process.env.HOST_FQDN) { + return `https://${process.env.HOST_FQDN.replace(/\/$/, '')}`; + } + + if (process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'dev_stage') { + throw createHttpError( + 'Stripe checkout is missing FRONTEND_APP_URL or NEXT_PUBLIC_BACK_API.', + 500, + ); + } + + return 'http://localhost:3000'; +} + +function getStripeWebhookUrl() { + return `${getFrontendBaseUrl()}/api/billing/webhook`; +} + +function isMockStripeId(value) { + if (!value) { + return false; + } + + return String(value).includes('_mock_'); +} + +function normalizeStripeSubscriptionStatus(status) { + if (status === 'trialing') { + return 'trialing'; + } + + if (status === 'active') { + return 'active'; + } + + if (status === 'canceled') { + return 'canceled'; + } + + if (status === 'past_due') { + return 'past_due'; + } + + if (status === 'unpaid') { + return 'past_due'; + } + + if (status === 'incomplete') { + return 'past_due'; + } + + if (status === 'incomplete_expired') { + return 'canceled'; + } + + if (status === 'paused') { + return 'past_due'; + } + + return 'past_due'; +} + +function serializePlan(plan) { + if (!plan) { + return null; + } + + return { + id: plan.id, + name: plan.name, + slug: plan.slug, + description: plan.description, + priceMonthly: plan.price_monthly, + currency: plan.currency, + stripeProductId: plan.stripe_product_id, + stripePriceId: plan.stripe_price_id, + messageLimit: plan.message_limit, + agentLimit: plan.agent_limit, + isFeatured: plan.is_featured, + isActive: plan.is_active, + features: parseFeatures(plan.features_json), + }; +} + +function serializeSubscription(subscription) { + if (!subscription) { + return null; + } + + return { + id: subscription.id, + status: subscription.status, + stripeCustomerId: subscription.stripe_customer_id, + stripeSubscriptionId: subscription.stripe_subscription_id, + currentPeriodStart: subscription.current_period_start, + currentPeriodEnd: subscription.current_period_end, + cancelAtPeriodEnd: subscription.cancel_at_period_end, + plan: serializePlan(subscription.plan), + }; +} + +function getStripeWebhookUrlForDisplay(requestHost) { + if (requestHost) { + const normalizedHost = String(requestHost) + .replace(/^https?:\/\//, '') + .replace(/\/$/, ''); + + return `https://${normalizedHost}/api/billing/webhook`; + } + + try { + return getStripeWebhookUrl(); + } catch (error) { + return '/api/billing/webhook'; + } +} + +function serializeStripeSettings(settings, plans, requestHost) { + const savedSecretKey = settings ? settings.stripe_secret_key || '' : ''; + const savedWebhookSecret = settings ? settings.stripe_webhook_secret || '' : ''; + + return { + plans: plans.map(serializePlan), + stripeSecretKey: savedSecretKey, + stripeSecretKeyPreview: maskSecret(savedSecretKey || process.env.STRIPE_SECRET_KEY || ''), + stripeSecretKeySource: savedSecretKey ? 'saved' : process.env.STRIPE_SECRET_KEY ? 'env' : 'missing', + stripeWebhookSecret: savedWebhookSecret, + stripeWebhookSecretPreview: maskSecret(savedWebhookSecret || process.env.STRIPE_WEBHOOK_SECRET || ''), + stripeWebhookSecretSource: savedWebhookSecret ? 'saved' : process.env.STRIPE_WEBHOOK_SECRET ? 'env' : 'missing', + webhookEndpoint: getStripeWebhookUrlForDisplay(requestHost), + webhookEvents: STRIPE_WEBHOOK_EVENTS, + }; +} + +function getStripePriceIdFromSubscription(stripeSubscription) { + if (!stripeSubscription || !stripeSubscription.items || !Array.isArray(stripeSubscription.items.data)) { + return ''; + } + + for (const item of stripeSubscription.items.data) { + if (item && item.price && item.price.id) { + return item.price.id; + } + } + + return ''; +} + +function getCustomerEmail(checkoutSession) { + if (checkoutSession.customer_details && checkoutSession.customer_details.email) { + return checkoutSession.customer_details.email; + } + + if (checkoutSession.customer_email) { + return checkoutSession.customer_email; + } + + return ''; +} + +module.exports = class BillingService { + static ensureCurrentUser(currentUser) { + if (!currentUser || !currentUser.id) { + throw new ForbiddenError(); + } + } + + static ensureAdministrator(currentUser) { + this.ensureCurrentUser(currentUser); + + if (currentUser.app_role?.name !== config.roles.admin) { + throw new ForbiddenError(); + } + } + + static async listPlans() { + const plans = await db.subscription_plans.findAll({ + where: { + is_active: true, + }, + order: [ + ['price_monthly', 'ASC'], + ['createdAt', 'ASC'], + ], + }); + + return plans.map(serializePlan); + } + + static async getCurrentSubscription(currentUser) { + this.ensureCurrentUser(currentUser); + + const subscription = await this.findVisibleSubscription(currentUser.id); + return serializeSubscription(subscription); + } + + static async getStripeSettings(currentUser, requestHost) { + this.ensureAdministrator(currentUser); + + const settings = await findOrCreateStripeSettingsRecord(currentUser.id); + const plans = await db.subscription_plans.findAll({ + order: [ + ['price_monthly', 'ASC'], + ['createdAt', 'ASC'], + ], + }); + + return serializeStripeSettings(settings, plans, requestHost); + } + + static async updateStripeSettings(data, currentUser, requestHost) { + this.ensureAdministrator(currentUser); + + if (!data || typeof data !== 'object') { + throw createHttpError('Stripe settings payload is required.', 400); + } + + const settings = await findOrCreateStripeSettingsRecord(currentUser.id); + + if (typeof data.stripeSecretKey === 'string') { + settings.stripe_secret_key = data.stripeSecretKey.trim(); + } + + if (typeof data.stripeWebhookSecret === 'string') { + settings.stripe_webhook_secret = data.stripeWebhookSecret.trim(); + } + + settings.updatedById = currentUser.id; + + if (!settings.createdById) { + settings.createdById = currentUser.id; + } + + await settings.save(); + + if (Array.isArray(data.plans)) { + for (const planData of data.plans) { + if (!planData || !planData.id) { + throw createHttpError('Each Stripe plan mapping must include an id.', 400); + } + + const plan = await db.subscription_plans.findOne({ + where: { + id: planData.id, + }, + }); + + if (!plan) { + throw createHttpError(`Subscription plan ${planData.id} was not found.`, 404); + } + + if (typeof planData.stripeProductId === 'string') { + plan.stripe_product_id = planData.stripeProductId.trim(); + } + + if (typeof planData.stripePriceId === 'string') { + plan.stripe_price_id = planData.stripePriceId.trim(); + } + + plan.updatedById = currentUser.id; + await plan.save(); + } + } + + return this.getStripeSettings(currentUser, requestHost); + } + + static async subscribe(planId, currentUser) { + this.ensureCurrentUser(currentUser); + + if (!planId) { + throw createHttpError('Subscription plan is required.', 400); + } + + const plan = await db.subscription_plans.findOne({ + where: { + id: planId, + is_active: true, + }, + }); + + if (!plan) { + throw createHttpError('Subscription plan not found.', 404); + } + + if (!plan.stripe_price_id) { + throw createHttpError('This plan does not have a Stripe price yet.', 400); + } + + if (isMockStripeId(plan.stripe_price_id)) { + throw createHttpError( + 'This plan is still using a mock Stripe price. Replace it with a real Stripe price ID first.', + 400, + ); + } + + const stripe = await getStripeClient(); + const customerId = await this.findOrCreateStripeCustomer(currentUser); + const frontendBaseUrl = getFrontendBaseUrl(); + const successUrl = `${frontendBaseUrl}/billing?checkout=success&session_id={CHECKOUT_SESSION_ID}`; + const cancelUrl = `${frontendBaseUrl}/billing?checkout=canceled`; + const checkoutSession = await stripe.checkout.sessions.create({ + allow_promotion_codes: true, + cancel_url: cancelUrl, + client_reference_id: currentUser.id, + customer: customerId, + line_items: [ + { + price: plan.stripe_price_id, + quantity: 1, + }, + ], + metadata: { + planId: plan.id, + userId: currentUser.id, + }, + mode: 'subscription', + subscription_data: { + metadata: { + planId: plan.id, + userId: currentUser.id, + }, + }, + success_url: successUrl, + }); + + if (!checkoutSession.url) { + throw createHttpError('Stripe did not return a checkout URL.', 500); + } + + return { + checkoutSessionId: checkoutSession.id, + checkoutUrl: checkoutSession.url, + }; + } + + static async confirmCheckoutSession(sessionId, currentUser) { + this.ensureCurrentUser(currentUser); + + if (!sessionId) { + throw createHttpError('Checkout session ID is required.', 400); + } + + const stripe = await getStripeClient(); + const checkoutSession = await stripe.checkout.sessions.retrieve(sessionId); + const sessionUserId = checkoutSession.metadata ? checkoutSession.metadata.userId : null; + const sessionEmail = getCustomerEmail(checkoutSession); + + if ( + checkoutSession.client_reference_id !== currentUser.id && + sessionUserId !== currentUser.id && + sessionEmail !== currentUser.email + ) { + throw new ForbiddenError(); + } + + if (checkoutSession.mode !== 'subscription') { + throw createHttpError('This checkout session does not contain a subscription.', 400); + } + + if (!checkoutSession.subscription) { + return serializeSubscription(await this.findVisibleSubscription(currentUser.id)); + } + + const stripeSubscription = await stripe.subscriptions.retrieve(checkoutSession.subscription); + const subscription = await this.syncStripeSubscription(stripeSubscription); + + return serializeSubscription(subscription); + } + + static async cancel(currentUser) { + this.ensureCurrentUser(currentUser); + + const currentSubscription = await this.findActiveSubscription(currentUser.id); + + if (!currentSubscription) { + throw createHttpError('No active subscription to cancel.', 404); + } + + if ( + !currentSubscription.stripe_subscription_id || + isMockStripeId(currentSubscription.stripe_subscription_id) + ) { + currentSubscription.status = 'canceled'; + currentSubscription.cancel_at_period_end = false; + currentSubscription.current_period_end = new Date(); + currentSubscription.updatedById = currentUser.id; + await currentSubscription.save(); + + const subscription = await this.findVisibleSubscription(currentUser.id); + return serializeSubscription(subscription); + } + + const stripe = await getStripeClient(); + const stripeSubscription = await stripe.subscriptions.update( + currentSubscription.stripe_subscription_id, + { + cancel_at_period_end: true, + }, + ); + const subscription = await this.syncStripeSubscription(stripeSubscription); + + return serializeSubscription(subscription); + } + + static async handleWebhook(rawBody, signature) { + const webhookSecret = await getStripeWebhookSecretValue(); + + if (!webhookSecret) { + throw createHttpError('Stripe webhook is not configured. Set STRIPE_WEBHOOK_SECRET.', 500); + } + + if (!signature) { + throw createHttpError('Stripe-Signature header is required.', 400); + } + + const stripe = await getStripeClient(); + const event = stripe.webhooks.constructEvent( + rawBody, + signature, + webhookSecret, + ); + + if (event.type === 'checkout.session.completed') { + const checkoutSession = event.data.object; + + if (checkoutSession.mode === 'subscription' && checkoutSession.subscription) { + const stripeSubscription = await stripe.subscriptions.retrieve(checkoutSession.subscription); + await this.syncStripeSubscription(stripeSubscription); + } + } + + if ( + event.type === 'customer.subscription.created' || + event.type === 'customer.subscription.updated' || + event.type === 'customer.subscription.deleted' + ) { + await this.syncStripeSubscription(event.data.object); + } + + return { + received: true, + type: event.type, + }; + } + + static async syncStripeSubscription(stripeSubscription) { + const userId = await this.resolveUserId(stripeSubscription); + const plan = await this.resolvePlan(stripeSubscription); + const transaction = await db.sequelize.transaction(); + + try { + let subscription = await this.findSubscriptionByStripeSubscriptionId( + stripeSubscription.id, + transaction, + ); + + if (!subscription && stripeSubscription.customer) { + subscription = await this.findSubscriptionByStripeCustomerId( + String(stripeSubscription.customer), + userId, + transaction, + ); + } + + if (!subscription) { + subscription = db.subscriptions.build({ + createdById: userId, + stripe_customer_id: stripeSubscription.customer ? String(stripeSubscription.customer) : null, + stripe_subscription_id: stripeSubscription.id, + userId, + }); + } + + subscription.userId = userId; + subscription.planId = plan.id; + subscription.status = normalizeStripeSubscriptionStatus(stripeSubscription.status); + subscription.stripe_customer_id = stripeSubscription.customer + ? String(stripeSubscription.customer) + : null; + subscription.stripe_subscription_id = stripeSubscription.id; + subscription.current_period_start = stripeSubscription.current_period_start + ? new Date(stripeSubscription.current_period_start * 1000) + : null; + subscription.current_period_end = stripeSubscription.current_period_end + ? new Date(stripeSubscription.current_period_end * 1000) + : null; + subscription.cancel_at_period_end = Boolean(stripeSubscription.cancel_at_period_end); + subscription.updatedById = userId; + await subscription.save({ transaction }); + + await db.subscriptions.update( + { + status: 'canceled', + updatedById: userId, + }, + { + transaction, + where: { + id: { + [Op.ne]: subscription.id, + }, + status: { + [Op.in]: ACTIVE_STATUSES, + }, + userId, + }, + }, + ); + + const visibleSubscription = await this.findSubscriptionById(subscription.id, transaction); + await transaction.commit(); + + return visibleSubscription; + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async resolveUserId(stripeSubscription) { + if ( + stripeSubscription.metadata && + stripeSubscription.metadata.userId && + typeof stripeSubscription.metadata.userId === 'string' + ) { + return stripeSubscription.metadata.userId; + } + + const existingSubscription = await this.findSubscriptionByStripeSubscriptionId( + stripeSubscription.id, + ); + + if (existingSubscription && existingSubscription.userId) { + return existingSubscription.userId; + } + + if (stripeSubscription.customer) { + const existingCustomerSubscription = await this.findSubscriptionByStripeCustomerId( + String(stripeSubscription.customer), + ); + + if (existingCustomerSubscription && existingCustomerSubscription.userId) { + return existingCustomerSubscription.userId; + } + } + + throw createHttpError( + `Stripe subscription ${stripeSubscription.id} is missing a user mapping.`, + 500, + ); + } + + static async resolvePlan(stripeSubscription) { + if ( + stripeSubscription.metadata && + stripeSubscription.metadata.planId && + typeof stripeSubscription.metadata.planId === 'string' + ) { + const planById = await db.subscription_plans.findOne({ + where: { + id: stripeSubscription.metadata.planId, + }, + }); + + if (planById) { + return planById; + } + } + + const stripePriceId = getStripePriceIdFromSubscription(stripeSubscription); + + if (stripePriceId) { + const planByPrice = await db.subscription_plans.findOne({ + where: { + stripe_price_id: stripePriceId, + }, + }); + + if (planByPrice) { + return planByPrice; + } + } + + const existingSubscription = await this.findSubscriptionByStripeSubscriptionId( + stripeSubscription.id, + ); + + if (existingSubscription && existingSubscription.planId) { + const existingPlan = await db.subscription_plans.findOne({ + where: { + id: existingSubscription.planId, + }, + }); + + if (existingPlan) { + return existingPlan; + } + } + + throw createHttpError( + `Stripe subscription ${stripeSubscription.id} could not be matched to a plan.`, + 500, + ); + } + + static async findOrCreateStripeCustomer(currentUser) { + const existingSubscription = await db.subscriptions.findOne({ + order: [['createdAt', 'DESC']], + where: { + stripe_customer_id: { + [Op.ne]: null, + }, + userId: currentUser.id, + }, + }); + + if ( + existingSubscription && + existingSubscription.stripe_customer_id && + !isMockStripeId(existingSubscription.stripe_customer_id) + ) { + return existingSubscription.stripe_customer_id; + } + + const stripe = await getStripeClient(); + const fullName = [currentUser.firstName, currentUser.lastName].filter(Boolean).join(' ').trim(); + const customer = await stripe.customers.create({ + email: currentUser.email || undefined, + metadata: { + userId: currentUser.id, + }, + name: fullName || undefined, + }); + + return customer.id; + } + + static async findActiveSubscription(userId, transaction) { + return db.subscriptions.findOne({ + where: { + status: { + [Op.in]: ACTIVE_STATUSES, + }, + stripe_subscription_id: { + [Op.notLike]: '%_mock_%', + }, + userId, + }, + include: [ + { + model: db.subscription_plans, + as: 'plan', + }, + ], + order: [['createdAt', 'DESC']], + transaction, + }); + } + + static async findVisibleSubscription(userId, transaction) { + const activeSubscription = await this.findActiveSubscription(userId, transaction); + + if (activeSubscription) { + return activeSubscription; + } + + return db.subscriptions.findOne({ + where: { + stripe_subscription_id: { + [Op.notLike]: '%_mock_%', + }, + userId, + }, + include: [ + { + model: db.subscription_plans, + as: 'plan', + }, + ], + order: [['createdAt', 'DESC']], + transaction, + }); + } + + static async findSubscriptionById(id, transaction) { + return db.subscriptions.findOne({ + where: { + id, + }, + include: [ + { + model: db.subscription_plans, + as: 'plan', + }, + ], + transaction, + }); + } + + static async findSubscriptionByStripeSubscriptionId(stripeSubscriptionId, transaction) { + if (!stripeSubscriptionId) { + return null; + } + + return db.subscriptions.findOne({ + where: { + stripe_subscription_id: stripeSubscriptionId, + }, + include: [ + { + model: db.subscription_plans, + as: 'plan', + }, + ], + order: [['createdAt', 'DESC']], + transaction, + }); + } + + static async findSubscriptionByStripeCustomerId(stripeCustomerId, userId, transaction) { + if (!stripeCustomerId) { + return null; + } + + const where = { + stripe_customer_id: stripeCustomerId, + }; + + if (userId) { + where.userId = userId; + } + + return db.subscriptions.findOne({ + where, + include: [ + { + model: db.subscription_plans, + as: 'plan', + }, + ], + order: [['createdAt', 'DESC']], + transaction, + }); + } +}; diff --git a/backend/src/services/workspace.js b/backend/src/services/workspace.js index 23a1f5e..8a2a2d9 100644 --- a/backend/src/services/workspace.js +++ b/backend/src/services/workspace.js @@ -1,5 +1,10 @@ +const fs = require('fs'); +const path = require('path'); + +const config = require('../config'); const db = require('../db/models'); const { LocalAIApi } = require('../ai/LocalAIApi'); +const AttachmentsDBApi = require('../db/api/attachments'); const ValidationError = require('./notifications/errors/validation'); const { Op } = db.Sequelize; @@ -8,6 +13,10 @@ const DEFAULT_CONVERSATION_TITLE = 'New conversation'; const MAX_MESSAGE_LENGTH = 8000; const MAX_TITLE_LENGTH = 120; const MAX_CONTEXT_MESSAGES = 12; +const MAX_ATTACHMENTS = 5; +const MAX_ATTACHMENT_TEXT_LENGTH = 12000; +const MAX_INLINE_IMAGE_BYTES = 2 * 1024 * 1024; +const IMAGE_INPUT_MODEL = 'gpt-5.2'; function normalizeText(value) { if (typeof value !== 'string') { @@ -26,6 +35,189 @@ function cleanMarkdownPreview(value) { .trim(); } +function normalizeAttachmentText(value) { + const normalized = normalizeText(value); + + if (!normalized) { + return null; + } + + if (normalized.length <= MAX_ATTACHMENT_TEXT_LENGTH) { + return normalized; + } + + return `${normalized.slice(0, MAX_ATTACHMENT_TEXT_LENGTH)}...`; +} + +function normalizeAttachmentsInput(value) { + if (!Array.isArray(value)) { + return []; + } + + return value + .slice(0, MAX_ATTACHMENTS) + .map((item) => { + const kind = item?.kind === 'image' ? 'image' : 'file'; + const filename = normalizeText(item?.filename); + const mimeType = normalizeText(item?.mime_type); + const fileRelation = Array.isArray(item?.file) ? item.file.slice(0, 1) : []; + const imageRelation = Array.isArray(item?.image) ? item.image.slice(0, 1) : []; + const relation = kind === 'image' ? imageRelation : fileRelation; + + if (!filename || relation.length === 0) { + return null; + } + + return { + kind, + filename, + mime_type: mimeType || null, + size_bytes: Number(item?.size_bytes || 0) || null, + storage_key: normalizeText(item?.storage_key) || relation[0]?.privateUrl || null, + notes: normalizeAttachmentText(item?.notes), + file: kind === 'file' ? relation : [], + image: kind === 'image' ? relation : [], + }; + }) + .filter(Boolean); +} + +function buildAttachmentTitleSeed(attachments) { + if (!attachments.length) { + return DEFAULT_CONVERSATION_TITLE; + } + + if (attachments.length === 1) { + return `Review ${attachments[0].filename}`; + } + + return `Review ${attachments.length} attached files`; +} + +function buildAttachmentSummaryForAi(attachment) { + const lines = []; + const typeLabel = attachment.kind === 'image' ? 'image' : 'file'; + const mimeType = attachment.mime_type ? ` (${attachment.mime_type})` : ''; + + lines.push(`Attached ${typeLabel}: ${attachment.filename}${mimeType}`); + + if (attachment.notes) { + lines.push(`Begin attached content: ${attachment.filename}`); + lines.push(attachment.notes); + lines.push(`End attached content: ${attachment.filename}`); + } + + return lines.join('\n'); +} + +async function attachmentImageInputUrl(attachment) { + if (!attachment) { + return null; + } + + const relatedFile = attachment.kind === 'image' + ? Array.isArray(attachment.image) ? attachment.image[0] : null + : Array.isArray(attachment.file) ? attachment.file[0] : null; + + if (relatedFile?.privateUrl) { + const absolutePath = path.join(config.uploadDir, relatedFile.privateUrl); + + try { + const fileBuffer = await fs.promises.readFile(absolutePath); + + if (fileBuffer.length <= MAX_INLINE_IMAGE_BYTES) { + const mimeType = normalizeText(attachment.mime_type) || 'application/octet-stream'; + return `data:${mimeType};base64,${fileBuffer.toString('base64')}`; + } + } catch (error) { + console.error('[workspace] Failed to read image attachment from local storage.', { + attachmentId: attachment.id, + privateUrl: relatedFile.privateUrl, + message: error.message, + }); + } + } + + const publicUrl = normalizeText(relatedFile?.publicUrl); + if (!publicUrl) { + return null; + } + + return publicUrl; +} + +function buildMessageContentForAi(message) { + const content = normalizeText(message.content_markdown || message.content); + const attachments = Array.isArray(message.attachments_message) ? message.attachments_message : []; + + if (!attachments.length) { + return content; + } + + const attachmentSummary = attachments.map(buildAttachmentSummaryForAi).join('\n\n'); + + if (!content) { + return `User attached files without additional text.\n\n${attachmentSummary}`; + } + + return `${content}\n\n${attachmentSummary}`; +} + +async function buildUserMessageContentBlocksForAi(message) { + const blocks = []; + const content = normalizeText(message.content_markdown || message.content); + const attachments = Array.isArray(message.attachments_message) ? message.attachments_message : []; + + if (content) { + blocks.push({ + type: 'input_text', + text: content, + }); + } + + for (const attachment of attachments) { + const summary = buildAttachmentSummaryForAi(attachment); + if (summary) { + blocks.push({ + type: 'input_text', + text: summary, + }); + } + + if (attachment.kind !== 'image') { + continue; + } + + const imageInputUrl = await attachmentImageInputUrl(attachment); + if (!imageInputUrl) { + continue; + } + + blocks.push({ + type: 'input_image', + image_url: imageInputUrl, + }); + } + + if (!blocks.length) { + return null; + } + + return blocks; +} + +function messageHasImageAttachments(message) { + const attachments = Array.isArray(message?.attachments_message) ? message.attachments_message : []; + + for (const attachment of attachments) { + if (attachment?.kind === 'image') { + return true; + } + } + + return false; +} + function buildVisibleAgentWhere(currentUser) { return { is_active: true, @@ -94,7 +286,7 @@ function buildAssistantFailureMessage(errorMessage) { ].join('\n'); } -function buildAiInput(agent, historyMessages) { +async function buildAiInput(agent, historyMessages) { const input = []; const systemPrompt = normalizeText(agent?.system_prompt); const agentDescription = normalizeText(agent?.description); @@ -116,7 +308,20 @@ function buildAiInput(agent, historyMessages) { continue; } - const content = normalizeText(message.content_markdown || message.content); + if (message.role === 'user') { + const contentBlocks = await buildUserMessageContentBlocksForAi(message); + if (!contentBlocks) { + continue; + } + + input.push({ + role: message.role, + content: contentBlocks, + }); + continue; + } + + const content = buildMessageContentForAi(message); if (!content) { continue; } @@ -144,12 +349,29 @@ async function requestAssistantReply(conversationId, assistantMessageId, agent) [Op.in]: ['user', 'assistant', 'system'], }, }, + include: [ + { + model: db.attachments, + as: 'attachments_message', + include: [ + { + model: db.file, + as: 'file', + }, + { + model: db.file, + as: 'image', + }, + ], + }, + ], order: [['sequence', 'DESC']], limit: MAX_CONTEXT_MESSAGES, }); const orderedMessages = [...historyMessages].reverse(); - const input = buildAiInput(agent, orderedMessages); + const input = await buildAiInput(agent, orderedMessages); + const hasImageInput = orderedMessages.some(messageHasImageAttachments); if (input.length === 0) { throw new Error('AI input could not be built from the conversation history.'); @@ -159,7 +381,9 @@ async function requestAssistantReply(conversationId, assistantMessageId, agent) input, }; - if (agent?.model) { + if (hasImageInput) { + payload.model = IMAGE_INPUT_MODEL; + } else if (agent?.model) { payload.model = agent.model; } @@ -211,6 +435,22 @@ async function findLatestUserMessageBeforeSequence(conversationId, sequence, tra [Op.lt]: sequence, }, }, + include: [ + { + model: db.attachments, + as: 'attachments_message', + include: [ + { + model: db.file, + as: 'file', + }, + { + model: db.file, + as: 'image', + }, + ], + }, + ], order: [['sequence', 'DESC']], transaction, }); @@ -417,6 +657,35 @@ function serializeAgent(agent) { }; } +function serializeAttachment(attachment) { + if (!attachment) { + return null; + } + + const relatedFile = attachment.kind === 'image' + ? Array.isArray(attachment.image) ? attachment.image[0] : null + : Array.isArray(attachment.file) ? attachment.file[0] : null; + + return { + id: attachment.id, + kind: attachment.kind, + filename: attachment.filename, + mime_type: attachment.mime_type, + size_bytes: attachment.size_bytes, + storage_key: attachment.storage_key, + notes: attachment.notes, + file: relatedFile + ? { + id: relatedFile.id, + name: relatedFile.name, + sizeInBytes: relatedFile.sizeInBytes, + privateUrl: relatedFile.privateUrl, + publicUrl: relatedFile.publicUrl, + } + : null, + }; +} + function serializeMessage(message) { return { id: message.id, @@ -437,6 +706,11 @@ function serializeMessage(message) { email: message.author_user.email, } : null, + attachments: Array.isArray(message.attachments_message) + ? message.attachments_message + .map(serializeAttachment) + .filter(Boolean) + : [], }; } @@ -600,6 +874,20 @@ module.exports = class WorkspaceService { as: 'author_user', attributes: ['id', 'firstName', 'lastName', 'email'], }, + { + model: db.attachments, + as: 'attachments_message', + include: [ + { + model: db.file, + as: 'file', + }, + { + model: db.file, + as: 'image', + }, + ], + }, ], }, ], @@ -798,8 +1086,9 @@ module.exports = class WorkspaceService { static async sendMessage(id, data, currentUser) { const content = normalizeText(data?.content); + const attachments = normalizeAttachmentsInput(data?.attachments); - if (!content) { + if (!content && !attachments.length) { throw new ValidationError(); } @@ -813,6 +1102,8 @@ module.exports = class WorkspaceService { let userMessageId = null; let agentId = null; let agentModel = null; + let titleSeed = content || buildAttachmentTitleSeed(attachments); + let aiSourceContent = ''; try { const conversation = await findOwnedConversation(id, currentUser, createTransaction); @@ -855,8 +1146,8 @@ module.exports = class WorkspaceService { const userMessage = await db.messages.create( { role: 'user', - content, - content_markdown: content, + content: content || '', + content_markdown: content || '', delivery_status: 'completed', sent_at: sentAt, completed_at: sentAt, @@ -869,6 +1160,26 @@ module.exports = class WorkspaceService { { transaction: createTransaction }, ); + for (const attachment of attachments) { + await AttachmentsDBApi.create( + { + kind: attachment.kind, + filename: attachment.filename, + mime_type: attachment.mime_type, + size_bytes: attachment.size_bytes, + storage_key: attachment.storage_key, + notes: attachment.notes, + message: userMessage.id, + file: attachment.file, + image: attachment.image, + }, + { + currentUser, + transaction: createTransaction, + }, + ); + } + const assistantMessage = await db.messages.create( { role: 'assistant', @@ -895,15 +1206,19 @@ module.exports = class WorkspaceService { { transaction: createTransaction }, ); - const inputTokens = estimateTokens(content); + aiSourceContent = buildMessageContentForAi({ + content_markdown: content || '', + content: content || '', + attachments_message: attachments, + }); await createUsageEvent( { event_type: 'message_sent', occurred_at: sentAt, - input_tokens: inputTokens, + input_tokens: estimateTokens(aiSourceContent), output_tokens: 0, - total_tokens: inputTokens, + total_tokens: estimateTokens(aiSourceContent), cost_usd: 0, provider: 'local-ai', model: agent?.model || 'default-ai-model', @@ -934,10 +1249,10 @@ module.exports = class WorkspaceService { assistantMessageId, conversationId, currentUser, - sourceContent: content, + sourceContent: aiSourceContent, agentId, agentModel, - titleSeed: content, + titleSeed, metadataAction: 'initial', }); @@ -960,6 +1275,7 @@ module.exports = class WorkspaceService { let sourceContent = ''; let agentId = null; let agentModel = null; + let titleSeed = DEFAULT_CONVERSATION_TITLE; try { const conversation = await findOwnedConversation(id, currentUser, transaction); @@ -1019,9 +1335,10 @@ module.exports = class WorkspaceService { conversationId = conversation.id; assistantMessageId = assistantMessage.id; - sourceContent = sourceMessage.content_markdown || sourceMessage.content || ''; + sourceContent = buildMessageContentForAi(sourceMessage); agentId = agent?.id || null; agentModel = agent?.model || 'default-ai-model'; + titleSeed = sourceMessage.content_markdown || sourceMessage.content || buildAttachmentTitleSeed(sourceMessage.attachments_message || []); } catch (error) { await transaction.rollback(); throw error; @@ -1034,7 +1351,7 @@ module.exports = class WorkspaceService { sourceContent, agentId, agentModel, - titleSeed: sourceContent, + titleSeed, metadataAction: 'retry', }); @@ -1057,6 +1374,7 @@ module.exports = class WorkspaceService { let sourceContent = ''; let agentId = null; let agentModel = null; + let titleSeed = DEFAULT_CONVERSATION_TITLE; try { const conversation = await findOwnedConversation(id, currentUser, transaction); @@ -1116,9 +1434,10 @@ module.exports = class WorkspaceService { conversationId = conversation.id; assistantMessageId = assistantMessage.id; - sourceContent = sourceMessage.content_markdown || sourceMessage.content || ''; + sourceContent = buildMessageContentForAi(sourceMessage); agentId = agent?.id || null; agentModel = agent?.model || 'default-ai-model'; + titleSeed = sourceMessage.content_markdown || sourceMessage.content || buildAttachmentTitleSeed(sourceMessage.attachments_message || []); } catch (error) { await transaction.rollback(); throw error; @@ -1131,7 +1450,7 @@ module.exports = class WorkspaceService { sourceContent, agentId, agentModel, - titleSeed: sourceContent, + titleSeed, metadataAction: 'regenerate', }); diff --git a/backend/yarn.lock b/backend/yarn.lock index 222a4f9..31683d8 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -190,6 +190,38 @@ jsonwebtoken "^9.0.0" uuid "^8.3.0" +"@eslint-community/eslint-utils@^4.2.0": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.6.1": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.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" + +"@eslint/js@8.57.1": + version "8.57.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" + integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== + "@google-cloud/paginator@^3.0.7": version "3.0.7" resolved "https://registry.yarnpkg.com/@google-cloud/paginator/-/paginator-3.0.7.tgz#fb6f8e24ec841f99defaebf62c75c2e744dd419b" @@ -237,6 +269,25 @@ uuid "^8.0.0" xdg-basedir "^4.0.0" +"@humanwhocodes/config-array@^0.13.0": + version "0.13.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" + integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== + dependencies: + "@humanwhocodes/object-schema" "^2.0.3" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -274,6 +325,27 @@ semver "^7.3.5" tar "^6.1.11" +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + "@one-ini/wasm@0.1.1": version "0.1.1" resolved "https://registry.yarnpkg.com/@one-ini/wasm/-/wasm-0.1.1.tgz#6013659736c9dbfccc96e8a9c2b3de317df39323" @@ -284,6 +356,11 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@rtsao/scc@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" + integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -313,6 +390,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + "@types/ms@*": version "0.7.34" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" @@ -338,6 +420,11 @@ resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.12.0.tgz#1fe4c3ae9de5cf5193ce64717c99ef2fa7d8756f" integrity sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag== +"@ungap/structured-clone@^1.2.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.1.tgz#0e8f34854df7966b09304a18e808b23997bb9fc1" + integrity sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ== + abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -363,6 +450,16 @@ accepts@^1.3.7, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.16.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -377,6 +474,16 @@ agent-base@^7.0.2, agent-base@^7.1.0: dependencies: debug "^4.3.4" +ajv@^6.12.4: + version "6.15.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.15.0.tgz#07e982c74626167aa7a2495c53817892d7139492" + integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw== + 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" + ansi-align@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" @@ -474,11 +581,66 @@ array-buffer-byte-length@^1.0.1: call-bind "^1.0.5" is-array-buffer "^3.0.4" +array-buffer-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" + integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== + dependencies: + call-bound "^1.0.3" + is-array-buffer "^3.0.5" + array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== +array-includes@^3.1.9: + version "3.1.9" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.9.tgz#1f0ccaa08e90cdbc3eb433210f903ad0f17c3f3a" + integrity sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-abstract "^1.24.0" + es-object-atoms "^1.1.1" + get-intrinsic "^1.3.0" + is-string "^1.1.1" + math-intrinsics "^1.1.0" + +array.prototype.findlastindex@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz#cfa1065c81dcb64e34557c9b81d012f6a421c564" + integrity sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-shim-unscopables "^1.1.0" + +array.prototype.flat@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5" + integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-shim-unscopables "^1.0.2" + +array.prototype.flatmap@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz#712cc792ae70370ae40586264629e33aab5dd38b" + integrity sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-shim-unscopables "^1.0.2" + array.prototype.map@^1.0.1: version "1.0.7" resolved "https://registry.yarnpkg.com/array.prototype.map/-/array.prototype.map-1.0.7.tgz#82fa4d6027272d1fca28a63bbda424d0185d78a7" @@ -505,11 +667,29 @@ arraybuffer.prototype.slice@^1.0.3: is-array-buffer "^3.0.4" is-shared-array-buffer "^1.0.2" +arraybuffer.prototype.slice@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" + integrity sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ== + dependencies: + array-buffer-byte-length "^1.0.1" + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + is-array-buffer "^3.0.4" + arrify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== +async-function@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" + integrity sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA== + async-retry@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280" @@ -699,6 +879,14 @@ cacheable-request@^6.0.0: normalize-url "^4.1.0" responselike "^1.0.2" +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" @@ -710,11 +898,34 @@ call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: get-intrinsic "^1.2.4" set-function-length "^1.2.1" +call-bind@^1.0.8, call-bind@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.9.tgz#39a644700c80bc7d0ca9102fc6d1d43b2fd7eee7" + integrity sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + get-intrinsic "^1.3.0" + set-function-length "^1.2.2" + +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" + call-me-maybe@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa" integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + camelcase@^5.0.0, camelcase@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -766,6 +977,13 @@ chokidar@^3.2.2: optionalDependencies: fsevents "~2.3.2" +chokidar@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" + integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== + dependencies: + readdirp "^4.0.1" + chownr@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" @@ -966,6 +1184,15 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1: shebang-command "^2.0.0" which "^2.0.1" +cross-spawn@^7.0.2: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" @@ -995,6 +1222,15 @@ data-view-buffer@^1.0.1: es-errors "^1.3.0" is-data-view "^1.0.1" +data-view-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz#211a03ba95ecaf7798a8c7198d79536211f88570" + integrity sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + data-view-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz#90721ca95ff280677eb793749fce1011347669e2" @@ -1004,6 +1240,15 @@ data-view-byte-length@^1.0.1: es-errors "^1.3.0" is-data-view "^1.0.1" +data-view-byte-length@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz#9e80f7ca52453ce3e93d25a35318767ea7704735" + integrity sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-data-view "^1.0.2" + data-view-byte-offset@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz#5e0bbfb4828ed2d1b9b400cd8a7d119bca0ff18a" @@ -1013,6 +1258,15 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" +data-view-byte-offset@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz#068307f9b71ab76dbbe10291389e020856606191" + integrity sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-data-view "^1.0.1" + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -1034,13 +1288,20 @@ debug@4.1.1: dependencies: ms "^2.1.1" -debug@^3.2.6: +debug@^3.2.6, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" +debug@^4.3.1, debug@^4.3.2: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -1058,6 +1319,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + defer-to-connect@^1.0.1: version "1.1.3" resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591" @@ -1134,13 +1400,20 @@ diff@4.0.2: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -doctrine@3.0.0: +doctrine@3.0.0, doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + dot-prop@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -1153,6 +1426,15 @@ dottie@^2.0.6: resolved "https://registry.yarnpkg.com/dottie/-/dottie-2.0.6.tgz#34564ebfc6ec5e5772272d466424ad5b696484d4" integrity sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA== +dunder-proto@^1.0.0, dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + duplexer3@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.5.tgz#0b5e4d7bad5de8901ea4440624c8e1d20099217e" @@ -1281,6 +1563,66 @@ es-abstract@^1.17.0-next.1, es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstrac unbox-primitive "^1.0.2" which-typed-array "^1.1.15" +es-abstract@^1.23.5, es-abstract@^1.23.9, es-abstract@^1.24.0: + version "1.24.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.2.tgz#2dbd38c180735ee983f77585140a2706a963ed9a" + integrity sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg== + dependencies: + array-buffer-byte-length "^1.0.2" + arraybuffer.prototype.slice "^1.0.4" + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + data-view-buffer "^1.0.2" + data-view-byte-length "^1.0.2" + data-view-byte-offset "^1.0.1" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + es-set-tostringtag "^2.1.0" + es-to-primitive "^1.3.0" + function.prototype.name "^1.1.8" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" + get-symbol-description "^1.1.0" + globalthis "^1.0.4" + gopd "^1.2.0" + has-property-descriptors "^1.0.2" + has-proto "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + internal-slot "^1.1.0" + is-array-buffer "^3.0.5" + is-callable "^1.2.7" + is-data-view "^1.0.2" + is-negative-zero "^2.0.3" + is-regex "^1.2.1" + is-set "^2.0.3" + is-shared-array-buffer "^1.0.4" + is-string "^1.1.1" + is-typed-array "^1.1.15" + is-weakref "^1.1.1" + math-intrinsics "^1.1.0" + object-inspect "^1.13.4" + object-keys "^1.1.1" + object.assign "^4.1.7" + own-keys "^1.0.1" + regexp.prototype.flags "^1.5.4" + safe-array-concat "^1.1.3" + safe-push-apply "^1.0.0" + safe-regex-test "^1.1.0" + set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" + string.prototype.trim "^1.2.10" + string.prototype.trimend "^1.0.9" + string.prototype.trimstart "^1.0.8" + typed-array-buffer "^1.0.3" + typed-array-byte-length "^1.0.3" + typed-array-byte-offset "^1.0.4" + typed-array-length "^1.0.7" + unbox-primitive "^1.1.0" + which-typed-array "^1.1.19" + es-array-method-boxes-properly@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" @@ -1293,6 +1635,11 @@ es-define-property@^1.0.0: dependencies: get-intrinsic "^1.2.4" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + es-errors@^1.2.1, es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" @@ -1320,6 +1667,13 @@ es-object-atoms@^1.0.0: dependencies: es-errors "^1.3.0" +es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + es-set-tostringtag@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz#8bb60f0a440c2e4281962428438d58545af39777" @@ -1329,6 +1683,23 @@ es-set-tostringtag@^2.0.3: has-tostringtag "^1.0.2" hasown "^2.0.1" +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +es-shim-unscopables@^1.0.2, es-shim-unscopables@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz#438df35520dac5d105f3943d927549ea3b00f4b5" + integrity sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw== + dependencies: + hasown "^2.0.2" + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -1338,6 +1709,15 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es-to-primitive@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz#96c89c82cc49fd8794a24835ba3e1ff87f214e18" + integrity sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g== + dependencies: + is-callable "^1.2.7" + is-date-object "^1.0.5" + is-symbol "^1.0.4" + es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.62, es5-ext@^0.10.64, es5-ext@~0.10.14, es5-ext@~0.10.2: version "0.10.64" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714" @@ -1390,11 +1770,109 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== -escape-string-regexp@4.0.0: +escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +eslint-import-resolver-node@^0.3.9: + version "0.3.10" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz#84ce3005abfc300588cf23bbac1aabec1fc6e8c1" + integrity sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ== + dependencies: + debug "^3.2.7" + is-core-module "^2.16.1" + resolve "^2.0.0-next.6" + +eslint-module-utils@^2.12.1: + version "2.12.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz#f76d3220bfb83c057651359295ab5854eaad75ff" + integrity sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw== + dependencies: + debug "^3.2.7" + +eslint-plugin-import@^2.29.1: + version "2.32.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz#602b55faa6e4caeaa5e970c198b5c00a37708980" + integrity sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA== + dependencies: + "@rtsao/scc" "^1.1.0" + array-includes "^3.1.9" + array.prototype.findlastindex "^1.2.6" + array.prototype.flat "^1.3.3" + array.prototype.flatmap "^1.3.3" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.12.1" + hasown "^2.0.2" + is-core-module "^2.16.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.8" + object.groupby "^1.0.3" + object.values "^1.2.1" + semver "^6.3.1" + string.prototype.trimend "^1.0.9" + tsconfig-paths "^3.15.0" + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.23.1: + version "8.57.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" + integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.1" + "@humanwhocodes/config-array" "^0.13.0" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + 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" + esniff@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308" @@ -1405,11 +1883,39 @@ esniff@^2.0.1: event-emitter "^0.3.5" type "^2.7.2" +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esquery@^1.4.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -1487,11 +1993,40 @@ extend@^3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + fast-text-encoding@^1.0.0: version "1.0.6" resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867" integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w== +fastq@^1.6.0: + version "1.20.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + fill-range@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" @@ -1512,7 +2047,7 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -find-up@5.0.0: +find-up@5.0.0, find-up@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== @@ -1527,6 +2062,15 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + flat@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.1.tgz#a392059cc382881ff98642f5da4dde0a959f309b" @@ -1534,6 +2078,11 @@ flat@^4.1.0: dependencies: is-buffer "~2.0.3" +flatted@^3.2.9: + version "3.4.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== + follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" @@ -1546,6 +2095,13 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +for-each@^0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" + integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== + dependencies: + is-callable "^1.2.7" + foreground-child@^3.1.0: version "3.2.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" @@ -1625,6 +2181,18 @@ function.prototype.name@^1.1.6: es-abstract "^1.22.1" functions-have-names "^1.2.3" +function.prototype.name@^1.1.8: + version "1.1.8" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz#e68e1df7b259a5c949eeef95cdbde53edffabb78" + integrity sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + functions-have-names "^1.2.3" + hasown "^2.0.2" + is-callable "^1.2.7" + functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" @@ -1671,6 +2239,11 @@ generate-function@^2.3.1: dependencies: is-property "^1.0.2" +generator-function@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/generator-function/-/generator-function-2.0.1.tgz#0e75dd410d1243687a0ba2e951b94eedb8f737a2" + integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g== + get-caller-file@^2.0.1, get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -1687,6 +2260,30 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.3, get-intrinsic@ has-symbols "^1.0.3" hasown "^2.0.0" +get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== + dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.1.1" + function-bind "^1.1.2" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" + get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -1710,6 +2307,22 @@ get-symbol-description@^1.0.2: es-errors "^1.3.0" get-intrinsic "^1.2.4" +get-symbol-description@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz#7bdd54e0befe8ffc9f3b4e203220d9f1e881b6ee" + integrity sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + glob-parent@~5.1.0, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -1760,7 +2373,14 @@ global-dirs@^2.0.1: dependencies: ini "1.3.7" -globalthis@^1.0.3: +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.3, globalthis@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" integrity sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== @@ -1797,6 +2417,11 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + got@^9.6.0: version "9.6.0" resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85" @@ -1819,6 +2444,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -1860,11 +2490,23 @@ has-proto@^1.0.1, has-proto@^1.0.3: resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== +has-proto@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.2.0.tgz#5de5a6eabd95fdffd9818b43055e8065e39fe9d5" + integrity sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ== + dependencies: + dunder-proto "^1.0.0" + has-symbols@^1.0.0, has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + has-tostringtag@^1.0.0, has-tostringtag@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" @@ -1894,6 +2536,13 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2: dependencies: function-bind "^1.1.2" +hasown@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.3.tgz#5e5c2b15b60370a4c7930c383dfb76bf17bc403c" + integrity sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg== + dependencies: + function-bind "^1.1.2" + he@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -1977,6 +2626,19 @@ ignore-by-default@^1.0.1: resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +import-fresh@^3.2.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-lazy@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" @@ -2024,6 +2686,15 @@ internal-slot@^1.0.4, internal-slot@^1.0.7: hasown "^2.0.0" side-channel "^1.0.4" +internal-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" + integrity sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw== + dependencies: + es-errors "^1.3.0" + hasown "^2.0.2" + side-channel "^1.1.0" + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -2045,6 +2716,26 @@ is-array-buffer@^3.0.4: call-bind "^1.0.2" get-intrinsic "^1.2.1" +is-array-buffer@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz#65742e1e687bd2cc666253068fd8707fe4d44280" + integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + +is-async-function@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.1.1.tgz#3e69018c8e04e73b738793d020bfe884b9fd3523" + integrity sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ== + dependencies: + async-function "^1.0.0" + call-bound "^1.0.3" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + is-bigint@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" @@ -2052,6 +2743,13 @@ is-bigint@^1.0.1: dependencies: has-bigints "^1.0.1" +is-bigint@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.1.0.tgz#dda7a3445df57a42583db4228682eba7c4170672" + integrity sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ== + dependencies: + has-bigints "^1.0.2" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -2067,6 +2765,14 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-boolean-object@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz#7067f47709809a393c71ff5bb3e135d8a9215d9e" + integrity sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-buffer@~2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" @@ -2091,6 +2797,13 @@ is-core-module@^2.13.0: dependencies: hasown "^2.0.2" +is-core-module@^2.16.1: + version "2.16.2" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.2.tgz#3e07450a8080ebce3fbf0cac494f4d2ab324e082" + integrity sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA== + dependencies: + hasown "^2.0.3" + is-data-view@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.1.tgz#4b4d3a511b70f3dc26d42c03ca9ca515d847759f" @@ -2098,6 +2811,15 @@ is-data-view@^1.0.1: dependencies: is-typed-array "^1.1.13" +is-data-view@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-data-view/-/is-data-view-1.0.2.tgz#bae0a41b9688986c2188dda6657e56b8f9e63b8e" + integrity sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw== + dependencies: + call-bound "^1.0.2" + get-intrinsic "^1.2.6" + is-typed-array "^1.1.13" + is-date-object@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" @@ -2105,6 +2827,14 @@ is-date-object@^1.0.1: dependencies: has-tostringtag "^1.0.0" +is-date-object@^1.0.5, is-date-object@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.1.0.tgz#ad85541996fc7aa8b2729701d27b7319f95d82f7" + integrity sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg== + dependencies: + call-bound "^1.0.2" + has-tostringtag "^1.0.2" + is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" @@ -2115,6 +2845,13 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== +is-finalizationregistry@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz#eefdcdc6c94ddd0674d9c85887bf93f944a97c90" + integrity sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg== + dependencies: + call-bound "^1.0.3" + is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -2125,7 +2862,18 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== -is-glob@^4.0.1, is-glob@~4.0.1: +is-generator-function@^1.0.10: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5" + integrity sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA== + dependencies: + call-bound "^1.0.4" + generator-function "^2.0.0" + get-proto "^1.0.1" + has-tostringtag "^1.0.2" + safe-regex-test "^1.1.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -2140,7 +2888,7 @@ is-installed-globally@^0.3.1: global-dirs "^2.0.1" is-path-inside "^3.0.1" -is-map@^2.0.2: +is-map@^2.0.2, is-map@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== @@ -2162,6 +2910,14 @@ is-number-object@^1.0.4: dependencies: has-tostringtag "^1.0.0" +is-number-object@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" + integrity sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -2172,7 +2928,7 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-path-inside@^3.0.1: +is-path-inside@^3.0.1, is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== @@ -2200,7 +2956,17 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-set@^2.0.2: +is-regex@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" + integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== + dependencies: + call-bound "^1.0.2" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + hasown "^2.0.2" + +is-set@^2.0.2, is-set@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== @@ -2212,6 +2978,13 @@ is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.3: dependencies: call-bind "^1.0.7" +is-shared-array-buffer@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz#9b67844bd9b7f246ba0708c3a93e34269c774f6f" + integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== + dependencies: + call-bound "^1.0.3" + is-stream@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" @@ -2224,6 +2997,14 @@ is-string@^1.0.5, is-string@^1.0.7: dependencies: has-tostringtag "^1.0.0" +is-string@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.1.1.tgz#92ea3f3d5c5b6e039ca8677e5ac8d07ea773cbb9" + integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== + dependencies: + call-bound "^1.0.3" + has-tostringtag "^1.0.2" + is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" @@ -2231,6 +3012,15 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" +is-symbol@^1.0.4, is-symbol@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.1.1.tgz#f47761279f532e2b05a7024a7506dbbedacd0634" + integrity sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w== + dependencies: + call-bound "^1.0.2" + has-symbols "^1.1.0" + safe-regex-test "^1.1.0" + is-typed-array@^1.1.13: version "1.1.13" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.13.tgz#d6c5ca56df62334959322d7d7dd1cca50debe229" @@ -2238,11 +3028,23 @@ is-typed-array@^1.1.13: dependencies: which-typed-array "^1.1.14" +is-typed-array@^1.1.14, is-typed-array@^1.1.15: + version "1.1.15" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.15.tgz#4bfb4a45b61cee83a5a46fba778e4e8d59c0ce0b" + integrity sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ== + dependencies: + which-typed-array "^1.1.16" + is-typedarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== +is-weakmap@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" + integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== + is-weakref@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -2250,6 +3052,21 @@ is-weakref@^1.0.2: dependencies: call-bind "^1.0.2" +is-weakref@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" + integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== + dependencies: + call-bound "^1.0.3" + +is-weakset@^2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.4.tgz#c9f5deb0bc1906c6d6f1027f284ddf459249daca" + integrity sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ== + dependencies: + call-bound "^1.0.3" + get-intrinsic "^1.2.6" + is-wsl@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" @@ -2352,6 +3169,21 @@ json-buffer@3.0.0: resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" integrity sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + json2csv@^5.0.7: version "5.0.7" resolved "https://registry.yarnpkg.com/json2csv/-/json2csv-5.0.7.tgz#f3a583c25abd9804be873e495d1e65ad8d1b54ae" @@ -2361,6 +3193,13 @@ json2csv@^5.0.7: jsonparse "^1.3.1" lodash.get "^4.4.2" +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -2448,6 +3287,13 @@ keyv@^3.0.0: dependencies: json-buffer "3.0.0" +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + latest-version@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" @@ -2455,6 +3301,14 @@ latest-version@^5.0.0: dependencies: package-json "^6.3.0" +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -2510,6 +3364,11 @@ lodash.isstring@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash.mergewith@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" @@ -2578,6 +3437,11 @@ make-dir@^3.0.0, make-dir@^3.1.0: dependencies: semver "^6.0.0" +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -2665,6 +3529,13 @@ minimatch@^3.0.4, minimatch@^3.1.1: dependencies: brace-expansion "^1.1.7" +minimatch@^3.0.5, minimatch@^3.1.2: + version "3.1.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== + dependencies: + brace-expansion "^1.1.7" + minimatch@^9.0.4: version "9.0.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" @@ -2767,7 +3638,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.1.1: +ms@2.1.3, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -2812,6 +3683,11 @@ native-duplexpair@^1.0.0: resolved "https://registry.yarnpkg.com/native-duplexpair/-/native-duplexpair-1.0.0.tgz#7899078e64bf3c8a3d732601b3d40ff05db58fa0" integrity sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA== +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + negotiator@0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" @@ -2827,6 +3703,16 @@ node-addon-api@^5.0.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.1.0.tgz#49da1ca055e109a23d537e9de43c09cca21eb762" integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== +node-exports-info@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/node-exports-info/-/node-exports-info-1.6.0.tgz#1aedafb01a966059c9a5e791a94a94d93f5c2a13" + integrity sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw== + dependencies: + array.prototype.flatmap "^1.3.3" + es-errors "^1.3.0" + object.entries "^1.1.9" + semver "^6.3.1" + node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" @@ -2929,6 +3815,11 @@ object-inspect@^1.13.1: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== +object-inspect@^1.13.3, object-inspect@^1.13.4: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== + object-keys@^1.0.11, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -2954,6 +3845,57 @@ object.assign@^4.1.5: has-symbols "^1.0.3" object-keys "^1.1.1" +object.assign@^4.1.7: + version "4.1.7" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.7.tgz#8c14ca1a424c6a561b0bb2a22f66f5049a945d3d" + integrity sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + has-symbols "^1.1.0" + object-keys "^1.1.1" + +object.entries@^1.1.9: + version "1.1.9" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.9.tgz#e4770a6a1444afb61bd39f984018b5bede25f8b3" + integrity sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.4" + define-properties "^1.2.1" + es-object-atoms "^1.1.1" + +object.fromentries@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + +object.groupby@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + +object.values@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216" + integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.3" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + on-finished@2.4.1, on-finished@^2.3.0: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -2977,6 +3919,27 @@ open@^8.0.0: is-docker "^2.1.1" is-wsl "^2.2.0" +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + 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" + +own-keys@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" + integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg== + dependencies: + get-intrinsic "^1.2.6" + object-keys "^1.1.1" + safe-push-apply "^1.0.0" + p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -3035,6 +3998,13 @@ packet-reader@1.0.0: resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + parseurl@^1.3.3, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -3235,6 +4205,11 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + prepend-http@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" @@ -3306,6 +4281,11 @@ punycode@^1.4.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + pupa@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/pupa/-/pupa-2.1.1.tgz#f5e8fd4afc2c5d97828faa523549ed8744a20d62" @@ -3320,6 +4300,11 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -3395,6 +4380,11 @@ readable-stream@^4.2.0: process "^0.11.10" string_decoder "^1.3.0" +readdirp@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d" + integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== + readdirp@~3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.4.0.tgz#9fdccdf9e9155805449221ac645e8303ab5b9ada" @@ -3409,6 +4399,20 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" +reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: + version "1.0.10" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz#c629219e78a3316d8b604c765ef68996964e7bf9" + integrity sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-abstract "^1.23.9" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.7" + get-proto "^1.0.1" + which-builtin-type "^1.2.1" + regexp.prototype.flags@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" @@ -3419,6 +4423,18 @@ regexp.prototype.flags@^1.5.2: es-errors "^1.3.0" set-function-name "^2.0.1" +regexp.prototype.flags@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" + integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== + dependencies: + call-bind "^1.0.8" + define-properties "^1.2.1" + es-errors "^1.3.0" + get-proto "^1.0.1" + gopd "^1.2.0" + set-function-name "^2.0.2" + registry-auth-token@^4.0.0: version "4.2.2" resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.2.tgz#f02d49c3668884612ca031419491a13539e21fac" @@ -3443,6 +4459,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve@^1.22.1: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" @@ -3452,6 +4473,18 @@ resolve@^1.22.1: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^2.0.0-next.6: + version "2.0.0-next.6" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.6.tgz#b3961812be69ace7b3bc35d5bf259434681294af" + integrity sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA== + dependencies: + es-errors "^1.3.0" + is-core-module "^2.16.1" + node-exports-info "^1.6.0" + object-keys "^1.1.1" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + responselike@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" @@ -3477,6 +4510,11 @@ retry@0.13.1: resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== +reusify@^1.0.4: + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== + rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -3484,6 +4522,13 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + safe-array-concat@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" @@ -3494,6 +4539,17 @@ safe-array-concat@^1.1.2: has-symbols "^1.0.3" isarray "^2.0.5" +safe-array-concat@^1.1.3: + version "1.1.4" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.4.tgz#a54cc9b61a57f33b42abad3cbdda3a2b38cc5719" + integrity sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg== + dependencies: + call-bind "^1.0.9" + call-bound "^1.0.4" + get-intrinsic "^1.3.0" + has-symbols "^1.1.0" + isarray "^2.0.5" + safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -3504,6 +4560,14 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-push-apply@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" + integrity sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA== + dependencies: + es-errors "^1.3.0" + isarray "^2.0.5" + safe-regex-test@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.3.tgz#a5b4c0f06e0ab50ea2c395c14d8371232924c377" @@ -3513,6 +4577,15 @@ safe-regex-test@^1.0.3: es-errors "^1.3.0" is-regex "^1.1.4" +safe-regex-test@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" + integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + is-regex "^1.2.1" + "safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -3530,7 +4603,7 @@ semver@^5.6.0, semver@^5.7.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: +semver@^6.0.0, semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -3631,7 +4704,7 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== -set-function-length@^1.2.1: +set-function-length@^1.2.1, set-function-length@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== @@ -3643,7 +4716,7 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" -set-function-name@^2.0.1: +set-function-name@^2.0.1, set-function-name@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.2.tgz#16a705c5a0dc2f5e638ca96d8a8cd4e1c2b90985" integrity sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ== @@ -3653,6 +4726,15 @@ set-function-name@^2.0.1: functions-have-names "^1.2.3" has-property-descriptors "^1.0.2" +set-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/set-proto/-/set-proto-1.0.0.tgz#0760dbcff30b2d7e801fd6e19983e56da337565e" + integrity sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw== + dependencies: + dunder-proto "^1.0.1" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -3670,6 +4752,35 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== +side-channel-list@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127" + integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.4" + +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + side-channel@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" @@ -3680,6 +4791,17 @@ side-channel@^1.0.4: get-intrinsic "^1.2.4" object-inspect "^1.13.1" +side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -3727,6 +4849,14 @@ stop-iteration-iterator@^1.0.0: dependencies: internal-slot "^1.0.4" +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" + stoppable@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/stoppable/-/stoppable-1.1.0.tgz#32da568e83ea488b08e4d7ea2c3bcc9d75015d5b" @@ -3793,6 +4923,19 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" +string.prototype.trim@^1.2.10: + version "1.2.10" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz#40b2dd5ee94c959b4dcfb1d65ce72e90da480c81" + integrity sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-data-property "^1.1.4" + define-properties "^1.2.1" + es-abstract "^1.23.5" + es-object-atoms "^1.0.0" + has-property-descriptors "^1.0.2" + string.prototype.trim@^1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" @@ -3812,6 +4955,16 @@ string.prototype.trimend@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" +string.prototype.trimend@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942" + integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ== + dependencies: + call-bind "^1.0.8" + call-bound "^1.0.2" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" + string.prototype.trimstart@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" @@ -3875,16 +5028,31 @@ strip-ansi@^7.0.1: dependencies: ansi-regex "^6.0.1" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + strip-json-comments@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== +stripe@^22.1.1: + version "22.1.1" + resolved "https://registry.yarnpkg.com/stripe/-/stripe-22.1.1.tgz#b01f0bb7fa7b1c07e32a726a968eafe73335388d" + integrity sha512-cmodIYP27tBkJ8G7DuGgWw0PFuemlFZbuF3Wwr1TrjFjUa3T7NIgCe6TVwX8BO2ynu+xtTuDGfHafNDCPt9lXA== + stubs@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" @@ -3990,6 +5158,11 @@ term-size@^2.1.0: resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + timers-ext@^0.1.7: version "0.1.8" resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.8.tgz#b4e442f10b7624a29dd2aa42c295e257150cf16c" @@ -4030,11 +5203,33 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + tslib@^2.2.0, tslib@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -4062,6 +5257,15 @@ typed-array-buffer@^1.0.2: es-errors "^1.3.0" is-typed-array "^1.1.13" +typed-array-buffer@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" + integrity sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw== + dependencies: + call-bound "^1.0.3" + es-errors "^1.3.0" + is-typed-array "^1.1.14" + typed-array-byte-length@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz#d92972d3cff99a3fa2e765a28fcdc0f1d89dec67" @@ -4073,6 +5277,17 @@ typed-array-byte-length@^1.0.1: has-proto "^1.0.3" is-typed-array "^1.1.13" +typed-array-byte-length@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz#8407a04f7d78684f3d252aa1a143d2b77b4160ce" + integrity sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg== + dependencies: + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.14" + typed-array-byte-offset@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz#f9ec1acb9259f395093e4567eb3c28a580d02063" @@ -4085,6 +5300,19 @@ typed-array-byte-offset@^1.0.2: has-proto "^1.0.3" is-typed-array "^1.1.13" +typed-array-byte-offset@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz#ae3698b8ec91a8ab945016108aef00d5bff12355" + integrity sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + for-each "^0.3.3" + gopd "^1.2.0" + has-proto "^1.2.0" + is-typed-array "^1.1.15" + reflect.getprototypeof "^1.0.9" + typed-array-length@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" @@ -4097,6 +5325,18 @@ typed-array-length@^1.0.6: is-typed-array "^1.1.13" possible-typed-array-names "^1.0.0" +typed-array-length@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.7.tgz#ee4deff984b64be1e118b0de8c9c877d5ce73d3d" + integrity sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg== + dependencies: + call-bind "^1.0.7" + for-each "^0.3.3" + gopd "^1.0.1" + is-typed-array "^1.1.13" + possible-typed-array-names "^1.0.0" + reflect.getprototypeof "^1.0.6" + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -4131,6 +5371,16 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +unbox-primitive@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz#8d9d2c9edeea8460c7f35033a88867944934d1e2" + integrity sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw== + dependencies: + call-bound "^1.0.3" + has-bigints "^1.0.2" + has-symbols "^1.1.0" + which-boxed-primitive "^1.1.1" + undefsafe@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" @@ -4182,6 +5432,13 @@ update-notifier@^4.1.0: semver-diff "^3.1.1" xdg-basedir "^4.0.0" +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + url-parse-lax@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" @@ -4238,6 +5495,46 @@ which-boxed-primitive@^1.0.2: is-string "^1.0.5" is-symbol "^1.0.3" +which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz#d76ec27df7fa165f18d5808374a5fe23c29b176e" + integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== + dependencies: + is-bigint "^1.1.0" + is-boolean-object "^1.2.1" + is-number-object "^1.1.1" + is-string "^1.1.1" + is-symbol "^1.1.1" + +which-builtin-type@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz#89183da1b4907ab089a6b02029cc5d8d6574270e" + integrity sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q== + dependencies: + call-bound "^1.0.2" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" + is-async-function "^2.0.0" + is-date-object "^1.1.0" + is-finalizationregistry "^1.1.0" + is-generator-function "^1.0.10" + is-regex "^1.2.1" + is-weakref "^1.0.2" + isarray "^2.0.5" + which-boxed-primitive "^1.1.0" + which-collection "^1.0.2" + which-typed-array "^1.1.16" + +which-collection@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" + integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== + dependencies: + is-map "^2.0.3" + is-set "^2.0.3" + is-weakmap "^2.0.2" + is-weakset "^2.0.3" + which-module@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" @@ -4254,6 +5551,19 @@ which-typed-array@^1.1.14, which-typed-array@^1.1.15: gopd "^1.0.1" has-tostringtag "^1.0.2" +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.20" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.20.tgz#3fdb7adfafe0ea69157b1509f3a1cd892bd1d122" + integrity sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg== + dependencies: + available-typed-arrays "^1.0.7" + call-bind "^1.0.8" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" + gopd "^1.2.0" + has-tostringtag "^1.0.2" + which@2.0.2, which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -4289,6 +5599,11 @@ wkx@^0.5.0: dependencies: "@types/node" "*" +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + workerpool@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.0.0.tgz#85aad67fa1a2c8ef9386a1b43539900f61d03d58" diff --git a/frontend/src/components/AdminEntity/PageKit.tsx b/frontend/src/components/AdminEntity/PageKit.tsx new file mode 100644 index 0000000..d3fbb82 --- /dev/null +++ b/frontend/src/components/AdminEntity/PageKit.tsx @@ -0,0 +1,240 @@ +import Link from 'next/link'; +import React from 'react'; + +import BaseIcon from '../BaseIcon'; + +export const actionButtonClassName = + 'inline-flex items-center justify-center rounded-[8px] border px-4 py-2 text-sm font-medium'; + +export const inputClassName = + 'w-full rounded-[10px] border border-slate-200 bg-white px-4 py-3 text-[14px] text-slate-900 outline-none transition-colors placeholder:text-slate-400 focus:border-slate-300'; + +export const textAreaClassName = `${inputClassName} min-h-[132px] resize-y leading-6`; + +export function formatDateTime(value?: string | null) { + if (!value) { + return 'No date'; + } + + const date = new Date(value); + + if (Number.isNaN(date.getTime())) { + return value; + } + + return date.toLocaleString('en-US', { + day: 'numeric', + hour: 'numeric', + minute: '2-digit', + month: 'short', + year: 'numeric', + }); +} + +export function formatShortDate(value?: string | null) { + if (!value) { + return 'No activity yet'; + } + + const date = new Date(value); + + if (Number.isNaN(date.getTime())) { + return value; + } + + return date.toLocaleString('en-US', { + day: 'numeric', + hour: 'numeric', + minute: '2-digit', + month: 'short', + }); +} + +export function formatRole(value?: string | null) { + if (!value) { + return 'Unknown'; + } + + return value + .replace(/_/g, ' ') + .replace(/\b\w/g, (letter) => letter.toUpperCase()); +} + +export function formatName(person: any) { + if (!person) { + return 'No user'; + } + + const fullName = [person.firstName, person.lastName].filter(Boolean).join(' ').trim(); + + if (fullName) { + return fullName; + } + + if (person.email) { + return person.email; + } + + if (person.name) { + return person.name; + } + + if (person.title) { + return person.title; + } + + return 'Unnamed record'; +} + +export function formatMoney(value: any) { + const number = Number(value || 0); + + if (!number) { + return '$0.00'; + } + + return `$${number.toFixed(2)}`; +} + +export function formatTokens(value: any) { + const number = Number(value || 0); + + if (!number) { + return '0'; + } + + return number.toLocaleString('en-US'); +} + +export function EntityIntro({ + backHref, + backLabel, + description, + kicker, + title, +}: { + backHref: string; + backLabel: string; + description: string; + kicker: string; + title: string; +}) { + return ( +
+ + {backLabel} + +

{kicker}

+

{title}

+

{description}

+
+ ); +} + +export function EntitySection({ + children, + description, + icon, + title, +}: { + children: React.ReactNode; + description: string; + icon: string; + title: string; +}) { + return ( +
+
+
+ +
+
+

{title}

+

{description}

+
+
+ {children} +
+ ); +} + +export function EntityValueCard({ + label, + value, +}: { + label: string; + value: React.ReactNode; +}) { + return ( +
+

{label}

+
{value}
+
+ ); +} + +export function EntityLinkCard({ + description, + href, + icon, + label, + value, +}: { + description: string; + href?: string; + icon: string; + label: string; + value: string; +}) { + const content = ( +
+
+
+ +
+
+

{label}

+

{value}

+

{description}

+
+
+
+ ); + + if (!href) { + return content; + } + + return {content}; +} + +export function EntityAsideCard({ + children, + title, +}: { + children: React.ReactNode; + title: string; +}) { + return ( +
+

{title}

+
{children}
+
+ ); +} + +export function EntityEmptyState({ + description, + title, +}: { + description: string; + title: string; +}) { + return ( +
+

No data

+

{title}

+

{description}

+
+ ); +} diff --git a/frontend/src/components/Agents/AgentFormSections.tsx b/frontend/src/components/Agents/AgentFormSections.tsx new file mode 100644 index 0000000..a7089d1 --- /dev/null +++ b/frontend/src/components/Agents/AgentFormSections.tsx @@ -0,0 +1,222 @@ +import React from 'react'; +import { Field } from 'formik'; +import { mdiRobotOutline, mdiTextBoxOutline, mdiTuneVariant } from '@mdi/js'; + +import BaseIcon from '../BaseIcon'; + +const inputClassName = + 'w-full rounded-[10px] border border-slate-200 bg-white px-3.5 py-2.5 text-[14px] text-slate-900 outline-none placeholder:text-slate-400 focus:border-slate-900'; + +const textareaClassName = + 'w-full rounded-[10px] border border-slate-200 bg-white px-3.5 py-3 text-[14px] leading-6 text-slate-900 outline-none placeholder:text-slate-400 focus:border-slate-900'; + +const labelClassName = 'mb-2 block text-[12px] font-medium text-slate-600'; + +function ToggleCard({ + help, + label, + name, + onToggle, + value, +}: { + help: string; + label: string; + name: string; + onToggle: (name: string, value: boolean) => void; + value: boolean; +}) { + return ( + + ); +} + +type Props = { + setFieldValue: (field: string, value: any) => void; + values: any; +}; + +export default function AgentFormSections({ setFieldValue, values }: Props) { + return ( + <> +
+
+
+ +
+
+

Identity

+

+ Give the agent a name, a model, and a short description people can recognize quickly. +

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

Prompting

+

+ Define the standing instructions this agent should follow in every conversation. +

+
+
+ +
+ + +

+ Keep it direct. This is the instruction set the model receives before the user message. +

+
+
+ +
+
+
+ +
+
+

Behavior

+

+ Tune response shape, token budget, and any structured metadata you want to keep with the agent. +

+
+
+ +
+
+ + +

+ Lower is more deterministic. Higher is more exploratory. +

+
+
+ + +

+ Leave blank if you want the model default. +

+
+
+ + +

+ Optional. Use valid JSON if you want to tag the agent with structured metadata. +

+
+
+ +
+ + +
+
+ + ); +} diff --git a/frontend/src/components/Agents/CardAgents.tsx b/frontend/src/components/Agents/CardAgents.tsx index 04b2435..3ce6f56 100644 --- a/frontend/src/components/Agents/CardAgents.tsx +++ b/frontend/src/components/Agents/CardAgents.tsx @@ -1,14 +1,12 @@ import React from 'react'; -import ImageField from '../ImageField'; +import Link from 'next/link'; +import { mdiRobotOutline, mdiTextBoxOutline } from '@mdi/js'; +import BaseIcon from '../BaseIcon'; import ListActionsPopover from '../ListActionsPopover'; import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; import { Pagination } from '../Pagination'; -import {saveFile} from "../../helpers/fileSaver"; -import LoadingSpinner from "../LoadingSpinner"; -import Link from 'next/link'; - -import {hasPermission} from "../../helpers/userPermissions"; +import LoadingSpinner from '../LoadingSpinner'; +import { hasPermission } from '../../helpers/userPermissions'; type Props = { @@ -20,6 +18,61 @@ type Props = { onPageChange: (page: number) => void; }; +function truncateText(value: string, limit: number) { + if (!value) { + return ''; + } + + if (value.length <= limit) { + return value; + } + + return `${value.slice(0, limit).trim()}…`; +} + +function formatTemperature(value: any) { + if (value === null || value === undefined || value === '') { + return 'Auto'; + } + + return value; +} + +function formatTokenLimit(value: any) { + if (!value) { + return 'Default'; + } + + return `${value} max tokens`; +} + +function getMetadataBadges(value: string) { + if (!value) { + return []; + } + + try { + const parsed = JSON.parse(value); + const badges = []; + + if (parsed.surface) { + badges.push(String(parsed.surface)); + } + + if (parsed.role) { + badges.push(String(parsed.role)); + } + + if (parsed.style) { + badges.push(String(parsed.style)); + } + + return badges.slice(0, 2); + } catch (error) { + return []; + } +} + const CardAgents = ({ agents, loading, @@ -28,172 +81,160 @@ const CardAgents = ({ numPages, onPageChange, }: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_AGENTS') - + const currentUser = useAppSelector((state) => state.auth.currentUser); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_AGENTS'); + const hasCreatePermission = hasPermission(currentUser, 'CREATE_AGENTS'); return ( -
+
{loading && }
    - {!loading && agents.map((item, index) => ( + {!loading && agents.map((item) => (
  • - -
    - - - {item.name} - - - -
    - +
    +
    + +
    +
    +
    +
    + + {item.name || 'Untitled agent'} + +

    + {item.model || 'Default model'} +

    +
    +
    + +
    +
    +
    + + {item.is_active ? 'Active' : 'Inactive'} + + + {item.is_default ? 'Default' : 'Custom'} + + {getMetadataBadges(item.metadata_json).map((badge) => ( + + {badge} + + ))} +
    -
    - - -
    -
    Name
    -
    -
    - { item.name } -
    -
    -
    - - - -
    -
    Description
    -
    -
    - { item.description } -
    -
    -
    - +
    +
    +

    + {truncateText( + item.description || item.system_prompt || 'No description yet.', + 180, + )} +

    +
    - - -
    -
    Model
    -
    -
    - { item.model } -
    -
    + {item.system_prompt && ( +
    +
    + + System prompt +
    +

    + {truncateText(item.system_prompt, 220)} +

    - + )} - - -
    -
    Systemprompt
    -
    -
    - { item.system_prompt } -
    -
    +
    +
    +

    + Temperature +

    +

    + {formatTemperature(item.temperature)} +

    - - - - -
    -
    Temperature
    -
    -
    - { item.temperature } -
    -
    +
    +

    + Output +

    +

    + {formatTokenLimit(item.max_output_tokens)} +

    - +
    - - -
    -
    Maxoutputtokens
    -
    -
    - { item.max_output_tokens } -
    -
    -
    - - - - -
    -
    Isdefault
    -
    -
    - { dataFormatter.booleanFormatter(item.is_default) } -
    -
    -
    - - - - -
    -
    Isactive
    -
    -
    - { dataFormatter.booleanFormatter(item.is_active) } -
    -
    -
    - - - - -
    -
    MetadataJSON
    -
    -
    - { item.metadata_json } -
    -
    -
    - - - -
    +
    + + Open + + {hasUpdatePermission && ( + + Edit + + )} +
    +
  • ))} {!loading && agents.length === 0 && ( -
    -

    No data to display

    +
    +

    + No agents yet +

    +

    + Create the first assistant profile for this workspace. +

    +

    + Add a reusable agent preset with a model, prompt, and response settings so the + workspace can start conversations with consistent behavior. +

    + {hasCreatePermission && ( +
    + + New agent + +
    + )}
    )}
-
+
Are you sure you want to delete this item?

- - {dataGrid} + {showGrid ? ( + dataGrid + ) : ( + + )} @@ -461,7 +472,6 @@ const TableSampleAgents = ({ filterItems, setFilterItems, filters, showGrid }) = />, document.getElementById('delete-rows-button'), )} - ) } diff --git a/frontend/src/components/AsideMenu.tsx b/frontend/src/components/AsideMenu.tsx index e53d84c..8dd98ef 100644 --- a/frontend/src/components/AsideMenu.tsx +++ b/frontend/src/components/AsideMenu.tsx @@ -1,18 +1,26 @@ import React from 'react' import { MenuAsideItem } from '../interfaces' import AsideMenuLayer from './AsideMenuLayer' +import OverlayLayer from './OverlayLayer' type Props = { menu: MenuAsideItem[] + isAsideMobileExpanded: boolean + onAsideMobileClose: () => void } export default function AsideMenu({ + isAsideMobileExpanded = false, ...props }: Props) { return ( - + <> + + {isAsideMobileExpanded && } + ) } diff --git a/frontend/src/components/AsideMenuLayer.tsx b/frontend/src/components/AsideMenuLayer.tsx index eb55e27..b08d9d3 100644 --- a/frontend/src/components/AsideMenuLayer.tsx +++ b/frontend/src/components/AsideMenuLayer.tsx @@ -1,4 +1,6 @@ import React from 'react' +import { mdiClose } from '@mdi/js' +import BaseIcon from './BaseIcon' import AsideMenuList from './AsideMenuList' import { MenuAsideItem } from '../interfaces' import { useAppSelector } from '../stores/hooks' @@ -7,17 +9,23 @@ import { useAppSelector } from '../stores/hooks' type Props = { menu: MenuAsideItem[] className?: string + onAsideMobileCloseClick: () => void } -export default function AsideMenuLayer({ menu, className = '' }: Props) { +export default function AsideMenuLayer({ menu, className = '', ...props }: Props) { const asideScrollbarsStyle = useAppSelector((state) => state.style.asideScrollbarsStyle) const darkMode = useAppSelector((state) => state.style.darkMode) + const handleAsideMobileCloseClick = (e: React.MouseEvent) => { + e.preventDefault() + props.onAsideMobileCloseClick() + } + return (
void; +}; + +const primaryButtonClassName = + 'inline-flex h-10 items-center justify-center rounded-[9px] bg-slate-900 px-4 text-sm font-medium text-white'; + +const secondaryButtonClassName = + 'inline-flex h-10 items-center justify-center rounded-[9px] border border-slate-200 bg-white px-4 text-sm font-medium text-slate-700'; + +export default function PricingPlanCard({ + plan, + current = false, + actionDisabled = false, + actionHref, + actionLabel, + actionLoading = false, + caption, + onAction, +}: Props) { + const actionClassName = current || plan.isFeatured ? primaryButtonClassName : secondaryButtonClassName; + + return ( +
+
+
+

+ {plan.name} +

+

+ {formatBillingPrice(plan.priceMonthly, plan.currency)} +

+

+ per month +

+
+ {current && ( + + Current + + )} + {!current && plan.isFeatured && ( + + Popular + + )} +
+ +

+ {plan.description} +

+ +
+
{formatBillingLimit(plan.messageLimit, 'messages / month')}
+
{formatBillingLimit(plan.agentLimit, 'custom agents')}
+
+ +
    + {plan.features.map((feature) => ( +
  • + + {feature} +
  • + ))} +
+ +
+ {actionHref ? ( + + {actionLabel} + + ) : ( + + )} +
+ + {caption && ( +

+ {caption} +

+ )} +
+ ); +} diff --git a/frontend/src/components/DevModeBadge.tsx b/frontend/src/components/DevModeBadge.tsx deleted file mode 100644 index bc238c4..0000000 --- a/frontend/src/components/DevModeBadge.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import useDevCompilationStatus from '../hooks/useDevCompilationStatus'; -const DevModeBadge: React.FC = () => { - const [isVisible, setIsVisible] = useState(false); - const [isCollapsed, setIsCollapsed] = useState(true); - const compilationStatus = useDevCompilationStatus(); - - const [badgeStyles, setBadgeStyles] = useState({ - position: 'fixed', - bottom: '20px', - left: '70px', - background: 'rgba(0, 0, 0, 0.85)', - color: 'white', - padding: '15px', - borderRadius: '8px', - fontFamily: 'sans-serif', - fontSize: '14px', - lineHeight: '1.5', - textAlign: 'left', - zIndex: 2147483647, - boxShadow: '0 4px 10px rgba(0, 0, 0, 0.3)', - whiteSpace: 'pre-wrap', - transition: 'width 0.3s cubic-bezier(0.25, 0.1, 0.25, 1), padding 0.3s ease-in-out, opacity 0.3s ease-in-out, background-color 0.3s ease-in-out', // Improved transition for width - opacity: 0, - pointerEvents: 'none', - width: '340px', - maxWidth: '340px', - height: 'auto', - overflow: 'hidden', - cursor: 'pointer', - }); - - const fullText = `🚧 Your app is running in development mode. -Current request is compiling and may take a few moments. - -💡 Tip: Set up a stable environment to run your app in production mode—pages will load instantly without compilation delays.`; - - const collapsedText = '🚧 DEV stage'; - - useEffect(() => { - if (compilationStatus === 'ready') { - setIsCollapsed(true); - } else { - setIsCollapsed(false); - } - - }, [compilationStatus]); - - useEffect(() => { - if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev_stage') { - setIsVisible(true); - - setBadgeStyles(prev => ({ - ...prev, - opacity: 1, - width: '120px', - maxWidth: '120px', - padding: '6px 10px', - borderRadius: '18px', - whiteSpace: 'nowrap', - fontSize: '12px', - cursor: 'pointer', - pointerEvents: 'auto', - })); - - } else { - setIsVisible(false); - setBadgeStyles(prev => ({ ...prev, opacity: 0 })); - } - }, []); - - useEffect(() => { - if (!isVisible) return; - - if (isCollapsed) { - setBadgeStyles(prev => ({ - ...prev, - width: '140px', - maxWidth: '160px', - padding: '6px 20px', - borderRadius: '18px', - whiteSpace: 'nowrap', - fontSize: '12px', - })); - } else { - setBadgeStyles(prev => ({ - ...prev, - width: '340px', - maxWidth: '340px', - padding: '15px', - borderRadius: '8px', - whiteSpace: 'pre-wrap', - fontSize: '14px', - })); - } - }, [isCollapsed, isVisible]); - - const handleToggleCollapse = (e: React.MouseEvent) => { - e.stopPropagation(); - setIsCollapsed(prev => !prev); - }; - - if (!isVisible) { - return null; - } - - return ( -
- - - {!isCollapsed && ( -
- {fullText} -
- )} - {isCollapsed && ( -
- {collapsedText} -
- )} -
- ); -}; - -export default DevModeBadge; diff --git a/frontend/src/components/ListActionsPopover.tsx b/frontend/src/components/ListActionsPopover.tsx index 0f93245..d009002 100644 --- a/frontend/src/components/ListActionsPopover.tsx +++ b/frontend/src/components/ListActionsPopover.tsx @@ -1,6 +1,5 @@ import React from 'react'; import Link from 'next/link'; -import Button from '@mui/material/Button'; import BaseIcon from './BaseIcon'; import { mdiDotsVertical, @@ -9,7 +8,6 @@ import { mdiTrashCan, } from '@mdi/js'; import Popover from '@mui/material/Popover'; -import { IconButton } from '@mui/material'; type Props = { @@ -31,8 +29,8 @@ const ListActionsPopover = ({ pathEdit, pathView, }: Props) => { - const [anchorEl, setAnchorEl] = React.useState(null); - const handleClick = (event) => { + const [anchorEl, setAnchorEl] = React.useState(null); + const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; const linkView = pathView; @@ -46,20 +44,20 @@ const ListActionsPopover = ({ return ( <> - - + -
- + + View + {hasUpdatePermission && ( - + + Edit + )} {hasUpdatePermission && ( - + + Delete + )}
diff --git a/frontend/src/components/Workspace/WorkspaceShell.tsx b/frontend/src/components/Workspace/WorkspaceShell.tsx index 4a33630..db6331c 100644 --- a/frontend/src/components/Workspace/WorkspaceShell.tsx +++ b/frontend/src/components/Workspace/WorkspaceShell.tsx @@ -5,11 +5,16 @@ import { mdiClose, mdiCogOutline, mdiDeleteOutline, + mdiFileOutline, + mdiImageOutline, mdiMenu, + mdiMicrophoneOutline, mdiOpenInNew, mdiPencilOutline, + mdiPaperclip, mdiPlus, mdiRefresh, + mdiStopCircleOutline, } from '@mdi/js'; import axios from 'axios'; import Link from 'next/link'; @@ -20,6 +25,7 @@ import BaseIcon from '../BaseIcon'; import { ADMIN_ENTRY_PERMISSIONS, hasPermission } from '../../helpers/userPermissions'; import { useAppSelector } from '../../stores/hooks'; import ChatMarkdown from './ChatMarkdown'; +import FileUploader from '../Uploaders/UploadService'; type AgentSummary = { id: string; @@ -29,6 +35,26 @@ type AgentSummary = { is_default?: boolean; }; +type WorkspaceAttachmentFile = { + id?: string; + name: string; + sizeInBytes?: number; + privateUrl?: string; + publicUrl?: string; + new?: boolean; +}; + +type WorkspaceAttachment = { + id: string; + kind: 'file' | 'image'; + filename: string; + mime_type?: string | null; + size_bytes?: number | null; + storage_key?: string | null; + notes?: string | null; + file?: WorkspaceAttachmentFile | null; +}; + type WorkspaceMessage = { id: string; role: 'user' | 'assistant' | 'system' | 'tool'; @@ -41,8 +67,53 @@ type WorkspaceMessage = { sequence?: number; optimistic?: boolean; pending?: boolean; + attachments?: WorkspaceAttachment[]; }; +type ComposerAttachment = { + id: string; + kind: 'file' | 'image'; + filename: string; + mime_type: string; + size_bytes: number; + storage_key: string; + notes?: string | null; + upload: WorkspaceAttachmentFile; +}; + +type SpeechRecognitionAlternativeLike = { + transcript: string; +}; + +type SpeechRecognitionResultLike = { + 0: SpeechRecognitionAlternativeLike; + isFinal: boolean; + length: number; +}; + +type SpeechRecognitionEventLike = { + resultIndex: number; + results: ArrayLike; +}; + +type SpeechRecognitionLike = { + continuous: boolean; + interimResults: boolean; + lang: string; + onend: null | (() => void); + onerror: null | (() => void); + onresult: null | ((event: SpeechRecognitionEventLike) => void); + start: () => void; + stop: () => void; +}; + +declare global { + interface Window { + SpeechRecognition?: new () => SpeechRecognitionLike; + webkitSpeechRecognition?: new () => SpeechRecognitionLike; + } +} + type ConversationSummary = { id: string; title: string; @@ -137,6 +208,19 @@ const NOTES_AGENT_STARTER: AgentStarterConfig = { ], }; +const ATTACHMENT_UPLOAD_PATH = 'messages/attachments'; +const MAX_ATTACHMENTS = 5; +const MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024; +const TEXT_ATTACHMENT_EXTENSIONS = ['txt', 'md', 'markdown', 'json', 'csv', 'tsv', 'log', 'xml', 'yml', 'yaml']; +const TEXT_ATTACHMENT_MIME_PREFIXES = ['text/']; +const TEXT_ATTACHMENT_MIME_TYPES = [ + 'application/json', + 'application/xml', + 'application/javascript', + 'application/x-yaml', +]; +const MAX_ATTACHMENT_TEXT_LENGTH = 12000; + function getAgentStarterConfig(agent?: AgentSummary | null): AgentStarterConfig { const haystack = `${agent?.name || ''} ${agent?.description || ''}`.toLowerCase(); @@ -217,6 +301,64 @@ const formatMessageTime = (value?: string) => { }).format(date); }; +const getFileExtension = (filename: string) => { + const parts = filename.toLowerCase().split('.'); + + if (parts.length < 2) { + return ''; + } + + return parts[parts.length - 1]; +}; + +const canExtractTextFromAttachment = (file: File) => { + const extension = getFileExtension(file.name); + + if (TEXT_ATTACHMENT_EXTENSIONS.includes(extension)) { + return true; + } + + if (TEXT_ATTACHMENT_MIME_PREFIXES.some((prefix) => file.type.startsWith(prefix))) { + return true; + } + + return TEXT_ATTACHMENT_MIME_TYPES.includes(file.type); +}; + +const readAttachmentText = async (file: File) => { + if (!canExtractTextFromAttachment(file)) { + return null; + } + + const text = (await file.text()).trim(); + + if (!text) { + return null; + } + + if (text.length <= MAX_ATTACHMENT_TEXT_LENGTH) { + return text; + } + + return `${text.slice(0, MAX_ATTACHMENT_TEXT_LENGTH)}...`; +}; + +const formatAttachmentSize = (value?: number | null) => { + if (!value) { + return ''; + } + + if (value < 1024) { + return `${value} B`; + } + + if (value < 1024 * 1024) { + return `${(value / 1024).toFixed(1)} KB`; + } + + return `${(value / (1024 * 1024)).toFixed(1)} MB`; +}; + const getErrorMessage = (error: unknown, fallback: string) => { if (axios.isAxiosError(error)) { if (typeof error.response?.data === 'string') { @@ -281,6 +423,116 @@ function TypingIndicator() { ); } +type AttachmentChipProps = { + attachment: { + id: string; + kind: 'file' | 'image'; + filename: string; + file?: WorkspaceAttachmentFile | null; + size_bytes?: number | null; + }; + onRemove?: (id: string) => void; + muted?: boolean; +}; + +function AttachmentChip({ attachment, onRemove, muted = false }: AttachmentChipProps) { + const href = attachment.file?.publicUrl || ''; + const icon = attachment.kind === 'image' ? mdiImageOutline : mdiFileOutline; + const isImagePreview = attachment.kind === 'image' && Boolean(href); + + if (isImagePreview) { + return ( +
+ + {attachment.filename} + +
+
+
+ + Image +
+
+ {attachment.filename} +
+ {attachment.size_bytes ? ( +
+ {formatAttachmentSize(attachment.size_bytes)} +
+ ) : null} +
+ {onRemove ? ( + + ) : null} +
+
+ ); + } + + return ( +
+ + {href ? ( + + {attachment.filename} + + ) : ( + {attachment.filename} + )} + {attachment.size_bytes ? ( + + {formatAttachmentSize(attachment.size_bytes)} + + ) : null} + {onRemove ? ( + + ) : null} +
+ ); +} + type MessageBubbleProps = { currentUserAvatarUrl: string; currentUserInitial: string; @@ -355,6 +607,18 @@ function MessageBubble({ )}
+ {Array.isArray(message.attachments) && message.attachments.length ? ( +
+ {message.attachments.map((attachment) => ( + + ))} +
+ ) : null} + {message.pending ? ( ) : isStreaming ? ( @@ -438,6 +702,8 @@ function ConversationRow({ conversation, isActive, onSelect }: ConversationRowPr export default function WorkspaceShell() { const router = useRouter(); const composerRef = useRef(null); + const fileInputRef = useRef(null); + const recognitionRef = useRef(null); const titleInputRef = useRef(null); const scrollContainerRef = useRef(null); const didBootstrapRef = useRef(false); @@ -452,6 +718,9 @@ export default function WorkspaceShell() { const [loadingBootstrap, setLoadingBootstrap] = useState(true); const [loadingConversation, setLoadingConversation] = useState(false); const [sending, setSending] = useState(false); + const [uploadingAttachment, setUploadingAttachment] = useState(false); + const [speechSupported, setSpeechSupported] = useState(false); + const [isListening, setIsListening] = useState(false); const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [showArchived, setShowArchived] = useState(false); const [isEditingTitle, setIsEditingTitle] = useState(false); @@ -461,6 +730,7 @@ export default function WorkspaceShell() { const [streamingMessageId, setStreamingMessageId] = useState(null); const [streamingText, setStreamingText] = useState(''); const [hasBootstrapped, setHasBootstrapped] = useState(false); + const [composerAttachments, setComposerAttachments] = useState([]); const showSuccessToast = useCallback((message: string) => { toast(message, { @@ -476,6 +746,23 @@ export default function WorkspaceShell() { const currentUserInitial = getUserInitial(currentUser); const canAccessAdmin = hasPermission(currentUser, ADMIN_ENTRY_PERMISSIONS); + useEffect(() => { + if (typeof window === 'undefined') { + return; + } + + if (window.SpeechRecognition || window.webkitSpeechRecognition) { + setSpeechSupported(true); + } + }, []); + + useEffect(() => () => { + if (recognitionRef.current) { + recognitionRef.current.stop(); + recognitionRef.current = null; + } + }, []); + const activeConversations = useMemo( () => conversations.filter((conversation) => conversation.status !== 'archived'), [conversations], @@ -488,6 +775,7 @@ export default function WorkspaceShell() { () => [...(activeConversation?.messages || []), ...optimisticMessages], [activeConversation?.messages, optimisticMessages], ); + const canSubmitMessage = Boolean(composer.trim() || composerAttachments.length); const upsertConversation = useCallback((conversation: ConversationSummary) => { setConversations((previous) => { @@ -500,6 +788,10 @@ export default function WorkspaceShell() { setConversations((previous) => previous.filter((conversation) => conversation.id !== conversationId)); }, []); + const removeComposerAttachment = useCallback((attachmentId: string) => { + setComposerAttachments((previous) => previous.filter((attachment) => attachment.id !== attachmentId)); + }, []); + const applyConversationPayload = useCallback( (conversation: ConversationDetail) => { setActiveConversation(conversation); @@ -800,16 +1092,157 @@ export default function WorkspaceShell() { [applyConversationPayload, router], ); + const handlePickAttachments = useCallback(() => { + fileInputRef.current?.click(); + }, []); + + const handleAttachmentChange = useCallback( + async (event: React.ChangeEvent) => { + const files = Array.from(event.target.files || []); + event.target.value = ''; + + if (!files.length) { + return; + } + + if (composerAttachments.length + files.length > MAX_ATTACHMENTS) { + setNotice({ + type: 'error', + message: `You can attach up to ${MAX_ATTACHMENTS} files per message.`, + }); + return; + } + + setNotice(null); + setUploadingAttachment(true); + + try { + const nextAttachments: ComposerAttachment[] = []; + + for (const file of files) { + const extractedText = await readAttachmentText(file); + const uploadedFile = await FileUploader.upload(ATTACHMENT_UPLOAD_PATH, file, { + size: MAX_ATTACHMENT_SIZE, + }); + + nextAttachments.push({ + id: uploadedFile.id, + kind: file.type.startsWith('image/') ? 'image' : 'file', + filename: uploadedFile.name, + mime_type: file.type || 'application/octet-stream', + size_bytes: uploadedFile.sizeInBytes || file.size, + storage_key: uploadedFile.privateUrl || '', + notes: extractedText, + upload: uploadedFile, + }); + } + + setComposerAttachments((previous) => [...previous, ...nextAttachments]); + } catch (error) { + setNotice({ + type: 'error', + message: getErrorMessage(error, 'Failed to attach the file.'), + }); + } finally { + setUploadingAttachment(false); + } + }, + [composerAttachments.length], + ); + + const stopVoiceCapture = useCallback(() => { + if (recognitionRef.current) { + recognitionRef.current.stop(); + recognitionRef.current = null; + } + + setIsListening(false); + }, []); + + const handleVoiceInput = useCallback(() => { + if (isListening) { + stopVoiceCapture(); + return; + } + + if (typeof window === 'undefined') { + return; + } + + const SpeechRecognitionConstructor = window.SpeechRecognition || window.webkitSpeechRecognition; + + if (!SpeechRecognitionConstructor) { + setNotice({ + type: 'error', + message: 'Voice input is not supported in this browser.', + }); + return; + } + + const recognition = new SpeechRecognitionConstructor(); + const initialValue = composer.trim(); + let lastTranscript = ''; + + recognition.continuous = false; + recognition.interimResults = true; + recognition.lang = 'en-US'; + recognition.onresult = (event) => { + let transcript = ''; + + for (let index = event.resultIndex; index < event.results.length; index += 1) { + transcript += event.results[index][0]?.transcript || ''; + } + + const normalizedTranscript = transcript.trim(); + + if (!normalizedTranscript) { + return; + } + + if (normalizedTranscript === lastTranscript) { + return; + } + + lastTranscript = normalizedTranscript; + + if (!initialValue) { + setComposer(normalizedTranscript); + return; + } + + setComposer(`${initialValue} ${normalizedTranscript}`.trim()); + }; + recognition.onerror = () => { + setNotice({ + type: 'error', + message: 'Voice input failed. Try speaking again or use the keyboard.', + }); + setIsListening(false); + recognitionRef.current = null; + }; + recognition.onend = () => { + setIsListening(false); + recognitionRef.current = null; + }; + + recognitionRef.current = recognition; + setNotice(null); + setIsListening(true); + recognition.start(); + }, [composer, isListening, stopVoiceCapture]); + const handleSendMessage = useCallback(async () => { const messageToSend = composer.trim(); + const attachmentsToSend = composerAttachments; - if (!messageToSend || sending) { + if ((!messageToSend && !attachmentsToSend.length) || sending || uploadingAttachment) { return; } setNotice(null); setSending(true); setComposer(''); + setComposerAttachments([]); let conversation = activeConversation; let createdConversationId: string | null = null; @@ -832,6 +1265,16 @@ export default function WorkspaceShell() { content_markdown: messageToSend, createdAt: new Date().toISOString(), optimistic: true, + attachments: attachmentsToSend.map((attachment) => ({ + id: attachment.id, + kind: attachment.kind, + filename: attachment.filename, + mime_type: attachment.mime_type, + size_bytes: attachment.size_bytes, + storage_key: attachment.storage_key, + notes: attachment.notes, + file: attachment.upload, + })), }, { id: `optimistic-assistant-${optimisticBase}`, @@ -844,6 +1287,16 @@ export default function WorkspaceShell() { const { data } = await axios.post(`/workspace/conversations/${conversation.id}/messages`, { content: messageToSend, + attachments: attachmentsToSend.map((attachment) => ({ + kind: attachment.kind, + filename: attachment.filename, + mime_type: attachment.mime_type, + size_bytes: attachment.size_bytes, + storage_key: attachment.storage_key, + notes: attachment.notes, + file: attachment.kind === 'file' ? [attachment.upload] : [], + image: attachment.kind === 'image' ? [attachment.upload] : [], + })), agentId: conversation.agent?.id || selectedAgentId || undefined, }); @@ -851,6 +1304,7 @@ export default function WorkspaceShell() { } catch (error) { setOptimisticMessages([]); setComposer(messageToSend); + setComposerAttachments(attachmentsToSend); setNotice({ type: 'error', message: getErrorMessage(error, 'Failed to send the message.'), @@ -871,9 +1325,11 @@ export default function WorkspaceShell() { applyConversationResponse, applyConversationPayload, composer, + composerAttachments, router, selectedAgentId, sending, + uploadingAttachment, upsertConversation, ]); @@ -961,6 +1417,14 @@ export default function WorkspaceShell() { activeConversation ? 'h-full min-h-0 overflow-hidden' : 'min-h-[calc(100vh-3rem)]' }`} > +