为何pnpm在Azure App Service中安装失败?
问题:切换到pnpm后部署出现预安装脚本错误
切换步骤
我按照以下步骤将项目从npm切换为pnpm:
npm install pnpm -g- 删除
node_modules - 删除
package-lock.json pnpm install
同时在package.json中添加了预安装脚本:
"preinstall": "npx only-allow pnpm",
报错情况
部署应用时遇到错误,报错信息显示:
Error: Could not find the package "only-allow" required by your preinstall script. Please install it first or remove the preinstall script from package.json.
相关配置文件
package.json
{ "name": "credium-gersthofen-app", "version": "0.1.0", "private": true, "scripts": { "preinstall": "npx only-allow pnpm", "predev": "node scripts/babel/backup.js", "prebuild": "node scripts/babel/backup.js", "precypress-run-component": "node scripts/babel/restore.js", "precypress-open-component": "node scripts/babel/restore.js", "precypress-run-e2e": "node scripts/babel/restore.js", "precypress-open-e2e": "node scripts/babel/restore.js", "dev": "next dev", "build": "next build", "test": "jest", "test-watch": "jest --watch", "start": "next start", "lint": "next lint", "lint-staged": "npx lint-staged", "lint-style": "npx stylelint src/*.css src/**/**/*.css", "cypress-open-component": "cypress open --component", "cypress-open-e2e": "cypress open --e2e", "cypress-run-component": "cypress run --component --headless", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build" }, "dependencies": { "@headlessui/tailwindcss": "^0.2.0", "@mdx-js/loader": "^3.0.0", "@mdx-js/react": "^3.0.0", "@next/mdx": "^14.0.2", "@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@reduxjs/toolkit": "^1.9.5", "@sentry/nextjs": "^7.80.0", "@testing-library/jest-dom": "^6.1.2", "@tremor/react": "^3.11.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/mdx": "^2.0.10", "@types/node": "20.4.0", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", "chai-colors": "^1.0.1", "chart.js": "^4.3.0", "class-variance-authority": "^0.7.0", "classnames": "^2.3.2", "clsx": "^2.0.0", "contentlayer": "^0.3.4", "eslint-config-next": "^14.0.2", "eslint-plugin-simple-import-sort": "^10.0.0", "framer-motion": "^10.16.4", "i18next": "^23.4.3", "i18next-browser-languagedetector": "^7.1.0", "i18next-resources-to-backend": "^1.1.4", "istanbul-lib-coverage": "^3.2.0", "jest-environment-jsdom": "^29.7.0", "lucide-react": "^0.292.0", "next": "^14.0.2", "next-contentlayer": "^0.3.4", "next-themes": "^0.2.1", "normalize.css": "^8.0.1", "react": "^18.2.0", "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.0", "react-i18next": "^13.0.3", "react-icons": "^4.10.1", "react-redux": "^8.1.1", "react-to-print": "^2.14.15", "redux-persist": "^6.0.0", "sharp": "^0.32.3", "stylelint": "^15.11.0", "swr": "^2.2.0", "tailwind-merge": "^2.0.0", "tailwindcss-animate": "^1.0.7", "tsconfig-paths-jest": "^0.0.1", "whatwg-fetch": "^3.6.18", "zod": "^3.22.4" }, "devDependencies": { "@babel/preset-react": "^7.22.5", "@commitlint/cli": "^17.7.2", "@commitlint/config-conventional": "^17.7.0", "@cypress/code-coverage": "^3.11.0", "@istanbuljs/nyc-config-typescript": "^1.0.2", "@storybook/addon-essentials": "^7.5.1", "@storybook/addon-interactions": "^7.5.1", "@storybook/addon-links": "^7.5.1", "@storybook/addon-onboarding": "^1.0.8", "@storybook/blocks": "^7.5.1", "@storybook/nextjs": "^7.5.1", "@storybook/react": "^7.5.1", "@storybook/testing-library": "^0.2.2", "@testing-library/react": "^14.1.2", "@typescript-eslint/eslint-plugin": "^6.3.0", "@typescript-eslint/parser": "^6.3.0", "autoprefixer": "^10.4.16", "babel-plugin-istanbul": "^6.1.1", "cypress": "^13.0.0", "cypress-react-selector": "^3.0.0", "eslint": "^8.46.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-cypress": "^2.14.0", "eslint-plugin-import": "^2.28.0", "eslint-plugin-jsx-a11y": "^6.7.1", "eslint-plugin-react": "^7.33.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-storybook": "^0.6.15", "fetch-mock": "^9.11.0", "husky": "^8.0.3", "jest": "^29.7.0", "jest-next-dynamic": "^1.0.1", "lint-staged": "^13.2.3", "msw": "^1.2.5", "node-fetch": "^3.3.2", "nyc": "^15.1.0", "postcss": "^8.4.31", "prettier": "^3.0.1", "storybook": "^7.5.1", "stylelint-config-standard": "^34.0.0", "stylelint-prettier": "^4.0.2", "tailwindcss": "^3.3.5", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", "typescript": "^5.1.6" } }
Azure Pipeline执行状态
Azure Pipeline的所有阶段(安装、测试、构建、归档发布)均执行成功,无报错。
Azure Pipeline配置
# Node.js with React Pipeline # Build a Node.js project that uses React using Azure DevOps. trigger: - dev pool: vmImage: ubuntu-latest stages: - stage: Install displayName: "Install" jobs: - job: InstallJob displayName: "Install Node.js and Dependencies" steps: - task: NodeTool@0 inputs: versionSpec: "^20.x" displayName: "Install Node.js" - script: | npm install -g pnpm pnpm install displayName: "Install Dependencies" - stage: Tests displayName: "Run Unit and Component Tests" jobs: - job: TestsJob displayName: "Run Unit and Component Tests" steps: - script: | npm install -g pnpm pnpm install pnpm test pnpm cypress-run-component displayName: "Run Unit and Component Tests" - stage: Build displayName: "Build Application" jobs: - job: BuildJob displayName: "Build Application" steps: - script: | npm install -g pnpm pnpm install pnpm build displayName: "Build Application" - stage: ArchiveAndPublish displayName: "Archive and Publish Artifacts" jobs: - job: ArchiveAndPublishJob displayName: "Archive Build Artifacts and Publish" steps: - task: ArchiveFiles@2 inputs: rootFolderOrFile: "." includeRootFolder: false archiveType: "zip" archiveFile: "$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip" replaceExistingArchive: true - task: PublishBuildArtifacts@1 inputs: PathtoPublish: "$(Build.ArtifactStagingDirectory)" ArtifactName: "drop" publishLocation: "Container"
解决方案
原因分析
预安装脚本使用npx only-allow pnpm时,部署环境可能无法通过npx临时获取该包,或者网络/权限问题导致下载失败。另外,pnpm的预安装脚本执行逻辑和npm略有不同,直接使用npx可能触发问题。
解决方法
将only-allow安装为开发依赖
执行命令:pnpm add -D only-allow然后修改preinstall脚本为:
"preinstall": "only-allow pnpm",这样部署环境安装依赖后就能直接调用该工具,无需npx临时下载。
使用无依赖的包管理器判断脚本
无需额外安装依赖,直接通过Node.js判断当前包管理器是否为pnpm,修改preinstall脚本为:"preinstall": "node -e \"if(process.env.npm_execpath.indexOf('pnpm') === -1) throw new Error('请使用pnpm安装依赖!')\""调整Pipeline预安装逻辑(可选)
如果是内部信任环境,可以在Pipeline的安装步骤中提前全局安装only-allow:npm install -g pnpm only-allow但更推荐前两种方法,避免环境差异导致的问题。
内容的提问来源于stack exchange,提问作者Ala Eddine Menai
相关产品推荐
相关产品推荐

