pkgprn 0.5.0 → 0.5.2
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/{src/extract-references.js → extract-references.js} +4 -23
- package/index.d.ts.map +14 -14
- package/{src/index.js → index.js} +3 -20
- package/package.json +4 -4
- package/{src/prune.js → prune.js} +38 -214
- package/strip-comments.js +496 -0
- package/src/strip-comments.js +0 -731
|
@@ -4,75 +4,28 @@ import path from 'node:path';
|
|
|
4
4
|
import { extractReferences } from './extract-references.js';
|
|
5
5
|
import { adjustSourcemapLineMappings, isStrippableFile, parseCommentTypes, stripCommentsWithLineMap } from './strip-comments.js';
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Files always included by npm regardless of the `files` array.
|
|
9
|
-
* README & LICENSE/LICENCE are matched case-insensitively by basename (without extension).
|
|
10
|
-
*/
|
|
11
7
|
const alwaysIncludedExact = ['package.json'];
|
|
12
8
|
const alwaysIncludedBasenames = ['README', 'LICENSE', 'LICENCE'];
|
|
13
9
|
|
|
14
|
-
/**
|
|
15
|
-
* Files/directories always ignored by npm by default.
|
|
16
|
-
*/
|
|
17
10
|
const alwaysIgnored = ['.DS_Store', '.hg', '.lock-wscript', '.svn', 'CVS', 'config.gypi', 'npm-debug.log'];
|
|
18
11
|
|
|
19
|
-
/**
|
|
20
|
-
* Glob-like patterns for always-ignored files.
|
|
21
|
-
* Each entry has a `test` function that checks whether a basename matches.
|
|
22
|
-
*/
|
|
23
12
|
const alwaysIgnoredPatterns = [
|
|
24
|
-
|
|
25
|
-
{ test: (
|
|
26
|
-
|
|
27
|
-
{ test: (
|
|
28
|
-
|
|
29
|
-
{ test: (
|
|
30
|
-
|
|
31
|
-
{ test: (
|
|
13
|
+
|
|
14
|
+
{ test: ( basename) => basename.endsWith('.orig') },
|
|
15
|
+
|
|
16
|
+
{ test: ( basename) => basename.startsWith('.') && basename.endsWith('.swp') },
|
|
17
|
+
|
|
18
|
+
{ test: ( basename) => basename.startsWith('._') },
|
|
19
|
+
|
|
20
|
+
{ test: ( basename) => /^\.wafpickle-\d+$/.test(basename) },
|
|
32
21
|
];
|
|
33
22
|
|
|
34
|
-
/**
|
|
35
|
-
* Subset of always-ignored that can never be included, even if listed in `files`.
|
|
36
|
-
*/
|
|
37
23
|
const hardIgnored = new Set(['.git', '.npmrc', 'node_modules', 'package-lock.json', 'pnpm-lock.yaml', 'yarn.lock', 'bun.lockb']);
|
|
38
24
|
|
|
39
|
-
/**
|
|
40
|
-
* @typedef {import('@niceties/logger').Logger} Logger
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @typedef {Object} PackageJson
|
|
45
|
-
* @property {Object.<string, string>} [scripts]
|
|
46
|
-
* @property {Object.<string, string>} [devDependencies]
|
|
47
|
-
* @property {string} [packageManager]
|
|
48
|
-
* @property {string} [main]
|
|
49
|
-
* @property {string|Object.<string, string>} [bin]
|
|
50
|
-
* @property {Array<string>} [files]
|
|
51
|
-
* @property {Record<string, unknown>} [directories]
|
|
52
|
-
* @property {Record<string, unknown>} [exports]
|
|
53
|
-
* @property {Record<string, unknown>} [typesVersions]
|
|
54
|
-
*/
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* @typedef {Object} PruneOptions
|
|
58
|
-
* @property {string} profile
|
|
59
|
-
* @property {string|boolean} flatten
|
|
60
|
-
* @property {boolean} removeSourcemaps
|
|
61
|
-
* @property {string|boolean} stripComments
|
|
62
|
-
* @property {boolean} optimizeFiles
|
|
63
|
-
* @property {boolean} cleanupFiles
|
|
64
|
-
*/
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Prunes a package.json object according to the given options.
|
|
68
|
-
* @param {PackageJson} pkg
|
|
69
|
-
* @param {PruneOptions} options
|
|
70
|
-
* @param {Logger} logger
|
|
71
|
-
*/
|
|
72
25
|
export async function prunePkg(pkg, options, logger) {
|
|
73
26
|
const scriptsToKeep = getScriptsData();
|
|
74
27
|
|
|
75
|
-
const keys = scriptsToKeep[
|
|
28
|
+
const keys = scriptsToKeep[ (options.profile)];
|
|
76
29
|
|
|
77
30
|
if (!keys) {
|
|
78
31
|
throw new Error(`unknown profile ${options.profile}`);
|
|
@@ -106,30 +59,28 @@ export async function prunePkg(pkg, options, logger) {
|
|
|
106
59
|
if (options.removeSourcemaps) {
|
|
107
60
|
const sourceMaps = await walkDir('.', ['node_modules']).then(files => files.filter(file => file.endsWith('.map')));
|
|
108
61
|
for (const sourceMap of sourceMaps) {
|
|
109
|
-
|
|
62
|
+
|
|
110
63
|
const sourceFile = sourceMap.slice(0, -4);
|
|
111
|
-
|
|
64
|
+
|
|
112
65
|
const sourceFileContent = await readFile(sourceFile, 'utf8');
|
|
113
|
-
|
|
66
|
+
|
|
114
67
|
const sourceMappingUrl = `\n//# sourceMappingURL=${path.basename(sourceMap)}`;
|
|
115
|
-
|
|
68
|
+
|
|
116
69
|
const newContent = sourceFileContent.replace(sourceMappingUrl, '');
|
|
117
|
-
|
|
70
|
+
|
|
118
71
|
await writeFile(sourceFile, newContent, 'utf8');
|
|
119
|
-
|
|
72
|
+
|
|
120
73
|
await rm(sourceMap);
|
|
121
74
|
}
|
|
122
75
|
}
|
|
123
76
|
|
|
124
77
|
if (options.stripComments) {
|
|
125
|
-
const typesToStrip = parseCommentTypes(
|
|
78
|
+
const typesToStrip = parseCommentTypes( (options.stripComments));
|
|
126
79
|
logger.update('stripping comments...');
|
|
127
80
|
const allFiles = await walkDir('.', ['node_modules']);
|
|
128
81
|
const jsFiles = allFiles.filter(isStrippableFile);
|
|
129
82
|
const dtsMapFiles = allFiles.filter(f => f.endsWith('.d.ts.map'));
|
|
130
83
|
|
|
131
|
-
// Strip comments from JS files and collect line maps keyed by file path.
|
|
132
|
-
/** @type {Map<string, Int32Array>} */
|
|
133
84
|
const lineMaps = new Map();
|
|
134
85
|
for (const file of jsFiles) {
|
|
135
86
|
const content = await readFile(file, 'utf8');
|
|
@@ -140,7 +91,6 @@ export async function prunePkg(pkg, options, logger) {
|
|
|
140
91
|
}
|
|
141
92
|
}
|
|
142
93
|
|
|
143
|
-
// Adjust .d.ts.map files that reference any of the stripped JS files.
|
|
144
94
|
if (lineMaps.size > 0 && dtsMapFiles.length > 0) {
|
|
145
95
|
for (const mapFile of dtsMapFiles) {
|
|
146
96
|
const mapContent = await readFile(mapFile, 'utf8');
|
|
@@ -184,7 +134,6 @@ export async function prunePkg(pkg, options, logger) {
|
|
|
184
134
|
}
|
|
185
135
|
}
|
|
186
136
|
|
|
187
|
-
// walk depth keys from the highest to the lowest
|
|
188
137
|
const maxDepth = Math.max(...depthToFiles.keys());
|
|
189
138
|
for (let depth = maxDepth; depth > 0; --depth) {
|
|
190
139
|
const files = depthToFiles.get(depth);
|
|
@@ -199,9 +148,9 @@ export async function prunePkg(pkg, options, logger) {
|
|
|
199
148
|
}
|
|
200
149
|
}
|
|
201
150
|
for (const [dirname, filesInDir] of mapDirToFiles) {
|
|
202
|
-
|
|
151
|
+
|
|
203
152
|
const realFiles = await readdir(dirname);
|
|
204
|
-
|
|
153
|
+
|
|
205
154
|
const allFilesInDir = realFiles.every(file => filesInDir.includes(file)) || realFiles.length === 0;
|
|
206
155
|
if (allFilesInDir && dirname !== '.') {
|
|
207
156
|
if (!depthToFiles.has(depth - 1)) {
|
|
@@ -212,8 +161,8 @@ export async function prunePkg(pkg, options, logger) {
|
|
|
212
161
|
const thisDepth = depthToFiles.get(depth);
|
|
213
162
|
depthToFiles.set(
|
|
214
163
|
depth,
|
|
215
|
-
thisDepth.filter((
|
|
216
|
-
filesInDir.every((
|
|
164
|
+
thisDepth.filter(( file) =>
|
|
165
|
+
filesInDir.every(( fileInDir) => path.join(dirname, fileInDir) !== file)
|
|
217
166
|
)
|
|
218
167
|
);
|
|
219
168
|
}
|
|
@@ -222,7 +171,7 @@ export async function prunePkg(pkg, options, logger) {
|
|
|
222
171
|
|
|
223
172
|
pkg.files = [...new Set(Array.from(depthToFiles.values()).flat())];
|
|
224
173
|
|
|
225
|
-
pkg.files = pkg.files.filter((
|
|
174
|
+
pkg.files = pkg.files.filter(( file) => {
|
|
226
175
|
const fileNormalized = normalizePath(file);
|
|
227
176
|
const dirname = path.dirname(fileNormalized);
|
|
228
177
|
const basenameWithoutExtension = path.basename(fileNormalized, path.extname(fileNormalized)).toUpperCase();
|
|
@@ -232,16 +181,13 @@ export async function prunePkg(pkg, options, logger) {
|
|
|
232
181
|
);
|
|
233
182
|
});
|
|
234
183
|
|
|
235
|
-
/**
|
|
236
|
-
* @type {string[]}
|
|
237
|
-
*/
|
|
238
184
|
const ignoreDirs = [];
|
|
239
185
|
|
|
240
186
|
for (const fileOrDir of pkg.files) {
|
|
241
187
|
if (await isDirectory(fileOrDir)) {
|
|
242
188
|
const allFiles = await walkDir(fileOrDir);
|
|
243
189
|
if (
|
|
244
|
-
allFiles.every((
|
|
190
|
+
allFiles.every(( file) => {
|
|
245
191
|
const fileNormalized = normalizePath(file);
|
|
246
192
|
return filterFiles.includes(fileNormalized);
|
|
247
193
|
})
|
|
@@ -263,26 +209,12 @@ export async function prunePkg(pkg, options, logger) {
|
|
|
263
209
|
}
|
|
264
210
|
}
|
|
265
211
|
|
|
266
|
-
/**
|
|
267
|
-
* Flattens the dist directory and updates package.json references.
|
|
268
|
-
* Supports multiple directories (comma-separated when passed as a string).
|
|
269
|
-
* @param {PackageJson} pkg
|
|
270
|
-
* @param {string|true} flatten
|
|
271
|
-
* @param {Logger} logger
|
|
272
|
-
*/
|
|
273
212
|
async function flatten(pkg, flatten, logger) {
|
|
274
|
-
// find out where is the dist folder
|
|
275
213
|
|
|
276
214
|
const allReferences = extractReferences(pkg);
|
|
277
215
|
|
|
278
|
-
/** @type {string[]} */
|
|
279
216
|
let distDirs;
|
|
280
217
|
|
|
281
|
-
// at this point we requested directories.bin, but it is the only one that is directory and not a file
|
|
282
|
-
// later when we get dirname we can't flatten directories.bin completely
|
|
283
|
-
// it is easy to fix by checking element is a directory but it is kind of good
|
|
284
|
-
// to have it as a separate directory, but user still can flatten it by specifying the directory
|
|
285
|
-
|
|
286
218
|
if (flatten === true) {
|
|
287
219
|
let commonSegments;
|
|
288
220
|
|
|
@@ -311,7 +243,7 @@ async function flatten(pkg, flatten, logger) {
|
|
|
311
243
|
}
|
|
312
244
|
distDirs = [distDir];
|
|
313
245
|
} else {
|
|
314
|
-
|
|
246
|
+
|
|
315
247
|
distDirs = flatten
|
|
316
248
|
.split(',')
|
|
317
249
|
.map(d => normalizePath(d.trim()))
|
|
@@ -320,9 +252,6 @@ async function flatten(pkg, flatten, logger) {
|
|
|
320
252
|
|
|
321
253
|
logger.update(`flattening ${distDirs.join(', ')}...`);
|
|
322
254
|
|
|
323
|
-
// collect files from all dist directories
|
|
324
|
-
|
|
325
|
-
/** @type {Map<string, { distDir: string, relativeDistDir: string, files: string[] }>} */
|
|
326
255
|
const distDirInfo = new Map();
|
|
327
256
|
|
|
328
257
|
for (const distDir of distDirs) {
|
|
@@ -331,26 +260,21 @@ async function flatten(pkg, flatten, logger) {
|
|
|
331
260
|
distDirInfo.set(distDir, { distDir, relativeDistDir, files });
|
|
332
261
|
}
|
|
333
262
|
|
|
334
|
-
// check for conflicts: files already existing in root AND cross-directory collisions
|
|
335
|
-
|
|
336
|
-
/** @type {Map<string, string>} */
|
|
337
263
|
const destinationToSource = new Map();
|
|
338
264
|
const existsPromises = [];
|
|
339
|
-
|
|
265
|
+
|
|
340
266
|
const existsKeys = [];
|
|
341
267
|
|
|
342
268
|
for (const [distDir, info] of distDirInfo) {
|
|
343
269
|
for (const file of info.files) {
|
|
344
270
|
const relativePath = path.relative(info.relativeDistDir, file);
|
|
345
271
|
|
|
346
|
-
// check for cross-directory conflicts
|
|
347
272
|
if (destinationToSource.has(relativePath)) {
|
|
348
273
|
const otherDir = destinationToSource.get(relativePath);
|
|
349
274
|
throw new Error(`cannot flatten because '${relativePath}' exists in both '${otherDir}' and '${distDir}'`);
|
|
350
275
|
}
|
|
351
276
|
destinationToSource.set(relativePath, distDir);
|
|
352
277
|
|
|
353
|
-
// check if file already exists in root
|
|
354
278
|
existsKeys.push(relativePath);
|
|
355
279
|
existsPromises.push(isExists(relativePath));
|
|
356
280
|
}
|
|
@@ -364,7 +288,6 @@ async function flatten(pkg, flatten, logger) {
|
|
|
364
288
|
throw new Error(`dist folder cannot be flattened because files already exist: ${filesAlreadyExist.join(', ')}`);
|
|
365
289
|
}
|
|
366
290
|
|
|
367
|
-
// handle directories.bin special case for each dist dir
|
|
368
291
|
for (const distDir of distDirs) {
|
|
369
292
|
if (
|
|
370
293
|
'directories' in pkg &&
|
|
@@ -390,7 +313,6 @@ async function flatten(pkg, flatten, logger) {
|
|
|
390
313
|
}
|
|
391
314
|
}
|
|
392
315
|
|
|
393
|
-
// create new directory structure
|
|
394
316
|
const mkdirPromises = [];
|
|
395
317
|
for (const [, info] of distDirInfo) {
|
|
396
318
|
for (const file of info.files) {
|
|
@@ -401,11 +323,9 @@ async function flatten(pkg, flatten, logger) {
|
|
|
401
323
|
|
|
402
324
|
await Promise.all(mkdirPromises);
|
|
403
325
|
|
|
404
|
-
// move files to root dir (rename)
|
|
405
326
|
const renamePromises = [];
|
|
406
327
|
const newFiles = [];
|
|
407
328
|
|
|
408
|
-
/** @type {Map<string, string>} maps new path -> old path */
|
|
409
329
|
const movedFiles = new Map();
|
|
410
330
|
|
|
411
331
|
for (const [, info] of distDirInfo) {
|
|
@@ -419,12 +339,8 @@ async function flatten(pkg, flatten, logger) {
|
|
|
419
339
|
|
|
420
340
|
await Promise.all(renamePromises);
|
|
421
341
|
|
|
422
|
-
// adjust sourcemap paths for explicit flatten only
|
|
423
|
-
// (automatic flatten is safe because the common prefix is derived from package.json references)
|
|
424
342
|
if (typeof flatten === 'string') {
|
|
425
|
-
|
|
426
|
-
// so we can fix sources that point to files which themselves moved
|
|
427
|
-
/** @type {Map<string, string>} */
|
|
343
|
+
|
|
428
344
|
const oldToNew = new Map();
|
|
429
345
|
for (const [newPath, oldPath] of movedFiles) {
|
|
430
346
|
oldToNew.set(path.normalize(oldPath), newPath);
|
|
@@ -439,8 +355,6 @@ async function flatten(pkg, flatten, logger) {
|
|
|
439
355
|
}
|
|
440
356
|
}
|
|
441
357
|
|
|
442
|
-
// clean up empty source directories
|
|
443
|
-
/** @type {string[]} */
|
|
444
358
|
const cleanedDirs = [];
|
|
445
359
|
for (const [, info] of distDirInfo) {
|
|
446
360
|
let cleanedDir = info.relativeDistDir;
|
|
@@ -457,7 +371,6 @@ async function flatten(pkg, flatten, logger) {
|
|
|
457
371
|
|
|
458
372
|
const allReferencesSet = new Set(allReferences);
|
|
459
373
|
|
|
460
|
-
// update package.json - replace each distDir prefix in references
|
|
461
374
|
const stringsToReplace = distDirs.map(d => `${d}/`);
|
|
462
375
|
const pkgClone = cloneAndUpdate(pkg, value => {
|
|
463
376
|
if (!allReferencesSet.has(value)) {
|
|
@@ -472,7 +385,6 @@ async function flatten(pkg, flatten, logger) {
|
|
|
472
385
|
});
|
|
473
386
|
Object.assign(pkg, pkgClone);
|
|
474
387
|
|
|
475
|
-
// update files
|
|
476
388
|
let files = pkg.files;
|
|
477
389
|
if (files) {
|
|
478
390
|
files = files.filter(file => {
|
|
@@ -483,7 +395,6 @@ async function flatten(pkg, flatten, logger) {
|
|
|
483
395
|
pkg.files = [...files];
|
|
484
396
|
}
|
|
485
397
|
|
|
486
|
-
// remove extra directories with package.json
|
|
487
398
|
const exports = pkg.exports ? Object.keys(pkg.exports) : [];
|
|
488
399
|
for (const key of exports) {
|
|
489
400
|
if (key === '.') {
|
|
@@ -493,7 +404,7 @@ async function flatten(pkg, flatten, logger) {
|
|
|
493
404
|
if (isDir) {
|
|
494
405
|
const pkgPath = path.join(key, 'package.json');
|
|
495
406
|
const pkgExists = await isExists(pkgPath);
|
|
496
|
-
|
|
407
|
+
|
|
497
408
|
const files = await readdir(key);
|
|
498
409
|
if (files.length === 1 && pkgExists) {
|
|
499
410
|
await rm(key, { recursive: true, force: true });
|
|
@@ -502,25 +413,15 @@ async function flatten(pkg, flatten, logger) {
|
|
|
502
413
|
}
|
|
503
414
|
}
|
|
504
415
|
|
|
505
|
-
/**
|
|
506
|
-
* @param {string} file
|
|
507
|
-
* @returns {string}
|
|
508
|
-
*/
|
|
509
416
|
function normalizePath(file) {
|
|
510
417
|
let fileNormalized = path.normalize(file);
|
|
511
418
|
if (fileNormalized.endsWith('/') || fileNormalized.endsWith('\\')) {
|
|
512
|
-
|
|
419
|
+
|
|
513
420
|
fileNormalized = fileNormalized.slice(0, -1);
|
|
514
421
|
}
|
|
515
422
|
return fileNormalized;
|
|
516
423
|
}
|
|
517
424
|
|
|
518
|
-
/**
|
|
519
|
-
* Deep clones an object/array and updates all string values using the updater function
|
|
520
|
-
* @param {unknown} pkg
|
|
521
|
-
* @param {(value: string) => string} updater
|
|
522
|
-
* @returns {unknown}
|
|
523
|
-
*/
|
|
524
425
|
function cloneAndUpdate(pkg, updater) {
|
|
525
426
|
if (typeof pkg === 'string') {
|
|
526
427
|
return updater(pkg);
|
|
@@ -529,24 +430,16 @@ function cloneAndUpdate(pkg, updater) {
|
|
|
529
430
|
return pkg.map(value => cloneAndUpdate(value, updater));
|
|
530
431
|
}
|
|
531
432
|
if (typeof pkg === 'object' && pkg !== null) {
|
|
532
|
-
|
|
433
|
+
|
|
533
434
|
const clone = {};
|
|
534
435
|
for (const key of Object.keys(pkg)) {
|
|
535
|
-
clone[key] = cloneAndUpdate(
|
|
436
|
+
clone[key] = cloneAndUpdate( (pkg)[key], updater);
|
|
536
437
|
}
|
|
537
438
|
return clone;
|
|
538
439
|
}
|
|
539
440
|
return pkg;
|
|
540
441
|
}
|
|
541
442
|
|
|
542
|
-
/**
|
|
543
|
-
* Adjusts the `sources` (and `sourceRoot`) in a v3 sourcemap file after it has been moved.
|
|
544
|
-
* Resolves each source against the old location, then makes it relative to the new location.
|
|
545
|
-
* If a source target was itself moved during flatten, the new location is used instead.
|
|
546
|
-
* @param {string} newMapPath - The new path of the .map file (relative to project root).
|
|
547
|
-
* @param {string} oldMapPath - The old path of the .map file (relative to project root).
|
|
548
|
-
* @param {Map<string, string>} oldToNew - Map from normalized old file paths to their new paths.
|
|
549
|
-
*/
|
|
550
443
|
async function adjustSourcemapPaths(newMapPath, oldMapPath, oldToNew) {
|
|
551
444
|
const content = await readFile(newMapPath, 'utf8');
|
|
552
445
|
|
|
@@ -554,7 +447,7 @@ async function adjustSourcemapPaths(newMapPath, oldMapPath, oldToNew) {
|
|
|
554
447
|
try {
|
|
555
448
|
map = JSON.parse(content);
|
|
556
449
|
} catch {
|
|
557
|
-
return;
|
|
450
|
+
return;
|
|
558
451
|
}
|
|
559
452
|
|
|
560
453
|
if (map.version !== 3 || !Array.isArray(map.sources)) {
|
|
@@ -565,18 +458,17 @@ async function adjustSourcemapPaths(newMapPath, oldMapPath, oldToNew) {
|
|
|
565
458
|
const newDir = path.dirname(newMapPath) || '.';
|
|
566
459
|
const sourceRoot = map.sourceRoot || '';
|
|
567
460
|
|
|
568
|
-
map.sources = map.sources.map((
|
|
569
|
-
|
|
461
|
+
map.sources = map.sources.map(( source) => {
|
|
462
|
+
|
|
570
463
|
const resolved = path.normalize(path.join(oldDir, sourceRoot, source));
|
|
571
|
-
|
|
464
|
+
|
|
572
465
|
const effective = oldToNew.get(resolved) ?? resolved;
|
|
573
|
-
|
|
466
|
+
|
|
574
467
|
const newRelative = path.relative(newDir, effective);
|
|
575
|
-
|
|
468
|
+
|
|
576
469
|
return newRelative.split(path.sep).join('/');
|
|
577
470
|
});
|
|
578
471
|
|
|
579
|
-
// sourceRoot has been incorporated into the individual source paths
|
|
580
472
|
if (map.sourceRoot !== undefined) {
|
|
581
473
|
delete map.sourceRoot;
|
|
582
474
|
}
|
|
@@ -584,47 +476,26 @@ async function adjustSourcemapPaths(newMapPath, oldMapPath, oldToNew) {
|
|
|
584
476
|
await writeFile(newMapPath, `${JSON.stringify(map, null, 2)}\n`, 'utf8');
|
|
585
477
|
}
|
|
586
478
|
|
|
587
|
-
/**
|
|
588
|
-
* @param {string} parent
|
|
589
|
-
* @param {string} child
|
|
590
|
-
* @returns {boolean}
|
|
591
|
-
*/
|
|
592
479
|
function isSubDirectory(parent, child) {
|
|
593
480
|
const rel = path.relative(parent, child);
|
|
594
481
|
return rel !== '' && !rel.startsWith('..');
|
|
595
482
|
}
|
|
596
483
|
|
|
597
|
-
/**
|
|
598
|
-
* @param {string} dir
|
|
599
|
-
* @returns {Promise<boolean>}
|
|
600
|
-
*/
|
|
601
484
|
async function isEmptyDir(dir) {
|
|
602
485
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
603
486
|
return entries.filter(entry => !entry.isDirectory()).length === 0;
|
|
604
487
|
}
|
|
605
488
|
|
|
606
|
-
/**
|
|
607
|
-
* @param {string} file
|
|
608
|
-
* @returns {Promise<boolean>}
|
|
609
|
-
*/
|
|
610
489
|
async function isDirectory(file) {
|
|
611
490
|
const fileStat = await stat(file);
|
|
612
491
|
return fileStat.isDirectory();
|
|
613
492
|
}
|
|
614
493
|
|
|
615
|
-
/**
|
|
616
|
-
* @param {string} dir
|
|
617
|
-
* @param {Array<string>} [ignoreDirs=[]]
|
|
618
|
-
* @returns {Promise<Array<string>>}
|
|
619
|
-
*/
|
|
620
494
|
async function walkDir(dir, ignoreDirs = []) {
|
|
621
495
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
622
|
-
|
|
623
|
-
* @type {string[]}
|
|
624
|
-
*/
|
|
496
|
+
|
|
625
497
|
const files = [];
|
|
626
498
|
|
|
627
|
-
// Process files first
|
|
628
499
|
for (const entry of entries) {
|
|
629
500
|
if (!entry.isDirectory()) {
|
|
630
501
|
const childPath = path.join(entry.parentPath, entry.name);
|
|
@@ -632,7 +503,6 @@ async function walkDir(dir, ignoreDirs = []) {
|
|
|
632
503
|
}
|
|
633
504
|
}
|
|
634
505
|
|
|
635
|
-
// Then process directories
|
|
636
506
|
for (const entry of entries) {
|
|
637
507
|
if (entry.isDirectory()) {
|
|
638
508
|
const childPath = path.join(entry.parentPath, entry.name);
|
|
@@ -649,9 +519,6 @@ async function walkDir(dir, ignoreDirs = []) {
|
|
|
649
519
|
return files;
|
|
650
520
|
}
|
|
651
521
|
|
|
652
|
-
/**
|
|
653
|
-
* @param {string} file
|
|
654
|
-
*/
|
|
655
522
|
async function isExists(file) {
|
|
656
523
|
try {
|
|
657
524
|
await access(file);
|
|
@@ -664,12 +531,6 @@ async function isExists(file) {
|
|
|
664
531
|
return file;
|
|
665
532
|
}
|
|
666
533
|
|
|
667
|
-
/**
|
|
668
|
-
* Returns the list of files always included by npm for a given package.
|
|
669
|
-
* This includes `package.json`, the `main` entry, and all `bin` entries.
|
|
670
|
-
* @param {PackageJson} pkg
|
|
671
|
-
* @returns {string[]}
|
|
672
|
-
*/
|
|
673
534
|
function getAlwaysIncludedFiles(pkg) {
|
|
674
535
|
const files = [...alwaysIncludedExact];
|
|
675
536
|
if (pkg.main && typeof pkg.main === 'string') {
|
|
@@ -686,11 +547,6 @@ function getAlwaysIncludedFiles(pkg) {
|
|
|
686
547
|
return files;
|
|
687
548
|
}
|
|
688
549
|
|
|
689
|
-
/**
|
|
690
|
-
* Checks whether a file or directory name matches the always-ignored patterns.
|
|
691
|
-
* @param {string} basename - The basename of the file or directory.
|
|
692
|
-
* @returns {boolean}
|
|
693
|
-
*/
|
|
694
550
|
function isAlwaysIgnored(basename) {
|
|
695
551
|
if (alwaysIgnored.includes(basename)) {
|
|
696
552
|
return true;
|
|
@@ -698,10 +554,6 @@ function isAlwaysIgnored(basename) {
|
|
|
698
554
|
return alwaysIgnoredPatterns.some(pattern => pattern.test(basename));
|
|
699
555
|
}
|
|
700
556
|
|
|
701
|
-
/**
|
|
702
|
-
* Recursively removes junk files (always-ignored by npm) from a directory tree.
|
|
703
|
-
* @param {string} dir
|
|
704
|
-
*/
|
|
705
557
|
async function removeJunkFiles(dir) {
|
|
706
558
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
707
559
|
for (const entry of entries) {
|
|
@@ -717,11 +569,6 @@ async function removeJunkFiles(dir) {
|
|
|
717
569
|
}
|
|
718
570
|
}
|
|
719
571
|
|
|
720
|
-
/**
|
|
721
|
-
* Checks whether a root-level file is always included by npm (case-insensitive basename match).
|
|
722
|
-
* @param {string} file - The file path relative to the package root.
|
|
723
|
-
* @returns {boolean}
|
|
724
|
-
*/
|
|
725
572
|
function isAlwaysIncludedByBasename(file) {
|
|
726
573
|
const dir = path.dirname(file);
|
|
727
574
|
if (dir !== '' && dir !== '.') {
|
|
@@ -731,17 +578,11 @@ function isAlwaysIncludedByBasename(file) {
|
|
|
731
578
|
return alwaysIncludedBasenames.includes(basenameWithoutExtension);
|
|
732
579
|
}
|
|
733
580
|
|
|
734
|
-
/**
|
|
735
|
-
* Removes files from the working directory that are not included in the `files` array
|
|
736
|
-
* or the always-included list, then drops the `files` array from package.json.
|
|
737
|
-
* @param {PackageJson} pkg
|
|
738
|
-
* @param {Logger} logger
|
|
739
|
-
*/
|
|
740
581
|
async function cleanupDir(pkg, logger) {
|
|
741
582
|
logger.update('cleaning up files...');
|
|
742
583
|
|
|
743
584
|
const alwaysIncludedFiles = getAlwaysIncludedFiles(pkg);
|
|
744
|
-
const filesEntries =
|
|
585
|
+
const filesEntries = (pkg.files).map(normalizePath);
|
|
745
586
|
|
|
746
587
|
const entries = await readdir('.');
|
|
747
588
|
|
|
@@ -752,42 +593,30 @@ async function cleanupDir(pkg, logger) {
|
|
|
752
593
|
|
|
753
594
|
const normalized = normalizePath(entry);
|
|
754
595
|
|
|
755
|
-
// check if matched by files entries (exact or parent directory)
|
|
756
596
|
if (filesEntries.some(f => normalized === f || normalized.startsWith(`${f}/`))) {
|
|
757
597
|
continue;
|
|
758
598
|
}
|
|
759
599
|
|
|
760
|
-
// check if any files entry is under this directory
|
|
761
600
|
if (filesEntries.some(f => f.startsWith(`${normalized}/`))) {
|
|
762
|
-
|
|
601
|
+
|
|
763
602
|
await cleanupSubDir(normalized, filesEntries, alwaysIncludedFiles);
|
|
764
603
|
continue;
|
|
765
604
|
}
|
|
766
605
|
|
|
767
|
-
// check if always-included by exact path
|
|
768
606
|
if (alwaysIncludedFiles.includes(normalized)) {
|
|
769
607
|
continue;
|
|
770
608
|
}
|
|
771
609
|
|
|
772
|
-
// check if always-included by basename (root level)
|
|
773
610
|
if (isAlwaysIncludedByBasename(normalized)) {
|
|
774
611
|
continue;
|
|
775
612
|
}
|
|
776
613
|
|
|
777
|
-
// not matched - remove
|
|
778
614
|
await rm(entry, { recursive: true, force: true });
|
|
779
615
|
}
|
|
780
616
|
|
|
781
617
|
pkg.files = undefined;
|
|
782
618
|
}
|
|
783
619
|
|
|
784
|
-
/**
|
|
785
|
-
* Recursively cleans up a subdirectory, keeping only files matched by the files entries
|
|
786
|
-
* or always-included files.
|
|
787
|
-
* @param {string} dir
|
|
788
|
-
* @param {string[]} filesEntries
|
|
789
|
-
* @param {string[]} alwaysIncludedFiles
|
|
790
|
-
*/
|
|
791
620
|
async function cleanupSubDir(dir, filesEntries, alwaysIncludedFiles) {
|
|
792
621
|
const entries = await readdir(dir);
|
|
793
622
|
|
|
@@ -800,27 +629,22 @@ async function cleanupSubDir(dir, filesEntries, alwaysIncludedFiles) {
|
|
|
800
629
|
|
|
801
630
|
const normalized = normalizePath(fullPath);
|
|
802
631
|
|
|
803
|
-
// check if matched by files entries
|
|
804
632
|
if (filesEntries.some(f => normalized === f || normalized.startsWith(`${f}/`))) {
|
|
805
633
|
continue;
|
|
806
634
|
}
|
|
807
635
|
|
|
808
|
-
// check if any files entry is under this path
|
|
809
636
|
if (filesEntries.some(f => f.startsWith(`${normalized}/`))) {
|
|
810
637
|
await cleanupSubDir(normalized, filesEntries, alwaysIncludedFiles);
|
|
811
638
|
continue;
|
|
812
639
|
}
|
|
813
640
|
|
|
814
|
-
// check if always-included by exact path
|
|
815
641
|
if (alwaysIncludedFiles.includes(normalized)) {
|
|
816
642
|
continue;
|
|
817
643
|
}
|
|
818
644
|
|
|
819
|
-
// not matched - remove
|
|
820
645
|
await rm(fullPath, { recursive: true, force: true });
|
|
821
646
|
}
|
|
822
647
|
|
|
823
|
-
// remove the directory if it's now empty
|
|
824
648
|
const remaining = await readdir(dir);
|
|
825
649
|
if (remaining.length === 0) {
|
|
826
650
|
await rm(dir, { recursive: true, force: true });
|