offline-npm-manager 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +279 -0
- package/bin/offline-npm.js +60 -0
- package/dist/cli.cjs +720 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
# 📦 Offline NPM Manager - CLI Tool
|
|
2
|
+
|
|
3
|
+
A command-line tool for downloading npm packages when online and installing them offline later.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
**✅ Add Packages** - Download packages and dependencies for offline use
|
|
10
|
+
|
|
11
|
+
**✅ Install Packages** - Install from local cache without internet
|
|
12
|
+
|
|
13
|
+
**✅ List Packages** - View all cached packages
|
|
14
|
+
|
|
15
|
+
**✅ Remove Packages** - Clean up cached packages
|
|
16
|
+
|
|
17
|
+
**✅ Dependency Management** - Automatically cache package dependencies
|
|
18
|
+
|
|
19
|
+
**✅ Smart Caching** - Detect already-cached packages
|
|
20
|
+
|
|
21
|
+
**✅ Scoped Packages** - Full support for `@scope/package` naming
|
|
22
|
+
|
|
23
|
+
**✅ Cross-Platform** - Works on Windows, macOS, and Linux
|
|
24
|
+
|
|
25
|
+
**✅ Version Control** - Store and manage multiple versions
|
|
26
|
+
|
|
27
|
+
**✅ Real-time Feedback** - Progress indicators and error messages
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
### Prerequisites
|
|
34
|
+
|
|
35
|
+
- **Node.js** 16 or higher
|
|
36
|
+
- **npm** 8 or higher
|
|
37
|
+
|
|
38
|
+
### Install Globally
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install -g offline-npm-manager
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This installs the `offline-npm` command globally.
|
|
45
|
+
|
|
46
|
+
### Offline Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm install -g offline-npm-manager --offline
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### Add a Package
|
|
57
|
+
|
|
58
|
+
Download and store a package locally (requires internet):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
offline-npm add <package>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Examples:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
offline-npm add axios
|
|
68
|
+
offline-npm add react --deps
|
|
69
|
+
offline-npm add react@17.0.2
|
|
70
|
+
offline-npm add @babel/core
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Options:**
|
|
74
|
+
|
|
75
|
+
- `-d, --deps` → Download all dependencies recursively
|
|
76
|
+
- `-s, --storage <path>` → Custom storage directory
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### Install a Package
|
|
81
|
+
|
|
82
|
+
Install from local cache (works offline):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
offline-npm install <package>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Examples:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
offline-npm install axios --save
|
|
92
|
+
offline-npm install lodash --save-dev
|
|
93
|
+
offline-npm install react@17.0.2
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Options:**
|
|
97
|
+
|
|
98
|
+
- `--save` → Add to dependencies
|
|
99
|
+
- `--save-dev` → Add to devDependencies
|
|
100
|
+
- `-s, --storage <path>` → Custom storage
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### List Cached Packages
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
offline-npm list
|
|
108
|
+
offline-npm ls
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Example output:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
📦 offline-npm list
|
|
115
|
+
|
|
116
|
+
Storage: ~/.offline-npm-cache
|
|
117
|
+
|
|
118
|
+
┌──────────┬─────────┬──────────┬───────────────────────┬─────────┐
|
|
119
|
+
│ Package │ Version │ Size │ Downloaded │ Status │
|
|
120
|
+
├──────────┼─────────┼──────────┼───────────────────────┼─────────┤
|
|
121
|
+
│ lodash │ 4.17.23 │ 307.5 KB │ 3/24/2026, 9:48 AM │ ✔ ready │
|
|
122
|
+
│ express │ 5.2.1 │ 22.6 KB │ 3/24/2026, 10:15 AM │ ✔ ready │
|
|
123
|
+
└──────────┴─────────┴──────────┴───────────────────────┴─────────┘
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
### Remove a Package
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
offline-npm remove <package>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Example:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
offline-npm remove lodash
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Storage
|
|
143
|
+
|
|
144
|
+
Default locations:
|
|
145
|
+
|
|
146
|
+
- **Windows** → `%USERPROFILE%\.offline-npm-cache`
|
|
147
|
+
- **macOS/Linux** → `~/.offline-npm-cache`
|
|
148
|
+
|
|
149
|
+
Override storage:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
offline-npm add react --storage /custom/path
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Storage Structure
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
~/.offline-npm-cache/
|
|
161
|
+
├── lodash/
|
|
162
|
+
│ └── 4.17.23/
|
|
163
|
+
│ ├── lodash-4.17.23.tgz
|
|
164
|
+
│ └── meta.json
|
|
165
|
+
├── express/
|
|
166
|
+
│ └── 5.2.1/
|
|
167
|
+
│ ├── express-5.2.1.tgz
|
|
168
|
+
│ └── meta.json
|
|
169
|
+
└── @babel/
|
|
170
|
+
└── core/
|
|
171
|
+
└── 7.20.5/
|
|
172
|
+
├── core-7.20.5.tgz
|
|
173
|
+
└── meta.json
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## meta.json Example
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"name": "express",
|
|
183
|
+
"version": "5.2.1",
|
|
184
|
+
"size": 23150,
|
|
185
|
+
"downloadedAt": "2026-03-24T10:15:30Z",
|
|
186
|
+
"hasDeps": true
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Uninstall
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
npm uninstall -g offline-npm-manager
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
This removes the CLI.
|
|
199
|
+
|
|
200
|
+
(Optional: manually delete cache if needed)
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
rm -rf ~/.offline-npm-cache
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Example Workflow
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# Step 1: Online
|
|
212
|
+
offline-npm add express@4.18.2 --deps
|
|
213
|
+
offline-npm add lodash
|
|
214
|
+
offline-npm list
|
|
215
|
+
|
|
216
|
+
# Step 2: Go Offline
|
|
217
|
+
|
|
218
|
+
# Step 3: Install
|
|
219
|
+
mkdir my-project && cd my-project
|
|
220
|
+
npm init -y
|
|
221
|
+
|
|
222
|
+
offline-npm install express@4.18.2 --save
|
|
223
|
+
offline-npm install lodash --save
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## How It Works
|
|
229
|
+
|
|
230
|
+
| Operation | Description |
|
|
231
|
+
| ------------ | ---------------------------------- |
|
|
232
|
+
| Add Package | Uses `npm pack` to download `.tgz` |
|
|
233
|
+
| Install | Installs from local `.tgz` |
|
|
234
|
+
| List | Reads cached metadata |
|
|
235
|
+
| Remove | Deletes cached files |
|
|
236
|
+
| Dependencies | Recursively cached with `--deps` |
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Requirements
|
|
241
|
+
|
|
242
|
+
- Node.js ≥ 16
|
|
243
|
+
- npm ≥ 8
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Troubleshooting
|
|
248
|
+
|
|
249
|
+
### npm not found
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
npm --version
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Install Node.js or fix PATH.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
### Package not found
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
offline-npm add lodash
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Check spelling or internet connection.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
### Missing dependencies
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
offline-npm add express --deps
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## License
|
|
278
|
+
|
|
279
|
+
MIT
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const { program } = require("commander");
|
|
6
|
+
const { addPackage } = require("../src/add.js");
|
|
7
|
+
const { installPackage } = require("../src/install.js");
|
|
8
|
+
const { listPackages } = require("../src/list.js");
|
|
9
|
+
const { removePackage } = require("../src/remove.js");
|
|
10
|
+
const { version } = require("../package.json");
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.name("offline-npm")
|
|
14
|
+
.description(
|
|
15
|
+
"📦 Download npm packages when online, install them offline later",
|
|
16
|
+
)
|
|
17
|
+
.version(version);
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.command("add <package>")
|
|
21
|
+
.description("Download a package and store it locally (requires internet)")
|
|
22
|
+
.option("-d, --deps", "Also download all dependencies recursively", false)
|
|
23
|
+
.option("-s, --storage <path>", "Custom storage directory")
|
|
24
|
+
.action(async (pkg, options) => {
|
|
25
|
+
await addPackage(pkg, options);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
program
|
|
29
|
+
.command("install <package>")
|
|
30
|
+
.description("Install a package from local offline storage")
|
|
31
|
+
.option("-s, --storage <path>", "Custom storage directory")
|
|
32
|
+
.option("--save", "Add to package.json dependencies", false)
|
|
33
|
+
.option("--save-dev", "Add to package.json devDependencies", false)
|
|
34
|
+
.action(async (pkg, options) => {
|
|
35
|
+
await installPackage(pkg, options);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
program
|
|
39
|
+
.command("list")
|
|
40
|
+
.alias("ls")
|
|
41
|
+
.description("List all locally stored packages")
|
|
42
|
+
.option("-s, --storage <path>", "Custom storage directory")
|
|
43
|
+
.action(async (options) => {
|
|
44
|
+
await listPackages(options);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
program
|
|
48
|
+
.command("remove <package>")
|
|
49
|
+
.alias("rm")
|
|
50
|
+
.description("Remove a package from local offline storage")
|
|
51
|
+
.option("-s, --storage <path>", "Custom storage directory")
|
|
52
|
+
.action(async (pkg, options) => {
|
|
53
|
+
await removePackage(pkg, options);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
program.parse(process.argv);
|
|
57
|
+
|
|
58
|
+
if (!process.argv.slice(2).length) {
|
|
59
|
+
program.outputHelp();
|
|
60
|
+
}
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
5
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// src/parser.js
|
|
9
|
+
var require_parser = __commonJS({
|
|
10
|
+
"src/parser.js"(exports2, module2) {
|
|
11
|
+
"use strict";
|
|
12
|
+
function parsePackage(input) {
|
|
13
|
+
input = input.trim();
|
|
14
|
+
if (input.startsWith("@")) {
|
|
15
|
+
const secondAt = input.indexOf("@", 1);
|
|
16
|
+
if (secondAt === -1) {
|
|
17
|
+
return { name: input, version: "latest", raw: input };
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
name: input.slice(0, secondAt),
|
|
21
|
+
version: input.slice(secondAt + 1),
|
|
22
|
+
raw: input
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const atIndex = input.indexOf("@");
|
|
26
|
+
if (atIndex === -1) {
|
|
27
|
+
return { name: input, version: "latest", raw: input };
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
name: input.slice(0, atIndex),
|
|
31
|
+
version: input.slice(atIndex + 1),
|
|
32
|
+
raw: input
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function packageLabel(name, version2) {
|
|
36
|
+
return `${name}@${version2}`;
|
|
37
|
+
}
|
|
38
|
+
module2.exports = { parsePackage, packageLabel };
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// src/storage.js
|
|
43
|
+
var require_storage = __commonJS({
|
|
44
|
+
"src/storage.js"(exports2, module2) {
|
|
45
|
+
"use strict";
|
|
46
|
+
var path = require("path");
|
|
47
|
+
var os = require("os");
|
|
48
|
+
var fs = require("fs");
|
|
49
|
+
var DEFAULT_STORAGE = path.join(os.homedir(), ".offline-npm-cache");
|
|
50
|
+
function getStorageDir(customPath = null) {
|
|
51
|
+
const dir = customPath ? path.resolve(customPath) : DEFAULT_STORAGE;
|
|
52
|
+
ensureDir(dir);
|
|
53
|
+
return dir;
|
|
54
|
+
}
|
|
55
|
+
function getPackageDir(storageDir, name, version2) {
|
|
56
|
+
const dir = path.join(storageDir, name, version2);
|
|
57
|
+
ensureDir(dir);
|
|
58
|
+
return dir;
|
|
59
|
+
}
|
|
60
|
+
function ensureDir(dir) {
|
|
61
|
+
if (!fs.existsSync(dir)) {
|
|
62
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function readMeta(pkgDir) {
|
|
66
|
+
const metaPath = path.join(pkgDir, "meta.json");
|
|
67
|
+
if (!fs.existsSync(metaPath))
|
|
68
|
+
return null;
|
|
69
|
+
return JSON.parse(fs.readFileSync(metaPath, "utf-8"));
|
|
70
|
+
}
|
|
71
|
+
function writeMeta(pkgDir, meta) {
|
|
72
|
+
const metaPath = path.join(pkgDir, "meta.json");
|
|
73
|
+
fs.writeFileSync(metaPath, JSON.stringify(meta, null, 2), "utf-8");
|
|
74
|
+
}
|
|
75
|
+
module2.exports = {
|
|
76
|
+
DEFAULT_STORAGE,
|
|
77
|
+
getStorageDir,
|
|
78
|
+
getPackageDir,
|
|
79
|
+
ensureDir,
|
|
80
|
+
readMeta,
|
|
81
|
+
writeMeta
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// src/logger.js
|
|
87
|
+
var require_logger = __commonJS({
|
|
88
|
+
"src/logger.js"(exports2, module2) {
|
|
89
|
+
"use strict";
|
|
90
|
+
var RESET = "\x1B[0m";
|
|
91
|
+
var BOLD = "\x1B[1m";
|
|
92
|
+
var DIM = "\x1B[2m";
|
|
93
|
+
var GREEN = "\x1B[32m";
|
|
94
|
+
var CYAN = "\x1B[36m";
|
|
95
|
+
var YELLOW = "\x1B[33m";
|
|
96
|
+
var RED = "\x1B[31m";
|
|
97
|
+
var BLUE = "\x1B[34m";
|
|
98
|
+
function info(msg) {
|
|
99
|
+
console.log(`${CYAN}\u2139${RESET} ${msg}`);
|
|
100
|
+
}
|
|
101
|
+
function success(msg) {
|
|
102
|
+
console.log(`${GREEN}\u2714${RESET} ${msg}`);
|
|
103
|
+
}
|
|
104
|
+
function warn(msg) {
|
|
105
|
+
console.log(`${YELLOW}\u26A0${RESET} ${msg}`);
|
|
106
|
+
}
|
|
107
|
+
function error(msg) {
|
|
108
|
+
console.error(`${RED}\u2716${RESET} ${msg}`);
|
|
109
|
+
}
|
|
110
|
+
function step(msg) {
|
|
111
|
+
console.log(`${BLUE}\u203A${RESET} ${msg}`);
|
|
112
|
+
}
|
|
113
|
+
function dim(msg) {
|
|
114
|
+
console.log(`${DIM} ${msg}${RESET}`);
|
|
115
|
+
}
|
|
116
|
+
function bold(msg) {
|
|
117
|
+
return `${BOLD}${msg}${RESET}`;
|
|
118
|
+
}
|
|
119
|
+
function header(msg) {
|
|
120
|
+
console.log(`
|
|
121
|
+
${BOLD}${CYAN}${msg}${RESET}
|
|
122
|
+
`);
|
|
123
|
+
}
|
|
124
|
+
function table(rows) {
|
|
125
|
+
if (!rows.length)
|
|
126
|
+
return;
|
|
127
|
+
const cols = Object.keys(rows[0]);
|
|
128
|
+
const widths = cols.map(
|
|
129
|
+
(c) => Math.max(c.length, ...rows.map((r) => String(r[c] ?? "").length))
|
|
130
|
+
);
|
|
131
|
+
const line = widths.map((w) => "\u2500".repeat(w + 2)).join("\u253C");
|
|
132
|
+
const header2 = cols.map((c, i) => ` ${BOLD}${c.padEnd(widths[i])}${RESET} `).join("\u2502");
|
|
133
|
+
console.log("\u250C" + widths.map((w) => "\u2500".repeat(w + 2)).join("\u252C") + "\u2510");
|
|
134
|
+
console.log("\u2502" + header2 + "\u2502");
|
|
135
|
+
console.log("\u251C" + line + "\u2524");
|
|
136
|
+
rows.forEach((row) => {
|
|
137
|
+
const cells = cols.map((c, i) => ` ${String(row[c] ?? "").padEnd(widths[i])} `).join("\u2502");
|
|
138
|
+
console.log("\u2502" + cells + "\u2502");
|
|
139
|
+
});
|
|
140
|
+
console.log("\u2514" + widths.map((w) => "\u2500".repeat(w + 2)).join("\u2534") + "\u2518");
|
|
141
|
+
}
|
|
142
|
+
module2.exports = { info, success, warn, error, step, dim, bold, header, table };
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// src/add.js
|
|
147
|
+
var require_add = __commonJS({
|
|
148
|
+
"src/add.js"(exports2, module2) {
|
|
149
|
+
"use strict";
|
|
150
|
+
var { execSync, spawnSync } = require("child_process");
|
|
151
|
+
var fs = require("fs");
|
|
152
|
+
var path = require("path");
|
|
153
|
+
var { parsePackage, packageLabel } = require_parser();
|
|
154
|
+
var { getStorageDir, getPackageDir, writeMeta } = require_storage();
|
|
155
|
+
var log = require_logger();
|
|
156
|
+
function resolveVersion(name, version2) {
|
|
157
|
+
try {
|
|
158
|
+
const result = execSync(
|
|
159
|
+
`npm view ${packageLabel(name, version2)} version --json`,
|
|
160
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
161
|
+
).trim();
|
|
162
|
+
return result.replace(/"/g, "");
|
|
163
|
+
} catch (err) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Could not resolve version for ${packageLabel(name, version2)}. Are you online? Does this package exist?`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function getDependencies(name, version2) {
|
|
170
|
+
try {
|
|
171
|
+
const raw = execSync(
|
|
172
|
+
`npm view ${packageLabel(name, version2)} dependencies --json`,
|
|
173
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
174
|
+
).trim();
|
|
175
|
+
if (!raw || raw === "undefined")
|
|
176
|
+
return {};
|
|
177
|
+
return JSON.parse(raw);
|
|
178
|
+
} catch {
|
|
179
|
+
return {};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
function npmPack(pkgLabel, destDir) {
|
|
183
|
+
if (!fs.existsSync(destDir)) {
|
|
184
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
const cmd = `npm pack ${pkgLabel} --pack-destination "${destDir}"`;
|
|
188
|
+
const output = execSync(cmd, { encoding: "utf-8" });
|
|
189
|
+
const lines = output.trim().split("\n").filter((l) => l.trim() && !l.includes("npm"));
|
|
190
|
+
const tgzName = lines.pop().trim();
|
|
191
|
+
if (!tgzName) {
|
|
192
|
+
throw new Error(`No output from npm pack for ${pkgLabel}`);
|
|
193
|
+
}
|
|
194
|
+
return path.join(destDir, tgzName);
|
|
195
|
+
} catch (err) {
|
|
196
|
+
log.error(`npm pack failed: ${err.message}`);
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async function downloadPackage(name, version2, storageDir, downloadDeps, visited = /* @__PURE__ */ new Set()) {
|
|
201
|
+
const label = packageLabel(name, version2);
|
|
202
|
+
if (visited.has(label))
|
|
203
|
+
return;
|
|
204
|
+
visited.add(label);
|
|
205
|
+
log.step(`Resolving ${log.bold(label)} ...`);
|
|
206
|
+
let resolvedVersion;
|
|
207
|
+
try {
|
|
208
|
+
resolvedVersion = resolveVersion(name, version2);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
log.error(err.message);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
const pkgDir = getPackageDir(storageDir, name, resolvedVersion);
|
|
214
|
+
const metaPath = path.join(pkgDir, "meta.json");
|
|
215
|
+
if (fs.existsSync(metaPath)) {
|
|
216
|
+
const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
|
|
217
|
+
if (meta.tgz && fs.existsSync(path.join(pkgDir, meta.tgz))) {
|
|
218
|
+
log.info(
|
|
219
|
+
`Already cached: ${log.bold(packageLabel(name, resolvedVersion))}`
|
|
220
|
+
);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
log.step(`Downloading ${log.bold(packageLabel(name, resolvedVersion))} ...`);
|
|
225
|
+
let tgzPath;
|
|
226
|
+
try {
|
|
227
|
+
tgzPath = npmPack(packageLabel(name, resolvedVersion), pkgDir);
|
|
228
|
+
} catch (err) {
|
|
229
|
+
log.error(`Failed to download ${label}: ${err.message}`);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
const tgzName = path.basename(tgzPath);
|
|
233
|
+
const stats = fs.statSync(tgzPath);
|
|
234
|
+
writeMeta(pkgDir, {
|
|
235
|
+
name,
|
|
236
|
+
version: resolvedVersion,
|
|
237
|
+
requestedVersion: version2,
|
|
238
|
+
tgz: tgzName,
|
|
239
|
+
size: stats.size,
|
|
240
|
+
downloadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
241
|
+
hasDeps: downloadDeps
|
|
242
|
+
});
|
|
243
|
+
log.success(
|
|
244
|
+
`Saved ${log.bold(packageLabel(name, resolvedVersion))} (${(stats.size / 1024).toFixed(1)} KB) \u2192 ${pkgDir}`
|
|
245
|
+
);
|
|
246
|
+
if (downloadDeps) {
|
|
247
|
+
const deps = getDependencies(name, resolvedVersion);
|
|
248
|
+
const depEntries = Object.entries(deps);
|
|
249
|
+
if (depEntries.length > 0) {
|
|
250
|
+
log.info(
|
|
251
|
+
`Found ${depEntries.length} dependenc${depEntries.length === 1 ? "y" : "ies"} for ${packageLabel(name, resolvedVersion)}`
|
|
252
|
+
);
|
|
253
|
+
for (const [depName, depRange] of depEntries) {
|
|
254
|
+
await downloadPackage(
|
|
255
|
+
depName,
|
|
256
|
+
depRange,
|
|
257
|
+
storageDir,
|
|
258
|
+
downloadDeps,
|
|
259
|
+
visited
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async function addPackage2(pkgInput, options) {
|
|
266
|
+
const { name, version: version2 } = parsePackage(pkgInput);
|
|
267
|
+
const storageDir = getStorageDir(options.storage);
|
|
268
|
+
log.header(`\u{1F4E6} offline-npm add`);
|
|
269
|
+
log.info(`Storage directory: ${storageDir}`);
|
|
270
|
+
if (options.deps) {
|
|
271
|
+
log.info(`Dependency download: ${log.bold("enabled")}`);
|
|
272
|
+
}
|
|
273
|
+
console.log("");
|
|
274
|
+
await downloadPackage(name, version2, storageDir, options.deps);
|
|
275
|
+
console.log("");
|
|
276
|
+
log.success(
|
|
277
|
+
`Done! Run ${log.bold(`offline-npm install ${pkgInput}`)} anytime \u2014 even offline.`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
module2.exports = { addPackage: addPackage2 };
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// src/install.js
|
|
285
|
+
var require_install = __commonJS({
|
|
286
|
+
"src/install.js"(exports2, module2) {
|
|
287
|
+
"use strict";
|
|
288
|
+
var { spawnSync } = require("child_process");
|
|
289
|
+
var fs = require("fs");
|
|
290
|
+
var path = require("path");
|
|
291
|
+
var { parsePackage, packageLabel } = require_parser();
|
|
292
|
+
var { getStorageDir, readMeta } = require_storage();
|
|
293
|
+
var log = require_logger();
|
|
294
|
+
function findCachedVersions(storageDir, name) {
|
|
295
|
+
const pkgPath = path.join(storageDir, ...name.split("/"));
|
|
296
|
+
if (!fs.existsSync(pkgPath))
|
|
297
|
+
return [];
|
|
298
|
+
return fs.readdirSync(pkgPath).filter((v) => {
|
|
299
|
+
const meta = readMeta(path.join(pkgPath, v));
|
|
300
|
+
return meta !== null;
|
|
301
|
+
}).map((v) => ({
|
|
302
|
+
version: v,
|
|
303
|
+
pkgDir: path.join(pkgPath, v),
|
|
304
|
+
meta: readMeta(path.join(pkgPath, v))
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
function pickVersion(cached, requestedVersion) {
|
|
308
|
+
if (!cached.length)
|
|
309
|
+
return null;
|
|
310
|
+
if (requestedVersion === "latest") {
|
|
311
|
+
return cached.sort(
|
|
312
|
+
(a, b) => new Date(b.meta.downloadedAt) - new Date(a.meta.downloadedAt)
|
|
313
|
+
)[0];
|
|
314
|
+
}
|
|
315
|
+
return cached.find((c) => c.version === requestedVersion) || null;
|
|
316
|
+
}
|
|
317
|
+
function runNpmInstall(tgzPath, saveFlag) {
|
|
318
|
+
const args = [
|
|
319
|
+
"install",
|
|
320
|
+
tgzPath,
|
|
321
|
+
"--prefer-offline",
|
|
322
|
+
// Use cache first before fetching
|
|
323
|
+
"--no-audit"
|
|
324
|
+
// Skip npm audit (requires network)
|
|
325
|
+
];
|
|
326
|
+
if (saveFlag === "save")
|
|
327
|
+
args.push("--save");
|
|
328
|
+
if (saveFlag === "save-dev")
|
|
329
|
+
args.push("--save-dev");
|
|
330
|
+
const result = spawnSync("npm", args, {
|
|
331
|
+
encoding: "utf-8",
|
|
332
|
+
stdio: "inherit",
|
|
333
|
+
shell: true
|
|
334
|
+
// Windows compatibility
|
|
335
|
+
});
|
|
336
|
+
return result.status === 0;
|
|
337
|
+
}
|
|
338
|
+
function normalizeResolvedUrl(fileUri) {
|
|
339
|
+
if (typeof fileUri !== "string" || !fileUri.startsWith("file:"))
|
|
340
|
+
return null;
|
|
341
|
+
let absolute = fileUri.replace(/^file:\/\//, "").replace(/^file:/, "");
|
|
342
|
+
absolute = decodeURIComponent(absolute).replace(/\\/g, "/");
|
|
343
|
+
const marker = ".offline-npm-cache/";
|
|
344
|
+
const idx = absolute.indexOf(marker);
|
|
345
|
+
if (idx === -1)
|
|
346
|
+
return null;
|
|
347
|
+
const subpath = absolute.slice(idx + marker.length);
|
|
348
|
+
const parts = subpath.split("/").filter(Boolean);
|
|
349
|
+
let packageName;
|
|
350
|
+
let version2;
|
|
351
|
+
if (parts[0].startsWith("@")) {
|
|
352
|
+
if (parts.length < 4)
|
|
353
|
+
return null;
|
|
354
|
+
packageName = `${parts[0]}/${parts[1]}`;
|
|
355
|
+
version2 = parts[2];
|
|
356
|
+
} else {
|
|
357
|
+
if (parts.length < 3)
|
|
358
|
+
return null;
|
|
359
|
+
packageName = parts[0];
|
|
360
|
+
version2 = parts[1];
|
|
361
|
+
}
|
|
362
|
+
const baseName = packageName.includes("/") ? packageName.split("/").pop() : packageName;
|
|
363
|
+
const tarballName = `${baseName}-${version2}.tgz`;
|
|
364
|
+
return `https://registry.npmjs.org/${packageName}/-/${tarballName}`;
|
|
365
|
+
}
|
|
366
|
+
function sanitizePackageLock() {
|
|
367
|
+
const lockFile = path.resolve("package-lock.json");
|
|
368
|
+
if (!fs.existsSync(lockFile))
|
|
369
|
+
return;
|
|
370
|
+
try {
|
|
371
|
+
let sanitizeNode = function(node) {
|
|
372
|
+
if (!node || typeof node !== "object")
|
|
373
|
+
return;
|
|
374
|
+
if (node.resolved && typeof node.resolved === "string") {
|
|
375
|
+
const normalized = normalizeResolvedUrl(node.resolved);
|
|
376
|
+
if (normalized)
|
|
377
|
+
node.resolved = normalized;
|
|
378
|
+
}
|
|
379
|
+
if (node.dependencies && typeof node.dependencies === "object") {
|
|
380
|
+
for (const depName of Object.keys(node.dependencies)) {
|
|
381
|
+
sanitizeNode(node.dependencies[depName]);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
const lockData = JSON.parse(fs.readFileSync(lockFile, "utf-8"));
|
|
386
|
+
if (lockData.packages && typeof lockData.packages === "object") {
|
|
387
|
+
for (const pkgPath of Object.keys(lockData.packages)) {
|
|
388
|
+
sanitizeNode(lockData.packages[pkgPath]);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (lockData.dependencies && typeof lockData.dependencies === "object") {
|
|
392
|
+
sanitizeNode(lockData.dependencies);
|
|
393
|
+
}
|
|
394
|
+
fs.writeFileSync(lockFile, JSON.stringify(lockData, null, 2), "utf-8");
|
|
395
|
+
} catch (err) {
|
|
396
|
+
log.warn(`Could not sanitize package-lock.json: ${err.message}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function sanitizePackageJson(name, version2, saveFlag) {
|
|
400
|
+
if (!saveFlag)
|
|
401
|
+
return;
|
|
402
|
+
const pkgJsonPath = path.resolve("package.json");
|
|
403
|
+
if (!fs.existsSync(pkgJsonPath))
|
|
404
|
+
return;
|
|
405
|
+
try {
|
|
406
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
407
|
+
const section = saveFlag === "save-dev" ? "devDependencies" : "dependencies";
|
|
408
|
+
if (!pkgJson[section])
|
|
409
|
+
pkgJson[section] = {};
|
|
410
|
+
const current = pkgJson[section][name];
|
|
411
|
+
if (typeof current === "string" && current.startsWith("file:")) {
|
|
412
|
+
pkgJson[section][name] = version2;
|
|
413
|
+
}
|
|
414
|
+
console.log({ pkgJson });
|
|
415
|
+
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2), "utf-8");
|
|
416
|
+
} catch (err) {
|
|
417
|
+
log.warn(`Could not sanitize package.json (${name}): ${err.message}`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async function installPackage2(pkgInput, options) {
|
|
421
|
+
const { name, version: version2 } = parsePackage(pkgInput);
|
|
422
|
+
const storageDir = getStorageDir(options.storage);
|
|
423
|
+
log.header(`\u{1F4E6} offline-npm install`);
|
|
424
|
+
log.info(
|
|
425
|
+
`Looking up ${log.bold(packageLabel(name, version2))} in local cache...`
|
|
426
|
+
);
|
|
427
|
+
log.dim(`Storage: ${storageDir}`);
|
|
428
|
+
console.log("");
|
|
429
|
+
const cached = findCachedVersions(storageDir, name);
|
|
430
|
+
if (!cached.length) {
|
|
431
|
+
log.error(`Package ${log.bold(name)} not found in local cache.`);
|
|
432
|
+
log.info(
|
|
433
|
+
`Run ${log.bold(`offline-npm add ${pkgInput}`)} while online first.`
|
|
434
|
+
);
|
|
435
|
+
process.exit(1);
|
|
436
|
+
}
|
|
437
|
+
const match = pickVersion(cached, version2);
|
|
438
|
+
if (!match) {
|
|
439
|
+
log.error(
|
|
440
|
+
`Version ${log.bold(version2)} of ${log.bold(name)} is not cached.`
|
|
441
|
+
);
|
|
442
|
+
log.info(`Cached versions: ${cached.map((c) => c.version).join(", ")}`);
|
|
443
|
+
log.info(
|
|
444
|
+
`Run ${log.bold(`offline-npm add ${pkgInput}`)} while online to download it.`
|
|
445
|
+
);
|
|
446
|
+
process.exit(1);
|
|
447
|
+
}
|
|
448
|
+
const tgzPath = path.join(match.pkgDir, match.meta.tgz);
|
|
449
|
+
if (!fs.existsSync(tgzPath)) {
|
|
450
|
+
log.error(`Cached .tgz file is missing: ${tgzPath}`);
|
|
451
|
+
log.info(
|
|
452
|
+
`Run ${log.bold(`offline-npm add ${pkgInput}`)} again to re-download.`
|
|
453
|
+
);
|
|
454
|
+
process.exit(1);
|
|
455
|
+
}
|
|
456
|
+
log.step(
|
|
457
|
+
`Installing ${log.bold(packageLabel(name, match.version))} from cache...`
|
|
458
|
+
);
|
|
459
|
+
log.dim(`Source: ${tgzPath}`);
|
|
460
|
+
console.log("");
|
|
461
|
+
const saveFlag = options.saveDev ? "save-dev" : options.save ? "save" : null;
|
|
462
|
+
const ok = runNpmInstall(tgzPath, saveFlag);
|
|
463
|
+
console.log("");
|
|
464
|
+
if (ok) {
|
|
465
|
+
log.success(
|
|
466
|
+
`Installed ${log.bold(packageLabel(name, match.version))} successfully (offline).`
|
|
467
|
+
);
|
|
468
|
+
sanitizePackageJson(name, match.version, saveFlag);
|
|
469
|
+
sanitizePackageLock();
|
|
470
|
+
log.info("Package metadata was rewritten to production-friendly sources.");
|
|
471
|
+
} else {
|
|
472
|
+
log.error(`npm install failed. Check output above for details.`);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
module2.exports = { installPackage: installPackage2 };
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// src/list.js
|
|
481
|
+
var require_list = __commonJS({
|
|
482
|
+
"src/list.js"(exports2, module2) {
|
|
483
|
+
"use strict";
|
|
484
|
+
var fs = require("fs");
|
|
485
|
+
var path = require("path");
|
|
486
|
+
var { getStorageDir, readMeta } = require_storage();
|
|
487
|
+
var log = require_logger();
|
|
488
|
+
function collectPackages(storageDir) {
|
|
489
|
+
const results = [];
|
|
490
|
+
function walk(dir, nameParts) {
|
|
491
|
+
if (!fs.existsSync(dir))
|
|
492
|
+
return;
|
|
493
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
494
|
+
for (const entry of entries) {
|
|
495
|
+
if (!entry.isDirectory())
|
|
496
|
+
continue;
|
|
497
|
+
const fullPath = path.join(dir, entry.name);
|
|
498
|
+
if (nameParts.length === 0 && entry.name.startsWith("@")) {
|
|
499
|
+
walk(fullPath, [entry.name]);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (nameParts.length === 0 || nameParts.length === 1 && nameParts[0].startsWith("@")) {
|
|
503
|
+
const pkgName = nameParts.length ? `${nameParts[0]}/${entry.name}` : entry.name;
|
|
504
|
+
walkVersions(fullPath, pkgName);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
function walkVersions(pkgDir, pkgName) {
|
|
509
|
+
if (!fs.existsSync(pkgDir))
|
|
510
|
+
return;
|
|
511
|
+
const entries = fs.readdirSync(pkgDir, { withFileTypes: true });
|
|
512
|
+
for (const entry of entries) {
|
|
513
|
+
if (!entry.isDirectory())
|
|
514
|
+
continue;
|
|
515
|
+
const versionDir = path.join(pkgDir, entry.name);
|
|
516
|
+
const meta = readMeta(versionDir);
|
|
517
|
+
if (meta) {
|
|
518
|
+
const tgzPath = path.join(versionDir, meta.tgz || "");
|
|
519
|
+
const tgzExists = fs.existsSync(tgzPath);
|
|
520
|
+
results.push({
|
|
521
|
+
id: `${pkgName}@${entry.name}`,
|
|
522
|
+
name: pkgName,
|
|
523
|
+
version: entry.name,
|
|
524
|
+
size: meta.size || 0,
|
|
525
|
+
sizeLabel: meta.size ? `${(meta.size / 1024).toFixed(1)} KB` : "?",
|
|
526
|
+
downloadedAt: meta.downloadedAt || null,
|
|
527
|
+
tgz: meta.tgz || null,
|
|
528
|
+
tgzPath,
|
|
529
|
+
status: tgzExists ? "ready" : "missing",
|
|
530
|
+
hasDeps: meta.hasDeps || false
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
walk(storageDir, []);
|
|
536
|
+
return results.sort(
|
|
537
|
+
(a, b) => new Date(b.downloadedAt) - new Date(a.downloadedAt)
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
async function listPackages2(options) {
|
|
541
|
+
const storageDir = getStorageDir(options.storage);
|
|
542
|
+
log.header("\u{1F4E6} offline-npm list");
|
|
543
|
+
log.dim(`Storage: ${storageDir}`);
|
|
544
|
+
console.log("");
|
|
545
|
+
const packages = collectPackages(storageDir);
|
|
546
|
+
if (!packages.length) {
|
|
547
|
+
log.warn("No packages cached yet.");
|
|
548
|
+
log.info(
|
|
549
|
+
`Run ${log.bold("offline-npm add <package>")} to download packages.`
|
|
550
|
+
);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
const displayTable = packages.map((p) => ({
|
|
554
|
+
Package: p.name,
|
|
555
|
+
Version: p.version,
|
|
556
|
+
Size: p.sizeLabel,
|
|
557
|
+
Downloaded: p.downloadedAt ? new Date(p.downloadedAt).toLocaleString() : "?",
|
|
558
|
+
Status: p.status === "ready" ? "\u2714 ready" : "\u2716 missing"
|
|
559
|
+
}));
|
|
560
|
+
log.table(displayTable);
|
|
561
|
+
console.log("");
|
|
562
|
+
log.success(
|
|
563
|
+
`${packages.length} package version${packages.length === 1 ? "" : "s"} in cache.`
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
module2.exports = { listPackages: listPackages2, collectPackages };
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// src/remove.js
|
|
571
|
+
var require_remove = __commonJS({
|
|
572
|
+
"src/remove.js"(exports2, module2) {
|
|
573
|
+
"use strict";
|
|
574
|
+
var fs = require("fs");
|
|
575
|
+
var path = require("path");
|
|
576
|
+
var { parsePackage, packageLabel } = require_parser();
|
|
577
|
+
var { getStorageDir } = require_storage();
|
|
578
|
+
var log = require_logger();
|
|
579
|
+
function rmDir(dir) {
|
|
580
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
581
|
+
}
|
|
582
|
+
async function removePackage2(pkgInput, options) {
|
|
583
|
+
const { name, version: version2 } = parsePackage(pkgInput);
|
|
584
|
+
const storageDir = getStorageDir(options.storage);
|
|
585
|
+
log.header("\u{1F4E6} offline-npm remove");
|
|
586
|
+
const nameParts = name.startsWith("@") ? name.split("/") : [name];
|
|
587
|
+
const pkgDir = path.join(storageDir, ...nameParts);
|
|
588
|
+
if (!fs.existsSync(pkgDir)) {
|
|
589
|
+
log.error(`Package ${log.bold(name)} is not in the local cache.`);
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
if (version2 === "latest") {
|
|
593
|
+
rmDir(pkgDir);
|
|
594
|
+
log.success(`Removed all cached versions of ${log.bold(name)}.`);
|
|
595
|
+
} else {
|
|
596
|
+
const versionDir = path.join(pkgDir, version2);
|
|
597
|
+
if (!fs.existsSync(versionDir)) {
|
|
598
|
+
log.error(
|
|
599
|
+
`Version ${log.bold(version2)} of ${log.bold(name)} is not cached.`
|
|
600
|
+
);
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|
|
603
|
+
rmDir(versionDir);
|
|
604
|
+
log.success(`Removed ${log.bold(packageLabel(name, version2))} from cache.`);
|
|
605
|
+
try {
|
|
606
|
+
if (fs.readdirSync(pkgDir).length === 0)
|
|
607
|
+
fs.rmdirSync(pkgDir);
|
|
608
|
+
if (name.startsWith("@")) {
|
|
609
|
+
const scopeDir = path.join(storageDir, nameParts[0]);
|
|
610
|
+
if (fs.existsSync(scopeDir) && fs.readdirSync(scopeDir).length === 0) {
|
|
611
|
+
fs.rmdirSync(scopeDir);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
} catch {
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
module2.exports = { removePackage: removePackage2 };
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
// package.json
|
|
623
|
+
var require_package = __commonJS({
|
|
624
|
+
"package.json"(exports2, module2) {
|
|
625
|
+
module2.exports = {
|
|
626
|
+
name: "offline-npm-manager",
|
|
627
|
+
version: "1.0.5",
|
|
628
|
+
description: "Download npm packages online, install them offline later",
|
|
629
|
+
bin: {
|
|
630
|
+
"offline-npm": "./dist/cli.cjs"
|
|
631
|
+
},
|
|
632
|
+
scripts: {
|
|
633
|
+
build: "esbuild bin/offline-npm.js --bundle --platform=node --format=cjs --outfile=dist/cli.cjs --external:commander",
|
|
634
|
+
prepublishOnly: "npm run build",
|
|
635
|
+
test: "node test/test.js",
|
|
636
|
+
dev: "node ./bin/offline-npm.js",
|
|
637
|
+
start: "npm run dev"
|
|
638
|
+
},
|
|
639
|
+
keywords: [
|
|
640
|
+
"npm",
|
|
641
|
+
"offline",
|
|
642
|
+
"package-manager",
|
|
643
|
+
"cli",
|
|
644
|
+
"offline-package-manager",
|
|
645
|
+
"offline-install",
|
|
646
|
+
"npm-cache",
|
|
647
|
+
"node-cli",
|
|
648
|
+
"dependency-cache",
|
|
649
|
+
"dependency-manager",
|
|
650
|
+
"offline-first"
|
|
651
|
+
],
|
|
652
|
+
author: "Sagor Ahamed",
|
|
653
|
+
license: "MIT",
|
|
654
|
+
files: [
|
|
655
|
+
"bin",
|
|
656
|
+
"dist"
|
|
657
|
+
],
|
|
658
|
+
dependencies: {
|
|
659
|
+
commander: "^11.1.0"
|
|
660
|
+
},
|
|
661
|
+
devDependencies: {
|
|
662
|
+
esbuild: "^0.19.0"
|
|
663
|
+
},
|
|
664
|
+
engines: {
|
|
665
|
+
node: ">=16.0.0"
|
|
666
|
+
},
|
|
667
|
+
postuninstall: `node -e "const fs=require('fs');const path=require('path');const os=require('os');const dir=path.join(os.homedir(),'.offline-npm-cache');if(fs.existsSync(dir))fs.rmSync(dir,{recursive:true,force:true});"`,
|
|
668
|
+
repository: {
|
|
669
|
+
type: "git",
|
|
670
|
+
url: "https://github.com/SagorAhamed251245/offline-npm-manager.git"
|
|
671
|
+
},
|
|
672
|
+
bugs: {
|
|
673
|
+
url: "https://github.com/SagorAhamed251245/offline-npm-manager/issues"
|
|
674
|
+
},
|
|
675
|
+
homepage: "https://github.com/SagorAhamed251245",
|
|
676
|
+
publishConfig: {
|
|
677
|
+
access: "public"
|
|
678
|
+
},
|
|
679
|
+
contributors: [
|
|
680
|
+
{
|
|
681
|
+
name: "Sagor Ahamed",
|
|
682
|
+
url: "https://github.com/SagorAhamed251245"
|
|
683
|
+
}
|
|
684
|
+
],
|
|
685
|
+
funding: [
|
|
686
|
+
{
|
|
687
|
+
type: "github",
|
|
688
|
+
url: "https://github.com/sponsors/SagorAhamed251245"
|
|
689
|
+
}
|
|
690
|
+
]
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// bin/offline-npm.js
|
|
696
|
+
var { program } = require("commander");
|
|
697
|
+
var { addPackage } = require_add();
|
|
698
|
+
var { installPackage } = require_install();
|
|
699
|
+
var { listPackages } = require_list();
|
|
700
|
+
var { removePackage } = require_remove();
|
|
701
|
+
var { version } = require_package();
|
|
702
|
+
program.name("offline-npm").description(
|
|
703
|
+
"\u{1F4E6} Download npm packages when online, install them offline later"
|
|
704
|
+
).version(version);
|
|
705
|
+
program.command("add <package>").description("Download a package and store it locally (requires internet)").option("-d, --deps", "Also download all dependencies recursively", false).option("-s, --storage <path>", "Custom storage directory").action(async (pkg, options) => {
|
|
706
|
+
await addPackage(pkg, options);
|
|
707
|
+
});
|
|
708
|
+
program.command("install <package>").description("Install a package from local offline storage").option("-s, --storage <path>", "Custom storage directory").option("--save", "Add to package.json dependencies", false).option("--save-dev", "Add to package.json devDependencies", false).action(async (pkg, options) => {
|
|
709
|
+
await installPackage(pkg, options);
|
|
710
|
+
});
|
|
711
|
+
program.command("list").alias("ls").description("List all locally stored packages").option("-s, --storage <path>", "Custom storage directory").action(async (options) => {
|
|
712
|
+
await listPackages(options);
|
|
713
|
+
});
|
|
714
|
+
program.command("remove <package>").alias("rm").description("Remove a package from local offline storage").option("-s, --storage <path>", "Custom storage directory").action(async (pkg, options) => {
|
|
715
|
+
await removePackage(pkg, options);
|
|
716
|
+
});
|
|
717
|
+
program.parse(process.argv);
|
|
718
|
+
if (!process.argv.slice(2).length) {
|
|
719
|
+
program.outputHelp();
|
|
720
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "offline-npm-manager",
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "Download npm packages online, install them offline later",
|
|
5
|
+
"bin": {
|
|
6
|
+
"offline-npm": "./dist/cli.cjs"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "esbuild bin/offline-npm.js --bundle --platform=node --format=cjs --outfile=dist/cli.cjs --external:commander",
|
|
10
|
+
"prepublishOnly": "npm run build",
|
|
11
|
+
"test": "node test/test.js",
|
|
12
|
+
"dev": "node ./bin/offline-npm.js",
|
|
13
|
+
"start": "npm run dev"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"npm",
|
|
17
|
+
"offline",
|
|
18
|
+
"package-manager",
|
|
19
|
+
"cli",
|
|
20
|
+
"offline-package-manager",
|
|
21
|
+
"offline-install",
|
|
22
|
+
"npm-cache",
|
|
23
|
+
"node-cli",
|
|
24
|
+
"dependency-cache",
|
|
25
|
+
"dependency-manager",
|
|
26
|
+
"offline-first"
|
|
27
|
+
],
|
|
28
|
+
"author": "Sagor Ahamed",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"files": [
|
|
31
|
+
"bin",
|
|
32
|
+
"dist"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"commander": "^11.1.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"esbuild": "^0.19.0"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=16.0.0"
|
|
42
|
+
},
|
|
43
|
+
"postuninstall": "node -e \"const fs=require('fs');const path=require('path');const os=require('os');const dir=path.join(os.homedir(),'.offline-npm-cache');if(fs.existsSync(dir))fs.rmSync(dir,{recursive:true,force:true});\"",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "https://github.com/SagorAhamed251245/offline-npm-manager.git"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/SagorAhamed251245/offline-npm-manager/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/SagorAhamed251245",
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"contributors": [
|
|
56
|
+
{
|
|
57
|
+
"name": "Sagor Ahamed",
|
|
58
|
+
"url": "https://github.com/SagorAhamed251245"
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"funding": [
|
|
62
|
+
{
|
|
63
|
+
"type": "github",
|
|
64
|
+
"url": "https://github.com/sponsors/SagorAhamed251245"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|