From 7fb921e28e32175cb4e0ed48efe925a9733ee3e6 Mon Sep 17 00:00:00 2001 From: Xiangzhe Date: Sat, 22 Aug 2026 13:57:59 -0300 Subject: [PATCH] fix(install): make ONNX chain optional for Android/Termux installs (#11095) --- changelog.d/fixes/11095-termux-onnx.md | 1 + package-lock.json | 72 ++++++++--- package.json | 4 +- .../optional-transformers-dependency.test.ts | 113 +++++++++++++----- 4 files changed, 140 insertions(+), 50 deletions(-) create mode 100644 changelog.d/fixes/11095-termux-onnx.md diff --git a/changelog.d/fixes/11095-termux-onnx.md b/changelog.d/fixes/11095-termux-onnx.md new file mode 100644 index 0000000000..8c26803710 --- /dev/null +++ b/changelog.d/fixes/11095-termux-onnx.md @@ -0,0 +1 @@ +- fix(install): make the ONNX dependency chain optional so Termux/Android installs succeed again (#11095) diff --git a/package-lock.json b/package-lock.json index 57f810910e..bf77bab602 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,6 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@huggingface/transformers": "^4.2.0", "@lobehub/icons": "^5.16.0", "@modelcontextprotocol/sdk": "^1.29.0", "@monaco-editor/react": "^4.7.0", @@ -61,7 +60,6 @@ "next-themes": "^0.4.6", "node-machine-id": "^1.1.12", "omniglyph": "^1.4.0", - "onnxruntime-node": "1.24.3", "open": "^11.0.1", "ora": "^9.4.1", "parse5": "^8.0.1", @@ -156,9 +154,11 @@ }, "optionalDependencies": { "@atjsh/llmlingua-2": "3.0.0", + "@huggingface/transformers": "^4.2.0", "better-sqlite3": "^13.0.2", "js-tiktoken": "^1.0.20", "keytar": "^7.9.0", + "onnxruntime-node": "1.24.3", "sqlite-vec": "^0.1.9", "tls-client-node": "^0.2.0", "wreq-js": "^3.0.0" @@ -4510,6 +4510,7 @@ "resolved": "https://registry.npmjs.org/@huggingface/jinja/-/jinja-0.5.9.tgz", "integrity": "sha512-uWTG+l3VJRsl7EXxYizuL3P+cCPoc3cRqbWWRcQN0FhejRfbdq0RNhCmbY/YDtnTcz9icdLYuLDjsnz4d8JMuw==", "license": "MIT", + "optional": true, "engines": { "node": ">=18" } @@ -4518,13 +4519,15 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@huggingface/tokenizers/-/tokenizers-0.1.3.tgz", "integrity": "sha512-8rF/RRT10u+kn7YuUbUg0OF30K8rjTc78aHpxT+qJ1uWSqxT1MHi8+9ltwYfkFYJzT/oS+qw3JVfHtNMGAdqyA==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "optional": true }, "node_modules/@huggingface/transformers": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@huggingface/transformers/-/transformers-4.2.0.tgz", "integrity": "sha512-8BRCoBMH0XsWaEIamuR0LrJGAfftgHAfb2Vrffy0VKlSAE/MnUJ5/h/zTfEP3fDIft+nk7TqB8xXEyABGitBjQ==", "license": "Apache-2.0", + "optional": true, "dependencies": { "@huggingface/jinja": "^0.5.6", "@huggingface/tokenizers": "^0.1.3", @@ -9483,30 +9486,35 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.1" @@ -9516,24 +9524,28 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "devOptional": true, "license": "BSD-3-Clause" }, "node_modules/@radix-ui/number": { @@ -12737,6 +12749,7 @@ "version": "26.2.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~8.3.0" @@ -13998,6 +14011,7 @@ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.6.0.tgz", "integrity": "sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==", "license": "MIT", + "optional": true, "engines": { "node": ">=14.0" } @@ -14971,7 +14985,8 @@ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/bottleneck": { "version": "2.19.5", @@ -17935,6 +17950,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "devOptional": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -17964,6 +17980,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "devOptional": true, "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", @@ -18064,7 +18081,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/detect-node-es": { "version": "1.1.0", @@ -18908,7 +18926,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/es6-promisify": { "version": "7.0.0", @@ -20400,7 +20419,8 @@ "version": "25.9.23", "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.9.23.tgz", "integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "optional": true }, "node_modules/flatted": { "version": "3.4.2", @@ -21226,6 +21246,7 @@ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", "license": "BSD-3-Clause", + "optional": true, "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", @@ -21243,6 +21264,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", + "optional": true, "bin": { "semver": "bin/semver.js" }, @@ -21291,6 +21313,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "devOptional": true, "license": "MIT", "dependencies": { "define-properties": "^1.2.1", @@ -21645,7 +21668,8 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz", "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==", - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/hachure-fill": { "version": "0.5.2", @@ -21679,6 +21703,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "devOptional": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -24790,7 +24815,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/json5": { "version": "2.2.3", @@ -26654,6 +26680,7 @@ "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", "license": "MIT", + "optional": true, "dependencies": { "escape-string-regexp": "^4.0.0" }, @@ -29425,6 +29452,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -29632,7 +29660,8 @@ "version": "1.24.3", "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.3.tgz", "integrity": "sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/onnxruntime-node": { "version": "1.24.3", @@ -29640,6 +29669,7 @@ "integrity": "sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg==", "hasInstallScript": true, "license": "MIT", + "optional": true, "os": [ "win32", "darwin", @@ -29656,6 +29686,7 @@ "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.26.0-dev.20260416-b7804b056c.tgz", "integrity": "sha512-MD6Ss4GSpQBo6zqoJzyT9LRbKYs7x/JVN23FT24EcEvlqF4VuzPOeH6X38orZPKHQDbprn7K+SBpu0/mj2CQiw==", "license": "MIT", + "optional": true, "dependencies": { "flatbuffers": "^25.1.24", "guid-typescript": "^1.0.9", @@ -29669,13 +29700,15 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "optional": true }, "node_modules/onnxruntime-web/node_modules/onnxruntime-common": { "version": "1.24.0-dev.20251116-b39e144322", "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.24.0-dev.20251116-b39e144322.tgz", "integrity": "sha512-BOoomdHYmNRL5r4iQ4bMvsl2t0/hzVQ3OM3PHD0gxeXu1PmggqBv3puZicEUVOA3AtHHYmqZtjMj9FOfGrATTw==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/open": { "version": "11.0.1", @@ -30906,7 +30939,8 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz", "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/playwright": { "version": "1.62.1", @@ -31861,6 +31895,7 @@ "version": "7.6.5", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", + "devOptional": true, "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -31884,6 +31919,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "devOptional": true, "license": "Apache-2.0" }, "node_modules/proxy-addr": { @@ -33280,6 +33316,7 @@ "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", "license": "BSD-3-Clause", + "optional": true, "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", @@ -33655,7 +33692,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", - "license": "MIT" + "license": "MIT", + "optional": true }, "node_modules/send": { "version": "1.2.1", @@ -33688,6 +33726,7 @@ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", "license": "MIT", + "optional": true, "dependencies": { "type-fest": "^0.13.1" }, @@ -33703,6 +33742,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", "license": "(MIT OR CC0-1.0)", + "optional": true, "engines": { "node": ">=10" }, @@ -34474,7 +34514,8 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "optional": true }, "node_modules/sql.js": { "version": "1.14.2", @@ -36187,6 +36228,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "devOptional": true, "license": "MIT" }, "node_modules/unicode-emoji-modifier-base": { diff --git a/package.json b/package.json index bf2740f233..55cc574f9e 100644 --- a/package.json +++ b/package.json @@ -265,7 +265,6 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@huggingface/transformers": "^4.2.0", "@lobehub/icons": "^5.16.0", "@modelcontextprotocol/sdk": "^1.29.0", "@monaco-editor/react": "^4.7.0", @@ -308,7 +307,6 @@ "next-themes": "^0.4.6", "node-machine-id": "^1.1.12", "omniglyph": "^1.4.0", - "onnxruntime-node": "1.24.3", "open": "^11.0.1", "ora": "^9.4.1", "parse5": "^8.0.1", @@ -343,9 +341,11 @@ }, "optionalDependencies": { "@atjsh/llmlingua-2": "3.0.0", + "@huggingface/transformers": "^4.2.0", "better-sqlite3": "^13.0.2", "js-tiktoken": "^1.0.20", "keytar": "^7.9.0", + "onnxruntime-node": "1.24.3", "sqlite-vec": "^0.1.9", "tls-client-node": "^0.2.0", "wreq-js": "^3.0.0" diff --git a/tests/unit/build/optional-transformers-dependency.test.ts b/tests/unit/build/optional-transformers-dependency.test.ts index 7ad60fb09c..cf5997a5f5 100644 --- a/tests/unit/build/optional-transformers-dependency.test.ts +++ b/tests/unit/build/optional-transformers-dependency.test.ts @@ -9,40 +9,52 @@ function readJson>(relPath: string): T { return JSON.parse(readFileSync(join(repoRoot, relPath), "utf8")) as T; } -test("@huggingface/transformers is a regular dependency so npm ci never skips it", () => { - // #9962 deliberately moved @huggingface/transformers out of optionalDependencies: - // as an optional dep, npm silently skipped the whole subtree on Node 24/26 (old - // pin dragged onnxruntime-node@1.21.0 whose NAN build no longer compiles), which - // broke `npm ci`/`next build` with "Can't resolve @huggingface/transformers" - // (lazy import in src/lib/memory/embedding/transformersLocal.ts). As a regular - // dep with onnxruntime-node@~1.24.3 (napi prebuilds, no node-gyp) it stays - // installable and the memory embedding path requires() cleanly. +test("ONNX chain (@huggingface/transformers + onnxruntime-node) stays optional so Termux/Android installs succeed", () => { + // #11095: onnxruntime-node declares os ["win32","darwin","linux"], so while + // these lived in `dependencies` every npm install on Android/Termux aborted + // with a fatal EBADPLATFORM. As optionalDependencies npm skips only the + // unsupported-platform subtree (with a warning) and installs normally + // everywhere else. This deliberately reverses the MECHANISM of #9962 while + // keeping its goal: #9962's skip happened because the old onnxruntime-node@ + // 1.21.0 pin built from source (NAN) and failed to compile on Node 24/26; + // the current 1.24.3 pin ships napi prebuilds, so on supported platforms the + // chain always installs and `npm ci`/`next build` keep resolving it. On + // platforms where it IS skipped, both consumers degrade gracefully via lazy/ + // dynamic imports (asserted below). const pkg = readJson<{ dependencies?: Record; optionalDependencies?: Record; + overrides?: Record; }>("package.json"); assert.equal( pkg.dependencies?.["@huggingface/transformers"], + undefined, + "transformers must NOT be a hard dependency (fatal EBADPLATFORM on Android)" + ); + assert.equal( + pkg.optionalDependencies?.["@huggingface/transformers"], "^4.2.0", - "transformers must be a regular dependency (never optional) so npm ci cannot skip it" + "transformers must be an optionalDependency" ); - assert.equal(pkg.optionalDependencies?.["@huggingface/transformers"], undefined); -}); - -test("transformers + onnxruntime-node are regular dependencies (not optional)", () => { - const pkg = readJson<{ - dependencies?: Record; - optionalDependencies?: Record; - }>("package.json"); - assert.equal( pkg.dependencies?.["onnxruntime-node"], - "1.24.3", - "onnxruntime-node is a regular dep (napi prebuilds, installable on Node 24/26)" + undefined, + "onnxruntime-node must NOT be a hard dependency (fatal EBADPLATFORM on Android)" ); - assert.equal(pkg.optionalDependencies?.["onnxruntime-node"], undefined); + assert.equal( + pkg.optionalDependencies?.["onnxruntime-node"], + "1.24.3", + "onnxruntime-node must be an optionalDependency pinned in lockstep with the overrides pin" + ); + assert.equal( + pkg.overrides?.["onnxruntime-node"], + "1.24.3", + "the overrides pin must stay aligned with @huggingface/transformers' own pin (single-copy invariant)" + ); +}); +test("lockfile marks the whole ONNX chain optional", () => { const lock = readJson<{ packages: Record< string, @@ -51,26 +63,61 @@ test("transformers + onnxruntime-node are regular dependencies (not optional)", dependencies?: Record; optionalDependencies?: Record; } - >; + >; }>("package-lock.json"); assert.equal( - lock.packages[""]?.dependencies?.["@huggingface/transformers"], + lock.packages[""]?.optionalDependencies?.["@huggingface/transformers"], "^4.2.0", - "root lock dependencies must hold transformers as a regular (non-optional) dep" + "root lock optionalDependencies must hold transformers" ); - // Optional flag is only written `true` for genuinely optional packages; - // regular deps leave it absent/null. Assert each is NOT optional. - assert.ok( - !lock.packages["node_modules/@huggingface/transformers"]?.optional, - "transformers must not be marked optional in the lockfile" + assert.equal( + lock.packages[""]?.optionalDependencies?.["onnxruntime-node"], + "1.24.3", + "root lock optionalDependencies must hold onnxruntime-node" ); assert.ok( - !lock.packages["node_modules/onnxruntime-node"]?.optional, - "onnxruntime-node must not be marked optional in the lockfile" + lock.packages["node_modules/@huggingface/transformers"]?.optional, + "transformers must be marked optional in the lockfile" ); assert.ok( - !lock.packages["node_modules/onnxruntime-common"]?.optional, - "onnxruntime-common must not be marked optional in the lockfile" + lock.packages["node_modules/onnxruntime-node"]?.optional, + "onnxruntime-node must be marked optional in the lockfile" + ); + assert.ok( + lock.packages["node_modules/onnxruntime-common"]?.optional, + "onnxruntime-common must be marked optional in the lockfile" + ); +}); + +test("every @huggingface/transformers consumer loads it lazily so absent installs degrade gracefully", () => { + // If any module ever switches to a STATIC import of the optional chain, + // startup crashes on platforms where npm skipped it (Android/Termux). + // transformersLocal.ts must keep its lazy await import() (D8/D25); + // onnxWorker.ts must keep its runtime-variable dynamicImport indirection. + + const embeddingSrc = readFileSync( + join(repoRoot, "src/lib/memory/embedding/transformersLocal.ts"), + "utf8" + ); + assert.doesNotMatch( + embeddingSrc, + /^\s*import\s+(?:[^'"]*?\s+from\s+)?["']@huggingface\/transformers["']/m, + "transformersLocal.ts must not statically import @huggingface/transformers" + ); + assert.match( + embeddingSrc, + /await import\(["']@huggingface\/transformers["']\)/, + "transformersLocal.ts must load @huggingface/transformers via await import()" + ); + + const workerSrc = readFileSync( + join(repoRoot, "open-sse/services/compression/engines/llmlingua/onnxWorker.ts"), + "utf8" + ); + assert.doesNotMatch( + workerSrc, + /^\s*import\s+(?:[^'"]*?\s+from\s+)?["']@huggingface\/transformers["']/m, + "onnxWorker.ts must not statically import @huggingface/transformers" ); });