pkgprn 0.5.1 → 0.5.3

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/prune.js CHANGED
@@ -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
- /** `*.orig` */
25
- { test: (/** @type {string} */ basename) => basename.endsWith('.orig') },
26
- /** `.*.swp` */
27
- { test: (/** @type {string} */ basename) => basename.startsWith('.') && basename.endsWith('.swp') },
28
- /** `._*` */
29
- { test: (/** @type {string} */ basename) => basename.startsWith('._') },
30
- /** `.wafpickle-N` */
31
- { test: (/** @type {string} */ basename) => /^\.wafpickle-\d+$/.test(basename) },
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[/** @type {'library'|'app'} */ (options.profile)];
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
- // find corresponding file
62
+
110
63
  const sourceFile = sourceMap.slice(0, -4);
111
- // load file
64
+
112
65
  const sourceFileContent = await readFile(sourceFile, 'utf8');
113
- // find sourceMappingURL
66
+
114
67
  const sourceMappingUrl = `\n//# sourceMappingURL=${path.basename(sourceMap)}`;
115
- // remove sourceMappingURL
68
+
116
69
  const newContent = sourceFileContent.replace(sourceMappingUrl, '');
117
- // write file
70
+
118
71
  await writeFile(sourceFile, newContent, 'utf8');
119
- // remove sourceMap
72
+
120
73
  await rm(sourceMap);
121
74
  }
122
75
  }
123
76
 
124
77
  if (options.stripComments) {
125
- const typesToStrip = parseCommentTypes(/** @type {string | true} */ (options.stripComments));
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
- // find out real content of the directory
151
+
203
152
  const realFiles = await readdir(dirname);
204
- // check if all files in the directory are in the filesInDir
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((/** @type {string} */ file) =>
216
- filesInDir.every((/** @type {string} */ fileInDir) => path.join(dirname, fileInDir) !== file)
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((/** @type {string} */ file) => {
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((/** @type {string} */ file) => {
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
- // split on comma to support multiple directories
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
- /** @type {string[]} */
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
- // build reverse map: normalized old path -> new path
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
- // ensure nothing else is in the directory
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
- // remove trailing slash
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
- /** @type {Record<string, unknown>} */
433
+
533
434
  const clone = {};
534
435
  for (const key of Object.keys(pkg)) {
535
- clone[key] = cloneAndUpdate(/** @type {Record<string, unknown>} */ (pkg)[key], updater);
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; // not valid JSON, skip
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((/** @type {string} */ source) => {
569
- // Resolve source against old map location (incorporating sourceRoot)
461
+ map.sources = map.sources.map(( source) => {
462
+
570
463
  const resolved = path.normalize(path.join(oldDir, sourceRoot, source));
571
- // If the resolved source was itself moved, use its new location
464
+
572
465
  const effective = oldToNew.get(resolved) ?? resolved;
573
- // Make relative to new map location
466
+
574
467
  const newRelative = path.relative(newDir, effective);
575
- // Sourcemaps always use forward slashes
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,33 @@ 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
- const fileStat = await stat(file);
612
- return fileStat.isDirectory();
490
+ try {
491
+ const fileStat = await stat(file);
492
+ return fileStat.isDirectory();
493
+ } catch (e) {
494
+ if (typeof e === 'object' && e != null && 'code' in e && e.code === 'ENOENT') {
495
+ return false;
496
+ }
497
+ throw e;
498
+ }
613
499
  }
614
500
 
615
- /**
616
- * @param {string} dir
617
- * @param {Array<string>} [ignoreDirs=[]]
618
- * @returns {Promise<Array<string>>}
619
- */
620
501
  async function walkDir(dir, ignoreDirs = []) {
621
502
  const entries = await readdir(dir, { withFileTypes: true });
622
- /**
623
- * @type {string[]}
624
- */
503
+
625
504
  const files = [];
626
505
 
627
- // Process files first
628
506
  for (const entry of entries) {
629
507
  if (!entry.isDirectory()) {
630
508
  const childPath = path.join(entry.parentPath, entry.name);
@@ -632,7 +510,6 @@ async function walkDir(dir, ignoreDirs = []) {
632
510
  }
633
511
  }
634
512
 
635
- // Then process directories
636
513
  for (const entry of entries) {
637
514
  if (entry.isDirectory()) {
638
515
  const childPath = path.join(entry.parentPath, entry.name);
@@ -649,9 +526,6 @@ async function walkDir(dir, ignoreDirs = []) {
649
526
  return files;
650
527
  }
651
528
 
652
- /**
653
- * @param {string} file
654
- */
655
529
  async function isExists(file) {
656
530
  try {
657
531
  await access(file);
@@ -664,12 +538,6 @@ async function isExists(file) {
664
538
  return file;
665
539
  }
666
540
 
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
541
  function getAlwaysIncludedFiles(pkg) {
674
542
  const files = [...alwaysIncludedExact];
675
543
  if (pkg.main && typeof pkg.main === 'string') {
@@ -686,11 +554,6 @@ function getAlwaysIncludedFiles(pkg) {
686
554
  return files;
687
555
  }
688
556
 
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
557
  function isAlwaysIgnored(basename) {
695
558
  if (alwaysIgnored.includes(basename)) {
696
559
  return true;
@@ -698,10 +561,6 @@ function isAlwaysIgnored(basename) {
698
561
  return alwaysIgnoredPatterns.some(pattern => pattern.test(basename));
699
562
  }
700
563
 
701
- /**
702
- * Recursively removes junk files (always-ignored by npm) from a directory tree.
703
- * @param {string} dir
704
- */
705
564
  async function removeJunkFiles(dir) {
706
565
  const entries = await readdir(dir, { withFileTypes: true });
707
566
  for (const entry of entries) {
@@ -717,11 +576,6 @@ async function removeJunkFiles(dir) {
717
576
  }
718
577
  }
719
578
 
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
579
  function isAlwaysIncludedByBasename(file) {
726
580
  const dir = path.dirname(file);
727
581
  if (dir !== '' && dir !== '.') {
@@ -731,17 +585,11 @@ function isAlwaysIncludedByBasename(file) {
731
585
  return alwaysIncludedBasenames.includes(basenameWithoutExtension);
732
586
  }
733
587
 
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
588
  async function cleanupDir(pkg, logger) {
741
589
  logger.update('cleaning up files...');
742
590
 
743
591
  const alwaysIncludedFiles = getAlwaysIncludedFiles(pkg);
744
- const filesEntries = /** @type {string[]} */ (pkg.files).map(normalizePath);
592
+ const filesEntries = (pkg.files).map(normalizePath);
745
593
 
746
594
  const entries = await readdir('.');
747
595
 
@@ -752,42 +600,30 @@ async function cleanupDir(pkg, logger) {
752
600
 
753
601
  const normalized = normalizePath(entry);
754
602
 
755
- // check if matched by files entries (exact or parent directory)
756
603
  if (filesEntries.some(f => normalized === f || normalized.startsWith(`${f}/`))) {
757
604
  continue;
758
605
  }
759
606
 
760
- // check if any files entry is under this directory
761
607
  if (filesEntries.some(f => f.startsWith(`${normalized}/`))) {
762
- // need to recurse into this directory for granular cleanup
608
+
763
609
  await cleanupSubDir(normalized, filesEntries, alwaysIncludedFiles);
764
610
  continue;
765
611
  }
766
612
 
767
- // check if always-included by exact path
768
613
  if (alwaysIncludedFiles.includes(normalized)) {
769
614
  continue;
770
615
  }
771
616
 
772
- // check if always-included by basename (root level)
773
617
  if (isAlwaysIncludedByBasename(normalized)) {
774
618
  continue;
775
619
  }
776
620
 
777
- // not matched - remove
778
621
  await rm(entry, { recursive: true, force: true });
779
622
  }
780
623
 
781
624
  pkg.files = undefined;
782
625
  }
783
626
 
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
627
  async function cleanupSubDir(dir, filesEntries, alwaysIncludedFiles) {
792
628
  const entries = await readdir(dir);
793
629
 
@@ -800,27 +636,22 @@ async function cleanupSubDir(dir, filesEntries, alwaysIncludedFiles) {
800
636
 
801
637
  const normalized = normalizePath(fullPath);
802
638
 
803
- // check if matched by files entries
804
639
  if (filesEntries.some(f => normalized === f || normalized.startsWith(`${f}/`))) {
805
640
  continue;
806
641
  }
807
642
 
808
- // check if any files entry is under this path
809
643
  if (filesEntries.some(f => f.startsWith(`${normalized}/`))) {
810
644
  await cleanupSubDir(normalized, filesEntries, alwaysIncludedFiles);
811
645
  continue;
812
646
  }
813
647
 
814
- // check if always-included by exact path
815
648
  if (alwaysIncludedFiles.includes(normalized)) {
816
649
  continue;
817
650
  }
818
651
 
819
- // not matched - remove
820
652
  await rm(fullPath, { recursive: true, force: true });
821
653
  }
822
654
 
823
- // remove the directory if it's now empty
824
655
  const remaining = await readdir(dir);
825
656
  if (remaining.length === 0) {
826
657
  await rm(dir, { recursive: true, force: true });