您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在 NPM 包页面添加多个包管理器安装命令(yarn、pnpm、bun)。支持常规依赖和开发依赖,提供一键复制功能。
// ==UserScript== // @name Add More Install Commands On NPM Package Page // @name:zh-CN 在 NPM 包页面添加更多安装命令 // @namespace tinywaves // @version 0.0.2 // @description Enhances NPM package pages by adding multiple package manager install commands (yarn, pnpm, bun) alongside the default npm command. Supports both regular and dev dependencies with one-click copy functionality. // @description:zh-CN 在 NPM 包页面添加多个包管理器安装命令(yarn、pnpm、bun)。支持常规依赖和开发依赖,提供一键复制功能。 // @author tinywaves · https://github.com/tinywaves // @author:zh-CN tinywaves · https://github.com/tinywaves // @match https://www.npmjs.com/package/* // @icon https://static-production.npmjs.com/1996fcfdf7ca81ea795f67f093d7f449.png // @icon:zh-CN https://static-production.npmjs.com/1996fcfdf7ca81ea795f67f093d7f449.png // @grant none // @license MIT // ==/UserScript== (() => { 'use strict'; const showToast = (message) => { const existingToast = document.querySelector('.npm-toast'); if (existingToast) { existingToast.remove(); } const toast = document.createElement('div'); toast.className = 'npm-toast'; Object.assign(toast.style, { position: 'fixed', top: '20px', left: '50%', transform: 'translateX(-50%)', backgroundColor: '#f8f9fa', border: '1px solid #e9ecef', borderRadius: '20px', padding: '8px 16px', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', zIndex: '10000', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '14px', color: '#212529', opacity: '0', transition: 'opacity 0.3s ease-in-out', }); const text = document.createElement('span'); text.textContent = message; toast.appendChild(text); document.body.appendChild(toast); setTimeout(() => (toast.style.opacity = '1'), 10); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 300); }, 3000); }; const waitForElement = (text, options) => { options = options || {}; const containerSelector = options.containerSelector; const exact = options.exact || false; const timeout = options.timeout || 5000; const container = containerSelector ? document.querySelector(containerSelector) : document.body; if (!container) { return Promise.reject('Container not found'); } const matchFn = (el) => { if (!el.textContent) { return false; } return exact ? el.textContent.trim() === text : el.textContent.includes(text); }; const found = Array.from(container.querySelectorAll('*')).find(matchFn); if (found) { return Promise.resolve(found); } return new Promise((resolve, reject) => { const observer = new MutationObserver((mutations) => { for (let i = 0; i < mutations.length; i++) { const mutation = mutations[i]; for (let j = 0; j < mutation.addedNodes.length; j++) { const node = mutation.addedNodes[j]; if (!(node instanceof HTMLElement)) { continue; } if (matchFn(node)) { observer.disconnect(); clearTimeout(timer); return resolve(node); } const child = node.querySelector('*'); if (child && matchFn(child)) { observer.disconnect(); clearTimeout(timer); return resolve(child); } } } }); observer.observe(container, { childList: true, subtree: true }); const timer = setTimeout(() => { observer.disconnect(); reject('Element not found within timeout'); }, timeout); }); }; const createInstallCommandElement = (nextElement, commandText, packageName) => { const clonedNextElement = nextElement.cloneNode(true); const clonedNextCodeElement = clonedNextElement.querySelector('code'); if (clonedNextCodeElement) { clonedNextCodeElement.textContent = commandText; } const button = clonedNextElement.querySelector('button'); if (button) { button.addEventListener('click', () => { navigator.clipboard .writeText(commandText) .then(() => showToast(`${packageName} install command copied!`)) .catch(() => showToast(`${packageName} install command copy failed`)); }); } return clonedNextElement; }; waitForElement('Install', { exact: true }) .then((el) => { const nextElement = el.nextElementSibling; if (!nextElement) { return; } const nextCodeElement = nextElement.querySelector('code'); if (!nextCodeElement) { return; } const text = nextCodeElement.textContent; const textSplits = text.split(' '); const packageName = textSplits[2]; if (!packageName) { return; } [ 'yarn add <> -D', 'yarn add <>', 'bun add <> -D', 'bun add <>', 'pnpm add <> -D', 'pnpm add <>', ].forEach((command) => { const commandText = `${command.replace('<>', packageName)}`; el.parentNode.insertBefore( createInstallCommandElement(nextElement, commandText, packageName), el.nextSibling ); }); nextElement.parentNode.insertBefore( createInstallCommandElement(nextElement, `npm i ${packageName} -D`, packageName), nextElement.nextSibling ); }) .catch(console.error); })();