diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 27db39cc9..6b652dacf 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -28,6 +28,8 @@ What _type of change(s)_ does the PR contain? - [ ] Database change: _Remember the to include a new migration version, **or** explain here why it's not needed._ - [ ] Bug fix - [ ] Security Alert fix + - [ ] Package update + - [ ] Major version update - [ ] Documentation - [ ] Workflow - [ ] Tests **only** diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 30c19b0dc..1c52999f3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,20 @@ Changelog ========== +.. _2.6.0: + +2.6.0 - 2023-11-22 +~~~~~~~~~~~~~~~~~~~~~~~ + +- New endpoint `AddFailedFiles` to allow a retry of saving files to the database after issues during upload. +- Cronjobs: + - Updated command: `quarterly-usage` changed to `monthly-usage` and refactored to catch errors and send emails. + - New command `send-usage` to collect usage rows from the `Usage` table and send csv files to support email. +- Dependencies: + - `Pillow` from `9.3.0` to `10.1.0` + - `urllib3` from `1.26.8` to `1.26.18` + - `postcss` (npm) from `8.4.28` to `8.4.31` + .. _2.5.2: 2.5.2 - 2023-10-25 diff --git a/SPRINTLOG.md b/SPRINTLOG.md index e56b41d8d..761836922 100644 --- a/SPRINTLOG.md +++ b/SPRINTLOG.md @@ -315,7 +315,18 @@ _Nothing merged in CLI during this sprint_ - Add flag --verify-checksum to the comand in email template ([#1478])(https://github.com/ScilifelabDataCentre/dds_web/pull/1478) - Improved email layout; Highlighted information and commands when project is released ([#1479])(https://github.com/ScilifelabDataCentre/dds_web/pull/1479) -# 2023-10-16 - 2023-10-27 +# 2023-10-16 - 2023-11-03 (Longer sprint due to OKR prep and höstlov) - Added new API endpoint ProjectStatus.patch to extend the deadline ([#1480])(https://github.com/ScilifelabDataCentre/dds_web/pull/1480) - New version: 2.5.2 ([#1482](https://github.com/ScilifelabDataCentre/dds_web/pull/1482)) +- New endpoint `AddFailedFiles` for adding failed files to database ([#1472](https://github.com/ScilifelabDataCentre/dds_web/pull/1472)) +- Change the generate usage command to monthly instead of quartely, and add the command to send a usage report specifying the number of months ([#1476](https://github.com/ScilifelabDataCentre/dds_web/pull/1476)) +- New ADR record regarding OKR 2024 ([#1483](https://github.com/ScilifelabDataCentre/dds_web/pull/1483)) + +# 2023-11-6 - 2023-11-17 + +- Updated Pillow package version to address vulnerabities ([#1486](https://github.com/ScilifelabDataCentre/dds_web/pull/1486)) +- Updated urllib3 package version to address vulnerabities ([#1487](https://github.com/ScilifelabDataCentre/dds_web/pull/1487)) +- Updated PostCss Node package to address vulnerabities ([#1489](https://github.com/ScilifelabDataCentre/dds_web/pull/1489)) +- Updated Several node libraries to address vulnerabities ([#1492](https://github.com/ScilifelabDataCentre/dds_web/pull/1492)) +- New version: 2.6.0 ([#1494](https://github.com/ScilifelabDataCentre/dds_web/pull/1494)) diff --git a/dds_web/__init__.py b/dds_web/__init__.py index 318ff4c2f..2ba8d8ff8 100644 --- a/dds_web/__init__.py +++ b/dds_web/__init__.py @@ -274,7 +274,8 @@ def load_user(user_id): set_available_to_expired, set_expired_to_archived, delete_invites, - quarterly_usage, + monthly_usage, + send_usage, collect_stats, monitor_usage, update_unit, @@ -291,7 +292,8 @@ def load_user(user_id): app.cli.add_command(set_available_to_expired) app.cli.add_command(set_expired_to_archived) app.cli.add_command(delete_invites) - app.cli.add_command(quarterly_usage) + app.cli.add_command(monthly_usage) + app.cli.add_command(send_usage) app.cli.add_command(collect_stats) app.cli.add_command(monitor_usage) diff --git a/dds_web/api/__init__.py b/dds_web/api/__init__.py index 3a7da23ff..afdd0a467 100644 --- a/dds_web/api/__init__.py +++ b/dds_web/api/__init__.py @@ -50,6 +50,7 @@ def output_json(data, code, headers=None): api.add_resource(files.FileInfo, "/file/info", endpoint="file_info") api.add_resource(files.FileInfoAll, "/file/all/info", endpoint="all_file_info") api.add_resource(files.UpdateFile, "/file/update", endpoint="update_file") +api.add_resource(files.AddFailedFiles, "/file/failed/add", endpoint="add_failed_files") # Projects ############################################################################## Projects # api.add_resource(project.UserProjects, "/proj/list", endpoint="list_projects") diff --git a/dds_web/api/files.py b/dds_web/api/files.py index 415977924..2a5dddb68 100644 --- a/dds_web/api/files.py +++ b/dds_web/api/files.py @@ -740,3 +740,22 @@ def put(self): db.session.commit() return {"message": "File info updated."} + + +class AddFailedFiles(flask_restful.Resource): + """Get files from log file and save to database.""" + + @auth.login_required(role=["Unit Admin", "Unit Personnel"]) + @json_required + @handle_validation_errors + def put(self): + """Run flask command with failed_delivery_log.""" + + # Verify project ID and access + project = project_schemas.ProjectRequiredSchema().load(flask.request.args) + + # Get the request json and pass it to add_uploaded_files_to_db + request_json = flask.request.get_json(silent=True) + + files_added, errors = dds_web.utils.add_uploaded_files_to_db(project, request_json) + return {"files_added": [file.name for file in files_added], "message": errors} diff --git a/dds_web/commands.py b/dds_web/commands.py index c9d925cb3..071042bdd 100644 --- a/dds_web/commands.py +++ b/dds_web/commands.py @@ -9,6 +9,8 @@ import datetime from dateutil.relativedelta import relativedelta import gc +import pathlib +import csv # Installed import click @@ -211,71 +213,28 @@ def update_uploaded_file_with_log(project, path_to_log_file): """Update file details that weren't properly uploaded to db from cli log""" import botocore from dds_web.database import models - from dds_web import db - from dds_web.api.api_s3_connector import ApiS3Connector + from dds_web import utils import json proj_in_db = models.Project.query.filter_by(public_id=project).one_or_none() if not proj_in_db: flask.current_app.logger.error(f"The project '{project}' doesn't exist.") return + flask.current_app.logger.debug(f"Updating file in project '{project}'...") if not os.path.exists(path_to_log_file): flask.current_app.logger.error(f"The log file '{path_to_log_file}' doesn't exist.") return + flask.current_app.logger.debug(f"Reading file info from path '{path_to_log_file}'...") with open(path_to_log_file, "r") as f: log = json.load(f) - errors = {} - files_added = [] - for file, vals in log.items(): - status = vals.get("status") - if not status or not status.get("failed_op") == "add_file_db": - continue - - with ApiS3Connector(project=proj_in_db) as s3conn: - try: - _ = s3conn.resource.meta.client.head_object( - Bucket=s3conn.project.bucket, Key=vals["path_remote"] - ) - except botocore.client.ClientError as err: - if err.response["Error"]["Code"] == "404": - errors[file] = {"error": "File not found in S3", "traceback": err.__traceback__} - else: - file_object = models.File.query.filter( - sqlalchemy.and_( - models.File.name == sqlalchemy.func.binary(file), - models.File.project_id == proj_in_db.id, - ) - ).first() - if file_object: - errors[file] = {"error": "File already in database."} - else: - new_file = models.File( - name=file, - name_in_bucket=vals["path_remote"], - subpath=vals["subpath"], - project_id=proj_in_db.id, - size_original=vals["size_raw"], - size_stored=vals["size_processed"], - compressed=not vals["compressed"], - public_key=vals["public_key"], - salt=vals["salt"], - checksum=vals["checksum"], - ) - new_version = models.Version( - size_stored=new_file.size_stored, time_uploaded=datetime.datetime.utcnow() - ) - proj_in_db.file_versions.append(new_version) - proj_in_db.files.append(new_file) - new_file.versions.append(new_version) + flask.current_app.logger.debug("File contents were loaded...") - db.session.add(new_file) - files_added.append(new_file) - db.session.commit() + files_added, errors = utils.add_uploaded_files_to_db(proj_in_db=proj_in_db, log=log) - flask.current_app.logger.info(f"Files added: {files_added}") - flask.current_app.logger.info(f"Errors while adding files: {errors}") + flask.current_app.logger.info(f"Files added: {files_added}") + flask.current_app.logger.info(f"Errors while adding files: {errors}") @click.group(name="lost-files") @@ -798,15 +757,21 @@ def delete_invites(): flask.current_app.logger.error(f"{invite} not deleted: {error}") -@click.command("quartely-usage") +@click.command("monthly-usage") @flask.cli.with_appcontext -def quarterly_usage(): - """ - Get the monthly usage for the units - Should be run on the 1st of Jan,Apr,Jul,Oct at around 00:01. +def monthly_usage(): + """Get the monthly usage for the units. + + Should be run on the 1st of every month at around 00:01. + + 1. Mark projects as done (all files have been included in an invoice) + 2. Calculate project usage for all non-done projects + 3. Send success- or failure email """ - flask.current_app.logger.debug("Task: Collecting usage information from database.") + flask.current_app.logger.debug( + "Starting `monthly_usage`; Collecting usage information from database." + ) # Imports # Installed @@ -818,31 +783,43 @@ def quarterly_usage(): from dds_web.utils import ( current_time, page_query, - # calculate_period_usage, calculate_version_period_usage, + send_email_with_retry, ) + # Email settings + email_recipient: str = flask.current_app.config.get("MAIL_DDS") + # -- Success + email_subject: str = "[INVOICING CRONJOB]" + email_body: str = ( + "The calculation of the monthly usage succeeded; The byte hours " + "for all active projects have been saved to the database." + ) + # -- Failure + error_subject: str = f"{email_subject} Error in monthly-usage cronjob" + error_body: str = ( + "There was an error in the cronjob 'monthly-usage', used for calculating the" + " byte hours for every active project in the last month.\n\n" + "What to do:\n" + "1. Check the logs on OpenSearch.\n" + "2. The DDS team should enter the backend container and run the command `flask monthly-usage`.\n" + "3. Check that you receive a new email indicating that the command was successful.\n" + ) + + # 1. Mark projects as done (all files have been included in an invoice) + # .. a. Get projects where is_active = False + # .. b. Check if the versions are all time_deleted == time_invoiced + # .. c. Yes --> Set new column to True ("done") try: - # 1. Get projects where is_active = False - # .. a. Check if the versions are all time_deleted == time_invoiced - # .. b. Yes --> Set new column to True ("done") - flask.current_app.logger.info("Marking projects as 'done'....") - for unit, project in page_query( - db.session.query(models.Unit, models.Project) - .join(models.Project) - .filter(models.Project.is_active == False) + flask.current_app.logger.info("Marking projects as 'done'...") + + # Iterate through non-active projects + for project in page_query( + models.Project.query.filter_by(is_active=False).with_for_update() ): # Get number of versions in project that have been fully included in usage calcs - num_done = ( - db.session.query(models.Project, models.Version) - .join(models.Version) - .filter( - sqlalchemy.and_( - models.Project.id == project.id, - models.Version.time_deleted == models.Version.time_invoiced, - ) - ) - .count() + num_done = len( + list(v for v in project.file_versions if v.time_deleted == v.time_invoiced) ) # Check if there are any versions that are not fully included @@ -850,17 +827,39 @@ def quarterly_usage(): if num_done == len(project.file_versions): project.done = True + # Save any projects marked as done db.session.commit() - # 2. Get project where done = False - for unit, project in page_query( - db.session.query(models.Unit, models.Project) - .join(models.Project) - .filter(models.Project.done == False) - ): + except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.SQLAlchemyError) as err: + db.session.rollback() + flask.current_app.logger.error( + "Usage collection during step 1: marking projects as done. Sending email..." + ) + + # Send email about error + email_message: flask_mail.Message = flask_mail.Message( + subject=error_subject, + recipients=[email_recipient], + body=error_body, + ) + send_email_with_retry(msg=email_message) + raise + + # 2. Calculate project usage for all non-done projects + # .. a. Get projects where done = False + # .. b. Calculate usage + # .. c. Save usage + try: + flask.current_app.logger.info("Calculating usage...") + + # Save all new rows at once + all_new_rows = [] + + # Iterate through non-done projects + for project in page_query(models.Project.query.filter_by(done=False).with_for_update()): project_byte_hours: int = 0 for version in project.file_versions: - # Skipp deleted and already invoiced versions + # Skip deleted and already invoiced versions if version.time_deleted == version.time_invoiced and [ version.time_deleted, version.time_invoiced, @@ -868,25 +867,184 @@ def quarterly_usage(): continue version_bhours = calculate_version_period_usage(version=version) project_byte_hours += version_bhours - flask.current_app.logger.info( + flask.current_app.logger.debug( f"Project {project.public_id} byte hours: {project_byte_hours}" ) # Create a record in usage table - new_record = models.Usage( + new_usage_row = models.Usage( project_id=project.id, usage=project_byte_hours, - cost=0, time_collected=current_time(), ) - db.session.add(new_record) - db.session.commit() + all_new_rows.append(new_usage_row) + + # Save new rows + db.session.add_all(all_new_rows) + db.session.commit() except (sqlalchemy.exc.OperationalError, sqlalchemy.exc.SQLAlchemyError) as err: - flask.current_app.logger.exception(err) db.session.rollback() + flask.current_app.logger.error( + "Usage collection during step 2: calculating and saving usage. Sending email..." + ) + + # Send email about error + email_message: flask_mail.Message = flask_mail.Message( + subject=error_subject, + recipients=[email_recipient], + body=error_body, + ) + send_email_with_retry(msg=email_message) raise + # 3. Send success email + flask.current_app.logger.info("Usage collection successful; Sending email.") + email_subject += " Usage records available for collection" + email_message: flask_mail.Message = flask_mail.Message( + subject=email_subject, + recipients=[email_recipient], + body=email_body, + ) + send_email_with_retry(msg=email_message) + + +@click.command("send-usage") +@click.option("--months", type=click.IntRange(min=1, max=12), required=True) +@flask.cli.with_appcontext +def send_usage(months): + """Get unit storage usage for the last x months and send in email.""" + # Imports + from dds_web.database import models + from dds_web.utils import current_time, page_query, send_email_with_retry + + # Email settings + email_recipient: str = flask.current_app.config.get("MAIL_DDS") + # -- Success + email_subject: str = "[SEND-USAGE CRONJOB]" + email_body: str = f"Here is the usage for the last {months} months.\n" + # -- Failure + error_subject: str = f"{email_subject} Error in send-usage cronjob" + error_body: str = ( + "There was an error in the cronjob 'send-usage', used for sending" + " information about the storage usage for each SciLifeLab unit. \n\n" + "What to do:\n" + "1. Check the logs on OpenSearch.\n" + "2. The DDS team should enter the backend container and run the command `flask send-usage`.\n" + "3. Check that you receive a new email indicating that the command was successful.\n" + ) + + end = current_time() + flask.current_app.logger.debug(f"Month now: {end.month}") + + start = end - relativedelta(months=months) + flask.current_app.logger.debug(f"Month {months} months ago: {start.month}") + + flask.current_app.logger.debug(f"Start: {start}") + flask.current_app.logger.debug(f"End: {end}") + + # CSV files to send + csv_file_names = [] + + have_failed = False # Flag to check if any csv files failed to be generated + + # Iterate through units + for unit in models.Unit.query: + # Generate CSV file name + csv_file_name = pathlib.Path( + f"{unit.public_id}_Usage_Months-{start.month}-to-{end.month}.csv" + ) + flask.current_app.logger.debug(f"CSV file name: {csv_file_name}") + + # Total usage for unit + total_usage = 0 + + # Open the csv file + try: + with csv_file_name.open(mode="w+", newline="") as file: + csv_writer = csv.writer(file) + csv_writer.writerow( + [ + "Project ID", + "Project Title", + "Project Created", + "Time Collected", + "Byte Hours", + ] + ) + + # Get usage rows connected to unit, that have been collected between X months ago and now + for usage_row, project_row in page_query( + db.session.query(models.Usage, models.Project) + .join(models.Project) + .filter( + models.Project.responsible_unit == unit, + models.Usage.time_collected.between(start, end), + ) + ): + # Increase total unit usage + total_usage += usage_row.usage + + # Save usage row info to csv file + csv_writer.writerow( + [ + project_row.public_id, + project_row.title, + project_row.date_created, + usage_row.time_collected, + usage_row.usage, + ] + ) + + # Save total + csv_writer.writerow(["--", "--", "--", "--", total_usage]) + except Exception as e: + # Catch exception, dont raise it. So it can continue to next unit + flask.current_app.logger.error(f"Error writing to CSV file: {e}") + + # Set flag to True, so we know at least 1 file have failed + have_failed = True + + csv_file_name.unlink(missing_ok=True) # Delete the csv file if it was created + + # Update email body with files with errors + error_body += "File(s) with errors: \n" + error_body += f"{csv_file_name}\n" + else: + # Add correctly created csv to list of files to send + csv_file_names.append(csv_file_name) + + # IF any csv files failed to be generated, send email about error + if have_failed: + email_message: flask_mail.Message = flask_mail.Message( + subject=error_subject, + recipients=[email_recipient], + body=error_body, + ) + send_email_with_retry(msg=email_message) + + # IF no csv files were generated, log error and return + if not csv_file_names: + flask.current_app.logger.error("No CSV files generated.") + return + + # Send email with the csv + flask.current_app.logger.info("Sending email with the CSV.") + email_subject += " Usage records attached in the present mail" + email_message: flask_mail.Message = flask_mail.Message( + subject=email_subject, + recipients=[email_recipient], + body=email_body, + ) + # add atachments + for csv_file in csv_file_names: + with csv_file.open("r") as file: + email_message.attach(filename=str(csv_file), content_type="text/csv", data=file.read()) + send_email_with_retry(msg=email_message) + + # delete the csv after sending the email + [csv_file.unlink() for csv_file in csv_file_names] + @click.command("stats") @flask.cli.with_appcontext diff --git a/dds_web/database/models.py b/dds_web/database/models.py index 8d27ea779..104b413af 100644 --- a/dds_web/database/models.py +++ b/dds_web/database/models.py @@ -1053,7 +1053,6 @@ class Usage(db.Model): # Additional columns usage = db.Column(db.Float, nullable=False) - cost = db.Column(db.Float, nullable=False) time_collected = db.Column( db.DateTime(), unique=False, nullable=False, default=dds_web.utils.current_time ) diff --git a/dds_web/static/package-lock.json b/dds_web/static/package-lock.json index 9485590b5..482625544 100644 --- a/dds_web/static/package-lock.json +++ b/dds_web/static/package-lock.json @@ -18,13 +18,13 @@ }, "devDependencies": { "autoprefixer": "^10.4.15", - "node-sass": "^7.0.3", - "nodemon": "^2.0.22", + "node-sass": "^9.0.0", + "nodemon": "^3.0.1", "npm-run-all": "^4.1.5", - "postcss": "^8.4.28", + "postcss": "^8.4.31", "postcss-cli": "^9.1.0", "purgecss": "^4.1.3", - "serve": "^13.0.4", + "serve": "^14.2.1", "stylelint": "^14.16.1", "stylelint-config-twbs-bootstrap": "^3.2.1" } @@ -308,9 +308,9 @@ "dev": true }, "node_modules/@zeit/schemas": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz", - "integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.29.0.tgz", + "integrity": "sha512-g5QiLIfbg3pLuYUJPlisNKY+epQJTcMDsOnVNkscrDP1oi7vmJnzOANYJI/1pZcVJ6umUkBv3aFtlg1UvUHGzA==", "dev": true }, "node_modules/abbrev": { @@ -370,14 +370,14 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { @@ -457,23 +457,10 @@ } ] }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/arg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arg/-/arg-2.0.0.tgz", - "integrity": "sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true }, "node_modules/array-buffer-byte-length": { @@ -530,24 +517,6 @@ "node": ">=0.10.0" } }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -566,12 +535,6 @@ "node": "*" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, "node_modules/autoprefixer": { "version": "10.4.15", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz", @@ -621,36 +584,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -684,51 +623,142 @@ } }, "node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", "dev": true, "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" }, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boxen/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/boxen/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/boxen/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boxen/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/boxen/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boxen/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -889,12 +919,6 @@ } ] }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -911,6 +935,21 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -957,29 +996,32 @@ } }, "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", "dev": true, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/clipboardy": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", - "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", "dev": true, "dependencies": { - "arch": "^2.1.1", - "execa": "^1.0.0", - "is-wsl": "^2.1.1" + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cliui": { @@ -1029,18 +1071,6 @@ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -1063,16 +1093,16 @@ } }, "node_modules/compression": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", - "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", "dev": true, "dependencies": { "accepts": "~1.3.5", "bytes": "3.0.0", - "compressible": "~2.0.14", + "compressible": "~2.0.16", "debug": "2.6.9", - "on-headers": "~1.0.1", + "on-headers": "~1.0.2", "safe-buffer": "5.1.2", "vary": "~1.1.2" }, @@ -1197,18 +1227,6 @@ "node": ">=4" } }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/datatables.net": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.13.6.tgz", @@ -1302,15 +1320,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -1338,15 +1347,11 @@ "node": ">=8" } }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true }, "node_modules/electron-to-chromium": { "version": "1.4.504", @@ -1370,15 +1375,6 @@ "iconv-lite": "^0.6.2" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -1506,105 +1502,28 @@ } }, "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "dependencies": { - "shebang-regex": "^1.0.0" + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" + "node": ">=10" }, - "bin": { - "which": "bin/which" + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1627,12 +1546,6 @@ "node": ">=8.6.0" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, "node_modules/fast-url-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", @@ -1726,29 +1639,6 @@ "is-callable": "^1.1.3" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/fraction.js": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.1.tgz", @@ -1841,26 +1731,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/gaze": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", @@ -1907,15 +1777,15 @@ } }, "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/get-symbol-description": { @@ -1934,15 +1804,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2118,29 +1979,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -2281,21 +2119,6 @@ "node": ">= 6" } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -2309,6 +2132,15 @@ "node": ">= 6" } }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -2650,6 +2482,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -2679,12 +2523,15 @@ } }, "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-string": { @@ -2732,12 +2579,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, "node_modules/is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -2774,12 +2615,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, "node_modules/jquery": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", @@ -2797,12 +2632,6 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2821,22 +2650,10 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "node_modules/jsonfile": { @@ -2851,21 +2668,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -3049,6 +2851,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3092,6 +2900,15 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -3303,97 +3120,318 @@ "bin": { "node-gyp": "bin/node-gyp.js" }, - "engines": { - "node": ">= 10.12.0" + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/node-gyp/node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "dev": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/node-sass": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-9.0.0.tgz", + "integrity": "sha512-yltEuuLrfH6M7Pq2gAj5B6Zm7m+gdZoG66wTqG6mIZV/zijq3M2OO2HswtT6oBspPyFhHDcaxWpsBm0fRNDHPg==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "async-foreach": "^0.1.3", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "lodash": "^4.17.15", + "make-fetch-happen": "^10.0.4", + "meow": "^9.0.0", + "nan": "^2.17.0", + "node-gyp": "^8.4.1", + "sass-graph": "^4.0.1", + "stdout-stream": "^1.4.0", + "true-case-path": "^2.2.1" + }, + "bin": { + "node-sass": "bin/node-sass" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/node-sass/node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-sass/node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-sass/node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/node-sass/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/node-sass/node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-sass/node_modules/cacache/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-sass/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/node-sass/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/node-sass/node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-sass/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-sass/node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" } }, - "node_modules/node-gyp/node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "node_modules/node-sass/node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", "dev": true, "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 10" } }, - "node_modules/node-gyp/node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "node_modules/node-sass/node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", "dev": true, "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" + "minipass": "^3.1.1" }, "engines": { "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/node-gyp/node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "node_modules/node-sass/node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", "dev": true, "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" + "unique-slug": "^3.0.0" }, "engines": { "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", - "dev": true - }, - "node_modules/node-sass": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-7.0.3.tgz", - "integrity": "sha512-8MIlsY/4dXUkJDYht9pIWBhMil3uHmE8b/AdJPjmFn1nBx9X9BASzfzmsCy0uCCb8eqI3SYYzVPDswWqSx7gjw==", + "node_modules/node-sass/node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", "dev": true, - "hasInstallScript": true, "dependencies": { - "async-foreach": "^0.1.3", - "chalk": "^4.1.2", - "cross-spawn": "^7.0.3", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", - "lodash": "^4.17.15", - "meow": "^9.0.0", - "nan": "^2.13.2", - "node-gyp": "^8.4.1", - "npmlog": "^5.0.0", - "request": "^2.88.0", - "sass-graph": "^4.0.1", - "stdout-stream": "^1.4.0", - "true-case-path": "^1.0.2" - }, - "bin": { - "node-sass": "bin/node-sass" + "imurmurhash": "^0.1.4" }, "engines": { - "node": ">=12" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/nodemon": { - "version": "2.0.22", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", - "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", + "integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==", "dev": true, "dependencies": { "chokidar": "^3.5.2", @@ -3401,8 +3439,8 @@ "ignore-by-default": "^1.0.1", "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", "supports-color": "^5.5.0", "touch": "^3.1.0", "undefsafe": "^2.0.5" @@ -3411,7 +3449,7 @@ "nodemon": "bin/nodemon.js" }, "engines": { - "node": ">=8.10.0" + "node": ">=10" }, "funding": { "type": "opencollective", @@ -3436,15 +3474,6 @@ "node": ">=4" } }, - "node_modules/nodemon/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/nodemon/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -3660,54 +3689,15 @@ } }, "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "dependencies": { - "path-key": "^2.0.0" + "path-key": "^3.0.0" }, "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dev": true, - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/object-inspect": { @@ -3764,13 +3754,19 @@ "wrappy": "1" } }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, "engines": { - "node": ">=4" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-limit": { @@ -3903,12 +3899,6 @@ "node": ">=8" } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -3949,9 +3939,9 @@ } }, "node_modules/postcss": { - "version": "8.4.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", - "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "funding": [ { @@ -4182,28 +4172,12 @@ "node": ">=10" } }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -4225,15 +4199,6 @@ "purgecss": "bin/purgecss.js" } }, - "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4531,38 +4496,6 @@ "node": ">=0.10.0" } }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -4720,7 +4653,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "dev": true, + "optional": true }, "node_modules/sass-graph": { "version": "4.0.1", @@ -4766,36 +4700,41 @@ } }, "node_modules/serve": { - "version": "13.0.4", - "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.4.tgz", - "integrity": "sha512-Lj8rhXmphJCRQVv5qwu0NQZ2h+0MrRyRJxDZu5y3qLH2i/XY6a0FPj/VmjMUdkJb672MBfE8hJ274PU6JzBd0Q==", - "dev": true, - "dependencies": { - "@zeit/schemas": "2.6.0", - "ajv": "6.12.6", - "arg": "2.0.0", - "boxen": "5.1.2", - "chalk": "2.4.1", - "clipboardy": "2.3.0", - "compression": "1.7.3", - "serve-handler": "6.1.3", - "update-check": "1.5.2" + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.1.tgz", + "integrity": "sha512-48er5fzHh7GCShLnNyPBRPEjs2I6QBozeGr02gaacROiyS/8ARADlj595j39iZXAqBbJHH/ivJJyPRWY9sQWZA==", + "dev": true, + "dependencies": { + "@zeit/schemas": "2.29.0", + "ajv": "8.11.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.7.4", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.5", + "update-check": "1.5.4" }, "bin": { - "serve": "bin/serve.js" + "serve": "build/main.js" + }, + "engines": { + "node": ">= 14" } }, "node_modules/serve-handler": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz", - "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", + "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", "dev": true, "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", "fast-url-parser": "1.1.3", "mime-types": "2.1.18", - "minimatch": "3.0.4", + "minimatch": "3.1.2", "path-is-inside": "1.0.2", "path-to-regexp": "2.2.1", "range-parser": "1.2.0" @@ -4822,78 +4761,16 @@ "node": ">= 0.6" } }, - "node_modules/serve-handler/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/serve/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/serve/node_modules/chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/serve/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/serve/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/serve/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", "dev": true, "engines": { - "node": ">=4" - } - }, - "node_modules/serve/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/set-blocking": { @@ -4953,24 +4830,15 @@ "dev": true }, "node_modules/simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, "dependencies": { - "semver": "~7.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "node": ">=10" } }, "node_modules/slash": { @@ -5090,31 +4958,6 @@ "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", "dev": true }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/ssri": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", @@ -5278,13 +5121,13 @@ "node": ">=4" } }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, "node_modules/strip-indent": { @@ -5625,12 +5468,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/tar": { "version": "6.1.15", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", @@ -5702,62 +5539,19 @@ "node": "*" } }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tough-cookie/node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/true-case-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", - "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", - "dev": true, - "dependencies": { - "glob": "^7.1.2" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "node_modules/true-case-path": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz", + "integrity": "sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==", "dev": true }, "node_modules/type-fest": { @@ -5916,9 +5710,9 @@ } }, "node_modules/update-check": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.2.tgz", - "integrity": "sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", "dev": true, "dependencies": { "registry-auth-token": "3.3.2", @@ -5949,16 +5743,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, "node_modules/v8-compile-cache": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", @@ -5984,20 +5768,6 @@ "node": ">= 0.8" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6058,15 +5828,68 @@ } }, "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", "dev": true, "dependencies": { - "string-width": "^4.0.0" + "string-width": "^5.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/widest-line/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/widest-line/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/widest-line/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/wrap-ansi": { @@ -6387,9 +6210,9 @@ "dev": true }, "@zeit/schemas": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz", - "integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.29.0.tgz", + "integrity": "sha512-g5QiLIfbg3pLuYUJPlisNKY+epQJTcMDsOnVNkscrDP1oi7vmJnzOANYJI/1pZcVJ6umUkBv3aFtlg1UvUHGzA==", "dev": true }, "abbrev": { @@ -6437,14 +6260,14 @@ } }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, @@ -6494,20 +6317,10 @@ "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", "dev": true }, - "are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - } - }, "arg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arg/-/arg-2.0.0.tgz", - "integrity": "sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true }, "array-buffer-byte-length": { @@ -6546,21 +6359,6 @@ "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", "dev": true }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true - }, "astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -6573,12 +6371,6 @@ "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==", "dev": true }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, "autoprefixer": { "version": "10.4.15", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz", @@ -6599,33 +6391,12 @@ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", "dev": true }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true - }, - "aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -6647,32 +6418,87 @@ } }, "boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", "dev": true, "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" }, "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true + }, "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true + }, + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + }, "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } } } }, @@ -6778,12 +6604,6 @@ "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==", "dev": true }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -6794,6 +6614,15 @@ "supports-color": "^7.1.0" } }, + "chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dev": true, + "requires": { + "chalk": "^4.1.2" + } + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -6823,20 +6652,20 @@ "dev": true }, "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", "dev": true }, "clipboardy": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", - "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", "dev": true, "requires": { - "arch": "^2.1.1", - "execa": "^1.0.0", - "is-wsl": "^2.1.1" + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" } }, "cliui": { @@ -6877,15 +6706,6 @@ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", "dev": true }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, "commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -6902,16 +6722,16 @@ } }, "compression": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz", - "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", "dev": true, "requires": { "accepts": "~1.3.5", "bytes": "3.0.0", - "compressible": "~2.0.14", + "compressible": "~2.0.16", "debug": "2.6.9", - "on-headers": "~1.0.1", + "on-headers": "~1.0.2", "safe-buffer": "5.1.2", "vary": "~1.1.2" }, @@ -7013,15 +6833,6 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, "datatables.net": { "version": "1.13.6", "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.13.6.tgz", @@ -7088,12 +6899,6 @@ "object-keys": "^1.1.1" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -7115,15 +6920,11 @@ "path-type": "^4.0.0" } }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true }, "electron-to-chromium": { "version": "1.4.504", @@ -7147,15 +6948,6 @@ "iconv-lite": "^0.6.2" } }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, "env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -7259,83 +7051,22 @@ "dev": true }, "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true - }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7355,12 +7086,6 @@ "micromatch": "^4.0.4" } }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, "fast-url-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/fast-url-parser/-/fast-url-parser-1.1.3.tgz", @@ -7439,23 +7164,6 @@ "is-callable": "^1.1.3" } }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, "fraction.js": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.1.tgz", @@ -7519,23 +7227,6 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, - "gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dev": true, - "requires": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - } - }, "gaze": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", @@ -7570,13 +7261,10 @@ "dev": true }, "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true }, "get-symbol-description": { "version": "1.0.0", @@ -7588,15 +7276,6 @@ "get-intrinsic": "^1.1.1" } }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -7731,22 +7410,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, "hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", @@ -7842,17 +7505,6 @@ "debug": "4" } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, "https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -7863,6 +7515,12 @@ "debug": "4" } }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, "humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -8110,6 +7768,12 @@ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true }, + "is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "dev": true + }, "is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -8130,9 +7794,9 @@ } }, "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true }, "is-string": { @@ -8162,12 +7826,6 @@ "which-typed-array": "^1.1.11" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, "is-weakref": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", @@ -8198,12 +7856,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, "jquery": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", @@ -8221,12 +7873,6 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -8245,22 +7891,10 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, "jsonfile": { @@ -8273,18 +7907,6 @@ "universalify": "^2.0.0" } }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, "keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -8430,6 +8052,12 @@ "yargs-parser": "^20.2.3" } }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -8461,6 +8089,12 @@ "mime-db": "1.52.0" } }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -8661,9 +8295,9 @@ "dev": true }, "node-sass": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-7.0.3.tgz", - "integrity": "sha512-8MIlsY/4dXUkJDYht9pIWBhMil3uHmE8b/AdJPjmFn1nBx9X9BASzfzmsCy0uCCb8eqI3SYYzVPDswWqSx7gjw==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-9.0.0.tgz", + "integrity": "sha512-yltEuuLrfH6M7Pq2gAj5B6Zm7m+gdZoG66wTqG6mIZV/zijq3M2OO2HswtT6oBspPyFhHDcaxWpsBm0fRNDHPg==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -8673,20 +8307,197 @@ "get-stdin": "^4.0.1", "glob": "^7.0.3", "lodash": "^4.17.15", + "make-fetch-happen": "^10.0.4", "meow": "^9.0.0", - "nan": "^2.13.2", + "nan": "^2.17.0", "node-gyp": "^8.4.1", - "npmlog": "^5.0.0", - "request": "^2.88.0", "sass-graph": "^4.0.1", "stdout-stream": "^1.4.0", - "true-case-path": "^1.0.2" + "true-case-path": "^2.2.1" + }, + "dependencies": { + "@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "requires": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + } + }, + "@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "dev": true, + "requires": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "requires": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "dependencies": { + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + } + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "requires": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + } + }, + "socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "requires": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + } + }, + "ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "requires": { + "minipass": "^3.1.1" + } + }, + "unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "requires": { + "unique-slug": "^3.0.0" + } + }, + "unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + } } }, "nodemon": { - "version": "2.0.22", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", - "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", + "integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==", "dev": true, "requires": { "chokidar": "^3.5.2", @@ -8694,8 +8505,8 @@ "ignore-by-default": "^1.0.1", "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", "supports-color": "^5.5.0", "touch": "^3.1.0", "undefsafe": "^2.0.5" @@ -8716,12 +8527,6 @@ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -8885,46 +8690,14 @@ } }, "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "dev": true, - "requires": { - "path-key": "^2.0.0" - }, - "dependencies": { - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true - } - } - }, - "npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "requires": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" + "path-key": "^3.0.0" } }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true - }, "object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -8964,11 +8737,14 @@ "wrappy": "1" } }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } }, "p-limit": { "version": "2.3.0", @@ -9064,12 +8840,6 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -9095,9 +8865,9 @@ "dev": true }, "postcss": { - "version": "8.4.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", - "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "dev": true, "requires": { "nanoid": "^3.3.6", @@ -9230,28 +9000,12 @@ "retry": "^0.12.0" } }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, "pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -9270,12 +9024,6 @@ "postcss-selector-parser": "^6.0.6" } }, - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true - }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9502,34 +9250,6 @@ "rc": "^1.0.1" } }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9622,7 +9342,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "dev": true, + "optional": true }, "sass-graph": { "version": "4.0.1", @@ -9656,85 +9377,43 @@ } }, "serve": { - "version": "13.0.4", - "resolved": "https://registry.npmjs.org/serve/-/serve-13.0.4.tgz", - "integrity": "sha512-Lj8rhXmphJCRQVv5qwu0NQZ2h+0MrRyRJxDZu5y3qLH2i/XY6a0FPj/VmjMUdkJb672MBfE8hJ274PU6JzBd0Q==", + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.1.tgz", + "integrity": "sha512-48er5fzHh7GCShLnNyPBRPEjs2I6QBozeGr02gaacROiyS/8ARADlj595j39iZXAqBbJHH/ivJJyPRWY9sQWZA==", "dev": true, "requires": { - "@zeit/schemas": "2.6.0", - "ajv": "6.12.6", - "arg": "2.0.0", - "boxen": "5.1.2", - "chalk": "2.4.1", - "clipboardy": "2.3.0", - "compression": "1.7.3", - "serve-handler": "6.1.3", - "update-check": "1.5.2" + "@zeit/schemas": "2.29.0", + "ajv": "8.11.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.7.4", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.5", + "update-check": "1.5.4" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } } } }, "serve-handler": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.3.tgz", - "integrity": "sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.5.tgz", + "integrity": "sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==", "dev": true, "requires": { "bytes": "3.0.0", "content-disposition": "0.5.2", "fast-url-parser": "1.1.3", "mime-types": "2.1.18", - "minimatch": "3.0.4", + "minimatch": "3.1.2", "path-is-inside": "1.0.2", "path-to-regexp": "2.2.1", "range-parser": "1.2.0" @@ -9754,15 +9433,6 @@ "requires": { "mime-db": "~1.33.0" } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } } } }, @@ -9811,20 +9481,12 @@ "dev": true }, "simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, "requires": { - "semver": "~7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } + "semver": "^7.5.3" } }, "slash": { @@ -9915,23 +9577,6 @@ "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", "dev": true }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, "ssri": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", @@ -10067,10 +9712,10 @@ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true }, "strip-indent": { @@ -10336,12 +9981,6 @@ "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true } } }, @@ -10402,24 +10041,6 @@ } } }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "dependencies": { - "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true - } - } - }, "trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -10427,27 +10048,9 @@ "dev": true }, "true-case-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", - "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", - "dev": true, - "requires": { - "glob": "^7.1.2" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz", + "integrity": "sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==", "dev": true }, "type-fest": { @@ -10556,9 +10159,9 @@ } }, "update-check": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.2.tgz", - "integrity": "sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", "dev": true, "requires": { "registry-auth-token": "3.3.2", @@ -10588,12 +10191,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - }, "v8-compile-cache": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", @@ -10616,17 +10213,6 @@ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -10672,12 +10258,46 @@ } }, "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", "dev": true, "requires": { - "string-width": "^4.0.0" + "string-width": "^5.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } } }, "wrap-ansi": { diff --git a/dds_web/static/package.json b/dds_web/static/package.json index d27e864bd..8207cfcb1 100644 --- a/dds_web/static/package.json +++ b/dds_web/static/package.json @@ -34,13 +34,13 @@ }, "devDependencies": { "autoprefixer": "^10.4.15", - "node-sass": "^7.0.3", - "nodemon": "^2.0.22", + "node-sass": "^9.0.0", + "nodemon": "^3.0.1", "npm-run-all": "^4.1.5", - "postcss": "^8.4.28", + "postcss": "^8.4.31", "postcss-cli": "^9.1.0", "purgecss": "^4.1.3", - "serve": "^13.0.4", + "serve": "^14.2.1", "stylelint": "^14.16.1", "stylelint-config-twbs-bootstrap": "^3.2.1" } diff --git a/dds_web/utils.py b/dds_web/utils.py index eb34ef5a4..f815df4bf 100644 --- a/dds_web/utils.py +++ b/dds_web/utils.py @@ -13,6 +13,7 @@ import time import smtplib from dateutil.relativedelta import relativedelta +import gc # Installed import botocore @@ -749,3 +750,171 @@ def use_sto4(unit_object, project_object) -> bool: flask.current_app.logger.info(f"{project_id_logging}sto2") return False + + +def add_uploaded_files_to_db(proj_in_db, log: typing.Dict): + """Adds uploaded files to the database. + + Args: + proj_in_db (dds_web.models.Project): The project to add the files to. + log (typing.Dict): A dictionary containing information about the uploaded files. + + Returns: + A tuple containing a list of files that were successfully added to the database, + and a dictionary containing any errors that occurred while + adding the files. + """ + # Import necessary modules and initialize variables + from dds_web import db + from dds_web.api.api_s3_connector import ApiS3Connector + + errors = {} + files_added = [] + + flask.current_app.logger.info(type(log)) + # Loop through each file in the log + for file, vals in log.items(): + status = vals.get("status") + overwrite = vals.get("overwrite", False) + + # Check if the file was successfully uploaded but database not updated + if not status or not status.get("failed_op") == "add_file_db": + errors[file] = {"error": "Incorrect 'failed_op'."} + continue + + # Connect to S3 and check if the file exists + with ApiS3Connector(project=proj_in_db) as s3conn: + try: + _ = s3conn.resource.meta.client.head_object( + Bucket=s3conn.project.bucket, Key=vals["path_remote"] + ) + except botocore.client.ClientError as err: + if err.response["Error"]["Code"] == "404": + errors[file] = {"error": "File not found in S3", "traceback": err.__traceback__} + else: + try: + # Check if the file already exists in the database + file_object = models.File.query.filter( + sqlalchemy.and_( + models.File.name == sqlalchemy.func.binary(file), + models.File.project_id == proj_in_db.id, + ) + ).first() + + # If the file already exists, create a new version of it if "--overwrite" was specified + if file_object: + if overwrite: + try: + new_file_version(existing_file=file_object, new_info=vals) + files_added.append(file_object) + except KeyError as err: + errors[file] = {"error": f"Missing key: {err}"} + else: + errors[file] = {"error": "File already in database."} + + # If the file does not exist, create a new file and version + else: + new_file = models.File( + name=file, + name_in_bucket=vals["path_remote"], + subpath=vals["subpath"], + project_id=proj_in_db.id, + size_original=vals["size_raw"], + size_stored=vals["size_processed"], + compressed=not vals["compressed"], + public_key=vals["public_key"], + salt=vals["salt"], + checksum=vals["checksum"], + ) + new_version = models.Version( + size_stored=new_file.size_stored, + time_uploaded=datetime.datetime.utcnow(), + ) + proj_in_db.file_versions.append(new_version) + proj_in_db.files.append(new_file) + new_file.versions.append(new_version) + + db.session.add(new_file) + db.session.commit() + files_added.append(new_file) + except ( + sqlalchemy.exc.IntegrityError, + sqlalchemy.exc.OperationalError, + sqlalchemy.exc.SQLAlchemyError, + ) as err: + errors[file] = {"error": str(err)} + db.session.rollback() + if errors: + flask.current_app.logger.error(f"Error in new_file_version: {errors}") + + return files_added, errors + + +def new_file_version(existing_file, new_info): + """ + Create new version of a file. + + Args: + existing_file (dds_web.models.File): The existing file to create a new version of. + new_info (dict): A dictionary containing information about the new version of the file. + + Returns: + None + """ + from dds_web import db + import dds_web.utils + + # Get project + project = existing_file.project + + # Get versions + current_file_version = models.Version.query.filter( + sqlalchemy.and_( + models.Version.active_file == sqlalchemy.func.binary(existing_file.id), + models.Version.time_deleted.is_(None), + ) + ).all() + + # If there is more than one version of the file which does not yet have a deletion timestamp, log a warning + if len(current_file_version) > 1: + flask.current_app.logger.warning( + "There is more than one version of the file " + "which does not yet have a deletion timestamp." + ) + + # Same timestamp for deleted and created new version + new_timestamp = dds_web.utils.current_time() + + # Set the deletion timestamp for the latests version of the file + for version in current_file_version: + if version.time_deleted is None: + version.time_deleted = new_timestamp + + # Update file info + existing_file.subpath = new_info["subpath"] + existing_file.size_original = new_info["size_raw"] + existing_file.size_stored = new_info["size_processed"] + existing_file.compressed = not new_info["compressed"] + existing_file.salt = new_info["salt"] + existing_file.public_key = new_info["public_key"] + existing_file.time_uploaded = new_timestamp + existing_file.checksum = new_info["checksum"] + + # Create a new version of the file + new_version = models.Version( + size_stored=new_info["size_processed"], + time_uploaded=new_timestamp, + active_file=existing_file.id, + project_id=project, + ) + + # Update foreign keys and relationships + project.file_versions.append(new_version) + existing_file.versions.append(new_version) + + # Add the new version to the database and commit the changes + db.session.add(new_version) + db.session.commit() + + # Clean up information + del new_info diff --git a/dds_web/version.py b/dds_web/version.py index 667b52f95..1d61112a0 100644 --- a/dds_web/version.py +++ b/dds_web/version.py @@ -1 +1,3 @@ -__version__ = "2.5.2" +# Do not do major version upgrade during 2024. +# If mid or minor version reaches 9, continue with 10, 11 etc etc. +__version__ = "2.6.0" diff --git a/doc/architecture/decisions/0021-plan-for-2024-objectives-and-key-results.md b/doc/architecture/decisions/0021-plan-for-2024-objectives-and-key-results.md new file mode 100644 index 000000000..658aff18d --- /dev/null +++ b/doc/architecture/decisions/0021-plan-for-2024-objectives-and-key-results.md @@ -0,0 +1,203 @@ +# 21. Plan for 2024: Objectives and Key Results + +Date: 2023-10-16 + +## Status + +Accepted + +## Context + +The Product Owner (PO) / Project Leader (PL) of Team Hermes - responsible for the development and maintenance of the Data Delivery System (DDS) - is going on parental leave from November 17th 2023. Due do this, and that the substitute(s) / replacement(s) has not had enough time to learn the system in order to fully take over the PO / PL responsibilities, there needs to be a plan for what the team should work on during the coming year. Starting a more formal plan for the coming year (now and in the future) is also a general improvement to the team and stakeholders, since it will allow for more tranparency outward and help guide the team's focus. + +In order to plan for the coming year (2024, and December 2023), the team is using the tool / method _OKRs: Objects and Key Results_. + +> OKR [is] a collaborative goal-setting methodology used by teams and individuals to set challenging, ambitious goals with measurable results. +> +> -- [What Matters](https://www.whatmatters.com/faqs/okr-meaning-definition-example) + +> An **Objective** is what you want to accomplish. +> [Should be]: +> +> - Significant +> - Concrete +> - Action-oriented +> - Inspirational +> +> Can be set annually or over [a] [...] longer term. +> +> **Key Results** are how you will accomplish the _Objectives_. +> [Should be]: +> +> - Specific +> - Timebound +> - Aggressive yet realistic +> - Measurable +> - Verifiable +> +> Can be set quarterly and evolve as work progresses. +> +> -- [What Matters](https://www.whatmatters.com/faqs/okr-meaning-definition-example) +> +> Initiatives [are] tasks [that are] required to drive [the] progress of key results. +> +> -- [Intuit Mint](https://mint.intuit.com/blog/planning-2/okr-framework/) + +### Discussions regarding possible objectives + +#### Possible objective: "Improve user experience via UI" + +##### Alternative 1: Implement the `dds-cli` functionality in the web interface. + +- The web interface will not be a good way for uploads and downloads of huge amounts of data. The units are also saying this. +- Implementing the functionality in the web interface would require us to have a front-end developer that also has experience in cryptography. We (the PO mainly) have been asking- and waiting for this person for a long time, so we cannot expect that that's going to happen any time soon. The last time it was mentioned was both before and after summer 2023; since then we haven't heard or said anything regarding this. Therefore, creating the web interface that is envisioned - a complete reset of the web using some JS framework - is not possible. + - Even if a front-end developer was to get hired at some point during 2024, doing a complete reset of the frontend (which houses functionality required to register, reset password, setup 2FA, etc) and building the web from scratch, while the person who has been involved in developing the majority of the code, is away, is **not** a good idea. +- If we were to work on implementing the functionality into the web interface as it is now, without having a front-end developer, we would have to continue using pure flask in the web, and that would mean that we would need to make a duplicate of practically all of the API code that exists, because: + - Calling the API endpoints from the Flask routes does not work since those endpoints require authentication tokens - the ones that the CLI uses. + - Moving the API code, creating helper functions, and changing everything in order to use the new helper functions in both the API and web should not be done when the PO is away; It's too much work and it risks the functionality in the web. We should be adding functionality to the system during 2024, **not** refactoring and risking working functionality to break. +- Duplicating the code for listing projects, users, files, adding users to projects (etc, etc) in the web means that we later on will have to redo it all and the team will have spent time on something that will not be used anyway since the upload and download by pure flask and html is not a good idea (and not possible with huge files due to load on cluster). Also, upload and download of potentially huge amounts of data via browser is as mentioned above not a good solution. + +**Because of these items, implementing the functionality in the web interface is not an option; we won't plan for working on the web interface during the coming year.** + +##### Alternative 2: Creating a Graphical User Interface (GUI) + +- The unit's _end-users_ (the users downloading the data) would benefit from this. +- The NGI units that do not need the download functionality for their end users in the GUI also do not need it in the web, they just have bioinformaticians that help the users and the bioinformaticians are familiar with terminal tools +- Other smaller units have less experienced end users and are more likely to want to download locally and to want to click buttons instead of using the terminal +- This GUI would be very simple to begin with, it could (for example) be created by `tkinter` or another simple package (google "simple GUI python" or similar, there are several). The main thing here is that we **should not need to write new code for the actual DDS functionality**; The idea is that the GUI would run the CLI commands when buttons are clicked etc. Buttons would run the commands, and the same things that are displayed in the CLI would be displayed in the GUI. + - We could start with the authentication, listing of the projects, their deadlines etc, users, project users, inviting users etc. + - The GUI would automatically use the authentication token. + - We could technically implement download, but we could start with displaying the commands and allow them to copy paste the commands to the terminal + - The very simple GUI can be compiled with the pyinstaller package via GitHub actions, in a similar way that the CLI is currently. The user would then download the GUI, open it and then do what they want in a simple way. +- This would therefore mean that we wouldn't duplicate code, we would just use the code that already exist. +- The GUI would not be able to use on HPC clusters etc _but neither would the browser_. +- Both options would currently download to the local computer. +- Both the GUI and the web interface would later maybe be able to pipe the data to a long term storage location instead of downloading locally, but the plans for how that would work exactly are not made yet, and GUI or web interface shouldn't make a difference since the functionality will anyway likely be managed and executed via the API. +- Making the GUI is still not a simple task, we would still need to make reasonable plans and not go overboard. We would start small, let the users try it out or have demos, and if this is not something that would be used, you would scrap the GUI plan and move on to a new idea or a new objective. + +**So the choices we have are:** + +1. Start making a GUI **OR...** +2. Come up with a new objective. + +## Decision + +The sections below have the following structure: + +> **Objective X: [short title] -- [long description]** +> +> - **Key Result Y for Objective X [COMMITTED / ASPIRATIONAL / LEARNING]:** [Description / goal] +> - Initiative 1 +> - _Notes or possible task example_ +> - Initiative 2 +> - _etc..._ + +> **The objectives, key results and initiatives are also available for the team on Confluence: https://scilifelab.atlassian.net/wiki/spaces/deliveryportal/pages/2615705604/Plan+for+2024** + +> The initiatives have been added to the [Jira Board](https://scilifelab.atlassian.net/jira/software/projects/DDS/boards/13/backlog?epics=visible) as _epics_. Tasks that fall under the initiatives should therefore be marked as a part of those epics. This can be changed if the team wants to try another structure. + +### Objective 1: Support -- "Optimize support" + +- **Workshop for users [COMMITTED]:** Plan for a workshop for DDS users, intended to present the system, help users get started and answer common questions. The workshop should be run at least once a year. + - Schedule the workshop for autumn 2024, before summer 2024. + - _Talk to training hub to plan for event._ + - Create workshop material before the workshop (autumn 2024) + - _Decide on target audience_ + - _List parts to include in workshop, depending on audience_ + - _Create workshop content_ + - Improve workshop material based on audience feedback by the end of 2024 + - _Collect feedback within 2 weeks after the workshop_ +- **Support documentation [COMMITTED]:** Create or update support documentation for the top 5 most common support inquiries to facilitate self-service + - Identify the top 5 most common support inquiries by [??] + - _List top 5 most common support inquiries send to Data Centre_ + - _Ask Units (production) what the 5 most common support inquiries they get from their users_ + - _Ask Units (production) about what 5 things they think should be clarified_ + - _Ask Units (testing) about what 5 things they think should be clarified_ + - Analyse the support inquiries and feedback by ?? + - _Group the support inquiries into "themes"_ + - _Choose the 5 most common inquiries_ + - _Investigate which of the inquiries need to have their documentation updated / created_ + - Create / update the documentation for the 5 most common support inquiries by ?? + - _Inquiry 1_ + - _Inquiry 2…_ + - Get feedback/review of documentation from ?? (outside team) +- **Ticket Volume [ASPIRATIONAL]:** Reduce the number of "unnecessary" support tickets submitted by 50% percent within the next 6 months months, by implementing a chatbot in the web interface. + "Unnecessary": questions that should go to units or that units should already know. + - Identify number of unnecessary support tickets in the last x months + - _Find 3 possible tools for creating the chatbot_ + - _List pros and cons for the possible tools_ + - _Make decision_ + - Decide on design and architecture of Chatbot by ?? + - Create a Chatbot prototype by ?? that can answer questions regarding where the documentation and technical overview is located and who they should contact in the case of a question that the bot cannot answer + - Implement "was this helpful?" in the chatbot by ?? + - Implement responses to top 10 most common irrelevant questions by ?? + - Find a way to evaluate the chatbot + +### Objective 2: Quality -- "Improve software quality" + +- **Resolve security alerts [ASPIRATIONAL]:** Resolve all solvable (with known fixes) critical security alerts within 7 days, high-severity alerts within 14 days and medium-severity alerts within a month. + - Find a way to implement a routine on the scrum cycle + - Evaluate if different notification/alerts systems outside GitHub could also be helpful + - Decide a new release procedure for critical alerts when there is no schedule release soon + - _Study how feasible is to redeploy more often_ + - _Study if there is a way, and if so, how to redeploy backend (rolling updates) in Kubernetes_ +- **Test coverage [ASPIRATIONAL]:** Increase test coverage for dds_web to 100% and dds_cli to 70%. + - Increase the coverage for the 10 files within dds_web with the least amount of coverage to above 90% by ?? + - _List 10 files with the least amount of coverage_ + - _Increase coverage for file 1_ + - _Increase coverage for file 2... etc_ + - Increase the coverage for the 10 files within dds_cli with the least amount of coverage to above 40% by ?? + - _List 10 files with the least amount of coverage_ + - _Increase coverage for file 1_ + - _Increase coverage for file 2... etc_ + - Add tests for all changes in the dds_cli code, aiming at at least 50% coverage. The tests should be added prior to merging the code. + - For all changes in the dds_web code, add tests so that the coverage is 100%. The tests should be added prior to merging the code. +- **API documentation [COMMITTED]:** Create / Generate documentation for the dds_web/api, which covers all endpoints. + - Decide on a tool for generating the API documentation + - _Find 3 possible solutions / tools_ + - _List pros and cons for the possible tools/solutions_ + - _Make decision_ + - Research if it is possible to automate the documentation generation when a new endpoint is added + - _Maybe GitHub Actions_ + - _Maybe the tool used allows for that_ + - Make API documentation accessible first for the team members, then discuss if we should publish for everyone + +### Objective 3: GUI -- "Improve user experience via a GUI prototype" + +- **Reduced learning curve [ASPIRATIONAL]:** Users can perform common tasks in the new GUI in less time and effort compared to the CLI. + - Make a list of the 5 most used features that could be simplified via the GUI + - Design the layout of those features + - Implement the GUI so it is easily accessible for users + - _Decide a framework/technology that can be easily ported to different OS architectures_ + - _Find 3 possible tools/solutions for creating the GUI_ + - _List pros and cons for the possible tools/solutions_ + - _Make decision_ + - Ask for feedback from someone that is using the CLI atm +- **Cross-Platform Consistency [COMMITTED]:** Ensure the GUI functions consistently and seamlessly on macOS (latest), Linux (ubuntu) and Windows (latest). + - Research best way to publish the binaries to the users + - Create a GitHub action for generating the GUI binary for macOS lastest + - Create a GitHub action for generating the GUI binary for ubuntu latest + - Create a GitHub action for generating the GUI binary for windows latest + - Continuously test all implemented features for cross-platform consistency. + - _Discuss if we should focus more on some OS over others because of:_ + 1. macOS (team develops on mac) + 2. Windows (most users that need the GUI) + 3. Linux (would usually be more comfortable with command line) +- **Feedback [LEARNING]:** Get feedback on GUI prototype from 5 users on each OS. + - Identify users: Make list of 20 people to ask for feedback from + - Create testing protocol, covering the testing of all features implemented in the GUI prototype + - Gather, analyze and prioritize the feedback for implementation + - _Gather feedback from both unit users and researchers_ + - _Prioritize feedback to add new task to the backlog according to their importance_ + +## Consequences + +- The GUI prototype should not be prioritized over the other two - this is why it's listed as objective #3. + - If, during the key results and initiatives within objective #3 (GUI), the team finds that this is not an appropriate objective, or that there's a new, better, objective, the team can switch direction and work on the new one. In this case, the team must inform the users of the new plan. + - For more consequences regarding the GUI, see the **Context** section above. +- The task examples listed under some of the initiatives are just that; _examples_. The team will decide on the appropriate tasks during the sprints, depending on which objective and key result they will be working on. +- The key results and initiatives _may change_ as time passes since, for example, they may depend on another initiative or information that has been gathered previous to starting the initiative. + +## Footnotes + +[^fn1]: https://www.whatmatters.com/faqs/okr-meaning-definition-example diff --git a/migrations/versions/3d610b382383_remove_cost_from_usage_table.py b/migrations/versions/3d610b382383_remove_cost_from_usage_table.py new file mode 100644 index 000000000..6cc5db230 --- /dev/null +++ b/migrations/versions/3d610b382383_remove_cost_from_usage_table.py @@ -0,0 +1,28 @@ +"""remove-cost-from-usage-table + +Revision ID: 3d610b382383 +Revises: f27c5988d640 +Create Date: 2023-11-03 08:36:35.425045 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = "3d610b382383" +down_revision = "f27c5988d640" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("usage", "cost") + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("usage", sa.Column("cost", mysql.FLOAT(), nullable=False)) + # ### end Alembic commands ### diff --git a/requirements.txt b/requirements.txt index 984882c5f..69d9d4fa5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -42,7 +42,7 @@ MarkupSafe==2.1.1 marshmallow==3.14.1 marshmallow-sqlalchemy==0.27.0 packaging==21.3 -Pillow==9.3.0 # required by qrcode +Pillow==10.1.0 # required by qrcode pycparser==2.21 PyMySQL==1.0.2 PyNaCl==1.5.0 @@ -62,7 +62,7 @@ SQLAlchemy==1.4.31 structlog==21.5.0 tzdata==2021.5 tzlocal==4.1 -urllib3==1.26.8 +urllib3==1.26.18 visitor==0.1.3 Werkzeug==2.2.3 wrapt==1.13.3 diff --git a/tests/__init__.py b/tests/__init__.py index 9f45f2447..94887177b 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -195,6 +195,7 @@ class DDSEndpoint: FILE_INFO = BASE_ENDPOINT + "/file/info" FILE_INFO_ALL = BASE_ENDPOINT + "/file/all/info" FILE_UPDATE = BASE_ENDPOINT + "/file/update" + FILE_ADD_FAILED = BASE_ENDPOINT + "/file/failed/add" # Project specific urls PROJECT_CREATE = BASE_ENDPOINT + "/proj/create" diff --git a/tests/api/test_user.py b/tests/api/test_user.py index 615590e5a..e256e07c2 100644 --- a/tests/api/test_user.py +++ b/tests/api/test_user.py @@ -1,6 +1,5 @@ from datetime import datetime from datetime import timedelta -from tracemalloc import start import typing from unittest import mock import dds_web diff --git a/tests/test_commands.py b/tests/test_commands.py index 4f82dd869..2d5ea05ea 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -3,7 +3,7 @@ # Standard import typing from unittest import mock -from unittest.mock import patch +from unittest.mock import patch, mock_open from unittest.mock import PropertyMock from unittest.mock import MagicMock import os @@ -14,6 +14,7 @@ import pathlib import csv from dateutil.relativedelta import relativedelta +import json # Installed import click @@ -21,6 +22,7 @@ import flask_mail import freezegun import rich.prompt +import sqlalchemy # Own from dds_web.commands import ( @@ -31,13 +33,14 @@ set_available_to_expired, set_expired_to_archived, delete_invites, - quarterly_usage, + monthly_usage, collect_stats, lost_files_s3_db, update_unit, + send_usage, ) from dds_web.database import models -from dds_web import db +from dds_web import db, mail from dds_web.utils import current_time # Tools @@ -458,13 +461,22 @@ def test_update_uploaded_file_with_log_nonexisting_project( # Run command assert db.session.query(models.Project).all() with patch("dds_web.database.models.Project.query.filter_by", mock_no_project): - result: click.testing.Result = runner.invoke(update_uploaded_file_with_log, command_options) + _: click.testing.Result = runner.invoke(update_uploaded_file_with_log, command_options) _, err = capfd.readouterr() assert "The project 'projectdoesntexist' doesn't exist." in err + # Verify that things are not printed out + assert "Files added:" not in err + assert "Errors while adding files:" not in err -def test_update_uploaded_file_with_log_nonexisting_file(client, runner, fs: FakeFilesystem) -> None: + +def test_update_uploaded_file_with_log_nonexisting_file( + client, runner, capfd: LogCaptureFixture +) -> None: """Attempt to read file which does not exist.""" + # Get project + project = models.Project.query.first() + # Verify that fake file does not exist non_existent_log_file: str = "this_is_not_a_file.json" assert not os.path.exists(non_existent_log_file) @@ -472,16 +484,73 @@ def test_update_uploaded_file_with_log_nonexisting_file(client, runner, fs: Fake # Create command options command_options: typing.List = [ "--project", - "projectdoesntexist", + project.public_id, "--path-to-log-file", non_existent_log_file, ] # Run command - result: click.testing.Result = runner.invoke(update_uploaded_file_with_log, command_options) - # TODO: Add check for logging or change command to return or raise error. capfd does not work together with fs - # _, err = capfd.readouterr() - # assert "The project 'projectdoesntexist' doesn't exist." in result.stderr + _: click.testing.Result = runner.invoke(update_uploaded_file_with_log, command_options) + + # Check logging + _, err = capfd.readouterr() + assert f"The log file '{non_existent_log_file}' doesn't exist." in err + + # Verify that things are not printed out + assert "Files added:" not in err + assert "Errors while adding files:" not in err + + +def test_update_uploaded_file(client, runner, capfd: LogCaptureFixture, boto3_session) -> None: + """Attempt to read file which does not exist.""" + # Get project + project = models.Project.query.first() + + # # Verify that fake file exists + log_file: str = "this_is_a_file.json" + + # Get file from db + file_object: models.File = models.File.query.first() + file_dict = { + file_object.name: { + "status": {"failed_op": "add_file_db"}, + "path_remote": file_object.name_in_bucket, + "subpath": file_object.subpath, + "size_raw": file_object.size_original, + "size_processed": file_object.size_stored, + "compressed": not file_object.compressed, + "public_key": file_object.public_key, + "salt": file_object.salt, + "checksum": file_object.checksum, + } + } + + # Create command options + command_options: typing.List = [ + "--project", + project.public_id, + "--path-to-log-file", + log_file, + ] + with patch("os.path.exists") as mock_exists: + mock_exists.return_value = True + with patch("dds_web.commands.open"): + with patch("json.load") as mock_json_load: + mock_json_load.return_value = file_dict + _: click.testing.Result = runner.invoke( + update_uploaded_file_with_log, command_options + ) + + # Check logging + _, err = capfd.readouterr() + assert f"The project '{project.public_id}' doesn't exist." not in err + assert f"Updating file in project '{project.public_id}'..." in err + assert f"The log file '{log_file}' doesn't exist." not in err + assert f"Reading file info from path '{log_file}'..." in err + assert "File contents were loaded..." in err + assert "Files added: []" in err + assert "Errors while adding files:" in err + assert "File already in database" in err # lost_files_s3_db @@ -1346,12 +1415,176 @@ def test_delete_invite_timestamp_issue(client, cli_runner): assert len(db.session.query(models.Invite).all()) == 0 -# quarterly usage +# monthly usage -def test_quarterly_usage(client, cli_runner): - """Test the quarterly_usage cron job.""" - cli_runner.invoke(quarterly_usage) +def test_monthly_usage_mark_as_done(client, cli_runner, capfd: LogCaptureFixture): + """Projects should be marked as done.""" + # Imports + from tests.api.test_project import mock_sqlalchemyerror + + # Helper function - can be moved out if we need to use in other places later + def create_file_versions(project: models.Project): + """Create file versions for project.""" + # Create new file in project + new_file = models.File( + name=f"filename_{project.public_id}", + name_in_bucket=f"name_in_bucket_{project.public_id}", + subpath=f"filename/subpath", + size_original=15000, + size_stored=10000, + compressed=True, + salt="A" * 32, + public_key="B" * 64, + checksum="C" * 64, + ) + project.files.append(new_file) + + # Create new versions + for x in range(3): + new_version = models.Version( + size_stored=10000 * x, + time_uploaded=current_time() - timedelta(days=1), + time_deleted=current_time(), + ) + project.file_versions.append(new_version) + new_file.versions.append(new_version) + db.session.add(new_file) + + db.session.commit() + + # Check if there's a non active project + non_active_projects = models.Project.query.filter_by(is_active=False).all() + project: models.Project = None + if not non_active_projects: + # Make at least one project not active + project = models.Project.query.first() + project.is_active = False + db.session.commit() + else: + project = non_active_projects[0] + + # There needs to be file versions in non active project + if not project.file_versions: + create_file_versions(project=project) + assert project.file_versions + + # Get active project - to verify that nothing happens with it + project_active: models.Project = models.Project.query.filter_by(is_active=True).first() + + # There needs to be file versions in active project + if not project_active.file_versions: + create_file_versions(project=project_active) + assert project_active.file_versions + + # Set file versions as invoiced + for version in project.file_versions: + time_now = current_time() + version.time_deleted = time_now + version.time_invoiced = time_now + db.session.commit() + + # 1. Marking projects as done + # Run command - commit should result in sqlalchemy query + with mail.record_messages() as outbox1: + with patch("dds_web.db.session.commit", mock_sqlalchemyerror): + cli_runner.invoke(monthly_usage) + + # Check that non-active project is not marked as done + assert not project.done + assert not project_active.done + + # Verify correct logging + _, logs = capfd.readouterr() + assert ( + "Usage collection during step 1: marking projects as done. Sending email..." + in logs + ) + assert "Calculating usage..." not in logs + + # Error email should be sent + assert len(outbox1) == 1 + assert "[INVOICING CRONJOB] Error in monthly-usage cronjob" in outbox1[-1].subject + assert "What to do:" in outbox1[-1].body + + # No usage rows should have been saved + num_usage_rows = models.Usage.query.count() + assert num_usage_rows == 0 + + # 2. Calculating project usage + # Run command again - part 1 should be successful + with mail.record_messages() as outbox2: + with patch("dds_web.db.session.add_all", mock_sqlalchemyerror): + cli_runner.invoke(monthly_usage) + + # The non-active project should have been marked as done + assert project.done + assert not project_active.done + + # Verify correct logging + _, logs = capfd.readouterr() + assert ( + "Usage collection during step 1: marking projects as done. Sending email..." + not in logs + ) + assert "Calculating usage..." in logs + assert f"Project {project.public_id} byte hours:" not in logs + assert f"Project {project_active.public_id} byte hours:" in logs + assert ( + "Usage collection during step 2: calculating and saving usage. Sending email..." + in logs + ) + + # Error email should have been sent + assert len(outbox2) == 1 + assert "[INVOICING CRONJOB] Error in monthly-usage cronjob" in outbox2[-1].subject + assert "What to do:" in outbox2[-1].body + + # Project versions should not be altered + assert project_active.file_versions + for version in project_active.file_versions: + assert version.time_deleted != version.time_invoiced + + # No usage rows should've been saved + usage_row_1 = models.Usage.query.filter_by(project_id=project.id).one_or_none() + usage_row_2 = models.Usage.query.filter_by(project_id=project_active.id).one_or_none() + assert not usage_row_1 + assert not usage_row_2 + + # 3. Send success email + # Run command a third time - part 1 and 2 should be successful + with mail.record_messages() as outbox3: + cli_runner.invoke(monthly_usage) + + # Verify correct logging + _, logs = capfd.readouterr() + assert ( + "Usage collection during step 1: marking projects as done. Sending email..." + not in logs + ) + assert ( + "Usage collection during step 2: calculating and saving usage. Sending email..." + not in logs + ) + assert "Usage collection successful; Sending email." in logs + + # Email should be sent + assert len(outbox3) == 1 + assert "[INVOICING CRONJOB] Usage records available for collection" in outbox3[-1].subject + assert ( + "The calculation of the monthly usage succeeded; The byte hours for all active projects have been saved to the database." + in outbox3[-1].body + ) + + # Project versions should have been altered + for version in project_active.file_versions: + assert version.time_deleted == version.time_invoiced + + # Usage rows should have been saved for active project + usage_row_1 = models.Usage.query.filter_by(project_id=project.id).one_or_none() + usage_row_2 = models.Usage.query.filter_by(project_id=project_active.id).one_or_none() + assert not usage_row_1 + assert usage_row_2 # reporting units and users @@ -1472,3 +1705,198 @@ def verify_reporting_row(row, time_date): reporting_rows = Reporting.query.all() for row in reporting_rows: verify_reporting_row(row=row, time_date=first_time if row.id == 1 else second_time) + + +def test_send_usage(client, cli_runner, capfd: LogCaptureFixture): + """Test that the email with the usage report is send""" + # Imports + from dds_web.database.models import Usage + + # Get projects + projects = models.Project.query.filter( + models.Project.public_id.in_( + ["public_project_id", "second_public_project_id", "unit2testing"] + ) + ).all() + project_1_unit_1 = next(p for p in projects if p.public_id == "public_project_id") + project_2_unit_1 = next(p for p in projects if p.public_id == "second_public_project_id") + project_1_unit_2 = next(p for p in projects if p.public_id == "unit2testing") + + # Loop to populate usage table with fake entries across two years + january_2021 = datetime(2021, 1, 1) # Start at Jan 2021 + usage_list = [] + for i in range(25): + time = january_2021 + relativedelta(months=i) + usage_1 = Usage( + project_id=project_1_unit_1.id, + usage=100, + time_collected=time, + ) + usage_2 = Usage( + project_id=project_2_unit_1.id, + usage=100, + time_collected=time, + ) + usage_3 = Usage( + project_id=project_1_unit_2.id, + usage=100, + time_collected=time, + ) + usage_list.extend([usage_1, usage_2, usage_3]) + + db.session.add_all(usage_list) + db.session.commit() + # Fake data included from Jan 2021 to Jan 2023 + + def run_command_and_check_output(months_to_test, start_time): + """ + This function tests the output of the `send_usage` command by running the command with given arguments and checking the output. + It mocks the current time and checks that the email sent contains the correct subject and body. + It also checks that the csv files attached to the email have the correct names and content. + + Return the csv files attached to the email. + """ + + with mail.record_messages() as outbox: + with patch("dds_web.utils.current_time") as current_time_func: # Mock current time + current_time_func.return_value = start_time + cli_runner.invoke(send_usage, ["--months", months_to_test]) + + # Verify output and sent email + assert len(outbox) == 1 + assert ( + "[SEND-USAGE CRONJOB] Usage records attached in the present mail" + in outbox[-1].subject + ) + assert f"Here is the usage for the last {months_to_test} months." in outbox[-1].body + + end_time = start_time - relativedelta(months=months_to_test) + start_month = start_time.month + end_month = end_time.month + unit_1_id = project_1_unit_1.responsible_unit.public_id + unit_2_id = project_1_unit_2.responsible_unit.public_id + csv_1_name = f"{unit_1_id}_Usage_Months-{end_month}-to-{start_month}.csv" + csv_2_name = f"{unit_2_id}_Usage_Months-{end_month}-to-{start_month}.csv" + + # check that the files no longer exist in the filesystem + assert not os.path.exists(csv_1_name) + assert not os.path.exists(csv_2_name) + + _, logs = capfd.readouterr() + assert f"Month now: {start_month}" in logs + assert f"Month {months_to_test} months ago: {end_month}" in logs + assert f"CSV file name: {csv_1_name}" in logs + assert f"CSV file name: {csv_2_name}" in logs + assert "Sending email with the CSV." in logs + + # Verify that the csv files are attached - two files, one for each unit + assert len(outbox[-1].attachments) == 2 + for attachment, file_name in zip(outbox[-1].attachments, [csv_1_name, csv_2_name]): + assert attachment.filename == file_name + assert attachment.content_type == "text/csv" + + # Check csv content + # retrieve the files from the email + csv_1 = outbox[-1].attachments[0].data + csv_2 = outbox[-1].attachments[1].data + + # check that the header and summatory at the end is correct + assert "Project ID,Project Title,Project Created,Time Collected,Byte Hours" in csv_1 + assert "Project ID,Project Title,Project Created,Time Collected,Byte Hours" in csv_2 + usage = 100.0 * months_to_test * 2 # 2 projects + assert f"--,--,--,--,{str(usage)}" in csv_1 + usage = 100.0 * months_to_test + assert f"--,--,--,--,{str(usage)}" in csv_2 + + # check that the content is correct + import re + + csv_1 = re.split(",|\n", csv_1) # split by comma or newline + csv_2 = re.split(",|\n", csv_2) + + # Projects and data is correct + assert csv_1.count("public_project_id") == months_to_test + assert csv_1.count("second_public_project_id") == months_to_test + assert csv_1.count("unit2testing") == 0 # this project is not in the unit + assert csv_1.count("100.0") == months_to_test * 2 + + assert csv_2.count("public_project_id") == 0 # this project is not in the unit + assert csv_2.count("second_public_project_id") == 0 # this project is not in the unit + assert csv_2.count("unit2testing") == months_to_test + assert csv_2.count("100.0") == months_to_test + + # Check that the months included in the report are the correct ones + # move start time to the first day of the month + start_collected_time = start_time.replace( + day=1, hour=0, minute=0, second=0, microsecond=0 + ) + for i in range(months_to_test): + check_time_collected = start_collected_time - relativedelta( + months=i + ) # every month is included + assert f"{check_time_collected}" in csv_1 + assert f"{check_time_collected}" in csv_1 + return csv_1, csv_2 + + # Test that the command works for 4 months from Jan 2022 + start_time = datetime(2022, 1, 15) # Mid Jan 2022 + csv_1, csv_2 = run_command_and_check_output(months_to_test=4, start_time=start_time) + # Hardcode the expected csv content to double check + # October, November, December, January (4 months) + assert "2021-10-01 00:00:00" in csv_1 + assert "2021-11-01 00:00:00" in csv_1 + assert "2021-12-01 00:00:00" in csv_1 + assert "2022-01-01 00:00:00" in csv_1 + assert "2021-10-01 00:00:00" in csv_2 + assert "2021-11-01 00:00:00" in csv_2 + assert "2021-12-01 00:00:00" in csv_2 + assert "2022-01-01 00:00:00" in csv_2 + + # Test that the command works for 4 months from May 2022 + start_time = datetime(2022, 5, 15) # Mid May 2022 + csv_1, csv_2 = run_command_and_check_output(months_to_test=4, start_time=start_time) + # Hardcode the expected csv content to double check + # February, March, April, May (4 months) + assert "2022-02-01 00:00:00" in csv_1 + assert "2022-03-01 00:00:00" in csv_1 + assert "2022-04-01 00:00:00" in csv_1 + assert "2022-05-01 00:00:00" in csv_1 + assert "2022-02-01 00:00:00" in csv_2 + assert "2022-03-01 00:00:00" in csv_2 + assert "2022-04-01 00:00:00" in csv_2 + assert "2022-05-01 00:00:00" in csv_2 + + # Test that the command works for 4 months from Sept 2022 + start_time = datetime(2022, 9, 15) # Mid Sep 2022 + csv_1, csv_2 = run_command_and_check_output(months_to_test=4, start_time=start_time) + # Hardcode the expected csv content to double check + # June, July, August, September (4 months) + assert "2022-06-01 00:00:00" in csv_1 + assert "2022-07-01 00:00:00" in csv_1 + assert "2022-08-01 00:00:00" in csv_1 + assert "2022-09-01 00:00:00" in csv_1 + assert "2022-06-01 00:00:00" in csv_2 + assert "2022-07-01 00:00:00" in csv_2 + assert "2022-08-01 00:00:00" in csv_2 + assert "2022-09-01 00:00:00" in csv_2 + + +def test_send_usage_error_csv(client, cli_runner, capfd: LogCaptureFixture): + """Test that checks errors in the csv handling""" + + with mail.record_messages() as outbox: + with patch("csv.writer") as mock_writing_file: + mock_writing_file.side_effect = IOError() + cli_runner.invoke(send_usage, ["--months", 3]) + + _, logs = capfd.readouterr() + assert "Error writing to CSV file:" in logs # error in writing the csv file + assert "No CSV files generated." in logs # no csv files generated + + # Check that no files were generated in the fs + assert not os.path.exists("*.csv") + + # Verify error email :- At least one email was sent + assert len(outbox) == 1 + assert "[SEND-USAGE CRONJOB] Error in send-usage cronjob" in outbox[-1].subject + assert "There was an error in the cronjob 'send-usage'" in outbox[-1].body diff --git a/tests/test_files_new.py b/tests/test_files_new.py index 336637f05..591ec3492 100644 --- a/tests/test_files_new.py +++ b/tests/test_files_new.py @@ -17,6 +17,31 @@ "checksum": "c" * 64, } +FAILED_FILES = { + "file1.txt": { + "status": {"failed_op": "add_file_db"}, + "path_remote": "path/to/file1.txt", + "subpath": "subpath", + "size_raw": 100, + "size_processed": 200, + "compressed": False, + "public_key": "public_key", + "salt": "salt", + "checksum": "checksum", + }, + "file2.txt": { + "status": {"failed_op": "add_file_db"}, + "path_remote": "path/to/file2.txt", + "subpath": "subpath", + "size_raw": 100, + "size_processed": 200, + "compressed": False, + "public_key": "public_key", + "salt": "salt", + "checksum": "checksum", + }, +} + # TOOLS #################################################################################### TOOLS # @@ -909,3 +934,65 @@ def test_delete_contents_and_upload_again(client, boto3_session): .first() ) assert file_in_db + + +# Test AddFailedFiles endpoint + + +def test_update_failed_files_success(client, boto3_session): + """Update failed files with valid data.""" + + # get project and verify in progress + project_1 = project_row(project_id="file_testing_project") + assert project_1 + assert project_1.current_status == "In Progress" + + response = client.put( + tests.DDSEndpoint.FILE_ADD_FAILED, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": "file_testing_project"}, + json=FAILED_FILES, + ) + + assert response.status_code == http.HTTPStatus.OK + assert response.json["message"] == {} + for file in FAILED_FILES: + assert db.session.query(models.File).filter(models.File.name == file).first() + assert file in response.json["files_added"] + + +def test_update_failed_files_no_json(client, boto3_session): + """Update failed files without log json.""" + + # get project and verify in progress + project_1 = project_row(project_id="file_testing_project") + assert project_1 + assert project_1.current_status == "In Progress" + + response = client.put( + tests.DDSEndpoint.FILE_ADD_FAILED, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + query_string={"project": "file_testing_project"}, + ) + + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert response.json["message"] == "Required data missing from request!" + # check that none of the files in the list exist in the database. + for file in FAILED_FILES: + assert not db.session.query(models.File).filter(models.File.name == file).first() + + +def test_update_failed_files_no_project(client, boto3_session): + """Update failed files without project.""" + + response = client.put( + tests.DDSEndpoint.FILE_ADD_FAILED, + headers=tests.UserAuth(tests.USER_CREDENTIALS["unitadmin"]).token(client), + json=FAILED_FILES, + ) + + assert response.status_code == http.HTTPStatus.BAD_REQUEST + assert response.json["project"]["message"] == "Project ID required." + # check that none of the files in the list exist in the database. + for file in FAILED_FILES: + assert not db.session.query(models.File).filter(models.File.name == file).first() diff --git a/tests/test_utils.py b/tests/test_utils.py index bc6864db7..7d70681b5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,7 +1,7 @@ import marshmallow from dds_web import utils import pytest -from unittest.mock import patch +from unittest.mock import patch, MagicMock from unittest.mock import PropertyMock from dds_web import db @@ -25,6 +25,7 @@ import boto3 import botocore import sqlalchemy +from _pytest.logging import LogCaptureFixture # Variables @@ -1435,6 +1436,43 @@ def test_list_lost_files_in_project_overlap( ) +def test_list_lost_files_in_project_sql_error( + client: flask.testing.FlaskClient, boto3_session, capfd +): + """Verify proper behaviour when sql OperationalError occurs.""" + # Imports + from dds_web.utils import list_lost_files_in_project + from sqlalchemy.exc import OperationalError + + # Get project + project = models.Project.query.first() + assert project + + # Mock files in s3 + boto3_session.Bucket(project.bucket).objects.all = mock_items_in_bucket + # Get created testfiles + fake_files_in_bucket = mock_items_in_bucket() + + # mock db.session.commit + files_name_in_bucket_mock = PropertyMock( + side_effect=sqlalchemy.exc.OperationalError("OperationalError", "test", "sqlalchemy") + ) + + # Run listing + with patch("dds_web.database.models.Project.files", files_name_in_bucket_mock): + try: + in_db_but_not_in_s3, in_s3_but_not_in_db = list_lost_files_in_project( + project=project, s3_resource=boto3_session + ) + except OperationalError as e: + print(f"OperationalError occurred: {e}") + + # Get logging output + out, err = capfd.readouterr() + assert "OperationalError occurred" in out + assert "Unable to connect to db" in err + + # use_sto4 @@ -1510,3 +1548,500 @@ def test_use_sto4_return_true(client: flask.testing.FlaskClient): # Run function result: bool = use_sto4(unit_object=unit, project_object=project) assert result is True + + +# add_uploaded_files_to_db + + +def test_add_uploaded_files_to_db_other_failed_op(client: flask.testing.FlaskClient): + """Test calling the function with "failed_op" other than "add_file_db".""" + # Prepare input data + proj_in_db = models.Project.query.first() + log = { + "file1.txt": { + "status": {"failed_op": "some_other_failed_op"}, + "path_remote": "path/to/file1.txt", + "subpath": "subpath", + "size_raw": 100, + "size_processed": 200, + "compressed": False, + "public_key": "public_key", + "salt": "salt", + "checksum": "checksum", + } + } + + # Mock the S3 connector and head_object method + mock_s3conn = MagicMock() + mock_s3conn.resource.meta.client.head_object.return_value = None + + # Call the function + with patch("dds_web.api.api_s3_connector.ApiS3Connector", return_value=mock_s3conn): + files_added, errors = utils.add_uploaded_files_to_db(proj_in_db, log) + + # check that the file is added to the database + file = models.File.query.filter_by(name="file1.txt").first() + assert not file + + # check that the error is returned and files_added is empty + assert file not in files_added + assert files_added == [] + + assert "Incorrect 'failed_op'." in errors["file1.txt"]["error"] + + +def test_add_uploaded_files_to_db_correct_failed_op_file_not_found_in_s3( + client: flask.testing.FlaskClient, +): + """Test the return values of the function when file is not found on S3.""" + from botocore.exceptions import ClientError + + # Prepare input data + proj_in_db = models.Project.query.first() + log = { + "file1.txt": { + "status": {"failed_op": "add_file_db"}, + "path_remote": "path/to/file1.txt", + "subpath": "subpath", + "size_raw": 100, + "size_processed": 200, + "compressed": False, + "public_key": "public_key", + "salt": "salt", + "checksum": "checksum", + } + } + + # mock ApiS3Connector + mock_api_s3_conn = MagicMock() + mock_s3conn = mock_api_s3_conn.return_value.__enter__.return_value + + # call add_uploaded_files_to_db + with patch("dds_web.api.api_s3_connector.ApiS3Connector", mock_api_s3_conn): + mock_s3conn.resource.meta.client.head_object.side_effect = ClientError( + {"Error": {"Code": "404"}}, "operation_name" + ) + files_added, errors = utils.add_uploaded_files_to_db(proj_in_db, log) + + # check that the file is not added to the database + file = models.File.query.filter_by(name="file1.txt").first() + assert not file + + # check that the error is returned and files_added is empty + assert file not in files_added + assert files_added == [] + assert "File not found in S3" in errors["file1.txt"]["error"] + + +def test_add_uploaded_files_to_db_correct_failed_op_file_not_found_in_db( + client: flask.testing.FlaskClient, +): + """Test calling the function with correct "failed_op" and file isn't found in database.""" + # Mock input data + proj_in_db = models.Project.query.first() + file_name = "file1.txt" + log = { + file_name: { + "status": {"failed_op": "add_file_db"}, + "path_remote": f"path/to/{file_name}", + "subpath": "subpath", + "size_raw": 100, + "size_processed": 200, + "compressed": False, + "public_key": "public_key", + "salt": "salt", + "checksum": "checksum", + } + } + + # Verify that file does not exist + file_object = models.File.query.filter( + sqlalchemy.and_( + models.File.name == sqlalchemy.func.binary(file_name), + models.File.project_id == proj_in_db.id, + ) + ).first() + assert not file_object + + # Mock the S3 connector and head_object method + mock_s3conn = MagicMock() + mock_s3conn.resource.meta.client.head_object.return_value = None + + # Call the function + with patch("dds_web.api.api_s3_connector.ApiS3Connector", return_value=mock_s3conn): + files_added, errors = utils.add_uploaded_files_to_db(proj_in_db, log) + + # check that the file is added to the database + file = models.File.query.filter( + sqlalchemy.and_( + models.File.name == sqlalchemy.func.binary(file_name), + models.File.project_id == proj_in_db.id, + ) + ).first() + assert file + assert file.name == file_name + assert file.name_in_bucket == log[file_name]["path_remote"] + assert file.subpath == log[file_name]["subpath"] + assert file.size_original == log[file_name]["size_raw"] + assert file.size_stored == log[file_name]["size_processed"] + assert file.compressed != log[file_name]["compressed"] + assert file.public_key == log[file_name]["public_key"] + assert file.salt == log[file_name]["salt"] + assert file.checksum == log[file_name]["checksum"] + + # Check that the file is added to the project + assert file in proj_in_db.files + + # Check that the version is added to the database + version = models.Version.query.filter_by(active_file=file.id).first() + assert version + assert version.size_stored == log[file_name]["size_processed"] + + # Check the return values + assert file in files_added + assert errors == {} + + +def test_add_uploaded_files_to_db_correct_failed_op_file_is_found_in_db_no_overwrite( + client: flask.testing.FlaskClient, +): + """Test calling the function with correct "failed_op" and file IS found in database.""" + # Mock input data + proj_in_db = models.Project.query.first() + file_name = "file1.txt" + log = { + file_name: { + "status": {"failed_op": "add_file_db"}, + "path_remote": f"path/to/{file_name}", + "subpath": "subpath", + "size_raw": 100, + "size_processed": 200, + "compressed": False, + "public_key": "public_key", + "salt": "salt", + "checksum": "checksum", + } + } + + # Create new file + new_file = models.File( + name=file_name, + name_in_bucket=log[file_name]["path_remote"], + subpath=log[file_name]["subpath"], + size_original=log[file_name]["size_raw"], + size_stored=log[file_name]["size_processed"], + compressed=not log[file_name]["compressed"], + public_key=log[file_name]["public_key"], + salt=log[file_name]["salt"], + checksum=log[file_name]["checksum"], + ) + proj_in_db.files.append(new_file) + db.session.add(new_file) + db.session.commit() + + # Mock the S3 connector and head_object method + mock_s3conn = MagicMock() + mock_s3conn.resource.meta.client.head_object.return_value = None + + # Call the function + with patch("dds_web.api.api_s3_connector.ApiS3Connector", return_value=mock_s3conn): + files_added, errors = utils.add_uploaded_files_to_db(proj_in_db, log) + + # check that the error is returned and files_added is empty + assert files_added == [] + assert "File already in database" in errors[file_name]["error"] + + # Check that the version is not added to the database + version = models.Version.query.filter_by(active_file=new_file.id).first() + assert not version + + +def test_add_uploaded_files_to_db_correct_failed_op_file_is_found_in_db_overwrite_missing_key( + client: flask.testing.FlaskClient, +): + """Test calling the function with correct "failed_op" and file IS found in database but there's at least one key missing.""" + # Mock input data + proj_in_db = models.Project.query.first() + file_name = "file1.txt" + log = { + file_name: { + "status": {"failed_op": "add_file_db"}, + "path_remote": f"path/to/{file_name}", + "subpath": "subpath", + "size_raw": 100, + "size_processed": 200, + "compressed": False, + "public_key": "public_key", + "salt": "salt", + "checksum": "checksum", + "overwrite": True, + } + } + + # Create new file + new_file = models.File( + name=file_name, + name_in_bucket=log[file_name]["path_remote"], + subpath=log[file_name]["subpath"], + size_original=log[file_name]["size_raw"], + size_stored=log[file_name]["size_processed"], + compressed=not log[file_name]["compressed"], + public_key=log[file_name]["public_key"], + salt=log[file_name]["salt"], + checksum=log[file_name]["checksum"], + ) + proj_in_db.files.append(new_file) + db.session.add(new_file) + db.session.commit() + + # Mock the S3 connector and head_object method + mock_s3conn = MagicMock() + mock_s3conn.resource.meta.client.head_object.return_value = None + + # Remove key + log[file_name].pop("checksum") + + # Call the function + with patch("dds_web.api.api_s3_connector.ApiS3Connector", return_value=mock_s3conn): + files_added, errors = utils.add_uploaded_files_to_db(proj_in_db, log) + + # check that the error is returned and files_added is empty + assert files_added == [] + assert "Missing key: 'checksum'" in errors[file_name]["error"] + + # Check that the version is not added to the database + version = models.Version.query.filter_by(active_file=new_file.id).first() + assert not version + + +def test_add_uploaded_files_to_db_correct_failed_op_file_is_found_in_db_overwrite_ok( + client: flask.testing.FlaskClient, +): + """Test calling the function with correct "failed_op" and file IS found in database but overwrite is specified.""" + # Mock input data + proj_in_db = models.Project.query.first() + file_name = "file1.txt" + original_file_info = { + "name": file_name, + "path_remote": f"path/to/{file_name}", + "subpath": "subpath", + "size_raw": 100, + "size_processed": 200, + "compressed": False, + "public_key": "public_key", + "salt": "salt", + "checksum": "checksum", + } + log = { + file_name: { + "status": {"failed_op": "add_file_db"}, + "path_remote": f"path/to/{file_name}", + "subpath": "subpath2", + "size_raw": 1001, + "size_processed": 201, + "compressed": True, + "public_key": "public_key2", + "salt": "salt2", + "checksum": "checksum2", + "overwrite": True, + } + } + + # Create new file + new_file = models.File( + name=file_name, + name_in_bucket=original_file_info["path_remote"], + subpath=original_file_info["subpath"], + size_original=original_file_info["size_raw"], + size_stored=original_file_info["size_processed"], + compressed=original_file_info["compressed"], + public_key=original_file_info["public_key"], + salt=original_file_info["salt"], + checksum=original_file_info["checksum"], + ) + proj_in_db.files.append(new_file) + db.session.add(new_file) + db.session.commit() + + # Mock the S3 connector and head_object method + mock_s3conn = MagicMock() + mock_s3conn.resource.meta.client.head_object.return_value = None + + # Call the function + with patch("dds_web.api.api_s3_connector.ApiS3Connector", return_value=mock_s3conn): + files_added, errors = utils.add_uploaded_files_to_db(proj_in_db, log) + + # check that no error is returned and that there's a file and version added + file = models.File.query.filter( + sqlalchemy.and_( + models.File.name == sqlalchemy.func.binary(file_name), + models.File.project_id == proj_in_db.id, + ) + ).first() + assert file + assert file.name == file_name + assert file.name_in_bucket == log[file_name]["path_remote"] + assert file.subpath == log[file_name]["subpath"] + assert file.size_original == log[file_name]["size_raw"] + assert file.size_stored == log[file_name]["size_processed"] + assert file.compressed != log[file_name]["compressed"] + assert file.public_key == log[file_name]["public_key"] + assert file.salt == log[file_name]["salt"] + assert file.checksum == log[file_name]["checksum"] + assert files_added and file in files_added + + assert errors == {} + + # Check that the version is added to the database + version = models.Version.query.filter_by(active_file=new_file.id).first() + assert version + assert version.size_stored == file.size_stored + + +def test_add_uploaded_files_to_db_sql_error(client: flask.testing.FlaskClient): + """Test the return values of the function when sqlalchemy error occurs.""" + import sqlalchemy.exc + from dds_web import db + + # Prepare input data + proj_in_db = models.Project.query.first() + log = { + "file1.txt": { + "status": {"failed_op": "add_file_db"}, + "path_remote": "path/to/file1.txt", + "subpath": "subpath", + "size_raw": 100, + "size_processed": 200, + "compressed": False, + "public_key": "public_key", + "salt": "salt", + "checksum": "checksum", + } + } + + # mock ApiS3Connector + mock_s3conn = MagicMock() + mock_s3conn.resource.meta.client.head_object.return_value = None + + # mock db.session.commit + db_session_commit_mock = MagicMock( + side_effect=sqlalchemy.exc.OperationalError("OperationalError", "test", "sqlalchemy") + ) + + # call add_uploaded_files_to_db + with patch("dds_web.api.api_s3_connector.ApiS3Connector", return_value=mock_s3conn): + with patch("dds_web.db.session.commit", db_session_commit_mock): + files_added, errors = utils.add_uploaded_files_to_db(proj_in_db, log) + + # check that the file is not added to the database + file = models.File.query.filter_by(name="file1.txt").first() + assert not file + + # check that the error is returned and files_added is empty + assert file not in files_added + assert files_added == [] + assert "OperationalError" in errors["file1.txt"]["error"] + + +# new_file_version + + +def test_new_file_version_multiple_versions( + client: flask.testing.FlaskClient, capfd: LogCaptureFixture +): + """If there are multiple versions for the same file then they should be updated identically.""" + # Get any project + project = models.Project.query.first() + + # Define file info + file_name = "file1.txt" + original_file_info = { + "name": file_name, + "path_remote": f"path/to/{file_name}", + "subpath": "subpath", + "size_raw": 100, + "size_processed": 200, + "compressed": False, + "public_key": "public_key", + "salt": "salt", + "checksum": "checksum", + } + + # Create new file + new_file = models.File( + name=file_name, + name_in_bucket=original_file_info["path_remote"], + subpath=original_file_info["subpath"], + size_original=original_file_info["size_raw"], + size_stored=original_file_info["size_processed"], + compressed=original_file_info["compressed"], + public_key=original_file_info["public_key"], + salt=original_file_info["salt"], + checksum=original_file_info["checksum"], + ) + + # Create new versions (multiple) of the file + new_version_1 = models.Version( + size_stored=original_file_info["size_processed"], + time_uploaded=utils.current_time(), + active_file=new_file.id, + project_id=project, + ) + new_version_2 = models.Version( + size_stored=original_file_info["size_processed"] + 10, + time_uploaded=utils.current_time(), + active_file=new_file.id, + project_id=project, + ) + + # Append to relationships + project.files.append(new_file) + project.file_versions.extend([new_version_1, new_version_2]) + new_file.versions.extend([new_version_1, new_version_2]) + + db.session.add(new_file) + db.session.commit() + + # Define new file info + new_file_info = { + "name": file_name, + "path_remote": f"path/to/{file_name}", + "subpath": "subpath", + "size_raw": 1001, + "size_processed": 2001, + "compressed": True, + "public_key": "public_key2", + "salt": "salt2", + "checksum": "checksum2", + } + + # Run function + utils.new_file_version(existing_file=new_file, new_info=new_file_info) + + # Verify that logging printed + _, err = capfd.readouterr() + assert ( + "There is more than one version of the file which does not yet have a deletion timestamp." + in err + ) + + # Verify that there's a new version + assert len(new_file.versions) == 3 + + # Verify that the file info has been updated + assert new_file.subpath == new_file_info["subpath"] == original_file_info["subpath"] + assert new_file.size_original == new_file_info["size_raw"] != original_file_info["size_raw"] + assert ( + new_file.size_stored + == new_file_info["size_processed"] + != original_file_info["size_processed"] + ) + assert ( + new_file.compressed + == (not new_file_info["compressed"]) + != (not original_file_info["compressed"]) + ) + assert new_file.salt == new_file_info["salt"] != original_file_info["salt"] + assert new_file.public_key == new_file_info["public_key"] != original_file_info["public_key"] + assert new_file.time_uploaded != new_version_1.time_deleted == new_version_2.time_deleted + assert new_file.checksum == new_file_info["checksum"] != original_file_info["checksum"] diff --git a/tests/test_version.py b/tests/test_version.py index 534eab711..aaf84a488 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -2,4 +2,4 @@ def test_version(): - assert version.__version__ == "2.5.2" + assert version.__version__ == "2.6.0"