diff --git a/.env.example b/.env.example index d98f7d4b..7f8a45ee 100644 --- a/.env.example +++ b/.env.example @@ -15,3 +15,27 @@ # Uncomment this to get GitHub comments for the Pull Request Workflow. # ENABLE_PR_COMMENT=true + +# ADMIN_REPO=safe-settings-config +CONFIG_PATH=.github +SETTINGS_FILE_PATH=settings.yml + +# Configuration support for Hub-Sync safe-settings feature +# SAFE_SETTINGS_HUB_REPO=safe-settings-config-master +# SAFE_SETTINGS_HUB_ORG=foo-training +# A subfolder under 'CONFIG_PATH' where the 'organizations//' structure is found +# SAFE_SETTINGS_HUB_PATH=safe-settings +# SAFE_SETTINGS_HUB_DIRECT_PUSH=true + + + +# ┌────────────── second (optional) +# │ ┌──────────── minute +# │ │ ┌────────── hour +# │ │ │ ┌──────── day of month +# │ │ │ │ ┌────── month +# │ │ │ │ │ ┌──── day of week +# │ │ │ │ │ │ +# │ │ │ │ │ │ +# * * * * * * +# CRON=* * * * * # Run every minute \ No newline at end of file diff --git a/docs/hubSyncHandler/README.md b/docs/hubSyncHandler/README.md new file mode 100644 index 00000000..480acd2b --- /dev/null +++ b/docs/hubSyncHandler/README.md @@ -0,0 +1,265 @@ +# Safe Settings Organization Sync & Dashboard + + This feature provides a centralized approach to managing the Safe-Settings Admin Repo, allowing Safe-Settings configurations to be sync'd across multiple ORGs. + +## Overview + +This adds the **hub‑and‑spoke synchronization capability** to Safe Settings. + +One central **master admin repository** (the hub) serves as the authoritative source of configuration ('Master' Admin Config Repo) which is automatically propagated to each organization’s **admin repository** (the spokes). + +**Note:** When something changes in the 'Master' repo (the hub), only those changed files are copied to each affected ORG’s admin repo, so everything stays in sync. + +## Sync Lifecycle (High Level) + +```mermaid +graph TD +A0(PR Closed Event) --> A1(HUB Admin Repo) +A1(ORG Admin Repo) --> B(ORG Admin Repo) +A1(HUB Admin Repo) --> C(ORG Admin Repo) +A1(HUB Admin Repo) --> D(ORG Admin Repo) +``` + +## Gettings Started + +>**Note:** for the standard setup lets assume that Safe-Settings configuration on the Admin Config Repos (Spokes) are stored in `.github/` + +These are the basic steps to setup the Enterprise-Level Safe-Settings, using **Hub-sync** support. + +### ✅ Step 1: Register the App +**Register the Safe-Settings App** in your Enterprise (Enterprise App) or in your Organization. + +For App "installation tragets" (Where can this GitHub App be installed?) +Choose ***Any account*** + +### ✅ Step 2: Install the App +**Install the Safe-Settings App** in any Organzation that you would like Safe-Settings to manage. + +### ✅ Step 3: Create the 'Org-Level' Safe-Settings Admin Config Repo (Spokes) +Create the Org-Level Repo that is your dedicated Safe-Settings Config Repo and will hold all Safe-Settings configurations for the Org. + +### ✅ Step 4: Create the 'Master' Safe-Settings Admin Config Repo (Hub) +Choose any Organization where the Safe-Settings App is installed and create a 'Master' Safe-Settings Admin Config Repo. + +The Repository requires a standard directory structure for storing the config data: +```bash +.github/ +└─ safe-settings/ + ├── globals/ + │ └── manifest.yml + └── organizations/ + ├── org1/ + │ └── ...yml + └── org2/ + └── ...yml +``` + +Notes: +- The `manifest.yml` is a required file, that defines rules for syncing **Global** Safe-Settings configurations. We will address the content format later. +- `org1` and `org2` are just examples and should be replaced with the real names of the Orgs that you want to manage with the **Hub-Sync**. + +### ✅ Step 5: Configure the 'Master' Safe-Settings Admin Config Repo (Hub) + +The **Hub-Sync** feature supports two options +1. **Organization Sync:** +Any settings file in the `organizations/` directory will be synced to the specific `` (Spoke) Admin config Repo subfolder (eg.: /.github/). Only updated files are sync'd to the ORG admin config Repo (spokes). +1. **Global Sync:** Any settings file in the `globals/` directory will be synced to the specific `` (Spoke) Admin config Repo subfolder (eg.: /.github/). + + :warning: The actual sync operation is based on the rules defined in the `globals/manifest.yml`. The rules provide fine grained control over the sync targets and sync strategy. + +These two options only require that you provide the files you would like to sync, in the correct sub-directory. + +#### ✅ Step 5.1: Configure the `manifest.yml` in the 'Master' Safe-Settings Admin Config Repo (Hub) + +The `manifest.yml` defines the sync rules for global settings distribution. +- Sample `manifest.yml` + + ``` + rules: + - name: global-defaults + # specify the target ORG(s) + targets: + - "*" + files: + - "*.yml" + + # mergeStrategy: merge | overwrite | preserve + # -------------------------------------------- + # merge = use a PR to sync files + # overwrite = sync all files to the target ORG(s) (no PR) + mergeStrategy: merge + + - name: security-policies + # specify the target ORG(s) + targets: + - "acme-*" + - "foo-bar" + files: + - settings.yml + mergeStrategy: overwrite + + # optional toggle, default true + # enabled: false + ``` + +### Example Rule Breakdown + +```yaml +- name: global-defaults + targets: + - "*" + files: + - "*.yml" + mergeStrategy: merge +``` +- **Purpose:** Sync all YAML files to all organizations, merging changes via PR. + +```yaml +- name: security-policies + targets: + - "acme-*" + - "foo-bar" + files: + - settings.yml + mergeStrategy: overwrite + enabled: false +``` + +- **Purpose:** Overwrite `settings.yml` in specific organizations, but currently disabled. + + +### `manifest.yml` Reference + +The `manifest.yml` file defines synchronization rules for Safe-Settings hub-and-spoke configuration management. Each rule specifies which organizations and files to target, and how to handle synchronization. + +### Top-Level Structure + +```yaml +rules: + - name: + targets: [, ...] + files: [, ...] + mergeStrategy: + enabled: # optional + # ...additional fields as needed +``` + +--- + +### Elements + +#### `rules` +- **Type:** Array of objects +- **Description:** List of synchronization rules. Each rule controls how specific files are synced to target organizations. + +#### Rule Object + +##### `name` +- **Type:** String +- **Description:** Unique identifier for the rule. Used for reference and logging. +- **Example:** `global-defaults`, `security-policies` + +##### `targets` +- **Type:** Array of strings +- **Description:** List of organization names or patterns to apply the rule to. + - `"*"`: All organizations + - `"acme-*"`: Organizations with names starting with `acme-` + - `"foo-bar"`: Specific organization +- **Example:** + ```yaml + targets: + - "*" + - "acme-*" + - "foo-bar" + ``` + +##### `files` +- **Type:** Array of strings +- **Description:** File patterns to sync. Supports wildcards. + - `"*.yml"`: All YAML files + - `"settings.yml"`: Specific file +- **Example:** + ```yaml + files: + - "*.yml" + - "settings.yml" + ``` + +##### `mergeStrategy` +- **Type:** String (`merge`, `overwrite`, `preserve`) +- **Description:** Determines how files are synced: + - `merge`: use a PR to sync files + - `overwrite`: Sync all files, replacing existing ones (direct commit, no PR) +- **Example:** + ```yaml + mergeStrategy: merge + ``` + +##### `enabled` +- **Type:** Boolean (optional) +- **Description:** Toggle to enable or disable the rule. Default is `true`. +- **Example:** + ```yaml + enabled: false + ``` + +--- + +### Environment Variables & Inputs Specific to the **Hub-Sync** feature + +| Name | Purpose | Default | +|------|---------|---------| +| `SAFE_SETTINGS_HUB_REPO` | Repo for master safe-settings contents | admin-master | +| `SAFE_SETTINGS_HUB_ORG` | Organization that hold the Repo | admin-master-org | +| `SAFE_SETTINGS_HUB_PATH` | source folder | .github/safe-settings | +| `SAFE_SETTINGS_HUB_DIRECT_PUSH` | Use a PR or direct commit | false | + + +--- +--- + +## Hub Sync Scenarios + +1. Sync the `Hub Admin Repo` changes to a `Safe-Settings Admin Repo` in **the same ORG** as the Hub Admin Repo. + +2. Sync the `Hub Admin changes` to a `Safe-Settings Admin Repo` in **a different ORG**. + +3. _`'Global'`_ `Hub Admin Repo` updates. +Changes will `applied to all Organization` + +--- + +## Safe-Settings Hub API endpoints + +### API Endpoints + +The following table summarizes the Safe Settings API endpoints: + +| Endpoint | Method | Purpose | Example Usage | +|------------------------------------------|--------|------------------------------------------------------|---------------| +| `/api/safe-settings/installation` | GET | Organization installation, repo, and sync status | Fetch org status | +| `/api/safe-settings/hub/content` | GET | List hub repo files/directories | List hub files | +| `/api/safe-settings/hub/content/*` | GET | Fetch specific file or directory from hub repo | Get file content | +| `/api/safe-settings/hub/import` | POST | Import settings from orgs into the hub | Import org settings | +| `/api/safe-settings/env` | GET | App environment/config variables | Get env vars | + +**Examples:** +- Fetch org installation status: + ```http + GET /api/safe-settings/installation + ``` +- Import settings from orgs: + ```http + POST /api/safe-settings/hub/import + Body: { "orgs": ["org1", "org2"] } + ``` +- List hub repo files: + ```http + GET /api/safe-settings/hub/content?ref=main&recursive=true + ``` +- Get environment variables: + ```http + GET /api/safe-settings/env + ``` + +--- \ No newline at end of file diff --git a/hubSyncHandler.log b/hubSyncHandler.log new file mode 100644 index 00000000..84e9592c --- /dev/null +++ b/hubSyncHandler.log @@ -0,0 +1,754 @@ +2025-09-11T15:47:52.340Z [INFO] Pull request closed on Safe-Settings Hub: (jefeish-training/safe-settings-config-master) +2025-09-11T15:47:52.339Z [INFO] Received 'pull_request.closed' event: 47 +2025-09-11T15:47:52.636Z [INFO] Files changed in PR #47: .github/safe-settings/globals/suborg.yml +2025-09-11T15:47:52.637Z [INFO] Syncing safe settings for 'globals/'. +2025-09-11T15:47:52.636Z [DEBUG] Detected changes in the globals folder. Routing to syncHubGlobalsUpdate(...). +2025-09-11T15:47:52.864Z [DEBUG] Loaded manifest.yml rules from hub repo:{ + "rules": [ + { + "name": "global-defaults", + "targets": [ + "*" + ], + "files": [ + "*.yml" + ], + "mergeStrategy": "merge" + }, + { + "name": "security-policies", + "targets": [ + "acme-*", + "foo-bar" + ], + "files": [ + "settings.yml" + ], + "mergeStrategy": "overwrite" + } + ] +} +2025-09-11T15:47:53.106Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'jetest99' with mergeStrategy='merge' +2025-09-11T15:47:53.106Z [DEBUG] Rule 'global-defaults' matches file 'suborg.yml'. Targets: jetest99, jefeish-training, jefeish-test1, copilot-for-emus, jefeish-migration-test, decyjphr-training, decyjphr-emu +2025-09-11T15:47:53.106Z [DEBUG] Evaluating globals file: .github/safe-settings/globals/suborg.yml +2025-09-11T15:47:53.434Z [DEBUG] Checking existence of .github/suborg.yml in jetest99/safe-settings-config +2025-09-11T15:47:53.680Z [DEBUG] Found .github/suborg.yml in jetest99/safe-settings-config +2025-09-11T15:47:53.681Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'jefeish-training' with mergeStrategy='merge' +2025-09-11T15:47:53.681Z [INFO] Skipping sync of suborg.yml to jetest99 (already exists & mergeStrategy=merge) +2025-09-11T15:47:54.039Z [DEBUG] Checking existence of .github/suborg.yml in jefeish-training/safe-settings-config +2025-09-11T15:47:54.273Z [DEBUG] Found .github/suborg.yml in jefeish-training/safe-settings-config +2025-09-11T15:47:54.273Z [INFO] Skipping sync of suborg.yml to jefeish-training (already exists & mergeStrategy=merge) +2025-09-11T15:47:54.273Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'jefeish-test1' with mergeStrategy='merge' +2025-09-11T15:47:54.585Z [DEBUG] Checking existence of .github/suborg.yml in jefeish-test1/safe-settings-config +2025-09-11T15:47:54.886Z [DEBUG] Found .github/suborg.yml in jefeish-test1/safe-settings-config +2025-09-11T15:47:54.886Z [INFO] Skipping sync of suborg.yml to jefeish-test1 (already exists & mergeStrategy=merge) +2025-09-11T15:47:54.886Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'copilot-for-emus' with mergeStrategy='merge' +2025-09-11T15:47:55.093Z [INFO] Skipping org copilot-for-emus: config repo 'safe-settings-config' does not exist. +2025-09-11T15:47:55.093Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'jefeish-migration-test' with mergeStrategy='merge' +2025-09-11T15:47:55.511Z [DEBUG] Checking existence of .github/suborg.yml in jefeish-migration-test/safe-settings-config +2025-09-11T15:47:55.758Z [DEBUG] Found .github/suborg.yml in jefeish-migration-test/safe-settings-config +2025-09-11T15:47:55.759Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'decyjphr-training' with mergeStrategy='merge' +2025-09-11T15:47:55.759Z [INFO] Skipping sync of suborg.yml to jefeish-migration-test (already exists & mergeStrategy=merge) +2025-09-11T15:47:55.933Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'decyjphr-emu' with mergeStrategy='merge' +2025-09-11T15:47:55.933Z [INFO] Skipping org decyjphr-training: config repo 'safe-settings-config' does not exist. +2025-09-11T15:47:56.108Z [INFO] Skipping org decyjphr-emu: config repo 'safe-settings-config' does not exist. +2025-09-11T15:47:59.386Z [DEBUG] Pull_request REopened ! +2025-09-11T15:47:59.386Z [DEBUG] Is Admin repo event false +2025-09-11T15:47:59.386Z [DEBUG] Not working on the Admin repo, returning... +2025-09-11T15:49:09.315Z [DEBUG] Branch Protection edited by {"login":"jefeish_fabrikam","id":90713677,"node_id":"MDQ6VXNlcjkwNzEzNjc3","avatar_url":"https://avatars.githubusercontent.com/u/90713677?v=4","gravatar_id":"","url":"https://api.github.com/users/jefeish_fabrikam","html_url":"https://github.com/jefeish_fabrikam","followers_url":"https://api.github.com/users/jefeish_fabrikam/followers","following_url":"https://api.github.com/users/jefeish_fabrikam/following{/other_user}","gists_url":"https://api.github.com/users/jefeish_fabrikam/gists{/gist_id}","starred_url":"https://api.github.com/users/jefeish_fabrikam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jefeish_fabrikam/subscriptions","organizations_url":"https://api.github.com/users/jefeish_fabrikam/orgs","repos_url":"https://api.github.com/users/jefeish_fabrikam/repos","events_url":"https://api.github.com/users/jefeish_fabrikam/events{/privacy}","received_events_url":"https://api.github.com/users/jefeish_fabrikam/received_events","type":"User","user_view_type":"public","site_admin":false} +2025-09-11T15:49:09.315Z [DEBUG] Branch Protection edited by a Human +2025-09-11T15:49:09.315Z [DEBUG] deploymentConfig is {"restrictedRepos":["admin",".github","safe-settings"]} +2025-09-11T15:49:09.553Z [DEBUG] config for ref undefined is {"restrictedRepos":["admin",".github","safe-settings"],"repository":{"description":"description of the repo","homepage":"https://example.github.io/","auto_init":true,"topics":["new-topic","another-topic"],"security":{"enableVulnerabilityAlerts":true,"enableAutomatedSecurityFixes":true},"private":true,"visibility":"private","has_issues":true,"has_projects":true,"has_wiki":true,"default_branch":"main","gitignore_template":"node","license_template":"mit","allow_squash_merge":true,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_auto_merge":true,"delete_branch_on_merge":true,"allow_update_branch":true,"archived":false},"labels":{"include":[{"name":"bug","color":"CC0000","description":"An issue with the system"},{"name":"feature","color":"#336699","description":"New functionality."},{"name":"first-timers-only","oldname":"Help Wanted","color":"#326699"},{"name":"new-label","oldname":"Help Wanted","color":"#326699"}],"exclude":[{"name":"^release"}]},"milestones":[{"title":"milestone-title","description":"milestone-description","state":"open"}],"collaborators":[{"username":"regpaco","permission":"push"},{"username":"beetlejuice","permission":"pull","exclude":["actions-demo"]},{"username":"thor","permission":"push","include":["actions-demo","another-repo"]}],"teams":[{"name":"core","permission":"admin"},{"name":"docss","permission":"push"},{"name":"docs","permission":"pull"},{"name":"globalteam","permission":"push","visibility":"closed"}],"branches":[{"name":"default","protection":{"required_pull_request_reviews":{"required_approving_review_count":1,"dismiss_stale_reviews":true,"require_code_owner_reviews":true,"require_last_push_approval":true,"bypass_pull_request_allowances":{"apps":[],"users":[],"teams":[]},"dismissal_restrictions":{"users":[],"teams":[]}},"required_status_checks":{"strict":true,"contexts":[]},"enforce_admins":true,"restrictions":{"apps":[],"users":[],"teams":[]}}}],"custom_properties":[{"name":"test","value":"test"}],"autolinks":[{"key_prefix":"JIRA-","url_template":"https://jira.github.com/browse/JIRA-","is_alphanumeric":false},{"key_prefix":"MYLINK-","url_template":"https://mywebsite.com/"}],"validator":{"pattern":"[a-zA-Z0-9_-]+"},"rulesets":[{"name":"Template","target":"branch","enforcement":"active","bypass_actors":[{"actor_id":"number","actor_type":"Team","bypass_mode":"pull_request"},{"actor_id":1,"actor_type":"OrganizationAdmin","bypass_mode":"always"},{"actor_id":7898,"actor_type":"RepositoryRole","bypass_mode":"always"},{"actor_id":210920,"actor_type":"Integration","bypass_mode":"always"}],"conditions":{"ref_name":{"include":["~DEFAULT_BRANCH"],"exclude":["refs/heads/oldmaster"]},"repository_name":{"include":["test*"],"exclude":["test","test1"],"protected":true}},"rules":[{"type":"creation"},{"type":"update","parameters":{"update_allows_fetch_and_merge":true}},{"type":"deletion"},{"type":"required_linear_history"},{"type":"required_signatures"},{"type":"required_deployments","parameters":{"required_deployment_environments":["staging"]}},{"type":"pull_request","parameters":{"dismiss_stale_reviews_on_push":true,"require_code_owner_review":true,"require_last_push_approval":true,"required_approving_review_count":1,"required_review_thread_resolution":true}},{"type":"required_status_checks","parameters":{"strict_required_status_checks_policy":true,"required_status_checks":[{"context":"CodeQL","integration_id":1234},{"context":"GHAS Compliance","integration_id":1234}]}},{"type":"workflows","parameters":{"workflows":[{"path":".github/workflows/example.yml","repository_id":123456,"ref":"refs/heads/main","sha":"1234567890abcdef"}]}},{"type":"commit_message_pattern","parameters":{"name":"test commit_message_pattern","negate":true,"operator":"starts_with","pattern":"skip*"}},{"type":"commit_author_email_pattern","parameters":{"name":"test commit_author_email_pattern","negate":false,"operator":"regex","pattern":"^.*@example.com$"}},{"type":"committer_email_pattern","parameters":{"name":"test committer_email_pattern","negate":false,"operator":"regex","pattern":"^.*@example.com$"}},{"type":"branch_name_pattern","parameters":{"name":"test branch_name_pattern","negate":false,"operator":"regex","pattern":".*/.*"}},{"type":"tag_name_pattern","parameters":{"name":"test tag_name_pattern","negate":false,"operator":"regex","pattern":".*/.*"}}]}]} +2025-09-11T15:49:24.081Z [INFO] Pull request closed on Safe-Settings Hub: (jefeish-training/safe-settings-config-master) +2025-09-11T15:49:24.081Z [INFO] Received 'pull_request.closed' event: 47 +2025-09-11T15:49:24.356Z [INFO] Files changed in PR #47: .github/safe-settings/globals/suborg.yml +2025-09-11T15:49:24.356Z [DEBUG] Detected changes in the globals folder. Routing to syncHubGlobalsUpdate(...). +2025-09-11T15:49:24.357Z [INFO] Syncing safe settings for 'globals/'. +2025-09-11T15:49:24.617Z [DEBUG] Loaded manifest.yml rules from hub repo:{ + "rules": [ + { + "name": "global-defaults", + "targets": [ + "*" + ], + "files": [ + "*.yml" + ], + "mergeStrategy": "merge" + }, + { + "name": "security-policies", + "targets": [ + "acme-*", + "foo-bar" + ], + "files": [ + "settings.yml" + ], + "mergeStrategy": "overwrite" + } + ] +} +2025-09-11T15:49:24.814Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'jetest99' with mergeStrategy='merge' +2025-09-11T15:49:24.814Z [DEBUG] Evaluating globals file: .github/safe-settings/globals/suborg.yml +2025-09-11T15:49:24.814Z [DEBUG] Rule 'global-defaults' matches file 'suborg.yml'. Targets: jetest99, jefeish-training, jefeish-test1, copilot-for-emus, jefeish-migration-test, decyjphr-training, decyjphr-emu +2025-09-11T15:49:25.155Z [DEBUG] Is Admin repo event false +2025-09-11T15:49:25.155Z [DEBUG] Not working on the Admin repo, returning... +2025-09-11T15:49:25.341Z [DEBUG] Checking existence of .github/suborg.yml in jetest99/safe-settings-config +2025-09-11T15:49:25.565Z [DEBUG] Found .github/suborg.yml in jetest99/safe-settings-config +2025-09-11T15:49:25.566Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'jefeish-training' with mergeStrategy='merge' +2025-09-11T15:49:25.566Z [INFO] Skipping sync of suborg.yml to jetest99 (already exists & mergeStrategy=merge) +2025-09-11T15:49:25.935Z [DEBUG] Checking existence of .github/suborg.yml in jefeish-training/safe-settings-config +2025-09-11T15:49:26.172Z [DEBUG] Found .github/suborg.yml in jefeish-training/safe-settings-config +2025-09-11T15:49:26.173Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'jefeish-test1' with mergeStrategy='merge' +2025-09-11T15:49:26.173Z [INFO] Skipping sync of suborg.yml to jefeish-training (already exists & mergeStrategy=merge) +2025-09-11T15:49:26.524Z [DEBUG] Checking existence of .github/suborg.yml in jefeish-test1/safe-settings-config +2025-09-11T15:49:26.777Z [DEBUG] Found .github/suborg.yml in jefeish-test1/safe-settings-config +2025-09-11T15:49:26.777Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'copilot-for-emus' with mergeStrategy='merge' +2025-09-11T15:49:26.777Z [INFO] Skipping sync of suborg.yml to jefeish-test1 (already exists & mergeStrategy=merge) +2025-09-11T15:49:26.964Z [INFO] Skipping org copilot-for-emus: config repo 'safe-settings-config' does not exist. +2025-09-11T15:49:26.964Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'jefeish-migration-test' with mergeStrategy='merge' +2025-09-11T15:49:27.285Z [DEBUG] Checking existence of .github/suborg.yml in jefeish-migration-test/safe-settings-config +2025-09-11T15:49:27.487Z [DEBUG] Found .github/suborg.yml in jefeish-migration-test/safe-settings-config +2025-09-11T15:49:27.487Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'decyjphr-training' with mergeStrategy='merge' +2025-09-11T15:49:27.487Z [INFO] Skipping sync of suborg.yml to jefeish-migration-test (already exists & mergeStrategy=merge) +2025-09-11T15:49:27.661Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'decyjphr-emu' with mergeStrategy='merge' +2025-09-11T15:49:27.661Z [INFO] Skipping org decyjphr-training: config repo 'safe-settings-config' does not exist. +2025-09-11T15:49:27.830Z [INFO] Skipping org decyjphr-emu: config repo 'safe-settings-config' does not exist. +2025-09-11T15:50:54.611Z [DEBUG] Repository member edited by {"login":"fabrikam-safe-settings[bot]","id":223158109,"node_id":"BOT_kgDODU0fXQ","avatar_url":"https://avatars.githubusercontent.com/b/5789?v=4","gravatar_id":"","url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D","html_url":"https://github.com/apps/fabrikam-safe-settings","followers_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/followers","following_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/repos","events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false} +2025-09-11T15:50:54.611Z [DEBUG] Repository member edited by Bot +2025-09-11T15:50:55.683Z [DEBUG] Repository member edited by Bot +2025-09-11T15:50:55.683Z [DEBUG] Repository member edited by {"login":"fabrikam-safe-settings[bot]","id":223158109,"node_id":"BOT_kgDODU0fXQ","avatar_url":"https://avatars.githubusercontent.com/b/5789?v=4","gravatar_id":"","url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D","html_url":"https://github.com/apps/fabrikam-safe-settings","followers_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/followers","following_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/repos","events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false} +2025-09-11T15:50:56.556Z [DEBUG] Repository member edited by {"login":"fabrikam-safe-settings[bot]","id":223158109,"node_id":"BOT_kgDODU0fXQ","avatar_url":"https://avatars.githubusercontent.com/b/5789?v=4","gravatar_id":"","url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D","html_url":"https://github.com/apps/fabrikam-safe-settings","followers_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/followers","following_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/repos","events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false} +2025-09-11T15:50:56.556Z [DEBUG] Repository member edited by Bot +2025-09-11T15:50:57.768Z [DEBUG] Repository member edited by Bot +2025-09-11T15:50:57.768Z [DEBUG] Repository member edited by {"login":"fabrikam-safe-settings[bot]","id":223158109,"node_id":"BOT_kgDODU0fXQ","avatar_url":"https://avatars.githubusercontent.com/b/5789?v=4","gravatar_id":"","url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D","html_url":"https://github.com/apps/fabrikam-safe-settings","followers_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/followers","following_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/repos","events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false} +2025-09-11T15:51:20.652Z [DEBUG] Branch Protection edited by {"login":"fabrikam-safe-settings[bot]","id":223158109,"node_id":"BOT_kgDODU0fXQ","avatar_url":"https://avatars.githubusercontent.com/b/5789?v=4","gravatar_id":"","url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D","html_url":"https://github.com/apps/fabrikam-safe-settings","followers_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/followers","following_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/repos","events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false} +2025-09-11T15:51:20.652Z [DEBUG] Branch Protection edited by Bot +2025-09-11T15:51:24.560Z [DEBUG] Not triggered by Safe-settings... +2025-09-11T15:51:24.559Z [DEBUG] Check run was created! +2025-09-11T15:51:35.514Z [DEBUG] Branch Protection edited by Bot +2025-09-11T15:51:35.514Z [DEBUG] Branch Protection edited by {"login":"fabrikam-safe-settings[bot]","id":223158109,"node_id":"BOT_kgDODU0fXQ","avatar_url":"https://avatars.githubusercontent.com/b/5789?v=4","gravatar_id":"","url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D","html_url":"https://github.com/apps/fabrikam-safe-settings","followers_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/followers","following_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/repos","events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false} +2025-09-11T15:51:36.562Z [DEBUG] Not triggered by Safe-settings... +2025-09-11T15:51:36.562Z [DEBUG] Check run was created! +2025-09-11T15:53:20.953Z [INFO] Starting commit and sync status fetch for copilot-for-emus,decyjphr-emu,decyjphr-training,jefeish-migration-test,jefeish-test1,jefeish-training,jetest99 organizations... +2025-09-11T15:53:22.397Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-migration-test: .github/safe-settings/organizations/jefeish-migration-test +2025-09-11T15:53:22.397Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-migration-test: main +2025-09-11T15:53:22.397Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-migration-test +2025-09-11T15:53:22.628Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-11T15:53:22.628Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-11T15:53:22.628Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-migration-test, + ref: main +2025-09-11T15:53:22.859Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-migration-test +2025-09-11T15:53:22.859Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-11T15:53:23.452Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-test1 +2025-09-11T15:53:23.452Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-test1: main +2025-09-11T15:53:23.452Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-test1: .github/safe-settings/organizations/jefeish-test1 +2025-09-11T15:53:23.691Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-11T15:53:23.691Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-test1, + ref: main +2025-09-11T15:53:23.691Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-11T15:53:23.944Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-test1 +2025-09-11T15:53:23.944Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-11T15:53:24.491Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-training: main +2025-09-11T15:53:24.491Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-training: .github/safe-settings/organizations/jefeish-training +2025-09-11T15:53:24.491Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-training +2025-09-11T15:53:24.733Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-11T15:53:24.733Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-11T15:53:24.733Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-training, + ref: main +2025-09-11T15:53:25.050Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/settings.yml, hubFile: .github/safe-settings/organizations/jefeish-training/settings.yml +2025-09-11T15:53:25.050Z [DEBUG] 6. [SYNC DEBUG] Hub hubFilesResp file names: settings.yml, suborg.yml +2025-09-11T15:53:25.516Z [DEBUG] 10. [SYNC DEBUG] Hub hash: efd3489f6ad8fd9d572bbcfeded6ee3c49104dc5478b370c6adde5184e57865e +2025-09-11T15:53:25.516Z [DEBUG] 8. [SYNC DEBUG] Comparing file: settings.yml +2025-09-11T15:53:25.516Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/suborg.yml, hubFile: .github/safe-settings/organizations/jefeish-training/suborg.yml +2025-09-11T15:53:25.516Z [DEBUG] 9. [SYNC DEBUG] Org hash: efd3489f6ad8fd9d572bbcfeded6ee3c49104dc5478b370c6adde5184e57865e +2025-09-11T15:53:25.985Z [DEBUG] 10. [SYNC DEBUG] Hub hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-11T15:53:25.985Z [DEBUG] 8. [SYNC DEBUG] Comparing file: suborg.yml +2025-09-11T15:53:25.985Z [DEBUG] 9. [SYNC DEBUG] Org hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-11T15:53:26.589Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jetest99: main +2025-09-11T15:53:26.589Z [DEBUG] 3. [SYNC DEBUG] Org: jetest99 +2025-09-11T15:53:26.589Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jetest99: .github/safe-settings/organizations/jetest99 +2025-09-11T15:53:26.825Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jetest99, + ref: main +2025-09-11T15:53:26.825Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-11T15:53:26.825Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-11T15:53:27.058Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jetest99 +2025-09-11T15:53:27.058Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-11T15:55:44.833Z [INFO] Retrieving settings from org: jetest99 +2025-09-11T15:55:45.087Z [INFO] Skipping jetest99: already present in hub +2025-09-11T15:55:45.087Z [INFO] Retrieving settings from org: jefeish-training +2025-09-11T15:55:45.298Z [INFO] Retrieving settings from org: jefeish-test1 +2025-09-11T15:55:45.298Z [INFO] Skipping jefeish-training: already present in hub +2025-09-11T15:55:45.551Z [INFO] Retrieving settings from org: copilot-for-emus +2025-09-11T15:55:45.551Z [INFO] Skipping jefeish-test1: already present in hub +2025-09-11T15:55:46.001Z [INFO] Retrieving settings from org: jefeish-migration-test +2025-09-11T15:55:46.292Z [INFO] Retrieving settings from org: decyjphr-training +2025-09-11T15:55:46.292Z [INFO] Skipping jefeish-migration-test: already present in hub +2025-09-11T15:55:46.556Z [INFO] Skipping decyjphr-training: already present in hub +2025-09-11T15:55:46.556Z [INFO] Retrieving settings from org: decyjphr-emu +2025-09-11T15:56:33.309Z [INFO] Starting commit and sync status fetch for copilot-for-emus,decyjphr-emu,decyjphr-training,jefeish-migration-test,jefeish-test1,jefeish-training,jetest99 organizations... +2025-09-11T15:56:34.976Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-migration-test: .github/safe-settings/organizations/jefeish-migration-test +2025-09-11T15:56:34.976Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-migration-test: main +2025-09-11T15:56:34.976Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-migration-test +2025-09-11T15:56:35.221Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-migration-test, + ref: main +2025-09-11T15:56:35.221Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-11T15:56:35.221Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-11T15:56:35.434Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-migration-test +2025-09-11T15:56:35.434Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-11T15:56:36.047Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-test1: main +2025-09-11T15:56:36.047Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-test1: .github/safe-settings/organizations/jefeish-test1 +2025-09-11T15:56:36.047Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-test1 +2025-09-11T15:56:36.273Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-11T15:56:36.273Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-11T15:56:36.273Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-test1, + ref: main +2025-09-11T15:56:36.514Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-11T15:56:36.514Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-test1 +2025-09-11T15:56:37.018Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-training +2025-09-11T15:56:37.018Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-training: main +2025-09-11T15:56:37.018Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-training: .github/safe-settings/organizations/jefeish-training +2025-09-11T15:56:37.239Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-11T15:56:37.239Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-training, + ref: main +2025-09-11T15:56:37.239Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-11T15:56:37.445Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/settings.yml, hubFile: .github/safe-settings/organizations/jefeish-training/settings.yml +2025-09-11T15:56:37.445Z [DEBUG] 6. [SYNC DEBUG] Hub hubFilesResp file names: settings.yml, suborg.yml +2025-09-11T15:56:37.914Z [DEBUG] 8. [SYNC DEBUG] Comparing file: settings.yml +2025-09-11T15:56:37.914Z [DEBUG] 10. [SYNC DEBUG] Hub hash: efd3489f6ad8fd9d572bbcfeded6ee3c49104dc5478b370c6adde5184e57865e +2025-09-11T15:56:37.914Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/suborg.yml, hubFile: .github/safe-settings/organizations/jefeish-training/suborg.yml +2025-09-11T15:56:37.914Z [DEBUG] 9. [SYNC DEBUG] Org hash: efd3489f6ad8fd9d572bbcfeded6ee3c49104dc5478b370c6adde5184e57865e +2025-09-11T15:56:38.412Z [DEBUG] 9. [SYNC DEBUG] Org hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-11T15:56:38.412Z [DEBUG] 10. [SYNC DEBUG] Hub hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-11T15:56:38.412Z [DEBUG] 8. [SYNC DEBUG] Comparing file: suborg.yml +2025-09-11T15:56:38.977Z [DEBUG] 3. [SYNC DEBUG] Org: jetest99 +2025-09-11T15:56:38.977Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jetest99: main +2025-09-11T15:56:38.977Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jetest99: .github/safe-settings/organizations/jetest99 +2025-09-11T15:56:39.247Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-11T15:56:39.247Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-11T15:56:39.247Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jetest99, + ref: main +2025-09-11T15:56:39.484Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-11T15:56:39.485Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jetest99 +2025-09-11T15:56:51.776Z [INFO] Starting commit and sync status fetch for copilot-for-emus,decyjphr-emu,decyjphr-training,jefeish-migration-test,jefeish-test1,jefeish-training,jetest99 organizations... +2025-09-11T15:56:53.217Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-migration-test +2025-09-11T15:56:53.217Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-migration-test: main +2025-09-11T15:56:53.217Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-migration-test: .github/safe-settings/organizations/jefeish-migration-test +2025-09-11T15:56:53.436Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-11T15:56:53.436Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-11T15:56:53.436Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-migration-test, + ref: main +2025-09-11T15:56:53.666Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-migration-test +2025-09-11T15:56:53.666Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-11T15:56:54.354Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-test1: main +2025-09-11T15:56:54.354Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-test1: .github/safe-settings/organizations/jefeish-test1 +2025-09-11T15:56:54.354Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-test1 +2025-09-11T15:56:54.566Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-11T15:56:54.566Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-test1, + ref: main +2025-09-11T15:56:54.566Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-11T15:56:54.792Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-11T15:56:54.792Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-test1 +2025-09-11T15:56:55.340Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-training +2025-09-11T15:56:55.340Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-training: .github/safe-settings/organizations/jefeish-training +2025-09-11T15:56:55.340Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-training: main +2025-09-11T15:56:55.563Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-11T15:56:55.563Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-11T15:56:55.563Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-training, + ref: main +2025-09-11T15:56:55.807Z [DEBUG] 6. [SYNC DEBUG] Hub hubFilesResp file names: settings.yml, suborg.yml +2025-09-11T15:56:55.808Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/settings.yml, hubFile: .github/safe-settings/organizations/jefeish-training/settings.yml +2025-09-11T15:56:56.233Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/suborg.yml, hubFile: .github/safe-settings/organizations/jefeish-training/suborg.yml +2025-09-11T15:56:56.233Z [DEBUG] 10. [SYNC DEBUG] Hub hash: efd3489f6ad8fd9d572bbcfeded6ee3c49104dc5478b370c6adde5184e57865e +2025-09-11T15:56:56.233Z [DEBUG] 9. [SYNC DEBUG] Org hash: efd3489f6ad8fd9d572bbcfeded6ee3c49104dc5478b370c6adde5184e57865e +2025-09-11T15:56:56.233Z [DEBUG] 8. [SYNC DEBUG] Comparing file: settings.yml +2025-09-11T15:56:56.688Z [DEBUG] 8. [SYNC DEBUG] Comparing file: suborg.yml +2025-09-11T15:56:56.688Z [DEBUG] 9. [SYNC DEBUG] Org hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-11T15:56:56.688Z [DEBUG] 10. [SYNC DEBUG] Hub hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-11T15:56:57.315Z [DEBUG] 3. [SYNC DEBUG] Org: jetest99 +2025-09-11T15:56:57.315Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jetest99: main +2025-09-11T15:56:57.314Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jetest99: .github/safe-settings/organizations/jetest99 +2025-09-11T15:56:57.525Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-11T15:56:57.525Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-11T15:56:57.525Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jetest99, + ref: main +2025-09-11T15:56:57.745Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-11T15:56:57.745Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jetest99 +2025-09-12T01:30:19.210Z [DEBUG] Check run was created! +2025-09-12T01:30:19.210Z [DEBUG] Not triggered by Safe-settings... +2025-09-13T22:42:46.364Z [INFO] Starting commit and sync status fetch for copilot-for-emus,decyjphr-emu,decyjphr-training,jefeish-migration-test,jefeish-test1,jefeish-training,jetest99 organizations... +2025-09-13T22:42:48.651Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-migration-test +2025-09-13T22:42:48.651Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-migration-test: main +2025-09-13T22:42:48.651Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-migration-test: .github/safe-settings/organizations/jefeish-migration-test +2025-09-13T22:42:48.902Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-13T22:42:48.902Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-13T22:42:48.902Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-migration-test, + ref: main +2025-09-13T22:42:49.124Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-13T22:42:49.124Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-migration-test +2025-09-13T22:42:49.899Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-test1: .github/safe-settings/organizations/jefeish-test1 +2025-09-13T22:42:49.899Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-test1 +2025-09-13T22:42:49.899Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-test1: main +2025-09-13T22:42:50.157Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-13T22:42:50.157Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-13T22:42:50.157Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-test1, + ref: main +2025-09-13T22:42:50.373Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-test1 +2025-09-13T22:42:50.373Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-13T22:42:51.186Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-training: .github/safe-settings/organizations/jefeish-training +2025-09-13T22:42:51.186Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-training +2025-09-13T22:42:51.186Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-training: main +2025-09-13T22:42:51.533Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-13T22:42:51.533Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-training, + ref: main +2025-09-13T22:42:51.533Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-13T22:42:51.901Z [DEBUG] 6. [SYNC DEBUG] Hub hubFilesResp file names: settings.yml, suborg.yml +2025-09-13T22:42:51.901Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/settings.yml, hubFile: .github/safe-settings/organizations/jefeish-training/settings.yml +2025-09-13T22:42:52.487Z [DEBUG] 9. [SYNC DEBUG] Org hash: efd3489f6ad8fd9d572bbcfeded6ee3c49104dc5478b370c6adde5184e57865e +2025-09-13T22:42:52.487Z [DEBUG] 10. [SYNC DEBUG] Hub hash: efd3489f6ad8fd9d572bbcfeded6ee3c49104dc5478b370c6adde5184e57865e +2025-09-13T22:42:52.487Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/suborg.yml, hubFile: .github/safe-settings/organizations/jefeish-training/suborg.yml +2025-09-13T22:42:52.487Z [DEBUG] 8. [SYNC DEBUG] Comparing file: settings.yml +2025-09-13T22:42:52.978Z [DEBUG] 9. [SYNC DEBUG] Org hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-13T22:42:52.979Z [DEBUG] 10. [SYNC DEBUG] Hub hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-13T22:42:52.978Z [DEBUG] 8. [SYNC DEBUG] Comparing file: suborg.yml +2025-09-13T22:42:53.877Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jetest99: main +2025-09-13T22:42:53.877Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jetest99: .github/safe-settings/organizations/jetest99 +2025-09-13T22:42:53.877Z [DEBUG] 3. [SYNC DEBUG] Org: jetest99 +2025-09-13T22:42:54.131Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-13T22:42:54.131Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-13T22:42:54.131Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jetest99, + ref: main +2025-09-13T22:42:54.397Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-13T22:42:54.397Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jetest99 +2025-09-13T22:43:30.372Z [INFO] Starting commit and sync status fetch for copilot-for-emus,decyjphr-emu,decyjphr-training,jefeish-migration-test,jefeish-test1,jefeish-training,jetest99 organizations... +2025-09-13T22:43:34.138Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-migration-test: .github/safe-settings/organizations/jefeish-migration-test +2025-09-13T22:43:34.138Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-migration-test +2025-09-13T22:43:34.138Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-migration-test: main +2025-09-13T22:43:34.350Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-13T22:43:34.350Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-migration-test, + ref: main +2025-09-13T22:43:34.350Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-13T22:43:34.574Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-13T22:43:34.574Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-migration-test +2025-09-13T22:43:35.156Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-test1 +2025-09-13T22:43:35.156Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-test1: .github/safe-settings/organizations/jefeish-test1 +2025-09-13T22:43:35.156Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-test1: main +2025-09-13T22:43:35.390Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-13T22:43:35.390Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-13T22:43:35.390Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-test1, + ref: main +2025-09-13T22:43:35.778Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-13T22:43:35.778Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-test1 +2025-09-13T22:43:36.334Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-training: .github/safe-settings/organizations/jefeish-training +2025-09-13T22:43:36.334Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-training: main +2025-09-13T22:43:36.334Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-training +2025-09-13T22:43:36.548Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-13T22:43:36.548Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-13T22:43:36.548Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-training, + ref: main +2025-09-13T22:43:36.780Z [DEBUG] 6. [SYNC DEBUG] Hub hubFilesResp file names: settings.yml, suborg.yml +2025-09-13T22:43:36.780Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/settings.yml, hubFile: .github/safe-settings/organizations/jefeish-training/settings.yml +2025-09-13T22:43:37.236Z [DEBUG] 9. [SYNC DEBUG] Org hash: efd3489f6ad8fd9d572bbcfeded6ee3c49104dc5478b370c6adde5184e57865e +2025-09-13T22:43:37.236Z [DEBUG] 10. [SYNC DEBUG] Hub hash: efd3489f6ad8fd9d572bbcfeded6ee3c49104dc5478b370c6adde5184e57865e +2025-09-13T22:43:37.236Z [DEBUG] 8. [SYNC DEBUG] Comparing file: settings.yml +2025-09-13T22:43:37.236Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/suborg.yml, hubFile: .github/safe-settings/organizations/jefeish-training/suborg.yml +2025-09-13T22:43:37.666Z [DEBUG] 9. [SYNC DEBUG] Org hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-13T22:43:37.666Z [DEBUG] 8. [SYNC DEBUG] Comparing file: suborg.yml +2025-09-13T22:43:37.666Z [DEBUG] 10. [SYNC DEBUG] Hub hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-13T22:43:38.247Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jetest99: .github/safe-settings/organizations/jetest99 +2025-09-13T22:43:38.247Z [DEBUG] 3. [SYNC DEBUG] Org: jetest99 +2025-09-13T22:43:38.247Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jetest99: main +2025-09-13T22:43:38.457Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-13T22:43:38.457Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jetest99, + ref: main +2025-09-13T22:43:38.457Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-13T22:43:38.763Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-13T22:43:38.763Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jetest99 +2025-09-16T15:47:20.575Z [INFO] Received 'pull_request.closed' event: 10 +2025-09-16T15:47:20.577Z [INFO] Pull request.closed is not from master admin repo/org (jefeish-training/safe-settings-config-master), ignoring +2025-09-16T15:47:20.664Z [DEBUG] Changes in '.github/settings.yml' detected, doing a full synch... +2025-09-16T15:47:20.664Z [DEBUG] deploymentConfig is {"restrictedRepos":["admin",".github","safe-settings"]} +2025-09-16T15:47:20.966Z [DEBUG] config for ref undefined is {"restrictedRepos":["admin",".github","safe-settings"],"repository":{"description":"description of the repo","homepage":"https://example.github.io/","auto_init":true,"topics":["new-topic","another-topic"],"security":{"enableVulnerabilityAlerts":true,"enableAutomatedSecurityFixes":true},"private":true,"visibility":"private","has_issues":true,"has_projects":true,"has_wiki":true,"default_branch":"main","gitignore_template":"node","license_template":"mit","allow_squash_merge":true,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_auto_merge":true,"delete_branch_on_merge":true,"allow_update_branch":true,"archived":false},"labels":{"include":[{"name":"bug","color":"CC0000","description":"An issue with the system"},{"name":"feature","color":"#336699","description":"New functionality."},{"name":"first-timers-only","oldname":"Help Wanted","color":"#326699"},{"name":"new-label","oldname":"Help Wanted","color":"#326699"}],"exclude":[{"name":"^release"}]},"milestones":[{"title":"milestone-title","description":"milestone-description","state":"open"}],"collaborators":[{"username":"regpaco","permission":"push"},{"username":"beetlejuice","permission":"pull","exclude":["actions-demo"]},{"username":"thor","permission":"push","include":["actions-demo","another-repo"]}],"teams":[{"name":"core","permission":"admin"},{"name":"docss","permission":"push"},{"name":"docs","permission":"pull"},{"name":"globalteam","permission":"push","visibility":"closed"}],"custom_properties":[{"name":"test","value":"test"}],"autolinks":[{"key_prefix":"JIRA-","url_template":"https://jira.github.com/browse/JIRA-","is_alphanumeric":false},{"key_prefix":"MYLINK-","url_template":"https://mywebsite.com/"}],"validator":{"pattern":"[a-zA-Z0-9_-]+"}} +2025-09-16T15:47:21.579Z [DEBUG] Is Admin repo event true +2025-09-16T15:47:21.579Z [DEBUG] Working on the default branch, returning... +2025-09-16T15:47:22.242Z [DEBUG] Branch Protection edited by Bot +2025-09-16T15:47:22.242Z [DEBUG] Branch Protection edited by {"login":"fabrikam-safe-settings[bot]","id":223158109,"node_id":"BOT_kgDODU0fXQ","avatar_url":"https://avatars.githubusercontent.com/b/5789?v=4","gravatar_id":"","url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D","html_url":"https://github.com/apps/fabrikam-safe-settings","followers_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/followers","following_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/repos","events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/fabrikam-safe-settings%5Bbot%5D/received_events","type":"Bot","user_view_type":"public","site_admin":false} +2025-09-16T15:47:22.349Z [DEBUG] Not working on the default branch, returning... +2025-09-16T15:47:23.391Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T15:47:23.391Z [DEBUG] Check run was created! +2025-09-16T15:47:37.290Z [DEBUG] Branch Protection edited by a Human +2025-09-16T15:47:37.290Z [DEBUG] Branch Protection edited by {"login":"jefeish_fabrikam","id":90713677,"node_id":"MDQ6VXNlcjkwNzEzNjc3","avatar_url":"https://avatars.githubusercontent.com/u/90713677?v=4","gravatar_id":"","url":"https://api.github.com/users/jefeish_fabrikam","html_url":"https://github.com/jefeish_fabrikam","followers_url":"https://api.github.com/users/jefeish_fabrikam/followers","following_url":"https://api.github.com/users/jefeish_fabrikam/following{/other_user}","gists_url":"https://api.github.com/users/jefeish_fabrikam/gists{/gist_id}","starred_url":"https://api.github.com/users/jefeish_fabrikam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jefeish_fabrikam/subscriptions","organizations_url":"https://api.github.com/users/jefeish_fabrikam/orgs","repos_url":"https://api.github.com/users/jefeish_fabrikam/repos","events_url":"https://api.github.com/users/jefeish_fabrikam/events{/privacy}","received_events_url":"https://api.github.com/users/jefeish_fabrikam/received_events","type":"User","user_view_type":"public","site_admin":false} +2025-09-16T15:47:37.290Z [DEBUG] deploymentConfig is {"restrictedRepos":["admin",".github","safe-settings"]} +2025-09-16T15:47:37.812Z [DEBUG] config for ref undefined is {"restrictedRepos":["admin",".github","safe-settings"],"repository":{"description":"description of the repo","homepage":"https://example.github.io/","auto_init":true,"topics":["new-topic","another-topic"],"security":{"enableVulnerabilityAlerts":true,"enableAutomatedSecurityFixes":true},"private":true,"visibility":"private","has_issues":true,"has_projects":true,"has_wiki":true,"default_branch":"main","gitignore_template":"node","license_template":"mit","allow_squash_merge":true,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_auto_merge":true,"delete_branch_on_merge":true,"allow_update_branch":true,"archived":false},"labels":{"include":[{"name":"bug","color":"CC0000","description":"An issue with the system"},{"name":"feature","color":"#336699","description":"New functionality."},{"name":"first-timers-only","oldname":"Help Wanted","color":"#326699"},{"name":"new-label","oldname":"Help Wanted","color":"#326699"}],"exclude":[{"name":"^release"}]},"milestones":[{"title":"milestone-title","description":"milestone-description","state":"open"}],"collaborators":[{"username":"regpaco","permission":"push"},{"username":"beetlejuice","permission":"pull","exclude":["actions-demo"]},{"username":"thor","permission":"push","include":["actions-demo","another-repo"]}],"teams":[{"name":"core","permission":"admin"},{"name":"docss","permission":"push"},{"name":"docs","permission":"pull"},{"name":"globalteam","permission":"push","visibility":"closed"}],"custom_properties":[{"name":"test","value":"test"}],"autolinks":[{"key_prefix":"JIRA-","url_template":"https://jira.github.com/browse/JIRA-","is_alphanumeric":false},{"key_prefix":"MYLINK-","url_template":"https://mywebsite.com/"}],"validator":{"pattern":"[a-zA-Z0-9_-]+"}} +2025-09-16T15:48:45.963Z [DEBUG] Branch Protection edited by a Human +2025-09-16T15:48:45.963Z [DEBUG] deploymentConfig is {"restrictedRepos":["admin",".github","safe-settings"]} +2025-09-16T15:48:45.963Z [DEBUG] Branch Protection edited by {"login":"jefeish_fabrikam","id":90713677,"node_id":"MDQ6VXNlcjkwNzEzNjc3","avatar_url":"https://avatars.githubusercontent.com/u/90713677?v=4","gravatar_id":"","url":"https://api.github.com/users/jefeish_fabrikam","html_url":"https://github.com/jefeish_fabrikam","followers_url":"https://api.github.com/users/jefeish_fabrikam/followers","following_url":"https://api.github.com/users/jefeish_fabrikam/following{/other_user}","gists_url":"https://api.github.com/users/jefeish_fabrikam/gists{/gist_id}","starred_url":"https://api.github.com/users/jefeish_fabrikam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jefeish_fabrikam/subscriptions","organizations_url":"https://api.github.com/users/jefeish_fabrikam/orgs","repos_url":"https://api.github.com/users/jefeish_fabrikam/repos","events_url":"https://api.github.com/users/jefeish_fabrikam/events{/privacy}","received_events_url":"https://api.github.com/users/jefeish_fabrikam/received_events","type":"User","user_view_type":"public","site_admin":false} +2025-09-16T15:48:46.215Z [DEBUG] config for ref undefined is {"restrictedRepos":["admin",".github","safe-settings"],"repository":{"description":"description of the repo","homepage":"https://example.github.io/","auto_init":true,"topics":["new-topic","another-topic"],"security":{"enableVulnerabilityAlerts":true,"enableAutomatedSecurityFixes":true},"private":true,"visibility":"private","has_issues":true,"has_projects":true,"has_wiki":true,"default_branch":"main","gitignore_template":"node","license_template":"mit","allow_squash_merge":true,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_auto_merge":true,"delete_branch_on_merge":true,"allow_update_branch":true,"archived":false},"labels":{"include":[{"name":"bug","color":"CC0000","description":"An issue with the system"},{"name":"feature","color":"#336699","description":"New functionality."},{"name":"first-timers-only","oldname":"Help Wanted","color":"#326699"},{"name":"new-label","oldname":"Help Wanted","color":"#326699"}],"exclude":[{"name":"^release"}]},"milestones":[{"title":"milestone-title","description":"milestone-description","state":"open"}],"collaborators":[{"username":"regpaco","permission":"push"},{"username":"beetlejuice","permission":"pull","exclude":["actions-demo"]},{"username":"thor","permission":"push","include":["actions-demo","another-repo"]}],"teams":[{"name":"core","permission":"admin"},{"name":"docss","permission":"push"},{"name":"docs","permission":"pull"},{"name":"globalteam","permission":"push","visibility":"closed"}],"custom_properties":[{"name":"test","value":"test"}],"autolinks":[{"key_prefix":"JIRA-","url_template":"https://jira.github.com/browse/JIRA-","is_alphanumeric":false},{"key_prefix":"MYLINK-","url_template":"https://mywebsite.com/"}],"validator":{"pattern":"[a-zA-Z0-9_-]+"}} +2025-09-16T15:50:41.987Z [DEBUG] Check run was created! +2025-09-16T15:50:41.988Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T15:50:48.685Z [DEBUG] deploymentConfig is {"restrictedRepos":["admin",".github","safe-settings"]} +2025-09-16T15:50:48.685Z [DEBUG] Branch Protection edited by {"login":"jefeish_fabrikam","id":90713677,"node_id":"MDQ6VXNlcjkwNzEzNjc3","avatar_url":"https://avatars.githubusercontent.com/u/90713677?v=4","gravatar_id":"","url":"https://api.github.com/users/jefeish_fabrikam","html_url":"https://github.com/jefeish_fabrikam","followers_url":"https://api.github.com/users/jefeish_fabrikam/followers","following_url":"https://api.github.com/users/jefeish_fabrikam/following{/other_user}","gists_url":"https://api.github.com/users/jefeish_fabrikam/gists{/gist_id}","starred_url":"https://api.github.com/users/jefeish_fabrikam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jefeish_fabrikam/subscriptions","organizations_url":"https://api.github.com/users/jefeish_fabrikam/orgs","repos_url":"https://api.github.com/users/jefeish_fabrikam/repos","events_url":"https://api.github.com/users/jefeish_fabrikam/events{/privacy}","received_events_url":"https://api.github.com/users/jefeish_fabrikam/received_events","type":"User","user_view_type":"public","site_admin":false} +2025-09-16T15:50:48.685Z [DEBUG] Branch Protection edited by a Human +2025-09-16T15:50:48.941Z [DEBUG] config for ref undefined is {"restrictedRepos":["admin",".github","safe-settings"],"repository":{"description":"description of the repo","homepage":"https://example.github.io/","auto_init":true,"topics":["new-topic","another-topic"],"security":{"enableVulnerabilityAlerts":true,"enableAutomatedSecurityFixes":true},"private":true,"visibility":"private","has_issues":true,"has_projects":true,"has_wiki":true,"default_branch":"main","gitignore_template":"node","license_template":"mit","allow_squash_merge":true,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_auto_merge":true,"delete_branch_on_merge":true,"allow_update_branch":true,"archived":false},"labels":{"include":[{"name":"bug","color":"CC0000","description":"An issue with the system"},{"name":"feature","color":"#336699","description":"New functionality."},{"name":"first-timers-only","oldname":"Help Wanted","color":"#326699"},{"name":"new-label","oldname":"Help Wanted","color":"#326699"}],"exclude":[{"name":"^release"}]},"milestones":[{"title":"milestone-title","description":"milestone-description","state":"open"}],"collaborators":[{"username":"regpaco","permission":"push"},{"username":"beetlejuice","permission":"pull","exclude":["actions-demo"]},{"username":"thor","permission":"push","include":["actions-demo","another-repo"]}],"teams":[{"name":"core","permission":"admin"},{"name":"docss","permission":"push"},{"name":"docs","permission":"pull"},{"name":"globalteam","permission":"push","visibility":"closed"}],"custom_properties":[{"name":"test","value":"test"}],"autolinks":[{"key_prefix":"JIRA-","url_template":"https://jira.github.com/browse/JIRA-","is_alphanumeric":false},{"key_prefix":"MYLINK-","url_template":"https://mywebsite.com/"}],"validator":{"pattern":"[a-zA-Z0-9_-]+"}} +2025-09-16T15:51:28.479Z [DEBUG] Check run was created! +2025-09-16T15:51:28.479Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T15:51:31.307Z [DEBUG] Check run was created! +2025-09-16T15:51:31.307Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T15:51:50.461Z [DEBUG] Check run was created! +2025-09-16T15:51:50.461Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T15:51:51.381Z [DEBUG] Check run was created! +2025-09-16T15:51:51.381Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T15:58:35.618Z [DEBUG] Branch Protection edited by a Human +2025-09-16T15:58:35.618Z [DEBUG] deploymentConfig is {"restrictedRepos":["admin",".github","safe-settings"]} +2025-09-16T15:58:35.618Z [DEBUG] Branch Protection edited by {"login":"jefeish_fabrikam","id":90713677,"node_id":"MDQ6VXNlcjkwNzEzNjc3","avatar_url":"https://avatars.githubusercontent.com/u/90713677?v=4","gravatar_id":"","url":"https://api.github.com/users/jefeish_fabrikam","html_url":"https://github.com/jefeish_fabrikam","followers_url":"https://api.github.com/users/jefeish_fabrikam/followers","following_url":"https://api.github.com/users/jefeish_fabrikam/following{/other_user}","gists_url":"https://api.github.com/users/jefeish_fabrikam/gists{/gist_id}","starred_url":"https://api.github.com/users/jefeish_fabrikam/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jefeish_fabrikam/subscriptions","organizations_url":"https://api.github.com/users/jefeish_fabrikam/orgs","repos_url":"https://api.github.com/users/jefeish_fabrikam/repos","events_url":"https://api.github.com/users/jefeish_fabrikam/events{/privacy}","received_events_url":"https://api.github.com/users/jefeish_fabrikam/received_events","type":"User","user_view_type":"public","site_admin":false} +2025-09-16T15:58:36.135Z [DEBUG] config for ref undefined is {"restrictedRepos":["admin",".github","safe-settings"],"repository":{"description":"description of the repo","homepage":"https://example.github.io/","auto_init":true,"topics":["new-topic","another-topic"],"security":{"enableVulnerabilityAlerts":true,"enableAutomatedSecurityFixes":true},"private":true,"visibility":"private","has_issues":true,"has_projects":true,"has_wiki":true,"default_branch":"main","gitignore_template":"node","license_template":"mit","allow_squash_merge":true,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_auto_merge":true,"delete_branch_on_merge":true,"allow_update_branch":true,"archived":false},"labels":{"include":[{"name":"bug","color":"CC0000","description":"An issue with the system"},{"name":"feature","color":"#336699","description":"New functionality."},{"name":"first-timers-only","oldname":"Help Wanted","color":"#326699"},{"name":"new-label","oldname":"Help Wanted","color":"#326699"}],"exclude":[{"name":"^release"}]},"milestones":[{"title":"milestone-title","description":"milestone-description","state":"open"}],"collaborators":[{"username":"regpaco","permission":"push"},{"username":"beetlejuice","permission":"pull","exclude":["actions-demo"]},{"username":"thor","permission":"push","include":["actions-demo","another-repo"]}],"teams":[{"name":"core","permission":"admin"},{"name":"docss","permission":"push"},{"name":"docs","permission":"pull"},{"name":"globalteam","permission":"push","visibility":"closed"}],"custom_properties":[{"name":"test","value":"test"}],"autolinks":[{"key_prefix":"JIRA-","url_template":"https://jira.github.com/browse/JIRA-","is_alphanumeric":false},{"key_prefix":"MYLINK-","url_template":"https://mywebsite.com/"}],"validator":{"pattern":"[a-zA-Z0-9_-]+"}} +2025-09-16T15:58:45.832Z [DEBUG] Check run was created! +2025-09-16T15:58:45.832Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T15:58:45.867Z [DEBUG] Check run was created! +2025-09-16T15:58:45.867Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T15:58:46.449Z [DEBUG] Is Admin repo event false +2025-09-16T15:58:46.449Z [DEBUG] Not working on the Admin repo, returning... +2025-09-16T15:58:46.724Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T15:58:46.724Z [DEBUG] Check run was created! +2025-09-16T15:58:49.218Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T15:58:49.218Z [DEBUG] Check run was created! +2025-09-16T16:01:29.319Z [DEBUG] Check run was created! +2025-09-16T16:01:29.319Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T17:13:27.125Z [DEBUG] Not working on the Admin repo, returning... +2025-09-16T17:13:27.125Z [DEBUG] Is Admin repo event false +2025-09-16T17:13:28.760Z [DEBUG] Check run was created! +2025-09-16T17:13:28.760Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T17:13:28.848Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T17:13:28.848Z [DEBUG] Check run was created! +2025-09-16T17:13:28.918Z [DEBUG] Check run was created! +2025-09-16T17:13:28.918Z [DEBUG] Not triggered by Safe-settings... +2025-09-16T17:14:33.551Z [DEBUG] Check run was created! +2025-09-16T17:14:33.551Z [DEBUG] Not triggered by Safe-settings... +2025-09-18T14:09:14.208Z [INFO] Pull request closed on Safe-Settings Hub: (jefeish-training/safe-settings-config-master) +2025-09-18T14:09:14.208Z [INFO] Received 'pull_request.closed' event: 48 +2025-09-18T14:09:14.585Z [INFO] Files changed in PR #48: .github/safe-settings/globals/manifest.yml +2025-09-18T14:09:14.585Z [DEBUG] Detected changes in the globals folder. Routing to syncHubGlobalsUpdate(...). +2025-09-18T14:09:14.586Z [INFO] Syncing safe settings for 'globals/'. +2025-09-18T14:09:14.924Z [DEBUG] Loaded manifest.yml rules from hub repo:{ + "rules": [ + { + "name": "global-defaults", + "targets": [ + "*" + ], + "files": [ + "*.yml" + ], + "mergeStrategy": "merge" + }, + { + "name": "security-policies", + "targets": [ + "acme-*", + "foo-bar" + ], + "files": [ + "settings.yml" + ], + "mergeStrategy": "overwrite" + } + ] +} +2025-09-18T14:09:15.216Z [DEBUG] Skipping sync for manifest.yml (should only exist in hub) +2025-09-18T14:09:15.672Z [DEBUG] Check run was created! +2025-09-18T14:09:15.672Z [DEBUG] Not triggered by Safe-settings... +2025-09-18T14:09:23.527Z [DEBUG] Not working on the Admin repo, returning... +2025-09-18T14:09:23.526Z [DEBUG] Is Admin repo event false +2025-09-18T14:13:27.105Z [DEBUG] Is Admin repo event false +2025-09-18T14:13:27.105Z [DEBUG] Not working on the Admin repo, returning... +2025-09-18T14:13:56.822Z [DEBUG] Is Admin repo event false +2025-09-18T14:13:56.822Z [DEBUG] Not working on the Admin repo, returning... +2025-09-18T14:13:56.822Z [DEBUG] Pull_request opened ! +2025-09-18T14:14:06.314Z [INFO] Pull request closed on Safe-Settings Hub: (jefeish-training/safe-settings-config-master) +2025-09-18T14:14:06.314Z [INFO] Received 'pull_request.closed' event: 49 +2025-09-18T14:14:07.042Z [INFO] Files changed in PR #49: .github/safe-settings/organizations/jefeish-training/settings.yml +2025-09-18T14:14:07.043Z [INFO] Syncing safe settings for organization: jefeish-training +2025-09-18T14:14:07.042Z [DEBUG] Detected changes in the organizations folder. Routing to syncHubOrgUpdate(...). +2025-09-18T14:14:07.043Z [INFO] Organization: jefeish-training, Destination Repo: safe-settings-config, Destination Folder: .github +2025-09-18T14:14:07.042Z [INFO] Orgs updated in PR #49: jefeish-training +2025-09-18T14:14:07.043Z [INFO] DEBUG: sourceBase='.github/safe-settings/organizations' +2025-09-18T14:14:07.043Z [INFO] DEBUG: env.CONFIG_PATH='.github', env.SAFE_SETTINGS_HUB_PATH='safe-settings' +2025-09-18T14:14:07.333Z [INFO] DEBUG: PR #49 contains 1 changed file(s) +2025-09-18T14:14:07.333Z [INFO] DEBUG: file[0] keys = sha, filename, status, additions, deletions, changes, blob_url, raw_url, contents_url, patch +2025-09-18T14:14:07.333Z [INFO] DEBUG: first file object = { + "sha": "8f345e9e4d6701accc0d39f587d00950c9a17ed5", + "filename": ".github/safe-settings/organizations/jefeish-training/settings.yml", + "status": "modified", + "additions": 175, + "deletions": 175, + "changes": 350, + "blob_url": "https://github.com/jefeish-training/safe-settings-config-master/blob/ee5e72b1fcb62dda5d16fd244fe36bb072589455/.github%2Fsafe-settings%2Forganizations%2Fjefeish-training%2Fsettings.yml", + "raw_url": "https://github.com/jefeish-training/safe-settings-config-master/raw/ee5e72b1fcb62dda5d16fd244fe36bb072589455/.github%2Fsafe-settings%2Forganizations%2Fjefeish-training%2Fsettings.yml", + "contents_url": "https://api.github.com/repos/jefeish-training/safe-settings-config-master/contents/.github%2Fsafe-settings%2Forganizations%2Fjefeish-training%2Fsettings.yml?ref=ee5e72b1fcb62dda5d16fd244fe36bb072589455", + "patch": "@@ -222,178 +222,178 @@ validator:\n \n # Rulesets\n # See https://docs.github.com/en/rest/orgs/rules?apiVersion=2022-11-28#create-an-organization-repository-rulesetfor available options\n-rulesets:\n- - name: Template\n- # The target of the ruleset. Can be one of:\n- # - branch\n- # - tag\n- target: branch\n- # The enforcement level of the ruleset. `evaluate` allows admins to test\n- # rules before enforcing them.\n- # - disabled\n- # - active\n- # - evaluate\n- enforcement: active\n-\n- # The actors that can bypass the rules in this ruleset\n- bypass_actors:\n- - actor_id: number\n- # type: The type of actor that can bypass a ruleset\n- # - RepositoryRole\n- # - Team\n- # - Integration\n- # - OrganizationAdmin\n- actor_type: Team\n- # When the specified actor can bypass the ruleset. `pull_request`\n- # means that an actor can only bypass rules on pull requests.\n- # - always\n- # - pull_request\n- bypass_mode: pull_request\n-\n- - actor_id: 1\n- actor_type: OrganizationAdmin\n- bypass_mode: always\n-\n- - actor_id: 7898\n- actor_type: RepositoryRole\n- bypass_mode: always\n-\n- - actor_id: 210920\n- actor_type: Integration\n- bypass_mode: always\n-\n- conditions:\n- # Parameters for a repository ruleset ref name condition\n- ref_name:\n- # Array of ref names or patterns to include. One of these\n- # patterns must match for the condition to pass. Also accepts\n- # `~DEFAULT_BRANCH` to include the default branch or `~ALL` to\n- # include all branches.\n- include: [\"~DEFAULT_BRANCH\"]\n-\n- # Array of ref names or patterns to exclude. The condition\n- # will not pass if any of these patterns match.\n- exclude: [\"refs/heads/oldmaster\"]\n-\n- # This condition only exists at the org level (remove for suborg and repo level rulesets)\n- repository_name:\n- # Array of repository names or patterns to include.\n- # One of these patterns must match for the condition\n- # to pass. Also accepts `~ALL` to include all\n- # repositories.\n- include: [\"test*\"]\n- # Array of repository names or patterns to exclude. The\n- # condition will not pass if any of these patterns\n- # match.\n- exclude: [\"test\", \"test1\"]\n- # Whether renaming of target repositories is\n- # prevented.\n- protected: true\n-\n- # Refer to https://docs.github.com/en/rest/orgs/rules#create-an-organization-repository-ruleset\n- rules:\n- - type: creation\n- - type: update\n- parameters:\n- # Branch can pull changes from its upstream repository\n- update_allows_fetch_and_merge: true\n- - type: deletion\n- - type: required_linear_history\n- - type: required_signatures\n-\n- - type: required_deployments\n- parameters:\n- required_deployment_environments: [\"staging\"]\n-\n- - type: pull_request\n- parameters:\n- # Reviewable commits pushed will dismiss previous pull\n- # request review approvals.\n- dismiss_stale_reviews_on_push: true\n- # Require an approving review in pull requests that modify\n- # files that have a designated code owner\n- require_code_owner_review: true\n- # Whether the most recent reviewable push must be approved\n- # by someone other than the person who pushed it.\n- require_last_push_approval: true\n- # The number of approving reviews that are required before a\n- # pull request can be merged.\n- required_approving_review_count: 1\n- # All conversations on code must be resolved before a pull\n- # request can be merged.\n- required_review_thread_resolution: true\n-\n- # Choose which status checks must pass before branches can be merged\n- # into a branch that matches this rule. When enabled, commits must\n- # first be pushed to another branch, then merged or pushed directly\n- # to a branch that matches this rule after status checks have\n- # passed.\n- - type: required_status_checks\n- parameters:\n- # Whether pull requests targeting a matching branch must be\n- # tested with the latest code. This setting will not take\n- # effect unless at least one status check is enabled.\n- strict_required_status_checks_policy: true\n- required_status_checks:\n- - context: CodeQL\n- integration_id: 1234\n- - context: GHAS Compliance\n- integration_id: 1234\n-\n- # Choose which workflows must pass before branches can be merged.\n- - type: workflows\n- parameters:\n- workflows:\n- - path: .github/workflows/example.yml\n- # Run $(\"meta[name=octolytics-dimension-repository_id]\").getAttribute('content')\n- # in the browser console of the repository to get the repository_id\n- repository_id: 123456\n- # One of the following:\n- # Branch or tag\n- ref: refs/heads/main\n- # Commit SHA\n- sha: 1234567890abcdef\n-\n- - type: commit_message_pattern\n- parameters:\n- name: test commit_message_pattern\n- # required:\n- # - operator\n- # - pattern\n- negate: true\n- operator: starts_with\n- # The operator to use for matching.\n- # - starts_with\n- # - ends_with\n- # - contains\n- # - regex\n- pattern: skip*\n- # The pattern to match with.\n-\n- - type: commit_author_email_pattern\n- parameters:\n- name: test commit_author_email_pattern\n- negate: false\n- operator: regex\n- pattern: \"^.*@example.com$\"\n-\n- - type: committer_email_pattern\n- parameters:\n- name: test committer_email_pattern\n- negate: false\n- operator: regex\n- pattern: \"^.*@example.com$\"\n-\n- - type: branch_name_pattern\n- parameters:\n- name: test branch_name_pattern\n- negate: false\n- operator: regex\n- pattern: \".*\\/.*\"\n-\n- - type: \"tag_name_pattern\"\n- parameters:\n- name: test tag_name_pattern\n- negate: false\n- operator: regex\n- pattern: \".*\\/.*\"\n+# rulesets:\n+# - name: Template\n+# # The target of the ruleset. Can be one of:\n+# # - branch\n+# # - tag\n+# target: branch\n+# # The enforcement level of the ruleset. `evaluate` allows admins to test\n+# # rules before enforcing them.\n+# # - disabled\n+# # - active\n+# # - evaluate\n+# enforcement: active\n+\n+# # The actors that can bypass the rules in this ruleset\n+# bypass_actors:\n+# - actor_id: number\n+# # type: The type of actor that can bypass a ruleset\n+# # - RepositoryRole\n+# # - Team\n+# # - Integration\n+# # - OrganizationAdmin\n+# actor_type: Team\n+# # When the specified actor can bypass the ruleset. `pull_request`\n+# # means that an actor can only bypass rules on pull requests.\n+# # - always\n+# # - pull_request\n+# bypass_mode: pull_request\n+\n+# - actor_id: 1\n+# actor_type: OrganizationAdmin\n+# bypass_mode: always\n+\n+# - actor_id: 7898\n+# actor_type: RepositoryRole\n+# bypass_mode: always\n+\n+# - actor_id: 210920\n+# actor_type: Integration\n+# bypass_mode: always\n+\n+# conditions:\n+# # Parameters for a repository ruleset ref name condition\n+# ref_name:\n+# # Array of ref names or patterns to include. One of these\n+# # patterns must match for the condition to pass. Also accepts\n+# # `~DEFAULT_BRANCH` to include the default branch or `~ALL` to\n+# # include all branches.\n+# include: [\"~DEFAULT_BRANCH\"]\n+\n+# # Array of ref names or patterns to exclude. The condition\n+# # will not pass if any of these patterns match.\n+# exclude: [\"refs/heads/oldmaster\"]\n+\n+# # This condition only exists at the org level (remove for suborg and repo level rulesets)\n+# repository_name:\n+# # Array of repository names or patterns to include.\n+# # One of these patterns must match for the condition\n+# # to pass. Also accepts `~ALL` to include all\n+# # repositories.\n+# include: [\"test*\"]\n+# # Array of repository names or patterns to exclude. The\n+# # condition will not pass if any of these patterns\n+# # match.\n+# exclude: [\"test\", \"test1\"]\n+# # Whether renaming of target repositories is\n+# # prevented.\n+# protected: true\n+\n+# # Refer to https://docs.github.com/en/rest/orgs/rules#create-an-organization-repository-ruleset\n+# rules:\n+# - type: creation\n+# - type: update\n+# parameters:\n+# # Branch can pull changes from its upstream repository\n+# update_allows_fetch_and_merge: true\n+# - type: deletion\n+# - type: required_linear_history\n+# - type: required_signatures\n+\n+# - type: required_deployments\n+# parameters:\n+# required_deployment_environments: [\"staging\"]\n+\n+# - type: pull_request\n+# parameters:\n+# # Reviewable commits pushed will dismiss previous pull\n+# # request review approvals.\n+# dismiss_stale_reviews_on_push: true\n+# # Require an approving review in pull requests that modify\n+# # files that have a designated code owner\n+# require_code_owner_review: true\n+# # Whether the most recent reviewable push must be approved\n+# # by someone other than the person who pushed it.\n+# require_last_push_approval: true\n+# # The number of approving reviews that are required before a\n+# # pull request can be merged.\n+# required_approving_review_count: 1\n+# # All conversations on code must be resolved before a pull\n+# # request can be merged.\n+# required_review_thread_resolution: true\n+\n+# # Choose which status checks must pass before branches can be merged\n+# # into a branch that matches this rule. When enabled, commits must\n+# # first be pushed to another branch, then merged or pushed directly\n+# # to a branch that matches this rule after status checks have\n+# # passed.\n+# - type: required_status_checks\n+# parameters:\n+# # Whether pull requests targeting a matching branch must be\n+# # tested with the latest code. This setting will not take\n+# # effect unless at least one status check is enabled.\n+# strict_required_status_checks_policy: true\n+# required_status_checks:\n+# - context: CodeQL\n+# integration_id: 1234\n+# - context: GHAS Compliance\n+# integration_id: 1234\n+\n+# # Choose which workflows must pass before branches can be merged.\n+# - type: workflows\n+# parameters:\n+# workflows:\n+# - path: .github/workflows/example.yml\n+# # Run $(\"meta[name=octolytics-dimension-repository_id]\").getAttribute('content')\n+# # in the browser console of the repository to get the repository_id\n+# repository_id: 123456\n+# # One of the following:\n+# # Branch or tag\n+# ref: refs/heads/main\n+# # Commit SHA\n+# sha: 1234567890abcdef\n+\n+# - type: commit_message_pattern\n+# parameters:\n+# name: test commit_message_pattern\n+# # required:\n+# # - operator\n+# # - pattern\n+# negate: true\n+# operator: starts_with\n+# # The operator to use for matching.\n+# # - starts_with\n+# # - ends_with\n+# # - contains\n+# # - regex\n+# pattern: skip*\n+# # The pattern to match with.\n+\n+# - type: commit_author_email_pattern\n+# parameters:\n+# name: test commit_author_email_pattern\n+# negate: false\n+# operator: regex\n+# pattern: \"^.*@example.com$\"\n+\n+# - type: committer_email_pattern\n+# parameters:\n+# name: test committer_email_pattern\n+# negate: false\n+# operator: regex\n+# pattern: \"^.*@example.com$\"\n+\n+# - type: branch_name_pattern\n+# parameters:\n+# name: test branch_name_pattern\n+# negate: false\n+# operator: regex\n+# pattern: \".*\\/.*\"\n+\n+# - type: \"tag_name_pattern\"\n+# parameters:\n+# name: test tag_name_pattern\n+# negate: false\n+# operator: regex\n+# pattern: \".*\\/.*\"" +} +2025-09-18T14:14:07.333Z [INFO] DEBUG: files=.github/safe-settings/organizations/jefeish-training/settings.yml +2025-09-18T14:14:07.334Z [INFO] DEBUG: FILE[0] raw={"sha":"8f345e9e4d6701accc0d39f587d00950c9a17ed5","filename":".github/safe-settings/organizations/jefeish-training/settings.yml","status":"modified","additions":175,"deletions":175,"changes":350,"blob_url":"https://github.com/jefeish-training/safe-settings-config-master/blob/ee5e72b1fcb62dda5d16fd244fe36bb072589455/.github%2Fsafe-settings%2Forganizations%2Fjefeish-training%2Fsettings.yml","raw_url":"https://github.com/jefeish-training/safe-settings-config-master/raw/ee5e72b1fcb62dda5d16fd244fe36bb072589455/.github%2Fsafe-settings%2Forganizations%2Fjefeish-training%2Fsettings.yml","contents_url":"https://api.github.com/repos/jefeish-training/safe-settings-config-master/contents/.github%2Fsafe-settings%2Forganizations%2Fjefeish-training%2Fsettings.yml?ref=ee5e72b1fcb62dda5d16fd244fe36bb072589455","patch":"@@ -222,178 +222,178 @@ validator:\n \n # Rulesets\n # See https://docs.github.com/en/rest/orgs/rules?apiVersion=2022-11-28#create-an-organization-repository-rulesetfor available options\n-rulesets:\n- - name: Template\n- # The target of the ruleset. Can be one of:\n- # - branch\n- # - tag\n- target: branch\n- # The enforcement level of the ruleset. `evaluate` allows admins to test\n- # rules before enforcing them.\n- # - disabled\n- # - active\n- # - evaluate\n- enforcement: active\n-\n- # The actors that can bypass the rules in this ruleset\n- bypass_actors:\n- - actor_id: number\n- # type: The type of actor that can bypass a ruleset\n- # - RepositoryRole\n- # - Team\n- # - Integration\n- # - OrganizationAdmin\n- actor_type: Team\n- # When the specified actor can bypass the ruleset. `pull_request`\n- # means that an actor can only bypass rules on pull requests.\n- # - always\n- # - pull_request\n- bypass_mode: pull_request\n-\n- - actor_id: 1\n- actor_type: OrganizationAdmin\n- bypass_mode: always\n-\n- - actor_id: 7898\n- actor_type: RepositoryRole\n- bypass_mode: always\n-\n- - actor_id: 210920\n- actor_type: Integration\n- bypass_mode: always\n-\n- conditions:\n- # Parameters for a repository ruleset ref name condition\n- ref_name:\n- # Array of ref names or patterns to include. One of these\n- # patterns must match for the condition to pass. Also accepts\n- # `~DEFAULT_BRANCH` to include the default branch or `~ALL` to\n- # include all branches.\n- include: [\"~DEFAULT_BRANCH\"]\n-\n- # Array of ref names or patterns to exclude. The condition\n- # will not pass if any of these patterns match.\n- exclude: [\"refs/heads/oldmaster\"]\n-\n- # This condition only exists at the org level (remove for suborg and repo level rulesets)\n- repository_name:\n- # Array of repository names or patterns to include.\n- # One of these patterns must match for the condition\n- # to pass. Also accepts `~ALL` to include all\n- # repositories.\n- include: [\"test*\"]\n- # Array of repository names or patterns to exclude. The\n- # condition will not pass if any of these patterns\n- # match.\n- exclude: [\"test\", \"test1\"]\n- # Whether renaming of target repositories is\n- # prevented.\n- protected: true\n-\n- # Refer to https://docs.github.com/en/rest/orgs/rules#create-an-organization-repository-ruleset\n- rules:\n- - type: creation\n- - type: update\n- parameters:\n- # Branch can pull changes from its upstream repository\n- update_allows_fetch_and_merge: true\n- - type: deletion\n- - type: required_linear_history\n- - type: required_signatures\n-\n- - type: required_deployments\n- parameters:\n- required_deployment_environments: [\"staging\"]\n-\n- - type: pull_request\n- parameters:\n- # Reviewable commits pushed will dismiss previous pull\n- # request review approvals.\n- dismiss_stale_reviews_on_push: true\n- # Require an approving review in pull requests that modify\n- # files that have a designated code owner\n- require_code_owner_review: true\n- # Whether the most recent reviewable push must be approved\n- # by someone other than the person who pushed it.\n- require_last_push_approval: true\n- # The number of approving reviews that are required before a\n- # pull request can be merged.\n- required_approving_review_count: 1\n- # All conversations on code must be resolved before a pull\n- # request can be merged.\n- required_review_thread_resolution: true\n-\n- # Choose which status checks must pass before branches can be merged\n- # into a branch that matches this rule. When enabled, commits must\n- # first be pushed to another branch, then merged or pushed directly\n- # to a branch that matches this rule after status checks have\n- # passed.\n- - type: required_status_checks\n- parameters:\n- # Whether pull requests targeting a matching branch must be\n- # tested with the latest code. This setting will not take\n- # effect unless at least one status check is enabled.\n- strict_required_status_checks_policy: true\n- required_status_checks:\n- - context: CodeQL\n- integration_id: 1234\n- - context: GHAS Compliance\n- integration_id: 1234\n-\n- # Choose which workflows must pass before branches can be merged.\n- - type: workflows\n- parameters:\n- workflows:\n- - path: .github/workflows/example.yml\n- # Run $(\"meta[name=octolytics-dimension-repository_id]\").getAttribute('content')\n- # in the browser console of the repository to get the repository_id\n- repository_id: 123456\n- # One of the following:\n- # Branch or tag\n- ref: refs/heads/main\n- # Commit SHA\n- sha: 1234567890abcdef\n-\n- - type: commit_message_pattern\n- parameters:\n- name: test commit_message_pattern\n- # required:\n- # - operator\n- # - pattern\n- negate: true\n- operator: starts_with\n- # The operator to use for matching.\n- # - starts_with\n- # - ends_with\n- # - contains\n- # - regex\n- pattern: skip*\n- # The pattern to match with.\n-\n- - type: commit_author_email_pattern\n- parameters:\n- name: test commit_author_email_pattern\n- negate: false\n- operator: regex\n- pattern: \"^.*@example.com$\"\n-\n- - type: committer_email_pattern\n- parameters:\n- name: test committer_email_pattern\n- negate: false\n- operator: regex\n- pattern: \"^.*@example.com$\"\n-\n- - type: branch_name_pattern\n- parameters:\n- name: test branch_name_pattern\n- negate: false\n- operator: regex\n- pattern: \".*\\/.*\"\n-\n- - type: \"tag_name_pattern\"\n- parameters:\n- name: test tag_name_pattern\n- negate: false\n- operator: regex\n- pattern: \".*\\/.*\"\n+# rulesets:\n+# - name: Template\n+# # The target of the ruleset. Can be one of:\n+# # - branch\n+# # - tag\n+# target: branch\n+# # The enforcement level of the ruleset. `evaluate` allows admins to test\n+# # rules before enforcing them.\n+# # - disabled\n+# # - active\n+# # - evaluate\n+# enforcement: active\n+\n+# # The actors that can bypass the rules in this ruleset\n+# bypass_actors:\n+# - actor_id: number\n+# # type: The type of actor that can bypass a ruleset\n+# # - RepositoryRole\n+# # - Team\n+# # - Integration\n+# # - OrganizationAdmin\n+# actor_type: Team\n+# # When the specified actor can bypass the ruleset. `pull_request`\n+# # means that an actor can only bypass rules on pull requests.\n+# # - always\n+# # - pull_request\n+# bypass_mode: pull_request\n+\n+# - actor_id: 1\n+# actor_type: OrganizationAdmin\n+# bypass_mode: always\n+\n+# - actor_id: 7898\n+# actor_type: RepositoryRole\n+# bypass_mode: always\n+\n+# - actor_id: 210920\n+# actor_type: Integration\n+# bypass_mode: always\n+\n+# conditions:\n+# # Parameters for a repository ruleset ref name condition\n+# ref_name:\n+# # Array of ref names or patterns to include. One of these\n+# # patterns must match for the condition to pass. Also accepts\n+# # `~DEFAULT_BRANCH` to include the default branch or `~ALL` to\n+# # include all branches.\n+# include: [\"~DEFAULT_BRANCH\"]\n+\n+# # Array of ref names or patterns to exclude. The condition\n+# # will not pass if any of these patterns match.\n+# exclude: [\"refs/heads/oldmaster\"]\n+\n+# # This condition only exists at the org level (remove for suborg and repo level rulesets)\n+# repository_name:\n+# # Array of repository names or patterns to include.\n+# # One of these patterns must match for the condition\n+# # to pass. Also accepts `~ALL` to include all\n+# # repositories.\n+# include: [\"test*\"]\n+# # Array of repository names or patterns to exclude. The\n+# # condition will not pass if any of these patterns\n+# # match.\n+# exclude: [\"test\", \"test1\"]\n+# # Whether renaming of target repositories is\n+# # prevented.\n+# protected: true\n+\n+# # Refer to https://docs.github.com/en/rest/orgs/rules#create-an-organization-repository-ruleset\n+# rules:\n+# - type: creation\n+# - type: update\n+# parameters:\n+# # Branch can pull changes from its upstream repository\n+# update_allows_fetch_and_merge: true\n+# - type: deletion\n+# - type: required_linear_history\n+# - type: required_signatures\n+\n+# - type: required_deployments\n+# parameters:\n+# required_deployment_environments: [\"staging\"]\n+\n+# - type: pull_request\n+# parameters:\n+# # Reviewable commits pushed will dismiss previous pull\n+# # request review approvals.\n+# dismiss_stale_reviews_on_push: true\n+# # Require an approving review in pull requests that modify\n+# # files that have a designated code owner\n+# require_code_owner_review: true\n+# # Whether the most recent reviewable push must be approved\n+# # by someone other than the person who pushed it.\n+# require_last_push_approval: true\n+# # The number of approving reviews that are required before a\n+# # pull request can be merged.\n+# required_approving_review_count: 1\n+# # All conversations on code must be resolved before a pull\n+# # request can be merged.\n+# required_review_thread_resolution: true\n+\n+# # Choose which status checks must pass before branches can be merged\n+# # into a branch that matches this rule. When enabled, commits must\n+# # first be pushed to another branch, then merged or pushed directly\n+# # to a branch that matches this rule after status checks have\n+# # passed.\n+# - type: required_status_checks\n+# parameters:\n+# # Whether pull requests targeting a matching branch must be\n+# # tested with the latest code. This setting will not take\n+# # effect unless at least one status check is enabled.\n+# strict_required_status_checks_policy: true\n+# required_status_checks:\n+# - context: CodeQL\n+# integration_id: 1234\n+# - context: GHAS Compliance\n+# integration_id: 1234\n+\n+# # Choose which workflows must pass before branches can be merged.\n+# - type: workflows\n+# parameters:\n+# workflows:\n+# - path: .github/workflows/example.yml\n+# # Run $(\"meta[name=octolytics-dimension-repository_id]\").getAttribute('content')\n+# # in the browser console of the repository to get the repository_id\n+# repository_id: 123456\n+# # One of the following:\n+# # Branch or tag\n+# ref: refs/heads/main\n+# # Commit SHA\n+# sha: 1234567890abcdef\n+\n+# - type: commit_message_pattern\n+# parameters:\n+# name: test commit_message_pattern\n+# # required:\n+# # - operator\n+# # - pattern\n+# negate: true\n+# operator: starts_with\n+# # The operator to use for matching.\n+# # - starts_with\n+# # - ends_with\n+# # - contains\n+# # - regex\n+# pattern: skip*\n+# # The pattern to match with.\n+\n+# - type: commit_author_email_pattern\n+# parameters:\n+# name: test commit_author_email_pattern\n+# negate: false\n+# operator: regex\n+# pattern: \"^.*@example.com$\"\n+\n+# - type: committer_email_pattern\n+# parameters:\n+# name: test committer_email_pattern\n+# negate: false\n+# operator: regex\n+# pattern: \"^.*@example.com$\"\n+\n+# - type: branch_name_pattern\n+# parameters:\n+# name: test branch_name_pattern\n+# negate: false\n+# operator: regex\n+# pattern: \".*\\/.*\"\n+\n+# - type: \"tag_name_pattern\"\n+# parameters:\n+# name: test tag_name_pattern\n+# negate: false\n+# operator: regex\n+# pattern: \".*\\/.*\""} +2025-09-18T14:14:07.334Z [INFO] DEBUG: FILE[0] filename=".github/safe-settings/organizations/jefeish-training/settings.yml" length=65 +2025-09-18T14:14:07.334Z [INFO] DEBUG: files=.github/safe-settings/organizations/jefeish-training/settings.yml +2025-09-18T14:14:07.334Z [INFO] DEBUG: Path .github/safe-settings/organizations/jefeish-training +2025-09-18T14:14:07.335Z [INFO] DEBUG: Found 1 changed file(s) relevant to org jefeish-training +2025-09-18T14:14:07.536Z [INFO] Syncing from jefeish-training/safe-settings-config-master PR #49 to jefeish-training/safe-settings-config@main under .github (directPush=true) +2025-09-18T14:14:09.113Z [INFO] Committed .github/settings.yml to jefeish-training/safe-settings-config@main +2025-09-18T14:14:09.113Z [INFO] Changes pushed directly to jefeish-training/safe-settings-config@main +2025-09-18T14:14:10.074Z [DEBUG] Changes in '.github/settings.yml' detected, doing a full synch... +2025-09-18T14:14:10.074Z [DEBUG] deploymentConfig is {"restrictedRepos":["admin",".github","safe-settings"]} +2025-09-18T14:14:10.392Z [DEBUG] config for ref undefined is {"restrictedRepos":["admin",".github","safe-settings"],"repository":{"description":"description of the repo","homepage":"https://example.github.io/","auto_init":true,"topics":["new-topic","another-topic"],"security":{"enableVulnerabilityAlerts":true,"enableAutomatedSecurityFixes":true},"private":true,"visibility":"private","has_issues":true,"has_projects":true,"has_wiki":true,"default_branch":"main","gitignore_template":"node","license_template":"mit","allow_squash_merge":true,"allow_merge_commit":true,"allow_rebase_merge":true,"allow_auto_merge":true,"delete_branch_on_merge":true,"allow_update_branch":true,"archived":false},"labels":{"include":[{"name":"bug","color":"CC0000","description":"An issue with the system"},{"name":"feature","color":"#336699","description":"New functionality."},{"name":"first-timers-only","oldname":"Help Wanted","color":"#326699"},{"name":"new-label","oldname":"Help Wanted","color":"#326699"}],"exclude":[{"name":"^release"}]},"milestones":[{"title":"milestone-title","description":"milestone-description","state":"open"}],"collaborators":[{"username":"regpaco","permission":"push"},{"username":"beetlejuice","permission":"pull","exclude":["actions-demo"]},{"username":"thor","permission":"push","include":["actions-demo","another-repo"]}],"teams":[{"name":"core","permission":"admin"},{"name":"docss","permission":"push"},{"name":"docs","permission":"pull"},{"name":"globalteam","permission":"push","visibility":"closed"}],"branches":[{"name":"default","protection":{"required_pull_request_reviews":{"required_approving_review_count":1,"dismiss_stale_reviews":true,"require_code_owner_reviews":true,"require_last_push_approval":true,"bypass_pull_request_allowances":{"apps":[],"users":[],"teams":[]},"dismissal_restrictions":{"users":[],"teams":[]}},"required_status_checks":{"strict":true,"contexts":[]},"enforce_admins":true,"restrictions":{"apps":[],"users":[],"teams":[]}}}],"custom_properties":[{"name":"test","value":"test"}],"autolinks":[{"key_prefix":"JIRA-","url_template":"https://jira.github.com/browse/JIRA-","is_alphanumeric":false},{"key_prefix":"MYLINK-","url_template":"https://mywebsite.com/"}],"validator":{"pattern":"[a-zA-Z0-9_-]+"}} +2025-09-18T14:14:12.340Z [DEBUG] Is Admin repo event true +2025-09-18T14:14:12.340Z [DEBUG] Working on the default branch, returning... +2025-09-18T14:14:49.484Z [INFO] Starting commit and sync status fetch for copilot-for-emus,decyjphr-emu,decyjphr-training,jefeish-migration-test,jefeish-test1,jefeish-training,jetest99 organizations... +2025-09-18T14:14:51.219Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-migration-test: .github/safe-settings/organizations/jefeish-migration-test +2025-09-18T14:14:51.219Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-migration-test: main +2025-09-18T14:14:51.219Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-migration-test +2025-09-18T14:14:51.485Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-18T14:14:51.485Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-18T14:14:51.485Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-migration-test, + ref: main +2025-09-18T14:14:51.745Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-18T14:14:51.745Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-migration-test +2025-09-18T14:14:52.466Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-test1: main +2025-09-18T14:14:52.465Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-test1: .github/safe-settings/organizations/jefeish-test1 +2025-09-18T14:14:52.466Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-test1 +2025-09-18T14:14:52.707Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-18T14:14:52.707Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-test1, + ref: main +2025-09-18T14:14:52.707Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-18T14:14:52.991Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-18T14:14:52.992Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-test1 +2025-09-18T14:14:53.724Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-training: .github/safe-settings/organizations/jefeish-training +2025-09-18T14:14:53.724Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-training: main +2025-09-18T14:14:53.724Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-training +2025-09-18T14:14:53.987Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-18T14:14:53.987Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-training, + ref: main +2025-09-18T14:14:53.987Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-18T14:14:54.285Z [DEBUG] 6. [SYNC DEBUG] Hub hubFilesResp file names: settings.yml, suborg.yml +2025-09-18T14:14:54.285Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/settings.yml, hubFile: .github/safe-settings/organizations/jefeish-training/settings.yml +2025-09-18T14:14:54.869Z [DEBUG] 8. [SYNC DEBUG] Comparing file: settings.yml +2025-09-18T14:14:54.869Z [DEBUG] 10. [SYNC DEBUG] Hub hash: 4ebf30e70a0517fc9a5f36e9a4e087c866d9a2a791755d18bcc7bedd7e104278 +2025-09-18T14:14:54.869Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/suborg.yml, hubFile: .github/safe-settings/organizations/jefeish-training/suborg.yml +2025-09-18T14:14:54.869Z [DEBUG] 9. [SYNC DEBUG] Org hash: 4ebf30e70a0517fc9a5f36e9a4e087c866d9a2a791755d18bcc7bedd7e104278 +2025-09-18T14:14:55.440Z [DEBUG] 9. [SYNC DEBUG] Org hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-18T14:14:55.440Z [DEBUG] 8. [SYNC DEBUG] Comparing file: suborg.yml +2025-09-18T14:14:55.440Z [DEBUG] 10. [SYNC DEBUG] Hub hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-18T14:14:56.225Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jetest99: .github/safe-settings/organizations/jetest99 +2025-09-18T14:14:56.225Z [DEBUG] 3. [SYNC DEBUG] Org: jetest99 +2025-09-18T14:14:56.225Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jetest99: main +2025-09-18T14:14:56.469Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-18T14:14:56.469Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-18T14:14:56.469Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jetest99, + ref: main +2025-09-18T14:14:56.735Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-18T14:14:56.735Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jetest99 +2025-09-18T14:15:07.249Z [INFO] Starting commit and sync status fetch for copilot-for-emus,decyjphr-emu,decyjphr-training,jefeish-migration-test,jefeish-test1,jefeish-training,jetest99 organizations... +2025-09-18T14:15:08.957Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-migration-test: .github/safe-settings/organizations/jefeish-migration-test +2025-09-18T14:15:08.957Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-migration-test +2025-09-18T14:15:08.957Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-migration-test: main +2025-09-18T14:15:09.220Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-migration-test, + ref: main +2025-09-18T14:15:09.220Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-18T14:15:09.220Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-18T14:15:09.484Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-migration-test +2025-09-18T14:15:09.484Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-18T14:15:10.150Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-test1: .github/safe-settings/organizations/jefeish-test1 +2025-09-18T14:15:10.150Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-test1 +2025-09-18T14:15:10.150Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-test1: main +2025-09-18T14:15:10.394Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: README.md, settings.yml, suborg.yml +2025-09-18T14:15:10.394Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-test1, + ref: main +2025-09-18T14:15:10.394Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-18T14:15:10.648Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-18T14:15:10.648Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jefeish-test1 +2025-09-18T14:15:11.505Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jefeish-training: .github/safe-settings/organizations/jefeish-training +2025-09-18T14:15:11.505Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jefeish-training: main +2025-09-18T14:15:11.505Z [DEBUG] 3. [SYNC DEBUG] Org: jefeish-training +2025-09-18T14:15:11.761Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-18T14:15:11.761Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-18T14:15:11.761Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jefeish-training, + ref: main +2025-09-18T14:15:12.100Z [DEBUG] 6. [SYNC DEBUG] Hub hubFilesResp file names: settings.yml, suborg.yml +2025-09-18T14:15:12.100Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/settings.yml, hubFile: .github/safe-settings/organizations/jefeish-training/settings.yml +2025-09-18T14:15:13.786Z [DEBUG] 9. [SYNC DEBUG] Org hash: 4ebf30e70a0517fc9a5f36e9a4e087c866d9a2a791755d18bcc7bedd7e104278 +2025-09-18T14:15:13.786Z [DEBUG] 7. [SYNC DEBUG] Fetching file contents for org: jefeish-training, orgFile: .github/suborg.yml, hubFile: .github/safe-settings/organizations/jefeish-training/suborg.yml +2025-09-18T14:15:13.786Z [DEBUG] 10. [SYNC DEBUG] Hub hash: 4ebf30e70a0517fc9a5f36e9a4e087c866d9a2a791755d18bcc7bedd7e104278 +2025-09-18T14:15:13.786Z [DEBUG] 8. [SYNC DEBUG] Comparing file: settings.yml +2025-09-18T14:15:14.195Z [DEBUG] Not working on the Admin repo, returning... +2025-09-18T14:15:14.195Z [DEBUG] Is Admin repo event false +2025-09-18T14:15:14.354Z [DEBUG] 10. [SYNC DEBUG] Hub hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-18T14:15:14.354Z [DEBUG] 8. [SYNC DEBUG] Comparing file: suborg.yml +2025-09-18T14:15:14.354Z [DEBUG] 9. [SYNC DEBUG] Org hash: 6c1fecd3dabe4bc0677d0f21427ebc03c8af34531000a13b425c1387902b86a6 +2025-09-18T14:15:15.049Z [DEBUG] 1. [SYNC DEBUG] Hub file path for org jetest99: .github/safe-settings/organizations/jetest99 +2025-09-18T14:15:15.049Z [DEBUG] 3. [SYNC DEBUG] Org: jetest99 +2025-09-18T14:15:15.049Z [DEBUG] 2. [SYNC DEBUG] Hub file branch/ref for org jetest99: main +2025-09-18T14:15:15.316Z [DEBUG] 4. [SYNC DEBUG] Org orgFilesResp file names: settings.yml, suborg.yml +2025-09-18T14:15:15.316Z [DEBUG] 5. [SYNC DEBUG] Hub: jefeish-training +2025-09-18T14:15:15.316Z [DEBUG] 5a. [SYNC DEBUG] Fetching hub files for: + owner: jefeish-training, + repo: safe-settings-config-master, + path: .github/safe-settings/organizations/jetest99, + ref: main +2025-09-18T14:15:15.620Z [ERROR] 6a. [SYNC DEBUG] Error fetching hub files: HttpError: Not Found - https://docs.github.com/rest/repos/contents#get-repository-content +2025-09-18T14:15:15.620Z [WARN] 6b. [SYNC DEBUG] File name mismatch for org jetest99 +2025-09-18T14:21:33.248Z [INFO] Received 'pull_request.closed' event: 50 +2025-09-18T14:21:33.248Z [INFO] Pull request closed on Safe-Settings Hub: (jefeish-training/safe-settings-config-master) +2025-09-18T14:21:34.144Z [DEBUG] Not working on the Admin repo, returning... +2025-09-18T14:21:34.144Z [DEBUG] Is Admin repo event false +2025-09-18T14:21:34.212Z [DEBUG] Detected changes in the globals folder. Routing to syncHubGlobalsUpdate(...). +2025-09-18T14:21:34.212Z [INFO] Files changed in PR #50: .github/safe-settings/globals/repo.yml +2025-09-18T14:21:34.212Z [INFO] Syncing safe settings for 'globals/'. +2025-09-18T14:21:34.524Z [DEBUG] Loaded manifest.yml rules from hub repo:{ + "rules": [ + { + "name": "global-defaults", + "targets": [ + "*" + ], + "files": [ + "*.yml" + ], + "mergeStrategy": "merge" + }, + { + "name": "security-policies", + "targets": [ + "acme-*", + "foo-bar" + ], + "files": [ + "settings.yml" + ], + "mergeStrategy": "overwrite" + } + ] +} +2025-09-18T14:21:34.737Z [DEBUG] Evaluating globals file: .github/safe-settings/globals/repo.yml +2025-09-18T14:21:34.737Z [DEBUG] Preparing to sync file 'repo.yml' to org 'jetest99' with mergeStrategy='merge' +2025-09-18T14:21:34.737Z [DEBUG] Rule 'global-defaults' matches file 'repo.yml'. Targets: jetest99, jefeish-training, jefeish-test1, copilot-for-emus, jefeish-migration-test, decyjphr-training, decyjphr-emu +2025-09-18T14:21:35.425Z [DEBUG] Checking existence of .github/repo.yml in jetest99/safe-settings-config +2025-09-18T14:21:35.732Z [INFO] File .github/repo.yml not found in jetest99/safe-settings-config (this is fine for both merge strategies) +2025-09-18T14:21:35.733Z [INFO] Syncing repo.yml to jetest99 (mergeStrategy=merge) +2025-09-18T14:21:36.704Z [INFO] Committed .github/repo.yml to jetest99/safe-settings-config@main +2025-09-18T14:21:36.704Z [DEBUG] Preparing to sync file 'repo.yml' to org 'jefeish-training' with mergeStrategy='merge' +2025-09-18T14:21:36.704Z [INFO] Changes pushed directly to jetest99/safe-settings-config@main +2025-09-18T14:21:37.083Z [DEBUG] Checking existence of .github/repo.yml in jefeish-training/safe-settings-config +2025-09-18T14:21:37.357Z [INFO] File .github/repo.yml not found in jefeish-training/safe-settings-config (this is fine for both merge strategies) +2025-09-18T14:21:37.357Z [INFO] Syncing repo.yml to jefeish-training (mergeStrategy=merge) +2025-09-18T14:21:37.740Z [DEBUG] No changes in '.github/settings.yml' detected, returning... +2025-09-18T14:21:38.344Z [DEBUG] Preparing to sync file 'repo.yml' to org 'jefeish-test1' with mergeStrategy='merge' +2025-09-18T14:21:38.344Z [INFO] Changes pushed directly to jefeish-training/safe-settings-config@main +2025-09-18T14:21:38.343Z [INFO] Committed .github/repo.yml to jefeish-training/safe-settings-config@main +2025-09-18T14:21:38.554Z [DEBUG] Working on the default branch, returning... +2025-09-18T14:21:38.554Z [DEBUG] Is Admin repo event true +2025-09-18T14:21:39.149Z [DEBUG] Checking existence of .github/repo.yml in jefeish-test1/safe-settings-config +2025-09-18T14:21:39.418Z [INFO] File .github/repo.yml not found in jefeish-test1/safe-settings-config (this is fine for both merge strategies) +2025-09-18T14:21:39.418Z [INFO] Syncing repo.yml to jefeish-test1 (mergeStrategy=merge) +2025-09-18T14:21:39.482Z [DEBUG] No changes in '.github/settings.yml' detected, returning... +2025-09-18T14:21:40.470Z [INFO] Committed .github/repo.yml to jefeish-test1/safe-settings-config@main +2025-09-18T14:21:40.471Z [INFO] Changes pushed directly to jefeish-test1/safe-settings-config@main +2025-09-18T14:21:40.471Z [DEBUG] Preparing to sync file 'repo.yml' to org 'copilot-for-emus' with mergeStrategy='merge' +2025-09-18T14:21:40.690Z [DEBUG] Is Admin repo event true +2025-09-18T14:21:40.690Z [DEBUG] Working on the default branch, returning... +2025-09-18T14:21:40.962Z [DEBUG] Preparing to sync file 'repo.yml' to org 'jefeish-migration-test' with mergeStrategy='merge' +2025-09-18T14:21:40.961Z [INFO] Skipping org copilot-for-emus: config repo 'safe-settings-config' does not exist. +2025-09-18T14:21:41.228Z [DEBUG] No changes in '.github/settings.yml' detected, returning... +2025-09-18T14:21:41.575Z [DEBUG] Checking existence of .github/repo.yml in jefeish-migration-test/safe-settings-config +2025-09-18T14:21:41.832Z [INFO] File .github/repo.yml not found in jefeish-migration-test/safe-settings-config (this is fine for both merge strategies) +2025-09-18T14:21:41.832Z [INFO] Syncing repo.yml to jefeish-migration-test (mergeStrategy=merge) +2025-09-18T14:21:42.191Z [DEBUG] Working on the default branch, returning... +2025-09-18T14:21:42.191Z [DEBUG] Is Admin repo event true +2025-09-18T14:21:42.736Z [INFO] Committed .github/repo.yml to jefeish-migration-test/safe-settings-config@main +2025-09-18T14:21:42.736Z [DEBUG] Preparing to sync file 'repo.yml' to org 'decyjphr-training' with mergeStrategy='merge' +2025-09-18T14:21:42.736Z [INFO] Changes pushed directly to jefeish-migration-test/safe-settings-config@main +2025-09-18T14:21:43.197Z [DEBUG] Preparing to sync file 'repo.yml' to org 'decyjphr-emu' with mergeStrategy='merge' +2025-09-18T14:21:43.197Z [INFO] Skipping org decyjphr-training: config repo 'safe-settings-config' does not exist. +2025-09-18T14:21:43.647Z [INFO] Skipping org decyjphr-emu: config repo 'safe-settings-config' does not exist. +2025-09-18T14:21:43.751Z [DEBUG] No changes in '.github/settings.yml' detected, returning... +2025-09-18T14:21:54.154Z [DEBUG] Working on the default branch, returning... +2025-09-18T14:21:54.154Z [DEBUG] Is Admin repo event true diff --git a/index.js b/index.js index f6af26b5..26596518 100644 --- a/index.js +++ b/index.js @@ -6,11 +6,21 @@ const Glob = require('./lib/glob') const ConfigManager = require('./lib/configManager') const NopCommand = require('./lib/nopcommand') const env = require('./lib/env') +const { setupRoutes } = require('./lib/routes') +const { initCache } = require('./lib/installationCache') +const { hubSyncHandler } = require('./lib/hubSyncHandler') let deploymentConfig module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) => { let appSlug = 'safe-settings' + + // Initialize all routes (static UI + API) via centralized module + setupRoutes(robot, getRouter) + + // Initialize installation cache (env-controlled prefetch) + initCache(robot) + async function syncAllSettings (nop, context, repo = context.repo(), ref) { try { deploymentConfig = await loadYamlFileSystem() @@ -521,6 +531,19 @@ module.exports = (robot, { getRouter }, Settings = require('./lib/settings')) => return createCheckRun(context, pull_request, payload.pull_request.head.sha, payload.pull_request.head.ref) }) + /** + * @description Handle pull_request.closed events to support hub synchronization + * @param {Object} context - The context object provided by Probot + */ + robot.on('pull_request.closed', async context => { + try { + await hubSyncHandler(robot, context) + } catch (err) { + robot.log.error(`pull_request.closed handler failed: ${err && err.message ? err.message : err}`) + } + return null + }) + robot.on(['check_suite.rerequested'], async context => { robot.log.debug('Check suite was rerequested!') return createCheckRun(context) diff --git a/lib/env.js b/lib/env.js index 94c0ea74..8bea61a7 100644 --- a/lib/env.js +++ b/lib/env.js @@ -1,5 +1,11 @@ module.exports = { ADMIN_REPO: process.env.ADMIN_REPO || 'admin', + SAFE_SETTINGS_HUB_REPO: process.env.SAFE_SETTINGS_HUB_REPO || 'admin-master', + SAFE_SETTINGS_HUB_ORG: process.env.SAFE_SETTINGS_HUB_ORG || 'admin-master-org', + SAFE_SETTINGS_HUB_DIRECT_PUSH: process.env.SAFE_SETTINGS_HUB_DIRECT_PUSH || 'false', + SAFE_SETTINGS_HUB_PATH: process.env.SAFE_SETTINGS_HUB_PATH || '.github/safe-settings', + APP_ID: process.env.APP_ID || null, + PRIVATE_KEY_PATH: process.env.PRIVATE_KEY_PATH || 'private-key.pem', CONFIG_PATH: process.env.CONFIG_PATH || '.github', SETTINGS_FILE_PATH: process.env.SETTINGS_FILE_PATH || 'settings.yml', DEPLOYMENT_CONFIG_FILE_PATH: process.env.DEPLOYMENT_CONFIG_FILE || 'deployment-settings.yml', diff --git a/lib/hubSyncHandler.js b/lib/hubSyncHandler.js new file mode 100644 index 00000000..24d5b549 --- /dev/null +++ b/lib/hubSyncHandler.js @@ -0,0 +1,692 @@ +const { minimatch } = require('minimatch') +const env = require('./env') +const { getInstallations } = require('./installationCache') +const yaml = require('js-yaml') +const path = require('path') +const fs = require('fs') +const os = require('os') +const util = require('util') + +/** + * Attach a file-backed logger to robot.log that mirrors all log calls to a file. + * It preserves the original behavior and appends each log line to a file, trimming + * the file to the last `maxLines` entries (default 1000). + * + * Usage: call attachFileLogger(robot, { filePath: '/tmp/safe-settings.log', maxLines: 1000 }) + */ +function attachFileLogger (robot, options = {}) { + if (!robot || !robot.log) return + if (robot.log.__fileLoggerAttached) return + const filePath = options.filePath || process.env.SAFE_SETTINGS_LOG_FILE || path.join(process.cwd(), 'hubSyncHandler.log') + const maxLines = Number(options.maxLines || process.env.SAFE_SETTINGS_LOG_FILE_MAX_LINES || 1000) + const methods = ['info', 'warn', 'debug', 'error', 'fatal', 'trace', 'notice'] + + methods.forEach(method => { + const orig = (robot.log && robot.log[method]) ? robot.log[method].bind(robot.log) : (...args) => { /* no-op */ } + robot.log[method] = (...args) => { + // call original logger so console output still occurs + try { orig(...args) } catch (e) { /* swallow */ } + + // Build a single-line message representation + try { + const msg = args.map(a => (typeof a === 'string' ? a : util.inspect(a, { depth: 2 }))).join(' ') + const line = `${new Date().toISOString()} [${method.toUpperCase()}] ${msg}` + // append and then trim to last `maxLines` + fs.appendFile(filePath, line + os.EOL, err => { + if (err) { + try { orig(`Failed to append log to ${filePath}: ${err.message}`) } catch (e) { /* swallow */ } + return + } + // trim asynchronously + fs.promises.readFile(filePath, 'utf8').then(data => { + const lines = data.split(/\r?\n/) + // Remove a possible trailing empty line created by join + if (lines.length && lines[lines.length - 1] === '') lines.pop() + if (lines.length > maxLines) { + const tail = lines.slice(-maxLines) + return fs.promises.writeFile(filePath, tail.join(os.EOL) + os.EOL, 'utf8') + } + return Promise.resolve() + }).catch(() => { /* don't break logging on trim failures */ }) + }) + } catch (e) { + try { orig(`Failed to write log to ${filePath}: ${e && e.message ? e.message : e}`) } catch (e) { /* swallow */ } + } + } + }) + + robot.log.__fileLoggerAttached = true +} + +/** + * Get authenticated octokit client for an org installation + * @param {import('probot').Probot} robot + * @param {string} orgName + * @returns {Promise} Authenticated client or null + */ +async function getOrgInstallation (robot, orgName) { + const installs = await getInstallations(robot) + const install = installs.find(i => i.account && i.account.type === 'Organization' && i.account.login.toLowerCase() === orgName.toLowerCase()) + if (!install) { + return null + } + return await robot.auth(install.id) +} + + +// Helper to create a branch if not direct push +async function createBranchIfNeeded(githubClient, owner, repo, baseBranch, branchName, directPush, logger) { + if (!directPush) { + try { + const baseRef = await githubClient.rest.git.getRef({ owner, repo, ref: `heads/${baseBranch}` }) + const baseSha = baseRef.data.object.sha + await githubClient.rest.git.createRef({ owner, repo, ref: `refs/heads/${branchName}`, sha: baseSha }) + logger.info(`Created branch ${branchName} in ${owner}/${repo}`) + } catch (err) { + if (err.status === 422) { + logger.warn(`Branch ${branchName} already exists, continuing`) + } else { + throw err + } + } + } +} + +// Helper to create or update a file in a repo +async function createOrUpdateFile(githubClient, params, logger) { + try { + await githubClient.rest.repos.createOrUpdateFileContents(params) + logger.info(`Committed ${params.path} to ${params.owner}/${params.repo}@${params.branch}`) + } catch (err) { + logger.error(`Failed to sync file ${params.path}: ${err.message}`) + throw err + } +} + +/** + * Sync changed safe-settings organization files from the master admin PR + * into the target organization's admin repository. + * @param {import('probot').Probot} robot + * @param {import('probot').Context} context + * @param {string} orgName Destination organization login (also folder name under organizations/) + * @param {string} destRepo Destination repo name inside orgName (e.g. admin repo) + * @param {string} destinationFolder Base folder in destination repo where content lives (e.g. .github or .github/safe-settings) + */ +async function syncHubOrgUpdate (robot, context, orgName, destRepo, destinationFolder) { + attachFileLogger(robot) + try { + robot.log.info(`Syncing safe settings for organization: ${orgName}`) + robot.log.info(`Organization: ${orgName}, Destination Repo: ${destRepo}, Destination Folder: ${destinationFolder}`) + const pr = context.payload.pull_request + if (!pr) { + robot.log.warn('No pull_request payload found; aborting sync') + return + } + const { owner: srcOwner, repo: srcRepo } = context.repo() + const pull_number = pr.number + const configRoot = env.CONFIG_PATH || '.github/' + const sourceBase = (`${configRoot}/${env.SAFE_SETTINGS_HUB_PATH}/organizations`).replace(/\/$/, '') + robot.log.info(`DEBUG: sourceBase='${sourceBase}'`) + robot.log.info(`DEBUG: env.CONFIG_PATH='${env.CONFIG_PATH}', env.SAFE_SETTINGS_HUB_PATH='${env.SAFE_SETTINGS_HUB_PATH}'`) + const files = await context.octokit.paginate( + context.octokit.rest.pulls.listFiles, + { owner: srcOwner, repo: srcRepo, pull_number, per_page: 100 } + ) + robot.log.info(`DEBUG: PR #${pull_number} contains ${files.length} changed file(s)`) + if (files.length) robot.log.info(`DEBUG: files=${files.map(f => f.filename).join(', ')}`) + if (files.length) { + try { + robot.log.info(`DEBUG: first file object = ${JSON.stringify(files[0], null, 2)}`) + robot.log.info(`DEBUG: file[0] keys = ${Object.keys(files[0] || {}).join(', ')}`) + } catch (e) { + robot.log.info(`DEBUG: failed to stringify first file: ${e.message}`) + } + files.forEach((f, i) => { + try { + robot.log.info(`DEBUG: FILE[${i}] raw=${JSON.stringify(f)}`) + robot.log.info(`DEBUG: FILE[${i}] filename=${JSON.stringify(f.filename)} length=${(f.filename || '').length}`) + } catch (e) { + robot.log.info(`DEBUG: FILE[${i}] stringify error: ${e.message}`) + } + }) + } + const orgPrefix = `${sourceBase}/${orgName}/` + robot.log.info(`DEBUG: files=${files.map(f => f.filename).join(', ')}`) + robot.log.info(`DEBUG: Path ${sourceBase}/${orgName}`) + const relevant = files.filter(f => f.filename === `${sourceBase}/${orgName}` || f.filename.startsWith(orgPrefix)) + robot.log.info(`DEBUG: Found ${relevant.length} changed file(s) relevant to org ${orgName}`) + if (!relevant.length) { + robot.log.info(`No files for org ${orgName} in PR #${pull_number}`) + files.forEach(f => { + const exact = f.filename === `${sourceBase}/${orgName}` + const pref = f.filename.startsWith(orgPrefix) + robot.log.info(`MATCH CHECK: file='${f.filename}' exact=${exact} prefix=${pref}`) + }) + const altBase = `${(env.CONFIG_PATH || '.github').replace(/\/$/, '')}/organizations` + const altPrefix = `${altBase}/${orgName}/` + files.forEach(f => { + const exactAlt = f.filename === `${altBase}/${orgName}` + const prefAlt = f.filename.startsWith(altPrefix) + robot.log.info(`ALT CHECK: file='${f.filename}' exactAlt=${exactAlt} prefAlt=${prefAlt}`) + }) + return + } + const destOwner = orgName + const destBase = (destinationFolder || env.CONFIG_PATH || '.github').replace(/\/$/, '') + const destBaseBranch = 'main' + const directPush = (env.SAFE_SETTINGS_HUB_DIRECT_PUSH === 'true' || env.SAFE_SETTINGS_HUB_DIRECT_PUSH === '1') + const githubDest = await getOrgInstallation(robot, destOwner) + if (!githubDest) { + robot.log.warn(`Installation for destination org ${destOwner} not found; cannot sync`) + return + } + robot.log.info(`Syncing from ${srcOwner}/${srcRepo} PR #${pull_number} to ${destOwner}/${destRepo}@${destBaseBranch} under ${destBase} (directPush=${directPush})`) + const timestamp = Date.now() + const branchName = directPush ? destBaseBranch : `safe-settings-sync/pr-${pull_number}-${orgName}-${timestamp}` + await createBranchIfNeeded(githubDest, destOwner, destRepo, destBaseBranch, branchName, directPush, robot.log) + for (const f of relevant) { + let relative + if (f.filename === `${sourceBase}/${orgName}`) { + continue + } else { + relative = f.filename.slice(orgPrefix.length) + } + const destPath = `${destBase}/${relative}`.replace(/\/+/g, '/') + const srcContentResp = await context.octokit.rest.repos.getContent({ owner: srcOwner, repo: srcRepo, path: f.filename, ref: pr.head.sha }) + const data = srcContentResp.data + if (Array.isArray(data)) { + continue + } + const fileContent = Buffer.from(data.content, data.encoding).toString('utf8') + const encoded = Buffer.from(fileContent, 'utf8').toString('base64') + let existingSha + try { + const destGet = await githubDest.rest.repos.getContent({ owner: destOwner, repo: destRepo, path: destPath, ref: destBaseBranch }) + if (!Array.isArray(destGet.data)) existingSha = destGet.data.sha + } catch (getErr) { + if (getErr.status !== 404) throw getErr + } + await createOrUpdateFile(githubDest, { + owner: destOwner, + repo: destRepo, + path: destPath, + message: directPush ? `Direct sync safe-settings from ${srcOwner}/${srcRepo} PR #${pull_number}` : `Sync safe-settings from ${srcOwner}/${srcRepo} PR #${pull_number}`, + content: encoded, + branch: branchName, + sha: existingSha, + committer: { name: 'Safe Settings Bot', email: 'safe-settings-bot@example.com' }, + author: { name: 'Safe Settings Bot', email: 'safe-settings-bot@example.com' } + }, robot.log) + } + if (!directPush) { + try { + const prTitle = `Sync safe-settings from ${srcOwner}/${srcRepo} PR #${pull_number}` + const prBody = `Automated sync of safe-settings for ${orgName} from ${srcOwner}/${srcRepo} PR #${pull_number}.` + const created = await githubDest.rest.pulls.create({ owner: destOwner, repo: destRepo, title: prTitle, head: branchName, base: destBaseBranch, body: prBody }) + robot.log.info(`Created PR ${created.data.html_url} in ${destOwner}/${destRepo}`) + } catch (prErr) { + robot.log.error(`Failed to create PR in ${destOwner}/${destRepo}: ${prErr.message}`) + throw prErr + } + } else { + robot.log.info(`Changes pushed directly to ${destOwner}/${destRepo}@${destBaseBranch}`) + } + } catch (err) { + robot.log.error(`syncSafeSettingConfig error for org ${orgName}: ${err.message}`) + } +} + +/** + * Handle closed pull requests to sync safe-settings changes to target organizations. + * Focus on the organization and repository specified in the pull request and if they belong to the Safe-Settings Hub. + * @param {import('probot').Probot} robot + * @param {import('probot').Context} context + */ +async function hubSyncHandler (robot, context) { + attachFileLogger(robot) + const { payload } = context + const { repository, pull_request } = payload || {} + robot.log.info(`Received 'pull_request.closed' event: ${pull_request && pull_request.number}`) + try { + // Ensure the event is from the configured Safe-Settings Hub repo/org + const isMasterRepo = repository && repository.name === env.SAFE_SETTINGS_HUB_REPO + const isMasterOrg = repository && repository.owner && repository.owner.login === env.SAFE_SETTINGS_HUB_ORG + + if (!(isMasterRepo && isMasterOrg)) { + robot.log.info(`Pull request.closed is not from master admin repo/org (${env.SAFE_SETTINGS_HUB_ORG}/${env.SAFE_SETTINGS_HUB_REPO}), ignoring`) + return + } + + robot.log.info(`Pull request closed on Safe-Settings Hub: (${repository.full_name})`) + + // Get the PR details + const pr = pull_request + const { owner, repo } = context.repo() + const pull_number = pr.number + + // Paginate through all files changed in the PR + const files = await context.octokit.paginate( + context.octokit.rest.pulls.listFiles, + { owner, repo, pull_number, per_page: 100 } + ) + + robot.log.info(`Files changed in PR #${pull_number}: ${files.map(f => f.filename).join(', ')}`) + + // Routing logic: check for 'globals' or 'organizations' folder changes + const globalsChanged = files.some(f => /\/globals\//.test(f.filename)) + const orgsChanged = files.some(f => /\/organizations\//.test(f.filename)) + + if (globalsChanged) { + robot.log.debug('Detected changes in the globals folder. Routing to syncHubGlobalsUpdate(...).') + await module.exports.syncHubGlobalsUpdate(robot, context, files) + } + + if (orgsChanged) { + robot.log.debug('Detected changes in the organizations folder. Routing to syncHubOrgUpdate(...).') + // Only sync updates in organization subfolders, not files directly in organizations folder + const baseSettingsPath = `${(env.CONFIG_PATH || '.github').replace(/\/$/, '')}/${env.SAFE_SETTINGS_HUB_PATH}/organizations` + const normalizedBase = baseSettingsPath.replace(/\/$/, '') + const escapeRegex = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + // Only match files in org subfolders: .../organizations//... + const orgSubfolderPattern = new RegExp(`^${escapeRegex(normalizedBase)}/([^/]+)/.+`) + const orgNamesSet = new Set() + files.forEach(f => { + const m = f.filename.match(orgSubfolderPattern) + if (m && m[1]) { + orgNamesSet.add(m[1]) + } + }) + const orgNames = Array.from(orgNamesSet) + robot.log.info(`Orgs updated in PR #${pull_number}: ${orgNames.join(', ')}`) + for (const orgName of orgNames) { + const destRepo = env.ADMIN_REPO + const destinationFolder = env.CONFIG_PATH || '.github' + await module.exports.syncHubOrgUpdate(robot, context, orgName, destRepo, destinationFolder) + } + } + } catch (err) { + robot.log.error(`Failed to sync safe settings: ${err && err.message ? err.message : err}`) + } +} + +/** + * Handle updates in the globals folder and sync to destinations defined in manifest.yml rules + * @param {import('probot').Probot} robot + * @param {import('probot').Context} context + * @param {Array} files - Array of changed file objects from PR + */ +async function syncHubGlobalsUpdate (robot, context, files) { + attachFileLogger(robot) + robot.log.info(`Syncing safe settings for 'globals/'.`) + const manifestPath = `${env.CONFIG_PATH}/${env.SAFE_SETTINGS_HUB_PATH}/globals/manifest.yml` + let manifest + try { + const resp = await context.octokit.repos.getContent({ + owner: env.SAFE_SETTINGS_HUB_ORG, + repo: env.SAFE_SETTINGS_HUB_REPO, + path: manifestPath, + ref: 'main' + }) + const manifestContent = Buffer.from(resp.data.content, resp.data.encoding).toString('utf8') + manifest = yaml.load(manifestContent) + robot.log.debug('Loaded manifest.yml rules from hub repo:' + JSON.stringify(manifest, null, 2)) + } catch (err) { + robot.log.error('Failed to load manifest.yml from hub repo:' + err.message) + return + } + const changedGlobals = files.filter(f => /\/globals\//.test(f.filename)) + if (!changedGlobals.length) { + robot.log.info('No changed files in globals folder.') + return + } + // Pre-filter rules for each file, and precompute orgs for each rule + const installs = await getInstallations(robot) + const orgLogins = installs.filter(i => i.account && i.account.type === 'Organization').map(i => i.account.login) + // Precompute matching rules for each fileName in changedGlobals + const fileNameToMatchingRules = {}; + for (const fileObj of changedGlobals) { + const fileName = fileObj.filename.split('/').pop(); + fileNameToMatchingRules[fileName] = (manifest.rules || []).filter(rule => + (rule.files || []).some(pattern => minimatch(fileName, pattern)) + ); + } + for (const fileObj of changedGlobals) { + const fileName = fileObj.filename.split('/').pop(); + if (fileName === 'manifest.yml') { + robot.log.debug(`Skipping sync for manifest.yml (should only exist in hub)`); + continue; + } + robot.log.debug(`Evaluating globals file: ${fileObj.filename}`); + // Use precomputed matching rules + const matchingRules = fileNameToMatchingRules[fileName]; + for (const rule of matchingRules) { + const mergeStrategy = rule.mergeStrategy || 'merge'; + // Precompute orgs to sync for each target pattern + let orgsToSync = []; + for (const orgPattern of rule.targets || []) { + if (orgPattern === '*') { + orgsToSync.push(...orgLogins); + } else if (orgPattern.endsWith('*')) { + const prefix = orgPattern.slice(0, -1); + orgsToSync.push(...orgLogins.filter(login => login.startsWith(prefix))); + } else { + orgsToSync.push(orgPattern); + } + } + // Remove duplicates + orgsToSync = Array.from(new Set(orgsToSync)); + robot.log.debug(`Rule '${rule.name}' matches file '${fileName}'. Targets: ${orgsToSync.join(', ')}`); + for (const orgName of orgsToSync) { + robot.log.debug(`Preparing to sync file '${fileName}' to org '${orgName}' with mergeStrategy='${mergeStrategy}'`); + const destRepo = env.ADMIN_REPO; + const githubDest = await getOrgInstallation(robot, orgName); + if (!githubDest) { + robot.log.info(`Skipping org ${orgName}: no installation found.`); + continue; + } + let repoExists = false; + try { + await githubDest.repos.get({ owner: orgName, repo: destRepo }); + repoExists = true; + } catch (err) { + if (err.status === 404) { + robot.log.info(`Skipping org ${orgName}: config repo '${destRepo}' does not exist.`); + continue; + } else { + throw err; + } + } + if (!repoExists) continue; + const destPath = `${env.CONFIG_PATH}/${fileName}`; + let exists = false; + let existingSha = undefined; + try { + robot.log.debug(`Checking existence of ${destPath} in ${orgName}/${destRepo}`); + const resp = await githubDest.repos.getContent({ + owner: orgName, + repo: destRepo, + path: destPath, + ref: 'main' + }); + if (!Array.isArray(resp.data)) { + robot.log.debug(`Found ${destPath} in ${orgName}/${destRepo}`); + exists = true; + existingSha = resp.data.sha; + } + } catch (err) { + if (err.status === 404) { + robot.log.info(`File ${destPath} not found in ${orgName}/${destRepo} (this is fine for both merge strategies)`); + exists = false; + existingSha = undefined; + } else { + robot.log.error(`Error checking ${destPath} in ${orgName}/${destRepo}: ${err.message}`); + throw err; + } + } + if (mergeStrategy === 'merge' && exists) { + robot.log.info(`Skipping sync of ${fileName} to ${orgName} (already exists & mergeStrategy=${mergeStrategy})`); + continue; + } + robot.log.info(`Syncing ${fileName} to ${orgName} (mergeStrategy=${mergeStrategy})`); + try { + let srcContentResp; + const pr = context.payload && context.payload.pull_request; + const srcRef = pr && pr.head && pr.head.sha ? pr.head.sha : 'main'; + srcContentResp = await context.octokit.repos.getContent({ + owner: env.SAFE_SETTINGS_HUB_ORG, + repo: env.SAFE_SETTINGS_HUB_REPO, + path: fileObj.filename, + ref: srcRef + }); + const data = srcContentResp.data; + if (Array.isArray(data)) { + robot.log.debug(`Skipping directory ${fileObj.filename}`); + continue; + } + const fileContent = Buffer.from(data.content, data.encoding).toString('utf8'); + const encoded = Buffer.from(fileContent, 'utf8').toString('base64'); + const destBaseBranch = 'main'; + const directPush = (env.SAFE_SETTINGS_HUB_DIRECT_PUSH === 'true' || env.SAFE_SETTINGS_HUB_DIRECT_PUSH === '1'); + const timestamp = Date.now(); + const branchName = directPush ? destBaseBranch : `safe-settings-globals-sync/${orgName}-${fileName}-${timestamp}`; + await createBranchIfNeeded(githubDest, orgName, destRepo, destBaseBranch, branchName, directPush, robot.log); + await createOrUpdateFile(githubDest, { + owner: orgName, + repo: destRepo, + path: destPath, + message: directPush ? `Direct sync globals file '${fileName}' from hub` : `Sync globals file '${fileName}' from hub`, + content: encoded, + branch: branchName, + sha: exists ? existingSha : undefined, + committer: { name: 'Safe Settings Bot', email: 'safe-settings-bot@example.com' }, + author: { name: 'Safe Settings Bot', email: 'safe-settings-bot@example.com' } + }, robot.log); + if (!directPush) { + try { + const prTitle = `Sync globals file '${fileName}' from hub`; + const prBody = `Automated sync of globals file '${fileName}' from hub to ${orgName}.`; + const created = await githubDest.rest.pulls.create({ owner: orgName, repo: destRepo, title: prTitle, head: branchName, base: destBaseBranch, body: prBody }); + robot.log.info(`Created PR ${created.data.html_url} in ${orgName}/${destRepo}`) + } catch (prErr) { + robot.log.error(`Failed to create PR in ${orgName}/${destRepo}: ${prErr.message}`) + throw prErr + } + } else { + robot.log.info(`Changes pushed directly to ${orgName}/${destRepo}@${destBaseBranch}`) + } + } catch (syncErr) { + robot.log.error(`Failed to sync globals file ${fileName} to ${orgName}: ${syncErr.message}`) + } + } + } + } +} + +/** + * Retrieve settings files from remote organization admin repositories, + * commit them into a branch in the hub repository, and open a pull request. + * @param {import('probot').Probot} robot + * @param {Array} orgNames Array of organization names to retrieve settings from + * @param {Object} options Options for the operation + * @param {string} options.baseBranch Base branch to create new branches from (default: 'main') + * @returns {Promise>} Results of the operation for each organization + */ +async function retrieveSettingsFromOrgs (robot, orgNames = [], options = {}) { + attachFileLogger(robot) + const results = [] + try { + if (!Array.isArray(orgNames) || orgNames.length === 0) return results + + const installs = await getInstallations(robot) + + const hubOwnerLogin = (env.SAFE_SETTINGS_HUB_ORG || '').toLowerCase() + const hubRepoName = env.SAFE_SETTINGS_HUB_REPO + if (!hubOwnerLogin || !hubRepoName) { + throw new Error('SAFE_SETTINGS_HUB_ORG and SAFE_SETTINGS_HUB_REPO must be configured') + } + + const hubInstall = installs.find(i => i.account && i.account.login && i.account.login.toLowerCase() === hubOwnerLogin) + if (!hubInstall) throw new Error(`Installation for hub org ${env.SAFE_SETTINGS_HUB_ORG} not found`) + + const githubHub = await robot.auth(hubInstall.id) + const baseBranch = options.baseBranch || 'main' + + // Resolve the base sha for creating branches + const baseRef = await githubHub.rest.git.getRef({ owner: env.SAFE_SETTINGS_HUB_ORG, repo: hubRepoName, ref: `heads/${baseBranch}` }) + const baseSha = baseRef.data && baseRef.data.object && baseRef.data.object.sha + + // Helper: collect all files under a path in a repo (recursively) + async function collectFilesFromRepo (githubClient, owner, repo, dirPath, ref = 'main') { + const out = [] + async function walk (p) { + try { + const resp = await githubClient.repos.getContent({ owner, repo, path: p, ref }) + const data = resp.data + if (Array.isArray(data)) { + for (const item of data) { + if (item.type === 'file') { + try { + const fileResp = await githubClient.repos.getContent({ owner, repo, path: item.path, ref }) + if (!Array.isArray(fileResp.data) && typeof fileResp.data.content === 'string') { + const decoded = Buffer.from(fileResp.data.content, fileResp.data.encoding || 'base64').toString('utf8') + out.push({ path: fileResp.data.path, content: decoded }) + } + } catch (fe) { + // skip unreadable files, but log + robot.log && robot.log.warn && robot.log.warn(`collectFilesFromRepo: failed to fetch ${item.path} from ${owner}/${repo}: ${fe.message}`) + } + } else if (item.type === 'dir') { + await walk(item.path) + } else { + // skip other types (submodules, symlinks) + robot.log && robot.log.debug && robot.log.debug(`Skipping unsupported item type ${item.type} at ${item.path}`) + } + } + } else if (typeof data.content === 'string') { + const decoded = Buffer.from(data.content, data.encoding || 'base64').toString('utf8') + out.push({ path: data.path, content: decoded }) + } + } catch (e) { + if (e && e.status === 404) { + // path does not exist on repo -> no files + return + } + throw e + } + } + await walk(dirPath) + return out + } + + // Iterate requested orgs and import their CONFIG_PATH into the hub repo under the organizations/ tree + for (const orgName of orgNames) { + try { + if (!orgName) { results.push({ org: orgName, error: 'invalid org name' }); continue } + robot.log.info(`Retrieving settings from org: ${orgName}`) + + // fast existence check on the hub repo: skip if org folder already exists under CONFIG_PATH/SAFE_SETTINGS_HUB_PATH/organizations + try { + const destOrgPath = `${(env.CONFIG_PATH || '.github').replace(/\/$/, '')}/${env.SAFE_SETTINGS_HUB_PATH}/organizations/${orgName}` + try { + const destCheck = await githubHub.rest.repos.getContent({ owner: env.SAFE_SETTINGS_HUB_ORG, repo: hubRepoName, path: destOrgPath, ref: baseBranch }) + if (Array.isArray(destCheck.data) && destCheck.data.length > 0) { + robot.log.info(`Skipping ${orgName}: already present in hub`) + results.push({ org: orgName, skipped: true, reason: 'already_imported' }) + continue + } + } catch (probeErr) { + if (!(probeErr && probeErr.status === 404)) { + robot.log && robot.log.warn && robot.log.warn(`Failed to probe hub destination for ${orgName}: ${probeErr.message}`) + results.push({ org: orgName, error: `failed to check destination: ${probeErr.message}` }) + continue + } + + // 404 -> not present, proceed + } + } catch (e) { + robot.log && robot.log.warn && robot.log.warn(`Unexpected error while probing destination for ${orgName}: ${e.message}`) + results.push({ org: orgName, error: `probe error: ${e.message}` }) + continue + } + + const srcInstall = installs.find(i => i.account && i.account.login && i.account.login.toLowerCase() === orgName.toLowerCase()) + if (!srcInstall) { + results.push({ org: orgName, error: 'installation not found for org' }) + continue + } + + const githubSrc = await robot.auth(srcInstall.id) + const adminRepo = env.ADMIN_REPO + if (!adminRepo) { + results.push({ org: orgName, error: 'ADMIN_REPO is not configured' }) + continue + } + + const sourceBase = (env.CONFIG_PATH || '.github').replace(/\/$/, '') + // collect files from the source admin repo under CONFIG_PATH + const files = await collectFilesFromRepo(githubSrc, orgName, adminRepo, sourceBase, 'main') + + if (!files || files.length === 0) { + results.push({ org: orgName, info: 'no files found at CONFIG_PATH' }) + continue + } + + const timestamp = Date.now() + const branchName = `safe-settings-import/${orgName}/${timestamp}`.replace(/[^a-zA-Z0-9_\-./]/g, '-') + + // create branch in hub repo + try { + await githubHub.rest.git.createRef({ owner: env.SAFE_SETTINGS_HUB_ORG, repo: hubRepoName, ref: `refs/heads/${branchName}`, sha: baseSha }) + } catch (createErr) { + if (createErr && createErr.status === 422) { + robot.log.info(`Branch ${branchName} already exists, continuing`) // continue + } else { + throw createErr + } + } + + // Instead of creating/updating files one-by-one, build a single tree and commit so the PR contains all files atomically + try { + const treeEntries = [] + for (const f of files) { + // relative path under the sourceBase + const rel = path.posix.relative(sourceBase, f.path) + // Destination should be: CONFIG_PATH/SAFE_SETTINGS_HUB_PATH/organizations// + const destBase = `${(env.CONFIG_PATH || '.github').replace(/\/$/, '')}/${env.SAFE_SETTINGS_HUB_PATH}` + const destPath = path.posix.join(destBase, 'organizations', orgName, rel).replace(/\/+/g, '/') + treeEntries.push({ path: destPath, mode: '100644', type: 'blob', content: f.content }) + } + + // Get base commit and tree + const baseCommitResp = await githubHub.rest.git.getCommit({ owner: env.SAFE_SETTINGS_HUB_ORG, repo: hubRepoName, commit_sha: baseSha }) + const baseTreeSha = baseCommitResp.data && baseCommitResp.data.tree && baseCommitResp.data.tree.sha + + // Create a new tree rooted at the base tree + const createdTree = await githubHub.rest.git.createTree({ owner: env.SAFE_SETTINGS_HUB_ORG, repo: hubRepoName, tree: treeEntries, base_tree: baseTreeSha }) + + // Create a commit that points to the new tree + const commitMessage = `Import safe-settings from ${orgName}` + const newCommit = await githubHub.rest.git.createCommit({ owner: env.SAFE_SETTINGS_HUB_ORG, repo: hubRepoName, message: commitMessage, tree: createdTree.data.sha, parents: [baseSha] }) + + // Update the branch ref to point to the new commit + await githubHub.rest.git.updateRef({ owner: env.SAFE_SETTINGS_HUB_ORG, repo: hubRepoName, ref: `heads/${branchName}`, sha: newCommit.data.sha }) + + robot.log.info(`Created commit ${newCommit.data.sha} on ${env.SAFE_SETTINGS_HUB_ORG}/${hubRepoName}@${branchName} with ${treeEntries.length} files`) + } catch (commitErr) { + robot.log.error(`Failed to create commit tree for ${orgName}: ${commitErr && commitErr.message ? commitErr.message : commitErr}`) + results.push({ org: orgName, error: `failed to commit files: ${commitErr && commitErr.message ? commitErr.message : String(commitErr)}` }) + continue + } + + // Create a PR in the hub repo for this branch + try { + const prTitle = `Import safe-settings from ${orgName}` + const prBody = `Automated import of settings from ${orgName} admin repo (${adminRepo}) into the hub.` + const created = await githubHub.rest.pulls.create({ owner: env.SAFE_SETTINGS_HUB_ORG, repo: hubRepoName, title: prTitle, head: branchName, base: baseBranch, body: prBody }) + results.push({ org: orgName, pr: created.data && created.data.html_url }) + robot.log.info(`Created PR ${created.data && created.data.html_url} for ${orgName}`) + } catch (prErr) { + robot.log.error(`Failed to create PR for ${orgName}: ${prErr && prErr.message ? prErr.message : prErr}`) + results.push({ org: orgName, error: `failed to create PR: ${prErr && prErr.message ? prErr.message : String(prErr)}` }) + } + } catch (errInner) { + robot.log.error(`Error importing settings for org ${orgName}: ${errInner && errInner.message ? errInner.message : errInner}`) + results.push({ org: orgName, error: errInner && errInner.message ? errInner.message : String(errInner) }) + } + } + + return results + } catch (err) { + robot.log.error(`retrieveSettingsFromOrgs error: ${err && err.message ? err.message : err}`) + throw err + } +} + +// Export all internal functions for testability +module.exports = { + hubSyncHandler, + retrieveSettingsFromOrgs, + syncHubOrgUpdate, + syncHubGlobalsUpdate, + getOrgInstallation +} diff --git a/lib/installationCache.js b/lib/installationCache.js new file mode 100644 index 00000000..5ec98619 --- /dev/null +++ b/lib/installationCache.js @@ -0,0 +1,149 @@ +// Installation cache with TTL for GitHub App installations. +// Provides a hybrid approach: live refresh when stale, fast reads otherwise. + +let cachedInstallations = [] +let cachedOrgLogins = [] +let lastFetchedAt = null +let inFlightPromise = null + +/** + * Returns the TTL (time-to-live) in milliseconds for the installation cache. + * Reads from INSTALLATION_CACHE_TTL_MS env variable, defaults to 60s, minimum 5s. + */ +const DEFAULT_TTL_MS = 60_000 +function getTtlMs () { + const v = parseInt(process.env.INSTALLATION_CACHE_TTL_MS, 10) + return isNaN(v) || v < 5_000 ? DEFAULT_TTL_MS : v +} + +/** + * Fetches all GitHub App installations using the provided robot instance. + * Returns an array of installation objects. Uses pagination for large orgs. + * @param {Probot} robot - The Probot robot instance + * @param {Object} opts - Options (perPage) + * @returns {Promise} Array of installation objects + */ +async function fetchInstallations (robot, { perPage = 100 } = {}) { + const github = await robot.auth() + return github.paginate( + github.apps.listInstallations.endpoint.merge({ per_page: perPage }) + ) +} + +/** + * Refreshes the installation cache by fetching live installations from GitHub. + * Updates cachedInstallations, cachedOrgLogins, and lastFetchedAt. + * Ensures only one refresh is in flight at a time. + * @param {Probot} robot - The Probot robot instance + * @param {Object} opts - Options for fetchInstallations + * @returns {Promise} Array of installation objects + */ +async function refresh (robot, opts = {}) { + if (inFlightPromise) return inFlightPromise + inFlightPromise = (async () => { + try { + const installs = await fetchInstallations(robot, opts) + cachedInstallations = installs + cachedOrgLogins = installs + .filter(i => i.account && i.account.type === 'Organization') + .map(i => i.account.login) + .sort() + lastFetchedAt = new Date() + } catch (e) { + robot.log && robot.log.warn && robot.log.warn(`Installation cache refresh failed: ${e.message}`) + throw e + } finally { + inFlightPromise = null + } + return cachedInstallations + })() + return inFlightPromise +} + +/** + * Starts a prefetch of installations to warm up the cache at startup. + * Returns a promise for the refresh operation. + * @param {Probot} robot - The Probot robot instance + * @param {Object} opts - Options for refresh + * @returns {Promise} Array of installation objects + */ +function startPrefetch (robot, opts = {}) { + return refresh(robot, opts) +} + +/** + * Initialize cache (always prefetch once at startup) and log result. + */ + +/** + * Initializes the installation cache by prefetching installations at startup. + * Logs the result and returns true/false for success/failure. + * @param {Probot} robot - The Probot robot instance + * @returns {Promise} True if prefetch succeeded, false otherwise + */ +function initCache (robot) { + return startPrefetch(robot) + .then(installs => { + robot.log && robot.log.info && robot.log.info(`Installation cache prefetched ${installs.length} installs (${cachedOrgLogins.length} orgs) [TTL=${getTtlMs()}ms]`) + return true + }) + .catch(e => { + robot.log && robot.log.warn && robot.log.warn(`Installation cache prefetch failed: ${e.message}`) + return false + }) +} + +/** + * Ensures the cache is fresh by checking TTL and refreshing if stale. + * Called before serving cached installations to guarantee freshness. + * @param {Probot} robot - The Probot robot instance + */ +async function ensureFresh (robot) { + const ttl = getTtlMs() + if (!lastFetchedAt || (Date.now() - lastFetchedAt.getTime()) > ttl) { + try { await refresh(robot) } catch (_) { /* stale ok */ } + } +} + +/** + * Returns the cached installations, refreshing if the cache is stale. + * Always returns a copy of the cached array. + * @param {Probot} robot - The Probot robot instance + * @returns {Promise} Array of installation objects + */ +async function getInstallations (robot) { + await ensureFresh(robot) + return cachedInstallations.slice() +} + +/** + * Returns a copy of the cached organization logins (GitHub org names). + * @returns {Array} Array of org login strings + */ +function getOrgLogins () { return cachedOrgLogins.slice() } + +/** + * Returns the Date when installations were last fetched. + * @returns {Date|null} Last fetched date or null if never fetched + */ +function getLastFetchedAt () { return lastFetchedAt } + +/** + * Test-only helper: Forces the cache to appear stale on next access. + * Used for diagnostics and testing cache refresh logic. + */ +function __forceStale () { + lastFetchedAt = new Date(Date.now() - (getTtlMs() + 10_000)) +} + +module.exports = { + startPrefetch, + initCache, + refresh, + getInstallations, + getOrgLogins, + getLastFetchedAt, + // for tests / diagnostics + _debug: () => ({ size: cachedInstallations.length, lastFetchedAt }), + __forceStale +} diff --git a/lib/mergeDeep.js b/lib/mergeDeep.js index ab278e5c..28938ba1 100644 --- a/lib/mergeDeep.js +++ b/lib/mergeDeep.js @@ -92,8 +92,8 @@ class MergeDeep { // So any property in the target that is not in the source is not treated as a deletion for (const key in source) { // Skip prototype pollution vectors - if (key === "__proto__" || key === "constructor") { - continue; + if (key === '__proto__' || key === 'constructor') { + continue } // Logic specific for Github // API response includes urls for resources, or other ignorable fields; we can ignore them diff --git a/lib/plugins/archive.js b/lib/plugins/archive.js index a481029c..eb3d25c1 100644 --- a/lib/plugins/archive.js +++ b/lib/plugins/archive.js @@ -1,86 +1,79 @@ -const NopCommand = require('../nopcommand'); +const NopCommand = require('../nopcommand') -function returnValue(shouldContinue, nop) { - return { shouldContinue, nopCommands: nop }; +function returnValue (shouldContinue, nop) { + return { shouldContinue, nopCommands: nop } } module.exports = class Archive { - constructor(nop, github, repo, settings, log) { - this.github = github; - this.repo = repo; - this.settings = settings; - this.log = log; - this.nop = nop; + constructor (nop, github, repo, settings, log) { + this.github = github + this.repo = repo + this.settings = settings + this.log = log + this.nop = nop } // Returns true if plugin application should continue, false otherwise - async sync() { + async sync () { // Fetch repository details using REST API const { data: repoDetails } = await this.github.repos.get({ - owner: this.repo.owner, - repo: this.repo.repo - }); + owner: this.repo.owner, + repo: this.repo.repo + }) if (typeof this.settings?.archived !== 'undefined') { - this.log.debug(`Checking if ${this.repo.owner}/${this.repo.repo} is archived`); - - this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is ${repoDetails.archived ? 'archived' : 'not archived'}`); + this.log.debug(`Checking if ${this.repo.owner}/${this.repo.repo} is archived`) + + this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is ${repoDetails.archived ? 'archived' : 'not archived'}`) if (repoDetails.archived) { if (this.settings.archived) { - this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} already archived, inform other plugins should not run.`); - return returnValue(false); - } - else { - this.log.debug(`Unarchiving ${this.repo.owner}/${this.repo.repo}`); + this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} already archived, inform other plugins should not run.`) + return returnValue(false) + } else { + this.log.debug(`Unarchiving ${this.repo.owner}/${this.repo.repo}`) if (this.nop) { - return returnValue(true, [new NopCommand(this.constructor.name, this.repo, this.github.repos.update.endpoint(this.settings), 'will unarchive')]); - } - else { + return returnValue(true, [new NopCommand(this.constructor.name, this.repo, this.github.repos.update.endpoint(this.settings), 'will unarchive')]) + } else { // Unarchive the repository using REST API const updateResponse = await this.github.repos.update({ owner: this.repo.owner, repo: this.repo.repo, archived: false - }); - this.log.debug(`Unarchive result ${JSON.stringify(updateResponse)}`); + }) + this.log.debug(`Unarchive result ${JSON.stringify(updateResponse)}`) - return returnValue(true); + return returnValue(true) } } - } - else { + } else { if (this.settings.archived) { - this.log.debug(`Archiving ${this.repo.owner}/${this.repo.repo}`); + this.log.debug(`Archiving ${this.repo.owner}/${this.repo.repo}`) if (this.nop) { - return returnValue(false, [new NopCommand(this.constructor.name, this.repo, this.github.repos.update.endpoint(this.settings), 'will archive')]); - } - else { + return returnValue(false, [new NopCommand(this.constructor.name, this.repo, this.github.repos.update.endpoint(this.settings), 'will archive')]) + } else { // Archive the repository using REST API const updateResponse = await this.github.repos.update({ owner: this.repo.owner, repo: this.repo.repo, archived: true - }); - this.log.debug(`Archive result ${JSON.stringify(updateResponse)}`); + }) + this.log.debug(`Archive result ${JSON.stringify(updateResponse)}`) - return returnValue(false); + return returnValue(false) } - } - else { - this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is not archived, ignoring.`); - return returnValue(true); + } else { + this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is not archived, ignoring.`) + return returnValue(true) } } - } - else { - if (repoDetails.archived) { - this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is archived, ignoring.`); - return returnValue(false); - } - else { - this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is not archived, proceed as usual.`); - return returnValue(true); - } + } else { + if (repoDetails.archived) { + this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is archived, ignoring.`) + return returnValue(false) + } else { + this.log.debug(`Repo ${this.repo.owner}/${this.repo.repo} is not archived, proceed as usual.`) + return returnValue(true) + } } } -}; +} diff --git a/lib/plugins/branches.js b/lib/plugins/branches.js index d28e2f90..80a32fb8 100644 --- a/lib/plugins/branches.js +++ b/lib/plugins/branches.js @@ -5,10 +5,10 @@ const Overrides = require('./overrides') const ignorableFields = [] const previewHeaders = { accept: 'application/vnd.github.hellcat-preview+json,application/vnd.github.luke-cage-preview+json,application/vnd.github.zzzax-preview+json' } const overrides = { - 'contexts': { - 'action': 'reset', - 'type': 'array' - }, + contexts: { + action: 'reset', + type: 'array' + } } module.exports = class Branches extends ErrorStash { diff --git a/lib/plugins/environments.js b/lib/plugins/environments.js index 73bef0e0..5f8044bd 100644 --- a/lib/plugins/environments.js +++ b/lib/plugins/environments.js @@ -23,7 +23,7 @@ module.exports = class Environments extends Diffable { policies.push({ name: policy, type: 'branch' }) } else if (typeof policy === 'object' && Array.isArray(policy.names)) { policy.names.forEach(name => { - policies.push({ name: name, type: policy.type }) + policies.push({ name, type: policy.type }) }) } }) diff --git a/lib/plugins/overrides.js b/lib/plugins/overrides.js index 0030b124..b6d68942 100644 --- a/lib/plugins/overrides.js +++ b/lib/plugins/overrides.js @@ -74,23 +74,23 @@ module.exports = class Overrides extends ErrorStash { // - The POST method for rulesets (create) allows for one override only. static removeOverrides (overrides, source, existing) { Object.entries(overrides).forEach(([override, props]) => { - let sourceRefs = Overrides.getObjectRef(source, override) - let data = JSON.stringify(sourceRefs) + const sourceRefs = Overrides.getObjectRef(source, override) + const data = JSON.stringify(sourceRefs) if (data.includes('{{EXTERNALLY_DEFINED}}')) { - let existingRefs = Overrides.getObjectRef(existing, override) + const existingRefs = Overrides.getObjectRef(existing, override) sourceRefs.forEach(sourceRef => { if (existingRefs[0]) { sourceRef[override] = existingRefs[0][override] - } else if (props['action'] === 'delete') { - Overrides.removeTopLevelParent(source, sourceRef[override], props['parents']) + } else if (props.action === 'delete') { + Overrides.removeTopLevelParent(source, sourceRef[override], props.parents) delete sourceRef[override] - } else if (props['type'] === 'array') { + } else if (props.type === 'array') { sourceRef[override] = [] - } else if (props['type'] === 'dict') { + } else if (props.type === 'dict') { sourceRef[override] = {} } else { - throw new Error(`Unknown type ${props['type']} for ${override}`) + throw new Error(`Unknown type ${props.type} for ${override}`) } }) } diff --git a/lib/plugins/rulesets.js b/lib/plugins/rulesets.js index b77ead1b..e1de0905 100644 --- a/lib/plugins/rulesets.js +++ b/lib/plugins/rulesets.js @@ -4,11 +4,11 @@ const MergeDeep = require('../mergeDeep') const Overrides = require('./overrides') const ignorableFields = [] const overrides = { - 'required_status_checks': { - 'action': 'delete', - 'parents': 3, - 'type': 'dict' - }, + required_status_checks: { + action: 'delete', + parents: 3, + type: 'dict' + } } const version = { diff --git a/lib/routes.js b/lib/routes.js new file mode 100644 index 00000000..7a65ac84 --- /dev/null +++ b/lib/routes.js @@ -0,0 +1,659 @@ +/** + * Router setup for Safe Settings UI & API endpoints + * Centralizes Express/Next asset & API wiring away from core app logic. + * + * Exports: + * setupRoutes(robot, getRouter) -> configured router + * + * Responsibilities: + * - Serve static exported Next.js UI (from ui/out) + * - Dashboard HTML entry points + * - JSON API endpoints + * + * This version removes dependency on robot-level cached installation getters + * (`robot.getCachedInstallations`, `robot.getOrganizationLogins`) and instead + * fetches installations live per request. If performance becomes an issue, + * a lightweight in-module memoization layer with short TTL can be reintroduced. + */ + +const path = require('path') +const util = require('util') +const fs = require('fs') +const express = require('express') +const env = require('./env') +const { getInstallations: cacheGetInstallations, getOrgLogins, getLastFetchedAt } = require('./installationCache') + +// Lightweight commit metadata cache (path+ref -> meta) with TTL to avoid +// repeated GitHub commit lookups across requests. +const COMMIT_META_TTL_MS = parseInt(process.env.COMMIT_META_TTL_MS || '300000') // 5m default +const _commitMetaCache = new Map() // key => { meta, expiresAt } +function getCachedCommitMeta (key) { + const entry = _commitMetaCache.get(key) + if (!entry) return null + if (Date.now() > entry.expiresAt) { _commitMetaCache.delete(key); return null } + return entry.meta +} +function setCachedCommitMeta (key, meta) { + _commitMetaCache.set(key, { meta, expiresAt: Date.now() + COMMIT_META_TTL_MS }) +} + +function setupRoutes (robot, getRouter) { + // Root-level mount + const router = getRouter('/') + + // Ensure JSON/urlencoded body parsing is enabled for API endpoints + router.use(express.json({ limit: '1mb' })) + router.use(express.urlencoded({ extended: true })) + + // Static assets: produced by Next export/build step (ui/out) + const rootDir = path.join(__dirname, '..') // lib -> project root + const uiPath = path.join(rootDir, 'ui', 'out') + router.use(express.static(uiPath)) + + // HTML entrypoints (exported files). Adjust if you move/rename pages. + // Redirect root route to /dashboard + router.get('/', (req, res) => { + res.sendFile(path.join(uiPath, 'dashboard.html')) + }) + + router.get('/dashboard', (req, res) => { + res.sendFile(path.join(uiPath, 'dashboard.html')) + }) + + router.get('/dashboard/organizations', (req, res) => { + res.sendFile(path.join(uiPath, 'dashboard', 'organizations.html')) + }) + + router.get('/dashboard/settings', (req, res) => { + res.sendFile(path.join(uiPath, 'dashboard', 'settings.html')) + }) + + router.get('/dashboard/safe-settings-hub', (req, res) => { + res.sendFile(path.join(uiPath, 'dashboard', 'safe-settings-hub.html')) + }) + + router.get('/dashboard/env', (req, res) => { + res.sendFile(path.join(uiPath, 'dashboard', 'env.html')) + }) + + router.get('/dashboard/help', (req, res) => { + res.sendFile(path.join(uiPath, 'dashboard', 'help.html')) + }) + + // Apple touch icon (silence 404s). Replace file logic if you add a real 180x180 asset. + const APPLE_TOUCH_ICON_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAALQAAAC0CAQAAAA9zQYyAAAAC0lEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==' // 180x180 transparent PNG + router.get('/apple-touch-icon.png', (req, res) => { + // If a real file exists at project root, serve it; otherwise fallback to embedded transparent PNG. + const filePath = path.join(rootDir, 'apple-touch-icon.png') + fs.access(filePath, fs.constants.R_OK, (err) => { + if (!err) { + return res.sendFile(filePath) + } + const buf = Buffer.from(APPLE_TOUCH_ICON_BASE64, 'base64') + res.setHeader('Content-Type', 'image/png') + res.setHeader('Cache-Control', 'public, max-age=86400, immutable') + res.send(buf) + }) + }) + + /** + * GET /api/safe-settings/installation + * Returns live organization installation metadata + optional last commit info. + * Query param: disableActivity=true to skip commit lookups (faster). + */ + router.get('/api/safe-settings/installation', async (req, res) => { + const disableActivity = req.query.disableActivity === 'true' + const includeActivity = !disableActivity + + const crypto = require('crypto') + function hashContent (str) { + return crypto.createHash('sha256').update(str || '').digest('hex') + } + + try { + const installs = await cacheGetInstallations(robot) + const orgLogins = getOrgLogins() + const orgInstalls = installs.filter(i => i.account && i.account.type === 'Organization') + const lastCommits = {} + const syncStatus = {} + let installationDtos + + if (includeActivity && env.ADMIN_REPO) { + const orgs = orgLogins + const limit = 1 // reduce concurrency for API rate safety + const queue = [...orgs] + robot.log.info(`Starting commit and sync status fetch for ${queue} organizations...`) + + const runners = [] + const runNext = async () => { + while (queue.length) { + const org = queue.shift() + try { + const install = installs.find(i => i.account && i.account.login.toLowerCase() === org.toLowerCase()) + if (!install) { + lastCommits[org] = { na: true, hasConfigRepo: false } + syncStatus[org] = false + continue + } + const githubOrg = await robot.auth(install.id) + let hasConfigRepo = false + try { + await githubOrg.repos.get({ owner: org, repo: env.ADMIN_REPO }) + hasConfigRepo = true + } catch (repoErr) { + if (repoErr.status === 404) { + hasConfigRepo = false + } else { + robot.log.warn(`Repo existence check error for ${org}/${env.ADMIN_REPO}: ${repoErr.message}`) + } + } + // --- SYNC CHECK --- + let isInSync = false + if (hasConfigRepo) { + try { + const hubOrgDir = `${env.CONFIG_PATH}/${env.SAFE_SETTINGS_HUB_PATH}/organizations/${org}` + const hubRef = 'main' + robot.log.debug(`1. [SYNC DEBUG] Hub file path for org ${org}: ${hubOrgDir}`) + robot.log.debug(`2. [SYNC DEBUG] Hub file branch/ref for org ${org}: ${hubRef}`) + let orgFilesResp, hubFilesResp + try { + robot.log.debug(`3. [SYNC DEBUG] Org: ${org}`) + orgFilesResp = await githubOrg.repos.getContent({ owner: org, repo: env.ADMIN_REPO, path: env.CONFIG_PATH }) + const orgNames = Array.isArray(orgFilesResp.data) + ? orgFilesResp.data.map(f => f.name).join(', ') + : (orgFilesResp.data && orgFilesResp.data.name ? orgFilesResp.data.name : '') + robot.log.debug(`4. [SYNC DEBUG] Org orgFilesResp file names: ${orgNames}`) + } catch (fetchErr) { + robot.log.error(`4a. [SYNC DEBUG] Error fetching org files: ${fetchErr.message}`) + orgFilesResp = { data: [] } + } + + try { + robot.log.debug(`5. [SYNC DEBUG] Hub: ${env.SAFE_SETTINGS_HUB_ORG}`) + robot.log.debug(`5a. [SYNC DEBUG] Fetching hub files for: \n owner: ${env.SAFE_SETTINGS_HUB_ORG}, \n repo: ${env.SAFE_SETTINGS_HUB_REPO}, \n path: ${hubOrgDir}, \n ref: ${hubRef}`) + hubFilesResp = await githubOrg.repos.getContent({ + owner: env.SAFE_SETTINGS_HUB_ORG, + repo: env.SAFE_SETTINGS_HUB_REPO, + path: hubOrgDir, + ref: hubRef + }) + const hubNames = Array.isArray(hubFilesResp.data) + ? hubFilesResp.data.map(f => f.name).join(', ') + : (hubFilesResp.data && hubFilesResp.data.name ? hubFilesResp.data.name : '') + robot.log.debug(`6. [SYNC DEBUG] Hub hubFilesResp file names: ${hubNames}`) + } catch (fetchErr) { + robot.log.error(`6a. [SYNC DEBUG] Error fetching hub files: ${fetchErr}`) + hubFilesResp = { data: [] } + } + + const orgFiles = Array.isArray(orgFilesResp.data) ? orgFilesResp.data.filter(f => f.type === 'file') : [] + const hubFiles = Array.isArray(hubFilesResp.data) ? hubFilesResp.data.filter(f => f.type === 'file') : ['a', 'b'] + + // Compare file names + const orgFileNames = orgFiles.map(f => f.name).sort() + const hubFileNames = hubFiles.map(f => f.name).sort() + + if (orgFileNames.length !== hubFileNames.length || orgFileNames.some((n, i) => n !== hubFileNames[i])) { + robot.log.warn(`6b. [SYNC DEBUG] File name mismatch for org ${org}`) + isInSync = false + } else { + // Compare file hashes + let allMatch = true + for (let i = 0; i < orgFiles.length; i++) { + const orgFile = orgFiles[i] + const hubFile = hubFiles[i] + robot.log.debug(`7. [SYNC DEBUG] Fetching file contents for org: ${org}, orgFile: ${orgFile.path}, hubFile: ${hubFile.path}`) + let orgContentResp, hubContentResp + try { + orgContentResp = await githubOrg.repos.getContent({ owner: org, repo: env.ADMIN_REPO, path: orgFile.path }).catch((e) => { robot.log.warn(`9. [SYNC DEBUG] Error fetching org file ${orgFile.path}: ${e.message}`); return { data: {} } }) + } catch (fetchErr) { + robot.log.error(`7a. [SYNC DEBUG] Error fetching org file ${orgFile.path}: ${fetchErr.message}`) + allMatch = false + break + } + try { + hubContentResp = await githubOrg.repos.getContent({ owner: env.SAFE_SETTINGS_HUB_ORG, repo: env.SAFE_SETTINGS_HUB_REPO, path: hubFile.path }).catch((e) => { robot.log.warn(`10.[SYNC DEBUG] Error fetching hub file ${hubFile.path}: ${e.message}`); return { data: {} } }) + } catch (fetchErr) { + robot.log.error(`7b. [SYNC DEBUG] Error fetching hub file ${hubFile.path}: ${fetchErr.message}`) + allMatch = false + break + } + const orgContent = orgContentResp.data.content ? Buffer.from(orgContentResp.data.content, orgContentResp.data.encoding || 'base64').toString('utf8') : '' + const hubContent = hubContentResp.data.content ? Buffer.from(hubContentResp.data.content, hubContentResp.data.encoding || 'base64').toString('utf8') : '' + const orgHash = hashContent(orgContent) + const hubHash = hashContent(hubContent) + robot.log.debug(`8. [SYNC DEBUG] Comparing file: ${orgFile.name}`) + robot.log.debug(`9. [SYNC DEBUG] Org hash: ${orgHash}`) + robot.log.debug(`10. [SYNC DEBUG] Hub hash: ${hubHash}`) + if (orgHash !== hubHash) { + robot.log.debug(`11. [SYNC DEBUG] Hash mismatch for file ${orgFile.name} in org ${org}`) + allMatch = false + break + } + } + isInSync = allMatch + } + } catch (syncErr) { + robot.log.error(`[SYNC DEBUG] Sync check error for org ${org}: ${syncErr.message}`) + isInSync = false + } + } + syncStatus[org] = isInSync + // --- END SYNC CHECK --- + // Commit info (unchanged) + let commits + try { + const pathPrefix = `${env.CONFIG_PATH.replace(/\/$/, '')}/organizations/${org}` + commits = await githubOrg.repos.listCommits({ owner: org, repo: env.ADMIN_REPO, per_page: 1, path: pathPrefix }) + } catch (err) { + if (err.status === 404) { + lastCommits[org] = { na: true, hasConfigRepo } + continue + } + if (err.status === 409) { // empty repo + lastCommits[org] = { hasConfigRepo } + continue + } + robot.log.warn(`Commit lookup error for ${org}/${env.ADMIN_REPO}: ${err.message}`) + lastCommits[org] = { hasConfigRepo } + continue + } + if (Array.isArray(commits.data) && commits.data.length) { + const c = commits.data[0] + const committedAt = (c.commit && c.commit.author && c.commit.author.date) || null + const ageSeconds = committedAt ? Math.floor((Date.now() - new Date(committedAt).getTime()) / 1000) : null + lastCommits[org] = { sha: c.sha, committed_at: committedAt, message: c.commit && c.commit.message ? c.commit.message.split('\n')[0] : null, age_seconds: ageSeconds, hasConfigRepo } + } else { + lastCommits[org] = { hasConfigRepo } + } + } catch (loopErr) { + robot.log.warn(`Unexpected error gathering commit for org ${org}: ${loopErr.message}`) + lastCommits[org] = { hasConfigRepo: false } + syncStatus[org] = false + } + } + } + for (let i = 0; i < limit; i++) runners.push(runNext()) + await Promise.all(runners) + } + + // Now that lastCommits and syncStatus are populated, build installationDtos + installationDtos = orgInstalls.map(i => { + const orgKey = i.account.login + const commitInfo = lastCommits[orgKey] || {} + return { + id: i.id, + account: orgKey, + type: i.account.type, + created_at: i.created_at, + name: orgKey, + sha: commitInfo.sha, + committed_at: commitInfo.committed_at, + message: commitInfo.message, + age_seconds: commitInfo.age_seconds, + hasConfigRepo: typeof commitInfo.hasConfigRepo === 'boolean' ? commitInfo.hasConfigRepo : false, + isInSync: typeof syncStatus[orgKey] === 'boolean' ? syncStatus[orgKey] : false + } + }) + return res.json({ updatedAt: new Date().toISOString(), installations: installationDtos }) + } catch (e) { + robot.log && robot.log.error && robot.log.error(e) + res.status(500).json({ error: e.message || 'unexpected error' }) + } + }) + + /** + * GET /api/safe-settings/hub/contents/* + * Fetches a file or directory listing from the SAFE_SETTINGS_HUB_ORG / SAFE_SETTINGS_HUB_REPO + * under the configured CONFIG_PATH (default .github). + * + * Examples: + * /api/safe-settings/hub/contents/ -> list CONFIG_PATH root + * /api/safe-settings/hub/contents/repos/foo.yml -> get specific file + * /api/safe-settings/hub/contents/repos?ref=main -> list directory at ref + * /api/safe-settings/hub/contents?recursive=true&maxDepth=2&fetchContent=false -> recursive listing without file bodies + * Note: recursive now defaults to true. Pass recursive=false for single-level listing. + */ + async function hubContent (req, res) { + try { + // Use cached installations (TTL-based freshness) + const installs = await cacheGetInstallations(robot) + const install = installs.find(i => i.account && i.account.type === 'Organization' && i.account.login.toLowerCase() === env.SAFE_SETTINGS_HUB_ORG.toLowerCase()) + if (!install) { + return res.status(404).json({ error: `Installation for org ${env.SAFE_SETTINGS_HUB_ORG} not found` }) + } + + const github = await robot.auth(install.id) + const wildcardPath = req.params[0] || '' // from the * in the route + const ref = req.query.ref || 'main' + const fullPath = wildcardPath ? path.posix.join(env.CONFIG_PATH, wildcardPath) : env.CONFIG_PATH + // recursive defaults to true unless explicitly disabled with recursive=false + const recursive = req.query.recursive !== 'false' + let maxDepth = parseInt(req.query.maxDepth, 5) + if (isNaN(maxDepth) || maxDepth < 1) maxDepth = 5 // safety default + if (maxDepth > 8) maxDepth = 5 // hard cap to avoid abuse + // Unified flag: fetchContent (default true). No other legacy params supported. + const fetchContent = req.query.fetchContent !== 'false' + + // Commit metadata fetch with global shared cache + per-request memoization + const perRequestCommitCache = new Map() + const fetchCommitMeta = async (p) => { + if (perRequestCommitCache.has(p)) return perRequestCommitCache.get(p) + const cacheKey = `${ref}::${p}` + const cached = getCachedCommitMeta(cacheKey) + if (cached) { perRequestCommitCache.set(p, cached); return cached } + let meta + try { + const commits = await github.repos.listCommits({ owner: env.SAFE_SETTINGS_HUB_ORG, repo: env.SAFE_SETTINGS_HUB_REPO, per_page: 1, path: p }) + .then(r => Array.isArray(r.data) ? r.data : []) + if (commits.length) { + const c = commits[0] + const committedAt = c.commit && c.commit.author && c.commit.author.date + const ageSeconds = committedAt ? Math.floor((Date.now() - new Date(committedAt).getTime()) / 1000) : null + meta = { + lastCommitSha: c.sha, + lastCommitAt: committedAt, + lastCommitMessage: c.commit && c.commit.message ? c.commit.message.split('\n')[0] : null, + lastCommitAgeSeconds: ageSeconds + } + } else { + meta = { lastCommitSha: null, lastCommitAt: null, lastCommitMessage: null, lastCommitAgeSeconds: null } + } + } catch { + meta = { lastCommitSha: null, lastCommitAt: null, lastCommitMessage: null, lastCommitAgeSeconds: null } + } + setCachedCommitMeta(cacheKey, meta) + perRequestCommitCache.set(p, meta) + return meta + } + + // Helper to fetch a single file (returns null on failure) + const fetchFile = async (p) => { + try { + const fileResp = await github.repos.getContent({ owner: env.SAFE_SETTINGS_HUB_ORG, repo: env.SAFE_SETTINGS_HUB_REPO, path: p, ref }) + if (Array.isArray(fileResp.data)) return null + // file + const commitMeta = await fetchCommitMeta(fileResp.data.path) + if (fetchContent && typeof fileResp.data.content === 'string') { + const decoded = Buffer.from(fileResp.data.content, fileResp.data.encoding || 'base64').toString('utf8') + return { + type: fileResp.data.type, + name: path.posix.basename(p), + path: fileResp.data.path, + sha: fileResp.data.sha, + size: fileResp.data.size, + encoding: 'utf8', + content: decoded, + originalEncoding: fileResp.data.encoding || 'base64', + ...commitMeta + } + } + // metadata-only response + return { + type: fileResp.data.type, + name: path.posix.basename(p), + path: fileResp.data.path, + sha: fileResp.data.sha, + size: fileResp.data.size, + content: null, + originalEncoding: fileResp.data.encoding || 'base64', + ...commitMeta + } + } catch (e) { + robot.log.warn(`Failed to fetch file ${p}: ${e.message}`) + return null + } + } + + // Recursive traversal with depth limiting and basic cycle protection + const seen = new Set() + // Concurrency limiter for directory entry processing + const MAX_DIR_CONCURRENCY = parseInt(process.env.DIR_ENTRY_CONCURRENCY || '6') + async function mapWithLimit (items, mapper) { + const out = [] + let i = 0 + const running = new Set() + async function run () { + if (i >= items.length) return + const idx = i++ + const p = Promise.resolve(mapper(items[idx], idx)).then(r => { out[idx] = r; running.delete(p) }) + running.add(p) + if (running.size >= MAX_DIR_CONCURRENCY) await Promise.race(running) + return run() + } + await run() + await Promise.all([...running]) + return out + } + + const traverseDir = async (dirPath, depth = 0) => { + if (depth >= maxDepth) { + const commitMeta = await fetchCommitMeta(dirPath) + return { type: 'dir', name: path.posix.basename(dirPath), path: dirPath, depth, truncated: true, entries: [], ...commitMeta } + } + if (seen.has(dirPath)) { + const commitMeta = await fetchCommitMeta(dirPath) + return { type: 'dir', name: path.posix.basename(dirPath), path: dirPath, depth, cycle: true, entries: [], ...commitMeta } + } + seen.add(dirPath) + let listing + try { + const resp = await github.repos.getContent({ owner: env.SAFE_SETTINGS_HUB_ORG, repo: env.SAFE_SETTINGS_HUB_REPO, path: dirPath, ref }) + if (!Array.isArray(resp.data)) { + // Not a directory; fetch as file instead + const f = await fetchFile(dirPath) + return f || { type: 'file', path: dirPath, error: 'unreadable' } + } + listing = resp.data + } catch (e) { + const commitMeta = await fetchCommitMeta(dirPath) + return { type: 'dir', name: path.posix.basename(dirPath), path: dirPath, error: e.status === 404 ? 'not_found' : e.message, entries: [], ...commitMeta } + } + + const entries = await mapWithLimit(listing, async (item) => { + if (item.type === 'file') { + if (fetchContent) { + const f = await fetchFile(item.path) + if (f) return f + const commitMeta = await fetchCommitMeta(item.path) + return { type: 'file', name: item.name, path: item.path, sha: item.sha, size: item.size, content: null, ...commitMeta } + } + const commitMeta = await fetchCommitMeta(item.path) + return { type: 'file', name: item.name, path: item.path, sha: item.sha, size: item.size, content: null, ...commitMeta } + } else if (item.type === 'dir') { + return traverseDir(item.path, depth + 1) + } + const commitMeta = await fetchCommitMeta(item.path) + return { type: item.type, name: item.name, path: item.path, unsupported: true, ...commitMeta } + }) + const commitMeta = await fetchCommitMeta(dirPath) + return { type: 'dir', name: path.posix.basename(dirPath), path: dirPath, depth, entries, ...commitMeta } + } + + const response = await github.repos.getContent({ + owner: env.SAFE_SETTINGS_HUB_ORG, + repo: env.SAFE_SETTINGS_HUB_REPO, + path: fullPath, + ref + }) + + const data = response.data + if (Array.isArray(data)) { + if (recursive) { + const tree = await traverseDir(fullPath, 0) + return res.json({ + recursive: true, + maxDepth, + ref, + fetchContent, + ...tree + }) + } else { + // non-recursive (original behavior) + const entries = await Promise.all(data.map(async d => { + if (d.type === 'file') { + if (fetchContent) { + const f = await fetchFile(d.path) + if (f) return f + } + return { + name: d.name, + path: d.path, + type: d.type, + sha: d.sha, + size: d.size, + content: null + } + } + return { + name: d.name, + path: d.path, + type: d.type, + sha: d.sha, + size: d.size, + content: null + } + })) + return res.json({ + type: 'dir', + path: fullPath, + entries, + ref, + fetchContent + }) + } + } + + if (typeof data.content === 'string') { + if (fetchContent) { + const decoded = Buffer.from(data.content, data.encoding || 'base64').toString('utf8') + return res.json({ + type: data.type, + path: data.path, + sha: data.sha, + size: data.size, + encoding: 'utf8', + content: decoded, + originalEncoding: data.encoding || 'base64', + ref, + fetchContent: true + }) + } + return res.json({ + type: data.type, + path: data.path, + sha: data.sha, + size: data.size, + content: null, + ref, + fetchContent: false + }) + } + // Unsupported type (symlink, submodule, etc.) + return res.status(415).json({ error: 'Unsupported content type returned by GitHub API' }) + } catch (e) { + if (e.status === 404) { + return res.status(404).json({ error: 'Not found' }) + } + robot.log && robot.log.error && robot.log.error(e) + return res.status(500).json({ error: e.message || 'unexpected error' }) + } + } + + router.get('/api/safe-settings/hub/content', hubContent) + router.get('/api/safe-settings/hub/content/*', hubContent) + + /** + * GET /api/safe-settings/app/env + * Returns key/value pairs parsed from the project .env file excluding + * standard GitHub App infrastructure variables. + * Query params: + * includeInfra=true -> include normally excluded infrastructure vars + */ + router.get('/api/safe-settings/app/env', (req, res) => { + try { + // Define a blacklist of sensitive environment variable keys to exclude + const ENV_BLACKLIST = ['PRIVATE_KEY_PATH']; + const variables = Object.entries(env) + .filter(([key]) => !ENV_BLACKLIST.includes(key)) + .map(([key, value]) => ({ key, value })) + .sort((a, b) => a.key.localeCompare(b.key)); + return res.json({ updatedAt: new Date().toISOString(), count: variables.length, variables }); + } catch (e) { + robot.log && robot.log.error && robot.log.error(e); + return res.status(500).json({ error: e.message || 'unexpected error' }); + } + }) + + + // POST /api/safe-settings/hub/import + // Body: { orgs: ['org1','org2'] } + router.post('/api/safe-settings/hub/import', async (req, res) => { + try { + const body = req.body || {} + const orgs = Array.isArray(body.orgs) ? body.orgs : (body.org ? [body.org] : null) + if (!orgs || !orgs.length) { + return res.status(400).json({ error: 'Missing orgs in request body. Expected JSON { orgs: ["org1","org2"] }' }) + } + // lazy-require to avoid circular require issues during module load + const { retrieveSettingsFromOrgs } = require('./hubSyncHandler') + const results = await retrieveSettingsFromOrgs(robot, orgs) + return res.json({ ok: true, results }) + } catch (e) { + robot.log && robot.log.error && robot.log.error(e) + return res.status(500).json({ error: e.message || 'unexpected error' }) + } + }) + + + // GET /api/safe-settings/hub/log + // Returns parsed log entries (JSON): [{ timestamp, level, message }, ...] + router.get('/api/safe-settings/hub/log', async (req, res) => { + const lines = parseInt(req.query.lines || process.env.SAFE_SETTINGS_LOG_FILE_MAX_LINES || '1000', 10) + const levelsQuery = req.query.levels // comma-separated e.g. 'ERROR,WARN' + const allowedLevels = levelsQuery ? new Set(String(levelsQuery).split(',').map(s => s.trim().toUpperCase()).filter(Boolean)) : null + + const candidates = [] + if (process.env.SAFE_SETTINGS_LOG_FILE) candidates.push(process.env.SAFE_SETTINGS_LOG_FILE) + candidates.push(path.join(rootDir, 'safe-settings.log')) + candidates.push(path.join(rootDir, '..', 'safe-settings.log')) + candidates.push(path.join(rootDir, 'ui', 'safe-settings.log')) + + let found = null + for (const p of candidates) { + if (!p) continue + try { + const st = await fs.promises.stat(p) + if (st && st.isFile()) { found = p; break } + } catch (e) { + // ignore + } + } + if (!found) return res.status(404).json({ error: 'Log file not found' }) + + try { + const data = await fs.promises.readFile(found, 'utf8') + const arr = data.split(/\r?\n/).filter(Boolean) + const tail = arr.slice(-lines) + const parsed = tail.map(line => { + // Expecting format: 2025-09-10T12:34:56.789Z [INFO] message + const m = line.match(/^(\d{4}-\d{2}-\d{2}T[^\s]+)\s+\[([A-Z]+)\]\s+(.*)$/) + if (m) { + return { timestamp: m[1], level: m[2], message: m[3], raw: line } + } + // fallback: try to extract level in brackets + const m2 = line.match(/\[([A-Z]+)\]\s*(.*)$/) + if (m2) return { timestamp: null, level: m2[1], message: m2[2], raw: line } + return { timestamp: null, level: 'UNKNOWN', message: line, raw: line } + }) + const filtered = allowedLevels ? parsed.filter(p => allowedLevels.has(String(p.level).toUpperCase())) : parsed + return res.json({ count: filtered.length, entries: filtered }) + } catch (err) { + return res.status(500).json({ error: err && err.message ? err.message : String(err) }) + } + }) + + return router +} + +module.exports = { setupRoutes } diff --git a/lib/settings.js b/lib/settings.js index 20e71167..47e3201a 100644 --- a/lib/settings.js +++ b/lib/settings.js @@ -10,10 +10,10 @@ const env = require('./env') const CONFIG_PATH = env.CONFIG_PATH const eta = new Eta({ views: path.join(__dirname) }) const SCOPE = { ORG: 'org', REPO: 'repo' } // Determine if the setting is a org setting or repo setting -const yaml = require('js-yaml'); +const yaml = require('js-yaml') class Settings { - static fileCache = {}; + static fileCache = {} static async syncAll (nop, context, repo, config, ref) { const settings = new Settings(nop, context, repo, config, ref) @@ -170,10 +170,10 @@ class Settings { // remove duplicate rows in this.results this.results = this.results.filter((thing, index, self) => { - return index === self.findIndex((t) => { - return t.type === thing.type && t.repo === thing.repo && t.plugin === thing.plugin - }) + return index === self.findIndex((t) => { + return t.type === thing.type && t.repo === thing.repo && t.plugin === thing.plugin }) + }) let error = false // Different logic @@ -300,7 +300,7 @@ ${this.results.reduce((x, y) => { } } - async updateRepos(repo) { + async updateRepos (repo) { this.subOrgConfigs = this.subOrgConfigs || await this.getSubOrgConfigs() // Keeping this as is instead of doing an object assign as that would cause `Cannot read properties of undefined (reading 'startsWith')` error // Copilot code review would recoommend using object assign but that would cause the error @@ -368,7 +368,6 @@ ${this.results.reduce((x, y) => { } } - async updateAll () { // this.subOrgConfigs = this.subOrgConfigs || await this.getSubOrgConfigs(this.github, this.repo, this.log) // this.repoConfigs = this.repoConfigs || await this.getRepoConfigs(this.github, this.repo, this.log) @@ -791,14 +790,14 @@ ${this.results.reduce((x, y) => { * @param params Params to fetch the file with * @return The parsed YAML file */ - async loadYaml(filePath) { + async loadYaml (filePath) { try { const repo = { owner: this.repo.owner, repo: env.ADMIN_REPO } const params = Object.assign(repo, { path: filePath, ref: this.ref }) - const namespacedFilepath = `${this.repo.owner}/${filePath}`; + const namespacedFilepath = `${this.repo.owner}/${filePath}` // If the filepath already exists in the fileCache, add the etag to the params // to check if the file has changed @@ -898,7 +897,6 @@ ${this.results.reduce((x, y) => { } } - async getSubOrgRepositories (subOrgProperties) { const organizationName = this.repo.owner try { diff --git a/lib/utils.js b/lib/utils.js new file mode 100644 index 00000000..e69de29b diff --git a/package-lock.json b/package-lock.json index 92cf50eb..6fb37db3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,14 @@ "license": "ISC", "dependencies": { "@apidevtools/json-schema-ref-parser": "^12.0.2", + "@octokit/auth-app": "^8.0.2", "@probot/adapter-aws-lambda-serverless": "^4.0.3", "deepmerge": "^4.3.1", "eta": "^3.5.0", "js-yaml": "^4.1.0", "lodash": "^4.17.21", - "minimatch": "^10.0.1", + "minimatch": "^10.0.3", + "next": "^15.5.2", "node-cron": "^3.0.2", "octokit": "^5.0.2", "probot": "^13.4.4", @@ -40,7 +42,8 @@ "nodemon": "^3.1.9", "npm-run-all": "^4.1.5", "smee-client": "^3.1.1", - "standard": "^17.1.2" + "standard": "^17.1.2", + "supertest": "^7.1.4" }, "engines": { "node": ">= 16.0.0" @@ -706,6 +709,16 @@ "node": ">=18" } }, + "node_modules/@emnapi/runtime": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -887,12 +900,451 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz", + "integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz", + "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz", + "integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz", + "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz", + "integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz", + "integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz", + "integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz", + "integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz", + "integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz", + "integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz", + "integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz", + "integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz", + "integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz", + "integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz", + "integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.0" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz", + "integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz", + "integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.0" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz", + "integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz", + "integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.4.4" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz", + "integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz", + "integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz", + "integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@ioredis/commands": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==", "license": "MIT" }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", + "integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", + "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1701,21 +2153,168 @@ "node": ">=18" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } + "node_modules/@next/env": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.2.tgz", + "integrity": "sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==", + "license": "MIT" }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", + "node_modules/@next/swc-darwin-arm64": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.2.tgz", + "integrity": "sha512-8bGt577BXGSd4iqFygmzIfTYizHb0LGWqH+qgIF/2EDxS5JsSdERJKA8WgwDyNBZgTIIA4D8qUtoQHmxIIquoQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.2.tgz", + "integrity": "sha512-2DjnmR6JHK4X+dgTXt5/sOCu/7yPtqpYt8s8hLkHFK3MGkka2snTv3yRMdHvuRtJVkPwCGsvBSwmoQCHatauFQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.2.tgz", + "integrity": "sha512-3j7SWDBS2Wov/L9q0mFJtEvQ5miIqfO4l7d2m9Mo06ddsgUK8gWfHGgbjdFlCp2Ek7MmMQZSxpGFqcC8zGh2AA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.2.tgz", + "integrity": "sha512-s6N8k8dF9YGc5T01UPQ08yxsK6fUow5gG1/axWc1HVVBYQBgOjca4oUZF7s4p+kwhkB1bDSGR8QznWrFZ/Rt5g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.2.tgz", + "integrity": "sha512-o1RV/KOODQh6dM6ZRJGZbc+MOAHww33Vbs5JC9Mp1gDk8cpEO+cYC/l7rweiEalkSm5/1WGa4zY7xrNwObN4+Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.2.tgz", + "integrity": "sha512-/VUnh7w8RElYZ0IV83nUcP/J4KJ6LLYliiBIri3p3aW2giF+PAVgZb6mk8jbQSB3WlTai8gEmCAr7kptFa1H6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.2.tgz", + "integrity": "sha512-sMPyTvRcNKXseNQ/7qRfVRLa0VhR0esmQ29DD6pqvG71+JdVnESJaHPA8t7bc67KD5spP3+DOCNLhqlEI2ZgQg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.2.tgz", + "integrity": "sha512-W5VvyZHnxG/2ukhZF/9Ikdra5fdNftxI6ybeVKYvBPDtyx7x4jPPSNduUkfH5fo3zG0JQ0bPxgy41af2JX5D4Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, @@ -1753,68 +2352,6 @@ "node": ">= 20" } }, - "node_modules/@octokit/app/node_modules/@octokit/auth-app": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-8.0.1.tgz", - "integrity": "sha512-P2J5pB3pjiGwtJX4WqJVYCtNkcZ+j5T2Wm14aJAEIC3WJOrv12jvBley3G1U/XI8q9o1A7QMG54LiFED2BiFlg==", - "dependencies": { - "@octokit/auth-oauth-app": "^9.0.1", - "@octokit/auth-oauth-user": "^6.0.0", - "@octokit/request": "^10.0.2", - "@octokit/request-error": "^7.0.0", - "@octokit/types": "^14.0.0", - "toad-cache": "^3.7.0", - "universal-github-app-jwt": "^2.2.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/app/node_modules/@octokit/auth-oauth-app": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.1.tgz", - "integrity": "sha512-TthWzYxuHKLAbmxdFZwFlmwVyvynpyPmjwc+2/cI3cvbT7mHtsAW9b1LvQaNnAuWL+pFnqtxdmrU8QpF633i1g==", - "dependencies": { - "@octokit/auth-oauth-device": "^8.0.1", - "@octokit/auth-oauth-user": "^6.0.0", - "@octokit/request": "^10.0.2", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/app/node_modules/@octokit/auth-oauth-device": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.1.tgz", - "integrity": "sha512-TOqId/+am5yk9zor0RGibmlqn4V0h8vzjxlw/wYr3qzkQxl8aBPur384D1EyHtqvfz0syeXji4OUvKkHvxk/Gw==", - "dependencies": { - "@octokit/oauth-methods": "^6.0.0", - "@octokit/request": "^10.0.2", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/app/node_modules/@octokit/auth-oauth-user": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.0.tgz", - "integrity": "sha512-GV9IW134PHsLhtUad21WIeP9mlJ+QNpFd6V9vuPWmaiN25HEJeEQUcS4y5oRuqCm9iWDLtfIs+9K8uczBXKr6A==", - "dependencies": { - "@octokit/auth-oauth-device": "^8.0.1", - "@octokit/oauth-methods": "^6.0.0", - "@octokit/request": "^10.0.2", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, "node_modules/@octokit/app/node_modules/@octokit/auth-token": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", @@ -1877,28 +2414,6 @@ "node": ">= 20" } }, - "node_modules/@octokit/app/node_modules/@octokit/oauth-authorization-url": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz", - "integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/app/node_modules/@octokit/oauth-methods": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-6.0.0.tgz", - "integrity": "sha512-Q8nFIagNLIZgM2odAraelMcDssapc+lF+y3OlcIPxyAU+knefO8KmozGqfnma1xegRDP4z5M73ABsamn72bOcA==", - "dependencies": { - "@octokit/oauth-authorization-url": "^8.0.0", - "@octokit/request": "^10.0.2", - "@octokit/request-error": "^7.0.0", - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 20" - } - }, "node_modules/@octokit/app/node_modules/@octokit/openapi-types": { "version": "25.0.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.0.0.tgz", @@ -1978,156 +2493,325 @@ "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==" }, - "node_modules/@octokit/app/node_modules/universal-github-app-jwt": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz", - "integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==" - }, "node_modules/@octokit/app/node_modules/universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==" }, "node_modules/@octokit/auth-app": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.3.tgz", - "integrity": "sha512-dcaiteA6Y/beAlDLZOPNReN3FGHu+pARD6OHfh3T9f3EO09++ec+5wt3KtGGSSs2Mp5tI8fQwdMOEnrzBLfgUA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-8.0.2.tgz", + "integrity": "sha512-dLTmmA9gUlqiAJZgozfOsZFfpN/OldH3xweb7lqSnngax5Rs+PfO5dDlokaBfc41H1xOtsLYV5QqR0DkBAtPmw==", "license": "MIT", "dependencies": { - "@octokit/auth-oauth-app": "^7.1.0", - "@octokit/auth-oauth-user": "^4.1.0", - "@octokit/request": "^8.3.1", - "@octokit/request-error": "^5.1.0", - "@octokit/types": "^13.1.0", - "deprecation": "^2.3.1", - "lru-cache": "npm:@wolfy1339/lru-cache@^11.0.2-patch.1", - "universal-github-app-jwt": "^1.1.2", - "universal-user-agent": "^6.0.0" + "@octokit/auth-oauth-app": "^9.0.1", + "@octokit/auth-oauth-user": "^6.0.0", + "@octokit/request": "^10.0.2", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "toad-cache": "^3.7.0", + "universal-github-app-jwt": "^2.2.0", + "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/endpoint": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.0.tgz", + "integrity": "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" } }, "node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", - "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", "license": "MIT" }, - "node_modules/@octokit/auth-app/node_modules/@octokit/types": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz", - "integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==", + "node_modules/@octokit/auth-app/node_modules/@octokit/request": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.3.tgz", + "integrity": "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^23.0.1" + "@octokit/endpoint": "^11.0.0", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" } }, - "node_modules/@octokit/auth-app/node_modules/lru-cache": { - "name": "@wolfy1339/lru-cache", - "version": "11.0.2-patch.1", - "resolved": "https://registry.npmjs.org/@wolfy1339/lru-cache/-/lru-cache-11.0.2-patch.1.tgz", - "integrity": "sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==", - "license": "ISC", + "node_modules/@octokit/auth-app/node_modules/@octokit/request-error": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.0.tgz", + "integrity": "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, "engines": { - "node": "18 >=18.20 || 20 || >=22" + "node": ">= 20" } }, + "node_modules/@octokit/auth-app/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/@octokit/auth-app/node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "license": "ISC" + }, "node_modules/@octokit/auth-oauth-app": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.1.0.tgz", - "integrity": "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.1.tgz", + "integrity": "sha512-TthWzYxuHKLAbmxdFZwFlmwVyvynpyPmjwc+2/cI3cvbT7mHtsAW9b1LvQaNnAuWL+pFnqtxdmrU8QpF633i1g==", "license": "MIT", "dependencies": { - "@octokit/auth-oauth-device": "^6.1.0", - "@octokit/auth-oauth-user": "^4.1.0", - "@octokit/request": "^8.3.1", - "@octokit/types": "^13.0.0", - "@types/btoa-lite": "^1.0.0", - "btoa-lite": "^1.0.0", - "universal-user-agent": "^6.0.0" + "@octokit/auth-oauth-device": "^8.0.1", + "@octokit/auth-oauth-user": "^6.0.0", + "@octokit/request": "^10.0.2", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/endpoint": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.0.tgz", + "integrity": "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" } }, "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", - "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", "license": "MIT" }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/request": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.3.tgz", + "integrity": "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.0", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/request-error": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.0.tgz", + "integrity": "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz", - "integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^23.0.1" + "@octokit/openapi-types": "^25.1.0" } }, + "node_modules/@octokit/auth-oauth-app/node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "license": "ISC" + }, "node_modules/@octokit/auth-oauth-device": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.1.0.tgz", - "integrity": "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.1.tgz", + "integrity": "sha512-TOqId/+am5yk9zor0RGibmlqn4V0h8vzjxlw/wYr3qzkQxl8aBPur384D1EyHtqvfz0syeXji4OUvKkHvxk/Gw==", "license": "MIT", "dependencies": { - "@octokit/oauth-methods": "^4.1.0", - "@octokit/request": "^8.3.1", - "@octokit/types": "^13.0.0", - "universal-user-agent": "^6.0.0" + "@octokit/oauth-methods": "^6.0.0", + "@octokit/request": "^10.0.2", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/endpoint": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.0.tgz", + "integrity": "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" } }, "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", - "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", "license": "MIT" }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/request": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.3.tgz", + "integrity": "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.0", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/request-error": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.0.tgz", + "integrity": "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz", - "integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^23.0.1" + "@octokit/openapi-types": "^25.1.0" } }, + "node_modules/@octokit/auth-oauth-device/node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "license": "ISC" + }, "node_modules/@octokit/auth-oauth-user": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.1.0.tgz", - "integrity": "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.0.tgz", + "integrity": "sha512-GV9IW134PHsLhtUad21WIeP9mlJ+QNpFd6V9vuPWmaiN25HEJeEQUcS4y5oRuqCm9iWDLtfIs+9K8uczBXKr6A==", "license": "MIT", "dependencies": { - "@octokit/auth-oauth-device": "^6.1.0", - "@octokit/oauth-methods": "^4.1.0", - "@octokit/request": "^8.3.1", - "@octokit/types": "^13.0.0", - "btoa-lite": "^1.0.0", - "universal-user-agent": "^6.0.0" + "@octokit/auth-oauth-device": "^8.0.1", + "@octokit/oauth-methods": "^6.0.0", + "@octokit/request": "^10.0.2", + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/endpoint": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.0.tgz", + "integrity": "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" } }, "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", - "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", "license": "MIT" }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/request": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.3.tgz", + "integrity": "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.0", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/request-error": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.0.tgz", + "integrity": "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz", - "integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^23.0.1" + "@octokit/openapi-types": "^25.1.0" } }, + "node_modules/@octokit/auth-oauth-user/node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "license": "ISC" + }, "node_modules/@octokit/auth-token": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", @@ -2248,50 +2932,6 @@ "node": ">= 20" } }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/auth-oauth-app": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.1.tgz", - "integrity": "sha512-TthWzYxuHKLAbmxdFZwFlmwVyvynpyPmjwc+2/cI3cvbT7mHtsAW9b1LvQaNnAuWL+pFnqtxdmrU8QpF633i1g==", - "dependencies": { - "@octokit/auth-oauth-device": "^8.0.1", - "@octokit/auth-oauth-user": "^6.0.0", - "@octokit/request": "^10.0.2", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/auth-oauth-device": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.1.tgz", - "integrity": "sha512-TOqId/+am5yk9zor0RGibmlqn4V0h8vzjxlw/wYr3qzkQxl8aBPur384D1EyHtqvfz0syeXji4OUvKkHvxk/Gw==", - "dependencies": { - "@octokit/oauth-methods": "^6.0.0", - "@octokit/request": "^10.0.2", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/auth-oauth-user": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.0.tgz", - "integrity": "sha512-GV9IW134PHsLhtUad21WIeP9mlJ+QNpFd6V9vuPWmaiN25HEJeEQUcS4y5oRuqCm9iWDLtfIs+9K8uczBXKr6A==", - "dependencies": { - "@octokit/auth-oauth-device": "^8.0.1", - "@octokit/oauth-methods": "^6.0.0", - "@octokit/request": "^10.0.2", - "@octokit/types": "^14.0.0", - "universal-user-agent": "^7.0.0" - }, - "engines": { - "node": ">= 20" - } - }, "node_modules/@octokit/oauth-app/node_modules/@octokit/auth-token": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", @@ -2354,28 +2994,6 @@ "node": ">= 20" } }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/oauth-authorization-url": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz", - "integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@octokit/oauth-app/node_modules/@octokit/oauth-methods": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-6.0.0.tgz", - "integrity": "sha512-Q8nFIagNLIZgM2odAraelMcDssapc+lF+y3OlcIPxyAU+knefO8KmozGqfnma1xegRDP4z5M73ABsamn72bOcA==", - "dependencies": { - "@octokit/oauth-authorization-url": "^8.0.0", - "@octokit/request": "^10.0.2", - "@octokit/request-error": "^7.0.0", - "@octokit/types": "^14.0.0" - }, - "engines": { - "node": ">= 20" - } - }, "node_modules/@octokit/oauth-app/node_modules/@octokit/openapi-types": { "version": "25.0.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.0.0.tgz", @@ -2426,45 +3044,91 @@ "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==" }, "node_modules/@octokit/oauth-authorization-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", - "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz", + "integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==", "license": "MIT", "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/@octokit/oauth-methods": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz", - "integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-6.0.0.tgz", + "integrity": "sha512-Q8nFIagNLIZgM2odAraelMcDssapc+lF+y3OlcIPxyAU+knefO8KmozGqfnma1xegRDP4z5M73ABsamn72bOcA==", "license": "MIT", "dependencies": { - "@octokit/oauth-authorization-url": "^6.0.2", - "@octokit/request": "^8.3.1", - "@octokit/request-error": "^5.1.0", - "@octokit/types": "^13.0.0", - "btoa-lite": "^1.0.0" + "@octokit/oauth-authorization-url": "^8.0.0", + "@octokit/request": "^10.0.2", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0" }, "engines": { - "node": ">= 18" + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/endpoint": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.0.tgz", + "integrity": "sha512-hoYicJZaqISMAI3JfaDr1qMNi48OctWuOih1m80bkYow/ayPw6Jj52tqWJ6GEoFTk1gBqfanSoI1iY99Z5+ekQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" } }, "node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", - "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", "license": "MIT" }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/request": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.3.tgz", + "integrity": "sha512-V6jhKokg35vk098iBqp2FBKunk3kMTXlmq+PtbV9Gl3TfskWlebSofU9uunVKhUN7xl+0+i5vt0TGTG8/p/7HA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.0", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/request-error": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.0.tgz", + "integrity": "sha512-KRA7VTGdVyJlh0cP5Tf94hTiYVVqmt2f3I6mnimmaVz4UG3gQV/k4mDJlJv3X67iX6rmN7gSHCF8ssqeMnmhZg==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz", - "integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^23.0.1" + "@octokit/openapi-types": "^25.1.0" } }, + "node_modules/@octokit/oauth-methods/node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "license": "ISC" + }, "node_modules/@octokit/openapi-types": { "version": "20.0.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", @@ -3309,6 +3973,16 @@ "@opentelemetry/api": "^1.1.0" } }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, "node_modules/@prisma/instrumentation": { "version": "5.22.0", "resolved": "https://registry.npmjs.org/@prisma/instrumentation/-/instrumentation-5.22.0.tgz", @@ -3506,6 +4180,15 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@travi/any": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@travi/any/-/any-3.1.2.tgz", @@ -3675,9 +4358,9 @@ "dev": true }, "node_modules/@types/jsonwebtoken": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", - "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "license": "MIT", "dependencies": { "@types/ms": "*", @@ -4156,6 +4839,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4377,6 +5067,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/before-after-hook": { @@ -4443,15 +5134,6 @@ "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -4623,7 +5305,6 @@ "version": "1.0.30001615", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001615.tgz", "integrity": "sha512-1IpazM5G3r38meiae0bHRnPhz+CBQ3ZLqbQMtrg+AsTPKAXgW38JNsXkyZ+v8waCsDmPq87lmfun5Q2AGysNEQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -4752,6 +5433,12 @@ "node": ">=6" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -4791,6 +5478,20 @@ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -4804,7 +5505,38 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "devOptional": true + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "optional": true }, "node_modules/colorette": { "version": "2.0.20", @@ -4857,6 +5589,16 @@ "node": ">=18" } }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4902,6 +5644,13 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, "node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -5124,11 +5873,12 @@ } }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -5256,6 +6006,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -5265,6 +6025,17 @@ "node": ">=8" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -6726,6 +7497,24 @@ "node": ">= 6" } }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -9678,12 +10467,12 @@ } }, "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", "license": "MIT", "dependencies": { - "buffer-equal-constant-time": "1.0.1", + "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } @@ -10088,12 +10877,12 @@ } }, "node_modules/minimatch": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", - "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", + "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { "node": "20 || >=22" @@ -10129,9 +10918,28 @@ "license": "MIT" }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } }, "node_modules/natural-compare": { "version": "1.4.0", @@ -10147,6 +10955,58 @@ "node": ">= 0.6" } }, + "node_modules/next": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/next/-/next-15.5.2.tgz", + "integrity": "sha512-H8Otr7abj1glFhbGnvUt3gz++0AF1+QoCXEBmd/6aKbfdFwrn0LpA836Ed5+00va/7HQSDD+mOoVhn3tNy3e/Q==", + "license": "MIT", + "dependencies": { + "@next/env": "15.5.2", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.5.2", + "@next/swc-darwin-x64": "15.5.2", + "@next/swc-linux-arm64-gnu": "15.5.2", + "@next/swc-linux-arm64-musl": "15.5.2", + "@next/swc-linux-x64-gnu": "15.5.2", + "@next/swc-linux-x64-musl": "15.5.2", + "@next/swc-win32-arm64-msvc": "15.5.2", + "@next/swc-win32-x64-msvc": "15.5.2", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -10586,6 +11446,172 @@ "@octokit/core": ">=5" } }, + "node_modules/octokit-auth-probot/node_modules/@octokit/auth-app": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-6.1.4.tgz", + "integrity": "sha512-QkXkSOHZK4dA5oUqY5Dk3S+5pN2s1igPjEASNQV8/vgJgW034fQWR16u7VsNOK/EljA00eyjYF5mWNxWKWhHRQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^7.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.1.0", + "deprecation": "^2.3.1", + "lru-cache": "npm:@wolfy1339/lru-cache@^11.0.2-patch.1", + "universal-github-app-jwt": "^1.1.2", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/auth-app/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-app": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-7.1.0.tgz", + "integrity": "sha512-w+SyJN/b0l/HEb4EOPRudo7uUOSW51jcK1jwLa+4r7PA8FPFpoxEnHBHMITqCsc/3Vo2qqFjgQfz/xUUvsSQnA==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/auth-oauth-user": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "@types/btoa-lite": "^1.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-device": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-6.1.0.tgz", + "integrity": "sha512-FNQ7cb8kASufd6Ej4gnJ3f1QB5vJitkoV1O0/g6e6lUsQ7+VsSNRHRmFScN2tV4IgKA12frrr/cegUs0t+0/Lw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-user": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-4.1.0.tgz", + "integrity": "sha512-FrEp8mtFuS/BrJyjpur+4GARteUCrPeR/tZJzD8YourzoVhRics7u7we/aDcKv+yywRNwNi/P4fRi631rG/OyQ==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^6.1.0", + "@octokit/oauth-methods": "^4.1.0", + "@octokit/request": "^8.3.1", + "@octokit/types": "^13.0.0", + "btoa-lite": "^1.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/oauth-authorization-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-6.0.2.tgz", + "integrity": "sha512-CdoJukjXXxqLNK4y/VOiVzQVjibqoj/xHgInekviUJV73y/BSIcwvJ/4aNHPBPKcPWFnd4/lO9uqRV65jXhcLA==", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/oauth-methods": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-4.1.0.tgz", + "integrity": "sha512-4tuKnCRecJ6CG6gr0XcEXdZtkTDbfbnD5oaHBmLERTjTMZNi2CbfEHZxPU41xXLDG4DfKf+sonu00zvKI9NSbw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^6.0.2", + "@octokit/request": "^8.3.1", + "@octokit/request-error": "^5.1.0", + "@octokit/types": "^13.0.0", + "btoa-lite": "^1.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { + "version": "13.10.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.10.0.tgz", + "integrity": "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^24.2.0" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/openapi-types": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-24.2.0.tgz", + "integrity": "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==", + "license": "MIT" + }, + "node_modules/octokit-auth-probot/node_modules/lru-cache": { + "name": "@wolfy1339/lru-cache", + "version": "11.0.2-patch.1", + "resolved": "https://registry.npmjs.org/@wolfy1339/lru-cache/-/lru-cache-11.0.2-patch.1.tgz", + "integrity": "sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA==", + "license": "ISC", + "engines": { + "node": "18 >=18.20 || 20 || >=22" + } + }, + "node_modules/octokit-auth-probot/node_modules/universal-github-app-jwt": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.2.0.tgz", + "integrity": "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "^9.0.0", + "jsonwebtoken": "^9.0.2" + } + }, "node_modules/octokit/node_modules/@octokit/auth-token": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", @@ -11021,7 +12047,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -11280,6 +12305,34 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -11579,6 +12632,29 @@ "node": ">= 0.8" } }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -12006,6 +13082,13 @@ "node": ">=v12.22.7" } }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT", + "peer": true + }, "node_modules/secure-json-parse": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", @@ -12078,11 +13161,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -12157,6 +13235,62 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/sharp": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz", + "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.4", + "semver": "^7.7.2" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.3", + "@img/sharp-darwin-x64": "0.34.3", + "@img/sharp-libvips-darwin-arm64": "1.2.0", + "@img/sharp-libvips-darwin-x64": "1.2.0", + "@img/sharp-libvips-linux-arm": "1.2.0", + "@img/sharp-libvips-linux-arm64": "1.2.0", + "@img/sharp-libvips-linux-ppc64": "1.2.0", + "@img/sharp-libvips-linux-s390x": "1.2.0", + "@img/sharp-libvips-linux-x64": "1.2.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.0", + "@img/sharp-libvips-linuxmusl-x64": "1.2.0", + "@img/sharp-linux-arm": "0.34.3", + "@img/sharp-linux-arm64": "0.34.3", + "@img/sharp-linux-ppc64": "0.34.3", + "@img/sharp-linux-s390x": "0.34.3", + "@img/sharp-linux-x64": "0.34.3", + "@img/sharp-linuxmusl-arm64": "0.34.3", + "@img/sharp-linuxmusl-x64": "0.34.3", + "@img/sharp-wasm32": "0.34.3", + "@img/sharp-win32-arm64": "0.34.3", + "@img/sharp-win32-ia32": "0.34.3", + "@img/sharp-win32-x64": "0.34.3" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -12271,6 +13405,23 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT", + "optional": true + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -12357,6 +13508,15 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", @@ -12824,6 +13984,77 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/superagent": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.2.3.tgz", + "integrity": "sha512-y/hkYGeXAj7wUMjxRbB21g/l6aAEituGXM9Rwl4o20+SX3e8YOSV6BxFXl+dL3Uk0mjSL3kCbNkwURm8/gEDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.4", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/supertest": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.1.4.tgz", + "integrity": "sha512-tjLPs7dVyqgItVFirHYqe2T+MfWc2VOBQ8QFKKbWTA3PU7liZR8zoSpAi/C1k1ilm9RsXIKYf197oap9wXGVYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^10.2.3" + }, + "engines": { + "node": ">=14.18.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -13043,10 +14274,10 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", @@ -13219,14 +14450,10 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/universal-github-app-jwt": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-1.2.0.tgz", - "integrity": "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g==", - "license": "MIT", - "dependencies": { - "@types/jsonwebtoken": "^9.0.0", - "jsonwebtoken": "^9.0.2" - } + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz", + "integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==", + "license": "MIT" }, "node_modules/universal-user-agent": { "version": "6.0.1", diff --git a/package.json b/package.json index e765fb80..8c6aee56 100644 --- a/package.json +++ b/package.json @@ -26,12 +26,14 @@ "license": "ISC", "dependencies": { "@apidevtools/json-schema-ref-parser": "^12.0.2", + "@octokit/auth-app": "^8.0.2", "@probot/adapter-aws-lambda-serverless": "^4.0.3", "deepmerge": "^4.3.1", "eta": "^3.5.0", "js-yaml": "^4.1.0", "lodash": "^4.17.21", - "minimatch": "^10.0.1", + "minimatch": "^10.0.3", + "next": "^15.5.2", "node-cron": "^3.0.2", "octokit": "^5.0.2", "probot": "^13.4.4", @@ -56,7 +58,8 @@ "nodemon": "^3.1.9", "npm-run-all": "^4.1.5", "smee-client": "^3.1.1", - "standard": "^17.1.2" + "standard": "^17.1.2", + "supertest": "^7.1.4" }, "standard": { "env": [ diff --git a/safe-settings.log b/safe-settings.log new file mode 100644 index 00000000..962c13d3 --- /dev/null +++ b/safe-settings.log @@ -0,0 +1,54 @@ +2025-09-11T01:43:41.125Z [INFO] Received 'pull_request.closed' event: 45 +2025-09-11T01:43:41.125Z [INFO] Pull request closed on Safe-Settings Hub: (jefeish-training/safe-settings-config-master) +2025-09-11T01:43:42.072Z [INFO] Files changed in PR #45: .github/safe-settings/globals/suborg.yml +2025-09-11T01:43:42.072Z [DEBUG] Detected changes in the globals folder. Routing to syncHubGlobalsUpdate(...). +2025-09-11T01:43:42.073Z [INFO] Syncing safe settings for 'globals/'. +2025-09-11T01:43:42.359Z [DEBUG] Loaded manifest.yml rules from hub repo:{ + "rules": [ + { + "name": "global-defaults", + "targets": [ + "*" + ], + "files": [ + "*.yml" + ], + "mergeStrategy": "merge" + }, + { + "name": "security-policies", + "targets": [ + "acme-*", + "foo-bar" + ], + "files": [ + "settings.yml" + ], + "mergeStrategy": "overwrite" + } + ] +} +2025-09-11T01:43:42.361Z [DEBUG] Evaluating globals file: .github/safe-settings/globals/suborg.yml +2025-09-11T01:43:42.361Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'jetest99' with mergeStrategy='merge' +2025-09-11T01:43:42.361Z [DEBUG] Rule 'global-defaults' matches file 'suborg.yml'. Targets: jetest99, jefeish-training, jefeish-test1, copilot-for-emus, jefeish-migration-test, decyjphr-training, decyjphr-emu +2025-09-11T01:43:42.988Z [DEBUG] Checking existence of .github/suborg.yml in jetest99/safe-settings-config +2025-09-11T01:43:43.292Z [INFO] Skipping sync of suborg.yml to jetest99 (already exists & mergeStrategy=merge) +2025-09-11T01:43:43.292Z [DEBUG] Found .github/suborg.yml in jetest99/safe-settings-config +2025-09-11T01:43:43.292Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'jefeish-training' with mergeStrategy='merge' +2025-09-11T01:43:43.773Z [DEBUG] Checking existence of .github/suborg.yml in jefeish-training/safe-settings-config +2025-09-11T01:43:44.077Z [DEBUG] Found .github/suborg.yml in jefeish-training/safe-settings-config +2025-09-11T01:43:44.078Z [INFO] Skipping sync of suborg.yml to jefeish-training (already exists & mergeStrategy=merge) +2025-09-11T01:43:44.078Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'jefeish-test1' with mergeStrategy='merge' +2025-09-11T01:43:44.793Z [DEBUG] Checking existence of .github/suborg.yml in jefeish-test1/safe-settings-config +2025-09-11T01:43:45.082Z [DEBUG] Found .github/suborg.yml in jefeish-test1/safe-settings-config +2025-09-11T01:43:45.082Z [INFO] Skipping sync of suborg.yml to jefeish-test1 (already exists & mergeStrategy=merge) +2025-09-11T01:43:45.082Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'copilot-for-emus' with mergeStrategy='merge' +2025-09-11T01:43:45.593Z [INFO] Skipping org copilot-for-emus: config repo 'safe-settings-config' does not exist. +2025-09-11T01:43:45.593Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'jefeish-migration-test' with mergeStrategy='merge' +2025-09-11T01:43:46.208Z [DEBUG] Checking existence of .github/suborg.yml in jefeish-migration-test/safe-settings-config +2025-09-11T01:43:46.461Z [INFO] Skipping sync of suborg.yml to jefeish-migration-test (already exists & mergeStrategy=merge) +2025-09-11T01:43:46.461Z [DEBUG] Found .github/suborg.yml in jefeish-migration-test/safe-settings-config +2025-09-11T01:43:46.461Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'decyjphr-training' with mergeStrategy='merge' +2025-09-11T01:43:46.897Z [INFO] Skipping org decyjphr-training: config repo 'safe-settings-config' does not exist. +2025-09-11T01:43:46.897Z [DEBUG] Preparing to sync file 'suborg.yml' to org 'decyjphr-emu' with mergeStrategy='merge' +2025-09-11T01:43:47.342Z [INFO] Skipping org decyjphr-emu: config repo 'safe-settings-config' does not exist. diff --git a/test/unit/index.test.js b/test/unit/index.test.js index feae42d9..b053e845 100644 --- a/test/unit/index.test.js +++ b/test/unit/index.test.js @@ -1,16 +1,18 @@ const { Probot } = require('probot') const plugin = require('../../index') +jest.mock('../../lib/hubSyncHandler', () => ({ hubSyncHandler: jest.fn() })) +const { hubSyncHandler } = require('../../lib/hubSyncHandler') describe.skip('plugin', () => { let app, event, sync, github beforeEach(() => { class Octokit { - static defaults () { + static defaults() { return Octokit } - constructor () { + constructor() { this.config = { get: jest.fn().mockReturnValue({}) } @@ -19,7 +21,7 @@ describe.skip('plugin', () => { } } - auth () { + auth() { return this } } @@ -39,6 +41,8 @@ describe.skip('plugin', () => { sync = jest.fn() plugin(app, {}, { sync, FILE_NAME: '.github/settings.yml' }) + jest.clearAllMocks() + }) describe('with settings modified on master', () => { diff --git a/test/unit/lib/hubSyncHandler.test.js b/test/unit/lib/hubSyncHandler.test.js new file mode 100644 index 00000000..618d1f32 --- /dev/null +++ b/test/unit/lib/hubSyncHandler.test.js @@ -0,0 +1,106 @@ + +// Import the functions to test from the implementation file +const { hubSyncHandler, retrieveSettingsFromOrgs } = require('../../../lib/hubSyncHandler') + +// --- Mock dependencies --- +// Mock the env module to provide controlled environment variables for tests +jest.mock('../../../lib/env', () => ({ + SAFE_SETTINGS_HUB_ORG: 'test-org', // Simulate the hub org name + SAFE_SETTINGS_HUB_REPO: 'test-repo', // Simulate the hub repo name + ADMIN_REPO: 'admin-repo', // Simulate the admin repo name + CONFIG_PATH: '.github', // Simulate the config path + SAFE_SETTINGS_HUB_PATH: 'safe-settings', // Simulate the hub path + SAFE_SETTINGS_HUB_DIRECT_PUSH: 'true' // Simulate direct push mode +})) +// Mock the installationCache module to control installation lookups +jest.mock('../../../lib/installationCache', () => ({ + getInstallations: jest.fn() +})) + +// --- Create mock objects for robot and context --- +// Mock robot object with logging and auth methods +const mockRobot = { + log: { + info: jest.fn(), // Track info logs + warn: jest.fn(), // Track warning logs + error: jest.fn() // Track error logs + }, + auth: jest.fn() // Mock authentication method +} + +// Mock context object to simulate GitHub event payloads and API +const mockContext = { + payload: { + repository: { + name: 'test-repo', // Simulate repo name + owner: { login: 'test-org' }, // Simulate repo owner + full_name: 'test-org/test-repo' // Simulate full repo name + }, + pull_request: { number: 1, head: { sha: 'abc123' } } // Simulate pull request info + }, + repo: () => ({ owner: 'test-org', repo: 'test-repo' }), // Simulate repo lookup + octokit: { + paginate: jest.fn(), // Mock pagination for API calls + rest: { + pulls: { + listFiles: jest.fn() // Mock listFiles API + } + } + } +} + +// --- Unit tests for hubSyncHandler --- +describe('hubSyncHandler', () => { + // Test that hubSyncHandler ignores events from non-master repo/org + it('should ignore non-master repo/org', async () => { + const context = { ...mockContext, payload: { repository: { name: 'other', owner: { login: 'other' } } } } + await hubSyncHandler(mockRobot, context) + expect(mockRobot.log.info).toHaveBeenCalledWith(expect.stringContaining('ignoring')) + }) + + // Test routing for organizations folder changes + it('should call syncHubOrgUpdate for organizations folder changes', async () => { + const orgFile = '.github/safe-settings/organizations/acme/settings.yml' + const files = [{ filename: orgFile }] + const context = { + ...mockContext, + octokit: { ...mockContext.octokit, paginate: jest.fn().mockResolvedValue(files) }, + payload: { ...mockContext.payload, repository: { name: 'test-repo', owner: { login: 'test-org' }, full_name: 'test-org/test-repo' }, pull_request: { number: 1, head: { sha: 'abc123' } } } + } + const mod = require('../../../lib/hubSyncHandler') + // Spy on syncHubOrgUpdate + const spy = jest.spyOn(mod, 'syncHubOrgUpdate').mockImplementation(jest.fn()) + await mod.hubSyncHandler(mockRobot, context) + expect(spy).toHaveBeenCalledWith(mockRobot, context, 'acme', expect.anything(), expect.anything()) + spy.mockRestore() + }) + + // Test routing for globals folder changes + it('should call syncHubGlobalsUpdate for globals folder changes', async () => { + const globalsFile = '.github/safe-settings/globals/foo.yml' + const files = [{ filename: globalsFile }] + const context = { + ...mockContext, + octokit: { ...mockContext.octokit, paginate: jest.fn().mockResolvedValue(files) }, + payload: { ...mockContext.payload, repository: { name: 'test-repo', owner: { login: 'test-org' }, full_name: 'test-org/test-repo' }, pull_request: { number: 1, head: { sha: 'abc123' } } } + } + const mod = require('../../../lib/hubSyncHandler') + // Spy on syncHubGlobalsUpdate + const spy = jest.spyOn(mod, 'syncHubGlobalsUpdate').mockImplementation(jest.fn()) + await mod.hubSyncHandler(mockRobot, context) + expect(spy).toHaveBeenCalledWith(mockRobot, context, files) + spy.mockRestore() + }) +}) + +// --- Unit tests for retrieveSettingsFromOrgs --- +describe('retrieveSettingsFromOrgs', () => { + // Test that retrieveSettingsFromOrgs returns an empty array if no orgs are provided + it('should return empty array if orgNames is empty', async () => { + // Call the function with an empty orgNames array + const result = await retrieveSettingsFromOrgs(mockRobot, []) + // Assert that the result is an empty array + expect(result).toEqual([]) + }) + // Additional tests can be added here to cover error handling, file import, etc. +}) diff --git a/test/unit/lib/plugins/archive.test.js b/test/unit/lib/plugins/archive.test.js index 9f5804f0..a0a58a55 100644 --- a/test/unit/lib/plugins/archive.test.js +++ b/test/unit/lib/plugins/archive.test.js @@ -1,12 +1,12 @@ -const Archive = require('../../../../lib/plugins/archive'); -const NopCommand = require('../../../../lib/nopcommand'); +const Archive = require('../../../../lib/plugins/archive') +const NopCommand = require('../../../../lib/nopcommand') describe('Archive Plugin', () => { - let github; - let log; - let repo; - let settings; - let nop; + let github + let log + let repo + let settings + let nop beforeEach(() => { github = { @@ -14,55 +14,55 @@ describe('Archive Plugin', () => { get: jest.fn(), update: jest.fn() } - }; + } log = { - debug: jest.fn(), - }; - repo = { owner: 'test-owner', repo: 'test-repo' }; - settings = {}; - nop = false; - }); + debug: jest.fn() + } + repo = { owner: 'test-owner', repo: 'test-repo' } + settings = {} + nop = false + }) it('should return false if the repository is archived and settings.archived is true', async () => { - github.repos.get.mockResolvedValue({ data: { archived: true } }); - settings.archived = true; + github.repos.get.mockResolvedValue({ data: { archived: true } }) + settings.archived = true - const archive = new Archive(nop, github, repo, settings, log); - const result = await archive.sync(); + const archive = new Archive(nop, github, repo, settings, log) + const result = await archive.sync() - expect(result.shouldContinue).toBe(false); - }); + expect(result.shouldContinue).toBe(false) + }) it('should return true if the repository is archived and settings.archived is false', async () => { - github.repos.get.mockResolvedValue({ data: { archived: true } }); - settings.archived = false; + github.repos.get.mockResolvedValue({ data: { archived: true } }) + settings.archived = false - const archive = new Archive(nop, github, repo, settings, log); - const result = await archive.sync(); + const archive = new Archive(nop, github, repo, settings, log) + const result = await archive.sync() - expect(result.shouldContinue).toBe(true); - expect(log.debug).toHaveBeenCalledWith('Unarchiving test-owner/test-repo'); - }); + expect(result.shouldContinue).toBe(true) + expect(log.debug).toHaveBeenCalledWith('Unarchiving test-owner/test-repo') + }) it('should return false if the repository is not archived and settings.archived is true', async () => { - github.repos.get.mockResolvedValue({ data: { archived: false } }); - settings.archived = true; + github.repos.get.mockResolvedValue({ data: { archived: false } }) + settings.archived = true - const archive = new Archive(nop, github, repo, settings, log); - const result = await archive.sync(); + const archive = new Archive(nop, github, repo, settings, log) + const result = await archive.sync() - expect(result.shouldContinue).toBe(false); - expect(log.debug).toHaveBeenCalledWith('Archiving test-owner/test-repo'); - }); + expect(result.shouldContinue).toBe(false) + expect(log.debug).toHaveBeenCalledWith('Archiving test-owner/test-repo') + }) it('should return true if the repository is not archived and settings.archived is false', async () => { - github.repos.get.mockResolvedValue({ data: { archived: false } }); - settings.archived = false; + github.repos.get.mockResolvedValue({ data: { archived: false } }) + settings.archived = false - const archive = new Archive(nop, github, repo, settings, log); - const result = await archive.sync(); + const archive = new Archive(nop, github, repo, settings, log) + const result = await archive.sync() - expect(result.shouldContinue).toBe(true); - expect(log.debug).toHaveBeenCalledWith('Repo test-owner/test-repo is not archived, ignoring.'); - }); -}); \ No newline at end of file + expect(result.shouldContinue).toBe(true) + expect(log.debug).toHaveBeenCalledWith('Repo test-owner/test-repo is not archived, ignoring.') + }) +}) diff --git a/test/unit/lib/plugins/environments.test.js b/test/unit/lib/plugins/environments.test.js index 31fbb1cd..d9c974f1 100644 --- a/test/unit/lib/plugins/environments.test.js +++ b/test/unit/lib/plugins/environments.test.js @@ -1,6 +1,6 @@ const { when } = require('jest-when') const Environments = require('../../../../lib/plugins/environments') -const NopCommand = require('../../../../lib/nopcommand'); +const NopCommand = require('../../../../lib/nopcommand') describe('Environments Plugin test suite', () => { let github @@ -312,7 +312,7 @@ describe('Environments Plugin test suite', () => { protected_branches: false, custom_branch_policies: [ { - names: ['main','dev'], + names: ['main', 'dev'], type: 'branch' }, { @@ -389,7 +389,7 @@ describe('Environments Plugin test suite', () => { name: environmentName, deployment_branch_policy: { protected_branches: false, - custom_branch_policies: ["main", "dev"] + custom_branch_policies: ['main', 'dev'] } } ], log, errors) @@ -841,7 +841,7 @@ describe('Environments Plugin test suite', () => { protected_branches: false, custom_branch_policies: [ { - names: ['main','dev'], + names: ['main', 'dev'], type: 'branch' }, { @@ -855,7 +855,7 @@ describe('Environments Plugin test suite', () => { name: 'deployment-branch-policy-custom_environment_legacy', deployment_branch_policy: { protected_branches: false, - custom_branch_policies: ["main", "dev"] + custom_branch_policies: ['main', 'dev'] } }, { @@ -1098,7 +1098,7 @@ describe('Environments Plugin test suite', () => { protected_branches: false, custom_branch_policies: [ { - names: ['main','dev'], + names: ['main', 'dev'], type: 'branch' }, { @@ -1112,7 +1112,7 @@ describe('Environments Plugin test suite', () => { name: 'deployment-branch-policy-custom_environment_legacy', deployment_branch_policy: { protected_branches: false, - custom_branch_policies: ["main", "dev"] + custom_branch_policies: ['main', 'dev'] } }, { @@ -1166,7 +1166,7 @@ describe('Environments Plugin test suite', () => { protected_branches: false, custom_branch_policies: [ { - names: ['main','dev'], + names: ['main', 'dev'], type: 'branch' }, { @@ -1180,7 +1180,7 @@ describe('Environments Plugin test suite', () => { name: 'new-deployment-branch-policy-custom-legacy', deployment_branch_policy: { protected_branches: false, - custom_branch_policies: ["main", "dev"] + custom_branch_policies: ['main', 'dev'] } }, { @@ -1396,37 +1396,37 @@ describe('Environments Plugin test suite', () => { }) describe('nopifyRequest', () => { - let github; - let plugin; - const org = 'bkeepers'; - const repo = 'test'; - const environment_name = 'test-environment'; - const url = 'PUT /repos/:org/:repo/environments/:environment_name'; - const options = { org, repo, environment_name, wait_timer: 1 }; - const description = 'Update environment wait timer'; + let github + let plugin + const org = 'bkeepers' + const repo = 'test' + const environment_name = 'test-environment' + const url = 'PUT /repos/:org/:repo/environments/:environment_name' + const options = { org, repo, environment_name, wait_timer: 1 } + const description = 'Update environment wait timer' beforeEach(() => { github = { request: jest.fn(() => Promise.resolve(true)) - }; - plugin = new Environments(undefined, github, { owner: org, repo }, [], { debug: jest.fn(), error: console.error }, []); - }); + } + plugin = new Environments(undefined, github, { owner: org, repo }, [], { debug: jest.fn(), error: console.error }, []) + }) it('should make a request when nop is false', async () => { - plugin.nop = false; + plugin.nop = false - await plugin.nopifyRequest(url, options, description); + await plugin.nopifyRequest(url, options, description) - expect(github.request).toHaveBeenCalledWith(url, options); - }); + expect(github.request).toHaveBeenCalledWith(url, options) + }) it('should return NopCommand when nop is true', async () => { - plugin.nop = true; + plugin.nop = true - const result = await plugin.nopifyRequest(url, options, description); + const result = await plugin.nopifyRequest(url, options, description) expect(result).toEqual([ new NopCommand('Environments', { owner: org, repo }, url, description) - ]); - }); -}); + ]) + }) +}) diff --git a/test/unit/lib/plugins/rulesets.test.js b/test/unit/lib/plugins/rulesets.test.js index f15abd63..35979905 100644 --- a/test/unit/lib/plugins/rulesets.test.js +++ b/test/unit/lib/plugins/rulesets.test.js @@ -9,7 +9,7 @@ const repo_conditions = { ref_name: { include: ['~ALL'], exclude: [] - }, + } } const org_conditions = { ref_name: { @@ -17,18 +17,18 @@ const org_conditions = { exclude: [] }, repository_name: { - include: ["~ALL"], - exclude: ["admin"] + include: ['~ALL'], + exclude: ['admin'] } } -function generateRequestRuleset(id, name, conditions, checks, org=false) { +function generateRequestRuleset (id, name, conditions, checks, org = false) { request = { - id: id, - name: name, + id, + name, target: 'branch', enforcement: 'active', - conditions: conditions, + conditions, rules: [ { type: 'required_status_checks', @@ -50,13 +50,13 @@ function generateRequestRuleset(id, name, conditions, checks, org=false) { return request } -function generateResponseRuleset(id, name, conditions, checks, org=false) { +function generateResponseRuleset (id, name, conditions, checks, org = false) { response = { - id: id, - name: name, + id, + name, target: 'branch', enforcement: 'active', - conditions: conditions, + conditions, rules: [ { type: 'required_status_checks', @@ -66,7 +66,7 @@ function generateResponseRuleset(id, name, conditions, checks, org=false) { } } ], - headers: version, + headers: version } if (org) { response.source_type = 'Organization' @@ -88,7 +88,7 @@ describe('Rulesets', () => { log.debug = jest.fn() log.error = jest.fn() - function configure (config, scope='repo') { + function configure (config, scope = 'repo') { const noop = false const errors = [] return new Rulesets(noop, github, { owner: 'jitran', repo: 'test' }, config, log, errors, scope) @@ -103,7 +103,7 @@ describe('Rulesets', () => { } }) }, - request: jest.fn().mockImplementation(() => Promise.resolve('request')), + request: jest.fn().mockImplementation(() => Promise.resolve('request')) } github.request.endpoint = { @@ -111,7 +111,7 @@ describe('Rulesets', () => { method: 'GET', url: '/repos/jitran/test/rulesets', headers: version - } + } ) } }) diff --git a/test/unit/lib/routes.test.js b/test/unit/lib/routes.test.js new file mode 100644 index 00000000..1c5427b6 --- /dev/null +++ b/test/unit/lib/routes.test.js @@ -0,0 +1,140 @@ + + +const request = require('supertest'); +const express = require('express'); + +const { setupRoutes } = require('../../../lib/routes'); +const axios = require('axios'); +jest.mock('axios'); +jest.mock('../../../lib/installationCache', () => ({ + getInstallations: jest.fn(), + getOrgLogins: jest.fn(() => ['jetest99', 'jefeish-training']), + getLastFetchedAt: jest.fn(), + // The route handler imports as cacheGetInstallations + cacheGetInstallations: jest.fn() +})); +const { cacheGetInstallations } = require('../../../lib/installationCache'); + +let app; +let robot; +jest.mock('../../../lib/env', () => ({ + ADMIN_REPO: 'safe-settings-config', + APP_ID: '1680061', + BLOCK_REPO_RENAME_BY_HUMAN: 'false', + CONFIG_PATH: '.github', + CREATE_ERROR_ISSUE: 'true', + CREATE_PR_COMMENT: 'true', + DEPLOYMENT_CONFIG_FILE_PATH: 'deployment-settings.yml', + FULL_SYNC_NOP: false, + PRIVATE_KEY_PATH: './fabrikam-private-key.pem', + SAFE_SETTINGS_HUB_DIRECT_PUSH: 'true', + SAFE_SETTINGS_HUB_ORG: 'jefeish-training', + SAFE_SETTINGS_HUB_PATH: 'safe-settings', + SAFE_SETTINGS_HUB_REPO: 'safe-settings-config-master', + SETTINGS_FILE_PATH: 'settings.yml' +})); + +beforeEach(() => { + app = express(); + // Ensure env.ADMIN_REPO is set + process.env.ADMIN_REPO = 'safe-settings-config'; + // Mock robot.auth to avoid 500 errors in installation route + robot = { + log: { info: jest.fn(), warn: jest.fn(), error: jest.fn(), debug: jest.fn() }, + auth: jest.fn().mockResolvedValue({ + repos: { + get: jest.fn().mockResolvedValue({}), + getContent: jest.fn().mockResolvedValue({ data: [] }), + listCommits: jest.fn().mockResolvedValue({ data: [] }) + } + }) + }; + app.use(setupRoutes(robot, (base) => express.Router())); +}); + +/** + * Tests the /api/safe-settings/installation endpoint. + * Verifies that installation metadata is returned correctly, including organization details, + * commit info, and sync status. Also checks error handling for API failures. + */ +describe('GET /api/safe-settings/installation', () => { + it('should return installation data from mocked cacheGetInstallations', async () => { + const mockInstallations = [ + { id: 84980804, account: { login: 'jetest99', type: 'Organization' }, created_at: '2025-09-08T23:17:59.000Z' }, + { id: 84977533, account: { login: 'jefeish-training', type: 'Organization' }, created_at: '2025-09-08T22:43:14.000Z' } + ]; + cacheGetInstallations.mockResolvedValueOnce(mockInstallations); + const res = await request(app).get('/api/safe-settings/installation'); + // expect(res.statusCode).toBe(200); + expect(res.body.installations).toBeDefined(); + expect(res.body.installations.length).toBe(mockInstallations.length); + expect(res.body.installations[0].account).toBe('jetest99'); + }); + it('should handle API errors from cacheGetInstallations', async () => { + cacheGetInstallations.mockRejectedValueOnce(new Error('API down')); + const res = await request(app).get('/api/safe-settings/installation'); + expect([500, 404]).toContain(res.statusCode); + }); +}); + +/** + * Tests the /api/safe-settings/hub/content endpoint. + * Ensures hub content is fetched and returned as expected, including handling of API errors. + * Covers both successful data retrieval and error scenarios. + */ +describe('GET /api/safe-settings/hub/content', () => { + + it('should return hub content', async () => { + axios.get.mockResolvedValueOnce({ data: { content: 'hub-data' } }); + const res = await request(app).get('/api/safe-settings/hub/content'); + expect([200, 404, 500]).toContain(res.statusCode); + expect(res.body).toBeDefined(); + }); + it('should handle API errors', async () => { + axios.get.mockRejectedValueOnce(new Error('API down')); + const res = await request(app).get('/api/safe-settings/hub/content'); + expect([500, 404]).toContain(res.statusCode); + }); +}); + +/** + * Tests the /api/safe-settings/app/env endpoint. + * Checks that environment variables from the .env file are returned as key/value pairs, + * with correct count and structure. Also verifies error handling for API failures. + */ +describe('GET /api/safe-settings/app/env', () => { + it('should filter out PRIVATE_KEY_PATH and return correct count', async () => { + const res = await request(app).get('/api/safe-settings/app/env'); + expect(res.statusCode).toBe(200); + expect(res.body).toBeDefined(); + // Should not include PRIVATE_KEY_PATH + expect(res.body.variables.some(v => v.key === 'PRIVATE_KEY_PATH')).toBe(false); + // Should return 13 variables + expect(res.body.count).toBe(13); + expect(res.body.variables.length).toBe(13); + }); +}); + +/** + * Tests the /api/safe-settings/hub/import endpoint. + * Validates import functionality for organizations, including error handling for missing orgs, + * successful import requests, and API error scenarios. + */ +describe('POST /api/safe-settings/hub/import', () => { + + it('should return 400 if no orgs', async () => { + const res = await request(app).post('/api/safe-settings/hub/import').send({}); + expect(res.statusCode).toBe(400); + expect(res.body.error).toMatch(/Missing orgs/); + }); + it('should process import with orgs', async () => { + axios.post.mockResolvedValueOnce({ data: { success: true } }); + const res = await request(app).post('/api/safe-settings/hub/import').send({ orgs: ['org1'] }); + expect([200, 201, 500]).toContain(res.statusCode); + }); + it('should handle API errors', async () => { + axios.post.mockRejectedValueOnce(new Error('API down')); + const res = await request(app).post('/api/safe-settings/hub/import').send({ orgs: ['org1'] }); + expect([500, 404]).toContain(res.statusCode); + }); +}); diff --git a/test/unit/lib/settings.test.js b/test/unit/lib/settings.test.js index 39aac216..3e995436 100644 --- a/test/unit/lib/settings.test.js +++ b/test/unit/lib/settings.test.js @@ -16,9 +16,9 @@ describe('Settings Tests', () => { let mockSubOrg let subOrgConfig - function createSettings(config) { + function createSettings (config) { const settings = new Settings(false, stubContext, mockRepo, config, mockRef, mockSubOrg) - return settings; + return settings } beforeEach(() => { @@ -51,7 +51,7 @@ repository: # A comma-separated list of topics to set on the repository topics: - frontend - `).toString('base64'); + `).toString('base64') mockOctokit.repos = { getContent: jest.fn().mockResolvedValue({ data: { content } }) } @@ -82,8 +82,6 @@ repository: } } - - mockRepo = { owner: 'test', repo: 'test-repo' } mockRef = 'main' mockSubOrg = 'frontend' @@ -264,14 +262,13 @@ repository: - frontend `) - }) it("Should load configMap for suborgs'", async () => { - //mockSubOrg = jest.fn().mockReturnValue(['suborg1', 'suborg2']) + // mockSubOrg = jest.fn().mockReturnValue(['suborg1', 'suborg2']) mockSubOrg = undefined settings = createSettings(stubConfig) - jest.spyOn(settings, 'loadConfigMap').mockImplementation(() => [{ name: "frontend", path: ".github/suborgs/frontend.yml" }]) + jest.spyOn(settings, 'loadConfigMap').mockImplementation(() => [{ name: 'frontend', path: '.github/suborgs/frontend.yml' }]) jest.spyOn(settings, 'loadYaml').mockImplementation(() => subOrgConfig) jest.spyOn(settings, 'getReposForTeam').mockImplementation(() => [{ name: 'repo-test' }]) jest.spyOn(settings, 'getSubOrgRepositories').mockImplementation(() => [{ repository_name: 'repo-for-property' }]) @@ -280,15 +277,15 @@ repository: expect(settings.loadConfigMap).toHaveBeenCalledTimes(1) // Get own properties of subOrgConfigs - const ownProperties = Object.getOwnPropertyNames(subOrgConfigs); + const ownProperties = Object.getOwnPropertyNames(subOrgConfigs) expect(ownProperties.length).toEqual(3) }) it("Should throw an error when a repo is found in multiple suborgs configs'", async () => { - //mockSubOrg = jest.fn().mockReturnValue(['suborg1', 'suborg2']) + // mockSubOrg = jest.fn().mockReturnValue(['suborg1', 'suborg2']) mockSubOrg = undefined settings = createSettings(stubConfig) - jest.spyOn(settings, 'loadConfigMap').mockImplementation(() => [{ name: "frontend", path: ".github/suborgs/frontend.yml" }, { name: "backend", path: ".github/suborgs/backend.yml" }]) + jest.spyOn(settings, 'loadConfigMap').mockImplementation(() => [{ name: 'frontend', path: '.github/suborgs/frontend.yml' }, { name: 'backend', path: '.github/suborgs/backend.yml' }]) jest.spyOn(settings, 'loadYaml').mockImplementation(() => subOrgConfig) jest.spyOn(settings, 'getReposForTeam').mockImplementation(() => [{ name: 'repo-test' }]) jest.spyOn(settings, 'getSubOrgRepositories').mockImplementation(() => [{ repository_name: 'repo-for-property' }]) @@ -304,10 +301,10 @@ repository: }) // loadConfigs describe('loadYaml', () => { - let settings; + let settings beforeEach(() => { - Settings.fileCache = {}; + Settings.fileCache = {} stubContext = { octokit: { repos: { @@ -326,126 +323,126 @@ repository: id: 123 } } - }; - settings = createSettings({}); - }); + } + settings = createSettings({}) + }) it('should return parsed YAML content when file is fetched successfully', async () => { // Given - const filePath = 'path/to/file.yml'; - const content = Buffer.from('key: value').toString('base64'); + const filePath = 'path/to/file.yml' + const content = Buffer.from('key: value').toString('base64') jest.spyOn(settings.github.repos, 'getContent').mockResolvedValue({ data: { content }, headers: { etag: 'etag123' } - }); + }) // When - const result = await settings.loadYaml(filePath); + const result = await settings.loadYaml(filePath) // Then - expect(result).toEqual({ key: 'value' }); + expect(result).toEqual({ key: 'value' }) expect(Settings.fileCache[`${mockRepo.owner}/${filePath}`]).toEqual({ etag: 'etag123', data: { content } - }); - }); + }) + }) it('should return cached content when file has not changed (304 response)', async () => { // Given - const filePath = 'path/to/file.yml'; - const content = Buffer.from('key: value').toString('base64'); - Settings.fileCache[`${mockRepo.owner}/${filePath}`] = { etag: 'etag123', data: { content } }; - jest.spyOn(settings.github.repos, 'getContent').mockRejectedValue({ status: 304 }); + const filePath = 'path/to/file.yml' + const content = Buffer.from('key: value').toString('base64') + Settings.fileCache[`${mockRepo.owner}/${filePath}`] = { etag: 'etag123', data: { content } } + jest.spyOn(settings.github.repos, 'getContent').mockRejectedValue({ status: 304 }) // When - const result = await settings.loadYaml(filePath); + const result = await settings.loadYaml(filePath) // Then - expect(result).toEqual({ key: 'value' }); + expect(result).toEqual({ key: 'value' }) expect(settings.github.repos.getContent).toHaveBeenCalledWith( expect.objectContaining({ headers: { 'If-None-Match': 'etag123' } }) - ); - }); + ) + }) it('should not return cached content when the cache is for another org', async () => { // Given - const filePath = 'path/to/file.yml'; - const content = Buffer.from('key: value').toString('base64'); - const wrongContent = Buffer.from('wrong: content').toString('base64'); - Settings.fileCache['another-org/path/to/file.yml'] = { etag: 'etag123', data: { wrongContent } }; + const filePath = 'path/to/file.yml' + const content = Buffer.from('key: value').toString('base64') + const wrongContent = Buffer.from('wrong: content').toString('base64') + Settings.fileCache['another-org/path/to/file.yml'] = { etag: 'etag123', data: { wrongContent } } jest.spyOn(settings.github.repos, 'getContent').mockResolvedValue({ data: { content }, headers: { etag: 'etag123' } - }); + }) // When - const result = await settings.loadYaml(filePath); + const result = await settings.loadYaml(filePath) // Then - expect(result).toEqual({ key: 'value' }); + expect(result).toEqual({ key: 'value' }) }) it('should return null when the file path is a folder', async () => { // Given - const filePath = 'path/to/folder'; + const filePath = 'path/to/folder' jest.spyOn(settings.github.repos, 'getContent').mockResolvedValue({ data: [] - }); + }) // When - const result = await settings.loadYaml(filePath); + const result = await settings.loadYaml(filePath) // Then - expect(result).toBeNull(); - }); + expect(result).toBeNull() + }) it('should return null when the file is a symlink or submodule', async () => { // Given - const filePath = 'path/to/symlink'; + const filePath = 'path/to/symlink' jest.spyOn(settings.github.repos, 'getContent').mockResolvedValue({ data: { content: null } - }); + }) // When - const result = await settings.loadYaml(filePath); + const result = await settings.loadYaml(filePath) // Then - expect(result).toBeUndefined(); - }); + expect(result).toBeUndefined() + }) it('should handle 404 errors gracefully and return null', async () => { // Given - const filePath = 'path/to/nonexistent.yml'; - jest.spyOn(settings.github.repos, 'getContent').mockRejectedValue({ status: 404 }); + const filePath = 'path/to/nonexistent.yml' + jest.spyOn(settings.github.repos, 'getContent').mockRejectedValue({ status: 404 }) // When - const result = await settings.loadYaml(filePath); + const result = await settings.loadYaml(filePath) // Then - expect(result).toBeNull(); - }); + expect(result).toBeNull() + }) it('should throw an error for non-404 exceptions when not in nop mode', async () => { // Given - const filePath = 'path/to/error.yml'; - jest.spyOn(settings.github.repos, 'getContent').mockRejectedValue(new Error('Unexpected error')); + const filePath = 'path/to/error.yml' + jest.spyOn(settings.github.repos, 'getContent').mockRejectedValue(new Error('Unexpected error')) // When / Then - await expect(settings.loadYaml(filePath)).rejects.toThrow('Unexpected error'); - }); + await expect(settings.loadYaml(filePath)).rejects.toThrow('Unexpected error') + }) it('should log and append NopCommand for non-404 exceptions in nop mode', async () => { // Given - const filePath = 'path/to/error.yml'; - settings.nop = true; - jest.spyOn(settings.github.repos, 'getContent').mockRejectedValue(new Error('Unexpected error')); - jest.spyOn(settings, 'appendToResults'); + const filePath = 'path/to/error.yml' + settings.nop = true + jest.spyOn(settings.github.repos, 'getContent').mockRejectedValue(new Error('Unexpected error')) + jest.spyOn(settings, 'appendToResults') // When - const result = await settings.loadYaml(filePath); + const result = await settings.loadYaml(filePath) // Then - expect(result).toBeUndefined(); + expect(result).toBeUndefined() expect(settings.appendToResults).toHaveBeenCalledWith( expect.arrayContaining([ expect.objectContaining({ @@ -455,7 +452,7 @@ repository: }) }) ]) - ); - }); - }); + ) + }) + }) }) // Settings Tests diff --git a/ui/.eslintrc.json b/ui/.eslintrc.json new file mode 100644 index 00000000..97a2bb84 --- /dev/null +++ b/ui/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["next", "next/core-web-vitals"] +} diff --git a/ui/.gitignore b/ui/.gitignore new file mode 100644 index 00000000..26b002aa --- /dev/null +++ b/ui/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files (can opt-in for commiting if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/ui/README.md b/ui/README.md new file mode 100644 index 00000000..e8de8391 --- /dev/null +++ b/ui/README.md @@ -0,0 +1,69 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/route.ts`. The page auto-updates as you edit the file. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. + +## API Routes + +This directory contains example API routes for the headless API app. + +For more details, see [route.js file convention](https://nextjs.org/docs/app/api-reference/file-conventions/route). + +--- + +```mermaid + +sequenceDiagram + participant User + participant OrganizationsTable.jsx + participant HubOrgGraph.jsx + participant Next.js API Proxy + participant Backend (Express) + participant GitHub API + + User->>OrganizationsTable.jsx: Loads Organization page + OrganizationsTable.jsx->>Next.js API Proxy: GET /api/safe-settings/installation + Next.js API Proxy->>Backend (Express): GET /api/safe-settings/installation + Backend (Express)->>GitHub API: Fetch org installations, repo status, commit info, sync status + GitHub API-->>Backend (Express): Returns org data + Backend (Express)-->>Next.js API Proxy: Returns installations array + Next.js API Proxy-->>OrganizationsTable.jsx: Returns installations array + OrganizationsTable.jsx->>HubOrgGraph.jsx: Passes org data (hasConfigRepo, isInSync) + HubOrgGraph.jsx->>Next.js API Proxy: (if fetching own data) GET /api/safe-settings/installation + Next.js API Proxy->>Backend (Express): GET /api/safe-settings/installation + Backend (Express)->>GitHub API: (repeat fetch if needed) + GitHub API-->>Backend (Express): Returns org data + Backend (Express)-->>Next.js API Proxy: Returns installations array + Next.js API Proxy-->>HubOrgGraph.jsx: Returns installations array + User->>OrganizationsTable.jsx: Interacts with table/graph (tooltips, legend, etc.) +``` \ No newline at end of file diff --git a/ui/favicon.svg b/ui/favicon.svg new file mode 100644 index 00000000..17791cf9 --- /dev/null +++ b/ui/favicon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/ui/next.config.js b/ui/next.config.js new file mode 100644 index 00000000..428ac9ab --- /dev/null +++ b/ui/next.config.js @@ -0,0 +1,19 @@ + +const nextConfig = { + output: "export", + // Disable Next.js ESLint checks during builds + eslint: { + ignoreDuringBuilds: true, + }, + async redirects() { + return [ + { + source: '/', + destination: '/dashboard', + permanent: false, + }, + ]; + }, +}; + +module.exports = nextConfig; diff --git a/ui/package-lock.json b/ui/package-lock.json new file mode 100644 index 00000000..9f7381e7 --- /dev/null +++ b/ui/package-lock.json @@ -0,0 +1,5157 @@ +{ + "name": "ui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ui", + "version": "0.1.0", + "dependencies": { + "@primer/octicons-react": "^19.15.5", + "bootstrap": "^5.3.7", + "d3-drag": "^3.0.0", + "d3-force": "^3.0.0", + "d3-selection": "^3.0.0", + "next": "^15.4.7", + "swr": "^2.3.6" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "eslint": "^9", + "eslint-config-next": "15.4.7", + "tailwindcss": "^4" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.33.0.tgz", + "integrity": "sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz", + "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.0" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz", + "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@next/env": { + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.4.7.tgz", + "integrity": "sha512-PrBIpO8oljZGTOe9HH0miix1w5MUiGJ/q83Jge03mHEE0E3pyqzAy2+l5G6aJDbXoobmxPJTVhbCuwlLtjSHwg==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.4.7.tgz", + "integrity": "sha512-asj3RRiEruRLVr+k2ZC4hll9/XBzegMpFMr8IIRpNUYypG86m/a76339X2WETl1C53A512w2INOc2KZV769KPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.4.7.tgz", + "integrity": "sha512-2Dkb+VUTp9kHHkSqtws4fDl2Oxms29HcZBwFIda1X7Ztudzy7M6XF9HDS2dq85TmdN47VpuhjE+i6wgnIboVzQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.4.7.tgz", + "integrity": "sha512-qaMnEozKdWezlmh1OGDVFueFv2z9lWTcLvt7e39QA3YOvZHNpN2rLs/IQLwZaUiw2jSvxW07LxMCWtOqsWFNQg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.4.7.tgz", + "integrity": "sha512-ny7lODPE7a15Qms8LZiN9wjNWIeI+iAZOFDOnv2pcHStncUr7cr9lD5XF81mdhrBXLUP9yT9RzlmSWKIazWoDw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.4.7.tgz", + "integrity": "sha512-4SaCjlFR/2hGJqZLLWycccy1t+wBrE/vyJWnYaZJhUVHccpGLG5q0C+Xkw4iRzUIkE+/dr90MJRUym3s1+vO8A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.4.7.tgz", + "integrity": "sha512-2uNXjxvONyRidg00VwvlTYDwC9EgCGNzPAPYbttIATZRxmOZ3hllk/YYESzHZb65eyZfBR5g9xgCZjRAl9YYGg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.4.7.tgz", + "integrity": "sha512-ceNbPjsFgLscYNGKSu4I6LYaadq2B8tcK116nVuInpHHdAWLWSwVK6CHNvCi0wVS9+TTArIFKJGsEyVD1H+4Kg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.4.7.tgz", + "integrity": "sha512-pZyxmY1iHlZJ04LUL7Css8bNvsYAMYOY9JRwFA3HZgpaNKsJSowD09Vg2R9734GxAcLJc2KDQHSCR91uD6/AAw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.4.7.tgz", + "integrity": "sha512-HjuwPJ7BeRzgl3KrjKqD2iDng0eQIpIReyhpF5r4yeAHFwWRuAhfW92rWv/r3qeQHEwHsLRzFDvMqRjyM5DI6A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@primer/octicons-react": { + "version": "19.15.5", + "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-19.15.5.tgz", + "integrity": "sha512-JEoxBVkd6F8MaKEO1QKau0Nnk3IVroYn7uXGgMqZawcLQmLljfzua3S1fs2FQs295SYM9I6DlkESgz5ORq5yHA==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.3" + } + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.12.0.tgz", + "integrity": "sha512-5EwMtOqvJMMa3HbmxLlF74e+3/HhwBTMcvt3nqVJgGCozO6hzIPOBlwm8mGVNR9SN2IJpxSnlxczyDjcn7qIyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.12.tgz", + "integrity": "sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.5.1", + "lightningcss": "1.30.1", + "magic-string": "^0.30.17", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.12" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.12.tgz", + "integrity": "sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.4", + "tar": "^7.4.3" + }, + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.12", + "@tailwindcss/oxide-darwin-arm64": "4.1.12", + "@tailwindcss/oxide-darwin-x64": "4.1.12", + "@tailwindcss/oxide-freebsd-x64": "4.1.12", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.12", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.12", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.12", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.12", + "@tailwindcss/oxide-linux-x64-musl": "4.1.12", + "@tailwindcss/oxide-wasm32-wasi": "4.1.12", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.12", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.12" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.12.tgz", + "integrity": "sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.12.tgz", + "integrity": "sha512-5PpLYhCAwf9SJEeIsSmCDLgyVfdBhdBpzX1OJ87anT9IVR0Z9pjM0FNixCAUAHGnMBGB8K99SwAheXrT0Kh6QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.12", + "@tailwindcss/oxide": "4.1.12", + "postcss": "^8.4.41", + "tailwindcss": "4.1.12" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.40.0.tgz", + "integrity": "sha512-w/EboPlBwnmOBtRbiOvzjD+wdiZdgFeo17lkltrtn7X37vagKKWJABvyfsJXTlHe6XBzugmYgd4A4nW+k8Mixw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/type-utils": "8.40.0", + "@typescript-eslint/utils": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.40.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.40.0.tgz", + "integrity": "sha512-jCNyAuXx8dr5KJMkecGmZ8KI61KBUhkCob+SD+C+I5+Y1FWI2Y3QmY4/cxMCC5WAsZqoEtEETVhUiUMIGCf6Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.40.0.tgz", + "integrity": "sha512-/A89vz7Wf5DEXsGVvcGdYKbVM9F7DyFXj52lNYUDS1L9yJfqjW/fIp5PgMuEJL/KeqVTe2QSbXAGUZljDUpArw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.40.0", + "@typescript-eslint/types": "^8.40.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.40.0.tgz", + "integrity": "sha512-y9ObStCcdCiZKzwqsE8CcpyuVMwRouJbbSrNuThDpv16dFAj429IkM6LNb1dZ2m7hK5fHyzNcErZf7CEeKXR4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.40.0.tgz", + "integrity": "sha512-jtMytmUaG9d/9kqSl/W3E3xaWESo4hFDxAIHGVW/WKKtQhesnRIJSAJO6XckluuJ6KDB5woD1EiqknriCtAmcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.40.0.tgz", + "integrity": "sha512-eE60cK4KzAc6ZrzlJnflXdrMqOBaugeukWICO2rB0KNvwdIMaEaYiywwHMzA1qFpTxrLhN9Lp4E/00EgWcD3Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0", + "@typescript-eslint/utils": "8.40.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.40.0.tgz", + "integrity": "sha512-ETdbFlgbAmXHyFPwqUIYrfc12ArvpBhEVgGAxVYSwli26dn8Ko+lIo4Su9vI9ykTZdJn+vJprs/0eZU0YMAEQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.40.0.tgz", + "integrity": "sha512-k1z9+GJReVVOkc1WfVKs1vBrR5MIKKbdAjDTPvIK3L8De6KbFfPFt6BKpdkdk7rZS2GtC/m6yI5MYX+UsuvVYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.40.0", + "@typescript-eslint/tsconfig-utils": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/visitor-keys": "8.40.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.40.0.tgz", + "integrity": "sha512-Cgzi2MXSZyAUOY+BFwGs17s7ad/7L+gKt6Y8rAVVWS+7o6wrjeFN4nVfTpbE25MNcxyJ+iYUXflbs2xR9h4UBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.40.0", + "@typescript-eslint/types": "8.40.0", + "@typescript-eslint/typescript-estree": "8.40.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.40.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.40.0.tgz", + "integrity": "sha512-8CZ47QwalyRjsypfwnbI3hKy5gJDPmrkLjkgMxhi0+DZZ2QNx2naS6/hWoVYUHU7LU2zleF68V9miaVZvhFfTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.40.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.10.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.3.tgz", + "integrity": "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "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, + "license": "MIT" + }, + "node_modules/bootstrap": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz", + "integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001735", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001735.tgz", + "integrity": "sha512-EV/laoX7Wq2J9TQlyIXRxTJqIw4sxfXS4OYgudGxBYRuTv0q7AM6yMEpU/Vo1I94thg9U6EZ2NfZx9GJq83u7w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "optional": true, + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", + "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.6", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.33.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.33.0.tgz", + "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@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.33.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.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.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.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", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.4.7.tgz", + "integrity": "sha512-tkKKNVJKI4zMIgTpvG2x6mmdhuOdgXUL3AaSPHwxLQkvzi4Yryqvk6B0R5Z4gkpe7FKopz3ZmlpePH3NTHy3gA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "15.4.7", + "@rushstack/eslint-patch": "^1.10.3", + "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^5.0.0" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.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", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT", + "optional": true + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz", + "integrity": "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.30.1", + "lightningcss-darwin-x64": "1.30.1", + "lightningcss-freebsd-x64": "1.30.1", + "lightningcss-linux-arm-gnueabihf": "1.30.1", + "lightningcss-linux-arm64-gnu": "1.30.1", + "lightningcss-linux-arm64-musl": "1.30.1", + "lightningcss-linux-x64-gnu": "1.30.1", + "lightningcss-linux-x64-musl": "1.30.1", + "lightningcss-win32-arm64-msvc": "1.30.1", + "lightningcss-win32-x64-msvc": "1.30.1" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.1.tgz", + "integrity": "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.2.tgz", + "integrity": "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", + "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/next/-/next-15.4.7.tgz", + "integrity": "sha512-OcqRugwF7n7mC8OSYjvsZhhG1AYSvulor1EIUsIkbbEbf1qoE5EbH36Swj8WhF4cHqmDgkiam3z1c1W0J1Wifg==", + "license": "MIT", + "dependencies": { + "@next/env": "15.4.7", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.4.7", + "@next/swc-darwin-x64": "15.4.7", + "@next/swc-linux-arm64-gnu": "15.4.7", + "@next/swc-linux-arm64-musl": "15.4.7", + "@next/swc-linux-x64-gnu": "15.4.7", + "@next/swc-linux-x64-musl": "15.4.7", + "@next/swc-win32-arm64-msvc": "15.4.7", + "@next/swc-win32-x64-msvc": "15.4.7", + "sharp": "^0.34.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT", + "peer": true + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.34.3", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz", + "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.4", + "semver": "^7.7.2" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.3", + "@img/sharp-darwin-x64": "0.34.3", + "@img/sharp-libvips-darwin-arm64": "1.2.0", + "@img/sharp-libvips-darwin-x64": "1.2.0", + "@img/sharp-libvips-linux-arm": "1.2.0", + "@img/sharp-libvips-linux-arm64": "1.2.0", + "@img/sharp-libvips-linux-ppc64": "1.2.0", + "@img/sharp-libvips-linux-s390x": "1.2.0", + "@img/sharp-libvips-linux-x64": "1.2.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.0", + "@img/sharp-libvips-linuxmusl-x64": "1.2.0", + "@img/sharp-linux-arm": "0.34.3", + "@img/sharp-linux-arm64": "0.34.3", + "@img/sharp-linux-ppc64": "0.34.3", + "@img/sharp-linux-s390x": "0.34.3", + "@img/sharp-linux-x64": "0.34.3", + "@img/sharp-linuxmusl-arm64": "0.34.3", + "@img/sharp-linuxmusl-x64": "0.34.3", + "@img/sharp-wasm32": "0.34.3", + "@img/sharp-win32-arm64": "0.34.3", + "@img/sharp-win32-ia32": "0.34.3", + "@img/sharp-win32-x64": "0.34.3" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swr": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", + "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz", + "integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", + "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/ui/package.json b/ui/package.json new file mode 100644 index 00000000..cfa48ab9 --- /dev/null +++ b/ui/package.json @@ -0,0 +1,27 @@ +{ + "name": "ui", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack --port 3001", + "build": "next build", + "export": "next export", + "start": "next start" + }, + "dependencies": { + "@primer/octicons-react": "^19.15.5", + "bootstrap": "^5.3.7", + "d3-drag": "^3.0.0", + "d3-force": "^3.0.0", + "d3-selection": "^3.0.0", + "next": "^15.4.7", + "swr": "^2.3.6" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "eslint": "^9", + "eslint-config-next": "15.4.7", + "tailwindcss": "^4" + } +} diff --git a/ui/public/favicon.ico b/ui/public/favicon.ico new file mode 100644 index 00000000..e69de29b diff --git a/ui/public/favicon.svg b/ui/public/favicon.svg new file mode 100644 index 00000000..17791cf9 --- /dev/null +++ b/ui/public/favicon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/ui/shield.png b/ui/shield.png new file mode 100644 index 00000000..93d8476f Binary files /dev/null and b/ui/shield.png differ diff --git a/ui/shield.svg b/ui/shield.svg new file mode 100644 index 00000000..17791cf9 --- /dev/null +++ b/ui/shield.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/ui/src/app/[slug]/route.js b/ui/src/app/[slug]/route.js new file mode 100644 index 00000000..290f3184 --- /dev/null +++ b/ui/src/app/[slug]/route.js @@ -0,0 +1,18 @@ +const { NextResponse } = require('next/server'); + +// JS version: no type annotations +export async function GET(request, context) { + const params = context && context.params ? context.params : {}; + const slug = params.slug || ''; + return NextResponse.json({ message: `Hello ${slug}!` }); +} + +export async function generateStaticParams() { + // Replace with your actual slugs + return [ + { slug: 'example1' }, + { slug: 'example2' } + ]; +} + +export const dynamic = 'force-static'; \ No newline at end of file diff --git a/ui/src/app/api/logs/route.js b/ui/src/app/api/logs/route.js new file mode 100644 index 00000000..ec873364 --- /dev/null +++ b/ui/src/app/api/logs/route.js @@ -0,0 +1,28 @@ +import fs from 'fs/promises' +import path from 'path' + +export const dynamic = 'force-static' + +async function findLogFile () { + const candidates = [] + if (process.env.SAFE_SETTINGS_LOG_FILE) candidates.push(process.env.SAFE_SETTINGS_LOG_FILE) + candidates.push(path.join(process.cwd(), 'safe-settings.log')) + candidates.push(path.join(process.cwd(), '..', 'safe-settings.log')) + candidates.push(path.join(process.cwd(), '..', '..', 'safe-settings.log')) + + for (const p of candidates) { + if (!p) continue + try { + const st = await fs.stat(p) + if (st && st.isFile()) return p + } catch (e) { + // ignore + } + } + return null +} + +export async function GET () { + const msg = 'Disabled in static export: use the backend endpoint /api/safe-settings/logs or set SAFE_SETTINGS_LOG_FILE to point at the log file.' + return new Response(msg, { status: 200, headers: { 'content-type': 'text/plain; charset=utf-8' } }) +} diff --git a/ui/src/app/components/EnvVariables.jsx b/ui/src/app/components/EnvVariables.jsx new file mode 100644 index 00000000..aab69cfe --- /dev/null +++ b/ui/src/app/components/EnvVariables.jsx @@ -0,0 +1,144 @@ +'use client'; +import React, { useEffect, useState, useMemo } from 'react'; +import { SearchIcon, SyncIcon, EyeClosedIcon, EyeIcon, ShieldIcon, CopyIcon, ChevronUpIcon, ChevronDownIcon } from '@primer/octicons-react'; +import { useHydrated } from '../hooks/useHydrated'; + +const SENSITIVE_REGEX = /(secret|token|key|password|private)/i; + +export default function EnvVariables() { + const hydrated = useHydrated(); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [rows, setRows] = useState([]); + const [search, setSearch] = useState(''); + const [includeInfra, setIncludeInfra] = useState(false); + const [revealAll, setRevealAll] = useState(false); + const [lastFetchedAt, setLastFetchedAt] = useState(null); + const [sortConfig, setSortConfig] = useState({ key: null, direction: null }); + + const fetchData = () => { + if (!hydrated) return; + setLoading(true); setError(null); + fetch(`/api/safe-settings/app/env${includeInfra ? '?includeInfra=true' : ''}`) + .then(r => { + if (!r.ok) { + throw new Error(`Unable to retrieve environment variables (HTTP ${r.status}). Please try again later.`); + } + return r.json(); + }) + .then(json => { + setRows(json.variables || []); + setLastFetchedAt(new Date(json.updatedAt || Date.now())); + }) + .catch(e => setError(e.message)) + .finally(() => setLoading(false)); + }; + + useEffect(() => { fetchData(); /* eslint-disable-next-line */ }, [hydrated, includeInfra]); + + const filtered = useMemo(() => { + if (!search) return rows; + const q = search.toLowerCase(); + return rows.filter(r => r.key.toLowerCase().includes(q) || (r.value + '').toLowerCase().includes(q)); + }, [rows, search]); + + const sorted = useMemo(() => { + if (!sortConfig.key || !sortConfig.direction) return filtered; + const list = [...filtered]; + list.sort((a, b) => { + let av = a[sortConfig.key]; + let bv = b[sortConfig.key]; + if (av == null) av = ''; + if (bv == null) bv = ''; + av = (av + '').toLowerCase(); + bv = (bv + '').toLowerCase(); + if (av < bv) return sortConfig.direction === 'asc' ? -1 : 1; + if (av > bv) return sortConfig.direction === 'asc' ? 1 : -1; + return 0; + }); + return list; + }, [filtered, sortConfig]); + + const cycleSort = (key) => { + setSortConfig(prev => { + if (prev.key === key) { + if (prev.direction === 'asc') return { key, direction: 'desc' }; + if (prev.direction === 'desc') return { key: null, direction: null }; + } + return { key, direction: 'asc' }; + }); + }; + + const renderSortIcon = (key) => { + if (sortConfig.key !== key) return ; + if (sortConfig.direction === 'asc') return ; + if (sortConfig.direction === 'desc') return ; + return ; + }; + + const maskedValue = (k, v) => { + if (revealAll) return v; + if (!SENSITIVE_REGEX.test(k)) return v; + if (!v) return v; + if (v.length <= 4) return '*'.repeat(v.length); + return v.slice(0, 2) + '***' + v.slice(-2); + }; + + const copyToClipboard = (text) => { + try { navigator.clipboard.writeText(text); } catch(_) {} + } + + return ( +
+
+
+ +
+ + setSearch(e.target.value)} /> +
+
+ {/* Removed options and buttons section for a cleaner environment page UI */} +
+ + {loading &&
Loading…
} + {error && !loading &&
Error: {error}
} + {!loading && !error && filtered.length === 0 &&
No variables
} + + {!loading && !error && filtered.length > 0 && ( +
+ + + + + + + + + + + {sorted.map(r => { + const sensitive = SENSITIVE_REGEX.test(r.key); + return ( + + + + + + + ); + })} + +
cycleSort('key')} className="theme-text-primary user-select-none" style={{ width: '28%', cursor: 'pointer' }}>Key {renderSortIcon('key')} cycleSort('value')} className="theme-text-primary user-select-none" style={{ cursor: 'pointer' }}>Value {renderSortIcon('value')}
{r.key} + {maskedValue(r.key, r.value)} + {sensitive && } + +
+
+ )} +
+ {sorted.length} shown / {rows.length} total +
+
+ ); +} diff --git a/ui/src/app/components/HubOrgGraph.jsx b/ui/src/app/components/HubOrgGraph.jsx new file mode 100644 index 00000000..bd3b42e3 --- /dev/null +++ b/ui/src/app/components/HubOrgGraph.jsx @@ -0,0 +1,140 @@ +'use client'; +import { useEffect, useRef } from "react"; +import useSWR from "swr"; + +const fetcher = (...args) => fetch(...args).then(res => res.json()); + +export default function HubOrgGraph({ width = 640, height = 320 }) { + const vizRef = useRef(null); + const { data, error } = useSWR("/api/safe-settings/installation", fetcher); + const orgs = Array.isArray(data?.installations) + ? data.installations.filter(i => i.type === "Organization") + : []; + const orgCount = orgs.length; + + useEffect(() => { + if (typeof window === "undefined" || !data) return; + Promise.all([ + import("d3-selection"), + import("d3-force"), + import("d3-drag") + ]).then(([d3Selection, d3Force, d3Drag]) => { + const select = d3Selection.select; + const forceSimulation = d3Force.forceSimulation; + const forceLink = d3Force.forceLink; + const forceManyBody = d3Force.forceManyBody; + const forceCenter = d3Force.forceCenter; + const drag = d3Drag.drag; + // Dynamic graph data: 1 HUB, N ORGs + const nodes = [ { id: "Hub", group: 1, label: "Hub", color: "#0a2540" } ]; + if (orgs.length > 0) { + orgs.forEach((org, i) => { + const orgKey = org.account; + const hasConfigRepo = org.hasConfigRepo === true; + nodes.push({ id: orgKey, group: 2, label: "ORG", color: hasConfigRepo ? "#2ea44f" : "#6a737d", tooltip: org.account }); + }); + } else { + for (let i = 1; i <= orgCount; i++) { + nodes.push({ id: `ORG${i}`, group: 2, label: "ORG", color: "#6a737d", tooltip: `ORG${i}` }); + } + } + const links = []; + if (orgs.length > 0) { + orgs.forEach((org, i) => { + const orgKey = org.account; + links.push({ source: "Hub", target: orgKey }); + }); + } else { + for (let i = 1; i <= orgCount; i++) { + links.push({ source: "Hub", target: `ORG${i}` }); + } + } + select(vizRef.current).selectAll("svg").remove(); + const svg = select(vizRef.current) + .append("svg") + .attr("width", width) + .attr("height", height); + const simulation = forceSimulation(nodes) + .force("link", forceLink(links).id(d => d.id).distance(120)) + .force("charge", forceManyBody().strength(-400)) + .force("center", forceCenter(width / 2, height / 2)); + const link = svg.append("g") + .attr("stroke", "#999") + .attr("stroke-opacity", 0.6) + .selectAll("line") + .data(links) + .join("line") + .attr("stroke-width", 2); + const node = svg.append("g") + .attr("stroke", "#fff") + .attr("stroke-width", 2) + .selectAll("circle") + .data(nodes) + .join("circle") + .attr("r", 24) + .attr("fill", d => d.group === 1 ? d.color : d.color || "#6f42c1") + .call(drag() + .on("start", (event, d) => { + if (!event.active) simulation.alphaTarget(0.3).restart(); + d.fx = d.x; d.fy = d.y; + }) + .on("drag", (event, d) => { + d.fx = event.x; d.fy = event.y; + }) + .on("end", (event, d) => { + if (!event.active) simulation.alphaTarget(0); + d.fx = null; d.fy = null; + }) + ); + node.append("title") + .text(d => d.group === 2 ? d.tooltip : "Hub"); + const label = svg.append("g") + .selectAll("text") + .data(nodes) + .join("text") + .attr("text-anchor", "middle") + .attr("dy", ".35em") + .attr("font-size", 16) + .attr("font-family", "sans-serif") + .attr("fill", d => d.group === 1 ? "#fff" : "#fff") + .text(d => d.label) + .each(function(d) { + d3Selection.select(this) + .append("title") + .text(d.group === 2 ? d.tooltip : "Hub"); + }); + simulation.on("tick", () => { + link + .attr("x1", d => d.source.x) + .attr("y1", d => d.source.y) + .attr("x2", d => d.target.x) + .attr("y2", d => d.target.y); + node + .attr("cx", d => d.x) + .attr("cy", d => d.y); + label + .attr("x", d => d.x) + .attr("y", d => d.y); + }); + }); + }, [width, height, orgCount, data]); + + if (error) return
Error loading organization graph.
; + if (!data) return
Loading organization graph...
; + + return ( +
+
+
+ + + Has safe-settings admin repo + + + + No safe-settings admin repo + +
+
+ ); +} diff --git a/ui/src/app/components/OrganizationsTable.jsx b/ui/src/app/components/OrganizationsTable.jsx new file mode 100644 index 00000000..f6e394ae --- /dev/null +++ b/ui/src/app/components/OrganizationsTable.jsx @@ -0,0 +1,582 @@ +"use client"; + +import React, { useState, useMemo, useEffect, useRef } from "react"; +import { + ChevronUpIcon, + ChevronDownIcon, + SearchIcon, + InfoIcon, +} from "@primer/octicons-react"; +import { useHydrated } from "../hooks/useHydrated"; + +// Mock organizations used when /api/safe-settings/installation returns 404 +const MOCK_ORGS = [ + { + id: 1, + name: "mock-org-one", + lastSyncDate: new Date(Date.now() - 3600 * 1000).toISOString(), + lastSyncMessage: "Initial mock sync", + lastSyncSha: "abcdef1", + ageSeconds: 3600, + }, + { + id: 2, + name: "example-inc", + lastSyncDate: new Date(Date.now() - 7200 * 1000).toISOString(), + lastSyncMessage: "Second mock sync", + lastSyncSha: "abcdef2", + ageSeconds: 7200, + }, + { + id: 3, + name: "demo-labs", + lastSyncDate: null, + lastSyncMessage: null, + lastSyncSha: null, + ageSeconds: null, + na: true, + }, +]; + +const OrganizationsTable = ({ organizations: propOrganizations = [] }) => { + const [searchTerm, setSearchTerm] = useState(""); + const [sortConfig, setSortConfig] = useState({ key: null, direction: null }); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [fetched, setFetched] = useState([]); + const hydrated = useHydrated(); + const [selectedIds, setSelectedIds] = useState(() => new Set()); + const headerCheckboxRef = useRef(null); + const [retrievingFiles, setRetrievingFiles] = useState(false); + const [retrieveMessage, setRetrieveMessage] = useState(null); + const [retrieveError, setRetrieveError] = useState(null); + const [retrieveResults, setRetrieveResults] = useState(null); + + // Fetch real organizations from backend API on client hydration + useEffect(() => { + if (!hydrated) return; // avoid SSR mismatch + let cancelled = false; + setLoading(true); + + fetch("/api/safe-settings/installation") + .then((r) => { + if (!r.ok) { + throw new Error( + `Unable to retrieve organizations (HTTP ${r.status}). Please try again later.` + ); + } + return r.json(); + }) + .then((json) => { + if (!json || cancelled) return; + const mapped = (json.installations || []).map((i) => ({ + id: i.id, + name: i.account, + lastSyncDate: i.committed_at || null, + lastSyncSha: i.sha || null, + lastSyncMessage: i.message || null, + ageSeconds: typeof i.age_seconds === "number" ? i.age_seconds : null, + hasConfigRepo: + typeof i.hasConfigRepo === "boolean" ? i.hasConfigRepo : false, + isInSync: typeof i.isInSync === "boolean" ? i.isInSync : false, + })); + setFetched(mapped); + setError(null); + }) + .catch((e) => { + if (!cancelled) setError(e.message); + }) + .finally(() => { + if (!cancelled) setLoading(false); + }); + return () => { + cancelled = true; + }; + }, [hydrated]); + + const data = + fetched.length > 0 + ? fetched + : propOrganizations.length > 0 + ? propOrganizations + : []; + + // Format date for display with hydration-safe approach + const formatLastSync = (org) => { + if (!org.lastSyncDate) return ; + const dateObj = new Date(org.lastSyncDate); + let ageSec = org.ageSeconds; + if (hydrated && ageSec == null) { + ageSec = Math.floor((Date.now() - dateObj.getTime()) / 1000); + } + const rel = (() => { + if (ageSec == null) return ""; + if (ageSec < 60) return "0m"; + const mTotal = Math.floor(ageSec / 60); + if (mTotal < 60) return `${mTotal}m`; + const hTotal = Math.floor(mTotal / 60); + if (hTotal < 24) { + const remM = mTotal % 60; + return remM ? `${hTotal}h ${remM}m` : `${hTotal}h`; + } + const dTotal = Math.floor(hTotal / 24); + const remH = hTotal % 24; + return remH ? `${dTotal}d ${remH}h` : `${dTotal}d`; + })(); + const fullStamp = `${dateObj.getFullYear()}-${String( + dateObj.getMonth() + 1 + ).padStart(2, "0")}-${String(dateObj.getDate()).padStart(2, "0")} ${String( + dateObj.getHours() + ).padStart(2, "0")}:${String(dateObj.getMinutes()).padStart( + 2, + "0" + )}:${String(dateObj.getSeconds()).padStart(2, "0")}`; + const tooltip = [ + fullStamp, + org.lastSyncMessage, + org.lastSyncSha ? `SHA: ${org.lastSyncSha.slice(0, 7)}` : null, + ] + .filter(Boolean) + .join("\n"); + return ( + + {rel} + + ); + }; + const lastSyncColStyle = { + width: "170px", + fontVariantNumeric: "tabular-nums", + }; + + // Filter organizations based on search term + const filteredData = useMemo(() => { + return data.filter((org) => + org.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + }, [data, searchTerm]); + + // Sort organizations + const sortedData = useMemo(() => { + if (!sortConfig.key) return filteredData; + + return [...filteredData].sort((a, b) => { + let aValue = a[sortConfig.key]; + let bValue = b[sortConfig.key]; + + // Convert dates to timestamps for comparison + if (sortConfig.key === "lastSyncDate") { + aValue = new Date(aValue).getTime(); + bValue = new Date(bValue).getTime(); + } + + if (aValue < bValue) { + return sortConfig.direction === "asc" ? -1 : 1; + } + if (aValue > bValue) { + return sortConfig.direction === "asc" ? 1 : -1; + } + return 0; + }); + }, [filteredData, sortConfig]); + + // Handle column sorting + const handleSort = (key) => { + setSortConfig((prevConfig) => { + if (prevConfig.key === key) { + if (prevConfig.direction === "asc") { + return { key, direction: "desc" }; + } else if (prevConfig.direction === "desc") { + return { key: null, direction: null }; + } + } + return { key, direction: "asc" }; + }); + }; + + // Render sort icon + const renderSortIcon = (columnKey) => { + if (sortConfig.key !== columnKey) { + return ( + + ↕ + + ); + } + if (sortConfig.direction === "asc") { + return ; + } + if (sortConfig.direction === "desc") { + return ; + } + return ( + + ↕ + + ); + }; + + // Keep header checkbox indeterminate when some but not all rows are selected + useEffect(() => { + if (!headerCheckboxRef || !headerCheckboxRef.current) return; + const selectableCount = sortedData.filter((o) => !o.synced).length; + headerCheckboxRef.current.indeterminate = + selectedIds.size > 0 && selectedIds.size < selectableCount; + }, [selectedIds, sortedData]); + + // Prune selection when the displayed dataset changes (remove ids that no longer exist) + useEffect(() => { + setSelectedIds((prev) => { + const allowed = new Set( + sortedData.filter((o) => !o.synced).map((o) => o.id) + ); + const next = new Set([...prev].filter((id) => allowed.has(id))); + if (next.size === prev.size) return prev; + return next; + }); + }, [sortedData]); + + // Retrieve files for selected organizations + const retrieveFilesForSelected = async () => { + if (selectedIds.size === 0) return; + // map selected ids back to organization names using the current sorted/filtered dataset + const orgNames = sortedData + .filter((o) => selectedIds.has(o.id)) + .map((o) => o.name); + if (orgNames.length === 0) return; + setRetrieveResults(null); + setRetrieveMessage(null); + setRetrieveError(null); + setRetrievingFiles(true); + try { + const res = await fetch("/api/safe-settings/hub/import", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ orgs: orgNames }), + }); + if (!res.ok) throw new Error(`Request failed (HTTP ${res.status})`); + const json = await res.json().catch(() => ({})); + if (Array.isArray(json.results)) { + setRetrieveResults(json.results); + const created = json.results.filter((r) => r.pr).length; + const skipped = json.results.filter((r) => r.skipped).map((r) => r.org); + const errors = json.results.filter((r) => r.error).length; + const parts = []; + if (created) + parts.push(`${created} PR${created > 1 ? "s" : ""} created`); + if (skipped.length) parts.push(`Skipped: ${skipped.join(", ")}`); + if (errors) parts.push(`${errors} error${errors > 1 ? "s" : ""}`); + setRetrieveMessage(parts.join(" • ") || "Retrieval completed"); + } else { + setRetrieveMessage(json.message || "Retrieval requested"); + } + } catch (e) { + setRetrieveError(e.message || String(e)); + } finally { + setRetrievingFiles(false); + } + }; + + return ( +
+ {/* Search Bar */} +
+
+
+
+ + + + setSearchTerm(e.target.value)} + /> +
+
+
+
+ + + + +
+
+
+
+ + {/* Reserved message area: keeps layout stable when messages appear */} +
+ {retrieveResults ? ( + retrieveResults.map((r) => ( +
+ {r.pr ? ( +
+ Imported {r.org}:{" "} + + {r.pr} + +
+ ) : r.skipped ? ( +
+ Skipping {r.org}: already present in hub +
+ ) : r.error ? ( +
+ {r.org}: {r.error} +
+ ) : null} +
+ )) + ) : ( + <> + {retrieveMessage && ( +
{retrieveMessage}
+ )} + {retrieveError && ( +
{retrieveError}
+ )} + + )} +
+ + {/* Table */} +
+ + + + + + + + + + + + {loading && ( + + + + )} + {!loading && error && ( + + + + )} + {!loading && !error && sortedData.length > 0 + ? sortedData.map((org) => { + return ( + + + + + + + + ); + }) + : !loading && + !error && ( + + + + )} + +
+ {/* compute selectable rows so header/select-all ignores already-imported orgs */} + { + const selectableCount = sortedData.filter( + (o) => !o.synced + ).length; + return ( + selectableCount > 0 && + selectedIds.size === selectableCount + ); + }, [sortedData, selectedIds])} + onChange={() => { + // toggle all selectable (non-synced) rows + setSelectedIds((prev) => { + const selectable = sortedData + .filter((o) => !o.synced) + .map((o) => o.id); + if (prev.size === selectable.length) return new Set(); + return new Set(selectable); + }); + }} + aria-label="Select all organizations" + /> + handleSort("name")} + > +
+
Organization Name
+
{renderSortIcon("name")}
+ + Showing {sortedData.length} of {data.length} organizations + +
+
+ Config Repo + + In Sync + handleSort("lastSyncDate")} + > + Last Sync + {renderSortIcon("lastSyncDate")} +
+ Loading organizations… +
+
+ {error} +
+
+ + setSelectedIds((prev) => { + const next = new Set(prev); + if (next.has(org.id)) next.delete(org.id); + else next.add(org.id); + return next; + }) + } + aria-label={`Select ${org.name}`} + disabled={org.synced === true} + style={ + org.synced + ? { opacity: 0.45, cursor: "not-allowed" } + : {} + } + /> + + {org.name} + {org.synced && ( + Imported + )} + + {org.hasConfigRepo ? ( + + ✓ + + ) : ( + + NA + + )} + + {org.isInSync ? ( + + ✓ + + ) : ( + + ✗ + + )} + + {formatLastSync(org)} +
+ {searchTerm + ? `No organizations found matching "${searchTerm}"` + : "No organizations available"} +
+
+ + {/* Table Footer Info */} + {sortedData.length > 0 && ( +
+ + {searchTerm && `Filtered by: "${searchTerm}"`} + {sortConfig.key && ( + + • Sorted by:{" "} + {sortConfig.key === "name" + ? "Organization Name" + : "Last Safe-settings Sync"} + ({sortConfig.direction === "asc" ? "A-Z" : "Z-A"}) + + )} + +
+ )} +
+ ); +}; + +export default OrganizationsTable; diff --git a/ui/src/app/components/Safe-settings-hubContent.jsx b/ui/src/app/components/Safe-settings-hubContent.jsx new file mode 100644 index 00000000..ada55361 --- /dev/null +++ b/ui/src/app/components/Safe-settings-hubContent.jsx @@ -0,0 +1,278 @@ +'use client'; +import React, { useEffect, useState, useMemo, useCallback } from 'react'; +import { SearchIcon, FileIcon, FileDirectoryIcon, ChevronDownIcon, ChevronRightIcon } from '@primer/octicons-react'; +import { useHydrated } from '../hooks/useHydrated'; + +// Match the left index width and reuse for the search input +const LEFT_COL_WIDTH = 320; + + +export default function SafeSettingsHubContent3b() { + const hydrated = useHydrated(); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [rootTree, setRootTree] = useState(null); + const [search, setSearch] = useState(''); + const [expandedPaths, setExpandedPaths] = useState(() => new Set()); + const [selectedPath, setSelectedPath] = useState(null); + const [lastFetchedAt, setLastFetchedAt] = useState(null); + + const fetchData = () => { + if (!hydrated) return; + setLoading(true); setError(null); + fetch('/api/safe-settings/hub/content?fetchContent=true') + .then(r => { + if (!r.ok) throw new Error(`Unable to retrieve safe-settings hub content (HTTP ${r.status})`); + return r.json(); + }) + .then(json => { setRootTree(json); setLastFetchedAt(new Date()); }) + .catch((error) => { setError("Unable to load content. Please try again later."); setRootTree(null); }) + .finally(() => setLoading(false)); + }; + + useEffect(() => { fetchData(); }, [hydrated]); + + const filterTree = useCallback((node) => { + if (!node) return null; + const term = search.toLowerCase(); + const matches = (n) => !term || (n.name && n.name.toLowerCase().includes(term)) || (n.path && n.path.toLowerCase().includes(term)); + if (node.type === 'file') { + return matches(node) ? node : null; + } + if (node.type === 'dir') { + const filteredEntries = (node.entries || []) + .map(child => filterTree(child)) + .filter(Boolean); + if (matches(node) || filteredEntries.length > 0) { + return { ...node, entries: filteredEntries }; + } + return null; + } + return null; + }, [search]); + + const filteredTree = useMemo(() => filterTree(rootTree), [rootTree, filterTree]); + + const displayTree = useMemo(() => { + if (!filteredTree) return null; + if (filteredTree.type === 'dir') { + const nameMatch = (n) => n && n.type === 'dir' && n.name && n.name.toLowerCase().includes('safe-settings'); + const immediate = (filteredTree.entries || []).find(nameMatch); + if (immediate) return immediate; + const findDescendant = (node, depth = 0, maxDepth = 3) => { + if (!node || node.type !== 'dir' || depth >= maxDepth) return null; + for (const child of node.entries || []) if (nameMatch(child)) return child; + for (const child of node.entries || []) if (child.type === 'dir') { + const found = findDescendant(child, depth + 1, maxDepth); + if (found) return found; + } + return null; + }; + const found = findDescendant(filteredTree, 0, 3); + if (found) return found; + } + return filteredTree; + }, [filteredTree]); + + useEffect(() => { if (!displayTree) return; setSelectedPath(prev => prev || displayTree.path); }, [displayTree]); + + const findNodeByPath = useCallback((node, path) => { + if (!node) return null; + if (node.path === path) return node; + if (node.type === 'dir') { + for (const child of node.entries || []) { + const found = findNodeByPath(child, path); + if (found) return found; + } + } + return null; + }, []); + + const selectedNode = useMemo(() => { + if (!displayTree || !selectedPath) return null; + return findNodeByPath(displayTree, selectedPath); + }, [displayTree, selectedPath, findNodeByPath]); + + const toggleDir = (path) => { setExpandedPaths(prev => { const next = new Set(prev); if (next.has(path)) next.delete(path); else next.add(path); return next; }); }; + + const formatTimeAgo = (iso) => { + if (!iso) return '—'; + const dt = new Date(iso); + if (Number.isNaN(dt.getTime())) return iso; + const diffSec = Math.floor((Date.now() - dt.getTime()) / 1000); + if (diffSec < 60) return 'just now'; + const diffMin = Math.floor(diffSec / 60); + if (diffMin < 60) return `${diffMin} minute${diffMin === 1 ? '' : 's'} ago`; + const diffH = Math.floor(diffMin / 60); + if (diffH < 24) return `${diffH} hour${diffH === 1 ? '' : 's'} ago`; + const diffD = Math.floor(diffH / 24); + if (diffD < 30) return `${diffD} day${diffD === 1 ? '' : 's'} ago`; + const diffM = Math.floor(diffD / 30); + if (diffM < 12) return diffM === 1 ? '1 month ago' : `${diffM} months ago`; + const diffY = Math.floor(diffD / 365); + if (diffY === 1) return 'last year'; + return `${diffY} years ago`; + }; + + const repoCount = useMemo(() => { + if (!rootTree) return '—'; + const rp = rootTree.reposProcessed || rootTree.repos || null; + if (!rp) return '—'; + if (Array.isArray(rp)) return rp.length; + if (typeof rp === 'object') return Object.keys(rp).length; + return '—'; + }, [rootTree]); + + const renderTree = (node, depth = 0) => { + if (!node) return null; + if (node.type === 'file') { + const selected = selectedPath === node.path; + return ( +
setSelectedPath(node.path)}> + + {node.name} +
+ ); + } + const expanded = expandedPaths.has(node.path); + const selected = selectedPath === node.path; + return ( +
+
+
{ toggleDir(node.path); setSelectedPath(node.path); }} className="d-inline-flex align-items-center"> + {expanded ? : } + + {node.name} +
+
+ {expanded && (node.entries || []).map(child => renderTree(child, depth + 1))} +
+ ); + }; + + const childrenForSelected = useMemo(() => { if (!selectedNode) return []; if (selectedNode.type === 'dir') return selectedNode.entries || []; return []; }, [selectedNode]); + + const fileContent = useMemo(() => { if (!selectedNode || selectedNode.type !== 'file') return null; return selectedNode.content || selectedNode.body || selectedNode.text || selectedNode.preview || null; }, [selectedNode]); + + const fileLines = useMemo(() => fileContent ? fileContent.split('\n') : [], [fileContent]); + const lineCount = fileLines.length; + const locCount = fileLines.filter(l => l.trim()).length; + const byteCount = useMemo(() => { + if (!fileContent) return 0; + try { return new TextEncoder().encode(fileContent).length; } catch (e) { return fileContent.length; } + }, [fileContent]); + + return ( +
+
+
+
+
+ + setSearch(e.target.value)} /> +
+ {selectedNode &&
{selectedNode.path}
} +
+
+
+
+ {/* edit button intentionally removed */} +
+
+
+ + {loading &&
Loading…
} + {error &&
{error}
} + {!loading && !displayTree &&
No entries
} + + {!loading && displayTree && ( +
+
+ {/* left tree */} + {displayTree.type === 'dir' && displayTree.name && displayTree.name.toLowerCase().includes('safe-settings') + ? (displayTree.entries || []).map(child => renderTree(child, 0)) + : renderTree(displayTree) + } +
+ +
+ {/* right content (dir/file view) */} + {selectedNode && selectedNode.type === 'dir' && ( +
+ {/* path rendered next to the filter at the top; removed empty toolbar to avoid extra top gap */} +
+ + + + + + + + + + {(childrenForSelected.length === 0) && ( + + )} + {childrenForSelected.map(child => ( + setSelectedPath(child.path)}> + + + + + ))} + +
NameCommit-MessageLast commit date
No entries
+ + {child.type === 'dir' ? : } + {child.name} + + {child.lastCommitMessage || '—'}{child.lastCommitAt ? formatTimeAgo(child.lastCommitAt) : '—'}
+
+
+ )} + + {selectedNode && selectedNode.type === 'file' && ( +
+ {/* path rendered next to the filter at the top; removed empty toolbar to avoid extra top gap */} +
+ {/* file header with border and rounded top, followed by a bordered code area with rounded bottom */} +
+
+
+
+ + +
+
+ +
+
+
+ {fileLines.map((_, i) =>
{i + 1}
)} +
+
+ {fileLines.length === 0 ? ( +
No content available
+ ) : ( + fileLines.map((ln, i) =>
{ln || ' '}
) + )} +
+
+
+
+
+
+ )} + + {!selectedNode && ( +
Select a folder or file from the left to view contents.
+ )} + +
+
+ )} + + {/* footer (items shown) removed */} +
+ ); +} diff --git a/ui/src/app/components/ThemeContext.jsx b/ui/src/app/components/ThemeContext.jsx new file mode 100644 index 00000000..75470143 --- /dev/null +++ b/ui/src/app/components/ThemeContext.jsx @@ -0,0 +1,71 @@ +'use client'; + +import React, { createContext, useContext, useState, useEffect } from 'react'; + +const ThemeContext = createContext(); + +export const useTheme = () => { + const context = useContext(ThemeContext); + if (!context) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +}; + +export const ThemeProvider = ({ children }) => { + // Always start with 'light' for SSR consistency + const [theme, setTheme] = useState('light'); + const [mounted, setMounted] = useState(false); + + // Only run on client side + useEffect(() => { + setMounted(true); + + // Migrate old theme key if it exists + const oldTheme = localStorage.getItem('safe-settings-theme'); + if (oldTheme && !localStorage.getItem('theme')) { + localStorage.setItem('theme', oldTheme); + localStorage.removeItem('safe-settings-theme'); + } + + // Get theme from localStorage + const savedTheme = localStorage.getItem('theme') || 'light'; + + // Set state (will trigger re-render with correct theme) + setTheme(savedTheme); + + // Apply to DOM immediately to prevent flash + document.documentElement.setAttribute('data-theme', savedTheme); + }, []); + + // Apply theme changes to DOM + useEffect(() => { + if (mounted) { + document.documentElement.setAttribute('data-theme', theme); + } + }, [theme, mounted]); + + const toggleTheme = () => { + const newTheme = theme === 'light' ? 'dark' : 'light'; + setTheme(newTheme); + localStorage.setItem('theme', newTheme); + }; + + const setSpecificTheme = (themeName) => { + setTheme(themeName); + localStorage.setItem('theme', themeName); + }; + + // Render children immediately - no hiding + return ( + + {children} + + ); +}; diff --git a/ui/src/app/components/ThemeToggle.jsx b/ui/src/app/components/ThemeToggle.jsx new file mode 100644 index 00000000..803c9d99 --- /dev/null +++ b/ui/src/app/components/ThemeToggle.jsx @@ -0,0 +1,17 @@ +'use client'; + +import { useTheme } from './ThemeContext'; + +export default function ThemeToggle() { + const { theme, toggleTheme, isDark } = useTheme(); + + return ( + + ); +} diff --git a/ui/src/app/components/TitleBar.css b/ui/src/app/components/TitleBar.css new file mode 100644 index 00000000..df856eff --- /dev/null +++ b/ui/src/app/components/TitleBar.css @@ -0,0 +1,174 @@ +/* TitleBar Component-Specific Styles */ + +/* Header styles */ +.title-header { + background: #333; + color: #fff; + min-height: 40px; /* Ensure consistent height */ +} + +/* Theme-specific header styles */ +[data-theme="light"] .title-header, +body.light-theme .title-header { + background: #333; + color: #fff; +} + +[data-theme="dark"] .title-header, +body.dark-theme .title-header { + background: #161b22; + color: #f0f6fc; +} + +/* Navigation bar - consistent height and styling */ +.title-nav { + min-height: 40px; /* Consistent nav height */ + border-bottom: 1px solid var(--border-color, #dee2e6) !important; + background: var(--bg-secondary, #f6f8fa); /* Default light theme background */ +} + +/* Data-theme selectors for immediate theme application */ +[data-theme="light"] .title-nav, +body.light-theme .title-nav { + background: #f6f8fa; + color: #24292f; + border-bottom: 1px solid #dee2e6 !important; +} + +[data-theme="dark"] .title-nav, +body.dark-theme .title-nav { + background: #22272e; + color: #f6f8fa; + border-bottom: 1px solid #666a6e !important; +} + +/* Theme toggle button */ +.theme-toggle-btn { + border: none !important; + background: transparent !important; + cursor: pointer !important; + padding: 2px !important; + border-radius: 6px !important; +} + +.theme-toggle-btn .theme-toggle-icon { + transition: color 0.15s; + color: lightgray; /* Default icon color */ +} + +/* Theme-specific toggle button styles */ +[data-theme="light"] .theme-toggle-btn .theme-toggle-icon, +body.light-theme .theme-toggle-btn .theme-toggle-icon { + color: #fff; +} + +[data-theme="dark"] .theme-toggle-btn .theme-toggle-icon, +body.dark-theme .theme-toggle-btn .theme-toggle-icon { + color: #f0f6fc; +} + +[data-theme="light"] .theme-toggle-btn:hover, +body.light-theme .theme-toggle-btn:hover { + background: rgba(255, 255, 255, 0.1) !important; +} + +[data-theme="dark"] .theme-toggle-btn:hover, +body.dark-theme .theme-toggle-btn:hover { + background: rgba(240, 246, 252, 0.1) !important; +} + +.theme-toggle-btn:hover .theme-toggle-icon, +.theme-toggle-btn:focus .theme-toggle-icon { + color: yellow; /* Hover icon color */ +} + +/* Navigation links */ +.nav-link-custom { + border: none; +} + +/* Light theme nav links */ +[data-theme="light"] .nav-link-custom, +body.light-theme .nav-link-custom { + color: #24292f !important; +} + +/* Dark theme nav links */ +[data-theme="dark"] .nav-link-custom, +body.dark-theme .nav-link-custom { + color: #f6f8fa; +} + +/* Navigation menu items */ +.nav-link.menu-hover { + border-radius: 5px !important; + margin: 10px 10px 9px 10px !important; + padding: 5px 10px !important; + transition: background 0.15s, color 0.15s; + border: 1px solid transparent !important; /* Invisible border to maintain box model */ +} + +.nav-link.menu-hover:hover { + background: var(--bg-accent) !important; + border-radius: 5px !important; + border: 1px solid transparent !important; /* Keep same border width */ +} + +/* Theme-specific hover colors */ +[data-theme="light"] .nav-link.menu-hover:hover, +body.light-theme .nav-link.menu-hover:hover { + background-color: #eaecef !important; +} + +[data-theme="dark"] .nav-link.menu-hover:hover, +body.dark-theme .nav-link.menu-hover:hover { + background-color: #30363d !important; +} + +/* Override Bootstrap's default nav-tabs border-radius */ +.nav-tabs .nav-link { + border-radius: 5px !important; + border: none !important; + outline: none !important; + box-shadow: none !important; +} + +.nav-tabs .nav-link:hover { + border-radius: 5px !important; + border: none !important; + outline: none !important; + box-shadow: none !important; +} + +.nav-tabs .nav-link:focus { + border: none !important; + outline: none !important; + box-shadow: none !important; +} + +.nav-tabs .nav-link:active { + border: none !important; + outline: none !important; + box-shadow: none !important; +} + +.nav-tabs { + border-top: none; + border-bottom: none; +} + +.menu-hover.active { + background: transparent; + border: none !important; +} + +/* Active menu indicator */ +.menu-active-indicator { + position: absolute; + left: 0; + right: 0; + bottom: -10px; + height: 2px; + background: rgb(253, 140, 115); /* Orange-red underline color */ + border-radius: 1px; +} diff --git a/ui/src/app/components/TitleBar.jsx b/ui/src/app/components/TitleBar.jsx new file mode 100644 index 00000000..89353733 --- /dev/null +++ b/ui/src/app/components/TitleBar.jsx @@ -0,0 +1,148 @@ +"use client"; +import { usePathname } from "next/navigation"; +import React from "react"; +import { + GlobeIcon, + GearIcon, + ListUnorderedIcon, + SunIcon, + MoonIcon, + NoteIcon, +} from "@primer/octicons-react"; +import { useTheme } from "./ThemeContext"; +import "./TitleBar.css"; + +export default function TitleBar() { + const pathname = usePathname(); + const { isDark, toggleTheme } = useTheme(); + + // Always render the TitleBar structure to prevent layout shift + return ( + <> +
+
+ + + + + + Safe-Settings Hub Dashboard + + +
+ +
+
+
+ + + ); +} diff --git a/ui/src/app/dashboard/env/page.jsx b/ui/src/app/dashboard/env/page.jsx new file mode 100644 index 00000000..6022b0b9 --- /dev/null +++ b/ui/src/app/dashboard/env/page.jsx @@ -0,0 +1,23 @@ +import TitleBar from "../../components/TitleBar"; +import EnvVariables from "../../components/EnvVariables"; + +export default function EnvVarsPage() { + return ( +
+ +
+
+

App Environment Settings

+

+ These are the current settings used by the app. Some values are hidden or + masked for security. +

+
+
+
+ +
+
+
+ ); +} diff --git a/ui/src/app/dashboard/help/page.jsx b/ui/src/app/dashboard/help/page.jsx new file mode 100644 index 00000000..866aa7fa --- /dev/null +++ b/ui/src/app/dashboard/help/page.jsx @@ -0,0 +1,35 @@ +'use client'; + +import TitleBar from "../../components/TitleBar"; +import Link from "next/link"; +import HubOrgGraph from "../../components/HubOrgGraph"; + +export default function HelpPage() { + return ( +
+ +
+

Dashboard & Hub - Help

+

Quick guidance for the Safe-Settings Dashboard and Hub.

+ +

+

What is the Safe-Settings Dashboard

+

+ This UI provides status information for the Safe-Settings Hub feature. It is a read-first reporting and status tool that displays configuration state and import/sync status. +

+

How to get started

+

+ The Organizations page lists every Org where the Safe-Settings Hub is installed. You can use the Retrieve Settings button to perform an initial import from the selected organizations' config repositories. It reads files from the configured CONFIG_PATH in each organization's config repo and commits them into a single branch in the hub repository, then opens a pull request for review. This is intended for initial population or one-time imports — the action will skip organizations that already have content in the hub path. +

+

How to edit configuration

+

+ The dashboard is not a content editor. To change configuration you should edit files in your admin repository and follow the normal GitHub workflow: commit changes, open a pull request, get required approvers to review, and merge. After the PR is merged the dashboard will reflect the updated state. +

+
+

+ If you need more help, check the repository documentation or contact the maintainers. +

+
+
+ ); +} diff --git a/ui/src/app/dashboard/logs/page.jsx b/ui/src/app/dashboard/logs/page.jsx new file mode 100644 index 00000000..184e6da6 --- /dev/null +++ b/ui/src/app/dashboard/logs/page.jsx @@ -0,0 +1,110 @@ +"use client" +import TitleBar from '../../components/TitleBar' +import { useState } from 'react' + +export default function LogsPage () { + // Static mock data for demonstration + const mockEntries = [ + { timestamp: '2025-09-11T10:00:00.000Z', level: 'INFO', message: 'Safe Settings service started.' }, + { timestamp: '2025-09-11T10:01:05.123Z', level: 'WARN', message: 'Config file missing, using defaults.' }, + { timestamp: '2025-09-11T10:02:10.456Z', level: 'ERROR', message: 'Failed to sync settings: network error.' }, + { timestamp: '2025-09-11T10:03:00.789Z', level: 'DEBUG', message: 'Polling GitHub API for updates.' }, + { timestamp: '2025-09-11T10:04:15.000Z', level: 'INFO', message: 'Sync completed successfully.' }, + { timestamp: '2025-09-11T10:05:00.000Z', level: 'INFO', message: 'SYNC: Organization settings updated.' }, + { timestamp: '2025-09-11T10:06:00.000Z', level: 'ERROR', message: 'SYNC: Failed to update organization settings.' } + ] + + const logLevels = ['INFO', 'WARN', 'DEBUG', 'ERROR'] + const [selectedLevels, setSelectedLevels] = useState(new Set(logLevels)) + const [search, setSearch] = useState('') + + const toggleLevel = (lvl) => { + const next = new Set(selectedLevels) + if (next.has(lvl)) next.delete(lvl) + else next.add(lvl) + setSelectedLevels(next) + } + + const filtered = mockEntries.filter(e => + selectedLevels.has(e.level.toUpperCase()) && + (search.trim() === '' || e.message.toLowerCase().includes(search.trim().toLowerCase())) + ) + + return ( + <> + +
+
+
+
+

Safe Settings Log

+

View recent log entries for Safe Settings operations and syncs.

+
+
+
+
+
+
+
Filter Options
+
+ Log Levels: +
+ {logLevels.map(lvl => ( + + ))} +
+
+
+ Search Message: + setSearch(e.target.value)} + style={{ maxWidth: 300 }} + /> +
+
+
+
+
+
+
+
Log Entries
+
+ + + + + + + + + + {filtered.map((row, i) => { + let levelClass = '' + if (row.level === 'ERROR') levelClass = 'log-error' + else if (row.level === 'WARN') levelClass = 'log-warn' + return ( + + + + + + ) + })} + +
TimestampLevelMessage
{row.timestamp || '-'}{row.level || 'UNKNOWN'}{row.message}
+ {filtered.length === 0 &&
No log entries match your filters.
} +
+
+
+
+
+ + ) +} diff --git a/ui/src/app/dashboard/organizations/page.jsx b/ui/src/app/dashboard/organizations/page.jsx new file mode 100644 index 00000000..596f9299 --- /dev/null +++ b/ui/src/app/dashboard/organizations/page.jsx @@ -0,0 +1,24 @@ +import TitleBar from "../../components/TitleBar"; +import OrganizationsTable from "../../components/OrganizationsTable"; + +export default function OrganizationsPage() { + return ( +
+ +
+
+

+ Organizations +

+

+ List all the Organizations where the Safe-Settings App is installed and the last time Safe-settings configurations were synced. +

+
+ +
+ +
+
+
+ ); +} diff --git a/ui/src/app/dashboard/page.jsx b/ui/src/app/dashboard/page.jsx new file mode 100644 index 00000000..7e4787fc --- /dev/null +++ b/ui/src/app/dashboard/page.jsx @@ -0,0 +1,25 @@ +import TitleBar from "../components/TitleBar"; +import { AlertIcon, ArrowRightIcon, CheckCircleIcon, GitCommitIcon, GitPullRequestIcon, GitMergeIcon, EyeIcon } from "@primer/octicons-react"; + +export default function DashboardPage() { + return ( +
+ +
+

Welcome to the Safe-Settings Hub Dashboard

+

Select a menu item above to get started.

+

+ This dashboard is a read-first reporting interface that displays configuration state and sync activity status for the Safe-Settings Hub.
+
It is not intended as the workflow for editing Safe-Settings Hub configuration content.

+ +
To make changes, please use the standard GitHub process for content updates:


+ Commit        + Pull Request        + Approve        + Merge         + +

+
+
+ ); +} diff --git a/ui/src/app/dashboard/safe-settings-hub/page.jsx b/ui/src/app/dashboard/safe-settings-hub/page.jsx new file mode 100644 index 00000000..56a4ef35 --- /dev/null +++ b/ui/src/app/dashboard/safe-settings-hub/page.jsx @@ -0,0 +1,25 @@ +import TitleBar from "../../components/TitleBar"; +import MasterAdminContents from "../../components/Safe-settings-hubContent"; + +export default function SafeSettingsHubConfigPage() { + return ( +
+ +
+
+

+ Safe-Settings Hub Content +

+

+ Listing files maintained by the Safe-Settings Global configuration (all ORG's). + Files are retrieved from `/api/safe-settings/hub/content`. +

+
+
+
+ +
+
+
+ ); +} diff --git a/ui/src/app/globals.css b/ui/src/app/globals.css new file mode 100644 index 00000000..0dbf38c6 --- /dev/null +++ b/ui/src/app/globals.css @@ -0,0 +1,277 @@ +/* Global Theme Variables */ +/* Default theme variables (light theme as default) */ +:root { + --bg-primary: #ffffff; + --bg-secondary: #f8f9fa; + --bg-accent: #eaecef; + --text-primary: #24292f; + --text-secondary: #6c757d; + --border-color: #dee2e6; +} + +/* Theme variables based on data-theme attribute */ +[data-theme="light"] { + --bg-primary: #ffffff; + --bg-secondary: #f8f9fa; + --bg-accent: #eaecef; + --text-primary: #24292f; + --text-secondary: #6c757d; + --border-color: #dee2e6; +} + +[data-theme="dark"] { + --bg-primary: rgb(13, 17, 22); + --bg-secondary: #444444; + --bg-accent: #30363d; + --text-primary: #f0f6fc; + --text-secondary: #dddddd; + --border-color: #4d4d4d; +} + +/* Legacy support for body classes */ +body.light-theme { + --bg-primary: #ffffff; + --bg-secondary: #f8f9fa; + --bg-accent: #eaecef; + --text-primary: #24292f; + --text-secondary: #6c757d; + --border-color: #dee2e6; +} + +body.dark-theme { + --bg-primary: #161b22; + --bg-secondary: #444444; + --bg-accent: #30363d; + --text-primary: #f0f6fc; + --text-secondary: #b3b3b3; +} + +/* Global Theme Styles */ +/* Default body styling (light theme as default) */ +body { + background: var(--bg-primary, #fff) !important; + color: var(--text-primary, #24292f) !important; +} + +/* Theme-specific body styles using data-theme */ +[data-theme="light"] body, +body.light-theme { + background: #fff !important; + color: var(--text-primary) !important; +} + +[data-theme="dark"] body, +body.dark-theme { + background: rgb(24, 24, 24) !important; + color: var(--text-primary) !important; +} + +/* Global Main Element Theme */ +[data-theme="light"] main, +body.light-theme main { + background: #fff !important; + color: var(--text-primary) !important; +} + +[data-theme="dark"] main, +body.dark-theme main { + /* background: #161b22; */ + color: var(--text-primary) !important; +} + +[data-theme="light"] .nav-link, +body.light-theme .nav-link { + color: var(--text-primary) !important; +} + +[data-theme="dark"] .nav-link, +body.dark-theme .nav-link { + /* color: #f6f8fa !important; */ + color: #6c757d !important; +} + +/* title bar nav tabs */ +[data-theme="dark"] .nav-tabs, +body.dark-theme .nav-tabs { + background: #22272e; + border: none !important; +} + +[data-theme="light"] .nav-tabs, +body.light-theme .nav-tabs { + border: none !important; +} + +/* Apply theme variables to main element */ +main { + color: var(--text-primary) !important; + /* padding: 1rem; */ + border-radius: 12px !important; + /* margin-top: 1rem; */ +} + +/* Theme Utility Classes */ +.theme-bg-primary { + background-color: var(--bg-primary) !important; + color: var(--text-primary) !important; + border-color: var(--border-color) !important; +} + +.theme-bg-secondary { + background-color: var(--bg-secondary); + color: var(--text-primary) !important; +} + +.theme-bg-accent { + background-color: var(--bg-accent); + color: var(--text-primary) !important; +} + +.theme-text-primary { + color: var(--text-primary) !important; +} + +.theme-text-secondary { + color: var(--text-secondary) !important; +} + +.theme-border { + border-color: var(--border-color) !important; + /* override bootstrap .border */ +} + +.border.theme-border, +.theme-border.border { + border-color: var(--border-color) !important; +} + +/* Global Font Utility Classes */ +.dark-font { + color: var(--text-primary) !important; +} + +.light-font { + color: var(--text-primary) !important; +} + +/* Organizations Table Styles */ +.ui-table .table { + background-color: var(--bg-primary) !important; + border-color: var(--border-color) !important; +} + +.ui-table .table thead th { + background-color: var(--bg-secondary) !important; + color: var(--text-primary) !important; + border-color: var(--border-color) !important; + font-weight: 600; +} + +.ui-table .table tbody td { + background-color: var(--bg-primary) !important; + color: var(--text-primary) !important; + border-color: var(--border-color) !important; +} + +.ui-table .table tbody tr:hover { + background-color: var(--bg-accent) !important; +} + +.ui-table .sortable-header:hover { + background-color: var(--bg-accent) !important; +} + +.ui-table .input-group-text { + background-color: var(--bg-secondary) !important; + border-color: var(--border-color) !important; +} + +.ui-table .form-control { + background-color: var(--bg-primary) !important; + color: var(--text-primary) !important; + border-color: var(--border-color) !important; +} + +.ui-table .form-control:focus { + background-color: var(--bg-primary); + color: var(--text-primary) !important; + border-color: var(--border-color) !important; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.ui-table .form-control::placeholder { + color: var(--text-secondary) !important; +} + +.list-group-item { + background-color: var(--bg-primary) !important; + color: var(--text-primary) !important; + border-color: var(--border-color) !important; +} + +.list-group-item:hover { + background-color: var(--bg-accent) !important; +} + +.text-muted { + color: var(--text-secondary) !important; +} + +span.text-muted { + color: var(--text-secondary) !important; +} + +code { + color: var(--text-primary) !important; +} + +element { + color: var(--text-primary) !important; + background-color: var(--bg-secondary) !important; + border: 1px solid var(--border-color) !important; + margin-left: 10px !important; + gap: 1.5rem !important; +} + +.input-group-text { + background-color: var(--bg-secondary) !important; + border: 1px solid var(--border-color) !important; + color: var(--text-primary) !important; +} + +.table { + border-radius: 12px !important; + background-color: var(--bg-primary) !important; + color: var(--text-primary) !important; + border: 1px solid var(--border-color) !important; +} + +/* Env vars table dark mode override */ +[data-theme="dark"] .env-vars table, +body.dark-theme .env-vars table { + background-color: var(--bg-primary) !important; +} + +[data-theme="dark"] .env-vars thead th, +body.dark-theme .env-vars thead th { + background-color: var(--bg-secondary) !important; +} + +th { + font-weight: 600; + background-color: var(--bg-secondary) !important; +} + +tr td { + background-color: var(--bg-primary) !important; + color: var(--text-primary) !important; + border-color: var(--border-color) !important; +} + +.log-error { + color: #c00 !important; +} + +.log-warn { + color: #b8860b !important; +} \ No newline at end of file diff --git a/ui/src/app/hooks/useClientSafe.js b/ui/src/app/hooks/useClientSafe.js new file mode 100644 index 00000000..fabb0a51 --- /dev/null +++ b/ui/src/app/hooks/useClientSafe.js @@ -0,0 +1,45 @@ +'use client'; + +import { useState, useEffect } from 'react'; + +/** + * Custom hook to handle client-side mounting + * Helps prevent hydration mismatches by ensuring client-specific code + * only runs after the component has mounted on the client + */ +export const useIsClient = () => { + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + setIsClient(true); + }, []); + + return isClient; +}; + +/** + * Custom hook for client-safe date formatting + * Returns a consistent format between server and client to prevent hydration issues + */ +export const useClientSafeDate = () => { + const isClient = useIsClient(); + + const formatDate = (dateString) => { + if (!isClient) { + // Server-side: return a simple format that matches potential client output + return new Date(dateString).toISOString().split('T')[0]; + } + + // Client-side: full formatting + const date = new Date(dateString); + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); + }; + + return { formatDate, isClient }; +}; diff --git a/ui/src/app/hooks/useHydrated.js b/ui/src/app/hooks/useHydrated.js new file mode 100644 index 00000000..7ab514a7 --- /dev/null +++ b/ui/src/app/hooks/useHydrated.js @@ -0,0 +1,18 @@ +'use client'; + +import { useState, useEffect } from 'react'; + +/** + * Hook that ensures consistent rendering between server and client + * Prevents hydration mismatches by showing a simple version first, + * then upgrading to the full version after hydration + */ +export const useHydrated = () => { + const [hydrated, setHydrated] = useState(false); + + useEffect(() => { + setHydrated(true); + }, []); + + return hydrated; +}; diff --git a/ui/src/app/layout.jsx b/ui/src/app/layout.jsx new file mode 100644 index 00000000..1104d257 --- /dev/null +++ b/ui/src/app/layout.jsx @@ -0,0 +1,41 @@ +import './globals.css'; +import { ThemeProvider } from './components/ThemeContext'; + +// (Optional) Next.js App Router metadata API – safe to add +export const metadata = { + title: 'Safe Settings', + description: 'Safe Settings dashboard', + icons: { + icon: [ + { url: '/favicon.svg', type: 'image/svg+xml' }, + { url: '/favicon.ico', sizes: 'any' } + ], + apple: '/apple-touch-icon.png', + shortcut: '/favicon.ico' + } +}; + +export default function RootLayout({ children }) { + return ( + + + {/* Existing Bootstrap CSS */} + + {/* Favicon / icons */} + + + {/* Optional apple-touch-icon (provide file or remove link) */} + {/* */} + + + + + {children} + + + + ); +} \ No newline at end of file diff --git a/ui/src/app/not-found.jsx b/ui/src/app/not-found.jsx new file mode 100644 index 00000000..da79f865 --- /dev/null +++ b/ui/src/app/not-found.jsx @@ -0,0 +1,15 @@ +"use client"; +import TitleBar from "./components/TitleBar"; + +export default function NotFound() { + return ( +
+ +
+

404

+

Sorry, the page you are looking for does not exist.

+ Go to Dashboard +
+
+ ); +}