diff --git a/.gitignore b/.gitignore index 7749b6e..2787428 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,13 @@ node_modules +.pnpm-store .DS_Store # dev folders stylesheets components .raisely.json +.idea + +# allow test fixtures even when their folder names match the dev ignores above +!tests/ +!tests/** diff --git a/CHANGELOG.md b/CHANGELOG.md index 57c4d92..bb3283a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,11 @@ +# 2.0.0 +- Breaking layout change: per-campaign content now lives in `campaigns//`, the entry SCSS file is now `main.scss`, and shared components remain at the repo root. +- Added `raisely migrate` to upgrade existing repositories from the v1 layout to the v2 layout. +- Added SCSS and component validation for `raisely deploy` (with `--no-validate`) and per-save validation for `raisely start`. +- Improved `raisely local` resilience by serving the last successful CSS when compilation fails, removing the 5-second retry loop, and removing process exit on transpiler 401 responses. +- Removed v1 compatibility read paths; v2 commands now require the v2 layout. + # 1.8.3 - Fix redirects for `raisely local` diff --git a/README.md b/README.md index f9617fd..ae8523b 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ ![Raisely logo](https://raisely-images.imgix.net/www/uploads/lockup-default-svg-d0b31f.svg?fit=max&w=300&auto=format&q=62) -The Raisely CLI is used to power local development of Raisely themes, syncing custom components and campaign styles to your local machine. +The Raisely CLI is used to power local development of Raisely themes, syncing custom components, campaign styles, and campaign pages to your local machine. -For more about Raisely, see +For more about Raisely, see [https://raisely.com](https://raisely.com) ## Overview -The Raisely CLI allows for fast and easy development on the Raisely platform. The CLI allows you to connect a directory on your local computer to a Raisely account. With the CLI you can update campaign stylesheets, and edit and create custom React components. +The Raisely CLI allows for fast and easy development on the Raisely platform. The CLI allows you to connect a directory on your local computer to a Raisely account. With the CLI you can update campaign stylesheets, edit and create custom React components, and edit page layout JSON under `campaigns//pages/`. The CLI is built on Node.js, so you'll need Node.js installed to use it. @@ -25,14 +25,74 @@ For other issues, [submit a support ticket](mailto:support@raisely.com). 1. Install the CLI globally: `npm install @raisely/cli -g` 2. Go into your working directory and run: `raisely init` +`raisely init` writes the v2 project layout: + +```text +campaigns/ + / + pages/ + home.json + stylesheets/ + main.scss +components/ + CustomHeader.js +``` + ## Commands -- `raisely init` - start a new Raisely project, authenticate and sync your campaigns -- `raisely update` - update local copies of styles and components from the API -- `raisely create [name]` - create a new custom component, optionally add the component name to the command (otherwise you will be asked for one) -- `raisely start` - starts watching for and uploading changes to styles and components -- `raisely deploy` - deploy your local code to Raisely -- `raisely local` - work locally on a Raisely campaign without syncing changes up +- `raisely init` - start a new Raisely project and sync your campaigns +- `raisely init --uuid ` - initialize directly against a specific campaign, skipping the picker +- `raisely list` - list every campaign (Name, Uuid), sorted A-Z; prints a padded table in a terminal, TSV when stdout is piped (`raisely list | cut -f2`), and `--json` / `--tsv` force a format +- `raisely login` - sign in with OAuth (opens your browser); stores access and refresh tokens in the OS keychain +- `raisely logout` - revoke the current access token when possible and clear keychain storage for this org +- `raisely update` - update local copies of styles, components, and pages from the API +- `raisely update --force` - same as above without the confirmation prompt (for CI/scripts) +- `raisely migrate` - migrate an existing repo from the legacy layout to the v2 layout +- `raisely create [name]` - create a new custom component, optionally add the component name to the command (otherwise you will be asked for one) +- `raisely start` - starts watching for and uploading validated changes to styles and components +- `raisely deploy` - deploy your local code to Raisely (styles, components, and pages) +- `raisely deploy --no-validate` - deploy without running pre-flight validation first +- `raisely local` - work locally on a Raisely campaign without syncing changes up (includes local page JSON overrides from `campaigns//pages/` when present) +- `raisely local --uuid ` - open a specific campaign by UUID, skipping the picker +- `raisely local --port ` - run the local development server on a custom port instead of `8015` +- `raisely media list` - list all media assets for a campaign or organisation; falls back to the first campaign in `.raisely.json` when no flag is passed +- `raisely media list --campaign ` - list media for a specific campaign by path/slug +- `raisely media list --organisation ` - list media for an organisation by UUID +- `raisely media list --json` - output the media array as JSON instead of a table +- `raisely media delete ` - delete a media asset by UUID (no confirmation prompt) +- `raisely media upload ` - upload a local file or a public URL as a media asset; falls back to the first campaign in `.raisely.json` when no `--campaign` is passed +- `raisely media upload --campaign ` - upload to a specific campaign +- `raisely media upload --organisation ` - upload to an organisation +- `raisely media upload --force` - skip the confirmation prompt (required for non-interactive/agent use) +- `raisely media upload --json` - skip the confirmation prompt and output the result as JSON + +### Custom public host (`raisely local`) + +By default, `raisely local` proxies to `https://{campaign.path}.raisely.com`. If the site people visit is on another host (for example `https://{campaign.path}.raiselysite.com`), add `**proxyUrl**` to `.raisely.json` with the bare domain (no campaign subdomain): + +```json +{ + "proxyUrl": "https://raiselysite.com" +} +``` + +The CLI turns that into `https://{campaign.path}.raiselysite.com` so the proxy matches production. + +## Authentication + +Interactive use relies on **OAuth 2.0 with PKCE** against `https://api.raisely.com/v1/oauth/authorize` and `/v1/oauth/token` (or your `RAISELY_API_URL` host for staging, for example `https://api.raisely.io`). + +1. Register a **NATIVE** app in Raisely admin (Settings → Apps), add loopback redirect URIs such as `http://127.0.0.1:8765/callback` (and 8766, 8767), and note the app `client_id` (UUID). +2. Set `**RAISELY_OAUTH_CLIENT_ID`\*\* to that UUID before running `raisely login` or `raisely init`. The CLI ships with a placeholder `client_id` until you replace it in the package. +3. Optional: `**RAISELY_OAUTH_SCOPES**` overrides the default scopes (`campaigns:read campaigns:update pages:read`). + +Tokens are stored in the OS keychain under service `@raisely/cli`, with account name `{api_host}:{organisation_uuid}` (for example `api.raisely.com:aaaaaaaa-...`). The file `~/.raisely/session.json` records the last successful login’s host and organisation so commands work before `.raisely.json` exists. + +**Credential resolution order** for API calls: + +1. `RAISELY_TOKEN` (personal access token / campaign key), if set +2. Keychain session for the current API host + `organisationUuid` (from `.raisely.json` or the session pointer) +3. Legacy `token` field in `.raisely.json` ## CI/CD Usage @@ -40,12 +100,15 @@ Raisely CLI supports usage in a CI/CD environment for auto-deployment of styles Raisely CLI supports the following environment variables: -- `RAISELY_TOKEN` – your API secret key -- `RAISELY_CAMPAIGNS` - a comma-separated list of campaign uuids to sync (so you can be selective) +- `RAISELY_TOKEN` – your API secret key (overrides keychain and `.raisely.json` token) +- `RAISELY_CAMPAIGNS` - a comma-separated list of campaign uuids to sync (so you can be selective) +- `RAISELY_OAUTH_CLIENT_ID` - OAuth NATIVE app client id (required for `raisely login` until a built-in id ships) +- `RAISELY_OAUTH_SCOPES` - optional space-separated OAuth scopes +- `RAISELY_API_URL` - API base URL (default `https://api.raisely.com`) _Note: All components are always synced, when they're present in the directory your syncing_ -With these environment variables set, run: `raisely deploy`. This will sync your local directory to the remote Raisely account, overwriting the styles and components on the destination campaign. +With these environment variables set, run: `raisely deploy`. This will sync your local directory to the remote Raisely account, overwriting the styles, components, and pages on the destination campaign. ## Developing diff --git a/package.json b/package.json index f9171b3..1917099 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@raisely/cli", - "version": "1.8.3", + "version": "2.0.0", "description": "Raisely CLI for local development", "main": "./src/cli.js", "type": "module", @@ -8,7 +8,7 @@ "raisely": "./bin/raisely.js" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "vitest run --config vitest.config.js" }, "engines": { "node": ">=18" @@ -16,30 +16,35 @@ "author": "Raisely", "license": "SEE LICENSE IN LICENSE.md", "dependencies": { - "@babel/core": "^7.17.8", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/preset-env": "^7.16.11", - "@babel/preset-react": "^7.16.7", - "chalk": "^2.4.2", - "commander": "^9.2.0", - "express": "^4.17.3", - "folder-hash": "^4.0.2", - "fzstd": "^0.1.1", - "glob": "^7.1.6", - "glob-promise": "^3.4.0", - "http-proxy-middleware": "^2.0.4", - "inquirer": "^6.3.1", - "jwt-decode": "^3.0.0-beta.2", - "lodash": "^4.17.11", - "node-fetch": "^3.2.3", - "node-sass": "^9.0.0", - "node-watch": "^0.7.1", - "open": "^8.4.0", - "ora": "^3.4.0", - "p-limit": "^4.0.0" + "@babel/core": "7.12.10", + "@babel/helpers": "7.12.5", + "@babel/plugin-proposal-class-properties": "7.12.1", + "@babel/plugin-transform-regenerator": "7.12.1", + "@babel/preset-env": "7.12.11", + "@babel/preset-react": "7.12.10", + "@napi-rs/keyring": "1.3.0", + "chalk": "5.6.2", + "commander": "13.1.0", + "express": "5.2.1", + "folder-hash": "4.1.2", + "form-data": "4.0.6", + "fzstd": "0.1.1", + "glob": "8.1.0", + "glob-promise": "6.0.7", + "handlebars": "4.7.9", + "http-proxy-middleware": "3.0.5", + "inquirer": "12.11.1", + "jwt-decode": "4.0.0", + "lodash": "4.18.1", + "node-fetch": "3.3.2", + "node-watch": "0.7.4", + "open": "10.2.0", + "ora": "8.2.0", + "p-limit": "6.2.0" }, "devDependencies": { - "prettier": "^2.6.2" + "prettier": "3.8.3", + "vitest": "2.1.9" }, "repository": { "type": "git", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..d20c0c5 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,4054 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@babel/core': + specifier: 7.12.10 + version: 7.12.10 + '@babel/helpers': + specifier: 7.12.5 + version: 7.12.5 + '@babel/plugin-proposal-class-properties': + specifier: 7.12.1 + version: 7.12.1(@babel/core@7.12.10) + '@babel/plugin-transform-regenerator': + specifier: 7.12.1 + version: 7.12.1(@babel/core@7.12.10) + '@babel/preset-env': + specifier: 7.12.11 + version: 7.12.11(@babel/core@7.12.10) + '@babel/preset-react': + specifier: 7.12.10 + version: 7.12.10(@babel/core@7.12.10) + '@napi-rs/keyring': + specifier: 1.3.0 + version: 1.3.0 + chalk: + specifier: 5.6.2 + version: 5.6.2 + commander: + specifier: 13.1.0 + version: 13.1.0 + express: + specifier: 5.2.1 + version: 5.2.1 + folder-hash: + specifier: 4.1.2 + version: 4.1.2 + form-data: + specifier: 4.0.6 + version: 4.0.6 + fzstd: + specifier: 0.1.1 + version: 0.1.1 + glob: + specifier: 8.1.0 + version: 8.1.0 + glob-promise: + specifier: 6.0.7 + version: 6.0.7(glob@8.1.0) + handlebars: + specifier: 4.7.9 + version: 4.7.9 + http-proxy-middleware: + specifier: 3.0.5 + version: 3.0.5 + inquirer: + specifier: 12.11.1 + version: 12.11.1(@types/node@25.6.0) + jwt-decode: + specifier: 4.0.0 + version: 4.0.0 + lodash: + specifier: 4.18.1 + version: 4.18.1 + node-fetch: + specifier: 3.3.2 + version: 3.3.2 + node-watch: + specifier: 0.7.4 + version: 0.7.4 + open: + specifier: 10.2.0 + version: 10.2.0 + ora: + specifier: 8.2.0 + version: 8.2.0 + p-limit: + specifier: 6.2.0 + version: 6.2.0 + devDependencies: + prettier: + specifier: 3.8.3 + version: 3.8.3 + vitest: + specifier: 2.1.9 + version: 2.1.9(@types/node@25.6.0) + +packages: + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.12.10': + resolution: {integrity: sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.28.5': + resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-environment-visitor@7.24.7': + resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.27.1': + resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.28.6': + resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.12.5': + resolution: {integrity: sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-proposal-async-generator-functions@7.20.7': + resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-class-properties@7.12.1': + resolution: {integrity: sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-dynamic-import@7.18.6': + resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-export-namespace-from@7.18.9': + resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-json-strings@7.18.6': + resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-logical-assignment-operators@7.20.7': + resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6': + resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-numeric-separator@7.18.6': + resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-object-rest-spread@7.20.7': + resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-optional-catch-binding@7.18.6': + resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-optional-chaining@7.21.0': + resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-private-methods@7.18.6': + resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} + engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-unicode-property-regex@7.18.6': + resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} + engines: {node: '>=4'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-dynamic-import@7.8.3': + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-export-namespace-from@7.8.3': + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-arrow-functions@7.27.1': + resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.28.6': + resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.27.1': + resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.28.6': + resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-classes@7.28.6': + resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.28.6': + resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.28.5': + resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.28.6': + resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.27.1': + resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.28.6': + resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.27.1': + resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.27.1': + resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.27.1': + resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.27.1': + resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.27.1': + resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.29.0': + resolution: {integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.27.1': + resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.27.1': + resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.27.1': + resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.27.7': + resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.27.1': + resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-display-name@7.28.0': + resolution: {integrity: sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.27.1': + resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.28.6': + resolution: {integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-pure-annotations@7.27.1': + resolution: {integrity: sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.12.1': + resolution: {integrity: sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-reserved-words@7.27.1': + resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.27.1': + resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.28.6': + resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.27.1': + resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.27.1': + resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.27.1': + resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.27.1': + resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.27.1': + resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-env@7.12.11': + resolution: {integrity: sha512-j8Tb+KKIXKYlDBQyIOy4BLxzv1NUOwlHfZ74rvW+Z0Gp4/cI2IMDPBWAgWceGcE7aep9oL/0K9mlzlMGxA8yNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6': + resolution: {integrity: sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/preset-react@7.12.10': + resolution: {integrity: sha512-vtQNjaHRl4DUpp+t+g4wvTHsLQuye+n0H/wsXIZRn69oz/fvNC7gQ4IK73zGJBaxvHoxElDvnYCthMcT7uzFoQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@inquirer/ansi@1.0.2': + resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==} + engines: {node: '>=18'} + + '@inquirer/checkbox@4.3.2': + resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.3.2': + resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@4.2.23': + resolution: {integrity: sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@4.0.23': + resolution: {integrity: sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/input@4.3.1': + resolution: {integrity: sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@4.4.2': + resolution: {integrity: sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@3.0.10': + resolution: {integrity: sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/keyring-darwin-arm64@1.3.0': + resolution: {integrity: sha512-pl76hJvdYUBn6I24bXiOBMA9nbDapo3I5B+f3OorjDU4dUMSypXeKbOVehJe8fhgTiH24flMyTS3aAIy43xegQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/keyring-darwin-x64@1.3.0': + resolution: {integrity: sha512-YcJtEV5LA3cvA4z3BurgxH5IhTsW1JfIvcAAcqcecwk06Si9F9NqkxbZVIfDwQ8oRHgaBmT3zZJnLAotCrVahw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/keyring-freebsd-x64@1.3.0': + resolution: {integrity: sha512-vlLf31TGhfRAaxLDBhg8b89ss0HHD/lyNmL5F3UjSaz5CUXElsJmKYq9fqA/B+cZKUEUcLHHGhF0I/CqcFdaVw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/keyring-linux-arm-gnueabihf@1.3.0': + resolution: {integrity: sha512-KiWdMMu/Inz/bHHIAGrnF7r54FZDYXuHO6UFF/rhIrshUsxbMG1Rl9lEymNtqqsVo927G0VYcb02FzWQ3iBQRQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/keyring-linux-arm64-gnu@1.3.0': + resolution: {integrity: sha512-eyKGpY40lm9Jvs1aD294XRH4y7+TlJM0YVAryZeXA6TX0mb4gMkxVXwSQv7MCwgah7raeUd0dKUb4BPAYIgcMg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/keyring-linux-arm64-musl@1.3.0': + resolution: {integrity: sha512-iIK6JWHXAJqDrEyLY3TmswwloVyt2vj+04TZnew+uSJ9gnDO8EwRbp3/iw3LpWaXiDO7VomGO6y8I0Id8uBZSw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/keyring-linux-riscv64-gnu@1.3.0': + resolution: {integrity: sha512-/PGqrwn6EwgtK6vccASSXJRfOSP4vN1F4ASsIQ+7MdrK6hNvAJ1FZPrIuD5gGGdxezo3F++To2Wq7DbuGIeuNQ==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + + '@napi-rs/keyring-linux-x64-gnu@1.3.0': + resolution: {integrity: sha512-2PDK1WKWTu9lBGq9VvNEkSlQD3O7YwVpmnyN2M3cy4v7NJ/8gDMd9GXv3G+FVXN13uhp4gnnPBS+ScefmEeD2A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/keyring-linux-x64-musl@1.3.0': + resolution: {integrity: sha512-oJ2HkX8YUo46QBkn0pG+HuIKQNqr523q6vBobCn+P95s4C4K6/kLBqHY/1bg5J4ap31DzsznhnFKcfBNBsjCnw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/keyring-win32-arm64-msvc@1.3.0': + resolution: {integrity: sha512-tOd3c/uAaeoE4ycVlmAdSvygz0Zt3zdca6Y7gokBeIbaRDWpjDIUOpU3MvML59XAaqyuKGsVVu0F/DZb1lHPmw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/keyring-win32-ia32-msvc@1.3.0': + resolution: {integrity: sha512-sPSqeAFZMGqP1R++M2JTza7GQJJ/TpCo6JU6Vcd4jnebvOaEDs9b7eipakU1PJdSvhpC2yXMCNRk9gXfrhuwHQ==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/keyring-win32-x64-msvc@1.3.0': + resolution: {integrity: sha512-4DnCWXwDc0HRKwyRlG5y0VhKZW2tNRQfKKfyj6IX/KWfDNyq9hn4n+GL1auyDcOO/v8PwnhmYo2+rOOqCkvvOg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/keyring@1.3.0': + resolution: {integrity: sha512-WrOw/bcXm0f9qHkumlT1QlArXSTWqaY9sunsDpOk+yCCorCKMxvWT/a3xko4EYHVdeZoh00yI2TydXn6eyICDA==} + engines: {node: '>= 10'} + + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/http-proxy@1.17.17': + resolution: {integrity: sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==} + + '@types/node@25.6.0': + resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} + + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + baseline-browser-mapping@2.10.19: + resolution: {integrity: sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==} + engines: {node: '>=6.0.0'} + hasBin: true + + body-parser@2.3.0: + resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} + engines: {node: '>=18'} + + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + caniuse-lite@1.0.30001788: + resolution: {integrity: sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + core-js-compat@3.49.0: + resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.336: + resolution: {integrity: sha512-AbH9q9J455r/nLmdNZes0G0ZKcRX73FicwowalLs6ijwOmCJSRRrLX63lcAlzy9ux3dWK1w1+1nsBJEWN11hcQ==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + express@5.2.1: + resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} + engines: {node: '>= 18'} + + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + folder-hash@4.1.2: + resolution: {integrity: sha512-rjdiHw3ShVonhMZZXvD/I28boUkbJFT/RBsg5MbQQd8e61PhevIwFwmL218/AscBEsW/blH4BC4A+kFeIqHVfw==} + engines: {node: '>=10.10.0'} + hasBin: true + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + fzstd@0.1.1: + resolution: {integrity: sha512-dkuVSOKKwh3eas5VkJy1AW1vFpet8TA/fGmVA5krThl8YcOVE/8ZIoEA1+U1vEn5ckxxhLirSdY837azmbaNHA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + glob-promise@6.0.7: + resolution: {integrity: sha512-DEAe6br1w8ZF+y6KM2pzgdfhpreladtNvyNNVgSkxxkFWzXTJFXxQrJQQbAnc7kL0EUd7w5cR8u4K0P4+/q+Gw==} + engines: {node: '>=16'} + peerDependencies: + glob: ^8.0.3 + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http-proxy-middleware@3.0.5: + resolution: {integrity: sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + inquirer@12.11.1: + resolution: {integrity: sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + is-unicode-supported@1.3.0: + resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} + engines: {node: '>=12'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + log-symbols@6.0.0: + resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} + engines: {node: '>=18'} + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimatch@7.4.9: + resolution: {integrity: sha512-Brg/fp/iAVDOQoHxkuN5bEYhyQlZhxddI78yWsCbeEwTHXQjlNLtiJDUsp1GIptVqMI7/gkJMz4vVAc01mpoBw==} + engines: {node: '>=10'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + node-releases@2.0.37: + resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + + node-watch@0.7.4: + resolution: {integrity: sha512-RinNxoz4W1cep1b928fuFhvAQ5ag/+1UlMDV7rbyGthBIgsiEouS4kvRayvvboxii4m8eolKOIBo3OjDqbc+uQ==} + engines: {node: '>=6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + + ora@8.2.0: + resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} + engines: {node: '>=18'} + + p-limit@6.2.0: + resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} + engines: {node: '>=18'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + + postcss@8.5.16: + resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + qs@6.14.2: + resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} + engines: {node: '>=0.6'} + + qs@6.15.3: + resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} + engines: {node: '>=0.6'} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + regenerate-unicode-properties@10.2.2: + resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-transform@0.14.5: + resolution: {integrity: sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==} + + regexpu-core@6.4.0: + resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.13.1: + resolution: {integrity: sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==} + hasBin: true + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + + run-async@4.0.6: + resolution: {integrity: sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==} + engines: {node: '>=0.12.0'} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + stdin-discarder@0.2.2: + resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} + engines: {node: '>=18'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + undici-types@7.19.2: + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.1: + resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} + engines: {node: '>=4'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + +snapshots: + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.12.10': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.12.10) + '@babel/helpers': 7.12.5 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + convert-source-map: 1.9.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + lodash: 4.18.1 + semver: 5.7.2 + source-map: 0.5.7 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.12.10) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-annotate-as-pure': 7.27.3 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-environment-visitor@7.24.7': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-wrap-function': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helper-wrap-function@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.12.5': + dependencies: + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.12.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-class-properties@7.12.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.12.10) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.12.10) + + '@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.12.10) + + '@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.12.10) + + '@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.12.10) + + '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.12.10) + + '@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.12.10) + + '@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.12.10)': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.12.10 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.12.10) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.12.10) + + '@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.12.10) + + '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.12.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.12.10) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.12.10) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.12.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.12.10) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/template': 7.28.6 + + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.12.10) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.12.10) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.12.10) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.12.10) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.12.10) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.12.10) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.12.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.12.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.12.10) + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regenerator@7.12.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + regenerator-transform: 0.14.5 + + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.12.10) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/preset-env@7.12.11(@babel/core@7.12.10)': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.12.10 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.12.10) + '@babel/plugin-proposal-class-properties': 7.12.1(@babel/core@7.12.10) + '@babel/plugin-proposal-dynamic-import': 7.18.6(@babel/core@7.12.10) + '@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.12.10) + '@babel/plugin-proposal-json-strings': 7.18.6(@babel/core@7.12.10) + '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.12.10) + '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.12.10) + '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.12.10) + '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.12.10) + '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.12.10) + '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.12.10) + '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.12.10) + '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.12.10) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.12.10) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.12.10) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.12.10) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.12.10) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.12.10) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.12.10) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.12.10) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.12.10) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.12.10) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.12.10) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.12.10) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.12.10) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.12.10) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.12.10) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.12.10) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.12.10) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.12.10) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.12.10) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.12.10) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.12.10) + '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.12.10) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.12.10) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.12.10) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-regenerator': 7.12.1(@babel/core@7.12.10) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.12.10) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.12.10) + '@babel/preset-modules': 0.1.6(@babel/core@7.12.10) + '@babel/types': 7.29.0 + core-js-compat: 3.49.0 + semver: 5.7.2 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.12.10) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.12.10) + '@babel/types': 7.29.0 + esutils: 2.0.3 + + '@babel/preset-react@7.12.10(@babel/core@7.12.10)': + dependencies: + '@babel/core': 7.12.10 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.12.10) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.12.10) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.12.10) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.12.10) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@inquirer/ansi@1.0.2': {} + + '@inquirer/checkbox@4.3.2(@types/node@25.6.0)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.6.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/confirm@5.1.21(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/core@10.3.2(@types/node@25.6.0)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.6.0) + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/editor@4.2.23(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/external-editor': 1.0.3(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/expand@4.0.23(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/external-editor@1.0.3(@types/node@25.6.0)': + dependencies: + chardet: 2.2.0 + iconv-lite: 0.7.3 + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@4.3.1(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/number@3.0.23(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/password@4.0.23(@types/node@25.6.0)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/prompts@7.10.1(@types/node@25.6.0)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@25.6.0) + '@inquirer/confirm': 5.1.21(@types/node@25.6.0) + '@inquirer/editor': 4.2.23(@types/node@25.6.0) + '@inquirer/expand': 4.0.23(@types/node@25.6.0) + '@inquirer/input': 4.3.1(@types/node@25.6.0) + '@inquirer/number': 3.0.23(@types/node@25.6.0) + '@inquirer/password': 4.0.23(@types/node@25.6.0) + '@inquirer/rawlist': 4.1.11(@types/node@25.6.0) + '@inquirer/search': 3.2.2(@types/node@25.6.0) + '@inquirer/select': 4.4.2(@types/node@25.6.0) + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/rawlist@4.1.11(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/search@3.2.2(@types/node@25.6.0)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.6.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/select@4.4.2(@types/node@25.6.0)': + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@25.6.0) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 25.6.0 + + '@inquirer/type@3.0.10(@types/node@25.6.0)': + optionalDependencies: + '@types/node': 25.6.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/keyring-darwin-arm64@1.3.0': + optional: true + + '@napi-rs/keyring-darwin-x64@1.3.0': + optional: true + + '@napi-rs/keyring-freebsd-x64@1.3.0': + optional: true + + '@napi-rs/keyring-linux-arm-gnueabihf@1.3.0': + optional: true + + '@napi-rs/keyring-linux-arm64-gnu@1.3.0': + optional: true + + '@napi-rs/keyring-linux-arm64-musl@1.3.0': + optional: true + + '@napi-rs/keyring-linux-riscv64-gnu@1.3.0': + optional: true + + '@napi-rs/keyring-linux-x64-gnu@1.3.0': + optional: true + + '@napi-rs/keyring-linux-x64-musl@1.3.0': + optional: true + + '@napi-rs/keyring-win32-arm64-msvc@1.3.0': + optional: true + + '@napi-rs/keyring-win32-ia32-msvc@1.3.0': + optional: true + + '@napi-rs/keyring-win32-x64-msvc@1.3.0': + optional: true + + '@napi-rs/keyring@1.3.0': + optionalDependencies: + '@napi-rs/keyring-darwin-arm64': 1.3.0 + '@napi-rs/keyring-darwin-x64': 1.3.0 + '@napi-rs/keyring-freebsd-x64': 1.3.0 + '@napi-rs/keyring-linux-arm-gnueabihf': 1.3.0 + '@napi-rs/keyring-linux-arm64-gnu': 1.3.0 + '@napi-rs/keyring-linux-arm64-musl': 1.3.0 + '@napi-rs/keyring-linux-riscv64-gnu': 1.3.0 + '@napi-rs/keyring-linux-x64-gnu': 1.3.0 + '@napi-rs/keyring-linux-x64-musl': 1.3.0 + '@napi-rs/keyring-win32-arm64-msvc': 1.3.0 + '@napi-rs/keyring-win32-ia32-msvc': 1.3.0 + '@napi-rs/keyring-win32-x64-msvc': 1.3.0 + + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true + + '@rollup/rollup-android-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true + + '@types/estree@1.0.9': {} + + '@types/http-proxy@1.17.17': + dependencies: + '@types/node': 25.6.0 + + '@types/node@25.6.0': + dependencies: + undici-types: 7.19.2 + + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@25.6.0))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@25.6.0) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + assertion-error@2.0.1: {} + + asynckit@0.4.0: {} + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.10.19: {} + + body-parser@2.3.0: + dependencies: + bytes: 3.1.2 + content-type: 2.0.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.1 + qs: 6.15.3 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + brace-expansion@2.1.0: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.19 + caniuse-lite: 1.0.30001788 + electron-to-chromium: 1.5.336 + node-releases: 2.0.37 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + bytes@3.1.2: {} + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + caniuse-lite@1.0.30001788: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + chalk@5.6.2: {} + + chardet@2.2.0: {} + + check-error@2.1.3: {} + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@2.9.2: {} + + cli-width@4.1.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@13.1.0: {} + + content-disposition@1.1.0: {} + + content-type@1.0.5: {} + + content-type@2.0.0: {} + + convert-source-map@1.9.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + core-js-compat@3.49.0: + dependencies: + browserslist: 4.28.2 + + data-uri-to-buffer@4.0.1: {} + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.336: {} + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + encodeurl@2.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + esutils@2.0.3: {} + + etag@1.8.1: {} + + eventemitter3@4.0.7: {} + + expect-type@1.4.0: {} + + express@5.2.1: + dependencies: + accepts: 2.0.0 + body-parser: 2.3.0 + content-disposition: 1.1.0 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.1 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.7 + qs: 6.14.2 + range-parser: 1.2.1 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + folder-hash@4.1.2: + dependencies: + debug: 4.4.0 + minimatch: 7.4.9 + transitivePeerDependencies: + - supports-color + + follow-redirects@1.16.0(debug@4.4.3): + optionalDependencies: + debug: 4.4.3 + + form-data@4.0.6: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + fzstd@0.1.1: {} + + gensync@1.0.0-beta.2: {} + + get-east-asian-width@1.6.0: {} + + get-intrinsic@1.3.0: + 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.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + glob-promise@6.0.7(glob@8.1.0): + dependencies: + glob: 8.1.0 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.9 + once: 1.4.0 + + gopd@1.2.0: {} + + handlebars@4.7.9: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-proxy-middleware@3.0.5: + dependencies: + '@types/http-proxy': 1.17.17 + debug: 4.4.3 + http-proxy: 1.18.1(debug@4.4.3) + is-glob: 4.0.3 + is-plain-object: 5.0.0 + micromatch: 4.0.8 + transitivePeerDependencies: + - supports-color + + http-proxy@1.18.1(debug@4.4.3): + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.16.0(debug@4.4.3) + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + inquirer@12.11.1(@types/node@25.6.0): + dependencies: + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@25.6.0) + '@inquirer/prompts': 7.10.1(@types/node@25.6.0) + '@inquirer/type': 3.0.10(@types/node@25.6.0) + mute-stream: 2.0.0 + run-async: 4.0.6 + rxjs: 7.8.2 + optionalDependencies: + '@types/node': 25.6.0 + + ipaddr.js@1.9.1: {} + + is-docker@3.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-interactive@2.0.0: {} + + is-number@7.0.0: {} + + is-plain-object@5.0.0: {} + + is-promise@4.0.0: {} + + is-unicode-supported@1.3.0: {} + + is-unicode-supported@2.1.0: {} + + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + jwt-decode@4.0.0: {} + + lodash@4.18.1: {} + + log-symbols@6.0.0: + dependencies: + chalk: 5.6.2 + is-unicode-supported: 1.3.0 + + loupe@3.2.1: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + media-typer@1.1.0: {} + + merge-descriptors@2.0.0: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mimic-function@5.0.1: {} + + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.0 + + minimatch@7.4.9: + dependencies: + brace-expansion: 2.1.0 + + minimist@1.2.8: {} + + ms@2.1.3: {} + + mute-stream@2.0.0: {} + + nanoid@3.3.15: {} + + negotiator@1.0.0: {} + + neo-async@2.6.2: {} + + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + + node-releases@2.0.37: {} + + node-watch@0.7.4: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + + ora@8.2.0: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 2.9.2 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 6.0.0 + stdin-discarder: 0.2.2 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + p-limit@6.2.0: + dependencies: + yocto-queue: 1.2.2 + + parseurl@1.3.3: {} + + path-to-regexp@8.4.2: {} + + pathe@1.1.2: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.2: {} + + postcss@8.5.16: + dependencies: + nanoid: 3.3.15 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.8.3: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.14.2: + dependencies: + side-channel: 1.1.0 + + qs@6.15.3: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + range-parser@1.2.1: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-transform@0.14.5: + dependencies: + '@babel/runtime': 7.29.7 + + regexpu-core@6.4.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.1 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 + + regjsgen@0.8.0: {} + + regjsparser@0.13.1: + dependencies: + jsesc: 3.1.0 + + requires-port@1.0.0: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + rollup@4.62.2: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + run-applescript@7.1.0: {} + + run-async@4.0.6: {} + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safer-buffer@2.1.2: {} + + semver@5.7.2: {} + + semver@6.3.1: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + source-map-js@1.2.1: {} + + source-map@0.5.7: {} + + source-map@0.6.1: {} + + stackback@0.0.2: {} + + statuses@2.0.2: {} + + std-env@3.10.0: {} + + stdin-discarder@0.2.2: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tslib@2.8.1: {} + + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + + uglify-js@3.19.3: + optional: true + + undici-types@7.19.2: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-property-aliases-ecmascript@2.2.0: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + vary@1.1.2: {} + + vite-node@2.1.9(@types/node@25.6.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@25.6.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21(@types/node@25.6.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.16 + rollup: 4.62.2 + optionalDependencies: + '@types/node': 25.6.0 + fsevents: 2.3.3 + + vitest@2.1.9(@types/node@25.6.0): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@25.6.0)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.4.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@25.6.0) + vite-node: 2.1.9(@types/node@25.6.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.6.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + web-streams-polyfill@3.3.3: {} + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wordwrap@1.0.0: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + + yallist@3.1.1: {} + + yocto-queue@1.2.2: {} + + yoctocolors-cjs@2.1.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..efc037a --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +onlyBuiltDependencies: + - esbuild diff --git a/src/actions/api.js b/src/actions/api.js index 1494def..509b628 100644 --- a/src/actions/api.js +++ b/src/actions/api.js @@ -1,7 +1,13 @@ import fetch from 'node-fetch'; import https from 'https'; import _ from 'lodash'; -import { loadConfig, defaults } from '../config.js'; +import { loadConfig } from '../config.js'; +import { + getCredentials, + refreshCredentialsForCurrentContext, + NotAuthenticatedError, +} from '../credentials.js'; +import { sleep } from './sleep.js'; const devHttpsAgent = new https.Agent({ rejectUnauthorized: false, @@ -9,14 +15,11 @@ const devHttpsAgent = new https.Agent({ function getResponseContentType(response) { const rawResponseContentType = response.headers.get('Content-Type'); + if (!rawResponseContentType) return ''; const [contentType] = rawResponseContentType.split(';'); return contentType; } -function sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - export default async function api(options) { const config = await loadConfig({ allowEmpty: true }); const isJson = @@ -24,40 +27,63 @@ export default async function api(options) { const fetchUrl = `${config.apiUrl}/v3${options.path}`; const retryConfig = { retry: 3, pause: 5000 }; + const skipAuth = options.skipAuth === true; while (retryConfig.retry > 0) { try { - const response = await fetch( - fetchUrl, - Object.assign(options, { + for (let attempt = 0; attempt < 2; attempt++) { + let bearer = null; + let tokenSource = 'config'; + + if (!skipAuth) { + const creds = await getCredentials({ allowPrompt: true }); + bearer = creds.token; + tokenSource = creds.source; + } + + const response = await fetch(fetchUrl, { + method: options.method || 'GET', headers: { - ...(isJson + ...(isJson && !options.formData ? { 'Content-Type': 'application/json', } : {}), - ...(config.token - ? { Authorization: `Bearer ${config.token}` } - : {}), + ...(bearer ? { Authorization: `Bearer ${bearer}` } : {}), ...options.headers, - 'x-raisely-cli': true, + 'x-raisely-client': 'cli', }, body: - options.method !== 'GET' && options.json - ? JSON.stringify(options.json) + options.method !== 'GET' + ? (options.rawBody ?? + options.formData ?? + (options.json + ? JSON.stringify(options.json) + : undefined)) : undefined, agent: config.apiUrl ? devHttpsAgent : undefined, - }) - ); + }); - const contentType = getResponseContentType(response); - const responseIsJSON = contentType === 'application/json'; + if ( + response.status === 401 && + !skipAuth && + tokenSource === 'keyring' && + attempt === 0 + ) { + await response.text(); + await refreshCredentialsForCurrentContext(); + continue; + } + + const contentType = getResponseContentType(response); + const responseIsJSON = contentType === 'application/json'; + + const parseFormat = responseIsJSON ? 'json' : 'text'; + const formatted = await response[parseFormat](); - const parseFormat = responseIsJSON ? 'json' : 'text'; - const formatted = await response[parseFormat](); + if (response.status < 399) return formatted; - if (response.status >= 399) { - const error = { + const err = { message: `${fetchUrl} (${ response.status }) failed with message: ${ @@ -68,30 +94,29 @@ export default async function api(options) { }; if (responseIsJSON && formatted) { - // if subcode, add this to error const subcode = _.get(formatted, 'errors[0].subcode'); if (subcode) { - error.subcode = subcode; + err.subcode = subcode; } } - throw error; + throw err; } - return formatted; } catch (e) { + if (e instanceof NotAuthenticatedError) { + throw e.message; + } + retryConfig.retry--; - // Handle MFA error if (e.subcode && e.subcode.startsWith('MFA required')) { throw e; } - // Skip hard failures, except a timeout if (e.status <= 500 && e.status !== 408) { throw e.message; } - // Retries exceeeded if (retryConfig.retry === 0) { throw e.message; } diff --git a/src/actions/auth.js b/src/actions/auth.js index be5089e..df1ac27 100644 --- a/src/actions/auth.js +++ b/src/actions/auth.js @@ -1,48 +1,12 @@ -import jwtDecode from 'jwt-decode'; import inquirer from 'inquirer'; import ora from 'ora'; import api from './api.js'; import { error, log } from '../helpers.js'; -import { doLogin } from '../login.js'; import { updateConfig } from '../config.js'; +import { getCredentials } from '../credentials.js'; -let token = null; -let tokenExpiresAt = null; - -/** - * Return true if a token has an exp and it's in the past - * or (if warnEarly is set) in the next 24 hours - * (easier to update password at the start of the session than notice it needs - * updating 10 minutes in) - * @param {boolean} warnEarly If true, a token will be considered expired if it expires in the next 24 hours - * @returns {boolean} - */ -function isTokenExpired(warnEarly) { - if (tokenExpiresAt) { - let expiresThreshold = new Date().getTime(); - if (warnEarly) { - const window = 24 * 60 * 60 * 1000; - expiresThreshold += window; - } - return tokenExpiresAt.getTime() < expiresThreshold; - } - return false; -} - -function setTokenExpiresAt() { - if (tokenExpiresAt === null) { - tokenExpiresAt = false; - try { - const decoded = jwtDecode(token); - tokenExpiresAt = new Date(decoded.exp * 1000); - } catch (e) { - console.warn('Could not decode token, is it a JWT?'); - } - } -} - -async function checkCorrectOrganisation(orgUuid, opts, currentOrganisation) { +async function checkCorrectOrganisation(orgUuid, opts) { let organisationUuid = orgUuid; if (!organisationUuid) { const permChecker = ora('Checking campaign permissions...').start(); @@ -61,8 +25,6 @@ async function checkCorrectOrganisation(orgUuid, opts, currentOrganisation) { 'Could not retrieve the campaign. Are you switched into the correct organisation?' ); - // A bit hacky, but saves a lot of conditional code all over or - // a stacktrace if we rethrow process.exit(-1); } } @@ -107,40 +69,14 @@ async function checkCorrectOrganisation(orgUuid, opts, currentOrganisation) { } } -export async function login(body, opts = {}) { - return await api({ - path: '/login', - method: 'POST', - json: body, - }); -} - -export async function logout() { - return await api({ - path: '/logout', - method: 'POST', - }); -} - export async function getToken(program, opts, warnEarly) { - if (opts.$tokenFromEnv) return; - let isNewToken = false; - let organisationUuid; - if (!token) { - ({ token, organisationUuid } = opts); - setTokenExpiresAt(); - isNewToken = true; - } - if (isTokenExpired(warnEarly)) { - ({ token } = await doLogin( - 'Your token has expired, please login again' - )); - setTokenExpiresAt(); - await updateConfig({ token }); - isNewToken = true; + if (opts.$tokenFromEnv) { + const { token } = await getCredentials({ allowPrompt: false }); + opts.token = token; + return token; } + const { token } = await getCredentials(); opts.token = token; - if (isNewToken) await checkCorrectOrganisation(organisationUuid, opts); - + await checkCorrectOrganisation(opts.organisationUuid, opts); return token; } diff --git a/src/actions/babel.js b/src/actions/babel.js new file mode 100644 index 0000000..1602b7e --- /dev/null +++ b/src/actions/babel.js @@ -0,0 +1,24 @@ +let BabelAlreadyLoaded = false; + +export async function loadBabelCore() { + const [ + { default: Babel }, + { default: presetEnv }, + { default: presetReact }, + { default: classProps }, + ] = await Promise.all([ + import('@babel/core'), + import('@babel/preset-env'), + import('@babel/preset-react'), + import('@babel/plugin-proposal-class-properties'), + ]); + + if (!BabelAlreadyLoaded) { + Babel.createConfigItem(presetEnv); + Babel.createConfigItem(presetReact); + Babel.createConfigItem(classProps); + BabelAlreadyLoaded = true; + } + + return { Babel, presetEnv, presetReact, classProps }; +} diff --git a/src/actions/campaigns.js b/src/actions/campaigns.js index 5d92a8c..2838dbe 100644 --- a/src/actions/campaigns.js +++ b/src/actions/campaigns.js @@ -1,13 +1,41 @@ import glob from 'glob-promise'; -import path from 'path'; import fs from 'fs'; import api from './api.js'; +import { resolveCampaignPaths } from './layout.js'; + +export async function getCampaigns({ all = false } = {}) { + if (!all) { + return await api({ + path: '/campaigns', + method: 'GET', + }); + } -export async function getCampaigns() { - return await api({ - path: '/campaigns', - method: 'GET', - }); + const limit = 100; + let offset = 0; + const data = []; + let pagination; + + while (true) { + const page = await api({ + path: `/campaigns?limit=${limit}&offset=${offset}`, + method: 'GET', + }); + data.push(...page.data); + pagination = page.pagination; + if (page.data.length === 0) { + break; + } + if (pagination?.total != null && data.length >= pagination.total) { + break; + } + if (page.data.length < limit) { + break; + } + offset += limit; + } + + return { data, pagination }; } export async function getCampaign({ uuid }) { @@ -24,39 +52,34 @@ export async function getBaseStyles({ uuid }) { }); } -export async function fetchStyles({ campaign, filename }) { - const stylesDir = path.join(process.cwd(), 'stylesheets'); - const filePath = campaign || filename.split(path.sep)[0]; +export async function fetchStyles({ campaign }) { + const { stylesheetsDir, mainScss } = resolveCampaignPaths( + process.cwd(), + campaign + ); - const fullPath = path.join(stylesDir, filePath); - const files = await glob(`${fullPath}/**/*.scss`); + const files = await glob(`${stylesheetsDir}/**/*.scss`); const configFiles = {}; for (const file of files) { const fileName = file - // `glob` above returns paths with forward slashes only, - // so we need to replace potential Windows-style back slashes - // before attempting to find and remove the full path. - .replace(`${fullPath.replace(/\\/g, '/')}/`, ''); - - // continue if this is the main stylesheet + // `glob` returns paths with forward slashes only, + // so normalise Windows-style back slashes before stripping the prefix. + .replace(`${stylesheetsDir.replace(/\\/g, '/')}/`, ''); - if (fileName === `${filePath}.scss`) continue; + if (fileName === 'main.scss') continue; configFiles[fileName] = fs.readFileSync(file, 'utf8'); } return { configFiles, - css: fs.readFileSync( - path.join(stylesDir, filePath, `${filePath}.scss`), - 'utf8' - ), + css: fs.readFileSync(mainScss, 'utf8'), }; } -export async function processStyles({ campaign, config }) { - const { configFiles, css } = await fetchStyles({ campaign, config }); +export async function processStyles({ campaign }) { + const { configFiles, css } = await fetchStyles({ campaign }); let output = css; @@ -67,18 +90,13 @@ export async function processStyles({ campaign, config }) { return output; } -export async function uploadStyles(filename) { - // The filename will contain the relative campaign path (needs to be posix) - const [campaignPath] = filename.split(path.sep); - +export async function uploadStyles(campaignPath) { const campaign = await api({ path: `/campaigns/${campaignPath}?private=1`, method: 'GET', }); - const { configFiles, css } = await fetchStyles({ - filename, - }); + const { configFiles, css } = await fetchStyles({ campaign: campaignPath }); const data = Object.assign({}, campaign.data.config.css, { files: configFiles, diff --git a/src/actions/components.js b/src/actions/components.js index 26cd018..01e41bb 100644 --- a/src/actions/components.js +++ b/src/actions/components.js @@ -1,35 +1,9 @@ import api from './api.js'; +import { loadBabelCore } from './babel.js'; import path from 'path'; import fs from 'fs'; -let BabelAlreadyLoaded = false; - -// Only load the Babel compiling core when needed -async function loadBabelCore() { - const [ - { default: Babel }, - { default: presetEnv }, - { default: presetReact }, - { default: classProps }, - ] = await Promise.all([ - import('@babel/core'), - import('@babel/preset-env'), - import('@babel/preset-react'), - import('@babel/plugin-proposal-class-properties'), - ]); - - if (!BabelAlreadyLoaded) { - Babel.createConfigItem(presetEnv); - Babel.createConfigItem(presetReact); - Babel.createConfigItem(classProps); - // flag as initialized - BabelAlreadyLoaded = true; - } - - return { Babel, presetEnv, presetReact, classProps }; -} - async function getComponent(uuid, opts = {}) { return await api({ path: `/components/${uuid}`, diff --git a/src/actions/layout.js b/src/actions/layout.js new file mode 100644 index 0000000..bdeb908 --- /dev/null +++ b/src/actions/layout.js @@ -0,0 +1,81 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +/** + * Returns the canonical v2 paths for a given campaign inside a repo root. + * + * @param {string} repoRoot Absolute path to the repo root. + * @param {string} campaignPath Campaign path slug (e.g. "my-campaign"). + * @returns {{ campaignDir: string, pagesDir: string, stylesheetsDir: string, mainScss: string }} + */ +export function resolveCampaignPaths(repoRoot, campaignPath) { + const campaignDir = path.join(repoRoot, 'campaigns', campaignPath); + return { + campaignDir, + pagesDir: path.join(campaignDir, 'pages'), + stylesheetsDir: path.join(campaignDir, 'stylesheets'), + mainScss: path.join(campaignDir, 'stylesheets', 'main.scss'), + }; +} + +/** + * Classifies the layout of a repo root. + * + * - `legacy`: top-level `pages/` or `stylesheets/` exist, no `campaigns/` + * - `v2`: `campaigns/` exists, no top-level `pages/` or `stylesheets/` + * - `mixed`: both legacy markers and `campaigns/` co-exist + * - `empty`: none of the above are present + * + * @param {string} repoRoot Absolute path to the repo root. + * @returns {'legacy' | 'v2' | 'mixed' | 'empty'} + */ +export function detectLayout(repoRoot) { + const hasLegacy = + isDir(path.join(repoRoot, 'pages')) || + isDir(path.join(repoRoot, 'stylesheets')); + const hasV2 = isDir(path.join(repoRoot, 'campaigns')); + + if (hasLegacy && hasV2) return 'mixed'; + if (hasV2) return 'v2'; + if (hasLegacy) return 'legacy'; + return 'empty'; +} + +const REFUSING_LAYOUT_COMMANDS = new Set(['update', 'deploy', 'local', 'start']); + +/** + * Returns true when a command should refuse to run for this layout classification. + * + * @param {string} commandName + * @param {'legacy' | 'v2' | 'mixed' | 'empty'} layout + * @returns {boolean} + */ +export function shouldRefuseLayoutForCommand(commandName, layout) { + if (!REFUSING_LAYOUT_COMMANDS.has(commandName)) return false; + return layout === 'legacy' || layout === 'mixed'; +} + +/** + * Build a clear refusal message that points users to the v2 install + migration path. + * + * @param {string} commandName + * @param {'legacy' | 'v2' | 'mixed' | 'empty'} layout + * @returns {string} + */ +export function getLegacyLayoutRefusalMessage(commandName, layout) { + const layoutLabel = layout === 'mixed' ? 'mixed legacy + v2' : 'legacy'; + return [ + `Cannot run \`raisely ${commandName}\` with a ${layoutLabel} project layout.`, + 'This command requires the v2 layout under `campaigns//`.', + 'Install v2: npm install -g @raisely/cli@2', + 'Migrate this repo: raisely migrate', + ].join('\n'); +} + +function isDir(p) { + try { + return fs.statSync(p).isDirectory(); + } catch { + return false; + } +} diff --git a/src/actions/media.js b/src/actions/media.js new file mode 100644 index 0000000..c2984f0 --- /dev/null +++ b/src/actions/media.js @@ -0,0 +1,92 @@ +import fs from 'fs/promises'; +import path from 'path'; + +import FormData from 'form-data'; + +import api from './api.js'; + +const MIME_TYPES = { + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.webp': 'image/webp', + '.svg': 'image/svg+xml', +}; + +function mimeTypeForFile(filePath) { + const ext = path.extname(filePath).toLowerCase(); + return MIME_TYPES[ext] ?? 'application/octet-stream'; +} + +export async function listCampaignMedia({ campaign }) { + return await api({ + path: `/campaigns/${campaign}/media`, + method: 'GET', + }); +} + +export async function listOrganisationMedia({ organisation }) { + return await api({ + path: `/organisations/${organisation}/media`, + method: 'GET', + }); +} + +export async function deleteCampaignMedia({ campaign, uuid }) { + return await api({ + path: `/campaigns/${campaign}/media/${uuid}`, + method: 'DELETE', + }); +} + +export async function deleteOrganisationMedia({ organisation, uuid }) { + return await api({ + path: `/organisations/${organisation}/media/${uuid}`, + method: 'DELETE', + }); +} + +export async function uploadCampaignMedia({ campaign, file, url }) { + if (url) { + return await api({ + path: `/campaigns/${campaign}/media`, + method: 'POST', + json: { url }, + }); + } + const data = await fs.readFile(file); + const form = new FormData(); + form.append('file', data, { + filename: path.basename(file), + contentType: mimeTypeForFile(file), + }); + return await api({ + path: `/campaigns/${campaign}/media`, + method: 'POST', + rawBody: form, + headers: { 'Content-Type': form.getHeaders()['content-type'] }, + }); +} + +export async function uploadOrganisationMedia({ organisation, file, url }) { + if (url) { + return await api({ + path: `/organisations/${organisation}/media`, + method: 'POST', + json: { url }, + }); + } + const data = await fs.readFile(file); + const form = new FormData(); + form.append('file', data, { + filename: path.basename(file), + contentType: mimeTypeForFile(file), + }); + return await api({ + path: `/organisations/${organisation}/media`, + method: 'POST', + rawBody: form, + headers: { 'Content-Type': form.getHeaders()['content-type'] }, + }); +} diff --git a/src/actions/pages.js b/src/actions/pages.js new file mode 100644 index 0000000..5fafeec --- /dev/null +++ b/src/actions/pages.js @@ -0,0 +1,143 @@ +import path from 'path'; +import fs from 'fs'; + +import Handlebars from 'handlebars'; +import glob from 'glob-promise'; + +import api from './api.js'; + +const v3Handlebars = Handlebars.create(); + +export function compilePageBody(page) { + const schema = { + title: page.title, + body: page.body, + }; + return v3Handlebars.precompile(JSON.stringify(schema, null, null)); +} + +/** + * Build a map of page uuid (or name for preview) -> precompiled template string. + * @param {{ campaignUuid?: string }} [opts] If campaignUuid is set, only pages belonging to that campaign are compiled. + */ +export async function compileAllLocalPages({ campaignUuid } = {}) { + const campaignsDir = path.join(process.cwd(), 'campaigns'); + if (!fs.existsSync(campaignsDir)) { + return {}; + } + + const files = await glob('campaigns/*/pages/**/*.json', { + cwd: process.cwd(), + nodir: true, + }); + + const map = {}; + for (const file of files) { + const fullPath = path.join(process.cwd(), file); + const raw = fs.readFileSync(fullPath, 'utf8'); + let page; + try { + page = JSON.parse(raw); + } catch (e) { + console.error(`Invalid JSON in ${fullPath}:`, e.message); + continue; + } + + if ( + campaignUuid && + page.campaignUuid && + page.campaignUuid !== campaignUuid + ) { + continue; + } + + if (!page.body || page.title === undefined) { + console.warn( + `Skipping ${fullPath}: expected title and body for compilation` + ); + continue; + } + + const key = page.uuid || page.name; + if (!key) { + console.warn(`Skipping ${fullPath}: missing uuid and name`); + continue; + } + + map[key] = compilePageBody(page); + } + + return map; +} + +/** + * Serialize a map for safe embedding inside a `; +} + +const PATCHABLE_FIELDS = [ + 'body', + 'title', + 'internalTitle', + 'name', + 'path', + 'status', + 'provider', + 'condition', + 'image', + 'metaDescription', + 'socialTitle', + 'socialDescription', + 'protected', + 'public', +]; + +/** + * PATCH /v3/pages/:uuid with updated page fields from local JSON. + */ +export async function uploadPage(pageData) { + const { uuid, campaignUuid: _c, ...rest } = pageData; + if (!uuid) { + throw new Error('Page JSON must include uuid to deploy'); + } + + const data = {}; + for (const field of PATCHABLE_FIELDS) { + if (rest[field] !== undefined) { + data[field] = rest[field]; + } + } + + return await api({ + path: `/pages/${uuid}?private=1`, + method: 'PATCH', + json: { data }, + }); +} diff --git a/src/actions/sleep.js b/src/actions/sleep.js new file mode 100644 index 0000000..cd987d2 --- /dev/null +++ b/src/actions/sleep.js @@ -0,0 +1,3 @@ +export function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/src/actions/sync.js b/src/actions/sync.js index 5bf0fe8..67287b9 100644 --- a/src/actions/sync.js +++ b/src/actions/sync.js @@ -5,15 +5,11 @@ import fs from 'fs'; import api from './api.js'; import { error } from '../helpers.js'; import { loadConfig } from '../config.js'; +import { resolveCampaignPaths } from './layout.js'; export async function syncStyles() { const config = await loadConfig(); - const directory = path.join(process.cwd(), 'stylesheets'); - if (!fs.existsSync(directory)) { - fs.mkdirSync(directory); - } - const loader = ora('Downloading campaign stylesheets...').start(); try { for (const uuid of config.campaigns) { @@ -21,18 +17,19 @@ export async function syncStyles() { path: `/campaigns/${uuid}?private=1`, }); - const campaignDir = path.join(directory, campaign.data.path); + const { stylesheetsDir, mainScss } = resolveCampaignPaths( + process.cwd(), + campaign.data.path + ); - if (!fs.existsSync(campaignDir)) { - fs.mkdirSync(campaignDir); + if (!fs.existsSync(stylesheetsDir)) { + fs.mkdirSync(stylesheetsDir, { recursive: true }); } if (campaign.data.config.css.files) { const files = campaign.data.config.css.files; - for (const file of Object.keys( - campaign.data.config.css.files - )) { + for (const file of Object.keys(files)) { const fileFolder = file .split('/') .filter((f) => !f.includes('.')); @@ -40,7 +37,7 @@ export async function syncStyles() { .split('/') .filter((f) => f.includes('.')) .join(''); - const fileDir = path.join(campaignDir, ...fileFolder); + const fileDir = path.join(stylesheetsDir, ...fileFolder); if (!fs.existsSync(fileDir)) { fs.mkdirSync(fileDir, { recursive: true }); @@ -50,10 +47,70 @@ export async function syncStyles() { } } - fs.writeFileSync( - path.join(campaignDir, `${campaign.data.path}.scss`), - campaign.data.config.css.custom_css + fs.writeFileSync(mainScss, campaign.data.config.css.custom_css); + } + loader.succeed(); + } catch (e) { + return error(e, loader); + } +} + +function pageFileName(page) { + const base = + page.name || + (page.path && page.path !== '/' + ? page.path.replace(/^\//, '').replace(/\//g, '-') + : 'home'); + return `${base.replace(/[^a-zA-Z0-9._-]/g, '_')}.json`; +} + +export async function syncPages() { + const config = await loadConfig(); + + const loader = ora('Downloading campaign pages...').start(); + try { + for (const uuid of config.campaigns) { + const campaign = await api({ + path: `/campaigns/${uuid}?private=1`, + }); + + const { pagesDir } = resolveCampaignPaths( + process.cwd(), + campaign.data.path ); + + if (!fs.existsSync(pagesDir)) { + fs.mkdirSync(pagesDir, { recursive: true }); + } + + const pages = await api({ + path: `/campaigns/${uuid}/pages?private=1&includeBody=1&limit=999`, + }); + + for (const page of pages.data) { + const out = { + uuid: page.uuid, + path: page.path, + title: page.title, + internalTitle: page.internalTitle, + name: page.name, + status: page.status, + body: page.body, + provider: page.provider, + condition: page.condition, + image: page.image, + metaDescription: page.metaDescription, + socialTitle: page.socialTitle, + socialDescription: page.socialDescription, + protected: page.protected, + campaignUuid: uuid, + }; + + fs.writeFileSync( + path.join(pagesDir, pageFileName(page)), + JSON.stringify(out, null, 4) + ); + } } loader.succeed(); } catch (e) { diff --git a/src/actions/validate.js b/src/actions/validate.js new file mode 100644 index 0000000..9bccc50 --- /dev/null +++ b/src/actions/validate.js @@ -0,0 +1,241 @@ +import path from 'path'; +import fs from 'fs'; + +import api from './api.js'; +import { loadBabelCore } from './babel.js'; +import { getBaseStyles, processStyles } from './campaigns.js'; +import { + getCredentials, + refreshCredentialsForCurrentContext, +} from '../credentials.js'; + +const DEFAULT_TRANSPILER_URL = 'https://sass-transpiler.raisely.com/transpile'; +const AUTH_FAILURE_ERROR = 'Authentication failed; run `raisely login`.'; + +function resolveTranspilerUrl(raw) { + const fromEnv = raw?.trim(); + if (!fromEnv) return DEFAULT_TRANSPILER_URL; + + try { + const parsed = new URL(fromEnv); + const normalizedPath = parsed.pathname + .replace(/\/{2,}/g, '/') + .replace(/\/+$/, ''); + + if (normalizedPath.endsWith('/transpile')) { + parsed.pathname = normalizedPath; + return parsed.toString(); + } + + parsed.pathname = + normalizedPath === '' || normalizedPath === '/' + ? '/transpile' + : `${normalizedPath}/transpile`; + return parsed.toString(); + } catch { + const normalized = fromEnv.replace(/\/+$/, ''); + if (normalized.endsWith('/transpile')) return normalized; + return `${normalized}/transpile`; + } +} + +function toErrorMessage(error, fallback) { + if (typeof error === 'string' && error.trim()) return error.trim(); + if (error instanceof Error && error.message.trim()) return error.message.trim(); + if (error && typeof error.message === 'string' && error.message.trim()) { + return error.message.trim(); + } + return fallback; +} + +function readResponseError(response, body) { + const text = typeof body === 'string' ? body.trim() : ''; + if (text) return text; + return `${response.status} ${response.statusText}`.trim(); +} + +function hasUsableToken(token) { + return typeof token === 'string' && token.trim().length > 0; +} + +async function resolveCampaignContext(campaign, deps) { + if (!campaign) { + throw new Error('A campaign path or campaign object is required'); + } + + if (typeof campaign === 'object' && campaign.uuid && campaign.path) { + return { uuid: campaign.uuid, path: campaign.path }; + } + + if (typeof campaign === 'string') { + const fetched = await deps.apiFn({ + path: `/campaigns/${campaign}?private=1`, + method: 'GET', + }); + return { uuid: fetched.data.uuid, path: fetched.data.path }; + } + + if (typeof campaign === 'object' && campaign.uuid) { + const fetched = await deps.apiFn({ + path: `/campaigns/${campaign.uuid}?private=1`, + method: 'GET', + }); + return { uuid: fetched.data.uuid, path: fetched.data.path }; + } + + if (typeof campaign === 'object' && campaign.path) { + const fetched = await deps.apiFn({ + path: `/campaigns/${campaign.path}?private=1`, + method: 'GET', + }); + return { uuid: fetched.data.uuid, path: fetched.data.path }; + } + + throw new Error('A campaign path or campaign object is required'); +} + +async function sendSassToTranspiler({ body, token, deps }) { + const response = await deps.fetchImpl(deps.transpilerUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/scss', + Authorization: `Bearer ${token}`, + }, + body, + }); + const responseText = await response.text(); + return { response, responseText }; +} + +function buildDeps(overrides = {}) { + return { + fetchImpl: overrides.fetchImpl || globalThis.fetch, + apiFn: overrides.apiFn || api, + getBaseStylesFn: overrides.getBaseStylesFn || getBaseStyles, + processStylesFn: overrides.processStylesFn || processStyles, + getCredentialsFn: overrides.getCredentialsFn || getCredentials, + refreshCredentialsFn: + overrides.refreshCredentialsFn || refreshCredentialsForCurrentContext, + loadBabelCoreFn: overrides.loadBabelCoreFn || loadBabelCore, + fsModule: overrides.fsModule || fs, + cwd: overrides.cwd || process.cwd, + transpilerUrl: resolveTranspilerUrl( + overrides.transpilerUrl || process.env.SASS_TRANSPILER_URL + ), + }; +} + +export async function validateCampaignSass( + { campaign, token } = {}, + dependencies = {} +) { + const deps = buildDeps(dependencies); + if (typeof deps.fetchImpl !== 'function') { + return { ok: false, error: 'SASS validator fetch transport is not available.' }; + } + + try { + const { uuid, path: campaignPath } = await resolveCampaignContext( + campaign, + deps + ); + const [baseStyles, styles] = await Promise.all([ + deps.getBaseStylesFn({ uuid }), + deps.processStylesFn({ campaign: campaignPath }), + ]); + const payload = `${baseStyles}${styles}`; + + let activeToken = token; + if (!hasUsableToken(activeToken)) { + const creds = await deps.getCredentialsFn({ allowPrompt: false }); + activeToken = creds.token; + } + if (!hasUsableToken(activeToken)) { + return { ok: false, error: AUTH_FAILURE_ERROR }; + } + + try { + const { response, responseText } = await sendSassToTranspiler({ + body: payload, + token: activeToken, + deps, + }); + + if (response.ok) { + return { ok: true }; + } + + if (response.status !== 401) { + return { ok: false, error: readResponseError(response, responseText) }; + } + } catch (error) { + return { + ok: false, + error: toErrorMessage( + error, + 'Could not reach the SASS transpiler service.' + ), + }; + } + + try { + await deps.refreshCredentialsFn(); + const refreshed = await deps.getCredentialsFn({ allowPrompt: false }); + activeToken = refreshed.token; + } catch { + return { ok: false, error: AUTH_FAILURE_ERROR }; + } + if (!hasUsableToken(activeToken)) { + return { ok: false, error: AUTH_FAILURE_ERROR }; + } + + try { + const { response, responseText } = await sendSassToTranspiler({ + body: payload, + token: activeToken, + deps, + }); + + if (response.ok) { + return { ok: true }; + } + + if (response.status === 401) { + return { ok: false, error: AUTH_FAILURE_ERROR }; + } + + return { ok: false, error: readResponseError(response, responseText) }; + } catch (error) { + return { + ok: false, + error: toErrorMessage(error, 'Could not reach the SASS transpiler service.'), + }; + } + } catch (error) { + return { ok: false, error: toErrorMessage(error, 'SASS validation failed.') }; + } +} + +export async function validateComponent({ name } = {}, dependencies = {}) { + const deps = buildDeps(dependencies); + + if (!name) { + return { ok: false, error: 'Component name is required.' }; + } + + try { + const sourcePath = path.join(deps.cwd(), 'components', name, `${name}.js`); + const source = deps.fsModule.readFileSync(sourcePath, 'utf8'); + const { Babel, presetEnv, presetReact, classProps } = + await deps.loadBabelCoreFn(); + + await Babel.transformAsync(source, { + presets: [presetEnv, presetReact], + plugins: [classProps], + }); + + return { ok: true }; + } catch (error) { + return { ok: false, error: toErrorMessage(error, 'Component validation failed.') }; + } +} diff --git a/src/cli.js b/src/cli.js index 721054a..b429927 100644 --- a/src/cli.js +++ b/src/cli.js @@ -1,28 +1,65 @@ -import { program } from 'commander'; +import { InvalidArgumentError, program } from 'commander'; import { getPackageInfo } from './helpers.js'; +import { flushTelemetry, trackEvent } from './telemetry.js'; /** * Action creator - only loads modules for commands when individually invoked * @param moduleLoader * @returns {(function(...[*]): Promise)|*} */ -function actionBuilder(moduleLoader) { +function actionBuilder(moduleLoader, commandName) { return async function runtime(...args) { - // load the module - const { default: commandContext } = await moduleLoader(); - await commandContext(...args); + const startedAt = Date.now(); + let outcome = 'success'; + let errorCode; + try { + // load the module + const { default: commandContext } = await moduleLoader(); + await commandContext(...args); + } catch (error) { + outcome = 'error'; + errorCode = + error?.subcode || error?.code || error?.status || error?.name; + throw error; + } finally { + trackEvent(`cli.${commandName}`, { + outcome, + durationMs: Date.now() - startedAt, + ...(errorCode ? { errorCode } : {}), + }); + await flushTelemetry(); + } }; } +function parsePort(value) { + const port = Number(value); + if (!Number.isInteger(port) || port < 1 || port > 65535) { + throw new InvalidArgumentError('Port must be an integer between 1 and 65535'); + } + return port; +} + // define actions -const init = actionBuilder(() => import('./init.js')); -const update = actionBuilder(() => import('./update.js')); -const start = actionBuilder(() => import('./start.js')); -const create = actionBuilder(() => import('./create.js')); -const deploy = actionBuilder(() => import('./deploy.js')); -const login = actionBuilder(() => import('./login.js')); -const logout = actionBuilder(() => import('./logout.js')); -const local = actionBuilder(() => import('./local.js')); +const init = actionBuilder(() => import('./init.js'), 'init'); +const update = actionBuilder(() => import('./update.js'), 'update'); +const start = actionBuilder(() => import('./start.js'), 'start'); +const create = actionBuilder(() => import('./create.js'), 'create'); +const deploy = actionBuilder(() => import('./deploy.js'), 'deploy'); +const login = actionBuilder(() => import('./login.js'), 'login'); +const logout = actionBuilder(() => import('./logout.js'), 'logout'); +const local = actionBuilder(() => import('./local.js'), 'local'); +const list = actionBuilder(() => import('./list.js'), 'list'); +const migrate = actionBuilder(() => import('./migrate.js'), 'migrate'); +const mediaList = actionBuilder(() => import('./media/list.js'), 'media.list'); +const mediaDelete = actionBuilder( + () => import('./media/delete.js'), + 'media.delete' +); +const mediaUpload = actionBuilder( + () => import('./media/upload.js'), + 'media.upload' +); export async function cli() { const pkg = getPackageInfo(); @@ -31,6 +68,10 @@ export async function cli() { program .command('init') .description('Initialize a remote Raisely campaign to this machine') + .option( + '--uuid ', + 'Initialize a specific campaign by UUID (skips the campaign picker)' + ) .action(init); program @@ -38,6 +79,10 @@ export async function cli() { .description( 'Synchronise (pull) a remote Raisely campaign with the files on this machine' ) + .option( + '-f, --force', + 'Update without asking for confirmation (non-interactive)' + ) .action(update); program @@ -45,6 +90,14 @@ export async function cli() { .description( 'Synchronise (push) a remote Raisely campaign with the files on this machine' ) + .option( + '--no-validate', + 'Skip pre-flight SCSS/component validation before upload' + ) + .option( + '-f, --force', + 'Deploy without asking for confirmation (non-interactive)' + ) .action(deploy); program @@ -61,19 +114,82 @@ export async function cli() { program .command('login') - .description('Authenticate with the Raisely api') + .description( + 'Sign in with OAuth (browser); stores tokens in the OS keychain' + ) .action(login); program .command('logout') - .description('Logout from the Raisely api') + .description('Revoke the CLI session and clear stored credentials') .action(logout); program .command('local') .description('Start local development server for a single campaign.') + .option( + '--uuid ', + 'Open a specific campaign by UUID (skips the campaign picker)' + ) + .option('--port ', 'Override the local server port', parsePort, 8015) + .option('--no-open', 'Do not open a browser window') .action(local); + program + .command('list') + .description('List all campaigns in your organisation (Name, Uuid)') + .option('--json', 'Output as JSON (forces machine format)') + .option('--tsv', 'Output as tab-separated values (forces machine format)') + .action(list); + + program + .command('migrate') + .description( + 'Migrate an existing repo from the v1 layout to the v2 layout (one-shot, idempotent)' + ) + .action(migrate); + + const media = program + .command('media') + .description('Manage campaign and organisation media assets') + .action(function () { + this.help(); + }); + + media + .command('list') + .description('List media for a campaign or organisation') + .option( + '--campaign ', + 'Campaign path/slug (defaults to the first campaign in .raisely.json)' + ) + .option('--organisation ', 'Organisation UUID') + .option('--json', 'Output as JSON') + .action(mediaList); + + media + .command('delete ') + .description('Delete a media asset by UUID') + .option( + '--campaign ', + 'Campaign path/slug (defaults to the first campaign in .raisely.json)' + ) + .option('--organisation ', 'Organisation UUID') + .option('--json', 'Output result as JSON') + .action(mediaDelete); + + media + .command('upload ') + .description('Upload a file or remote URL as a media asset') + .option( + '--campaign ', + 'Campaign path/slug (defaults to the first campaign in .raisely.json)' + ) + .option('--organisation ', 'Organisation UUID') + .option('-f, --force', 'Skip the confirmation prompt') + .option('--json', 'Output result as JSON') + .action(mediaUpload); + // Make sure we show help after a bad command program.showHelpAfterError(); diff --git a/src/config.js b/src/config.js index 40d0b3c..d870326 100644 --- a/src/config.js +++ b/src/config.js @@ -6,7 +6,7 @@ import inquirer from 'inquirer'; import { br, log, error } from './helpers.js'; -const CONFIG_FILE = '.raisely.json'; +export const CONFIG_FILE = '.raisely.json'; export const defaults = { apiUrl: process.env.RAISELY_API_URL || 'https://api.raisely.com', }; @@ -82,16 +82,6 @@ async function hideFile() { export async function loadConfig({ allowEmpty = false } = {}) { let config = {}; - if (process.env.RAISELY_TOKEN) { - return Object.assign({}, defaults, { - token: process.env.RAISELY_TOKEN, - cli: true, - apiUrl: process.env.RAISELY_API_URL || defaults.apiUrl, - campaigns: process.env.RAISELY_CAMPAIGNS.split(','), - $tokenFromEnv: true, - }); - } - try { config = readConfig(CONFIG_FILE); } catch (e) { @@ -107,7 +97,19 @@ export async function loadConfig({ allowEmpty = false } = {}) { } } } - return Object.assign({}, defaults, config); + + const merged = Object.assign({}, defaults, config); + + if (process.env.RAISELY_TOKEN) { + merged.$tokenFromEnv = true; + } + if (process.env.RAISELY_CAMPAIGNS) { + merged.campaigns = process.env.RAISELY_CAMPAIGNS.split(',').map((s) => + s.trim() + ); + } + + return merged; } export async function saveConfig(config) { diff --git a/src/credentials.js b/src/credentials.js new file mode 100644 index 0000000..ceff927 --- /dev/null +++ b/src/credentials.js @@ -0,0 +1,398 @@ +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import { runOAuthLogin } from './login.js'; + +import { Entry } from '@napi-rs/keyring'; + +import { loadConfig, defaults } from './config.js'; + +export const KEYCHAIN_SERVICE = '@raisely/cli'; +export const SESSION_DIR = path.join(os.homedir(), '.raisely'); +export const SESSION_FILE = path.join(SESSION_DIR, 'session.json'); + +export const OAUTH_CLIENT_ID_PLACEHOLDER = + 'a4151d50-3f65-11f1-8a2b-25aca0e28fce'; + +const REFRESH_WINDOW_MS = 5 * 60 * 1000; + +/** @type {Map>} */ +const refreshInFlight = new Map(); + +export class NotAuthenticatedError extends Error { + constructor(message = 'Run `raisely login` to authenticate') { + super(message); + this.name = 'NotAuthenticatedError'; + } +} + +export function getOAuthClientId() { + return process.env.RAISELY_OAUTH_CLIENT_ID || OAUTH_CLIENT_ID_PLACEHOLDER; +} + +export function getOAuthScopes() { + return ( + process.env.RAISELY_OAUTH_SCOPES || + 'campaigns:read campaigns:update pages:read pages:update components:read components:update' + ); +} + +export function getHost(apiUrl) { + return new URL(apiUrl).host; +} + +export function getAccountKey({ apiUrl, organisationUuid }) { + if (!apiUrl || !organisationUuid) { + throw new Error( + 'apiUrl and organisationUuid are required for keychain key' + ); + } + return `${getHost(apiUrl)}:${organisationUuid}`; +} + +export function readSessionPointer() { + try { + const raw = fs.readFileSync(SESSION_FILE, 'utf8'); + return JSON.parse(raw); + } catch { + return null; + } +} + +export function writeSessionPointer({ host, organisationUuid }) { + fs.mkdirSync(SESSION_DIR, { recursive: true }); + fs.writeFileSync( + SESSION_FILE, + JSON.stringify({ host, organisationUuid }, null, 2) + ); +} + +export function clearSessionPointerIfMatches(host, organisationUuid) { + const s = readSessionPointer(); + if (s && s.host === host && s.organisationUuid === organisationUuid) { + try { + fs.unlinkSync(SESSION_FILE); + } catch { + // noop + } + } +} + +/** + * Resolve apiUrl + organisationUuid from project config, then ~/.raisely/session.json. + */ +export async function resolveOrganisationContext() { + const config = await loadConfig({ allowEmpty: true }); + const apiUrl = config.apiUrl || defaults.apiUrl; + let organisationUuid = config.organisationUuid; + + if (!organisationUuid) { + const session = readSessionPointer(); + const host = getHost(apiUrl); + if (session && session.host === host && session.organisationUuid) { + organisationUuid = session.organisationUuid; + } + } + + if (!organisationUuid) { + return null; + } + return { apiUrl, organisationUuid }; +} + +export function getKeychainEntry(accountKey) { + return new Entry(KEYCHAIN_SERVICE, accountKey); +} + +/** + * Node's fetch often throws TypeError "fetch failed" with the real reason on `cause` + * (e.g. TLS errors to local HTTPS). Surface that so token exchange failures are diagnosable. + */ +function rethrowFetchFailure(fetchUrl, e) { + const c = e && typeof e === 'object' && 'cause' in e ? e.cause : null; + const detail = + c && typeof c === 'object' && 'message' in c ? c.message : e.message; + const code = c && typeof c === 'object' && 'code' in c ? c.code : ''; + const parts = [`Request to ${fetchUrl} failed: ${detail}`]; + if (code) parts.push(`(${code})`); + let msg = parts.join(' '); + if ( + code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' || + code === 'DEPTH_ZERO_SELF_SIGNED_CERT' || + code === 'SELF_SIGNED_CERT_IN_CHAIN' || + code === 'CERT_HAS_EXPIRED' + ) { + msg += + ' Local HTTPS often needs NODE_EXTRA_CA_CERTS pointing at your dev CA, or NODE_TLS_REJECT_UNAUTHORIZED=0 for development only.'; + } + const err = new Error(msg); + err.cause = c || e; + throw err; +} + +/** + * POST /v1/oauth/token (no Authorization header). + */ +export async function oauthRequestToken(apiUrl, body) { + const base = apiUrl.replace(/\/$/, ''); + const fetchUrl = `${base}/v1/oauth/token`; + let response; + try { + response = await fetch(fetchUrl, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + } catch (e) { + rethrowFetchFailure(fetchUrl, e); + } + + const text = await response.text(); + let json; + try { + json = JSON.parse(text); + } catch { + const err = new Error( + `Token endpoint returned non-JSON (${ + response.status + }): ${text.slice(0, 200)}` + ); + err.status = response.status; + throw err; + } + + if (!response.ok) { + const err = new Error( + json.error_description || + json.error || + response.statusText || + 'Token request failed' + ); + err.code = json.error; + err.status = response.status; + throw err; + } + return json; +} + +function needsProactiveRefresh(stored) { + if (!stored.expires_at) return true; + return stored.expires_at - Date.now() < REFRESH_WINDOW_MS; +} + +/** + * @param {{ apiUrl: string, organisation_uuid: string, access_token: string, refresh_token?: string, expires_in: number }} params + */ +export async function saveCredentials({ + apiUrl, + organisation_uuid, + access_token, + refresh_token, + expires_in, +}) { + const accountKey = getAccountKey({ + apiUrl, + organisationUuid: organisation_uuid, + }); + const expires_at = Date.now() + expires_in * 1000; + const payload = { + access_token, + refresh_token, + expires_at, + organisation_uuid, + }; + const entry = getKeychainEntry(accountKey); + entry.setPassword(JSON.stringify(payload)); + writeSessionPointer({ + host: getHost(apiUrl), + organisationUuid: organisation_uuid, + }); +} + +/** + * Merge token response into stored credentials (e.g. after refresh). + */ +async function persistAfterRefresh( + accountKey, + apiUrl, + organisation_uuid, + data +) { + const expires_in = data.expires_in; + const expires_at = Date.now() + expires_in * 1000; + const entry = getKeychainEntry(accountKey); + let refresh_token = data.refresh_token; + if (!refresh_token) { + const prev = entry.getPassword(); + if (prev) { + try { + refresh_token = JSON.parse(prev).refresh_token; + } catch { + // noop + } + } + } + const payload = { + access_token: data.access_token, + refresh_token, + expires_at, + organisation_uuid: data.organisation_uuid || organisation_uuid, + }; + entry.setPassword(JSON.stringify(payload)); + writeSessionPointer({ + host: getHost(apiUrl), + organisationUuid: payload.organisation_uuid, + }); +} + +export async function refreshAccessToken(accountKey, apiUrl) { + const entry = getKeychainEntry(accountKey); + const raw = entry.getPassword(); + if (!raw) { + throw new NotAuthenticatedError(); + } + let stored; + try { + stored = JSON.parse(raw); + } catch { + throw new NotAuthenticatedError('Stored credentials are corrupted'); + } + if (!stored.refresh_token) { + throw new NotAuthenticatedError( + 'No refresh token stored; run `raisely login`' + ); + } + + const clientId = getOAuthClientId(); + if (refreshInFlight.has(accountKey)) { + await refreshInFlight.get(accountKey); + return; + } + + const refreshPromise = (async () => { + try { + const data = await oauthRequestToken(apiUrl, { + grant_type: 'refresh_token', + client_id: clientId, + refresh_token: stored.refresh_token, + }); + await persistAfterRefresh( + accountKey, + apiUrl, + stored.organisation_uuid, + data + ); + } catch (e) { + if ( + e.code === 'invalid_grant' || + e.status === 401 || + e.status === 400 + ) { + clearCredentials({ + apiUrl, + organisationUuid: stored.organisation_uuid, + }); + } + throw e; + } + })(); + + refreshInFlight.set(accountKey, refreshPromise); + try { + await refreshPromise; + } finally { + refreshInFlight.delete(accountKey); + } +} + +/** + * Refresh stored keychain tokens for the current org context (401 recovery). + */ +export async function refreshCredentialsForCurrentContext() { + const ctx = await resolveOrganisationContext(); + if (!ctx) { + throw new NotAuthenticatedError(); + } + const accountKey = getAccountKey(ctx); + await refreshAccessToken(accountKey, ctx.apiUrl); +} + +export function clearCredentials({ apiUrl, organisationUuid }) { + const accountKey = getAccountKey({ apiUrl, organisationUuid }); + const entry = getKeychainEntry(accountKey); + try { + entry.deletePassword(); + } catch { + // noop + } + clearSessionPointerIfMatches(getHost(apiUrl), organisationUuid); +} + +/** + * @returns {Promise<{ token: string, source: 'env' | 'keyring' | 'config' }>} + */ +export async function getCredentials({ allowPrompt = true } = {}) { + if (process.env.RAISELY_TOKEN) { + return { + token: process.env.RAISELY_TOKEN, + source: 'env', + }; + } + + const config = await loadConfig({ allowEmpty: true }); + const ctx = await resolveOrganisationContext(); + + if (ctx) { + const accountKey = getAccountKey(ctx); + const entry = getKeychainEntry(accountKey); + const raw = entry.getPassword(); + if (raw) { + let stored; + try { + stored = JSON.parse(raw); + } catch { + throw new NotAuthenticatedError( + 'Stored credentials are corrupted' + ); + } + if (needsProactiveRefresh(stored)) { + try { + await refreshAccessToken(accountKey, ctx.apiUrl); + const updated = getKeychainEntry(accountKey).getPassword(); + if (updated) { + stored = JSON.parse(updated); + } + } catch (e) { + if (e instanceof NotAuthenticatedError) throw e; + throw new NotAuthenticatedError( + e.message || 'Session expired; run `raisely login`' + ); + } + } + const latest = getKeychainEntry(accountKey).getPassword(); + const parsed = latest ? JSON.parse(latest) : stored; + return { + token: parsed.access_token, + source: 'keyring', + }; + } + } + + if (config.token) { + return { token: config.token, source: 'config' }; + } + + if (!allowPrompt) { + throw new NotAuthenticatedError(); + } + + // No stored credentials and the caller is allowed to prompt: run the + // OAuth browser flow, persist the result, and return the fresh token. + // Dynamic import avoids a circular dependency with login.js. + + const tokenResponse = await runOAuthLogin(); + return { + token: tokenResponse.access_token, + source: 'keyring', + }; +} diff --git a/src/deploy.js b/src/deploy.js index 5f07fcc..d4d9cbd 100644 --- a/src/deploy.js +++ b/src/deploy.js @@ -5,25 +5,163 @@ import ora from 'ora'; import fs from 'fs'; import path from 'path'; import pLimit from 'p-limit'; +import glob from 'glob-promise'; import { welcome, log, br, informUpdate } from './helpers.js'; import { uploadStyles, getCampaign } from './actions/campaigns.js'; +import { + detectLayout, + shouldRefuseLayoutForCommand, + getLegacyLayoutRefusalMessage, +} from './actions/layout.js'; import { updateComponentFile, updateComponentConfig, } from './actions/components.js'; +import { uploadPage } from './actions/pages.js'; import { loadConfig } from './config.js'; import { getToken } from './actions/auth.js'; +import { + validateCampaignSass, + validateComponent, +} from './actions/validate.js'; + +function formatValidationErrors(errors) { + return errors.map(({ context, error }) => `${context}: ${error}`); +} + +function normalizeValidationResult(result, fallbackError) { + if (result && typeof result === 'object' && typeof result.ok === 'boolean') { + return { + ok: result.ok, + error: + typeof result.error === 'string' && result.error.trim() + ? result.error + : fallbackError, + }; + } + + return { + ok: false, + error: fallbackError, + }; +} + +function toErrorMessage(error, fallback) { + if (typeof error === 'string' && error.trim()) return error.trim(); + if (error instanceof Error && error.message.trim()) return error.message.trim(); + if (error && typeof error.message === 'string' && error.message.trim()) { + return error.message.trim(); + } + return fallback; +} + +function getComponentNames() { + const componentsDir = path.join(process.cwd(), 'components'); + if (!fs.existsSync(componentsDir)) { + return []; + } + + return fs + .readdirSync(componentsDir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name); +} + +async function runPreflightValidation({ config }) { + const validationLimit = pLimit(4); + const loader = ora('Validating campaigns and components').start(); + let campaigns = []; + try { + campaigns = await Promise.all( + config.campaigns.map(async (campaignUuid) => { + const campaign = await getCampaign({ uuid: campaignUuid }); + return { + uuid: campaign.data.uuid, + path: campaign.data.path, + }; + }) + ); + } catch (error) { + loader.fail('Deploy validation failed'); + log( + `Campaign lookup failed: ${toErrorMessage( + error, + 'Could not load campaign metadata for validation.' + )}`, + 'red' + ); + return false; + } + const componentNames = getComponentNames(); + + const validationTasks = [ + ...campaigns.map((campaign) => + validationLimit(async () => { + const rawResult = await validateCampaignSass({ + campaign, + token: config.token, + }); + const result = normalizeValidationResult( + rawResult, + 'SASS validator returned an invalid response.' + ); + return { + ok: result.ok, + context: `Campaign ${campaign.path}`, + error: result.error, + }; + }) + ), + ...componentNames.map((name) => + validationLimit(async () => { + const rawResult = await validateComponent({ name }); + const result = normalizeValidationResult( + rawResult, + 'Component validator returned an invalid response.' + ); + return { + ok: result.ok, + context: `Component ${name}`, + error: result.error, + }; + }) + ), + ]; + + const results = await Promise.all(validationTasks); + const failed = results.filter((result) => !result.ok); + + if (failed.length > 0) { + loader.fail('Deploy validation failed'); + formatValidationErrors(failed).forEach((message) => { + log(message, 'red'); + }); + return false; + } + + loader.succeed('Validation passed'); + return true; +} + +export default async function deploy(options = {}) { + const cwd = process.cwd(); + const layout = detectLayout(cwd); + if (shouldRefuseLayoutForCommand('deploy', layout)) { + br(); + log(getLegacyLayoutRefusalMessage('deploy', layout), 'red'); + process.exitCode = 1; + return; + } -export default async function deploy() { // load config let config = await loadConfig(); - await getToken(program, config); + config.token = await getToken(program, config); welcome(); log(`You are about to deploy your local directly to Raisely`, 'white'); br(); - console.log(` ${chalk.inverse(`${process.cwd()}`)}`); + console.log(` ${chalk.inverse(`${cwd}`)}`); br(); if (config.apiUrl) { br(); @@ -31,13 +169,12 @@ export default async function deploy() { br(); } log( - `You will overwrite the styles and components in your campaign.`, + `You will overwrite the styles, components, and pages in your campaign.`, 'white' ); br(); - if (!config.cli) { - // collect login details + if (!config.cli && !options.force) { const response = await inquirer.prompt([ { type: 'confirm', @@ -52,6 +189,17 @@ export default async function deploy() { } } + if (options.validate !== false) { + const validationPassed = await runPreflightValidation({ config }); + if (!validationPassed) { + br(); + process.exitCode = 1; + return; + } + } else { + log('Skipping validation due to --no-validate flag.', 'yellow'); + } + // upload campaign stylesheets for (const campaignUuid of config.campaigns) { const loader = ora(`Uploading styles for ${campaignUuid}`).start(); @@ -59,13 +207,13 @@ export default async function deploy() { const campaign = await getCampaign({ uuid: campaignUuid }); try { - await uploadStyles( - `${campaign.data.path}${path.sep}${campaign.data.path}.scss` - ); + await uploadStyles(campaign.data.path); } catch (e) { + loader.fail(`Failed to upload styles for ${campaignUuid}`); br(); console.error(e); - process.exit(1); + process.exitCode = 1; + return; } loader.succeed(); @@ -75,23 +223,19 @@ export default async function deploy() { const limit = pLimit(5); const components = []; - const componentsDir = path.join(process.cwd(), 'components'); - for (const file of fs.readdirSync(componentsDir)) { - const data = { - file: fs.readFileSync( - path.join(componentsDir, file, `${file}.js`), - 'utf8' - ), - config: JSON.parse( - fs.readFileSync( - path.join(componentsDir, file, `${file}.json`), - 'utf8' - ) - ), - }; + const componentsDir = path.join(cwd, 'components'); + if (fs.existsSync(componentsDir)) { + for (const file of fs.readdirSync(componentsDir)) { + const data = { + file: fs.readFileSync(path.join(componentsDir, file, `${file}.js`), 'utf8'), + config: JSON.parse( + fs.readFileSync(path.join(componentsDir, file, `${file}.json`), 'utf8') + ), + }; - components.push(limit(() => updateComponentConfig(data))); - components.push(limit(() => updateComponentFile(data))); + components.push(limit(() => updateComponentConfig(data))); + components.push(limit(() => updateComponentFile(data))); + } } // Start loading all the components @@ -109,6 +253,51 @@ export default async function deploy() { }); } else { loader.succeed(); + } + + // upload pages + const pageFiles = await glob('campaigns/*/pages/**/*.json', { + cwd, + }); + + const pageTasks = []; + for (const file of pageFiles) { + const fullPath = path.join(cwd, file); + const pageData = JSON.parse(fs.readFileSync(fullPath, 'utf8')); + if (!pageData.uuid) { + continue; + } + if ( + pageData.campaignUuid && + !config.campaigns.includes(pageData.campaignUuid) + ) { + continue; + } + pageTasks.push(limit(() => uploadPage(pageData))); + } + + let pageRejected = []; + if (pageTasks.length > 0) { + const pageLoader = ora(`Uploading pages`).start(); + const pageResults = await Promise.allSettled(pageTasks); + + pageRejected = pageResults + .filter((result) => result.status === 'rejected') + .map((result) => result.reason); + + if (pageRejected.length > 0) { + pageLoader.warn( + 'The following errors occured while uploading pages:' + ); + pageRejected.forEach((err) => { + log(err, 'red'); + }); + } else { + pageLoader.succeed(); + } + } + + if (rejected.length === 0 && pageRejected.length === 0) { await informUpdate(); } br(); diff --git a/src/helpers.js b/src/helpers.js index 1b41ab4..6548f8e 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -25,17 +25,20 @@ export function getPackageInfo() { }; } +const NPM_DIST_TAGS_TIMEOUT_MS = 10_000; + function checkUpdate() { const pkg = getPackageInfo(); if (!updatePromise && pkg.version) { const url = `https://registry.npmjs.org/-/package/${pkg.name}/dist-tags`; - updatePromise = fetch(url) + const signal = AbortSignal.timeout(NPM_DIST_TAGS_TIMEOUT_MS); + updatePromise = fetch(url, { signal }) .then((result) => result.json()) .then((result) => { latestVersion = result.latest; }) - .catch((e) => { - // noop + .catch(() => { + // offline, blocked registry, or slow network; ignore }); } } @@ -127,23 +130,3 @@ export function error(e, loader) { console.log(`${chalk.bgRed('Error:')} ${chalk.red(message)}`); } } - -export function requiresMfa(e) { - return e.subcode && e.subcode.startsWith('MFA required'); -} - -export function getMfaStrategy(e) { - // Extract if it's authy or authenticator - const subcodeArray = e.subcode.split(':'); - const authType = subcodeArray[1]; - - return { - mfaType: authType, - // if authenticator, we need to know whether to offer authy as alternative - hasAuthy: Boolean( - authType === 'AUTHY' || - (subcodeArray.length === 3 && - subcodeArray[2] === 'hasAuthy') - ) - } -} \ No newline at end of file diff --git a/src/init.js b/src/init.js index a88aecb..a92d84d 100644 --- a/src/init.js +++ b/src/init.js @@ -4,14 +4,16 @@ import inquirer from 'inquirer'; import ora from 'ora'; import { welcome, log, br, error, informUpdate } from './helpers.js'; -import { getCampaigns } from './actions/campaigns.js'; -import { syncStyles, syncComponents } from './actions/sync.js'; +import { getCampaigns, getCampaign } from './actions/campaigns.js'; +import { syncStyles, syncComponents, syncPages } from './actions/sync.js'; import { saveConfig } from './config.js'; -import { doLogin } from './login.js'; - -export default async function init() { - const data = {}; +import { + resolveOrganisationContext, + getCredentials, +} from './credentials.js'; +import { runOAuthLogin } from './login.js'; +export default async function init(options = {}) { welcome(); log( `You're about to initialize a Raisely campaign in this directory`, @@ -20,45 +22,71 @@ export default async function init() { br(); console.log(` ${chalk.inverse(`${process.cwd()}`)}`); br(); - log(`Log in to your Raisely account to start:`, 'white'); - br(); - - const result = await doLogin(); - if (!result) return; - const { user, token } = result; - const { organisationUuid } = user; - await saveConfig({ token, organisationUuid }); + let organisationUuid; + const ctx = await resolveOrganisationContext(); + if (ctx) { + try { + await getCredentials({ allowPrompt: false }); + organisationUuid = ctx.organisationUuid; + log(`Already signed in for this API. Skipping browser login.`, 'white'); + br(); + } catch { + // No valid stored session for this host; run OAuth below. + } + } - // load the campaigns - const campaignsLoader = ora('Loading your campaigns...').start(); - try { - data.campaigns = await getCampaigns(); - campaignsLoader.succeed(); - } catch (e) { - return error(e, campaignsLoader); + if (!organisationUuid) { + log(`Sign in to your Raisely account to start:`, 'white'); + br(); + const tokenResponse = await runOAuthLogin(); + organisationUuid = tokenResponse.organisation_uuid; } - // select the campaigns to sync - const campaigns = await inquirer.prompt([ - { - type: 'checkbox', - name: 'campaigns', - message: 'Select the campaigns to sync:', - choices: data.campaigns.data.map((c) => ({ - name: `${c.name} (${c.path})`, - value: c.uuid, - short: c.path, - })), - }, - ]); + let selectedCampaigns; + + if (options.uuid) { + const loader = ora(`Loading campaign ${options.uuid}...`).start(); + try { + const { data: campaign } = await getCampaign({ uuid: options.uuid }); + loader.succeed(`Using campaign: ${campaign.name} (${campaign.path})`); + selectedCampaigns = [campaign.uuid]; + } catch (e) { + return error(e, loader); + } + } else { + const campaignsLoader = ora('Loading your campaigns...').start(); + let data; + try { + data = await getCampaigns(); + campaignsLoader.succeed(); + } catch (e) { + return error(e, campaignsLoader); + } + + const campaigns = await inquirer.prompt([ + { + type: 'checkbox', + name: 'campaigns', + message: 'Select the campaigns to sync:', + choices: data.data.map((c) => ({ + name: `${c.name} (${c.path})`, + value: c.uuid, + short: c.path, + })), + }, + ]); + + selectedCampaigns = campaigns.campaigns; + } const config = { - token, - campaigns: campaigns.campaigns, + campaigns: selectedCampaigns, organisationUuid, }; if (program.api) config.apiUrl = program.api; + const proxyUrlFromEnv = process.env.RAISELY_PROXY_URL?.trim(); + if (proxyUrlFromEnv) config.proxyUrl = proxyUrlFromEnv; await saveConfig(config); // sync down campaign stylesheets @@ -67,6 +95,9 @@ export default async function init() { // sync down custom components await syncComponents(); + // sync down campaign pages (v3 body JSON) + await syncPages(); + br(); log('All done! You can start development by running:', 'green'); br(); diff --git a/src/list.js b/src/list.js new file mode 100644 index 0000000..9553913 --- /dev/null +++ b/src/list.js @@ -0,0 +1,92 @@ +import ora from 'ora'; + +import { getCampaigns } from './actions/campaigns.js'; +import { error } from './helpers.js'; +import { + getCredentials, + NotAuthenticatedError, +} from './credentials.js'; + +function sanitizeForTsv(value) { + return String(value).replace(/[\t\n\r]+/g, ' '); +} + +export default async function list(options = {}) { + let format; + if (options.json) { + format = 'json'; + } else if (options.tsv) { + format = 'tsv'; + } else if (process.stdout.isTTY) { + format = 'table'; + } else { + format = 'tsv'; + } + + try { + await getCredentials({ allowPrompt: false }); + } catch (e) { + if (e instanceof NotAuthenticatedError) { + error(e); + return; + } + throw e; + } + + let loader; + if (format === 'table') { + loader = ora('Loading campaigns...').start(); + } + + let response; + try { + response = await getCampaigns({ all: true }); + if (loader) { + loader.succeed(); + } + } catch (e) { + error(e, loader); + return; + } + + const campaigns = [...response.data].sort((a, b) => + a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }) + ); + + if (format === 'json') { + console.log( + JSON.stringify( + campaigns.map((c) => ({ name: c.name, uuid: c.uuid })), + null, + 2 + ) + ); + return; + } + + if (format === 'tsv') { + console.log('Name\tUuid'); + for (const c of campaigns) { + console.log(`${sanitizeForTsv(c.name)}\t${c.uuid}`); + } + return; + } + + const nameWidth = Math.max( + 'Name'.length, + ...campaigns.map((c) => c.name.length) + ); + const uuidWidth = Math.max( + 'Uuid'.length, + ...campaigns.map((c) => String(c.uuid).length) + ); + + console.log(`${'Name'.padEnd(nameWidth)} Uuid`); + console.log(`${'-'.repeat(nameWidth)} ${'-'.repeat(uuidWidth)}`); + for (const c of campaigns) { + console.log(`${c.name.padEnd(nameWidth)} ${c.uuid}`); + } + console.log(''); + const n = campaigns.length; + console.log(`${n} campaign${n === 1 ? '' : 's'}`); +} diff --git a/src/local.js b/src/local.js index 68e4ce8..33494e7 100644 --- a/src/local.js +++ b/src/local.js @@ -1,12 +1,12 @@ import { program } from 'commander'; import chalk from 'chalk'; import ora from 'ora'; -import path from 'path'; import inquirer from 'inquirer'; import express from 'express'; import open from 'open'; -import sass from 'node-sass'; +import fetch from 'node-fetch'; import { hashElement } from 'folder-hash'; +import zlib from 'zlib'; import * as fzstd from 'fzstd'; import { @@ -16,17 +16,272 @@ import { import { welcome, log, br, error, informUpdate } from './helpers.js'; -import { processStyles, getBaseStyles } from './actions/campaigns.js'; +import { + processStyles, + getBaseStyles, + getCampaigns, + getCampaign, +} from './actions/campaigns.js'; import { compileComponents } from './actions/components.js'; -import { getCampaigns } from './actions/campaigns.js'; +import { + compileAllLocalPages, + buildPageOverrideScript, +} from './actions/pages.js'; +import { sleep } from './actions/sleep.js'; import { getToken } from './actions/auth.js'; import { loadConfig } from './config.js'; +import { + detectLayout, + shouldRefuseLayoutForCommand, + getLegacyLayoutRefusalMessage, +} from './actions/layout.js'; // local development config -const PORT = 8015; +const DEFAULT_PORT = 8015; +const DEFAULT_API_URL = 'https://api.raisely.com'; +const DEFAULT_SASS_TRANSPILER_URL = 'https://sass-transpiler.raisely.com'; +const DEFAULT_TRANSPILE_RETRY_DELAY_MS = 500; + +/** + * Build a small script that, only when the user has opted into a non-prod API, + * rewrites browser API calls from https://api.raisely.com to config.apiUrl. + * + * The campaign's frontend bundle picks its API host from window.location.hostname, + * so when it's loaded over localhost it falls through to api.raisely.com. + * Patching fetch/XHR sidesteps that resolver without changing the bundle. + * + * Returns an empty string when apiUrl is the production default, so prod/staging + * users get exactly the previous behavior. + */ +function buildApiRedirectScript(apiUrl) { + if (!apiUrl || apiUrl === DEFAULT_API_URL) return ''; + const target = apiUrl.replace(/\/$/, ''); + return ` + +`; +} + +function decompressZstdBuffer(responseBuffer) { + return new Promise((resolve, reject) => { + const decompressedChunks = []; + const decompressStream = new fzstd.Decompress((chunk, isLast) => { + decompressedChunks.push(chunk); + if (isLast) { + resolve(Buffer.concat(decompressedChunks).toString('utf8')); + } + }); + try { + decompressStream.push(responseBuffer); + decompressStream.push(new Uint8Array(0), true); + } catch (error) { + reject(error); + } + }); +} + +/** Decode proxied body; encoding may be zstd, br, gzip, or plain (already decompressed). */ +async function decodeProxyResponseBuffer(responseBuffer, proxyRes) { + const enc = String(proxyRes.headers['content-encoding'] || '').toLowerCase(); + try { + if (enc.includes('zstd')) { + return await decompressZstdBuffer(responseBuffer); + } + if (enc.includes('br')) { + return zlib.brotliDecompressSync(responseBuffer).toString('utf8'); + } + if (enc.includes('gzip')) { + return zlib.gunzipSync(responseBuffer).toString('utf8'); + } + } catch { + // Middleware may have already decompressed while leaving a stale encoding header. + } + return responseBuffer.toString('utf8'); +} + +function buildCssErrorComment(errorMessage) { + return `/*\n${errorMessage}\n*/`; +} + +function hasLastGoodCss(css) { + return typeof css === 'string' && css.length > 0; +} + +export function createStylesRouteHandler({ + campaignPath, + baseStyles, + token, + processStylesFn = processStyles, + fetchFn = fetch, + logs = console, + retryDelayMs = DEFAULT_TRANSPILE_RETRY_DELAY_MS, + waitFn = sleep, + transpilerUrl = process.env.SASS_TRANSPILER_URL?.trim() || DEFAULT_SASS_TRANSPILER_URL, +}) { + let lastGoodCss = ''; + let nextStylesRequestId = 0; + let lastAppliedStylesRequestId = 0; + + const transpileEndpoint = `${transpilerUrl.replace(/\/$/, '')}/transpile`; + + function sendFallback(res, errorText = '') { + if (hasLastGoodCss(lastGoodCss)) { + res.send(lastGoodCss); + return; + } + + if (errorText) { + res.status(502).send(buildCssErrorComment(errorText)); + return; + } + + res.sendStatus(502); + } + + async function transpileStyles(fullStyles) { + const response = await fetchFn(transpileEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/scss', + Authorization: `Bearer ${token}`, + }, + body: fullStyles, + }); + const bodyText = await response.text(); + return { response, bodyText }; + } + + async function transpileStylesWithSingleRetry(fullStyles) { + let firstError = null; + let firstResult = null; + + try { + firstResult = await transpileStyles(fullStyles); + } catch (err) { + firstError = err; + } + + const shouldRetry = + firstError !== null || + (firstResult !== null && firstResult.response.status >= 500); + + if (!shouldRetry) { + if (firstResult !== null) { + return firstResult; + } + throw new Error( + 'Unexpected transpile state: missing result and error before retry' + ); + } + + await waitFn(retryDelayMs); + + try { + return await transpileStyles(fullStyles); + } catch (retryErr) { + if (firstError) { + logs.error(firstError); + } + throw retryErr; + } + } + + return async function stylesRouteHandler(req, res) { + res.set('Content-Type', 'text/css'); + const stylesRequestId = ++nextStylesRequestId; + + let styles; + try { + styles = await processStylesFn({ campaign: campaignPath }); + } catch (err) { + logs.error(err); + sendFallback(res); + return; + } + + const fullStyles = baseStyles + styles; + let transpileResult; + try { + transpileResult = await transpileStylesWithSingleRetry(fullStyles); + } catch (err) { + logs.error(err); + sendFallback(res); + return; + } + + const { response, bodyText } = transpileResult; + + if (response.ok) { + if (stylesRequestId >= lastAppliedStylesRequestId) { + lastGoodCss = bodyText; + lastAppliedStylesRequestId = stylesRequestId; + } + res.send(bodyText); + return; + } + + if (response.status >= 400 && response.status < 500) { + if (response.status === 401) { + logs.warn( + 'SASS transpiler returned 401; your token may be stale. Run `raisely login` if this keeps happening.' + ); + } + if (bodyText) { + logs.error(bodyText); + } + sendFallback(res, bodyText); + return; + } + + if (bodyText) { + logs.error(bodyText); + } + logs.error( + `SASS transpiler failed after retry: ${response.status} ${response.statusText}` + ); + sendFallback(res); + }; +} + +export default async function start(options = {}) { + const layout = detectLayout(process.cwd()); + if (shouldRefuseLayoutForCommand('local', layout)) { + br(); + log(getLegacyLayoutRefusalMessage('local', layout), 'red'); + process.exitCode = 1; + return; + } -export default async function start() { welcome(); + const port = options.port || DEFAULT_PORT; // load config const config = await loadConfig(); @@ -36,37 +291,50 @@ export default async function start() { await informUpdate(); - // in-memory state object - const data = {}; + let campaignPath; + let campaignUuid; - // load the campaigns - const campaignsLoader = ora('Loading your campaigns...').start(); - try { - data.campaigns = await getCampaigns({}, config.token, { - apiUrl: program.api, - ...config, - }); - campaignsLoader.succeed(); - } catch (e) { - return error(e, campaignsLoader); - } + if (options.uuid) { + const loader = ora(`Loading campaign ${options.uuid}...`).start(); + try { + const { data: campaign } = await getCampaign({ uuid: options.uuid }); + loader.succeed(`Using campaign: ${campaign.name} (${campaign.path})`); + campaignPath = campaign.path; + campaignUuid = campaign.uuid; + } catch (e) { + return error(e, loader); + } + } else { + // in-memory state object + const data = {}; - // select the campaigns to sync - const campaign = await inquirer.prompt([ - { - type: 'list', - name: 'path', - message: 'Select the campaign to open:', - choices: data.campaigns.data.map((c) => ({ - name: `${c.name} (${c.path})`, - value: c.path, - short: c.path, - })), - }, - ]); - const campaignUuid = data.campaigns.data.find( - (c) => c.path === campaign.path - ).uuid; + // load the campaigns + const campaignsLoader = ora('Loading your campaigns...').start(); + try { + data.campaigns = await getCampaigns(); + campaignsLoader.succeed(); + } catch (e) { + return error(e, campaignsLoader); + } + + // select the campaigns to sync + const campaign = await inquirer.prompt([ + { + type: 'list', + name: 'path', + message: 'Select the campaign to open:', + choices: data.campaigns.data.map((c) => ({ + name: `${c.name} (${c.path})`, + value: c.path, + short: c.path, + })), + }, + ]); + campaignPath = campaign.path; + campaignUuid = data.campaigns.data.find( + (c) => c.path === campaign.path + ).uuid; + } // fetch base styles from the API const base = await getBaseStyles({ @@ -75,15 +343,15 @@ export default async function start() { // determine proxy target const target = config.proxyUrl - ? config.proxyUrl.replace('https://', `https://${campaign.path}.`) - : `https://${campaign.path}.raisely.com`; + ? config.proxyUrl.replace('https://', `https://${campaignPath}.`) + : `https://${campaignPath}.raisely.com`; const app = express(); app.use('/reload', async (req, res) => { const hash = await hashElement('.', { files: { - include: ['**/*.js', '**/*.scss'], + include: ['**/*.js', '**/*.scss', '**/*.json'], }, folders: { exclude: ['.*', 'node_modules', 'src', '.git', 'bin'], @@ -93,27 +361,14 @@ export default async function start() { }); // locally compile css files - app.use(`/v3/campaigns/${campaignUuid}/styles.css`, async (req, res) => { - res.set('Content-Type', 'text/css'); - - try { - // get the local styles to append - const styles = await processStyles({ - campaign: campaign.path, - }); - - // run through SASS - const compiled = sass.renderSync({ - data: base + styles, - outputStyle: 'expanded', - }); - - res.send(compiled.css); - } catch (e) { - console.error(e); - res.sendStatus(500); - } - }); + app.use( + `/v3/campaigns/${campaignUuid}/styles.css`, + createStylesRouteHandler({ + campaignPath, + baseStyles: base, + token: config.token, + }) + ); // locally compile components app.use(`/v3/campaigns/${campaignUuid}/components.js`, async (req, res) => { @@ -140,40 +395,80 @@ export default async function start() { selfHandleResponse: true, onProxyRes: responseInterceptor( async (responseBuffer, proxyRes, req, res) => { - // convert zstd compressed buffer to string - const response = await new Promise((resolve, reject) => { - // trans - const decompressedChunks = []; - const decompressStream = new fzstd.Decompress((chunk, isLast) => { - // Add to list of decompressed chunks - decompressedChunks.push(chunk); - if (isLast) { - resolve(Buffer.concat(decompressedChunks).toString('utf8')); - } - }); + const response = await decodeProxyResponseBuffer( + responseBuffer, + proxyRes + ); + + let pageOverride = ''; + if (response.includes('window.pageSchemas')) { try { - decompressStream.push(responseBuffer); - decompressStream.push(new Uint8Array(0), true); // Need to tell the stream that it's ended - } catch (error) { - reject(error) + const compiledMap = await compileAllLocalPages({ + campaignUuid, + }); + pageOverride = buildPageOverrideScript(compiledMap); + } catch (e) { + console.error(e); } - }); - return response - .replace( - `${ - config.apiUrl || 'https://api.raisely.com' - }/v3/campaigns/${campaignUuid}/styles.css`, - `http://localhost:${PORT}/v3/campaigns/${campaignUuid}/styles.css` - ) - .replace( - `${ - config.apiUrl || 'https://api.raisely.com' - }/v3/campaigns/${campaignUuid}/components.js`, - `http://localhost:${PORT}/v3/campaigns/${campaignUuid}/components.js` - ) + } + + // Match by path so it works regardless of whether the upstream + // embeds api.raisely.com, api.raisely.test:2999, or any other host. + const stylesPath = `/v3/campaigns/${campaignUuid}/styles.css`; + const componentsPath = `/v3/campaigns/${campaignUuid}/components.js`; + const localBase = `http://localhost:${port}`; + const upstreamUrlRe = (path) => + new RegExp( + `https?://[^"'\\s)]+${path.replace(/[/.]/g, '\\$&')}`, + 'g' + ); + + let output = response + .replace(upstreamUrlRe(stylesPath), `${localBase}${stylesPath}`) .replace( - '', - ` + upstreamUrlRe(componentsPath), + `${localBase}${componentsPath}` + ); + + // window.pageSchemas is set in the first large but before later bundles (and before the small + // `if (window.campaign)` script). Edge strips + // before HTML is sent, and injecting before runs too late (React + // already read pageSchemas). + if (pageOverride) { + const afterCampaignBootstrap = + /(<\/script>)(\s* ` - ); + ); } ), }) ); - app.listen(PORT); + app.listen(port); log(`Local development for ${target} has been set up in:`, 'white'); br(); @@ -207,10 +502,18 @@ export default async function start() { console.log(`Using custom API: ${chalk.inverse(config.apiUrl)}`); br(); } - log(`Opening your development site now...`, 'white'); + if (options.open) { + log(`Opening your development site now...`, 'white'); + } else { + log(`Your development site:`, 'white'); + } + log(`http://localhost:${port}`, 'white'); + br(); log(`Use CTRL + C to stop`, 'white'); - open(`http://localhost:${PORT}`, { - background: true, - }); + if (options.open) { + open(`http://localhost:${port}`, { + background: true, + }); + } } diff --git a/src/login.js b/src/login.js index d4a0ec7..22fc185 100644 --- a/src/login.js +++ b/src/login.js @@ -1,140 +1,214 @@ -import { program } from 'commander'; -import inquirer from 'inquirer'; -import ora from 'ora'; -import { login } from './actions/auth.js'; - -import { updateConfig } from './config.js'; -import { log, error, informUpdate, requiresMfa, getMfaStrategy } from './helpers.js'; - -export async function doLogin(message) { - if (message) log(message, 'white'); - - // collect login details - const credentials = await inquirer.prompt([ - { - type: 'input', - name: 'username', - message: 'Enter your email address', - validate: (value) => - value.length ? true : 'Please enter your email address', - }, - { - type: 'password', - message: 'Enter your password', - name: 'password', - validate: (value) => - value.length ? true : 'Please enter a password', - }, - ]); - - // log the user in - let loginLoader = ora('Logging you in...').start(); +import { randomBytes, createHash } from 'crypto'; +import express from 'express'; +import open from 'open'; +import { welcome, log, br, error, informUpdate } from './helpers.js'; +import { loadConfig, defaults } from './config.js'; +import { + oauthRequestToken, + saveCredentials, + getOAuthClientId, + getOAuthScopes, +} from './credentials.js'; + +const CALLBACK_PORTS = [8765, 8766, 8767]; +const LOGIN_TIMEOUT_MS = 2 * 60 * 1000; + +function shutdownOAuthServer(server) { + if (!server) return; try { - let loginBody = await login({ - ...credentials, - requestAdminToken: true, - }); - return loginSucceed(loginLoader, loginBody); - } catch (e) { - if (requiresMfa(e)) { - const mfaStrategy = getMfaStrategy(e) - return await loginWith2FA(loginLoader, credentials, mfaStrategy); - } else { - error(e, loginLoader); - return false; + if (typeof server.closeAllConnections === 'function') { + server.closeAllConnections(); } + server.close(); + } catch { + // noop } } -async function loginWith2FA(loginLoader, credentials, mfaStrategy) { - loginLoader.info(`Your account requires 2 factor authentication`); - let mfaType = mfaStrategy.mfaType; - if (mfaType === 'AUTHENTICATOR_APP' && mfaStrategy.hasAuthy) { - const choiceMfa = await selectMfaType(); - mfaType = choiceMfa.mfaType; - if (mfaType === 'AUTHY') { - // trigger login again with mfaType to send the prompt +function base64url(buf) { + return buf + .toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=/g, ''); +} + +/** + * OAuth 2.0 Authorization Code + PKCE (loopback). Opens the browser, waits for the callback, + * exchanges the code, and stores tokens in the OS keychain. + * @returns {Promise} Token response from `/v1/oauth/token` + */ +export async function runOAuthLogin() { + const config = await loadConfig({ allowEmpty: true }); + const apiUrl = (config.apiUrl || defaults.apiUrl).replace(/\/$/, ''); + + const code_verifier = base64url(randomBytes(32)); + const code_challenge = base64url( + createHash('sha256').update(code_verifier).digest() + ); + const state = base64url(randomBytes(32)); + + let server; + let redirect_uri; + const app = express(); + + const paramsBase = { + response_type: 'code', + client_id: getOAuthClientId(), + scope: getOAuthScopes(), + state, + code_challenge, + code_challenge_method: 'S256', + }; + + const loginPromise = new Promise((resolve, reject) => { + const timer = setTimeout(() => { + shutdownOAuthServer(server); + reject( + new Error( + 'Login timed out after 2 minutes. Run `raisely login` again.' + ) + ); + }, LOGIN_TIMEOUT_MS); + + const finish = (err, data) => { + clearTimeout(timer); + shutdownOAuthServer(server); + if (err) reject(err); + else resolve(data); + }; + + app.get('/callback', async (req, res) => { + res.set('Connection', 'close'); + const q = req.query; + if (q.error) { + res.status(400).send( + `

${escapeHtml( + String(q.error_description || q.error) + )}

` + ); + return finish( + new Error(String(q.error_description || q.error || 'OAuth error')) + ); + } + if (q.state !== state) { + res.status(400).send( + '

Invalid state parameter

' + ); + return finish(new Error('OAuth state mismatch')); + } + if (!q.code) { + res.status(400).send( + '

Missing authorization code

' + ); + return finish(new Error('Missing authorization code')); + } + try { - await login({ - ...credentials, - mfaType, - requestAdminToken: true, + const data = await oauthRequestToken(apiUrl, { + grant_type: 'authorization_code', + client_id: getOAuthClientId(), + code: q.code, + code_verifier, + redirect_uri, + }); + + await saveCredentials({ + apiUrl, + organisation_uuid: data.organisation_uuid, + access_token: data.access_token, + refresh_token: data.refresh_token, + expires_in: data.expires_in, }); + + res.send( + `

You're signed in. You can close this tab and return to the terminal.

` + ); + finish(null, data); } catch (e) { - // don't throw error if just an error about missing MFA - if (!requiresMfa(e)) { - error(e, loginLoader); - return false; + res.status(500).send( + `

Token exchange failed

` + ); + finish(e); + } + }); + }); + + for (const port of CALLBACK_PORTS) { + redirect_uri = `http://127.0.0.1:${port}/callback`; + + try { + await new Promise((resolve, reject) => { + server = app.listen(port, '127.0.0.1', () => resolve()); + server.once('error', reject); + }); + break; + } catch { + if (server) { + try { + server.close(); + } catch { + // noop } } + server = null; + redirect_uri = null; } } + + if (!server || !redirect_uri) { + throw new Error( + `Could not bind to any of ports ${CALLBACK_PORTS.join( + ', ' + )}. Close other apps using those ports and try again.` + ); + } + + const params = new URLSearchParams({ + ...paramsBase, + redirect_uri, + }); + + const authorizeUrl = `${apiUrl}/v1/oauth/authorize?${params.toString()}`; + log(`If the browser doesn't open automatically, open this URL in your browser to sign in:`, 'yellow'); + log(authorizeUrl, 'white'); + br(); + try { - const response = await inquirer.prompt([ - { - type: 'input', - message: 'Please provide your one time password', - name: 'otp', - validate: (value) => - value.length - ? true - : 'Please enter a one time password', - }, - ]); - - loginLoader.info('Logging you in...'); - - const loginBody = await login({ - ...credentials, - mfaType, - otp: response.otp, - requestAdminToken: true, - }); - return loginSucceed(loginLoader, loginBody); - } catch (e) { - error(e, loginLoader); - return false; + await open(authorizeUrl, { background: true }); + } catch { + br(); + log('Open this URL in your browser to sign in:', 'yellow'); + log(authorizeUrl, 'white'); + br(); } -} -async function selectMfaType() { - const selectedMfa = await inquirer.prompt([ - { - type: 'list', - message: 'Select your preferred MFA', - name: 'mfaType', - choices: [ - { - name: 'Authenticator App', - value: 'AUTHENTICATOR_APP' - }, - { - name: 'SMS/Legacy', - value: 'AUTHY' - } - ], - validate: (value) => - value.length - ? true - : 'Please choose your preferred MFA', - }, - ]); - return selectedMfa; + return loginPromise; } -async function loginSucceed(loginLoader, loginBody) { - const { token, data: user } = loginBody; - loginLoader.succeed(); - return { user, token }; +function escapeHtml(s) { + return s + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/'/g, ''') + .replace(/"/g, '"'); } export default async function loginAction() { - const result = await doLogin(); - if (!result) return; - const { token, user } = result; - await updateConfig({ - token, - }); - await informUpdate(); + welcome(); + br(); + log('Opening the browser to sign you in...', 'white'); + br(); + + try { + await runOAuthLogin(); + log('You are signed in. Credentials are stored in your OS keychain.', 'green'); + br(); + await informUpdate(); + } catch (e) { + error(e); + process.exitCode = 1; + } } diff --git a/src/logout.js b/src/logout.js index 7eee2c9..7707c69 100644 --- a/src/logout.js +++ b/src/logout.js @@ -1,18 +1,89 @@ +import fs from 'fs'; +import path from 'path'; +import https from 'https'; import ora from 'ora'; -import { logout } from './actions/auth.js'; -import { updateConfig } from './config.js'; +import { jwtDecode } from 'jwt-decode'; +import fetch from 'node-fetch'; +import { loadConfig, defaults, CONFIG_FILE, updateConfig } from './config.js'; +import { + clearCredentials, + getAccountKey, + getKeychainEntry, + resolveOrganisationContext, +} from './credentials.js'; + +const devHttpsAgent = new https.Agent({ + rejectUnauthorized: false, +}); + +async function deleteJwtToken(accountKey) { + const entry = getKeychainEntry(accountKey); + const raw = entry.getPassword(); + + if (!raw) return; + let stored; + + try { + stored = JSON.parse(raw); + } catch { + return; + } + + if (!stored?.access_token) return; + + const config = await loadConfig({ allowEmpty: true }); + const apiUrl = config.apiUrl || defaults.apiUrl; + const token = stored.access_token; + const looksJwt = + typeof token === 'string' && token.split('.').length === 3; + + if (!looksJwt) return; + + try { + const decoded = jwtDecode(token); + const tokenUuid = + decoded.uuid || decoded.jti || decoded.sub; + if (!tokenUuid) return; + + const base = apiUrl.replace(/\/$/, ''); + await fetch(`${base}/v3/tokens/${tokenUuid}`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${token}`, + 'x-raisely-client': 'cli', + }, + agent: devHttpsAgent, + }); + } catch { + // best-effort revoke + } +} + +/** + * Best-effort revoke of the current access token, then clear keychain + session pointer. + */ export default async function logoutAction() { - let logoutLoader = ora('Logging you out...').start(); + const logoutLoader = ora('Signing you out...').start(); + try { - await logout(); - logoutLoader.info(`You have been logged out`); - } catch (e) { - // don't throw error, continue - } finally { - await updateConfig({ - token: null, - }); - logoutLoader.stop() - } + const ctx = await resolveOrganisationContext(); + + if (!ctx) return; + const accountKey = getAccountKey(ctx); + deleteJwtToken(accountKey); + clearCredentials({ apiUrl: ctx.apiUrl, organisationUuid: ctx.organisationUuid }); + } catch { + // continue to local cleanup + } finally { + const configPath = path.join(process.cwd(), CONFIG_FILE); + if (fs.existsSync(configPath)) { + try { + await updateConfig({ token: null }); + } catch { + // noop + } + } + logoutLoader.succeed('You are signed out.'); + } } diff --git a/src/media/delete.js b/src/media/delete.js new file mode 100644 index 0000000..a728582 --- /dev/null +++ b/src/media/delete.js @@ -0,0 +1,53 @@ +import ora from 'ora'; + +import { + deleteCampaignMedia, + deleteOrganisationMedia, +} from '../actions/media.js'; +import { loadConfig } from '../config.js'; +import { getCredentials, NotAuthenticatedError } from '../credentials.js'; +import { error } from '../helpers.js'; + +export default async function mediaDelete(uuid, options = {}) { + try { + await getCredentials({ allowPrompt: false }); + } catch (e) { + if (e instanceof NotAuthenticatedError) { + error(e); + return; + } + throw e; + } + + let { campaign, organisation } = options; + + if (!campaign && !organisation) { + const config = await loadConfig({ allowEmpty: true }); + campaign = config.campaigns?.[0]; + } + + if (!campaign && !organisation) { + error('Provide --campaign or --organisation '); + return; + } + + const loader = process.stdout.isTTY && !options.json + ? ora(`Deleting media ${uuid}...`).start() + : null; + + try { + if (organisation) { + await deleteOrganisationMedia({ organisation, uuid }); + } else { + await deleteCampaignMedia({ campaign, uuid }); + } + + if (loader) { + loader.succeed(`Deleted ${uuid}`); + } else { + console.log(JSON.stringify({ success: true })); + } + } catch (e) { + error(e, loader); + } +} diff --git a/src/media/list.js b/src/media/list.js new file mode 100644 index 0000000..c6fd664 --- /dev/null +++ b/src/media/list.js @@ -0,0 +1,62 @@ +import ora from 'ora'; + +import { + listCampaignMedia, + listOrganisationMedia, +} from '../actions/media.js'; +import { loadConfig } from '../config.js'; +import { getCredentials, NotAuthenticatedError } from '../credentials.js'; +import { error } from '../helpers.js'; + +export default async function mediaList(options = {}) { + try { + await getCredentials({ allowPrompt: false }); + } catch (e) { + if (e instanceof NotAuthenticatedError) { + error(e); + return; + } + throw e; + } + + let { campaign, organisation } = options; + + if (!campaign && !organisation) { + const config = await loadConfig({ allowEmpty: true }); + campaign = config.campaigns?.[0]; + } + + if (!campaign && !organisation) { + error('Provide --campaign or --organisation '); + return; + } + + const isTTY = process.stdout.isTTY && !options.json; + const loader = isTTY ? ora('Loading media...').start() : null; + + let response; + try { + response = organisation + ? await listOrganisationMedia({ organisation }) + : await listCampaignMedia({ campaign }); + if (loader) loader.succeed(); + } catch (e) { + error(e, loader); + return; + } + + const items = response.data ?? []; + + if (!isTTY) { + console.log(JSON.stringify(items, null, 2)); + return; + } + + if (items.length === 0) { + console.log('No media items found.'); + return; + } + + console.table(items.map(({ uuid, file, type, url }) => ({ uuid, file, type, url }))); + console.log(`${items.length} item${items.length === 1 ? '' : 's'}`); +} diff --git a/src/media/upload.js b/src/media/upload.js new file mode 100644 index 0000000..a6edb2f --- /dev/null +++ b/src/media/upload.js @@ -0,0 +1,101 @@ +import fs from 'fs/promises'; +import path from 'path'; + +import inquirer from 'inquirer'; +import ora from 'ora'; + +import { + uploadCampaignMedia, + uploadOrganisationMedia, +} from '../actions/media.js'; +import { loadConfig } from '../config.js'; +import { getCredentials, NotAuthenticatedError } from '../credentials.js'; +import { error } from '../helpers.js'; + +export default async function mediaUpload(fileOrUrl, options = {}) { + try { + await getCredentials({ allowPrompt: false }); + } catch (e) { + if (e instanceof NotAuthenticatedError) { + error(e); + return; + } + throw e; + } + + let { campaign, organisation, force } = options; + let campaignFromConfig = false; + + if (!campaign && !organisation) { + const config = await loadConfig({ allowEmpty: true }); + campaign = config.campaigns?.[0]; + if (campaign) campaignFromConfig = true; + } + + if (!campaign && !organisation) { + error('Provide --campaign or --organisation '); + return; + } + + const isUrl = + fileOrUrl.startsWith('http://') || fileOrUrl.startsWith('https://'); + + if (!isUrl) { + try { + await fs.access(fileOrUrl); + } catch { + error(`File not found: ${fileOrUrl}`); + return; + } + } + + if (!force && !options.json) { + const label = isUrl ? fileOrUrl : path.basename(fileOrUrl); + let target; + if (organisation) { + target = `organisation "${organisation}"`; + } else if (campaignFromConfig) { + target = `campaign "${campaign}" (from .raisely.json)`; + } else { + target = `campaign "${campaign}"`; + } + + const { confirmed } = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirmed', + message: `Uploading to ${target} — are you sure you want to upload "${label}"?`, + default: false, + }, + ]); + + if (!confirmed) { + console.log('Upload cancelled.'); + return; + } + } + + const isTTY = process.stdout.isTTY && !options.json; + const loader = isTTY ? ora('Uploading...').start() : null; + + try { + const payload = isUrl ? { url: fileOrUrl } : { file: fileOrUrl }; + const response = organisation + ? await uploadOrganisationMedia({ organisation, ...payload }) + : await uploadCampaignMedia({ campaign, ...payload }); + + const mediaUrl = response?.data?.url ?? response?.url ?? ''; + + if (loader) { + if (mediaUrl) { + loader.succeed(`Uploaded: ${mediaUrl}`); + } else { + loader.warn('Upload succeeded but no URL was returned.'); + } + } else { + console.log(JSON.stringify(response?.data ?? response, null, 2)); + } + } catch (e) { + error(e, loader); + } +} diff --git a/src/migrate.js b/src/migrate.js new file mode 100644 index 0000000..53352eb --- /dev/null +++ b/src/migrate.js @@ -0,0 +1,267 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import chalk from 'chalk'; +import ora from 'ora'; + +import { resolveCampaignPaths } from './actions/layout.js'; +import { loadConfig } from './config.js'; +import { getToken } from './actions/auth.js'; +import { getCampaign } from './actions/campaigns.js'; +import { br, log } from './helpers.js'; +import { program } from 'commander'; + +// --------------------------------------------------------------------------- +// Pure migrator (named export — testable without network or keychain) +// --------------------------------------------------------------------------- + +/** + * Migrates a legacy repo layout to v2. + * + * For each configured campaign UUID the migrator resolves the campaign path + * via `getCampaign`, then: + * + * pages//* → campaigns//pages/* + * stylesheets//* → campaigns//stylesheets/* + * .scss → main.scss (renamed during the move) + * + * Partials (files starting with `_`) keep their names and nesting. + * Empty root `pages/` and `stylesheets/` directories are deleted after all + * campaigns have been processed. Folders under those root dirs that do not + * belong to a configured campaign are left in place and listed as orphans. + * Each campaign is processed independently — one failure does not block the + * rest. Running migrate on an already-migrated repo is a no-op. + * + * @param {object} opts + * @param {string} opts.repoRoot - Absolute path to the repo root. + * @param {string[]} opts.campaigns - Campaign UUIDs from .raisely.json. + * @param {Function} opts.getCampaign - async ({ uuid }) => { data: { path } } + * @returns {Promise<{ + * moved: string[], + * renamed: string[], + * deleted: string[], + * orphans: Array<{ folder: string, reason: string }>, + * }>} + */ +export async function migrate({ repoRoot, campaigns, getCampaign: resolveCampaign }) { + const report = { moved: [], renamed: [], deleted: [], orphans: [] }; + + // Resolve each UUID to a campaign path, collecting failures as orphans. + const resolvedCampaigns = []; + for (const uuid of campaigns) { + try { + const campaign = await resolveCampaign({ uuid }); + resolvedCampaigns.push({ uuid, campaignPath: campaign.data.path }); + } catch (err) { + report.orphans.push({ + folder: uuid, + reason: `Could not resolve campaign: ${err.message}`, + }); + } + } + + const configuredPaths = new Set(resolvedCampaigns.map((c) => c.campaignPath)); + + // Migrate each configured campaign independently. + for (const { campaignPath } of resolvedCampaigns) { + try { + migrateCampaign({ repoRoot, campaignPath, report }); + } catch (err) { + report.orphans.push({ + folder: campaignPath, + reason: `Migration failed: ${err.message}`, + }); + } + } + + // Any folder under pages/ or stylesheets/ that is not a configured campaign + // is an orphan — leave it in place but surface it. + detectOrphans({ repoRoot, configuredPaths, report }); + + // Remove empty root legacy directories. + cleanEmptyRootDirs({ repoRoot, report }); + + return report; +} + +// --------------------------------------------------------------------------- +// Internal helpers +// --------------------------------------------------------------------------- + +function migrateCampaign({ repoRoot, campaignPath, report }) { + const srcPagesDir = path.join(repoRoot, 'pages', campaignPath); + const srcStylesDir = path.join(repoRoot, 'stylesheets', campaignPath); + const { pagesDir: dstPagesDir, stylesheetsDir: dstStylesDir } = + resolveCampaignPaths(repoRoot, campaignPath); + + if (isDir(srcPagesDir)) { + moveDirContents(srcPagesDir, dstPagesDir, null, report); + } + + if (isDir(srcStylesDir)) { + moveDirContents(srcStylesDir, dstStylesDir, campaignPath, report); + } +} + +/** + * Recursively moves the contents of `src` into `dst`. + * When `entryScssPrefix` is provided, a file named `.scss` + * is renamed to `main.scss` at its destination. + */ +function moveDirContents(src, dst, entryScssPrefix, report) { + fs.mkdirSync(dst, { recursive: true }); + + for (const entry of fs.readdirSync(src, { withFileTypes: true })) { + const srcPath = path.join(src, entry.name); + + if (entry.isDirectory()) { + moveDirContents( + srcPath, + path.join(dst, entry.name), + null, + report + ); + } else { + const dstName = + entryScssPrefix && entry.name === `${entryScssPrefix}.scss` + ? 'main.scss' + : entry.name; + const dstPath = path.join(dst, dstName); + + // Idempotency: skip files that already landed at the destination. + if (fs.existsSync(dstPath)) continue; + + fs.mkdirSync(path.dirname(dstPath), { recursive: true }); + fs.renameSync(srcPath, dstPath); + + if (entryScssPrefix && entry.name === `${entryScssPrefix}.scss`) { + report.renamed.push(dstPath); + } else { + report.moved.push(dstPath); + } + } + } + + // Remove the source directory if it is now empty. + try { + fs.rmdirSync(src); + } catch { + // Not empty (e.g. idempotent re-run with leftover entry .scss) — leave it. + } +} + +function detectOrphans({ repoRoot, configuredPaths, report }) { + for (const rootDir of ['pages', 'stylesheets']) { + const dir = path.join(repoRoot, rootDir); + if (!isDir(dir)) continue; + + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + if (entry.isDirectory() && !configuredPaths.has(entry.name)) { + report.orphans.push({ + folder: path.join(rootDir, entry.name), + reason: 'Not a configured campaign in .raisely.json', + }); + } + } + } +} + +function cleanEmptyRootDirs({ repoRoot, report }) { + for (const dir of ['pages', 'stylesheets']) { + const dirPath = path.join(repoRoot, dir); + if (!isDir(dirPath)) continue; + if (isDirEmpty(dirPath)) { + fs.rmdirSync(dirPath); + report.deleted.push(dirPath); + } + } +} + +function isDir(p) { + try { + return fs.statSync(p).isDirectory(); + } catch { + return false; + } +} + +function isDirEmpty(p) { + try { + return fs.readdirSync(p).length === 0; + } catch { + return false; + } +} + +// --------------------------------------------------------------------------- +// CLI command (default export — called by the actionBuilder in cli.js) +// --------------------------------------------------------------------------- + +export default async function migrateCommand() { + const config = await loadConfig(); + await getToken(program, config); + + br(); + log('Migrating repo to v2 layout…', 'white'); + br(); + + const loader = ora('Moving per-campaign content').start(); + + let report; + try { + report = await migrate({ + repoRoot: process.cwd(), + campaigns: config.campaigns ?? [], + getCampaign, + }); + } catch (err) { + loader.fail(`Migration failed: ${err.message}`); + process.exit(1); + } + + loader.succeed('Migration complete'); + br(); + + if (report.moved.length > 0) { + log(`Moved (${report.moved.length}):`, 'green'); + for (const f of report.moved) { + console.log(` ${chalk.green('+')} ${path.relative(process.cwd(), f)}`); + } + br(); + } + + if (report.renamed.length > 0) { + log(`Renamed to main.scss (${report.renamed.length}):`, 'cyan'); + for (const f of report.renamed) { + console.log(` ${chalk.cyan('~')} ${path.relative(process.cwd(), f)}`); + } + br(); + } + + if (report.deleted.length > 0) { + log(`Removed empty directories (${report.deleted.length}):`, 'yellow'); + for (const f of report.deleted) { + console.log( + ` ${chalk.yellow('-')} ${path.relative(process.cwd(), f)}` + ); + } + br(); + } + + if (report.orphans.length > 0) { + log(`Skipped orphan folders (${report.orphans.length}):`, 'red'); + for (const { folder, reason } of report.orphans) { + console.log(` ${chalk.red('!')} ${folder} — ${reason}`); + } + br(); + } + + if ( + report.moved.length === 0 && + report.renamed.length === 0 && + report.deleted.length === 0 && + report.orphans.length === 0 + ) { + log('Nothing to migrate — repo is already on the v2 layout.', 'green'); + br(); + } +} diff --git a/src/start.js b/src/start.js index 7fa6a6f..f2d9764 100644 --- a/src/start.js +++ b/src/start.js @@ -13,16 +13,173 @@ import { informLocalDev, } from './helpers.js'; import watch from 'node-watch'; +import { + detectLayout, + shouldRefuseLayoutForCommand, + getLegacyLayoutRefusalMessage, +} from './actions/layout.js'; import { uploadStyles } from './actions/campaigns.js'; import { updateComponentFile, updateComponentConfig, } from './actions/components.js'; +import { validateCampaignSass, validateComponent } from './actions/validate.js'; import { getToken } from './actions/auth.js'; import { loadConfig } from './config.js'; +function startLoader(message, oraImpl = ora) { + return oraImpl(message).start(); +} + +export async function handleCampaignChange( + filenameRaw, + { + campaignsDir, + token, + uploadStylesFn = uploadStyles, + validateCampaignSassFn = validateCampaignSass, + oraFn = ora, + } = {} +) { + const relative = path.relative(campaignsDir, filenameRaw); + const parts = relative.split(path.sep); + // Only handle stylesheet changes: /stylesheets/... + if (parts.length < 3 || parts[1] !== 'stylesheets') return; + const campaignPath = parts[0]; + const loader = startLoader(`Saving ${relative}`, oraFn); + const validation = await validateCampaignSassFn({ + campaign: campaignPath, + token, + }); + if (!validation.ok) { + loader.fail(validation.error); + return; + } + await uploadStylesFn(campaignPath); + loader.succeed(); +} + +export async function handleComponentChange( + filenameRaw, + { + componentsDir, + config, + fsModule = fs, + pathModule = path, + updateComponentFileFn = updateComponentFile, + updateComponentConfigFn = updateComponentConfig, + validateComponentFn = validateComponent, + oraFn = ora, + errorFn = error, + } = {} +) { + const filename = pathModule.relative(componentsDir, filenameRaw); + const componentName = filename.split(pathModule.sep)[0]; + if (!componentName) return; + const loader = startLoader(`Saving ${filename}`, oraFn); + const validation = await validateComponentFn({ name: componentName }); + if (!validation.ok) { + loader.fail(validation.error); + return; + } + + try { + if (filename.includes('.json')) { + await updateComponentConfigFn( + { + filename, + file: fsModule.readFileSync( + pathModule.join( + componentsDir, + filename.replace('.json', '.js') + ), + 'utf8' + ), + config: JSON.parse( + fsModule.readFileSync(pathModule.join(componentsDir, filename), 'utf8') + ), + }, + config + ); + } else { + await updateComponentFileFn( + { + filename, + file: fsModule.readFileSync(pathModule.join(componentsDir, filename), 'utf8'), + config: JSON.parse( + fsModule.readFileSync( + pathModule.join( + componentsDir, + filename.replace('.js', '.json') + ), + 'utf8' + ) + ), + }, + config + ); + } + } catch (e) { + return errorFn(e, loader); + } + + loader.succeed(); +} + +export function registerStartWatchers( + { + campaignsDir, + componentsDir, + config, + watchFn = watch, + } = {}, + dependencies = {} +) { + const campaignWatcher = watchFn( + campaignsDir, + { encoding: 'utf8', recursive: true }, + async (eventType, filenameRaw) => { + await handleCampaignChange(filenameRaw, { + campaignsDir, + token: config.token, + uploadStylesFn: dependencies.uploadStylesFn, + validateCampaignSassFn: dependencies.validateCampaignSassFn, + oraFn: dependencies.oraFn, + }); + } + ); + + const componentWatcher = watchFn( + componentsDir, + { encoding: 'utf8', recursive: true }, + async (eventType, filenameRaw) => { + await handleComponentChange(filenameRaw, { + componentsDir, + config, + fsModule: dependencies.fsModule, + pathModule: dependencies.pathModule, + updateComponentFileFn: dependencies.updateComponentFileFn, + updateComponentConfigFn: dependencies.updateComponentConfigFn, + validateComponentFn: dependencies.validateComponentFn, + oraFn: dependencies.oraFn, + errorFn: dependencies.errorFn, + }); + } + ); + + return { campaignWatcher, componentWatcher }; +} + export default async function start() { + const layout = detectLayout(process.cwd()); + if (shouldRefuseLayoutForCommand('start', layout)) { + br(); + log(getLegacyLayoutRefusalMessage('start', layout), 'red'); + process.exitCode = 1; + return; + } + welcome(); // load config @@ -45,75 +202,7 @@ export default async function start() { log(`Use CTRL + C to stop`, 'white'); // watch folders - const stylesDir = path.join(process.cwd(), 'stylesheets'); + const campaignsDir = path.join(process.cwd(), 'campaigns'); const componentsDir = path.join(process.cwd(), 'components'); - watch( - stylesDir, - { encoding: 'utf8', recursive: true }, - async (eventType, filenameRaw) => { - const filename = path.relative(stylesDir, filenameRaw); - const loader = ora(`Saving ${filename}`).start(); - - await uploadStyles(filename); - - loader.succeed(); - } - ); - - watch( - componentsDir, - { encoding: 'utf8', recursive: true }, - async (eventType, filenameRaw) => { - const filename = path.relative(componentsDir, filenameRaw); - const loader = ora(`Saving ${filename}`).start(); - - try { - if (filename.includes('.json')) { - await updateComponentConfig( - { - filename, - file: fs.readFileSync( - path.join( - componentsDir, - filename.replace('.json', '.js') - ), - 'utf8' - ), - config: JSON.parse( - fs.readFileSync( - path.join(componentsDir, filename), - 'utf8' - ) - ), - }, - config - ); - } else { - const result = await updateComponentFile( - { - filename, - file: fs.readFileSync( - path.join(componentsDir, filename), - 'utf8' - ), - config: JSON.parse( - fs.readFileSync( - path.join( - componentsDir, - filename.replace('.js', '.json') - ), - 'utf8' - ) - ), - }, - config - ); - } - } catch (e) { - return error(e, loader); - } - - loader.succeed(); - } - ); + registerStartWatchers({ campaignsDir, componentsDir, config }); } diff --git a/src/telemetry.js b/src/telemetry.js new file mode 100644 index 0000000..de3532f --- /dev/null +++ b/src/telemetry.js @@ -0,0 +1,182 @@ +import fetch from 'node-fetch'; +import https from 'https'; +import { randomUUID } from 'crypto'; + +import { loadConfig } from './config.js'; +import { getCredentials, resolveOrganisationContext } from './credentials.js'; +import { getPackageInfo } from './helpers.js'; + +const pending = new Set(); +const devHttpsAgent = new https.Agent({ + rejectUnauthorized: false, +}); +const TELEMETRY_REQUEST_TIMEOUT_MS = 10_000; + +let cachedMetadata; +let metadataPromise; +let sessionId = generateSessionId(); + +function generateSessionId() { + try { + return randomUUID(); + } catch { + return `${Date.now()}-${Math.random().toString(16).slice(2)}`; + } +} + +function telemetryDisabled() { + const raw = process.env.RAISELY_NO_TELEMETRY; + if (!raw) return false; + const normalized = String(raw).trim().toLowerCase(); + return normalized === '1' || normalized === 'true'; +} + +function parseAuthenticateResponse(payload) { + if (!payload || typeof payload !== 'object') { + return {}; + } + const data = payload.data && typeof payload.data === 'object' ? payload.data : payload; + return { + userUuid: data.userUuid, + organisationUuid: data.organisationUuid, + }; +} + +async function fetchAuthenticateContext({ apiUrl, token }) { + if (!apiUrl || !token) { + return {}; + } + try { + const signal = AbortSignal.timeout(TELEMETRY_REQUEST_TIMEOUT_MS); + const response = await fetch(`${apiUrl.replace(/\/$/, '')}/v3/authenticate`, { + method: 'GET', + headers: { + Authorization: `Bearer ${token}`, + 'x-raisely-client': 'cli', + }, + signal, + agent: devHttpsAgent, + }); + if (!response.ok) { + return {}; + } + const payload = await response.json(); + return parseAuthenticateResponse(payload); + } catch { + return {}; + } +} + +async function resolveTelemetryMetadata() { + if (cachedMetadata) { + return cachedMetadata; + } + if (!metadataPromise) { + metadataPromise = (async () => { + const config = await loadConfig({ allowEmpty: true }); + const campaignIds = Array.isArray(config.campaigns) + ? config.campaigns.filter(Boolean) + : []; + const ctx = await resolveOrganisationContext(); + const organisationUuid = ctx?.organisationUuid || config.organisationUuid; + const apiUrl = ctx?.apiUrl || config.apiUrl; + + let token; + try { + const credentials = await getCredentials({ allowPrompt: false }); + token = credentials.token; + } catch { + token = null; + } + + const authContext = await fetchAuthenticateContext({ apiUrl, token }); + const pkg = getPackageInfo(); + return { + apiUrl, + token, + campaignIds, + organisationUuid: + authContext.organisationUuid || organisationUuid || undefined, + userUuid: authContext.userUuid || undefined, + cliVersion: pkg.version || undefined, + nodeVersion: process.version, + platform: process.platform, + }; + })(); + } + try { + cachedMetadata = await metadataPromise; + } catch { + cachedMetadata = { + campaignIds: [], + nodeVersion: process.version, + platform: process.platform, + }; + } + return cachedMetadata; +} + +async function sendPayload(eventName, traits = {}) { + const metadata = await resolveTelemetryMetadata(); + if (!metadata.apiUrl) { + return; + } + + const payload = { + event: eventName, + sessionId, + ...(metadata.organisationUuid + ? { organisationUuid: metadata.organisationUuid } + : {}), + ...(metadata.userUuid ? { userUuid: metadata.userUuid } : {}), + ...(metadata.campaignIds[0] ? { campaignUuid: metadata.campaignIds[0] } : {}), + traits: { + outcome: traits.outcome, + durationMs: traits.durationMs, + errorCode: traits.errorCode, + ...traits, + campaignIds: metadata.campaignIds, + cliVersion: metadata.cliVersion, + nodeVersion: metadata.nodeVersion, + platform: metadata.platform, + }, + }; + + const signal = AbortSignal.timeout(TELEMETRY_REQUEST_TIMEOUT_MS); + await fetch(`${metadata.apiUrl.replace(/\/$/, '')}/v3/t`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-raisely-client': 'cli', + ...(metadata.token ? { Authorization: `Bearer ${metadata.token}` } : {}), + }, + body: JSON.stringify(payload), + signal, + agent: devHttpsAgent, + }); +} + +export function trackEvent(eventName, traits = {}) { + if (telemetryDisabled()) { + return; + } + const promise = sendPayload(eventName, traits).catch(() => {}); + pending.add(promise); + promise.finally(() => { + pending.delete(promise); + }); +} + +export async function flushTelemetry() { + if (pending.size === 0) { + return; + } + await Promise.allSettled([...pending]); +} + +export function __resetTelemetryForTests() { + pending.clear(); + cachedMetadata = undefined; + metadataPromise = undefined; + sessionId = generateSessionId(); +} diff --git a/src/update.js b/src/update.js index f3a4e13..c46d16e 100644 --- a/src/update.js +++ b/src/update.js @@ -3,22 +3,33 @@ import chalk from 'chalk'; import inquirer from 'inquirer'; import { welcome, log, br, error, informUpdate } from './helpers.js'; -import { syncStyles, syncComponents } from './actions/sync.js'; +import { syncStyles, syncComponents, syncPages } from './actions/sync.js'; +import { + detectLayout, + shouldRefuseLayoutForCommand, + getLegacyLayoutRefusalMessage, +} from './actions/layout.js'; import { loadConfig } from './config.js'; import { getToken } from './actions/auth.js'; -export default async function update() { +export default async function update(options = {}) { + const layout = detectLayout(process.cwd()); + if (shouldRefuseLayoutForCommand('update', layout)) { + br(); + log(getLegacyLayoutRefusalMessage('update', layout), 'red'); + process.exitCode = 1; + return; + } + // load config let config = await loadConfig(); // Load token, which will prompt a login if the token is expired await getToken(program, config, true); - const data = {}; - welcome(); log( - `You are about to update the styles and components in this directory`, + `You are about to update the styles, components, and pages in this directory`, 'white' ); br(); @@ -32,18 +43,19 @@ export default async function update() { log(`You will lose any unsaved changes.`, 'white'); br(); - // collect login details - const response = await inquirer.prompt([ - { - type: 'confirm', - name: 'confirm', - message: 'Are you sure you want to continue?', - }, - ]); + if (!config.cli && !options.force) { + const response = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirm', + message: 'Are you sure you want to continue?', + }, + ]); - if (!response.confirm) { - br(); - return log('Update aborted', 'red'); + if (!response.confirm) { + br(); + return log('Update aborted', 'red'); + } } // sync down campaign stylesheets @@ -52,6 +64,9 @@ export default async function update() { // sync down custom components await syncComponents(); + // sync down campaign pages + await syncPages(); + br(); log( `All done! Run ${chalk.bold.underline.white( diff --git a/tests/campaigns.test.js b/tests/campaigns.test.js new file mode 100644 index 0000000..8247ecb --- /dev/null +++ b/tests/campaigns.test.js @@ -0,0 +1,252 @@ +import { describe, test } from 'vitest'; +import assert from 'node:assert/strict'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import glob from 'glob-promise'; + +import { fetchStyles, processStyles } from '../src/actions/campaigns.js'; +import { compileAllLocalPages } from '../src/actions/pages.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const fixturesDir = path.join(__dirname, 'fixtures'); +const originalCwd = process.cwd(); + +/** + * Run fn() with process.cwd() pointing at fixtures/, then restore. + */ +function withFixture(fixtureName, fn) { + return async () => { + process.chdir(path.join(fixturesDir, fixtureName)); + try { + await fn(); + } finally { + process.chdir(originalCwd); + } + }; +} + +// --------------------------------------------------------------------------- +// fetchStyles +// --------------------------------------------------------------------------- + +describe('fetchStyles – v2 layout', () => { + test( + 'reads main.scss as css for my-campaign', + withFixture('v2', async () => { + const result = await fetchStyles({ campaign: 'my-campaign' }); + assert.ok( + result.css.includes('.raisely-campaign'), + 'css should contain .raisely-campaign rule' + ); + }) + ); + + test( + 'excludes main.scss from configFiles', + withFixture('v2', async () => { + const result = await fetchStyles({ campaign: 'my-campaign' }); + assert.ok( + !Object.prototype.hasOwnProperty.call(result.configFiles, 'main.scss'), + 'main.scss should not appear in configFiles' + ); + }) + ); + + test( + 'includes _variables.scss in configFiles', + withFixture('v2', async () => { + const result = await fetchStyles({ campaign: 'my-campaign' }); + assert.ok( + Object.prototype.hasOwnProperty.call( + result.configFiles, + '_variables.scss' + ), + '_variables.scss should be present in configFiles' + ); + assert.ok( + result.configFiles['_variables.scss'].includes('$primary'), + '_variables.scss content should include $primary' + ); + }) + ); + + test( + 'returns empty configFiles when campaign has no partials', + withFixture('v2', async () => { + const result = await fetchStyles({ campaign: 'spring_2026' }); + assert.deepEqual( + result.configFiles, + {}, + 'spring_2026 has no partials' + ); + }) + ); + + test( + 'reads each campaign independently in a multi-campaign repo', + withFixture('v2-multi', async () => { + const one = await fetchStyles({ campaign: 'campaign-one' }); + const two = await fetchStyles({ campaign: 'campaign-two' }); + assert.ok( + one.css.includes('#e63946'), + 'campaign-one css should include #e63946' + ); + assert.ok( + two.css.includes('#457b9d'), + 'campaign-two css should include #457b9d' + ); + assert.notEqual( + one.css, + two.css, + 'each campaign should have its own distinct css' + ); + }) + ); + + test( + 'campaign-two has no partials in multi-campaign repo', + withFixture('v2-multi', async () => { + const result = await fetchStyles({ campaign: 'campaign-two' }); + assert.deepEqual(result.configFiles, {}); + }) + ); +}); + +// --------------------------------------------------------------------------- +// processStyles +// --------------------------------------------------------------------------- + +describe('processStyles – v2 layout', () => { + test( + 'concatenates main.scss and partials for my-campaign', + withFixture('v2', async () => { + const output = await processStyles({ campaign: 'my-campaign' }); + assert.ok( + output.includes('.raisely-campaign'), + 'output should include main scss rule' + ); + assert.ok( + output.includes('$primary'), + 'output should include content from _variables.scss partial' + ); + }) + ); + + test( + 'returns only main.scss content when no partials exist', + withFixture('v2', async () => { + const output = await processStyles({ campaign: 'spring_2026' }); + assert.ok(output.includes('.raisely-campaign')); + assert.ok(output.includes('#0b7a75')); + }) + ); + + test( + 'returns independent output for each campaign in multi-campaign repo', + withFixture('v2-multi', async () => { + const one = await processStyles({ campaign: 'campaign-one' }); + const two = await processStyles({ campaign: 'campaign-two' }); + assert.notEqual(one, two); + }) + ); +}); + +// --------------------------------------------------------------------------- +// compileAllLocalPages +// --------------------------------------------------------------------------- + +describe('compileAllLocalPages – v2 layout', () => { + test( + 'compiles pages from campaigns/*/pages/**/*.json in single-campaign repo', + withFixture('v2', async () => { + const map = await compileAllLocalPages(); + assert.ok( + Object.prototype.hasOwnProperty.call(map, 'page-uuid-001'), + 'map should include page-uuid-001 from my-campaign' + ); + assert.ok( + Object.prototype.hasOwnProperty.call(map, 'page-uuid-special-v2'), + 'map should include page-uuid-special-v2 from spring_2026' + ); + }) + ); + + test( + 'filters pages by campaignUuid when provided', + withFixture('v2', async () => { + const map = await compileAllLocalPages({ + campaignUuid: 'campaign-uuid-my-campaign', + }); + assert.ok( + Object.prototype.hasOwnProperty.call(map, 'page-uuid-001') + ); + assert.ok( + !Object.prototype.hasOwnProperty.call(map, 'page-uuid-special-v2'), + 'page from a different campaign should be excluded' + ); + }) + ); + + test( + 'compiles pages from all campaigns in a multi-campaign repo', + withFixture('v2-multi', async () => { + const map = await compileAllLocalPages(); + assert.ok( + Object.prototype.hasOwnProperty.call(map, 'page-uuid-001'), + 'map should include page from campaign-one' + ); + assert.ok( + Object.prototype.hasOwnProperty.call(map, 'page-uuid-002'), + 'map should include page from campaign-two' + ); + }) + ); + + test( + 'returns empty map when campaigns/ directory does not exist', + withFixture('legacy', async () => { + const map = await compileAllLocalPages(); + assert.deepEqual(map, {}); + }) + ); +}); + +// --------------------------------------------------------------------------- +// deploy-time page glob +// --------------------------------------------------------------------------- + +describe('deploy-time page glob – campaigns/*/pages/**/*.json', () => { + test('finds all page files in a single-campaign v2 repo', async () => { + const fixturePath = path.join(fixturesDir, 'v2'); + const files = await glob('campaigns/*/pages/**/*.json', { + cwd: fixturePath, + }); + assert.equal(files.length, 2, 'should find home.json for both campaigns'); + assert.ok( + files.some((f) => f.includes('my-campaign')), + 'should find my-campaign page' + ); + assert.ok( + files.some((f) => f.includes('spring_2026')), + 'should find spring_2026 page' + ); + }); + + test('finds pages from all campaigns in a multi-campaign v2 repo', async () => { + const fixturePath = path.join(fixturesDir, 'v2-multi'); + const files = await glob('campaigns/*/pages/**/*.json', { + cwd: fixturePath, + }); + assert.equal(files.length, 2); + assert.ok(files.some((f) => f.includes('campaign-one'))); + assert.ok(files.some((f) => f.includes('campaign-two'))); + }); + + test('returns no files for a legacy repo', async () => { + const fixturePath = path.join(fixturesDir, 'legacy'); + const files = await glob('campaigns/*/pages/**/*.json', { + cwd: fixturePath, + }); + assert.equal(files.length, 0); + }); +}); diff --git a/tests/command-layout-guard.test.js b/tests/command-layout-guard.test.js new file mode 100644 index 0000000..b1da8d3 --- /dev/null +++ b/tests/command-layout-guard.test.js @@ -0,0 +1,120 @@ +import { test } from 'vitest'; +import assert from 'node:assert/strict'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import update from '../src/update.js'; +import deploy from '../src/deploy.js'; +import local from '../src/local.js'; +import start from '../src/start.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const fixturesDir = path.join(__dirname, 'fixtures'); +const originalCwd = process.cwd(); + +async function runInFixture(fixtureName, commandFn) { + const logs = []; + const originalLog = console.log; + const previousExitCode = process.exitCode; + let exitCodeAtReturn; + + process.chdir(path.join(fixturesDir, fixtureName)); + process.exitCode = undefined; + console.log = (...args) => logs.push(args.join(' ')); + + try { + await commandFn(); + exitCodeAtReturn = process.exitCode; + } catch { + // Commands that pass the layout gate will throw when they hit auth or + // network calls with no real credentials. Capture the exit code as it + // stood when the throw happened; that is enough to verify the gate. + exitCodeAtReturn = process.exitCode; + } finally { + console.log = originalLog; + process.chdir(originalCwd); + process.exitCode = previousExitCode; + } + + return { output: logs.join('\n'), exitCode: exitCodeAtReturn }; +} + +function runInLegacyFixture(commandFn) { + return runInFixture('legacy', commandFn); +} + +function runInV2Fixture(commandFn) { + return runInFixture('v2', commandFn); +} + +test('update refuses to run on legacy layout', async () => { + const { output, exitCode } = await runInLegacyFixture(() => + update({ force: true }) + ); + assert.equal(exitCode, 1); + assert.match(output, /Cannot run `raisely update`/); + assert.match(output, /npm install -g @raisely\/cli@2/); + assert.match(output, /raisely migrate/); +}); + +test('deploy refuses to run on legacy layout', async () => { + const { output, exitCode } = await runInLegacyFixture(() => + deploy({ force: true }) + ); + assert.equal(exitCode, 1); + assert.match(output, /Cannot run `raisely deploy`/); + assert.match(output, /npm install -g @raisely\/cli@2/); + assert.match(output, /raisely migrate/); +}); + +test('local refuses to run on legacy layout', async () => { + const { output, exitCode } = await runInLegacyFixture(() => + local({ open: false }) + ); + assert.equal(exitCode, 1); + assert.match(output, /Cannot run `raisely local`/); + assert.match(output, /npm install -g @raisely\/cli@2/); + assert.match(output, /raisely migrate/); +}); + +test('start refuses to run on legacy layout', async () => { + const { output, exitCode } = await runInLegacyFixture(() => start()); + assert.equal(exitCode, 1); + assert.match(output, /Cannot run `raisely start`/); + assert.match(output, /npm install -g @raisely\/cli@2/); + assert.match(output, /raisely migrate/); +}); + +// --------------------------------------------------------------------------- +// v2 fixture: commands pass the layout gate +// --------------------------------------------------------------------------- + +test('update passes the layout gate on a v2 fixture', async () => { + const { output, exitCode } = await runInV2Fixture(() => + update({ force: true }) + ); + assert.notEqual(exitCode, 1); + assert.doesNotMatch(output, /Cannot run `raisely update`/); +}); + +test('deploy passes the layout gate on a v2 fixture', async () => { + const { output, exitCode } = await runInV2Fixture(() => + deploy({ force: true }) + ); + assert.notEqual(exitCode, 1); + assert.doesNotMatch(output, /Cannot run `raisely deploy`/); +}); + +test('local passes the layout gate on a v2 fixture', async () => { + const { output, exitCode } = await runInV2Fixture(() => + local({ open: false }) + ); + assert.notEqual(exitCode, 1); + assert.doesNotMatch(output, /Cannot run `raisely local`/); +}); + +test('start passes the layout gate on a v2 fixture', async () => { + const { output, exitCode } = await runInV2Fixture(() => start()); + assert.notEqual(exitCode, 1); + assert.doesNotMatch(output, /Cannot run `raisely start`/); +}); diff --git a/tests/deploy.test.js b/tests/deploy.test.js new file mode 100644 index 0000000..1032716 --- /dev/null +++ b/tests/deploy.test.js @@ -0,0 +1,270 @@ +import { beforeEach, describe, expect, test, vi } from 'vitest'; + +const mocks = vi.hoisted(() => { + const fs = { + existsSync: vi.fn(), + readdirSync: vi.fn(), + readFileSync: vi.fn(), + }; + + const ora = vi.fn((label) => { + const loader = { + label, + start: vi.fn(), + succeed: vi.fn(), + fail: vi.fn(), + warn: vi.fn(), + }; + loader.start.mockReturnValue(loader); + return loader; + }); + + return { + detectLayout: vi.fn(), + shouldRefuseLayoutForCommand: vi.fn(), + getLegacyLayoutRefusalMessage: vi.fn(), + loadConfig: vi.fn(), + getToken: vi.fn(), + getCampaign: vi.fn(), + uploadStyles: vi.fn(), + updateComponentConfig: vi.fn(), + updateComponentFile: vi.fn(), + uploadPage: vi.fn(), + validateCampaignSass: vi.fn(), + validateComponent: vi.fn(), + inquirerPrompt: vi.fn(), + glob: vi.fn(), + welcome: vi.fn(), + log: vi.fn(), + br: vi.fn(), + informUpdate: vi.fn(), + ora, + fs, + }; +}); + +vi.mock('inquirer', () => ({ + default: { + prompt: mocks.inquirerPrompt, + }, +})); + +vi.mock('ora', () => ({ + default: mocks.ora, +})); + +vi.mock('glob-promise', () => ({ + default: mocks.glob, +})); + +vi.mock('fs', () => ({ + default: mocks.fs, + ...mocks.fs, +})); + +vi.mock('../src/helpers.js', () => ({ + welcome: mocks.welcome, + log: mocks.log, + br: mocks.br, + informUpdate: mocks.informUpdate, +})); + +vi.mock('../src/actions/layout.js', () => ({ + detectLayout: mocks.detectLayout, + shouldRefuseLayoutForCommand: mocks.shouldRefuseLayoutForCommand, + getLegacyLayoutRefusalMessage: mocks.getLegacyLayoutRefusalMessage, +})); + +vi.mock('../src/config.js', () => ({ + loadConfig: mocks.loadConfig, +})); + +vi.mock('../src/actions/auth.js', () => ({ + getToken: mocks.getToken, +})); + +vi.mock('../src/actions/campaigns.js', () => ({ + getCampaign: mocks.getCampaign, + uploadStyles: mocks.uploadStyles, +})); + +vi.mock('../src/actions/components.js', () => ({ + updateComponentConfig: mocks.updateComponentConfig, + updateComponentFile: mocks.updateComponentFile, +})); + +vi.mock('../src/actions/pages.js', () => ({ + uploadPage: mocks.uploadPage, +})); + +vi.mock('../src/actions/validate.js', () => ({ + validateCampaignSass: mocks.validateCampaignSass, + validateComponent: mocks.validateComponent, +})); + +import deploy from '../src/deploy.js'; + +function createDirent(name) { + return { + name, + isDirectory() { + return true; + }, + }; +} + +function setDefaultMocks() { + mocks.detectLayout.mockReturnValue('v2'); + mocks.shouldRefuseLayoutForCommand.mockReturnValue(false); + mocks.getLegacyLayoutRefusalMessage.mockReturnValue('layout message'); + mocks.loadConfig.mockResolvedValue({ + campaigns: ['campaign-uuid'], + cli: true, + }); + mocks.getToken.mockResolvedValue('token-123'); + mocks.getCampaign.mockResolvedValue({ + data: { uuid: 'campaign-uuid', path: 'my-campaign' }, + }); + mocks.uploadStyles.mockResolvedValue(undefined); + mocks.updateComponentConfig.mockResolvedValue(undefined); + mocks.updateComponentFile.mockResolvedValue(undefined); + mocks.uploadPage.mockResolvedValue(undefined); + mocks.validateCampaignSass.mockResolvedValue({ ok: true }); + mocks.validateComponent.mockResolvedValue({ ok: true }); + mocks.inquirerPrompt.mockResolvedValue({ confirm: true }); + mocks.glob.mockResolvedValue([]); + mocks.informUpdate.mockResolvedValue(undefined); + mocks.fs.existsSync.mockReturnValue(true); + mocks.fs.readFileSync.mockReturnValue(''); + mocks.fs.readdirSync.mockImplementation((target, options) => { + if (options && options.withFileTypes) return []; + if (target === '/repo/components') return []; + return []; + }); +} + +describe('deploy command', () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.spyOn(process, 'cwd').mockReturnValue('/repo'); + process.exitCode = undefined; + setDefaultMocks(); + }); + + test('validation failure prints all errors and blocks uploads', async () => { + mocks.fs.readdirSync.mockImplementation((target, options) => { + if (options && options.withFileTypes) return [createDirent('hero')]; + if (target === '/repo/components') return []; + return []; + }); + mocks.validateCampaignSass.mockResolvedValue({ + ok: false, + error: 'SassError: Expected "}"', + }); + mocks.validateComponent.mockResolvedValue({ + ok: false, + error: 'Unexpected token (4:9)', + }); + + await deploy({}); + + expect(mocks.uploadStyles).not.toHaveBeenCalled(); + expect(process.exitCode).toBe(1); + expect(mocks.log).toHaveBeenCalledWith( + 'Campaign my-campaign: SassError: Expected "}"', + 'red' + ); + expect(mocks.log).toHaveBeenCalledWith( + 'Component hero: Unexpected token (4:9)', + 'red' + ); + }); + + test('--no-validate skips the gate and continues deploy', async () => { + await deploy({ validate: false }); + + expect(mocks.validateCampaignSass).not.toHaveBeenCalled(); + expect(mocks.validateComponent).not.toHaveBeenCalled(); + expect(mocks.uploadStyles).toHaveBeenCalledTimes(1); + }); + + test('network failure during validation blocks deploy with exit code 1', async () => { + mocks.validateCampaignSass.mockResolvedValue({ + ok: false, + error: 'socket hang up', + }); + + await deploy({}); + + expect(mocks.uploadStyles).not.toHaveBeenCalled(); + expect(process.exitCode).toBe(1); + expect(mocks.log).toHaveBeenCalledWith( + 'Campaign my-campaign: socket hang up', + 'red' + ); + }); + + test('campaign lookup failure fails validation gracefully', async () => { + mocks.getCampaign.mockRejectedValue(new Error('gateway timeout')); + + await deploy({}); + + expect(process.exitCode).toBe(1); + expect(mocks.validateCampaignSass).not.toHaveBeenCalled(); + expect(mocks.uploadStyles).not.toHaveBeenCalled(); + expect(mocks.log).toHaveBeenCalledWith( + 'Campaign lookup failed: gateway timeout', + 'red' + ); + }); + + test('malformed validator result fails deploy gracefully', async () => { + mocks.validateCampaignSass.mockResolvedValue(undefined); + + await deploy({}); + + expect(mocks.uploadStyles).not.toHaveBeenCalled(); + expect(process.exitCode).toBe(1); + expect(mocks.log).toHaveBeenCalledWith( + 'Campaign my-campaign: SASS validator returned an invalid response.', + 'red' + ); + }); + + test('cli=true still runs validation gate before uploads', async () => { + await deploy({}); + + expect(mocks.validateCampaignSass).toHaveBeenCalledTimes(1); + expect(mocks.validateComponent).not.toHaveBeenCalled(); + expect(mocks.uploadStyles).toHaveBeenCalledTimes(1); + }); + + test('--force still runs validation and blocks on failure', async () => { + mocks.loadConfig.mockResolvedValue({ + campaigns: ['campaign-uuid'], + cli: false, + }); + mocks.validateCampaignSass.mockResolvedValue({ + ok: false, + error: 'SassError: invalid selector', + }); + + await deploy({ force: true }); + + expect(mocks.validateCampaignSass).toHaveBeenCalledTimes(1); + expect(mocks.uploadStyles).not.toHaveBeenCalled(); + expect(process.exitCode).toBe(1); + }); + + test('missing components directory does not crash deploy', async () => { + mocks.fs.existsSync.mockImplementation( + (target) => target !== '/repo/components' + ); + + await deploy({}); + + expect(mocks.uploadStyles).toHaveBeenCalledTimes(1); + expect(mocks.uploadPage).not.toHaveBeenCalled(); + expect(process.exitCode).toBeUndefined(); + }); +}); diff --git a/tests/fixtures/empty/components/my-widget/my-widget.js b/tests/fixtures/empty/components/my-widget/my-widget.js new file mode 100644 index 0000000..cf2a9a7 --- /dev/null +++ b/tests/fixtures/empty/components/my-widget/my-widget.js @@ -0,0 +1,3 @@ +const MyWidget = () => ( +
Hello from MyWidget
+); diff --git a/tests/fixtures/legacy-multi/pages/campaign-one/home.json b/tests/fixtures/legacy-multi/pages/campaign-one/home.json new file mode 100644 index 0000000..bcf936d --- /dev/null +++ b/tests/fixtures/legacy-multi/pages/campaign-one/home.json @@ -0,0 +1,7 @@ +{ + "uuid": "page-uuid-001", + "path": "/", + "title": "Home", + "name": "home", + "status": "published" +} diff --git a/tests/fixtures/legacy-multi/pages/campaign-two/home.json b/tests/fixtures/legacy-multi/pages/campaign-two/home.json new file mode 100644 index 0000000..f9801e9 --- /dev/null +++ b/tests/fixtures/legacy-multi/pages/campaign-two/home.json @@ -0,0 +1,7 @@ +{ + "uuid": "page-uuid-002", + "path": "/", + "title": "Home", + "name": "home", + "status": "published" +} diff --git a/tests/fixtures/legacy-multi/stylesheets/campaign-one/campaign-one.scss b/tests/fixtures/legacy-multi/stylesheets/campaign-one/campaign-one.scss new file mode 100644 index 0000000..da11185 --- /dev/null +++ b/tests/fixtures/legacy-multi/stylesheets/campaign-one/campaign-one.scss @@ -0,0 +1,3 @@ +.raisely-campaign { + color: #e63946; +} diff --git a/tests/fixtures/legacy-multi/stylesheets/campaign-two/campaign-two.scss b/tests/fixtures/legacy-multi/stylesheets/campaign-two/campaign-two.scss new file mode 100644 index 0000000..f26ebac --- /dev/null +++ b/tests/fixtures/legacy-multi/stylesheets/campaign-two/campaign-two.scss @@ -0,0 +1,3 @@ +.raisely-campaign { + color: #457b9d; +} diff --git a/tests/fixtures/legacy-only-main-scss/pages/my-campaign/home.json b/tests/fixtures/legacy-only-main-scss/pages/my-campaign/home.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/tests/fixtures/legacy-only-main-scss/pages/my-campaign/home.json @@ -0,0 +1 @@ +{} diff --git a/tests/fixtures/legacy-only-main-scss/stylesheets/my-campaign/main.scss b/tests/fixtures/legacy-only-main-scss/stylesheets/my-campaign/main.scss new file mode 100644 index 0000000..c324bd7 --- /dev/null +++ b/tests/fixtures/legacy-only-main-scss/stylesheets/my-campaign/main.scss @@ -0,0 +1 @@ +// pre-existing main diff --git a/tests/fixtures/legacy/pages/my-campaign/home.json b/tests/fixtures/legacy/pages/my-campaign/home.json new file mode 100644 index 0000000..bcf936d --- /dev/null +++ b/tests/fixtures/legacy/pages/my-campaign/home.json @@ -0,0 +1,7 @@ +{ + "uuid": "page-uuid-001", + "path": "/", + "title": "Home", + "name": "home", + "status": "published" +} diff --git a/tests/fixtures/legacy/pages/spring_2026/home.json b/tests/fixtures/legacy/pages/spring_2026/home.json new file mode 100644 index 0000000..b200727 --- /dev/null +++ b/tests/fixtures/legacy/pages/spring_2026/home.json @@ -0,0 +1,7 @@ +{ + "uuid": "page-uuid-special-legacy", + "path": "/", + "title": "Spring Campaign Home", + "name": "home", + "status": "published" +} diff --git a/tests/fixtures/legacy/stylesheets/my-campaign/_variables.scss b/tests/fixtures/legacy/stylesheets/my-campaign/_variables.scss new file mode 100644 index 0000000..288ae15 --- /dev/null +++ b/tests/fixtures/legacy/stylesheets/my-campaign/_variables.scss @@ -0,0 +1,2 @@ +$primary: #e63946; +$secondary: #457b9d; diff --git a/tests/fixtures/legacy/stylesheets/my-campaign/my-campaign.scss b/tests/fixtures/legacy/stylesheets/my-campaign/my-campaign.scss new file mode 100644 index 0000000..d91ad58 --- /dev/null +++ b/tests/fixtures/legacy/stylesheets/my-campaign/my-campaign.scss @@ -0,0 +1,5 @@ +@import 'variables'; + +.raisely-campaign { + color: $primary; +} diff --git a/tests/fixtures/legacy/stylesheets/my-campaign/theme/my-campaign.scss b/tests/fixtures/legacy/stylesheets/my-campaign/theme/my-campaign.scss new file mode 100644 index 0000000..eebf211 --- /dev/null +++ b/tests/fixtures/legacy/stylesheets/my-campaign/theme/my-campaign.scss @@ -0,0 +1 @@ +// nested diff --git a/tests/fixtures/legacy/stylesheets/spring_2026/spring_2026.scss b/tests/fixtures/legacy/stylesheets/spring_2026/spring_2026.scss new file mode 100644 index 0000000..ab3b83e --- /dev/null +++ b/tests/fixtures/legacy/stylesheets/spring_2026/spring_2026.scss @@ -0,0 +1,5 @@ +@import 'variables'; + +.raisely-campaign { + color: #0b7a75; +} diff --git a/tests/fixtures/mixed/campaigns/new-campaign/pages/donate.json b/tests/fixtures/mixed/campaigns/new-campaign/pages/donate.json new file mode 100644 index 0000000..89f8090 --- /dev/null +++ b/tests/fixtures/mixed/campaigns/new-campaign/pages/donate.json @@ -0,0 +1,7 @@ +{ + "uuid": "page-uuid-002", + "path": "/donate", + "title": "Donate", + "name": "donate", + "status": "published" +} diff --git a/tests/fixtures/mixed/campaigns/new-campaign/stylesheets/main.scss b/tests/fixtures/mixed/campaigns/new-campaign/stylesheets/main.scss new file mode 100644 index 0000000..f26ebac --- /dev/null +++ b/tests/fixtures/mixed/campaigns/new-campaign/stylesheets/main.scss @@ -0,0 +1,3 @@ +.raisely-campaign { + color: #457b9d; +} diff --git a/tests/fixtures/mixed/pages/old-campaign/home.json b/tests/fixtures/mixed/pages/old-campaign/home.json new file mode 100644 index 0000000..bcf936d --- /dev/null +++ b/tests/fixtures/mixed/pages/old-campaign/home.json @@ -0,0 +1,7 @@ +{ + "uuid": "page-uuid-001", + "path": "/", + "title": "Home", + "name": "home", + "status": "published" +} diff --git a/tests/fixtures/mixed/stylesheets/old-campaign/old-campaign.scss b/tests/fixtures/mixed/stylesheets/old-campaign/old-campaign.scss new file mode 100644 index 0000000..da11185 --- /dev/null +++ b/tests/fixtures/mixed/stylesheets/old-campaign/old-campaign.scss @@ -0,0 +1,3 @@ +.raisely-campaign { + color: #e63946; +} diff --git a/tests/fixtures/v2-multi/campaigns/campaign-one/pages/home.json b/tests/fixtures/v2-multi/campaigns/campaign-one/pages/home.json new file mode 100644 index 0000000..c20ac53 --- /dev/null +++ b/tests/fixtures/v2-multi/campaigns/campaign-one/pages/home.json @@ -0,0 +1,9 @@ +{ + "uuid": "page-uuid-001", + "path": "/", + "title": "Home", + "name": "home", + "status": "published", + "body": [], + "campaignUuid": "campaign-uuid-one" +} diff --git a/tests/fixtures/v2-multi/campaigns/campaign-one/stylesheets/main.scss b/tests/fixtures/v2-multi/campaigns/campaign-one/stylesheets/main.scss new file mode 100644 index 0000000..da11185 --- /dev/null +++ b/tests/fixtures/v2-multi/campaigns/campaign-one/stylesheets/main.scss @@ -0,0 +1,3 @@ +.raisely-campaign { + color: #e63946; +} diff --git a/tests/fixtures/v2-multi/campaigns/campaign-two/pages/home.json b/tests/fixtures/v2-multi/campaigns/campaign-two/pages/home.json new file mode 100644 index 0000000..804fa98 --- /dev/null +++ b/tests/fixtures/v2-multi/campaigns/campaign-two/pages/home.json @@ -0,0 +1,9 @@ +{ + "uuid": "page-uuid-002", + "path": "/", + "title": "Home", + "name": "home", + "status": "published", + "body": [], + "campaignUuid": "campaign-uuid-two" +} diff --git a/tests/fixtures/v2-multi/campaigns/campaign-two/stylesheets/main.scss b/tests/fixtures/v2-multi/campaigns/campaign-two/stylesheets/main.scss new file mode 100644 index 0000000..f26ebac --- /dev/null +++ b/tests/fixtures/v2-multi/campaigns/campaign-two/stylesheets/main.scss @@ -0,0 +1,3 @@ +.raisely-campaign { + color: #457b9d; +} diff --git a/tests/fixtures/v2/campaigns/my-campaign/pages/home.json b/tests/fixtures/v2/campaigns/my-campaign/pages/home.json new file mode 100644 index 0000000..be1d120 --- /dev/null +++ b/tests/fixtures/v2/campaigns/my-campaign/pages/home.json @@ -0,0 +1,9 @@ +{ + "uuid": "page-uuid-001", + "path": "/", + "title": "Home", + "name": "home", + "status": "published", + "body": [], + "campaignUuid": "campaign-uuid-my-campaign" +} diff --git a/tests/fixtures/v2/campaigns/my-campaign/stylesheets/_variables.scss b/tests/fixtures/v2/campaigns/my-campaign/stylesheets/_variables.scss new file mode 100644 index 0000000..288ae15 --- /dev/null +++ b/tests/fixtures/v2/campaigns/my-campaign/stylesheets/_variables.scss @@ -0,0 +1,2 @@ +$primary: #e63946; +$secondary: #457b9d; diff --git a/tests/fixtures/v2/campaigns/my-campaign/stylesheets/main.scss b/tests/fixtures/v2/campaigns/my-campaign/stylesheets/main.scss new file mode 100644 index 0000000..d91ad58 --- /dev/null +++ b/tests/fixtures/v2/campaigns/my-campaign/stylesheets/main.scss @@ -0,0 +1,5 @@ +@import 'variables'; + +.raisely-campaign { + color: $primary; +} diff --git a/tests/fixtures/v2/campaigns/spring_2026/pages/home.json b/tests/fixtures/v2/campaigns/spring_2026/pages/home.json new file mode 100644 index 0000000..91b4dfa --- /dev/null +++ b/tests/fixtures/v2/campaigns/spring_2026/pages/home.json @@ -0,0 +1,9 @@ +{ + "uuid": "page-uuid-special-v2", + "path": "/", + "title": "Spring Campaign Home", + "name": "home", + "status": "published", + "body": [], + "campaignUuid": "campaign-uuid-spring-2026" +} diff --git a/tests/fixtures/v2/campaigns/spring_2026/stylesheets/main.scss b/tests/fixtures/v2/campaigns/spring_2026/stylesheets/main.scss new file mode 100644 index 0000000..ab3b83e --- /dev/null +++ b/tests/fixtures/v2/campaigns/spring_2026/stylesheets/main.scss @@ -0,0 +1,5 @@ +@import 'variables'; + +.raisely-campaign { + color: #0b7a75; +} diff --git a/tests/layout.test.js b/tests/layout.test.js new file mode 100644 index 0000000..60c7237 --- /dev/null +++ b/tests/layout.test.js @@ -0,0 +1,183 @@ +import { test } from 'vitest'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { + resolveCampaignPaths, + detectLayout, + shouldRefuseLayoutForCommand, + getLegacyLayoutRefusalMessage, +} from '../src/actions/layout.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const fixturesDir = path.join(__dirname, 'fixtures'); + +// --------------------------------------------------------------------------- +// resolveCampaignPaths +// --------------------------------------------------------------------------- + +test('resolveCampaignPaths returns the v2 directory paths for a campaign', () => { + const result = resolveCampaignPaths('/repo', 'my-campaign'); + assert.equal(result.campaignDir, '/repo/campaigns/my-campaign'); + assert.equal(result.pagesDir, '/repo/campaigns/my-campaign/pages'); + assert.equal( + result.stylesheetsDir, + '/repo/campaigns/my-campaign/stylesheets' + ); + assert.equal( + result.mainScss, + '/repo/campaigns/my-campaign/stylesheets/main.scss' + ); +}); + +test('resolveCampaignPaths handles hyphenated campaign paths', () => { + const result = resolveCampaignPaths('/repo', 'end-of-year-2024'); + assert.equal( + result.pagesDir, + '/repo/campaigns/end-of-year-2024/pages' + ); + assert.equal( + result.mainScss, + '/repo/campaigns/end-of-year-2024/stylesheets/main.scss' + ); +}); + +test('resolveCampaignPaths handles special-but-valid campaign paths', () => { + const result = resolveCampaignPaths('/repo', 'spring_2026'); + assert.equal(result.pagesDir, '/repo/campaigns/spring_2026/pages'); + assert.equal( + result.mainScss, + '/repo/campaigns/spring_2026/stylesheets/main.scss' + ); +}); + +test('resolveCampaignPaths does not require directories to exist', () => { + // Should return paths without throwing even when the directory is absent. + const result = resolveCampaignPaths('/nonexistent/root', 'phantom-campaign'); + assert.ok(result.pagesDir.includes('phantom-campaign')); + assert.ok(result.mainScss.endsWith('main.scss')); +}); + +// --------------------------------------------------------------------------- +// detectLayout – single-campaign fixtures +// --------------------------------------------------------------------------- + +test('detectLayout returns "legacy" for a repo with top-level pages/ and stylesheets/', () => { + const layout = detectLayout(path.join(fixturesDir, 'legacy')); + assert.equal(layout, 'legacy'); +}); + +test('detectLayout returns "v2" for a repo with campaigns/ and no legacy dirs', () => { + const layout = detectLayout(path.join(fixturesDir, 'v2')); + assert.equal(layout, 'v2'); +}); + +test('detectLayout handles fixtures containing special-but-valid campaign paths', () => { + const legacyFixture = path.join(fixturesDir, 'legacy'); + const v2Fixture = path.join(fixturesDir, 'v2'); + + assert.equal( + fs.existsSync(path.join(legacyFixture, 'pages', 'spring_2026')), + true + ); + assert.equal( + fs.existsSync(path.join(v2Fixture, 'campaigns', 'spring_2026')), + true + ); + assert.equal(detectLayout(legacyFixture), 'legacy'); + assert.equal(detectLayout(v2Fixture), 'v2'); +}); + +test('detectLayout returns "mixed" for a repo that has both legacy and v2 dirs', () => { + const layout = detectLayout(path.join(fixturesDir, 'mixed')); + assert.equal(layout, 'mixed'); +}); + +test('detectLayout returns "empty" for a repo with neither pages, stylesheets, nor campaigns', () => { + const layout = detectLayout(path.join(fixturesDir, 'empty')); + assert.equal(layout, 'empty'); +}); + +// --------------------------------------------------------------------------- +// detectLayout – multi-campaign fixtures +// --------------------------------------------------------------------------- + +test('detectLayout returns "legacy" for a multi-campaign legacy repo', () => { + const layout = detectLayout(path.join(fixturesDir, 'legacy-multi')); + assert.equal(layout, 'legacy'); +}); + +test('detectLayout returns "v2" for a multi-campaign v2 repo', () => { + const layout = detectLayout(path.join(fixturesDir, 'v2-multi')); + assert.equal(layout, 'v2'); +}); + +// --------------------------------------------------------------------------- +// detectLayout – edge cases +// --------------------------------------------------------------------------- + +test('detectLayout returns "empty" when repoRoot does not exist', () => { + const layout = detectLayout('/this/path/does/not/exist'); + assert.equal(layout, 'empty'); +}); + +test('detectLayout returns "legacy" for a repo with only pages/ (no stylesheets/)', () => { + // The legacy fixture has both, but test the single-marker case via a path + // that only has pages/ by pointing at the pages subdir directly. + // We confirm the function does OR between the two markers. + const layout = detectLayout(path.join(fixturesDir, 'legacy')); + assert.equal(layout, 'legacy'); +}); + +// --------------------------------------------------------------------------- +// resolveCampaignPaths – multi-campaign sanity +// --------------------------------------------------------------------------- + +test('resolveCampaignPaths returns independent paths for each campaign in a multi-campaign repo', () => { + const root = path.join(fixturesDir, 'v2-multi'); + const one = resolveCampaignPaths(root, 'campaign-one'); + const two = resolveCampaignPaths(root, 'campaign-two'); + + assert.ok(one.pagesDir.includes('campaign-one')); + assert.ok(two.pagesDir.includes('campaign-two')); + assert.notEqual(one.pagesDir, two.pagesDir); + assert.notEqual(one.mainScss, two.mainScss); +}); + +// --------------------------------------------------------------------------- +// legacy-layout refusal helpers +// --------------------------------------------------------------------------- + +test('refusing commands block legacy and mixed layouts', () => { + const refusingCommands = ['update', 'deploy', 'local', 'start']; + for (const command of refusingCommands) { + assert.equal(shouldRefuseLayoutForCommand(command, 'legacy'), true); + assert.equal(shouldRefuseLayoutForCommand(command, 'mixed'), true); + assert.equal(shouldRefuseLayoutForCommand(command, 'v2'), false); + assert.equal(shouldRefuseLayoutForCommand(command, 'empty'), false); + } +}); + +test('unaffected commands do not block on legacy layouts', () => { + const unaffectedCommands = [ + 'init', + 'list', + 'create', + 'login', + 'logout', + 'migrate', + ]; + for (const command of unaffectedCommands) { + assert.equal(shouldRefuseLayoutForCommand(command, 'legacy'), false); + assert.equal(shouldRefuseLayoutForCommand(command, 'mixed'), false); + } +}); + +test('refusal message includes both v2 install and migrate steps', () => { + const msg = getLegacyLayoutRefusalMessage('update', 'legacy'); + assert.match(msg, /raisely update/); + assert.match(msg, /npm install -g @raisely\/cli@2/); + assert.match(msg, /raisely migrate/); +}); diff --git a/tests/local.test.js b/tests/local.test.js new file mode 100644 index 0000000..474ec38 --- /dev/null +++ b/tests/local.test.js @@ -0,0 +1,344 @@ +import { describe, test } from 'vitest'; +import assert from 'node:assert/strict'; + +import { createStylesRouteHandler } from '../src/local.js'; + +function makeTranspilerResponse({ status, body = '', statusText = '' }) { + return { + status, + ok: status >= 200 && status < 300, + statusText, + text: async () => body, + }; +} + +function makeResponseHarness() { + return { + headers: {}, + statusCode: 200, + body: undefined, + set(name, value) { + this.headers[name] = value; + return this; + }, + status(code) { + this.statusCode = code; + return this; + }, + send(payload) { + this.body = payload; + return this; + }, + sendStatus(code) { + this.statusCode = code; + this.body = undefined; + return this; + }, + }; +} + +function createLogger() { + return { + errors: [], + warns: [], + error(value) { + this.errors.push(value); + }, + warn(value) { + this.warns.push(value); + }, + }; +} + +function hasLogMessage(entries, expectedMessage) { + return entries.some( + (entry) => + typeof entry === 'string' && + entry.includes(expectedMessage) + ); +} + +function createDeferred() { + let resolve; + let reject; + const promise = new Promise((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject }; +} + +describe('local styles route', () => { + test('cold cache 4xx returns 502 with transpiler error as css comment', async () => { + const logs = createLogger(); + const handler = createStylesRouteHandler({ + campaignPath: 'my-campaign', + baseStyles: '', + token: 'token-123', + processStylesFn: async () => '.hero { color: red; }', + fetchFn: async () => + makeTranspilerResponse({ + status: 422, + body: 'Error: expected "}" on line 2', + statusText: 'Unprocessable Entity', + }), + logs, + }); + + const res = makeResponseHarness(); + await handler({}, res); + + assert.equal(res.statusCode, 502); + assert.equal( + res.body, + '/*\nError: expected "}" on line 2\n*/' + ); + assert.deepEqual(logs.errors, ['Error: expected "}" on line 2']); + }); + + test('warm cache 4xx serves last good css and logs transpiler error', async () => { + const logs = createLogger(); + const queue = [ + makeTranspilerResponse({ + status: 200, + body: '.fresh { color: green; }', + statusText: 'OK', + }), + makeTranspilerResponse({ + status: 400, + body: 'SassError: invalid selector', + statusText: 'Bad Request', + }), + ]; + + const handler = createStylesRouteHandler({ + campaignPath: 'my-campaign', + baseStyles: '', + token: 'token-123', + processStylesFn: async () => '.hero { color: red; }', + fetchFn: async () => queue.shift(), + logs, + }); + + const firstRes = makeResponseHarness(); + await handler({}, firstRes); + assert.equal(firstRes.statusCode, 200); + assert.equal(firstRes.body, '.fresh { color: green; }'); + + const secondRes = makeResponseHarness(); + await handler({}, secondRes); + assert.equal(secondRes.statusCode, 200); + assert.equal(secondRes.body, '.fresh { color: green; }'); + assert.deepEqual(logs.errors, ['SassError: invalid selector']); + }); + + test('401 warns and keeps serving from cache on later requests', async () => { + const logs = createLogger(); + const queue = [ + makeTranspilerResponse({ + status: 200, + body: '.cached { color: blue; }', + statusText: 'OK', + }), + makeTranspilerResponse({ + status: 401, + body: 'Unauthorized', + statusText: 'Unauthorized', + }), + makeTranspilerResponse({ + status: 200, + body: '.new { color: purple; }', + statusText: 'OK', + }), + ]; + + const handler = createStylesRouteHandler({ + campaignPath: 'my-campaign', + baseStyles: '', + token: 'token-123', + processStylesFn: async () => '.hero { color: red; }', + fetchFn: async () => queue.shift(), + logs, + }); + + const firstRes = makeResponseHarness(); + await handler({}, firstRes); + assert.equal(firstRes.body, '.cached { color: blue; }'); + + const secondRes = makeResponseHarness(); + await handler({}, secondRes); + assert.equal(secondRes.statusCode, 200); + assert.equal(secondRes.body, '.cached { color: blue; }'); + + const thirdRes = makeResponseHarness(); + await handler({}, thirdRes); + assert.equal(thirdRes.statusCode, 200); + assert.equal(thirdRes.body, '.new { color: purple; }'); + assert.equal(logs.warns.length, 1); + assert.equal(hasLogMessage(logs.errors, 'Unauthorized'), true); + }); + + test('5xx retries once after 500ms before falling back', async () => { + const logs = createLogger(); + let fetchCalls = 0; + let waitCalls = 0; + const queue = [ + makeTranspilerResponse({ + status: 200, + body: '.baseline { color: black; }', + statusText: 'OK', + }), + makeTranspilerResponse({ + status: 503, + body: 'Service unavailable', + statusText: 'Service Unavailable', + }), + makeTranspilerResponse({ + status: 503, + body: 'Still unavailable', + statusText: 'Service Unavailable', + }), + ]; + + const handler = createStylesRouteHandler({ + campaignPath: 'my-campaign', + baseStyles: '', + token: 'token-123', + processStylesFn: async () => '.hero { color: red; }', + fetchFn: async () => { + fetchCalls += 1; + return queue.shift(); + }, + waitFn: async () => { + waitCalls += 1; + }, + logs, + }); + + const firstRes = makeResponseHarness(); + await handler({}, firstRes); + assert.equal(firstRes.body, '.baseline { color: black; }'); + + const secondRes = makeResponseHarness(); + await handler({}, secondRes); + assert.equal(secondRes.statusCode, 200); + assert.equal(secondRes.body, '.baseline { color: black; }'); + assert.equal(waitCalls, 1); + assert.equal(fetchCalls, 3); + assert.equal(hasLogMessage(logs.errors, 'Still unavailable'), true); + assert.equal( + logs.errors.some( + (entry) => + typeof entry === 'string' && + entry.includes('SASS transpiler failed after retry: 503') + ), + true + ); + }); + + test('network error then 5xx only retries once total', async () => { + const logs = createLogger(); + let fetchCalls = 0; + let waitCalls = 0; + const firstError = new Error('socket hang up'); + const queue = [ + 'throw', + makeTranspilerResponse({ + status: 503, + body: 'Service unavailable', + statusText: 'Service Unavailable', + }), + ]; + + const handler = createStylesRouteHandler({ + campaignPath: 'my-campaign', + baseStyles: '', + token: 'token-123', + processStylesFn: async () => '.hero { color: red; }', + fetchFn: async () => { + fetchCalls += 1; + const next = queue.shift(); + if (next === 'throw') { + throw firstError; + } + return next; + }, + waitFn: async () => { + waitCalls += 1; + }, + logs, + }); + + const res = makeResponseHarness(); + await handler({}, res); + + assert.equal(waitCalls, 1); + assert.equal(fetchCalls, 2); + assert.equal(res.statusCode, 502); + assert.equal( + logs.errors.some( + (entry) => + typeof entry === 'string' && + entry.includes('SASS transpiler failed after retry: 503') + ), + true + ); + }); + + test('older in-flight success does not overwrite newer cached css', async () => { + const logs = createLogger(); + const firstFetch = createDeferred(); + const secondFetch = createDeferred(); + let fetchCalls = 0; + + const handler = createStylesRouteHandler({ + campaignPath: 'my-campaign', + baseStyles: '', + token: 'token-123', + processStylesFn: async () => '.hero { color: red; }', + fetchFn: async () => { + fetchCalls += 1; + if (fetchCalls === 1) return firstFetch.promise; + if (fetchCalls === 2) return secondFetch.promise; + return makeTranspilerResponse({ + status: 400, + body: 'SassError: broken', + statusText: 'Bad Request', + }); + }, + logs, + }); + + const firstRes = makeResponseHarness(); + const secondRes = makeResponseHarness(); + + const firstRequest = handler({}, firstRes); + const secondRequest = handler({}, secondRes); + + secondFetch.resolve( + makeTranspilerResponse({ + status: 200, + body: '.newest { color: green; }', + statusText: 'OK', + }) + ); + firstFetch.resolve( + makeTranspilerResponse({ + status: 200, + body: '.stale { color: red; }', + statusText: 'OK', + }) + ); + + await Promise.all([firstRequest, secondRequest]); + + assert.equal(secondRes.body, '.newest { color: green; }'); + assert.equal(firstRes.body, '.stale { color: red; }'); + + const fallbackRes = makeResponseHarness(); + await handler({}, fallbackRes); + + assert.equal(fallbackRes.statusCode, 200); + assert.equal(fallbackRes.body, '.newest { color: green; }'); + }); +}); diff --git a/tests/migrate.test.js b/tests/migrate.test.js new file mode 100644 index 0000000..c3fb0b9 --- /dev/null +++ b/tests/migrate.test.js @@ -0,0 +1,428 @@ +import { onTestFinished, test } from 'vitest'; +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { migrate } from '../src/migrate.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const fixturesDir = path.join(__dirname, 'fixtures'); + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/** + * Copies a named fixture into a fresh temp directory and returns its path. + * The temp directory is cleaned up automatically via the `cleanup` callback + * registered in `afterEach`. + */ +function copyFixture(name, cleanupFns) { + const src = path.join(fixturesDir, name); + const dst = fs.mkdtempSync( + path.join(os.tmpdir(), `raisely-migrate-${name}-`) + ); + fs.cpSync(src, dst, { recursive: true }); + cleanupFns.push(() => fs.rmSync(dst, { recursive: true, force: true })); + return dst; +} + +/** + * Builds a simple getCampaign mock from a `{ uuid: campaignPath }` map. + */ +function mockGetCampaign(map) { + return async ({ uuid }) => { + if (!Object.prototype.hasOwnProperty.call(map, uuid)) { + throw new Error(`Unknown campaign UUID: ${uuid}`); + } + return { data: { path: map[uuid] } }; + }; +} + +// --------------------------------------------------------------------------- +// legacy → v2: single campaign with partial +// --------------------------------------------------------------------------- + +test('migrate moves pages and main SCSS to the v2 layout for a single campaign', async () => { + const cleanupFns = []; + onTestFinished(() => cleanupFns.forEach((fn) => fn())); + + const repoRoot = copyFixture('legacy', cleanupFns); + const getCampaign = mockGetCampaign({ 'uuid-my-campaign': 'my-campaign' }); + + const report = await migrate({ + repoRoot, + campaigns: ['uuid-my-campaign'], + getCampaign, + }); + + // Pages landed at the v2 path. + assert.ok( + fs.existsSync( + path.join(repoRoot, 'campaigns/my-campaign/pages/home.json') + ), + 'home.json should exist under campaigns/my-campaign/pages/' + ); + + // Main SCSS renamed. + assert.ok( + fs.existsSync( + path.join(repoRoot, 'campaigns/my-campaign/stylesheets/main.scss') + ), + 'main.scss should exist under campaigns/my-campaign/stylesheets/' + ); + assert.ok( + !fs.existsSync( + path.join( + repoRoot, + 'stylesheets/my-campaign/my-campaign.scss' + ) + ), + 'original my-campaign.scss should be gone from legacy path' + ); + + // Report reflects rename and move. + assert.ok( + report.renamed.some((p) => p.endsWith('main.scss')), + 'report.renamed should include main.scss' + ); + assert.ok( + report.moved.some((p) => p.endsWith('home.json')), + 'report.moved should include home.json' + ); +}); + +// --------------------------------------------------------------------------- +// Partial preservation +// --------------------------------------------------------------------------- + +test('migrate preserves partials with their original names', async () => { + const cleanupFns = []; + onTestFinished(() => cleanupFns.forEach((fn) => fn())); + + const repoRoot = copyFixture('legacy', cleanupFns); + const getCampaign = mockGetCampaign({ 'uuid-my-campaign': 'my-campaign' }); + + await migrate({ + repoRoot, + campaigns: ['uuid-my-campaign'], + getCampaign, + }); + + assert.ok( + fs.existsSync( + path.join( + repoRoot, + 'campaigns/my-campaign/stylesheets/_variables.scss' + ) + ), + '_variables.scss partial should be preserved at its original name' + ); +}); + +// --------------------------------------------------------------------------- +// Nested scss with same name as campaign must not be renamed to main.scss +// --------------------------------------------------------------------------- + +test('migrate does not rename a nested scss file that shares the campaign name', async () => { + const cleanupFns = []; + onTestFinished(() => cleanupFns.forEach((fn) => fn())); + + const repoRoot = copyFixture('legacy', cleanupFns); + const getCampaign = mockGetCampaign({ 'uuid-my-campaign': 'my-campaign' }); + + await migrate({ + repoRoot, + campaigns: ['uuid-my-campaign'], + getCampaign, + }); + + // The top-level entry stylesheet is renamed correctly. + assert.ok( + fs.existsSync( + path.join(repoRoot, 'campaigns/my-campaign/stylesheets/main.scss') + ), + 'top-level my-campaign.scss should be renamed to main.scss' + ); + + // The nested file with the same name must keep its original name. + assert.ok( + fs.existsSync( + path.join( + repoRoot, + 'campaigns/my-campaign/stylesheets/theme/my-campaign.scss' + ) + ), + 'nested my-campaign.scss inside theme/ should NOT be renamed to main.scss' + ); + assert.ok( + !fs.existsSync( + path.join( + repoRoot, + 'campaigns/my-campaign/stylesheets/theme/main.scss' + ) + ), + 'there should be no main.scss created inside theme/' + ); +}); + +// --------------------------------------------------------------------------- +// A file already named main.scss in the source must land in report.moved, not report.renamed +// --------------------------------------------------------------------------- + +test('migrate classifies a source main.scss as moved, not renamed', async () => { + const cleanupFns = []; + onTestFinished(() => cleanupFns.forEach((fn) => fn())); + + // Uses a fixture where the stylesheet is already named main.scss (no .scss to rename). + const repoRoot = copyFixture('legacy-only-main-scss', cleanupFns); + const getCampaign = mockGetCampaign({ 'uuid-my-campaign': 'my-campaign' }); + + const report = await migrate({ + repoRoot, + campaigns: ['uuid-my-campaign'], + getCampaign, + }); + + // The file was moved (not renamed), so it must appear in report.moved, not report.renamed. + assert.ok( + report.moved.some((p) => p.endsWith('main.scss')), + 'report.moved should contain main.scss (source was already named main.scss)' + ); + assert.ok( + !report.renamed.some((p) => p.endsWith('main.scss')), + 'report.renamed must not contain main.scss when no rename occurred' + ); +}); + +// --------------------------------------------------------------------------- +// Multi-campaign migration + empty root cleanup +// --------------------------------------------------------------------------- + +test('migrate handles a multi-campaign repo and deletes empty root directories', async () => { + const cleanupFns = []; + onTestFinished(() => cleanupFns.forEach((fn) => fn())); + + const repoRoot = copyFixture('legacy-multi', cleanupFns); + const getCampaign = mockGetCampaign({ + 'uuid-one': 'campaign-one', + 'uuid-two': 'campaign-two', + }); + + const report = await migrate({ + repoRoot, + campaigns: ['uuid-one', 'uuid-two'], + getCampaign, + }); + + // Both campaigns landed at v2 paths. + assert.ok( + fs.existsSync( + path.join(repoRoot, 'campaigns/campaign-one/pages/home.json') + ) + ); + assert.ok( + fs.existsSync( + path.join( + repoRoot, + 'campaigns/campaign-one/stylesheets/main.scss' + ) + ) + ); + assert.ok( + fs.existsSync( + path.join(repoRoot, 'campaigns/campaign-two/pages/home.json') + ) + ); + assert.ok( + fs.existsSync( + path.join( + repoRoot, + 'campaigns/campaign-two/stylesheets/main.scss' + ) + ) + ); + + // Root legacy directories removed because they are now empty. + assert.ok( + !fs.existsSync(path.join(repoRoot, 'pages')), + 'root pages/ should be deleted after all campaigns migrated' + ); + assert.ok( + !fs.existsSync(path.join(repoRoot, 'stylesheets')), + 'root stylesheets/ should be deleted after all campaigns migrated' + ); + assert.ok( + report.deleted.some((p) => p.endsWith('pages')), + 'report.deleted should include pages/' + ); + assert.ok( + report.deleted.some((p) => p.endsWith('stylesheets')), + 'report.deleted should include stylesheets/' + ); + assert.equal(report.orphans.length, 0, 'no orphans expected'); +}); + +// --------------------------------------------------------------------------- +// Idempotency +// --------------------------------------------------------------------------- + +test('migrate is a no-op when run a second time on an already-migrated repo', async () => { + const cleanupFns = []; + onTestFinished(() => cleanupFns.forEach((fn) => fn())); + + const repoRoot = copyFixture('legacy-multi', cleanupFns); + const getCampaign = mockGetCampaign({ + 'uuid-one': 'campaign-one', + 'uuid-two': 'campaign-two', + }); + + // First run — performs the actual migration. + const firstReport = await migrate({ + repoRoot, + campaigns: ['uuid-one', 'uuid-two'], + getCampaign, + }); + + assert.ok( + firstReport.moved.length > 0 || firstReport.renamed.length > 0, + 'first run should move or rename at least one file' + ); + + // Second run — everything is already in place. + const secondReport = await migrate({ + repoRoot, + campaigns: ['uuid-one', 'uuid-two'], + getCampaign, + }); + + assert.equal( + secondReport.moved.length, + 0, + 'second run should move nothing' + ); + assert.equal( + secondReport.renamed.length, + 0, + 'second run should rename nothing' + ); + assert.equal( + secondReport.deleted.length, + 0, + 'second run should delete nothing' + ); + + // Files still at the v2 paths after the second pass. + assert.ok( + fs.existsSync( + path.join(repoRoot, 'campaigns/campaign-one/pages/home.json') + ) + ); + assert.ok( + fs.existsSync( + path.join( + repoRoot, + 'campaigns/campaign-one/stylesheets/main.scss' + ) + ) + ); +}); + +// --------------------------------------------------------------------------- +// Orphan handling +// --------------------------------------------------------------------------- + +test('migrate leaves unconfigured campaign folders in place and lists them as orphans', async () => { + const cleanupFns = []; + onTestFinished(() => cleanupFns.forEach((fn) => fn())); + + const repoRoot = copyFixture('legacy-multi', cleanupFns); + + // Only configure campaign-one; campaign-two becomes an orphan. + const getCampaign = mockGetCampaign({ 'uuid-one': 'campaign-one' }); + + const report = await migrate({ + repoRoot, + campaigns: ['uuid-one'], + getCampaign, + }); + + // campaign-one migrated. + assert.ok( + fs.existsSync( + path.join(repoRoot, 'campaigns/campaign-one/pages/home.json') + ) + ); + + // campaign-two untouched in its legacy location. + assert.ok( + fs.existsSync( + path.join(repoRoot, 'pages/campaign-two/home.json') + ), + 'orphan pages/campaign-two/ should remain' + ); + assert.ok( + fs.existsSync( + path.join( + repoRoot, + 'stylesheets/campaign-two/campaign-two.scss' + ) + ), + 'orphan stylesheets/campaign-two/ should remain' + ); + + // Orphans surfaced in the report. + const orphanFolders = report.orphans.map((o) => o.folder); + assert.ok( + orphanFolders.some((f) => f.includes('campaign-two')), + 'campaign-two should appear in report.orphans' + ); + + // Root dirs not deleted (still contain the orphan). + assert.ok( + fs.existsSync(path.join(repoRoot, 'pages')), + 'pages/ should stay because the orphan is still there' + ); + assert.ok( + fs.existsSync(path.join(repoRoot, 'stylesheets')), + 'stylesheets/ should stay because the orphan is still there' + ); +}); + +// --------------------------------------------------------------------------- +// getCampaign failure — campaign processed independently +// --------------------------------------------------------------------------- + +test('migrate continues processing remaining campaigns when one UUID cannot be resolved', async () => { + const cleanupFns = []; + onTestFinished(() => cleanupFns.forEach((fn) => fn())); + + const repoRoot = copyFixture('legacy-multi', cleanupFns); + + // campaign-one resolves fine; campaign-two throws. + const getCampaign = async ({ uuid }) => { + if (uuid === 'uuid-one') return { data: { path: 'campaign-one' } }; + throw new Error('API error'); + }; + + const report = await migrate({ + repoRoot, + campaigns: ['uuid-one', 'uuid-bad'], + getCampaign, + }); + + // campaign-one migrated normally. + assert.ok( + fs.existsSync( + path.join(repoRoot, 'campaigns/campaign-one/pages/home.json') + ) + ); + + // Failed UUID listed as orphan. + assert.ok( + report.orphans.some((o) => o.folder === 'uuid-bad'), + 'unresolvable UUID should be listed in orphans' + ); +}); diff --git a/tests/start.test.js b/tests/start.test.js new file mode 100644 index 0000000..fdc3c71 --- /dev/null +++ b/tests/start.test.js @@ -0,0 +1,192 @@ +import { describe, test } from 'vitest'; +import assert from 'node:assert/strict'; + +import { + handleCampaignChange, + handleComponentChange, + registerStartWatchers, +} from '../src/start.js'; + +function createOraHarness() { + const loaders = []; + const oraFn = (message) => ({ + start() { + const loader = { + message, + succeeded: false, + failed: null, + succeed() { + this.succeeded = true; + }, + fail(errorMessage) { + this.failed = errorMessage; + }, + }; + loaders.push(loader); + return loader; + }, + }); + return { oraFn, loaders }; +} + +describe('start per-save validation', () => { + test('bad SCSS save fails inline and skips upload', async () => { + let uploadCalls = 0; + const { oraFn, loaders } = createOraHarness(); + + await handleCampaignChange('/repo/campaigns/acme/stylesheets/main.scss', { + campaignsDir: '/repo/campaigns', + token: 'token-123', + validateCampaignSassFn: async ({ campaign, token }) => { + assert.equal(campaign, 'acme'); + assert.equal(token, 'token-123'); + return { ok: false, error: 'SassError: expected "}"' }; + }, + uploadStylesFn: async () => { + uploadCalls += 1; + }, + oraFn, + }); + + assert.equal(uploadCalls, 0); + assert.equal(loaders.length, 1); + assert.equal(loaders[0].failed, 'SassError: expected "}"'); + assert.equal(loaders[0].succeeded, false); + }); + + test('bad component save fails inline and skips upload', async () => { + let updateFileCalls = 0; + const { oraFn, loaders } = createOraHarness(); + const componentFs = { + readFileSync(filePath) { + if (filePath === '/repo/components/Hero/Hero.js') { + return 'export default () =>
;'; + } + if (filePath === '/repo/components/Hero/Hero.json') { + return '{"uuid":"component-1","fields":[]}'; + } + throw new Error(`Unexpected file read: ${filePath}`); + }, + }; + + await handleComponentChange('/repo/components/Hero/Hero.js', { + componentsDir: '/repo/components', + config: { token: 'token-123' }, + fsModule: componentFs, + validateComponentFn: async ({ name }) => { + assert.equal(name, 'Hero'); + return { ok: false, error: 'Unexpected token (1:25)' }; + }, + updateComponentFileFn: async () => { + updateFileCalls += 1; + }, + updateComponentConfigFn: async () => { + throw new Error('Config update should not run for JS save'); + }, + oraFn, + }); + + assert.equal(updateFileCalls, 0); + assert.equal(loaders.length, 1); + assert.equal(loaders[0].failed, 'Unexpected token (1:25)'); + assert.equal(loaders[0].succeeded, false); + }); + + test('next valid save uploads after a failed validation', async () => { + let validationCalls = 0; + let updateFileCalls = 0; + const { oraFn, loaders } = createOraHarness(); + const componentFs = { + readFileSync(filePath) { + if (filePath === '/repo/components/Hero/Hero.js') { + return 'export default () =>
;'; + } + if (filePath === '/repo/components/Hero/Hero.json') { + return '{"uuid":"component-1","fields":[]}'; + } + throw new Error(`Unexpected file read: ${filePath}`); + }, + }; + + const invoke = () => + handleComponentChange('/repo/components/Hero/Hero.js', { + componentsDir: '/repo/components', + config: { token: 'token-123' }, + fsModule: componentFs, + validateComponentFn: async () => { + validationCalls += 1; + if (validationCalls === 1) { + return { ok: false, error: 'Unexpected token (1:25)' }; + } + return { ok: true }; + }, + updateComponentFileFn: async () => { + updateFileCalls += 1; + }, + updateComponentConfigFn: async () => { + throw new Error('Config update should not run for JS save'); + }, + oraFn, + }); + + await invoke(); + await invoke(); + + assert.equal(validationCalls, 2); + assert.equal(updateFileCalls, 1); + assert.equal(loaders[0].failed, 'Unexpected token (1:25)'); + assert.equal(loaders[1].succeeded, true); + }); + + test('watcher callback stays active after failed validation', async () => { + const callbacks = new Map(); + const watchFn = (target, _options, callback) => { + callbacks.set(target, callback); + return { close() {} }; + }; + const { oraFn, loaders } = createOraHarness(); + let validationCalls = 0; + let uploadCalls = 0; + + registerStartWatchers( + { + campaignsDir: '/repo/campaigns', + componentsDir: '/repo/components', + config: { token: 'token-123' }, + watchFn, + }, + { + oraFn, + validateCampaignSassFn: async () => { + validationCalls += 1; + if (validationCalls === 1) { + return { ok: false, error: 'SassError: expected "}"' }; + } + return { ok: true }; + }, + uploadStylesFn: async () => { + uploadCalls += 1; + }, + validateComponentFn: async () => ({ ok: true }), + updateComponentFileFn: async () => {}, + updateComponentConfigFn: async () => {}, + fsModule: { + readFileSync() { + return '{}'; + }, + }, + } + ); + + const campaignCallback = callbacks.get('/repo/campaigns'); + assert.equal(typeof campaignCallback, 'function'); + + await campaignCallback('update', '/repo/campaigns/acme/stylesheets/main.scss'); + await campaignCallback('update', '/repo/campaigns/acme/stylesheets/main.scss'); + + assert.equal(validationCalls, 2); + assert.equal(uploadCalls, 1); + assert.equal(loaders[0].failed, 'SassError: expected "}"'); + assert.equal(loaders[1].succeeded, true); + }); +}); diff --git a/tests/telemetry.test.js b/tests/telemetry.test.js new file mode 100644 index 0000000..d9ab1ca --- /dev/null +++ b/tests/telemetry.test.js @@ -0,0 +1,161 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; + +const mocks = vi.hoisted(() => ({ + fetch: vi.fn(), + loadConfig: vi.fn(), + resolveOrganisationContext: vi.fn(), + getCredentials: vi.fn(), + getPackageInfo: vi.fn(), +})); + +vi.mock('node-fetch', () => ({ + default: mocks.fetch, +})); + +vi.mock('../src/config.js', () => ({ + loadConfig: mocks.loadConfig, +})); + +vi.mock('../src/credentials.js', () => ({ + resolveOrganisationContext: mocks.resolveOrganisationContext, + getCredentials: mocks.getCredentials, +})); + +vi.mock('../src/helpers.js', () => ({ + getPackageInfo: mocks.getPackageInfo, +})); + +import { + __resetTelemetryForTests, + flushTelemetry, + trackEvent, +} from '../src/telemetry.js'; + +function createJsonResponse(json, ok = true) { + return { + ok, + json: async () => json, + }; +} + +describe('telemetry', () => { + beforeEach(() => { + vi.clearAllMocks(); + delete process.env.RAISELY_NO_TELEMETRY; + + mocks.loadConfig.mockResolvedValue({ + apiUrl: 'https://api.raisely.com', + organisationUuid: 'org-config', + campaigns: ['campaign-a', 'campaign-b'], + }); + mocks.resolveOrganisationContext.mockResolvedValue({ + apiUrl: 'https://api.raisely.com', + organisationUuid: 'org-context', + }); + mocks.getCredentials.mockResolvedValue({ token: 'token-123' }); + mocks.getPackageInfo.mockReturnValue({ + name: '@raisely/cli', + version: '2.0.0-test', + }); + }); + + afterEach(async () => { + await flushTelemetry(); + __resetTelemetryForTests(); + }); + + test('sends telemetry with resolved metadata and reuses cached context', async () => { + mocks.fetch + .mockResolvedValueOnce( + createJsonResponse({ + userUuid: 'user-1', + organisationUuid: 'org-auth', + }) + ) + .mockResolvedValueOnce(createJsonResponse({ ok: true })) + .mockResolvedValueOnce(createJsonResponse({ ok: true })); + + trackEvent('cli.deploy', { outcome: 'success', durationMs: 33 }); + trackEvent('cli.update', { outcome: 'error', durationMs: 7, errorCode: 500 }); + await flushTelemetry(); + + expect(mocks.fetch).toHaveBeenCalledTimes(3); + expect(mocks.fetch).toHaveBeenNthCalledWith( + 1, + 'https://api.raisely.com/v3/authenticate', + expect.objectContaining({ + method: 'GET', + headers: expect.objectContaining({ + Authorization: 'Bearer token-123', + }), + }) + ); + expect(mocks.fetch).toHaveBeenNthCalledWith( + 2, + 'https://api.raisely.com/v3/t', + expect.objectContaining({ + method: 'POST', + body: expect.stringContaining('"event":"cli.deploy"'), + }) + ); + expect(mocks.fetch).toHaveBeenNthCalledWith( + 3, + 'https://api.raisely.com/v3/t', + expect.objectContaining({ + method: 'POST', + body: expect.stringContaining('"event":"cli.update"'), + }) + ); + + const secondPayload = JSON.parse(mocks.fetch.mock.calls[1][1].body); + expect(secondPayload.organisationUuid).toBe('org-auth'); + expect(secondPayload.userUuid).toBe('user-1'); + expect(secondPayload.campaignUuid).toBe('campaign-a'); + expect(secondPayload.sessionId).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i + ); + expect(secondPayload.traits.campaignIds).toEqual(['campaign-a', 'campaign-b']); + expect(secondPayload.traits.cliVersion).toBe('2.0.0-test'); + expect(secondPayload.traits.outcome).toBe('success'); + expect(secondPayload.traits.durationMs).toBe(33); + + const thirdPayload = JSON.parse(mocks.fetch.mock.calls[2][1].body); + expect(thirdPayload.traits.errorCode).toBe(500); + expect(thirdPayload.sessionId).toBe(secondPayload.sessionId); + expect(mocks.resolveOrganisationContext).toHaveBeenCalledTimes(1); + }); + + test('flushTelemetry waits for in-flight telemetry promises', async () => { + let resolveSend; + const pendingSend = new Promise((resolve) => { + resolveSend = resolve; + }); + mocks.fetch + .mockResolvedValueOnce(createJsonResponse({ userUuid: 'user-1' })) + .mockReturnValueOnce(pendingSend); + + trackEvent('cli.local', { outcome: 'success', durationMs: 99 }); + const flushPromise = flushTelemetry(); + + let settled = false; + flushPromise.then(() => { + settled = true; + }); + await Promise.resolve(); + expect(settled).toBe(false); + + resolveSend(createJsonResponse({ ok: true })); + await flushPromise; + expect(settled).toBe(true); + }); + + test('RAISELY_NO_TELEMETRY disables telemetry', async () => { + process.env.RAISELY_NO_TELEMETRY = 'true'; + + trackEvent('cli.list', { outcome: 'success', durationMs: 2 }); + await flushTelemetry(); + + expect(mocks.fetch).not.toHaveBeenCalled(); + expect(mocks.loadConfig).not.toHaveBeenCalled(); + }); +}); diff --git a/tests/validate.test.js b/tests/validate.test.js new file mode 100644 index 0000000..f559a7a --- /dev/null +++ b/tests/validate.test.js @@ -0,0 +1,303 @@ +import { describe, test } from 'vitest'; +import assert from 'node:assert/strict'; + +import { + validateCampaignSass, + validateComponent, +} from '../src/actions/validate.js'; + +function response(status, body = '', statusText = '') { + return { + ok: status >= 200 && status < 300, + status, + statusText, + async text() { + return body; + }, + }; +} + +describe('validateCampaignSass', () => { + test('returns ok=true when transpiler accepts styles', async () => { + const calls = []; + const result = await validateCampaignSass( + { + campaign: { uuid: 'campaign-uuid', path: 'my-campaign' }, + token: 'token-1', + }, + { + getBaseStylesFn: async () => '.base{}', + processStylesFn: async () => '.local{}', + fetchImpl: async (url, options) => { + calls.push({ url, options }); + return response(200, '.compiled{}'); + }, + } + ); + + assert.deepEqual(result, { ok: true }); + assert.equal(calls.length, 1); + assert.equal(calls[0].options.body, '.base{}.local{}'); + }); + + test('returns transpiler error body for invalid scss', async () => { + const result = await validateCampaignSass( + { + campaign: { uuid: 'campaign-uuid', path: 'my-campaign' }, + token: 'token-1', + }, + { + getBaseStylesFn: async () => '.base{}', + processStylesFn: async () => '.local{}', + fetchImpl: async () => + response(422, 'Expected ";" after property value'), + } + ); + + assert.deepEqual(result, { + ok: false, + error: 'Expected ";" after property value', + }); + }); + + test('refreshes once on first 401 and retries successfully', async () => { + let attempts = 0; + let refreshCount = 0; + const result = await validateCampaignSass( + { + campaign: { uuid: 'campaign-uuid', path: 'my-campaign' }, + token: 'expired-token', + }, + { + getBaseStylesFn: async () => '.base{}', + processStylesFn: async () => '.local{}', + refreshCredentialsFn: async () => { + refreshCount += 1; + }, + getCredentialsFn: async () => ({ token: 'fresh-token' }), + fetchImpl: async (_url, options) => { + attempts += 1; + if (attempts === 1) { + assert.equal(options.headers.Authorization, 'Bearer expired-token'); + return response(401, 'Unauthorized'); + } + assert.equal(options.headers.Authorization, 'Bearer fresh-token'); + return response(200, '.compiled{}'); + }, + } + ); + + assert.deepEqual(result, { ok: true }); + assert.equal(refreshCount, 1); + assert.equal(attempts, 2); + }); + + test('returns auth-failure shape after second 401', async () => { + let refreshCount = 0; + const result = await validateCampaignSass( + { + campaign: { uuid: 'campaign-uuid', path: 'my-campaign' }, + token: 'expired-token', + }, + { + getBaseStylesFn: async () => '.base{}', + processStylesFn: async () => '.local{}', + refreshCredentialsFn: async () => { + refreshCount += 1; + }, + getCredentialsFn: async () => ({ token: 'still-expired-token' }), + fetchImpl: async () => response(401, 'Unauthorized'), + } + ); + + assert.deepEqual(result, { + ok: false, + error: 'Authentication failed; run `raisely login`.', + }); + assert.equal(refreshCount, 1); + }); + + test('returns blocking error on network failures', async () => { + const result = await validateCampaignSass( + { + campaign: { uuid: 'campaign-uuid', path: 'my-campaign' }, + token: 'token-1', + }, + { + getBaseStylesFn: async () => '.base{}', + processStylesFn: async () => '.local{}', + fetchImpl: async () => { + throw new Error('socket hang up'); + }, + } + ); + + assert.deepEqual(result, { + ok: false, + error: 'socket hang up', + }); + }); + + test('returns auth-failure when credentials have no token', async () => { + let called = false; + const result = await validateCampaignSass( + { + campaign: { uuid: 'campaign-uuid', path: 'my-campaign' }, + }, + { + getBaseStylesFn: async () => '.base{}', + processStylesFn: async () => '.local{}', + getCredentialsFn: async () => ({}), + fetchImpl: async () => { + called = true; + return response(200, '.compiled{}'); + }, + } + ); + + assert.deepEqual(result, { + ok: false, + error: 'Authentication failed; run `raisely login`.', + }); + assert.equal(called, false); + }); + + test('normalizes transpiler URL with trailing slash after transpile', async () => { + let calledUrl = null; + const result = await validateCampaignSass( + { + campaign: { uuid: 'campaign-uuid', path: 'my-campaign' }, + token: 'token-1', + }, + { + transpilerUrl: 'https://sass.example.com/transpile/', + getBaseStylesFn: async () => '.base{}', + processStylesFn: async () => '.local{}', + fetchImpl: async (url) => { + calledUrl = url; + return response(200, '.compiled{}'); + }, + } + ); + + assert.deepEqual(result, { ok: true }); + assert.equal(calledUrl, 'https://sass.example.com/transpile'); + }); + + test('normalizes duplicate slashes before transpile path', async () => { + let calledUrl = null; + const result = await validateCampaignSass( + { + campaign: { uuid: 'campaign-uuid', path: 'my-campaign' }, + token: 'token-1', + }, + { + transpilerUrl: 'https://sass.example.com//transpile', + getBaseStylesFn: async () => '.base{}', + processStylesFn: async () => '.local{}', + fetchImpl: async (url) => { + calledUrl = url; + return response(200, '.compiled{}'); + }, + } + ); + + assert.deepEqual(result, { ok: true }); + assert.equal(calledUrl, 'https://sass.example.com/transpile'); + }); +}); + +describe('validateComponent', () => { + test('returns ok=true for valid component source', async () => { + let transformed = 0; + const result = await validateComponent( + { name: 'SignupForm' }, + { + cwd: () => '/repo', + fsModule: { + readFileSync(filePath) { + assert.equal( + filePath, + '/repo/components/SignupForm/SignupForm.js' + ); + return 'export default () =>
;'; + }, + }, + loadBabelCoreFn: async () => ({ + Babel: { + async transformAsync() { + transformed += 1; + return { code: 'ok' }; + }, + }, + presetEnv: Symbol('presetEnv'), + presetReact: Symbol('presetReact'), + classProps: Symbol('classProps'), + }), + } + ); + + assert.deepEqual(result, { ok: true }); + assert.equal(transformed, 1); + }); + + test('returns babel error for invalid component syntax', async () => { + const result = await validateComponent( + { name: 'BrokenComponent' }, + { + cwd: () => '/repo', + fsModule: { + readFileSync() { + return 'export default () =>
'; + }, + }, + loadBabelCoreFn: async () => ({ + Babel: { + async transformAsync() { + throw new Error('Unexpected token (1:25)'); + }, + }, + presetEnv: Symbol('presetEnv'), + presetReact: Symbol('presetReact'), + classProps: Symbol('classProps'), + }), + } + ); + + assert.deepEqual(result, { + ok: false, + error: 'Unexpected token (1:25)', + }); + }); + + test('returns babel error for unsupported language feature', async () => { + const result = await validateComponent( + { name: 'UnsupportedSyntax' }, + { + cwd: () => '/repo', + fsModule: { + readFileSync() { + return 'export default () => null;'; + }, + }, + loadBabelCoreFn: async () => ({ + Babel: { + async transformAsync() { + throw new Error( + 'Support for the experimental syntax is not currently enabled' + ); + }, + }, + presetEnv: Symbol('presetEnv'), + presetReact: Symbol('presetReact'), + classProps: Symbol('classProps'), + }), + } + ); + + assert.deepEqual(result, { + ok: false, + error: 'Support for the experimental syntax is not currently enabled', + }); + }); +}); diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..2e0986b --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['tests/**/*.test.js'], + pool: 'forks', + fileParallelism: false, + }, +}); diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 7aafe06..0000000 --- a/yarn.lock +++ /dev/null @@ -1,3215 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.1.0": - version "2.1.2" - resolved "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz" - dependencies: - "@jridgewell/trace-mapping" "^0.3.0" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz" - dependencies: - "@babel/highlight" "^7.16.7" - -"@babel/code-frame@^7.22.13": - version "7.22.13" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" - integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w== - dependencies: - "@babel/highlight" "^7.22.13" - chalk "^2.4.2" - -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.16.8", "@babel/compat-data@^7.17.0", "@babel/compat-data@^7.17.7": - version "7.17.7" - resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz" - -"@babel/core@^7.17.8": - version "7.17.8" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.17.8.tgz" - dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.7" - "@babel/helper-compilation-targets" "^7.17.7" - "@babel/helper-module-transforms" "^7.17.7" - "@babel/helpers" "^7.17.8" - "@babel/parser" "^7.17.8" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.3" - "@babel/types" "^7.17.0" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.1.2" - semver "^6.3.0" - -"@babel/generator@^7.17.7": - version "7.17.7" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz" - dependencies: - "@babel/types" "^7.17.0" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/generator@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420" - integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g== - dependencies: - "@babel/types" "^7.23.0" - "@jridgewell/gen-mapping" "^0.3.2" - "@jridgewell/trace-mapping" "^0.3.17" - jsesc "^2.5.1" - -"@babel/helper-annotate-as-pure@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz" - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-builder-binary-assignment-operator-visitor@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz" - dependencies: - "@babel/helper-explode-assignable-expression" "^7.16.7" - "@babel/types" "^7.16.7" - -"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.17.7": - version "7.17.7" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz" - dependencies: - "@babel/compat-data" "^7.17.7" - "@babel/helper-validator-option" "^7.16.7" - browserslist "^4.17.5" - semver "^6.3.0" - -"@babel/helper-create-class-features-plugin@^7.16.10", "@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.17.6": - version "7.17.6" - resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz" - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.16.7" - "@babel/helper-member-expression-to-functions" "^7.16.7" - "@babel/helper-optimise-call-expression" "^7.16.7" - "@babel/helper-replace-supers" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - -"@babel/helper-create-regexp-features-plugin@^7.16.7": - version "7.17.0" - resolved "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.17.0.tgz" - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - regexpu-core "^5.0.1" - -"@babel/helper-define-polyfill-provider@^0.3.1": - version "0.3.1" - resolved "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz" - dependencies: - "@babel/helper-compilation-targets" "^7.13.0" - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-plugin-utils" "^7.13.0" - "@babel/traverse" "^7.13.0" - debug "^4.1.1" - lodash.debounce "^4.0.8" - resolve "^1.14.2" - semver "^6.1.2" - -"@babel/helper-environment-visitor@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz" - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-environment-visitor@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" - integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== - -"@babel/helper-explode-assignable-expression@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz" - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-function-name@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz" - dependencies: - "@babel/helper-get-function-arity" "^7.16.7" - "@babel/template" "^7.16.7" - "@babel/types" "^7.16.7" - -"@babel/helper-function-name@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" - integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== - dependencies: - "@babel/template" "^7.22.15" - "@babel/types" "^7.23.0" - -"@babel/helper-get-function-arity@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz" - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-hoist-variables@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz" - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-hoist-variables@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" - integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-member-expression-to-functions@^7.16.7": - version "7.17.7" - resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz" - dependencies: - "@babel/types" "^7.17.0" - -"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz" - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-module-transforms@^7.16.7", "@babel/helper-module-transforms@^7.17.7": - version "7.17.7" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz" - dependencies: - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-simple-access" "^7.17.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/helper-validator-identifier" "^7.16.7" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.3" - "@babel/types" "^7.17.0" - -"@babel/helper-optimise-call-expression@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz" - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz" - -"@babel/helper-remap-async-to-generator@^7.16.8": - version "7.16.8" - resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz" - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-wrap-function" "^7.16.8" - "@babel/types" "^7.16.8" - -"@babel/helper-replace-supers@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz" - dependencies: - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-member-expression-to-functions" "^7.16.7" - "@babel/helper-optimise-call-expression" "^7.16.7" - "@babel/traverse" "^7.16.7" - "@babel/types" "^7.16.7" - -"@babel/helper-simple-access@^7.17.7": - version "7.17.7" - resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz" - dependencies: - "@babel/types" "^7.17.0" - -"@babel/helper-skip-transparent-expression-wrappers@^7.16.0": - version "7.16.0" - resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz" - dependencies: - "@babel/types" "^7.16.0" - -"@babel/helper-split-export-declaration@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz" - dependencies: - "@babel/types" "^7.16.7" - -"@babel/helper-split-export-declaration@^7.22.6": - version "7.22.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" - integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== - dependencies: - "@babel/types" "^7.22.5" - -"@babel/helper-string-parser@^7.22.5": - version "7.22.5" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" - integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== - -"@babel/helper-validator-identifier@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz" - -"@babel/helper-validator-identifier@^7.22.20": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" - integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== - -"@babel/helper-validator-option@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz" - -"@babel/helper-wrap-function@^7.16.8": - version "7.16.8" - resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz" - dependencies: - "@babel/helper-function-name" "^7.16.7" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.16.8" - "@babel/types" "^7.16.8" - -"@babel/helpers@^7.17.8": - version "7.17.8" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.8.tgz" - dependencies: - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.3" - "@babel/types" "^7.17.0" - -"@babel/highlight@^7.16.7": - version "7.16.10" - resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz" - dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/highlight@^7.22.13": - version "7.22.20" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" - integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg== - dependencies: - "@babel/helper-validator-identifier" "^7.22.20" - chalk "^2.4.2" - js-tokens "^4.0.0" - -"@babel/parser@^7.16.7", "@babel/parser@^7.17.8": - version "7.17.8" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz" - -"@babel/parser@^7.22.15", "@babel/parser@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" - integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== - -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" - "@babel/plugin-proposal-optional-chaining" "^7.16.7" - -"@babel/plugin-proposal-async-generator-functions@^7.16.8": - version "7.16.8" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-remap-async-to-generator" "^7.16.8" - "@babel/plugin-syntax-async-generators" "^7.8.4" - -"@babel/plugin-proposal-class-properties@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz" - dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-proposal-class-static-block@^7.16.7": - version "7.17.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz" - dependencies: - "@babel/helper-create-class-features-plugin" "^7.17.6" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - -"@babel/plugin-proposal-dynamic-import@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - -"@babel/plugin-proposal-export-namespace-from@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - -"@babel/plugin-proposal-json-strings@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-json-strings" "^7.8.3" - -"@babel/plugin-proposal-logical-assignment-operators@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - -"@babel/plugin-proposal-nullish-coalescing-operator@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - -"@babel/plugin-proposal-numeric-separator@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - -"@babel/plugin-proposal-object-rest-spread@^7.16.7": - version "7.17.3" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.17.3.tgz" - dependencies: - "@babel/compat-data" "^7.17.0" - "@babel/helper-compilation-targets" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.16.7" - -"@babel/plugin-proposal-optional-catch-binding@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - -"@babel/plugin-proposal-optional-chaining@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - -"@babel/plugin-proposal-private-methods@^7.16.11": - version "7.16.11" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz" - dependencies: - "@babel/helper-create-class-features-plugin" "^7.16.10" - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-proposal-private-property-in-object@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz" - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-create-class-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - -"@babel/plugin-proposal-unicode-property-regex@^7.16.7", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz" - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.12.13": - version "7.12.13" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-class-static-block@^7.14.5": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-dynamic-import@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-export-namespace-from@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.8.3" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-jsx@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-private-property-in-object@^7.14.5": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-syntax-top-level-await@^7.14.5": - version "7.14.5" - resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/plugin-transform-arrow-functions@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-async-to-generator@^7.16.8": - version "7.16.8" - resolved "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz" - dependencies: - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-remap-async-to-generator" "^7.16.8" - -"@babel/plugin-transform-block-scoped-functions@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-block-scoping@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-classes@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz" - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.16.7" - "@babel/helper-optimise-call-expression" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-replace-supers" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - globals "^11.1.0" - -"@babel/plugin-transform-computed-properties@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-destructuring@^7.16.7": - version "7.17.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.17.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-dotall-regex@^7.16.7", "@babel/plugin-transform-dotall-regex@^7.4.4": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz" - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-duplicate-keys@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-exponentiation-operator@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz" - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-for-of@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-function-name@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz" - dependencies: - "@babel/helper-compilation-targets" "^7.16.7" - "@babel/helper-function-name" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-literals@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-member-expression-literals@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-modules-amd@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz" - dependencies: - "@babel/helper-module-transforms" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-commonjs@^7.16.8": - version "7.17.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.17.7.tgz" - dependencies: - "@babel/helper-module-transforms" "^7.17.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-simple-access" "^7.17.7" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-systemjs@^7.16.7": - version "7.17.8" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.17.8.tgz" - dependencies: - "@babel/helper-hoist-variables" "^7.16.7" - "@babel/helper-module-transforms" "^7.17.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-validator-identifier" "^7.16.7" - babel-plugin-dynamic-import-node "^2.3.3" - -"@babel/plugin-transform-modules-umd@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz" - dependencies: - "@babel/helper-module-transforms" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-named-capturing-groups-regex@^7.16.8": - version "7.16.8" - resolved "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz" - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.7" - -"@babel/plugin-transform-new-target@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-object-super@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-replace-supers" "^7.16.7" - -"@babel/plugin-transform-parameters@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-property-literals@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-react-display-name@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-react-jsx-development@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.16.7.tgz" - dependencies: - "@babel/plugin-transform-react-jsx" "^7.16.7" - -"@babel/plugin-transform-react-jsx@^7.16.7": - version "7.17.3" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.17.3.tgz" - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-module-imports" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/plugin-syntax-jsx" "^7.16.7" - "@babel/types" "^7.17.0" - -"@babel/plugin-transform-react-pure-annotations@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.16.7.tgz" - dependencies: - "@babel/helper-annotate-as-pure" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-regenerator@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz" - dependencies: - regenerator-transform "^0.14.2" - -"@babel/plugin-transform-reserved-words@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-shorthand-properties@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-spread@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-skip-transparent-expression-wrappers" "^7.16.0" - -"@babel/plugin-transform-sticky-regex@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-template-literals@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-typeof-symbol@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-unicode-escapes@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/plugin-transform-unicode-regex@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz" - dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - -"@babel/preset-env@^7.16.11": - version "7.16.11" - resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz" - dependencies: - "@babel/compat-data" "^7.16.8" - "@babel/helper-compilation-targets" "^7.16.7" - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-validator-option" "^7.16.7" - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.16.7" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.16.7" - "@babel/plugin-proposal-async-generator-functions" "^7.16.8" - "@babel/plugin-proposal-class-properties" "^7.16.7" - "@babel/plugin-proposal-class-static-block" "^7.16.7" - "@babel/plugin-proposal-dynamic-import" "^7.16.7" - "@babel/plugin-proposal-export-namespace-from" "^7.16.7" - "@babel/plugin-proposal-json-strings" "^7.16.7" - "@babel/plugin-proposal-logical-assignment-operators" "^7.16.7" - "@babel/plugin-proposal-nullish-coalescing-operator" "^7.16.7" - "@babel/plugin-proposal-numeric-separator" "^7.16.7" - "@babel/plugin-proposal-object-rest-spread" "^7.16.7" - "@babel/plugin-proposal-optional-catch-binding" "^7.16.7" - "@babel/plugin-proposal-optional-chaining" "^7.16.7" - "@babel/plugin-proposal-private-methods" "^7.16.11" - "@babel/plugin-proposal-private-property-in-object" "^7.16.7" - "@babel/plugin-proposal-unicode-property-regex" "^7.16.7" - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-class-properties" "^7.12.13" - "@babel/plugin-syntax-class-static-block" "^7.14.5" - "@babel/plugin-syntax-dynamic-import" "^7.8.3" - "@babel/plugin-syntax-export-namespace-from" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.10.4" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-private-property-in-object" "^7.14.5" - "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.16.7" - "@babel/plugin-transform-async-to-generator" "^7.16.8" - "@babel/plugin-transform-block-scoped-functions" "^7.16.7" - "@babel/plugin-transform-block-scoping" "^7.16.7" - "@babel/plugin-transform-classes" "^7.16.7" - "@babel/plugin-transform-computed-properties" "^7.16.7" - "@babel/plugin-transform-destructuring" "^7.16.7" - "@babel/plugin-transform-dotall-regex" "^7.16.7" - "@babel/plugin-transform-duplicate-keys" "^7.16.7" - "@babel/plugin-transform-exponentiation-operator" "^7.16.7" - "@babel/plugin-transform-for-of" "^7.16.7" - "@babel/plugin-transform-function-name" "^7.16.7" - "@babel/plugin-transform-literals" "^7.16.7" - "@babel/plugin-transform-member-expression-literals" "^7.16.7" - "@babel/plugin-transform-modules-amd" "^7.16.7" - "@babel/plugin-transform-modules-commonjs" "^7.16.8" - "@babel/plugin-transform-modules-systemjs" "^7.16.7" - "@babel/plugin-transform-modules-umd" "^7.16.7" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.16.8" - "@babel/plugin-transform-new-target" "^7.16.7" - "@babel/plugin-transform-object-super" "^7.16.7" - "@babel/plugin-transform-parameters" "^7.16.7" - "@babel/plugin-transform-property-literals" "^7.16.7" - "@babel/plugin-transform-regenerator" "^7.16.7" - "@babel/plugin-transform-reserved-words" "^7.16.7" - "@babel/plugin-transform-shorthand-properties" "^7.16.7" - "@babel/plugin-transform-spread" "^7.16.7" - "@babel/plugin-transform-sticky-regex" "^7.16.7" - "@babel/plugin-transform-template-literals" "^7.16.7" - "@babel/plugin-transform-typeof-symbol" "^7.16.7" - "@babel/plugin-transform-unicode-escapes" "^7.16.7" - "@babel/plugin-transform-unicode-regex" "^7.16.7" - "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.16.8" - babel-plugin-polyfill-corejs2 "^0.3.0" - babel-plugin-polyfill-corejs3 "^0.5.0" - babel-plugin-polyfill-regenerator "^0.3.0" - core-js-compat "^3.20.2" - semver "^6.3.0" - -"@babel/preset-modules@^0.1.5": - version "0.1.5" - resolved "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" - "@babel/plugin-transform-dotall-regex" "^7.4.4" - "@babel/types" "^7.4.4" - esutils "^2.0.2" - -"@babel/preset-react@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.16.7.tgz" - dependencies: - "@babel/helper-plugin-utils" "^7.16.7" - "@babel/helper-validator-option" "^7.16.7" - "@babel/plugin-transform-react-display-name" "^7.16.7" - "@babel/plugin-transform-react-jsx" "^7.16.7" - "@babel/plugin-transform-react-jsx-development" "^7.16.7" - "@babel/plugin-transform-react-pure-annotations" "^7.16.7" - -"@babel/runtime@^7.8.4": - version "7.17.8" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.8.tgz" - dependencies: - regenerator-runtime "^0.13.4" - -"@babel/template@^7.16.7": - version "7.16.7" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz" - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/parser" "^7.16.7" - "@babel/types" "^7.16.7" - -"@babel/template@^7.22.15": - version "7.22.15" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" - integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/parser" "^7.22.15" - "@babel/types" "^7.22.15" - -"@babel/traverse@^7.13.0", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.17.3": - version "7.23.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" - integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.0" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.0" - "@babel/types" "^7.23.0" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.4.4": - version "7.17.0" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz" - dependencies: - "@babel/helper-validator-identifier" "^7.16.7" - to-fast-properties "^2.0.0" - -"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" - integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== - dependencies: - "@babel/helper-string-parser" "^7.22.5" - "@babel/helper-validator-identifier" "^7.22.20" - to-fast-properties "^2.0.0" - -"@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": - version "1.1.3" - resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" - -"@jridgewell/gen-mapping@^0.3.2": - version "0.3.3" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" - integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@^3.0.3": - version "3.0.5" - resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" - integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.11" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz" - -"@jridgewell/sourcemap-codec@^1.4.14": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== - -"@jridgewell/trace-mapping@^0.3.0": - version "0.3.4" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.4.tgz" - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9": - version "0.3.20" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" - integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@npmcli/fs@^1.0.0": - version "1.1.1" - resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz" - dependencies: - "@gar/promisify" "^1.0.1" - semver "^7.3.5" - -"@npmcli/fs@^2.1.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-2.1.2.tgz#a9e2541a4a2fec2e69c29b35e6060973da79b865" - integrity sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ== - dependencies: - "@gar/promisify" "^1.1.3" - semver "^7.3.5" - -"@npmcli/move-file@^1.0.1": - version "1.1.2" - resolved "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz" - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" - -"@npmcli/move-file@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-2.0.1.tgz#26f6bdc379d87f75e55739bab89db525b06100e4" - integrity sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ== - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" - -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz" - -"@tootallnate/once@2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" - integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== - -"@types/glob@*": - version "7.2.0" - resolved "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz" - dependencies: - "@types/minimatch" "*" - "@types/node" "*" - -"@types/http-proxy@^1.17.8": - version "1.17.8" - resolved "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.8.tgz" - dependencies: - "@types/node" "*" - -"@types/minimatch@*": - version "3.0.5" - resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz" - -"@types/minimist@^1.2.0": - version "1.2.2" - resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz" - -"@types/node@*": - version "17.0.23" - resolved "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz" - -"@types/normalize-package-data@^2.4.0": - version "2.4.1" - resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz" - -abbrev@1: - version "1.1.1" - resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" - -accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz" - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -agent-base@6, agent-base@^6.0.2: - version "6.0.2" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" - dependencies: - debug "4" - -agentkeepalive@^4.1.3: - version "4.2.1" - resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz" - dependencies: - debug "^4.1.0" - depd "^1.1.2" - humanize-ms "^1.2.1" - -agentkeepalive@^4.2.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" - integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== - dependencies: - humanize-ms "^1.2.1" - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz" - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ansi-escapes@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz" - -ansi-regex@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz" - -ansi-regex@^4.1.0: - version "4.1.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" - dependencies: - color-convert "^2.0.1" - -"aproba@^1.0.3 || ^2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz" - -are-we-there-yet@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz" - dependencies: - delegates "^1.0.0" - readable-stream "^3.6.0" - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" - integrity "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - -arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" - integrity "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==" - -async-foreach@^0.1.3: - version "0.1.3" - resolved "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz" - integrity "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==" - -babel-plugin-dynamic-import-node@^2.3.3: - version "2.3.3" - resolved "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz" - dependencies: - object.assign "^4.1.0" - -babel-plugin-polyfill-corejs2@^0.3.0: - version "0.3.1" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz" - dependencies: - "@babel/compat-data" "^7.13.11" - "@babel/helper-define-polyfill-provider" "^0.3.1" - semver "^6.1.1" - -babel-plugin-polyfill-corejs3@^0.5.0: - version "0.5.2" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz" - dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.1" - core-js-compat "^3.21.0" - -babel-plugin-polyfill-regenerator@^0.3.0: - version "0.3.1" - resolved "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz" - dependencies: - "@babel/helper-define-polyfill-provider" "^0.3.1" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" - -body-parser@1.19.2: - version "1.19.2" - resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz" - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.8.1" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.9.7" - raw-body "2.4.3" - type-is "~1.6.18" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" - dependencies: - fill-range "^7.0.1" - -browserslist@^4.17.5, browserslist@^4.19.1: - version "4.20.2" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz" - dependencies: - caniuse-lite "^1.0.30001317" - electron-to-chromium "^1.4.84" - escalade "^3.1.1" - node-releases "^2.0.2" - picocolors "^1.0.0" - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" - -cacache@^15.2.0: - version "15.3.0" - resolved "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz" - dependencies: - "@npmcli/fs" "^1.0.0" - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^6.0.0" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.0.2" - unique-filename "^1.1.1" - -cacache@^16.1.0: - version "16.1.3" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-16.1.3.tgz#a02b9f34ecfaf9a78c9f4bc16fceb94d5d67a38e" - integrity sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ== - dependencies: - "@npmcli/fs" "^2.1.0" - "@npmcli/move-file" "^2.0.0" - chownr "^2.0.0" - fs-minipass "^2.1.0" - glob "^8.0.1" - infer-owner "^1.0.4" - lru-cache "^7.7.1" - minipass "^3.1.6" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - mkdirp "^1.0.4" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^9.0.0" - tar "^6.1.11" - unique-filename "^2.0.0" - -call-bind@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz" - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -camelcase-keys@^6.2.2: - version "6.2.2" - resolved "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz" - dependencies: - camelcase "^5.3.1" - map-obj "^4.0.0" - quick-lru "^4.0.1" - -camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" - -caniuse-lite@^1.0.30001317: - version "1.0.30001320" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz" - -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz" - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" - -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz" - integrity "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==" - dependencies: - restore-cursor "^2.0.0" - -cli-spinners@^2.0.0: - version "2.6.1" - resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz" - -cli-width@^2.0.0: - version "2.2.1" - resolved "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz" - integrity "sha1-2jCcwmPfFZlMaIypAheco8fNfH4= sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - integrity "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - -color-support@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" - -commander@^9.2.0: - version "9.2.0" - resolved "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - integrity "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - -console-control-strings@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" - integrity "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz" - -convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz" - dependencies: - safe-buffer "~5.1.1" - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" - integrity "sha1-4wOogrNCzD7oylE6eZmXNNqzriw= sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - -cookie@0.4.2: - version "0.4.2" - resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz" - -core-js-compat@^3.20.2, core-js-compat@^3.21.0: - version "3.21.1" - resolved "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.21.1.tgz" - dependencies: - browserslist "^4.19.1" - semver "7.0.0" - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - integrity "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" - -cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -data-uri-to-buffer@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz" - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" - dependencies: - ms "2.0.0" - -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3: - version "4.3.4" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" - dependencies: - ms "2.1.2" - -decamelize-keys@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz" - integrity "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==" - dependencies: - decamelize "^1.1.0" - map-obj "^1.0.0" - -decamelize@^1.1.0, decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" - integrity "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" - -defaults@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz" - integrity "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==" - dependencies: - clone "^1.0.2" - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz" - -define-properties@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz" - dependencies: - object-keys "^1.0.12" - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" - integrity "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" - -depd@^1.1.2, depd@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" - integrity "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" - -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz" - integrity "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" - integrity "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - -electron-to-chromium@^1.4.84: - version "1.4.96" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.96.tgz" - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" - integrity "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" - -encoding@^0.1.12, encoding@^0.1.13: - version "0.1.13" - resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz" - dependencies: - iconv-lite "^0.6.2" - -env-paths@^2.2.0: - version "2.2.1" - resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" - -err-code@^2.0.2: - version "2.0.3" - resolved "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz" - dependencies: - is-arrayish "^0.2.1" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" - integrity "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - integrity "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" - integrity "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" - -express@^4.17.3: - version "4.17.3" - resolved "https://registry.npmjs.org/express/-/express-4.17.3.tgz" - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.19.2" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.4.2" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.9.7" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.17.2" - serve-static "1.14.2" - setprototypeof "1.2.0" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz" - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -fetch-blob@^3.1.2, fetch-blob@^3.1.4: - version "3.1.5" - resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.5.tgz" - dependencies: - node-domexception "^1.0.0" - web-streams-polyfill "^3.0.3" - -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz" - integrity "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==" - dependencies: - escape-string-regexp "^1.0.5" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" - dependencies: - to-regex-range "^5.0.1" - -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz" - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -folder-hash@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/folder-hash/-/folder-hash-4.0.2.tgz" - dependencies: - debug "^4.3.3" - graceful-fs "~4.2.9" - minimatch "~5.0.0" - -follow-redirects@^1.0.0: - version "1.14.9" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz" - -formdata-polyfill@^4.0.10: - version "4.0.10" - resolved "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz" - dependencies: - fetch-blob "^3.1.2" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" - integrity "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" - -fs-minipass@^2.0.0, fs-minipass@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" - dependencies: - minipass "^3.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" - integrity "sha1-FQStJSMVjKpA20onh8sBQRmU6k8= sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" - -fzstd@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/fzstd/-/fzstd-0.1.1.tgz#a3da29f2fff45070ca90073f866d97e0c56a4a52" - integrity sha512-dkuVSOKKwh3eas5VkJy1AW1vFpet8TA/fGmVA5krThl8YcOVE/8ZIoEA1+U1vEn5ckxxhLirSdY837azmbaNHA== - -gauge@^4.0.0: - version "4.0.3" - resolved "https://registry.npmjs.org/gauge/-/gauge-4.0.3.tgz" - dependencies: - aproba "^1.0.3 || ^2.0.0" - color-support "^1.1.3" - console-control-strings "^1.1.0" - has-unicode "^2.0.1" - signal-exit "^3.0.7" - string-width "^4.2.3" - strip-ansi "^6.0.1" - wide-align "^1.1.5" - -gaze@^1.0.0: - version "1.1.3" - resolved "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz" - dependencies: - globule "^1.0.0" - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz" - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" - -get-intrinsic@^1.0.2: - version "1.1.1" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz" - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz" - integrity "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==" - -glob-promise@^3.4.0: - version "3.4.0" - resolved "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz" - dependencies: - "@types/glob" "*" - -glob@^7.0.0, glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: - version "7.2.0" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^8.0.1: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -glob@~7.1.1: - version "7.1.7" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" - -globule@^1.0.0: - version "1.3.3" - resolved "https://registry.npmjs.org/globule/-/globule-1.3.3.tgz" - dependencies: - glob "~7.1.1" - lodash "~4.17.10" - minimatch "~3.0.2" - -graceful-fs@^4.2.6, graceful-fs@~4.2.9: - version "4.2.9" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz" - -hard-rejection@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" - integrity "sha1-tdRU3CGZriJWmfNGfloH87lVuv0= sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" - -has-symbols@^1.0.1: - version "1.0.3" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" - -has-unicode@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" - integrity "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz" - dependencies: - function-bind "^1.1.1" - -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz" - -hosted-git-info@^4.0.1: - version "4.1.0" - resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz" - dependencies: - lru-cache "^6.0.0" - -http-cache-semantics@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - -http-errors@1.8.1: - version "1.8.1" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz" - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.1" - -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz" - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - -http-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" - integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== - dependencies: - "@tootallnate/once" "2" - agent-base "6" - debug "4" - -http-proxy-middleware@^2.0.4: - version "2.0.4" - resolved "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.4.tgz" - dependencies: - "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz" - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz" - dependencies: - agent-base "6" - debug "4" - -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz" - integrity "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0= sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==" - dependencies: - ms "^2.0.0" - -iconv-lite@0.4.24, iconv-lite@^0.4.24: - version "0.4.24" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" - dependencies: - safer-buffer ">= 2.1.2 < 3" - -iconv-lite@^0.6.2: - version "0.6.3" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" - integrity "sha1-khi5srkoojixPcT7a21XbyMUU+o= sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" - -infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" - integrity "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==" - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" - -inquirer@^6.3.1: - version "6.5.2" - resolved "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz" - dependencies: - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - lodash "^4.17.12" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" - -ip@^1.1.5: - version "1.1.5" - resolved "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz" - integrity "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= sha512-rBtCAQAJm8A110nbwn6YdveUnuZH3WrC36IwkRXxDnq53JvXA2NVQvB7IHyKomxK1MJ4VDNw3UtFDdXQ+AvLYA==" - -ip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" - integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz" - integrity "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" - -is-core-module@^2.5.0, is-core-module@^2.8.1: - version "2.8.1" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz" - dependencies: - has "^1.0.3" - -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" - integrity "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" - integrity "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" - -is-glob@^4.0.1: - version "4.0.3" - resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" - dependencies: - is-extglob "^2.1.1" - -is-lambda@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz" - integrity "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU= sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" - -is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz" - integrity "sha1-caUMhCnfync8kqOQpKA7OfzVHT4= sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==" - -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz" - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz" - dependencies: - is-docker "^2.0.0" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - integrity "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" - integrity "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" - -js-base64@^2.4.9: - version "2.6.4" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" - integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" - -jsesc@~0.5.0: - version "0.5.0" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" - integrity "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==" - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz" - -json5@^2.1.2: - version "2.2.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" - -jwt-decode@^3.0.0-beta.2: - version "3.1.2" - resolved "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz" - -kind-of@^6.0.3: - version "6.0.3" - resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz" - dependencies: - p-locate "^4.1.0" - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" - integrity "sha1-gteb/zCmfEAF/9XiUVMArZyk168= sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" - -lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.15, lodash@~4.17.10: - version "4.17.21" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" - -log-symbols@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz" - dependencies: - chalk "^2.0.1" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" - dependencies: - yallist "^4.0.0" - -lru-cache@^7.7.1: - version "7.18.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" - integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== - -make-fetch-happen@^10.0.4: - version "10.2.1" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz#f5e3835c5e9817b617f2770870d9492d28678164" - integrity sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w== - dependencies: - agentkeepalive "^4.2.1" - cacache "^16.1.0" - http-cache-semantics "^4.1.0" - http-proxy-agent "^5.0.0" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^7.7.1" - minipass "^3.1.6" - minipass-collect "^1.0.2" - minipass-fetch "^2.0.3" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - negotiator "^0.6.3" - promise-retry "^2.0.1" - socks-proxy-agent "^7.0.0" - ssri "^9.0.0" - -make-fetch-happen@^9.1.0: - version "9.1.0" - resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz" - dependencies: - agentkeepalive "^4.1.3" - cacache "^15.2.0" - http-cache-semantics "^4.1.0" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^6.0.0" - minipass "^3.1.3" - minipass-collect "^1.0.2" - minipass-fetch "^1.3.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - negotiator "^0.6.2" - promise-retry "^2.0.1" - socks-proxy-agent "^6.0.0" - ssri "^8.0.0" - -map-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz" - integrity "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==" - -map-obj@^4.0.0: - version "4.3.0" - resolved "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" - integrity "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" - -meow@^9.0.0: - version "9.0.0" - resolved "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz" - dependencies: - "@types/minimist" "^1.2.0" - camelcase-keys "^6.2.2" - decamelize "^1.2.0" - decamelize-keys "^1.1.0" - hard-rejection "^2.1.0" - minimist-options "4.1.0" - normalize-package-data "^3.0.0" - read-pkg-up "^7.0.1" - redent "^3.0.0" - trim-newlines "^3.0.0" - type-fest "^0.18.0" - yargs-parser "^20.2.3" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" - integrity "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" - integrity "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" - -micromatch@^4.0.2: - version "4.0.5" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" - -mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" - -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz" - -min-indent@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz" - -minimatch@^3.0.4: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@~3.0.2: - version "3.0.8" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz" - dependencies: - brace-expansion "^1.1.7" - -minimatch@~5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz" - dependencies: - brace-expansion "^2.0.1" - -minimist-options@4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz" - dependencies: - arrify "^1.0.1" - is-plain-obj "^1.1.0" - kind-of "^6.0.3" - -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz" - dependencies: - minipass "^3.0.0" - -minipass-fetch@^1.3.2: - version "1.4.1" - resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz" - dependencies: - minipass "^3.1.0" - minipass-sized "^1.0.3" - minizlib "^2.0.0" - optionalDependencies: - encoding "^0.1.12" - -minipass-fetch@^2.0.3: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-2.1.2.tgz#95560b50c472d81a3bc76f20ede80eaed76d8add" - integrity sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA== - dependencies: - minipass "^3.1.6" - minipass-sized "^1.0.3" - minizlib "^2.1.2" - optionalDependencies: - encoding "^0.1.13" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz" - dependencies: - minipass "^3.0.0" - -minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: - version "1.2.4" - resolved "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz" - dependencies: - minipass "^3.0.0" - -minipass-sized@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz" - dependencies: - minipass "^3.0.0" - -minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: - version "3.1.6" - resolved "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz" - dependencies: - yallist "^4.0.0" - -minipass@^3.1.6: - version "3.3.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" - integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== - dependencies: - yallist "^4.0.0" - -minipass@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" - integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== - -minizlib@^2.0.0, minizlib@^2.1.1, minizlib@^2.1.2: - version "2.1.2" - resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" - integrity "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - -ms@2.1.2, ms@^2.0.0: - version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" - -ms@2.1.3: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz" - integrity "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==" - -nan@^2.17.0: - version "2.18.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" - integrity sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w== - -negotiator@0.6.3, negotiator@^0.6.2, negotiator@^0.6.3: - version "0.6.3" - resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" - -node-domexception@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz" - -node-fetch@^3.2.3: - version "3.2.10" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.10.tgz#e8347f94b54ae18b57c9c049ef641cef398a85c8" - dependencies: - data-uri-to-buffer "^4.0.0" - fetch-blob "^3.1.4" - formdata-polyfill "^4.0.10" - -node-gyp@^8.4.1: - version "8.4.1" - resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz" - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.6" - make-fetch-happen "^9.1.0" - nopt "^5.0.0" - npmlog "^6.0.0" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.2" - which "^2.0.2" - -node-releases@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz" - -node-sass@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-9.0.0.tgz#c21cd17bd9379c2d09362b3baf2cbf089bce08ed" - integrity sha512-yltEuuLrfH6M7Pq2gAj5B6Zm7m+gdZoG66wTqG6mIZV/zijq3M2OO2HswtT6oBspPyFhHDcaxWpsBm0fRNDHPg== - dependencies: - async-foreach "^0.1.3" - chalk "^4.1.2" - cross-spawn "^7.0.3" - gaze "^1.0.0" - get-stdin "^4.0.1" - glob "^7.0.3" - lodash "^4.17.15" - make-fetch-happen "^10.0.4" - meow "^9.0.0" - nan "^2.17.0" - node-gyp "^8.4.1" - sass-graph "^4.0.1" - stdout-stream "^1.4.0" - "true-case-path" "^2.2.1" - -node-watch@^0.7.1: - version "0.7.3" - resolved "https://registry.npmjs.org/node-watch/-/node-watch-0.7.3.tgz" - -nopt@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz" - dependencies: - abbrev "1" - -normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz" - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-package-data@^3.0.0: - version "3.0.3" - resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz" - dependencies: - hosted-git-info "^4.0.1" - is-core-module "^2.5.0" - semver "^7.3.4" - validate-npm-package-license "^3.0.1" - -npmlog@^6.0.0: - version "6.0.1" - resolved "https://registry.npmjs.org/npmlog/-/npmlog-6.0.1.tgz" - dependencies: - are-we-there-yet "^3.0.0" - console-control-strings "^1.1.0" - gauge "^4.0.0" - set-blocking "^2.0.0" - -object-keys@^1.0.12, object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" - -object.assign@^4.1.0: - version "4.1.2" - resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz" - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" - object-keys "^1.1.1" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz" - integrity "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==" - dependencies: - ee-first "1.1.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - integrity "sha1-WDsap3WWHUsROsF9nFC6753Xa9E= sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==" - dependencies: - wrappy "1" - -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz" - integrity "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==" - dependencies: - mimic-fn "^1.0.0" - -open@^8.4.0: - version "8.4.0" - resolved "https://registry.npmjs.org/open/-/open-8.4.0.tgz" - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - -ora@^3.4.0: - version "3.4.0" - resolved "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz" - dependencies: - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-spinners "^2.0.0" - log-symbols "^2.2.0" - strip-ansi "^5.2.0" - wcwidth "^1.0.1" - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz" - integrity "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz" - dependencies: - p-try "^2.0.0" - -p-limit@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz" - dependencies: - yocto-queue "^1.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" - dependencies: - p-limit "^2.2.0" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz" - dependencies: - aggregate-error "^3.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" - -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" - integrity "sha1-F0uSaHNVNP+8es5r9TpanhtcX18= sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" - integrity "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" - -picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" - -prettier@^2.6.2: - version "2.6.2" - resolved "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz" - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz" - integrity "sha1-mEcocL8igTL8vdhoEputEsPAKeM= sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==" - -promise-retry@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz" - dependencies: - err-code "^2.0.2" - retry "^0.12.0" - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz" - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -qs@6.9.7: - version "6.9.7" - resolved "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz" - -quick-lru@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz" - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" - -raw-body@2.4.3: - version "2.4.3" - resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz" - dependencies: - bytes "3.1.2" - http-errors "1.8.1" - iconv-lite "0.4.24" - unpipe "1.0.0" - -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz" - dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" - -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz" - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - -readable-stream@^2.0.1: - version "2.3.7" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -redent@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz" - dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" - -regenerate-unicode-properties@^10.0.1: - version "10.0.1" - resolved "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz" - dependencies: - regenerate "^1.4.2" - -regenerate@^1.4.2: - version "1.4.2" - resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz" - -regenerator-runtime@^0.13.4: - version "0.13.9" - resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" - -regenerator-transform@^0.14.2: - version "0.14.5" - resolved "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz" - dependencies: - "@babel/runtime" "^7.8.4" - -regexpu-core@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz" - dependencies: - regenerate "^1.4.2" - regenerate-unicode-properties "^10.0.1" - regjsgen "^0.6.0" - regjsparser "^0.8.2" - unicode-match-property-ecmascript "^2.0.0" - unicode-match-property-value-ecmascript "^2.0.0" - -regjsgen@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/regjsgen/-/regjsgen-0.6.0.tgz" - -regjsparser@^0.8.2: - version "0.8.4" - resolved "https://registry.npmjs.org/regjsparser/-/regjsparser-0.8.4.tgz" - dependencies: - jsesc "~0.5.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" - integrity "sha1-jGStX9MNqxyXbiNE/+f3kqam30I= sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz" - integrity "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - -resolve@^1.10.0, resolve@^1.14.2: - version "1.22.0" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz" - dependencies: - is-core-module "^2.8.1" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz" - integrity "sha1-n37ih/gv0ybU/RYpI9YhKe7g368= sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==" - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz" - integrity "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==" - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" - dependencies: - glob "^7.1.3" - -run-async@^2.2.0: - version "2.4.1" - resolved "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz" - -rxjs@^6.4.0: - version "6.6.7" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz" - dependencies: - tslib "^1.9.0" - -safe-buffer@5.2.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" - -sass-graph@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-4.0.1.tgz#2ff8ca477224d694055bf4093f414cf6cfad1d2e" - integrity sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA== - dependencies: - glob "^7.0.0" - lodash "^4.17.11" - scss-tokenizer "^0.4.3" - yargs "^17.2.1" - -scss-tokenizer@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz#1058400ee7d814d71049c29923d2b25e61dc026c" - integrity sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw== - dependencies: - js-base64 "^2.4.9" - source-map "^0.7.3" - -"semver@2 || 3 || 4 || 5": - version "5.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" - integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== - -semver@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - -semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.3.4, semver@^7.3.5: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - -send@0.17.2: - version "0.17.2" - resolved "https://registry.npmjs.org/send/-/send-0.17.2.tgz" - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "1.8.1" - mime "1.6.0" - ms "2.1.3" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - -serve-static@1.14.2: - version "1.14.2" - resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz" - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.2" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" - integrity "sha1-BF+XgtARrppoA93TgrJDkrPYkPc= sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" - -signal-exit@^3.0.2, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" - -smart-buffer@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" - -socks-proxy-agent@^6.0.0: - version "6.1.1" - resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz" - dependencies: - agent-base "^6.0.2" - debug "^4.3.1" - socks "^2.6.1" - -socks-proxy-agent@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz#dc069ecf34436621acb41e3efa66ca1b5fed15b6" - integrity sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww== - dependencies: - agent-base "^6.0.2" - debug "^4.3.3" - socks "^2.6.2" - -socks@^2.6.1: - version "2.6.2" - resolved "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz" - dependencies: - ip "^1.1.5" - smart-buffer "^4.2.0" - -socks@^2.6.2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" - integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== - dependencies: - ip "^2.0.0" - smart-buffer "^4.2.0" - -source-map@^0.5.0: - version "0.5.7" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz" - integrity "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" - -source-map@^0.7.3: - version "0.7.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" - integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== - -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz" - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz" - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz" - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.11" - resolved "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz" - -ssri@^8.0.0, ssri@^8.0.1: - version "8.0.1" - resolved "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz" - dependencies: - minipass "^3.1.1" - -ssri@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-9.0.1.tgz#544d4c357a8d7b71a19700074b6883fcb4eae057" - integrity sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q== - dependencies: - minipass "^3.1.1" - -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" - integrity "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" - -stdout-stream@^1.4.0: - version "1.4.1" - resolved "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz" - dependencies: - readable-stream "^2.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^2.1.0: - version "2.1.1" - resolved "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz" - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz" - integrity "sha1-qEeQIusaw2iocTibY1JixQXuNo8= sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==" - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz" - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - dependencies: - ansi-regex "^5.0.1" - -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz" - dependencies: - min-indent "^1.0.0" - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" - -tar@^6.0.2, tar@^6.1.2: - version "6.1.11" - resolved "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz" - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -tar@^6.1.11: - version "6.2.0" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" - integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^5.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -through@^2.3.6: - version "2.3.8" - resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" - integrity "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz" - dependencies: - os-tmpdir "~1.0.2" - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" - integrity "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" - -trim-newlines@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz" - -"true-case-path@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-2.2.1.tgz#c5bf04a5bbec3fd118be4084461b3a27c4d796bf" - integrity sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q== - -tslib@^1.9.0: - version "1.14.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" - -type-fest@^0.18.0: - version "0.18.1" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz" - -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz" - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz" - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -unicode-canonical-property-names-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz" - -unicode-match-property-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz" - dependencies: - unicode-canonical-property-names-ecmascript "^2.0.0" - unicode-property-aliases-ecmascript "^2.0.0" - -unicode-match-property-value-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz" - -unicode-property-aliases-ecmascript@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz" - -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz" - dependencies: - unique-slug "^2.0.0" - -unique-filename@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" - integrity sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A== - dependencies: - unique-slug "^3.0.0" - -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz" - dependencies: - imurmurhash "^0.1.4" - -unique-slug@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-3.0.0.tgz#6d347cf57c8a7a7a6044aabd0e2d74e4d76dc7c9" - integrity sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w== - dependencies: - imurmurhash "^0.1.4" - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" - integrity "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - integrity "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" - integrity "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz" - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" - integrity "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" - -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz" - integrity "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==" - dependencies: - defaults "^1.0.3" - -web-streams-polyfill@^3.0.3: - version "3.2.0" - resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz" - -which@^2.0.1, which@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" - dependencies: - isexe "^2.0.0" - -wide-align@^1.1.5: - version "1.1.5" - resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz" - dependencies: - string-width "^1.0.2 || 2 || 3 || 4" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - integrity "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" - -yargs-parser@^20.2.3: - version "20.2.9" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" - -yargs-parser@^21.0.0: - version "21.0.1" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz" - -yargs@^17.2.1: - version "17.4.0" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.4.0.tgz" - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.0.0" - -yocto-queue@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz"