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