From 816e3381446b50c79aacc419d6493a0216b172b9 Mon Sep 17 00:00:00 2001 From: Maximus Decimus Meridius Date: Tue, 30 Jun 2026 02:03:56 +0530 Subject: [PATCH 01/13] chore: initial commit for new commands --- package-lock.json | 9379 +++++++++++++++++++++++++ package.json | 3 +- pnpm-lock.yaml | 262 +- src/api/config.ts | 2 +- src/api/endpoints.ts | 19 +- src/api/user-client.ts | 194 + src/command-line/command.registry.ts | 20 +- src/commands/context.command.ts | 46 + src/commands/list-buckets.command.ts | 50 + src/commands/list-bundles.command.ts | 92 + src/commands/list-projects.command.ts | 46 + src/commands/list-releases.command.ts | 89 + src/commands/release-info.command.ts | 188 + src/commands/use.command.ts | 105 + src/commands/whoami.command.ts | 56 + src/utils/common.ts | 4 +- src/utils/context-store.ts | 49 + src/utils/prompt.ts | 120 +- src/utils/table.ts | 260 + src/utils/ui.ts | 83 + 20 files changed, 10889 insertions(+), 178 deletions(-) create mode 100644 package-lock.json create mode 100644 src/api/user-client.ts create mode 100644 src/commands/context.command.ts create mode 100644 src/commands/list-buckets.command.ts create mode 100644 src/commands/list-bundles.command.ts create mode 100644 src/commands/list-projects.command.ts create mode 100644 src/commands/list-releases.command.ts create mode 100644 src/commands/release-info.command.ts create mode 100644 src/commands/use.command.ts create mode 100644 src/commands/whoami.command.ts create mode 100644 src/utils/context-store.ts create mode 100644 src/utils/table.ts create mode 100644 src/utils/ui.ts diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1f84b81 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,9379 @@ +{ + "name": "stallion-cli", + "version": "2.5.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "stallion-cli", + "version": "2.5.0", + "license": "ISC", + "dependencies": { + "archiver": "^7.0.1", + "axios": "^1.9.0", + "chalk": "^4.1.2", + "commander": "^13.1.0", + "figlet": "^1.7.0", + "gradient-string": "^2.0.2", + "inquirer": "^12.6.1", + "jsonwebtoken": "^9.0.2", + "lodash": "^4.17.21", + "mime": "^3.0.0", + "opener": "^1.5.2", + "ora": "^5.4.1", + "reflect-metadata": "^0.2.2", + "rimraf": "^6.0.1", + "semver": "^7.7.2" + }, + "bin": { + "stallion": "index.js" + }, + "devDependencies": { + "@semantic-release/changelog": "^6.0.0", + "@semantic-release/git": "^10.0.0", + "@types/archiver": "^6.0.3", + "@types/chalk": "^2.2.4", + "@types/figlet": "^1.5.8", + "@types/gradient-string": "^1.1.6", + "@types/inquirer": "^9.0.8", + "@types/jsonwebtoken": "^9.0.9", + "@types/lodash": "^4.17.16", + "@types/node": "^22.15.17", + "@types/opener": "^1.4.3", + "@types/semver": "^7.7.0", + "cross-env": "^7.0.3", + "prettier": "^3.5.3", + "semantic-release": "19.0.3", + "ts-node": "^10.9.2", + "ts-node-dev": "^2.0.0", + "tsc-alias": "^1.8.8", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.8.3" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "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" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@octokit/auth-token": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-3.0.4.tgz", + "integrity": "sha512-TWFX7cZF2LXoCvdmJWY7XVPi74aSY0+FfBZNSXEXFkMpjcqsQwDSYVv5FhRFaI0V1ECnwbz4j59T/G+rXNWaIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/core": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-4.2.4.tgz", + "integrity": "sha512-rYKilwgzQ7/imScn3M9/pFfUf4I1AZEH3KhyJmtPdE2zfaXAn2mFfUy4FbKewzc2We5y/LlKLj36fWJLKC2SIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^3.0.0", + "@octokit/graphql": "^5.0.0", + "@octokit/request": "^6.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/endpoint": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-7.0.6.tgz", + "integrity": "sha512-5L4fseVRUsDFGR00tMWD/Trdeeihn999rTMGRMC1G/Ldi1uWlWJzI98H4Iak5DB/RVvQuyMYKqSK/R6mbSOQyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/graphql": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-5.0.6.tgz", + "integrity": "sha512-Fxyxdy/JH0MnIB5h+UQ3yCoh1FG4kWXfFKkpWqjZHw/p+Kc8Y44Hu/kCgNBT6nU1shNumEchmW/sUO1JuQnPcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^6.0.0", + "@octokit/types": "^9.0.0", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "18.1.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.1.1.tgz", + "integrity": "sha512-VRaeH8nCDtF5aXWnjPuEMIYf1itK/s3JYyJcWFJT8X9pSNnBtriDf7wlEWsGuhPLl4QIH4xM8fqTXDwJ3Mu6sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-6.1.2.tgz", + "integrity": "sha512-qhrmtQeHU/IivxucOV1bbI/xZyC/iOBhclokv7Sut5vnejAIAEXVcGQeRpQlU39E0WwK9lNvJHphHri/DB6lbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/tsconfig": "^1.0.2", + "@octokit/types": "^9.2.3" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "@octokit/core": ">=4" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-4.1.6.tgz", + "integrity": "sha512-obkYzIgEC75r8+9Pnfiiqy3y/x1bc3QLE5B7qvv9wi9Kj0R5tGQFC6QMBg1154WQ9lAVypuQDGyp3hNpp15gQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^9.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-5.2.3.tgz", + "integrity": "sha512-C9CFg9mrf6cugneKiaI841iG8DOv6P5XXkjmiNNut+swePxQ7RWEdAZRp5rJoE1hjsIqiYcKa/ZkOQ+ujPI39Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^9.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "@octokit/core": "^4.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-6.2.8.tgz", + "integrity": "sha512-ow4+pkVQ+6XVVsekSYBzJC0VTVvh/FCTUUgTsboGq+DTeWdyIFV8WSCdo0RIxk6wSkBTHqIK1mYuY7nOBXOchw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^7.0.0", + "@octokit/request-error": "^3.0.0", + "@octokit/types": "^9.0.0", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/request-error": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-3.0.3.tgz", + "integrity": "sha512-crqw3V5Iy2uOU5Np+8M/YexTlT8zxCfI+qu+LxUB7SZpje4Qmx3mub5DfEKSO8Ylyk0aogi6TYdf6kxzh2BguQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^9.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@octokit/tsconfig": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@octokit/tsconfig/-/tsconfig-1.0.2.tgz", + "integrity": "sha512-I0vDR0rdtP8p2lGMzvsJzbhdOWy405HcGovrspJ8RRibHnyRgggUSNO5AIox5LmqiwmatHKYsvj6VGFHkqS7lA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/types": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-9.3.2.tgz", + "integrity": "sha512-D4iHGTdAnEEVsB8fl95m1hiz7D5YiRdQ9b/OEb3BYRVwbLsGHcRVPz+u+BgRLNk0Q0/4iZCBqDN96j2XNxfXrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@pnpm/npm-conf": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.3.tgz", + "integrity": "sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@semantic-release/changelog": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.3.tgz", + "integrity": "sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "fs-extra": "^11.0.0", + "lodash": "^4.17.4" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0" + } + }, + "node_modules/@semantic-release/commit-analyzer": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-9.0.2.tgz", + "integrity": "sha512-E+dr6L+xIHZkX4zNMe6Rnwg4YQrWNXK+rNsvwOPpdFppvZO1olE2fIgWhv89TkQErygevbjsZFSIxp+u6w2e5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-changelog-angular": "^5.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.2.3", + "debug": "^4.0.0", + "import-from": "^4.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0-beta.1" + } + }, + "node_modules/@semantic-release/error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", + "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@semantic-release/git": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz", + "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "execa": "^5.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.0", + "p-reduce": "^2.0.0" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0" + } + }, + "node_modules/@semantic-release/github": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-8.1.0.tgz", + "integrity": "sha512-erR9E5rpdsz0dW1I7785JtndQuMWN/iDcemcptf67tBNOmBUN0b2YNOgcjYUnBpgRpZ5ozfBHrK7Bz+2ets/Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/core": "^4.2.1", + "@octokit/plugin-paginate-rest": "^6.1.2", + "@octokit/plugin-retry": "^4.1.3", + "@octokit/plugin-throttling": "^5.2.3", + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "fs-extra": "^11.0.0", + "globby": "^11.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "issue-parser": "^6.0.0", + "lodash": "^4.17.4", + "mime": "^3.0.0", + "p-filter": "^2.0.0", + "url-join": "^4.0.0" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0-beta.1" + } + }, + "node_modules/@semantic-release/github/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@semantic-release/github/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@semantic-release/npm": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-9.0.2.tgz", + "integrity": "sha512-zgsynF6McdzxPnFet+a4iO9HpAlARXOM5adz7VGVCvj0ne8wtL2ZOQoDV2wZPDmdEotDIbVeJjafhelZjs9j6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "execa": "^5.0.0", + "fs-extra": "^11.0.0", + "lodash": "^4.17.15", + "nerf-dart": "^1.0.0", + "normalize-url": "^6.0.0", + "npm": "^8.3.0", + "rc": "^1.2.8", + "read-pkg": "^5.0.0", + "registry-auth-token": "^5.0.0", + "semver": "^7.1.2", + "tempy": "^1.0.0" + }, + "engines": { + "node": ">=16 || ^14.17" + }, + "peerDependencies": { + "semantic-release": ">=19.0.0" + } + }, + "node_modules/@semantic-release/release-notes-generator": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-10.0.3.tgz", + "integrity": "sha512-k4x4VhIKneOWoBGHkx0qZogNjCldLPRiAjnIpMnlUh6PtaWXp/T+C9U7/TaNDDtgDa5HMbHl4WlREdxHio6/3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-changelog-angular": "^5.0.0", + "conventional-changelog-writer": "^5.0.0", + "conventional-commits-filter": "^2.0.0", + "conventional-commits-parser": "^3.2.3", + "debug": "^4.0.0", + "get-stream": "^6.0.0", + "import-from": "^4.0.0", + "into-stream": "^6.0.0", + "lodash": "^4.17.4", + "read-pkg-up": "^7.0.0" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0-beta.1" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/archiver": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.4.tgz", + "integrity": "sha512-ULdQpARQ3sz9WH4nb98mJDYA0ft2A8C4f4fovvUcFwINa1cgGjY36JCAYuP5YypRq4mco1lJp1/7jEMS2oR0Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/readdir-glob": "*" + } + }, + "node_modules/@types/chalk": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-2.2.4.tgz", + "integrity": "sha512-pb/QoGqtCpH2famSp72qEsXkNzcErlVmiXlQ/ww+5AddD8TmmYS7EWg5T20YiNCAiTgs8pMf2G8SJG5h/ER1ZQ==", + "deprecated": "This is a stub types definition. chalk provides its own type definitions, so you do not need this installed.", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "*" + } + }, + "node_modules/@types/figlet": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@types/figlet/-/figlet-1.7.0.tgz", + "integrity": "sha512-KwrT7p/8Eo3Op/HBSIwGXOsTZKYiM9NpWRBJ5sVjWP/SmlS+oxxRvJht/FNAtliJvja44N3ul1yATgohnVBV0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/gradient-string": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@types/gradient-string/-/gradient-string-1.1.6.tgz", + "integrity": "sha512-LkaYxluY4G5wR1M4AKQUal2q61Di1yVVCw42ImFTuaIoQVgmV0WP1xUaLB8zwb47mp82vWTpePI9JmrjEnJ7nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/tinycolor2": "*" + } + }, + "node_modules/@types/inquirer": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.10.tgz", + "integrity": "sha512-vFW2WbXwO9eZpRT5GJGFJ/shgyMNnYozmnjakt9jCQSS1lvqX8pZEQMjJ9RdDPct/YxwciQ8+V8OYn9euIrZDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/through": "*", + "rxjs": "^7.2.0" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.20.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz", + "integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/opener": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/opener/-/opener-1.4.3.tgz", + "integrity": "sha512-g7TYSmy2RKZkU3QT/9pMISrhVmQtMNaYq6Aojn3Y6pht29Nu9VuijJCYIjofRj7ZaFtKdxh1I8xf3vdW4l86fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/readdir-glob": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz", + "integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/tinycolor2": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz", + "integrity": "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==", + "license": "MIT" + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-escapes": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansicolors": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz", + "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/b4a": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/bare-fs": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.2.tgz", + "integrity": "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.3.tgz", + "integrity": "sha512-fF4Q7QsyKVF5Rj0qvI8BgUNjqzC2JvQlpTaPLjVJVxYVUX5Zr9un+y3w1HmA4nNKdFmRBT8z/WmrjvXzXVerKQ==", + "license": "Apache-2.0", + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.1.tgz", + "integrity": "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", + "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.8.1", + "streamx": "^2.25.0", + "teex": "^1.0.1" + }, + "peerDependencies": { + "bare-abort-controller": "*", + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + }, + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/bare-url": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz", + "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==", + "license": "Apache-2.0", + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cardinal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", + "integrity": "sha512-JSr5eOgoEymtYHBjNWyjrMqet9Am2miJhlfKNdqLp6zoeAh0KN5dRAcxlecj5mAJrmQomgiOBj35xHLrFjqBpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansicolors": "~0.3.2", + "redeyed": "~2.1.0" + }, + "bin": { + "cdl": "bin/cdl.js" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-table3/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-table3/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", + "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-writer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", + "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "conventional-commits-filter": "^2.0.7", + "dateformat": "^3.0.0", + "handlebars": "^4.7.7", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^4.0.0" + }, + "bin": { + "conventional-changelog-writer": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-changelog-writer/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/conventional-commits-filter": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", + "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/conventional-commits-parser": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", + "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-text-path": "^1.0.1", + "JSONStream": "^1.0.4", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "dev": true, + "license": "MIT", + "dependencies": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/del/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/del/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/del/node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/del/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "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" + } + }, + "node_modules/duplexer2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/env-ci": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-5.5.0.tgz", + "integrity": "sha512-o0JdWIbOLP+WJKIUt36hz1ImQQFuN92nhsfTkHHap+J8CiI8WgGpH/a9jEGHh4/TU5BUUGjlnKXNoDb57+ne+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "fromentries": "^1.3.2", + "java-properties": "^1.0.0" + }, + "engines": { + "node": ">=10.17" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/figlet": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.11.0.tgz", + "integrity": "sha512-EEx3OS/l2bFqcUNN2NM9FPJp8vAMrgbCxsbl2hbcJNNxOEwVe3mEzrhan7TbJQViZa8mMqhihlbCaqD+LyYKTQ==", + "license": "MIT", + "dependencies": { + "commander": "^14.0.0" + }, + "bin": { + "figlet": "bin/index.js" + }, + "engines": { + "node": ">= 17.0.0" + } + }, + "node_modules/figlet/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-versions": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz", + "integrity": "sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver-regex": "^3.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/from2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "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" + } + }, + "node_modules/from2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/from2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/git-log-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.1.tgz", + "integrity": "sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "0.6.8" + } + }, + "node_modules/git-log-parser/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "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" + } + }, + "node_modules/git-log-parser/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/git-log-parser/node_modules/split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "dev": true, + "license": "ISC", + "dependencies": { + "through2": "~2.0.0" + } + }, + "node_modules/git-log-parser/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/git-log-parser/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "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", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/gradient-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/gradient-string/-/gradient-string-2.0.2.tgz", + "integrity": "sha512-rEDCuqUQ4tbD78TpzsMtt5OIf0cBCSDWSJtUDaF6JsAh+k0v9r++NzxNEG87oDZx9ZwGhD8DaezR2L/yrw0Jdw==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "tinygradient": "^1.1.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hook-std": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-2.0.0.tgz", + "integrity": "sha512-zZ6T5WcuBMIUVh49iPQS9t977t7C0l7OtHrpeMb5uk48JdflRX0NSFvCekfYNmGQETnLq9W/isMyHl69kxGi8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-4.0.0.tgz", + "integrity": "sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "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.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "12.11.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.11.1.tgz", + "integrity": "sha512-9VF7mrY+3OmsAfjH3yKz/pLbJ5z22E23hENKw3/LNSaA/sAt3v49bDRY+Ygct1xwuKT+U+cBfTzjCPySna69Qw==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/prompts": "^7.10.1", + "@inquirer/type": "^3.0.10", + "mute-stream": "^2.0.0", + "run-async": "^4.0.6", + "rxjs": "^7.8.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/into-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/issue-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-6.0.0.tgz", + "integrity": "sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + }, + "engines": { + "node": ">=10.13" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "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" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/marked-terminal": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-5.2.0.tgz", + "integrity": "sha512-Piv6yNwAQXGFjZSaiNljyNFw7jKDdGrw70FSbtxEyldLsyeuV5ZHm/1wW++kWbrOF1VPnUgYOhB2oLL0ZpnekA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^6.2.0", + "cardinal": "^2.1.1", + "chalk": "^5.2.0", + "cli-table3": "^0.6.3", + "node-emoji": "^1.11.0", + "supports-hyperlinks": "^2.3.0" + }, + "engines": { + "node": ">=14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "marked": "^1.0.0 || ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/marked-terminal/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/mylas": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz", + "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm": { + "version": "8.19.4", + "resolved": "https://registry.npmjs.org/npm/-/npm-8.19.4.tgz", + "integrity": "sha512-3HANl8i9DKnUA89P4KEgVNN28EjSeDCmvEqbzOAuxCFDzdBZzjUl99zgnGpOUumvW5lvJo2HKcjrsc+tfyv1Hw==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/ci-detect", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/run-script", + "abbrev", + "archy", + "cacache", + "chalk", + "chownr", + "cli-columns", + "cli-table3", + "columnify", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "mkdirp", + "mkdirp-infer-owner", + "ms", + "node-gyp", + "nopt", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "npmlog", + "opener", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "read-package-json", + "read-package-json-fast", + "readdir-scoped-modules", + "rimraf", + "semver", + "ssri", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "dev": true, + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^5.6.3", + "@npmcli/ci-detect": "^2.0.0", + "@npmcli/config": "^4.2.1", + "@npmcli/fs": "^2.1.0", + "@npmcli/map-workspaces": "^2.0.3", + "@npmcli/package-json": "^2.0.0", + "@npmcli/run-script": "^4.2.1", + "abbrev": "~1.1.1", + "archy": "~1.0.0", + "cacache": "^16.1.3", + "chalk": "^4.1.2", + "chownr": "^2.0.0", + "cli-columns": "^4.0.0", + "cli-table3": "^0.6.2", + "columnify": "^1.6.0", + "fastest-levenshtein": "^1.0.12", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "graceful-fs": "^4.2.10", + "hosted-git-info": "^5.2.1", + "ini": "^3.0.1", + "init-package-json": "^3.0.2", + "is-cidr": "^4.0.2", + "json-parse-even-better-errors": "^2.3.1", + "libnpmaccess": "^6.0.4", + "libnpmdiff": "^4.0.5", + "libnpmexec": "^4.0.14", + "libnpmfund": "^3.0.5", + "libnpmhook": "^8.0.4", + "libnpmorg": "^4.0.4", + "libnpmpack": "^4.1.3", + "libnpmpublish": "^6.0.5", + "libnpmsearch": "^5.0.4", + "libnpmteam": "^4.0.4", + "libnpmversion": "^3.0.7", + "make-fetch-happen": "^10.2.0", + "minimatch": "^5.1.0", + "minipass": "^3.1.6", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "ms": "^2.1.2", + "node-gyp": "^9.1.0", + "nopt": "^6.0.0", + "npm-audit-report": "^3.0.0", + "npm-install-checks": "^5.0.0", + "npm-package-arg": "^9.1.0", + "npm-pick-manifest": "^7.0.2", + "npm-profile": "^6.2.0", + "npm-registry-fetch": "^13.3.1", + "npm-user-validate": "^1.0.1", + "npmlog": "^6.0.2", + "opener": "^1.5.2", + "p-map": "^4.0.0", + "pacote": "^13.6.2", + "parse-conflict-json": "^2.0.2", + "proc-log": "^2.0.1", + "qrcode-terminal": "^0.12.0", + "read": "~1.0.7", + "read-package-json": "^5.0.2", + "read-package-json-fast": "^2.0.3", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^9.0.1", + "tar": "^6.1.11", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^2.0.0", + "validate-npm-package-name": "^4.0.0", + "which": "^2.0.2", + "write-file-atomic": "^4.0.1" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/@colors/colors": { + "version": "1.5.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/npm/node_modules/@gar/promisify": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "5.6.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/map-workspaces": "^2.0.3", + "@npmcli/metavuln-calculator": "^3.0.1", + "@npmcli/move-file": "^2.0.0", + "@npmcli/name-from-folder": "^1.0.1", + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/package-json": "^2.0.0", + "@npmcli/query": "^1.2.0", + "@npmcli/run-script": "^4.1.3", + "bin-links": "^3.0.3", + "cacache": "^16.1.3", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^5.2.1", + "json-parse-even-better-errors": "^2.3.1", + "json-stringify-nice": "^1.1.4", + "minimatch": "^5.1.0", + "mkdirp": "^1.0.4", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^6.0.0", + "npm-install-checks": "^5.0.0", + "npm-package-arg": "^9.0.0", + "npm-pick-manifest": "^7.0.2", + "npm-registry-fetch": "^13.0.0", + "npmlog": "^6.0.2", + "pacote": "^13.6.1", + "parse-conflict-json": "^2.0.1", + "proc-log": "^2.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.1", + "read-package-json-fast": "^2.0.2", + "readdir-scoped-modules": "^1.1.0", + "rimraf": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^9.0.0", + "treeverse": "^2.0.0", + "walk-up-path": "^1.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/ci-detect": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "4.2.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^2.0.2", + "ini": "^3.0.0", + "mkdirp-infer-owner": "^2.0.0", + "nopt": "^6.0.0", + "proc-log": "^2.0.0", + "read-package-json-fast": "^2.0.3", + "semver": "^7.3.5", + "walk-up-path": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/disparity-colors": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "ansi-styles": "^4.3.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^3.0.0", + "lru-cache": "^7.4.4", + "mkdirp": "^1.0.4", + "npm-pick-manifest": "^7.0.0", + "proc-log": "^2.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^2.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "1.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^1.1.1", + "npm-normalize-package-bin": "^1.0.1" + }, + "bin": { + "installed-package-contents": "index.js" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents/node_modules/npm-bundled": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "2.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^1.0.1", + "glob": "^8.0.1", + "minimatch": "^5.0.1", + "read-package-json-fast": "^2.0.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^16.0.0", + "json-parse-even-better-errors": "^2.3.1", + "pacote": "^13.0.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/move-file": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "infer-owner": "^1.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "1.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^9.1.0", + "postcss-selector-parser": "^6.0.10", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "4.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^2.0.0", + "@npmcli/promise-spawn": "^3.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^2.0.3", + "which": "^2.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/@tootallnate/once": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/agent-base": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/npm/node_modules/agentkeepalive": { + "version": "4.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/npm/node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/are-we-there-yet": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/asap": { + "version": "2.0.6", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "3.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^5.0.0", + "mkdirp-infer-owner": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0", + "read-cmd-shim": "^3.0.0", + "rimraf": "^3.0.0", + "write-file-atomic": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/bin-links/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/builtins": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "16.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^4.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cli-table3": { + "version": "0.6.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/npm/node_modules/clone": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mkdirp-infer-owner": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/color-support": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/npm/node_modules/columnify": { + "version": "1.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "strip-ansi": "^6.0.1", + "wcwidth": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/console-control-strings": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.3.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/debuglog": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/defaults": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + } + }, + "node_modules/npm/node_modules/delegates": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/depd": { + "version": "1.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/dezalgo": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.1.0", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.12", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "2.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/fs.realpath": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/function-bind": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/gauge": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "8.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.10", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/has": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/npm/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/has-unicode": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "5.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/humanize-ms": { + "version": "1.2.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "5.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^5.0.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/infer-owner": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/inflight": { + "version": "1.0.6", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/inherits": { + "version": "2.0.4", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/ini": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^9.0.1", + "promzard": "^0.3.0", + "read": "^1.0.7", + "read-package-json": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/ip": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "4.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^3.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/is-core-module": { + "version": "2.10.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/is-lambda": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "5.1.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.4.1", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "6.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "minipass": "^3.1.1", + "npm-package-arg": "^9.0.1", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "4.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/disparity-colors": "^2.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "binary-extensions": "^2.2.0", + "diff": "^5.1.0", + "minimatch": "^5.0.1", + "npm-package-arg": "^9.0.1", + "pacote": "^13.6.1", + "tar": "^6.1.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "4.0.14", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^5.6.3", + "@npmcli/ci-detect": "^2.0.0", + "@npmcli/fs": "^2.1.1", + "@npmcli/run-script": "^4.2.0", + "chalk": "^4.1.0", + "mkdirp-infer-owner": "^2.0.0", + "npm-package-arg": "^9.0.1", + "npmlog": "^6.0.2", + "pacote": "^13.6.1", + "proc-log": "^2.0.0", + "read": "^1.0.7", + "read-package-json-fast": "^2.0.2", + "semver": "^7.3.7", + "walk-up-path": "^1.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "3.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^5.6.3" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "8.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "4.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/run-script": "^4.1.3", + "npm-package-arg": "^9.0.1", + "pacote": "^13.6.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "6.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "normalize-package-data": "^4.0.0", + "npm-package-arg": "^9.0.1", + "npm-registry-fetch": "^13.0.0", + "semver": "^7.3.7", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "5.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "4.0.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^13.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "3.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^3.0.0", + "@npmcli/run-script": "^4.1.3", + "json-parse-even-better-errors": "^2.3.1", + "proc-log": "^2.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "7.13.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "10.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "5.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "3.3.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "2.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-json-stream": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/mkdirp-infer-owner": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "infer-owner": "^1.0.4", + "mkdirp": "^1.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "0.0.8", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/negotiator": { + "version": "0.6.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "9.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.22 || ^14.13 || >=16" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/nopt": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "4.0.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^5.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-bundled/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "5.0.0", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "9.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^5.0.0", + "proc-log": "^2.0.1", + "semver": "^7.3.5", + "validate-npm-package-name": "^4.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "5.1.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^8.0.1", + "ignore-walk": "^5.0.1", + "npm-bundled": "^2.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "bin": { + "npm-packlist": "bin/index.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-packlist/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "7.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^5.0.0", + "npm-normalize-package-bin": "^2.0.0", + "npm-package-arg": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "6.2.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "13.3.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "make-fetch-happen": "^10.0.6", + "minipass": "^3.1.6", + "minipass-fetch": "^2.0.3", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^9.0.1", + "proc-log": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/npmlog": { + "version": "6.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/once": { + "version": "1.4.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/npm/node_modules/opener": { + "version": "1.5.2", + "dev": true, + "inBundle": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/pacote": { + "version": "13.6.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^3.0.0", + "@npmcli/installed-package-contents": "^1.0.7", + "@npmcli/promise-spawn": "^3.0.0", + "@npmcli/run-script": "^4.1.0", + "cacache": "^16.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "infer-owner": "^1.0.4", + "minipass": "^3.1.6", + "mkdirp": "^1.0.4", + "npm-package-arg": "^9.0.0", + "npm-packlist": "^5.1.0", + "npm-pick-manifest": "^7.0.0", + "npm-registry-fetch": "^13.0.1", + "proc-log": "^2.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^5.0.0", + "read-package-json-fast": "^2.0.3", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.1", + "just-diff": "^5.0.1", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/path-is-absolute": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "0.3.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "1" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "1.0.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "~0.0.4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json": { + "version": "5.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^8.0.1", + "json-parse-even-better-errors": "^2.3.1", + "normalize-package-data": "^4.0.0", + "npm-normalize-package-bin": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "2.0.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^2.3.0", + "npm-normalize-package-bin": "^1.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/read-package-json/node_modules/npm-normalize-package-bin": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/readable-stream": { + "version": "3.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm/node_modules/readdir-scoped-modules": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/npm/node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/npm/node_modules/safe-buffer": { + "version": "5.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.3.7", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/semver/node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "3.0.7", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.7.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "7.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.1.1", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.3.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.11", + "dev": true, + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/ssri": { + "version": "9.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/string_decoder": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "6.1.11", + "dev": true, + "inBundle": true, + "license": "ISC", + "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" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "2.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "dev": true, + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/wcwidth": { + "version": "1.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/npm/node_modules/which": { + "version": "2.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/wide-align": { + "version": "1.1.5", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/npm/node_modules/wrappy": { + "version": "1.0.2", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "4.0.2", + "dev": true, + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "ISC" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-each-series": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", + "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-filter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-map": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-reduce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", + "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg-conf/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/prettier": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.3.tgz", + "integrity": "sha512-HWmu+K+zvHNpaMfSnYeqdqrDbR16cuIXaPx8WoHaviQkDJh1/0BNtOZmHVQI5jc3wXv0H1yXc9wjvFdXh+n3hQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC" + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/read-pkg/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-pkg/node_modules/type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redeyed": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz", + "integrity": "sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esprima": "~4.0.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/registry-auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", + "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/npm-conf": "^3.0.2" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", + "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-async": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz", + "integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semantic-release": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-19.0.3.tgz", + "integrity": "sha512-HaFbydST1cDKZHuFZxB8DTrBLJVK/AnDExpK0s3EqLIAAUAHUgnd+VSJCUtTYQKkAkauL8G9CucODrVCc7BuAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@semantic-release/commit-analyzer": "^9.0.2", + "@semantic-release/error": "^3.0.0", + "@semantic-release/github": "^8.0.0", + "@semantic-release/npm": "^9.0.0", + "@semantic-release/release-notes-generator": "^10.0.0", + "aggregate-error": "^3.0.0", + "cosmiconfig": "^7.0.0", + "debug": "^4.0.0", + "env-ci": "^5.0.0", + "execa": "^5.0.0", + "figures": "^3.0.0", + "find-versions": "^4.0.0", + "get-stream": "^6.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^2.0.0", + "hosted-git-info": "^4.0.0", + "lodash": "^4.17.21", + "marked": "^4.0.10", + "marked-terminal": "^5.0.0", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "p-reduce": "^2.0.0", + "read-pkg-up": "^7.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.3.2", + "semver-diff": "^3.1.1", + "signale": "^1.2.1", + "yargs": "^16.2.0" + }, + "bin": { + "semantic-release": "bin/semantic-release.js" + }, + "engines": { + "node": ">=16 || ^14.17" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/semver-diff/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-regex": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.4.tgz", + "integrity": "sha512-6IiqeZNgq01qGf0TId0t3NvKzSvUsjcpdEO3AQNeIjR6A2+ckTnQlDpl4qu1bjRv0RzN3FP9hzFmws3lKqRWkA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/signale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", + "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/signale/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/signale/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/signale/node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/signale/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spawn-error-forwarder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", + "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "license": "ISC", + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "node_modules/split2/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-combiner2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "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" + } + }, + "node_modules/stream-combiner2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-combiner2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/streamx": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", + "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-hyperlinks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", + "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar-stream": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "bare-fs": "^4.5.5", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-1.0.1.tgz", + "integrity": "sha512-biM9brNqxSc04Ee71hzFbryD11nX7VPhQQY32AdDmjFvodsRFz/3ufeoTZ6uYkRFfGo188tENcASNs3vTdsM0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "del": "^6.0.0", + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/text-decoder": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", + "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, + "node_modules/tinygradient": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/tinygradient/-/tinygradient-1.1.5.tgz", + "integrity": "sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==", + "license": "MIT", + "dependencies": { + "@types/tinycolor2": "^1.4.0", + "tinycolor2": "^1.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/traverse": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", + "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "node-notifier": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/ts-node-dev/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/ts-node-dev/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "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", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ts-node-dev/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ts-node-dev/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/tsc-alias": { + "version": "1.8.17", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.17.tgz", + "integrity": "sha512-EIduCZHqbNwPm8BZYfq1aD7BQ697A4h6uSGMOFQfYGoQwfrYFTKwYfy9Bv42YxHkduVBcn9Zx0DkX111DKskyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "get-tsconfig": "^4.10.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + }, + "engines": { + "node": ">=16.20.2" + } + }, + "node_modules/tsc-alias/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + } + } +} diff --git a/package.json b/package.json index acc9e49..3366267 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stallion-cli", - "version": "2.5.0", + "version": "2.6.0", "description": "A powerful CLI tool for managing your Stallion deployments", "main": "index.js", "bin": { @@ -24,6 +24,7 @@ "license": "ISC", "packageManager": "pnpm@10.11.0", "dependencies": { + "@inquirer/prompts": "^7.10.1", "archiver": "^7.0.1", "axios": "^1.9.0", "chalk": "^4.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1eaadee..0352475 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@inquirer/prompts': + specifier: ^7.10.1 + version: 7.10.1(@types/node@22.15.17) archiver: specifier: ^7.0.1 version: 7.0.1 @@ -133,8 +136,12 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@inquirer/checkbox@4.1.6': - resolution: {integrity: sha512-62u896rWCtKKE43soodq5e/QcRsA22I+7/4Ov7LESWnKRO6BVo2A1DFLDmXL9e28TB0CfHc3YtkbPm7iwajqkg==} + '@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' @@ -142,8 +149,8 @@ packages: '@types/node': optional: true - '@inquirer/confirm@5.1.10': - resolution: {integrity: sha512-FxbQ9giWxUWKUk2O5XZ6PduVnH2CZ/fmMKMBkH71MHJvWr7WL5AHKevhzF1L5uYWB2P548o1RzVxrNd3dpmk6g==} + '@inquirer/confirm@5.1.21': + resolution: {integrity: sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -160,8 +167,26 @@ packages: '@types/node': optional: true - '@inquirer/editor@4.2.11': - resolution: {integrity: sha512-YoZr0lBnnLFPpfPSNsQ8IZyKxU47zPyVi9NLjCWtna52//M/xuL0PGPAxHxxYhdOhnvY2oBafoM+BI5w/JK7jw==} + '@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' @@ -169,8 +194,8 @@ packages: '@types/node': optional: true - '@inquirer/expand@4.0.13': - resolution: {integrity: sha512-HgYNWuZLHX6q5y4hqKhwyytqAghmx35xikOGY3TcgNiElqXGPas24+UzNPOwGUZa5Dn32y25xJqVeUcGlTv+QQ==} + '@inquirer/external-editor@1.0.3': + resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -182,8 +207,12 @@ packages: resolution: {integrity: sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==} engines: {node: '>=18'} - '@inquirer/input@4.1.10': - resolution: {integrity: sha512-kV3BVne3wJ+j6reYQUZi/UN9NZGZLxgc/tfyjeK3mrx1QI7RXPxGp21IUTv+iVHcbP4ytZALF8vCHoxyNSC6qg==} + '@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' @@ -191,8 +220,8 @@ packages: '@types/node': optional: true - '@inquirer/number@3.0.13': - resolution: {integrity: sha512-IrLezcg/GWKS8zpKDvnJ/YTflNJdG0qSFlUM/zNFsdi4UKW/CO+gaJpbMgQ20Q58vNKDJbEzC6IebdkprwL6ew==} + '@inquirer/number@3.0.23': + resolution: {integrity: sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -200,8 +229,8 @@ packages: '@types/node': optional: true - '@inquirer/password@4.0.13': - resolution: {integrity: sha512-NN0S/SmdhakqOTJhDwOpeBEEr8VdcYsjmZHDb0rblSh2FcbXQOr+2IApP7JG4WE3sxIdKytDn4ed3XYwtHxmJQ==} + '@inquirer/password@4.0.23': + resolution: {integrity: sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -209,8 +238,8 @@ packages: '@types/node': optional: true - '@inquirer/prompts@7.5.1': - resolution: {integrity: sha512-5AOrZPf2/GxZ+SDRZ5WFplCA2TAQgK3OYrXCYmJL5NaTu4ECcoWFlfUZuw7Es++6Njv7iu/8vpYJhuzxUH76Vg==} + '@inquirer/prompts@7.10.1': + resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -218,8 +247,8 @@ packages: '@types/node': optional: true - '@inquirer/rawlist@4.1.1': - resolution: {integrity: sha512-VBUC0jPN2oaOq8+krwpo/mf3n/UryDUkKog3zi+oIi8/e5hykvdntgHUB9nhDM78RubiyR1ldIOfm5ue+2DeaQ==} + '@inquirer/rawlist@4.1.11': + resolution: {integrity: sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -227,8 +256,8 @@ packages: '@types/node': optional: true - '@inquirer/search@3.0.13': - resolution: {integrity: sha512-9g89d2c5Izok/Gw/U7KPC3f9kfe5rA1AJ24xxNZG0st+vWekSk7tB9oE+dJv5JXd0ZSijomvW0KPMoBd8qbN4g==} + '@inquirer/search@3.2.2': + resolution: {integrity: sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==} engines: {node: '>=18'} peerDependencies: '@types/node': '>=18' @@ -236,8 +265,17 @@ packages: '@types/node': optional: true - '@inquirer/select@4.2.1': - resolution: {integrity: sha512-gt1Kd5XZm+/ddemcT3m23IP8aD8rC9drRckWoP/1f7OL46Yy2FGi8DSmNjEjQKtPl6SV96Kmjbl6p713KXJ/Jg==} + '@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' @@ -634,8 +672,8 @@ packages: resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + chardet@2.2.0: + resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} @@ -876,10 +914,6 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} - fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} @@ -1066,8 +1100,8 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} engines: {node: '>=0.10.0'} ieee754@1.2.1: @@ -1543,10 +1577,6 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - p-each-series@2.2.0: resolution: {integrity: sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==} engines: {node: '>=8'} @@ -1976,10 +2006,6 @@ packages: tinygradient@1.1.5: resolution: {integrity: sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==} - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2158,6 +2184,10 @@ packages: resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} engines: {node: '>=18'} + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} @@ -2179,20 +2209,22 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@inquirer/checkbox@4.1.6(@types/node@22.15.17)': + '@inquirer/ansi@1.0.2': {} + + '@inquirer/checkbox@4.3.2(@types/node@22.15.17)': dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.17) - '@inquirer/figures': 1.0.11 - '@inquirer/type': 3.0.6(@types/node@22.15.17) - ansi-escapes: 4.3.2 - yoctocolors-cjs: 2.1.2 + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.15.17) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.15.17) + yoctocolors-cjs: 2.1.3 optionalDependencies: '@types/node': 22.15.17 - '@inquirer/confirm@5.1.10(@types/node@22.15.17)': + '@inquirer/confirm@5.1.21(@types/node@22.15.17)': dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.17) - '@inquirer/type': 3.0.6(@types/node@22.15.17) + '@inquirer/core': 10.3.2(@types/node@22.15.17) + '@inquirer/type': 3.0.10(@types/node@22.15.17) optionalDependencies: '@types/node': 22.15.17 @@ -2209,85 +2241,111 @@ snapshots: optionalDependencies: '@types/node': 22.15.17 - '@inquirer/editor@4.2.11(@types/node@22.15.17)': + '@inquirer/core@10.3.2(@types/node@22.15.17)': dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.17) - '@inquirer/type': 3.0.6(@types/node@22.15.17) - external-editor: 3.1.0 + '@inquirer/ansi': 1.0.2 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.15.17) + 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': 22.15.17 - '@inquirer/expand@4.0.13(@types/node@22.15.17)': + '@inquirer/editor@4.2.23(@types/node@22.15.17)': dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.17) - '@inquirer/type': 3.0.6(@types/node@22.15.17) - yoctocolors-cjs: 2.1.2 + '@inquirer/core': 10.3.2(@types/node@22.15.17) + '@inquirer/external-editor': 1.0.3(@types/node@22.15.17) + '@inquirer/type': 3.0.10(@types/node@22.15.17) + optionalDependencies: + '@types/node': 22.15.17 + + '@inquirer/expand@4.0.23(@types/node@22.15.17)': + dependencies: + '@inquirer/core': 10.3.2(@types/node@22.15.17) + '@inquirer/type': 3.0.10(@types/node@22.15.17) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.15.17 + + '@inquirer/external-editor@1.0.3(@types/node@22.15.17)': + dependencies: + chardet: 2.2.0 + iconv-lite: 0.7.2 optionalDependencies: '@types/node': 22.15.17 '@inquirer/figures@1.0.11': {} - '@inquirer/input@4.1.10(@types/node@22.15.17)': + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@4.3.1(@types/node@22.15.17)': dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.17) - '@inquirer/type': 3.0.6(@types/node@22.15.17) + '@inquirer/core': 10.3.2(@types/node@22.15.17) + '@inquirer/type': 3.0.10(@types/node@22.15.17) optionalDependencies: '@types/node': 22.15.17 - '@inquirer/number@3.0.13(@types/node@22.15.17)': + '@inquirer/number@3.0.23(@types/node@22.15.17)': dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.17) - '@inquirer/type': 3.0.6(@types/node@22.15.17) + '@inquirer/core': 10.3.2(@types/node@22.15.17) + '@inquirer/type': 3.0.10(@types/node@22.15.17) optionalDependencies: '@types/node': 22.15.17 - '@inquirer/password@4.0.13(@types/node@22.15.17)': + '@inquirer/password@4.0.23(@types/node@22.15.17)': dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.17) - '@inquirer/type': 3.0.6(@types/node@22.15.17) - ansi-escapes: 4.3.2 + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.15.17) + '@inquirer/type': 3.0.10(@types/node@22.15.17) optionalDependencies: '@types/node': 22.15.17 - '@inquirer/prompts@7.5.1(@types/node@22.15.17)': - dependencies: - '@inquirer/checkbox': 4.1.6(@types/node@22.15.17) - '@inquirer/confirm': 5.1.10(@types/node@22.15.17) - '@inquirer/editor': 4.2.11(@types/node@22.15.17) - '@inquirer/expand': 4.0.13(@types/node@22.15.17) - '@inquirer/input': 4.1.10(@types/node@22.15.17) - '@inquirer/number': 3.0.13(@types/node@22.15.17) - '@inquirer/password': 4.0.13(@types/node@22.15.17) - '@inquirer/rawlist': 4.1.1(@types/node@22.15.17) - '@inquirer/search': 3.0.13(@types/node@22.15.17) - '@inquirer/select': 4.2.1(@types/node@22.15.17) + '@inquirer/prompts@7.10.1(@types/node@22.15.17)': + dependencies: + '@inquirer/checkbox': 4.3.2(@types/node@22.15.17) + '@inquirer/confirm': 5.1.21(@types/node@22.15.17) + '@inquirer/editor': 4.2.23(@types/node@22.15.17) + '@inquirer/expand': 4.0.23(@types/node@22.15.17) + '@inquirer/input': 4.3.1(@types/node@22.15.17) + '@inquirer/number': 3.0.23(@types/node@22.15.17) + '@inquirer/password': 4.0.23(@types/node@22.15.17) + '@inquirer/rawlist': 4.1.11(@types/node@22.15.17) + '@inquirer/search': 3.2.2(@types/node@22.15.17) + '@inquirer/select': 4.4.2(@types/node@22.15.17) optionalDependencies: '@types/node': 22.15.17 - '@inquirer/rawlist@4.1.1(@types/node@22.15.17)': + '@inquirer/rawlist@4.1.11(@types/node@22.15.17)': dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.17) - '@inquirer/type': 3.0.6(@types/node@22.15.17) - yoctocolors-cjs: 2.1.2 + '@inquirer/core': 10.3.2(@types/node@22.15.17) + '@inquirer/type': 3.0.10(@types/node@22.15.17) + yoctocolors-cjs: 2.1.3 optionalDependencies: '@types/node': 22.15.17 - '@inquirer/search@3.0.13(@types/node@22.15.17)': + '@inquirer/search@3.2.2(@types/node@22.15.17)': dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.17) - '@inquirer/figures': 1.0.11 - '@inquirer/type': 3.0.6(@types/node@22.15.17) - yoctocolors-cjs: 2.1.2 + '@inquirer/core': 10.3.2(@types/node@22.15.17) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.15.17) + yoctocolors-cjs: 2.1.3 optionalDependencies: '@types/node': 22.15.17 - '@inquirer/select@4.2.1(@types/node@22.15.17)': + '@inquirer/select@4.4.2(@types/node@22.15.17)': dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.17) - '@inquirer/figures': 1.0.11 - '@inquirer/type': 3.0.6(@types/node@22.15.17) - ansi-escapes: 4.3.2 - yoctocolors-cjs: 2.1.2 + '@inquirer/ansi': 1.0.2 + '@inquirer/core': 10.3.2(@types/node@22.15.17) + '@inquirer/figures': 1.0.15 + '@inquirer/type': 3.0.10(@types/node@22.15.17) + yoctocolors-cjs: 2.1.3 + optionalDependencies: + '@types/node': 22.15.17 + + '@inquirer/type@3.0.10(@types/node@22.15.17)': optionalDependencies: '@types/node': 22.15.17 @@ -2745,7 +2803,7 @@ snapshots: chalk@5.4.1: {} - chardet@0.7.0: {} + chardet@2.2.0: {} chokidar@3.6.0: dependencies: @@ -3000,12 +3058,6 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - external-editor@3.1.0: - dependencies: - chardet: 0.7.0 - iconv-lite: 0.4.24 - tmp: 0.0.33 - fast-fifo@1.3.2: {} fast-glob@3.3.3: @@ -3216,7 +3268,7 @@ snapshots: human-signals@2.1.0: {} - iconv-lite@0.4.24: + iconv-lite@0.7.2: dependencies: safer-buffer: 2.1.2 @@ -3245,7 +3297,7 @@ snapshots: inquirer@12.6.1(@types/node@22.15.17): dependencies: '@inquirer/core': 10.1.11(@types/node@22.15.17) - '@inquirer/prompts': 7.5.1(@types/node@22.15.17) + '@inquirer/prompts': 7.10.1(@types/node@22.15.17) '@inquirer/type': 3.0.6(@types/node@22.15.17) ansi-escapes: 4.3.2 mute-stream: 2.0.0 @@ -3575,8 +3627,6 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 - os-tmpdir@1.0.2: {} - p-each-series@2.2.0: {} p-filter@2.1.0: @@ -4007,10 +4057,6 @@ snapshots: '@types/tinycolor2': 1.4.6 tinycolor2: 1.6.0 - tmp@0.0.33: - dependencies: - os-tmpdir: 1.0.2 - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -4181,6 +4227,8 @@ snapshots: yoctocolors-cjs@2.1.2: {} + yoctocolors-cjs@2.1.3: {} + zip-stream@6.0.1: dependencies: archiver-utils: 5.0.2 diff --git a/src/api/config.ts b/src/api/config.ts index 057e463..2087381 100644 --- a/src/api/config.ts +++ b/src/api/config.ts @@ -1,6 +1,6 @@ export const CONFIG = { API: { - BASE_URL: "https://api.stalliontech.io/api/v1", + BASE_URL: "http://localhost:8000/api/v1", }, BUNDLE_EXTENSION: ".stallionsigned", } as const; diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index 024839f..7142bde 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -1,5 +1,5 @@ export const ENDPOINTS = { - CLI_LOGIN: "https://console.stalliontech.io/dashboard/cli/user", + CLI_LOGIN: "http://localhost:3000/dashboard/cli/user", USER: { VERIFY: "/auth/user-profile", }, @@ -13,5 +13,22 @@ export const ENDPOINTS = { }, ORG: { GET_ORG_REGION: "/cli/org/get-region", + LIST: "/org/list", + }, + PROJECT: { + LIST: "/project/list", + DETAIL: "/project/detail", + }, + BUCKET: { + LIST: "/bucket/list", + }, + BUNDLE: { + LIST: "/bundle/list", + ADVANCE_LISTING: "/bundle/advance-listing", + }, + PROMOTED: { + LISTING: "/promoted/promoted-bundle-listing", + LIST_APP_VERSIONS: "/promoted/list-app-versions", + DETAIL: "/promoted/promoted-bundle-detail", }, } as const; diff --git a/src/api/user-client.ts b/src/api/user-client.ts new file mode 100644 index 0000000..27da99d --- /dev/null +++ b/src/api/user-client.ts @@ -0,0 +1,194 @@ +import { ApiClient } from "@/api/api-client"; +import { CONFIG } from "@/api/config"; +import { ENDPOINTS } from "@/api/endpoints"; +import { getApiBaseUrl } from "@/utils/common"; +import { createDefaultTokenStore } from "@/utils/token-store"; +import { promptSelect } from "@/utils/prompt"; +import { getContext, setContext } from "@/utils/context-store"; + +/** + * Read the stored login JWT, or throw a friendly error if the user is not + * logged in. Mirrors how BaseCommand.verifyLogin reads the token. + */ +export async function getUserToken(): Promise { + const tokenStore = createDefaultTokenStore(); + const tokenData = await tokenStore.get("cli"); + const token = tokenData?.accessToken?.token; + if (!token) { + throw new Error('You are not logged in. Run "stallion login" first.'); + } + return token; +} + +/** + * Build an ApiClient authenticated with the logged-in user's token. + * Pass a region to target that region's regional API (buckets, bundles and + * releases live on the regional DB); omit it to hit the global API (used for + * org and project listings). + */ +export async function createUserApiClient(region?: string): Promise { + const token = await getUserToken(); + const client = new ApiClient( + region ? getApiBaseUrl(region) : CONFIG.API.BASE_URL + ); + client.setToken(token); + return client; +} + +export interface OrgSummary { + orgId: string; + name: string; + region: string; + access: string; +} + +/** Fetch the user's organizations (with their region) from the global API. */ +export async function fetchOrgs(client: ApiClient): Promise { + const res = await client.get<{ data: any[] }>(ENDPOINTS.ORG.LIST); + const orgs = res?.data ?? []; + return orgs.map((o: any) => ({ + orgId: String(o.orgId), + name: o.orgData?.[0]?.name ?? "(unknown)", + region: o.orgData?.[0]?.region ?? "ap", + access: o.access ?? "", + })); +} + +/** + * Resolve the org id and its region. Precedence: explicit flag > saved context + * > interactive pick. An interactive pick is saved back to the context (and + * clears any project from a different org). Pass useSaved: false to force a + * fresh pick (used by `stallion use`). + */ +export async function resolveOrgContext( + orgId?: string, + opts: { useSaved?: boolean } = {} +): Promise<{ orgId: string; region: string }> { + const useSaved = opts.useSaved !== false; + if (!orgId && useSaved) { + const ctx = getContext(); + if (ctx.orgId) { + return { orgId: ctx.orgId, region: ctx.region ?? "ap" }; + } + } + + const client = await createUserApiClient(); + const orgs = await fetchOrgs(client); + if (!orgs.length) { + throw new Error("No organizations found for this account."); + } + if (orgId) { + const match = orgs.find((o) => o.orgId === orgId); + return { orgId, region: match?.region ?? "ap" }; + } + + const chosen = await promptSelect( + "Select an organization", + orgs.map((o) => ({ + name: o.name, + value: o.orgId, + description: `region: ${o.region} id: ${o.orgId}`, + })) + ); + const match = orgs.find((o) => o.orgId === chosen); + const region = match?.region ?? "ap"; + + const prev = getContext(); + const patch: Partial<{ + orgId: string; + orgName: string; + region: string; + projectId: string | undefined; + projectName: string | undefined; + }> = { orgId: chosen, orgName: match?.name, region }; + // Picking a different org invalidates a project saved under the old org. + if (prev.orgId !== chosen) { + patch.projectId = undefined; + patch.projectName = undefined; + } + setContext(patch); + + return { orgId: chosen, region }; +} + +/** + * Resolve a project id. Precedence: explicit flag > saved context (when it + * belongs to this org) > interactive pick. An interactive pick is saved back + * to the context. Pass useSaved: false to force a fresh pick. + */ +export async function resolveProjectId( + orgId: string, + projectId?: string, + opts: { useSaved?: boolean } = {} +): Promise { + if (projectId) return projectId; + const useSaved = opts.useSaved !== false; + if (useSaved) { + const ctx = getContext(); + if (ctx.projectId && ctx.orgId === orgId) { + return ctx.projectId; + } + } + + const client = await createUserApiClient(); + const res = await client.post<{ data: any[] }>(ENDPOINTS.PROJECT.LIST, { + orgId, + }); + const projects = res?.data ?? []; + if (!projects.length) { + throw new Error("No projects found in this organization."); + } + const chosen = await promptSelect( + "Select a project", + projects.map((p: any) => { + const id = String(p.id ?? p._id); + const platforms = [ + p.androidEnabled ? "android" : null, + p.iosEnabled ? "ios" : null, + ] + .filter(Boolean) + .join("/"); + return { + name: p.name, + value: id, + description: `${platforms || "no platforms"} id: ${id}`, + }; + }) + ); + const proj = projects.find((p: any) => String(p.id ?? p._id) === chosen); + setContext({ orgId, projectId: chosen, projectName: proj?.name }); + return chosen; +} + +/** Resolve a bucket id, prompting from the project's bucket list if omitted. */ +export async function resolveBucketId( + region: string, + projectId: string, + bucketId?: string +): Promise { + if (bucketId) return bucketId; + const client = await createUserApiClient(region); + const res = await client.post<{ data: any[] }>(ENDPOINTS.BUCKET.LIST, { + projectId, + }); + const buckets = res?.data ?? []; + if (!buckets.length) { + throw new Error("No buckets found in this project."); + } + return promptSelect( + "Select a bucket", + buckets.map((b: any) => { + const id = String(b.id ?? b._id); + return { name: b.name, value: id, description: `id: ${id}` }; + }) + ); +} + +/** Prompt for a platform when one isn't supplied via flags. */ +export async function resolvePlatform(platform?: string): Promise { + if (platform) return platform; + return promptSelect("Select a platform", [ + { name: "android", value: "android" }, + { name: "ios", value: "ios" }, + ]); +} diff --git a/src/command-line/command.registry.ts b/src/command-line/command.registry.ts index dd29995..cca4a86 100644 --- a/src/command-line/command.registry.ts +++ b/src/command-line/command.registry.ts @@ -1,6 +1,7 @@ import { BaseCommand } from "@/command-line/base.command"; import { requiresValidation } from "@/decorators/validate-user.decorator"; import { logger } from "@/utils/logger"; +import { ui } from "@/utils/ui"; // Command Imports import "@/commands/help.command"; @@ -10,6 +11,14 @@ import "@/commands/logout.command"; import "@/commands/generate-key-pair.command"; import "@/commands/release-bundle.command"; import "@/commands/update-release.command"; +import "@/commands/whoami.command"; +import "@/commands/list-projects.command"; +import "@/commands/list-buckets.command"; +import "@/commands/list-bundles.command"; +import "@/commands/list-releases.command"; +import "@/commands/release-info.command"; +import "@/commands/use.command"; +import "@/commands/context.command"; export class CommandRegistry { private commands: Map = new Map(); @@ -51,18 +60,21 @@ export class CommandRegistry { return; } + // Consistent breadcrumb header for every command, except help (which shows + // its own banner) and JSON mode (kept clean for scripting). + if (name !== "help" && !options.json) { + ui.header(name); + } + const needsValidation = requiresValidation(command.constructor); const skipForCIToken = this.shouldSkipValidationForCIToken(command, options); if (needsValidation && !skipForCIToken) { try { - logger.info("Validating user"); await this.validateUser(command); } catch { - logger.info("User validation failed"); + ui.status.fail("Authentication failed."); return; } - } else if (needsValidation && skipForCIToken) { - logger.info("CI token provided, skipping user validation"); } await command.execute(options); diff --git a/src/commands/context.command.ts b/src/commands/context.command.ts new file mode 100644 index 0000000..34a18ad --- /dev/null +++ b/src/commands/context.command.ts @@ -0,0 +1,46 @@ +import { BaseCommand } from "@command-line/base.command"; +import { Command, CommandOption } from "@decorators/command.decorator"; +import { clearContext, getContext, getContextPath } from "@/utils/context-store"; +import { ui } from "@/utils/ui"; + +const expectedOptions: CommandOption[] = [ + { name: "clear", description: "Clear the saved context", required: false }, + { name: "json", description: "Output raw JSON", required: false }, +]; + +@Command({ + name: "context", + description: "Show or clear the saved org/project context", + alias: "ctx", + options: expectedOptions, +}) +export class ContextCommand extends BaseCommand { + async execute(options: Record): Promise { + if (options.clear) { + clearContext(); + ui.status.ok("Context cleared."); + return; + } + + const ctx = getContext(); + if (!ctx.orgId && !ctx.projectId) { + ui.status.info('No context set. Run "stallion use" to set one.'); + return; + } + + if (options.json) { + console.log(JSON.stringify(ctx, null, 2)); + return; + } + + ui.section("Context"); + ui.keyValue([ + ["Org", ctx.orgName], + ["Org Id", ctx.orgId], + ["Region", ctx.region], + ["Project", ctx.projectName], + ["Project Id", ctx.projectId], + ]); + ui.hint(`\nStored at ${getContextPath()}`); + } +} diff --git a/src/commands/list-buckets.command.ts b/src/commands/list-buckets.command.ts new file mode 100644 index 0000000..84dc5f4 --- /dev/null +++ b/src/commands/list-buckets.command.ts @@ -0,0 +1,50 @@ +import { BaseCommand } from "@command-line/base.command"; +import { Command, CommandOption } from "@decorators/command.decorator"; +import { ValidateUser } from "@decorators/validate-user.decorator"; +import { progress } from "@/utils/progress"; +import { ENDPOINTS } from "@/api/endpoints"; +import { + createUserApiClient, + resolveOrgContext, + resolveProjectId, +} from "@/api/user-client"; +import { printTable } from "@/utils/table"; +import { ui } from "@/utils/ui"; + +const expectedOptions: CommandOption[] = [ + { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, + { name: "project-id", description: "Project id (prompts if omitted)", required: false }, + { name: "json", description: "Output raw JSON", required: false }, +]; + +@Command({ + name: "list-buckets", + description: "List the buckets in a project", + alias: "lb", + options: expectedOptions, +}) +@ValidateUser() +export class ListBucketsCommand extends BaseCommand { + async execute(options: Record): Promise { + const json = Boolean(options.json); + const { orgId, region } = await resolveOrgContext(options.orgId); + const projectId = await resolveProjectId(orgId, options.projectId); + const client = await createUserApiClient(region); + + const res = await progress("Fetching buckets", () => + client.post<{ data: any[] }>(ENDPOINTS.BUCKET.LIST, { projectId }) + ); + const buckets = res?.data ?? []; + + if (!json) ui.section("Buckets"); + printTable( + buckets, + [ + { header: "NAME", value: (b) => b.name }, + { header: "BUCKET ID", value: (b) => b.id ?? b._id }, + { header: "UPDATED", value: (b) => b.updatedAt }, + ], + { json, indent: ui.INDENT } + ); + } +} diff --git a/src/commands/list-bundles.command.ts b/src/commands/list-bundles.command.ts new file mode 100644 index 0000000..14eb88a --- /dev/null +++ b/src/commands/list-bundles.command.ts @@ -0,0 +1,92 @@ +import { BaseCommand } from "@command-line/base.command"; +import { Command, CommandOption } from "@decorators/command.decorator"; +import { ValidateUser } from "@decorators/validate-user.decorator"; +import { progress } from "@/utils/progress"; +import { ui } from "@/utils/ui"; +import { ENDPOINTS } from "@/api/endpoints"; +import { + createUserApiClient, + resolveBucketId, + resolveOrgContext, + resolveProjectId, +} from "@/api/user-client"; +import { Column, printTable } from "@/utils/table"; + +const expectedOptions: CommandOption[] = [ + { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, + { name: "project-id", description: "Project id (prompts if omitted)", required: false }, + { name: "bucket-id", description: "Bucket id (prompts if omitted)", required: false }, + { name: "platform", description: "Filter by platform (android|ios); enables paging", required: false }, + { name: "page-size", description: "Page size when filtering by platform (default 20)", required: false }, + { name: "page", description: "Page number when filtering by platform (default 1)", required: false }, + { name: "json", description: "Output raw JSON", required: false }, +]; + +const bundleColumns: Column[] = [ + { header: "VERSION", value: (b) => b.version }, + { header: "PLATFORM", value: (b) => b.platform }, + { header: "PROMOTED", value: (b) => Boolean(b.isPromoted) }, + { header: "AUTHOR", value: (b) => b.author?.email ?? b.author?.fullName }, + { header: "SIZE", value: (b) => b.size }, + { header: "HASH", value: (b) => b.sha256Checksum }, + { header: "CREATED", value: (b) => b.createdAt }, +]; + +@Command({ + name: "list-bundles", + description: "List the staging bundles in a bucket (includes the hash for release)", + alias: "lbd", + options: expectedOptions, +}) +@ValidateUser() +export class ListBundlesCommand extends BaseCommand { + async execute(options: Record): Promise { + const json = Boolean(options.json); + const { orgId, region } = await resolveOrgContext(options.orgId); + const projectId = await resolveProjectId(orgId, options.projectId); + const bucketId = await resolveBucketId(region, projectId, options.bucketId); + const client = await createUserApiClient(region); + + if (options.platform) { + const pageSize = options.pageSize ? Number(options.pageSize) : 20; + const pageNumber = options.page ? Number(options.page) : 1; + const res = await progress("Fetching bundles", () => + client.post<{ data: any }>(ENDPOINTS.BUNDLE.ADVANCE_LISTING, { + projectId, + bucketId, + platform: options.platform, + pageSize, + pageNumber, + }) + ); + const payload = res?.data ?? {}; + if (json) { + console.log(JSON.stringify(payload, null, 2)); + return; + } + ui.section(`Bundles · ${options.platform}`); + printTable(payload.paginatedData ?? [], bundleColumns, { + indent: ui.INDENT, + }); + const current = payload.currentPageNumber ?? pageNumber; + const total = payload.totalPages; + ui.hint(total ? ` Page ${current} of ${total}` : ` Page ${current}`); + return; + } + + const res = await progress("Fetching bundles", () => + client.post<{ data: any }>(ENDPOINTS.BUNDLE.LIST, { projectId, bucketId }) + ); + const payload = res?.data ?? {}; + if (json) { + console.log(JSON.stringify(payload, null, 2)); + return; + } + const bundles = [ + ...(payload.androidBundles ?? []), + ...(payload.iosBundles ?? []), + ]; + ui.section("Bundles"); + printTable(bundles, bundleColumns, { indent: ui.INDENT }); + } +} diff --git a/src/commands/list-projects.command.ts b/src/commands/list-projects.command.ts new file mode 100644 index 0000000..b30da25 --- /dev/null +++ b/src/commands/list-projects.command.ts @@ -0,0 +1,46 @@ +import { BaseCommand } from "@command-line/base.command"; +import { Command, CommandOption } from "@decorators/command.decorator"; +import { ValidateUser } from "@decorators/validate-user.decorator"; +import { progress } from "@/utils/progress"; +import { ENDPOINTS } from "@/api/endpoints"; +import { createUserApiClient, resolveOrgContext } from "@/api/user-client"; +import { printTable } from "@/utils/table"; +import { ui } from "@/utils/ui"; + +const expectedOptions: CommandOption[] = [ + { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, + { name: "json", description: "Output raw JSON", required: false }, +]; + +@Command({ + name: "list-projects", + description: "List the projects in an organization", + alias: "lp", + options: expectedOptions, +}) +@ValidateUser() +export class ListProjectsCommand extends BaseCommand { + async execute(options: Record): Promise { + const json = Boolean(options.json); + const { orgId } = await resolveOrgContext(options.orgId); + const client = await createUserApiClient(); + + const res = await progress("Fetching projects", () => + client.post<{ data: any[] }>(ENDPOINTS.PROJECT.LIST, { orgId }) + ); + const projects = res?.data ?? []; + + if (!json) ui.section("Projects"); + printTable( + projects, + [ + { header: "NAME", value: (p) => p.name }, + { header: "PROJECT ID", value: (p) => p.id ?? p._id }, + { header: "ANDROID", value: (p) => Boolean(p.androidEnabled) }, + { header: "IOS", value: (p) => Boolean(p.iosEnabled) }, + { header: "PATCH", value: (p) => Boolean(p.isPatchEnabled) }, + ], + { json, indent: ui.INDENT } + ); + } +} diff --git a/src/commands/list-releases.command.ts b/src/commands/list-releases.command.ts new file mode 100644 index 0000000..6a54ce5 --- /dev/null +++ b/src/commands/list-releases.command.ts @@ -0,0 +1,89 @@ +import { BaseCommand } from "@command-line/base.command"; +import { Command, CommandOption } from "@decorators/command.decorator"; +import { ValidateUser } from "@decorators/validate-user.decorator"; +import { logger } from "@utils/logger"; +import { progress } from "@/utils/progress"; +import { promptSelect } from "@/utils/prompt"; +import { ENDPOINTS } from "@/api/endpoints"; +import { + createUserApiClient, + resolveOrgContext, + resolvePlatform, + resolveProjectId, +} from "@/api/user-client"; +import { printTable } from "@/utils/table"; +import { ui } from "@/utils/ui"; + +const expectedOptions: CommandOption[] = [ + { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, + { name: "project-id", description: "Project id (prompts if omitted)", required: false }, + { name: "platform", description: "Platform (android|ios); prompts if omitted", required: false }, + { name: "app-version", description: "App version; prompts if omitted", required: false }, + { name: "json", description: "Output raw JSON", required: false }, +]; + +@Command({ + name: "list-releases", + description: "List the production releases for an app version", + alias: "lr", + options: expectedOptions, +}) +@ValidateUser() +export class ListReleasesCommand extends BaseCommand { + async execute(options: Record): Promise { + const json = Boolean(options.json); + const { orgId, region } = await resolveOrgContext(options.orgId); + const projectId = await resolveProjectId(orgId, options.projectId); + const platform = await resolvePlatform(options.platform); + const client = await createUserApiClient(region); + + let appVersion = options.appVersion; + if (!appVersion) { + const verRes = await progress("Fetching app versions", () => + client.post<{ data: any[] }>(ENDPOINTS.PROMOTED.LIST_APP_VERSIONS, { + projectId, + platform, + }) + ); + const versions = verRes?.data ?? []; + if (!versions.length) { + logger.info("No app versions found for this platform."); + return; + } + appVersion = await promptSelect( + "Select an app version", + versions.map((v: any) => ({ + name: v.targetVersion, + value: v.targetVersion, + description: `${v.count} release(s)${ + v.latestReleaseUserEmail ? ` latest by ${v.latestReleaseUserEmail}` : "" + }`, + })) + ); + } + + const res = await progress("Fetching releases", () => + client.post<{ data: any }>(ENDPOINTS.PROMOTED.LISTING, { + projectId, + platform, + appVersion, + }) + ); + const releases = res?.data?.paginatedData ?? []; + + if (!json) ui.section(`Releases · v${appVersion}`); + printTable( + releases, + [ + { header: "APP VERSION", value: (r) => r.appVersion }, + { header: "BUNDLE VERSION", value: (r) => r.bundleVersion }, + { header: "RELEASE ID", value: (r) => r._id }, + { header: "AUTHOR", value: (r) => r.user?.email ?? r.user?.fullName }, + { header: "PAUSED", value: (r) => Boolean(r.isPaused) }, + { header: "ROLLED BACK", value: (r) => Boolean(r.isRolledBack) }, + { header: "CREATED", value: (r) => r.createdAt }, + ], + { json, indent: ui.INDENT } + ); + } +} diff --git a/src/commands/release-info.command.ts b/src/commands/release-info.command.ts new file mode 100644 index 0000000..d69d37d --- /dev/null +++ b/src/commands/release-info.command.ts @@ -0,0 +1,188 @@ +import { BaseCommand } from "@command-line/base.command"; +import { Command, CommandOption } from "@decorators/command.decorator"; +import { ValidateUser } from "@decorators/validate-user.decorator"; +import { logger } from "@utils/logger"; +import { progress } from "@/utils/progress"; +import { promptSelect } from "@/utils/prompt"; +import { ENDPOINTS } from "@/api/endpoints"; +import { + createUserApiClient, + resolveOrgContext, + resolvePlatform, + resolveProjectId, +} from "@/api/user-client"; +import { printBoxes, printTable } from "@/utils/table"; +import { ui } from "@/utils/ui"; +import chalk from "chalk"; + +const expectedOptions: CommandOption[] = [ + { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, + { name: "project-id", description: "Project id (prompts if omitted)", required: false }, + { name: "platform", description: "Platform (android|ios); prompts if omitted", required: false }, + { name: "app-version", description: "App version; prompts if omitted", required: false }, + { name: "promoted-id", description: "Release id (from list-releases); prompts if omitted", required: false }, + { name: "json", description: "Output raw JSON", required: false }, +]; + +@Command({ + name: "release-info", + description: "Show full detail of a single production release", + alias: "ri", + options: expectedOptions, +}) +@ValidateUser() +export class ReleaseInfoCommand extends BaseCommand { + async execute(options: Record): Promise { + const json = Boolean(options.json); + const { orgId, region } = await resolveOrgContext(options.orgId); + const projectId = await resolveProjectId(orgId, options.projectId); + const platform = await resolvePlatform(options.platform); + const client = await createUserApiClient(region); + + let appVersion = options.appVersion; + if (!appVersion) { + const verRes = await progress("Fetching app versions", () => + client.post<{ data: any[] }>(ENDPOINTS.PROMOTED.LIST_APP_VERSIONS, { + projectId, + platform, + }) + ); + const versions = verRes?.data ?? []; + if (!versions.length) { + logger.info("No app versions found for this platform."); + return; + } + appVersion = await promptSelect( + "Select an app version", + versions.map((v: any) => ({ + name: v.targetVersion, + value: v.targetVersion, + description: `${v.count} release(s)${ + v.latestReleaseUserEmail ? ` latest by ${v.latestReleaseUserEmail}` : "" + }`, + })) + ); + } + + let promotedId = options.promotedId; + if (!promotedId) { + const relRes = await progress("Fetching releases", () => + client.post<{ data: any }>(ENDPOINTS.PROMOTED.LISTING, { + projectId, + platform, + appVersion, + }) + ); + const releases = relRes?.data?.paginatedData ?? []; + if (!releases.length) { + logger.info("No releases found for this app version."); + return; + } + promotedId = await promptSelect( + "Select a release", + releases.map((r: any) => { + const status = [ + r.isPaused ? "paused" : null, + r.isRolledBack ? "rolled-back" : null, + ] + .filter(Boolean) + .join(", "); + const created = r.createdAt + ? new Date(r.createdAt).toISOString().slice(0, 10) + : ""; + return { + name: `v${r.bundleVersion}`, + value: String(r._id), + description: `${created}${status ? ` ${status}` : ""} id: ${r._id}`, + }; + }) + ); + } + + const res = await progress("Fetching release detail", () => + client.post<{ data: any }>(ENDPOINTS.PROMOTED.DETAIL, { + projectId, + platform, + appVersion, + promotedId, + }) + ); + const detail = res?.data ?? {}; + + // In JSON mode emit the raw detail once and skip the formatted sections. + if (json) { + console.log(JSON.stringify(detail, null, 2)); + return; + } + + // Summary + Details — two boxes side by side at the top. + ui.section("Release"); + printBoxes([ + { + obj: detail, + title: "App Version Summary", + description: + "Key identifiers and rollout status for the selected bundle.", + fields: [ + { label: "App Version", value: (d) => d.appVersion }, + { label: "Platform", value: (d) => d.platform }, + { + label: "Rollout", + value: (d) => + d.rolloutPercent != null ? `${d.rolloutPercent}%` : null, + }, + { label: "Promoted At", value: (d) => d.createdAt }, + { + label: "Promoted By", + value: (d) => d.user?.email ?? d.user?.fullName, + }, + ], + }, + { + obj: detail, + title: "Details", + description: "Bundle version and release flags.", + fields: [ + { label: "Bundle Version", value: (d) => d.bundleVersion }, + { label: "Mandatory", value: (d) => Boolean(d.isMandatory) }, + { label: "Paused", value: (d) => Boolean(d.isPaused) }, + { label: "Rolled Back", value: (d) => Boolean(d.isRolledBack) }, + ], + }, + ], { indent: ui.INDENT }); + + const events = detail.eventCount ?? {}; + + // Events — counts shown as their own compact table. + ui.section("Events"); + printTable( + [ + { + download: events.DOWNLOAD_COMPLETE_PROD, + install: events.INSTALLED_PROD, + rollback: events.AUTO_ROLLED_BACK_PROD, + totalUsers: detail.totalUsers, + }, + ], + [ + { header: "DOWNLOADS", value: (r) => r.download }, + { header: "INSTALLS", value: (r) => r.install }, + { header: "ROLLBACKS", value: (r) => r.rollback }, + { header: "TOTAL USERS", value: (r) => r.totalUsers }, + ], + { indent: ui.INDENT } + ); + + // Release note — free text, kept out of the tables. + ui.section("Release Note"); + ui.text(detail.releaseNote ? String(detail.releaseNote) : chalk.dim("-")); + + // Bundle hash — shown as a full-width line (too long to box neatly). + ui.section("Bundle Hash"); + ui.text( + detail.publishedBundle?.sha256Checksum + ? String(detail.publishedBundle.sha256Checksum) + : chalk.dim("-") + ); + } +} diff --git a/src/commands/use.command.ts b/src/commands/use.command.ts new file mode 100644 index 0000000..857c47b --- /dev/null +++ b/src/commands/use.command.ts @@ -0,0 +1,105 @@ +import { BaseCommand } from "@command-line/base.command"; +import { Command, CommandOption } from "@decorators/command.decorator"; +import { ValidateUser } from "@decorators/validate-user.decorator"; +import { progress } from "@/utils/progress"; +import { ui } from "@/utils/ui"; +import { promptSelect } from "@/utils/prompt"; +import { ENDPOINTS } from "@/api/endpoints"; +import { createUserApiClient, fetchOrgs } from "@/api/user-client"; +import { setContext } from "@/utils/context-store"; + +const expectedOptions: CommandOption[] = [ + { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, + { name: "project-id", description: "Project id (prompts if omitted)", required: false }, +]; + +@Command({ + name: "use", + description: "Set the default org and project used by other commands", + alias: "u", + options: expectedOptions, +}) +@ValidateUser() +export class UseCommand extends BaseCommand { + async execute(options: Record): Promise { + const client = await createUserApiClient(); + const orgs = await progress("Fetching organizations", () => + fetchOrgs(client) + ); + if (!orgs.length) { + ui.status.fail("No organizations found for this account."); + return; + } + + let orgId = options.orgId as string | undefined; + if (orgId && !orgs.some((o) => o.orgId === orgId)) { + ui.status.fail(`Organization "${orgId}" not found for this account.`); + return; + } + if (!orgId) { + orgId = await promptSelect( + "Select an organization", + orgs.map((o) => ({ + name: o.name, + value: o.orgId, + description: `region: ${o.region} id: ${o.orgId}`, + })) + ); + } + const org = orgs.find((o) => o.orgId === orgId); + const region = org?.region ?? "ap"; + + const projRes = await progress("Fetching projects", () => + client.post<{ data: any[] }>(ENDPOINTS.PROJECT.LIST, { orgId }) + ); + const projects = projRes?.data ?? []; + if (!projects.length) { + ui.status.fail("No projects found in this organization."); + return; + } + + let projectId = options.projectId as string | undefined; + if ( + projectId && + !projects.some((p: any) => String(p.id ?? p._id) === projectId) + ) { + ui.status.fail(`Project "${projectId}" not found in this organization.`); + return; + } + if (!projectId) { + projectId = await promptSelect( + "Select a project", + projects.map((p: any) => { + const id = String(p.id ?? p._id); + const platforms = [ + p.androidEnabled ? "android" : null, + p.iosEnabled ? "ios" : null, + ] + .filter(Boolean) + .join("/"); + return { + name: p.name, + value: id, + description: `${platforms || "no platforms"} id: ${id}`, + }; + }) + ); + } + const project = projects.find( + (p: any) => String(p.id ?? p._id) === projectId + ); + + setContext({ + orgId, + orgName: org?.name, + region, + projectId, + projectName: project?.name, + }); + + ui.status.ok( + `Context set: ${org?.name ?? orgId} / ${project?.name ?? projectId} (${region})` + ); + ui.hint('Commands now default to this context. Override with flags, or change it with "stallion use".'); + } +} diff --git a/src/commands/whoami.command.ts b/src/commands/whoami.command.ts new file mode 100644 index 0000000..48cc3fb --- /dev/null +++ b/src/commands/whoami.command.ts @@ -0,0 +1,56 @@ +import { BaseCommand } from "@command-line/base.command"; +import { Command, CommandOption } from "@decorators/command.decorator"; +import { ValidateUser } from "@decorators/validate-user.decorator"; +import { progress } from "@/utils/progress"; +import { ENDPOINTS } from "@/api/endpoints"; +import { createUserApiClient, fetchOrgs } from "@/api/user-client"; +import { printTable } from "@/utils/table"; +import { ui } from "@/utils/ui"; + +const expectedOptions: CommandOption[] = [ + { name: "json", description: "Output raw JSON", required: false }, +]; + +@Command({ + name: "whoami", + description: "Show the logged-in user and their organizations", + alias: "me", + options: expectedOptions, +}) +@ValidateUser() +export class WhoamiCommand extends BaseCommand { + async execute(options: Record): Promise { + const json = Boolean(options.json); + const client = await createUserApiClient(); + + const { profile, orgs } = await progress("Fetching profile", async () => { + const profileRes = await client.get<{ data: any }>(ENDPOINTS.USER.VERIFY); + const orgsList = await fetchOrgs(client); + return { profile: profileRes?.data, orgs: orgsList }; + }); + + if (json) { + console.log(JSON.stringify({ profile, orgs }, null, 2)); + return; + } + + ui.section("User"); + ui.keyValue([ + ["Name", profile?.fullName], + ["Email", profile?.email], + ["Id", profile?._id], + ]); + + ui.section("Organizations"); + printTable( + orgs, + [ + { header: "NAME", value: (o) => o.name }, + { header: "ORG ID", value: (o) => o.orgId }, + { header: "REGION", value: (o) => o.region }, + { header: "ACCESS", value: (o) => o.access }, + ], + { indent: ui.INDENT } + ); + } +} diff --git a/src/utils/common.ts b/src/utils/common.ts index d205f33..c39195c 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -2,8 +2,8 @@ import { CONFIG } from "@/api/config"; export function getApiBaseUrl(region: string): string { if (!region) { - return CONFIG.API.BASE_URL; + return `http://localhost:8000/api/v1`; } - return `https://api-${region}.stalliontech.io/api/v1`; + return `http://localhost:8000/api/v1`; } diff --git a/src/utils/context-store.ts b/src/utils/context-store.ts new file mode 100644 index 0000000..8d0c9fb --- /dev/null +++ b/src/utils/context-store.ts @@ -0,0 +1,49 @@ +import * as fs from "fs"; +import * as path from "path"; +import * as os from "os"; + +/** + * The user's saved working context — the default org/project that commands + * fall back to when their ids aren't passed as flags. Stored next to the + * token at ~/.stallion/context.json. + */ +export interface StallionContext { + orgId?: string; + orgName?: string; + region?: string; + projectId?: string; + projectName?: string; +} + +function getContextFilePath(): string { + return path.join(os.homedir(), ".stallion", "context.json"); +} + +export function getContext(): StallionContext { + try { + return JSON.parse(fs.readFileSync(getContextFilePath(), "utf8")); + } catch { + return {}; + } +} + +/** Merge a patch into the saved context. Keys set to undefined are removed. */ +export function setContext(patch: Partial): StallionContext { + const filePath = getContextFilePath(); + const next: StallionContext = { ...getContext(), ...patch }; + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, JSON.stringify(next, null, 2), "utf8"); + return next; +} + +export function clearContext(): void { + try { + fs.rmSync(getContextFilePath()); + } catch { + /* nothing to clear */ + } +} + +export function getContextPath(): string { + return getContextFilePath(); +} diff --git a/src/utils/prompt.ts b/src/utils/prompt.ts index 20fd3ff..68eb6fd 100644 --- a/src/utils/prompt.ts +++ b/src/utils/prompt.ts @@ -1,4 +1,34 @@ -import inquirer from "inquirer"; +import { + input, + confirm, + select, + search, + checkbox, + password, + number, +} from "@inquirer/prompts"; +import chalk from "chalk"; + +export interface SelectChoice { + name: string; + value: T; + /** Optional dimmed secondary line shown under the highlighted item. */ + description?: string; +} + +/** Shared look for list/search prompts. */ +const theme = { + prefix: chalk.cyan("?"), + icon: { cursor: chalk.cyan("❯") }, + style: { + answer: (text: string) => chalk.cyan(text), + highlight: (text: string) => chalk.cyan.bold(text), + description: (text: string) => chalk.dim(text), + }, +} as const; + +/** Lists longer than this switch to a type-ahead (filterable) prompt. */ +const SEARCH_THRESHOLD = 8; /** * Prompt for a text input @@ -7,15 +37,7 @@ export async function promptText( message: string, defaultValue?: string ): Promise { - const { answer } = await inquirer.prompt<{ answer: string }>([ - { - type: "input", - name: "answer", - message, - default: defaultValue, - }, - ]); - return answer; + return input({ message, default: defaultValue, theme }); } /** @@ -25,33 +47,34 @@ export async function promptConfirm( message: string, defaultValue: boolean = true ): Promise { - const { answer } = await inquirer.prompt<{ answer: boolean }>([ - { - type: "confirm", - name: "answer", - message, - default: defaultValue, - }, - ]); - return answer; + return confirm({ message, default: defaultValue, theme }); } /** - * Prompt to select a single choice from a list + * Prompt to select a single choice from a list. Short lists use arrow-key + * selection; long lists (> SEARCH_THRESHOLD) switch to a type-ahead filter so + * you can narrow by typing. */ export async function promptSelect( message: string, - choices: Array<{ name: string; value: T }> + choices: Array> ): Promise { - const { answer } = await inquirer.prompt<{ answer: T }>([ - { - type: "list", - name: "answer", - message, - choices, - }, - ]); - return answer; + if (choices.length > SEARCH_THRESHOLD) { + return search({ + message: `${message} ${chalk.dim("(type to filter)")}`, + source: (term) => { + const t = (term ?? "").toLowerCase(); + return choices.filter( + (c) => + !t || + c.name.toLowerCase().includes(t) || + (c.description ?? "").toLowerCase().includes(t) + ); + }, + theme, + }); + } + return select({ message, choices, pageSize: 12, loop: false, theme }); } /** @@ -59,32 +82,16 @@ export async function promptSelect( */ export async function promptMultiSelect( message: string, - choices: Array<{ name: string; value: T }> + choices: Array> ): Promise { - const { answer } = await inquirer.prompt<{ answer: T[] }>([ - { - type: "checkbox", - name: "answer", - message, - choices, - }, - ]); - return answer; + return checkbox({ message, choices, pageSize: 12, loop: false, theme }); } /** * Prompt for a password */ export async function promptPassword(message: string): Promise { - const { answer } = await inquirer.prompt<{ answer: string }>([ - { - type: "password", - name: "answer", - message, - mask: "*", - }, - ]); - return answer; + return password({ message, mask: "*", theme }); } /** @@ -94,17 +101,6 @@ export async function promptNumber( message: string, defaultValue?: number ): Promise { - const { answer } = await inquirer.prompt<{ answer: number }>([ - { - type: "number", - name: "answer", - message, - default: defaultValue, - validate: (value) => - typeof value === "number" && !isNaN(value) - ? true - : "Please enter a valid number", - }, - ]); - return answer; + const answer = await number({ message, default: defaultValue, theme }); + return answer ?? 0; } diff --git a/src/utils/table.ts b/src/utils/table.ts new file mode 100644 index 0000000..9771113 --- /dev/null +++ b/src/utils/table.ts @@ -0,0 +1,260 @@ +import chalk from "chalk"; + +export interface Column { + header: string; + value: (row: any) => any; +} + +export interface DetailField { + label: string; + value: (obj: any) => any; +} + +/** Solid outer frame + header separator. */ +const solid = (s: string) => chalk.gray(s); +/** Very light, dotted separator between rows. */ +const light = (s: string) => chalk.dim(chalk.gray(s)); +const headerStyle = chalk.bold.cyan; +/** Subdued style for box descriptions — dim + italic to read as fine print. */ +const descStyle = (s: string) => chalk.dim.italic(s); + +const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/; + +/** Render a Date as a compact, human-readable "YYYY-MM-DD HH:mm UTC" string. */ +function formatDate(d: Date): string { + const p = (n: number) => String(n).padStart(2, "0"); + return ( + `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ` + + `${p(d.getUTCHours())}:${p(d.getUTCMinutes())} UTC` + ); +} + +/** Format a single cell value into a plain (uncolored) string. */ +export function formatValue(value: any): string { + return cell(value); +} + +/** Format a value and apply its status color (for inline key/value rendering). */ +export function renderValue(value: any): string { + const plain = cell(value); + return colorize(plain, plain); +} + +/** Shared accent style for headers and labels. */ +export const accentStyle = headerStyle; + +/** Format a single cell value into a plain (uncolored) string. */ +function cell(value: any): string { + if (value === null || value === undefined || value === "") return "-"; + if (value instanceof Date) return formatDate(value); + if (typeof value === "boolean") return value ? "Yes" : "No"; + if (typeof value === "string" && ISO_DATE.test(value)) { + const d = new Date(value); + if (!isNaN(d.getTime())) return formatDate(d); + } + return String(value); +} + +function pad(text: string, width: number): string { + return text + " ".repeat(Math.max(0, width - text.length)); +} + +/** + * Apply color to a value based on its plain content. Color codes are + * zero-width, so this never affects column alignment as long as padding has + * already been applied to the plain text. + */ +function colorize(plain: string, padded: string): string { + const trimmed = plain.trim(); + if (trimmed === "-") return chalk.dim(padded); + if (trimmed === "Yes") return chalk.green(padded); + if (trimmed === "No") return chalk.dim(padded); + return padded; +} + +/** A solid horizontal rule (outer frame / header separator). */ +function rule( + left: string, + mid: string, + right: string, + widths: number[] +): string { + return solid(left + widths.map((w) => "─".repeat(w + 2)).join(mid) + right); +} + +/** A light dotted separator placed between data rows. Outer edges stay solid. */ +function rowSeparator(widths: number[]): string { + return ( + solid("├") + + widths.map((w) => light("╌".repeat(w + 2))).join(light("┼")) + + solid("┤") + ); +} + +/** + * Print rows as a bordered table, or as raw JSON when `json` is set. + * In JSON mode the original `rows` objects are emitted (not the formatted + * columns) so the output stays script-friendly. + */ +export function printTable( + rows: any[], + columns: Column[], + options: { json?: boolean; indent?: number } = {} +): void { + if (options.json) { + console.log(JSON.stringify(rows, null, 2)); + return; + } + const pre = " ".repeat(options.indent ?? 0); + const out = (line: string) => console.log(pre + line); + if (!rows.length) { + out(chalk.dim("No results.")); + return; + } + + const headers = columns.map((c) => c.header); + const data = rows.map((row) => columns.map((c) => cell(c.value(row)))); + const widths = headers.map((header, i) => + Math.max(header.length, ...data.map((row) => row[i].length)) + ); + + const v = solid("│"); + + out(rule("┌", "┬", "┐", widths)); + out( + v + + headers + .map((h, i) => headerStyle(" " + pad(h, widths[i]) + " ")) + .join(v) + + v + ); + out(rule("├", "┼", "┤", widths)); + data.forEach((row, idx) => { + if (idx > 0) out(rowSeparator(widths)); + out( + v + + row + .map((c, i) => colorize(c, " " + pad(c, widths[i]) + " ")) + .join(v) + + v + ); + }); + out(rule("└", "┴", "┘", widths)); +} + +/** + * Print a single object as an aligned key/value detail view, or raw JSON when + * `json` is set. + */ +export function printDetail( + obj: any, + fields: DetailField[], + options: { json?: boolean; indent?: number } = {} +): void { + if (options.json) { + console.log(JSON.stringify(obj, null, 2)); + return; + } + const pre = " ".repeat(options.indent ?? 0); + const labelWidth = Math.max(...fields.map((f) => f.label.length)); + for (const field of fields) { + const plain = cell(field.value(obj)); + console.log( + `${pre}${headerStyle(pad(field.label, labelWidth))} ${colorize(plain, plain)}` + ); + } +} + +/** Greedily wrap text into lines no wider than `width`. */ +function wrapText(text: string, width: number): string[] { + const lines: string[] = []; + let current = ""; + for (const word of text.split(/\s+/)) { + if (!current) current = word; + else if ((current + " " + word).length <= width) current += " " + word; + else { + lines.push(current); + current = word; + } + } + if (current) lines.push(current); + return lines; +} + +export interface BoxSpec { + obj: any; + fields: DetailField[]; + title?: string; + description?: string; +} + +/** + * Build a framed box as an array of equal-width lines: a title heading and an + * optional dim/italic description live inside the frame, above the aligned + * key/value rows. Returned (not printed) so boxes can be laid out side by side. + */ +function buildBox(spec: BoxSpec): { lines: string[]; width: number } { + const { obj, fields, title = "", description } = spec; + const labelWidth = Math.max(...fields.map((f) => f.label.length)); + const gap = " "; + const rows = fields.map((f) => { + const plain = cell(f.value(obj)); + return { label: f.label, plain, len: labelWidth + gap.length + plain.length }; + }); + + const target = Math.max(title.length, ...rows.map((r) => r.len), 24); + const descLines = description ? wrapText(description, target) : []; + const innerWidth = Math.max(target, ...descLines.map((l) => l.length)); + + const v = solid("│"); + const mk = (content: string, plainLen: number) => + `${v} ${content}${" ".repeat(Math.max(0, innerWidth - plainLen))} ${v}`; + + const lines: string[] = []; + lines.push(solid("┌" + "─".repeat(innerWidth + 2) + "┐")); + if (title) lines.push(mk(headerStyle(title), title.length)); + for (const dl of descLines) lines.push(mk(descStyle(dl), dl.length)); + if (title || descLines.length) lines.push(mk("", 0)); + for (const r of rows) { + const content = + headerStyle(pad(r.label, labelWidth)) + gap + colorize(r.plain, r.plain); + lines.push(mk(content, r.len)); + } + lines.push(solid("└" + "─".repeat(innerWidth + 2) + "┘")); + + return { lines, width: innerWidth + 4 }; +} + +/** Print a single framed box (title + optional description + key/value rows). */ +export function printBox( + obj: any, + fields: DetailField[], + options: { title?: string; description?: string; indent?: number } = {} +): void { + const pre = " ".repeat(options.indent ?? 0); + const { lines } = buildBox({ obj, fields, ...options }); + for (const line of lines) console.log(pre + line); +} + +/** + * Print several boxes side by side, top-aligned. Shorter boxes are padded with + * blank lines so the row stays aligned. + */ +export function printBoxes( + specs: BoxSpec[], + options: { gap?: number; indent?: number } = {} +): void { + const gap = options.gap ?? 3; + const pre = " ".repeat(options.indent ?? 0); + const boxes = specs.map(buildBox); + const height = Math.max(...boxes.map((b) => b.lines.length)); + const sep = " ".repeat(gap); + for (let i = 0; i < height; i++) { + console.log( + pre + + boxes + .map((b) => (i < b.lines.length ? b.lines[i] : " ".repeat(b.width))) + .join(sep) + ); + } +} diff --git a/src/utils/ui.ts b/src/utils/ui.ts new file mode 100644 index 0000000..0f50942 --- /dev/null +++ b/src/utils/ui.ts @@ -0,0 +1,83 @@ +import chalk from "chalk"; +import { accentStyle, renderValue } from "@/utils/table"; + +/** + * Shared visual language for the CLI. Every command should compose its output + * from these primitives so the look stays consistent: + * + * ui.header("whoami") STALLION · whoami breadcrumb + * ui.section("User") ┃ USER accent-bar heading + * ui.keyValue([["Name", ...]]) indented, aligned key/values + * ui.hint("...") dim helper line + * ui.badge(true, ...) colored status pill + * + * Tables and boxes live in ./table and accept an `indent` so they line up + * under section headings (use INDENT). + */ + +/** Standard left indent for content under a section heading. */ +export const INDENT = 2; + +const pre = " ".repeat(INDENT); + +/** Top-of-command breadcrumb, e.g. `STALLION · whoami`. */ +export function header(command: string, subtitle?: string): void { + const crumb = chalk.dim("STALLION") + chalk.dim(" · ") + chalk.bold.white(command); + console.log("\n" + crumb + (subtitle ? chalk.dim(` ${subtitle}`) : "")); +} + +/** Accent-bar section heading, e.g. `┃ ORGANIZATIONS`. */ +export function section(title: string): void { + console.log("\n" + accentStyle("┃ " + title.toUpperCase())); +} + +/** Indented, aligned key/value block. Values get status coloring + formatting. */ +export function keyValue(rows: Array<[string, any]>): void { + const labelWidth = Math.max(...rows.map(([label]) => label.length)); + for (const [label, value] of rows) { + const paddedLabel = label + " ".repeat(labelWidth - label.length); + console.log(`${pre}${chalk.dim(paddedLabel)} ${renderValue(value)}`); + } +} + +/** A dim, secondary helper line (tips, file paths, pagination). */ +export function hint(text: string): void { + console.log(chalk.dim(text)); +} + +/** Plain free-text line under a section, indented to match key/values. */ +export function text(value: string): void { + console.log(pre + value); +} + +type BadgeKind = "ok" | "warn" | "danger" | "muted"; + +/** A small colored status pill, e.g. badge("active", "ok"). */ +export function badge(label: string, kind: BadgeKind = "muted"): string { + const styles: Record string> = { + ok: chalk.bgGreen.black, + warn: chalk.bgYellow.black, + danger: chalk.bgRed.white, + muted: chalk.bgGray.white, + }; + return styles[kind](` ${label} `); +} + +/** Success / failure / info status lines (consistent glyphs + spacing). */ +export const status = { + ok: (msg: string) => console.log(chalk.green("✓"), msg), + fail: (msg: string) => console.log(chalk.red("✗"), msg), + info: (msg: string) => console.log(chalk.cyan("ℹ"), msg), + warn: (msg: string) => console.log(chalk.yellow("⚠"), msg), +}; + +export const ui = { + INDENT, + header, + section, + keyValue, + hint, + text, + badge, + status, +}; From 843e18ec6dfff926ac8d29e37c300e45b523e09b Mon Sep 17 00:00:00 2001 From: Maximus Decimus Meridius Date: Thu, 2 Jul 2026 01:04:44 +0530 Subject: [PATCH 02/13] feat(cli): branded terminal UI + CI-friendly JSON output --- package.json | 4 +- pnpm-lock.yaml | 121 --------- src/api/endpoints.ts | 26 +- src/api/user-client.ts | 24 +- src/command-line/base.command.ts | 31 ++- src/command-line/command.registry.ts | 7 +- src/commands/context.command.ts | 2 +- src/commands/generate-key-pair.command.ts | 77 ++---- src/commands/generate-secret-key.command.ts | 96 ++------ src/commands/help.command.ts | 26 +- src/commands/list-buckets.command.ts | 58 +++-- src/commands/list-bundles.command.ts | 110 +++++---- src/commands/list-projects.command.ts | 37 ++- src/commands/list-releases.command.ts | 104 ++++++-- src/commands/publish-bundle.command.ts | 109 +++++++- src/commands/release-bundle.command.ts | 34 ++- src/commands/release-info.command.ts | 198 ++++++++------- src/commands/update-release.command.ts | 37 ++- src/commands/use.command.ts | 2 +- src/commands/whoami.command.ts | 38 +-- src/index.ts | 35 ++- src/ui/ansi.ts | 26 ++ src/ui/banner.ts | 113 +++++++++ src/ui/box.ts | 59 +++++ src/ui/format.ts | 44 ++++ src/ui/index.ts | 85 +++++++ src/ui/layout.ts | 99 ++++++++ src/ui/logger.ts | 16 ++ src/ui/logo.generated.ts | 33 +++ src/ui/meter.ts | 20 ++ src/ui/pipeline.ts | 86 +++++++ src/ui/spinner.ts | 101 ++++++++ src/ui/status.ts | 13 + src/ui/table.ts | 198 +++++++++++++++ src/ui/theme.ts | 94 +++++++ src/utils/banner.ts | 37 --- src/utils/context-store.ts | 3 + src/utils/logger.ts | 15 -- src/utils/progress.ts | 47 +--- src/utils/react-native-utils.ts | 25 +- src/utils/stdout.ts | 15 ++ src/utils/table.ts | 260 -------------------- src/utils/token-store.ts | 11 + src/utils/ui.ts | 83 ------- 44 files changed, 1677 insertions(+), 982 deletions(-) create mode 100644 src/ui/ansi.ts create mode 100644 src/ui/banner.ts create mode 100644 src/ui/box.ts create mode 100644 src/ui/format.ts create mode 100644 src/ui/index.ts create mode 100644 src/ui/layout.ts create mode 100644 src/ui/logger.ts create mode 100644 src/ui/logo.generated.ts create mode 100644 src/ui/meter.ts create mode 100644 src/ui/pipeline.ts create mode 100644 src/ui/spinner.ts create mode 100644 src/ui/status.ts create mode 100644 src/ui/table.ts create mode 100644 src/ui/theme.ts delete mode 100644 src/utils/banner.ts delete mode 100644 src/utils/logger.ts create mode 100644 src/utils/stdout.ts delete mode 100644 src/utils/table.ts delete mode 100644 src/utils/ui.ts diff --git a/package.json b/package.json index 3366267..c3ffc46 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,6 @@ "commander": "^13.1.0", "figlet": "^1.7.0", "gradient-string": "^2.0.2", - "inquirer": "^12.6.1", "jsonwebtoken": "^9.0.2", "lodash": "^4.17.21", "mime": "^3.0.0", @@ -48,7 +47,6 @@ "@types/chalk": "^2.2.4", "@types/figlet": "^1.5.8", "@types/gradient-string": "^1.1.6", - "@types/inquirer": "^9.0.8", "@types/jsonwebtoken": "^9.0.9", "@types/lodash": "^4.17.16", "@types/node": "^22.15.17", @@ -56,11 +54,11 @@ "@types/semver": "^7.7.0", "cross-env": "^7.0.3", "prettier": "^3.5.3", + "semantic-release": "19.0.3", "ts-node": "^10.9.2", "ts-node-dev": "^2.0.0", "tsc-alias": "^1.8.8", "tsconfig-paths": "^4.2.0", - "semantic-release": "19.0.3", "typescript": "^5.8.3" }, "release": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0352475..faf556e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,9 +29,6 @@ importers: gradient-string: specifier: ^2.0.2 version: 2.0.2 - inquirer: - specifier: ^12.6.1 - version: 12.6.1(@types/node@22.15.17) jsonwebtoken: specifier: ^9.0.2 version: 9.0.2 @@ -75,9 +72,6 @@ importers: '@types/gradient-string': specifier: ^1.1.6 version: 1.1.6 - '@types/inquirer': - specifier: ^9.0.8 - version: 9.0.8 '@types/jsonwebtoken': specifier: ^9.0.9 version: 9.0.9 @@ -158,15 +152,6 @@ packages: '@types/node': optional: true - '@inquirer/core@10.1.11': - resolution: {integrity: sha512-BXwI/MCqdtAhzNQlBEFE7CEflhPkl/BqvAuV/aK6lW3DClIfYVDWPP/kXuXHtBWC7/EEbNqd/1BGq2BGBBnuxw==} - 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'} @@ -203,10 +188,6 @@ packages: '@types/node': optional: true - '@inquirer/figures@1.0.11': - resolution: {integrity: sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==} - engines: {node: '>=18'} - '@inquirer/figures@1.0.15': resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} engines: {node: '>=18'} @@ -283,15 +264,6 @@ packages: '@types/node': optional: true - '@inquirer/type@3.0.6': - resolution: {integrity: sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -450,9 +422,6 @@ packages: '@types/gradient-string@1.1.6': resolution: {integrity: sha512-LkaYxluY4G5wR1M4AKQUal2q61Di1yVVCw42ImFTuaIoQVgmV0WP1xUaLB8zwb47mp82vWTpePI9JmrjEnJ7nQ==} - '@types/inquirer@9.0.8': - resolution: {integrity: sha512-CgPD5kFGWsb8HJ5K7rfWlifao87m4ph8uioU7OTncJevmE/VLIqAAjfQtko578JZg7/f69K4FgqYym3gNr7DeA==} - '@types/jsonwebtoken@9.0.9': resolution: {integrity: sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==} @@ -489,9 +458,6 @@ packages: '@types/strip-json-comments@0.0.30': resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==} - '@types/through@0.0.33': - resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} - '@types/tinycolor2@1.4.6': resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==} @@ -520,10 +486,6 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - ansi-escapes@6.2.1: resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==} engines: {node: '>=14.16'} @@ -1133,15 +1095,6 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - inquirer@12.6.1: - resolution: {integrity: sha512-MGFnzHVS3l3oM3cy+LWkyR7UUtVEn3D5U41CZbEY34szToWoJAvaVtCTz1mxsEzZFk/HXWyCArn0HDgloTXMDw==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - into-stream@6.0.0: resolution: {integrity: sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==} engines: {node: '>=10'} @@ -1814,16 +1767,9 @@ packages: engines: {node: 20 || >=22} hasBin: true - run-async@3.0.0: - resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} - engines: {node: '>=0.12.0'} - run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rxjs@7.8.2: - resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -2062,9 +2008,6 @@ packages: tsconfig@7.0.0: resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==} - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - type-fest@0.16.0: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} engines: {node: '>=10'} @@ -2073,10 +2016,6 @@ packages: resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} engines: {node: '>=10'} - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} @@ -2180,10 +2119,6 @@ packages: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} - yoctocolors-cjs@2.1.2: - resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} - engines: {node: '>=18'} - yoctocolors-cjs@2.1.3: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} @@ -2228,19 +2163,6 @@ snapshots: optionalDependencies: '@types/node': 22.15.17 - '@inquirer/core@10.1.11(@types/node@22.15.17)': - dependencies: - '@inquirer/figures': 1.0.11 - '@inquirer/type': 3.0.6(@types/node@22.15.17) - ansi-escapes: 4.3.2 - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.2 - optionalDependencies: - '@types/node': 22.15.17 - '@inquirer/core@10.3.2(@types/node@22.15.17)': dependencies: '@inquirer/ansi': 1.0.2 @@ -2277,8 +2199,6 @@ snapshots: optionalDependencies: '@types/node': 22.15.17 - '@inquirer/figures@1.0.11': {} - '@inquirer/figures@1.0.15': {} '@inquirer/input@4.3.1(@types/node@22.15.17)': @@ -2349,10 +2269,6 @@ snapshots: optionalDependencies: '@types/node': 22.15.17 - '@inquirer/type@3.0.6(@types/node@22.15.17)': - optionalDependencies: - '@types/node': 22.15.17 - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -2585,11 +2501,6 @@ snapshots: dependencies: '@types/tinycolor2': 1.4.6 - '@types/inquirer@9.0.8': - dependencies: - '@types/through': 0.0.33 - rxjs: 7.8.2 - '@types/jsonwebtoken@9.0.9': dependencies: '@types/ms': 2.1.0 @@ -2623,10 +2534,6 @@ snapshots: '@types/strip-json-comments@0.0.30': {} - '@types/through@0.0.33': - dependencies: - '@types/node': 22.15.17 - '@types/tinycolor2@1.4.6': {} JSONStream@1.3.5: @@ -2651,10 +2558,6 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 - ansi-escapes@6.2.1: {} ansi-regex@5.0.1: {} @@ -3294,18 +3197,6 @@ snapshots: ini@1.3.8: {} - inquirer@12.6.1(@types/node@22.15.17): - dependencies: - '@inquirer/core': 10.1.11(@types/node@22.15.17) - '@inquirer/prompts': 7.10.1(@types/node@22.15.17) - '@inquirer/type': 3.0.6(@types/node@22.15.17) - ansi-escapes: 4.3.2 - mute-stream: 2.0.0 - run-async: 3.0.0 - rxjs: 7.8.2 - optionalDependencies: - '@types/node': 22.15.17 - into-stream@6.0.0: dependencies: from2: 2.3.0 @@ -3837,16 +3728,10 @@ snapshots: glob: 11.0.2 package-json-from-dist: 1.0.1 - run-async@3.0.0: {} - run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 - rxjs@7.8.2: - dependencies: - tslib: 2.8.1 - safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -4128,14 +4013,10 @@ snapshots: strip-bom: 3.0.0 strip-json-comments: 2.0.1 - tslib@2.8.1: {} - type-fest@0.16.0: {} type-fest@0.18.1: {} - type-fest@0.21.3: {} - type-fest@0.6.0: {} type-fest@0.8.1: {} @@ -4225,8 +4106,6 @@ snapshots: yn@3.1.1: {} - yoctocolors-cjs@2.1.2: {} - yoctocolors-cjs@2.1.3: {} zip-stream@6.0.1: diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index 7142bde..606f6ca 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -11,24 +11,32 @@ export const ENDPOINTS = { PROMOTE_BUNDLE: "/cli/ci/promote", UPDATE_RELEASE: "/cli/ci/update-release", }, + // All reads go through CLI-owned /cli routes (lean payloads, stable contract). + // Project-scoped reads also have a /cli/ci variant for CI-token auth. ORG: { GET_ORG_REGION: "/cli/org/get-region", - LIST: "/org/list", + LIST: "/cli/list-orgs", // user token only (org scope needs a user) }, PROJECT: { - LIST: "/project/list", - DETAIL: "/project/detail", + LIST: "/cli/list-projects", // user token only }, BUCKET: { - LIST: "/bucket/list", + LIST: "/cli/list-buckets", + CI_LIST: "/cli/ci/list-buckets", }, BUNDLE: { - LIST: "/bundle/list", - ADVANCE_LISTING: "/bundle/advance-listing", + LIST: "/cli/list-bundles", + CI_LIST: "/cli/ci/list-bundles", + // Existence check polled by publish-bundle after upload. + BY_HASH: "/cli/bundle-by-hash", + CI_BY_HASH: "/cli/ci/bundle-by-hash", }, PROMOTED: { - LISTING: "/promoted/promoted-bundle-listing", - LIST_APP_VERSIONS: "/promoted/list-app-versions", - DETAIL: "/promoted/promoted-bundle-detail", + LISTING: "/cli/list-releases", + CI_LISTING: "/cli/ci/list-releases", + LIST_APP_VERSIONS: "/cli/list-app-versions", + CI_LIST_APP_VERSIONS: "/cli/ci/list-app-versions", + DETAIL: "/cli/release-info", + CI_DETAIL: "/cli/ci/release-info", }, } as const; diff --git a/src/api/user-client.ts b/src/api/user-client.ts index 27da99d..931dc93 100644 --- a/src/api/user-client.ts +++ b/src/api/user-client.ts @@ -5,6 +5,19 @@ import { getApiBaseUrl } from "@/utils/common"; import { createDefaultTokenStore } from "@/utils/token-store"; import { promptSelect } from "@/utils/prompt"; import { getContext, setContext } from "@/utils/context-store"; +import { parseTokenRegion } from "@/utils/region"; + +/** + * Build an ApiClient authenticated with a CI token (x-ci-token). Region is + * derived from the token itself. Used by the non-interactive --ci-token path of + * the project-scoped read commands. + */ +export function createCiApiClient(ciToken: string): ApiClient { + const region = parseTokenRegion(ciToken) ?? "ap"; + const client = new ApiClient(getApiBaseUrl(region)); + client.setHeaders({ "x-ci-token": ciToken }); + return client; +} /** * Read the stored login JWT, or throw a friendly error if the user is not @@ -42,14 +55,13 @@ export interface OrgSummary { access: string; } -/** Fetch the user's organizations (with their region) from the global API. */ +/** Fetch the user's organizations (lean shape) from /cli/list-orgs. */ export async function fetchOrgs(client: ApiClient): Promise { - const res = await client.get<{ data: any[] }>(ENDPOINTS.ORG.LIST); - const orgs = res?.data ?? []; - return orgs.map((o: any) => ({ + const res = await client.get<{ data: OrgSummary[] }>(ENDPOINTS.ORG.LIST); + return (res?.data ?? []).map((o) => ({ orgId: String(o.orgId), - name: o.orgData?.[0]?.name ?? "(unknown)", - region: o.orgData?.[0]?.region ?? "ap", + name: o.name ?? "(unknown)", + region: o.region ?? "ap", access: o.access ?? "", })); } diff --git a/src/command-line/base.command.ts b/src/command-line/base.command.ts index 3f72868..e998b02 100644 --- a/src/command-line/base.command.ts +++ b/src/command-line/base.command.ts @@ -1,9 +1,10 @@ import { promptText } from "@/utils/prompt"; -import { logger } from "@/utils/logger"; +import { ui } from "@/ui"; import { ENDPOINTS } from "@/api/endpoints"; import opener from "opener"; import os from "os"; import { createDefaultTokenStore } from "@/utils/token-store"; +import { setContext } from "@/utils/context-store"; import { ApiClient } from "@/api/api-client"; import { CONFIG } from "@/api/config"; import { progress } from "@/utils/progress"; @@ -23,7 +24,7 @@ export abstract class BaseCommand { .map((opt) => `--${opt.name}`); if (missing.length) { - logger.error(`Missing required options: ${missing.join(", ")}`); + ui.status.fail(`Missing required options: ${missing.join(", ")}`); return false; } @@ -49,7 +50,7 @@ export abstract class BaseCommand { ); if (providedDisallowed.length) { - logger.error( + ui.status.fail( `When --custom-bundle-path is provided, these options are not allowed: ${providedDisallowed .map((n) => `--${n}`) .join(", ")}` @@ -63,7 +64,7 @@ export abstract class BaseCommand { async login(): Promise { try { - logger.info( + ui.status.info( `Opening your browser...${os.EOL}• Visit ${ENDPOINTS.CLI_LOGIN} and enter the code:` ); @@ -72,7 +73,7 @@ export abstract class BaseCommand { const token = await promptText("Enter your access token:"); if (!token || token.trim().length < 5) { - logger.error("Invalid token entered."); + ui.status.fail("Invalid token entered."); return false; } @@ -84,7 +85,7 @@ export abstract class BaseCommand { }); await progress("Verifying login", () => this.verifyLogin()); - logger.success("Token saved successfully. Login successful."); + ui.status.ok("Token saved successfully. Login successful."); return true; } catch (error) { throw new Error("Failed to login and store token"); @@ -100,7 +101,19 @@ export abstract class BaseCommand { } const apiClient = new ApiClient(CONFIG.API.BASE_URL); apiClient.setToken(tokenData.accessToken.token); - await apiClient.get(ENDPOINTS.USER.VERIFY); + const res = await apiClient.get<{ data: any }>(ENDPOINTS.USER.VERIFY); + // Cache the profile so the welcome banner can greet the user offline. + try { + const profile = res?.data; + if (profile) { + setContext({ + userName: profile.fullName, + userEmail: profile.email, + }); + } + } catch { + /* caching the name is best-effort; never fail auth for it */ + } return true; } catch (error: any) { throw new Error("Failed to authenticate. Invalid token."); @@ -111,7 +124,9 @@ export abstract class BaseCommand { try { const tokenStore = createDefaultTokenStore(); await tokenStore.remove("cli"); - logger.success("Logged out successfully"); + // Drop the cached profile so the greeting doesn't show a stale name. + setContext({ userName: undefined, userEmail: undefined }); + ui.status.ok("Logged out successfully"); return true; } catch (error) { throw new Error("Failed to logout"); diff --git a/src/command-line/command.registry.ts b/src/command-line/command.registry.ts index cca4a86..d65c6b1 100644 --- a/src/command-line/command.registry.ts +++ b/src/command-line/command.registry.ts @@ -1,7 +1,6 @@ import { BaseCommand } from "@/command-line/base.command"; import { requiresValidation } from "@/decorators/validate-user.decorator"; -import { logger } from "@/utils/logger"; -import { ui } from "@/utils/ui"; +import { ui } from "@/ui"; // Command Imports import "@/commands/help.command"; @@ -55,8 +54,8 @@ export class CommandRegistry { ): Promise { const command = this.getCommand(name); if (!command) { - logger.error(`Command not found "${name}"`); - logger.info('Use "stallion help" to list all available commands'); + ui.status.fail(`Command not found "${name}"`); + ui.hint('Use "stallion help" to list all available commands'); return; } diff --git a/src/commands/context.command.ts b/src/commands/context.command.ts index 34a18ad..419479b 100644 --- a/src/commands/context.command.ts +++ b/src/commands/context.command.ts @@ -1,7 +1,7 @@ import { BaseCommand } from "@command-line/base.command"; import { Command, CommandOption } from "@decorators/command.decorator"; import { clearContext, getContext, getContextPath } from "@/utils/context-store"; -import { ui } from "@/utils/ui"; +import { ui } from "@/ui"; const expectedOptions: CommandOption[] = [ { name: "clear", description: "Clear the saved context", required: false }, diff --git a/src/commands/generate-key-pair.command.ts b/src/commands/generate-key-pair.command.ts index a70571a..d095e12 100644 --- a/src/commands/generate-key-pair.command.ts +++ b/src/commands/generate-key-pair.command.ts @@ -4,8 +4,7 @@ import { BaseCommand } from "@command-line/base.command"; import path from "path"; import fs from "fs/promises"; import { generateKeyPairSync } from "crypto"; -import chalk from "chalk"; -import { logger } from "@/utils/logger"; +import { ui } from "@/ui"; @Command({ name: "generate-key-pair", @@ -32,73 +31,37 @@ export class GenerateKeyPairCommand extends BaseCommand { const publicKeyPath = path.join(secretKeysPath, "public-key.pem"); try { - - // Generate key pair using crypto module - const { privateKey, publicKey } = generateKeyPairSync('rsa', { + const { privateKey, publicKey } = generateKeyPairSync("rsa", { modulusLength: 2048, - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem' - } + publicKeyEncoding: { type: "spki", format: "pem" }, + privateKeyEncoding: { type: "pkcs8", format: "pem" }, }); - // Write keys to files await fs.writeFile(privateKeyPath, privateKey); await fs.writeFile(publicKeyPath, publicKey); - // Relative paths for display const relativePrivatePath = path.relative(this.contentRootPath, privateKeyPath); const relativePublicPath = path.relative(this.contentRootPath, publicKeyPath); const projectDir = path.basename(this.contentRootPath); - console.log( - "\n" + - chalk.green("🔐 Key Pair Generated Successfully!\n") - ); - console.log(chalk.cyan("📍 Location\n")); - console.log(` Public Key : ${chalk.yellow(`${projectDir}/${relativePublicPath}`)}\n`); - console.log(` Private Key: ${chalk.yellow(`${projectDir}/${relativePrivatePath}`)}\n`); - console.log(chalk.cyan("📆 Created At:"), chalk.white(new Date().toString()), "\n\n"); - console.log(chalk.red("🚫 Keep your private key secure. Do NOT share it.\n\n")); - - // Add important notes about key management - console.log(chalk.bold.yellow('⚠️ IMPORTANT NOTICE – READ CAREFULLY ⚠️\n')); - - console.log( - chalk.yellow('1.') + - ' ' + - chalk.white( - '🔐 It is ' + - chalk.bold('solely your responsibility') + - ' to securely store and manage your cryptographic signing keys. Losing them can critically disrupt your release pipeline.\n' - ) - ); + ui.status.ok("Key pair generated"); + ui.section("Location"); + ui.keyValue([ + ["Public key", `${projectDir}/${relativePublicPath}`], + ["Private key", `${projectDir}/${relativePrivatePath}`], + ["Created", new Date().toISOString()], + ]); - console.log( - chalk.yellow('2.') + - ' ' + - chalk.white( - '❌ ' + - chalk.bold('Do NOT regenerate keys') + - ' unless absolutely necessary. Regenerating keys may break compatibility with existing Stallion releases and hinder OTA delivery.\n' - ) - ); + ui.section("Important"); + ui.text("It is solely your responsibility to store and manage your signing keys — losing them can break your release pipeline."); + ui.text("Do NOT regenerate keys unless absolutely necessary; it may break compatibility with existing Stallion releases."); - console.log( - chalk.red.bold('\nStallion cannot recover or validate lost keys.\n') - ); + ui.blank(); + ui.status.warn("Keep your private key secure — never share or commit it."); + ui.status.fail("Stallion cannot recover or validate lost keys."); } catch (error: any) { - console.log( - "\n" + - chalk.red("🔐 Failed to generate keys!\n") - ); - if (error instanceof Error) { - console.log(chalk.red(`Error: ${error.message}\n`)); - } + ui.status.fail("Failed to generate keys"); + if (error instanceof Error) ui.hint(` ${error.message}`); } } -} \ No newline at end of file +} diff --git a/src/commands/generate-secret-key.command.ts b/src/commands/generate-secret-key.command.ts index 42214e9..2880753 100644 --- a/src/commands/generate-secret-key.command.ts +++ b/src/commands/generate-secret-key.command.ts @@ -1,11 +1,10 @@ import { Command } from "@/decorators/command.decorator"; -import { ValidateUser } from "@/decorators/validate-user.decorator"; import { getReactNativeVersion } from "@/utils/react-native-utils"; import { BaseCommand } from "@command-line/base.command"; import path from "path"; import fs from "fs/promises"; import { generateKeyPairSync } from "crypto"; -import chalk from "chalk"; +import { ui } from "@/ui"; @Command({ name: "generate-secret-key", @@ -32,89 +31,42 @@ export class GenerateSecretKeyCommand extends BaseCommand { const publicKeyPath = path.join(secretKeysPath, "public-key.pem"); try { - - // Generate key pair using crypto module - const { privateKey, publicKey } = generateKeyPairSync('rsa', { + const { privateKey, publicKey } = generateKeyPairSync("rsa", { modulusLength: 2048, - publicKeyEncoding: { - type: 'spki', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem' - } + publicKeyEncoding: { type: "spki", format: "pem" }, + privateKeyEncoding: { type: "pkcs8", format: "pem" }, }); - // Write keys to files await fs.writeFile(privateKeyPath, privateKey); await fs.writeFile(publicKeyPath, publicKey); - // Relative paths for display const relativePrivatePath = path.relative(this.contentRootPath, privateKeyPath); const relativePublicPath = path.relative(this.contentRootPath, publicKeyPath); const projectDir = path.basename(this.contentRootPath); - console.log( - "\n" + - chalk.green("🔐 Key Pair Generated Successfully!\n") - ); - console.log(chalk.cyan("📍 Location\n")); - console.log(` Public Key : ${chalk.yellow(`${projectDir}/${relativePublicPath}`)}\n`); - console.log(` Private Key: ${chalk.yellow(`${projectDir}/${relativePrivatePath}`)}\n`); - console.log(chalk.cyan("📆 Created At:"), chalk.white(new Date().toString()), "\n"); - console.log(chalk.red("🚫 Keep your private key secure. Do NOT share it.\n")); - - // Add important notes about key management - console.log(chalk.bold.yellow('⚠️ IMPORTANT NOTICE – READ CAREFULLY ⚠️\n')); - - console.log( - chalk.yellow('1.') + - ' ' + - chalk.white( - '🔐 It is ' + - chalk.bold('solely your responsibility') + - ' to securely store and manage your cryptographic signing keys. Losing them can critically disrupt your release pipeline.\n' - ) - ); + ui.status.ok("Key pair generated"); + ui.section("Location"); + ui.keyValue([ + ["Public key", `${projectDir}/${relativePublicPath}`], + ["Private key", `${projectDir}/${relativePrivatePath}`], + ["Created", new Date().toISOString()], + ]); - console.log( - chalk.yellow('2.') + - ' ' + - chalk.white( - '❌ ' + - chalk.bold('Do NOT regenerate keys') + - ' unless absolutely necessary. Regenerating keys may break compatibility with existing Stallion releases and hinder OTA delivery.\n' - ) - ); + ui.section("Important"); + ui.text("It is solely your responsibility to store and manage your signing keys — losing them can break your release pipeline."); + ui.text("Do NOT regenerate keys unless absolutely necessary; it may break compatibility with existing Stallion releases."); - console.log( - chalk.yellow('3.') + - ' ' + - chalk.white('🛠️ If keys are lost:\n') + - chalk.white( - ` - You must publish your next ${chalk.bold( - 'Stallion release without bundle signing.' - )}\n` + - ` - Then, ${chalk.bold( - 'regenerate and include the new keys' - )} in your next ${chalk.bold('Play Store release')}.\n` + - ` - After the Play Store update is live with the new keys, you may resume signed Stallion releases as normal.\n` - ) - ); + ui.section("If keys are lost"); + ui.text("Publish your next Stallion release without bundle signing."); + ui.text("Regenerate and include the new keys in your next Play Store release."); + ui.text("Once that Play Store update is live, resume signed Stallion releases as normal."); - console.log( - chalk.red.bold('\nStallion cannot recover or validate lost keys. ') + - chalk.white('Handle them as you would production secrets—securely and irreversibly.\n') - ); + ui.blank(); + ui.status.warn("Keep your private key secure — never share or commit it."); + ui.status.fail("Stallion cannot recover or validate lost keys — treat them as production secrets."); } catch (error: any) { - console.log( - "\n" + - chalk.red("🔐 Failed to generate keys!\n") - ); - if (error instanceof Error) { - console.log(chalk.red(`Error: ${error.message}\n`)); - } + ui.status.fail("Failed to generate keys"); + if (error instanceof Error) ui.hint(` ${error.message}`); } } -} \ No newline at end of file +} diff --git a/src/commands/help.command.ts b/src/commands/help.command.ts index b0664a9..c751701 100644 --- a/src/commands/help.command.ts +++ b/src/commands/help.command.ts @@ -1,6 +1,7 @@ import { BaseCommand } from "@command-line/base.command"; import { Command, getCommands } from "@decorators/command.decorator"; -import { logger } from "@utils/logger"; +import { printTable } from "@/ui"; +import { ui } from "@/ui"; @Command({ name: "help", @@ -9,13 +10,24 @@ import { logger } from "@utils/logger"; }) export class HelpCommand extends BaseCommand { async execute(): Promise { - const commands = getCommands(); + const commands = [...getCommands().entries()].map(([name, options]) => ({ + name, + alias: options.alias ?? "-", + description: options.description, + })); - logger.title("\nAvailable Commands"); - logger.subtitle("==================\n"); + ui.section("Commands"); + printTable( + commands, + [ + { header: "COMMAND", value: (c) => c.name }, + { header: "ALIAS", value: (c) => c.alias }, + { header: "DESCRIPTION", value: (c) => c.description }, + ], + { indent: ui.INDENT } + ); - commands.forEach((options, name) => { - logger.command(name, options.description, options.alias); - }); + ui.blank(); + ui.hint(' Run "stallion --help" for command-specific options.'); } } diff --git a/src/commands/list-buckets.command.ts b/src/commands/list-buckets.command.ts index 84dc5f4..46ce830 100644 --- a/src/commands/list-buckets.command.ts +++ b/src/commands/list-buckets.command.ts @@ -4,16 +4,18 @@ import { ValidateUser } from "@decorators/validate-user.decorator"; import { progress } from "@/utils/progress"; import { ENDPOINTS } from "@/api/endpoints"; import { + createCiApiClient, createUserApiClient, resolveOrgContext, resolveProjectId, } from "@/api/user-client"; -import { printTable } from "@/utils/table"; -import { ui } from "@/utils/ui"; +import { ui } from "@/ui"; +import { printBoard, renderValue } from "@/ui"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, - { name: "project-id", description: "Project id (prompts if omitted)", required: false }, + { name: "project-id", description: "Project id (prompts if omitted; required with --ci-token)", required: false }, + { name: "ci-token", description: "CI token (non-interactive; requires --project-id)", required: false }, { name: "json", description: "Output raw JSON", required: false }, ]; @@ -27,24 +29,48 @@ const expectedOptions: CommandOption[] = [ export class ListBucketsCommand extends BaseCommand { async execute(options: Record): Promise { const json = Boolean(options.json); + const buckets = await this.fetchBuckets(options); + + if (json) { + console.log(JSON.stringify(buckets, null, 2)); + return; + } + ui.section("Buckets"); + if (!buckets.length) { + ui.hint(" No buckets."); + return; + } + printBoard( + buckets, + [ + { header: "NAME", render: (b) => renderValue(b.name) }, + { header: "BUCKET ID", render: (b) => renderValue(b.id ?? b._id) }, + { header: "UPDATED", render: (b) => renderValue(b.updatedAt) }, + ], + { indent: ui.INDENT, spaced: true } + ); + } + + private async fetchBuckets(options: Record): Promise { + if (options.ciToken) { + if (!options.projectId) { + throw new Error("--project-id is required with --ci-token"); + } + const client = createCiApiClient(options.ciToken); + const res = await progress("Fetching buckets", () => + client.post<{ data: any[] }>(ENDPOINTS.BUCKET.CI_LIST, { + projectId: options.projectId, + }) + ); + return res?.data ?? []; + } + const { orgId, region } = await resolveOrgContext(options.orgId); const projectId = await resolveProjectId(orgId, options.projectId); const client = await createUserApiClient(region); - const res = await progress("Fetching buckets", () => client.post<{ data: any[] }>(ENDPOINTS.BUCKET.LIST, { projectId }) ); - const buckets = res?.data ?? []; - - if (!json) ui.section("Buckets"); - printTable( - buckets, - [ - { header: "NAME", value: (b) => b.name }, - { header: "BUCKET ID", value: (b) => b.id ?? b._id }, - { header: "UPDATED", value: (b) => b.updatedAt }, - ], - { json, indent: ui.INDENT } - ); + return res?.data ?? []; } } diff --git a/src/commands/list-bundles.command.ts b/src/commands/list-bundles.command.ts index 14eb88a..7ffb0ca 100644 --- a/src/commands/list-bundles.command.ts +++ b/src/commands/list-bundles.command.ts @@ -2,34 +2,37 @@ import { BaseCommand } from "@command-line/base.command"; import { Command, CommandOption } from "@decorators/command.decorator"; import { ValidateUser } from "@decorators/validate-user.decorator"; import { progress } from "@/utils/progress"; -import { ui } from "@/utils/ui"; +import { ui } from "@/ui"; import { ENDPOINTS } from "@/api/endpoints"; import { + createCiApiClient, createUserApiClient, resolveBucketId, resolveOrgContext, resolveProjectId, } from "@/api/user-client"; -import { Column, printTable } from "@/utils/table"; +import { BoardColumn, printBoard, renderValue } from "@/ui"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, - { name: "project-id", description: "Project id (prompts if omitted)", required: false }, - { name: "bucket-id", description: "Bucket id (prompts if omitted)", required: false }, - { name: "platform", description: "Filter by platform (android|ios); enables paging", required: false }, - { name: "page-size", description: "Page size when filtering by platform (default 20)", required: false }, - { name: "page", description: "Page number when filtering by platform (default 1)", required: false }, + { name: "project-id", description: "Project id (prompts if omitted; required with --ci-token)", required: false }, + { name: "bucket-id", description: "Bucket id (prompts if omitted; required with --ci-token)", required: false }, + { name: "platform", description: "Filter by platform (android|ios)", required: false }, + { name: "ci-token", description: "CI token (non-interactive; requires --project-id and --bucket-id)", required: false }, { name: "json", description: "Output raw JSON", required: false }, ]; -const bundleColumns: Column[] = [ - { header: "VERSION", value: (b) => b.version }, - { header: "PLATFORM", value: (b) => b.platform }, - { header: "PROMOTED", value: (b) => Boolean(b.isPromoted) }, - { header: "AUTHOR", value: (b) => b.author?.email ?? b.author?.fullName }, - { header: "SIZE", value: (b) => b.size }, - { header: "HASH", value: (b) => b.sha256Checksum }, - { header: "CREATED", value: (b) => b.createdAt }, +const bundleColumns: BoardColumn[] = [ + { header: "VERSION", render: (b) => renderValue(b.version) }, + { header: "PLATFORM", render: (b) => renderValue(b.platform) }, + { header: "PROMOTED", render: (b) => renderValue(Boolean(b.isPromoted)) }, + { + header: "AUTHOR", + render: (b) => renderValue(b.author?.email ?? b.author?.fullName), + }, + { header: "SIZE", render: (b) => renderValue(b.size), align: "right" }, + { header: "HASH", render: (b) => renderValue(b.sha256Checksum) }, + { header: "CREATED", render: (b) => renderValue(b.createdAt) }, ]; @Command({ @@ -42,51 +45,54 @@ const bundleColumns: Column[] = [ export class ListBundlesCommand extends BaseCommand { async execute(options: Record): Promise { const json = Boolean(options.json); - const { orgId, region } = await resolveOrgContext(options.orgId); - const projectId = await resolveProjectId(orgId, options.projectId); - const bucketId = await resolveBucketId(region, projectId, options.bucketId); - const client = await createUserApiClient(region); + let bundles = await this.fetchBundles(options); if (options.platform) { - const pageSize = options.pageSize ? Number(options.pageSize) : 20; - const pageNumber = options.page ? Number(options.page) : 1; + const platform = String(options.platform).toLowerCase(); + bundles = bundles.filter( + (b: any) => String(b.platform).toLowerCase() === platform + ); + } + + if (json) { + console.log(JSON.stringify(bundles, null, 2)); + return; + } + ui.section(options.platform ? `Bundles · ${options.platform}` : "Bundles"); + if (!bundles.length) { + ui.hint(" No bundles."); + return; + } + printBoard(bundles, bundleColumns, { indent: ui.INDENT, spaced: true }); + } + + private async fetchBundles(options: Record): Promise { + if (options.ciToken) { + if (!options.projectId || !options.bucketId) { + throw new Error( + "--project-id and --bucket-id are required with --ci-token" + ); + } + const client = createCiApiClient(options.ciToken); const res = await progress("Fetching bundles", () => - client.post<{ data: any }>(ENDPOINTS.BUNDLE.ADVANCE_LISTING, { - projectId, - bucketId, - platform: options.platform, - pageSize, - pageNumber, + client.post<{ data: any[] }>(ENDPOINTS.BUNDLE.CI_LIST, { + projectId: options.projectId, + bucketId: options.bucketId, }) ); - const payload = res?.data ?? {}; - if (json) { - console.log(JSON.stringify(payload, null, 2)); - return; - } - ui.section(`Bundles · ${options.platform}`); - printTable(payload.paginatedData ?? [], bundleColumns, { - indent: ui.INDENT, - }); - const current = payload.currentPageNumber ?? pageNumber; - const total = payload.totalPages; - ui.hint(total ? ` Page ${current} of ${total}` : ` Page ${current}`); - return; + return res?.data ?? []; } + const { orgId, region } = await resolveOrgContext(options.orgId); + const projectId = await resolveProjectId(orgId, options.projectId); + const bucketId = await resolveBucketId(region, projectId, options.bucketId); + const client = await createUserApiClient(region); const res = await progress("Fetching bundles", () => - client.post<{ data: any }>(ENDPOINTS.BUNDLE.LIST, { projectId, bucketId }) + client.post<{ data: any[] }>(ENDPOINTS.BUNDLE.LIST, { + projectId, + bucketId, + }) ); - const payload = res?.data ?? {}; - if (json) { - console.log(JSON.stringify(payload, null, 2)); - return; - } - const bundles = [ - ...(payload.androidBundles ?? []), - ...(payload.iosBundles ?? []), - ]; - ui.section("Bundles"); - printTable(bundles, bundleColumns, { indent: ui.INDENT }); + return res?.data ?? []; } } diff --git a/src/commands/list-projects.command.ts b/src/commands/list-projects.command.ts index b30da25..582e24c 100644 --- a/src/commands/list-projects.command.ts +++ b/src/commands/list-projects.command.ts @@ -4,8 +4,8 @@ import { ValidateUser } from "@decorators/validate-user.decorator"; import { progress } from "@/utils/progress"; import { ENDPOINTS } from "@/api/endpoints"; import { createUserApiClient, resolveOrgContext } from "@/api/user-client"; -import { printTable } from "@/utils/table"; -import { ui } from "@/utils/ui"; +import { ui } from "@/ui"; +import { getContext } from "@/utils/context-store"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, @@ -30,17 +30,28 @@ export class ListProjectsCommand extends BaseCommand { ); const projects = res?.data ?? []; - if (!json) ui.section("Projects"); - printTable( - projects, - [ - { header: "NAME", value: (p) => p.name }, - { header: "PROJECT ID", value: (p) => p.id ?? p._id }, - { header: "ANDROID", value: (p) => Boolean(p.androidEnabled) }, - { header: "IOS", value: (p) => Boolean(p.iosEnabled) }, - { header: "PATCH", value: (p) => Boolean(p.isPatchEnabled) }, - ], - { json, indent: ui.INDENT } + if (json) { + console.log(JSON.stringify(projects, null, 2)); + return; + } + + const ctx = getContext(); + ui.section("projects"); + ui.numbered( + projects.map((p: any) => { + const platforms = + [p.androidEnabled ? "android" : null, p.iosEnabled ? "ios" : null] + .filter(Boolean) + .join("·") || "—"; + return { + label: p.name, + meta: platforms, + current: ctx.projectId === String(p.id ?? p._id), + }; + }), + { indent: ui.INDENT } ); + ui.blank(); + ui.hint(` ${projects.length} project${projects.length === 1 ? "" : "s"}`); } } diff --git a/src/commands/list-releases.command.ts b/src/commands/list-releases.command.ts index 6a54ce5..976812a 100644 --- a/src/commands/list-releases.command.ts +++ b/src/commands/list-releases.command.ts @@ -1,24 +1,25 @@ import { BaseCommand } from "@command-line/base.command"; import { Command, CommandOption } from "@decorators/command.decorator"; import { ValidateUser } from "@decorators/validate-user.decorator"; -import { logger } from "@utils/logger"; import { progress } from "@/utils/progress"; import { promptSelect } from "@/utils/prompt"; import { ENDPOINTS } from "@/api/endpoints"; import { + createCiApiClient, createUserApiClient, resolveOrgContext, resolvePlatform, resolveProjectId, } from "@/api/user-client"; -import { printTable } from "@/utils/table"; -import { ui } from "@/utils/ui"; +import { ui } from "@/ui"; +import { bar, glyph, printBoard, renderValue, theme } from "@/ui"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, - { name: "project-id", description: "Project id (prompts if omitted)", required: false }, + { name: "project-id", description: "Project id (prompts if omitted; required with --ci-token)", required: false }, { name: "platform", description: "Platform (android|ios); prompts if omitted", required: false }, - { name: "app-version", description: "App version; prompts if omitted", required: false }, + { name: "app-version", description: "App version; prompts if omitted (required with --ci-token)", required: false }, + { name: "ci-token", description: "CI token (non-interactive; requires --project-id, --platform, --app-version)", required: false }, { name: "json", description: "Output raw JSON", required: false }, ]; @@ -32,6 +33,76 @@ const expectedOptions: CommandOption[] = [ export class ListReleasesCommand extends BaseCommand { async execute(options: Record): Promise { const json = Boolean(options.json); + const { appVersion, releases } = await this.fetchReleases(options); + + if (json) { + console.log(JSON.stringify(releases, null, 2)); + return; + } + + ui.section(`releases · v${appVersion}`); + if (!releases.length) { + ui.hint(" No releases."); + return; + } + + printBoard( + releases, + [ + { + header: "VERSION", + render: (r) => { + const v = `v${r.bundleVersion ?? "-"}`; + return r === releases[0] ? theme.accent(v) : v; + }, + }, + { + header: "ROLLOUT", + render: (r) => { + const pct = r.isRolledBack ? 0 : 100; + return `${bar(pct)} ${String(pct).padStart(3)}%`; + }, + }, + { + header: "STATUS", + render: (r) => { + if (r.isRolledBack) + return `${theme.danger(glyph.cross)} rolled back`; + if (r.isPaused) return `${theme.warn(glyph.warn)} paused`; + return `${theme.ok(glyph.tick)} live`; + }, + }, + { header: "RELEASE ID", render: (r) => renderValue(r.id) }, + ], + { indent: ui.INDENT, spaced: true } + ); + ui.blank(); + ui.hint( + ` ${releases.length} release${releases.length === 1 ? "" : "s"} · v${appVersion}` + ); + } + + private async fetchReleases( + options: Record + ): Promise<{ appVersion: string; releases: any[] }> { + if (options.ciToken) { + const { projectId, platform, appVersion } = options; + if (!projectId || !platform || !appVersion) { + throw new Error( + "--project-id, --platform and --app-version are required with --ci-token" + ); + } + const client = createCiApiClient(options.ciToken); + const res = await progress("Fetching releases", () => + client.post<{ data: any[] }>(ENDPOINTS.PROMOTED.CI_LISTING, { + projectId, + platform, + appVersion, + }) + ); + return { appVersion, releases: res?.data ?? [] }; + } + const { orgId, region } = await resolveOrgContext(options.orgId); const projectId = await resolveProjectId(orgId, options.projectId); const platform = await resolvePlatform(options.platform); @@ -47,8 +118,8 @@ export class ListReleasesCommand extends BaseCommand { ); const versions = verRes?.data ?? []; if (!versions.length) { - logger.info("No app versions found for this platform."); - return; + ui.hint("No app versions found for this platform."); + return { appVersion: "-", releases: [] }; } appVersion = await promptSelect( "Select an app version", @@ -63,27 +134,12 @@ export class ListReleasesCommand extends BaseCommand { } const res = await progress("Fetching releases", () => - client.post<{ data: any }>(ENDPOINTS.PROMOTED.LISTING, { + client.post<{ data: any[] }>(ENDPOINTS.PROMOTED.LISTING, { projectId, platform, appVersion, }) ); - const releases = res?.data?.paginatedData ?? []; - - if (!json) ui.section(`Releases · v${appVersion}`); - printTable( - releases, - [ - { header: "APP VERSION", value: (r) => r.appVersion }, - { header: "BUNDLE VERSION", value: (r) => r.bundleVersion }, - { header: "RELEASE ID", value: (r) => r._id }, - { header: "AUTHOR", value: (r) => r.user?.email ?? r.user?.fullName }, - { header: "PAUSED", value: (r) => Boolean(r.isPaused) }, - { header: "ROLLED BACK", value: (r) => Boolean(r.isRolledBack) }, - { header: "CREATED", value: (r) => r.createdAt }, - ], - { json, indent: ui.INDENT } - ); + return { appVersion, releases: res?.data ?? [] }; } } diff --git a/src/commands/publish-bundle.command.ts b/src/commands/publish-bundle.command.ts index 4b1d394..bb11f07 100644 --- a/src/commands/publish-bundle.command.ts +++ b/src/commands/publish-bundle.command.ts @@ -1,7 +1,7 @@ import { BaseCommand } from "@command-line/base.command"; import { Command, CommandOption } from "@decorators/command.decorator"; import { ValidateUser } from "@decorators/validate-user.decorator"; -import { logger } from "@utils/logger"; +import { ui } from "@/ui"; import path from "path"; import fs from "fs/promises"; import { @@ -21,6 +21,7 @@ import { createZip } from "@/utils/archive"; import { keepArtifacts as saveArtifacts } from "@/utils/copy"; import { getApiBaseUrl } from "@/utils/common"; import { resolveRegion } from "@/utils/region"; +import { silenceStdout } from "@/utils/stdout"; const expectedOptions: CommandOption[] = [ { @@ -82,7 +83,13 @@ const expectedOptions: CommandOption[] = [ name: "custom-bundle-path", description: "The path to the custom bundle to upload", required: false, - } + }, + { + name: "json", + description: + "Print only a JSON result ({version, hash, platform, uploadPath}) on stdout for scripting", + required: false, + }, ]; @Command({ @@ -101,7 +108,7 @@ export class PublishBundleCommand extends BaseCommand { } async execute(options: Record): Promise { - logger.info("Starting publish-bundle command"); + const json = Boolean(options.json); if (!this.validateOptions(options, expectedOptions)) { return; @@ -111,6 +118,11 @@ export class PublishBundleCommand extends BaseCommand { throw new Error("No react native project found in current directory"); } + // In JSON mode, route all incidental output (logs, spinners, the RN bundler's + // own stdout) to stderr so stdout carries ONLY the final JSON result. Left in + // place on error: the failure goes to stderr and stdout stays empty. + const restoreStdout = json ? silenceStdout() : undefined; + let { uploadPath, platform, @@ -202,7 +214,7 @@ export class PublishBundleCommand extends BaseCommand { ); const zipPath = path.resolve(contentTempRootPath, "build.zip"); const stats = await fs.stat(zipPath); - logger.info(`Bundle size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`); + ui.hint(` Bundle size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`); const tokenStore = createDefaultTokenStore(); const tokenData = await tokenStore.get("cli"); @@ -215,10 +227,15 @@ export class PublishBundleCommand extends BaseCommand { }); const client = new ApiClient(getApiBaseUrl(region)); - const hash = await progress( + // One spinner covers the whole publish: upload + waiting for the bundle to + // register. Registration is async (S3 event → update-uploaded-data), so we + // poll until it's queryable, otherwise a following release-bundle can race + // ahead into "bundle not found". Best-effort — it never fails the publish. + let registered = true; + const { hash, version } = await progress( chalk.white("Publishing bundle"), - (updateProgress) => - this.uploadBundle( + async (updateProgress) => { + const result = await this.uploadBundle( client, zipPath, uploadPath, @@ -226,10 +243,70 @@ export class PublishBundleCommand extends BaseCommand { releaseNote, ciToken, updateProgress - ), + ); + if (result.projectId) { + registered = await this.waitForBundle(client, { + ciToken, + projectId: result.projectId, + hash: result.hash, + }); + } + return result; + } ); - logger.success("Success!, Published new version"); - logger.info(`Published bundle hash: ${hash}`); + + if (!registered) { + ui.status.warn( + "Bundle is still registering — if release-bundle can't find it yet, retry in a moment." + ); + } + + if (json) { + restoreStdout?.(); + console.log(JSON.stringify({ version, hash, platform, uploadPath })); + } else { + ui.status.ok(`Published version ${version}`); + ui.keyValue([ + ["Hash", hash], + ["Platform", platform], + ["Upload Path", uploadPath], + ]); + } + } + + /** + * Poll the by-hash endpoint until the just-uploaded bundle is registered. + * Best-effort: returns true once confirmed, false if it doesn't register + * within the window. Never throws — a slow (or older) backend must not fail a + * publish whose upload already succeeded. Uses the CI-token route when a + * ci-token was supplied, otherwise the user route (client carries the token). + */ + private async waitForBundle( + client: ApiClient, + opts: { ciToken?: string; projectId: string; hash: string } + ): Promise { + const { ciToken, projectId, hash } = opts; + const endpoint = ciToken + ? ENDPOINTS.BUNDLE.CI_BY_HASH + : ENDPOINTS.BUNDLE.BY_HASH; + const config = ciToken ? { headers: { "x-ci-token": ciToken } } : undefined; + const intervalMs = 2000; + const deadline = Date.now() + 60_000; + + while (Date.now() < deadline) { + try { + const res = await client.post<{ data: { exists: boolean } }>( + endpoint, + { projectId, checksum: hash }, + config + ); + if (res?.data?.exists) return true; + } catch { + // Endpoint missing (older backend) or transient — keep trying to the deadline. + } + await new Promise((resolve) => setTimeout(resolve, intervalMs)); + } + return false; } private async uploadBundle( @@ -273,9 +350,19 @@ export class PublishBundleCommand extends BaseCommand { if (!url) { throw new Error("Internal Error: invalid signed url"); } + // The server returns the version it assigned to this upload + the + // project it belongs to (used to poll for registration afterwards). + const rawVersion = signedUrlResp?.meta?.version; + const version = + rawVersion != null && !isNaN(Number(rawVersion)) + ? Number(rawVersion) + : null; + const projectId = signedUrlResp?.meta?.projectId + ? String(signedUrlResp.meta.projectId) + : null; await client.putWithProgress(url, filePath, "application/zip", onProgress); - return hash; + return { hash, version, projectId }; } catch (e: any) { if (e.toString().includes("SignatureDoesNotMatch")) { throw "Error uploading bundle. Signature does not match."; diff --git a/src/commands/release-bundle.command.ts b/src/commands/release-bundle.command.ts index 2b12b9d..6acc132 100644 --- a/src/commands/release-bundle.command.ts +++ b/src/commands/release-bundle.command.ts @@ -1,13 +1,14 @@ import { BaseCommand } from "@command-line/base.command"; import { Command, CommandOption } from "@decorators/command.decorator"; import { ValidateUser } from "@decorators/validate-user.decorator"; -import { logger } from "@utils/logger"; +import { ui } from "@/ui"; import chalk from "chalk"; import { progress } from "@/utils/progress"; import { ApiClient } from "@/api/api-client"; import { ENDPOINTS } from "@/api/endpoints"; import { getApiBaseUrl } from "@/utils/common"; import { parseTokenRegion } from "@/utils/region"; +import { silenceStdout } from "@/utils/stdout"; const expectedOptions: CommandOption[] = [ { @@ -45,6 +46,12 @@ const expectedOptions: CommandOption[] = [ description: "To mark this release as paused", required: false, }, + { + name: "json", + description: + "Print only a JSON result ({id, version, appVersion, hash, projectId}) on stdout for scripting", + required: false, + }, ]; @Command({ @@ -60,7 +67,7 @@ export class ReleaseBundleCommand extends BaseCommand { } async execute(options: Record): Promise { - logger.info("Starting release-bundle command"); + const json = Boolean(options.json); if (!this.validateOptions(options, expectedOptions)) { return; } @@ -87,12 +94,27 @@ export class ReleaseBundleCommand extends BaseCommand { const region = parseTokenRegion(ciToken) ?? "ap"; const client = new ApiClient(getApiBaseUrl(region)); - await progress( - chalk.cyanBright("Releasing bundle"), - () => this.releaseBundle(client, data, ciToken) + // In JSON mode, keep stdout for the result only; diagnostics go to stderr. + const restoreStdout = json ? silenceStdout() : undefined; + const resp = await progress(chalk.cyanBright("Releasing bundle"), () => + this.releaseBundle(client, data, ciToken) ); - logger.success("Bundle released successfully!"); + const releaseId = resp?.id ?? null; + const version = resp?.version ?? null; + + if (json) { + restoreStdout?.(); + console.log( + JSON.stringify({ id: releaseId, version, appVersion, hash, projectId }) + ); + } else { + ui.status.ok("Bundle released."); + ui.keyValue([ + ["version", version], + ["release id", releaseId], + ]); + } } private async releaseBundle(client: ApiClient, data: any, ciToken: string) { diff --git a/src/commands/release-info.command.ts b/src/commands/release-info.command.ts index d69d37d..94bd253 100644 --- a/src/commands/release-info.command.ts +++ b/src/commands/release-info.command.ts @@ -1,26 +1,27 @@ import { BaseCommand } from "@command-line/base.command"; import { Command, CommandOption } from "@decorators/command.decorator"; import { ValidateUser } from "@decorators/validate-user.decorator"; -import { logger } from "@utils/logger"; import { progress } from "@/utils/progress"; import { promptSelect } from "@/utils/prompt"; import { ENDPOINTS } from "@/api/endpoints"; import { + createCiApiClient, createUserApiClient, resolveOrgContext, resolvePlatform, resolveProjectId, } from "@/api/user-client"; -import { printBoxes, printTable } from "@/utils/table"; -import { ui } from "@/utils/ui"; +import { printBoard } from "@/ui"; +import { ui } from "@/ui"; import chalk from "chalk"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, - { name: "project-id", description: "Project id (prompts if omitted)", required: false }, + { name: "project-id", description: "Project id (prompts if omitted; required with --ci-token)", required: false }, { name: "platform", description: "Platform (android|ios); prompts if omitted", required: false }, - { name: "app-version", description: "App version; prompts if omitted", required: false }, - { name: "promoted-id", description: "Release id (from list-releases); prompts if omitted", required: false }, + { name: "app-version", description: "App version; prompts if omitted (required with --ci-token)", required: false }, + { name: "promoted-id", description: "Release id from list-releases; prompts if omitted (required with --ci-token)", required: false }, + { name: "ci-token", description: "CI token (non-interactive; requires --project-id, --platform, --app-version, --promoted-id)", required: false }, { name: "json", description: "Output raw JSON", required: false }, ]; @@ -34,6 +35,97 @@ const expectedOptions: CommandOption[] = [ export class ReleaseInfoCommand extends BaseCommand { async execute(options: Record): Promise { const json = Boolean(options.json); + const detail = await this.fetchDetail(options); + if (!detail) return; + + // In JSON mode emit the raw detail once and skip the formatted sections. + if (json) { + console.log(JSON.stringify(detail, null, 2)); + return; + } + + // Console — pipeline stages anchored by an inline rollout meter (3f). + const events = detail.eventCount ?? {}; + const hashShort = detail.publishedBundle?.sha256Checksum + ? String(detail.publishedBundle.sha256Checksum).slice(0, 12) + : null; + const rollout = + detail.rolloutPercent != null ? Number(detail.rolloutPercent) : 0; + const users = Number(detail.totalUsers ?? 0); + const live = !detail.isRolledBack && !detail.isPaused; + const stage = live ? "active" : "pending"; + const fmt = (n: unknown) => Number(n ?? 0).toLocaleString("en-US"); + + ui.pipeline( + [ + { + status: "done", + label: "build", + value: `v${detail.bundleVersion ?? "-"}${hashShort ? ` · ${hashShort}` : ""}`, + }, + { status: "done", label: "platform", value: detail.platform }, + { + status: stage, + label: "rollout", + value: `${ui.bar(rollout)} ${rollout}%`, + }, + { status: stage, label: "total users", value: fmt(users) }, + ], + { spaced: true } + ); + + ui.section("release note"); + ui.text(detail.releaseNote ? String(detail.releaseNote) : chalk.dim("-")); + + ui.section("bundle hash"); + ui.text( + detail.publishedBundle?.sha256Checksum + ? String(detail.publishedBundle.sha256Checksum) + : chalk.dim("-") + ); + + ui.blank(); + if (detail.isRolledBack) ui.status.fail("rolled back · review required"); + else if (detail.isPaused) ui.status.warn("paused · rollout halted"); + else ui.status.ok("healthy · no rollback configured"); + + // Adoption Metrics — raw counts, shown last. + ui.section("Adoption Metrics"); + printBoard( + [detail], + [ + { header: "DOWNLOADS", render: () => fmt(events.DOWNLOAD_COMPLETE_PROD), align: "right" }, + { header: "INSTALLS", render: () => fmt(events.INSTALLED_PROD), align: "right" }, + { header: "ROLLBACKS", render: () => fmt(events.AUTO_ROLLED_BACK_PROD), align: "right" }, + { header: "USERS", render: () => fmt(detail.totalUsers), align: "right" }, + ], + { indent: ui.INDENT } + ); + } + + /** Resolve the release detail via the CI-token or interactive user path. */ + private async fetchDetail( + options: Record + ): Promise { + if (options.ciToken) { + const { projectId, platform, appVersion, promotedId } = options; + if (!projectId || !platform || !appVersion || !promotedId) { + throw new Error( + "--project-id, --platform, --app-version and --promoted-id are required with --ci-token" + ); + } + const client = createCiApiClient(options.ciToken); + const res = await progress("Fetching release detail", () => + client.post<{ data: any }>(ENDPOINTS.PROMOTED.CI_DETAIL, { + projectId, + platform, + appVersion, + promotedId, + }) + ); + return res?.data ?? null; + } + const { orgId, region } = await resolveOrgContext(options.orgId); const projectId = await resolveProjectId(orgId, options.projectId); const platform = await resolvePlatform(options.platform); @@ -49,8 +141,8 @@ export class ReleaseInfoCommand extends BaseCommand { ); const versions = verRes?.data ?? []; if (!versions.length) { - logger.info("No app versions found for this platform."); - return; + ui.hint("No app versions found for this platform."); + return null; } appVersion = await promptSelect( "Select an app version", @@ -67,16 +159,16 @@ export class ReleaseInfoCommand extends BaseCommand { let promotedId = options.promotedId; if (!promotedId) { const relRes = await progress("Fetching releases", () => - client.post<{ data: any }>(ENDPOINTS.PROMOTED.LISTING, { + client.post<{ data: any[] }>(ENDPOINTS.PROMOTED.LISTING, { projectId, platform, appVersion, }) ); - const releases = relRes?.data?.paginatedData ?? []; + const releases = relRes?.data ?? []; if (!releases.length) { - logger.info("No releases found for this app version."); - return; + ui.hint("No releases found for this app version."); + return null; } promotedId = await promptSelect( "Select a release", @@ -92,8 +184,8 @@ export class ReleaseInfoCommand extends BaseCommand { : ""; return { name: `v${r.bundleVersion}`, - value: String(r._id), - description: `${created}${status ? ` ${status}` : ""} id: ${r._id}`, + value: String(r.id), + description: `${created}${status ? ` ${status}` : ""} id: ${r.id}`, }; }) ); @@ -107,82 +199,6 @@ export class ReleaseInfoCommand extends BaseCommand { promotedId, }) ); - const detail = res?.data ?? {}; - - // In JSON mode emit the raw detail once and skip the formatted sections. - if (json) { - console.log(JSON.stringify(detail, null, 2)); - return; - } - - // Summary + Details — two boxes side by side at the top. - ui.section("Release"); - printBoxes([ - { - obj: detail, - title: "App Version Summary", - description: - "Key identifiers and rollout status for the selected bundle.", - fields: [ - { label: "App Version", value: (d) => d.appVersion }, - { label: "Platform", value: (d) => d.platform }, - { - label: "Rollout", - value: (d) => - d.rolloutPercent != null ? `${d.rolloutPercent}%` : null, - }, - { label: "Promoted At", value: (d) => d.createdAt }, - { - label: "Promoted By", - value: (d) => d.user?.email ?? d.user?.fullName, - }, - ], - }, - { - obj: detail, - title: "Details", - description: "Bundle version and release flags.", - fields: [ - { label: "Bundle Version", value: (d) => d.bundleVersion }, - { label: "Mandatory", value: (d) => Boolean(d.isMandatory) }, - { label: "Paused", value: (d) => Boolean(d.isPaused) }, - { label: "Rolled Back", value: (d) => Boolean(d.isRolledBack) }, - ], - }, - ], { indent: ui.INDENT }); - - const events = detail.eventCount ?? {}; - - // Events — counts shown as their own compact table. - ui.section("Events"); - printTable( - [ - { - download: events.DOWNLOAD_COMPLETE_PROD, - install: events.INSTALLED_PROD, - rollback: events.AUTO_ROLLED_BACK_PROD, - totalUsers: detail.totalUsers, - }, - ], - [ - { header: "DOWNLOADS", value: (r) => r.download }, - { header: "INSTALLS", value: (r) => r.install }, - { header: "ROLLBACKS", value: (r) => r.rollback }, - { header: "TOTAL USERS", value: (r) => r.totalUsers }, - ], - { indent: ui.INDENT } - ); - - // Release note — free text, kept out of the tables. - ui.section("Release Note"); - ui.text(detail.releaseNote ? String(detail.releaseNote) : chalk.dim("-")); - - // Bundle hash — shown as a full-width line (too long to box neatly). - ui.section("Bundle Hash"); - ui.text( - detail.publishedBundle?.sha256Checksum - ? String(detail.publishedBundle.sha256Checksum) - : chalk.dim("-") - ); + return res?.data ?? null; } } diff --git a/src/commands/update-release.command.ts b/src/commands/update-release.command.ts index 1be6dcc..df3e466 100644 --- a/src/commands/update-release.command.ts +++ b/src/commands/update-release.command.ts @@ -1,13 +1,14 @@ import { BaseCommand } from "@command-line/base.command"; import { Command, CommandOption } from "@decorators/command.decorator"; import { ValidateUser } from "@decorators/validate-user.decorator"; -import { logger } from "@utils/logger"; +import { ui } from "@/ui"; import chalk from "chalk"; import { progress } from "@/utils/progress"; import { ApiClient } from "@/api/api-client"; import { ENDPOINTS } from "@/api/endpoints"; import { getApiBaseUrl } from "@/utils/common"; import { parseTokenRegion } from "@/utils/region"; +import { silenceStdout } from "@/utils/stdout"; const expectedOptions: CommandOption[] = [ { @@ -50,6 +51,12 @@ const expectedOptions: CommandOption[] = [ description: "The CI token generated from the stallion dashboard", required: true, }, + { + name: "json", + description: + "Print only a JSON result of the updated fields on stdout for scripting", + required: false, + }, ]; @Command({ @@ -65,7 +72,7 @@ export class UpdateReleaseCommand extends BaseCommand { } async execute(options: Record): Promise { - logger.info("Starting update-release command"); + const json = Boolean(options.json); if (!this.validateOptions(options, expectedOptions)) { return; } @@ -94,13 +101,33 @@ export class UpdateReleaseCommand extends BaseCommand { const region = parseTokenRegion(ciToken) ?? "ap"; const client = new ApiClient(getApiBaseUrl(region)); + // In JSON mode, keep stdout for the result only; diagnostics go to stderr. + const restoreStdout = json ? silenceStdout() : undefined; + const toBool = (v: unknown) => + v === undefined ? undefined : v === true || v === "true"; + try { await progress(chalk.white("Updating release"), () => - this.updateRelease(client, data, ciToken), + this.updateRelease(client, data, ciToken) ); - logger.success("Release updated successfully!"); + + if (json) { + restoreStdout?.(); + console.log( + JSON.stringify({ + projectId, + hash, + rolloutPercent: data.rolloutPercent, + isMandatory: toBool(isMandatory), + isPaused: toBool(isPaused), + isRolledBack: toBool(isRolledBack), + }) + ); + } else { + ui.status.ok("Release updated successfully!"); + } } catch (error) { - logger.error("Failed to update release"); + ui.status.fail("Failed to update release"); throw error; } } diff --git a/src/commands/use.command.ts b/src/commands/use.command.ts index 857c47b..57cc4f4 100644 --- a/src/commands/use.command.ts +++ b/src/commands/use.command.ts @@ -2,7 +2,7 @@ import { BaseCommand } from "@command-line/base.command"; import { Command, CommandOption } from "@decorators/command.decorator"; import { ValidateUser } from "@decorators/validate-user.decorator"; import { progress } from "@/utils/progress"; -import { ui } from "@/utils/ui"; +import { ui } from "@/ui"; import { promptSelect } from "@/utils/prompt"; import { ENDPOINTS } from "@/api/endpoints"; import { createUserApiClient, fetchOrgs } from "@/api/user-client"; diff --git a/src/commands/whoami.command.ts b/src/commands/whoami.command.ts index 48cc3fb..f7c1f0c 100644 --- a/src/commands/whoami.command.ts +++ b/src/commands/whoami.command.ts @@ -4,8 +4,8 @@ import { ValidateUser } from "@decorators/validate-user.decorator"; import { progress } from "@/utils/progress"; import { ENDPOINTS } from "@/api/endpoints"; import { createUserApiClient, fetchOrgs } from "@/api/user-client"; -import { printTable } from "@/utils/table"; -import { ui } from "@/utils/ui"; +import { ui } from "@/ui"; +import { getContext } from "@/utils/context-store"; const expectedOptions: CommandOption[] = [ { name: "json", description: "Output raw JSON", required: false }, @@ -34,22 +34,28 @@ export class WhoamiCommand extends BaseCommand { return; } - ui.section("User"); - ui.keyValue([ - ["Name", profile?.fullName], - ["Email", profile?.email], - ["Id", profile?._id], + const ctx = getContext(); + ui.pipeline([ + { status: "done", label: "session", value: "authorized" }, + { status: "done", label: "user", value: profile?.fullName }, + { status: "done", label: "email", value: profile?.email }, + { + status: ctx.orgId ? "active" : "pending", + label: "context", + value: + ctx.projectName || ctx.orgName + ? `${ctx.projectName ?? "-"} · ${ctx.orgName ?? "-"}` + : "not set", + }, ]); - ui.section("Organizations"); - printTable( - orgs, - [ - { header: "NAME", value: (o) => o.name }, - { header: "ORG ID", value: (o) => o.orgId }, - { header: "REGION", value: (o) => o.region }, - { header: "ACCESS", value: (o) => o.access }, - ], + ui.section("organizations"); + ui.numbered( + orgs.map((o) => ({ + label: o.name, + meta: `${o.region} · ${o.access}`, + current: ctx.orgId === o.orgId, + })), { indent: ui.INDENT } ); } diff --git a/src/index.ts b/src/index.ts index 2b3d54a..6dbdb2c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,8 +8,8 @@ import { getCommandMetadata, registeredCommands, } from "@decorators/command.decorator"; -import { showBanner } from "@utils/banner"; -import { logger } from "@utils/logger"; +import { showBanner, showWelcome } from "@/ui"; +import { ui } from "@/ui"; import { getVersion } from "@utils/version"; import { normalizeOptions } from "@utils/normalize"; import { rimraf } from "rimraf"; @@ -32,7 +32,7 @@ getCommands().forEach((options, name) => { }); if (!commandClass) { - logger.error(`No class found for command "${name}"`); + ui.status.fail(`No class found for command "${name}"`); return; } @@ -52,26 +52,37 @@ getCommands().forEach((options, name) => { }); command.action(async (...args) => { + const opts = normalizeOptions(args.slice(0, args.length - 1)); try { - await showBanner(); + // Banner is chrome — never emit it in JSON mode (keeps stdout parseable). + if (!opts.json) showBanner(); - await registry.executeCommand( - name, - normalizeOptions(args.slice(0, args.length - 1)) - ); + await registry.executeCommand(name, opts); } catch (error) { - logger.error(error instanceof Error ? error.message : "Unknown error"); + const message = error instanceof Error ? error.message : "Unknown error"; + // In JSON mode, surface the failure as JSON on stdout so `jq` consumers + // get a parseable error object; the human line still goes to stderr. + if (opts.json) { + process.stdout.write(JSON.stringify({ error: message }) + "\n"); + } + ui.status.fail(message); process.exit(1); } }); }); program.on("command:*", (operands) => { - logger.error(`Command "${operands[0]}" not found`); - logger.info(`Run "stallion help" to see all available commands`); - process.exit(0); + ui.status.fail(`Command "${operands[0]}" not found`); + ui.hint(`Run "stallion help" to see all available commands`); + process.exit(1); }); +// Bare `stallion` (no subcommand) shows the hero welcome screen. +if (process.argv.slice(2).length === 0) { + showWelcome(); + process.exit(0); +} + program.parse(); ["SIGINT", "SIGTERM", "SIGQUIT"].forEach((signal) => diff --git a/src/ui/ansi.ts b/src/ui/ansi.ts new file mode 100644 index 0000000..0f689e3 --- /dev/null +++ b/src/ui/ansi.ts @@ -0,0 +1,26 @@ +// eslint-disable-next-line no-control-regex +const ANSI_PATTERN = /\u001b\[[0-9;]*m/g; + +/** Strip SGR escape codes so we can measure printable width. */ +export function stripAnsi(text: string): string { + return text.replace(ANSI_PATTERN, ""); +} + +/** Printable width of a string, ignoring color codes. */ +export function visibleLength(text: string): number { + return [...stripAnsi(text)].length; +} + +/** Pad a (possibly colored) string to `width` printable columns. */ +export function padVisible(text: string, width: number): string { + const len = visibleLength(text); + return text + " ".repeat(Math.max(0, width - len)); +} + +/** Center a (possibly colored) string within `width` printable columns. */ +export function centerVisible(text: string, width: number): string { + const len = visibleLength(text); + const total = Math.max(0, width - len); + const left = Math.floor(total / 2); + return " ".repeat(left) + text + " ".repeat(total - left); +} diff --git a/src/ui/banner.ts b/src/ui/banner.ts new file mode 100644 index 0000000..b542f6d --- /dev/null +++ b/src/ui/banner.ts @@ -0,0 +1,113 @@ +import { glyph, theme } from "./theme"; +import { padVisible } from "./ansi"; +import { box } from "./box"; +import { getVersion } from "@/utils/version"; +import { getContext } from "@/utils/context-store"; +import { hasStoredToken } from "@/utils/token-store"; +import { + LOGO_LARGE, + LOGO_LARGE_WIDTH, + LOGO_SMALL, + LOGO_SMALL_WIDTH, +} from "./logo.generated"; + +/** Full product name and one-line description. */ +const PRODUCT = "React Native Stallion"; +const TAGLINE = "Over-the-air updates for React Native"; + +/** Place the logo and text side by side, vertically centering the text. */ +function sideBySide( + logo: string[], + logoWidth: number, + right: string[], + gap = 4 +): string[] { + const height = Math.max(logo.length, right.length); + const rTop = Math.max(0, Math.floor((logo.length - right.length) / 2)); + const sep = " ".repeat(gap); + const blank = " ".repeat(logoWidth); + + const out: string[] = []; + for (let i = 0; i < height; i++) { + const l = i < logo.length ? logo[i] : blank; + const r = i - rTop >= 0 && i - rTop < right.length ? right[i - rTop] : ""; + out.push(padVisible(l, logoWidth) + sep + r); + } + return out; +} + +/** The greeting line — personalized when the user is logged in. */ +function greetingLine(): string { + if (!hasStoredToken()) { + return ( + theme.dim("Not logged in · run ") + + theme.accent("stallion login") + + theme.dim(" to get started") + ); + } + const name = getContext().userName?.trim(); + const who = name ? theme.text.bold(name) : theme.text.bold("there"); + return `${theme.accent(glyph.bullet)} ${theme.text("Welcome back, ")}${who}`; +} + +/** Product name, tagline, and greeting — printed below the logo. */ +function metaLines(): string[] { + const version = theme.dim(`v${getVersion()}`); + return [ + theme.accentBold(PRODUCT) + theme.dim(` ${glyph.dot} `) + version, + theme.muted(TAGLINE), + greetingLine(), + ]; +} + +/** + * Hero welcome — horse logo, the product name / tagline / greeting below it, + * then a rounded box of quick-start commands. Shown for bare `stallion`. + */ +export function showWelcome(): void { + console.log(""); + for (const line of sideBySide(LOGO_LARGE, LOGO_LARGE_WIDTH, metaLines(), 4)) { + console.log(" " + line); + } + console.log(""); + + const entries: Array<[string, string]> = [ + ["stallion login", "Authenticate this machine"], + ["stallion whoami", "Show the logged-in account"], + ["stallion list-projects", "Browse your projects"], + ["stallion publish-bundle", "Ship an OTA update"], + ["stallion help", "See every command"], + ]; + const nameWidth = Math.max(...entries.map(([n]) => n.length)) + 2; + const cmd = ([name, desc]: [string, string]) => + `${theme.accent(glyph.arrow)} ${theme.text.bold(padVisible(name, nameWidth))}${theme.dim(desc)}`; + + const lines = [ + ...entries.map(cmd), + "", + `${theme.dim("cwd")} ${theme.muted(shortCwd())}`, + ]; + + for (const line of box(lines, { title: "Getting Started", indent: 2, padX: 2 })) { + console.log(line); + } + console.log(""); +} + +/** + * Compact header — horse logo with the product name, tagline, and greeting + * below it. Shown before each command so the brand and user are acknowledged. + */ +export function showBanner(): void { + // Per-command chrome — stderr, so piping stdout (incl. --json) stays clean. + console.error(""); + for (const line of sideBySide(LOGO_SMALL, LOGO_SMALL_WIDTH, metaLines(), 3)) { + console.error(" " + line); + } +} + +function shortCwd(): string { + const cwd = process.cwd(); + const home = process.env.HOME || process.env.USERPROFILE; + return home && cwd.startsWith(home) ? "~" + cwd.slice(home.length) : cwd; +} diff --git a/src/ui/box.ts b/src/ui/box.ts new file mode 100644 index 0000000..2f9d62f --- /dev/null +++ b/src/ui/box.ts @@ -0,0 +1,59 @@ +import { glyph, theme } from "./theme"; +import { padVisible, visibleLength } from "./ansi"; + +export interface BoxOptions { + title?: string; + /** Inner horizontal padding (spaces). */ + padX?: number; + /** Indent of the whole box from the left margin. */ + indent?: number; + /** Fixed inner width; otherwise sized to the widest line. */ + width?: number; + /** Border color function. */ + border?: (s: string) => string; +} + +/** + * Render a rounded box around the given (possibly colored) content lines. + * Returns the framed lines; callers print them. Width is measured ignoring + * ANSI codes so colored content stays aligned. + */ +export function box(lines: string[], options: BoxOptions = {}): string[] { + const padX = options.padX ?? 1; + const indent = " ".repeat(options.indent ?? 0); + const border = options.border ?? theme.accent; + + const contentWidth = Math.max( + options.width ?? 0, + options.title ? visibleLength(options.title) + 2 : 0, + ...lines.map((l) => visibleLength(l)) + ); + const inner = contentWidth + padX * 2; + + const top = options.title + ? border(glyph.tl + glyph.h) + + " " + + theme.accentBold(options.title) + + " " + + border(glyph.h.repeat(Math.max(0, inner - visibleLength(options.title) - 3)) + glyph.tr) + : border(glyph.tl + glyph.h.repeat(inner) + glyph.tr); + + const bottom = border(glyph.bl + glyph.h.repeat(inner) + glyph.br); + + const pad = " ".repeat(padX); + const body = lines.map( + (l) => + border(glyph.v) + + pad + + padVisible(l, contentWidth) + + pad + + border(glyph.v) + ); + + return [top, ...body, bottom].map((l) => indent + l); +} + +/** Print a rounded box directly. */ +export function printBox(lines: string[], options: BoxOptions = {}): void { + for (const line of box(lines, options)) console.log(line); +} diff --git a/src/ui/format.ts b/src/ui/format.ts new file mode 100644 index 0000000..fe67410 --- /dev/null +++ b/src/ui/format.ts @@ -0,0 +1,44 @@ +import { theme } from "./theme"; + +const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/; + +function formatDate(d: Date): string { + const p = (n: number) => String(n).padStart(2, "0"); + return ( + `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ` + + `${p(d.getUTCHours())}:${p(d.getUTCMinutes())} UTC` + ); +} + +/** Plain (uncolored) string for a cell value. */ +export function formatValue(value: unknown): string { + if (value === null || value === undefined || value === "") return "-"; + if (value instanceof Date) return formatDate(value); + if (typeof value === "boolean") return value ? "Yes" : "No"; + if (typeof value === "string" && ISO_DATE.test(value)) { + const d = new Date(value); + if (!isNaN(d.getTime())) return formatDate(d); + } + return String(value); +} + +/** Apply semantic coloring to a formatted (already padded) value. */ +export function colorizeCell(plain: string, padded: string): string { + const trimmed = plain.trim(); + if (trimmed === "-") return theme.dim(padded); + if (trimmed === "Yes") return theme.ok(padded); + if (trimmed === "No") return theme.dim(padded); + return padded; +} + +/** Format + color a value for inline rendering. */ +export function renderValue(value: unknown): string { + const plain = formatValue(value); + return colorizeCell(plain, plain); +} + +export const accentStyle = theme.accentBold; + +export function pad(text: string, width: number): string { + return text + " ".repeat(Math.max(0, width - text.length)); +} diff --git a/src/ui/index.ts b/src/ui/index.ts new file mode 100644 index 0000000..78655e6 --- /dev/null +++ b/src/ui/index.ts @@ -0,0 +1,85 @@ +/** + * Stallion UI — a minimal, branded terminal design system. + * + * showWelcome() hero logo + quick-start box + * showBanner() compact logo header + * ui.header("whoami") ▌ STALLION › whoami breadcrumb + * ui.section("User") ▌ User + thin rule + * ui.keyValue([["Name", ...]]) dot-leader alignment + * ui.badge("active", "ok") ● active + * ui.status.ok("Saved") ✓ Saved + * printTable(rows, columns) accent header + clean rows + * + * The logo is committed truecolor half-block ANSI art in logo.generated.ts + * (a static, checked-in asset — no build-time image processing). + */ + +export { theme, glyph, space, brand, palette, brandGradient } from "./theme"; +export { stripAnsi, visibleLength, padVisible, centerVisible } from "./ansi"; +export { formatValue, renderValue, accentStyle } from "./format"; +export { box, printBox as printRoundedBox } from "./box"; +export { + INDENT, + header, + section, + keyValue, + hint, + text, + divider, + blank, + badge, +} from "./layout"; +export { status } from "./status"; +export { + pipeline, + numbered, + type Stage, + type StageStatus, + type NumberedItem, +} from "./pipeline"; +export { bar } from "./meter"; +export { createSpinner, task, type Spinner } from "./spinner"; +export { + printTable, + printDetail, + printBox, + printBoxes, + printBoard, + type Column, + type DetailField, + type BoxSpec, + type BoardColumn, +} from "./table"; +export { logger } from "./logger"; +export { showBanner, showWelcome } from "./banner"; + +import { + INDENT, + badge, + blank, + divider, + header, + hint, + keyValue, + section, + text, +} from "./layout"; +import { status } from "./status"; +import { pipeline, numbered } from "./pipeline"; +import { bar } from "./meter"; + +export const ui = { + INDENT, + header, + section, + keyValue, + pipeline, + numbered, + bar, + hint, + text, + divider, + blank, + badge, + status, +}; diff --git a/src/ui/layout.ts b/src/ui/layout.ts new file mode 100644 index 0000000..2b9f3b7 --- /dev/null +++ b/src/ui/layout.ts @@ -0,0 +1,99 @@ +import { brand, glyph, space, theme } from "./theme"; +import { formatValue, renderValue } from "./format"; + +export const INDENT = space.indent; +const pre = " ".repeat(INDENT); + +function termWidth(): number { + const c = process.stdout.columns; + return c && c > 40 ? c : 80; +} + +/** + * Top-of-command header (Pipeline): `♞ whoami`. + * Pass extra crumbs for a breadcrumb trail: `releases ⟩ rel_9f3c` + * (rendered without the mark). + */ +export function header(command: string, ...crumbs: string[]): void { + // Breadcrumb is chrome — stderr, so it never mixes with stdout data. + if (crumbs.length) { + const sep = theme.dim(` ${glyph.arrow} `); + const trail = [command, ...crumbs]; + const last = trail.pop() as string; + console.error( + "\n" + trail.map((c) => theme.dim(c)).join(sep) + sep + theme.head(last) + ); + return; + } + console.error("\n" + theme.accent(glyph.mark) + " " + theme.head(command)); +} + +/** Capitalize each plain-alphabetic word; leave tokens like `v1.8.0` alone. */ +export function titleCase(title: string): string { + return title + .split(" ") + .map((w) => (/^[a-zA-Z]+$/.test(w) ? w[0].toUpperCase() + w.slice(1) : w)) + .join(" "); +} + +/** Quiet section heading — Title Case, dim-bold, no underline. */ +export function section(title: string): void { + console.log("\n" + theme.dim.bold(titleCase(title))); +} + +/** + * Aligned key/value block — the signature Stallion layout. + * Labels pad to a shared column; values start together. No leaders. + * email jane@acme.io + * bucket production + */ +export function keyValue( + rows: Array<[string, unknown]>, + options: { indent?: number } = {} +): void { + if (!rows.length) return; + const indent = " ".repeat(options.indent ?? INDENT); + // Labels render Title Cased for a consistent look across every command. + const labels = rows.map(([label]) => titleCase(label)); + const labelWidth = Math.max(...labels.map((l) => l.length)); + rows.forEach(([, value], i) => { + const key = theme.dim(labels[i] + " ".repeat(labelWidth - labels[i].length)); + console.log(`${indent}${key} ${renderValue(value)}`); + }); +} + +/** Dim helper line (tips, paths, pagination) — diagnostics, so stderr. */ +export function hint(text: string): void { + console.error(theme.dim(text)); +} + +/** Indented free-text under a section. */ +export function text(value: string): void { + console.log(pre + value); +} + +/** A faint full-width divider. */ +export function divider(width?: number): void { + const w = width ?? Math.min(termWidth() - 2, 60); + console.log(theme.dim(glyph.rule.repeat(w))); +} + +/** Vertical spacing. */ +export function blank(lines = 1): void { + for (let i = 0; i < lines; i++) console.log(""); +} + +type BadgeKind = "ok" | "warn" | "danger" | "muted" | "accent"; + +/** Minimal status dot + label (no heavy pill backgrounds). */ +export function badge(label: string, kind: BadgeKind = "muted"): string { + const color: Record string> = { + ok: theme.ok, + warn: theme.warn, + danger: theme.danger, + muted: theme.dim, + accent: theme.accent, + }; + const dot = kind === "muted" ? theme.dim(glyph.ring) : color[kind](glyph.bullet); + return `${dot} ${color[kind](label)}`; +} diff --git a/src/ui/logger.ts b/src/ui/logger.ts new file mode 100644 index 0000000..6b2b903 --- /dev/null +++ b/src/ui/logger.ts @@ -0,0 +1,16 @@ +import { glyph, theme } from "./theme"; + +/** Structured log lines for operational / verbose output. */ +export const logger = { + success: (message: string) => console.log(theme.ok(glyph.tick), message), + error: (message: string) => console.log(theme.danger(glyph.cross), message), + info: (message: string) => console.log(theme.info(glyph.info), message), + warning: (message: string) => console.log(theme.warn(glyph.warn), message), + title: (message: string) => console.log(theme.accentBold(message)), + subtitle: (message: string) => console.log(theme.accent(message)), + command: (name: string, description: string, alias?: string) => { + const aliasText = alias ? theme.dim(`(${alias})`) : ""; + console.log(theme.accent(name), aliasText); + console.log(theme.dim(` ${description}\n`)); + }, +}; diff --git a/src/ui/logo.generated.ts b/src/ui/logo.generated.ts new file mode 100644 index 0000000..b529ac8 --- /dev/null +++ b/src/ui/logo.generated.ts @@ -0,0 +1,33 @@ +/* eslint-disable */ +// AUTO-GENERATED by scripts/generate-logo.js — do not edit by hand. +// Source: assets/logo.png + +export const LOGO_LARGE: string[] = [ + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;79;70;229m▄\u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;52;52;169m▄\u001b[38;2;79;70;229m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;50;50;165m▀\u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;79;70;229m▄\u001b[38;2;50;50;165m\u001b[48;2;79;70;229m▀\u001b[38;2;50;50;165m\u001b[48;2;79;70;229m▀\u001b[38;2;50;50;165m\u001b[48;2;79;70;229m▀\u001b[38;2;50;50;165m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;54;53;173m▀\u001b[38;2;52;52;169m\u001b[48;2;50;50;165m▀\u001b[38;2;46;46;143m\u001b[48;2;33;31;74m▀\u001b[38;2;79;70;229m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m\u001b[38;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;65;60;197m▀\u001b[38;2;56;54;177m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;57;55;181m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;50;50;165m\u001b[48;2;26;26;51m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[0m\u001b[38;2;26;26;51m▄\u001b[0m \u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;50;50;165m▄\u001b[0m\u001b[38;2;50;50;165m▄\u001b[38;2;50;50;165m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;68;63;205m▀\u001b[38;2;65;60;197m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;50;50;165m\u001b[48;2;26;26;51m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[0m\u001b[38;2;26;26;51m▄\u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m\u001b[38;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;64;60;195m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;54;53;173m▀\u001b[38;2;54;53;173m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;26;26;51m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;79;70;229m▀\u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m \u001b[0m\u001b[38;2;50;50;165m▄\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;56;54;177m\u001b[48;2;79;70;229m▀\u001b[38;2;50;50;165m\u001b[48;2;26;26;51m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m \u001b[0m \u001b[0m\u001b[38;2;50;50;165m▀\u001b[0m\u001b[38;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;26;26;51m▀\u001b[38;2;79;70;229m\u001b[48;2;26;26;51m▀\u001b[38;2;79;70;229m\u001b[48;2;26;26;51m▀\u001b[0m\u001b[38;2;26;26;51m▀\u001b[0m", + "\u001b[0m \u001b[0m\u001b[38;2;50;50;165m▄\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;50;50;165m\u001b[48;2;26;26;51m▀\u001b[38;2;50;50;165m\u001b[48;2;26;26;51m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;59;56;185m▀\u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m\u001b[38;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m\u001b[38;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m\u001b[38;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m\u001b[38;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[0m\u001b[38;2;26;26;51m▄\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;80;70;229m▀\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m", +]; +export const LOGO_LARGE_WIDTH = 26; + +export const LOGO_SMALL: string[] = [ + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;79;70;229m▄\u001b[38;2;79;70;229m\u001b[48;2;72;65;213m▀\u001b[0m \u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;79;70;229m▄\u001b[38;2;50;50;165m\u001b[48;2;62;59;193m▀\u001b[38;2;50;50;165m\u001b[48;2;79;70;229m▀\u001b[38;2;50;50;165m\u001b[48;2;79;70;229m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;26;26;51m▀\u001b[38;2;79;70;229m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[0m \u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;50;50;165m▄\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;79;70;229m▀\u001b[38;2;40;37;96m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;50;50;165m\u001b[48;2;26;26;51m▀\u001b[38;2;44;44;136m\u001b[48;2;26;26;51m▀\u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m\u001b[38;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;76;68;221m▀\u001b[38;2;79;70;229m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[0m\u001b[38;2;26;26;51m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;79;70;229m▀\u001b[0m\u001b[38;2;79;70;229m▄\u001b[0m", + "\u001b[0m \u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[38;2;50;50;165m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;79;70;229m▀\u001b[38;2;50;50;165m\u001b[48;2;26;26;51m▀\u001b[38;2;50;50;165m\u001b[48;2;26;26;51m▀\u001b[38;2;50;50;165m\u001b[48;2;50;50;165m▀\u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;26;26;51m▀\u001b[0m\u001b[38;2;26;26;51m▀\u001b[0m", + "\u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m\u001b[38;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m\u001b[38;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;79;70;229m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;79;70;229m▀\u001b[38;2;26;26;51m\u001b[48;2;26;26;51m▀\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m", + "\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m\u001b[38;2;79;70;229m▀\u001b[0m\u001b[38;2;76;67;218m▀\u001b[0m\u001b[38;2;26;26;51m▄\u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m \u001b[0m", +]; +export const LOGO_SMALL_WIDTH = 16; + diff --git a/src/ui/meter.ts b/src/ui/meter.ts new file mode 100644 index 0000000..6bd057e --- /dev/null +++ b/src/ui/meter.ts @@ -0,0 +1,20 @@ +import { theme } from "./theme"; + +// Eighth-block characters for smooth sub-cell fills. +const EIGHTHS = ["", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]; + +/** + * A horizontal meter bar — filled blocks (iris) over an empty track (faint). + * ████████ 100% ███████▏ 89.8% ░░░░░░░░ 0% + * Width is in cells; the visible width is always exactly `width`. + */ +export function bar(percent: number, width = 8): string { + const p = Math.max(0, Math.min(100, Number(percent) || 0)); + const eighths = Math.round((p / 100) * width * 8); + const full = Math.floor(eighths / 8); + const rem = eighths % 8; + const filled = "█".repeat(full) + (rem ? EIGHTHS[rem] : ""); + const filledCells = full + (rem ? 1 : 0); + const empty = "░".repeat(Math.max(0, width - filledCells)); + return theme.iris(filled) + theme.faint(empty); +} diff --git a/src/ui/pipeline.ts b/src/ui/pipeline.ts new file mode 100644 index 0000000..f8fd347 --- /dev/null +++ b/src/ui/pipeline.ts @@ -0,0 +1,86 @@ +import { glyph, theme } from "./theme"; +import { renderValue } from "./format"; +import { titleCase } from "./layout"; + +export type StageStatus = "done" | "active" | "pending" | "spin"; + +export interface Stage { + status: StageStatus; + label: string; + value?: unknown; +} + +function statusMark(status: StageStatus): string { + switch (status) { + case "done": + return theme.ok(glyph.tick); + case "active": + return theme.accent(glyph.bullet); + case "spin": + return theme.iris(glyph.spin); + case "pending": + default: + return theme.faint(glyph.ring); + } +} + +/** + * Connected pipeline of stages — the signature 3b composition. A ┌ ├ └ gutter + * ties the stages together; each carries a status mark. + * ┌ ✓ session authorized + * ├ ✓ token valid · expires 27d + * └ ● context acme-mobile · production + */ +export function pipeline( + stages: Stage[], + options: { indent?: number; spaced?: boolean } = {} +): void { + if (!stages.length) return; + const pre = " ".repeat(options.indent ?? 0); + const labels = stages.map((s) => titleCase(s.label)); + const labelWidth = Math.max(...labels.map((l) => l.length)); + stages.forEach((s, i) => { + if (i > 0 && options.spaced) { + console.log(pre + theme.border(glyph.v)); + } + const connector = + i === 0 + ? glyph.branchTop + : i === stages.length - 1 + ? glyph.branchEnd + : glyph.branchMid; + const label = theme.dim(labels[i] + " ".repeat(labelWidth - labels[i].length)); + const value = s.value !== undefined ? " " + renderValue(s.value) : ""; + console.log( + `${pre}${theme.border(connector)} ${statusMark(s.status)} ${label}${value}` + ); + }); +} + +export interface NumberedItem { + label: string; + meta?: unknown; + current?: boolean; +} + +/** + * Numbered list with an accented current row — the 3b list-projects style. + * 1 · acme-mobile ios·android · 142 + * 2 · acme-driver android · 38 + */ +export function numbered( + items: NumberedItem[], + options: { indent?: number } = {} +): void { + if (!items.length) return; + const pre = " ".repeat(options.indent ?? 0); + const labelWidth = Math.max(...items.map((i) => i.label.length)); + items.forEach((it, i) => { + const tag = `${i + 1} ${glyph.dot}`; + const num = it.current ? theme.accent(tag) : theme.dim(tag); + const name = it.current ? theme.accent(it.label) : theme.text(it.label); + const padding = " ".repeat(Math.max(0, labelWidth - it.label.length)); + const meta = it.meta != null ? " " + theme.dim(String(it.meta)) : ""; + console.log(`${pre}${num} ${name}${padding}${meta}`); + }); +} diff --git a/src/ui/spinner.ts b/src/ui/spinner.ts new file mode 100644 index 0000000..5e89912 --- /dev/null +++ b/src/ui/spinner.ts @@ -0,0 +1,101 @@ +import * as tty from "tty"; +import { glyph, theme } from "./theme"; +import { bar } from "./meter"; + +/** Braille spinner frames — same family as glyph.spin, animated. */ +const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; +const INTERVAL = 80; + +const HIDE = "\x1b[?25l"; +const SHOW = "\x1b[?25h"; +const CLEAR = "\r\x1b[2K"; + +export interface Spinner { + /** Replace the trailing text (keeps spinning). */ + setText(text: string): void; + /** Stop and print a green ✓ line. */ + succeed(text?: string): void; + /** Stop and print a red ✗ line. */ + fail(text?: string): void; + /** Stop and clear, printing nothing. */ + stop(): void; +} + +/** + * A themed spinner matching the Stallion UI: an iris braille spinner while + * running, resolving to the shared ✓ / ✗ status marks. Falls back to a no-op + * (no animation) when stdout is not a TTY, so piped/CI output stays clean. + */ +export function createSpinner(initial: string): Spinner { + // Progress is a diagnostic — render on stderr, gated on stderr being a TTY. + // This keeps the animation visible during `cmd --json | jq` (stdout piped, + // stderr still a terminal) without ever corrupting stdout. + const isTTY = tty.isatty(2); + let text = initial; + let i = 0; + let timer: ReturnType | null = null; + + const draw = () => { + const mark = theme.iris(FRAMES[i]); + i = (i + 1) % FRAMES.length; + process.stderr.write(`${CLEAR}${mark} ${theme.text(text)}`); + }; + + const restoreCursor = () => process.stderr.write(SHOW); + + if (isTTY) { + process.stderr.write(HIDE); + process.once("exit", restoreCursor); + draw(); + timer = setInterval(draw, INTERVAL); + if (typeof timer.unref === "function") timer.unref(); + } + + const finish = (mark: string, msg: string) => { + if (timer) clearInterval(timer); + if (isTTY) { + process.stderr.write(CLEAR + SHOW); + console.error(`${mark} ${msg}`); + } + }; + + return { + setText: (t) => { + text = t; + }, + succeed: (t) => finish(theme.ok(glyph.tick), t ?? text), + fail: (t) => finish(theme.danger(glyph.cross), t ?? text), + stop: () => { + if (timer) clearInterval(timer); + if (isTTY) process.stderr.write(CLEAR + SHOW); + }, + }; +} + +/** + * Run an async action behind a themed spinner. The action receives an + * `update(percent)` callback that renders a live meter bar next to the title: + * ⠋ Publishing bundle ██████░░ 74% + * On success the line collapses to a clean `✓ Publishing bundle`. + */ +export async function task( + title: string, + action: (update: (percent: number) => void) => Promise +): Promise { + if (!tty.isatty(2)) { + return action(() => {}); + } + const spinner = createSpinner(title); + const update = (percent: number) => { + const pct = Math.max(0, Math.min(100, Number(percent) || 0)); + spinner.setText(`${title} ${bar(pct)} ${theme.dim(`${pct.toFixed(0)}%`)}`); + }; + try { + const result = await action(update); + spinner.succeed(title); + return result; + } catch (err) { + spinner.fail(title); + throw err; + } +} diff --git a/src/ui/status.ts b/src/ui/status.ts new file mode 100644 index 0000000..15977c7 --- /dev/null +++ b/src/ui/status.ts @@ -0,0 +1,13 @@ +import { glyph, theme } from "./theme"; + +// Status lines are diagnostics — emitted on stderr so stdout stays clean for +// data (JSON in --json mode, tables otherwise). Single-space gutter matches +// the ♞ header and the spinner's ✓/✗. +const line = (mark: string, msg: string) => console.error(`${mark} ${msg}`); + +export const status = { + ok: (msg: string) => line(theme.ok(glyph.tick), msg), + fail: (msg: string) => line(theme.danger(glyph.cross), msg), + info: (msg: string) => line(theme.iris(glyph.arrow), msg), + warn: (msg: string) => line(theme.warn(glyph.warn), msg), +}; diff --git a/src/ui/table.ts b/src/ui/table.ts new file mode 100644 index 0000000..707bcf8 --- /dev/null +++ b/src/ui/table.ts @@ -0,0 +1,198 @@ +import { glyph, space, theme } from "./theme"; +import { colorizeCell, formatValue, pad } from "./format"; +import { padVisible, visibleLength } from "./ansi"; + +export interface Column { + header: string; + value: (row: any) => any; +} + +export interface BoardColumn { + header: string; + /** Pre-colored display string for the cell (measured by visible width). */ + render: (row: any) => string; + align?: "left" | "right"; +} + +/** + * Fully bordered status board — the 3f Console anchor. Cells are rendered + * (and may carry color / meter bars); widths are measured on visible width so + * ANSI never breaks the frame. + */ +export function printBoard( + rows: any[], + columns: BoardColumn[], + options: { indent?: number; spaced?: boolean } = {} +): void { + const pre = " ".repeat(options.indent ?? 0); + const B = theme.border; + const cells = rows.map((r) => columns.map((c) => c.render(r))); + const widths = columns.map((c, i) => + Math.max( + visibleLength(c.header), + ...cells.map((row) => visibleLength(row[i])), + 1 + ) + ); + + const rule = (l: string, m: string, r: string) => + B(l + widths.map((w) => "─".repeat(w + 2)).join(m) + r); + + const rowLine = (vals: string[]): string => + B(glyph.v) + + vals + .map((v, i) => { + const w = widths[i]; + const cell = + columns[i].align === "right" + ? " ".repeat(Math.max(0, w - visibleLength(v))) + v + : padVisible(v, w); + return " " + cell + " "; + }) + .join(B(glyph.v)) + + B(glyph.v); + + // Blank padded row used to space data rows apart. + const spacer = rowLine(columns.map(() => "")); + + console.log(pre + rule("┌", "┬", "┐")); + console.log(pre + rowLine(columns.map((c) => theme.dim(c.header)))); + console.log(pre + rule("├", "┼", "┤")); + cells.forEach((row, idx) => { + if (idx > 0 && options.spaced) console.log(pre + spacer); + console.log(pre + rowLine(row)); + }); + console.log(pre + rule("└", "┴", "┘")); +} + +export interface DetailField { + label: string; + value: (obj: any) => any; +} + +export interface BoxSpec { + obj: any; + fields: DetailField[]; + title?: string; + description?: string; +} + +/** Open table — accent header + thin rule, no heavy outer frame. */ +export function printTable( + rows: any[], + columns: Column[], + options: { json?: boolean; indent?: number } = {} +): void { + if (options.json) { + console.log(JSON.stringify(rows, null, 2)); + return; + } + + const pre = " ".repeat(options.indent ?? 0); + const out = (line: string) => console.log(pre + line); + + if (!rows.length) { + out(theme.dim("No results.")); + return; + } + + const gap = " ".repeat(space.colGap); + const headers = columns.map((c) => c.header); + const data = rows.map((row) => columns.map((c) => formatValue(c.value(row)))); + const widths = headers.map((h, i) => + Math.max(h.length, ...data.map((row) => row[i].length)) + ); + + // Hairline table: dim header, no rule, plain rows. + out(theme.dim(headers.map((h, i) => pad(h, widths[i])).join(gap))); + data.forEach((row) => { + out(row.map((c, i) => colorizeCell(c, pad(c, widths[i]))).join(gap)); + }); +} + +/** Aligned key/value detail view (no framing). */ +export function printDetail( + obj: any, + fields: DetailField[], + options: { json?: boolean; indent?: number } = {} +): void { + if (options.json) { + console.log(JSON.stringify(obj, null, 2)); + return; + } + const pre = " ".repeat(options.indent ?? 0); + const labelWidth = Math.max(...fields.map((f) => f.label.length)); + for (const field of fields) { + const plain = formatValue(field.value(obj)); + console.log(`${pre}${theme.dim(pad(field.label, labelWidth))} ${colorizeCell(plain, plain)}`); + } +} + +function wrapText(text: string, width: number): string[] { + const lines: string[] = []; + let current = ""; + for (const word of text.split(/\s+/)) { + if (!current) current = word; + else if ((current + " " + word).length <= width) current += " " + word; + else { + lines.push(current); + current = word; + } + } + if (current) lines.push(current); + return lines; +} + +/** Build a left-rail panel (title + optional description + fields). */ +function buildPanel(spec: BoxSpec): { lines: string[]; width: number } { + const { obj, fields, title = "", description } = spec; + const labelWidth = Math.max(...fields.map((f) => f.label.length)); + const gap = " "; + const rows = fields.map((f) => { + const plain = formatValue(f.value(obj)); + return { label: f.label, plain, len: labelWidth + gap.length + plain.length }; + }); + + const target = Math.max(title.length, ...rows.map((r) => r.len), 18); + const descLines = description ? wrapText(description, target) : []; + const innerWidth = Math.max(target, ...descLines.map((l) => l.length)); + const rail = theme.accent(glyph.rail); + + const lines: string[] = []; + if (title) lines.push(`${rail} ${theme.accentBold(title)}`); + for (const dl of descLines) lines.push(`${rail} ${theme.dim.italic(dl)}`); + if (title || descLines.length) lines.push(""); + for (const r of rows) { + lines.push(` ${theme.dim(pad(r.label, labelWidth))}${gap}${colorizeCell(r.plain, r.plain)}`); + } + return { lines, width: innerWidth + 4 }; +} + +export function printBox( + obj: any, + fields: DetailField[], + options: { title?: string; description?: string; indent?: number } = {} +): void { + const pre = " ".repeat(options.indent ?? 0); + const { lines } = buildPanel({ obj, fields, ...options }); + for (const line of lines) console.log(pre + line); +} + +export function printBoxes( + specs: BoxSpec[], + options: { gap?: number; indent?: number } = {} +): void { + const gap = options.gap ?? 4; + const pre = " ".repeat(options.indent ?? 0); + const panels = specs.map(buildPanel); + const height = Math.max(...panels.map((p) => p.lines.length)); + const sep = " ".repeat(gap); + for (let i = 0; i < height; i++) { + console.log( + pre + + panels + .map((p) => (i < p.lines.length ? p.lines[i] : " ".repeat(p.width))) + .join(sep) + ); + } +} diff --git a/src/ui/theme.ts b/src/ui/theme.ts new file mode 100644 index 0000000..40a37dc --- /dev/null +++ b/src/ui/theme.ts @@ -0,0 +1,94 @@ +import chalk from "chalk"; + +/** + * Stallion brand palette — derived from the logo's geometric indigo→violet + * horse. One confident accent, the rest grayscale, so output stays clean. + */ +export const palette = { + indigo: "#4F46E5", + violet: "#6D28D9", + iris: "#818CF8", + ink: "#1E1B4B", +} as const; + +export const theme = { + accent: chalk.hex(palette.indigo), + accentBold: chalk.hex(palette.indigo).bold, + violet: chalk.hex(palette.violet), + iris: chalk.hex(palette.iris), + /** One bright heading per screen. */ + head: chalk.whiteBright.bold, + text: chalk.reset, + dim: chalk.dim, + muted: chalk.gray, + /** Recessive chrome — quieter than dim. */ + faint: chalk.hex("#474753"), + /** Pipeline connectors / structural lines. */ + border: chalk.hex("#3A3A6A"), + ok: chalk.hex("#3FB950"), + warn: chalk.hex("#E3B341"), + danger: chalk.hex("#F85149"), + info: chalk.hex(palette.iris), +} as const; + +/** Signature glyph set — kept tiny so the CLI stays recognizable. */ +export const glyph = { + rail: "▌", + dot: "·", + rule: "─", + tick: "✓", + cross: "✗", + info: "ℹ", + warn: "!", + bullet: "●", + ring: "○", + arrow: "⟩", + cursor: "❯", + // Stallion knight mark + pipeline connectors. + mark: "♞", + branchTop: "┌", + branchMid: "├", + branchEnd: "└", + spin: "⠋", + // Rounded box-drawing set. + tl: "╭", + tr: "╮", + bl: "╰", + br: "╯", + h: "─", + v: "│", +} as const; + +export const space = { + indent: 2, + colGap: 2, + kvMinWidth: 40, +} as const; + +export const brand = "STALLION"; + +/** Apply a left→right indigo→violet gradient across a single line of text. */ +export function brandGradient(textLine: string): string { + const stops = [ + [129, 140, 248], // iris + [79, 70, 229], // indigo + [109, 40, 217], // violet + ]; + const chars = [...textLine]; + const n = Math.max(1, chars.length - 1); + return chars + .map((ch, i) => { + if (ch === " ") return ch; + const t = i / n; + const seg = t * (stops.length - 1); + const idx = Math.min(stops.length - 2, Math.floor(seg)); + const f = seg - idx; + const [r1, g1, b1] = stops[idx]; + const [r2, g2, b2] = stops[idx + 1]; + const r = Math.round(r1 + (r2 - r1) * f); + const g = Math.round(g1 + (g2 - g1) * f); + const b = Math.round(b1 + (b2 - b1) * f); + return chalk.rgb(r, g, b)(ch); + }) + .join(""); +} diff --git a/src/utils/banner.ts b/src/utils/banner.ts deleted file mode 100644 index 0a950de..0000000 --- a/src/utils/banner.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { logger } from "@utils/logger"; -import gradient from "gradient-string"; -import chalk from "chalk"; -const figlet = require("figlet"); - -import { getVersion } from "./version"; - -export const showBanner = async (): Promise => { - return new Promise((resolve, reject) => { - figlet.text( - "STALLION", - { - font: "Larry 3D", - horizontalLayout: "default", - verticalLayout: "default", - }, - (err: any, data: any) => { - if (err) { - reject("Something went wrong..."); - return; - } - // Apply retro gradient to the banner - const gradientBanner = gradient.retro.multiline(data || ""); - console.log(gradientBanner); - console.log( - "\n" + - chalk.whiteBright( - `⚡ Welcome to the Stallion CLI ${chalk.bold( - chalk.greenBright(`v${getVersion()}`) - )} ⚡ \n` - ) - ); - resolve(); - } - ); - }); -}; diff --git a/src/utils/context-store.ts b/src/utils/context-store.ts index 8d0c9fb..7469120 100644 --- a/src/utils/context-store.ts +++ b/src/utils/context-store.ts @@ -13,6 +13,9 @@ export interface StallionContext { region?: string; projectId?: string; projectName?: string; + /** Cached profile of the logged-in user (for the welcome greeting). */ + userName?: string; + userEmail?: string; } function getContextFilePath(): string { diff --git a/src/utils/logger.ts b/src/utils/logger.ts deleted file mode 100644 index afd79e2..0000000 --- a/src/utils/logger.ts +++ /dev/null @@ -1,15 +0,0 @@ -import chalk from "chalk"; - -export const logger = { - success: (message: string) => console.log(chalk.green("✓"), message), - error: (message: string) => console.log(chalk.red("✗"), message), - info: (message: string) => console.log(chalk.blue("ℹ"), message), - warning: (message: string) => console.log(chalk.yellow("⚠"), message), - title: (message: string) => console.log(chalk.bold.cyan(message)), - subtitle: (message: string) => console.log(chalk.cyan(message)), - command: (name: string, description: string, alias?: string) => { - const aliasText = alias ? chalk.gray(`(${alias})`) : ""; - console.log(chalk.green(name), aliasText); - console.log(chalk.gray(` ${description}\n`)); - }, -}; diff --git a/src/utils/progress.ts b/src/utils/progress.ts index 20039c5..f253749 100644 --- a/src/utils/progress.ts +++ b/src/utils/progress.ts @@ -1,52 +1,19 @@ -import * as tty from "tty"; -const ora = require("ora"); -import { noop } from "lodash"; +import { task } from "@/ui"; type ProgressUpdater = (percentage: number) => void; /** - * Shows a progress spinner for an async operation + * Shows a themed progress spinner for an async operation. Delegates to the + * shared UI spinner (`@/ui`) so progress matches the rest of the CLI: an iris + * braille spinner with a live meter bar, resolving to a ✓ / ✗ status line. + * * @param title The text to show while the operation is in progress * @param action Function that receives a progress updater callback * @returns The result of the promise */ export function progress( title: string, - action: (updateProgress: ProgressUpdater) => Promise, + action: (updateProgress: ProgressUpdater) => Promise ): Promise { - const stdoutIsTerminal = tty.isatty(1); - - if (stdoutIsTerminal) { - const spinner = ora({ - text: title, - color: "white", - }).start(); - - const updateProgress: ProgressUpdater = (percentage: number) => { - spinner.text = `${title} (${percentage.toFixed(0)}%)`; - }; - - return action(updateProgress) - .then((result) => { - spinner.succeed(); - return result; - }) - .catch((ex) => { - spinner.fail(); - throw ex; - }); - } else { - return action(noop); - } -} - -// Helper functions that need to be implemented -function formatIsParsingCompatible(): boolean { - // TODO: Implement based on your needs - return false; -} - -function isQuiet(): boolean { - // TODO: Implement based on your needs - return false; + return task(title, action); } diff --git a/src/utils/react-native-utils.ts b/src/utils/react-native-utils.ts index d1dc9ca..ca67e20 100644 --- a/src/utils/react-native-utils.ts +++ b/src/utils/react-native-utils.ts @@ -2,7 +2,7 @@ import * as path from "path"; import * as fs from "fs"; import * as os from "os"; import * as rimraf from "rimraf"; -import { logger } from "./logger"; +import { ui } from "@/ui"; import * as childProcess from "child_process"; import { coerce, compare } from "semver"; import chalk from "chalk"; @@ -146,8 +146,8 @@ export async function runReactNativeBundleCommand( ] : []), ]); - logger.info(`Running \"react-native bundle\" command`); - logger.subtitle(reactNativeBundleArgs.join(" ")); + ui.hint(` Running "react-native bundle"`); + ui.hint(` ${reactNativeBundleArgs.join(" ")}`); const reactNativeBundleProcess = childProcess.spawn( "node", @@ -160,7 +160,8 @@ export async function runReactNativeBundleCommand( }); reactNativeBundleProcess.stderr.on("data", (data: Buffer) => { - logger.error(data.toString().trim()); + // Raw bundler stderr passthrough (kept unformatted). + console.error(data.toString().trim()); }); reactNativeBundleProcess.on("close", (exitCode: number, signal: string) => { @@ -193,12 +194,12 @@ export async function runHermesEmitBinaryCommand( path.join(outputFolder, "bundles", bundleName), ]); - logger.title(chalk.bold.italic.white("\nHermes Command Information\n")); - logger.info(chalk.cyan.bold("Converting JS bundle to byte code via Hermes")); + ui.section("Hermes"); + ui.hint(" Converting JS bundle to byte code via Hermes"); const hermesCommand = await getHermesCommand(); - logger.info(`Hermesc path: ${chalk.yellow(hermescPath || hermesCommand)}`); + ui.hint(` Hermesc: ${hermescPath || hermesCommand}`); const hermesProcess = childProcess.spawn(hermescPath || hermesCommand, hermesArgs); - logger.info(`Running: ${hermesCommand} ${hermesArgs.join(" ")}`); + ui.hint(` Running: ${hermesCommand} ${hermesArgs.join(" ")}`); let logFile: fs.WriteStream | null = null; let isWarned = false; if (hermesLogs) { @@ -206,7 +207,7 @@ export async function runHermesEmitBinaryCommand( } return new Promise((resolve, reject) => { hermesProcess.stdout.on("data", (data: Buffer) => { - logger.info(data.toString().trim()); + console.log(data.toString().trim()); }); hermesProcess.stderr.on("data", (data: Buffer) => { @@ -217,14 +218,14 @@ export async function runHermesEmitBinaryCommand( return; } isWarned = true; - logger.warning( - "⚠️ Hermes command executed successfully with some warnings. If you need full logs, use the --hermes-logs command.\n" + ui.status.warn( + "Hermes finished with warnings. Use --hermes-logs for the full log." ); }); hermesProcess.on("close", (exitCode: number, signal: string) => { if (hermesLogs && logFile) { - logger.success("📕 Done writing logs in output.log file."); + ui.status.ok("Wrote logs to output.log"); logFile.end(); } diff --git a/src/utils/stdout.ts b/src/utils/stdout.ts new file mode 100644 index 0000000..ce471a7 --- /dev/null +++ b/src/utils/stdout.ts @@ -0,0 +1,15 @@ +/** + * Redirect stdout to stderr until the returned restore fn is called. + * + * Used by `--json` command modes so stdout carries ONLY the final JSON result + * (safe for `$(... | jq)` capture in CI) while all diagnostics — logs, spinners, + * and child-process stdout (e.g. the RN bundler) — still stream to stderr. + */ +export function silenceStdout(): () => void { + const original = process.stdout.write.bind(process.stdout); + (process.stdout as any).write = (...args: any[]) => + (process.stderr.write as any)(...args); + return () => { + (process.stdout as any).write = original; + }; +} diff --git a/src/utils/table.ts b/src/utils/table.ts deleted file mode 100644 index 9771113..0000000 --- a/src/utils/table.ts +++ /dev/null @@ -1,260 +0,0 @@ -import chalk from "chalk"; - -export interface Column { - header: string; - value: (row: any) => any; -} - -export interface DetailField { - label: string; - value: (obj: any) => any; -} - -/** Solid outer frame + header separator. */ -const solid = (s: string) => chalk.gray(s); -/** Very light, dotted separator between rows. */ -const light = (s: string) => chalk.dim(chalk.gray(s)); -const headerStyle = chalk.bold.cyan; -/** Subdued style for box descriptions — dim + italic to read as fine print. */ -const descStyle = (s: string) => chalk.dim.italic(s); - -const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/; - -/** Render a Date as a compact, human-readable "YYYY-MM-DD HH:mm UTC" string. */ -function formatDate(d: Date): string { - const p = (n: number) => String(n).padStart(2, "0"); - return ( - `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ` + - `${p(d.getUTCHours())}:${p(d.getUTCMinutes())} UTC` - ); -} - -/** Format a single cell value into a plain (uncolored) string. */ -export function formatValue(value: any): string { - return cell(value); -} - -/** Format a value and apply its status color (for inline key/value rendering). */ -export function renderValue(value: any): string { - const plain = cell(value); - return colorize(plain, plain); -} - -/** Shared accent style for headers and labels. */ -export const accentStyle = headerStyle; - -/** Format a single cell value into a plain (uncolored) string. */ -function cell(value: any): string { - if (value === null || value === undefined || value === "") return "-"; - if (value instanceof Date) return formatDate(value); - if (typeof value === "boolean") return value ? "Yes" : "No"; - if (typeof value === "string" && ISO_DATE.test(value)) { - const d = new Date(value); - if (!isNaN(d.getTime())) return formatDate(d); - } - return String(value); -} - -function pad(text: string, width: number): string { - return text + " ".repeat(Math.max(0, width - text.length)); -} - -/** - * Apply color to a value based on its plain content. Color codes are - * zero-width, so this never affects column alignment as long as padding has - * already been applied to the plain text. - */ -function colorize(plain: string, padded: string): string { - const trimmed = plain.trim(); - if (trimmed === "-") return chalk.dim(padded); - if (trimmed === "Yes") return chalk.green(padded); - if (trimmed === "No") return chalk.dim(padded); - return padded; -} - -/** A solid horizontal rule (outer frame / header separator). */ -function rule( - left: string, - mid: string, - right: string, - widths: number[] -): string { - return solid(left + widths.map((w) => "─".repeat(w + 2)).join(mid) + right); -} - -/** A light dotted separator placed between data rows. Outer edges stay solid. */ -function rowSeparator(widths: number[]): string { - return ( - solid("├") + - widths.map((w) => light("╌".repeat(w + 2))).join(light("┼")) + - solid("┤") - ); -} - -/** - * Print rows as a bordered table, or as raw JSON when `json` is set. - * In JSON mode the original `rows` objects are emitted (not the formatted - * columns) so the output stays script-friendly. - */ -export function printTable( - rows: any[], - columns: Column[], - options: { json?: boolean; indent?: number } = {} -): void { - if (options.json) { - console.log(JSON.stringify(rows, null, 2)); - return; - } - const pre = " ".repeat(options.indent ?? 0); - const out = (line: string) => console.log(pre + line); - if (!rows.length) { - out(chalk.dim("No results.")); - return; - } - - const headers = columns.map((c) => c.header); - const data = rows.map((row) => columns.map((c) => cell(c.value(row)))); - const widths = headers.map((header, i) => - Math.max(header.length, ...data.map((row) => row[i].length)) - ); - - const v = solid("│"); - - out(rule("┌", "┬", "┐", widths)); - out( - v + - headers - .map((h, i) => headerStyle(" " + pad(h, widths[i]) + " ")) - .join(v) + - v - ); - out(rule("├", "┼", "┤", widths)); - data.forEach((row, idx) => { - if (idx > 0) out(rowSeparator(widths)); - out( - v + - row - .map((c, i) => colorize(c, " " + pad(c, widths[i]) + " ")) - .join(v) + - v - ); - }); - out(rule("└", "┴", "┘", widths)); -} - -/** - * Print a single object as an aligned key/value detail view, or raw JSON when - * `json` is set. - */ -export function printDetail( - obj: any, - fields: DetailField[], - options: { json?: boolean; indent?: number } = {} -): void { - if (options.json) { - console.log(JSON.stringify(obj, null, 2)); - return; - } - const pre = " ".repeat(options.indent ?? 0); - const labelWidth = Math.max(...fields.map((f) => f.label.length)); - for (const field of fields) { - const plain = cell(field.value(obj)); - console.log( - `${pre}${headerStyle(pad(field.label, labelWidth))} ${colorize(plain, plain)}` - ); - } -} - -/** Greedily wrap text into lines no wider than `width`. */ -function wrapText(text: string, width: number): string[] { - const lines: string[] = []; - let current = ""; - for (const word of text.split(/\s+/)) { - if (!current) current = word; - else if ((current + " " + word).length <= width) current += " " + word; - else { - lines.push(current); - current = word; - } - } - if (current) lines.push(current); - return lines; -} - -export interface BoxSpec { - obj: any; - fields: DetailField[]; - title?: string; - description?: string; -} - -/** - * Build a framed box as an array of equal-width lines: a title heading and an - * optional dim/italic description live inside the frame, above the aligned - * key/value rows. Returned (not printed) so boxes can be laid out side by side. - */ -function buildBox(spec: BoxSpec): { lines: string[]; width: number } { - const { obj, fields, title = "", description } = spec; - const labelWidth = Math.max(...fields.map((f) => f.label.length)); - const gap = " "; - const rows = fields.map((f) => { - const plain = cell(f.value(obj)); - return { label: f.label, plain, len: labelWidth + gap.length + plain.length }; - }); - - const target = Math.max(title.length, ...rows.map((r) => r.len), 24); - const descLines = description ? wrapText(description, target) : []; - const innerWidth = Math.max(target, ...descLines.map((l) => l.length)); - - const v = solid("│"); - const mk = (content: string, plainLen: number) => - `${v} ${content}${" ".repeat(Math.max(0, innerWidth - plainLen))} ${v}`; - - const lines: string[] = []; - lines.push(solid("┌" + "─".repeat(innerWidth + 2) + "┐")); - if (title) lines.push(mk(headerStyle(title), title.length)); - for (const dl of descLines) lines.push(mk(descStyle(dl), dl.length)); - if (title || descLines.length) lines.push(mk("", 0)); - for (const r of rows) { - const content = - headerStyle(pad(r.label, labelWidth)) + gap + colorize(r.plain, r.plain); - lines.push(mk(content, r.len)); - } - lines.push(solid("└" + "─".repeat(innerWidth + 2) + "┘")); - - return { lines, width: innerWidth + 4 }; -} - -/** Print a single framed box (title + optional description + key/value rows). */ -export function printBox( - obj: any, - fields: DetailField[], - options: { title?: string; description?: string; indent?: number } = {} -): void { - const pre = " ".repeat(options.indent ?? 0); - const { lines } = buildBox({ obj, fields, ...options }); - for (const line of lines) console.log(pre + line); -} - -/** - * Print several boxes side by side, top-aligned. Shorter boxes are padded with - * blank lines so the row stays aligned. - */ -export function printBoxes( - specs: BoxSpec[], - options: { gap?: number; indent?: number } = {} -): void { - const gap = options.gap ?? 3; - const pre = " ".repeat(options.indent ?? 0); - const boxes = specs.map(buildBox); - const height = Math.max(...boxes.map((b) => b.lines.length)); - const sep = " ".repeat(gap); - for (let i = 0; i < height; i++) { - console.log( - pre + - boxes - .map((b) => (i < b.lines.length ? b.lines[i] : " ".repeat(b.width))) - .join(sep) - ); - } -} diff --git a/src/utils/token-store.ts b/src/utils/token-store.ts index ed1a001..d682b5c 100644 --- a/src/utils/token-store.ts +++ b/src/utils/token-store.ts @@ -96,3 +96,14 @@ export function createDefaultTokenStore(): TokenStore { const filePath = path.join(os.homedir(), ".stallion", "token-store.json"); return new FileTokenStore(filePath); } + +/** Synchronous check for a stored CLI token (used by the welcome banner). */ +export function hasStoredToken(): boolean { + try { + const filePath = path.join(os.homedir(), ".stallion", "token-store.json"); + const data = JSON.parse(fs.readFileSync(filePath, "utf8")); + return Boolean(data?.cli?.token); + } catch { + return false; + } +} diff --git a/src/utils/ui.ts b/src/utils/ui.ts deleted file mode 100644 index 0f50942..0000000 --- a/src/utils/ui.ts +++ /dev/null @@ -1,83 +0,0 @@ -import chalk from "chalk"; -import { accentStyle, renderValue } from "@/utils/table"; - -/** - * Shared visual language for the CLI. Every command should compose its output - * from these primitives so the look stays consistent: - * - * ui.header("whoami") STALLION · whoami breadcrumb - * ui.section("User") ┃ USER accent-bar heading - * ui.keyValue([["Name", ...]]) indented, aligned key/values - * ui.hint("...") dim helper line - * ui.badge(true, ...) colored status pill - * - * Tables and boxes live in ./table and accept an `indent` so they line up - * under section headings (use INDENT). - */ - -/** Standard left indent for content under a section heading. */ -export const INDENT = 2; - -const pre = " ".repeat(INDENT); - -/** Top-of-command breadcrumb, e.g. `STALLION · whoami`. */ -export function header(command: string, subtitle?: string): void { - const crumb = chalk.dim("STALLION") + chalk.dim(" · ") + chalk.bold.white(command); - console.log("\n" + crumb + (subtitle ? chalk.dim(` ${subtitle}`) : "")); -} - -/** Accent-bar section heading, e.g. `┃ ORGANIZATIONS`. */ -export function section(title: string): void { - console.log("\n" + accentStyle("┃ " + title.toUpperCase())); -} - -/** Indented, aligned key/value block. Values get status coloring + formatting. */ -export function keyValue(rows: Array<[string, any]>): void { - const labelWidth = Math.max(...rows.map(([label]) => label.length)); - for (const [label, value] of rows) { - const paddedLabel = label + " ".repeat(labelWidth - label.length); - console.log(`${pre}${chalk.dim(paddedLabel)} ${renderValue(value)}`); - } -} - -/** A dim, secondary helper line (tips, file paths, pagination). */ -export function hint(text: string): void { - console.log(chalk.dim(text)); -} - -/** Plain free-text line under a section, indented to match key/values. */ -export function text(value: string): void { - console.log(pre + value); -} - -type BadgeKind = "ok" | "warn" | "danger" | "muted"; - -/** A small colored status pill, e.g. badge("active", "ok"). */ -export function badge(label: string, kind: BadgeKind = "muted"): string { - const styles: Record string> = { - ok: chalk.bgGreen.black, - warn: chalk.bgYellow.black, - danger: chalk.bgRed.white, - muted: chalk.bgGray.white, - }; - return styles[kind](` ${label} `); -} - -/** Success / failure / info status lines (consistent glyphs + spacing). */ -export const status = { - ok: (msg: string) => console.log(chalk.green("✓"), msg), - fail: (msg: string) => console.log(chalk.red("✗"), msg), - info: (msg: string) => console.log(chalk.cyan("ℹ"), msg), - warn: (msg: string) => console.log(chalk.yellow("⚠"), msg), -}; - -export const ui = { - INDENT, - header, - section, - keyValue, - hint, - text, - badge, - status, -}; From cde085a7f07d95cda063d704e5fdbec8e3505efe Mon Sep 17 00:00:00 2001 From: Maximus Decimus Meridius Date: Thu, 2 Jul 2026 01:14:42 +0530 Subject: [PATCH 03/13] chore: reverted testing endpoints --- src/api/config.ts | 2 +- src/api/endpoints.ts | 2 +- src/utils/common.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/config.ts b/src/api/config.ts index 2087381..057e463 100644 --- a/src/api/config.ts +++ b/src/api/config.ts @@ -1,6 +1,6 @@ export const CONFIG = { API: { - BASE_URL: "http://localhost:8000/api/v1", + BASE_URL: "https://api.stalliontech.io/api/v1", }, BUNDLE_EXTENSION: ".stallionsigned", } as const; diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index 606f6ca..19f001d 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -1,5 +1,5 @@ export const ENDPOINTS = { - CLI_LOGIN: "http://localhost:3000/dashboard/cli/user", + CLI_LOGIN: "https://console.stalliontech.io/dashboard/cli/user", USER: { VERIFY: "/auth/user-profile", }, diff --git a/src/utils/common.ts b/src/utils/common.ts index c39195c..d205f33 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -2,8 +2,8 @@ import { CONFIG } from "@/api/config"; export function getApiBaseUrl(region: string): string { if (!region) { - return `http://localhost:8000/api/v1`; + return CONFIG.API.BASE_URL; } - return `http://localhost:8000/api/v1`; + return `https://api-${region}.stalliontech.io/api/v1`; } From 641bec48080c8095b696964274c330c8bc28fefe Mon Sep 17 00:00:00 2001 From: Maximus Decimus Meridius Date: Sat, 4 Jul 2026 18:27:31 +0530 Subject: [PATCH 04/13] feat(cli): list filters & limits, hardened --json contract, leaner output --- src/api/user-client.ts | 9 ++- src/commands/context.command.ts | 11 ++-- src/commands/list-buckets.command.ts | 36 +++++++++-- src/commands/list-bundles.command.ts | 86 ++++++++++++++++++++------ src/commands/list-projects.command.ts | 10 ++- src/commands/list-releases.command.ts | 49 ++++++++++++--- src/commands/publish-bundle.command.ts | 17 +++-- src/commands/release-info.command.ts | 11 ++++ src/commands/whoami.command.ts | 15 +---- src/index.ts | 9 ++- src/ui/ansi.ts | 66 ++++++++++++++++++++ src/ui/format.ts | 25 ++++++-- src/ui/table.ts | 45 ++++++++++++-- src/utils/errors.ts | 17 +++++ src/utils/list.ts | 66 ++++++++++++++++++++ src/utils/prompt.ts | 23 ++++--- src/utils/stdout.ts | 17 ++++- 17 files changed, 433 insertions(+), 79 deletions(-) create mode 100644 src/utils/errors.ts create mode 100644 src/utils/list.ts diff --git a/src/api/user-client.ts b/src/api/user-client.ts index 931dc93..affa7e2 100644 --- a/src/api/user-client.ts +++ b/src/api/user-client.ts @@ -133,8 +133,15 @@ export async function resolveProjectId( projectId?: string, opts: { useSaved?: boolean } = {} ): Promise { - if (projectId) return projectId; const useSaved = opts.useSaved !== false; + + // Explicit --project-id is passed through as-is: the API authorizes every + // request, so pre-checking here would only add a round-trip. An id the user + // can't access fails at the first API call. + if (projectId) { + return String(projectId); + } + if (useSaved) { const ctx = getContext(); if (ctx.projectId && ctx.orgId === orgId) { diff --git a/src/commands/context.command.ts b/src/commands/context.command.ts index 419479b..497bf63 100644 --- a/src/commands/context.command.ts +++ b/src/commands/context.command.ts @@ -23,13 +23,16 @@ export class ContextCommand extends BaseCommand { } const ctx = getContext(); - if (!ctx.orgId && !ctx.projectId) { - ui.status.info('No context set. Run "stallion use" to set one.'); + + // JSON mode always emits a parseable object on stdout — including `{}` when + // no context is set — so scripts can rely on the output shape. + if (options.json) { + console.log(JSON.stringify(ctx ?? {}, null, 2)); return; } - if (options.json) { - console.log(JSON.stringify(ctx, null, 2)); + if (!ctx.orgId && !ctx.projectId) { + ui.status.info('No context set. Run "stallion use" to set one.'); return; } diff --git a/src/commands/list-buckets.command.ts b/src/commands/list-buckets.command.ts index 46ce830..f1a1d17 100644 --- a/src/commands/list-buckets.command.ts +++ b/src/commands/list-buckets.command.ts @@ -11,10 +11,13 @@ import { } from "@/api/user-client"; import { ui } from "@/ui"; import { printBoard, renderValue } from "@/ui"; +import { CONSOLE_URL, MAX_LIST_LIMIT, resolveLimit } from "@/utils/list"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, { name: "project-id", description: "Project id (prompts if omitted; required with --ci-token)", required: false }, + { name: "name", description: "Filter buckets by name (case-insensitive substring)", required: false }, + { name: "limit", description: "Max buckets to show (default 15, max 30)", required: false }, { name: "ci-token", description: "CI token (non-interactive; requires --project-id)", required: false }, { name: "json", description: "Output raw JSON", required: false }, ]; @@ -29,29 +32,46 @@ const expectedOptions: CommandOption[] = [ export class ListBucketsCommand extends BaseCommand { async execute(options: Record): Promise { const json = Boolean(options.json); - const buckets = await this.fetchBuckets(options); + const limit = resolveLimit(options.limit); + const buckets = await this.fetchBuckets(options, limit); if (json) { console.log(JSON.stringify(buckets, null, 2)); return; } + ui.section("Buckets"); if (!buckets.length) { - ui.hint(" No buckets."); + ui.hint( + options.name ? ` No buckets match "${options.name}".` : " No buckets." + ); return; } printBoard( buckets, [ { header: "NAME", render: (b) => renderValue(b.name) }, - { header: "BUCKET ID", render: (b) => renderValue(b.id ?? b._id) }, { header: "UPDATED", render: (b) => renderValue(b.updatedAt) }, ], { indent: ui.INDENT, spaced: true } ); + + // The server returns at most `limit`; if we got exactly that many there + // may be more (narrow with --name, raise --limit, or use the console). + ui.blank(); + if (buckets.length >= limit) { + ui.hint( + ` Showing the ${limit} most recently updated. Narrow with --name, raise with --limit (max ${MAX_LIST_LIMIT}), or see all → ${CONSOLE_URL}` + ); + } else { + ui.hint(` ${buckets.length} bucket${buckets.length === 1 ? "" : "s"}`); + } } - private async fetchBuckets(options: Record): Promise { + private async fetchBuckets( + options: Record, + limit: number + ): Promise { if (options.ciToken) { if (!options.projectId) { throw new Error("--project-id is required with --ci-token"); @@ -60,6 +80,8 @@ export class ListBucketsCommand extends BaseCommand { const res = await progress("Fetching buckets", () => client.post<{ data: any[] }>(ENDPOINTS.BUCKET.CI_LIST, { projectId: options.projectId, + name: options.name, + limit, }) ); return res?.data ?? []; @@ -69,7 +91,11 @@ export class ListBucketsCommand extends BaseCommand { const projectId = await resolveProjectId(orgId, options.projectId); const client = await createUserApiClient(region); const res = await progress("Fetching buckets", () => - client.post<{ data: any[] }>(ENDPOINTS.BUCKET.LIST, { projectId }) + client.post<{ data: any[] }>(ENDPOINTS.BUCKET.LIST, { + projectId, + name: options.name, + limit, + }) ); return res?.data ?? []; } diff --git a/src/commands/list-bundles.command.ts b/src/commands/list-bundles.command.ts index 7ffb0ca..e383ba4 100644 --- a/src/commands/list-bundles.command.ts +++ b/src/commands/list-bundles.command.ts @@ -12,13 +12,16 @@ import { resolveProjectId, } from "@/api/user-client"; import { BoardColumn, printBoard, renderValue } from "@/ui"; +import { CONSOLE_URL, MAX_LIST_LIMIT, resolveLimit } from "@/utils/list"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, { name: "project-id", description: "Project id (prompts if omitted; required with --ci-token)", required: false }, - { name: "bucket-id", description: "Bucket id (prompts if omitted; required with --ci-token)", required: false }, + { name: "bucket", description: "Bucket name (prompts if omitted; --bucket or --bucket-id required with --ci-token)", required: false }, + { name: "bucket-id", description: "Bucket id (alternative to --bucket)", required: false }, { name: "platform", description: "Filter by platform (android|ios)", required: false }, - { name: "ci-token", description: "CI token (non-interactive; requires --project-id and --bucket-id)", required: false }, + { name: "limit", description: "Max bundles to show (default 15, max 30)", required: false }, + { name: "ci-token", description: "CI token (non-interactive; requires --project-id and --bucket/--bucket-id)", required: false }, { name: "json", description: "Output raw JSON", required: false }, ]; @@ -26,13 +29,20 @@ const bundleColumns: BoardColumn[] = [ { header: "VERSION", render: (b) => renderValue(b.version) }, { header: "PLATFORM", render: (b) => renderValue(b.platform) }, { header: "PROMOTED", render: (b) => renderValue(Boolean(b.isPromoted)) }, + { header: "CREATED", render: (b) => renderValue(b.createdAt) }, { header: "AUTHOR", - render: (b) => renderValue(b.author?.email ?? b.author?.fullName), + render: (b) => renderValue(b.author?.fullName ?? b.author?.email), + }, + // Short hash for quick row identification; the full (copy-pasteable) hash is + // printed below the table since 64 chars is too wide for a scannable column. + { + header: "HASH", + render: (b) => { + const h = b.sha256Checksum; + return renderValue(h ? `${String(h).slice(0, 10)}…` : h); + }, }, - { header: "SIZE", render: (b) => renderValue(b.size), align: "right" }, - { header: "HASH", render: (b) => renderValue(b.sha256Checksum) }, - { header: "CREATED", render: (b) => renderValue(b.createdAt) }, ]; @Command({ @@ -45,39 +55,66 @@ const bundleColumns: BoardColumn[] = [ export class ListBundlesCommand extends BaseCommand { async execute(options: Record): Promise { const json = Boolean(options.json); - let bundles = await this.fetchBundles(options); - - if (options.platform) { - const platform = String(options.platform).toLowerCase(); - bundles = bundles.filter( - (b: any) => String(b.platform).toLowerCase() === platform - ); - } + const limit = resolveLimit(options.limit); + const bundles = await this.fetchBundles(options, limit); if (json) { console.log(JSON.stringify(bundles, null, 2)); return; } - ui.section(options.platform ? `Bundles · ${options.platform}` : "Bundles"); + + const label = options.platform + ? String(options.platform).toLowerCase() + : null; + + ui.section(label ? `Bundles · ${label}` : "Bundles"); if (!bundles.length) { - ui.hint(" No bundles."); + ui.hint(label ? ` No ${label} bundles.` : " No bundles."); return; } + printBoard(bundles, bundleColumns, { indent: ui.INDENT, spaced: true }); + + // Full hashes as plain, copy-pasteable lines (for `release-bundle --hash`), + // keyed by version so they map back to the table above. + const withHash = bundles.filter((b: any) => b.sha256Checksum); + if (withHash.length) { + ui.section("Hashes"); + ui.keyValue(withHash.map((b: any) => [`v${b.version}`, b.sha256Checksum])); + } + + // The server returns at most `limit`; if we got exactly that many there + // may be more (narrow with --platform, raise --limit, or use the console). + ui.blank(); + if (bundles.length >= limit) { + ui.hint( + ` Showing the ${limit} most recent bundles. Narrow with --platform, raise with --limit (max ${MAX_LIST_LIMIT}), or see all → ${CONSOLE_URL}` + ); + } else { + ui.hint(` ${bundles.length} bundle${bundles.length === 1 ? "" : "s"}`); + } } - private async fetchBundles(options: Record): Promise { + private async fetchBundles( + options: Record, + limit: number + ): Promise { if (options.ciToken) { - if (!options.projectId || !options.bucketId) { + if (!options.projectId || (!options.bucketId && !options.bucket)) { throw new Error( - "--project-id and --bucket-id are required with --ci-token" + "--project-id and one of --bucket / --bucket-id are required with --ci-token" ); } const client = createCiApiClient(options.ciToken); + // Send name or id straight through — the API resolves the name against + // the project and authorizes it. const res = await progress("Fetching bundles", () => client.post<{ data: any[] }>(ENDPOINTS.BUNDLE.CI_LIST, { projectId: options.projectId, bucketId: options.bucketId, + bucketName: options.bucket, + platform: options.platform, + limit, }) ); return res?.data ?? []; @@ -85,12 +122,21 @@ export class ListBundlesCommand extends BaseCommand { const { orgId, region } = await resolveOrgContext(options.orgId); const projectId = await resolveProjectId(orgId, options.projectId); - const bucketId = await resolveBucketId(region, projectId, options.bucketId); + // Explicit --bucket / --bucket-id go straight to the server (it resolves + + // authorizes the name); only prompt with the picker when neither is given. + let bucketId = options.bucketId as string | undefined; + const bucketName = options.bucket as string | undefined; + if (!bucketId && !bucketName) { + bucketId = await resolveBucketId(region, projectId); + } const client = await createUserApiClient(region); const res = await progress("Fetching bundles", () => client.post<{ data: any[] }>(ENDPOINTS.BUNDLE.LIST, { projectId, bucketId, + bucketName, + platform: options.platform, + limit, }) ); return res?.data ?? []; diff --git a/src/commands/list-projects.command.ts b/src/commands/list-projects.command.ts index 582e24c..655f42e 100644 --- a/src/commands/list-projects.command.ts +++ b/src/commands/list-projects.command.ts @@ -6,6 +6,7 @@ import { ENDPOINTS } from "@/api/endpoints"; import { createUserApiClient, resolveOrgContext } from "@/api/user-client"; import { ui } from "@/ui"; import { getContext } from "@/utils/context-store"; +import { capRecent, printListFooter } from "@/utils/list"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, @@ -36,9 +37,13 @@ export class ListProjectsCommand extends BaseCommand { } const ctx = getContext(); + const { shown, total, capped } = capRecent( + projects, + (p: any) => p.createdAt ?? p.updatedAt + ); ui.section("projects"); ui.numbered( - projects.map((p: any) => { + shown.map((p: any) => { const platforms = [p.androidEnabled ? "android" : null, p.iosEnabled ? "ios" : null] .filter(Boolean) @@ -51,7 +56,6 @@ export class ListProjectsCommand extends BaseCommand { }), { indent: ui.INDENT } ); - ui.blank(); - ui.hint(` ${projects.length} project${projects.length === 1 ? "" : "s"}`); + printListFooter("project", shown.length, total, capped); } } diff --git a/src/commands/list-releases.command.ts b/src/commands/list-releases.command.ts index 976812a..fb18714 100644 --- a/src/commands/list-releases.command.ts +++ b/src/commands/list-releases.command.ts @@ -13,12 +13,14 @@ import { } from "@/api/user-client"; import { ui } from "@/ui"; import { bar, glyph, printBoard, renderValue, theme } from "@/ui"; +import { CONSOLE_URL, MAX_LIST_LIMIT, resolveLimit } from "@/utils/list"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, { name: "project-id", description: "Project id (prompts if omitted; required with --ci-token)", required: false }, { name: "platform", description: "Platform (android|ios); prompts if omitted", required: false }, { name: "app-version", description: "App version; prompts if omitted (required with --ci-token)", required: false }, + { name: "limit", description: "Max releases to show (default 15, max 30)", required: false }, { name: "ci-token", description: "CI token (non-interactive; requires --project-id, --platform, --app-version)", required: false }, { name: "json", description: "Output raw JSON", required: false }, ]; @@ -32,8 +34,15 @@ const expectedOptions: CommandOption[] = [ @ValidateUser() export class ListReleasesCommand extends BaseCommand { async execute(options: Record): Promise { + // An app version only identifies releases together with a platform — + // half-specifying is almost certainly a mistake, so fail fast. + if (options.appVersion && !options.platform) { + throw new Error("--platform is required when --app-version is provided"); + } + const json = Boolean(options.json); - const { appVersion, releases } = await this.fetchReleases(options); + const limit = resolveLimit(options.limit); + const { appVersion, releases } = await this.fetchReleases(options, limit); if (json) { console.log(JSON.stringify(releases, null, 2)); @@ -50,21 +59,30 @@ export class ListReleasesCommand extends BaseCommand { releases, [ { - header: "VERSION", + header: "Version", render: (r) => { const v = `v${r.bundleVersion ?? "-"}`; return r === releases[0] ? theme.accent(v) : v; }, }, { - header: "ROLLOUT", + header: "Released by", + render: (r) => renderValue(r.author?.email ?? r.author?.fullName), + }, + { + header: "Rollout", render: (r) => { - const pct = r.isRolledBack ? 0 : 100; + const pct = + r.rolloutPercent != null + ? Number(r.rolloutPercent) + : r.isRolledBack + ? 0 + : 100; return `${bar(pct)} ${String(pct).padStart(3)}%`; }, }, { - header: "STATUS", + header: "Status", render: (r) => { if (r.isRolledBack) return `${theme.danger(glyph.cross)} rolled back`; @@ -72,18 +90,27 @@ export class ListReleasesCommand extends BaseCommand { return `${theme.ok(glyph.tick)} live`; }, }, - { header: "RELEASE ID", render: (r) => renderValue(r.id) }, + { header: "Release note", render: (r) => renderValue(r.releaseNote) }, + { header: "Release ID", render: (r) => renderValue(r.id) }, ], { indent: ui.INDENT, spaced: true } ); + ui.blank(); - ui.hint( - ` ${releases.length} release${releases.length === 1 ? "" : "s"} · v${appVersion}` - ); + if (releases.length >= limit) { + ui.hint( + ` Showing the ${limit} most recent releases. Raise with --limit (max ${MAX_LIST_LIMIT}), or see all → ${CONSOLE_URL}` + ); + } else { + ui.hint( + ` ${releases.length} release${releases.length === 1 ? "" : "s"} · v${appVersion}` + ); + } } private async fetchReleases( - options: Record + options: Record, + limit: number ): Promise<{ appVersion: string; releases: any[] }> { if (options.ciToken) { const { projectId, platform, appVersion } = options; @@ -98,6 +125,7 @@ export class ListReleasesCommand extends BaseCommand { projectId, platform, appVersion, + limit, }) ); return { appVersion, releases: res?.data ?? [] }; @@ -138,6 +166,7 @@ export class ListReleasesCommand extends BaseCommand { projectId, platform, appVersion, + limit, }) ); return { appVersion, releases: res?.data ?? [] }; diff --git a/src/commands/publish-bundle.command.ts b/src/commands/publish-bundle.command.ts index bb11f07..25bdc49 100644 --- a/src/commands/publish-bundle.command.ts +++ b/src/commands/publish-bundle.command.ts @@ -232,7 +232,7 @@ export class PublishBundleCommand extends BaseCommand { // poll until it's queryable, otherwise a following release-bundle can race // ahead into "bundle not found". Best-effort — it never fails the publish. let registered = true; - const { hash, version } = await progress( + const { hash, version, bucketCreated, bucketName } = await progress( chalk.white("Publishing bundle"), async (updateProgress) => { const result = await this.uploadBundle( @@ -263,11 +263,16 @@ export class PublishBundleCommand extends BaseCommand { if (json) { restoreStdout?.(); - console.log(JSON.stringify({ version, hash, platform, uploadPath })); + console.log( + JSON.stringify({ version, hash, platform, uploadPath, bucketCreated }) + ); } else { + if (bucketCreated) { + ui.status.ok(`Created new bucket "${bucketName}"`); + } ui.status.ok(`Published version ${version}`); ui.keyValue([ - ["Hash", hash], + ["Published bundle hash", hash], ["Platform", platform], ["Upload Path", uploadPath], ]); @@ -360,9 +365,13 @@ export class PublishBundleCommand extends BaseCommand { const projectId = signedUrlResp?.meta?.projectId ? String(signedUrlResp.meta.projectId) : null; + // The server creates the bucket on the fly when `uploadPath` is new; it + // signals that via meta.bucketCreated so we can tell the user. + const bucketCreated = Boolean(signedUrlResp?.meta?.bucketCreated); + const bucketName = signedUrlResp?.meta?.bucketName ?? uploadPath; await client.putWithProgress(url, filePath, "application/zip", onProgress); - return { hash, version, projectId }; + return { hash, version, projectId, bucketCreated, bucketName }; } catch (e: any) { if (e.toString().includes("SignatureDoesNotMatch")) { throw "Error uploading bundle. Signature does not match."; diff --git a/src/commands/release-info.command.ts b/src/commands/release-info.command.ts index 94bd253..5bb72e2 100644 --- a/src/commands/release-info.command.ts +++ b/src/commands/release-info.command.ts @@ -34,6 +34,17 @@ const expectedOptions: CommandOption[] = [ @ValidateUser() export class ReleaseInfoCommand extends BaseCommand { async execute(options: Record): Promise { + // Flag dependency ladder: a release is identified by + // (platform, appVersion, promotedId) — partial combos are ambiguous. + if (options.appVersion && !options.platform) { + throw new Error("--platform is required when --app-version is provided"); + } + if (options.promotedId && (!options.appVersion || !options.platform)) { + throw new Error( + "--platform and --app-version are required when --promoted-id is provided" + ); + } + const json = Boolean(options.json); const detail = await this.fetchDetail(options); if (!detail) return; diff --git a/src/commands/whoami.command.ts b/src/commands/whoami.command.ts index f7c1f0c..86cf75c 100644 --- a/src/commands/whoami.command.ts +++ b/src/commands/whoami.command.ts @@ -1,5 +1,5 @@ import { BaseCommand } from "@command-line/base.command"; -import { Command, CommandOption } from "@decorators/command.decorator"; +import { Command } from "@decorators/command.decorator"; import { ValidateUser } from "@decorators/validate-user.decorator"; import { progress } from "@/utils/progress"; import { ENDPOINTS } from "@/api/endpoints"; @@ -7,20 +7,14 @@ import { createUserApiClient, fetchOrgs } from "@/api/user-client"; import { ui } from "@/ui"; import { getContext } from "@/utils/context-store"; -const expectedOptions: CommandOption[] = [ - { name: "json", description: "Output raw JSON", required: false }, -]; - @Command({ name: "whoami", description: "Show the logged-in user and their organizations", alias: "me", - options: expectedOptions, }) @ValidateUser() export class WhoamiCommand extends BaseCommand { - async execute(options: Record): Promise { - const json = Boolean(options.json); + async execute(): Promise { const client = await createUserApiClient(); const { profile, orgs } = await progress("Fetching profile", async () => { @@ -29,11 +23,6 @@ export class WhoamiCommand extends BaseCommand { return { profile: profileRes?.data, orgs: orgsList }; }); - if (json) { - console.log(JSON.stringify({ profile, orgs }, null, 2)); - return; - } - const ctx = getContext(); ui.pipeline([ { status: "done", label: "session", value: "authorized" }, diff --git a/src/index.ts b/src/index.ts index 6dbdb2c..8da9772 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,8 @@ import { showBanner, showWelcome } from "@/ui"; import { ui } from "@/ui"; import { getVersion } from "@utils/version"; import { normalizeOptions } from "@utils/normalize"; +import { restoreStdout } from "@utils/stdout"; +import { mapServerError } from "@utils/errors"; import { rimraf } from "rimraf"; import fs from "fs"; @@ -59,7 +61,12 @@ getCommands().forEach((options, name) => { await registry.executeCommand(name, opts); } catch (error) { - const message = error instanceof Error ? error.message : "Unknown error"; + // A command may have silenced stdout for --json; undo that first so the + // error object below reaches real stdout, not stderr. + restoreStdout(); + const message = mapServerError( + error instanceof Error ? error.message : "Unknown error" + ); // In JSON mode, surface the failure as JSON on stdout so `jq` consumers // get a parseable error object; the human line still goes to stderr. if (opts.json) { diff --git a/src/ui/ansi.ts b/src/ui/ansi.ts index 0f689e3..b84386e 100644 --- a/src/ui/ansi.ts +++ b/src/ui/ansi.ts @@ -24,3 +24,69 @@ export function centerVisible(text: string, width: number): string { const left = Math.floor(total / 2); return " ".repeat(left) + text + " ".repeat(total - left); } + +/** + * Truncate a (possibly colored) string to at most `width` printable columns, + * appending an ellipsis when clipped. SGR escape codes are copied through (and + * reset at the end) so color/formatting never bleeds past the cut. + */ +export function truncateVisible(text: string, width: number): string { + if (width <= 0) return ""; + if (visibleLength(text) <= width) return text; + + const budget = Math.max(0, width - 1); // leave one column for the ellipsis + let out = ""; + let count = 0; + let sawAnsi = false; + let i = 0; + while (i < text.length && count < budget) { + const m = text.slice(i).match(/^\[[0-9;]*m/); + if (m) { + out += m[0]; + sawAnsi = true; + i += m[0].length; + continue; + } + const ch = [...text.slice(i)][0]; // one visible char (surrogate-safe) + out += ch; + count += 1; + i += ch.length; + } + out += "…"; + if (sawAnsi) out += ""; + return out; +} + +/** + * Wrap a string to `width` printable columns, breaking on spaces where possible + * and hard-breaking over-long tokens (e.g. a 64-char hash). Returns one entry + * per line. Color is preserved when the text fits on a single line; multi-line + * (wrapped) output is emitted as plain text — in practice only long, uncolored + * cells ever wrap. + */ +export function wrapVisible(text: string, width: number): string[] { + if (width <= 0) return [text]; + const chars = [...stripAnsi(text)]; + if (chars.length <= width) return [text]; + + const lines: string[] = []; + let start = 0; + while (start < chars.length) { + let end = Math.min(start + width, chars.length); + if (end < chars.length) { + // prefer breaking at the last space inside the window + let brk = -1; + for (let k = end; k > start; k--) { + if (chars[k] === " ") { + brk = k; + break; + } + } + if (brk > start) end = brk; + } + lines.push(chars.slice(start, end).join("").trimEnd()); + start = end; + while (start < chars.length && chars[start] === " ") start++; // skip break spaces + } + return lines; +} diff --git a/src/ui/format.ts b/src/ui/format.ts index fe67410..f16f064 100644 --- a/src/ui/format.ts +++ b/src/ui/format.ts @@ -2,12 +2,27 @@ import { theme } from "./theme"; const ISO_DATE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/; +// Fixed en-US locale so the format is stable across machines; timezone is left +// unset so it renders in the machine's LOCAL time. Human-facing only — --json +// emits the raw ISO/UTC string (it never goes through this formatter). +const DATE_FMT = new Intl.DateTimeFormat("en-US", { + month: "long", + day: "numeric", + year: "numeric", +}); +const TIME_FMT = new Intl.DateTimeFormat("en-US", { + hour: "numeric", + minute: "2-digit", + hour12: true, +}); + +// e.g. "July 2, 2026 2:20 PM" (local time). function formatDate(d: Date): string { - const p = (n: number) => String(n).padStart(2, "0"); - return ( - `${d.getUTCFullYear()}-${p(d.getUTCMonth() + 1)}-${p(d.getUTCDate())} ` + - `${p(d.getUTCHours())}:${p(d.getUTCMinutes())} UTC` - ); + try { + return `${DATE_FMT.format(d)} ${TIME_FMT.format(d)}`; + } catch { + return d.toISOString(); + } } /** Plain (uncolored) string for a cell value. */ diff --git a/src/ui/table.ts b/src/ui/table.ts index 707bcf8..defd61d 100644 --- a/src/ui/table.ts +++ b/src/ui/table.ts @@ -1,6 +1,6 @@ import { glyph, space, theme } from "./theme"; import { colorizeCell, formatValue, pad } from "./format"; -import { padVisible, visibleLength } from "./ansi"; +import { padVisible, truncateVisible, visibleLength, wrapVisible } from "./ansi"; export interface Column { header: string; @@ -35,6 +35,29 @@ export function printBoard( ) ); + // Responsive: if the natural table is wider than the terminal, shrink the + // widest columns (down to a floor) so the frame still fits. Cells wider than + // their (reduced) column are ellipsized. Non-TTY (piped) output uses a wide + // fallback so redirected/`| less` output isn't needlessly clipped. + const n = columns.length; + const termWidth = process.stdout.columns || 120; + const overhead = (options.indent ?? 0) + (n + 1) + n * 2; // borders + padding + const budget = termWidth - overhead; + const MIN = 4; + const sum = () => widths.reduce((a, b) => a + b, 0); + while (sum() > budget) { + let idx = -1; + let max = MIN; + widths.forEach((w, i) => { + if (w > max) { + max = w; + idx = i; + } + }); + if (idx === -1) break; // every column at the floor — accept overflow + widths[idx] -= 1; + } + const rule = (l: string, m: string, r: string) => B(l + widths.map((w) => "─".repeat(w + 2)).join(m) + r); @@ -43,10 +66,11 @@ export function printBoard( vals .map((v, i) => { const w = widths[i]; + const t = truncateVisible(v, w); const cell = columns[i].align === "right" - ? " ".repeat(Math.max(0, w - visibleLength(v))) + v - : padVisible(v, w); + ? " ".repeat(Math.max(0, w - visibleLength(t))) + t + : padVisible(t, w); return " " + cell + " "; }) .join(B(glyph.v)) + @@ -55,12 +79,23 @@ export function printBoard( // Blank padded row used to space data rows apart. const spacer = rowLine(columns.map(() => "")); + // A row whose cells exceed their column width wraps onto extra lines instead + // of being clipped — each cell is wrapped to its width and the tallest cell + // sets the row height; shorter cells pad with blanks. + const drawRow = (vals: string[]) => { + const perCell = vals.map((v, i) => wrapVisible(v, widths[i])); + const height = Math.max(1, ...perCell.map((l) => l.length)); + for (let k = 0; k < height; k++) { + console.log(pre + rowLine(perCell.map((lines) => lines[k] ?? ""))); + } + }; + console.log(pre + rule("┌", "┬", "┐")); - console.log(pre + rowLine(columns.map((c) => theme.dim(c.header)))); + drawRow(columns.map((c) => theme.dim(c.header))); console.log(pre + rule("├", "┼", "┤")); cells.forEach((row, idx) => { if (idx > 0 && options.spaced) console.log(pre + spacer); - console.log(pre + rowLine(row)); + drawRow(row); }); console.log(pre + rule("└", "┴", "┘")); } diff --git a/src/utils/errors.ts b/src/utils/errors.ts new file mode 100644 index 0000000..564d21d --- /dev/null +++ b/src/utils/errors.ts @@ -0,0 +1,17 @@ +/** + * Map terse API errors to actionable CLI messages. + * + * A bare "access denied" means the logged-in account can't read the requested + * org/project — most often a mistyped or foreign --project-id/--org-id, since + * the CLI passes explicit ids through without pre-validation. + */ +export function mapServerError(message: string): string { + if (/^access denied\.?$/i.test(message.trim())) { + return [ + "Access denied. The org/project id may be wrong, belong to a different", + 'organization, or your account may not have access. Run "stallion use"', + "to pick a valid org and project, or verify --org-id/--project-id.", + ].join(" "); + } + return message; +} diff --git a/src/utils/list.ts b/src/utils/list.ts new file mode 100644 index 0000000..c9424dc --- /dev/null +++ b/src/utils/list.ts @@ -0,0 +1,66 @@ +import { ui } from "@/ui"; + +/** + * Listing commands cap output to the most-recent N rows so a project with + * hundreds of buckets/bundles/releases doesn't dump a wall of text. The full + * set lives in the web console (a link is shown when the list is capped). + */ +export const LIST_LIMIT = 15; + +/** Server-enforced ceiling for --limit. */ +export const MAX_LIST_LIMIT = 30; + +/** Web console — where the full, unbounded lists live. */ +export const CONSOLE_URL = "https://console.stalliontech.io"; + +/** + * Resolve a --limit flag value: default LIST_LIMIT, clamped to MAX_LIST_LIMIT + * (the server enforces the same ceiling; clamping here avoids a 400 round-trip). + */ +export function resolveLimit(value: unknown): number { + const n = Number(value); + if (!Number.isFinite(n) || n <= 0) return LIST_LIMIT; + return Math.min(Math.floor(n), MAX_LIST_LIMIT); +} + +/** + * Shared footer for listing commands: a count line, plus (when the list was + * capped) a pointer to the console for the full set. Diagnostics only, so it + * rides on stderr like other hints and never touches stdout data. + */ +export function printListFooter( + nounSingular: string, + shown: number, + total: number, + capped: boolean +): void { + const plural = total === 1 ? nounSingular : `${nounSingular}s`; + ui.blank(); + if (capped) { + ui.hint(` Showing ${shown} of ${total} most recent ${plural}.`); + ui.hint(` See all ${plural} in the console → ${CONSOLE_URL}`); + } else { + ui.hint(` ${total} ${plural}`); + } +} + +/** + * Sort `items` newest-first (by the given date accessor, when provided) and + * return the most-recent `LIST_LIMIT`, plus the untrimmed total so callers can + * show "Showing 15 of N". + */ +export function capRecent( + items: T[], + getDate?: (item: T) => string | number | Date | null | undefined +): { shown: T[]; total: number; capped: boolean } { + const total = items.length; + let ordered = items; + if (getDate) { + ordered = [...items].sort( + (a, b) => + new Date(getDate(b) ?? 0).getTime() - new Date(getDate(a) ?? 0).getTime() + ); + } + const shown = ordered.slice(0, LIST_LIMIT); + return { shown, total, capped: total > shown.length }; +} diff --git a/src/utils/prompt.ts b/src/utils/prompt.ts index 68eb6fd..39e0716 100644 --- a/src/utils/prompt.ts +++ b/src/utils/prompt.ts @@ -27,6 +27,9 @@ const theme = { }, } as const; +/** Interactive prompts render to stderr so stdout stays clean for --json/jq. */ +const PROMPT_CTX = { output: process.stderr }; + /** Lists longer than this switch to a type-ahead (filterable) prompt. */ const SEARCH_THRESHOLD = 8; @@ -37,7 +40,7 @@ export async function promptText( message: string, defaultValue?: string ): Promise { - return input({ message, default: defaultValue, theme }); + return input({ message, default: defaultValue, theme }, PROMPT_CTX); } /** @@ -47,7 +50,7 @@ export async function promptConfirm( message: string, defaultValue: boolean = true ): Promise { - return confirm({ message, default: defaultValue, theme }); + return confirm({ message, default: defaultValue, theme }, PROMPT_CTX); } /** @@ -72,9 +75,12 @@ export async function promptSelect( ); }, theme, - }); + }, PROMPT_CTX); } - return select({ message, choices, pageSize: 12, loop: false, theme }); + return select( + { message, choices, pageSize: 12, loop: false, theme }, + PROMPT_CTX + ); } /** @@ -84,14 +90,17 @@ export async function promptMultiSelect( message: string, choices: Array> ): Promise { - return checkbox({ message, choices, pageSize: 12, loop: false, theme }); + return checkbox( + { message, choices, pageSize: 12, loop: false, theme }, + PROMPT_CTX + ); } /** * Prompt for a password */ export async function promptPassword(message: string): Promise { - return password({ message, mask: "*", theme }); + return password({ message, mask: "*", theme }, PROMPT_CTX); } /** @@ -101,6 +110,6 @@ export async function promptNumber( message: string, defaultValue?: number ): Promise { - const answer = await number({ message, default: defaultValue, theme }); + const answer = await number({ message, default: defaultValue, theme }, PROMPT_CTX); return answer ?? 0; } diff --git a/src/utils/stdout.ts b/src/utils/stdout.ts index ce471a7..dd07bbd 100644 --- a/src/utils/stdout.ts +++ b/src/utils/stdout.ts @@ -5,11 +5,26 @@ * (safe for `$(... | jq)` capture in CI) while all diagnostics — logs, spinners, * and child-process stdout (e.g. the RN bundler) — still stream to stderr. */ +let activeRestore: (() => void) | null = null; + export function silenceStdout(): () => void { const original = process.stdout.write.bind(process.stdout); (process.stdout as any).write = (...args: any[]) => (process.stderr.write as any)(...args); - return () => { + const restore = () => { (process.stdout as any).write = original; + if (activeRestore === restore) activeRestore = null; }; + activeRestore = restore; + return restore; +} + +/** + * Restore stdout if it is currently silenced (idempotent). The top-level error + * handler calls this before emitting the `--json` error object, so a failure + * mid-command (which never reaches the command's own restore) still surfaces + * `{"error":...}` on real stdout for `jq`/CI consumers. + */ +export function restoreStdout(): void { + activeRestore?.(); } From 3e3218911c2a11c73d50e8236105bc58f547ddbf Mon Sep 17 00:00:00 2001 From: Maximus Decimus Meridius Date: Sat, 4 Jul 2026 18:55:57 +0530 Subject: [PATCH 05/13] chore: code review fixes --- src/commands/generate-key-pair.command.ts | 7 +- src/commands/generate-secret-key.command.ts | 72 --------------------- src/utils/errors.ts | 11 ++-- 3 files changed, 12 insertions(+), 78 deletions(-) delete mode 100644 src/commands/generate-secret-key.command.ts diff --git a/src/commands/generate-key-pair.command.ts b/src/commands/generate-key-pair.command.ts index d095e12..ad09b54 100644 --- a/src/commands/generate-key-pair.command.ts +++ b/src/commands/generate-key-pair.command.ts @@ -56,9 +56,14 @@ export class GenerateKeyPairCommand extends BaseCommand { ui.text("It is solely your responsibility to store and manage your signing keys — losing them can break your release pipeline."); ui.text("Do NOT regenerate keys unless absolutely necessary; it may break compatibility with existing Stallion releases."); + ui.section("If keys are lost"); + ui.text("Publish your next Stallion release without bundle signing."); + ui.text("Regenerate and include the new keys in your next Play Store release."); + ui.text("Once that Play Store update is live, resume signed Stallion releases as normal."); + ui.blank(); ui.status.warn("Keep your private key secure — never share or commit it."); - ui.status.fail("Stallion cannot recover or validate lost keys."); + ui.status.fail("Stallion cannot recover or validate lost keys — treat them as production secrets."); } catch (error: any) { ui.status.fail("Failed to generate keys"); if (error instanceof Error) ui.hint(` ${error.message}`); diff --git a/src/commands/generate-secret-key.command.ts b/src/commands/generate-secret-key.command.ts deleted file mode 100644 index 2880753..0000000 --- a/src/commands/generate-secret-key.command.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { Command } from "@/decorators/command.decorator"; -import { getReactNativeVersion } from "@/utils/react-native-utils"; -import { BaseCommand } from "@command-line/base.command"; -import path from "path"; -import fs from "fs/promises"; -import { generateKeyPairSync } from "crypto"; -import { ui } from "@/ui"; - -@Command({ - name: "generate-secret-key", - description: "Generate Private & Public keys", - alias: "gsk", -}) -export class GenerateSecretKeyCommand extends BaseCommand { - private contentRootPath: string; - - constructor() { - super(); - this.contentRootPath = process.cwd(); - } - - async execute(): Promise { - if (!getReactNativeVersion()) { - throw new Error("No react native project found in current directory"); - } - - const secretKeysPath = path.join(this.contentRootPath, "stallion", "secret-keys"); - await fs.mkdir(secretKeysPath, { recursive: true }); - - const privateKeyPath = path.join(secretKeysPath, "private-key.pem"); - const publicKeyPath = path.join(secretKeysPath, "public-key.pem"); - - try { - const { privateKey, publicKey } = generateKeyPairSync("rsa", { - modulusLength: 2048, - publicKeyEncoding: { type: "spki", format: "pem" }, - privateKeyEncoding: { type: "pkcs8", format: "pem" }, - }); - - await fs.writeFile(privateKeyPath, privateKey); - await fs.writeFile(publicKeyPath, publicKey); - - const relativePrivatePath = path.relative(this.contentRootPath, privateKeyPath); - const relativePublicPath = path.relative(this.contentRootPath, publicKeyPath); - const projectDir = path.basename(this.contentRootPath); - - ui.status.ok("Key pair generated"); - ui.section("Location"); - ui.keyValue([ - ["Public key", `${projectDir}/${relativePublicPath}`], - ["Private key", `${projectDir}/${relativePrivatePath}`], - ["Created", new Date().toISOString()], - ]); - - ui.section("Important"); - ui.text("It is solely your responsibility to store and manage your signing keys — losing them can break your release pipeline."); - ui.text("Do NOT regenerate keys unless absolutely necessary; it may break compatibility with existing Stallion releases."); - - ui.section("If keys are lost"); - ui.text("Publish your next Stallion release without bundle signing."); - ui.text("Regenerate and include the new keys in your next Play Store release."); - ui.text("Once that Play Store update is live, resume signed Stallion releases as normal."); - - ui.blank(); - ui.status.warn("Keep your private key secure — never share or commit it."); - ui.status.fail("Stallion cannot recover or validate lost keys — treat them as production secrets."); - } catch (error: any) { - ui.status.fail("Failed to generate keys"); - if (error instanceof Error) ui.hint(` ${error.message}`); - } - } -} diff --git a/src/utils/errors.ts b/src/utils/errors.ts index 564d21d..5350ed0 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -7,11 +7,12 @@ */ export function mapServerError(message: string): string { if (/^access denied\.?$/i.test(message.trim())) { - return [ - "Access denied. The org/project id may be wrong, belong to a different", - 'organization, or your account may not have access. Run "stallion use"', - "to pick a valid org and project, or verify --org-id/--project-id.", - ].join(" "); + return 'Access denied. Check --org-id/--project-id, or run "stallion use".'; + } + // The regional API doesn't host this project — usually a wrong or foreign + // --project-id. + if (/project not found in this region/i.test(message)) { + return 'Project not found. Check --project-id, or run "stallion use".'; } return message; } From 835f104fd0b9202dec1edaf05897bd0c229ca47a Mon Sep 17 00:00:00 2001 From: Maximus Decimus Meridius Date: Sat, 4 Jul 2026 19:23:33 +0530 Subject: [PATCH 06/13] chore: code review fixes --- src/commands/context.command.ts | 3 +-- src/ui/banner.ts | 11 +---------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/commands/context.command.ts b/src/commands/context.command.ts index 497bf63..c3bee64 100644 --- a/src/commands/context.command.ts +++ b/src/commands/context.command.ts @@ -1,6 +1,6 @@ import { BaseCommand } from "@command-line/base.command"; import { Command, CommandOption } from "@decorators/command.decorator"; -import { clearContext, getContext, getContextPath } from "@/utils/context-store"; +import { clearContext, getContext } from "@/utils/context-store"; import { ui } from "@/ui"; const expectedOptions: CommandOption[] = [ @@ -44,6 +44,5 @@ export class ContextCommand extends BaseCommand { ["Project", ctx.projectName], ["Project Id", ctx.projectId], ]); - ui.hint(`\nStored at ${getContextPath()}`); } } diff --git a/src/ui/banner.ts b/src/ui/banner.ts index b542f6d..106fc4d 100644 --- a/src/ui/banner.ts +++ b/src/ui/banner.ts @@ -82,11 +82,7 @@ export function showWelcome(): void { const cmd = ([name, desc]: [string, string]) => `${theme.accent(glyph.arrow)} ${theme.text.bold(padVisible(name, nameWidth))}${theme.dim(desc)}`; - const lines = [ - ...entries.map(cmd), - "", - `${theme.dim("cwd")} ${theme.muted(shortCwd())}`, - ]; + const lines = [...entries.map(cmd)]; for (const line of box(lines, { title: "Getting Started", indent: 2, padX: 2 })) { console.log(line); @@ -106,8 +102,3 @@ export function showBanner(): void { } } -function shortCwd(): string { - const cwd = process.cwd(); - const home = process.env.HOME || process.env.USERPROFILE; - return home && cwd.startsWith(home) ? "~" + cwd.slice(home.length) : cwd; -} From 9fd98c63129f15b9920a05060ef08a6f941a2e89 Mon Sep 17 00:00:00 2001 From: Maximus Decimus Meridius Date: Sat, 4 Jul 2026 19:48:29 +0530 Subject: [PATCH 07/13] fix: allow --ci-token on read commands without a stored login (skip interactive validation) --- src/command-line/command.registry.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/command-line/command.registry.ts b/src/command-line/command.registry.ts index d65c6b1..f483549 100644 --- a/src/command-line/command.registry.ts +++ b/src/command-line/command.registry.ts @@ -44,7 +44,15 @@ export class CommandRegistry { private shouldSkipValidationForCIToken(command: BaseCommand, options: Record): boolean { const commandName = command.constructor.name; - const supportsCIToken = ['PublishBundleCommand', 'ReleaseBundleCommand', 'UpdateReleaseCommand']; + const supportsCIToken = [ + 'PublishBundleCommand', + 'ReleaseBundleCommand', + 'UpdateReleaseCommand', + 'ListBucketsCommand', + 'ListBundlesCommand', + 'ListReleasesCommand', + 'ReleaseInfoCommand', + ]; return supportsCIToken.includes(commandName) && Boolean(options.ciToken); } From 5cd91d51efeb3d43159c6ae87d5147bb0890da23 Mon Sep 17 00:00:00 2001 From: Maximus Decimus Meridius Date: Sat, 4 Jul 2026 23:43:44 +0530 Subject: [PATCH 08/13] chore: added list-patches command --- src/command-line/command.registry.ts | 2 + src/commands/list-bundles.command.ts | 31 +++----- src/commands/list-patches.command.ts | 106 +++++++++++++++++++++++++++ src/ui/table.ts | 13 +++- 4 files changed, 129 insertions(+), 23 deletions(-) create mode 100644 src/commands/list-patches.command.ts diff --git a/src/command-line/command.registry.ts b/src/command-line/command.registry.ts index f483549..d47865d 100644 --- a/src/command-line/command.registry.ts +++ b/src/command-line/command.registry.ts @@ -15,6 +15,7 @@ import "@/commands/list-projects.command"; import "@/commands/list-buckets.command"; import "@/commands/list-bundles.command"; import "@/commands/list-releases.command"; +import "@/commands/list-patches.command"; import "@/commands/release-info.command"; import "@/commands/use.command"; import "@/commands/context.command"; @@ -51,6 +52,7 @@ export class CommandRegistry { 'ListBucketsCommand', 'ListBundlesCommand', 'ListReleasesCommand', + 'ListPatchesCommand', 'ReleaseInfoCommand', ]; return supportsCIToken.includes(commandName) && Boolean(options.ciToken); diff --git a/src/commands/list-bundles.command.ts b/src/commands/list-bundles.command.ts index e383ba4..dd19f9e 100644 --- a/src/commands/list-bundles.command.ts +++ b/src/commands/list-bundles.command.ts @@ -26,22 +26,21 @@ const expectedOptions: CommandOption[] = [ ]; const bundleColumns: BoardColumn[] = [ - { header: "VERSION", render: (b) => renderValue(b.version) }, - { header: "PLATFORM", render: (b) => renderValue(b.platform) }, - { header: "PROMOTED", render: (b) => renderValue(Boolean(b.isPromoted)) }, - { header: "CREATED", render: (b) => renderValue(b.createdAt) }, + { header: "Version", render: (b) => renderValue(b.version) }, + { header: "Platform", render: (b) => renderValue(b.platform) }, + { header: "Promoted", render: (b) => renderValue(Boolean(b.isPromoted)) }, + { header: "Created", render: (b) => renderValue(b.createdAt) }, { - header: "AUTHOR", + header: "author", render: (b) => renderValue(b.author?.fullName ?? b.author?.email), }, - // Short hash for quick row identification; the full (copy-pasteable) hash is - // printed below the table since 64 chars is too wide for a scannable column. + { header: "ReleaseNote", render: (b) => renderValue(b.releaseNote) }, + // Full hash (release-bundle consumes it). minWidth 32 → on narrow terminals + // the 64-char value wraps into two clean halves instead of being crushed. { - header: "HASH", - render: (b) => { - const h = b.sha256Checksum; - return renderValue(h ? `${String(h).slice(0, 10)}…` : h); - }, + header: "Hash", + render: (b) => renderValue(b.sha256Checksum), + minWidth: 32, }, ]; @@ -75,14 +74,6 @@ export class ListBundlesCommand extends BaseCommand { printBoard(bundles, bundleColumns, { indent: ui.INDENT, spaced: true }); - // Full hashes as plain, copy-pasteable lines (for `release-bundle --hash`), - // keyed by version so they map back to the table above. - const withHash = bundles.filter((b: any) => b.sha256Checksum); - if (withHash.length) { - ui.section("Hashes"); - ui.keyValue(withHash.map((b: any) => [`v${b.version}`, b.sha256Checksum])); - } - // The server returns at most `limit`; if we got exactly that many there // may be more (narrow with --platform, raise --limit, or use the console). ui.blank(); diff --git a/src/commands/list-patches.command.ts b/src/commands/list-patches.command.ts new file mode 100644 index 0000000..16e302f --- /dev/null +++ b/src/commands/list-patches.command.ts @@ -0,0 +1,106 @@ +import { BaseCommand } from "@command-line/base.command"; +import { Command, CommandOption } from "@decorators/command.decorator"; +import { ValidateUser } from "@decorators/validate-user.decorator"; +import { progress } from "@/utils/progress"; +import { ui } from "@/ui"; +import { ENDPOINTS } from "@/api/endpoints"; +import { + createCiApiClient, + createUserApiClient, + resolveOrgContext, + resolveProjectId, +} from "@/api/user-client"; +import { BoardColumn, printBoard, renderValue } from "@/ui"; + +const expectedOptions: CommandOption[] = [ + { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, + { name: "project-id", description: "Project id (prompts if omitted; required with --ci-token)", required: false }, + { name: "hash", description: "Release bundle hash (from list-bundles / list-releases)", required: true }, + { name: "ci-token", description: "CI token (non-interactive; requires --project-id)", required: false }, + { name: "json", description: "Output raw JSON", required: false }, +]; + +const fmtSize = (bytes: unknown): string | null => { + const n = Number(bytes); + if (!Number.isFinite(n) || n <= 0) return null; + if (n < 1024) return `${n} B`; + if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`; + return `${(n / 1024 / 1024).toFixed(2)} MB`; +}; + +const fmtRelease = (r: any): string | null => + r ? `v${r.bundleVersion ?? "-"} · ${r.appVersion ?? "-"}` : null; + +const patchColumns: BoardColumn[] = [ + { header: "From", render: (p) => renderValue(fmtRelease(p.fromProdBundle)) }, + { header: "To", render: (p) => renderValue(fmtRelease(p.toProdBundle)) }, + { header: "DiffSize", render: (p) => renderValue(fmtSize(p.bundleDiffSize)) }, + { header: "PatchSize", render: (p) => renderValue(fmtSize(p.totalPatchSize)) }, +]; + +@Command({ + name: "list-patches", + description: "List the delta patches generated toward a release bundle", + alias: "lpt", + options: expectedOptions, +}) +@ValidateUser() +export class ListPatchesCommand extends BaseCommand { + async execute(options: Record): Promise { + const json = Boolean(options.json); + const hash = typeof options.hash === "string" ? options.hash.trim() : ""; + if (!hash) { + throw new Error( + "--hash is required (copy it from list-bundles or list-releases)" + ); + } + + const patches = await this.fetchPatches(options, hash); + + if (json) { + console.log(JSON.stringify(patches, null, 2)); + return; + } + + ui.section("Patches"); + if (!patches.length) { + ui.hint(" No patches for this release yet."); + ui.hint(" Patches are generated when a newer release can delta-update from this bundle."); + return; + } + + printBoard(patches, patchColumns, { indent: ui.INDENT, spaced: true }); + ui.blank(); + ui.hint(` ${patches.length} patch${patches.length === 1 ? "" : "es"} · ${hash.slice(0, 12)}…`); + } + + private async fetchPatches( + options: Record, + releaseHash: string + ): Promise { + if (options.ciToken) { + if (!options.projectId) { + throw new Error("--project-id is required with --ci-token"); + } + const client = createCiApiClient(options.ciToken); + const res = await progress("Fetching patches", () => + client.post<{ data: { patchInfo: any[] } }>(ENDPOINTS.PATCH.CI_INFO, { + projectId: options.projectId, + releaseHash, + }) + ); + return res?.data?.patchInfo ?? []; + } + + const { orgId, region } = await resolveOrgContext(options.orgId); + const projectId = await resolveProjectId(orgId, options.projectId); + const client = await createUserApiClient(region); + const res = await progress("Fetching patches", () => + client.post<{ data: { patchInfo: any[] } }>(ENDPOINTS.PATCH.INFO, { + projectId, + releaseHash, + }) + ); + return res?.data?.patchInfo ?? []; + } +} diff --git a/src/ui/table.ts b/src/ui/table.ts index defd61d..8a78ec1 100644 --- a/src/ui/table.ts +++ b/src/ui/table.ts @@ -12,6 +12,12 @@ export interface BoardColumn { /** Pre-colored display string for the cell (measured by visible width). */ render: (row: any) => string; align?: "left" | "right"; + /** + * Floor for responsive shrinking. Content wider than the final column width + * wraps onto extra lines, so a column that must stay readable (e.g. a hash + * that wraps into copyable halves) can pin its minimum width here. + */ + minWidth?: number; } /** @@ -44,17 +50,18 @@ export function printBoard( const overhead = (options.indent ?? 0) + (n + 1) + n * 2; // borders + padding const budget = termWidth - overhead; const MIN = 4; + const floorFor = (i: number) => Math.max(MIN, columns[i].minWidth ?? MIN); const sum = () => widths.reduce((a, b) => a + b, 0); while (sum() > budget) { let idx = -1; - let max = MIN; + let max = 0; widths.forEach((w, i) => { - if (w > max) { + if (w > floorFor(i) && w > max) { max = w; idx = i; } }); - if (idx === -1) break; // every column at the floor — accept overflow + if (idx === -1) break; // every column at its floor — accept overflow widths[idx] -= 1; } From c4eda763e0da8aa1a69cc900f73a60b6ce4750a3 Mon Sep 17 00:00:00 2001 From: Maximus Decimus Meridius Date: Sun, 5 Jul 2026 01:46:05 +0530 Subject: [PATCH 09/13] chore: patch info endpoints --- src/api/endpoints.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index 19f001d..a3c9849 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -31,6 +31,11 @@ export const ENDPOINTS = { BY_HASH: "/cli/bundle-by-hash", CI_BY_HASH: "/cli/ci/bundle-by-hash", }, + PATCH: { + // Patches generated toward a release bundle (delta updates). + INFO: "/cli/patch-info", + CI_INFO: "/cli/ci/patch-info", + }, PROMOTED: { LISTING: "/cli/list-releases", CI_LISTING: "/cli/ci/list-releases", From 1bc0b1a9bf8bbea03c5626252d981e8f43bad6ac Mon Sep 17 00:00:00 2001 From: Maximus Decimus Meridius Date: Sun, 5 Jul 2026 21:54:15 +0530 Subject: [PATCH 10/13] chore: fixed region bug --- src/api/endpoints.ts | 4 +++- src/api/user-client.ts | 3 ++- src/command-line/base.command.ts | 5 ++--- src/commands/list-buckets.command.ts | 2 +- src/commands/list-bundles.command.ts | 2 +- src/commands/list-patches.command.ts | 2 +- src/commands/list-projects.command.ts | 6 ++++-- src/commands/list-releases.command.ts | 2 +- src/commands/publish-bundle.command.ts | 25 +++++++++++++++++-------- src/commands/release-info.command.ts | 2 +- src/commands/use.command.ts | 5 ++++- 11 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index a3c9849..41eef69 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -18,7 +18,9 @@ export const ENDPOINTS = { LIST: "/cli/list-orgs", // user token only (org scope needs a user) }, PROJECT: { - LIST: "/cli/list-projects", // user token only + // User token only. Projects are hosted per region — call this on the + // org's regional API, not the global one. + LIST: "/cli/list-projects", }, BUCKET: { LIST: "/cli/list-buckets", diff --git a/src/api/user-client.ts b/src/api/user-client.ts index affa7e2..7383d7e 100644 --- a/src/api/user-client.ts +++ b/src/api/user-client.ts @@ -130,6 +130,7 @@ export async function resolveOrgContext( */ export async function resolveProjectId( orgId: string, + region: string, projectId?: string, opts: { useSaved?: boolean } = {} ): Promise { @@ -149,7 +150,7 @@ export async function resolveProjectId( } } - const client = await createUserApiClient(); + const client = await createUserApiClient(region); const res = await client.post<{ data: any[] }>(ENDPOINTS.PROJECT.LIST, { orgId, }); diff --git a/src/command-line/base.command.ts b/src/command-line/base.command.ts index e998b02..8e8aff0 100644 --- a/src/command-line/base.command.ts +++ b/src/command-line/base.command.ts @@ -4,7 +4,7 @@ import { ENDPOINTS } from "@/api/endpoints"; import opener from "opener"; import os from "os"; import { createDefaultTokenStore } from "@/utils/token-store"; -import { setContext } from "@/utils/context-store"; +import { clearContext, setContext } from "@/utils/context-store"; import { ApiClient } from "@/api/api-client"; import { CONFIG } from "@/api/config"; import { progress } from "@/utils/progress"; @@ -124,8 +124,7 @@ export abstract class BaseCommand { try { const tokenStore = createDefaultTokenStore(); await tokenStore.remove("cli"); - // Drop the cached profile so the greeting doesn't show a stale name. - setContext({ userName: undefined, userEmail: undefined }); + clearContext(); ui.status.ok("Logged out successfully"); return true; } catch (error) { diff --git a/src/commands/list-buckets.command.ts b/src/commands/list-buckets.command.ts index f1a1d17..04aa255 100644 --- a/src/commands/list-buckets.command.ts +++ b/src/commands/list-buckets.command.ts @@ -88,7 +88,7 @@ export class ListBucketsCommand extends BaseCommand { } const { orgId, region } = await resolveOrgContext(options.orgId); - const projectId = await resolveProjectId(orgId, options.projectId); + const projectId = await resolveProjectId(orgId, region, options.projectId); const client = await createUserApiClient(region); const res = await progress("Fetching buckets", () => client.post<{ data: any[] }>(ENDPOINTS.BUCKET.LIST, { diff --git a/src/commands/list-bundles.command.ts b/src/commands/list-bundles.command.ts index dd19f9e..0cd1d61 100644 --- a/src/commands/list-bundles.command.ts +++ b/src/commands/list-bundles.command.ts @@ -112,7 +112,7 @@ export class ListBundlesCommand extends BaseCommand { } const { orgId, region } = await resolveOrgContext(options.orgId); - const projectId = await resolveProjectId(orgId, options.projectId); + const projectId = await resolveProjectId(orgId, region, options.projectId); // Explicit --bucket / --bucket-id go straight to the server (it resolves + // authorizes the name); only prompt with the picker when neither is given. let bucketId = options.bucketId as string | undefined; diff --git a/src/commands/list-patches.command.ts b/src/commands/list-patches.command.ts index 16e302f..22f2ca8 100644 --- a/src/commands/list-patches.command.ts +++ b/src/commands/list-patches.command.ts @@ -93,7 +93,7 @@ export class ListPatchesCommand extends BaseCommand { } const { orgId, region } = await resolveOrgContext(options.orgId); - const projectId = await resolveProjectId(orgId, options.projectId); + const projectId = await resolveProjectId(orgId, region, options.projectId); const client = await createUserApiClient(region); const res = await progress("Fetching patches", () => client.post<{ data: { patchInfo: any[] } }>(ENDPOINTS.PATCH.INFO, { diff --git a/src/commands/list-projects.command.ts b/src/commands/list-projects.command.ts index 655f42e..68b4b88 100644 --- a/src/commands/list-projects.command.ts +++ b/src/commands/list-projects.command.ts @@ -23,8 +23,10 @@ const expectedOptions: CommandOption[] = [ export class ListProjectsCommand extends BaseCommand { async execute(options: Record): Promise { const json = Boolean(options.json); - const { orgId } = await resolveOrgContext(options.orgId); - const client = await createUserApiClient(); + // Projects live on the org's regional API — the global API only sees its + // own region's projects. + const { orgId, region } = await resolveOrgContext(options.orgId); + const client = await createUserApiClient(region); const res = await progress("Fetching projects", () => client.post<{ data: any[] }>(ENDPOINTS.PROJECT.LIST, { orgId }) diff --git a/src/commands/list-releases.command.ts b/src/commands/list-releases.command.ts index fb18714..d871f80 100644 --- a/src/commands/list-releases.command.ts +++ b/src/commands/list-releases.command.ts @@ -132,7 +132,7 @@ export class ListReleasesCommand extends BaseCommand { } const { orgId, region } = await resolveOrgContext(options.orgId); - const projectId = await resolveProjectId(orgId, options.projectId); + const projectId = await resolveProjectId(orgId, region, options.projectId); const platform = await resolvePlatform(options.platform); const client = await createUserApiClient(region); diff --git a/src/commands/publish-bundle.command.ts b/src/commands/publish-bundle.command.ts index 25bdc49..56ccaf2 100644 --- a/src/commands/publish-bundle.command.ts +++ b/src/commands/publish-bundle.command.ts @@ -242,15 +242,20 @@ export class PublishBundleCommand extends BaseCommand { platform, releaseNote, ciToken, - updateProgress + (p) => updateProgress(p * 0.95) ); if (result.projectId) { - registered = await this.waitForBundle(client, { - ciToken, - projectId: result.projectId, - hash: result.hash, - }); + registered = await this.waitForBundle( + client, + { + ciToken, + projectId: result.projectId, + hash: result.hash, + }, + (elapsedFraction) => updateProgress(95 + elapsedFraction * 4) + ); } + updateProgress(100); return result; } ); @@ -288,7 +293,8 @@ export class PublishBundleCommand extends BaseCommand { */ private async waitForBundle( client: ApiClient, - opts: { ciToken?: string; projectId: string; hash: string } + opts: { ciToken?: string; projectId: string; hash: string }, + onTick?: (elapsedFraction: number) => void ): Promise { const { ciToken, projectId, hash } = opts; const endpoint = ciToken @@ -296,9 +302,12 @@ export class PublishBundleCommand extends BaseCommand { : ENDPOINTS.BUNDLE.BY_HASH; const config = ciToken ? { headers: { "x-ci-token": ciToken } } : undefined; const intervalMs = 2000; - const deadline = Date.now() + 60_000; + const windowMs = 60_000; + const start = Date.now(); + const deadline = start + windowMs; while (Date.now() < deadline) { + onTick?.(Math.min(1, (Date.now() - start) / windowMs)); try { const res = await client.post<{ data: { exists: boolean } }>( endpoint, diff --git a/src/commands/release-info.command.ts b/src/commands/release-info.command.ts index 5bb72e2..3ce9a95 100644 --- a/src/commands/release-info.command.ts +++ b/src/commands/release-info.command.ts @@ -138,7 +138,7 @@ export class ReleaseInfoCommand extends BaseCommand { } const { orgId, region } = await resolveOrgContext(options.orgId); - const projectId = await resolveProjectId(orgId, options.projectId); + const projectId = await resolveProjectId(orgId, region, options.projectId); const platform = await resolvePlatform(options.platform); const client = await createUserApiClient(region); diff --git a/src/commands/use.command.ts b/src/commands/use.command.ts index 57cc4f4..d93b915 100644 --- a/src/commands/use.command.ts +++ b/src/commands/use.command.ts @@ -49,8 +49,11 @@ export class UseCommand extends BaseCommand { const org = orgs.find((o) => o.orgId === orgId); const region = org?.region ?? "ap"; + // Projects live on the org's regional API — list them there, not on the + // global API the org listing came from. + const regionalClient = await createUserApiClient(region); const projRes = await progress("Fetching projects", () => - client.post<{ data: any[] }>(ENDPOINTS.PROJECT.LIST, { orgId }) + regionalClient.post<{ data: any[] }>(ENDPOINTS.PROJECT.LIST, { orgId }) ); const projects = projRes?.data ?? []; if (!projects.length) { From 381999cf951e2f739a7d2d3d5d2c857a42b90585 Mon Sep 17 00:00:00 2001 From: Maximus Decimus Meridius Date: Mon, 6 Jul 2026 01:43:55 +0530 Subject: [PATCH 11/13] chore: code review fixes --- src/api/types.ts | 97 +++++++++++++++++++++++ src/api/user-client.ts | 35 ++++---- src/commands/generate-key-pair.command.ts | 6 +- src/commands/list-buckets.command.ts | 7 +- src/commands/list-bundles.command.ts | 7 +- src/commands/list-patches.command.ts | 7 +- src/commands/list-projects.command.ts | 9 ++- src/commands/list-releases.command.ts | 9 ++- src/commands/publish-bundle.command.ts | 6 +- src/commands/release-info.command.ts | 15 ++-- src/commands/use.command.ts | 13 +-- src/commands/whoami.command.ts | 5 +- src/utils/common.ts | 4 + src/utils/react-native-utils.ts | 6 +- 14 files changed, 175 insertions(+), 51 deletions(-) create mode 100644 src/api/types.ts diff --git a/src/api/types.ts b/src/api/types.ts new file mode 100644 index 0000000..d9366d6 --- /dev/null +++ b/src/api/types.ts @@ -0,0 +1,97 @@ +/** + * Lean payload shapes returned by the CLI-owned /cli read routes. + * + * Fields a deployment may omit (older servers, per-route variants) are + * optional, and `_id` is modeled alongside `id` where routes differ — callers + * normalize with `id ?? _id`. These types describe the read contract only; + * command flags/options are typed at the command layer. + */ + +export interface UserProfile { + fullName?: string; + email?: string; +} + +export interface ProjectSummary { + id?: string; + _id?: string; + name?: string; + androidEnabled?: boolean; + iosEnabled?: boolean; + isPatchEnabled?: boolean; + createdAt?: string; + updatedAt?: string; +} + +export interface BucketSummary { + id?: string; + _id?: string; + name?: string; + updatedAt?: string; +} + +export interface BundleAuthor { + fullName?: string; + email?: string; +} + +export interface BundleSummary { + id?: string; + _id?: string; + version?: number; + platform?: string; + isPromoted?: boolean; + size?: number; + sha256Checksum?: string | null; + createdAt?: string; + releaseNote?: string; + author?: BundleAuthor; +} + +export interface AppVersionSummary { + targetVersion?: string; + count?: number; + latestReleaseUserEmail?: string; +} + +export interface ReleaseSummary { + id?: string; + bundleVersion?: number; + hash?: string; + rolloutPercent?: number; + isPaused?: boolean; + isRolledBack?: boolean; + releaseNote?: string; + createdAt?: string; + author?: BundleAuthor; +} + +export interface ReleaseDetail { + bundleVersion?: number; + platform?: string; + appVersion?: string; + rolloutPercent?: number; + isPaused?: boolean; + isRolledBack?: boolean; + releaseNote?: string; + totalUsers?: number; + publishedBundle?: { sha256Checksum?: string }; + /** Adoption counters keyed by event name (e.g. INSTALLED_PROD). */ + eventCount?: Record; +} + +/** A release the patch diffs from/to, as embedded in patch-info. */ +export interface PatchReleaseRef { + bundleVersion?: number; + appVersion?: string; + platform?: string; + createdAt?: string; +} + +export interface PatchInfo { + patchId?: string; + bundleDiffSize?: number | null; + totalPatchSize?: number | null; + fromProdBundle?: PatchReleaseRef | null; + toProdBundle?: PatchReleaseRef | null; +} diff --git a/src/api/user-client.ts b/src/api/user-client.ts index 7383d7e..9c92f5c 100644 --- a/src/api/user-client.ts +++ b/src/api/user-client.ts @@ -1,11 +1,12 @@ import { ApiClient } from "@/api/api-client"; import { CONFIG } from "@/api/config"; import { ENDPOINTS } from "@/api/endpoints"; -import { getApiBaseUrl } from "@/utils/common"; +import { getApiBaseUrl, PLATFORMS } from "@/utils/common"; import { createDefaultTokenStore } from "@/utils/token-store"; import { promptSelect } from "@/utils/prompt"; import { getContext, setContext } from "@/utils/context-store"; import { parseTokenRegion } from "@/utils/region"; +import { BucketSummary, ProjectSummary } from "@/api/types"; /** * Build an ApiClient authenticated with a CI token (x-ci-token). Region is @@ -151,16 +152,17 @@ export async function resolveProjectId( } const client = await createUserApiClient(region); - const res = await client.post<{ data: any[] }>(ENDPOINTS.PROJECT.LIST, { - orgId, - }); + const res = await client.post<{ data: ProjectSummary[] }>( + ENDPOINTS.PROJECT.LIST, + { orgId } + ); const projects = res?.data ?? []; if (!projects.length) { throw new Error("No projects found in this organization."); } const chosen = await promptSelect( "Select a project", - projects.map((p: any) => { + projects.map((p) => { const id = String(p.id ?? p._id); const platforms = [ p.androidEnabled ? "android" : null, @@ -169,13 +171,13 @@ export async function resolveProjectId( .filter(Boolean) .join("/"); return { - name: p.name, + name: p.name ?? "(unnamed)", value: id, description: `${platforms || "no platforms"} id: ${id}`, }; }) ); - const proj = projects.find((p: any) => String(p.id ?? p._id) === chosen); + const proj = projects.find((p) => String(p.id ?? p._id) === chosen); setContext({ orgId, projectId: chosen, projectName: proj?.name }); return chosen; } @@ -188,18 +190,19 @@ export async function resolveBucketId( ): Promise { if (bucketId) return bucketId; const client = await createUserApiClient(region); - const res = await client.post<{ data: any[] }>(ENDPOINTS.BUCKET.LIST, { - projectId, - }); + const res = await client.post<{ data: BucketSummary[] }>( + ENDPOINTS.BUCKET.LIST, + { projectId } + ); const buckets = res?.data ?? []; if (!buckets.length) { throw new Error("No buckets found in this project."); } return promptSelect( "Select a bucket", - buckets.map((b: any) => { + buckets.map((b) => { const id = String(b.id ?? b._id); - return { name: b.name, value: id, description: `id: ${id}` }; + return { name: b.name ?? "(unnamed)", value: id, description: `id: ${id}` }; }) ); } @@ -207,8 +210,8 @@ export async function resolveBucketId( /** Prompt for a platform when one isn't supplied via flags. */ export async function resolvePlatform(platform?: string): Promise { if (platform) return platform; - return promptSelect("Select a platform", [ - { name: "android", value: "android" }, - { name: "ios", value: "ios" }, - ]); + return promptSelect( + "Select a platform", + PLATFORMS.map((p) => ({ name: p, value: p })) + ); } diff --git a/src/commands/generate-key-pair.command.ts b/src/commands/generate-key-pair.command.ts index ad09b54..312f2e7 100644 --- a/src/commands/generate-key-pair.command.ts +++ b/src/commands/generate-key-pair.command.ts @@ -6,6 +6,10 @@ import fs from "fs/promises"; import { generateKeyPairSync } from "crypto"; import { ui } from "@/ui"; +// RSA key size for bundle-signing key pairs. 2048 is the size the Stallion +// SDK verifies against on-device — bump here only in lockstep with the SDK. +const RSA_MODULUS_LENGTH = 2048; + @Command({ name: "generate-key-pair", description: "Generate Private & Public keys", @@ -32,7 +36,7 @@ export class GenerateKeyPairCommand extends BaseCommand { try { const { privateKey, publicKey } = generateKeyPairSync("rsa", { - modulusLength: 2048, + modulusLength: RSA_MODULUS_LENGTH, publicKeyEncoding: { type: "spki", format: "pem" }, privateKeyEncoding: { type: "pkcs8", format: "pem" }, }); diff --git a/src/commands/list-buckets.command.ts b/src/commands/list-buckets.command.ts index 04aa255..30f2daf 100644 --- a/src/commands/list-buckets.command.ts +++ b/src/commands/list-buckets.command.ts @@ -12,6 +12,7 @@ import { import { ui } from "@/ui"; import { printBoard, renderValue } from "@/ui"; import { CONSOLE_URL, MAX_LIST_LIMIT, resolveLimit } from "@/utils/list"; +import { BucketSummary } from "@/api/types"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, @@ -71,14 +72,14 @@ export class ListBucketsCommand extends BaseCommand { private async fetchBuckets( options: Record, limit: number - ): Promise { + ): Promise { if (options.ciToken) { if (!options.projectId) { throw new Error("--project-id is required with --ci-token"); } const client = createCiApiClient(options.ciToken); const res = await progress("Fetching buckets", () => - client.post<{ data: any[] }>(ENDPOINTS.BUCKET.CI_LIST, { + client.post<{ data: BucketSummary[] }>(ENDPOINTS.BUCKET.CI_LIST, { projectId: options.projectId, name: options.name, limit, @@ -91,7 +92,7 @@ export class ListBucketsCommand extends BaseCommand { const projectId = await resolveProjectId(orgId, region, options.projectId); const client = await createUserApiClient(region); const res = await progress("Fetching buckets", () => - client.post<{ data: any[] }>(ENDPOINTS.BUCKET.LIST, { + client.post<{ data: BucketSummary[] }>(ENDPOINTS.BUCKET.LIST, { projectId, name: options.name, limit, diff --git a/src/commands/list-bundles.command.ts b/src/commands/list-bundles.command.ts index 0cd1d61..0bc735a 100644 --- a/src/commands/list-bundles.command.ts +++ b/src/commands/list-bundles.command.ts @@ -13,6 +13,7 @@ import { } from "@/api/user-client"; import { BoardColumn, printBoard, renderValue } from "@/ui"; import { CONSOLE_URL, MAX_LIST_LIMIT, resolveLimit } from "@/utils/list"; +import { BundleSummary } from "@/api/types"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, @@ -89,7 +90,7 @@ export class ListBundlesCommand extends BaseCommand { private async fetchBundles( options: Record, limit: number - ): Promise { + ): Promise { if (options.ciToken) { if (!options.projectId || (!options.bucketId && !options.bucket)) { throw new Error( @@ -100,7 +101,7 @@ export class ListBundlesCommand extends BaseCommand { // Send name or id straight through — the API resolves the name against // the project and authorizes it. const res = await progress("Fetching bundles", () => - client.post<{ data: any[] }>(ENDPOINTS.BUNDLE.CI_LIST, { + client.post<{ data: BundleSummary[] }>(ENDPOINTS.BUNDLE.CI_LIST, { projectId: options.projectId, bucketId: options.bucketId, bucketName: options.bucket, @@ -122,7 +123,7 @@ export class ListBundlesCommand extends BaseCommand { } const client = await createUserApiClient(region); const res = await progress("Fetching bundles", () => - client.post<{ data: any[] }>(ENDPOINTS.BUNDLE.LIST, { + client.post<{ data: BundleSummary[] }>(ENDPOINTS.BUNDLE.LIST, { projectId, bucketId, bucketName, diff --git a/src/commands/list-patches.command.ts b/src/commands/list-patches.command.ts index 22f2ca8..9df9334 100644 --- a/src/commands/list-patches.command.ts +++ b/src/commands/list-patches.command.ts @@ -11,6 +11,7 @@ import { resolveProjectId, } from "@/api/user-client"; import { BoardColumn, printBoard, renderValue } from "@/ui"; +import { PatchInfo } from "@/api/types"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, @@ -77,14 +78,14 @@ export class ListPatchesCommand extends BaseCommand { private async fetchPatches( options: Record, releaseHash: string - ): Promise { + ): Promise { if (options.ciToken) { if (!options.projectId) { throw new Error("--project-id is required with --ci-token"); } const client = createCiApiClient(options.ciToken); const res = await progress("Fetching patches", () => - client.post<{ data: { patchInfo: any[] } }>(ENDPOINTS.PATCH.CI_INFO, { + client.post<{ data: { patchInfo: PatchInfo[] } }>(ENDPOINTS.PATCH.CI_INFO, { projectId: options.projectId, releaseHash, }) @@ -96,7 +97,7 @@ export class ListPatchesCommand extends BaseCommand { const projectId = await resolveProjectId(orgId, region, options.projectId); const client = await createUserApiClient(region); const res = await progress("Fetching patches", () => - client.post<{ data: { patchInfo: any[] } }>(ENDPOINTS.PATCH.INFO, { + client.post<{ data: { patchInfo: PatchInfo[] } }>(ENDPOINTS.PATCH.INFO, { projectId, releaseHash, }) diff --git a/src/commands/list-projects.command.ts b/src/commands/list-projects.command.ts index 68b4b88..b996b81 100644 --- a/src/commands/list-projects.command.ts +++ b/src/commands/list-projects.command.ts @@ -7,6 +7,7 @@ import { createUserApiClient, resolveOrgContext } from "@/api/user-client"; import { ui } from "@/ui"; import { getContext } from "@/utils/context-store"; import { capRecent, printListFooter } from "@/utils/list"; +import { ProjectSummary } from "@/api/types"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, @@ -29,7 +30,7 @@ export class ListProjectsCommand extends BaseCommand { const client = await createUserApiClient(region); const res = await progress("Fetching projects", () => - client.post<{ data: any[] }>(ENDPOINTS.PROJECT.LIST, { orgId }) + client.post<{ data: ProjectSummary[] }>(ENDPOINTS.PROJECT.LIST, { orgId }) ); const projects = res?.data ?? []; @@ -41,17 +42,17 @@ export class ListProjectsCommand extends BaseCommand { const ctx = getContext(); const { shown, total, capped } = capRecent( projects, - (p: any) => p.createdAt ?? p.updatedAt + (p) => p.createdAt ?? p.updatedAt ); ui.section("projects"); ui.numbered( - shown.map((p: any) => { + shown.map((p) => { const platforms = [p.androidEnabled ? "android" : null, p.iosEnabled ? "ios" : null] .filter(Boolean) .join("·") || "—"; return { - label: p.name, + label: p.name ?? "(unnamed)", meta: platforms, current: ctx.projectId === String(p.id ?? p._id), }; diff --git a/src/commands/list-releases.command.ts b/src/commands/list-releases.command.ts index d871f80..59c3c19 100644 --- a/src/commands/list-releases.command.ts +++ b/src/commands/list-releases.command.ts @@ -14,6 +14,7 @@ import { import { ui } from "@/ui"; import { bar, glyph, printBoard, renderValue, theme } from "@/ui"; import { CONSOLE_URL, MAX_LIST_LIMIT, resolveLimit } from "@/utils/list"; +import { AppVersionSummary, ReleaseSummary } from "@/api/types"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, @@ -111,7 +112,7 @@ export class ListReleasesCommand extends BaseCommand { private async fetchReleases( options: Record, limit: number - ): Promise<{ appVersion: string; releases: any[] }> { + ): Promise<{ appVersion: string; releases: ReleaseSummary[] }> { if (options.ciToken) { const { projectId, platform, appVersion } = options; if (!projectId || !platform || !appVersion) { @@ -121,7 +122,7 @@ export class ListReleasesCommand extends BaseCommand { } const client = createCiApiClient(options.ciToken); const res = await progress("Fetching releases", () => - client.post<{ data: any[] }>(ENDPOINTS.PROMOTED.CI_LISTING, { + client.post<{ data: ReleaseSummary[] }>(ENDPOINTS.PROMOTED.CI_LISTING, { projectId, platform, appVersion, @@ -139,7 +140,7 @@ export class ListReleasesCommand extends BaseCommand { let appVersion = options.appVersion; if (!appVersion) { const verRes = await progress("Fetching app versions", () => - client.post<{ data: any[] }>(ENDPOINTS.PROMOTED.LIST_APP_VERSIONS, { + client.post<{ data: AppVersionSummary[] }>(ENDPOINTS.PROMOTED.LIST_APP_VERSIONS, { projectId, platform, }) @@ -162,7 +163,7 @@ export class ListReleasesCommand extends BaseCommand { } const res = await progress("Fetching releases", () => - client.post<{ data: any[] }>(ENDPOINTS.PROMOTED.LISTING, { + client.post<{ data: ReleaseSummary[] }>(ENDPOINTS.PROMOTED.LISTING, { projectId, platform, appVersion, diff --git a/src/commands/publish-bundle.command.ts b/src/commands/publish-bundle.command.ts index 56ccaf2..bf13f83 100644 --- a/src/commands/publish-bundle.command.ts +++ b/src/commands/publish-bundle.command.ts @@ -19,7 +19,7 @@ import { ENDPOINTS } from "@/api/endpoints"; import { createDefaultTokenStore } from "@/utils/token-store"; import { createZip } from "@/utils/archive"; import { keepArtifacts as saveArtifacts } from "@/utils/copy"; -import { getApiBaseUrl } from "@/utils/common"; +import { getApiBaseUrl, PLATFORMS } from "@/utils/common"; import { resolveRegion } from "@/utils/region"; import { silenceStdout } from "@/utils/stdout"; @@ -145,7 +145,9 @@ export class PublishBundleCommand extends BaseCommand { await fs.mkdir(this.contentRootPath); if (!isValidPlatform(platform)) { - throw new Error(`Platform must be "android" or "ios".`); + throw new Error( + `Platform must be ${PLATFORMS.map((p) => `"${p}"`).join(" or ")}.` + ); } // Run RN Command and Hermes Command only when expoBundlePath is not provided diff --git a/src/commands/release-info.command.ts b/src/commands/release-info.command.ts index 3ce9a95..55fbdef 100644 --- a/src/commands/release-info.command.ts +++ b/src/commands/release-info.command.ts @@ -14,6 +14,11 @@ import { import { printBoard } from "@/ui"; import { ui } from "@/ui"; import chalk from "chalk"; +import { + AppVersionSummary, + ReleaseDetail, + ReleaseSummary, +} from "@/api/types"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, @@ -117,7 +122,7 @@ export class ReleaseInfoCommand extends BaseCommand { /** Resolve the release detail via the CI-token or interactive user path. */ private async fetchDetail( options: Record - ): Promise { + ): Promise { if (options.ciToken) { const { projectId, platform, appVersion, promotedId } = options; if (!projectId || !platform || !appVersion || !promotedId) { @@ -127,7 +132,7 @@ export class ReleaseInfoCommand extends BaseCommand { } const client = createCiApiClient(options.ciToken); const res = await progress("Fetching release detail", () => - client.post<{ data: any }>(ENDPOINTS.PROMOTED.CI_DETAIL, { + client.post<{ data: ReleaseDetail }>(ENDPOINTS.PROMOTED.CI_DETAIL, { projectId, platform, appVersion, @@ -145,7 +150,7 @@ export class ReleaseInfoCommand extends BaseCommand { let appVersion = options.appVersion; if (!appVersion) { const verRes = await progress("Fetching app versions", () => - client.post<{ data: any[] }>(ENDPOINTS.PROMOTED.LIST_APP_VERSIONS, { + client.post<{ data: AppVersionSummary[] }>(ENDPOINTS.PROMOTED.LIST_APP_VERSIONS, { projectId, platform, }) @@ -170,7 +175,7 @@ export class ReleaseInfoCommand extends BaseCommand { let promotedId = options.promotedId; if (!promotedId) { const relRes = await progress("Fetching releases", () => - client.post<{ data: any[] }>(ENDPOINTS.PROMOTED.LISTING, { + client.post<{ data: ReleaseSummary[] }>(ENDPOINTS.PROMOTED.LISTING, { projectId, platform, appVersion, @@ -203,7 +208,7 @@ export class ReleaseInfoCommand extends BaseCommand { } const res = await progress("Fetching release detail", () => - client.post<{ data: any }>(ENDPOINTS.PROMOTED.DETAIL, { + client.post<{ data: ReleaseDetail }>(ENDPOINTS.PROMOTED.DETAIL, { projectId, platform, appVersion, diff --git a/src/commands/use.command.ts b/src/commands/use.command.ts index d93b915..60d8529 100644 --- a/src/commands/use.command.ts +++ b/src/commands/use.command.ts @@ -7,6 +7,7 @@ import { promptSelect } from "@/utils/prompt"; import { ENDPOINTS } from "@/api/endpoints"; import { createUserApiClient, fetchOrgs } from "@/api/user-client"; import { setContext } from "@/utils/context-store"; +import { ProjectSummary } from "@/api/types"; const expectedOptions: CommandOption[] = [ { name: "org-id", description: "Organization id (prompts if omitted)", required: false }, @@ -53,7 +54,9 @@ export class UseCommand extends BaseCommand { // global API the org listing came from. const regionalClient = await createUserApiClient(region); const projRes = await progress("Fetching projects", () => - regionalClient.post<{ data: any[] }>(ENDPOINTS.PROJECT.LIST, { orgId }) + regionalClient.post<{ data: ProjectSummary[] }>(ENDPOINTS.PROJECT.LIST, { + orgId, + }) ); const projects = projRes?.data ?? []; if (!projects.length) { @@ -64,7 +67,7 @@ export class UseCommand extends BaseCommand { let projectId = options.projectId as string | undefined; if ( projectId && - !projects.some((p: any) => String(p.id ?? p._id) === projectId) + !projects.some((p) => String(p.id ?? p._id) === projectId) ) { ui.status.fail(`Project "${projectId}" not found in this organization.`); return; @@ -72,7 +75,7 @@ export class UseCommand extends BaseCommand { if (!projectId) { projectId = await promptSelect( "Select a project", - projects.map((p: any) => { + projects.map((p) => { const id = String(p.id ?? p._id); const platforms = [ p.androidEnabled ? "android" : null, @@ -81,7 +84,7 @@ export class UseCommand extends BaseCommand { .filter(Boolean) .join("/"); return { - name: p.name, + name: p.name ?? "(unnamed)", value: id, description: `${platforms || "no platforms"} id: ${id}`, }; @@ -89,7 +92,7 @@ export class UseCommand extends BaseCommand { ); } const project = projects.find( - (p: any) => String(p.id ?? p._id) === projectId + (p) => String(p.id ?? p._id) === projectId ); setContext({ diff --git a/src/commands/whoami.command.ts b/src/commands/whoami.command.ts index 86cf75c..eb3d5ca 100644 --- a/src/commands/whoami.command.ts +++ b/src/commands/whoami.command.ts @@ -6,6 +6,7 @@ import { ENDPOINTS } from "@/api/endpoints"; import { createUserApiClient, fetchOrgs } from "@/api/user-client"; import { ui } from "@/ui"; import { getContext } from "@/utils/context-store"; +import { UserProfile } from "@/api/types"; @Command({ name: "whoami", @@ -18,7 +19,9 @@ export class WhoamiCommand extends BaseCommand { const client = await createUserApiClient(); const { profile, orgs } = await progress("Fetching profile", async () => { - const profileRes = await client.get<{ data: any }>(ENDPOINTS.USER.VERIFY); + const profileRes = await client.get<{ data: UserProfile }>( + ENDPOINTS.USER.VERIFY + ); const orgsList = await fetchOrgs(client); return { profile: profileRes?.data, orgs: orgsList }; }); diff --git a/src/utils/common.ts b/src/utils/common.ts index d205f33..7b9390c 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,5 +1,9 @@ import { CONFIG } from "@/api/config"; +/** Platforms a Stallion bundle can target — the single source of truth. */ +export const PLATFORMS = ["android", "ios"] as const; +export type Platform = (typeof PLATFORMS)[number]; + export function getApiBaseUrl(region: string): string { if (!region) { return CONFIG.API.BASE_URL; diff --git a/src/utils/react-native-utils.ts b/src/utils/react-native-utils.ts index ca67e20..0548ddb 100644 --- a/src/utils/react-native-utils.ts +++ b/src/utils/react-native-utils.ts @@ -5,7 +5,7 @@ import * as rimraf from "rimraf"; import { ui } from "@/ui"; import * as childProcess from "child_process"; import { coerce, compare } from "semver"; -import chalk from "chalk"; +import { PLATFORMS, Platform } from "@/utils/common"; function findUpwardReactNativePackageJson( startDir: string = process.cwd() @@ -80,9 +80,7 @@ function resolvePackageDirFromCwd(packageName: string): string | null { } export function isValidPlatform(platform: string): boolean { - return ( - platform?.toLowerCase() === "android" || platform?.toLowerCase() === "ios" - ); + return PLATFORMS.includes(platform?.toLowerCase() as Platform); } export function fileDoesNotExistOrIsDirectory(path: string): boolean { From c816d4ab954f03d12611c5c9f6da46d8a8c3d9b0 Mon Sep 17 00:00:00 2001 From: Maximus Decimus Meridius Date: Mon, 6 Jul 2026 01:49:20 +0530 Subject: [PATCH 12/13] chore: code review fixes --- src/api/endpoints.ts | 4 +--- src/api/types.ts | 8 ++------ src/api/user-client.ts | 3 --- src/command-line/command.registry.ts | 3 +-- src/commands/context.command.ts | 2 -- src/commands/generate-key-pair.command.ts | 3 +-- src/commands/list-buckets.command.ts | 2 -- src/commands/list-bundles.command.ts | 8 -------- src/commands/list-projects.command.ts | 2 -- src/commands/list-releases.command.ts | 2 -- src/commands/publish-bundle.command.ts | 17 ++++++---------- src/commands/release-info.command.ts | 5 ----- src/commands/use.command.ts | 2 -- src/index.ts | 6 +----- src/ui/table.ts | 6 +----- src/utils/common.ts | 2 +- src/utils/errors.ts | 10 +--------- src/utils/list.ts | 24 ++++------------------- src/utils/stdout.ts | 14 +++---------- 19 files changed, 22 insertions(+), 101 deletions(-) diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index 41eef69..da6cfe2 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -18,8 +18,7 @@ export const ENDPOINTS = { LIST: "/cli/list-orgs", // user token only (org scope needs a user) }, PROJECT: { - // User token only. Projects are hosted per region — call this on the - // org's regional API, not the global one. + // User token only; call on the org's regional API (projects are per-region). LIST: "/cli/list-projects", }, BUCKET: { @@ -34,7 +33,6 @@ export const ENDPOINTS = { CI_BY_HASH: "/cli/ci/bundle-by-hash", }, PATCH: { - // Patches generated toward a release bundle (delta updates). INFO: "/cli/patch-info", CI_INFO: "/cli/ci/patch-info", }, diff --git a/src/api/types.ts b/src/api/types.ts index d9366d6..c5787e0 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1,10 +1,6 @@ /** - * Lean payload shapes returned by the CLI-owned /cli read routes. - * - * Fields a deployment may omit (older servers, per-route variants) are - * optional, and `_id` is modeled alongside `id` where routes differ — callers - * normalize with `id ?? _id`. These types describe the read contract only; - * command flags/options are typed at the command layer. + * Payload shapes returned by the CLI-owned /cli read routes. Optional fields + * may be omitted by older deployments; some routes send `_id` instead of `id`. */ export interface UserProfile { diff --git a/src/api/user-client.ts b/src/api/user-client.ts index 9c92f5c..e56b6b9 100644 --- a/src/api/user-client.ts +++ b/src/api/user-client.ts @@ -137,9 +137,6 @@ export async function resolveProjectId( ): Promise { const useSaved = opts.useSaved !== false; - // Explicit --project-id is passed through as-is: the API authorizes every - // request, so pre-checking here would only add a round-trip. An id the user - // can't access fails at the first API call. if (projectId) { return String(projectId); } diff --git a/src/command-line/command.registry.ts b/src/command-line/command.registry.ts index d47865d..c8fdd6f 100644 --- a/src/command-line/command.registry.ts +++ b/src/command-line/command.registry.ts @@ -69,8 +69,7 @@ export class CommandRegistry { return; } - // Consistent breadcrumb header for every command, except help (which shows - // its own banner) and JSON mode (kept clean for scripting). + // Skip the breadcrumb for help (own banner) and JSON mode (clean stdout). if (name !== "help" && !options.json) { ui.header(name); } diff --git a/src/commands/context.command.ts b/src/commands/context.command.ts index c3bee64..c17d545 100644 --- a/src/commands/context.command.ts +++ b/src/commands/context.command.ts @@ -24,8 +24,6 @@ export class ContextCommand extends BaseCommand { const ctx = getContext(); - // JSON mode always emits a parseable object on stdout — including `{}` when - // no context is set — so scripts can rely on the output shape. if (options.json) { console.log(JSON.stringify(ctx ?? {}, null, 2)); return; diff --git a/src/commands/generate-key-pair.command.ts b/src/commands/generate-key-pair.command.ts index 312f2e7..230a566 100644 --- a/src/commands/generate-key-pair.command.ts +++ b/src/commands/generate-key-pair.command.ts @@ -6,8 +6,7 @@ import fs from "fs/promises"; import { generateKeyPairSync } from "crypto"; import { ui } from "@/ui"; -// RSA key size for bundle-signing key pairs. 2048 is the size the Stallion -// SDK verifies against on-device — bump here only in lockstep with the SDK. +// Must stay in lockstep with the key size the Stallion SDK verifies on-device. const RSA_MODULUS_LENGTH = 2048; @Command({ diff --git a/src/commands/list-buckets.command.ts b/src/commands/list-buckets.command.ts index 30f2daf..cd8247a 100644 --- a/src/commands/list-buckets.command.ts +++ b/src/commands/list-buckets.command.ts @@ -57,8 +57,6 @@ export class ListBucketsCommand extends BaseCommand { { indent: ui.INDENT, spaced: true } ); - // The server returns at most `limit`; if we got exactly that many there - // may be more (narrow with --name, raise --limit, or use the console). ui.blank(); if (buckets.length >= limit) { ui.hint( diff --git a/src/commands/list-bundles.command.ts b/src/commands/list-bundles.command.ts index 0bc735a..08b7d4b 100644 --- a/src/commands/list-bundles.command.ts +++ b/src/commands/list-bundles.command.ts @@ -36,8 +36,6 @@ const bundleColumns: BoardColumn[] = [ render: (b) => renderValue(b.author?.fullName ?? b.author?.email), }, { header: "ReleaseNote", render: (b) => renderValue(b.releaseNote) }, - // Full hash (release-bundle consumes it). minWidth 32 → on narrow terminals - // the 64-char value wraps into two clean halves instead of being crushed. { header: "Hash", render: (b) => renderValue(b.sha256Checksum), @@ -75,8 +73,6 @@ export class ListBundlesCommand extends BaseCommand { printBoard(bundles, bundleColumns, { indent: ui.INDENT, spaced: true }); - // The server returns at most `limit`; if we got exactly that many there - // may be more (narrow with --platform, raise --limit, or use the console). ui.blank(); if (bundles.length >= limit) { ui.hint( @@ -98,8 +94,6 @@ export class ListBundlesCommand extends BaseCommand { ); } const client = createCiApiClient(options.ciToken); - // Send name or id straight through — the API resolves the name against - // the project and authorizes it. const res = await progress("Fetching bundles", () => client.post<{ data: BundleSummary[] }>(ENDPOINTS.BUNDLE.CI_LIST, { projectId: options.projectId, @@ -114,8 +108,6 @@ export class ListBundlesCommand extends BaseCommand { const { orgId, region } = await resolveOrgContext(options.orgId); const projectId = await resolveProjectId(orgId, region, options.projectId); - // Explicit --bucket / --bucket-id go straight to the server (it resolves + - // authorizes the name); only prompt with the picker when neither is given. let bucketId = options.bucketId as string | undefined; const bucketName = options.bucket as string | undefined; if (!bucketId && !bucketName) { diff --git a/src/commands/list-projects.command.ts b/src/commands/list-projects.command.ts index b996b81..5785d97 100644 --- a/src/commands/list-projects.command.ts +++ b/src/commands/list-projects.command.ts @@ -24,8 +24,6 @@ const expectedOptions: CommandOption[] = [ export class ListProjectsCommand extends BaseCommand { async execute(options: Record): Promise { const json = Boolean(options.json); - // Projects live on the org's regional API — the global API only sees its - // own region's projects. const { orgId, region } = await resolveOrgContext(options.orgId); const client = await createUserApiClient(region); diff --git a/src/commands/list-releases.command.ts b/src/commands/list-releases.command.ts index 59c3c19..8be9ca3 100644 --- a/src/commands/list-releases.command.ts +++ b/src/commands/list-releases.command.ts @@ -35,8 +35,6 @@ const expectedOptions: CommandOption[] = [ @ValidateUser() export class ListReleasesCommand extends BaseCommand { async execute(options: Record): Promise { - // An app version only identifies releases together with a platform — - // half-specifying is almost certainly a mistake, so fail fast. if (options.appVersion && !options.platform) { throw new Error("--platform is required when --app-version is provided"); } diff --git a/src/commands/publish-bundle.command.ts b/src/commands/publish-bundle.command.ts index bf13f83..10470e7 100644 --- a/src/commands/publish-bundle.command.ts +++ b/src/commands/publish-bundle.command.ts @@ -118,9 +118,8 @@ export class PublishBundleCommand extends BaseCommand { throw new Error("No react native project found in current directory"); } - // In JSON mode, route all incidental output (logs, spinners, the RN bundler's - // own stdout) to stderr so stdout carries ONLY the final JSON result. Left in - // place on error: the failure goes to stderr and stdout stays empty. + // --json: stdout carries only the final JSON result; everything else + // (logs, spinners, the RN bundler's stdout) goes to stderr. const restoreStdout = json ? silenceStdout() : undefined; let { @@ -229,10 +228,8 @@ export class PublishBundleCommand extends BaseCommand { }); const client = new ApiClient(getApiBaseUrl(region)); - // One spinner covers the whole publish: upload + waiting for the bundle to - // register. Registration is async (S3 event → update-uploaded-data), so we - // poll until it's queryable, otherwise a following release-bundle can race - // ahead into "bundle not found". Best-effort — it never fails the publish. + // Registration is async server-side: poll until the bundle is queryable so + // a following release-bundle can't race into "bundle not found". let registered = true; const { hash, version, bucketCreated, bucketName } = await progress( chalk.white("Publishing bundle"), @@ -288,10 +285,8 @@ export class PublishBundleCommand extends BaseCommand { /** * Poll the by-hash endpoint until the just-uploaded bundle is registered. - * Best-effort: returns true once confirmed, false if it doesn't register - * within the window. Never throws — a slow (or older) backend must not fail a - * publish whose upload already succeeded. Uses the CI-token route when a - * ci-token was supplied, otherwise the user route (client carries the token). + * Best-effort and never throws — a slow backend must not fail a publish + * whose upload already succeeded. */ private async waitForBundle( client: ApiClient, diff --git a/src/commands/release-info.command.ts b/src/commands/release-info.command.ts index 55fbdef..5568593 100644 --- a/src/commands/release-info.command.ts +++ b/src/commands/release-info.command.ts @@ -39,8 +39,6 @@ const expectedOptions: CommandOption[] = [ @ValidateUser() export class ReleaseInfoCommand extends BaseCommand { async execute(options: Record): Promise { - // Flag dependency ladder: a release is identified by - // (platform, appVersion, promotedId) — partial combos are ambiguous. if (options.appVersion && !options.platform) { throw new Error("--platform is required when --app-version is provided"); } @@ -54,13 +52,11 @@ export class ReleaseInfoCommand extends BaseCommand { const detail = await this.fetchDetail(options); if (!detail) return; - // In JSON mode emit the raw detail once and skip the formatted sections. if (json) { console.log(JSON.stringify(detail, null, 2)); return; } - // Console — pipeline stages anchored by an inline rollout meter (3f). const events = detail.eventCount ?? {}; const hashShort = detail.publishedBundle?.sha256Checksum ? String(detail.publishedBundle.sha256Checksum).slice(0, 12) @@ -105,7 +101,6 @@ export class ReleaseInfoCommand extends BaseCommand { else if (detail.isPaused) ui.status.warn("paused · rollout halted"); else ui.status.ok("healthy · no rollback configured"); - // Adoption Metrics — raw counts, shown last. ui.section("Adoption Metrics"); printBoard( [detail], diff --git a/src/commands/use.command.ts b/src/commands/use.command.ts index 60d8529..4774efe 100644 --- a/src/commands/use.command.ts +++ b/src/commands/use.command.ts @@ -50,8 +50,6 @@ export class UseCommand extends BaseCommand { const org = orgs.find((o) => o.orgId === orgId); const region = org?.region ?? "ap"; - // Projects live on the org's regional API — list them there, not on the - // global API the org listing came from. const regionalClient = await createUserApiClient(region); const projRes = await progress("Fetching projects", () => regionalClient.post<{ data: ProjectSummary[] }>(ENDPOINTS.PROJECT.LIST, { diff --git a/src/index.ts b/src/index.ts index 8da9772..0691d9f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -56,19 +56,15 @@ getCommands().forEach((options, name) => { command.action(async (...args) => { const opts = normalizeOptions(args.slice(0, args.length - 1)); try { - // Banner is chrome — never emit it in JSON mode (keeps stdout parseable). if (!opts.json) showBanner(); await registry.executeCommand(name, opts); } catch (error) { - // A command may have silenced stdout for --json; undo that first so the - // error object below reaches real stdout, not stderr. + // Undo any --json stdout silencing so the error object reaches real stdout. restoreStdout(); const message = mapServerError( error instanceof Error ? error.message : "Unknown error" ); - // In JSON mode, surface the failure as JSON on stdout so `jq` consumers - // get a parseable error object; the human line still goes to stderr. if (opts.json) { process.stdout.write(JSON.stringify({ error: message }) + "\n"); } diff --git a/src/ui/table.ts b/src/ui/table.ts index 8a78ec1..9d00094 100644 --- a/src/ui/table.ts +++ b/src/ui/table.ts @@ -12,11 +12,7 @@ export interface BoardColumn { /** Pre-colored display string for the cell (measured by visible width). */ render: (row: any) => string; align?: "left" | "right"; - /** - * Floor for responsive shrinking. Content wider than the final column width - * wraps onto extra lines, so a column that must stay readable (e.g. a hash - * that wraps into copyable halves) can pin its minimum width here. - */ + /** Floor for responsive shrinking; over-wide content wraps instead of clipping. */ minWidth?: number; } diff --git a/src/utils/common.ts b/src/utils/common.ts index 7b9390c..57951d6 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,6 +1,6 @@ import { CONFIG } from "@/api/config"; -/** Platforms a Stallion bundle can target — the single source of truth. */ +/** Platforms a Stallion bundle can target. */ export const PLATFORMS = ["android", "ios"] as const; export type Platform = (typeof PLATFORMS)[number]; diff --git a/src/utils/errors.ts b/src/utils/errors.ts index 5350ed0..29ce4b9 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -1,16 +1,8 @@ -/** - * Map terse API errors to actionable CLI messages. - * - * A bare "access denied" means the logged-in account can't read the requested - * org/project — most often a mistyped or foreign --project-id/--org-id, since - * the CLI passes explicit ids through without pre-validation. - */ +/** Map terse API errors to actionable CLI messages. */ export function mapServerError(message: string): string { if (/^access denied\.?$/i.test(message.trim())) { return 'Access denied. Check --org-id/--project-id, or run "stallion use".'; } - // The regional API doesn't host this project — usually a wrong or foreign - // --project-id. if (/project not found in this region/i.test(message)) { return 'Project not found. Check --project-id, or run "stallion use".'; } diff --git a/src/utils/list.ts b/src/utils/list.ts index c9424dc..d312b8c 100644 --- a/src/utils/list.ts +++ b/src/utils/list.ts @@ -1,33 +1,21 @@ import { ui } from "@/ui"; -/** - * Listing commands cap output to the most-recent N rows so a project with - * hundreds of buckets/bundles/releases doesn't dump a wall of text. The full - * set lives in the web console (a link is shown when the list is capped). - */ +/** Default number of rows shown by listing commands. */ export const LIST_LIMIT = 15; /** Server-enforced ceiling for --limit. */ export const MAX_LIST_LIMIT = 30; -/** Web console — where the full, unbounded lists live. */ export const CONSOLE_URL = "https://console.stalliontech.io"; -/** - * Resolve a --limit flag value: default LIST_LIMIT, clamped to MAX_LIST_LIMIT - * (the server enforces the same ceiling; clamping here avoids a 400 round-trip). - */ +/** Resolve a --limit flag value: default LIST_LIMIT, clamped to MAX_LIST_LIMIT. */ export function resolveLimit(value: unknown): number { const n = Number(value); if (!Number.isFinite(n) || n <= 0) return LIST_LIMIT; return Math.min(Math.floor(n), MAX_LIST_LIMIT); } -/** - * Shared footer for listing commands: a count line, plus (when the list was - * capped) a pointer to the console for the full set. Diagnostics only, so it - * rides on stderr like other hints and never touches stdout data. - */ +/** Shared listing footer: count line, plus a console link when capped. */ export function printListFooter( nounSingular: string, shown: number, @@ -44,11 +32,7 @@ export function printListFooter( } } -/** - * Sort `items` newest-first (by the given date accessor, when provided) and - * return the most-recent `LIST_LIMIT`, plus the untrimmed total so callers can - * show "Showing 15 of N". - */ +/** Sort newest-first and return the most-recent LIST_LIMIT plus the total. */ export function capRecent( items: T[], getDate?: (item: T) => string | number | Date | null | undefined diff --git a/src/utils/stdout.ts b/src/utils/stdout.ts index dd07bbd..b68a451 100644 --- a/src/utils/stdout.ts +++ b/src/utils/stdout.ts @@ -1,9 +1,6 @@ /** - * Redirect stdout to stderr until the returned restore fn is called. - * - * Used by `--json` command modes so stdout carries ONLY the final JSON result - * (safe for `$(... | jq)` capture in CI) while all diagnostics — logs, spinners, - * and child-process stdout (e.g. the RN bundler) — still stream to stderr. + * Redirect stdout to stderr until the returned restore fn is called — --json + * mode uses this so stdout carries only the final JSON result. */ let activeRestore: (() => void) | null = null; @@ -19,12 +16,7 @@ export function silenceStdout(): () => void { return restore; } -/** - * Restore stdout if it is currently silenced (idempotent). The top-level error - * handler calls this before emitting the `--json` error object, so a failure - * mid-command (which never reaches the command's own restore) still surfaces - * `{"error":...}` on real stdout for `jq`/CI consumers. - */ +/** Restore stdout if it is currently silenced (idempotent). */ export function restoreStdout(): void { activeRestore?.(); } From fc03b99c202205c7b8cac00a68c3f5684a1fe400 Mon Sep 17 00:00:00 2001 From: Maximus Decimus Meridius Date: Mon, 6 Jul 2026 02:16:54 +0530 Subject: [PATCH 13/13] chore: code review fix --- src/commands/generate-key-pair.command.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/generate-key-pair.command.ts b/src/commands/generate-key-pair.command.ts index 230a566..f57d5ec 100644 --- a/src/commands/generate-key-pair.command.ts +++ b/src/commands/generate-key-pair.command.ts @@ -68,8 +68,8 @@ export class GenerateKeyPairCommand extends BaseCommand { ui.status.warn("Keep your private key secure — never share or commit it."); ui.status.fail("Stallion cannot recover or validate lost keys — treat them as production secrets."); } catch (error: any) { - ui.status.fail("Failed to generate keys"); - if (error instanceof Error) ui.hint(` ${error.message}`); + const reason = error instanceof Error ? error.message : String(error); + throw new Error(`Failed to generate keys: ${reason}`); } } }