无网络环境下如何将本地node_modules上传至Nexus搭建私有npm仓库?
Great question! Since your Jenkins server doesn't have network access and you can't use an npm proxy, uploading packages from your local node_modules to a Nexus private npm repository is a perfect workaround. Let's walk through exactly how to do this, including the required package structure and step-by-step workflows.
1. First: Set Up Your Nexus npm Hosted Repository
Before uploading anything, make sure you have a Hosted-type npm repository configured in Nexus:
- Log into your Nexus UI, go to Repositories → Create repository → Select npm (hosted).
- Configure basic settings (name, storage location, etc.), and ensure the repository allows deployments (set Deployment policy to "Allow redeploy" if you might need to update packages later).
- Note down the repository's URL (e.g.,
http://your-nexus-instance:8081/repository/npm-private/). - Make sure you have a Nexus user account with deploy permissions for this repository.
2. Required Package Structure: Don't Upload node_modules Directly
Nexus's npm repository only recognizes standard npm package archives (.tgz files) — you can't just upload the raw node_modules folder. Each package must:
- Be a valid npm package (contains a
package.jsonwith required fields likename,version,main, etc.). - Be packaged into a
.tgzarchive usingnpm pack(this ensures the package follows npm's structural rules, including proper file inclusion/exclusion based onpackage.json'sfilesfield or.npmignore).
3. Batch Package All Valid Packages from node_modules
Your node_modules folder has all the installed packages, but we need to turn each one into a .tgz file. You can automate this with a simple script:
Shell Script (Linux/macOS)
Create a script named pack-packages.sh in the same directory as your node_modules folder:
#!/bin/bash # Create a folder to store all packaged .tgz files mkdir -p npm-packages # Process regular (non-scoped) packages for dir in node_modules/*/; do if [ -f "$dir/package.json" ]; then echo "Packing $(basename "$dir")..." cd "$dir" npm pack > /dev/null 2>&1 mv *.tgz ../../npm-packages/ cd ../.. fi done # Process scoped packages (e.g., @babel/core) for scope_dir in node_modules/@*/*/; do if [ -f "$scope_dir/package.json" ]; then pkg_name=$(basename "$(dirname "$scope_dir")")/$(basename "$scope_dir") echo "Packing $pkg_name..." cd "$scope_dir" npm pack > /dev/null 2>&1 mv *.tgz ../../../npm-packages/ cd ../../.. fi done echo "Done! All packages are in the npm-packages folder."
Run it with:
chmod +x pack-packages.sh && ./pack-packages.sh
Node.js Script (Cross-Platform)
If you prefer a cross-platform option, create pack-packages.js:
const fs = require('fs').promises; const path = require('path'); const { execSync } = require('child_process'); const rootDir = process.cwd(); const outputDir = path.join(rootDir, 'npm-packages'); async function packPackage(packagePath) { try { const pkgJson = require(path.join(packagePath, 'package.json')); console.log(`Packing ${pkgJson.name}@${pkgJson.version}...`); process.chdir(packagePath); execSync('npm pack', { stdio: 'ignore' }); const tgzFile = `${pkgJson.name.replace('/', '-')}-${pkgJson.version}.tgz`; await fs.copyFile(tgzFile, path.join(outputDir, tgzFile)); await fs.unlink(tgzFile); process.chdir(rootDir); } catch (err) { console.error(`Failed to pack ${packagePath}:`, err.message); } } async function main() { await fs.mkdir(outputDir, { recursive: true }); // Process regular packages const regularPackages = await fs.readdir(path.join(rootDir, 'node_modules')); for (const pkg of regularPackages) { const pkgPath = path.join(rootDir, 'node_modules', pkg); const stats = await fs.stat(pkgPath); if (stats.isDirectory() && await fs.access(path.join(pkgPath, 'package.json')).then(() => true).catch(() => false)) { await packPackage(pkgPath); } } // Process scoped packages const scopes = await fs.readdir(path.join(rootDir, 'node_modules')); for (const scope of scopes) { if (scope.startsWith('@')) { const scopePath = path.join(rootDir, 'node_modules', scope); const scopedPackages = await fs.readdir(scopePath); for (const pkg of scopedPackages) { const pkgPath = path.join(scopePath, pkg); const stats = await fs.stat(pkgPath); if (stats.isDirectory() && await fs.access(path.join(pkgPath, 'package.json')).then(() => true).catch(() => false)) { await packPackage(pkgPath); } } } } console.log('All packages packed successfully!'); } main();
Run it with:
node pack-packages.js
4. Batch Upload to Nexus
Once you have all .tgz files in the npm-packages folder, upload them to Nexus:
Step 1: Authenticate with Nexus
First, log npm into your Nexus repository (you only need to do this once):
npm login --registry=http://your-nexus-url/repository/npm-private/
Enter your Nexus username, password, and email when prompted.
Step 2: Batch Upload Script
Create upload-packages.sh (Linux/macOS):
#!/bin/bash REGISTRY="http://your-nexus-url/repository/npm-private/" for tgz in npm-packages/*.tgz; do echo "Uploading $tgz..." npm publish "$tgz" --registry="$REGISTRY" done echo "All packages uploaded (or skipped if already present)!"
Run it with:
chmod +x upload-packages.sh && ./upload-packages.sh
Or a Node.js cross-platform script upload-packages.js:
const fs = require('fs').promises; const path = require('path'); const { execSync } = require('child_process'); const registry = 'http://your-nexus-url/repository/npm-private/'; const packagesDir = path.join(process.cwd(), 'npm-packages'); async function main() { const files = await fs.readdir(packagesDir); for (const file of files) { if (file.endsWith('.tgz')) { const tgzPath = path.join(packagesDir, file); console.log(`Uploading ${file}...`); try { execSync(`npm publish "${tgzPath}" --registry=${registry}`, { stdio: 'inherit' }); } catch (err) { console.error(`Failed to upload ${file}:`, err.message); } } } console.log('Upload process complete!'); } main();
Run with:
node upload-packages.js
5. Key Notes & Troubleshooting
- Duplicate Packages: If a package with the same
nameandversionalready exists in Nexus,npm publishwill fail. Either delete the existing package in Nexus or set the repository's Deployment policy to "Allow redeploy" (in Nexus repo settings) to overwrite. - Invalid Packages: Some packages in
node_modulesmight not be valid npm packages (e.g., missingpackage.json). The scripts above will skip these, so you can review the error logs to handle them manually if needed. - Testing: After uploading, verify you can install packages from Nexus by running
npm install <package-name> --registry=http://your-nexus-url/repository/npm-private/on a machine with access to Nexus.
内容的提问来源于stack exchange,提问作者Benny Afriat

