diff --git a/.gitignore b/.gitignore index 380a88047..112d09ee2 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ tmp # docker-compose database directory docker-db-data + +/config/credentials/ diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100644 index 000000000..de4202e08 --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,72 @@ +FROM debian:bookworm + +ENV DEBIAN_FRONTEND=noninteractive + +# Install system packages then clean up to minimize image size +RUN apt-get update \ + && apt-get install --no-install-recommends -y \ + build-essential \ + curl \ + default-jre-headless \ + file \ + git-core \ + gpg-agent \ + libarchive-dev \ + libffi-dev \ + libgd-dev \ + libpq-dev \ + libsasl2-dev \ + libvips-dev \ + libxml2-dev \ + libxslt1-dev \ + libyaml-dev \ + locales \ + postgresql-client \ + ruby-dev \ + ruby-bundler \ + tzdata \ + unzip \ + nodejs \ + npm \ + osmosis \ + ca-certificates \ + firefox-esr \ + optipng \ + pngcrush \ + advancecomp \ + pngquant \ + jhead \ + jpegoptim \ + libjpeg-turbo-progs \ + gifsicle + +# Install yarn globally +RUN npm install --global yarn + +# Add support for Postgres 16 +RUN apt-get install --no-install-recommends -y postgresql-common \ + && /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y \ + && apt-get install --no-install-recommends -y postgresql-client-16 + +# Setup app location +RUN mkdir -p /app +WORKDIR /app +COPY . . + +# Database template for Docker +COPY config/docker.database.yml config/database.yml +RUN sed -i'.bak' 's/development/production/g' config/database.yml + +COPY config/example.storage.yml config/storage.yml + +# https://help.openstreetmap.org/questions/69887/actionviewtemplateerror-couldnt-find-file-settingslocalyml +RUN touch config/settings.local.yml + +# Install Ruby packages +RUN bundle install + +# Install NodeJS packages using yarn +RUN bundle exec bin/yarn install + +# Build frontend assets +RUN RAILS_ENV=production SECRET_KEY_BASE=dummy rails i18n:js:export assets:precompile diff --git a/Gemfile b/Gemfile index 277346b83..6364b514a 100644 --- a/Gemfile +++ b/Gemfile @@ -144,6 +144,11 @@ gem "unicode-display_width" # Lock some modules to old versions for ruby 3.1 support gem "zeitwerk", "< 2.7" +# TDEI Workspaces: supports multi-tenancy +# The latest stable release doesn't support Rails 7.1, yet. Use dev for now: +gem "ros-apartment", github: 'rails-on-services/apartment', branch: 'development', :require => "apartment" +#gem "ros-apartment", :require => "apartment" + # Gems useful for development group :development do gem "better_errors" diff --git a/Gemfile.lock b/Gemfile.lock index f23429253..6fbc1dd87 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,15 @@ +GIT + remote: https://github.com/rails-on-services/apartment.git + revision: ec5a9bc7aef2ba65d7c3d369ca08aa916e56f880 + branch: development + specs: + ros-apartment (3.1.0) + activerecord (>= 6.1.0, < 8.1) + activesupport (>= 6.1.0, < 8.1) + parallel (< 2.0) + public_suffix (>= 2.0.5, <= 6.0.1) + rack (>= 1.3.6, < 4.0) + GEM remote: https://rubygems.org/ specs: @@ -742,6 +754,7 @@ DEPENDENCIES rails-i18n (~> 7.0.0) rails_param rinku (>= 2.0.6) + ros-apartment! rotp rtlcss rubocop diff --git a/Rakefile b/Rakefile index 9a5ea7383..01dcdcc85 100644 --- a/Rakefile +++ b/Rakefile @@ -3,4 +3,6 @@ require_relative "config/application" +Apartment.db_migrate_tenants = false + Rails.application.load_tasks diff --git a/app/controllers/api/users_controller.rb b/app/controllers/api/users_controller.rb index e9f42e12b..b26694db5 100644 --- a/app/controllers/api/users_controller.rb +++ b/app/controllers/api/users_controller.rb @@ -4,7 +4,8 @@ class UsersController < ApiController before_action :setup_user_auth, :only => [:show, :index] before_action :authorize, :only => [:details, :gpx_files] - authorize_resource + authorize_resource :except => [:provision] + skip_authorization_check :only => [:provision] load_resource :only => :show @@ -52,6 +53,64 @@ def gpx_files render :content_type => "application/xml" end + def provision + user = User.find_by(:auth_provider => "TDEI", :auth_uid => params[:auth_uid]) + + if user + user.email = params[:email] + user.display_name = params[:display_name] + + if user.status != "active" + user.activate + end + + user.save + return head :no_content + end + + # TODO: temporary for TDEI auth transition: + user = User.find_by(:email => params[:email]) + + if user + user.auth_provider = 'TDEI' + user.auth_uid = params[:auth_uid] + user.display_name = params[:display_name] + + if user.status != "active" + user.activate + end + + user.save + return head :no_content + end + + puts 'Create user' + user = User.new() + user.email = params[:email] + user.email_valid = true + user.display_name = params[:display_name] + + user.auth_provider = 'TDEI' + user.auth_uid = params[:auth_uid] + + # Set a random password--TDEI identity services manage the password: + user.pass_crypt = SecureRandom.base64(16) + + user.data_public = true + user.description = '' if user.description.nil? + user.creation_address = request.remote_ip + user.languages = http_accept_language.user_preferred_languages + user.terms_agreed = Time.now.utc + user.tou_agreed = Time.now.utc + user.terms_seen = true + user.auth_provider = nil + user.auth_uid = nil + user.activate + user.save + + head :no_content + end + private def disable_terms_redirect diff --git a/app/controllers/api/workspaces_controller.rb b/app/controllers/api/workspaces_controller.rb new file mode 100644 index 000000000..585816f57 --- /dev/null +++ b/app/controllers/api/workspaces_controller.rb @@ -0,0 +1,72 @@ +# TODO: this controller needs special auth with the workspaces backend +module Api + class WorkspacesController < ApiController + before_action :check_api_readable + before_action :set_request_formats + #before_action :authorize + + #authorize_resource + skip_authorization_check + + around_action :api_call_handle_error, :api_call_timeout + + def create + workspace_schema = 'workspace-' + params[:id] + + # This runs the migrations that scaffold all of the OSM tables into a new + # schema. We don't need every table, but we can't choose. The global non- + # tenant tables match excluded models in config/initializers/apartment.rb + # that Apartment associates with the "public" schema. + # + Apartment::Tenant.create(workspace_schema) + Apartment::Tenant.switch!(workspace_schema) + + # Drop tables that shadow the "public" schema. These tables cause foreign + # key constraint issues for tenant tables. The migrations include all OSM + # tables, and any unqualified references resolve to tenant schema tables. + # Removing these tables from the search path causes PostgreSQL to resolve + # tables references from the "public" schema instead: + # + connection = ActiveRecord::Base.connection + connection.execute("DROP TABLE \"#{workspace_schema}\".users CASCADE") + end + + def switch + cookies[:workspace] = { + :value => params[:id], + :expires => 1.year.from_now, + } + end + + def destroy + Apartment::Tenant.drop('workspace-' + params[:id]) + end + + def bbox + workspace_schema = 'workspace-' + params[:id] + Apartment::Tenant.switch!(workspace_schema) + + out = Node + .select('MAX(latitude) AS max_lat, + MAX(longitude) AS max_lon, + MIN(latitude) AS min_lat, + MIN(longitude) AS min_lon') + .take + + if out.min_lat.nil? # Workspace is empty (no nodes) + return head :no_content + end + + @bbox = BoundingBox.new(out.min_lon, out.min_lat, out.max_lon, out.max_lat) + + respond_to do |format| + format.xml + format.json + end + end + end +end + +# TODO stub +class Workspace +end diff --git a/app/middleware/workspaces_elevator.rb b/app/middleware/workspaces_elevator.rb new file mode 100644 index 000000000..0162b9f43 --- /dev/null +++ b/app/middleware/workspaces_elevator.rb @@ -0,0 +1,20 @@ +require 'apartment/elevators/generic' + +class WorkspacesElevator < Apartment::Elevators::Generic + def parse_tenant_name(request) + return nil if request.path.match? /^\/api\/[0-9.]+\/workspaces\// + + workspace_id = request.env['HTTP_X_WORKSPACE'] # X-Workspace header + + if workspace_id.blank? + workspace_id = request.cookies['workspace'] + end + + return nil if workspace_id.blank? + return nil unless workspace_id.match? /^\d+$/ + + puts 'Selecting workspace ' + workspace_id + + return 'workspace-' + workspace_id + end +end diff --git a/app/models/changeset.rb b/app/models/changeset.rb index e6bde19a5..c3394b493 100644 --- a/app/models/changeset.rb +++ b/app/models/changeset.rb @@ -55,10 +55,10 @@ class Changeset < ApplicationRecord before_save :update_closed_at # maximum number of elements allowed in a changeset - MAX_ELEMENTS = 10000 + MAX_ELEMENTS = 10000000 # maximum time a changeset is allowed to be open for. - MAX_TIME_OPEN = 1.day + MAX_TIME_OPEN = 7.day # idle timeout increment, one hour seems reasonable. IDLE_TIMEOUT = 1.hour diff --git a/app/views/api/workspaces/bbox.json.jbuilder b/app/views/api/workspaces/bbox.json.jbuilder new file mode 100644 index 000000000..087a4c62a --- /dev/null +++ b/app/views/api/workspaces/bbox.json.jbuilder @@ -0,0 +1,4 @@ +json.min_lat GeoRecord::Coord.new(@bbox.to_unscaled.min_lat) +json.min_lon GeoRecord::Coord.new(@bbox.to_unscaled.min_lon) +json.max_lat GeoRecord::Coord.new(@bbox.to_unscaled.max_lat) +json.max_lon GeoRecord::Coord.new(@bbox.to_unscaled.max_lon) diff --git a/config/.gitignore b/config/.gitignore index 95ba2dbdb..28d379bd9 100644 --- a/config/.gitignore +++ b/config/.gitignore @@ -1,2 +1 @@ -database.yml storage.yml diff --git a/config/application.rb b/config/application.rb index 05d7fb41d..0f87b72ee 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,4 +1,5 @@ require_relative "boot" +require_relative "../app/middleware/workspaces_elevator.rb" require "rails/all" @@ -45,5 +46,8 @@ class Application < Rails::Application config.logstasher.logger_path = Settings.logstash_path config.logstasher.log_controller_parameters = true end + + # TDEI Workspaces tenant partitioning: + config.middleware.use WorkspacesElevator end end diff --git a/config/database.yml b/config/database.yml new file mode 100644 index 000000000..5a0bb5b14 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,8 @@ +<%= ENV['RAILS_ENV'] %>: + adapter: postgresql + host: <%= ENV['WS_OSM_DB_HOST'] %> + username: <%= ENV['WS_OSM_DB_USER'] %> + password: <%= ENV['WS_OSM_DB_PASS'] %> + database: <%= ENV['WS_OSM_DB_NAME'] %> + encoding: utf8 + diff --git a/config/docker.database.yml b/config/docker.database.yml index b0e79b0f8..539938602 100644 --- a/config/docker.database.yml +++ b/config/docker.database.yml @@ -1,20 +1,9 @@ # This configuration is tailored for use with docker compose. See DOCKER.md for more information. -development: +<%= ENV['RAILS_ENV'] %>: adapter: postgresql - database: openstreetmap - username: openstreetmap - password: openstreetmap - host: db - encoding: utf8 - -# Warning: The database defined as 'test' will be erased and -# re-generated from your development database when you run 'rake'. -# Do not set this db to the same as development or production. -test: - adapter: postgresql - database: osm_test - username: openstreetmap - password: openstreetmap - host: db + host: <%= ENV['WS_OSM_DB_HOST'] %> + username: <%= ENV['WS_OSM_DB_USER'] %> + password: <%= ENV['WS_OSM_DB_PASS'] %> + database: <%= ENV['WS_OSM_DB_NAME'] %> encoding: utf8 diff --git a/config/initializers/apartment.rb b/config/initializers/apartment.rb new file mode 100644 index 000000000..8f103b3b7 --- /dev/null +++ b/config/initializers/apartment.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +# You can have Apartment route to the appropriate Tenant by adding some Rack middleware. +# Apartment can support many different "Elevators" that can take care of this routing to your data. +# Require whichever Elevator you're using below or none if you have a custom one. +# +# require 'apartment/elevators/generic' +# require 'apartment/elevators/domain' +# require 'apartment/elevators/subdomain' +# require 'apartment/elevators/first_subdomain' +# require 'apartment/elevators/host' +require_relative "../../app/middleware/workspaces_elevator.rb" + +# +# Apartment Configuration +# +Apartment.configure do |config| + # Add any models that you do not want to be multi-tenanted, but remain in the global (public) namespace. + # A typical example would be a Customer or Tenant model that stores each Tenant's information. + # + config.excluded_models = [ + "Acl", + "DiaryComment", + "DiaryEntry", + "DiaryEntrySubscription", + "Doorkeeper::AccessGrant", + "Doorkeeper::AccessToken", + "Doorkeeper::Application", + "Friendship", + "Issue", + "IssueComment", + "Language", + "Message", + "Oauth2Application", + "Report", + "User", + "UserBlock", + "UserMute", + "UserPreference", + "UserRole" + ] + + # In order to migrate all of your Tenants you need to provide a list of Tenant names to Apartment. + # You can make this dynamic by providing a Proc object to be called on migrations. + # This object should yield either: + # - an array of strings representing each Tenant name. + # - a hash which keys are tenant names, and values custom db config + # (must contain all key/values required in database.yml) + # + # config.tenant_names = lambda{ Customer.pluck(:tenant_name) } + # config.tenant_names = ['tenant1', 'tenant2'] + # config.tenant_names = { + # 'tenant1' => { + # adapter: 'postgresql', + # host: 'some_server', + # port: 5555, + # database: 'postgres' # this is not the name of the tenant's db + # # but the name of the database to connect to before creating the tenant's db + # # mandatory in postgresql + # }, + # 'tenant2' => { + # adapter: 'postgresql', + # database: 'postgres' # this is not the name of the tenant's db + # # but the name of the database to connect to before creating the tenant's db + # # mandatory in postgresql + # } + # } + # config.tenant_names = lambda do + # Tenant.all.each_with_object({}) do |tenant, hash| + # hash[tenant.name] = tenant.db_configuration + # end + # end + # + config.tenant_names = -> { ToDo_Tenant_Or_User_Model.pluck :database } + + # PostgreSQL: + # Specifies whether to use PostgreSQL schemas or create a new database per Tenant. + # + # MySQL: + # Specifies whether to switch databases by using `use` statement or re-establish connection. + # + # The default behaviour is true. + # + config.use_schemas = true + + # + # ==> PostgreSQL only options + + # Apartment can be forced to use raw SQL dumps instead of schema.rb for creating new schemas. + # Use this when you are using some extra features in PostgreSQL that can't be represented in + # schema.rb, like materialized views etc. (only applies with use_schemas set to true). + # (Note: this option doesn't use db/structure.sql, it creates SQL dump by executing pg_dump) + # + config.use_sql = true + + # There are cases where you might want some schemas to always be in your search_path + # e.g when using a PostgreSQL extension like hstore. + # Any schemas added here will be available along with your selected Tenant. + # + # config.persistent_schemas = %w{ hstore } + + # <== PostgreSQL only options + # + + # By default, and only when not using PostgreSQL schemas, Apartment will prepend the environment + # to the tenant name to ensure there is no conflict between your environments. + # This is mainly for the benefit of your development and test environments. + # Uncomment the line below if you want to disable this behaviour in production. + # + # config.prepend_environment = !Rails.env.production? + + # When using PostgreSQL schemas, the database dump will be namespaced, and + # apartment will substitute the default namespace (usually public) with the + # name of the new tenant when creating a new tenant. Some items must maintain + # a reference to the default namespace (ie public) - for instance, a default + # uuid generation. Uncomment the line below to create a list of namespaced + # items in the schema dump that should *not* have their namespace replaced by + # the new tenant + # + # config.pg_excluded_names = ["uuid_generate_v4"] + + # Specifies whether the database and schema (when using PostgreSQL schemas) will prepend in ActiveRecord log. + # Uncomment the line below if you want to enable this behavior. + # + # config.active_record_log = true +end + +# Setup a custom Tenant switching middleware. The Proc should return the name of the Tenant that +# you want to switch to. +# Rails.application.config.middleware.use Apartment::Elevators::Generic, lambda { |request| +# request.host.split('.').first +# } + +# Rails.application.config.middleware.use Apartment::Elevators::Domain +# Rails.application.config.middleware.use Apartment::Elevators::Subdomain +# Rails.application.config.middleware.use Apartment::Elevators::FirstSubdomain +# Rails.application.config.middleware.use Apartment::Elevators::Host +Rails.application.config.middleware.use WorkspacesElevator diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index e7b813b73..4813a7962 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -20,12 +20,12 @@ def call(env) # such as X-Requested-By to requests.) Rails.application.config.middleware.insert_before 0, OpenStreetMap::Cors do allow do - origins "*" + origins { |source, env| true } resource "/oauth/*", :headers => :any, :methods => [:get, :post] resource "/oauth2/token", :headers => :any, :methods => [:post] resource "/oauth2/revoke", :headers => :any, :methods => [:post] resource "/oauth2/introspect", :headers => :any, :methods => [:post] - resource "/api/*", :headers => :any, :methods => [:get, :post, :put, :delete] + resource "/api/*", :headers => :any, :methods => [:get, :post, :put, :delete], :credentials => true resource "/diary/rss", :headers => :any, :methods => [:get] resource "/diary/*/rss", :headers => :any, :methods => [:get] resource "/trace/*/data", :headers => :any, :methods => [:get] diff --git a/config/routes.rb b/config/routes.rb index f65042dd7..d0817512a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -113,6 +113,12 @@ end resources :user_blocks, :only => :show, :id => /\d+/, :controller => "user_blocks" + + # TDEI Workspaces + put "user/:auth_uid" => "users#provision" + put "workspaces/:id" => "workspaces#create", :id => /\d+/ + delete "workspaces/:id" => "workspaces#destroy", :id => /\d+/ + get "workspaces/:id/bbox" => "workspaces#bbox", :id => /\d+/ end # Data browsing diff --git a/config/settings/development.yml b/config/settings/development.yml index e69de29bb..b921bec33 100644 --- a/config/settings/development.yml +++ b/config/settings/development.yml @@ -0,0 +1,14 @@ +server_protocol: "https" +server_url: <%= ENV['WS_OSM_HOST'] %> + +support_email: <%= ENV['WS_MAIL_CONTACT'] %> +email_from: <%= ENV['WS_MAIL_NAME'] %> <<%= ENV['WS_MAIL_FROM'] %>> +email_return_path: <%= ENV['WS_MAIL_RETURN_PATH'] %> + +smtp_address: <%= ENV['WS_SMTP_HOST'] %> +smtp_port: <%= ENV['WS_SMTP_PORT'] %> +smtp_domain: <%= ENV['WS_SMTP_DOMAIN'] %> +smtp_user_name: <%= ENV['WS_SMTP_USER'] %> +smtp_password: <%= ENV['WS_SMTP_PASS'] %> +smtp_authentication: "plain" +smtp_enable_starttls_auto: true diff --git a/config/settings/production.yml b/config/settings/production.yml index e69de29bb..b921bec33 100644 --- a/config/settings/production.yml +++ b/config/settings/production.yml @@ -0,0 +1,14 @@ +server_protocol: "https" +server_url: <%= ENV['WS_OSM_HOST'] %> + +support_email: <%= ENV['WS_MAIL_CONTACT'] %> +email_from: <%= ENV['WS_MAIL_NAME'] %> <<%= ENV['WS_MAIL_FROM'] %>> +email_return_path: <%= ENV['WS_MAIL_RETURN_PATH'] %> + +smtp_address: <%= ENV['WS_SMTP_HOST'] %> +smtp_port: <%= ENV['WS_SMTP_PORT'] %> +smtp_domain: <%= ENV['WS_SMTP_DOMAIN'] %> +smtp_user_name: <%= ENV['WS_SMTP_USER'] %> +smtp_password: <%= ENV['WS_SMTP_PASS'] %> +smtp_authentication: "plain" +smtp_enable_starttls_auto: true diff --git a/script/create-workspace b/script/create-workspace new file mode 100644 index 000000000..9846ee783 --- /dev/null +++ b/script/create-workspace @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby + +require File.join(File.dirname(__FILE__), "..", "config", "environment") + + diff --git a/yarn.lock b/yarn.lock index 4e40de8d5..55aeb68e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,24 +19,31 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== -"@eslint/config-array@^0.19.0": - version "0.19.0" - resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.0.tgz#3251a528998de914d59bb21ba4c11767cf1b3519" - integrity sha512-zdHg2FPIFNKPdcHWtiNT+jEFCHYVplAXRDlQDyqy0zGx/q2parwh7brGJSiTxRk/TSMkbM//zt/f5CHgyTyaSQ== +"@eslint/config-array@^0.21.0": + version "0.21.0" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.21.0.tgz#abdbcbd16b124c638081766392a4d6b509f72636" + integrity sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ== dependencies: - "@eslint/object-schema" "^2.1.4" + "@eslint/object-schema" "^2.1.6" debug "^4.3.1" minimatch "^3.1.2" -"@eslint/core@^0.9.0": - version "0.9.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.9.0.tgz#168ee076f94b152c01ca416c3e5cf82290ab4fcd" - integrity sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg== +"@eslint/config-helpers@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.3.1.tgz#d316e47905bd0a1a931fa50e669b9af4104d1617" + integrity sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA== -"@eslint/eslintrc@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c" - integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w== +"@eslint/core@^0.15.2": + version "0.15.2" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.15.2.tgz#59386327d7862cc3603ebc7c78159d2dcc4a868f" + integrity sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg== + dependencies: + "@types/json-schema" "^7.0.15" + +"@eslint/eslintrc@^3.3.1": + version "3.3.1" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.1.tgz#e55f7f1dd400600dd066dbba349c4c0bac916964" + integrity sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -48,21 +55,22 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@9.15.0": - version "9.15.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.15.0.tgz#df0e24fe869143b59731942128c19938fdbadfb5" - integrity sha512-tMTqrY+EzbXmKJR5ToI8lxu7jaN5EdmrBFJpQk5JmSlyLsx6o4t27r883K5xsLuCYCpfKBCGswMSWXsM+jB7lg== +"@eslint/js@9.34.0": + version "9.34.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.34.0.tgz#fc423168b9d10e08dea9088d083788ec6442996b" + integrity sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw== -"@eslint/object-schema@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.4.tgz#9e69f8bb4031e11df79e03db09f9dbbae1740843" - integrity sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ== +"@eslint/object-schema@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f" + integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA== -"@eslint/plugin-kit@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz#812980a6a41ecf3a8341719f92a6d1e784a2e0e8" - integrity sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA== +"@eslint/plugin-kit@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz#fd8764f0ee79c8ddab4da65460c641cefee017c5" + integrity sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w== dependencies: + "@eslint/core" "^0.15.2" levn "^0.4.1" "@humanfs/core@^0.19.1": @@ -88,10 +96,10 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a" integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA== -"@humanwhocodes/retry@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b" - integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA== +"@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== "@types/estree@^1.0.6": version "1.0.6" @@ -108,10 +116,10 @@ acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.14.0: - version "8.14.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" - integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== +acorn@^8.15.0: + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== ajv@^6.12.4: version "6.12.6" @@ -189,10 +197,10 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -cross-spawn@^7.0.5: - version "7.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.5.tgz#910aac880ff5243da96b728bc6521a5f6c2f2f82" - integrity sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug== +cross-spawn@^7.0.6: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" @@ -246,10 +254,10 @@ eslint-formatter-compact@^8.40.0: resolved "https://registry.yarnpkg.com/eslint-formatter-compact/-/eslint-formatter-compact-8.40.0.tgz#d7455b2d75fd70e8c0e7a98a5e189f168e9dfe2d" integrity sha512-cwGUs113TgmTQXecx5kfRjB7m0y2wkDLSadPTE2pK6M/wO4N8PjmUaoWOFNCP9MHgsiZwgqd5bZFnDCnszC56Q== -eslint-scope@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.2.0.tgz#377aa6f1cb5dc7592cfd0b7f892fd0cf352ce442" - integrity sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A== +eslint-scope@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.4.0.tgz#88e646a207fad61436ffa39eb505147200655c82" + integrity sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" @@ -259,36 +267,37 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint-visitor-keys@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" - integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== +eslint-visitor-keys@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" + integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== eslint@^9.0.0: - version "9.15.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.15.0.tgz#77c684a4e980e82135ebff8ee8f0a9106ce6b8a6" - integrity sha512-7CrWySmIibCgT1Os28lUU6upBshZ+GxybLOrmRzi08kS8MBuO8QA7pXEgYgY5W8vK3e74xv0lpjo9DbaGU9Rkw== + version "9.34.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.34.0.tgz#0ea1f2c1b5d1671db8f01aa6b8ce722302016f7b" + integrity sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.12.1" - "@eslint/config-array" "^0.19.0" - "@eslint/core" "^0.9.0" - "@eslint/eslintrc" "^3.2.0" - "@eslint/js" "9.15.0" - "@eslint/plugin-kit" "^0.2.3" + "@eslint/config-array" "^0.21.0" + "@eslint/config-helpers" "^0.3.1" + "@eslint/core" "^0.15.2" + "@eslint/eslintrc" "^3.3.1" + "@eslint/js" "9.34.0" + "@eslint/plugin-kit" "^0.3.5" "@humanfs/node" "^0.16.6" "@humanwhocodes/module-importer" "^1.0.1" - "@humanwhocodes/retry" "^0.4.1" + "@humanwhocodes/retry" "^0.4.2" "@types/estree" "^1.0.6" "@types/json-schema" "^7.0.15" ajv "^6.12.4" chalk "^4.0.0" - cross-spawn "^7.0.5" + cross-spawn "^7.0.6" debug "^4.3.2" escape-string-regexp "^4.0.0" - eslint-scope "^8.2.0" - eslint-visitor-keys "^4.2.0" - espree "^10.3.0" + eslint-scope "^8.4.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" esquery "^1.5.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -304,14 +313,14 @@ eslint@^9.0.0: natural-compare "^1.4.0" optionator "^0.9.3" -espree@^10.0.1, espree@^10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" - integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== +espree@^10.0.1, espree@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== dependencies: - acorn "^8.14.0" + acorn "^8.15.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^4.2.0" + eslint-visitor-keys "^4.2.1" esquery@^1.5.0: version "1.5.0"