Compare commits

...

9 Commits

Author SHA1 Message Date
Beverly Nguyen
28f09674df Merge branch 'main' into 2721-create-enums-and-replace-hardcoded-status-strings-with-enums-3 2025-07-16 17:21:15 -07:00
ccostino
34aff103a0 Merge pull request #2747 from GSA/2721-create-enums-and-replace-hardcoded-status-strings-with-enums-2
enums part 2
2025-07-16 17:39:45 -04:00
Beverly Nguyen
de3730f04a Merge pull request #2755 from GSA/invites 2025-07-16 12:14:54 -07:00
ccostino
1b8975ec32 Merge pull request #2752 from GSA/dependabot/npm_and_yarn/rollup-4.45.1
Bump rollup from 4.45.0 to 4.45.1
2025-07-16 15:11:10 -04:00
Kenneth Kehl
6ee84af8f6 fix typo 2025-07-16 11:45:21 -07:00
Kenneth Kehl
594dca06ab more debug for invites 2025-07-16 09:19:43 -07:00
Beverly Nguyen
56175bd7aa added organizationtype to enum 2025-07-15 23:01:12 -07:00
dependabot[bot]
e2e5d54af5 Bump rollup from 4.45.0 to 4.45.1
Bumps [rollup](https://github.com/rollup/rollup) from 4.45.0 to 4.45.1.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.45.0...v4.45.1)

---
updated-dependencies:
- dependency-name: rollup
  dependency-version: 4.45.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-15 23:28:12 +00:00
Beverly Nguyen
e9f6b93d85 enums part 2 2025-07-11 14:18:54 -07:00
12 changed files with 265 additions and 179 deletions

View File

@@ -16,6 +16,41 @@ class NotificationStatus(StrEnum):
VALIDATION_FAILED = "validation-failed"
CANCELLED = "cancelled"
@classmethod
def sending_statuses(cls):
return [cls.CREATED, cls.PENDING, cls.SENDING]
@classmethod
def delivered_statuses(cls):
return [cls.DELIVERED, cls.SENT]
@classmethod
def failure_statuses(cls):
return [
cls.FAILED,
cls.TEMPORARY_FAILURE,
cls.PERMANENT_FAILURE,
cls.TECHNICAL_FAILURE,
cls.VALIDATION_FAILED,
]
@classmethod
def requested_statuses(cls):
return (
cls.sending_statuses() + cls.delivered_statuses() + cls.failure_statuses()
)
class NotificationType(StrEnum):
EMAIL = "email"
SMS = "sms"
class OrganizationType(StrEnum):
FEDERAL = "federal"
STATE = "state"
OTHER = "other"
class ApiKeyType(StrEnum):
NORMAL = "normal"

View File

@@ -17,6 +17,7 @@ from flask import render_template_string, url_for
from flask.helpers import get_root_path
from markupsafe import Markup
from app.enums import AuthType, NotificationStatus
from app.utils.csv import get_user_preferred_timezone
from app.utils.time import parse_naive_dt
from notifications_utils.field import Field
@@ -276,46 +277,53 @@ def format_notification_type(notification_type):
def format_notification_status(status, template_type):
return {
"email": {
"failed": "Failed",
"technical-failure": "Technical failure",
"temporary-failure": "Inbox not accepting messages right now",
"permanent-failure": "Email address does not exist",
"delivered": "Delivered",
"sending": "Sending",
"created": "Sending",
"sent": "Delivered",
NotificationStatus.FAILED: "Failed",
NotificationStatus.TECHNICAL_FAILURE: "Technical failure",
NotificationStatus.TEMPORARY_FAILURE: "Inbox not accepting messages right now",
NotificationStatus.PERMANENT_FAILURE: "Email address does not exist",
NotificationStatus.DELIVERED: "Delivered",
NotificationStatus.SENDING: "Sending",
NotificationStatus.CREATED: "Sending",
NotificationStatus.SENT: "Delivered",
},
"sms": {
"failed": "Failed",
"technical-failure": "Technical failure",
"temporary-failure": "Phone not accepting messages right now",
"permanent-failure": "Not delivered",
"delivered": "Delivered",
"sending": "Sending",
"created": "Sending",
"pending": "Sending",
"sent": "Sent",
NotificationStatus.FAILED: "Failed",
NotificationStatus.TECHNICAL_FAILURE: "Technical failure",
NotificationStatus.TEMPORARY_FAILURE: "Phone not accepting messages right now",
NotificationStatus.PERMANENT_FAILURE: "Not delivered",
NotificationStatus.DELIVERED: "Delivered",
NotificationStatus.SENDING: "Sending",
NotificationStatus.CREATED: "Sending",
NotificationStatus.PENDING: "Sending",
NotificationStatus.SENT: "Sent",
},
}[template_type].get(status, status)
def format_notification_status_as_time(status, created, updated):
return dict.fromkeys(
{"created", "pending", "sending"}, " since {}".format(created)
{
NotificationStatus.CREATED,
NotificationStatus.PENDING,
NotificationStatus.SENDING,
},
" since {}".format(created),
).get(status, updated)
def format_notification_status_as_field_status(status, notification_type):
return {
"failed": "error",
"technical-failure": "error",
"temporary-failure": "error",
"permanent-failure": "error",
"delivered": None,
"sent": "sent-international" if notification_type == "sms" else None,
"sending": "default",
"created": "default",
"pending": "default",
NotificationStatus.FAILED: "error",
NotificationStatus.TECHNICAL_FAILURE: "error",
NotificationStatus.TEMPORARY_FAILURE: "error",
NotificationStatus.PERMANENT_FAILURE: "error",
NotificationStatus.DELIVERED: None,
NotificationStatus.SENT: (
"sent-international" if notification_type == "sms" else None
),
NotificationStatus.SENDING: "default",
NotificationStatus.CREATED: "default",
NotificationStatus.PENDING: "default",
}.get(status, "error")
@@ -323,9 +331,9 @@ def format_notification_status_as_url(status, notification_type):
url = partial(url_for, "main.message_status")
if status not in {
"technical-failure",
"temporary-failure",
"permanent-failure",
NotificationStatus.TECHNICAL_FAILURE,
NotificationStatus.TEMPORARY_FAILURE,
NotificationStatus.PERMANENT_FAILURE,
}:
return None
@@ -559,8 +567,8 @@ def square_metres_to_square_miles(area):
def format_auth_type(auth_type, with_indefinite_article=False):
indefinite_article, auth_type = {
"email_auth": ("an", "Email link"),
"sms_auth": ("a", "Text message code"),
AuthType.EMAIL_AUTH: ("an", "Email link"),
AuthType.SMS_AUTH: ("a", "Text message code"),
}[auth_type]
if with_indefinite_article:

View File

@@ -2,6 +2,7 @@ from flask import current_app, redirect, render_template, session, url_for
from flask_login import current_user
from app import service_api_client
from app.enums import OrganizationType
from app.formatters import email_safe
from app.main import main
from app.main.forms import CreateServiceForm
@@ -46,7 +47,7 @@ def _create_example_template(service_id):
def add_service():
default_organization_type = current_user.default_organization_type
if default_organization_type is None:
default_organization_type = "federal"
default_organization_type = OrganizationType.FEDERAL
form = CreateServiceForm(
# This value is currently not useful but if it is not set it will result in a bug
organization_type=default_organization_type

View File

@@ -15,6 +15,7 @@ from app import (
template_statistics_client,
)
from app.enums import ServicePermission
from app.enums import JobStatus, NotificationStatus
from app.main import main
from app.main.views.user_profile import set_timezone
from app.statistics_utils import get_formatted_percentage
@@ -43,7 +44,9 @@ def service_dashboard(service_id):
job_response = job_api_client.get_jobs(service_id)["data"]
service_data_retention_days = 7
active_jobs = [job for job in job_response if job["job_status"] != "cancelled"]
active_jobs = [
job for job in job_response if job["job_status"] != JobStatus.CANCELLED
]
job_lists = [
{**job_dict, "finished_processing": job_is_finished(job_dict)}
for job_dict in active_jobs
@@ -70,7 +73,9 @@ def service_dashboard(service_id):
def job_is_finished(job_dict):
done_statuses = DELIVERED_STATUSES + FAILURE_STATUSES + ["cancelled"]
done_statuses = (
DELIVERED_STATUSES + FAILURE_STATUSES + [NotificationStatus.CANCELLED]
)
processed_count = sum(
stat["count"]
for stat in job_dict["statistics"]
@@ -107,8 +112,18 @@ def get_local_daily_stats_for_last_x_days(stats_utc, user_timezone, days):
]
aggregator = {
d: {
"sms": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
"email": {"delivered": 0, "failure": 0, "pending": 0, "requested": 0},
"sms": {
NotificationStatus.DELIVERED: 0,
"failure": 0,
NotificationStatus.PENDING: 0,
"requested": 0,
},
"email": {
NotificationStatus.DELIVERED: 0,
"failure": 0,
NotificationStatus.PENDING: 0,
"requested": 0,
},
}
for d in days_list
}
@@ -122,7 +137,12 @@ def get_local_daily_stats_for_last_x_days(stats_utc, user_timezone, days):
if local_day in aggregator:
for msg_type in ["sms", "email"]:
for status in ["delivered", "failure", "pending", "requested"]:
for status in [
NotificationStatus.DELIVERED,
"failure",
NotificationStatus.PENDING,
"requested",
]:
aggregator[local_day][msg_type][status] += data[msg_type][status]
return aggregator
@@ -232,7 +252,9 @@ def usage(service_id):
def filter_out_cancelled_stats(template_statistics):
return [s for s in template_statistics if s["status"] != "cancelled"]
return [
s for s in template_statistics if s["status"] != NotificationStatus.CANCELLED
]
def aggregate_template_usage(template_statistics, sort_key="count"):
@@ -266,7 +288,7 @@ def get_dashboard_totals(statistics):
for msg_type in statistics.values():
msg_type["failed_percentage"] = get_formatted_percentage(
msg_type["failed"], msg_type["requested"]
msg_type[NotificationStatus.FAILED], msg_type["requested"]
)
msg_type["show_warning"] = float(msg_type["failed_percentage"]) > 3
@@ -315,7 +337,9 @@ def aggregate_status_types(counts_dict):
return get_dashboard_totals(
{
"{}_counts".format(message_type): {
"failed": sum(stats.get(status, 0) for status in FAILURE_STATUSES),
NotificationStatus.FAILED: sum(
stats.get(status, 0) for status in FAILURE_STATUSES
),
"requested": sum(stats.get(status, 0) for status in REQUESTED_STATUSES),
}
for message_type, stats in counts_dict.items()

View File

@@ -23,7 +23,7 @@ from app import (
notification_api_client,
service_api_client,
)
from app.enums import JobStatus, ServicePermission
from app.enums import JobStatus, NotificationStatus, ServicePermission
from app.formatters import get_time_left, message_count_noun
from app.main import main
from app.main.forms import SearchNotificationsForm
@@ -304,22 +304,30 @@ def get_status_filters(service, message_type, statistics):
if message_type is None:
stats = {
key: sum(statistics[message_type][key] for message_type in {"email", "sms"})
for key in {"requested", "delivered", "failed"}
for key in {
"requested",
NotificationStatus.DELIVERED,
NotificationStatus.FAILED,
}
}
else:
stats = statistics[message_type]
if stats.get("failure") is not None:
stats["failed"] = stats["failure"]
stats[NotificationStatus.FAILED] = stats["failure"]
stats["pending"] = stats["requested"] - stats["delivered"] - stats["failed"]
stats[NotificationStatus.PENDING] = (
stats["requested"]
- stats[NotificationStatus.DELIVERED]
- stats[NotificationStatus.FAILED]
)
filters = [
# key, label, option
("requested", "total", "sending,delivered,failed"),
("pending", "pending", "sending,pending"),
("delivered", "delivered", "delivered"),
("failed", "failed", "failed"),
(NotificationStatus.PENDING, "pending", "sending,pending"),
(NotificationStatus.DELIVERED, "delivered", "delivered"),
(NotificationStatus.FAILED, "failed", "failed"),
]
return [
# return list containing label, option, link, count

View File

@@ -25,6 +25,7 @@ from app import (
service_api_client,
user_api_client,
)
from app.enums import NotificationStatus
from app.extensions import redis_client
from app.main import main
from app.main.forms import (
@@ -769,32 +770,41 @@ def filter_and_sort_services(services, trial_mode_services=False):
def create_global_stats(services):
stats = {
"email": {"delivered": 0, "failed": 0, "requested": 0},
"sms": {"delivered": 0, "failed": 0, "requested": 0},
"email": {
NotificationStatus.DELIVERED: 0,
NotificationStatus.FAILED: 0,
"requested": 0,
},
"sms": {
NotificationStatus.DELIVERED: 0,
NotificationStatus.FAILED: 0,
"requested": 0,
},
}
# Issue #1323. The back end is now sending 'failure' instead of
# 'failed'. Adjust it here, but keep it flexible in case
# the backend reverts to 'failed'.
for service in services:
if service["statistics"]["sms"].get("failure") is not None:
service["statistics"]["sms"]["failed"] = service["statistics"]["sms"][
"failure"
]
service["statistics"]["sms"][NotificationStatus.FAILED] = service[
"statistics"
]["sms"]["failure"]
if service["statistics"]["email"].get("failure") is not None:
service["statistics"]["email"]["failed"] = service["statistics"]["email"][
"failure"
]
service["statistics"]["email"][NotificationStatus.FAILED] = service[
"statistics"
]["email"]["failure"]
for service in services:
for msg_type, status in itertools.product(
("sms", "email"), ("delivered", "failed", "requested")
("sms", "email"),
(NotificationStatus.DELIVERED, NotificationStatus.FAILED, "requested"),
):
stats[msg_type][status] += service["statistics"][msg_type][status]
for stat in stats.values():
stat["failure_rate"] = get_formatted_percentage(
stat["failed"], stat["requested"]
stat[NotificationStatus.FAILED], stat["requested"]
)
return stats

View File

@@ -146,14 +146,14 @@ def check_invited_user_email_address_matches_expected(
@hide_from_search_engines
def set_up_your_profile():
debug_msg(f"Enter set_up_your_profile with request.args {request.args}")
current_app.logger.info("#invites: Enter set_up_your_profile")
code = request.args.get("code")
state = request.args.get("state")
state_key = f"login-state-{unquote(state)}"
stored_state = unquote(redis_client.get(state_key).decode("utf8"))
if state != stored_state:
current_app.logger.error(f"State Error: {state} != {stored_state}")
current_app.logger.error(f"#invites State Error: {state} != {stored_state}")
abort(403)
login_gov_error = request.args.get("error")
@@ -164,26 +164,29 @@ def set_up_your_profile():
new_user = user_email is None or user_uuid is None
if new_user: # invite path
current_app.logger.info(
f"#invites: we are processing a new user with login.gov user_uuid {user_uuid}"
)
access_token = sign_in._get_access_token(code)
debug_msg("Got the access token for login.gov")
user_email, user_uuid = sign_in._get_user_email_and_uuid(access_token)
debug_msg(
f"Got the user_email {user_email} and user_uuid {user_uuid} from login.gov"
current_app.logger.info(
f"#invites: Got the user_email and user_uuid {user_uuid} from login.gov"
)
invite_data = redis_client.get(f"invitedata-{state}")
invite_data = json.loads(invite_data)
debug_msg(f"final state {invite_data}")
invited_user_id = invite_data["invited_user_id"]
invited_user_email_address = get_invited_user_email_address(invited_user_id)
debug_msg(f"email address from the invite_date is {invited_user_email_address}")
current_app.logger.info(
f"#invites: does user email match expected? {user_email == invited_user_email_address}"
)
check_invited_user_email_address_matches_expected(
user_email, invited_user_email_address
)
invited_user_accept_invite(invited_user_id)
debug_msg(
f"accepted invite user {invited_user_email_address} to service {invite_data['service_id']}"
current_app.logger.info(
f"accepted invite user with invited_user_id {invited_user_id} to service {invite_data['service_id']}"
)
# We need to avoid taking a second trip through the login.gov code because we cannot pull the
# access token twice. So once we retrieve these values, let's park them in redis for 15 minutes
@@ -194,9 +197,11 @@ def set_up_your_profile():
form = SetupUserProfileForm()
if form.validate_on_submit() and not new_user:
current_app.logger.info("#invites: this is an invite for a pre-existing user")
invite_data, user_email, user_uuid, invited_user_email_address = (
get_invite_data_from_redis(state)
)
current_app.logger.info(f"#invites: login.gov user_uuid from redis {user_uuid}")
# create or update the user
user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email)
@@ -208,15 +213,20 @@ def set_up_your_profile():
password=str(uuid.uuid4()),
auth_type="sms_auth",
)
debug_msg(f"registered user {form.name.data} with email {user_email}")
current_app.logger.info(
f"#invites: registered the new user with login.gov user_uuid {user_uuid}"
)
else:
user.update(mobile_number=form.mobile_number.data, name=form.name.data)
debug_msg(f"updated user {form.name.data}")
current_app.logger.info(
f"#invites: updated the pre-existing user with login.gov user_uuid {user_uuid}"
)
# activate the user
user = user_api_client.get_user_by_uuid_or_email(user_uuid, user_email)
current_app.logger.info("#invites: going to activate user")
activate_user(user["id"])
debug_msg("activated user")
current_app.logger.info(f"#invites: activated user with user.id {user['id']}")
usr = User.from_id(user["id"])
usr.add_to_service(
@@ -225,18 +235,17 @@ def set_up_your_profile():
invite_data["folder_permissions"],
invite_data["from_user_id"],
)
debug_msg(
f"Added user {usr.email_address} to service {invite_data['service_id']}"
)
# notify-admin-1766
# redirect new users to templates area of new service instead of dashboard
service_id = invite_data["service_id"]
url = url_for(".service_dashboard", service_id=service_id)
url = f"{url}/templates"
current_app.logger.info(f"#invites redirecting to {url}")
return redirect(url)
elif login_gov_error:
current_app.logger.error(f"login.gov error: {login_gov_error}")
current_app.logger.error(f"#invites: login.gov error: {login_gov_error}")
abort(403)
# we take two trips through this method, but should only hit this

View File

@@ -2,14 +2,15 @@ from collections import OrderedDict
from werkzeug.utils import cached_property
from app.enums import OrganizationType
from app.models import JSONModel, ModelList, SerialisedModelCollection, SortByNameMixin
from app.notify_client.organizations_api_client import organizations_client
class Organization(JSONModel, SortByNameMixin):
TYPE_FEDERAL = "federal"
TYPE_STATE = "state"
TYPE_OTHER = "other"
TYPE_FEDERAL = OrganizationType.FEDERAL
TYPE_STATE = OrganizationType.STATE
TYPE_OTHER = OrganizationType.OTHER
TYPE_LABELS = OrderedDict(
[

View File

@@ -323,7 +323,7 @@ class Service(JSONModel, SortByNameMixin):
@property
def shouldnt_use_govuk_as_sms_sender(self):
return self.organization_type != Organization.TYPE_CENTRAL
return self.organization_type != Organization.TYPE_FEDERAL
@cached_property
def sms_senders(self):

View File

@@ -7,25 +7,15 @@ from ordered_set import OrderedSet
from werkzeug.datastructures import MultiDict
from werkzeug.routing import RequestRedirect
from app.enums import NotificationStatus, ServicePermission
from app.enums import NotificationStatus, NotificationType, ServicePermission
from notifications_utils.field import Field
SENDING_STATUSES = [
NotificationStatus.CREATED,
NotificationStatus.PENDING,
NotificationStatus.SENDING,
]
DELIVERED_STATUSES = [NotificationStatus.DELIVERED, NotificationStatus.SENT]
FAILURE_STATUSES = [
NotificationStatus.FAILED,
NotificationStatus.TEMPORARY_FAILURE,
NotificationStatus.PERMANENT_FAILURE,
NotificationStatus.TECHNICAL_FAILURE,
NotificationStatus.VALIDATION_FAILED,
]
REQUESTED_STATUSES = SENDING_STATUSES + DELIVERED_STATUSES + FAILURE_STATUSES
SENDING_STATUSES = NotificationStatus.sending_statuses()
DELIVERED_STATUSES = NotificationStatus.delivered_statuses()
FAILURE_STATUSES = NotificationStatus.failure_statuses()
REQUESTED_STATUSES = NotificationStatus.requested_statuses()
NOTIFICATION_TYPES = ["sms", "email"]
NOTIFICATION_TYPES = [NotificationType.SMS, NotificationType.EMAIL]
def service_has_permission(permission):

168
package-lock.json generated
View File

@@ -50,7 +50,7 @@
"jest-environment-jsdom": "^30.0.4",
"jshint": "2.13.6",
"jshint-stylish": "2.2.1",
"rollup": "^4.45.0",
"rollup": "^4.45.1",
"rollup-plugin-commonjs": "10.1.0",
"rollup-plugin-node-resolve": "5.2.0"
},
@@ -2805,9 +2805,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.0.tgz",
"integrity": "sha512-2o/FgACbji4tW1dzXOqAV15Eu7DdgbKsF2QKcxfG4xbh5iwU7yr5RRP5/U+0asQliSYv5M4o7BevlGIoSL0LXg==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.45.1.tgz",
"integrity": "sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==",
"cpu": [
"arm"
],
@@ -2818,9 +2818,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.0.tgz",
"integrity": "sha512-PSZ0SvMOjEAxwZeTx32eI/j5xSYtDCRxGu5k9zvzoY77xUNssZM+WV6HYBLROpY5CkXsbQjvz40fBb7WPwDqtQ==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.45.1.tgz",
"integrity": "sha512-ujQ+sMXJkg4LRJaYreaVx7Z/VMgBBd89wGS4qMrdtfUFZ+TSY5Rs9asgjitLwzeIbhwdEhyj29zhst3L1lKsRQ==",
"cpu": [
"arm64"
],
@@ -2831,9 +2831,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.0.tgz",
"integrity": "sha512-BA4yPIPssPB2aRAWzmqzQ3y2/KotkLyZukVB7j3psK/U3nVJdceo6qr9pLM2xN6iRP/wKfxEbOb1yrlZH6sYZg==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.45.1.tgz",
"integrity": "sha512-FSncqHvqTm3lC6Y13xncsdOYfxGSLnP+73k815EfNmpewPs+EyM49haPS105Rh4aF5mJKywk9X0ogzLXZzN9lA==",
"cpu": [
"arm64"
],
@@ -2844,9 +2844,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.0.tgz",
"integrity": "sha512-Pr2o0lvTwsiG4HCr43Zy9xXrHspyMvsvEw4FwKYqhli4FuLE5FjcZzuQ4cfPe0iUFCvSQG6lACI0xj74FDZKRA==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.45.1.tgz",
"integrity": "sha512-2/vVn/husP5XI7Fsf/RlhDaQJ7x9zjvC81anIVbr4b/f0xtSmXQTFcGIQ/B1cXIYM6h2nAhJkdMHTnD7OtQ9Og==",
"cpu": [
"x64"
],
@@ -2857,9 +2857,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.0.tgz",
"integrity": "sha512-lYE8LkE5h4a/+6VnnLiL14zWMPnx6wNbDG23GcYFpRW1V9hYWHAw9lBZ6ZUIrOaoK7NliF1sdwYGiVmziUF4vA==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.45.1.tgz",
"integrity": "sha512-4g1kaDxQItZsrkVTdYQ0bxu4ZIQ32cotoQbmsAnW1jAE4XCMbcBPDirX5fyUzdhVCKgPcrwWuucI8yrVRBw2+g==",
"cpu": [
"arm64"
],
@@ -2870,9 +2870,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.0.tgz",
"integrity": "sha512-PVQWZK9sbzpvqC9Q0GlehNNSVHR+4m7+wET+7FgSnKG3ci5nAMgGmr9mGBXzAuE5SvguCKJ6mHL6vq1JaJ/gvw==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.45.1.tgz",
"integrity": "sha512-L/6JsfiL74i3uK1Ti2ZFSNsp5NMiM4/kbbGEcOCps99aZx3g8SJMO1/9Y0n/qKlWZfn6sScf98lEOUe2mBvW9A==",
"cpu": [
"x64"
],
@@ -2883,9 +2883,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.0.tgz",
"integrity": "sha512-hLrmRl53prCcD+YXTfNvXd776HTxNh8wPAMllusQ+amcQmtgo3V5i/nkhPN6FakW+QVLoUUr2AsbtIRPFU3xIA==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.45.1.tgz",
"integrity": "sha512-RkdOTu2jK7brlu+ZwjMIZfdV2sSYHK2qR08FUWcIoqJC2eywHbXr0L8T/pONFwkGukQqERDheaGTeedG+rra6Q==",
"cpu": [
"arm"
],
@@ -2896,9 +2896,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.0.tgz",
"integrity": "sha512-XBKGSYcrkdiRRjl+8XvrUR3AosXU0NvF7VuqMsm7s5nRy+nt58ZMB19Jdp1RdqewLcaYnpk8zeVs/4MlLZEJxw==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.45.1.tgz",
"integrity": "sha512-3kJ8pgfBt6CIIr1o+HQA7OZ9mp/zDk3ctekGl9qn/pRBgrRgfwiffaUmqioUGN9hv0OHv2gxmvdKOkARCtRb8Q==",
"cpu": [
"arm"
],
@@ -2909,9 +2909,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.0.tgz",
"integrity": "sha512-fRvZZPUiBz7NztBE/2QnCS5AtqLVhXmUOPj9IHlfGEXkapgImf4W9+FSkL8cWqoAjozyUzqFmSc4zh2ooaeF6g==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.45.1.tgz",
"integrity": "sha512-k3dOKCfIVixWjG7OXTCOmDfJj3vbdhN0QYEqB+OuGArOChek22hn7Uy5A/gTDNAcCy5v2YcXRJ/Qcnm4/ma1xw==",
"cpu": [
"arm64"
],
@@ -2922,9 +2922,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.0.tgz",
"integrity": "sha512-Btv2WRZOcUGi8XU80XwIvzTg4U6+l6D0V6sZTrZx214nrwxw5nAi8hysaXj/mctyClWgesyuxbeLylCBNauimg==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.45.1.tgz",
"integrity": "sha512-PmI1vxQetnM58ZmDFl9/Uk2lpBBby6B6rF4muJc65uZbxCs0EA7hhKCk2PKlmZKuyVSHAyIw3+/SiuMLxKxWog==",
"cpu": [
"arm64"
],
@@ -2935,9 +2935,9 @@
]
},
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.0.tgz",
"integrity": "sha512-Li0emNnwtUZdLwHjQPBxn4VWztcrw/h7mgLyHiEI5Z0MhpeFGlzaiBHpSNVOMB/xucjXTTcO+dhv469Djr16KA==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.45.1.tgz",
"integrity": "sha512-9UmI0VzGmNJ28ibHW2GpE2nF0PBQqsyiS4kcJ5vK+wuwGnV5RlqdczVocDSUfGX/Na7/XINRVoUgJyFIgipoRg==",
"cpu": [
"loong64"
],
@@ -2948,9 +2948,9 @@
]
},
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.0.tgz",
"integrity": "sha512-sB8+pfkYx2kvpDCfd63d5ScYT0Fz1LO6jIb2zLZvmK9ob2D8DeVqrmBDE0iDK8KlBVmsTNzrjr3G1xV4eUZhSw==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.45.1.tgz",
"integrity": "sha512-7nR2KY8oEOUTD3pBAxIBBbZr0U7U+R9HDTPNy+5nVVHDXI4ikYniH1oxQz9VoB5PbBU1CZuDGHkLJkd3zLMWsg==",
"cpu": [
"ppc64"
],
@@ -2961,9 +2961,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.0.tgz",
"integrity": "sha512-5GQ6PFhh7E6jQm70p1aW05G2cap5zMOvO0se5JMecHeAdj5ZhWEHbJ4hiKpfi1nnnEdTauDXxPgXae/mqjow9w==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.45.1.tgz",
"integrity": "sha512-nlcl3jgUultKROfZijKjRQLUu9Ma0PeNv/VFHkZiKbXTBQXhpytS8CIj5/NfBeECZtY2FJQubm6ltIxm/ftxpw==",
"cpu": [
"riscv64"
],
@@ -2974,9 +2974,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.0.tgz",
"integrity": "sha512-N/euLsBd1rekWcuduakTo/dJw6U6sBP3eUq+RXM9RNfPuWTvG2w/WObDkIvJ2KChy6oxZmOSC08Ak2OJA0UiAA==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.45.1.tgz",
"integrity": "sha512-HJV65KLS51rW0VY6rvZkiieiBnurSzpzore1bMKAhunQiECPuxsROvyeaot/tcK3A3aGnI+qTHqisrpSgQrpgA==",
"cpu": [
"riscv64"
],
@@ -2987,9 +2987,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.0.tgz",
"integrity": "sha512-2l9sA7d7QdikL0xQwNMO3xURBUNEWyHVHfAsHsUdq+E/pgLTUcCE+gih5PCdmyHmfTDeXUWVhqL0WZzg0nua3g==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.45.1.tgz",
"integrity": "sha512-NITBOCv3Qqc6hhwFt7jLV78VEO/il4YcBzoMGGNxznLgRQf43VQDae0aAzKiBeEPIxnDrACiMgbqjuihx08OOw==",
"cpu": [
"s390x"
],
@@ -3000,9 +3000,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.0.tgz",
"integrity": "sha512-XZdD3fEEQcwG2KrJDdEQu7NrHonPxxaV0/w2HpvINBdcqebz1aL+0vM2WFJq4DeiAVT6F5SUQas65HY5JDqoPw==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.45.1.tgz",
"integrity": "sha512-+E/lYl6qu1zqgPEnTrs4WysQtvc/Sh4fC2nByfFExqgYrqkKWp1tWIbe+ELhixnenSpBbLXNi6vbEEJ8M7fiHw==",
"cpu": [
"x64"
],
@@ -3013,9 +3013,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.0.tgz",
"integrity": "sha512-7ayfgvtmmWgKWBkCGg5+xTQ0r5V1owVm67zTrsEY1008L5ro7mCyGYORomARt/OquB9KY7LpxVBZes+oSniAAQ==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.45.1.tgz",
"integrity": "sha512-a6WIAp89p3kpNoYStITT9RbTbTnqarU7D8N8F2CV+4Cl9fwCOZraLVuVFvlpsW0SbIiYtEnhCZBPLoNdRkjQFw==",
"cpu": [
"x64"
],
@@ -3026,9 +3026,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.0.tgz",
"integrity": "sha512-B+IJgcBnE2bm93jEW5kHisqvPITs4ddLOROAcOc/diBgrEiQJJ6Qcjby75rFSmH5eMGrqJryUgJDhrfj942apQ==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.45.1.tgz",
"integrity": "sha512-T5Bi/NS3fQiJeYdGvRpTAP5P02kqSOpqiopwhj0uaXB6nzs5JVi2XMJb18JUSKhCOX8+UE1UKQufyD6Or48dJg==",
"cpu": [
"arm64"
],
@@ -3039,9 +3039,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.0.tgz",
"integrity": "sha512-+CXwwG66g0/FpWOnP/v1HnrGVSOygK/osUbu3wPRy8ECXjoYKjRAyfxYpDQOfghC5qPJYLPH0oN4MCOjwgdMug==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.45.1.tgz",
"integrity": "sha512-lxV2Pako3ujjuUe9jiU3/s7KSrDfH6IgTSQOnDWr9aJ92YsFd7EurmClK0ly/t8dzMkDtd04g60WX6yl0sGfdw==",
"cpu": [
"ia32"
],
@@ -3052,9 +3052,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.0.tgz",
"integrity": "sha512-SRf1cytG7wqcHVLrBc9VtPK4pU5wxiB/lNIkNmW2ApKXIg+RpqwHfsaEK+e7eH4A1BpI6BX/aBWXxZCIrJg3uA==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.45.1.tgz",
"integrity": "sha512-M/fKi4sasCdM8i0aWJjCSFm2qEnYRR8AMLG2kxp6wD13+tMGA4Z1tVAuHkNRjud5SW2EM3naLuK35w9twvf6aA==",
"cpu": [
"x64"
],
@@ -12450,9 +12450,9 @@
"license": "Unlicense"
},
"node_modules/rollup": {
"version": "4.45.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.0.tgz",
"integrity": "sha512-WLjEcJRIo7i3WDDgOIJqVI2d+lAC3EwvOGy+Xfq6hs+GQuAA4Di/H72xmXkOhrIWFg2PFYSKZYfH0f4vfKXN4A==",
"version": "4.45.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.45.1.tgz",
"integrity": "sha512-4iya7Jb76fVpQyLoiVpzUrsjQ12r3dM7fIVz+4NwoYvZOShknRmiv+iu9CClZml5ZLGb0XMcYLutK6w9tgxHDw==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@@ -12465,26 +12465,26 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.45.0",
"@rollup/rollup-android-arm64": "4.45.0",
"@rollup/rollup-darwin-arm64": "4.45.0",
"@rollup/rollup-darwin-x64": "4.45.0",
"@rollup/rollup-freebsd-arm64": "4.45.0",
"@rollup/rollup-freebsd-x64": "4.45.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.45.0",
"@rollup/rollup-linux-arm-musleabihf": "4.45.0",
"@rollup/rollup-linux-arm64-gnu": "4.45.0",
"@rollup/rollup-linux-arm64-musl": "4.45.0",
"@rollup/rollup-linux-loongarch64-gnu": "4.45.0",
"@rollup/rollup-linux-powerpc64le-gnu": "4.45.0",
"@rollup/rollup-linux-riscv64-gnu": "4.45.0",
"@rollup/rollup-linux-riscv64-musl": "4.45.0",
"@rollup/rollup-linux-s390x-gnu": "4.45.0",
"@rollup/rollup-linux-x64-gnu": "4.45.0",
"@rollup/rollup-linux-x64-musl": "4.45.0",
"@rollup/rollup-win32-arm64-msvc": "4.45.0",
"@rollup/rollup-win32-ia32-msvc": "4.45.0",
"@rollup/rollup-win32-x64-msvc": "4.45.0",
"@rollup/rollup-android-arm-eabi": "4.45.1",
"@rollup/rollup-android-arm64": "4.45.1",
"@rollup/rollup-darwin-arm64": "4.45.1",
"@rollup/rollup-darwin-x64": "4.45.1",
"@rollup/rollup-freebsd-arm64": "4.45.1",
"@rollup/rollup-freebsd-x64": "4.45.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.45.1",
"@rollup/rollup-linux-arm-musleabihf": "4.45.1",
"@rollup/rollup-linux-arm64-gnu": "4.45.1",
"@rollup/rollup-linux-arm64-musl": "4.45.1",
"@rollup/rollup-linux-loongarch64-gnu": "4.45.1",
"@rollup/rollup-linux-powerpc64le-gnu": "4.45.1",
"@rollup/rollup-linux-riscv64-gnu": "4.45.1",
"@rollup/rollup-linux-riscv64-musl": "4.45.1",
"@rollup/rollup-linux-s390x-gnu": "4.45.1",
"@rollup/rollup-linux-x64-gnu": "4.45.1",
"@rollup/rollup-linux-x64-musl": "4.45.1",
"@rollup/rollup-win32-arm64-msvc": "4.45.1",
"@rollup/rollup-win32-ia32-msvc": "4.45.1",
"@rollup/rollup-win32-x64-msvc": "4.45.1",
"fsevents": "~2.3.2"
}
},

View File

@@ -66,7 +66,7 @@
"jest-environment-jsdom": "^30.0.4",
"jshint": "2.13.6",
"jshint-stylish": "2.2.1",
"rollup": "^4.45.0",
"rollup": "^4.45.1",
"rollup-plugin-commonjs": "10.1.0",
"rollup-plugin-node-resolve": "5.2.0"
}