diff --git a/api/docs/docs.go b/api/docs/docs.go index eb72ac76d..04729b6e2 100644 --- a/api/docs/docs.go +++ b/api/docs/docs.go @@ -1166,6 +1166,13 @@ const docTemplate = `{ "description": "include matching contact details", "name": "contacts", "in": "query" + }, + { + "type": "boolean", + "default": false, + "description": "only include threads with unread messages", + "name": "unread_only", + "in": "query" } ], "responses": { @@ -1203,6 +1210,62 @@ const docTemplate = `{ } }, "/message-threads/{messageThreadID}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns a conversation belonging to the authenticated user, including matching contact details when available.", + "produces": [ + "application/json" + ], + "tags": [ + "MessageThreads" + ], + "summary": "Get a message thread", + "parameters": [ + { + "type": "string", + "description": "ID of the message thread", + "name": "messageThreadID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.MessageThreadResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/responses.NotFound" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, "put": { "security": [ { @@ -4204,6 +4267,7 @@ const docTemplate = `{ "entities.Phone": { "type": "object", "required": [ + "archive_new_outgoing_message_threads", "created_at", "id", "max_send_attempts", @@ -4216,6 +4280,11 @@ const docTemplate = `{ "user_id" ], "properties": { + "archive_new_outgoing_message_threads": { + "description": "ArchiveNewOutgoingMessageThreads archives new conversations started by an outgoing message on this phone.", + "type": "boolean", + "example": false + }, "created_at": { "type": "string", "example": "2022-06-05T14:26:02.302718+03:00" @@ -4975,6 +5044,11 @@ const docTemplate = `{ "unarchive_thread" ], "properties": { + "archive_new_outgoing_message_threads": { + "description": "ArchiveNewOutgoingMessageThreads archives new conversations started by an outgoing message on this phone.", + "type": "boolean", + "example": false + }, "fcm_token": { "description": "FcmToken is either a Firebase registration token or a public HTTPS adapter callback URL.", "type": "string", diff --git a/api/docs/swagger.json b/api/docs/swagger.json index 59c5637ba..a24b2c520 100644 --- a/api/docs/swagger.json +++ b/api/docs/swagger.json @@ -1163,6 +1163,13 @@ "description": "include matching contact details", "name": "contacts", "in": "query" + }, + { + "type": "boolean", + "default": false, + "description": "only include threads with unread messages", + "name": "unread_only", + "in": "query" } ], "responses": { @@ -1200,6 +1207,62 @@ } }, "/message-threads/{messageThreadID}": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Returns a conversation belonging to the authenticated user, including matching contact details when available.", + "produces": [ + "application/json" + ], + "tags": [ + "MessageThreads" + ], + "summary": "Get a message thread", + "parameters": [ + { + "type": "string", + "description": "ID of the message thread", + "name": "messageThreadID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/responses.MessageThreadResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/responses.Unauthorized" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/responses.NotFound" + } + }, + "422": { + "description": "Unprocessable Entity", + "schema": { + "$ref": "#/definitions/responses.UnprocessableEntity" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/responses.InternalServerError" + } + } + } + }, "put": { "security": [ { @@ -4201,6 +4264,7 @@ "entities.Phone": { "type": "object", "required": [ + "archive_new_outgoing_message_threads", "created_at", "id", "max_send_attempts", @@ -4213,6 +4277,11 @@ "user_id" ], "properties": { + "archive_new_outgoing_message_threads": { + "description": "ArchiveNewOutgoingMessageThreads archives new conversations started by an outgoing message on this phone.", + "type": "boolean", + "example": false + }, "created_at": { "type": "string", "example": "2022-06-05T14:26:02.302718+03:00" @@ -4972,6 +5041,11 @@ "unarchive_thread" ], "properties": { + "archive_new_outgoing_message_threads": { + "description": "ArchiveNewOutgoingMessageThreads archives new conversations started by an outgoing message on this phone.", + "type": "boolean", + "example": false + }, "fcm_token": { "description": "FcmToken is either a Firebase registration token or a public HTTPS adapter callback URL.", "type": "string", diff --git a/api/docs/swagger.yaml b/api/docs/swagger.yaml index 6dc240216..838e36738 100644 --- a/api/docs/swagger.yaml +++ b/api/docs/swagger.yaml @@ -408,6 +408,11 @@ definitions: type: object entities.Phone: properties: + archive_new_outgoing_message_threads: + description: ArchiveNewOutgoingMessageThreads archives new conversations started + by an outgoing message on this phone. + example: false + type: boolean created_at: example: "2022-06-05T14:26:02.302718+03:00" type: string @@ -452,6 +457,7 @@ definitions: example: WB7DRDWrJZRGbYrv2CKGkqbzvqdC type: string required: + - archive_new_outgoing_message_threads - created_at - id - max_send_attempts @@ -990,6 +996,11 @@ definitions: type: object requests.PhoneUpsert: properties: + archive_new_outgoing_message_threads: + description: ArchiveNewOutgoingMessageThreads archives new conversations started + by an outgoing message on this phone. + example: false + type: boolean fcm_token: description: FcmToken is either a Firebase registration token or a public HTTPS adapter callback URL. @@ -2510,6 +2521,11 @@ paths: in: query name: contacts type: boolean + - default: false + description: only include threads with unread messages + in: query + name: unread_only + type: boolean produces: - application/json responses: @@ -2583,6 +2599,43 @@ paths: summary: Delete a message thread from the database. tags: - MessageThreads + get: + description: Returns a conversation belonging to the authenticated user, including + matching contact details when available. + parameters: + - description: ID of the message thread + in: path + name: messageThreadID + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/responses.MessageThreadResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/responses.Unauthorized' + "404": + description: Not Found + schema: + $ref: '#/definitions/responses.NotFound' + "422": + description: Unprocessable Entity + schema: + $ref: '#/definitions/responses.UnprocessableEntity' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/responses.InternalServerError' + security: + - ApiKeyAuth: [] + summary: Get a message thread + tags: + - MessageThreads put: consumes: - application/json diff --git a/api/pkg/entities/phone.go b/api/pkg/entities/phone.go index 36be28aeb..ca5deac14 100644 --- a/api/pkg/entities/phone.go +++ b/api/pkg/entities/phone.go @@ -30,6 +30,9 @@ type Phone struct { // UnarchiveThread moves an archived message thread back to the inbox when a new message is received on this phone. UnarchiveThread bool `json:"unarchive_thread" gorm:"default:false" example:"false"` + // ArchiveNewOutgoingMessageThreads archives new conversations started by an outgoing message on this phone. + ArchiveNewOutgoingMessageThreads bool `json:"archive_new_outgoing_message_threads" gorm:"default:false" example:"false"` + CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:02.302718+03:00"` UpdatedAt time.Time `json:"updated_at" example:"2022-06-05T14:26:10.303278+03:00"` } diff --git a/api/pkg/handlers/message_thread_handler.go b/api/pkg/handlers/message_thread_handler.go index 72e0b543d..0225ee894 100644 --- a/api/pkg/handlers/message_thread_handler.go +++ b/api/pkg/handlers/message_thread_handler.go @@ -42,6 +42,7 @@ func NewMessageThreadHandler( // RegisterRoutes registers the routes for the MessageHandler func (h *MessageThreadHandler) RegisterRoutes(router fiber.Router, middlewares ...fiber.Handler) { h.register(router, fiber.MethodGet, "/v1/message-threads", middlewares, h.Index) + h.register(router, fiber.MethodGet, "/v1/message-threads/:messageThreadID", middlewares, h.Get) h.register(router, fiber.MethodPut, "/v1/message-threads/:messageThreadID", middlewares, h.Update) h.register(router, fiber.MethodDelete, "/v1/message-threads/:messageThreadID", middlewares, h.Delete) } @@ -58,6 +59,7 @@ func (h *MessageThreadHandler) RegisterRoutes(router fiber.Router, middlewares . // @Param query query string false "filter message threads containing query" // @Param limit query int false "number of messages to return" minimum(1) maximum(20) // @Param contacts query bool false "include matching contact details" +// @Param unread_only query bool false "only include threads with unread messages" default(false) // @Success 200 {object} responses.MessageThreadsResponse // @Failure 400 {object} responses.BadRequest // @Failure 401 {object} responses.Unauthorized @@ -92,6 +94,39 @@ func (h *MessageThreadHandler) Index(c fiber.Ctx) error { return h.responseOK(c, fmt.Sprintf("fetched %d message %s", len(*threads), h.pluralize("thread", len(*threads))), threads) } +// Get returns a single entities.MessageThread independently of list filters and pagination. +// @Summary Get a message thread +// @Description Returns a conversation belonging to the authenticated user, including matching contact details when available. +// @Security ApiKeyAuth +// @Tags MessageThreads +// @Produce json +// @Param messageThreadID path string true "ID of the message thread" +// @Success 200 {object} responses.MessageThreadResponse +// @Failure 401 {object} responses.Unauthorized +// @Failure 404 {object} responses.NotFound +// @Failure 422 {object} responses.UnprocessableEntity +// @Failure 500 {object} responses.InternalServerError +// @Router /message-threads/{messageThreadID} [get] +func (h *MessageThreadHandler) Get(c fiber.Ctx) error { + ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger) + defer span.End() + + id := c.Params("messageThreadID") + if errors := h.validator.ValidateUUID(id, "messageThreadID"); len(errors) != 0 { + ctxLogger.Warn(stacktrace.NewErrorf("invalid message thread ID [%s]", id)) + return h.responseUnprocessableEntity(c, errors, "validation errors while fetching a message thread") + } + thread, err := h.service.GetThread(ctx, h.userIDFomContext(c), uuid.MustParse(id), true) + if stacktrace.GetCode(err) == repositories.ErrCodeNotFound { + return h.responseNotFound(c, fmt.Sprintf("cannot find message thread with ID [%s]", id)) + } + if err != nil { + ctxLogger.Error(stacktrace.Propagatef(err, "cannot fetch message thread [%s]", id)) + return h.responseInternalServerError(c) + } + return h.responseOK(c, "message thread fetched successfully", thread) +} + // Update an entities.MessageThread // @Summary Update a message thread // @Description Updates the details of a message thread @@ -161,7 +196,7 @@ func (h *MessageThreadHandler) Delete(c fiber.Ctx) error { return h.responseUnprocessableEntity(c, errors, "validation errors while deleting a thread thread") } - thread, err := h.service.GetThread(ctx, h.userIDFomContext(c), uuid.MustParse(messageThreadID)) + thread, err := h.service.GetThread(ctx, h.userIDFomContext(c), uuid.MustParse(messageThreadID), false) if stacktrace.GetCode(err) == repositories.ErrCodeNotFound { return h.responseNotFound(c, fmt.Sprintf("cannot find thread thread with ID [%s]", messageThreadID)) } diff --git a/api/pkg/handlers/message_thread_handler_contacts_test.go b/api/pkg/handlers/message_thread_handler_contacts_test.go index f6681fbd7..d6c74770e 100644 --- a/api/pkg/handlers/message_thread_handler_contacts_test.go +++ b/api/pkg/handlers/message_thread_handler_contacts_test.go @@ -3,6 +3,7 @@ package handlers import ( "context" "encoding/json" + "fmt" "net/http" "net/http/httptest" "testing" @@ -22,12 +23,18 @@ import ( type messageThreadHandlerIndexRepositoryStub struct { repositories.MessageThreadRepository - threads []entities.MessageThread + threads []entities.MessageThread + unreadOnly bool + limit int } -func (stub *messageThreadHandlerIndexRepositoryStub) Index(context.Context, entities.UserID, string, bool, repositories.IndexParams) (*[]entities.MessageThread, error) { +func (stub *messageThreadHandlerIndexRepositoryStub) Index(_ context.Context, _ entities.UserID, _ string, _ bool, unreadOnly bool, params repositories.IndexParams) (*[]entities.MessageThread, error) { + stub.unreadOnly = unreadOnly + stub.limit = params.Limit threads := make([]entities.MessageThread, len(stub.threads)) copy(threads, stub.threads) + start := min(params.Skip, len(threads)) + threads = threads[start:min(start+params.Limit, len(threads))] return &threads, nil } @@ -41,7 +48,7 @@ func (stub *messageThreadHandlerContactProviderStub) GetContactMap(context.Conte return stub.contacts, nil } -func TestMessageThreadHandlerIndex_ParsesContactsQuery(t *testing.T) { +func TestMessageThreadHandlerIndex_ParsesContactsAndUnreadQuery(t *testing.T) { contact := &entities.Contact{ID: uuid.New(), Name: "Alice", PhoneNumbers: []string{"+18005550100"}} repository := &messageThreadHandlerIndexRepositoryStub{threads: []entities.MessageThread{{Contact: "+18005550100"}}} provider := &messageThreadHandlerContactProviderStub{contacts: map[string]*entities.Contact{"+18005550100": contact}} @@ -57,19 +64,83 @@ func TestMessageThreadHandlerIndex_ParsesContactsQuery(t *testing.T) { }) handler.RegisterRoutes(app) - req := httptest.NewRequest(http.MethodGet, "/v1/message-threads?owner=%2B18005550199&contacts=true", nil) + req := httptest.NewRequest(http.MethodGet, "/v1/message-threads?owner=%2B18005550199&contacts=true&unread_only=true", nil) resp, err := app.Test(req, fiber.TestConfig{Timeout: time.Second}) require.NoError(t, err) require.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, 1, provider.calls) + assert.True(t, repository.unreadOnly) var payload struct { Data []entities.MessageThread `json:"data"` } + require.NoError(t, json.NewDecoder(resp.Body).Decode(&payload)) require.Len(t, payload.Data, 1) require.NotNil(t, payload.Data[0].ContactDetails) assert.Equal(t, contact.ID, payload.Data[0].ContactDetails.ID) assert.Equal(t, "Alice", payload.Data[0].ContactDetails.Name) } + +func TestMessageThreadHandlerIndexRejectsInvalidUnreadFilter(t *testing.T) { + repository := &messageThreadHandlerIndexRepositoryStub{} + logger := &messageThreadHandlerNoopLogger{} + tracer := telemetry.NewOtelLogger("test", logger) + service := services.NewMessageThreadService(logger, tracer, repository, nil, nil, nil) + handler := NewMessageThreadHandler(logger, tracer, validators.NewMessageThreadHandlerValidator(logger, tracer), service) + app := fiber.New() + app.Use(func(c fiber.Ctx) error { + c.Locals(middlewares.ContextKeyAuthUserID, entities.AuthContext{ID: entities.UserID("user-id"), Email: "user@example.com"}) + return c.Next() + }) + handler.RegisterRoutes(app) + + response, err := app.Test(httptest.NewRequest(http.MethodGet, "/v1/message-threads?owner=%2B18005550199&unread_only=invalid", nil)) + + require.NoError(t, err) + defer response.Body.Close() + assert.Equal(t, http.StatusUnprocessableEntity, response.StatusCode) +} + +func TestMessageThreadIndexUsesRequestedLimitWithoutLookAhead(t *testing.T) { + for _, test := range []struct { + name string + count int + wantLen int + limit int + }{ + {name: "empty", count: 0, wantLen: 0, limit: 20}, + {name: "partial page", count: 19, wantLen: 19, limit: 20}, + {name: "exactly one page", count: 20, wantLen: 20, limit: 20}, + {name: "another page exists", count: 21, wantLen: 20, limit: 20}, + {name: "custom limit", count: 4, wantLen: 3, limit: 3}, + } { + t.Run(test.name, func(t *testing.T) { + repository := &messageThreadHandlerIndexRepositoryStub{threads: make([]entities.MessageThread, test.count)} + logger := &messageThreadHandlerNoopLogger{} + tracer := telemetry.NewOtelLogger("test", logger) + service := services.NewMessageThreadService(logger, tracer, repository, nil, nil, nil) + handler := NewMessageThreadHandler(logger, tracer, validators.NewMessageThreadHandlerValidator(logger, tracer), service) + app := fiber.New() + app.Use(func(c fiber.Ctx) error { + c.Locals(middlewares.ContextKeyAuthUserID, entities.AuthContext{ID: entities.UserID("user-id"), Email: "user@example.com"}) + return c.Next() + }) + handler.RegisterRoutes(app) + + response, err := app.Test(httptest.NewRequest(http.MethodGet, fmt.Sprintf("/v1/message-threads?owner=%%2B18005550199&limit=%d", test.limit), nil)) + + require.NoError(t, err) + defer response.Body.Close() + require.Equal(t, http.StatusOK, response.StatusCode) + var body map[string]json.RawMessage + require.NoError(t, json.NewDecoder(response.Body).Decode(&body)) + var threads []entities.MessageThread + require.NoError(t, json.Unmarshal(body["data"], &threads)) + assert.Len(t, threads, test.wantLen) + assert.NotContains(t, body, "has_more") + assert.Equal(t, test.limit, repository.limit) + }) + } +} diff --git a/api/pkg/handlers/message_thread_handler_test.go b/api/pkg/handlers/message_thread_handler_test.go index a304f4ad3..e545adc31 100644 --- a/api/pkg/handlers/message_thread_handler_test.go +++ b/api/pkg/handlers/message_thread_handler_test.go @@ -23,7 +23,9 @@ import ( "gorm.io/gorm" ) -type messageThreadHandlerRepositoryStub struct{} +type messageThreadHandlerRepositoryStub struct { + load func(context.Context, entities.UserID, uuid.UUID) (*entities.MessageThread, error) +} func (stub *messageThreadHandlerRepositoryStub) Store(context.Context, *entities.MessageThread) error { return nil @@ -41,11 +43,14 @@ func (stub *messageThreadHandlerRepositoryStub) LoadByOwnerContact(context.Conte return nil, nil } -func (stub *messageThreadHandlerRepositoryStub) Load(context.Context, entities.UserID, uuid.UUID) (*entities.MessageThread, error) { +func (stub *messageThreadHandlerRepositoryStub) Load(ctx context.Context, userID entities.UserID, id uuid.UUID) (*entities.MessageThread, error) { + if stub.load != nil { + return stub.load(ctx, userID, id) + } return nil, nil } -func (stub *messageThreadHandlerRepositoryStub) Index(context.Context, entities.UserID, string, bool, repositories.IndexParams) (*[]entities.MessageThread, error) { +func (stub *messageThreadHandlerRepositoryStub) Index(context.Context, entities.UserID, string, bool, bool, repositories.IndexParams) (*[]entities.MessageThread, error) { return nil, nil } @@ -90,6 +95,67 @@ func TestMessageThreadHandlerUpdate_ReturnsNotFoundWhenThreadIsMissing(t *testin require.Equal(t, "cannot find message thread with ID ["+messageThreadID.String()+"]", payload.Message) } +func TestMessageThreadHandlerGetLoadsIndividualUserScopedThread(t *testing.T) { + for _, test := range []struct { + name string + status int + }{ + {name: "existing", status: http.StatusOK}, + {name: "missing", status: http.StatusNotFound}, + {name: "another user", status: http.StatusNotFound}, + {name: "invalid UUID", status: http.StatusUnprocessableEntity}, + {name: "repository failure", status: http.StatusInternalServerError}, + } { + t.Run(test.name, func(t *testing.T) { + id := uuid.New() + contact := &entities.Contact{ID: uuid.New(), Name: "Alice", PhoneNumbers: []string{"+18005550100"}} + repository := &messageThreadHandlerRepositoryStub{ + load: func(_ context.Context, userID entities.UserID, threadID uuid.UUID) (*entities.MessageThread, error) { + require.Equal(t, entities.UserID("user-id"), userID) + require.Equal(t, id, threadID) + if test.name == "missing" || test.name == "another user" { + return nil, stacktrace.PropagateWithCodef(gorm.ErrRecordNotFound, repositories.ErrCodeNotFound, "not found") + } + if test.name == "repository failure" { + return nil, stacktrace.NewError("database unavailable") + } + return &entities.MessageThread{ID: id, UserID: userID, Owner: "+18005550199", Contact: "+18005550100"}, nil + }, + } + logger := &messageThreadHandlerNoopLogger{} + tracer := telemetry.NewOtelLogger("test", logger) + provider := &messageThreadHandlerContactProviderStub{contacts: map[string]*entities.Contact{"+18005550100": contact}} + service := services.NewMessageThreadService(logger, tracer, repository, nil, nil, provider) + handler := NewMessageThreadHandler(logger, tracer, validators.NewMessageThreadHandlerValidator(logger, tracer), service) + app := fiber.New() + app.Use(func(c fiber.Ctx) error { + c.Locals(middlewares.ContextKeyAuthUserID, entities.AuthContext{ID: entities.UserID("user-id"), Email: "user@example.com"}) + return c.Next() + }) + handler.RegisterRoutes(app) + pathID := id.String() + if test.name == "invalid UUID" { + pathID = "invalid" + } + + response, err := app.Test(httptest.NewRequest(http.MethodGet, "/v1/message-threads/"+pathID, nil)) + + require.NoError(t, err) + defer response.Body.Close() + require.Equal(t, test.status, response.StatusCode) + if test.status == http.StatusOK { + var payload struct { + Data entities.MessageThread `json:"data"` + } + require.NoError(t, json.NewDecoder(response.Body).Decode(&payload)) + require.Equal(t, id, payload.Data.ID) + require.NotNil(t, payload.Data.ContactDetails) + require.Equal(t, "Alice", payload.Data.ContactDetails.Name) + } + }) + } +} + type messageThreadHandlerNoopLogger struct{} var _ telemetry.Logger = (*messageThreadHandlerNoopLogger)(nil) diff --git a/api/pkg/listeners/read_receipts_test_helpers_test.go b/api/pkg/listeners/read_receipts_test_helpers_test.go index 60cd27e65..3227685c5 100644 --- a/api/pkg/listeners/read_receipts_test_helpers_test.go +++ b/api/pkg/listeners/read_receipts_test_helpers_test.go @@ -52,7 +52,7 @@ func (repository *listenerMessageThreadRepository) Load(context.Context, entitie return &entities.MessageThread{}, nil } -func (repository *listenerMessageThreadRepository) Index(context.Context, entities.UserID, string, bool, repositories.IndexParams) (*[]entities.MessageThread, error) { +func (repository *listenerMessageThreadRepository) Index(context.Context, entities.UserID, string, bool, bool, repositories.IndexParams) (*[]entities.MessageThread, error) { threads := []entities.MessageThread{} return &threads, nil } diff --git a/api/pkg/repositories/gorm_message_repository.go b/api/pkg/repositories/gorm_message_repository.go index a4bf309f5..b6fdf9586 100644 --- a/api/pkg/repositories/gorm_message_repository.go +++ b/api/pkg/repositories/gorm_message_repository.go @@ -234,13 +234,32 @@ func (repository *gormMessageRepository) Update(ctx context.Context, message *en ctx, span := repository.tracer.Start(ctx) defer span.End() - if err := repository.db.WithContext(ctx).Save(message).Error; err != nil { + // Send attempts are owned by the atomic increment, not a loaded status snapshot. + if err := repository.db.WithContext(ctx).Omit("send_attempt_count").Save(message).Error; err != nil { return repository.tracer.WrapErrorSpan(span, stacktrace.Propagatef(err, "cannot update message with ID [%s]", message.ID)) } return nil } +func (repository *gormMessageRepository) IncreaseSendAttemptCount(ctx context.Context, userID entities.UserID, messageID uuid.UUID) error { + ctx, span := repository.tracer.Start(ctx) + defer span.End() + + result := repository.db.WithContext(ctx). + Model(&entities.Message{}). + Where("user_id = ?", userID). + Where("id = ?", messageID). + Update("send_attempt_count", gorm.Expr("send_attempt_count + ?", 1)) + if result.Error != nil { + return repository.tracer.WrapErrorSpan(span, stacktrace.Propagatef(result.Error, "cannot increment send attempts for message [%s] for user [%s]", messageID, userID)) + } + if result.RowsAffected == 0 { + return repository.tracer.WrapErrorSpan(span, stacktrace.PropagateWithCodef(gorm.ErrRecordNotFound, ErrCodeNotFound, "message [%s] for user [%s] does not exist", messageID, userID)) + } + return nil +} + // GetOutstanding fetches messages that still to be sent to the phone func (repository *gormMessageRepository) GetOutstanding(ctx context.Context, userID entities.UserID, messageID uuid.UUID, phoneNumbers []string) (*entities.Message, error) { ctx, span := repository.tracer.Start(ctx) diff --git a/api/pkg/repositories/gorm_message_repository_test.go b/api/pkg/repositories/gorm_message_repository_test.go new file mode 100644 index 000000000..11dc28b7e --- /dev/null +++ b/api/pkg/repositories/gorm_message_repository_test.go @@ -0,0 +1,55 @@ +package repositories + +import ( + "context" + "strings" + "testing" + + "github.com/NdoleStudio/httpsms/pkg/entities" + "github.com/NdoleStudio/httpsms/pkg/telemetry" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +func TestMessageStatusUpdatesPreserveConcurrentSendAttemptCounts(t *testing.T) { + pool := &messageThreadTestConnPool{} + db, err := gorm.Open(postgres.New(postgres.Config{Conn: pool, WithoutReturning: true}), &gorm.Config{DisableAutomaticPing: true}) + require.NoError(t, err) + logger := &messageThreadTestLogger{} + repository := NewGormMessageRepository(logger, telemetry.NewOtelLogger("test", logger), db) + + err = repository.Update(context.Background(), &entities.Message{ + ID: uuid.New(), + UserID: entities.UserID("user-id"), + Status: entities.MessageStatusScheduled, + SendAttemptCount: 0, + }) + + require.NoError(t, err) + require.NotEmpty(t, pool.statements) + update := pool.statements[len(pool.statements)-1] + assert.True(t, strings.HasPrefix(update.query, `UPDATE "messages"`)) + assert.Contains(t, update.query, `"status"=`) + assert.NotContains(t, update.query, `"send_attempt_count"=`, "a stale status snapshot must not reset an atomic attempt increment") +} + +func TestMessageSendAttemptIncrementOnlyUpdatesItsOwnedColumn(t *testing.T) { + pool := &messageThreadTestConnPool{} + db, err := gorm.Open(postgres.New(postgres.Config{Conn: pool, WithoutReturning: true}), &gorm.Config{DisableAutomaticPing: true}) + require.NoError(t, err) + logger := &messageThreadTestLogger{} + repository := NewGormMessageRepository(logger, telemetry.NewOtelLogger("test", logger), db) + + err = repository.IncreaseSendAttemptCount(context.Background(), entities.UserID("user-id"), uuid.New()) + + require.NoError(t, err) + require.Len(t, pool.statements, 1) + update := pool.statements[0] + assert.Contains(t, update.query, `"send_attempt_count"=send_attempt_count +`) + assert.NotContains(t, update.query, `"status"=`) + assert.Contains(t, update.query, "WHERE user_id =") + assert.Contains(t, update.query, "AND id =") +} diff --git a/api/pkg/repositories/gorm_message_thread_repository.go b/api/pkg/repositories/gorm_message_thread_repository.go index c30091b9b..316f8ab26 100644 --- a/api/pkg/repositories/gorm_message_thread_repository.go +++ b/api/pkg/repositories/gorm_message_thread_repository.go @@ -234,7 +234,7 @@ func (repository *gormMessageThreadRepository) Load(ctx context.Context, userID } // Index message threads for an owner -func (repository *gormMessageThreadRepository) Index(ctx context.Context, userID entities.UserID, owner string, isArchived bool, params IndexParams) (*[]entities.MessageThread, error) { +func (repository *gormMessageThreadRepository) Index(ctx context.Context, userID entities.UserID, owner string, isArchived bool, unreadOnly bool, params IndexParams) (*[]entities.MessageThread, error) { ctx, span := repository.tracer.Start(ctx) defer span.End() @@ -249,6 +249,10 @@ func (repository *gormMessageThreadRepository) Index(ctx context.Context, userID query.Where(repository.db.Where("is_archived = ?", isArchived).Or("is_archived IS NULL")) } + if unreadOnly { + query.Where("unread_count > ?", 0) + } + if len(params.Query) > 0 { queryPattern := "%" + params.Query + "%" query.Where( @@ -259,7 +263,7 @@ func (repository *gormMessageThreadRepository) Index(ctx context.Context, userID } threads := new([]entities.MessageThread) - if err := query.Order("order_timestamp DESC").Limit(params.Limit).Offset(params.Skip).Find(&threads).Error; err != nil { + if err := query.Order("order_timestamp DESC").Order("id DESC").Limit(params.Limit).Offset(params.Skip).Find(&threads).Error; err != nil { return nil, repository.tracer.WrapErrorSpan(span, stacktrace.Propagatef(err, "cannot fetch message threads with owner [%s] and params [%+#v]", owner, params)) } diff --git a/api/pkg/repositories/gorm_message_thread_repository_test.go b/api/pkg/repositories/gorm_message_thread_repository_test.go index 5ca7d4a3f..6f2fde4a3 100644 --- a/api/pkg/repositories/gorm_message_thread_repository_test.go +++ b/api/pkg/repositories/gorm_message_thread_repository_test.go @@ -5,6 +5,7 @@ import ( "database/sql" "database/sql/driver" "errors" + "fmt" "strings" "testing" "time" @@ -24,6 +25,42 @@ type messageThreadTestStatement struct { args []any } +func TestMessageThreadIndexFiltersUnreadBeforePagination(t *testing.T) { + for _, unreadOnly := range []bool{false, true} { + t.Run(fmt.Sprintf("unread_only=%t", unreadOnly), func(t *testing.T) { + db, err := gorm.Open(postgres.New(postgres.Config{Conn: &messageThreadTestConnPool{}}), &gorm.Config{ + DisableAutomaticPing: true, + DryRun: true, + }) + require.NoError(t, err) + var query string + var args []any + require.NoError(t, db.Callback().Query().After("gorm:query").Register("capture", func(db *gorm.DB) { + query = db.Statement.SQL.String() + args = db.Statement.Vars + })) + logger := &messageThreadTestLogger{} + repository := NewGormMessageThreadRepository(logger, telemetry.NewOtelLogger("test", logger), db) + + _, err = repository.Index(context.Background(), entities.UserID("user-id"), "+18005550199", true, unreadOnly, IndexParams{Limit: 20, Skip: 5}) + + require.NoError(t, err) + assert.Contains(t, query, "user_id = $1") + assert.Contains(t, query, "owner = $2") + assert.Contains(t, query, "is_archived = $3") + assert.Equal(t, []any{entities.UserID("user-id"), "+18005550199", true}, args[:3]) + if unreadOnly { + assert.Contains(t, query, "unread_count >") + assert.Less(t, strings.Index(query, "unread_count >"), strings.Index(query, "LIMIT")) + } else { + assert.NotContains(t, query, "unread_count >") + } + assert.Contains(t, query, "ORDER BY order_timestamp DESC,id DESC LIMIT") + assert.Contains(t, query, "OFFSET") + }) + } +} + type messageThreadTestConnPool struct { statements []messageThreadTestStatement } diff --git a/api/pkg/repositories/message_repository.go b/api/pkg/repositories/message_repository.go index c8f85fbb0..dd069d284 100644 --- a/api/pkg/repositories/message_repository.go +++ b/api/pkg/repositories/message_repository.go @@ -15,6 +15,9 @@ type MessageRepository interface { // Update a new entities.Message Update(ctx context.Context, message *entities.Message) error + // IncreaseSendAttemptCount atomically increments attempts without changing message status. + IncreaseSendAttemptCount(ctx context.Context, userID entities.UserID, messageID uuid.UUID) error + // Load an entities.Message by ID Load(ctx context.Context, userID entities.UserID, messageID uuid.UUID) (*entities.Message, error) diff --git a/api/pkg/repositories/message_thread_repository.go b/api/pkg/repositories/message_thread_repository.go index 28efc11ee..88e7a896d 100644 --- a/api/pkg/repositories/message_thread_repository.go +++ b/api/pkg/repositories/message_thread_repository.go @@ -53,7 +53,7 @@ type MessageThreadRepository interface { Load(ctx context.Context, userID entities.UserID, ID uuid.UUID) (*entities.MessageThread, error) // Index message threads for an owner - Index(ctx context.Context, userID entities.UserID, owner string, archived bool, params IndexParams) (*[]entities.MessageThread, error) + Index(ctx context.Context, userID entities.UserID, owner string, archived bool, unreadOnly bool, params IndexParams) (*[]entities.MessageThread, error) // UpdateAfterDeletedMessage updates a thread after the original message has been deleted UpdateAfterDeletedMessage(ctx context.Context, params MessageThreadDeletedUpdate) error diff --git a/api/pkg/requests/message_thread_index_request.go b/api/pkg/requests/message_thread_index_request.go index 053f90003..7cf20f693 100644 --- a/api/pkg/requests/message_thread_index_request.go +++ b/api/pkg/requests/message_thread_index_request.go @@ -14,6 +14,7 @@ import ( type MessageThreadIndex struct { request IsArchived string `json:"is_archived" query:"is_archived" example:"false"` + UnreadOnly string `json:"unread_only" query:"unread_only" example:"false"` Skip string `json:"skip" query:"skip"` Query string `json:"query" query:"query"` Limit string `json:"limit" query:"limit"` @@ -36,6 +37,10 @@ func (input *MessageThreadIndex) Sanitize() MessageThreadIndex { } input.IsArchived = input.sanitizeBool(input.IsArchived) + input.UnreadOnly = input.sanitizeBool(input.UnreadOnly) + if input.UnreadOnly == "" { + input.UnreadOnly = "false" + } input.Contacts = input.sanitizeBool(input.Contacts) if input.Contacts != "true" && input.Contacts != "false" { input.Contacts = "false" @@ -62,6 +67,7 @@ func (input *MessageThreadIndex) ToGetParams(userID entities.UserID) services.Me }, UserID: userID, IsArchived: input.getBool(input.IsArchived), + UnreadOnly: input.getBool(input.UnreadOnly), WithContacts: input.getBool(input.Contacts), Owner: input.Owner, } diff --git a/api/pkg/requests/phone_update_request.go b/api/pkg/requests/phone_update_request.go index cedd51a0d..48f8dbff7 100644 --- a/api/pkg/requests/phone_update_request.go +++ b/api/pkg/requests/phone_update_request.go @@ -33,6 +33,9 @@ type PhoneUpsert struct { // UnarchiveThread moves an archived thread back to the inbox when a new message is received on this phone. UnarchiveThread bool `json:"unarchive_thread" example:"false"` + // ArchiveNewOutgoingMessageThreads archives new conversations started by an outgoing message on this phone. + ArchiveNewOutgoingMessageThreads bool `json:"archive_new_outgoing_message_threads" example:"false" validate:"optional"` + // SIM is the SIM slot of the phone in case the phone has more than 1 SIM slot SIM string `json:"sim" example:"SIM1"` @@ -84,6 +87,11 @@ func (input *PhoneUpsert) ToUpsertParams(user entities.AuthContext, source strin unarchiveThread = &input.UnarchiveThread } + var archiveNewOutgoingMessageThreads *bool + if _, exists := fields["archive_new_outgoing_message_threads"]; exists { + archiveNewOutgoingMessageThreads = &input.ArchiveNewOutgoingMessageThreads + } + var scheduleID *uuid.UUID if _, exists := fields["message_send_schedule_id"]; exists { if parsed, err := uuid.Parse(strings.TrimSpace(input.MessageSendScheduleID)); err == nil { @@ -92,16 +100,17 @@ func (input *PhoneUpsert) ToUpsertParams(user entities.AuthContext, source strin } return &services.PhoneUpsertParams{ - Source: source, - PhoneNumber: phone, - MessagesPerMinute: messagesPerMinute, - MissedCallAutoReply: input.MissedCallAutoReply, - MessageExpirationDuration: timeout, - MaxSendAttempts: maxSendAttempts, - FcmToken: fcmToken, - UserID: user.ID, - SIM: entities.SIM(input.SIM), - UnarchiveThread: unarchiveThread, - MessageSendScheduleID: scheduleID, + Source: source, + PhoneNumber: phone, + MessagesPerMinute: messagesPerMinute, + MissedCallAutoReply: input.MissedCallAutoReply, + MessageExpirationDuration: timeout, + MaxSendAttempts: maxSendAttempts, + FcmToken: fcmToken, + UserID: user.ID, + SIM: entities.SIM(input.SIM), + UnarchiveThread: unarchiveThread, + ArchiveNewOutgoingMessageThreads: archiveNewOutgoingMessageThreads, + MessageSendScheduleID: scheduleID, } } diff --git a/api/pkg/requests/phone_update_request_test.go b/api/pkg/requests/phone_update_request_test.go new file mode 100644 index 000000000..ff5a0e0ae --- /dev/null +++ b/api/pkg/requests/phone_update_request_test.go @@ -0,0 +1,39 @@ +package requests + +import ( + "encoding/json" + "testing" + + "github.com/NdoleStudio/httpsms/pkg/entities" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPhoneUpsertArchiveNewOutgoingMessageThreadsPresence(t *testing.T) { + tests := []struct { + name string + body string + present bool + want bool + }{ + {name: "enabled", body: `{"phone_number":"+18005550199","archive_new_outgoing_message_threads":true}`, present: true, want: true}, + {name: "disabled", body: `{"phone_number":"+18005550199","archive_new_outgoing_message_threads":false}`, present: true, want: false}, + {name: "omitted by older client", body: `{"phone_number":"+18005550199"}`, present: false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var input PhoneUpsert + require.NoError(t, json.Unmarshal([]byte(test.body), &input)) + + params := input.ToUpsertParams(entities.AuthContext{}, "test", []byte(test.body)) + + if !test.present { + assert.Nil(t, params.ArchiveNewOutgoingMessageThreads) + return + } + require.NotNil(t, params.ArchiveNewOutgoingMessageThreads) + assert.Equal(t, test.want, *params.ArchiveNewOutgoingMessageThreads) + }) + } +} diff --git a/api/pkg/services/message_service.go b/api/pkg/services/message_service.go index 89c216817..75267be77 100644 --- a/api/pkg/services/message_service.go +++ b/api/pkg/services/message_service.go @@ -800,16 +800,11 @@ func (service *MessageService) HandleMessageNotificationSent(ctx context.Context ctxLogger := service.tracer.CtxLogger(service.logger, span) - message, err := service.repository.Load(ctx, params.UserID, params.ID) - if err != nil { - return service.tracer.WrapErrorSpan(span, stacktrace.Propagatef(err, "cannot find message with id [%s]", params.ID)) - } - - if err = service.repository.Update(ctx, message.AddSendAttemptCount()); err != nil { - return service.tracer.WrapErrorSpan(span, stacktrace.Propagatef(err, "cannot update message with id [%s] as expired", message.ID)) + if err := service.repository.IncreaseSendAttemptCount(ctx, params.UserID, params.ID); err != nil { + return service.tracer.WrapErrorSpan(span, stacktrace.Propagatef(err, "cannot increment send attempts for message with id [%s]", params.ID)) } - ctxLogger.Info(fmt.Sprintf("notification for message with id [%s] has been sent at [%s]", message.ID, params.Timestamp.String())) + ctxLogger.Info(fmt.Sprintf("notification for message with id [%s] has been sent at [%s]", params.ID, params.Timestamp.String())) return nil } diff --git a/api/pkg/services/message_service_test.go b/api/pkg/services/message_service_test.go index 263816f41..be17a3a14 100644 --- a/api/pkg/services/message_service_test.go +++ b/api/pkg/services/message_service_test.go @@ -1,16 +1,65 @@ package services import ( + "context" "testing" "time" + "github.com/NdoleStudio/httpsms/pkg/entities" "github.com/NdoleStudio/httpsms/pkg/events" + "github.com/NdoleStudio/httpsms/pkg/repositories" "github.com/NdoleStudio/httpsms/pkg/telemetry" "github.com/google/uuid" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/trace" ) +type notificationMessageRepository struct { + repositories.MessageRepository + stored entities.Message +} + +func (repository *notificationMessageRepository) Load(context.Context, entities.UserID, uuid.UUID) (*entities.Message, error) { + stale := repository.stored + stale.Status = entities.MessageStatusPending + return &stale, nil +} + +func (repository *notificationMessageRepository) Update(_ context.Context, message *entities.Message) error { + repository.stored = *message + return nil +} + +func (repository *notificationMessageRepository) IncreaseSendAttemptCount(context.Context, entities.UserID, uuid.UUID) error { + repository.stored.SendAttemptCount++ + return nil +} + +func TestNotificationSentDoesNotRestoreStalePendingStatus(t *testing.T) { + repository := ¬ificationMessageRepository{stored: entities.Message{ + ID: uuid.New(), + UserID: entities.UserID("user-id"), + Status: entities.MessageStatusScheduled, + }} + logger := &noopLogger{} + service := &MessageService{ + logger: logger, + tracer: telemetry.NewOtelLogger("test", logger), + repository: repository, + } + + err := service.HandleMessageNotificationSent(context.Background(), HandleMessageParams{ + ID: repository.stored.ID, + UserID: repository.stored.UserID, + Timestamp: time.Now().UTC(), + }) + + require.NoError(t, err) + assert.Equal(t, entities.MessageStatus(entities.MessageStatusScheduled), repository.stored.Status) + assert.EqualValues(t, 1, repository.stored.SendAttemptCount) +} + func TestGetSendDelay_WithSendAt_ReturnsTimeUntil(t *testing.T) { service := &MessageService{} logger := &noopLogger{} diff --git a/api/pkg/services/message_thread_service.go b/api/pkg/services/message_thread_service.go index efcfadd17..75488149a 100644 --- a/api/pkg/services/message_thread_service.go +++ b/api/pkg/services/message_thread_service.go @@ -209,6 +209,16 @@ func (service *MessageThreadService) createThread(ctx context.Context, params Me ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger) defer span.End() + isArchived := false + if params.Status != entities.MessageStatusReceived { + phone, err := service.phoneRepository.Load(ctx, params.UserID, params.Owner) + if err != nil { + ctxLogger.Warn(stacktrace.Propagatef(err, "cannot load phone [%s] for user [%s] to resolve ArchiveNewOutgoingMessageThreads; creating the thread unarchived", params.Owner, params.UserID)) + } else { + isArchived = phone.ArchiveNewOutgoingMessageThreads + } + } + unreadCount := uint(0) if params.MarkAsUnread { unreadCount = 1 @@ -220,7 +230,7 @@ func (service *MessageThreadService) createThread(ctx context.Context, params Me Owner: params.Owner, Contact: params.Contact, UserID: params.UserID, - IsArchived: false, + IsArchived: isArchived, UnreadCount: unreadCount, Color: service.getColor(), LastMessageContent: ¶ms.Content, @@ -274,6 +284,7 @@ func (service *MessageThreadService) getColor() string { type MessageThreadGetParams struct { repositories.IndexParams IsArchived bool + UnreadOnly bool WithContacts bool UserID entities.UserID Owner string @@ -284,7 +295,7 @@ func (service *MessageThreadService) GetThreads(ctx context.Context, params Mess ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger) defer span.End() - threads, err := service.repository.Index(ctx, params.UserID, params.Owner, params.IsArchived, params.IndexParams) + threads, err := service.repository.Index(ctx, params.UserID, params.Owner, params.IsArchived, params.UnreadOnly, params.IndexParams) if err != nil { return nil, service.tracer.WrapErrorSpan(span, stacktrace.Propagatef(err, "could not fetch messages threads for params [%+#v]", params)) } @@ -312,8 +323,8 @@ func (service *MessageThreadService) GetThreads(ctx context.Context, params Mess } // GetThread fetches an entities.MessageThread message thread by the ID -func (service *MessageThreadService) GetThread(ctx context.Context, userID entities.UserID, messageThreadID uuid.UUID) (*entities.MessageThread, error) { - ctx, span := service.tracer.Start(ctx) +func (service *MessageThreadService) GetThread(ctx context.Context, userID entities.UserID, messageThreadID uuid.UUID, withContacts bool) (*entities.MessageThread, error) { + ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger) defer span.End() thread, err := service.repository.Load(ctx, userID, messageThreadID) @@ -321,6 +332,14 @@ func (service *MessageThreadService) GetThread(ctx context.Context, userID entit return nil, service.tracer.WrapErrorSpan(span, stacktrace.PropagateWithCodef(err, stacktrace.GetCode(err), "could not fetch thread with ID [%s] for user [%s]", messageThreadID, userID)) } + if withContacts { + contacts, contactErr := service.contactService.GetContactMap(ctx, userID, []string{thread.Contact}) + if contactErr != nil { + ctxLogger.Error(service.tracer.WrapErrorSpan(span, stacktrace.Propagatef(contactErr, "cannot resolve contact for thread [%s]", messageThreadID))) + } else { + thread.ContactDetails = contacts[thread.Contact] + } + } return thread, nil } diff --git a/api/pkg/services/message_thread_service_contacts_test.go b/api/pkg/services/message_thread_service_contacts_test.go index 2c021d2f7..a941a0f7b 100644 --- a/api/pkg/services/message_thread_service_contacts_test.go +++ b/api/pkg/services/message_thread_service_contacts_test.go @@ -22,7 +22,7 @@ type messageThreadContactRepositoryStub struct { calls int } -func (stub *messageThreadContactRepositoryStub) Index(_ context.Context, _ entities.UserID, _ string, _ bool, _ repositories.IndexParams) (*[]entities.MessageThread, error) { +func (stub *messageThreadContactRepositoryStub) Index(_ context.Context, _ entities.UserID, _ string, _ bool, _ bool, _ repositories.IndexParams) (*[]entities.MessageThread, error) { stub.calls++ if stub.err != nil { return nil, stub.err diff --git a/api/pkg/services/message_thread_service_test.go b/api/pkg/services/message_thread_service_test.go index 1ef9b4bde..eaa487296 100644 --- a/api/pkg/services/message_thread_service_test.go +++ b/api/pkg/services/message_thread_service_test.go @@ -15,6 +15,15 @@ import ( "gorm.io/gorm" ) +type threadPhoneRepositoryStub struct { + repositories.PhoneRepository + load func(context.Context, entities.UserID, string) (*entities.Phone, error) +} + +func (stub *threadPhoneRepositoryStub) Load(ctx context.Context, userID entities.UserID, owner string) (*entities.Phone, error) { + return stub.load(ctx, userID, owner) +} + type messageThreadRepositoryStub struct { loadByOwnerContact func(context.Context, entities.UserID, string, string) (*entities.MessageThread, error) load func(context.Context, entities.UserID, uuid.UUID) (*entities.MessageThread, error) @@ -56,7 +65,7 @@ func (stub *messageThreadRepositoryStub) Load(ctx context.Context, userID entiti return stub.load(ctx, userID, id) } -func (stub *messageThreadRepositoryStub) Index(context.Context, entities.UserID, string, bool, repositories.IndexParams) (*[]entities.MessageThread, error) { +func (stub *messageThreadRepositoryStub) Index(context.Context, entities.UserID, string, bool, bool, repositories.IndexParams) (*[]entities.MessageThread, error) { threads := []entities.MessageThread{} return &threads, nil } @@ -233,3 +242,144 @@ func TestShouldCheckUnarchive(t *testing.T) { assert.False(t, service.shouldCheckUnarchive(notArchived, received), "already unarchived -> no check") assert.False(t, service.shouldCheckUnarchive(notArchived, sent), "not archived + outbound -> no check") } + +func TestCreateThreadArchivesOnlyNewOutgoingConversationsForEnabledPhone(t *testing.T) { + tests := []struct { + name string + status entities.MessageStatus + owner string + wantArchived bool + }{ + {name: "outgoing pending", status: entities.MessageStatusPending, owner: "+18005550199", wantArchived: true}, + {name: "outgoing sent", status: entities.MessageStatusSent, owner: "+18005550199", wantArchived: true}, + {name: "outgoing scheduled", status: entities.MessageStatusScheduled, owner: "+18005550199", wantArchived: true}, + {name: "incoming", status: entities.MessageStatusReceived, owner: "+18005550199", wantArchived: false}, + {name: "other phone defaults off", status: entities.MessageStatusPending, owner: "+18005550198", wantArchived: false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var stored *entities.MessageThread + repository := &messageThreadRepositoryStub{ + loadByOwnerContact: func(context.Context, entities.UserID, string, string) (*entities.MessageThread, error) { + return nil, stacktrace.PropagateWithCodef(gorm.ErrRecordNotFound, repositories.ErrCodeNotFound, "not found") + }, + store: func(_ context.Context, thread *entities.MessageThread) error { + stored = thread + return nil + }, + } + service := newMessageThreadServiceForTest(repository) + service.phoneRepository = &threadPhoneRepositoryStub{ + load: func(_ context.Context, userID entities.UserID, owner string) (*entities.Phone, error) { + assert.Equal(t, entities.UserID("user-id"), userID) + assert.Equal(t, test.owner, owner) + assert.NotEqual(t, entities.MessageStatusReceived, test.status, "incoming creation must not consult the setting") + phone := &entities.Phone{} + if owner == "+18005550199" { + phone.ArchiveNewOutgoingMessageThreads = true + } + return phone, nil + }, + } + + err := service.UpdateThread(context.Background(), MessageThreadUpdateParams{ + UserID: entities.UserID("user-id"), + Owner: test.owner, + Contact: "+18005550100", + MessageID: uuid.New(), + Status: test.status, + Timestamp: time.Now().UTC(), + }) + + require.NoError(t, err) + require.NotNil(t, stored) + assert.Equal(t, test.wantArchived, stored.IsArchived) + }) + } +} + +func TestCreateOutgoingThreadDefaultsToUnarchivedWhenPhoneCannotBeLoaded(t *testing.T) { + var stored *entities.MessageThread + service := newMessageThreadServiceForTest(&messageThreadRepositoryStub{ + loadByOwnerContact: func(context.Context, entities.UserID, string, string) (*entities.MessageThread, error) { + return nil, stacktrace.PropagateWithCodef(gorm.ErrRecordNotFound, repositories.ErrCodeNotFound, "not found") + }, + store: func(_ context.Context, thread *entities.MessageThread) error { + stored = thread + return nil + }, + }) + logger := &phoneNotificationLogger{} + service.logger = logger + phoneErr := stacktrace.NewError("phone unavailable") + service.phoneRepository = &threadPhoneRepositoryStub{ + load: func(context.Context, entities.UserID, string) (*entities.Phone, error) { + return nil, phoneErr + }, + } + + err := service.UpdateThread(context.Background(), MessageThreadUpdateParams{ + UserID: entities.UserID("user-id"), + Owner: "+18005550199", + Status: entities.MessageStatusPending, + }) + + require.NoError(t, err) + require.NotNil(t, stored) + assert.False(t, stored.IsArchived) + require.Len(t, logger.warnings, 1) + assert.Contains(t, logger.warnings[0], "phone unavailable") +} + +func TestArchiveNewOutgoingMessageThreadsPreservesExistingConversationBehavior(t *testing.T) { + tests := []struct { + name string + status entities.MessageStatus + archived bool + unarchive bool + wantUnarchive bool + }{ + {name: "outgoing existing inbox", status: entities.MessageStatusPending}, + {name: "outgoing existing archive", status: entities.MessageStatusPending, archived: true}, + {name: "reply stays archived", status: entities.MessageStatusReceived, archived: true}, + {name: "reply unarchives when enabled", status: entities.MessageStatusReceived, archived: true, unarchive: true, wantUnarchive: true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var captured repositories.MessageThreadActivityUpdate + repository := &messageThreadRepositoryStub{ + loadByOwnerContact: func(context.Context, entities.UserID, string, string) (*entities.MessageThread, error) { + return &entities.MessageThread{ID: uuid.New(), IsArchived: test.archived}, nil + }, + updateActivity: func(_ context.Context, params repositories.MessageThreadActivityUpdate) error { + captured = params + return nil + }, + store: func(context.Context, *entities.MessageThread) error { + t.Fatal("existing conversations must not be recreated") + return nil + }, + } + service := newMessageThreadServiceForTest(repository) + service.phoneRepository = &threadPhoneRepositoryStub{ + load: func(context.Context, entities.UserID, string) (*entities.Phone, error) { + return &entities.Phone{UnarchiveThread: test.unarchive, ArchiveNewOutgoingMessageThreads: true}, nil + }, + } + + err := service.UpdateThread(context.Background(), MessageThreadUpdateParams{ + UserID: entities.UserID("user-id"), + Owner: "+18005550199", + Contact: "+18005550100", + MessageID: uuid.New(), + Status: test.status, + Timestamp: time.Now().UTC(), + }) + + require.NoError(t, err) + assert.Equal(t, test.wantUnarchive, captured.Unarchive) + }) + } +} diff --git a/api/pkg/services/phone_service.go b/api/pkg/services/phone_service.go index c7078375b..46c984c82 100644 --- a/api/pkg/services/phone_service.go +++ b/api/pkg/services/phone_service.go @@ -94,18 +94,19 @@ func (service *PhoneService) Load(ctx context.Context, userID entities.UserID, o // PhoneUpsertParams are parameters for creating a new entities.Phone type PhoneUpsertParams struct { - PhoneNumber *phonenumbers.PhoneNumber - FcmToken *string - MessagesPerMinute *uint - MaxSendAttempts *uint - WebhookURL *string - MessageExpirationDuration *time.Duration - MissedCallAutoReply *string - UnarchiveThread *bool - SIM entities.SIM - MessageSendScheduleID *uuid.UUID - Source string - UserID entities.UserID + PhoneNumber *phonenumbers.PhoneNumber + FcmToken *string + MessagesPerMinute *uint + MaxSendAttempts *uint + WebhookURL *string + MessageExpirationDuration *time.Duration + MissedCallAutoReply *string + UnarchiveThread *bool + ArchiveNewOutgoingMessageThreads *bool + SIM entities.SIM + MessageSendScheduleID *uuid.UUID + Source string + UserID entities.UserID } // Upsert a new entities.Phone @@ -125,7 +126,7 @@ func (service *PhoneService) Upsert(ctx context.Context, params *PhoneUpsertPara FcmToken: params.FcmToken, SIM: params.SIM, MessageSendScheduleID: params.MessageSendScheduleID, - }) + }, params.ArchiveNewOutgoingMessageThreads != nil && *params.ArchiveNewOutgoingMessageThreads) } if err != nil { @@ -224,7 +225,7 @@ func (service *PhoneService) UpsertFCMToken(ctx context.Context, params *PhoneFC phone, err := service.repository.Load(ctx, params.UserID, phonenumbers.Format(params.PhoneNumber, phonenumbers.E164)) if stacktrace.GetCode(err) == repositories.ErrCodeNotFound { - return service.createPhone(ctx, params) + return service.createPhone(ctx, params, false) } if err != nil { @@ -242,7 +243,7 @@ func (service *PhoneService) UpsertFCMToken(ctx context.Context, params *PhoneFC return phone, service.dispatchPhoneUpdatedEvent(ctx, phone, params) } -func (service *PhoneService) createPhone(ctx context.Context, params *PhoneFCMTokenParams) (*entities.Phone, error) { +func (service *PhoneService) createPhone(ctx context.Context, params *PhoneFCMTokenParams, archiveNewOutgoingMessageThreads bool) (*entities.Phone, error) { ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger) defer span.End() @@ -252,15 +253,16 @@ func (service *PhoneService) createPhone(ctx context.Context, params *PhoneFCMTo FcmToken: params.FcmToken, // Android has a limit of 30 SMS messages per minute without user permission, to be safe let's use 10 messages per minute // https://android.googlesource.com/platform/frameworks/opt/telephony/+/master/src/java/com/android/internal/telephony/SmsUsageMonitor.java#80 - MessagesPerMinute: 10, - MessageExpirationSeconds: 10 * 60, // 10 minutes - MaxSendAttempts: 2, - SIM: params.SIM, - MissedCallAutoReply: nil, - MessageSendScheduleID: params.MessageSendScheduleID, - PhoneNumber: phonenumbers.Format(params.PhoneNumber, phonenumbers.E164), - CreatedAt: time.Now().UTC(), - UpdatedAt: time.Now().UTC(), + MessagesPerMinute: 10, + MessageExpirationSeconds: 10 * 60, // 10 minutes + MaxSendAttempts: 2, + SIM: params.SIM, + MissedCallAutoReply: nil, + MessageSendScheduleID: params.MessageSendScheduleID, + ArchiveNewOutgoingMessageThreads: archiveNewOutgoingMessageThreads, + PhoneNumber: phonenumbers.Format(params.PhoneNumber, phonenumbers.E164), + CreatedAt: time.Now().UTC(), + UpdatedAt: time.Now().UTC(), } if err := service.repository.Save(ctx, phone); err != nil { @@ -303,6 +305,10 @@ func (service *PhoneService) update(phone *entities.Phone, params *PhoneUpsertPa phone.UnarchiveThread = *params.UnarchiveThread } + if params.ArchiveNewOutgoingMessageThreads != nil { + phone.ArchiveNewOutgoingMessageThreads = *params.ArchiveNewOutgoingMessageThreads + } + phone.SIM = params.SIM phone.MessageSendScheduleID = params.MessageSendScheduleID diff --git a/api/pkg/services/phone_service_test.go b/api/pkg/services/phone_service_test.go new file mode 100644 index 000000000..d99cc7853 --- /dev/null +++ b/api/pkg/services/phone_service_test.go @@ -0,0 +1,94 @@ +package services + +import ( + "context" + "testing" + + "github.com/NdoleStudio/httpsms/pkg/entities" + "github.com/NdoleStudio/httpsms/pkg/repositories" + "github.com/NdoleStudio/httpsms/pkg/telemetry" + "github.com/NdoleStudio/stacktrace" + "github.com/nyaruka/phonenumbers" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/gorm" +) + +type phoneCreationRepository struct { + repositories.PhoneRepository + saved entities.Phone +} + +func (*phoneCreationRepository) Load(context.Context, entities.UserID, string) (*entities.Phone, error) { + return nil, stacktrace.PropagateWithCodef(gorm.ErrRecordNotFound, repositories.ErrCodeNotFound, "phone not found") +} + +func (repository *phoneCreationRepository) Save(_ context.Context, phone *entities.Phone) error { + repository.saved = *phone + return nil +} + +func TestPhoneUpsertAppliesArchiveSettingOnFirstRegistration(t *testing.T) { + enabled := true + disabled := false + for _, test := range []struct { + name string + setting *bool + want bool + }{ + {name: "enabled", setting: &enabled, want: true}, + {name: "disabled", setting: &disabled}, + {name: "omitted defaults off"}, + } { + t.Run(test.name, func(t *testing.T) { + repository := &phoneCreationRepository{} + logger := &noopLogger{} + tracer := telemetry.NewOtelLogger("test", logger) + dispatcher := NewEventDispatcher(logger, tracer, nil, &phoneNotificationEventQueue{}, PushQueueConfig{}) + service := NewPhoneService(logger, tracer, repository, dispatcher) + number, err := phonenumbers.Parse("+18005550199", phonenumbers.UNKNOWN_REGION) + require.NoError(t, err) + + phone, err := service.Upsert(context.Background(), &PhoneUpsertParams{ + Source: "https://httpsms.test", + UserID: entities.UserID("user-id"), + PhoneNumber: number, + SIM: entities.SIM1, + ArchiveNewOutgoingMessageThreads: test.setting, + }) + + require.NoError(t, err) + require.NotNil(t, phone) + assert.Equal(t, test.want, phone.ArchiveNewOutgoingMessageThreads) + assert.Equal(t, test.want, repository.saved.ArchiveNewOutgoingMessageThreads) + assert.Equal(t, "+18005550199", repository.saved.PhoneNumber) + }) + } +} + +func TestPhoneUpdateArchiveNewOutgoingMessageThreads(t *testing.T) { + enabled := true + disabled := false + tests := []struct { + name string + initial bool + setting *bool + want bool + }{ + {name: "enable", setting: &enabled, want: true}, + {name: "disable", initial: true, setting: &disabled, want: false}, + {name: "omitted preserves enabled", initial: true, want: true}, + {name: "omitted defaults off", want: false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + phone := &entities.Phone{ArchiveNewOutgoingMessageThreads: test.initial} + params := PhoneUpsertParams{ArchiveNewOutgoingMessageThreads: test.setting} + + updated := (&PhoneService{}).update(phone, ¶ms) + + assert.Equal(t, test.want, updated.ArchiveNewOutgoingMessageThreads) + }) + } +} diff --git a/api/pkg/validators/message_thread_handler_validator.go b/api/pkg/validators/message_thread_handler_validator.go index d4f08002f..3c43575b3 100644 --- a/api/pkg/validators/message_thread_handler_validator.go +++ b/api/pkg/validators/message_thread_handler_validator.go @@ -48,6 +48,10 @@ func (validator *MessageThreadHandlerValidator) ValidateMessageThreadIndex(_ con "required", "in:true,false", }, + "unread_only": []string{ + "required", + "in:true,false", + }, "query": []string{ "max:100", }, diff --git a/tests/archive_new_outgoing_message_threads_integration_test.go b/tests/archive_new_outgoing_message_threads_integration_test.go new file mode 100644 index 000000000..850ee491e --- /dev/null +++ b/tests/archive_new_outgoing_message_threads_integration_test.go @@ -0,0 +1,155 @@ +package tests + +import ( + "context" + "net/http" + "testing" + "time" + + httpsms "github.com/NdoleStudio/httpsms-go" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type phoneThreadSettings struct { + ArchiveNewOutgoingMessageThreads bool `json:"archive_new_outgoing_message_threads"` + UnarchiveThread bool `json:"unarchive_thread"` +} + +func updatePhoneThreadSettings(ctx context.Context, t *testing.T, phone testPhone, settings map[string]bool) phoneThreadSettings { + t.Helper() + + payload := map[string]any{ + "phone_number": phone.PhoneNumber, + "fcm_token": phone.FcmToken, + "sim": "SIM1", + } + for key, value := range settings { + payload[key] = value + } + var response struct { + Data phoneThreadSettings `json:"data"` + } + requestJSON(ctx, t, http.MethodPut, "/v1/phones", userAPIKey, payload, http.StatusOK, &response) + return response.Data +} + +func sendThreadMessage(ctx context.Context, t *testing.T, phone testPhone, contact, content string) { + t.Helper() + + _, response, err := newAPIClient().Messages.Send(ctx, &httpsms.MessageSendParams{ + From: phone.PhoneNumber, + To: contact, + Content: content, + }) + require.NoError(t, err) + require.Equal(t, http.StatusOK, response.HTTPResponse.StatusCode) +} + +func TestArchiveNewOutgoingMessageThreadsOnFirstRegistration(t *testing.T) { + ctx := context.Background() + phone := testPhone{ + PhoneNumber: randomPhoneNumber(), + FcmToken: "fcm-" + uuid.NewString(), + } + var response struct { + Data phoneThreadSettings `json:"data"` + } + requestJSON(ctx, t, http.MethodPut, "/v1/phones", userAPIKey, map[string]any{ + "phone_number": phone.PhoneNumber, + "fcm_token": phone.FcmToken, + "sim": "SIM1", + "archive_new_outgoing_message_threads": true, + }, http.StatusOK, &response) + require.True(t, response.Data.ArchiveNewOutgoingMessageThreads) + + contact := "+18005540100" + sendThreadMessage(ctx, t, phone, contact, "First registration archives outgoing") + require.NotNil(t, waitForThread(ctx, t, phone.PhoneNumber, contact, true, "First registration archives outgoing", 20*time.Second)) + assert.Nil(t, findThreadByContact(fetchThreads(ctx, t, phone.PhoneNumber, false), contact)) +} + +func TestArchiveNewOutgoingMessageThreadsPerPhoneAndDirection(t *testing.T) { + ctx := context.Background() + enabledPhone := setupPhone(ctx, t, 60) + defaultPhone := setupPhone(ctx, t, 60) + require.NotEqual(t, enabledPhone.PhoneNumber, defaultPhone.PhoneNumber) + + existingContact := randomPhoneNumber() + receiveInbound(ctx, t, enabledPhone.PhoneAPIKey, existingContact, enabledPhone.PhoneNumber, "Existing inbox conversation", time.Now()) + require.NotNil(t, waitForThread(ctx, t, enabledPhone.PhoneNumber, existingContact, false, "Existing inbox conversation", 20*time.Second)) + + settings := updatePhoneThreadSettings(ctx, t, enabledPhone, map[string]bool{"archive_new_outgoing_message_threads": true}) + require.True(t, settings.ArchiveNewOutgoingMessageThreads) + assert.False(t, updatePhoneThreadSettings(ctx, t, defaultPhone, nil).ArchiveNewOutgoingMessageThreads) + + contact := randomPhoneNumber() + sendThreadMessage(ctx, t, enabledPhone, contact, "New outgoing archived") + archived := waitForThread(ctx, t, enabledPhone.PhoneNumber, contact, true, "New outgoing archived", 20*time.Second) + require.NotNil(t, archived) + assert.True(t, archived.IsArchived) + assert.Nil(t, findThreadByContact(fetchThreads(ctx, t, enabledPhone.PhoneNumber, false), contact)) + + sendThreadMessage(ctx, t, defaultPhone, contact, "Other phone stays in inbox") + require.NotNil(t, waitForThread(ctx, t, defaultPhone.PhoneNumber, contact, false, "Other phone stays in inbox", 20*time.Second)) + + sendThreadMessage(ctx, t, enabledPhone, existingContact, "Existing conversation stays in inbox") + require.NotNil(t, waitForThread(ctx, t, enabledPhone.PhoneNumber, existingContact, false, "Existing conversation stays in inbox", 20*time.Second)) + + inboundContact := randomPhoneNumber() + receiveInbound(ctx, t, enabledPhone.PhoneAPIKey, inboundContact, enabledPhone.PhoneNumber, "New incoming stays in inbox", time.Now()) + require.NotNil(t, waitForThread(ctx, t, enabledPhone.PhoneNumber, inboundContact, false, "New incoming stays in inbox", 20*time.Second)) +} + +func TestArchiveNewOutgoingMessageThreadsToggleAndPartialUpdates(t *testing.T) { + ctx := context.Background() + phone := setupPhone(ctx, t, 60) + + require.True(t, updatePhoneThreadSettings(ctx, t, phone, map[string]bool{"archive_new_outgoing_message_threads": true}).ArchiveNewOutgoingMessageThreads) + settings := updatePhoneThreadSettings(ctx, t, phone, map[string]bool{"unarchive_thread": true}) + assert.True(t, settings.ArchiveNewOutgoingMessageThreads, "omitting the setting must preserve it") + assert.True(t, settings.UnarchiveThread) + + archivedContact := randomPhoneNumber() + sendThreadMessage(ctx, t, phone, archivedContact, "Archived before disabling") + require.NotNil(t, waitForThread(ctx, t, phone.PhoneNumber, archivedContact, true, "Archived before disabling", 20*time.Second)) + + settings = updatePhoneThreadSettings(ctx, t, phone, map[string]bool{"archive_new_outgoing_message_threads": false}) + require.False(t, settings.ArchiveNewOutgoingMessageThreads) + assert.True(t, settings.UnarchiveThread, "the two settings must be independent") + + inboxContact := randomPhoneNumber() + sendThreadMessage(ctx, t, phone, inboxContact, "Inbox after disabling") + require.NotNil(t, waitForThread(ctx, t, phone.PhoneNumber, inboxContact, false, "Inbox after disabling", 20*time.Second)) + assert.NotNil(t, findThreadByContact(fetchThreads(ctx, t, phone.PhoneNumber, true), archivedContact), "disabling must not change existing archived threads") +} + +func TestArchiveNewOutgoingMessageThreadsReplyRespectsUnarchiveSetting(t *testing.T) { + for _, test := range []struct { + name string + unarchive bool + }{ + {name: "reply remains archived", unarchive: false}, + {name: "reply returns to inbox", unarchive: true}, + } { + t.Run(test.name, func(t *testing.T) { + ctx := context.Background() + phone := setupPhone(ctx, t, 60) + updatePhoneThreadSettings(ctx, t, phone, map[string]bool{ + "archive_new_outgoing_message_threads": true, + "unarchive_thread": test.unarchive, + }) + contact := randomPhoneNumber() + sendThreadMessage(ctx, t, phone, contact, "Outgoing starts archived") + require.NotNil(t, waitForThread(ctx, t, phone.PhoneNumber, contact, true, "Outgoing starts archived", 20*time.Second)) + + receiveInbound(ctx, t, phone.PhoneAPIKey, contact, phone.PhoneNumber, "Incoming reply", time.Now()) + + thread := waitForThread(ctx, t, phone.PhoneNumber, contact, !test.unarchive, "Incoming reply", 20*time.Second) + require.NotNil(t, thread) + assert.Equal(t, !test.unarchive, thread.IsArchived) + assert.Nil(t, findThreadByContact(fetchThreads(ctx, t, phone.PhoneNumber, test.unarchive), contact)) + }) + } +} diff --git a/tests/helpers_test.go b/tests/helpers_test.go index 797b2f09c..6d8106a33 100644 --- a/tests/helpers_test.go +++ b/tests/helpers_test.go @@ -466,6 +466,7 @@ func fireEvent(ctx context.Context, t *testing.T, phoneAPIKey string, messageID func pollMessageStatus(ctx context.Context, t *testing.T, messageID string, targetStatus string, timeout time.Duration) httpsms.Message { t.Helper() deadline := time.Now().Add(timeout) + lastStatus := "not observed" for time.Now().Before(deadline) { url := fmt.Sprintf("%s/v1/messages/%s", apiBaseURL, messageID) @@ -480,8 +481,11 @@ func pollMessageStatus(ctx context.Context, t *testing.T, messageID string, targ resp.Body.Close() if readErr == nil && resp.StatusCode == http.StatusOK { var result httpsms.MessageResponse - if json.Unmarshal(body, &result) == nil && result.Data.Status == targetStatus { - return result.Data + if json.Unmarshal(body, &result) == nil { + lastStatus = result.Data.Status + if result.Data.Status == targetStatus { + return result.Data + } } } } @@ -490,7 +494,7 @@ func pollMessageStatus(ctx context.Context, t *testing.T, messageID string, targ time.Sleep(500 * time.Millisecond) } - t.Fatalf("message %s did not reach status %q within %v", messageID, targetStatus, timeout) + t.Fatalf("message %s did not reach status %q within %v (last observed status: %q)", messageID, targetStatus, timeout, lastStatus) return httpsms.Message{} } diff --git a/tests/unarchive_thread_integration_test.go b/tests/unarchive_thread_integration_test.go index 35ee8bf56..c7fb3f55c 100644 --- a/tests/unarchive_thread_integration_test.go +++ b/tests/unarchive_thread_integration_test.go @@ -21,6 +21,7 @@ type integrationThread struct { Contact string `json:"contact"` Owner string `json:"owner"` IsArchived bool `json:"is_archived"` + UnreadCount uint `json:"unread_count"` LastMessageContent *string `json:"last_message_content"` } diff --git a/tests/unread_filter_integration_test.go b/tests/unread_filter_integration_test.go new file mode 100644 index 000000000..6366517b5 --- /dev/null +++ b/tests/unread_filter_integration_test.go @@ -0,0 +1,144 @@ +package tests + +import ( + "context" + "fmt" + "net/http" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func fetchFilteredThreads(ctx context.Context, t *testing.T, owner string, archived, unreadOnly bool, limit, skip int) []integrationThread { + t.Helper() + + query := url.Values{ + "owner": {owner}, + "is_archived": {fmt.Sprint(archived)}, + "unread_only": {fmt.Sprint(unreadOnly)}, + "limit": {fmt.Sprint(limit)}, + "skip": {fmt.Sprint(skip)}, + } + var response struct { + Data []integrationThread `json:"data"` + } + requestJSON(ctx, t, http.MethodGet, "/v1/message-threads?"+query.Encode(), userAPIKey, nil, http.StatusOK, &response) + return response.Data +} + +func TestMessageThreadUnreadFilter(t *testing.T) { + ctx := context.Background() + phone := setupPhone(ctx, t, 60) + otherPhone := setupPhone(ctx, t, 60) + require.NotEqual(t, phone.PhoneNumber, otherPhone.PhoneNumber) + unreadContact := randomPhoneNumber() + readContact := randomPhoneNumber() + require.NotEqual(t, unreadContact, readContact) + + receiveInbound(ctx, t, phone.PhoneAPIKey, unreadContact, phone.PhoneNumber, "Older unread message", time.Now().Add(-2*time.Minute)) + unread := waitForThread(ctx, t, phone.PhoneNumber, unreadContact, false, "Older unread message", 20*time.Second) + require.NotNil(t, unread) + require.Equal(t, uint(1), unread.UnreadCount) + + receiveInbound(ctx, t, phone.PhoneAPIKey, readContact, phone.PhoneNumber, "Recent read message", time.Now().Add(-time.Minute)) + read := waitForThread(ctx, t, phone.PhoneNumber, readContact, false, "Recent read message", 20*time.Second) + require.NotNil(t, read) + clearMessageThreadUnreadCount(ctx, t, read.ID) + + receiveInbound(ctx, t, otherPhone.PhoneAPIKey, unreadContact, otherPhone.PhoneNumber, "Other phone unread", time.Now()) + require.NotNil(t, waitForThread(ctx, t, otherPhone.PhoneNumber, unreadContact, false, "Other phone unread", 20*time.Second)) + + all := fetchThreads(ctx, t, phone.PhoneNumber, false) + require.Len(t, all, 2, "All defaults to including read and unread conversations for this phone") + assert.Equal(t, []string{read.ID, unread.ID}, []string{all[0].ID, all[1].ID}) + + filtered := fetchFilteredThreads(ctx, t, phone.PhoneNumber, false, true, 1, 0) + require.Len(t, filtered, 1, "the unread condition must run before the result limit") + assert.Equal(t, unread.ID, filtered[0].ID) + assert.Positive(t, filtered[0].UnreadCount) + assert.Empty(t, fetchFilteredThreads(ctx, t, phone.PhoneNumber, false, true, 1, 1), "offset must apply to the filtered result") + + all = fetchFilteredThreads(ctx, t, phone.PhoneNumber, false, false, 1, 0) + require.Len(t, all, 1) + assert.Equal(t, read.ID, all[0].ID, "All must restore the most recent read conversation") + + archiveThread(ctx, t, unread.ID) + assert.Empty(t, fetchFilteredThreads(ctx, t, phone.PhoneNumber, false, true, 20, 0), "archived conversations must not appear in the inbox unread filter") + filtered = fetchFilteredThreads(ctx, t, phone.PhoneNumber, true, true, 20, 0) + require.Len(t, filtered, 1) + assert.Equal(t, unread.ID, filtered[0].ID) + assert.True(t, filtered[0].IsArchived) + + clearMessageThreadUnreadCount(ctx, t, unread.ID) + assert.Empty(t, fetchFilteredThreads(ctx, t, phone.PhoneNumber, true, true, 20, 0), "marking a conversation read removes it from Unread") + all = fetchFilteredThreads(ctx, t, phone.PhoneNumber, true, false, 20, 0) + require.Len(t, all, 1, "marking read must not delete or unarchive the conversation") + assert.Equal(t, unread.ID, all[0].ID) + assert.Zero(t, all[0].UnreadCount) + assert.True(t, all[0].IsArchived) +} + +func TestMessageThreadUnreadFilterRejectsInvalidValue(t *testing.T) { + query := url.Values{ + "owner": {randomPhoneNumber()}, + "unread_only": {"invalid"}, + } + requestJSON(context.Background(), t, http.MethodGet, "/v1/message-threads?"+query.Encode(), userAPIKey, nil, http.StatusUnprocessableEntity, nil) +} + +func TestMessageThreadPaginationReturnsRequestedPages(t *testing.T) { + ctx := context.Background() + phone := setupPhone(ctx, t, 60) + timestamp := time.Now().Add(-time.Minute).Truncate(time.Second) + for index := 0; index < 20; index++ { + receiveInbound(ctx, t, phone.PhoneAPIKey, fmt.Sprintf("+1800554%04d", index), phone.PhoneNumber, "Pagination thread", timestamp) + } + + var first []integrationThread + deadline := time.Now().Add(20 * time.Second) + for time.Now().Before(deadline) { + first = fetchFilteredThreads(ctx, t, phone.PhoneNumber, false, false, 20, 0) + if len(first) == 20 { + break + } + time.Sleep(100 * time.Millisecond) + } + require.Len(t, first, 20) + assert.Empty(t, fetchFilteredThreads(ctx, t, phone.PhoneNumber, false, false, 20, 20)) + + receiveInbound(ctx, t, phone.PhoneAPIKey, "+18005540020", phone.PhoneNumber, "One extra thread", timestamp) + var second []integrationThread + deadline = time.Now().Add(20 * time.Second) + for time.Now().Before(deadline) { + second = fetchFilteredThreads(ctx, t, phone.PhoneNumber, false, false, 20, 20) + if len(second) == 1 { + break + } + time.Sleep(100 * time.Millisecond) + } + require.Len(t, second, 1) + first = fetchFilteredThreads(ctx, t, phone.PhoneNumber, false, false, 20, 0) + require.Len(t, first, 20) + assert.Empty(t, fetchFilteredThreads(ctx, t, phone.PhoneNumber, false, false, 20, 40)) + seen := map[string]bool{} + for _, thread := range first { + seen[thread.ID] = true + } + assert.False(t, seen[second[0].ID], "equal timestamps must not duplicate threads between pages") + + var individual struct { + Data integrationThread `json:"data"` + } + path := "/v1/message-threads/" + second[0].ID + requestJSON(ctx, t, http.MethodGet, path, userAPIKey, nil, http.StatusOK, &individual) + assert.Equal(t, second[0].ID, individual.Data.ID, "an older conversation must load independently of the first page") + assert.Equal(t, phone.PhoneNumber, individual.Data.Owner) + archiveThread(ctx, t, individual.Data.ID) + requestJSON(ctx, t, http.MethodGet, path, userAPIKey, nil, http.StatusOK, &individual) + assert.True(t, individual.Data.IsArchived, "direct lookup must also resolve archived conversations") + requestJSON(ctx, t, http.MethodGet, path, systemAPIKey, nil, http.StatusNotFound, nil) + requestJSON(ctx, t, http.MethodGet, "/v1/message-threads/invalid", userAPIKey, nil, http.StatusUnprocessableEntity, nil) +} diff --git a/web/app/components/ContactDialog.vue b/web/app/components/ContactDialog.vue index 2df329a93..65e559521 100644 --- a/web/app/components/ContactDialog.vue +++ b/web/app/components/ContactDialog.vue @@ -45,7 +45,7 @@ const props = withDefaults( ) const emit = defineEmits<{ - saved: [] + saved: [contacts: EntitiesContact[]] 'update:modelValue': [value: boolean] }>() @@ -183,17 +183,17 @@ async function submitForm() { const payload = buildPayload() saving.value = true try { - if (props.contact) { - await contactsStore.updateContact(props.contact.id, payload, { - refresh: props.refreshContacts, - }) - } else { - await contactsStore.saveContacts([payload], { - refresh: props.refreshContacts, - }) - } + const savedContacts = props.contact + ? [ + await contactsStore.updateContact(props.contact.id, payload, { + refresh: props.refreshContacts, + }), + ] + : await contactsStore.saveContacts([payload], { + refresh: props.refreshContacts, + }) closeDialog() - emit('saved') + emit('saved', savedContacts) } catch (error: unknown) { const bag = new ErrorMessages() const messages = fieldErrorsFromApi(error) diff --git a/web/app/components/FirebaseAuth.vue b/web/app/components/FirebaseAuth.vue index 5f783e07c..659b426aa 100644 --- a/web/app/components/FirebaseAuth.vue +++ b/web/app/components/FirebaseAuth.vue @@ -300,7 +300,7 @@ function getGeneralErrorMessage( block color="white" size="large" - class="mb-3 position-relative" + class="mb-3 position-relative overflow-visible" :loading="loading" :disabled="loading" @click="signInWithGoogle" @@ -324,7 +324,7 @@ function getGeneralErrorMessage( size="large" variant="flat" color="black" - class="mb-3 position-relative" + class="mb-3 position-relative overflow-visible" :loading="loading" :disabled="loading" @click="signInWithGithub" @@ -349,7 +349,7 @@ function getGeneralErrorMessage( size="large" variant="flat" color="red" - class="mb-3 position-relative" + class="mb-3 position-relative overflow-visible" :disabled="loading" @click="showEmailForm = true" > diff --git a/web/app/components/MessageThread.vue b/web/app/components/MessageThread.vue index 2a699408a..b0a01dd0c 100644 --- a/web/app/components/MessageThread.vue +++ b/web/app/components/MessageThread.vue @@ -49,6 +49,18 @@ function onInstallApp() { }) } +async function loadMoreThreads() { + try { + await threadsStore.loadMoreThreads() + } catch (error) { + notificationsStore.addNotification({ + type: 'error', + message: 'More conversations could not be loaded', + }) + console.error(error) + } +} + function isWhatsappFormatted(thread: EntitiesMessageThread): boolean { const phone = phonesStore.phones.find((x) => x.phone_number === thread.owner) return isUrlNotificationToken(phone?.fcm_token) @@ -94,7 +106,8 @@ function threadAvatarInitial(thread: EntitiesMessageThread): string {
+

+ No unread conversations +

+
+ + Load More + +
diff --git a/web/app/components/MessageThreadHeader.vue b/web/app/components/MessageThreadHeader.vue index 12917b598..e1a8a318b 100644 --- a/web/app/components/MessageThreadHeader.vue +++ b/web/app/components/MessageThreadHeader.vue @@ -67,6 +67,24 @@ async function toggleArchive() { await threadsStore.loadThreads() } +async function filterThreads(unreadOnly: boolean) { + if (threadsStore.unreadOnly === unreadOnly) return + + const previous = threadsStore.unreadOnly + threadsStore.unreadOnly = unreadOnly + threadsStore.loadingThreads = true + try { + await threadsStore.loadThreads() + } catch (error) { + threadsStore.unreadOnly = previous + notificationsStore.addNotification({ + type: 'error', + message: 'The message thread filter could not be updated', + }) + console.error(error) + } +} + async function logout() { const auth = getAuth() await signOut(auth) @@ -83,134 +101,176 @@ async function logout() { diff --git a/web/app/composables/useApi.ts b/web/app/composables/useApi.ts index b69020c03..4c6e9a836 100644 --- a/web/app/composables/useApi.ts +++ b/web/app/composables/useApi.ts @@ -1,5 +1,3 @@ -import type { $Fetch } from 'ofetch' - let authToken: string | null = null let apiKey: string | null = null @@ -11,7 +9,7 @@ export function setApiKey(key: string | null) { apiKey = key } -function createApiFetch(): $Fetch { +function createApiFetch() { const config = useRuntimeConfig() const publicConfig = config.public as Record const baseURL = publicConfig.apiBaseUrl diff --git a/web/app/layouts/default.vue b/web/app/layouts/default.vue index 47c901de3..12f3dfc52 100644 --- a/web/app/layouts/default.vue +++ b/web/app/layouts/default.vue @@ -68,10 +68,12 @@ function startPoller() { @@ -96,8 +98,12 @@ function startPoller() { padding-left: 400px; } } + .message-thread-sidebar { + height: 100dvh; + } .v-navigation-drawer__message-thread { - height: calc(100vh - 120px); + flex: 1; + min-height: 0; &::-webkit-scrollbar { width: 8px; } diff --git a/web/app/pages/bulk-messages/index.vue b/web/app/pages/bulk-messages/index.vue index b25c64c07..cc3bf02dd 100644 --- a/web/app/pages/bulk-messages/index.vue +++ b/web/app/pages/bulk-messages/index.vue @@ -31,7 +31,7 @@ function parseErrors(error: unknown): ErrorMessages { const bag = new ErrorMessages() const data = toApiError(error).data?.data if (data && typeof data === 'object') { - Object.keys(data).forEach((key) => bag.addMany(key, data[key])) + Object.entries(data).forEach(([key, values]) => bag.addMany(key, values)) } return bag } @@ -42,7 +42,7 @@ function cleanName(requestId: string): string { if (requestId.startsWith('bulk-xls-')) return requestId.replace(/^bulk-xls-/, '') + '.xlsx' const newFormatMatch = requestId.match(/^bulk-[0-9A-Za-z]+-(.+)$/) - if (newFormatMatch) return newFormatMatch[1] + if (newFormatMatch?.[1]) return newFormatMatch[1] return requestId.replace(/^bulk-/, '') } diff --git a/web/app/pages/index.vue b/web/app/pages/index.vue index be30493f7..b0077847a 100644 --- a/web/app/pages/index.vue +++ b/web/app/pages/index.vue @@ -53,9 +53,7 @@ const pricing = ref(0) const pricingLabels = ['10K', '20K', '50K', '100K', '200K'] const pricingLabelsFull = ['10,000', '20,000', '50,000', '100,000', '200,000'] -const planMessages = computed(() => - pricingLabels[pricing.value].replace('K', ',000'), -) +const planMessages = computed(() => pricingLabelsFull[pricing.value]) const planMonthlyPrice = computed(() => [20, 35, 89, 175, 350][pricing.value]) const planYearlyPrice = computed( () => [200, 350, 1068, 2100, 4200][pricing.value], diff --git a/web/app/pages/phone-api-keys/index.vue b/web/app/pages/phone-api-keys/index.vue index aded9453d..5b3e4779f 100644 --- a/web/app/pages/phone-api-keys/index.vue +++ b/web/app/pages/phone-api-keys/index.vue @@ -44,7 +44,7 @@ function parseErrors(error: unknown): ErrorMessages { const bag = new ErrorMessages() const data = toApiError(error).data?.data if (data && typeof data === 'object') { - Object.keys(data).forEach((key) => bag.addMany(key, data[key])) + Object.entries(data).forEach(([key, values]) => bag.addMany(key, values)) } return bag } diff --git a/web/app/pages/search-messages/index.vue b/web/app/pages/search-messages/index.vue index 324131b7b..c77d5b7ef 100644 --- a/web/app/pages/search-messages/index.vue +++ b/web/app/pages/search-messages/index.vue @@ -152,7 +152,7 @@ function parseErrors(error: unknown): ErrorMessages { const bag = new ErrorMessages() const data = toApiError(error).data?.data if (data && typeof data === 'object') { - Object.keys(data).forEach((key) => bag.addMany(key, data[key])) + Object.entries(data).forEach(([key, values]) => bag.addMany(key, values)) } return bag } diff --git a/web/app/pages/settings/index.vue b/web/app/pages/settings/index.vue index f4c312035..cd608ef1a 100644 --- a/web/app/pages/settings/index.vue +++ b/web/app/pages/settings/index.vue @@ -1698,6 +1698,15 @@ onMounted(async () => { placeholder="We are currently closed at the moment, please send us a text message from 09:00 to 17:00" hint="Here you can configure an automated SMS message which is sent to the caller when this phone has a missed call" /> + { const polling = ref(false) const appData = computed(() => { - const publicConfig = config.public as Record + const publicConfig = config.public let url = publicConfig.appUrl || '' if (url.endsWith('/')) { url = url.substring(0, url.length - 1) @@ -29,9 +29,7 @@ export const useAppStore = defineStore('app', () => { } }) - const isLocal = computed( - () => (config.public as Record).appEnv === 'local', - ) + const isLocal = computed(() => config.public.appEnv === 'local') function setPolling(value: boolean) { polling.value = value diff --git a/web/app/stores/contacts.ts b/web/app/stores/contacts.ts index afa798f72..ce149410f 100644 --- a/web/app/stores/contacts.ts +++ b/web/app/stores/contacts.ts @@ -117,14 +117,15 @@ export const useContactsStore = defineStore('contacts', () => { async function saveContacts( items: ContactInput | ContactInput[], options: ContactMutationOptions = {}, - ): Promise { + ): Promise { const contactsToSave = Array.isArray(items) ? items : [items] const { refresh = true } = options loading.value = true try { + let response: { data: EntitiesContact[] } try { - await apiFetch('/v1/contacts', { + response = await apiFetch<{ data: EntitiesContact[] }>('/v1/contacts', { method: 'POST', body: contactsToSave, }) @@ -146,6 +147,7 @@ export const useContactsStore = defineStore('contacts', () => { if (refresh) { await loadContacts(true) } + return response.data } finally { loading.value = false } @@ -155,15 +157,19 @@ export const useContactsStore = defineStore('contacts', () => { id: string, payload: ContactInput, options: ContactMutationOptions = {}, - ): Promise { + ): Promise { const { refresh = true } = options loading.value = true try { + let response: { data: EntitiesContact } try { - await apiFetch(`/v1/contacts/${id}`, { - method: 'PUT', - body: payload, - }) + response = await apiFetch<{ data: EntitiesContact }>( + `/v1/contacts/${id}`, + { + method: 'PUT', + body: payload, + }, + ) } catch (error: unknown) { notificationsStore.addNotification({ message: getApiErrorMessage(error, 'Error while updating contact'), @@ -179,6 +185,7 @@ export const useContactsStore = defineStore('contacts', () => { if (refresh) { await loadContacts(true) } + return response.data } finally { loading.value = false } diff --git a/web/app/stores/phones.ts b/web/app/stores/phones.ts index 276fe2928..4c6a7a597 100644 --- a/web/app/stores/phones.ts +++ b/web/app/stores/phones.ts @@ -61,6 +61,8 @@ export const usePhonesStore = defineStore('phones', () => { messages_per_minute: parseInt(phone.messages_per_minute.toString()), message_send_schedule_id: phone.message_send_schedule_id ?? null, unarchive_thread: phone.unarchive_thread ?? false, + archive_new_outgoing_message_threads: + phone.archive_new_outgoing_message_threads ?? false, }, }) notificationsStore.addNotification({ diff --git a/web/app/stores/threads.test.ts b/web/app/stores/threads.test.ts new file mode 100644 index 000000000..2580efd8f --- /dev/null +++ b/web/app/stores/threads.test.ts @@ -0,0 +1,585 @@ +import assert from 'node:assert/strict' +import { test } from 'node:test' +import { createPinia, setActivePinia } from 'pinia' +import { computed, ref } from 'vue' +import type { + EntitiesContact, + EntitiesMessageThread, +} from '../../shared/types/api' +import { useThreadsStore } from './threads' + +function thread(id: string, unreadCount: number): EntitiesMessageThread { + return { + id, + unread_count: unreadCount, + owner: '+18005550199', + contact: '+18005550100', + color: 'indigo', + is_archived: false, + last_message_content: 'Hello', + last_message_id: 'message-id', + status: 'received', + created_at: '2026-10-27T10:00:00Z', + updated_at: '2026-10-27T10:00:00Z', + order_timestamp: '2026-10-27T10:00:00Z', + user_id: 'user-id', + } +} + +function setupStore() { + const calls: { path: string; params?: Record }[] = [] + let responseThreads = [thread('unread', 2), thread('read', 0)] + const individualThreads = new Map() + let nextLookup: Promise<{ data: EntitiesMessageThread }> | undefined + let nextResponse: Promise<{ data: EntitiesMessageThread[] }> | undefined + const phones = { + owner: '+18005550199', + phones: [], + getHeartbeat: async () => [], + } + Object.assign(globalThis, { + ref, + computed, + useApi: () => ({ + apiFetch: async ( + path: string, + options: { + params?: Record + method?: string + } = {}, + ) => { + calls.push({ path, params: options.params }) + if (options.method === 'PUT') { + responseThreads = responseThreads.map((item) => + item.id === 'unread' ? { ...item, unread_count: 0 } : item, + ) + return { data: thread('unread', 0) } + } + if (path === '/v1/messages') return { data: [] } + if ( + (!options.method || options.method === 'GET') && + path.startsWith('/v1/message-threads/') + ) { + if (nextLookup) { + const pending = nextLookup + nextLookup = undefined + return pending + } + const item = individualThreads.get(path.split('/').at(-1) ?? '') + if (!item) + throw Object.assign(new Error('Thread not found'), { status: 404 }) + return { data: item } + } + const pending = nextResponse + nextResponse = undefined + if (pending) return pending + const skip = Number(options.params?.skip ?? 0) + const limit = Number(options.params?.limit ?? 20) + return { + data: responseThreads.slice(skip, skip + limit), + } + }, + }), + usePhonesStore: () => phones, + useNotificationsStore: () => ({ addNotification: () => {} }), + }) + setActivePinia(createPinia()) + return { + store: useThreadsStore(), + phones, + calls, + respondWith: (threads: EntitiesMessageThread[]) => { + responseThreads = threads + }, + respondToLookup: (item: EntitiesMessageThread) => { + individualThreads.set(item.id, item) + }, + deferLookup: (response: Promise<{ data: EntitiesMessageThread }>) => { + nextLookup = response + }, + deferNextLoad: (response: Promise<{ data: EntitiesMessageThread[] }>) => { + nextResponse = response + }, + } +} + +function contact( + name: string, + phoneNumbers = ['+18005550100'], +): EntitiesContact { + return { + id: 'contact-id', + name, + phone_numbers: phoneNumbers, + emails: [], + properties: {}, + user_id: 'user-id', + created_at: '2026-10-27T10:00:00Z', + updated_at: '2026-10-27T11:00:00Z', + } +} + +async function setupReadConversationHistory() { + const fixture = setupStore() + const other = { ...thread('other', 1), contact: '+18005550101' } + fixture.respondWith([thread('unread', 1), other]) + await fixture.store.loadThreads() + fixture.store.setThreadId('unread') + fixture.store.unreadOnly = true + await fixture.store.resetThreadUnreadCount('unread') + fixture.respondWith([other]) + await fixture.store.loadThreads() + await fixture.store.loadThreadMessages('other') + return fixture +} + +test('Unread requests server-side filtering within the API limit', async () => { + const { store, calls } = setupStore() + store.unreadOnly = true + store.archivedThreads = true + + await store.loadThreads() + + assert.equal(calls[0]?.params?.unread_only, true) + assert.equal(calls[0]?.params?.is_archived, true) + assert.equal(calls[0]?.params?.owner, '+18005550199') + assert.equal(calls[0]?.params?.limit, 20) +}) + +test('marking a thread read hides it from Unread without closing it', async () => { + const { store, calls, respondWith } = setupStore() + await store.loadThreads() + store.setThreadId('unread') + store.unreadOnly = true + + await store.resetThreadUnreadCount('unread') + + assert.ok(store.visibleThreads) + assert.deepEqual(store.visibleThreads, []) + assert.equal(store.currentThread?.id, 'unread') + assert.equal(store.currentThread.unread_count, 0) + + respondWith([]) + await store.loadThreads() + assert.equal(store.currentThread?.id, 'unread') + await store.loadThreadMessages('unread') + assert.equal(calls.at(-1)?.path, '/v1/messages') +}) + +test('switching back to All restores read conversations', async () => { + const { store } = setupStore() + await store.loadThreads() + store.unreadOnly = true + + assert.ok(store.visibleThreads) + assert.deepEqual( + store.visibleThreads.map((item) => item.id), + ['unread'], + ) + + store.unreadOnly = false + await store.loadThreads() + assert.deepEqual( + store.visibleThreads.map((item) => item.id), + ['unread', 'read'], + ) +}) + +test('resetting the store clears the filter and retained conversation', async () => { + const { store, respondWith } = setupStore() + await store.loadThreads() + store.setThreadId('unread') + store.unreadOnly = true + respondWith([]) + await store.loadThreads() + + store.resetState() + + assert.equal(store.unreadOnly, false) + assert.equal(store.currentThread, null) + assert.deepEqual(store.threads, []) +}) + +test('an older polling response cannot overwrite a newer unread-filter load', async () => { + const { store, respondWith, deferNextLoad } = setupStore() + const pending = Promise.withResolvers<{ data: EntitiesMessageThread[] }>() + deferNextLoad(pending.promise) + const oldLoad = store.loadThreads() + store.unreadOnly = true + respondWith([thread('unread', 2)]) + await store.loadThreads() + + pending.resolve({ data: [thread('read', 0)] }) + await oldLoad + + assert.deepEqual( + store.threads.map((item) => item.id), + ['unread'], + ) +}) + +test('All does not retain a conversation removed from the server list', async () => { + const { store, respondWith } = setupStore() + await store.loadThreads() + store.setThreadId('unread') + respondWith([]) + + await store.loadThreads() + + assert.equal(store.currentThread, null) +}) + +for (const scenario of [ + { + name: 'adding a contact refreshes a retained read conversation', + existing: undefined, + saved: contact('New contact'), + expectedName: 'New contact', + }, + { + name: 'editing a contact refreshes a retained read conversation', + existing: contact('Old name'), + saved: contact('Updated name'), + expectedName: 'Updated name', + }, + { + name: 'removing the conversation number clears its retained contact', + existing: contact('Old name'), + saved: contact('Updated name', ['+18005550101']), + expectedName: undefined, + }, +]) { + test(scenario.name, async () => { + const { store, respondWith } = setupStore() + respondWith([ + { ...thread('unread', 1), contact_details: scenario.existing }, + ]) + await store.loadThreads() + store.setThreadId('unread') + store.unreadOnly = true + await store.resetThreadUnreadCount('unread') + respondWith([]) + await store.loadThreads() + + await store.loadThreads([scenario.saved]) + + assert.equal(store.currentThread?.id, 'unread') + assert.equal( + store.currentThread.contact_details?.name, + scenario.expectedName, + ) + assert.deepEqual(store.visibleThreads, []) + }) +} + +test('a late poll cannot restore unread counts after mark-read succeeds', async () => { + const { store, deferNextLoad } = setupStore() + await store.loadThreads() + store.setThreadId('unread') + store.unreadOnly = true + const pending = Promise.withResolvers<{ data: EntitiesMessageThread[] }>() + deferNextLoad(pending.promise) + store.loadingThreads = true + const poll = store.loadThreads() + + await store.resetThreadUnreadCount('unread') + pending.resolve({ data: [thread('unread', 2)] }) + await poll + + assert.equal(store.currentThread?.unread_count, 0) + assert.deepEqual(store.visibleThreads, []) + assert.equal(store.loadingThreads, false) +}) + +test('refreshing an overlapping read preserves messages received after the read', async () => { + const { store, deferNextLoad, respondWith } = setupStore() + await store.loadThreads() + store.setThreadId('unread') + store.unreadOnly = true + const pending = Promise.withResolvers<{ data: EntitiesMessageThread[] }>() + deferNextLoad(pending.promise) + const poll = store.loadThreads() + + await store.resetThreadUnreadCount('unread') + respondWith([ + { + ...thread('unread', 1), + last_message_id: 'new-inbound-message', + last_message_content: 'Received after mark-read', + }, + ]) + pending.resolve({ data: [thread('unread', 2)] }) + await poll + + assert.equal(store.currentThread?.unread_count, 1) + assert.equal(store.currentThread.last_message_id, 'new-inbound-message') + assert.equal(store.visibleThreads.length, 1) + assert.equal(store.loadingThreads, false) +}) + +test('Back navigation restores an earlier read conversation in Unread', async () => { + const { store, calls } = await setupReadConversationHistory() + + store.setThreadId('unread') + + assert.equal(store.currentThread?.id, 'unread') + assert.equal(store.currentThread.unread_count, 0) + await store.loadThreadMessages('unread') + assert.equal(calls.at(-1)?.params?.contact, '+18005550100') + assert.deepEqual( + store.visibleThreads.map((item) => item.id), + ['other'], + ) +}) + +test('saved contacts refresh previously opened read conversations too', async () => { + const { store } = await setupReadConversationHistory() + await store.loadThreads([contact('Alice')]) + + store.setThreadId('unread') + + assert.equal(store.currentThread?.contact_details?.name, 'Alice') +}) + +for (const action of ['reset', 'delete', 'archive', 'owner', 'view']) { + test(`navigation cache does not restore conversations after ${action}`, async () => { + const { store, phones, respondWith } = await setupReadConversationHistory() + if (action === 'reset') store.resetState() + if (action === 'delete') await store.deleteThread('unread') + if (action === 'archive') { + await store.updateThread({ threadId: 'unread', isArchived: true }) + } + if (action === 'owner' || action === 'view') { + if (action === 'owner') phones.owner = '+18005550198' + if (action === 'view') store.archivedThreads = true + respondWith([]) + await store.loadThreads() + } + + store.setThreadId('unread') + + assert.equal(store.currentThread, null) + assert.equal(store.hasThreadId('unread'), false) + }) +} + +function manyThreads(count: number) { + return Array.from({ length: count }, (_, index) => ({ + ...thread(`page-${index}`, 1), + contact: `+1800555${String(index).padStart(4, '0')}`, + })) +} + +for (const count of [0, 19, 20, 21]) { + test(`Load More is available when the last page is full (${count})`, async () => { + const { store, respondWith } = setupStore() + respondWith(manyThreads(count)) + + await store.loadThreads() + + assert.equal(store.threads.length, Math.min(count, 20)) + assert.equal(store.hasMoreThreads, count >= 20) + }) +} + +test('Load More expands the loaded list and disappears on the final page', async () => { + const { store, respondWith, calls } = setupStore() + respondWith(manyThreads(45)) + await store.loadThreads() + + await store.loadMoreThreads() + assert.equal(store.threads.length, 40) + assert.equal(store.hasMoreThreads, true) + await store.loadMoreThreads() + assert.equal(store.threads.length, 45) + assert.equal(new Set(store.threads.map((item) => item.id)).size, 45) + assert.equal(store.hasMoreThreads, false) + assert.equal(store.loadingMoreThreads, false) + const completedCalls = calls.length + await store.loadMoreThreads() + assert.equal(calls.length, completedCalls) +}) + +for (const count of [20, 40]) { + test(`an empty final page hides Load More without losing ${count} loaded threads`, async () => { + const { store, respondWith } = setupStore() + respondWith(manyThreads(count)) + await store.loadThreads() + if (count === 40) await store.loadMoreThreads() + assert.equal(store.threads.length, count) + assert.equal(store.hasMoreThreads, true) + + await store.loadMoreThreads() + + assert.equal(store.threads.length, count) + assert.equal(store.hasMoreThreads, false) + assert.equal(store.loadingMoreThreads, false) + }) +} + +test('polling preserves loaded pages and changing filters resets them', async () => { + const { store, respondWith } = setupStore() + respondWith(manyThreads(45)) + await store.loadThreads() + await store.loadMoreThreads() + + await store.loadThreads() + assert.equal(store.threads.length, 40) + + store.unreadOnly = true + await store.loadThreads() + assert.equal(store.threads.length, 20) + await store.loadMoreThreads() + store.archivedThreads = true + await store.loadThreads() + assert.equal(store.threads.length, 20) +}) + +test('switching phones resets loaded pages', async () => { + const { store, respondWith, phones } = setupStore() + respondWith(manyThreads(45)) + await store.loadThreads() + await store.loadMoreThreads() + + phones.owner = '+18005550198' + await store.loadThreads() + + assert.equal(store.threads.length, 20) +}) + +test('Load More failure preserves rows and permits a retry', async () => { + const { store, respondWith, deferNextLoad } = setupStore() + respondWith(manyThreads(45)) + await store.loadThreads() + const pending = Promise.withResolvers<{ data: EntitiesMessageThread[] }>() + deferNextLoad(pending.promise) + + const loading = store.loadMoreThreads() + pending.reject(new Error('Network failed')) + await assert.rejects(loading, /Network failed/) + + assert.equal(store.threads.length, 20) + assert.equal(store.hasMoreThreads, true) + assert.equal(store.loadingMoreThreads, false) + await store.loadMoreThreads() + assert.equal(store.threads.length, 40) +}) + +test('new activity does not close an open conversation pushed beyond the loaded page', async () => { + const { store, respondWith } = setupStore() + const existing = manyThreads(45) + respondWith(existing) + await store.loadThreads() + store.setThreadId('page-19') + respondWith([thread('newest', 1), ...existing]) + + await store.loadThreads() + + assert.equal(store.currentThread?.id, 'page-19') + await store.loadThreadMessages('page-19') +}) + +test('a second Load More click is ignored while the first is running', async () => { + const { store, respondWith, deferNextLoad } = setupStore() + const existing = manyThreads(65) + respondWith(existing) + await store.loadThreads() + const pending = Promise.withResolvers<{ data: EntitiesMessageThread[] }>() + deferNextLoad(pending.promise) + + const loading = store.loadMoreThreads() + assert.equal(store.loadingMoreThreads, true) + await store.loadMoreThreads() + pending.resolve({ data: existing.slice(0, 20) }) + await loading + + assert.equal(store.threads.length, 40) + assert.equal(store.loadingMoreThreads, false) +}) + +test('changing filters cancels an older Load More result', async () => { + const { store, respondWith, deferNextLoad } = setupStore() + const existing = manyThreads(45) + respondWith(existing) + await store.loadThreads() + const pending = Promise.withResolvers<{ data: EntitiesMessageThread[] }>() + deferNextLoad(pending.promise) + + const loading = store.loadMoreThreads() + assert.equal(store.loadingMoreThreads, true) + store.unreadOnly = true + await store.loadThreads() + pending.resolve({ data: existing.slice(0, 20) }) + await loading + + assert.equal(store.threads.length, 20) + assert.equal(store.loadingMoreThreads, false) + await store.loadMoreThreads() + assert.equal(store.threads.length, 40) +}) + +test('a cold deep link resolves an older conversation independently of page one', async () => { + const { store, respondWith, respondToLookup, calls } = setupStore() + const rows = manyThreads(65) + const requested = { + ...thread('page-60', 0), + contact: '+18005550060', + contact_details: contact('Older contact', ['+18005550060']), + } + respondWith(rows) + respondToLookup(requested) + store.setThreadId(requested.id) + + await store.fetchThread(requested.id) + await store.loadThreads() + store.setThreadId(requested.id) + + assert.equal(store.threads.length, 20) + assert.equal(store.currentThread?.id, requested.id) + assert.equal(store.currentThread.contact_details?.name, 'Older contact') + await store.loadThreadMessages(requested.id) + assert.equal(calls.at(-1)?.params?.contact, requested.contact) + assert.equal( + calls.filter((call) => call.path === '/v1/message-threads').length, + 1, + ) +}) + +test('individual lookup preserves a real 404 instead of searching every page', async () => { + const { store, calls } = setupStore() + + await assert.rejects(store.fetchThread('missing'), { status: 404 }) + + assert.equal(calls.length, 1) + assert.equal(calls[0]?.path, '/v1/message-threads/missing') +}) + +test('a successfully fetched conversation remains selectable independently of list metadata', async () => { + const { store, respondWith, respondToLookup } = setupStore() + respondWith(manyThreads(19)) + const requested = thread('directly-requested', 0) + respondToLookup(requested) + await store.loadThreads() + assert.equal(store.hasMoreThreads, false) + + await store.fetchThread(requested.id) + store.setThreadId(requested.id) + + assert.equal(store.currentThread?.id, requested.id) + assert.equal(store.threads.length, 19) +}) + +test('resetting the session cancels an older individual lookup', async () => { + const { store, deferLookup } = setupStore() + const pending = Promise.withResolvers<{ data: EntitiesMessageThread }>() + deferLookup(pending.promise) + const loading = store.fetchThread('old-session') + + store.resetState() + pending.resolve({ data: thread('old-session', 0) }) + + await assert.rejects(loading, { name: 'AbortError' }) + store.unreadOnly = true + store.setThreadId('old-session') + assert.equal(store.currentThread, null) +}) diff --git a/web/app/stores/threads.ts b/web/app/stores/threads.ts index 1abd68bea..77f2ccef0 100644 --- a/web/app/stores/threads.ts +++ b/web/app/stores/threads.ts @@ -1,76 +1,213 @@ import { defineStore } from 'pinia' import type { + EntitiesContact, EntitiesMessageThread, EntitiesMessage, } from '~~/shared/types/api' +const PAGE_SIZE = 20 + export const useThreadsStore = defineStore('threads', () => { const threads = ref([]) const threadId = ref(null) const loadingThreads = ref(true) + const loadingMoreThreads = ref(false) + const hasMoreThreads = ref(false) + let pageCount = 1 + let pageContext = '' + let loadMoreGeneration = 0 const archivedThreads = ref(false) + const unreadOnly = ref(false) + const retainedThread = ref(null) + const openedThreads = ref(new Map()) + const fetchedThreadIds = ref(new Set()) + let threadRequestId = 0 + let stateGeneration = 0 + let readMutationGeneration = 0 const { apiFetch } = useApi() const notificationsStore = useNotificationsStore() const currentThread = computed(() => { - return threads.value.find((x) => x.id === threadId.value) ?? null + return ( + threads.value.find((x) => x.id === threadId.value) ?? + ((unreadOnly.value || + loadingThreads.value || + hasMoreThreads.value || + (threadId.value !== null && + fetchedThreadIds.value.has(threadId.value))) && + retainedThread.value?.id === threadId.value + ? retainedThread.value + : null) + ) }) + const visibleThreads = computed(() => + unreadOnly.value + ? threads.value.filter((thread) => thread.unread_count > 0) + : threads.value, + ) + const hasThread = computed( - () => threadId.value != null && !loadingThreads.value, + () => currentThread.value !== null && !loadingThreads.value, ) function hasThreadId(id: string): boolean { - return threads.value.find((x) => x.id === id) !== undefined + return ( + threads.value.some((thread) => thread.id === id) || + currentThread.value?.id === id + ) } function replaceThread(updatedThread: EntitiesMessageThread) { const index = threads.value.findIndex( (thread) => thread.id === updatedThread.id, ) + const existingThread = + index !== -1 ? threads.value[index] : retainedThread.value + const replacement = { + ...updatedThread, + // Mark-read PUT responses are not contact-enriched. + contact_details: + updatedThread.contact_details ?? + (updatedThread.contact === existingThread?.contact + ? existingThread.contact_details + : undefined), + } if (index !== -1) { - const existingThread = threads.value[index]! - threads.value[index] = { - ...updatedThread, - // Mark-read PUT responses are not contact-enriched; keep the resolved - // contact so display names do not disappear after the thread updates. - contact_details: - updatedThread.contact_details ?? - (updatedThread.contact === existingThread.contact - ? existingThread.contact_details - : undefined), - } + threads.value[index] = replacement + } + if (updatedThread.id === threadId.value) { + retainedThread.value = replacement + } + if ( + openedThreads.value.has(updatedThread.id) || + updatedThread.id === threadId.value + ) { + openedThreads.value.set(updatedThread.id, replacement) } } - async function loadThreads() { + async function loadThreads(savedContacts: EntitiesContact[] = []) { + const requestId = ++threadRequestId + const readGeneration = readMutationGeneration + const selectedThread = currentThread.value + if (selectedThread) { + openedThreads.value.set(selectedThread.id, selectedThread) + } + for (const openedThread of openedThreads.value.values()) { + for (const contact of savedContacts) { + const matchesNumber = contact.phone_numbers.includes( + openedThread.contact, + ) + if (matchesNumber || openedThread.contact_details?.id === contact.id) { + openedThread.contact_details = matchesNumber ? contact : undefined + } + } + } const phonesStore = usePhonesStore() if (phonesStore.owner === null && phonesStore.phones.length === 0) { loadingThreads.value = false + hasMoreThreads.value = false return } - const response = await apiFetch<{ data: EntitiesMessageThread[] }>( - '/v1/message-threads', - { - params: { - owner: phonesStore.owner ?? phonesStore.phones[0]?.phone_number, - limit: 100, - is_archived: archivedThreads.value, - contacts: true, - }, - }, + const owner = phonesStore.owner ?? phonesStore.phones[0]?.phone_number + const archived = archivedThreads.value + const unread = unreadOnly.value + const context = JSON.stringify([owner, archived, unread]) + if (context !== pageContext) { + pageContext = context + pageCount = 1 + hasMoreThreads.value = false + loadingMoreThreads.value = false + loadMoreGeneration++ + } + + try { + const responses = await Promise.all( + Array.from({ length: pageCount }, (_, page) => + apiFetch<{ data: EntitiesMessageThread[] }>('/v1/message-threads', { + params: { + owner, + limit: PAGE_SIZE, + skip: page * PAGE_SIZE, + is_archived: archived, + unread_only: unread, + contacts: true, + }, + }), + ), + ) + if (requestId !== threadRequestId) return + if (readGeneration !== readMutationGeneration) { + await loadThreads() + return + } + + phonesStore.getHeartbeat().catch(console.error) + retainedThread.value = currentThread.value + const combined = new Map() + let hasMore = false + for (const response of responses) { + hasMore = response.data.length === PAGE_SIZE + for (const thread of response.data) { + if (!combined.has(thread.id)) combined.set(thread.id, thread) + } + } + threads.value = [...combined.values()] + hasMoreThreads.value = hasMore + for (const thread of threads.value) { + if (openedThreads.value.has(thread.id)) { + openedThreads.value.set(thread.id, thread) + } + } + } finally { + if (requestId === threadRequestId) { + loadingThreads.value = false + } + } + } + + async function loadMoreThreads() { + if ( + loadingThreads.value || + loadingMoreThreads.value || + !hasMoreThreads.value ) + return - phonesStore.getHeartbeat().catch(console.error) - threads.value = [...response.data] - loadingThreads.value = false + const generation = ++loadMoreGeneration + const previousPageCount = pageCount + pageCount++ + loadingMoreThreads.value = true + try { + await loadThreads() + } catch (error) { + if (generation !== loadMoreGeneration) return + pageCount = previousPageCount + throw error + } finally { + if (generation === loadMoreGeneration) loadingMoreThreads.value = false + } + } + + async function fetchThread(id: string): Promise { + const generation = stateGeneration + const response = await apiFetch<{ data: EntitiesMessageThread }>( + `/v1/message-threads/${id}`, + ) + if (generation !== stateGeneration) { + throw new DOMException('thread fetch was canceled', 'AbortError') + } + openedThreads.value.set(response.data.id, response.data) + fetchedThreadIds.value.add(response.data.id) + return response.data } async function loadThreadMessages( id: string | null, ): Promise { - threadId.value = id + setThreadId(id) const thread = currentThread.value if (!thread) throw new Error(`Cannot find thread with id ${id}`) @@ -88,6 +225,29 @@ export const useThreadsStore = defineStore('threads', () => { } function setThreadId(id: string | null) { + const previousThread = currentThread.value + if (previousThread) { + openedThreads.value.set(previousThread.id, previousThread) + } + const cached = id === null ? undefined : openedThreads.value.get(id) + const phonesStore = usePhonesStore() + const owner = phonesStore.owner ?? phonesStore.phones[0]?.phone_number + retainedThread.value = + id === null + ? null + : (threads.value.find((thread) => thread.id === id) ?? + ((unreadOnly.value || + loadingThreads.value || + hasMoreThreads.value || + fetchedThreadIds.value.has(id)) && + cached && + cached.owner === owner && + cached.is_archived === archivedThreads.value + ? cached + : null)) + if (retainedThread.value) { + openedThreads.value.set(retainedThread.value.id, retainedThread.value) + } threadId.value = id } @@ -106,7 +266,9 @@ export const useThreadsStore = defineStore('threads', () => { threads.value = threads.value.filter( (thread) => thread.id !== payload.threadId, ) - threadId.value = null + setThreadId(null) + openedThreads.value.delete(payload.threadId) + fetchedThreadIds.value.delete(payload.threadId) notificationsStore.addNotification({ message: payload.isArchived ? 'Archived' : 'Unarchived', type: 'success', @@ -114,7 +276,9 @@ export const useThreadsStore = defineStore('threads', () => { } async function resetThreadUnreadCount(threadId: string, force = false) { - const thread = threads.value.find((item) => item.id === threadId) + const thread = + threads.value.find((item) => item.id === threadId) ?? + (retainedThread.value?.id === threadId ? retainedThread.value : null) if (!thread) throw new Error(`Cannot find thread with id ${threadId}`) if (!force && thread.unread_count === 0) return @@ -126,6 +290,7 @@ export const useThreadsStore = defineStore('threads', () => { body: { unread_count: 0 }, }, ) + readMutationGeneration++ replaceThread(response.data) } catch (error) { notificationsStore.addNotification({ @@ -147,7 +312,9 @@ export const useThreadsStore = defineStore('threads', () => { async function deleteThread(id: string) { await apiFetch(`/v1/message-threads/${id}`, { method: 'DELETE' }) - threadId.value = null + setThreadId(null) + openedThreads.value.delete(id) + fetchedThreadIds.value.delete(id) notificationsStore.addNotification({ message: 'The message thread has been deleted successfully', type: 'success', @@ -155,9 +322,21 @@ export const useThreadsStore = defineStore('threads', () => { } function resetState() { + threadRequestId++ + stateGeneration++ + readMutationGeneration = 0 + loadMoreGeneration++ + pageCount = 1 + pageContext = '' + hasMoreThreads.value = false + loadingMoreThreads.value = false threads.value = [] threadId.value = null + retainedThread.value = null + openedThreads.value.clear() + fetchedThreadIds.value.clear() archivedThreads.value = false + unreadOnly.value = false loadingThreads.value = true } @@ -165,11 +344,17 @@ export const useThreadsStore = defineStore('threads', () => { threads, threadId, loadingThreads, + loadingMoreThreads, + hasMoreThreads, archivedThreads, + unreadOnly, + visibleThreads, currentThread, hasThread, hasThreadId, loadThreads, + loadMoreThreads, + fetchThread, loadThreadMessages, setThreadId, toggleArchive, diff --git a/web/app/utils/bag.ts b/web/app/utils/bag.ts index 73c46dd13..ce977fbe3 100644 --- a/web/app/utils/bag.ts +++ b/web/app/utils/bag.ts @@ -11,8 +11,8 @@ export default class Bag { static fromObject(items: Record>): Bag { const result = new Bag() - Object.keys(items).forEach((key) => { - result.addMany(key, items[key]) + Object.entries(items).forEach(([key, values]) => { + result.addMany(key, values) }) return result } diff --git a/web/app/utils/errors.ts b/web/app/utils/errors.ts index b72bfbf38..38bc5f942 100644 --- a/web/app/utils/errors.ts +++ b/web/app/utils/errors.ts @@ -39,8 +39,8 @@ export const getErrorMessages = (error: AxiosLikeError): ErrorMessages => { return errors } - Object.keys(error.response.data.data).forEach((key: string) => { - errors.addMany(key, sanitize(key, error.response!.data!.data![key])) + Object.entries(error.response.data.data).forEach(([key, values]) => { + errors.addMany(key, sanitize(key, values)) }) return errors diff --git a/web/nuxt.config.ts b/web/nuxt.config.ts index c574ce308..87bcc2ace 100644 --- a/web/nuxt.config.ts +++ b/web/nuxt.config.ts @@ -20,7 +20,7 @@ export default defineNuxtConfig({ download: true, }, - css: ['vuetify/styles'], + css: ['vuetify/styles', 'v-phone-input/styles'], site: { url: process.env.APP_URL || 'https://httpsms.com', diff --git a/web/package.json b/web/package.json index 9d88845c8..994deaa06 100644 --- a/web/package.json +++ b/web/package.json @@ -62,6 +62,7 @@ "stylelint-config-standard": "^40.0.0", "tsx": "^4.23.15", "typescript": "^6.0.3", + "vue-tsc": "^3.3.13", "vuetify-nuxt-module": "^0.19.5" } } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 729f94881..8dac5d1fa 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: version: 3.2.0(magicast@0.5.4) '@pinia/nuxt': specifier: ^0.11.3 - version: 0.11.3(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + version: 0.11.3(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) chart.js: specifier: ^4.5.1 version: 4.5.1 @@ -43,7 +43,7 @@ importers: version: 2.31.0 nuxt: specifier: ^4.5.1 - version: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.28.1)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3))(supports-color@10.2.2)(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(yaml@2.9.0) + version: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.25.12)(eslint@10.8.0(jiti@2.7.0))(ioredis@5.11.1)(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(typescript@6.0.3))(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue-tsc@3.3.13(typescript@6.0.3))(yaml@2.9.0) pinia: specifier: ^3.0.4 version: 3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)) @@ -70,7 +70,7 @@ importers: version: 5.3.4(chart.js@4.5.1)(vue@3.5.41(typescript@6.0.3)) vue-router: specifier: ^5.0.7 - version: 5.2.0(@vue/compiler-sfc@3.5.41)(esbuild@0.28.1)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) + version: 5.2.0(@vue/compiler-sfc@3.5.41)(esbuild@0.25.12)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) vuetify: specifier: ^4.0.7 version: 4.1.8(typescript@6.0.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.41(typescript@6.0.3)) @@ -83,19 +83,19 @@ importers: version: 21.2.0 '@nuxt/eslint': specifier: ^1.16.0 - version: 1.17.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(@vue/compiler-sfc@3.5.41)(esbuild@0.28.1)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(srvx@0.11.22)(supports-color@10.2.2)(typescript@6.0.3)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + version: 1.17.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.41)(esbuild@0.25.12)(eslint@10.8.0(jiti@2.7.0))(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(srvx@0.11.22)(typescript@6.0.3)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) '@nuxtjs/seo': specifier: ^5.3.2 - version: 5.3.10(3b3462a6837e0c9bc3f7a989bd53696e) + version: 5.3.10(544d347ad5be336d527eb113013fd0f1) '@types/qrcode': specifier: ^1.5.6 version: 1.5.6 eslint: specifier: ^10.5.0 - version: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + version: 10.8.0(jiti@2.7.0) eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) + version: 10.1.8(eslint@10.8.0(jiti@2.7.0)) husky: specifier: ^9.1.7 version: 9.1.7 @@ -107,22 +107,25 @@ importers: version: 1.8.1 stylelint: specifier: ^17.15.0 - version: 17.15.0(supports-color@10.2.2)(typescript@6.0.3) + version: 17.15.0(typescript@6.0.3) stylelint-config-recommended-vue: specifier: ^1.6.1 - version: 1.6.1(postcss-html@1.8.1)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3)) + version: 1.6.1(postcss-html@1.8.1)(stylelint@17.15.0(typescript@6.0.3)) stylelint-config-standard: specifier: ^40.0.0 - version: 40.0.0(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3)) + version: 40.0.0(stylelint@17.15.0(typescript@6.0.3)) tsx: specifier: ^4.23.15 version: 4.23.15 typescript: specifier: ^6.0.3 version: 6.0.3 + vue-tsc: + specifier: ^3.3.13 + version: 3.3.13(typescript@6.0.3) vuetify-nuxt-module: specifier: ^0.19.5 - version: 0.19.5(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(supports-color@10.2.2)(typescript@6.0.3)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) + version: 0.19.5(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(typescript@6.0.3)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) packages: @@ -2384,6 +2387,15 @@ packages: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 vue: ^3.2.25 + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + '@vue-macros/common@3.1.4': resolution: {integrity: sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw==} engines: {node: '>=20.19.0'} @@ -2444,6 +2456,9 @@ packages: '@vue/devtools-shared@8.2.1': resolution: {integrity: sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==} + '@vue/language-core@3.3.13': + resolution: {integrity: sha512-axA0Xemn5LuUKuKWob0/kVq7xQ2mJJM6VqA9Exvt35nM9yRC31goWVve/G/Zb2ZCLsXC8FHYjjfXlnshknSCew==} + '@vue/reactivity@3.5.41': resolution: {integrity: sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==} @@ -2544,6 +2559,9 @@ packages: ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + alien-signals@3.2.1: + resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -4524,6 +4542,9 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -5708,6 +5729,9 @@ packages: yaml: optional: true + vscode-uri@3.2.0: + resolution: {integrity: sha512-m2gXo3bn0G1kT9InzMf07fTbqMbGtyckj3bH5ktLO+1Ssv+yiATZ4dhwaQv9UZWxJh6E9IFGnQyjgWVDWVBDrg==} + vue-bundle-renderer@2.3.1: resolution: {integrity: sha512-7F4LNMopUw5RgYWo4zCmVUHCc6aQRC6dCKHUYkM/n+fux4AUGdL1x6m5A515WWyFysRRN7cx3hBzVqoisfRfzw==} @@ -5747,6 +5771,12 @@ packages: vite: optional: true + vue-tsc@3.3.13: + resolution: {integrity: sha512-jE4/bXdOMSHVxEGgQCqNllJy+9N3Gx6tfSAsg0yhFK/MAN6eXqISihupnDEKSCt12VVnjN2gI3AqLYcqoh5x/A==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + vue@3.5.41: resolution: {integrity: sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==} peerDependencies: @@ -5962,20 +5992,20 @@ snapshots: '@babel/compat-data@7.29.7': {} - '@babel/core@7.29.7(supports-color@10.2.2)': + '@babel/core@7.29.7': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.8 '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) '@babel/helpers': 7.29.7 '@babel/parser': 7.29.8 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@10.2.2) + '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -6011,41 +6041,41 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)': + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7(supports-color@10.2.2) + '@babel/helper-member-expression-to-functions': 7.29.7 '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@10.2.2) - '@babel/traverse': 7.29.8(supports-color@10.2.2) + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.8 semver: 6.3.1 transitivePeerDependencies: - supports-color '@babel/helper-globals@7.29.7': {} - '@babel/helper-member-expression-to-functions@7.29.7(supports-color@10.2.2)': + '@babel/helper-member-expression-to-functions@7.29.7': dependencies: - '@babel/traverse': 7.29.8(supports-color@10.2.2) + '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.29.7(supports-color@10.2.2)': + '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.29.8(supports-color@10.2.2) + '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) - '@babel/helper-module-imports': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@10.2.2) + '@babel/traverse': 7.29.8 transitivePeerDependencies: - supports-color @@ -6055,18 +6085,18 @@ snapshots: '@babel/helper-plugin-utils@7.29.7': {} - '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)': + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) - '@babel/helper-member-expression-to-functions': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@10.2.2) + '@babel/traverse': 7.29.8 transitivePeerDependencies: - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.29.7(supports-color@10.2.2)': + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': dependencies: - '@babel/traverse': 7.29.8(supports-color@10.2.2) + '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -6094,24 +6124,24 @@ snapshots: dependencies: '@babel/types': 8.0.4 - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))': + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))': + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)': + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7(supports-color@10.2.2) - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2)) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) transitivePeerDependencies: - supports-color @@ -6121,7 +6151,7 @@ snapshots: '@babel/parser': 7.29.8 '@babel/types': 7.29.8 - '@babel/traverse@7.29.8(supports-color@10.2.2)': + '@babel/traverse@7.29.8': dependencies: '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.8 @@ -6129,7 +6159,7 @@ snapshots: '@babel/parser': 7.29.8 '@babel/template': 7.29.7 '@babel/types': 7.29.8 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -6323,17 +6353,17 @@ snapshots: dependencies: postcss-selector-parser: 7.1.5 - '@dxup/nuxt@0.5.6(esbuild@0.28.1)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))': + '@dxup/nuxt@0.5.6(esbuild@0.25.12)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: '@dxup/unimport': 0.1.2 - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) '@vue/compiler-dom': 3.5.41 chokidar: 5.0.0 knitwork: 1.3.0 magic-string: 1.1.0 pathe: 2.0.3 tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -6552,23 +6582,23 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.10.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))': + '@eslint-community/eslint-utils@4.10.1(eslint@10.8.0(jiti@2.7.0))': dependencies: - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/compat@2.1.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))': + '@eslint/compat@2.1.0(eslint@10.8.0(jiti@2.7.0))': dependencies: '@eslint/core': 1.2.1 optionalDependencies: - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) - '@eslint/config-array@0.23.5(supports-color@10.2.2)': + '@eslint/config-array@0.23.5': dependencies: '@eslint/object-schema': 3.0.5 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 minimatch: 10.2.6 transitivePeerDependencies: - supports-color @@ -6581,13 +6611,13 @@ snapshots: dependencies: '@eslint/core': 1.2.1 - '@eslint/config-inspector@3.2.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(srvx@0.11.22)': + '@eslint/config-inspector@3.2.0(eslint@10.8.0(jiti@2.7.0))(srvx@0.11.22)': dependencies: ansis: 4.3.1 cac: 7.0.0 chokidar: 5.0.0 devframe: 0.8.2(cac@7.0.0)(srvx@0.11.22) - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) jiti: 2.7.0 tinyglobby: 0.2.17 transitivePeerDependencies: @@ -6604,9 +6634,9 @@ snapshots: mdn-data: 2.29.0 source-map-js: 1.2.1 - '@eslint/js@10.0.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))': + '@eslint/js@10.0.1(eslint@10.8.0(jiti@2.7.0))': optionalDependencies: - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) '@eslint/object-schema@3.0.5': {} @@ -7119,19 +7149,19 @@ snapshots: '@kurkle/color@0.3.4': {} - '@kwsites/file-exists@1.1.1(supports-color@10.2.2)': + '@kwsites/file-exists@1.1.1': dependencies: - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 transitivePeerDependencies: - supports-color '@kwsites/promise-deferred@1.1.1': {} - '@mapbox/node-pre-gyp@2.0.3(supports-color@10.2.2)': + '@mapbox/node-pre-gyp@2.0.3': dependencies: consola: 3.4.2 detect-libc: 2.1.2 - https-proxy-agent: 7.0.6(supports-color@10.2.2) + https-proxy-agent: 7.0.6 node-fetch: 2.7.0 nopt: 8.1.0 semver: 7.8.5 @@ -7173,7 +7203,7 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@nuxt/cli@3.37.0(@nuxt/schema@4.5.2)(@parcel/watcher@2.6.0)(cac@7.0.0)(magicast@0.5.4)(supports-color@10.2.2)': + '@nuxt/cli@3.37.0(@nuxt/schema@4.5.2)(@parcel/watcher@2.6.0)(cac@7.0.0)(magicast@0.5.4)': dependencies: '@bomb.sh/tab': 0.0.19(cac@7.0.0)(citty@0.2.2) '@clack/prompts': 1.7.0 @@ -7181,7 +7211,7 @@ snapshots: citty: 0.2.2 confbox: 0.2.4 consola: 3.4.2 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 defu: 6.1.7 exsolve: 1.1.1 fuse.js: 7.5.0 @@ -7214,11 +7244,11 @@ snapshots: '@nuxt/devalue@2.0.2': {} - '@nuxt/devtools-kit@3.4.1(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))': + '@nuxt/devtools-kit@3.4.1(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) execa: 8.0.1 - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - magic-string - magicast @@ -7226,11 +7256,11 @@ snapshots: - rolldown - unplugin - '@nuxt/devtools-kit@4.0.0-alpha.7(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))': + '@nuxt/devtools-kit@4.0.0-alpha.7(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) tinyexec: 1.3.0 - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - magic-string - magicast @@ -7249,11 +7279,11 @@ snapshots: pkg-types: 2.3.1 semver: 7.8.5 - '@nuxt/devtools@3.4.1(db0@0.3.4)(ioredis@5.11.1(supports-color@10.2.2))(magic-string@1.1.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(supports-color@10.2.2)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))': + '@nuxt/devtools@3.4.1(db0@0.3.4)(ioredis@5.11.1)(magic-string@1.1.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))': dependencies: - '@nuxt/devtools-kit': 3.4.1(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + '@nuxt/devtools-kit': 3.4.1(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) '@nuxt/devtools-wizard': 3.4.1 - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) '@vue/devtools-core': 8.2.1(vue@3.5.41(typescript@6.0.3)) '@vue/devtools-kit': 8.2.1 birpc: 4.0.0 @@ -7275,14 +7305,14 @@ snapshots: perfect-debounce: 2.1.0 pkg-types: 2.3.1 semver: 7.8.5 - simple-git: 3.36.0(supports-color@10.2.2) + simple-git: 3.36.0 sirv: 3.0.2 structured-clone-es: 2.0.1 tinyglobby: 0.2.17 - unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1(supports-color@10.2.2)) - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) - vite-plugin-inspect: 11.4.1(@nuxt/kit@4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) - vite-plugin-vue-tracer: 1.4.0(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) + unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite-plugin-inspect: 11.4.1(@nuxt/kit@4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + vite-plugin-vue-tracer: 1.4.0(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) which: 6.0.1 ws: 8.21.2 transitivePeerDependencies: @@ -7314,30 +7344,30 @@ snapshots: - utf-8-validate - vue - '@nuxt/eslint-config@1.17.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(@vue/compiler-sfc@3.5.41)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': + '@nuxt/eslint-config@1.17.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.41)(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@antfu/install-pkg': 2.0.1 '@clack/prompts': 1.7.0 - '@eslint/js': 10.0.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) - '@nuxt/eslint-plugin': 1.17.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@stylistic/eslint-plugin': 5.10.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) - '@typescript-eslint/eslint-plugin': 8.66.0(@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/parser': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) - eslint-config-flat-gitignore: 2.3.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) + '@eslint/js': 10.0.1(eslint@10.8.0(jiti@2.7.0)) + '@nuxt/eslint-plugin': 1.17.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3) + '@stylistic/eslint-plugin': 5.10.0(eslint@10.8.0(jiti@2.7.0)) + '@typescript-eslint/eslint-plugin': 8.66.0(@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.8.0(jiti@2.7.0) + eslint-config-flat-gitignore: 2.3.0(eslint@10.8.0(jiti@2.7.0)) eslint-flat-config-utils: 3.2.0 - eslint-merge-processors: 2.0.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) - eslint-plugin-import-lite: 0.6.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) - eslint-plugin-import-x: 4.17.1(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2) - eslint-plugin-jsdoc: 63.3.3(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2) - eslint-plugin-regexp: 3.1.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) - eslint-plugin-unicorn: 73.0.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) - eslint-plugin-vue: 10.10.0(@stylistic/eslint-plugin@5.10.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)))(@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(vue-eslint-parser@10.4.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)) - eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.41)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) + eslint-merge-processors: 2.0.0(eslint@10.8.0(jiti@2.7.0)) + eslint-plugin-import-lite: 0.6.0(eslint@10.8.0(jiti@2.7.0)) + eslint-plugin-import-x: 4.17.1(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0)) + eslint-plugin-jsdoc: 63.3.3(eslint@10.8.0(jiti@2.7.0)) + eslint-plugin-regexp: 3.1.1(eslint@10.8.0(jiti@2.7.0)) + eslint-plugin-unicorn: 73.0.0(eslint@10.8.0(jiti@2.7.0)) + eslint-plugin-vue: 10.10.0(@stylistic/eslint-plugin@5.10.0(eslint@10.8.0(jiti@2.7.0)))(@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@10.8.0(jiti@2.7.0))) + eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.41)(eslint@10.8.0(jiti@2.7.0)) globals: 17.9.0 local-pkg: 1.2.1 pathe: 2.0.3 - vue-eslint-parser: 10.4.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2) + vue-eslint-parser: 10.4.1(eslint@10.8.0(jiti@2.7.0)) transitivePeerDependencies: - '@typescript-eslint/utils' - '@vue/compiler-sfc' @@ -7345,31 +7375,31 @@ snapshots: - supports-color - typescript - '@nuxt/eslint-plugin@1.17.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': + '@nuxt/eslint-plugin@1.17.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3) + eslint: 10.8.0(jiti@2.7.0) transitivePeerDependencies: - supports-color - typescript - '@nuxt/eslint@1.17.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(@vue/compiler-sfc@3.5.41)(esbuild@0.28.1)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(srvx@0.11.22)(supports-color@10.2.2)(typescript@6.0.3)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))': + '@nuxt/eslint@1.17.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.41)(esbuild@0.25.12)(eslint@10.8.0(jiti@2.7.0))(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(srvx@0.11.22)(typescript@6.0.3)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: - '@eslint/config-inspector': 3.2.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(srvx@0.11.22) - '@nuxt/devtools-kit': 3.4.1(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) - '@nuxt/eslint-config': 1.17.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(@vue/compiler-sfc@3.5.41)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@nuxt/eslint-plugin': 1.17.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@eslint/config-inspector': 3.2.0(eslint@10.8.0(jiti@2.7.0))(srvx@0.11.22) + '@nuxt/devtools-kit': 3.4.1(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + '@nuxt/eslint-config': 1.17.0(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3))(@vue/compiler-sfc@3.5.41)(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3) + '@nuxt/eslint-plugin': 1.17.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) chokidar: 5.0.0 - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) eslint-flat-config-utils: 3.2.0 - eslint-typegen: 2.3.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) + eslint-typegen: 2.3.1(eslint@10.8.0(jiti@2.7.0)) find-up: 8.0.0 get-port-please: 3.2.0 mlly: 1.8.2 pathe: 2.0.3 - unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unimport: 6.4.0(esbuild@0.25.12)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) transitivePeerDependencies: - '@farmfe/core' - '@modelcontextprotocol/client' @@ -7420,7 +7450,7 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxt/kit@4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))': + '@nuxt/kit@4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))': dependencies: c12: 3.3.4(magicast@0.5.4) consola: 3.4.2 @@ -7440,7 +7470,7 @@ snapshots: scule: 1.3.0 tinyglobby: 0.2.17 ufo: 1.6.4 - unctx: 3.0.0(magic-string@1.1.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + unctx: 3.0.0(magic-string@1.1.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) untyped: 2.0.0 verkit: 0.3.2 transitivePeerDependencies: @@ -7450,11 +7480,11 @@ snapshots: - rolldown - unplugin - '@nuxt/nitro-server@4.5.2(fe9885ce02309805246d9295eb36b44b)': + '@nuxt/nitro-server@4.5.2(b9bd6ba5516dc543dbc917b33a0d10ad)': dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) - '@unhead/vue': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.28.1)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@unhead/vue': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.25.12)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) '@vue/shared': 3.5.41 consola: 3.4.2 defu: 6.1.7 @@ -7464,25 +7494,25 @@ snapshots: escape-string-regexp: 5.0.0 exsolve: 1.1.1 h3: 1.15.11 - impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + impound: 1.1.6(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.13.4(@parcel/watcher@2.6.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(srvx@0.11.22)(supports-color@10.2.2)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + nitropack: 2.13.4(@parcel/watcher@2.6.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(srvx@0.11.22)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) nostics: 1.2.0 - nuxt: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.28.1)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3))(supports-color@10.2.2)(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(yaml@2.9.0) + nuxt: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.25.12)(eslint@10.8.0(jiti@2.7.0))(ioredis@5.11.1)(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(typescript@6.0.3))(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue-tsc@3.3.13(typescript@6.0.3))(yaml@2.9.0) nypm: 0.6.9 ohash: 2.0.11 pathe: 2.0.3 rou3: 0.9.1 std-env: 4.2.0 ufo: 1.6.4 - unctx: 3.0.0(magic-string@1.1.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) - unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1(supports-color@10.2.2)) + unctx: 3.0.0(magic-string@1.1.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1) vue: 3.5.41(typescript@6.0.3) vue-bundle-renderer: 2.3.1 vue-devtools-stub: 0.1.0 optionalDependencies: - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2)) + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -7545,20 +7575,20 @@ snapshots: pkg-types: 2.3.1 std-env: 4.2.0 - '@nuxt/telemetry@2.8.0(@nuxt/kit@4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))))': + '@nuxt/telemetry@2.8.0(@nuxt/kit@4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))))': dependencies: - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) citty: 0.2.2 consola: 3.4.2 ofetch: 2.0.0-alpha.3 rc9: 3.0.1 std-env: 4.2.0 - '@nuxt/vite-builder@4.5.2(bbb59840355872275ede4355b56b34bc)': + '@nuxt/vite-builder@4.5.2(cbd7f0b9e291f24836832fe380a0c379)': dependencies: - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) - '@vitejs/plugin-vue': 6.0.8(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) - '@vitejs/plugin-vue-jsx': 5.1.6(supports-color@10.2.2)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@vitejs/plugin-vue': 6.0.8(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) + '@vitejs/plugin-vue-jsx': 5.1.6(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) autoprefixer: 10.5.4(postcss@8.5.26) consola: 3.4.2 cssnano: 8.0.4(postcss@8.5.26) @@ -7572,7 +7602,7 @@ snapshots: knitwork: 1.3.0 mlly: 1.8.2 mocked-exports: 0.1.1 - nuxt: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.28.1)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3))(supports-color@10.2.2)(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(yaml@2.9.0) + nuxt: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.25.12)(eslint@10.8.0(jiti@2.7.0))(ioredis@5.11.1)(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(typescript@6.0.3))(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue-tsc@3.3.13(typescript@6.0.3))(yaml@2.9.0) nypm: 0.6.9 pathe: 2.0.3 pkg-types: 2.3.1 @@ -7582,23 +7612,19 @@ snapshots: std-env: 4.2.0 ufo: 1.6.4 unenv: 2.0.0-rc.24 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) - vite-node: 6.0.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) - vite-plugin-checker: 0.14.5(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(meow@14.1.0)(optionator@0.9.4)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3))(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite-node: 6.0.0(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite-plugin-checker: 0.14.5(eslint@10.8.0(jiti@2.7.0))(meow@14.1.0)(optionator@0.9.4)(stylelint@17.15.0(typescript@6.0.3))(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue-tsc@3.3.13(typescript@6.0.3)) vue: 3.5.41(typescript@6.0.3) vue-bundle-renderer: 2.3.1 optionalDependencies: - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2)) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) rolldown: 1.2.14 rollup-plugin-visualizer: 7.0.1(rolldown@1.2.14)(rollup@4.62.4) transitivePeerDependencies: - '@biomejs/biome' - - '@farmfe/core' - - '@rspack/core' - '@types/node' - '@vitejs/devtools' - - bun-types-no-globals - esbuild - eslint - less @@ -7608,7 +7634,6 @@ snapshots: - optionator - oxc-parser - oxlint - - rollup - sass - sass-embedded - stylelint @@ -7618,9 +7643,8 @@ snapshots: - terser - tsx - typescript - - unloader + - unplugin - vue-tsc - - webpack - yaml '@nuxtjs/google-fonts@3.2.0(magicast@0.5.4)': @@ -7631,15 +7655,15 @@ snapshots: transitivePeerDependencies: - magicast - '@nuxtjs/robots@6.1.3(7d8087514e1614c3470bdbe0b86f0686)': + '@nuxtjs/robots@6.1.3(0655b6d95986fa09e450619a2471d7fb)': dependencies: '@fingerprintjs/botd': 2.0.0 - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) consola: 3.4.2 defu: 6.1.7 h3: 1.15.11 - nuxt-site-config: 4.2.0(7d8087514e1614c3470bdbe0b86f0686) - nuxtseo-shared: 5.3.10(db742c74deaf2b1c7500561a6d1e46b8) + nuxt-site-config: 4.2.0(0655b6d95986fa09e450619a2471d7fb) + nuxtseo-shared: 5.3.10(9475ed0abf43a57ec2edeac587f1a640) pathe: 2.0.3 pkg-types: 2.3.1 ufo: 1.6.4 @@ -7654,18 +7678,18 @@ snapshots: - vite - vue - '@nuxtjs/seo@5.3.10(3b3462a6837e0c9bc3f7a989bd53696e)': - dependencies: - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) - '@nuxtjs/robots': 6.1.3(7d8087514e1614c3470bdbe0b86f0686) - '@nuxtjs/sitemap': 8.3.3(7d8087514e1614c3470bdbe0b86f0686) - nuxt: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.28.1)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3))(supports-color@10.2.2)(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(yaml@2.9.0) - nuxt-link-checker: 5.1.3(2afc54efe4333e7b21be164e8edbf89c) - nuxt-og-image: 6.7.6(c5b09079d96a60970e5bd704c57de0fb) - nuxt-schema-org: 6.2.8(5a5c97359c4cfa887a5b2273eea7c903) - nuxt-seo-utils: 8.3.3(58a2c7fe40baae257b6ed398e9cddc7f) - nuxt-site-config: 4.2.0(7d8087514e1614c3470bdbe0b86f0686) - nuxtseo-shared: 5.3.10(db742c74deaf2b1c7500561a6d1e46b8) + '@nuxtjs/seo@5.3.10(544d347ad5be336d527eb113013fd0f1)': + dependencies: + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxtjs/robots': 6.1.3(0655b6d95986fa09e450619a2471d7fb) + '@nuxtjs/sitemap': 8.3.3(0655b6d95986fa09e450619a2471d7fb) + nuxt: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.25.12)(eslint@10.8.0(jiti@2.7.0))(ioredis@5.11.1)(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(typescript@6.0.3))(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue-tsc@3.3.13(typescript@6.0.3))(yaml@2.9.0) + nuxt-link-checker: 5.1.3(d5df5b90bf7ae7ca065098da8ab00f11) + nuxt-og-image: 6.7.6(1260c5194fdf2814eb31b9771c8bd346) + nuxt-schema-org: 6.2.8(765524ba5b9822e3426864f203b74fb3) + nuxt-seo-utils: 8.3.3(128dc6963dfcd1b0c746c3148ce2ad1c) + nuxt-site-config: 4.2.0(0655b6d95986fa09e450619a2471d7fb) + nuxtseo-shared: 5.3.10(9475ed0abf43a57ec2edeac587f1a640) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -7724,13 +7748,13 @@ snapshots: - webpack - zod - '@nuxtjs/sitemap@8.3.3(7d8087514e1614c3470bdbe0b86f0686)': + '@nuxtjs/sitemap@8.3.3(0655b6d95986fa09e450619a2471d7fb)': dependencies: - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) consola: 3.4.2 defu: 6.1.7 - nuxt-site-config: 4.2.0(7d8087514e1614c3470bdbe0b86f0686) - nuxtseo-shared: 5.3.10(db742c74deaf2b1c7500561a6d1e46b8) + nuxt-site-config: 4.2.0(0655b6d95986fa09e450619a2471d7fb) + nuxtseo-shared: 5.3.10(9475ed0abf43a57ec2edeac587f1a640) ofetch: 1.5.1 pathe: 2.0.3 pkg-types: 2.3.1 @@ -7850,7 +7874,7 @@ snapshots: '@parcel/watcher-wasm@2.6.0': dependencies: is-glob: 4.0.3 - picomatch: 4.0.5 + picomatch: 4.0.7 '@parcel/watcher-win32-arm64@2.6.0': optional: true @@ -7879,9 +7903,9 @@ snapshots: '@parcel/watcher-win32-x64': 2.6.0 optional: true - '@pinia/nuxt@0.11.3(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))': + '@pinia/nuxt@0.11.3(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))': dependencies: - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) pinia: 3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)) transitivePeerDependencies: - magic-string @@ -7983,10 +8007,10 @@ snapshots: '@rollup/pluginutils': 5.4.0(rollup@4.62.4) commondir: 1.0.1 estree-walker: 2.0.2 - fdir: 6.5.0(picomatch@4.0.5) + fdir: 6.5.0(picomatch@4.0.7) is-reference: 1.2.1 magic-string: 0.30.21 - picomatch: 4.0.5 + picomatch: 4.0.7 optionalDependencies: rollup: 4.62.4 @@ -8033,7 +8057,7 @@ snapshots: dependencies: '@types/estree': 1.0.9 estree-walker: 2.0.2 - picomatch: 4.0.5 + picomatch: 4.0.7 optionalDependencies: rollup: 4.62.4 @@ -8134,15 +8158,15 @@ snapshots: '@standard-schema/spec@1.1.0': {} - '@stylistic/eslint-plugin@5.10.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))': + '@stylistic/eslint-plugin@5.10.0(eslint@10.8.0(jiti@2.7.0))': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)) '@typescript-eslint/types': 8.66.0 - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) eslint-visitor-keys: 4.2.1 espree: 10.4.0 estraverse: 5.3.0 - picomatch: 4.0.5 + picomatch: 4.0.7 '@tybys/wasm-util@0.10.3': dependencies: @@ -8169,15 +8193,15 @@ snapshots: '@types/web-bluetooth@0.0.21': {} - '@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.66.0(@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/parser': 8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/scope-manager': 8.66.0 - '@typescript-eslint/type-utils': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/type-utils': 8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.66.0 - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -8185,23 +8209,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': + '@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/scope-manager': 8.66.0 '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/typescript-estree': 8.66.0(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.66.0(typescript@6.0.3) '@typescript-eslint/visitor-keys': 8.66.0 - debug: 4.4.3(supports-color@10.2.2) - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + debug: 4.4.3 + eslint: 10.8.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.66.0(supports-color@10.2.2)(typescript@6.0.3)': + '@typescript-eslint/project-service@8.66.0(typescript@6.0.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@6.0.3) '@typescript-eslint/types': 8.66.0 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -8215,13 +8239,13 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/typescript-estree': 8.66.0(supports-color@10.2.2)(typescript@6.0.3) - '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) - debug: 4.4.3(supports-color@10.2.2) - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + '@typescript-eslint/typescript-estree': 8.66.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3) + debug: 4.4.3 + eslint: 10.8.0(jiti@2.7.0) ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: @@ -8229,13 +8253,13 @@ snapshots: '@typescript-eslint/types@8.66.0': {} - '@typescript-eslint/typescript-estree@8.66.0(supports-color@10.2.2)(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.66.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.66.0(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/project-service': 8.66.0(typescript@6.0.3) '@typescript-eslint/tsconfig-utils': 8.66.0(typescript@6.0.3) '@typescript-eslint/types': 8.66.0 '@typescript-eslint/visitor-keys': 8.66.0 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 minimatch: 10.2.6 semver: 7.8.5 tinyglobby: 0.2.17 @@ -8244,13 +8268,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3)': + '@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)) '@typescript-eslint/scope-manager': 8.66.0 '@typescript-eslint/types': 8.66.0 - '@typescript-eslint/typescript-estree': 8.66.0(supports-color@10.2.2)(typescript@6.0.3) - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + '@typescript-eslint/typescript-estree': 8.66.0(typescript@6.0.3) + eslint: 10.8.0(jiti@2.7.0) typescript: 6.0.3 transitivePeerDependencies: - supports-color @@ -8260,18 +8284,18 @@ snapshots: '@typescript-eslint/types': 8.66.0 eslint-visitor-keys: 5.0.1 - '@unhead/bundler@3.3.1(@oxc-project/types@0.154.0)(esbuild@0.28.1)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(unhead@3.3.1(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))': + '@unhead/bundler@3.3.1(@oxc-project/types@0.154.0)(esbuild@0.25.12)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(unhead@3.3.1(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))': dependencies: magic-string: 1.1.0 oxc-walker: 1.1.1(@oxc-project/types@0.154.0)(oxc-parser@0.142.0)(rolldown@1.2.14) - unhead: 3.3.1(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unhead: 3.3.1(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) optionalDependencies: - esbuild: 0.28.1 + esbuild: 0.25.12 lightningcss: 1.33.0 oxc-parser: 0.142.0 rolldown: 1.2.14 - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - '@farmfe/core' - '@oxc-project/types' @@ -8280,15 +8304,15 @@ snapshots: - rollup - unloader - '@unhead/vue@3.3.1(@oxc-project/types@0.154.0)(esbuild@0.28.1)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))': + '@unhead/vue@3.3.1(@oxc-project/types@0.154.0)(esbuild@0.25.12)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))': dependencies: - '@unhead/bundler': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.28.1)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(unhead@3.3.1(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + '@unhead/bundler': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.25.12)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(unhead@3.3.1(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) hookable: 6.1.1 - unhead: 3.3.1(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unhead: 3.3.1(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) vue: 3.5.41(typescript@6.0.3) optionalDependencies: - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - '@farmfe/core' - '@oxc-project/types' @@ -8373,9 +8397,9 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.12.2': optional: true - '@vercel/nft@1.10.2(rollup@4.62.4)(supports-color@10.2.2)': + '@vercel/nft@1.10.2(rollup@4.62.4)': dependencies: - '@mapbox/node-pre-gyp': 2.0.3(supports-color@10.2.2) + '@mapbox/node-pre-gyp': 2.0.3 '@rollup/pluginutils': 5.4.0(rollup@4.62.4) acorn: 8.18.0 acorn-import-attributes: 1.9.5(acorn@8.18.0) @@ -8385,31 +8409,43 @@ snapshots: glob: 13.0.6 graceful-fs: 4.2.11 node-gyp-build: 4.8.4 - picomatch: 4.0.5 + picomatch: 4.0.7 resolve-from: 5.0.0 transitivePeerDependencies: - encoding - rollup - supports-color - '@vitejs/plugin-vue-jsx@5.1.6(supports-color@10.2.2)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))': + '@vitejs/plugin-vue-jsx@5.1.6(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))': dependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2)) - '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/core': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) '@rolldown/pluginutils': 1.0.1 - '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.7) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) vue: 3.5.41(typescript@6.0.3) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@6.0.8(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))': + '@vitejs/plugin-vue@6.0.8(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) vue: 3.5.41(typescript@6.0.3) + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.2.0 + '@vue-macros/common@3.1.4(vue@3.5.41(typescript@6.0.3))': dependencies: '@vue/compiler-sfc': 3.5.41 @@ -8422,27 +8458,27 @@ snapshots: '@vue/babel-helper-vue-transform-on@2.0.1': {} - '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)': + '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.29.7)': dependencies: - '@babel/helper-module-imports': 7.29.7(supports-color@10.2.2) + '@babel/helper-module-imports': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7(supports-color@10.2.2)) + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) '@babel/template': 7.29.7 - '@babel/traverse': 7.29.8(supports-color@10.2.2) + '@babel/traverse': 7.29.8 '@babel/types': 7.29.8 '@vue/babel-helper-vue-transform-on': 2.0.1 - '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2) + '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.29.7) '@vue/shared': 3.5.41 optionalDependencies: - '@babel/core': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 transitivePeerDependencies: - supports-color - '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.29.7(supports-color@10.2.2))(supports-color@10.2.2)': + '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.29.7)': dependencies: '@babel/code-frame': 7.29.7 - '@babel/core': 7.29.7(supports-color@10.2.2) - '@babel/helper-module-imports': 7.29.7(supports-color@10.2.2) + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 '@babel/helper-plugin-utils': 7.29.7 '@babel/parser': 7.29.8 '@vue/compiler-sfc': 3.5.41 @@ -8516,6 +8552,16 @@ snapshots: '@vue/devtools-shared@8.2.1': {} + '@vue/language-core@3.3.13': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.41 + '@vue/shared': 3.5.41 + alien-signals: 3.2.1 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.7 + '@vue/reactivity@3.5.41': dependencies: '@vue/shared': 3.5.41 @@ -8596,6 +8642,8 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + alien-signals@3.2.1: {} + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -8817,12 +8865,12 @@ snapshots: chownr@3.0.0: {} - chrome-launcher@1.2.1(supports-color@10.2.2): + chrome-launcher@1.2.1: dependencies: '@types/node': 26.1.2 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 - lighthouse-logger: 2.0.2(supports-color@10.2.2) + lighthouse-logger: 2.0.2 transitivePeerDependencies: - supports-color @@ -9037,11 +9085,9 @@ snapshots: db0@0.3.4: {} - debug@4.4.3(supports-color@10.2.2): + debug@4.4.3: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 10.2.2 decamelize@1.2.0: {} @@ -9222,14 +9268,14 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-flat-gitignore@2.3.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)): + eslint-config-flat-gitignore@2.3.0(eslint@10.8.0(jiti@2.7.0)): dependencies: - '@eslint/compat': 2.1.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + '@eslint/compat': 2.1.0(eslint@10.8.0(jiti@2.7.0)) + eslint: 10.8.0(jiti@2.7.0) - eslint-config-prettier@10.1.8(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)): + eslint-config-prettier@10.1.8(eslint@10.8.0(jiti@2.7.0)): dependencies: - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) eslint-flat-config-utils@3.2.0: dependencies: @@ -9243,20 +9289,20 @@ snapshots: optionalDependencies: unrs-resolver: 1.12.2 - eslint-merge-processors@2.0.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)): + eslint-merge-processors@2.0.0(eslint@10.8.0(jiti@2.7.0)): dependencies: - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) - eslint-plugin-import-lite@0.6.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)): + eslint-plugin-import-lite@0.6.0(eslint@10.8.0(jiti@2.7.0)): dependencies: - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) - eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2): + eslint-plugin-import-x@4.17.1(@typescript-eslint/utils@8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0)): dependencies: '@typescript-eslint/types': 8.66.0 comment-parser: 1.4.7 - debug: 4.4.3(supports-color@10.2.2) - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + debug: 4.4.3 + eslint: 10.8.0(jiti@2.7.0) eslint-import-context: 0.1.9(unrs-resolver@1.12.2) is-glob: 4.0.3 minimatch: 10.2.6 @@ -9264,19 +9310,19 @@ snapshots: stable-hash-x: 0.2.0 unrs-resolver: 1.12.2 optionalDependencies: - '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@typescript-eslint/utils': 8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3) transitivePeerDependencies: - supports-color - eslint-plugin-jsdoc@63.3.3(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2): + eslint-plugin-jsdoc@63.3.3(eslint@10.8.0(jiti@2.7.0)): dependencies: '@es-joy/jsdoccomment': 0.91.0 '@es-joy/resolve.exports': 1.2.0 are-docs-informative: 0.0.2 comment-parser: 1.4.7 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) espree: 11.2.0 esquery: 1.7.0 html-entities: 2.6.0 @@ -9288,20 +9334,20 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-regexp@3.1.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)): + eslint-plugin-regexp@3.1.1(eslint@10.8.0(jiti@2.7.0)): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 comment-parser: 1.4.7 - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) jsdoc-type-pratt-parser: 7.3.0 refa: 0.12.1 regexp-ast-analysis: 0.7.1 scslre: 0.3.0 - eslint-plugin-unicorn@73.0.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)): + eslint-plugin-unicorn@73.0.0(eslint@10.8.0(jiti@2.7.0)): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)) '@eslint/css-tree': 4.0.5 browserslist: 4.28.7 change-case: 5.4.4 @@ -9309,7 +9355,7 @@ snapshots: core-js-compat: 3.50.0 detect-indent: 7.0.2 entities: 4.5.0 - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) find-up-simple: 1.0.1 globals: 17.9.0 indent-string: 5.0.0 @@ -9323,24 +9369,24 @@ snapshots: strip-indent: 4.1.1 yaml: 2.9.0 - eslint-plugin-vue@10.10.0(@stylistic/eslint-plugin@5.10.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)))(@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(vue-eslint-parser@10.4.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)): + eslint-plugin-vue@10.10.0(@stylistic/eslint-plugin@5.10.0(eslint@10.8.0(jiti@2.7.0)))(@typescript-eslint/parser@8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3))(eslint@10.8.0(jiti@2.7.0))(vue-eslint-parser@10.4.1(eslint@10.8.0(jiti@2.7.0))): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)) + eslint: 10.8.0(jiti@2.7.0) natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 7.1.5 semver: 7.8.5 - vue-eslint-parser: 10.4.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2) + vue-eslint-parser: 10.4.1(eslint@10.8.0(jiti@2.7.0)) xml-name-validator: 5.0.0 optionalDependencies: - '@stylistic/eslint-plugin': 5.10.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) - '@typescript-eslint/parser': 8.66.0(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@6.0.3) + '@stylistic/eslint-plugin': 5.10.0(eslint@10.8.0(jiti@2.7.0)) + '@typescript-eslint/parser': 8.66.0(eslint@10.8.0(jiti@2.7.0))(typescript@6.0.3) - eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.41)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)): + eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.41)(eslint@10.8.0(jiti@2.7.0)): dependencies: '@vue/compiler-sfc': 3.5.41 - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) eslint-scope@9.1.2: dependencies: @@ -9349,9 +9395,9 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-typegen@2.3.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)): + eslint-typegen@2.3.1(eslint@10.8.0(jiti@2.7.0)): dependencies: - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) json-schema-to-typescript-lite: 15.0.0 ohash: 2.0.11 @@ -9361,11 +9407,11 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2): + eslint@10.8.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2)) + '@eslint-community/eslint-utils': 4.10.1(eslint@10.8.0(jiti@2.7.0)) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.23.5(supports-color@10.2.2) + '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.7.0 '@eslint/core': 1.2.1 '@eslint/plugin-kit': 0.7.2 @@ -9375,7 +9421,7 @@ snapshots: '@types/estree': 1.0.9 ajv: 6.15.0 cross-spawn: 7.0.6 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 escape-string-regexp: 4.0.0 eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 @@ -9510,9 +9556,9 @@ snapshots: dependencies: websocket-driver: 0.7.5 - fdir@6.5.0(picomatch@4.0.5): + fdir@6.5.0(picomatch@4.0.7): optionalDependencies: - picomatch: 4.0.5 + picomatch: 4.0.7 file-entry-cache@11.1.5: dependencies: @@ -9765,10 +9811,10 @@ snapshots: http-shutdown@1.2.2: {} - https-proxy-agent@7.0.6(supports-color@10.2.2): + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -9803,10 +9849,10 @@ snapshots: import-meta-resolve@4.2.0: {} - importx@0.5.2(supports-color@10.2.2): + importx@0.5.2: dependencies: bundle-require: 5.1.0(esbuild@0.25.12) - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 esbuild: 0.25.12 jiti: 2.7.0 pathe: 2.0.3 @@ -9814,12 +9860,12 @@ snapshots: transitivePeerDependencies: - supports-color - impound@1.1.6(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): + impound@1.1.6(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: '@jridgewell/trace-mapping': 0.3.31 es-module-lexer: 2.3.1 pathe: 2.0.3 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) unplugin-utils: 0.3.2 transitivePeerDependencies: - '@farmfe/core' @@ -9844,11 +9890,11 @@ snapshots: ini@6.0.0: {} - ioredis@5.11.1(supports-color@10.2.2): + ioredis@5.11.1: dependencies: '@ioredis/commands': 1.10.0 cluster-key-slot: 1.1.1 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 denque: 2.1.0 redis-errors: 1.2.0 redis-parser: 3.0.0 @@ -10005,9 +10051,9 @@ snapshots: libphonenumber-js@1.13.10: {} - lighthouse-logger@2.0.2(supports-color@10.2.2): + lighthouse-logger@2.0.2: dependencies: - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 marky: 1.3.0 transitivePeerDependencies: - supports-color @@ -10236,7 +10282,7 @@ snapshots: natural-compare@1.4.0: {} - nitropack@2.13.4(@parcel/watcher@2.6.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(srvx@0.11.22)(supports-color@10.2.2)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): + nitropack@2.13.4(@parcel/watcher@2.6.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(srvx@0.11.22)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.62.4) @@ -10246,7 +10292,7 @@ snapshots: '@rollup/plugin-node-resolve': 16.0.3(rollup@4.62.4) '@rollup/plugin-replace': 6.0.3(rollup@4.62.4) '@rollup/plugin-terser': 1.0.0(rollup@4.62.4) - '@vercel/nft': 1.10.2(rollup@4.62.4)(supports-color@10.2.2) + '@vercel/nft': 1.10.2(rollup@4.62.4) archiver: 7.0.1 c12: 3.3.4(magicast@0.5.4) chokidar: 5.0.0 @@ -10270,7 +10316,7 @@ snapshots: h3: 1.15.11 hookable: 5.5.3 httpxy: 0.5.5 - ioredis: 5.11.1(supports-color@10.2.2) + ioredis: 5.11.1 jiti: 2.7.0 klona: 2.0.6 knitwork: 1.3.0 @@ -10293,7 +10339,7 @@ snapshots: scule: 1.3.0 semver: 7.8.5 serve-placeholder: 2.0.2 - serve-static: 2.2.1(supports-color@10.2.2) + serve-static: 2.2.1 source-map: 0.7.6 std-env: 4.2.0 ufo: 1.6.4 @@ -10301,9 +10347,9 @@ snapshots: uncrypto: 0.1.3 unctx: 2.5.0 unenv: 2.0.0-rc.24 - unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) unplugin-utils: 0.3.2 - unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1(supports-color@10.2.2)) + unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1) untyped: 2.0.0 unwasm: 0.5.3 youch: 4.1.1 @@ -10386,18 +10432,18 @@ snapshots: dependencies: boolbase: 1.0.0 - nuxt-link-checker@5.1.3(2afc54efe4333e7b21be164e8edbf89c): + nuxt-link-checker@5.1.3(d5df5b90bf7ae7ca065098da8ab00f11): dependencies: - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) '@vueuse/core': 14.4.0(vue@3.5.41(typescript@6.0.3)) consola: 3.4.2 diff: 9.0.0 fuse.js: 7.5.0 h3: 1.15.11 magic-string: 1.1.0 - nuxt: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.28.1)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3))(supports-color@10.2.2)(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(yaml@2.9.0) - nuxt-site-config: 4.2.0(7d8087514e1614c3470bdbe0b86f0686) - nuxtseo-shared: 5.3.10(db742c74deaf2b1c7500561a6d1e46b8) + nuxt: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.25.12)(eslint@10.8.0(jiti@2.7.0))(ioredis@5.11.1)(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(typescript@6.0.3))(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue-tsc@3.3.13(typescript@6.0.3))(yaml@2.9.0) + nuxt-site-config: 4.2.0(0655b6d95986fa09e450619a2471d7fb) + nuxtseo-shared: 5.3.10(9475ed0abf43a57ec2edeac587f1a640) ofetch: 1.5.1 pathe: 2.0.3 pkg-types: 2.3.1 @@ -10405,7 +10451,7 @@ snapshots: site-config-stack: 4.2.0(vue@3.5.41(typescript@6.0.3)) ufo: 1.6.4 ultrahtml: 1.7.0 - unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1(supports-color@10.2.2)) + unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1) transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -10435,13 +10481,13 @@ snapshots: - vue - zod - nuxt-og-image@6.7.6(c5b09079d96a60970e5bd704c57de0fb): + nuxt-og-image@6.7.6(1260c5194fdf2814eb31b9771c8bd346): dependencies: '@clack/prompts': 1.7.0 - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) - '@unhead/vue': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.28.1)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@unhead/vue': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.25.12)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) '@vue/compiler-sfc': 3.5.41 - chrome-launcher: 1.2.1(supports-color@10.2.2) + chrome-launcher: 1.2.1 consola: 3.4.2 defu: 6.1.7 devalue: 5.9.4 @@ -10451,8 +10497,8 @@ snapshots: magic-string: 1.1.0 magicast: 0.5.4 mocked-exports: 0.1.1 - nuxt-site-config: 4.2.0(7d8087514e1614c3470bdbe0b86f0686) - nuxtseo-shared: 5.3.10(db742c74deaf2b1c7500561a6d1e46b8) + nuxt-site-config: 4.2.0(0655b6d95986fa09e450619a2471d7fb) + nuxtseo-shared: 5.3.10(9475ed0abf43a57ec2edeac587f1a640) nypm: 0.6.9 object-identity: 0.2.3 ofetch: 1.5.1 @@ -10467,10 +10513,10 @@ snapshots: tinyexec: 1.3.0 ufo: 1.6.4 ultrahtml: 1.7.0 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) - unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1(supports-color@10.2.2)) + unplugin: 3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unstorage: 1.17.5(db0@0.3.4)(ioredis@5.11.1) optionalDependencies: - nitropack: 2.13.4(@parcel/watcher@2.6.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(srvx@0.11.22)(supports-color@10.2.2)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + nitropack: 2.13.4(@parcel/watcher@2.6.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(srvx@0.11.22)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) transitivePeerDependencies: - '@farmfe/core' - '@nuxt/schema' @@ -10487,17 +10533,17 @@ snapshots: - vue - webpack - nuxt-schema-org@6.2.8(5a5c97359c4cfa887a5b2273eea7c903): + nuxt-schema-org@6.2.8(765524ba5b9822e3426864f203b74fb3): dependencies: - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) defu: 6.1.7 - nuxt-site-config: 4.2.0(7d8087514e1614c3470bdbe0b86f0686) - nuxtseo-shared: 5.3.10(db742c74deaf2b1c7500561a6d1e46b8) + nuxt-site-config: 4.2.0(0655b6d95986fa09e450619a2471d7fb) + nuxtseo-shared: 5.3.10(9475ed0abf43a57ec2edeac587f1a640) pkg-types: 2.3.1 ufo: 1.6.4 optionalDependencies: - '@unhead/vue': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.28.1)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) - unhead: 3.3.1(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + '@unhead/vue': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.25.12)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) + unhead: 3.3.1(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) transitivePeerDependencies: - '@nuxt/schema' - magic-string @@ -10509,31 +10555,31 @@ snapshots: - vite - vue - nuxt-seo-utils@8.3.3(58a2c7fe40baae257b6ed398e9cddc7f): + nuxt-seo-utils@8.3.3(128dc6963dfcd1b0c746c3148ce2ad1c): dependencies: - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) citty: 0.2.2 consola: 3.4.2 defu: 6.1.7 escape-string-regexp: 5.0.0 exsolve: 1.1.1 image-size: 2.0.2 - nuxt: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.28.1)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3))(supports-color@10.2.2)(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(yaml@2.9.0) - nuxt-site-config: 4.2.0(7d8087514e1614c3470bdbe0b86f0686) - nuxtseo-shared: 5.3.10(db742c74deaf2b1c7500561a6d1e46b8) + nuxt: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.25.12)(eslint@10.8.0(jiti@2.7.0))(ioredis@5.11.1)(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(typescript@6.0.3))(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue-tsc@3.3.13(typescript@6.0.3))(yaml@2.9.0) + nuxt-site-config: 4.2.0(0655b6d95986fa09e450619a2471d7fb) + nuxtseo-shared: 5.3.10(9475ed0abf43a57ec2edeac587f1a640) pathe: 2.0.3 pkg-types: 2.3.1 scule: 1.3.0 tinyglobby: 0.2.17 ufo: 1.6.4 optionalDependencies: - '@unhead/bundler': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.28.1)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(unhead@3.3.1(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) - '@unhead/vue': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.28.1)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) - esbuild: 0.28.1 + '@unhead/bundler': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.25.12)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(unhead@3.3.1(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + '@unhead/vue': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.25.12)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) + esbuild: 0.25.12 lightningcss: 1.33.0 rolldown: 1.2.14 sharp: 0.35.4(@types/node@26.1.2) - unhead: 3.3.1(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unhead: 3.3.1(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) transitivePeerDependencies: - '@nuxt/schema' - '@types/node' @@ -10545,9 +10591,9 @@ snapshots: - vue - zod - nuxt-site-config-kit@4.2.0(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vue@3.5.41(typescript@6.0.3)): + nuxt-site-config-kit@4.2.0(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vue@3.5.41(typescript@6.0.3)): dependencies: - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) site-config-stack: 4.2.0(vue@3.5.41(typescript@6.0.3)) std-env: 4.2.0 ufo: 1.6.4 @@ -10559,15 +10605,15 @@ snapshots: - unplugin - vue - nuxt-site-config@4.2.0(7d8087514e1614c3470bdbe0b86f0686): + nuxt-site-config@4.2.0(0655b6d95986fa09e450619a2471d7fb): dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) consola: 3.4.2 defu: 6.1.7 h3: 1.15.11 - nuxt-site-config-kit: 4.2.0(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vue@3.5.41(typescript@6.0.3)) - nuxtseo-shared: 5.3.10(db742c74deaf2b1c7500561a6d1e46b8) + nuxt-site-config-kit: 4.2.0(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vue@3.5.41(typescript@6.0.3)) + nuxtseo-shared: 5.3.10(9475ed0abf43a57ec2edeac587f1a640) pathe: 2.0.3 pkg-types: 2.3.1 site-config-stack: 4.2.0(vue@3.5.41(typescript@6.0.3)) @@ -10584,17 +10630,17 @@ snapshots: - vite - zod - nuxt@4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.28.1)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3))(supports-color@10.2.2)(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(yaml@2.9.0): + nuxt@4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.25.12)(eslint@10.8.0(jiti@2.7.0))(ioredis@5.11.1)(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(typescript@6.0.3))(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue-tsc@3.3.13(typescript@6.0.3))(yaml@2.9.0): dependencies: - '@dxup/nuxt': 0.5.6(esbuild@0.28.1)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) - '@nuxt/cli': 3.37.0(@nuxt/schema@4.5.2)(@parcel/watcher@2.6.0)(cac@7.0.0)(magicast@0.5.4)(supports-color@10.2.2) - '@nuxt/devtools': 3.4.1(db0@0.3.4)(ioredis@5.11.1(supports-color@10.2.2))(magic-string@1.1.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(supports-color@10.2.2)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) - '@nuxt/nitro-server': 4.5.2(fe9885ce02309805246d9295eb36b44b) + '@dxup/nuxt': 0.5.6(esbuild@0.25.12)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + '@nuxt/cli': 3.37.0(@nuxt/schema@4.5.2)(@parcel/watcher@2.6.0)(cac@7.0.0)(magicast@0.5.4) + '@nuxt/devtools': 3.4.1(db0@0.3.4)(ioredis@5.11.1)(magic-string@1.1.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/nitro-server': 4.5.2(b9bd6ba5516dc543dbc917b33a0d10ad) '@nuxt/schema': 4.5.2 - '@nuxt/telemetry': 2.8.0(@nuxt/kit@4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))) - '@nuxt/vite-builder': 4.5.2(bbb59840355872275ede4355b56b34bc) - '@unhead/vue': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.28.1)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) + '@nuxt/telemetry': 2.8.0(@nuxt/kit@4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))) + '@nuxt/vite-builder': 4.5.2(cbd7f0b9e291f24836832fe380a0c379) + '@unhead/vue': 3.3.1(@oxc-project/types@0.154.0)(esbuild@0.25.12)(lightningcss@1.33.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) '@vue/shared': 3.5.41 chokidar: 5.0.0 compatx: 0.2.0 @@ -10608,7 +10654,7 @@ snapshots: fnv1a-64: 0.1.2 hookable: 6.1.1 ignore: 7.0.6 - impound: 1.1.6(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + impound: 1.1.6(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) jiti: 2.7.0 klona: 2.0.6 knitwork: 1.3.0 @@ -10635,17 +10681,17 @@ snapshots: ufo: 1.6.4 ultrahtml: 1.7.0 uncrypto: 0.1.3 - unctx: 3.0.0(magic-string@1.1.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + unctx: 3.0.0(magic-string@1.1.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) undici: 8.11.2 - unhead: 3.3.1(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) - unimport: 6.4.0(esbuild@0.28.1)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unhead: 3.3.1(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unimport: 6.4.0(esbuild@0.25.12)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) unrouting: 0.2.2 untyped: 2.0.0 verkit: 0.3.2 vue: 3.5.41(typescript@6.0.3) vue-component-type-helpers: 3.3.9 - vue-router: 5.2.0(@vue/compiler-sfc@3.5.41)(esbuild@0.28.1)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) + vue-router: 5.2.0(@vue/compiler-sfc@3.5.41)(esbuild@0.25.12)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)) optionalDependencies: '@parcel/watcher': 2.6.0 '@types/node': 26.1.2 @@ -10726,16 +10772,16 @@ snapshots: - xml2js - yaml - nuxtseo-shared@5.3.10(db742c74deaf2b1c7500561a6d1e46b8): + nuxtseo-shared@5.3.10(9475ed0abf43a57ec2edeac587f1a640): dependencies: '@clack/prompts': 1.7.0 - '@nuxt/devtools-kit': 4.0.0-alpha.7(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/devtools-kit': 4.0.0-alpha.7(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) '@nuxt/schema': 4.5.2 birpc: 4.0.0 consola: 3.4.2 defu: 6.1.7 - nuxt: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7(supports-color@10.2.2)))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.28.1)(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(ioredis@5.11.1(supports-color@10.2.2))(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3))(supports-color@10.2.2)(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(yaml@2.9.0) + nuxt: 4.5.2(@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7))(@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7))(@oxc-project/types@0.154.0)(@parcel/watcher@2.6.0)(@types/node@26.1.2)(@vue/compiler-sfc@3.5.41)(cac@7.0.0)(db0@0.3.4)(esbuild@0.25.12)(eslint@10.8.0(jiti@2.7.0))(ioredis@5.11.1)(lightningcss@1.33.0)(magicast@0.5.4)(meow@14.1.0)(optionator@0.9.4)(oxc-parser@0.142.0)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4))(rollup@4.62.4)(sass@1.105.1)(srvx@0.11.22)(stylelint@17.15.0(typescript@6.0.3))(terser@5.49.2)(tsx@4.23.15)(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue-tsc@3.3.13(typescript@6.0.3))(yaml@2.9.0) nypm: 0.6.9 ofetch: 1.5.1 pathe: 2.0.3 @@ -10746,7 +10792,7 @@ snapshots: ufo: 1.6.4 vue: 3.5.41(typescript@6.0.3) optionalDependencies: - nuxt-site-config: 4.2.0(7d8087514e1614c3470bdbe0b86f0686) + nuxt-site-config: 4.2.0(0655b6d95986fa09e450619a2471d7fb) transitivePeerDependencies: - magic-string - magicast @@ -10891,6 +10937,8 @@ snapshots: parseurl@1.3.3: {} + path-browserify@1.0.1: {} + path-exists@4.0.0: {} path-expression-matcher@1.6.2: {} @@ -11300,7 +11348,7 @@ snapshots: rollup-plugin-visualizer@7.0.1(rolldown@1.2.14)(rollup@4.62.4): dependencies: open: 11.0.0 - picomatch: 4.0.5 + picomatch: 4.0.7 source-map: 0.7.6 yargs: 18.1.0 optionalDependencies: @@ -11373,9 +11421,9 @@ snapshots: semver@7.8.5: {} - send@1.2.1(supports-color@10.2.2): + send@1.2.1: dependencies: - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -11397,12 +11445,12 @@ snapshots: dependencies: defu: 6.1.7 - serve-static@2.2.1(supports-color@10.2.2): + serve-static@2.2.1: dependencies: encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 1.2.1(supports-color@10.2.2) + send: 1.2.1 transitivePeerDependencies: - supports-color @@ -11456,13 +11504,13 @@ snapshots: signal-exit@4.1.0: {} - simple-git@3.36.0(supports-color@10.2.2): + simple-git@3.36.0: dependencies: - '@kwsites/file-exists': 1.1.1(supports-color@10.2.2) + '@kwsites/file-exists': 1.1.1 '@kwsites/promise-deferred': 1.1.1 '@simple-git/args-pathspec': 1.0.3 '@simple-git/argv-parser': 1.1.1 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -11599,29 +11647,29 @@ snapshots: postcss: 8.5.26 postcss-selector-parser: 7.1.5 - stylelint-config-html@2.0.0(postcss-html@1.8.1)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3)): + stylelint-config-html@2.0.0(postcss-html@1.8.1)(stylelint@17.15.0(typescript@6.0.3)): dependencies: postcss-html: 1.8.1 - stylelint: 17.15.0(supports-color@10.2.2)(typescript@6.0.3) + stylelint: 17.15.0(typescript@6.0.3) - stylelint-config-recommended-vue@1.6.1(postcss-html@1.8.1)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3)): + stylelint-config-recommended-vue@1.6.1(postcss-html@1.8.1)(stylelint@17.15.0(typescript@6.0.3)): dependencies: postcss-html: 1.8.1 semver: 7.8.5 - stylelint: 17.15.0(supports-color@10.2.2)(typescript@6.0.3) - stylelint-config-html: 2.0.0(postcss-html@1.8.1)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3)) - stylelint-config-recommended: 18.0.0(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3)) + stylelint: 17.15.0(typescript@6.0.3) + stylelint-config-html: 2.0.0(postcss-html@1.8.1)(stylelint@17.15.0(typescript@6.0.3)) + stylelint-config-recommended: 18.0.0(stylelint@17.15.0(typescript@6.0.3)) - stylelint-config-recommended@18.0.0(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3)): + stylelint-config-recommended@18.0.0(stylelint@17.15.0(typescript@6.0.3)): dependencies: - stylelint: 17.15.0(supports-color@10.2.2)(typescript@6.0.3) + stylelint: 17.15.0(typescript@6.0.3) - stylelint-config-standard@40.0.0(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3)): + stylelint-config-standard@40.0.0(stylelint@17.15.0(typescript@6.0.3)): dependencies: - stylelint: 17.15.0(supports-color@10.2.2)(typescript@6.0.3) - stylelint-config-recommended: 18.0.0(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3)) + stylelint: 17.15.0(typescript@6.0.3) + stylelint-config-recommended: 18.0.0(stylelint@17.15.0(typescript@6.0.3)) - stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3): + stylelint@17.15.0(typescript@6.0.3): dependencies: '@csstools/css-calc': 3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) @@ -11634,7 +11682,7 @@ snapshots: cosmiconfig: 9.0.2(typescript@6.0.3) css-functions-list: 3.3.3 css-tree: 3.2.1 - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 fast-glob: 3.3.3 fastest-levenshtein: 1.0.16 file-entry-cache: 11.1.5 @@ -11754,8 +11802,8 @@ snapshots: tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 to-regex-range@5.0.1: dependencies: @@ -11808,11 +11856,11 @@ snapshots: ultrahtml@1.7.0: {} - unconfig@0.6.1(supports-color@10.2.2): + unconfig@0.6.1: dependencies: '@antfu/utils': 8.1.1 defu: 6.1.7 - importx: 0.5.2(supports-color@10.2.2) + importx: 0.5.2 transitivePeerDependencies: - supports-color @@ -11825,12 +11873,12 @@ snapshots: magic-string: 0.30.21 unplugin: 2.3.11 - unctx@3.0.0(magic-string@1.1.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))): + unctx@3.0.0(magic-string@1.1.0)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))): optionalDependencies: magic-string: 1.1.0 oxc-parser: 0.142.0 rolldown: 1.2.14 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) undici-types@8.3.0: {} @@ -11840,12 +11888,12 @@ snapshots: dependencies: pathe: 2.0.3 - unhead@3.3.1(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): + unhead@3.3.1(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: hookable: 6.1.1 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) optionalDependencies: - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -11860,7 +11908,7 @@ snapshots: unicorn-magic@0.4.0: {} - unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): + unimport@6.4.0(esbuild@0.25.12)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: acorn: 8.18.0 escape-string-regexp: 5.0.0 @@ -11874,7 +11922,36 @@ snapshots: scule: 1.3.0 strip-literal: 4.0.0 tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unplugin-utils: 0.3.2 + optionalDependencies: + oxc-parser: 0.142.0 + rolldown: 1.2.14 + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - esbuild + - rollup + - unloader + - vite + - webpack + + unimport@6.4.0(esbuild@0.28.1)(oxc-parser@0.142.0)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): + dependencies: + acorn: 8.18.0 + escape-string-regexp: 5.0.0 + estree-walker: 3.0.3 + local-pkg: 1.2.1 + magic-string: 1.1.0 + mlly: 1.8.2 + pathe: 2.0.3 + picomatch: 4.0.5 + pkg-types: 2.3.1 + scule: 1.3.0 + strip-literal: 4.0.0 + tinyglobby: 0.2.17 + unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) unplugin-utils: 0.3.2 optionalDependencies: oxc-parser: 0.142.0 @@ -11898,19 +11975,30 @@ snapshots: dependencies: '@jridgewell/remapping': 2.3.5 acorn: 8.18.0 - picomatch: 4.0.5 + picomatch: 4.0.7 webpack-virtual-modules: 0.6.2 - unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): + unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: '@jridgewell/remapping': 2.3.5 - picomatch: 4.0.5 + picomatch: 4.0.7 + webpack-virtual-modules: 0.6.2 + optionalDependencies: + esbuild: 0.25.12 + rolldown: 1.2.14 + rollup: 4.62.4 + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + + unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): + dependencies: + '@jridgewell/remapping': 2.3.5 + picomatch: 4.0.7 webpack-virtual-modules: 0.6.2 optionalDependencies: esbuild: 0.28.1 rolldown: 1.2.14 rollup: 4.62.4 - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) unrouting@0.2.2: dependencies: @@ -11944,7 +12032,7 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 - unstorage@1.17.5(db0@0.3.4)(ioredis@5.11.1(supports-color@10.2.2)): + unstorage@1.17.5(db0@0.3.4)(ioredis@5.11.1): dependencies: anymatch: 3.1.3 chokidar: 5.0.0 @@ -11956,7 +12044,7 @@ snapshots: ufo: 1.6.4 optionalDependencies: db0: 0.3.4 - ioredis: 5.11.1(supports-color@10.2.2) + ioredis: 5.11.1 untun@0.2.2: {} @@ -12018,23 +12106,23 @@ snapshots: verkit@0.3.2: {} - vite-dev-rpc@2.0.0(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): + vite-dev-rpc@2.0.0(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: birpc: 4.0.0 - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) - vite-hot-client: 2.2.0(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite-hot-client: 2.2.0(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) - vite-hot-client@2.2.0(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): + vite-hot-client@2.2.0(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) - vite-node@6.0.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0): + vite-node@6.0.0(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0): dependencies: cac: 7.0.0 es-module-lexer: 2.3.1 obug: 2.1.4 pathe: 2.0.3 - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - '@vitejs/devtools' @@ -12049,24 +12137,25 @@ snapshots: - tsx - yaml - vite-plugin-checker@0.14.5(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(meow@14.1.0)(optionator@0.9.4)(stylelint@17.15.0(supports-color@10.2.2)(typescript@6.0.3))(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): + vite-plugin-checker@0.14.5(eslint@10.8.0(jiti@2.7.0))(meow@14.1.0)(optionator@0.9.4)(stylelint@17.15.0(typescript@6.0.3))(typescript@6.0.3)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue-tsc@3.3.13(typescript@6.0.3)): dependencies: '@babel/code-frame': 7.29.7 chokidar: 5.0.0 npm-run-path: 6.0.0 picocolors: 1.1.1 - picomatch: 4.0.5 + picomatch: 4.0.7 proper-lockfile: 4.1.2 tiny-invariant: 1.3.3 - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) optionalDependencies: - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + eslint: 10.8.0(jiti@2.7.0) meow: 14.1.0 optionator: 0.9.4 - stylelint: 17.15.0(supports-color@10.2.2)(typescript@6.0.3) + stylelint: 17.15.0(typescript@6.0.3) typescript: 6.0.3 + vue-tsc: 3.3.13(typescript@6.0.3) - vite-plugin-inspect@11.4.1(@nuxt/kit@4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): + vite-plugin-inspect@11.4.1(@nuxt/kit@4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)): dependencies: ansis: 4.3.1 error-stack-parser-es: 1.0.5 @@ -12076,33 +12165,33 @@ snapshots: perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.2 - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) - vite-dev-rpc: 2.0.0(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite-dev-rpc: 2.0.0(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) optionalDependencies: - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) - vite-plugin-vue-tracer@1.4.0(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)): + vite-plugin-vue-tracer@1.4.0(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)): dependencies: estree-walker: 3.0.3 exsolve: 1.1.1 magic-string: 0.30.21 pathe: 2.0.3 source-map-js: 1.2.1 - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) vue: 3.5.41(typescript@6.0.3) - vite-plugin-vuetify@2.1.3(supports-color@10.2.2)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))(vuetify@4.1.8): + vite-plugin-vuetify@2.1.3(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))(vuetify@4.1.8): dependencies: '@vuetify/loader-shared': 2.1.2(vue@3.5.41(typescript@6.0.3))(vuetify@4.1.8) - debug: 4.4.3(supports-color@10.2.2) + debug: 4.4.3 upath: 2.0.1 - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) vue: 3.5.41(typescript@6.0.3) vuetify: 4.1.8(typescript@6.0.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.41(typescript@6.0.3)) transitivePeerDependencies: - supports-color - vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0): + vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0): dependencies: lightningcss: 1.33.0 picomatch: 4.0.7 @@ -12111,7 +12200,7 @@ snapshots: tinyglobby: 0.2.17 optionalDependencies: '@types/node': 26.1.2 - esbuild: 0.28.1 + esbuild: 0.25.12 fsevents: 2.3.3 jiti: 2.7.0 sass: 1.105.1 @@ -12119,6 +12208,8 @@ snapshots: tsx: 4.23.15 yaml: 2.9.0 + vscode-uri@3.2.0: {} + vue-bundle-renderer@2.3.1: dependencies: ufo: 1.6.4 @@ -12132,10 +12223,10 @@ snapshots: vue-devtools-stub@0.1.0: {} - vue-eslint-parser@10.4.1(eslint@10.8.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2): + vue-eslint-parser@10.4.1(eslint@10.8.0(jiti@2.7.0)): dependencies: - debug: 4.4.3(supports-color@10.2.2) - eslint: 10.8.0(jiti@2.7.0)(supports-color@10.2.2) + debug: 4.4.3 + eslint: 10.8.0(jiti@2.7.0) eslint-scope: 9.1.2 eslint-visitor-keys: 5.0.1 espree: 11.2.0 @@ -12144,7 +12235,7 @@ snapshots: transitivePeerDependencies: - supports-color - vue-router@5.2.0(@vue/compiler-sfc@3.5.41)(esbuild@0.28.1)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)): + vue-router@5.2.0(@vue/compiler-sfc@3.5.41)(esbuild@0.25.12)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)))(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)): dependencies: '@babel/generator': 8.0.0 '@vue-macros/common': 3.1.4(vue@3.5.41(typescript@6.0.3)) @@ -12161,14 +12252,14 @@ snapshots: picomatch: 4.0.5 scule: 1.3.0 tinyglobby: 0.2.17 - unplugin: 3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) + unplugin: 3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)) unplugin-utils: 0.3.2 vue: 3.5.41(typescript@6.0.3) yaml: 2.9.0 optionalDependencies: '@vue/compiler-sfc': 3.5.41 pinia: 3.0.4(typescript@6.0.3)(vue@3.5.41(typescript@6.0.3)) - vite: 8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) + vite: 8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0) transitivePeerDependencies: - '@farmfe/core' - '@rspack/core' @@ -12179,6 +12270,12 @@ snapshots: - unloader - webpack + vue-tsc@3.3.13(typescript@6.0.3): + dependencies: + '@volar/typescript': 2.4.28 + '@vue/language-core': 3.3.13 + typescript: 6.0.3 + vue@3.5.41(typescript@6.0.3): dependencies: '@vue/compiler-dom': 3.5.41 @@ -12189,9 +12286,9 @@ snapshots: optionalDependencies: typescript: 6.0.3 - vuetify-nuxt-module@0.19.5(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(supports-color@10.2.2)(typescript@6.0.3)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)): + vuetify-nuxt-module@0.19.5(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(typescript@6.0.3)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0)))(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3)): dependencies: - '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.28.1)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) + '@nuxt/kit': 4.5.2(magic-string@1.1.0)(magicast@0.5.4)(oxc-parser@0.142.0)(rolldown@1.2.14)(unplugin@3.3.0(esbuild@0.25.12)(rolldown@1.2.14)(rollup@4.62.4)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))) defu: 6.1.7 destr: 2.0.5 local-pkg: 1.2.1 @@ -12199,9 +12296,9 @@ snapshots: perfect-debounce: 1.0.0 semver: 7.8.5 ufo: 1.6.4 - unconfig: 0.6.1(supports-color@10.2.2) + unconfig: 0.6.1 upath: 2.0.1 - vite-plugin-vuetify: 2.1.3(supports-color@10.2.2)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))(vuetify@4.1.8) + vite-plugin-vuetify: 2.1.3(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))(vuetify@4.1.8) vuetify: 4.1.8(typescript@6.0.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.41(typescript@6.0.3)) transitivePeerDependencies: - magic-string @@ -12220,7 +12317,7 @@ snapshots: vue: 3.5.41(typescript@6.0.3) optionalDependencies: typescript: 6.0.3 - vite-plugin-vuetify: 2.1.3(supports-color@10.2.2)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))(vuetify@4.1.8) + vite-plugin-vuetify: 2.1.3(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))(vuetify@4.1.8) vuetify@4.2.2(typescript@6.0.3)(vite-plugin-vuetify@2.1.3)(vue@3.5.41(typescript@6.0.3)): dependencies: @@ -12228,7 +12325,7 @@ snapshots: vue: 3.5.41(typescript@6.0.3) optionalDependencies: typescript: 6.0.3 - vite-plugin-vuetify: 2.1.3(supports-color@10.2.2)(vite@8.3.4(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))(vuetify@4.1.8) + vite-plugin-vuetify: 2.1.3(vite@8.3.4(@types/node@26.1.2)(esbuild@0.25.12)(jiti@2.7.0)(sass@1.105.1)(terser@5.49.2)(tsx@4.23.15)(yaml@2.9.0))(vue@3.5.41(typescript@6.0.3))(vuetify@4.1.8) transitivePeerDependencies: - '@adobe/leonardo-contrast-colors' - '@ant-design/colors' diff --git a/web/shared/types/api.ts b/web/shared/types/api.ts index becb20636..9ecb82efb 100644 --- a/web/shared/types/api.ts +++ b/web/shared/types/api.ts @@ -244,6 +244,11 @@ export interface EntitiesMessageThread { } export interface EntitiesPhone { + /** + * ArchiveNewOutgoingMessageThreads archives new conversations started by an outgoing message on this phone. + * @example false + */ + archive_new_outgoing_message_threads: boolean; /** @example "2022-06-05T14:26:02.302718+03:00" */ created_at: string; /** @example "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....." */ @@ -537,7 +542,10 @@ export interface RequestsPhoneAPIKeyStoreRequest { } export interface RequestsPhoneFCMToken { - /** @example "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....." */ + /** + * FcmToken is either a Firebase registration token or a public HTTPS adapter callback URL. + * @example "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....." + */ fcm_token: string; /** @example "[+18005550199]" */ phone_number: string; @@ -549,7 +557,15 @@ export interface RequestsPhoneFCMToken { } export interface RequestsPhoneUpsert { - /** @example "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....." */ + /** + * ArchiveNewOutgoingMessageThreads archives new conversations started by an outgoing message on this phone. + * @example false + */ + archive_new_outgoing_message_threads?: boolean; + /** + * FcmToken is either a Firebase registration token or a public HTTPS adapter callback URL. + * @example "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzd....." + */ fcm_token: string; /** * MaxSendAttempts is the number of attempts when sending an SMS message to handle the case where the phone is offline. diff --git a/web/tsconfig.json b/web/tsconfig.json index 875ee5af5..307b2134b 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -14,17 +14,5 @@ { "path": "./.nuxt/tsconfig.node.json" } - ], - "compilerOptions": { - "strict": true, - "moduleResolution": "bundler", - "module": "ESNext", - "target": "ESNext", - "paths": { - "~/*": ["./app/*"], - "~~/*": ["./*"], - "#imports": ["./app/types/nuxt-shims"] - } - }, - "include": ["app/**/*.ts", "app/**/*.d.ts", "shared/**/*.ts"] + ] }