From c38247ed5cff515b1bb0f06f923cb9ee68f23056 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Thu, 4 Sep 2025 23:00:27 +0200 Subject: [PATCH] Reduce bundle size & check it in CI (#7395) --- .github/workflows/ci.yml | 21 + esbuild.config.js | 29 +- package-lock.json | 497 ++++-------------- package.json | 5 +- packages/cli/package.json | 3 - packages/core/package.json | 1 + .../core/src/tools/read-many-files.test.ts | 8 +- packages/core/src/utils/fileUtils.test.ts | 41 +- packages/core/src/utils/fileUtils.ts | 9 +- packages/vscode-ide-companion/package.json | 2 +- 10 files changed, 184 insertions(+), 432 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4264434ae..8d4ebdb77f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -371,3 +371,24 @@ jobs: - name: 'Perform CodeQL Analysis' uses: 'github/codeql-action/analyze@df559355d593797519d70b90fc8edd5db049e7a2' # ratchet:github/codeql-action/analyze@v3 + + # Check for changes in bundle size. + bundle_size: + name: 'Check Bundle Size' + runs-on: 'ubuntu-latest' + permissions: + contents: 'read' # For checkout + pull-requests: 'write' # For commenting + + steps: + - name: 'Checkout' + uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 + with: + fetch-depth: 1 + + - uses: 'preactjs/compressed-size-action@946a292cd35bd1088e0d7eb92b69d1a8d5b5d76a' + with: + repo-token: '${{ secrets.GITHUB_TOKEN }}' + pattern: './bundle/**/*.{js,sb}' + minimum-change-threshold: '1000' + compression: 'none' diff --git a/esbuild.config.js b/esbuild.config.js index 89f9197ef5..e7f2ea5a04 100644 --- a/esbuild.config.js +++ b/esbuild.config.js @@ -8,12 +8,24 @@ import esbuild from 'esbuild'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { createRequire } from 'node:module'; +import { writeFileSync } from 'node:fs'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const require = createRequire(import.meta.url); const pkg = require(path.resolve(__dirname, 'package.json')); +const external = [ + '@lydell/node-pty', + 'node-pty', + '@lydell/node-pty-darwin-arm64', + '@lydell/node-pty-darwin-x64', + '@lydell/node-pty-linux-x64', + '@lydell/node-pty-win32-arm64', + '@lydell/node-pty-win32-x64', + 'node-fetch', +]; + esbuild .build({ entryPoints: ['packages/cli/index.ts'], @@ -21,15 +33,7 @@ esbuild outfile: 'bundle/gemini.js', platform: 'node', format: 'esm', - external: [ - '@lydell/node-pty', - 'node-pty', - '@lydell/node-pty-darwin-arm64', - '@lydell/node-pty-darwin-x64', - '@lydell/node-pty-linux-x64', - '@lydell/node-pty-win32-arm64', - '@lydell/node-pty-win32-x64', - ], + external, alias: { 'is-in-ci': path.resolve( __dirname, @@ -43,5 +47,12 @@ esbuild js: `import { createRequire } from 'module'; const require = createRequire(import.meta.url); globalThis.__filename = require('url').fileURLToPath(import.meta.url); globalThis.__dirname = require('path').dirname(globalThis.__filename);`, }, loader: { '.node': 'file' }, + metafile: true, + write: true, + }) + .then(({ metafile }) => { + if (process.env.DEV === 'true') { + writeFileSync('./bundle/esbuild.json', JSON.stringify(metafile, null, 2)); + } }) .catch(() => process.exit(1)); diff --git a/package-lock.json b/package-lock.json index f8ea266209..b5163bb04c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,7 @@ ], "dependencies": { "@lvce-editor/ripgrep": "^1.6.0", - "simple-git": "^3.28.0", - "strip-ansi": "^7.1.0" + "simple-git": "^3.28.0" }, "bin": { "gemini": "bundle/gemini.js" @@ -26,7 +25,6 @@ "@types/shell-quote": "^1.7.5", "@vitest/coverage-v8": "^3.1.1", "@vitest/eslint-plugin": "^1.3.4", - "concurrently": "^9.2.0", "cross-env": "^7.0.3", "esbuild": "^0.25.0", "eslint": "^9.24.0", @@ -38,7 +36,6 @@ "glob": "^10.4.5", "globals": "^16.0.0", "json": "^11.0.0", - "lodash": "^4.17.21", "memfs": "^4.17.2", "mnemonist": "^0.40.3", "mock-fs": "^5.5.0", @@ -3831,23 +3828,6 @@ "@types/node": "*" } }, - "node_modules/@types/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/marked": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz", @@ -6361,32 +6341,6 @@ "dev": true, "license": "MIT" }, - "node_modules/concurrently": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.0.tgz", - "integrity": "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "lodash": "^4.17.21", - "rxjs": "^7.8.1", - "shell-quote": "^1.8.1", - "supports-color": "^8.1.1", - "tree-kill": "^1.2.2", - "yargs": "^17.7.2" - }, - "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" - } - }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -7167,16 +7121,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-abstract": { "version": "1.24.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", @@ -9635,13 +9579,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -10400,12 +10337,15 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "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==", + "node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -10655,36 +10595,6 @@ "uc.micro": "^2.0.0" } }, - "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": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -10708,12 +10618,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -11331,13 +11235,6 @@ "node": ">= 0.6" } }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true, - "license": "MIT" - }, "node_modules/node-abi": { "version": "3.75.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", @@ -11479,216 +11376,106 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all2": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-8.0.4.tgz", + "integrity": "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" + "picomatch": "^4.0.2", + "pidtree": "^0.6.0", + "read-package-json-fast": "^4.0.0", + "shell-quote": "^1.7.3", + "which": "^5.0.0" }, "bin": { "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", "run-p": "bin/run-p/index.js", "run-s": "bin/run-s/index.js" }, "engines": { - "node": ">= 4" + "node": "^20.5.0 || >=22.0.0", + "npm": ">= 10" } }, - "node_modules/npm-run-all/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==", + "node_modules/npm-run-all2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" + "engines": { + "node": ">=12" }, - "engines": { - "node": ">=4" + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/npm-run-all/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/npm-run-all/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/npm-run-all/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/npm-run-all/node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/npm-run-all/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/npm-run-all/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/npm-run-all/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/npm-run-all/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/npm-run-all/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/npm-run-all2/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm-run-all2/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/npm-run-all2/node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", "bin": { - "semver": "bin/semver" - } - }, - "node_modules/npm-run-all/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" + "pidtree": "bin/pidtree.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=0.10" } }, - "node_modules/npm-run-all/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all/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/npm-run-all/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/npm-run-all2/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { - "isexe": "^2.0.0" + "isexe": "^3.1.1" }, "bin": { - "which": "bin/which" + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/npm-run-path": { @@ -12279,19 +12066,6 @@ "license": "MIT", "peer": true }, - "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -12343,29 +12117,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", - "dev": true, - "license": "MIT", - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "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/pkce-challenge": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", @@ -12863,6 +12614,20 @@ "node": ">=0.8" } }, + "node_modules/read-package-json-fast": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", + "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/read-package-up": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", @@ -13289,16 +13054,6 @@ "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==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -14098,25 +13853,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/string.prototype.padend": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", - "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/string.prototype.repeat": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", @@ -14352,22 +14088,6 @@ "node": ">=14.18.0" } }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/supports-hyperlinks": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", @@ -14972,16 +14692,6 @@ "tslib": "2" } }, - "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/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -16684,9 +16394,7 @@ "ink": "^6.2.3", "ink-gradient": "^3.0.0", "ink-spinner": "^5.0.0", - "lodash-es": "^4.17.21", "lowlight": "^3.3.0", - "mime-types": "^3.0.1", "open": "^10.1.2", "react": "^19.1.0", "read-package-up": "^11.0.0", @@ -16710,7 +16418,6 @@ "@types/command-exists": "^1.2.3", "@types/diff": "^7.0.2", "@types/dotenv": "^6.1.1", - "@types/lodash-es": "^4.17.12", "@types/node": "^20.11.24", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", @@ -16894,6 +16601,7 @@ "https-proxy-agent": "^7.0.6", "ignore": "^7.0.0", "marked": "^15.0.12", + "mime": "4.0.7", "mnemonist": "^0.40.3", "open": "^10.1.2", "picomatch": "^4.0.1", @@ -16972,6 +16680,21 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "packages/core/node_modules/mime": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.7.tgz", + "integrity": "sha512-2OfDPL+e03E0LrXaGYOtTFIYhiuzep94NSsuhrNULq+stylcJedcHdzHtz0atMUuGwJfFYs0YL5xeC/Ca2x0eQ==", + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, "packages/core/node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", @@ -17015,7 +16738,7 @@ "@vscode/vsce": "^3.6.0", "esbuild": "^0.25.3", "eslint": "^9.25.1", - "npm-run-all": "^4.1.5", + "npm-run-all2": "^8.0.2", "typescript": "^5.8.3", "vitest": "^3.2.4" }, diff --git a/package.json b/package.json index fe7d8fb6ab..7f61c4d450 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "@types/shell-quote": "^1.7.5", "@vitest/coverage-v8": "^3.1.1", "@vitest/eslint-plugin": "^1.3.4", - "concurrently": "^9.2.0", "cross-env": "^7.0.3", "esbuild": "^0.25.0", "eslint": "^9.24.0", @@ -80,7 +79,6 @@ "glob": "^10.4.5", "globals": "^16.0.0", "json": "^11.0.0", - "lodash": "^4.17.21", "memfs": "^4.17.2", "mnemonist": "^0.40.3", "mock-fs": "^5.5.0", @@ -94,8 +92,7 @@ }, "dependencies": { "@lvce-editor/ripgrep": "^1.6.0", - "simple-git": "^3.28.0", - "strip-ansi": "^7.1.0" + "simple-git": "^3.28.0" }, "optionalDependencies": { "@lydell/node-pty": "1.1.0", diff --git a/packages/cli/package.json b/packages/cli/package.json index 5921088ceb..9203c50979 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -42,9 +42,7 @@ "ink": "^6.2.3", "ink-gradient": "^3.0.0", "ink-spinner": "^5.0.0", - "lodash-es": "^4.17.21", "lowlight": "^3.3.0", - "mime-types": "^3.0.1", "open": "^10.1.2", "react": "^19.1.0", "read-package-up": "^11.0.0", @@ -65,7 +63,6 @@ "@types/command-exists": "^1.2.3", "@types/diff": "^7.0.2", "@types/dotenv": "^6.1.1", - "@types/lodash-es": "^4.17.12", "@types/node": "^20.11.24", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", diff --git a/packages/core/package.json b/packages/core/package.json index 62a5d042ac..bf9a4d63a6 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -50,6 +50,7 @@ "https-proxy-agent": "^7.0.6", "ignore": "^7.0.0", "marked": "^15.0.12", + "mime": "4.0.7", "mnemonist": "^0.40.3", "open": "^10.1.2", "picomatch": "^4.0.1", diff --git a/packages/core/src/tools/read-many-files.test.ts b/packages/core/src/tools/read-many-files.test.ts index 2059f0d42e..f0711820dc 100644 --- a/packages/core/src/tools/read-many-files.test.ts +++ b/packages/core/src/tools/read-many-files.test.ts @@ -24,8 +24,8 @@ import * as glob from 'glob'; vi.mock('glob', { spy: true }); -vi.mock('mime-types', () => { - const lookup = (filename: string) => { +vi.mock('mime', () => { + const getType = (filename: string) => { if (filename.endsWith('.ts') || filename.endsWith('.js')) { return 'text/plain'; } @@ -45,9 +45,9 @@ vi.mock('mime-types', () => { }; return { default: { - lookup, + getType, }, - lookup, + getType, }; }); diff --git a/packages/core/src/utils/fileUtils.test.ts b/packages/core/src/utils/fileUtils.test.ts index 706b0dc077..fe6860f1f1 100644 --- a/packages/core/src/utils/fileUtils.test.ts +++ b/packages/core/src/utils/fileUtils.test.ts @@ -18,7 +18,8 @@ import * as actualNodeFs from 'node:fs'; // For setup/teardown import fsPromises from 'node:fs/promises'; import path from 'node:path'; import os from 'node:os'; -import mime from 'mime-types'; +// eslint-disable-next-line import/no-internal-modules +import mime from 'mime/lite'; import { isWithinRoot, @@ -30,12 +31,12 @@ import { } from './fileUtils.js'; import { StandardFileSystemService } from '../services/fileSystemService.js'; -vi.mock('mime-types', () => ({ - default: { lookup: vi.fn() }, - lookup: vi.fn(), +vi.mock('mime/lite', () => ({ + default: { getType: vi.fn() }, + getType: vi.fn(), })); -const mockMimeLookup = mime.lookup as Mock; +const mockMimeGetType = mime.getType as Mock; describe('fileUtils', () => { let tempRootDir: string; @@ -49,7 +50,7 @@ describe('fileUtils', () => { let directoryPath: string; beforeEach(() => { - vi.resetAllMocks(); // Reset all mocks, including mime.lookup + vi.resetAllMocks(); // Reset all mocks, including mime.getType tempRootDir = actualNodeFs.mkdtempSync( path.join(os.tmpdir(), 'fileUtils-test-'), @@ -570,12 +571,12 @@ describe('fileUtils', () => { }); it('should detect image type by extension (png)', async () => { - mockMimeLookup.mockReturnValueOnce('image/png'); + mockMimeGetType.mockReturnValueOnce('image/png'); expect(await detectFileType('file.png')).toBe('image'); }); it('should detect image type by extension (jpeg)', async () => { - mockMimeLookup.mockReturnValueOnce('image/jpeg'); + mockMimeGetType.mockReturnValueOnce('image/jpeg'); expect(await detectFileType('file.jpg')).toBe('image'); }); @@ -585,31 +586,31 @@ describe('fileUtils', () => { }); it('should detect pdf type by extension', async () => { - mockMimeLookup.mockReturnValueOnce('application/pdf'); + mockMimeGetType.mockReturnValueOnce('application/pdf'); expect(await detectFileType('file.pdf')).toBe('pdf'); }); it('should detect audio type by extension', async () => { - mockMimeLookup.mockReturnValueOnce('audio/mpeg'); + mockMimeGetType.mockReturnValueOnce('audio/mpeg'); expect(await detectFileType('song.mp3')).toBe('audio'); }); it('should detect video type by extension', async () => { - mockMimeLookup.mockReturnValueOnce('video/mp4'); + mockMimeGetType.mockReturnValueOnce('video/mp4'); expect(await detectFileType('movie.mp4')).toBe('video'); }); it('should detect known binary extensions as binary (e.g. .zip)', async () => { - mockMimeLookup.mockReturnValueOnce('application/zip'); + mockMimeGetType.mockReturnValueOnce('application/zip'); expect(await detectFileType('archive.zip')).toBe('binary'); }); it('should detect known binary extensions as binary (e.g. .exe)', async () => { - mockMimeLookup.mockReturnValueOnce('application/octet-stream'); // Common for .exe + mockMimeGetType.mockReturnValueOnce('application/octet-stream'); // Common for .exe expect(await detectFileType('app.exe')).toBe('binary'); }); it('should use isBinaryFile for unknown extensions and detect as binary', async () => { - mockMimeLookup.mockReturnValueOnce(false); // Unknown mime type + mockMimeGetType.mockReturnValueOnce(false); // Unknown mime type // Create a file that isBinaryFile will identify as binary const binaryContent = Buffer.from([ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, @@ -619,7 +620,7 @@ describe('fileUtils', () => { }); it('should default to text if mime type is unknown and content is not binary', async () => { - mockMimeLookup.mockReturnValueOnce(false); // Unknown mime type + mockMimeGetType.mockReturnValueOnce(false); // Unknown mime type // filePathForDetectTest is already a text file by default from beforeEach expect(await detectFileType(filePathForDetectTest)).toBe('text'); }); @@ -677,7 +678,7 @@ describe('fileUtils', () => { it('should handle read errors for image/pdf files', async () => { actualNodeFs.writeFileSync(testImageFilePath, 'content'); // File must exist - mockMimeLookup.mockReturnValue('image/png'); + mockMimeGetType.mockReturnValue('image/png'); const readError = new Error('Simulated image read error'); vi.spyOn(fsPromises, 'readFile').mockRejectedValueOnce(readError); @@ -693,7 +694,7 @@ describe('fileUtils', () => { it('should process an image file', async () => { const fakePngData = Buffer.from('fake png data'); actualNodeFs.writeFileSync(testImageFilePath, fakePngData); - mockMimeLookup.mockReturnValue('image/png'); + mockMimeGetType.mockReturnValue('image/png'); const result = await processSingleFileContent( testImageFilePath, tempRootDir, @@ -715,7 +716,7 @@ describe('fileUtils', () => { it('should process a PDF file', async () => { const fakePdfData = Buffer.from('fake pdf data'); actualNodeFs.writeFileSync(testPdfFilePath, fakePdfData); - mockMimeLookup.mockReturnValue('application/pdf'); + mockMimeGetType.mockReturnValue('application/pdf'); const result = await processSingleFileContent( testPdfFilePath, tempRootDir, @@ -743,7 +744,7 @@ describe('fileUtils', () => { const testSvgFilePath = path.join(tempRootDir, 'test.svg'); actualNodeFs.writeFileSync(testSvgFilePath, svgContent, 'utf-8'); - mockMimeLookup.mockReturnValue('image/svg+xml'); + mockMimeGetType.mockReturnValue('image/svg+xml'); const result = await processSingleFileContent( testSvgFilePath, @@ -760,7 +761,7 @@ describe('fileUtils', () => { testBinaryFilePath, Buffer.from([0x00, 0x01, 0x02]), ); - mockMimeLookup.mockReturnValueOnce('application/octet-stream'); + mockMimeGetType.mockReturnValueOnce('application/octet-stream'); // isBinaryFile will operate on the real file. const result = await processSingleFileContent( diff --git a/packages/core/src/utils/fileUtils.ts b/packages/core/src/utils/fileUtils.ts index fba5de922b..f623ae58c8 100644 --- a/packages/core/src/utils/fileUtils.ts +++ b/packages/core/src/utils/fileUtils.ts @@ -7,7 +7,8 @@ import fs from 'node:fs'; import path from 'node:path'; import type { PartUnion } from '@google/genai'; -import mime from 'mime-types'; +// eslint-disable-next-line import/no-internal-modules +import mime from 'mime/lite'; import type { FileSystemService } from '../services/fileSystemService.js'; import { ToolErrorType } from '../tools/tool-error.js'; import { BINARY_EXTENSIONS } from './ignorePatterns.js'; @@ -157,7 +158,7 @@ export async function readFileWithEncoding(filePath: string): Promise { * @returns The specific MIME type string (e.g., 'text/python', 'application/javascript') or undefined if not found or ambiguous. */ export function getSpecificMimeType(filePath: string): string | undefined { - const lookedUpMime = mime.lookup(filePath); + const lookedUpMime = mime.getType(filePath); return typeof lookedUpMime === 'string' ? lookedUpMime : undefined; } @@ -261,7 +262,7 @@ export async function detectFileType( return 'svg'; } - const lookedUpMimeType = mime.lookup(filePath); // Returns false if not found, or the mime type string + const lookedUpMimeType = mime.getType(filePath); // Returns null if not found, or the mime type string if (lookedUpMimeType) { if (lookedUpMimeType.startsWith('image/')) { return 'image'; @@ -437,7 +438,7 @@ export async function processSingleFileContent( llmContent: { inlineData: { data: base64Data, - mimeType: mime.lookup(filePath) || 'application/octet-stream', + mimeType: mime.getType(filePath) || 'application/octet-stream', }, }, returnDisplay: `Read ${fileType} file: ${relativePathForDisplay}`, diff --git a/packages/vscode-ide-companion/package.json b/packages/vscode-ide-companion/package.json index dc66c119f9..bc3a7030a3 100644 --- a/packages/vscode-ide-companion/package.json +++ b/packages/vscode-ide-companion/package.json @@ -119,7 +119,7 @@ "@vscode/vsce": "^3.6.0", "esbuild": "^0.25.3", "eslint": "^9.25.1", - "npm-run-all": "^4.1.5", + "npm-run-all2": "^8.0.2", "typescript": "^5.8.3", "vitest": "^3.2.4" },