diff --git a/.github/workflows/marketplace.yml b/.github/workflows/marketplace.yml new file mode 100644 index 000000000..ce54df48a --- /dev/null +++ b/.github/workflows/marketplace.yml @@ -0,0 +1,60 @@ +# Validates the plugin marketplace on every PR that touches it, and syncs +# marketplace/ to S3 via salmon on merge to main. +name: Marketplace + +on: + pull_request: + branches: + - main + paths: + - 'marketplace/**' + - 'tools/validate-marketplace.mjs' + - '.github/workflows/marketplace.yml' + push: + branches: + - main + paths: + - 'marketplace/**' + - 'tools/validate-marketplace.mjs' + - '.github/workflows/marketplace.yml' + +jobs: + # Schema + link validation for marketplace.json. + marketplace-validate: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Validate marketplace.json + run: npm run validate-marketplace + + # Publish to S3 only after a marketplace change lands on main. + marketplace-sync: + needs: marketplace-validate + if: github.event_name == 'push' + runs-on: ubuntu-24.04 + permissions: + id-token: write # Auth to AWS with OIDC + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Sync marketplace to S3 + uses: deephaven/salmon-sync@v1 + with: + source: marketplace/ + destination: deephaven/deephaven-plugins/marketplace/ + production: true + temporary: false + aws-role: ${{ vars.DOCS_AWS_ROLE }} diff --git a/marketplace/README.md b/marketplace/README.md new file mode 100644 index 000000000..fb24eb2c9 --- /dev/null +++ b/marketplace/README.md @@ -0,0 +1,48 @@ +# Plugin marketplace + +`marketplace.json` is the source of truth for the plugin directory shown at [deephaven.io/plugins](https://deephaven.io/plugins). On merge to `main` this folder is synced to S3 and the website reads it from there, so a merged PR is all it takes to list, update, or remove a plugin. + +## Add your plugin + +1. Add an entry to the `plugins` array in [`marketplace.json`](./marketplace.json). The fields are documented in [`marketplace.schema.json`](./marketplace.schema.json); editors that understand `$schema` will autocomplete and validate as you type. The required fields are `name`, `description`, `author`, `repo`, and `image`: + + ```json + { + "name": "My Plugin", + "description": "One sentence describing what it does, shown on the card.", + "author": "your-github-handle", + "repo": "https://github.com/your-github-handle/your-plugin", + "image": "/marketplace/images/my-plugin.png", + "registry": { + "kind": "pypi", + "package": "deephaven-plugin-my-plugin", + "url": "https://pypi.org/project/deephaven-plugin-my-plugin/" + }, + "tags": ["python"] + } + ``` + +2. Add a card image to [`images/`](./images) and reference it as `/marketplace/images/.png`. It must be a PNG committed to that folder (external URLs are not allowed); keep it under 300 KB and roughly 16:9 (600×338 works well). + +3. Validate locally, then open a PR: + + ```sh + npm run validate-marketplace + ``` + + CI runs the same check on every PR that touches this folder and blocks merge until it passes. Use `npm run validate-marketplace -- --skip-links` to skip the (slower) external link checks while iterating. + +## What the validator enforces + +Beyond the JSON schema, [`tools/validate-marketplace.mjs`](../tools/validate-marketplace.mjs) checks that: + +- plugin names are unique, +- the `repo` owner matches the `author` (the author drives the official badge), +- the `official` tag and `preInstalled` flag are reserved for Deephaven-owned plugins, +- each plugin's `image` is a PNG that exists directly in `images/` and is under 300 KB, +- no image in `images/` is orphaned (unreferenced by any plugin), and +- every link resolves: `repo`, `href`, `registry.url`, and the author's GitHub profile. + +## Reserved fields + +`author: "deephaven"` plugins get the official badge automatically, and only they may set `preInstalled: true`. Don't add the `official` tag by hand; it is derived from the author. diff --git a/marketplace/images/ag-grid.png b/marketplace/images/ag-grid.png new file mode 100644 index 000000000..88d2e9c2a Binary files /dev/null and b/marketplace/images/ag-grid.png differ diff --git a/marketplace/images/deephaven-ui.png b/marketplace/images/deephaven-ui.png new file mode 100644 index 000000000..8358c750c Binary files /dev/null and b/marketplace/images/deephaven-ui.png differ diff --git a/marketplace/images/matplotlib.png b/marketplace/images/matplotlib.png new file mode 100644 index 000000000..137af0480 Binary files /dev/null and b/marketplace/images/matplotlib.png differ diff --git a/marketplace/images/microphone.png b/marketplace/images/microphone.png new file mode 100644 index 000000000..397258333 Binary files /dev/null and b/marketplace/images/microphone.png differ diff --git a/marketplace/images/notebook.png b/marketplace/images/notebook.png new file mode 100644 index 000000000..054ab61aa Binary files /dev/null and b/marketplace/images/notebook.png differ diff --git a/marketplace/images/plotly-express.png b/marketplace/images/plotly-express.png new file mode 100644 index 000000000..49a7aeda6 Binary files /dev/null and b/marketplace/images/plotly-express.png differ diff --git a/marketplace/images/python-remote-file-source.png b/marketplace/images/python-remote-file-source.png new file mode 100644 index 000000000..2caf58104 Binary files /dev/null and b/marketplace/images/python-remote-file-source.png differ diff --git a/marketplace/images/theme-pack.png b/marketplace/images/theme-pack.png new file mode 100644 index 000000000..9ffa57dfe Binary files /dev/null and b/marketplace/images/theme-pack.png differ diff --git a/marketplace/images/tones.png b/marketplace/images/tones.png new file mode 100644 index 000000000..f818d1d9c Binary files /dev/null and b/marketplace/images/tones.png differ diff --git a/marketplace/images/voice-table.png b/marketplace/images/voice-table.png new file mode 100644 index 000000000..ec3c52157 Binary files /dev/null and b/marketplace/images/voice-table.png differ diff --git a/marketplace/marketplace.json b/marketplace/marketplace.json new file mode 100644 index 000000000..989167533 --- /dev/null +++ b/marketplace/marketplace.json @@ -0,0 +1,134 @@ +{ + "$schema": "./marketplace.schema.json", + "plugins": [ + { + "name": "deephaven.ui", + "description": "A reactive Python UI framework for building real-time data apps and dashboards entirely in Python.", + "author": "Deephaven", + "repo": "https://github.com/deephaven/deephaven-plugins/tree/main/plugins/ui", + "href": "/core/ui/docs", + "image": "/marketplace/images/deephaven-ui.png", + "registry": { + "kind": "pypi", + "package": "deephaven-plugin-ui", + "url": "https://pypi.org/project/deephaven-plugin-ui/" + }, + "tags": ["ui", "dashboards", "python"], + "preInstalled": true + }, + { + "name": "Plotly Express", + "description": "A Plotly Express wrapper that plots real-time, ticking Deephaven tables with automatic downsampling.", + "author": "Deephaven", + "repo": "https://github.com/deephaven/deephaven-plugins/tree/main/plugins/plotly-express", + "href": "/core/plotly/docs", + "image": "/marketplace/images/plotly-express.png", + "registry": { + "kind": "pypi", + "package": "deephaven-plugin-plotly-express", + "url": "https://pypi.org/project/deephaven-plugin-plotly-express/" + }, + "tags": ["plotting", "python"], + "preInstalled": true + }, + { + "name": "Matplotlib", + "description": "Display Matplotlib and Seaborn figures in Deephaven, with support for live updates from ticking tables.", + "author": "Deephaven", + "repo": "https://github.com/deephaven/deephaven-plugin-matplotlib", + "image": "/marketplace/images/matplotlib.png", + "registry": { + "kind": "pypi", + "package": "deephaven-plugin-matplotlib", + "url": "https://pypi.org/project/deephaven-plugin-matplotlib/" + }, + "tags": ["plotting", "python"] + }, + { + "name": "AG Grid", + "description": "Display and interact with Deephaven tables using AG Grid.", + "author": "Deephaven", + "repo": "https://github.com/deephaven/deephaven-plugins/tree/main/plugins/ag-grid", + "image": "/marketplace/images/ag-grid.png", + "tags": ["tables", "ui", "python"] + }, + { + "name": "Theme Pack", + "description": "A pack of additional UI themes for customizing the look of the Deephaven web IDE.", + "author": "Deephaven", + "repo": "https://github.com/deephaven/deephaven-plugins/tree/main/plugins/theme-pack", + "image": "/marketplace/images/theme-pack.png", + "registry": { + "kind": "pypi", + "package": "deephaven-plugin-theme-pack", + "url": "https://pypi.org/project/deephaven-plugin-theme-pack/" + }, + "tags": ["themes"] + }, + { + "name": "Python Remote File Source", + "description": "A bi-directional plugin that allows sourcing Python imports from a remote file source.", + "author": "Deephaven", + "repo": "https://github.com/deephaven/deephaven-plugins/tree/main/plugins/python-remote-file-source", + "image": "/marketplace/images/python-remote-file-source.png", + "registry": { + "kind": "pypi", + "package": "deephaven-plugin-python-remote-file-source", + "url": "https://pypi.org/project/deephaven-plugin-python-remote-file-source/" + }, + "tags": ["tooling", "python"] + }, + { + "name": "Tones", + "description": "A deephaven.ui plugin that plays audio tones from events or table data by wrapping Tone.js in Python components.", + "author": "dsmmcken", + "repo": "https://github.com/dsmmcken/deephaven-plugin-tones", + "image": "/marketplace/images/tones.png", + "registry": { + "kind": "pypi", + "package": "deephaven-plugin-tones", + "url": "https://pypi.org/project/deephaven-plugin-tones/" + }, + "tags": ["audio", "ui", "python"] + }, + { + "name": "Microphone", + "description": "A plugin for using microphone input in Deephaven.", + "author": "mofojed", + "repo": "https://github.com/mofojed/deephaven-plugin-microphone", + "image": "/marketplace/images/microphone.png", + "registry": { + "kind": "pypi", + "package": "deephaven-plugin-microphone", + "url": "https://pypi.org/project/deephaven-plugin-microphone/" + }, + "tags": ["audio", "python"] + }, + { + "name": "Voice Table", + "description": "A deephaven.ui plugin for controlling a table using voice input.", + "author": "mofojed", + "repo": "https://github.com/mofojed/deephaven-plugin-voice-table", + "image": "/marketplace/images/voice-table.png", + "registry": { + "kind": "pypi", + "package": "deephaven-plugin-voice-table", + "url": "https://pypi.org/project/deephaven-plugin-voice-table/" + }, + "tags": ["audio", "tables", "python"] + }, + { + "name": "Notebook", + "description": "An experimental plugin that creates and embeds simple Jupyter notebooks within Deephaven.", + "author": "jnumainville", + "repo": "https://github.com/jnumainville/deephaven-plugin-notebook", + "image": "/marketplace/images/notebook.png", + "registry": { + "kind": "pypi", + "package": "deephaven-plugin-notebook", + "url": "https://pypi.org/project/deephaven-plugin-notebook/" + }, + "tags": ["notebooks", "experimental", "python"] + } + ] +} diff --git a/marketplace/marketplace.schema.json b/marketplace/marketplace.schema.json new file mode 100644 index 000000000..f6f74ba42 --- /dev/null +++ b/marketplace/marketplace.schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://deephaven.io/marketplace.schema.json", + "title": "Deephaven Plugin Directory", + "description": "Defines the list of plugins shown on deephaven.io/plugins. Open a PR against this file to add your plugin.", + "type": "object", + "required": ["plugins"], + "additionalProperties": false, + "properties": { + "$schema": { + "type": "string" + }, + "plugins": { + "type": "array", + "items": { "$ref": "#/$defs/plugin" } + } + }, + "$defs": { + "plugin": { + "type": "object", + "required": ["name", "description", "author", "repo", "image"], + "additionalProperties": false, + "properties": { + "name": { + "description": "Display name of the plugin. Don't prefix with \"Deephaven\", that's implied.", + "type": "string", + "minLength": 1, + "maxLength": 64 + }, + "description": { + "description": "Short description shown on the plugin card", + "type": "string", + "minLength": 1, + "maxLength": 280 + }, + "author": { + "description": "GitHub user or org that owns the plugin's repo", + "type": "string", + "pattern": "^[a-zA-Z0-9]([a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?$" + }, + "repo": { + "description": "GitHub repository URL (a path within a repo is allowed for monorepos)", + "type": "string", + "pattern": "^https://github\\.com/[^/]+/[^/]+" + }, + "href": { + "description": "Where the card links to: an internal docs path (starts with /) or an external https URL. Defaults to the repo URL.", + "type": "string", + "pattern": "^(/|https://)" + }, + "image": { + "description": "Card image: a PNG committed to marketplace/images/ in this repo, referenced as /marketplace/images/.png", + "type": "string", + "pattern": "^/marketplace/images/[^/]+\\.png$" + }, + "registry": { + "description": "Optional package registry listing where the plugin is published", + "type": "object", + "required": ["kind", "url"], + "additionalProperties": false, + "properties": { + "kind": { + "description": "Which registry the package is published to", + "type": "string", + "enum": ["pypi", "npm", "maven", "conda", "gradle", "other"] + }, + "package": { + "description": "Package name on the registry", + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "url": { + "description": "URL of the package listing", + "type": "string", + "pattern": "^https://" + } + } + }, + "minDeephavenVersion": { + "description": "Minimum Deephaven version the plugin is known to work with, e.g. 0.36.0", + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+(\\.[0-9]+)?$" + }, + "preInstalled": { + "description": "Whether the plugin ships pre-installed with Deephaven. Reserved for Deephaven-owned plugins; enforced by the validator.", + "type": "boolean" + }, + "tags": { + "description": "Tags used for filtering in the directory. \"official\" is reserved; it is derived automatically from the author.", + "type": "array", + "maxItems": 5, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "maxLength": 24 + } + } + } + } + } +} diff --git a/package-lock.json b/package-lock.json index b0c159e92..46d5acfd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "@vitejs/plugin-react-swc": "^3.7.0", + "ajv": "^8.20.0", "conventional-changelog-conventionalcommits": "^7.0.0", "eslint": "^8.37.0", "identity-obj-proxy": "^3.0.0", @@ -230,7 +231,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -876,7 +876,6 @@ "integrity": "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, @@ -1802,7 +1801,6 @@ "integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-module-imports": "^7.28.6", @@ -2313,6 +2311,7 @@ "integrity": "sha512-FvEb29x5wVwu/Kf93IWwsOOEuhHh6dYCJF3vcKLzXc0KXIW181AOzv6ceT4ZpBHDvAfG60eqb+ekmrnLHIy+jw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cacheable/utils": "^2.4.0", "@keyv/bigmap": "^1.3.1", @@ -2326,6 +2325,7 @@ "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "hashery": "^1.4.0", "hookified": "^1.15.0" @@ -2354,6 +2354,7 @@ "integrity": "sha512-eiFgzCbIneyMlLOmNG4g9xzF7Hv3Mga4LjxjcSC/ues6VYq2+gUbQI8JqNuw/ZM8tJIeIaBGpswAsqV2V7ApgA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "hashery": "^1.5.1", "keyv": "^5.6.0" @@ -2365,6 +2366,7 @@ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -2397,6 +2399,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -2445,6 +2448,7 @@ } ], "license": "MIT-0", + "peer": true, "peerDependencies": { "css-tree": "^3.2.1" }, @@ -2491,6 +2495,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -2515,6 +2520,7 @@ } ], "license": "MIT-0", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -2538,6 +2544,7 @@ } ], "license": "MIT-0", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -3469,7 +3476,6 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", - "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -3972,6 +3978,30 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "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/@eslint/eslintrc/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/@eslint/js": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", @@ -4059,7 +4089,6 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", "license": "MIT", - "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" }, @@ -4073,7 +4102,6 @@ "integrity": "sha512-mtBFIi1UsYQo7rYonYFkjgYKGoL8T+fEH6NGUpvuqtY3ytMsAoDaPo5rk25KuMtKDipY4bGYM/CkmCHA1N3FUg==", "deprecated": "v0.2.x is no longer supported. Unless you are still using FontAwesome 5, please update to v3.1.1 or greater.", "license": "MIT", - "peer": true, "dependencies": { "prop-types": "^15.8.1" }, @@ -4136,8 +4164,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", @@ -5221,7 +5248,8 @@ "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@mapbox/geojson-rewind": { "version": "0.5.2", @@ -6391,7 +6419,6 @@ "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -6880,6 +6907,7 @@ "integrity": "sha512-fdDH1LSGfZdTH2sxdpVMw31BanV28K/Gry0cVFxaNP77neJSkd82mM8ErPNYs9e+0O7SdHBLTDzDgwUuy18RnQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -7109,7 +7137,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -7180,7 +7207,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -7251,7 +7277,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -7321,7 +7346,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -7391,7 +7415,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -7462,7 +7485,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -7532,7 +7554,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -7602,7 +7623,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -7672,7 +7692,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -7742,7 +7761,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -7813,7 +7831,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -7884,7 +7901,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -7954,7 +7970,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8024,7 +8039,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8094,7 +8108,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8164,7 +8177,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8236,7 +8248,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8306,7 +8317,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8390,7 +8400,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8460,7 +8469,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8530,7 +8538,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8600,7 +8607,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8670,7 +8676,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8740,7 +8745,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8811,7 +8815,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8881,7 +8884,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -8952,7 +8954,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9023,7 +9024,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9094,7 +9094,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9164,7 +9163,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9234,7 +9232,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9304,7 +9301,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9375,7 +9371,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9445,7 +9440,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9501,7 +9495,6 @@ "resolved": "https://registry.npmjs.org/@react-spectrum/provider/-/provider-3.11.0.tgz", "integrity": "sha512-W2Gxbj8AcG5OR2K5Ua3K8qQqxdsiytEiz+2rhr6oQyBM8VafEgDcNPYSOTtfjrQM3snl2Uhp8LzwN0jwQe/6nQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@adobe/react-spectrum": "3.47.0", "@swc/helpers": "^0.5.0" @@ -9516,7 +9509,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9586,7 +9578,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9656,7 +9647,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9726,7 +9716,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9796,7 +9785,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9866,7 +9854,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -9937,7 +9924,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -10008,7 +9994,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -10079,7 +10064,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -10149,7 +10133,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -10219,7 +10202,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -10289,7 +10271,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -10359,7 +10340,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -10429,7 +10409,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -10499,7 +10478,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -10569,7 +10547,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -10640,7 +10617,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -10710,7 +10686,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -10780,7 +10755,6 @@ "resolved": "https://registry.npmjs.org/@adobe/react-spectrum/-/react-spectrum-3.47.0.tgz", "integrity": "sha512-EDQuMzz0kUeiMUUlxoeLFQyyxOXaAC7qlBw2PYOUfFLYd87xcV7VVV0JxiYx8zGk1IIY3UgQHgXrS1fv7CgezQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@internationalized/date": "^3.12.1", "@react-types/shared": "^3.34.0", @@ -11464,6 +11438,7 @@ "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -12178,7 +12153,8 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -12490,7 +12466,6 @@ "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -12724,7 +12699,6 @@ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -13023,7 +12997,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -13179,16 +13152,16 @@ } }, "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "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" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -13539,6 +13512,7 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" } @@ -14115,7 +14089,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -14328,6 +14301,7 @@ "integrity": "sha512-djgxybDbw9fL/ZWMI3+CE8ZilNxcwFkVtDc1gJ+IlOSSWkSMPQabhV/XCHTQ6pwwN6aivXPZ43omTooZiX06Ew==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cacheable/memory": "^2.0.8", "@cacheable/utils": "^2.4.0", @@ -14342,6 +14316,7 @@ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@keyv/serialize": "^1.1.1" } @@ -14820,7 +14795,8 @@ "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/columnify": { "version": "1.6.0", @@ -15257,6 +15233,7 @@ "integrity": "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" } @@ -16152,6 +16129,7 @@ "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" @@ -16951,7 +16929,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -17050,7 +17027,6 @@ "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -17139,6 +17115,7 @@ "integrity": "sha512-DEfpfuk+O/T5e9HBZOxocmwMuUGkvQQd5WRiMJF9kKNT9amByqOyGlWoAZAQiv0SZSy4GMtG1clmnvQA/RzA0A==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "debug": "^4.3.4", "enhanced-resolve": "^5.10.0", @@ -17165,6 +17142,7 @@ "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "dir-glob": "^3.0.1", "fast-glob": "^3.3.0", @@ -17185,6 +17163,7 @@ "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -17266,7 +17245,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -17349,7 +17327,6 @@ "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", @@ -17390,6 +17367,7 @@ "integrity": "sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "prettier-linter-helpers": "^1.0.1", "synckit": "^0.11.12" @@ -17421,6 +17399,7 @@ "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -17434,6 +17413,7 @@ "integrity": "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@pkgr/core": "^0.2.9" }, @@ -17450,7 +17430,6 @@ "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -17484,7 +17463,6 @@ "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -17587,6 +17565,7 @@ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "eslint-visitor-keys": "^1.1.0" }, @@ -17603,6 +17582,7 @@ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=4" } @@ -17620,6 +17600,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "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/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -17654,6 +17651,13 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/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/esm": { "version": "3.2.25", "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", @@ -17899,7 +17903,8 @@ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true, - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/fast-glob": { "version": "3.3.3", @@ -17983,6 +17988,7 @@ "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4.9.1" } @@ -18441,6 +18447,7 @@ "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -18642,6 +18649,7 @@ "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" }, @@ -18837,6 +18845,7 @@ "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "global-prefix": "^3.0.0" }, @@ -18850,6 +18859,7 @@ "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ini": "^1.3.5", "kind-of": "^6.0.2", @@ -18865,6 +18875,7 @@ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "isexe": "^2.0.0" }, @@ -18931,7 +18942,8 @@ "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/glsl-inject-defines": { "version": "1.0.3", @@ -19373,6 +19385,7 @@ "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "hookified": "^1.15.0" }, @@ -19496,7 +19509,8 @@ "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/hosted-git-info": { "version": "9.0.2", @@ -19546,6 +19560,7 @@ "integrity": "sha512-n6l5uca7/y5joxZ3LUePhzmBFUJ+U2YWzhMa8XUTecSeSlQiZdF5XAd/Q3/WUl0VsXgUwWi8I7CNIwdI5WN1SQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=20.10" }, @@ -19765,6 +19780,7 @@ "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -20328,6 +20344,7 @@ "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -20693,7 +20710,6 @@ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -22114,8 +22130,7 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/js-cookie": { "version": "3.0.5", @@ -22240,9 +22255,9 @@ "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==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, @@ -23065,7 +23080,8 @@ "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -23148,6 +23164,7 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -23464,6 +23481,7 @@ "integrity": "sha512-aa6AU2Pcx0VP/XWnh8IGL0SYSgQHDT6Ucror2j2mXeFAlN3ahaNs8EZtG1YiticMkSLj3Gt6VPFfZogt7G5iFQ==", "dev": true, "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -23717,7 +23735,8 @@ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", "dev": true, - "license": "CC0-1.0" + "license": "CC0-1.0", + "peer": true }, "node_modules/memoize-one": { "version": "5.2.1", @@ -25618,7 +25637,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -26865,7 +26883,6 @@ "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-3.5.0.tgz", "integrity": "sha512-a3AYQIMG7OdZmrJ/fJ65HSt3g1l5qDeludKqjjafU1dh5E+fwqDhsEBndW7VCYwjlducCfN6KtPdWdiWFcoBWw==", "license": "MIT", - "peer": true, "dependencies": { "@plotly/d3": "3.8.2", "@plotly/d3-sankey": "0.7.2", @@ -26946,7 +26963,6 @@ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -26981,7 +26997,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -27011,6 +27026,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18.0" }, @@ -27024,7 +27040,6 @@ "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -27038,7 +27053,8 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/potpack": { "version": "1.0.2", @@ -27062,7 +27078,6 @@ "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -27079,6 +27094,7 @@ "integrity": "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-diff": "^1.1.2" }, @@ -27092,6 +27108,7 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -27107,6 +27124,7 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -27321,6 +27339,7 @@ "integrity": "sha512-n7mar4T0xQ+39dE2vGTAlbxUEpndwPANH0kDef1/MYsB8Bba9wshkybIRx74qgcvKQPEWErf9AqAdYjhzY2Ilg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "hookified": "^2.1.1" }, @@ -27333,7 +27352,8 @@ "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.1.1.tgz", "integrity": "sha512-AHb76R16GB5EsPBE2J7Ko5kiEyXwviB9P5SMrAKcuAu4vJPZttViAbj9+tZeaQE5zjDme+1vcHP78Yj/WoAveA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/querystringify": { "version": "2.2.0", @@ -27398,7 +27418,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -27450,7 +27469,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -27463,8 +27481,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-markdown": { "version": "8.0.7", @@ -27521,7 +27538,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.15.4", "@types/react-redux": "^7.1.20", @@ -27881,7 +27897,6 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.9.2" } @@ -27964,6 +27979,7 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=8" }, @@ -28261,6 +28277,7 @@ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } @@ -28727,7 +28744,6 @@ "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.1.5", @@ -29003,6 +29019,7 @@ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -29589,6 +29606,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", @@ -29640,6 +29658,7 @@ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -29653,6 +29672,7 @@ "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", @@ -29680,6 +29700,7 @@ "integrity": "sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "flat-cache": "^6.1.20" } @@ -29690,6 +29711,7 @@ "integrity": "sha512-N2dnzVJIphnNsjHcrxGW7DePckJ6haPrSFqpsBUhHYgwtKGVq4JrBGielEGD2fCVnsGm1zlBVZ8wGhkyuetgug==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cacheable": "^2.3.4", "flatted": "^3.4.2", @@ -29702,6 +29724,7 @@ "integrity": "sha512-QrJia2qDf5BB/V6HYlDTs0I0lBahyjLzpGQg3KT7FnCdTonAyPy2RtY802m2k4ALx6Dp752f82WsOczEVr3l6Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "fast-glob": "^3.3.3", @@ -29723,6 +29746,7 @@ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 4" } @@ -29733,6 +29757,7 @@ "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -29746,6 +29771,7 @@ "integrity": "sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=20" }, @@ -29759,6 +29785,7 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, "license": "ISC", + "peer": true, "engines": { "node": ">=14" }, @@ -29772,6 +29799,7 @@ "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=14.16" }, @@ -29785,6 +29813,7 @@ "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" @@ -29802,6 +29831,7 @@ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ansi-regex": "^6.2.2" }, @@ -29818,6 +29848,7 @@ "integrity": "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==", "dev": true, "license": "ISC", + "peer": true, "dependencies": { "signal-exit": "^4.0.1" }, @@ -29865,6 +29896,7 @@ "integrity": "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^5.0.1", "supports-color": "^10.2.2" @@ -29882,6 +29914,7 @@ "integrity": "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -29895,6 +29928,7 @@ "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -29958,7 +29992,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/symbol-tree": { "version": "3.2.4", @@ -29972,6 +30007,7 @@ "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@pkgr/core": "^0.1.0", "tslib": "^2.6.2" @@ -29989,6 +30025,7 @@ "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -30006,6 +30043,7 @@ "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -30017,19 +30055,13 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/table/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/tapable": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6" }, @@ -30240,7 +30272,6 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -30621,7 +30652,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -30720,6 +30750,7 @@ "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=20" }, @@ -31055,7 +31086,6 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/package.json b/package.json index 134920248..0d15b4b9e 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "e2e:update-snapshots": "./tools/run_docker.sh ./tests/docker-compose.yml update-snapshots", "update-dh-packages": "lerna run --concurrency 1 update-dh-packages", "update-dh-packages:ui": "npm run update-dh-packages -- --scope=@deephaven/js-plugin-ui --", + "validate-marketplace": "node tools/validate-marketplace.mjs", "update-doc-snapshots": "./tools/run_docker.sh ./docker-compose.docs-snapshots.yml deephaven-plugins-docs-snapshotter", "validate-doc-snapshots": "./tools/run_docker.sh ./docker-compose.docs-snapshots.yml deephaven-plugins-docs-validator" }, @@ -45,6 +46,7 @@ "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "@vitejs/plugin-react-swc": "^3.7.0", + "ajv": "^8.20.0", "conventional-changelog-conventionalcommits": "^7.0.0", "eslint": "^8.37.0", "identity-obj-proxy": "^3.0.0", diff --git a/tools/validate-marketplace.mjs b/tools/validate-marketplace.mjs new file mode 100644 index 000000000..0bc278183 --- /dev/null +++ b/tools/validate-marketplace.mjs @@ -0,0 +1,221 @@ +/** + * Validates marketplace/marketplace.json against marketplace/marketplace.schema.json, + * plus checks the schema can't express: unique names, repo owner matches author, + * each plugin's image being a PNG that exists in marketplace/images/ and is + * under the size limit, and that every link resolves (repo, href, registry, and + * the author's GitHub profile). Internal hrefs are checked against the live site. + * + * Usage: npm run validate-marketplace [-- --skip-links] + * Env: MARKETPLACE_LINK_BASE overrides the base URL for internal hrefs + * (default https://deephaven.io) + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { Ajv2020 } from 'ajv/dist/2020.js'; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const marketplaceDir = path.join(root, 'marketplace'); + +const IMAGE_EXTENSIONS = ['.png']; +const MAX_IMAGE_BYTES = 300 * 1024; + +const schema = JSON.parse( + fs.readFileSync(path.join(marketplaceDir, 'marketplace.schema.json'), 'utf-8') +); +const marketplace = JSON.parse( + fs.readFileSync(path.join(marketplaceDir, 'marketplace.json'), 'utf-8') +); + +const errors = []; + +const ajv = new Ajv2020({ allErrors: true }); +const validate = ajv.compile(schema); +if (!validate(marketplace)) { + validate.errors?.forEach(e => { + errors.push(`Schema error: ${e.instancePath || '/'} ${e.message}`); + }); +} + +const names = new Set(); +// local image files referenced by a plugin, so we can flag orphans afterward +const referencedImages = new Set(); +for (const plugin of marketplace.plugins ?? []) { + const label = plugin.name ?? '(unnamed)'; + + const key = String(plugin.name ?? '').toLowerCase(); + if (names.has(key)) { + errors.push(`Duplicate plugin name: ${label}`); + } + names.add(key); + + // the repo must belong to the listed author, since the author field + // determines the official @deephaven badge + const owner = String(plugin.repo ?? '').match( + /^https:\/\/github\.com\/([^/]+)/ + )?.[1]; + if ( + owner && + plugin.author && + owner.toLowerCase() !== plugin.author.toLowerCase() + ) { + errors.push( + `${label}: repo owner "${owner}" does not match author "${plugin.author}"` + ); + } + + // the directory derives the official tag from the author; it can't be + // self-assigned + if ((plugin.tags ?? []).some(t => String(t).toLowerCase() === 'official')) { + errors.push( + `${label}: the "official" tag is reserved and applied automatically to Deephaven-owned plugins` + ); + } + + // the "pre-installed" badge is reserved for Deephaven-owned plugins + if (plugin.preInstalled && plugin.author?.toLowerCase() !== 'deephaven') { + errors.push( + `${label}: "preInstalled" is reserved for Deephaven-owned plugins (author must be "deephaven")` + ); + } + + // every plugin must ship a card image, and it must be a local PNG file in + // marketplace/images/ (no external URLs) + if (!plugin.image) { + errors.push(`${label}: missing required image`); + } else if (!plugin.image.startsWith('/marketplace/images/')) { + errors.push( + `${label}: image must be a local PNG in marketplace/images/, referenced as /marketplace/images/.png` + ); + } else { + const file = plugin.image.slice('/marketplace/images/'.length); + const imagePath = path.join(marketplaceDir, 'images', file); + referencedImages.add(file); + if (file.includes('/') || file.includes('..')) { + errors.push(`${label}: image must be a file directly in marketplace/images/`); + } else if (path.extname(file).toLowerCase() !== '.png') { + errors.push(`${label}: image must be a .png file`); + } else if (!fs.existsSync(imagePath)) { + errors.push(`${label}: image not found at marketplace/images/${file}`); + } else { + const { size } = fs.statSync(imagePath); + if (size > MAX_IMAGE_BYTES) { + errors.push( + `${label}: image is ${Math.round(size / 1024)} KB, max is ${MAX_IMAGE_BYTES / 1024} KB` + ); + } + } + } +} + +// no orphan images: every file in marketplace/images/ must be referenced by a +// plugin (README.md and other non-image docs are ignored) +const imagesDir = path.join(marketplaceDir, 'images'); +if (fs.existsSync(imagesDir)) { + for (const file of fs.readdirSync(imagesDir)) { + if (!IMAGE_EXTENSIONS.includes(path.extname(file).toLowerCase())) { + continue; + } + if (!referencedImages.has(file)) { + errors.push( + `Orphan image marketplace/images/${file}: not referenced by any plugin` + ); + } + } +} + +// --- link validation --- + +const LINK_BASE = process.env.MARKETPLACE_LINK_BASE || 'https://deephaven.io'; +// 402 is accepted to match the site's link validator (see #111) +const ACCEPTED_STATUSES = [402]; + +async function fetchStatus(url, method) { + const res = await fetch(url, { + method, + redirect: 'follow', + signal: AbortSignal.timeout(15_000), + headers: { 'User-Agent': 'deephaven-marketplace-validator' }, + }); + return res; +} + +/** Returns an error string for a broken url, or null if it resolves. */ +async function checkUrl(url) { + for (let attempt = 0; attempt < 2; attempt++) { + try { + let res = await fetchStatus(url, 'HEAD'); + if (!res.ok && !ACCEPTED_STATUSES.includes(res.status)) { + // some servers reject HEAD; confirm with GET before failing + res = await fetchStatus(url, 'GET'); + } + if (res.ok || ACCEPTED_STATUSES.includes(res.status)) { + return null; + } + if (attempt === 0) { + continue; // CDNs occasionally throw transient errors, retry once + } + return `returned ${res.status}`; + } catch (e) { + if (attempt === 0) { + continue; + } + return `failed (${e.cause?.code ?? e.name})`; + } + } + return 'failed'; +} + +async function checkLinks() { + // url -> labels of plugins referencing it, deduped so shared urls + // (e.g. the author profile on monorepo plugins) are fetched once + const links = new Map(); + const addLink = (url, label) => { + if (!links.has(url)) { + links.set(url, []); + } + links.get(url).push(label); + }; + + for (const plugin of marketplace.plugins ?? []) { + const label = plugin.name ?? '(unnamed)'; + if (plugin.repo) { + addLink(plugin.repo, label); + } + if (plugin.author) { + addLink(`https://github.com/${plugin.author}`, label); + } + if (plugin.href) { + addLink( + plugin.href.startsWith('/') ? `${LINK_BASE}${plugin.href}` : plugin.href, + label + ); + } + if (plugin.registry?.url) { + addLink(plugin.registry.url, label); + } + } + + console.log(`Checking ${links.size} links...`); + const results = await Promise.all( + [...links.entries()].map(async ([url, labels]) => { + const error = await checkUrl(url); + return error ? `${labels.join(', ')}: ${url} ${error}` : null; + }) + ); + return results.filter(Boolean); +} + +if (!process.argv.includes('--skip-links')) { + errors.push(...(await checkLinks())); +} + +if (errors.length > 0) { + console.error(`marketplace.json is invalid:`); + errors.forEach(e => console.error(` - ${e}`)); + process.exit(1); +} + +console.log( + `marketplace.json is valid (${marketplace.plugins.length} plugins)` +);