npmdata 0.9.1 → 0.10.0
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/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +113 -9
- package/dist/cli.js.map +1 -1
- package/dist/consumer.d.ts +15 -0
- package/dist/consumer.d.ts.map +1 -1
- package/dist/consumer.js +316 -215
- package/dist/consumer.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/npmdata-0.0.1.tgz +0 -0
- package/dist/runner.d.ts +5 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +209 -21
- package/dist/runner.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +16 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +32 -0
- package/dist/utils.js.map +1 -1
- package/package.json +1 -1
package/dist/consumer.js
CHANGED
|
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.compressGitignoreEntries = compressGitignoreEntries;
|
|
7
|
+
exports.findNearestMarkerPath = findNearestMarkerPath;
|
|
6
8
|
exports.extract = extract;
|
|
7
9
|
exports.check = check;
|
|
8
10
|
exports.purge = purge;
|
|
@@ -12,6 +14,7 @@ exports.list = list;
|
|
|
12
14
|
/* eslint-disable no-continue */
|
|
13
15
|
/* eslint-disable functional/immutable-data */
|
|
14
16
|
/* eslint-disable no-restricted-syntax */
|
|
17
|
+
/* eslint-disable max-depth */
|
|
15
18
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
16
19
|
const node_path_1 = __importDefault(require("node:path"));
|
|
17
20
|
const node_child_process_1 = require("node:child_process");
|
|
@@ -22,6 +25,24 @@ const MARKER_FILE = '.npmdata';
|
|
|
22
25
|
const GITIGNORE_FILE = '.gitignore';
|
|
23
26
|
const GITIGNORE_START = '# npmdata:start';
|
|
24
27
|
const GITIGNORE_END = '# npmdata:end';
|
|
28
|
+
/**
|
|
29
|
+
* Read the .gitignore at dir and return parsed patterns, excluding the npmdata-managed
|
|
30
|
+
* section. These are the "external" patterns (e.g. node_modules, dist) that were written
|
|
31
|
+
* by the project author rather than by npmdata itself.
|
|
32
|
+
*/
|
|
33
|
+
function readExternalGitignorePatterns(dir) {
|
|
34
|
+
const gitignorePath = node_path_1.default.join(dir, GITIGNORE_FILE);
|
|
35
|
+
if (!node_fs_1.default.existsSync(gitignorePath))
|
|
36
|
+
return [];
|
|
37
|
+
let content = node_fs_1.default.readFileSync(gitignorePath, 'utf8');
|
|
38
|
+
// Strip out the npmdata-managed block so we only act on external entries.
|
|
39
|
+
const startIdx = content.indexOf(GITIGNORE_START);
|
|
40
|
+
const endIdx = content.indexOf(GITIGNORE_END);
|
|
41
|
+
if (startIdx !== -1 && endIdx !== -1 && startIdx < endIdx) {
|
|
42
|
+
content = content.slice(0, startIdx) + content.slice(endIdx + GITIGNORE_END.length);
|
|
43
|
+
}
|
|
44
|
+
return (0, utils_1.parseGitignorePatterns)(content);
|
|
45
|
+
}
|
|
25
46
|
/**
|
|
26
47
|
* Update (or create) a .gitignore in the given directory so that the managed
|
|
27
48
|
* files and the .npmdata marker file are ignored by git.
|
|
@@ -67,40 +88,161 @@ function updateGitignoreForDir(dir, managedFilenames, addEntries = true) {
|
|
|
67
88
|
node_fs_1.default.writeFileSync(gitignorePath, updatedContent, 'utf8');
|
|
68
89
|
}
|
|
69
90
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
91
|
+
* Optimise the list of managed file paths for use in .gitignore.
|
|
92
|
+
* When every file inside a directory (recursively, excluding MARKER_FILE, GITIGNORE_FILE, and
|
|
93
|
+
* symlinks) is present in managedPaths, the whole directory is represented as "dir/" rather than
|
|
94
|
+
* listing each file individually. Root-level files (no slash) are always emitted as-is.
|
|
95
|
+
*
|
|
96
|
+
* @param managedPaths - Paths relative to outputDir (e.g. ["docs/guide.md", "README.md"])
|
|
97
|
+
* @param outputDir - Absolute path to the root used to inspect actual disk contents
|
|
98
|
+
*/
|
|
99
|
+
function compressGitignoreEntries(managedPaths, outputDir) {
|
|
100
|
+
const managedSet = new Set(managedPaths);
|
|
101
|
+
const gitignorePatterns = readExternalGitignorePatterns(outputDir);
|
|
102
|
+
// Returns true when every non-special, non-symlink file inside absDir (recursively)
|
|
103
|
+
// appears in managedSet under its full outputDir-relative path (relDir prefix included).
|
|
104
|
+
const isDirFullyManaged = (absDir, relDir) => {
|
|
105
|
+
if (!node_fs_1.default.existsSync(absDir))
|
|
106
|
+
return false;
|
|
107
|
+
for (const entry of node_fs_1.default.readdirSync(absDir)) {
|
|
108
|
+
if (entry === MARKER_FILE || entry === GITIGNORE_FILE)
|
|
109
|
+
continue;
|
|
110
|
+
const absEntry = node_path_1.default.join(absDir, entry);
|
|
111
|
+
const relEntry = `${relDir}/${entry}`;
|
|
112
|
+
const lstat = node_fs_1.default.lstatSync(absEntry);
|
|
113
|
+
if (lstat.isSymbolicLink())
|
|
114
|
+
continue;
|
|
115
|
+
if (lstat.isDirectory()) {
|
|
116
|
+
// Skip gitignored subdirs that have no managed files — they are not our concern
|
|
117
|
+
// and traversing them (e.g. node_modules) causes serious performance problems.
|
|
118
|
+
if ((0, utils_1.isGitignored)(entry, gitignorePatterns) && !(0, utils_1.hasManagedFilesUnder)(relEntry, managedSet)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (!isDirFullyManaged(absEntry, relEntry))
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
else if (!managedSet.has(relEntry))
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
};
|
|
129
|
+
// paths: managed paths relative to the current directory scope
|
|
130
|
+
// absRoot: absolute path of the current directory scope
|
|
131
|
+
// relRoot: path of the current scope relative to outputDir (empty string at top level)
|
|
132
|
+
const compress = (paths, absRoot, relRoot) => {
|
|
133
|
+
const result = [];
|
|
134
|
+
const subdirNames = new Set();
|
|
135
|
+
for (const p of paths) {
|
|
136
|
+
const slashIdx = p.indexOf('/');
|
|
137
|
+
if (slashIdx === -1) {
|
|
138
|
+
// File lives directly in this scope — emit its full outputDir-relative path
|
|
139
|
+
result.push(relRoot ? `${relRoot}/${p}` : p);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
subdirNames.add(p.slice(0, slashIdx));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const dirName of subdirNames) {
|
|
146
|
+
const absDir = node_path_1.default.join(absRoot, dirName);
|
|
147
|
+
const relDir = relRoot ? `${relRoot}/${dirName}` : dirName;
|
|
148
|
+
const prefix = `${dirName}/`;
|
|
149
|
+
const subPaths = paths.filter((p) => p.startsWith(prefix)).map((p) => p.slice(prefix.length));
|
|
150
|
+
if (isDirFullyManaged(absDir, relDir)) {
|
|
151
|
+
result.push(`${relDir}/`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
result.push(...compress(subPaths, absDir, relDir));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
};
|
|
159
|
+
return compress(managedPaths, outputDir, '');
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Find the nearest .npmdata marker file by walking up from fromDir to outputDir (inclusive).
|
|
163
|
+
* Returns the path to the marker file, or null if none found within the outputDir boundary.
|
|
164
|
+
*/
|
|
165
|
+
function findNearestMarkerPath(fromDir, outputDir) {
|
|
166
|
+
let dir = fromDir;
|
|
167
|
+
const resolvedOutput = node_path_1.default.resolve(outputDir);
|
|
168
|
+
// eslint-disable-next-line no-constant-condition
|
|
169
|
+
while (true) {
|
|
170
|
+
const markerPath = node_path_1.default.join(dir, MARKER_FILE);
|
|
171
|
+
if (node_fs_1.default.existsSync(markerPath))
|
|
172
|
+
return markerPath;
|
|
173
|
+
if (node_path_1.default.resolve(dir) === resolvedOutput)
|
|
174
|
+
break;
|
|
175
|
+
const parent = node_path_1.default.dirname(dir);
|
|
176
|
+
if (parent === dir)
|
|
177
|
+
break; // reached filesystem root
|
|
178
|
+
dir = parent;
|
|
179
|
+
}
|
|
180
|
+
// eslint-disable-next-line unicorn/no-null
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Write one .gitignore at outputDir containing all managed file paths (relative to outputDir),
|
|
185
|
+
* and remove any npmdata sections from .gitignore files in subdirectories.
|
|
73
186
|
* When addEntries is false, existing sections are updated/removed but no new
|
|
74
187
|
* sections are created — use this to clean up without opting into gitignore management.
|
|
75
188
|
*/
|
|
76
189
|
function updateGitignores(outputDir, addEntries = true) {
|
|
77
190
|
if (!node_fs_1.default.existsSync(outputDir))
|
|
78
191
|
return;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
catch {
|
|
88
|
-
// Ignore unreadable marker files
|
|
192
|
+
// Read managed paths up-front so we can skip gitignored dirs that have no managed files.
|
|
193
|
+
const managedPaths = new Set();
|
|
194
|
+
const rootMarkerPathForRead = node_path_1.default.join(outputDir, MARKER_FILE);
|
|
195
|
+
if (node_fs_1.default.existsSync(rootMarkerPathForRead)) {
|
|
196
|
+
// eslint-disable-next-line functional/no-try-statements
|
|
197
|
+
try {
|
|
198
|
+
for (const m of (0, utils_1.readCsvMarker)(rootMarkerPathForRead)) {
|
|
199
|
+
managedPaths.add(m.path);
|
|
89
200
|
}
|
|
90
201
|
}
|
|
91
|
-
|
|
92
|
-
//
|
|
93
|
-
updateGitignoreForDir(dir, [], addEntries);
|
|
202
|
+
catch {
|
|
203
|
+
// ignore unreadable marker
|
|
94
204
|
}
|
|
205
|
+
}
|
|
206
|
+
// Read external gitignore patterns once for the whole walk.
|
|
207
|
+
const gitignorePatterns = readExternalGitignorePatterns(outputDir);
|
|
208
|
+
// Remove npmdata sections from all subdirectory .gitignore files (migration / cleanup of old format)
|
|
209
|
+
const cleanupSubDirGitignores = (dir) => {
|
|
95
210
|
for (const item of node_fs_1.default.readdirSync(dir)) {
|
|
96
211
|
const fullPath = node_path_1.default.join(dir, item);
|
|
97
212
|
const lstat = node_fs_1.default.lstatSync(fullPath);
|
|
98
213
|
if (!lstat.isSymbolicLink() && lstat.isDirectory()) {
|
|
99
|
-
|
|
214
|
+
const relPath = node_path_1.default.relative(outputDir, fullPath);
|
|
215
|
+
// Skip gitignored directories that have no managed files under them —
|
|
216
|
+
// traversing them (e.g. node_modules) causes serious performance problems.
|
|
217
|
+
if ((0, utils_1.isGitignored)(item, gitignorePatterns) && !(0, utils_1.hasManagedFilesUnder)(relPath, managedPaths)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const subGitignore = node_path_1.default.join(fullPath, GITIGNORE_FILE);
|
|
221
|
+
if (node_fs_1.default.existsSync(subGitignore)) {
|
|
222
|
+
updateGitignoreForDir(fullPath, [], false);
|
|
223
|
+
}
|
|
224
|
+
cleanupSubDirGitignores(fullPath);
|
|
100
225
|
}
|
|
101
226
|
}
|
|
102
227
|
};
|
|
103
|
-
|
|
228
|
+
cleanupSubDirGitignores(outputDir);
|
|
229
|
+
// Update (or remove) the single .gitignore at outputDir
|
|
230
|
+
const rootMarkerPath = node_path_1.default.join(outputDir, MARKER_FILE);
|
|
231
|
+
if (node_fs_1.default.existsSync(rootMarkerPath)) {
|
|
232
|
+
try {
|
|
233
|
+
const managedFiles = (0, utils_1.readCsvMarker)(rootMarkerPath);
|
|
234
|
+
const rawPaths = managedFiles.map((m) => m.path);
|
|
235
|
+
const optimisedPaths = compressGitignoreEntries(rawPaths, outputDir);
|
|
236
|
+
updateGitignoreForDir(outputDir, optimisedPaths, addEntries);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
// Ignore unreadable marker files
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
// Clean up any leftover npmdata section at root
|
|
244
|
+
updateGitignoreForDir(outputDir, [], false);
|
|
245
|
+
}
|
|
104
246
|
}
|
|
105
247
|
async function getPackageFiles(packageName, cwd) {
|
|
106
248
|
const pkgPath = require.resolve(`${packageName}/package.json`, {
|
|
@@ -177,41 +319,23 @@ async function ensurePackageInstalled(packageName, version, packageManager, cwd,
|
|
|
177
319
|
return installedVersion;
|
|
178
320
|
}
|
|
179
321
|
/**
|
|
180
|
-
* Load all managed files from marker
|
|
181
|
-
* Paths are relative to outputDir.
|
|
322
|
+
* Load all managed files from the root marker file at outputDir.
|
|
323
|
+
* Paths stored in the marker are already relative to outputDir.
|
|
324
|
+
* Uses findNearestMarkerPath starting from outputDir itself.
|
|
182
325
|
*/
|
|
183
326
|
function loadAllManagedFiles(outputDir) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const markerDir = node_path_1.default.dirname(fullPath);
|
|
197
|
-
const relMarkerDir = node_path_1.default.relative(outputDir, markerDir);
|
|
198
|
-
for (const managed of managedFiles) {
|
|
199
|
-
const relPath = relMarkerDir === '.' ? managed.path : node_path_1.default.join(relMarkerDir, managed.path);
|
|
200
|
-
files.push({ ...managed, path: relPath });
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
catch {
|
|
204
|
-
console.warn(`Warning: Failed to read marker file at ${fullPath}. Skipping.`); // eslint-disable-line no-console
|
|
205
|
-
// Ignore unreadable marker files
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
else if (stat.isDirectory()) {
|
|
209
|
-
walkDir(fullPath);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
walkDir(outputDir);
|
|
214
|
-
return files;
|
|
327
|
+
if (!node_fs_1.default.existsSync(outputDir))
|
|
328
|
+
return [];
|
|
329
|
+
const markerPath = findNearestMarkerPath(outputDir, outputDir);
|
|
330
|
+
if (!markerPath)
|
|
331
|
+
return [];
|
|
332
|
+
try {
|
|
333
|
+
return (0, utils_1.readCsvMarker)(markerPath);
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
console.warn(`Warning: Failed to read marker file at ${markerPath}. Skipping.`); // eslint-disable-line no-console
|
|
337
|
+
return [];
|
|
338
|
+
}
|
|
215
339
|
}
|
|
216
340
|
/**
|
|
217
341
|
* Load managed files from all marker files under outputDir, keyed by relative path.
|
|
@@ -221,34 +345,25 @@ function loadManagedFilesMap(outputDir) {
|
|
|
221
345
|
return new Map(loadAllManagedFiles(outputDir).map((m) => [m.path, m]));
|
|
222
346
|
}
|
|
223
347
|
function cleanupEmptyMarkers(outputDir) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
node_fs_1.default.unlinkSync(fullPath);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
catch {
|
|
238
|
-
// Ignore unreadable marker files
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
else {
|
|
242
|
-
const lstat = node_fs_1.default.lstatSync(fullPath);
|
|
243
|
-
if (!lstat.isSymbolicLink() && lstat.isDirectory()) {
|
|
244
|
-
walkDir(fullPath);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
348
|
+
if (!node_fs_1.default.existsSync(outputDir))
|
|
349
|
+
return;
|
|
350
|
+
const markerPath = node_path_1.default.join(outputDir, MARKER_FILE);
|
|
351
|
+
if (!node_fs_1.default.existsSync(markerPath))
|
|
352
|
+
return;
|
|
353
|
+
try {
|
|
354
|
+
const managedFiles = (0, utils_1.readCsvMarker)(markerPath);
|
|
355
|
+
if (managedFiles.length === 0) {
|
|
356
|
+
node_fs_1.default.chmodSync(markerPath, 0o644);
|
|
357
|
+
node_fs_1.default.unlinkSync(markerPath);
|
|
247
358
|
}
|
|
248
|
-
}
|
|
249
|
-
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
// Ignore unreadable marker files
|
|
362
|
+
}
|
|
250
363
|
}
|
|
251
364
|
function cleanupEmptyDirs(outputDir) {
|
|
365
|
+
const gitignorePatterns = readExternalGitignorePatterns(outputDir);
|
|
366
|
+
const managedPaths = new Set(loadAllManagedFiles(outputDir).map((m) => m.path));
|
|
252
367
|
const walkDir = (dir) => {
|
|
253
368
|
if (!node_fs_1.default.existsSync(dir))
|
|
254
369
|
return true;
|
|
@@ -257,6 +372,13 @@ function cleanupEmptyDirs(outputDir) {
|
|
|
257
372
|
const fullPath = node_path_1.default.join(dir, item);
|
|
258
373
|
const lstat = node_fs_1.default.lstatSync(fullPath);
|
|
259
374
|
if (!lstat.isSymbolicLink() && lstat.isDirectory()) {
|
|
375
|
+
const relPath = node_path_1.default.relative(outputDir, fullPath);
|
|
376
|
+
// Skip gitignored directories that have no managed files — they are not our concern
|
|
377
|
+
// and traversing them (e.g. node_modules) causes serious performance problems.
|
|
378
|
+
if ((0, utils_1.isGitignored)(item, gitignorePatterns) && !(0, utils_1.hasManagedFilesUnder)(relPath, managedPaths)) {
|
|
379
|
+
isEmpty = false; // treat as non-empty so we preserve the parent directory
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
260
382
|
const childEmpty = walkDir(fullPath);
|
|
261
383
|
if (!childEmpty)
|
|
262
384
|
isEmpty = false;
|
|
@@ -289,160 +411,145 @@ async function extractFiles(config, packageName) {
|
|
|
289
411
|
}
|
|
290
412
|
emit?.({ type: 'package-start', packageName, packageVersion: installedVersion });
|
|
291
413
|
const packageFiles = await getPackageFiles(packageName, config.cwd);
|
|
292
|
-
const
|
|
414
|
+
const extractedFiles = [];
|
|
293
415
|
const existingManagedMap = loadManagedFilesMap(config.outputDir);
|
|
294
|
-
|
|
295
|
-
// Tracks basenames (per directory) force-claimed from a different package so the
|
|
416
|
+
// Tracks full relPaths force-claimed from a different package so the
|
|
296
417
|
// marker-file merge can evict the previous owner's entry.
|
|
297
|
-
const
|
|
418
|
+
const forceClaimedPaths = new Set();
|
|
298
419
|
// eslint-disable-next-line functional/no-let
|
|
299
420
|
let wasForced = false;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
!(0, utils_1.
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
|
|
421
|
+
try {
|
|
422
|
+
for (const packageFile of packageFiles) {
|
|
423
|
+
if (!(0, utils_1.matchesFilenamePattern)(packageFile.relPath, config.filenamePatterns ?? types_1.DEFAULT_FILENAME_PATTERNS) ||
|
|
424
|
+
!(0, utils_1.matchesContentRegex)(packageFile.fullPath, config.contentRegexes)) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
const destPath = node_path_1.default.join(config.outputDir, packageFile.relPath);
|
|
428
|
+
if (!dryRun)
|
|
429
|
+
(0, utils_1.ensureDir)(node_path_1.default.dirname(destPath));
|
|
430
|
+
const existingOwner = existingManagedMap.get(packageFile.relPath);
|
|
431
|
+
// In unmanaged mode, skip files that already exist on disk.
|
|
432
|
+
if (config.unmanaged && node_fs_1.default.existsSync(destPath)) {
|
|
433
|
+
changes.skipped.push(packageFile.relPath);
|
|
434
|
+
emit?.({ type: 'file-skipped', packageName, file: packageFile.relPath });
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
// In keep-existing mode, skip files that already exist on disk but create missing ones normally.
|
|
438
|
+
if (config.keepExisting && node_fs_1.default.existsSync(destPath)) {
|
|
439
|
+
changes.skipped.push(packageFile.relPath);
|
|
440
|
+
emit?.({ type: 'file-skipped', packageName, file: packageFile.relPath });
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (node_fs_1.default.existsSync(destPath)) {
|
|
444
|
+
if (existingOwner?.packageName === packageName) {
|
|
445
|
+
if ((0, utils_1.calculateFileHash)(packageFile.fullPath) === (0, utils_1.calculateFileHash)(destPath)) {
|
|
446
|
+
changes.skipped.push(packageFile.relPath);
|
|
447
|
+
emit?.({ type: 'file-skipped', packageName, file: packageFile.relPath });
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
if (!dryRun)
|
|
451
|
+
(0, utils_1.copyFile)(packageFile.fullPath, destPath);
|
|
452
|
+
changes.modified.push(packageFile.relPath);
|
|
453
|
+
emit?.({ type: 'file-modified', packageName, file: packageFile.relPath });
|
|
454
|
+
}
|
|
455
|
+
wasForced = false;
|
|
326
456
|
}
|
|
327
457
|
else {
|
|
458
|
+
// File exists but is owned by a different package (clash) or is unmanaged (conflict).
|
|
459
|
+
// Behaviour is identical in both cases: throw when force is false, overwrite when true.
|
|
460
|
+
if (!config.force) {
|
|
461
|
+
if (existingOwner) {
|
|
462
|
+
throw new Error(`Package clash: ${packageFile.relPath} already managed by ${existingOwner.packageName}@${existingOwner.packageVersion}. Cannot extract from ${packageName}. Use force: true to override.`);
|
|
463
|
+
}
|
|
464
|
+
throw new Error(`File conflict: ${packageFile.relPath} already exists and is not managed by npmdata. Use force: true to override.`);
|
|
465
|
+
}
|
|
466
|
+
// force=true: overwrite the existing file and take ownership.
|
|
328
467
|
if (!dryRun)
|
|
329
468
|
(0, utils_1.copyFile)(packageFile.fullPath, destPath);
|
|
330
469
|
changes.modified.push(packageFile.relPath);
|
|
331
470
|
emit?.({ type: 'file-modified', packageName, file: packageFile.relPath });
|
|
332
|
-
|
|
333
|
-
wasForced = false;
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
// File exists but is owned by a different package (clash) or is unmanaged (conflict).
|
|
337
|
-
// Behaviour is identical in both cases: throw when force is false, overwrite when true.
|
|
338
|
-
if (!config.force) {
|
|
471
|
+
wasForced = true;
|
|
339
472
|
if (existingOwner) {
|
|
340
|
-
|
|
473
|
+
// Evict the previous owner's entry from the root marker file.
|
|
474
|
+
forceClaimedPaths.add(packageFile.relPath);
|
|
341
475
|
}
|
|
342
|
-
throw new Error(`File conflict: ${packageFile.relPath} already exists and is not managed by npmdata. Use force: true to override.`);
|
|
343
476
|
}
|
|
344
|
-
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
345
479
|
if (!dryRun)
|
|
346
480
|
(0, utils_1.copyFile)(packageFile.fullPath, destPath);
|
|
347
|
-
changes.
|
|
348
|
-
emit?.({ type: 'file-
|
|
349
|
-
wasForced =
|
|
350
|
-
if (existingOwner) {
|
|
351
|
-
// Evict the previous owner's entry from the marker file.
|
|
352
|
-
const claimDir = node_path_1.default.dirname(packageFile.relPath) || '.';
|
|
353
|
-
if (!forceClaimedByDir.has(claimDir))
|
|
354
|
-
forceClaimedByDir.set(claimDir, new Set());
|
|
355
|
-
forceClaimedByDir.get(claimDir).add(node_path_1.default.basename(packageFile.relPath));
|
|
356
|
-
}
|
|
481
|
+
changes.added.push(packageFile.relPath);
|
|
482
|
+
emit?.({ type: 'file-added', packageName, file: packageFile.relPath });
|
|
483
|
+
wasForced = false;
|
|
357
484
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (!
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (!config.unmanaged) {
|
|
369
|
-
const dir = node_path_1.default.dirname(packageFile.relPath) || '.';
|
|
370
|
-
if (!addedByDir.has(dir)) {
|
|
371
|
-
addedByDir.set(dir, []);
|
|
485
|
+
if (!dryRun && !config.unmanaged && node_fs_1.default.existsSync(destPath))
|
|
486
|
+
node_fs_1.default.chmodSync(destPath, 0o444);
|
|
487
|
+
if (!config.unmanaged) {
|
|
488
|
+
// eslint-disable-next-line functional/immutable-data
|
|
489
|
+
extractedFiles.push({
|
|
490
|
+
path: packageFile.relPath,
|
|
491
|
+
packageName,
|
|
492
|
+
packageVersion: installedVersion,
|
|
493
|
+
force: wasForced,
|
|
494
|
+
});
|
|
372
495
|
}
|
|
373
|
-
addedByDir.get(dir).push({
|
|
374
|
-
path: node_path_1.default.basename(packageFile.relPath),
|
|
375
|
-
packageName,
|
|
376
|
-
packageVersion: installedVersion,
|
|
377
|
-
force: wasForced,
|
|
378
|
-
});
|
|
379
496
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
changes.deleted.push(relPath);
|
|
394
|
-
emit?.({ type: 'file-deleted', packageName, file: relPath });
|
|
395
|
-
}
|
|
396
|
-
const dir = node_path_1.default.dirname(relPath) === '.' ? '.' : node_path_1.default.dirname(relPath);
|
|
397
|
-
if (!addedByDir.has(dir)) {
|
|
398
|
-
deletedOnlyDirs.add(dir);
|
|
497
|
+
// Delete files that were managed by this package but are no longer in the package
|
|
498
|
+
for (const [relPath, owner] of existingManagedMap) {
|
|
499
|
+
if (owner.packageName !== packageName)
|
|
500
|
+
continue;
|
|
501
|
+
const stillPresent = extractedFiles.some((m) => m.path === relPath);
|
|
502
|
+
if (!stillPresent) {
|
|
503
|
+
const fullPath = node_path_1.default.join(config.outputDir, relPath);
|
|
504
|
+
if (node_fs_1.default.existsSync(fullPath)) {
|
|
505
|
+
if (!dryRun)
|
|
506
|
+
(0, utils_1.removeFile)(fullPath);
|
|
507
|
+
changes.deleted.push(relPath);
|
|
508
|
+
emit?.({ type: 'file-deleted', packageName, file: relPath });
|
|
509
|
+
}
|
|
399
510
|
}
|
|
400
511
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
// eslint-disable-next-line unicorn/no-keyword-prefix
|
|
405
|
-
for (const [dir, newFiles] of addedByDir) {
|
|
406
|
-
const markerDir = dir === '.' ? config.outputDir : node_path_1.default.join(config.outputDir, dir);
|
|
407
|
-
(0, utils_1.ensureDir)(markerDir);
|
|
408
|
-
const markerPath = node_path_1.default.join(markerDir, MARKER_FILE);
|
|
409
|
-
// eslint-disable-next-line unicorn/no-null
|
|
512
|
+
if (!dryRun && !config.unmanaged) {
|
|
513
|
+
// Write a single root marker at outputDir with all managed file paths (relative to outputDir)
|
|
514
|
+
const rootMarkerPath = node_path_1.default.join(config.outputDir, MARKER_FILE);
|
|
410
515
|
let existingFiles = [];
|
|
411
|
-
if (node_fs_1.default.existsSync(
|
|
412
|
-
existingFiles = (0, utils_1.readCsvMarker)(
|
|
516
|
+
if (node_fs_1.default.existsSync(rootMarkerPath)) {
|
|
517
|
+
existingFiles = (0, utils_1.readCsvMarker)(rootMarkerPath);
|
|
413
518
|
}
|
|
414
|
-
// Keep entries from other packages,
|
|
415
|
-
// Also evict entries from other packages for any file force-claimed in this pass.
|
|
416
|
-
const claimedInDir = forceClaimedByDir.get(dir);
|
|
519
|
+
// Keep entries from other packages, evict entries from force-claimed paths.
|
|
417
520
|
const mergedFiles = [
|
|
418
|
-
...existingFiles.filter((m) => m.packageName !== packageName && !
|
|
419
|
-
|
|
420
|
-
...newFiles,
|
|
521
|
+
...existingFiles.filter((m) => m.packageName !== packageName && !forceClaimedPaths.has(m.path)),
|
|
522
|
+
...extractedFiles,
|
|
421
523
|
];
|
|
422
|
-
(0
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const markerDir = dir === '.' ? config.outputDir : node_path_1.default.join(config.outputDir, dir);
|
|
427
|
-
const markerPath = node_path_1.default.join(markerDir, MARKER_FILE);
|
|
428
|
-
if (!node_fs_1.default.existsSync(markerPath))
|
|
429
|
-
continue;
|
|
430
|
-
try {
|
|
431
|
-
const existingFiles = (0, utils_1.readCsvMarker)(markerPath);
|
|
432
|
-
const mergedFiles = existingFiles.filter((m) => m.packageName !== packageName);
|
|
433
|
-
if (mergedFiles.length === 0) {
|
|
434
|
-
node_fs_1.default.chmodSync(markerPath, 0o644);
|
|
435
|
-
node_fs_1.default.unlinkSync(markerPath);
|
|
436
|
-
}
|
|
437
|
-
else {
|
|
438
|
-
(0, utils_1.writeCsvMarker)(markerPath, mergedFiles);
|
|
524
|
+
if (mergedFiles.length === 0) {
|
|
525
|
+
if (node_fs_1.default.existsSync(rootMarkerPath)) {
|
|
526
|
+
node_fs_1.default.chmodSync(rootMarkerPath, 0o644);
|
|
527
|
+
node_fs_1.default.unlinkSync(rootMarkerPath);
|
|
439
528
|
}
|
|
440
529
|
}
|
|
441
|
-
|
|
442
|
-
|
|
530
|
+
else {
|
|
531
|
+
(0, utils_1.writeCsvMarker)(rootMarkerPath, mergedFiles);
|
|
532
|
+
}
|
|
533
|
+
cleanupEmptyMarkers(config.outputDir);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
catch (error) {
|
|
537
|
+
// On error, delete all files that were created during this extraction run
|
|
538
|
+
if (!dryRun) {
|
|
539
|
+
for (const relPath of changes.added) {
|
|
540
|
+
const fullPath = node_path_1.default.join(config.outputDir, relPath);
|
|
541
|
+
if (node_fs_1.default.existsSync(fullPath)) {
|
|
542
|
+
try {
|
|
543
|
+
(0, utils_1.removeFile)(fullPath);
|
|
544
|
+
}
|
|
545
|
+
catch {
|
|
546
|
+
// ignore cleanup errors
|
|
547
|
+
}
|
|
548
|
+
}
|
|
443
549
|
}
|
|
550
|
+
cleanupEmptyDirs(config.outputDir);
|
|
444
551
|
}
|
|
445
|
-
|
|
552
|
+
throw error;
|
|
446
553
|
}
|
|
447
554
|
emit?.({ type: 'package-end', packageName, packageVersion: installedVersion });
|
|
448
555
|
return changes;
|
|
@@ -631,7 +738,6 @@ async function purge(config) {
|
|
|
631
738
|
for (const spec of config.packages) {
|
|
632
739
|
const { name: packageName } = (0, utils_1.parsePackageSpec)(spec);
|
|
633
740
|
const deleted = [];
|
|
634
|
-
const deletedOnlyDirs = new Set();
|
|
635
741
|
emit?.({ type: 'package-start', packageName, packageVersion: 'unknown' });
|
|
636
742
|
const allManaged = loadManagedFilesMap(config.outputDir);
|
|
637
743
|
for (const [relPath, owner] of allManaged) {
|
|
@@ -644,26 +750,21 @@ async function purge(config) {
|
|
|
644
750
|
deleted.push(relPath);
|
|
645
751
|
emit?.({ type: 'file-deleted', packageName, file: relPath });
|
|
646
752
|
}
|
|
647
|
-
const dir = node_path_1.default.dirname(relPath) === '.' ? '.' : node_path_1.default.dirname(relPath);
|
|
648
|
-
deletedOnlyDirs.add(dir);
|
|
649
753
|
}
|
|
650
754
|
if (!dryRun) {
|
|
651
|
-
// Update marker
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
const markerPath = node_path_1.default.join(markerDir, MARKER_FILE);
|
|
655
|
-
if (!node_fs_1.default.existsSync(markerPath))
|
|
656
|
-
continue;
|
|
755
|
+
// Update root marker: remove entries owned by this package.
|
|
756
|
+
const rootMarkerPath = node_path_1.default.join(config.outputDir, MARKER_FILE);
|
|
757
|
+
if (node_fs_1.default.existsSync(rootMarkerPath)) {
|
|
657
758
|
// eslint-disable-next-line functional/no-try-statements
|
|
658
759
|
try {
|
|
659
|
-
const existingFiles = (0, utils_1.readCsvMarker)(
|
|
760
|
+
const existingFiles = (0, utils_1.readCsvMarker)(rootMarkerPath);
|
|
660
761
|
const mergedFiles = existingFiles.filter((m) => m.packageName !== packageName);
|
|
661
762
|
if (mergedFiles.length === 0) {
|
|
662
|
-
node_fs_1.default.chmodSync(
|
|
663
|
-
node_fs_1.default.unlinkSync(
|
|
763
|
+
node_fs_1.default.chmodSync(rootMarkerPath, 0o644);
|
|
764
|
+
node_fs_1.default.unlinkSync(rootMarkerPath);
|
|
664
765
|
}
|
|
665
766
|
else {
|
|
666
|
-
(0, utils_1.writeCsvMarker)(
|
|
767
|
+
(0, utils_1.writeCsvMarker)(rootMarkerPath, mergedFiles);
|
|
667
768
|
}
|
|
668
769
|
}
|
|
669
770
|
catch {
|