npmdata 0.10.3 → 0.12.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.
Files changed (153) hide show
  1. package/README.md +133 -58
  2. package/dist/cli/actions/check.d.ts +6 -0
  3. package/dist/cli/actions/check.d.ts.map +1 -0
  4. package/dist/cli/actions/check.js +57 -0
  5. package/dist/cli/actions/check.js.map +1 -0
  6. package/dist/cli/actions/extract.d.ts +7 -0
  7. package/dist/cli/actions/extract.d.ts.map +1 -0
  8. package/dist/cli/actions/extract.js +84 -0
  9. package/dist/cli/actions/extract.js.map +1 -0
  10. package/dist/cli/actions/init.d.ts +6 -0
  11. package/dist/cli/actions/init.d.ts.map +1 -0
  12. package/dist/cli/actions/init.js +36 -0
  13. package/dist/cli/actions/init.js.map +1 -0
  14. package/dist/cli/actions/list.d.ts +7 -0
  15. package/dist/cli/actions/list.d.ts.map +1 -0
  16. package/dist/cli/actions/list.js +42 -0
  17. package/dist/cli/actions/list.js.map +1 -0
  18. package/dist/cli/actions/purge.d.ts +6 -0
  19. package/dist/cli/actions/purge.d.ts.map +1 -0
  20. package/dist/cli/actions/purge.js +47 -0
  21. package/dist/cli/actions/purge.js.map +1 -0
  22. package/dist/cli/argv.d.ts +35 -0
  23. package/dist/cli/argv.d.ts.map +1 -0
  24. package/dist/cli/argv.js +125 -0
  25. package/dist/cli/argv.js.map +1 -0
  26. package/dist/cli/cli.d.ts +9 -0
  27. package/dist/cli/cli.d.ts.map +1 -0
  28. package/dist/cli/cli.js +69 -0
  29. package/dist/cli/cli.js.map +1 -0
  30. package/dist/cli/runner.d.ts +9 -0
  31. package/dist/cli/runner.d.ts.map +1 -0
  32. package/dist/cli/runner.js +106 -0
  33. package/dist/cli/runner.js.map +1 -0
  34. package/dist/cli/usage.d.ts +6 -0
  35. package/dist/cli/usage.d.ts.map +1 -0
  36. package/dist/cli/usage.js +126 -0
  37. package/dist/cli/usage.js.map +1 -0
  38. package/dist/fileset/check.d.ts +15 -0
  39. package/dist/fileset/check.d.ts.map +1 -0
  40. package/dist/fileset/check.js +68 -0
  41. package/dist/fileset/check.js.map +1 -0
  42. package/dist/fileset/constants.d.ts +13 -0
  43. package/dist/fileset/constants.d.ts.map +1 -0
  44. package/dist/fileset/constants.js +22 -0
  45. package/dist/fileset/constants.js.map +1 -0
  46. package/dist/fileset/diff.d.ts +16 -0
  47. package/dist/fileset/diff.d.ts.map +1 -0
  48. package/dist/fileset/diff.js +116 -0
  49. package/dist/fileset/diff.js.map +1 -0
  50. package/dist/fileset/execute.d.ts +29 -0
  51. package/dist/fileset/execute.d.ts.map +1 -0
  52. package/dist/fileset/execute.js +136 -0
  53. package/dist/fileset/execute.js.map +1 -0
  54. package/dist/fileset/gitignore.d.ts +16 -0
  55. package/dist/fileset/gitignore.d.ts.map +1 -0
  56. package/dist/fileset/gitignore.js +82 -0
  57. package/dist/fileset/gitignore.js.map +1 -0
  58. package/dist/fileset/index.d.ts +5 -0
  59. package/dist/fileset/index.d.ts.map +1 -0
  60. package/dist/fileset/index.js +21 -0
  61. package/dist/fileset/index.js.map +1 -0
  62. package/dist/fileset/list.d.ts +6 -0
  63. package/dist/fileset/list.d.ts.map +1 -0
  64. package/dist/fileset/list.js +11 -0
  65. package/dist/fileset/list.js.map +1 -0
  66. package/dist/fileset/markers.d.ts +22 -0
  67. package/dist/fileset/markers.d.ts.map +1 -0
  68. package/dist/fileset/markers.js +68 -0
  69. package/dist/fileset/markers.js.map +1 -0
  70. package/dist/fileset/package-files.d.ts +14 -0
  71. package/dist/fileset/package-files.d.ts.map +1 -0
  72. package/dist/fileset/package-files.js +81 -0
  73. package/dist/fileset/package-files.js.map +1 -0
  74. package/dist/fileset/purge.d.ts +12 -0
  75. package/dist/fileset/purge.d.ts.map +1 -0
  76. package/dist/fileset/purge.js +95 -0
  77. package/dist/fileset/purge.js.map +1 -0
  78. package/dist/fileset/test-utils.d.ts +12 -0
  79. package/dist/fileset/test-utils.d.ts.map +1 -0
  80. package/dist/fileset/test-utils.js +65 -0
  81. package/dist/fileset/test-utils.js.map +1 -0
  82. package/dist/index.d.ts +10 -7
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +12 -17
  85. package/dist/index.js.map +1 -1
  86. package/dist/main.js +9 -16
  87. package/dist/main.js.map +1 -1
  88. package/dist/npmdata-0.0.1.tgz +0 -0
  89. package/dist/package/action-check.d.ts +20 -0
  90. package/dist/package/action-check.d.ts.map +1 -0
  91. package/dist/package/action-check.js +61 -0
  92. package/dist/package/action-check.js.map +1 -0
  93. package/dist/package/action-extract.d.ts +21 -0
  94. package/dist/package/action-extract.d.ts.map +1 -0
  95. package/dist/package/action-extract.js +186 -0
  96. package/dist/package/action-extract.js.map +1 -0
  97. package/dist/package/action-init.d.ts +13 -0
  98. package/dist/package/action-init.d.ts.map +1 -0
  99. package/dist/package/action-init.js +77 -0
  100. package/dist/package/action-init.js.map +1 -0
  101. package/dist/package/action-list.d.ts +14 -0
  102. package/dist/package/action-list.d.ts.map +1 -0
  103. package/dist/package/action-list.js +46 -0
  104. package/dist/package/action-list.js.map +1 -0
  105. package/dist/package/action-purge.d.ts +21 -0
  106. package/dist/package/action-purge.d.ts.map +1 -0
  107. package/dist/package/action-purge.js +60 -0
  108. package/dist/package/action-purge.js.map +1 -0
  109. package/dist/package/config-merge.d.ts +18 -0
  110. package/dist/package/config-merge.d.ts.map +1 -0
  111. package/dist/package/config-merge.js +48 -0
  112. package/dist/package/config-merge.js.map +1 -0
  113. package/dist/package/config.d.ts +13 -0
  114. package/dist/package/config.d.ts.map +1 -0
  115. package/dist/package/config.js +29 -0
  116. package/dist/package/config.js.map +1 -0
  117. package/dist/package/content-replacements.d.ts +21 -0
  118. package/dist/package/content-replacements.d.ts.map +1 -0
  119. package/dist/package/content-replacements.js +96 -0
  120. package/dist/package/content-replacements.js.map +1 -0
  121. package/dist/package/index.d.ts +10 -0
  122. package/dist/package/index.d.ts.map +1 -0
  123. package/dist/package/index.js +16 -0
  124. package/dist/package/index.js.map +1 -0
  125. package/dist/package/symlinks.d.ts +17 -0
  126. package/dist/package/symlinks.d.ts.map +1 -0
  127. package/dist/package/symlinks.js +125 -0
  128. package/dist/package/symlinks.js.map +1 -0
  129. package/dist/types.d.ts +184 -270
  130. package/dist/types.d.ts.map +1 -1
  131. package/dist/types.js +0 -12
  132. package/dist/types.js.map +1 -1
  133. package/dist/utils.d.ts +25 -55
  134. package/dist/utils.d.ts.map +1 -1
  135. package/dist/utils.js +82 -181
  136. package/dist/utils.js.map +1 -1
  137. package/package.json +5 -2
  138. package/dist/cli.d.ts +0 -6
  139. package/dist/cli.d.ts.map +0 -1
  140. package/dist/cli.js +0 -514
  141. package/dist/cli.js.map +0 -1
  142. package/dist/consumer.d.ts +0 -74
  143. package/dist/consumer.d.ts.map +0 -1
  144. package/dist/consumer.js +0 -820
  145. package/dist/consumer.js.map +0 -1
  146. package/dist/publisher.d.ts +0 -38
  147. package/dist/publisher.d.ts.map +0 -1
  148. package/dist/publisher.js +0 -164
  149. package/dist/publisher.js.map +0 -1
  150. package/dist/runner.d.ts +0 -102
  151. package/dist/runner.d.ts.map +0 -1
  152. package/dist/runner.js +0 -748
  153. package/dist/runner.js.map +0 -1
package/dist/runner.js DELETED
@@ -1,748 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.buildCheckCommand = buildCheckCommand;
7
- exports.buildListCommand = buildListCommand;
8
- exports.buildPurgeCommand = buildPurgeCommand;
9
- exports.collectAllTags = collectAllTags;
10
- exports.printHelp = printHelp;
11
- exports.parseOutputFromArgv = parseOutputFromArgv;
12
- exports.parseDryRunFromArgv = parseDryRunFromArgv;
13
- exports.parseSilentFromArgv = parseSilentFromArgv;
14
- exports.parseVerboseFromArgv = parseVerboseFromArgv;
15
- exports.parseNoGitignoreFromArgv = parseNoGitignoreFromArgv;
16
- exports.parseUnmanagedFromArgv = parseUnmanagedFromArgv;
17
- exports.parseTagsFromArgv = parseTagsFromArgv;
18
- exports.filterEntriesByTags = filterEntriesByTags;
19
- exports.applySymlinks = applySymlinks;
20
- exports.applyContentReplacements = applyContentReplacements;
21
- exports.checkContentReplacements = checkContentReplacements;
22
- exports.run = run;
23
- /* eslint-disable no-restricted-syntax */
24
- const node_child_process_1 = require("node:child_process");
25
- const node_fs_1 = __importDefault(require("node:fs"));
26
- const node_path_1 = __importDefault(require("node:path"));
27
- const minimatch_1 = require("minimatch");
28
- const utils_1 = require("./utils");
29
- /**
30
- * Extract just the package name (without version specifier) from a package spec string.
31
- * Delegates to the shared parsePackageSpec utility.
32
- */
33
- function parseEntryPackageName(spec) {
34
- const { name } = (0, utils_1.parsePackageSpec)(spec);
35
- return { name };
36
- }
37
- function buildExtractCommand(cliPath, entry, cwd = process.cwd()) {
38
- const outputFlag = ` --output "${node_path_1.default.resolve(cwd, entry.outputDir)}"`;
39
- const forceFlag = entry.force ? ' --force' : '';
40
- const keepExistingFlag = entry.keepExisting ? ' --keep-existing' : '';
41
- const gitignoreFlag = entry.gitignore === false ? ' --no-gitignore' : '';
42
- const unmanagedFlag = entry.unmanaged ? ' --unmanaged' : '';
43
- const silentFlag = entry.silent ? ' --silent' : '';
44
- const verboseFlag = entry.verbose ? ' --verbose' : '';
45
- const dryRunFlag = entry.dryRun ? ' --dry-run' : '';
46
- const upgradeFlag = entry.upgrade ? ' --upgrade' : '';
47
- const filesFlag = entry.files && entry.files.length > 0 ? ` --files "${entry.files.join(',')}"` : '';
48
- const contentRegexFlag = entry.contentRegexes && entry.contentRegexes.length > 0
49
- ? ` --content-regex "${entry.contentRegexes.join(',')}"`
50
- : '';
51
- return `node "${cliPath}" extract --packages "${entry.package}"${outputFlag}${forceFlag}${keepExistingFlag}${gitignoreFlag}${unmanagedFlag}${silentFlag}${verboseFlag}${dryRunFlag}${upgradeFlag}${filesFlag}${contentRegexFlag}`;
52
- }
53
- /**
54
- * Build a CLI command string that checks whether local files are in sync with the entry's package.
55
- */
56
- function buildCheckCommand(cliPath, entry, cwd = process.cwd()) {
57
- const outputFlag = ` --output "${node_path_1.default.resolve(cwd, entry.outputDir)}"`;
58
- const verboseFlag = entry.verbose ? ' --verbose' : '';
59
- const filesFlag = entry.files && entry.files.length > 0 ? ` --files "${entry.files.join(',')}"` : '';
60
- const contentRegexFlag = entry.contentRegexes && entry.contentRegexes.length > 0
61
- ? ` --content-regex "${entry.contentRegexes.join(',')}"`
62
- : '';
63
- return `node "${cliPath}" check --packages "${entry.package}"${outputFlag}${verboseFlag}${filesFlag}${contentRegexFlag}`;
64
- }
65
- /**
66
- * Build a CLI command string that lists all managed files in the given output directory.
67
- */
68
- function buildListCommand(cliPath, outputDir, cwd = process.cwd(), verbose = false) {
69
- const resolvedOutput = node_path_1.default.resolve(cwd, outputDir);
70
- const verboseFlag = verbose ? ' --verbose' : '';
71
- return `node "${cliPath}" list --output "${resolvedOutput}"${verboseFlag}`;
72
- }
73
- /**
74
- * Build a CLI command string that purges (removes) all managed files for the entry's package
75
- * from its output directory. No package installation is required.
76
- */
77
- function buildPurgeCommand(cliPath, entry, cwd = process.cwd()) {
78
- const { name } = parseEntryPackageName(entry.package);
79
- const outputFlag = ` --output "${node_path_1.default.resolve(cwd, entry.outputDir)}"`;
80
- // Propagate silent/dry-run/verbose settings from the entry if present.
81
- const silentFlag = entry.silent ? ' --silent' : '';
82
- const verboseFlag = entry.verbose ? ' --verbose' : '';
83
- const dryRunFlag = entry.dryRun ? ' --dry-run' : '';
84
- return `node "${cliPath}" purge --packages "${name}"${outputFlag}${silentFlag}${verboseFlag}${dryRunFlag}`;
85
- }
86
- /**
87
- * Collects all unique tags that appear across the given npmdata entries, sorted alphabetically.
88
- */
89
- function collectAllTags(entries) {
90
- const tagSet = new Set();
91
- for (const entry of entries) {
92
- if (entry.tags) {
93
- for (const tag of entry.tags) {
94
- tagSet.add(tag);
95
- }
96
- }
97
- }
98
- return Array.from(tagSet).sort();
99
- }
100
- /**
101
- * Prints a help message to stdout, listing the extract action, all options, and available tags.
102
- */
103
- function printHelp(packageName, availableTags) {
104
- const tagsLine = availableTags.length > 0 ? availableTags.join(', ') : '(none defined in package.json)';
105
- const exampleTag = availableTags.length > 0 ? availableTags[0] : 'my-tag';
106
- process.stdout.write([
107
- `Usage: ${packageName} <action> [options]`,
108
- '',
109
- 'Actions:',
110
- ' extract Extract files from the source package(s) defined in package.json',
111
- ' check Verify local files are in sync with the source package(s)',
112
- ' list List all files managed by npmdata in the output directories',
113
- ' purge Remove all managed files previously extracted',
114
- '',
115
- 'Options:',
116
- ' --help Show this help message',
117
- ' --output, -o <dir> Base directory for resolving all outputDir paths (default: cwd)',
118
- ' --dry-run Simulate changes without writing or deleting any files',
119
- ' --tags <tag1,tag2> Limit to entries whose tags overlap (comma-separated)',
120
- ' --no-gitignore Disable .gitignore management for every entry (overrides per-entry setting)',
121
- ' --unmanaged Run every entry in unmanaged mode (overrides per-entry setting)',
122
- ' --verbose, -v Print detailed progress information for each step',
123
- '',
124
- `Available tags: ${tagsLine}`,
125
- '',
126
- 'Examples:',
127
- ` ${packageName} extract`,
128
- ' Extract files for all entries defined in package.json',
129
- '',
130
- ` ${packageName} extract --output <dir>`,
131
- ' Extract files, resolving all outputDir paths relative to <dir> instead of cwd',
132
- '',
133
- ` ${packageName} extract --dry-run`,
134
- ' Preview what would be extracted without writing any files',
135
- '',
136
- ` ${packageName} extract --tags ${exampleTag}`,
137
- ` Extract files only for entries tagged "${exampleTag}"`,
138
- '',
139
- ` ${packageName} check`,
140
- ' Check if local files are in sync with the source packages',
141
- '',
142
- ` ${packageName} list`,
143
- ' List all files managed by npmdata in the output directories',
144
- '',
145
- ` ${packageName} purge`,
146
- ' Remove all managed files from the output directories',
147
- '',
148
- ].join('\n'));
149
- }
150
- /**
151
- * Parses --output (or -o) from an argv array and returns the path string.
152
- * Returns undefined when the flag is not present.
153
- */
154
- function parseOutputFromArgv(argv) {
155
- const idx = argv.findIndex((a) => a === '--output' || a === '-o');
156
- if (idx === -1 || idx + 1 >= argv.length) {
157
- // eslint-disable-next-line no-undefined
158
- return undefined;
159
- }
160
- return argv[idx + 1];
161
- }
162
- /**
163
- * Returns true when --dry-run appears in the argv array.
164
- */
165
- function parseDryRunFromArgv(argv) {
166
- return argv.includes('--dry-run');
167
- }
168
- /**
169
- * Returns true when --silent appears in the argv array.
170
- */
171
- function parseSilentFromArgv(argv) {
172
- return argv.includes('--silent');
173
- }
174
- /**
175
- * Returns true when --verbose or -v appears in the argv array.
176
- */
177
- function parseVerboseFromArgv(argv) {
178
- return argv.includes('--verbose') || argv.includes('-v');
179
- }
180
- /**
181
- * Returns true when --no-gitignore appears in the argv array.
182
- * When true, overrides the gitignore setting of every entry to false.
183
- */
184
- function parseNoGitignoreFromArgv(argv) {
185
- return argv.includes('--no-gitignore');
186
- }
187
- /**
188
- * Returns true when --unmanaged appears in the argv array.
189
- * When true, overrides the unmanaged setting of every entry to true.
190
- */
191
- function parseUnmanagedFromArgv(argv) {
192
- return argv.includes('--unmanaged');
193
- }
194
- /**
195
- * Parses --tags from an argv array and returns the list of requested tags (split by comma).
196
- * Returns an empty array when --tags is not present.
197
- */
198
- function parseTagsFromArgv(argv) {
199
- const idx = argv.indexOf('--tags');
200
- if (idx === -1 || idx + 1 >= argv.length) {
201
- return [];
202
- }
203
- return argv[idx + 1]
204
- .split(',')
205
- .map((t) => t.trim())
206
- .filter(Boolean);
207
- }
208
- /**
209
- * Filter entries by requested tags. When no tags are requested all entries pass through.
210
- * When tags are requested only entries that share at least one tag with the requested list
211
- * are included.
212
- */
213
- function filterEntriesByTags(entries, requestedTags) {
214
- if (requestedTags.length === 0) {
215
- return entries;
216
- }
217
- return entries.filter((entry) => entry.tags && entry.tags.some((t) => requestedTags.includes(t)));
218
- }
219
- // ─── Managed path helpers ──────────────────────────────────────────────────────
220
- /**
221
- * From the flat list of managed file paths (relative to outputDir) recorded in
222
- * the .npmdata marker, derive every unique path that can be symlinked: each
223
- * file itself plus every intermediate ancestor directory.
224
- *
225
- * Example: 'skills/skill-a/README.md' yields
226
- * 'skills', 'skills/skill-a', 'skills/skill-a/README.md'
227
- */
228
- function managedPathsWithAncestors(managedFiles) {
229
- const paths = new Set();
230
- for (const mf of managedFiles) {
231
- // eslint-disable-next-line functional/immutable-data
232
- paths.add(mf.path);
233
- const parts = mf.path.split('/');
234
- // Add each ancestor directory by accumulating path segments.
235
- parts.slice(0, -1).reduce((prefix, seg) => {
236
- const ancestor = prefix ? `${prefix}/${seg}` : seg;
237
- // eslint-disable-next-line functional/immutable-data
238
- paths.add(ancestor);
239
- return ancestor;
240
- }, '');
241
- }
242
- return Array.from(paths);
243
- }
244
- /**
245
- * Read the .npmdata marker from outputDir and return managed file metadata.
246
- * Returns an empty array when the marker does not exist.
247
- */
248
- function readManagedFiles(outputDir) {
249
- const markerPath = node_path_1.default.join(outputDir, '.npmdata');
250
- return node_fs_1.default.existsSync(markerPath) ? (0, utils_1.readCsvMarker)(markerPath) : [];
251
- }
252
- /**
253
- * Collect all existing symlinks in `targetDir` whose resolved (or as-written)
254
- * link target starts with `outputDir`. Returns a map of basename → resolved
255
- * target path. Dead symlinks that still point into outputDir are included so
256
- * that they can be cleaned up.
257
- */
258
- function collectManagedSymlinks(targetDir, outputDir) {
259
- const owned = new Map();
260
- if (!node_fs_1.default.existsSync(targetDir))
261
- return owned;
262
- // Resolve outputDir through any intermediate symlinks (e.g. /var → /private/var on macOS)
263
- // so prefix comparisons work correctly on all platforms.
264
- // eslint-disable-next-line functional/no-let, functional/no-try-statements
265
- let resolvedOutputDir = outputDir;
266
- // eslint-disable-next-line functional/no-try-statements
267
- try {
268
- resolvedOutputDir = node_fs_1.default.realpathSync(outputDir);
269
- }
270
- catch {
271
- // If outputDir does not exist, fall back to the raw path.
272
- }
273
- const normalizedOutput = resolvedOutputDir.endsWith(node_path_1.default.sep)
274
- ? resolvedOutputDir
275
- : `${resolvedOutputDir}${node_path_1.default.sep}`;
276
- for (const name of node_fs_1.default.readdirSync(targetDir)) {
277
- const symlinkPath = node_path_1.default.join(targetDir, name);
278
- const lstat = node_fs_1.default.lstatSync(symlinkPath);
279
- if (lstat.isSymbolicLink()) {
280
- // Try to resolve (handles live symlinks).
281
- // eslint-disable-next-line functional/no-try-statements
282
- try {
283
- const resolved = node_fs_1.default.realpathSync(symlinkPath);
284
- if (resolved === resolvedOutputDir || resolved.startsWith(normalizedOutput)) {
285
- owned.set(name, resolved);
286
- }
287
- }
288
- catch {
289
- // Dead symlink – read the raw link target to see if it points into outputDir.
290
- const rawTarget = node_fs_1.default.readlinkSync(symlinkPath);
291
- const absTarget = node_path_1.default.resolve(targetDir, rawTarget);
292
- const resolvedAbsTarget = absTarget; // raw path is enough for dead-link check
293
- if (resolvedAbsTarget === outputDir ||
294
- resolvedAbsTarget.startsWith(`${outputDir}${node_path_1.default.sep}`)) {
295
- owned.set(name, absTarget);
296
- }
297
- }
298
- }
299
- }
300
- return owned;
301
- }
302
- /**
303
- * Determine the symlink action for a single target path.
304
- * Returns 'create' when the path does not exist, 'update' when an out-of-date
305
- * managed symlink exists, or 'skip' when nothing should be done.
306
- */
307
- function symlinkAction(symlinkPath, sourcePath, isManaged) {
308
- // eslint-disable-next-line functional/no-try-statements
309
- try {
310
- const lstat = node_fs_1.default.lstatSync(symlinkPath);
311
- if (!lstat.isSymbolicLink())
312
- return 'skip'; // Non-symlink – never clobber.
313
- if (!isManaged)
314
- return 'skip'; // Not managed by npmdata – leave alone.
315
- // Managed symlink: only recreate if the target has drifted.
316
- // eslint-disable-next-line functional/no-try-statements
317
- try {
318
- return node_fs_1.default.realpathSync(symlinkPath) === sourcePath ? 'skip' : 'update';
319
- }
320
- catch {
321
- return 'update'; // Dead link – recreate.
322
- }
323
- }
324
- catch {
325
- return 'create'; // Path does not exist.
326
- }
327
- }
328
- // ─── Post-extract operations ───────────────────────────────────────────────────
329
- /**
330
- * Apply the symlink configs from an extraction entry.
331
- *
332
- * For each config:
333
- * 1. Expands the `source` glob inside the resolved `outputDir`.
334
- * 2. Ensures the `target` directory exists.
335
- * 3. Removes stale symlinks from the target dir that previously pointed into
336
- * outputDir but are no longer matched by the current glob result.
337
- * 4. Creates (or updates) symlinks for every matched file/directory.
338
- *
339
- * Only symlinks whose targets live inside outputDir are managed; any other
340
- * symlinks in the target directory are left untouched.
341
- */
342
- function applySymlinks(entry, cwd = process.cwd()) {
343
- if (!entry.symlinks || entry.symlinks.length === 0)
344
- return;
345
- const outputDir = node_path_1.default.resolve(cwd, entry.outputDir);
346
- const allManagedPaths = managedPathsWithAncestors(readManagedFiles(outputDir));
347
- for (const cfg of entry.symlinks) {
348
- const targetDir = node_path_1.default.resolve(cwd, cfg.target);
349
- node_fs_1.default.mkdirSync(targetDir, { recursive: true });
350
- // Build desired symlink map from managed paths (files + ancestor dirs) matching the source pattern.
351
- const desired = new Map();
352
- for (const relPath of allManagedPaths) {
353
- if ((0, minimatch_1.minimatch)(relPath, cfg.source, { dot: true })) {
354
- const absMatch = node_path_1.default.join(outputDir, relPath);
355
- desired.set(node_path_1.default.basename(absMatch), absMatch);
356
- }
357
- }
358
- // Remove stale managed symlinks that are no longer in the desired set.
359
- const existing = collectManagedSymlinks(targetDir, outputDir);
360
- for (const [basename] of existing) {
361
- if (!desired.has(basename)) {
362
- const symlinkPath = node_path_1.default.join(targetDir, basename);
363
- node_fs_1.default.unlinkSync(symlinkPath);
364
- if (!entry.silent) {
365
- // eslint-disable-next-line no-console
366
- console.log(`D\t${node_path_1.default.relative(cwd, symlinkPath)}`);
367
- }
368
- }
369
- }
370
- // Create or update symlinks.
371
- for (const [basename, sourcePath] of desired) {
372
- const symlinkPath = node_path_1.default.join(targetDir, basename);
373
- const action = symlinkAction(symlinkPath, sourcePath, existing.has(basename));
374
- if (action === 'update') {
375
- node_fs_1.default.unlinkSync(symlinkPath);
376
- node_fs_1.default.symlinkSync(sourcePath, symlinkPath);
377
- if (!entry.silent) {
378
- // eslint-disable-next-line no-console
379
- console.log(`M\t${node_path_1.default.relative(cwd, symlinkPath)}`);
380
- }
381
- }
382
- else if (action === 'create') {
383
- node_fs_1.default.symlinkSync(sourcePath, symlinkPath);
384
- if (!entry.silent) {
385
- // eslint-disable-next-line no-console
386
- console.log(`A\t${node_path_1.default.relative(cwd, symlinkPath)}`);
387
- }
388
- }
389
- // 'skip' → do nothing
390
- }
391
- }
392
- }
393
- /**
394
- * Apply the content-replacement configs from an extraction entry.
395
- *
396
- * For each config:
397
- * 1. Expands the `files` glob inside `cwd`.
398
- * 2. Reads each matched file.
399
- * 3. Applies the regex replacement (global, multiline).
400
- * 4. Writes the file back only when the content changed.
401
- */
402
- function applyContentReplacements(entry, cwd = process.cwd()) {
403
- if (!entry.contentReplacements || entry.contentReplacements.length === 0)
404
- return;
405
- const outputDir = node_path_1.default.resolve(cwd, entry.outputDir);
406
- const managedFiles = readManagedFiles(outputDir);
407
- for (const cfg of entry.contentReplacements) {
408
- const regex = new RegExp(cfg.match, 'gm');
409
- for (const mf of managedFiles) {
410
- if ((0, minimatch_1.minimatch)(mf.path, cfg.files, { dot: true })) {
411
- const filePath = node_path_1.default.join(outputDir, mf.path);
412
- if (node_fs_1.default.existsSync(filePath)) {
413
- const original = node_fs_1.default.readFileSync(filePath, 'utf8');
414
- const updated = original.replace(regex, cfg.replace);
415
- if (updated !== original) {
416
- node_fs_1.default.writeFileSync(filePath, updated, 'utf8');
417
- }
418
- }
419
- }
420
- }
421
- }
422
- }
423
- /**
424
- * Check whether the content-replacement configs from an extraction entry are
425
- * currently in effect in the workspace.
426
- *
427
- * Returns a list of file paths where the replacement pattern still matches
428
- * (i.e. the replacement has not been applied or has drifted). An empty list
429
- * means everything is in sync.
430
- */
431
- function checkContentReplacements(entry, cwd = process.cwd()) {
432
- if (!entry.contentReplacements || entry.contentReplacements.length === 0)
433
- return [];
434
- const outputDir = node_path_1.default.resolve(cwd, entry.outputDir);
435
- const managedFiles = readManagedFiles(outputDir);
436
- const outOfSync = [];
437
- for (const cfg of entry.contentReplacements) {
438
- const regex = new RegExp(cfg.match, 'gm');
439
- for (const mf of managedFiles) {
440
- if ((0, minimatch_1.minimatch)(mf.path, cfg.files, { dot: true })) {
441
- const filePath = node_path_1.default.join(outputDir, mf.path);
442
- if (node_fs_1.default.existsSync(filePath)) {
443
- const content = node_fs_1.default.readFileSync(filePath, 'utf8');
444
- // A file is out of sync when applying the replacement would change it.
445
- const expected = content.replace(regex, cfg.replace);
446
- if (expected !== content) {
447
- // eslint-disable-next-line functional/immutable-data
448
- outOfSync.push(filePath);
449
- }
450
- }
451
- }
452
- }
453
- }
454
- return outOfSync;
455
- }
456
- // ─── Action handlers ───────────────────────────────────────────────────────────
457
- /**
458
- * Run a shell command, capturing its stdout while inheriting stderr.
459
- * The captured stdout is immediately written to process.stdout so the caller
460
- * sees it in real time (well, after the child exits). Returns the full
461
- * captured stdout string and the child's exit code. Non-zero exit codes do
462
- * NOT throw; callers are responsible for checking exitCode.
463
- */
464
- function runCommandCapture(command, cwd) {
465
- // eslint-disable-next-line functional/no-try-statements
466
- try {
467
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
468
- const stdout = (0, node_child_process_1.execSync)(command, {
469
- encoding: 'utf8',
470
- cwd,
471
- stdio: ['inherit', 'pipe', 'inherit'],
472
- }) ?? '';
473
- process.stdout.write(stdout);
474
- return { stdout, exitCode: 0 };
475
- }
476
- catch (error) {
477
- const err = error;
478
- const stdout = err.stdout ?? '';
479
- process.stdout.write(stdout);
480
- return { stdout, exitCode: err.status ?? 1 };
481
- }
482
- }
483
- // eslint-disable-next-line complexity
484
- function runExtract(entries, excludedEntries, cliPath, runCwd, dryRunFromArgv, silentFromArgv, verboseFromArgv, noGitignoreFromArgv, unmanagedFromArgv) {
485
- if (verboseFromArgv) {
486
- // eslint-disable-next-line no-console
487
- console.log(`[verbose] extract: processing ${entries.length} entr${entries.length === 1 ? 'y' : 'ies'} (cwd: ${runCwd})`);
488
- }
489
- // eslint-disable-next-line functional/no-let
490
- let totalAdded = 0;
491
- // eslint-disable-next-line functional/no-let
492
- let totalModified = 0;
493
- // eslint-disable-next-line functional/no-let
494
- let totalDeleted = 0;
495
- // eslint-disable-next-line functional/no-let
496
- let totalSkipped = 0;
497
- // eslint-disable-next-line functional/no-let
498
- let entryIndex = 0;
499
- for (const entry of entries) {
500
- const effectiveSilent = entry.silent || silentFromArgv;
501
- if (entryIndex > 0 && !effectiveSilent) {
502
- process.stdout.write('\n');
503
- }
504
- entryIndex += 1;
505
- const effectiveEntry = {
506
- ...entry,
507
- dryRun: entry.dryRun || dryRunFromArgv,
508
- silent: effectiveSilent,
509
- verbose: entry.verbose || verboseFromArgv,
510
- ...(noGitignoreFromArgv ? { gitignore: false } : {}),
511
- ...(unmanagedFromArgv ? { unmanaged: true } : {}),
512
- };
513
- if (verboseFromArgv) {
514
- // eslint-disable-next-line no-console
515
- console.log(`[verbose] extract: entry package=${entry.package} outputDir=${entry.outputDir}`);
516
- }
517
- node_fs_1.default.mkdirSync(node_path_1.default.resolve(runCwd, entry.outputDir), { recursive: true });
518
- const command = buildExtractCommand(cliPath, effectiveEntry, runCwd);
519
- if (verboseFromArgv) {
520
- // eslint-disable-next-line no-console
521
- console.log(`[verbose] extract: running command: ${command}`);
522
- }
523
- const { stdout: extractStdout, exitCode: extractExitCode } = runCommandCapture(command, runCwd);
524
- if (extractExitCode !== 0) {
525
- throw Object.assign(new Error('extract failed'), { status: extractExitCode });
526
- }
527
- const extractMatch = extractStdout.match(/Extraction complete:\s*(\d+) added,\s*(\d+) modified,\s*(\d+) deleted,\s*(\d+) skipped/);
528
- if (extractMatch) {
529
- totalAdded += Number.parseInt(extractMatch[1], 10);
530
- totalModified += Number.parseInt(extractMatch[2], 10);
531
- totalDeleted += Number.parseInt(extractMatch[3], 10);
532
- totalSkipped += Number.parseInt(extractMatch[4], 10);
533
- }
534
- if (!effectiveEntry.dryRun) {
535
- if (verboseFromArgv) {
536
- // eslint-disable-next-line no-console
537
- console.log(`[verbose] extract: applying symlinks for ${entry.package}`);
538
- }
539
- applySymlinks(effectiveEntry, runCwd);
540
- if (verboseFromArgv) {
541
- // eslint-disable-next-line no-console
542
- console.log(`[verbose] extract: applying content replacements for ${entry.package}`);
543
- }
544
- applyContentReplacements(entry, runCwd);
545
- }
546
- }
547
- // When a tag filter is active, purge managed files from excluded entries so that
548
- // the output directory contains only files from the currently active tag group.
549
- // Suppress the "Purging managed files..." banner for these implicit purges.
550
- for (const entry of excludedEntries) {
551
- if (verboseFromArgv) {
552
- // eslint-disable-next-line no-console
553
- console.log(`[verbose] extract: purging excluded entry ${entry.package} (tag filter active)`);
554
- }
555
- const effectiveEntry = {
556
- ...entry,
557
- dryRun: entry.dryRun || dryRunFromArgv,
558
- silent: true,
559
- };
560
- const command = buildPurgeCommand(cliPath, effectiveEntry, runCwd);
561
- (0, node_child_process_1.execSync)(command, { stdio: 'inherit', cwd: runCwd });
562
- }
563
- if (!silentFromArgv && entries.length > 1) {
564
- process.stdout.write(`\nTotal extracted: ${totalAdded} added, ${totalModified} modified, ${totalDeleted} deleted, ${totalSkipped} skipped${dryRunFromArgv ? ' (dry run)' : ''}\n`);
565
- }
566
- }
567
- function runCheck(entries, cliPath, runCwd, verboseFromArgv) {
568
- if (verboseFromArgv) {
569
- // eslint-disable-next-line no-console
570
- console.log(`[verbose] check: verifying ${entries.length} entr${entries.length === 1 ? 'y' : 'ies'} (cwd: ${runCwd})`);
571
- }
572
- // eslint-disable-next-line functional/no-let
573
- let outOfSyncFiles = [];
574
- // eslint-disable-next-line functional/no-let
575
- let checkIndex = 0;
576
- for (const entry of entries) {
577
- if (checkIndex > 0) {
578
- process.stdout.write('\n');
579
- }
580
- checkIndex += 1;
581
- if (verboseFromArgv) {
582
- // eslint-disable-next-line no-console
583
- console.log(`[verbose] check: checking package=${entry.package} outputDir=${entry.outputDir}`);
584
- }
585
- const effectiveEntry = {
586
- ...entry,
587
- verbose: entry.verbose || verboseFromArgv,
588
- };
589
- const command = buildCheckCommand(cliPath, effectiveEntry, runCwd);
590
- if (verboseFromArgv) {
591
- // eslint-disable-next-line no-console
592
- console.log(`[verbose] check: running command: ${command}`);
593
- }
594
- const { exitCode: checkExitCode } = runCommandCapture(command, runCwd);
595
- if (checkExitCode !== 0) {
596
- throw Object.assign(new Error('check failed'), { status: checkExitCode });
597
- }
598
- if (verboseFromArgv) {
599
- // eslint-disable-next-line no-console
600
- console.log(`[verbose] check: checking content replacements for ${entry.package}`);
601
- }
602
- const entryOutOfSync = checkContentReplacements(entry, runCwd);
603
- for (const f of entryOutOfSync) {
604
- process.stderr.write(`content-replacement out of sync: ${f}\n`);
605
- }
606
- // eslint-disable-next-line functional/immutable-data
607
- outOfSyncFiles = [...outOfSyncFiles, ...entryOutOfSync];
608
- }
609
- if (outOfSyncFiles.length > 0) {
610
- throw Object.assign(new Error('content-replacements out of sync'), { status: 1 });
611
- }
612
- if (entries.length > 1) {
613
- process.stdout.write(`\nTotal checked: ${entries.length} packages\n`);
614
- }
615
- }
616
- function runList(allEntries, cliPath, runCwd, verboseFromArgv) {
617
- // Collect unique resolved output dirs (tag filter not applied; list is informational).
618
- const seenDirs = new Set();
619
- if (verboseFromArgv) {
620
- // eslint-disable-next-line no-console
621
- console.log(`[verbose] list: listing managed files across ${allEntries.length} entr${allEntries.length === 1 ? 'y' : 'ies'} (cwd: ${runCwd})`);
622
- }
623
- for (const entry of allEntries) {
624
- const resolvedDir = node_path_1.default.resolve(runCwd, entry.outputDir);
625
- if (!seenDirs.has(resolvedDir)) {
626
- seenDirs.add(resolvedDir);
627
- if (verboseFromArgv) {
628
- // eslint-disable-next-line no-console
629
- console.log(`[verbose] list: scanning directory ${resolvedDir}`);
630
- }
631
- const command = buildListCommand(cliPath, entry.outputDir, runCwd, verboseFromArgv);
632
- (0, node_child_process_1.execSync)(command, { stdio: 'inherit', cwd: runCwd });
633
- }
634
- }
635
- }
636
- // eslint-disable-next-line complexity
637
- function runPurge(entries, cliPath, runCwd, dryRunFromArgv, silentFromArgv, verboseFromArgv) {
638
- if (verboseFromArgv) {
639
- // eslint-disable-next-line no-console
640
- console.log(`[verbose] purge: processing ${entries.length} entr${entries.length === 1 ? 'y' : 'ies'} (cwd: ${runCwd})`);
641
- }
642
- // eslint-disable-next-line functional/no-let
643
- let totalDeleted = 0;
644
- // eslint-disable-next-line functional/no-let
645
- let purgeIndex = 0;
646
- for (const entry of entries) {
647
- const effectiveSilent = entry.silent || silentFromArgv;
648
- if (purgeIndex > 0 && !effectiveSilent) {
649
- process.stdout.write('\n');
650
- }
651
- purgeIndex += 1;
652
- const effectiveEntry = {
653
- ...entry,
654
- dryRun: entry.dryRun || dryRunFromArgv,
655
- silent: effectiveSilent,
656
- verbose: entry.verbose || verboseFromArgv,
657
- };
658
- if (verboseFromArgv) {
659
- // eslint-disable-next-line no-console
660
- console.log(`[verbose] purge: entry package=${entry.package} outputDir=${entry.outputDir}`);
661
- }
662
- const command = buildPurgeCommand(cliPath, effectiveEntry, runCwd);
663
- if (verboseFromArgv) {
664
- // eslint-disable-next-line no-console
665
- console.log(`[verbose] purge: running command: ${command}`);
666
- }
667
- const { stdout: purgeStdout, exitCode: purgeExitCode } = runCommandCapture(command, runCwd);
668
- if (purgeExitCode !== 0) {
669
- throw Object.assign(new Error('purge failed'), { status: purgeExitCode });
670
- }
671
- const purgeMatch = purgeStdout.match(/Purge complete:\s*(\d+) deleted/);
672
- if (purgeMatch) {
673
- totalDeleted += Number.parseInt(purgeMatch[1], 10);
674
- }
675
- if (!effectiveEntry.dryRun) {
676
- if (verboseFromArgv) {
677
- // eslint-disable-next-line no-console
678
- console.log(`[verbose] purge: cleaning up symlinks for ${entry.package}`);
679
- }
680
- applySymlinks(effectiveEntry, runCwd);
681
- }
682
- }
683
- if (!silentFromArgv && entries.length > 1) {
684
- process.stdout.write(`\nTotal purged: ${totalDeleted}${dryRunFromArgv ? ' (dry run)' : ''}\n`);
685
- }
686
- }
687
- /**
688
- * Runs extraction for each entry defined in the publishable package's package.json "npmdata" array.
689
- * Invokes the npmdata CLI once per entry so that all CLI output and error handling is preserved.
690
- * Called from the minimal generated bin script with its own __dirname as binDir.
691
- *
692
- * Pass --tags <tag1,tag2> to limit processing to entries whose tags overlap with the given list.
693
- */
694
- function run(binDir, argv = process.argv) {
695
- const pkgJsonPath = node_path_1.default.join(binDir, '../package.json');
696
- const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgJsonPath).toString());
697
- const allEntries = pkg.npmdata && pkg.npmdata.length > 0 ? pkg.npmdata : [{ package: pkg.name, outputDir: '.' }];
698
- const userArgs = argv.slice(2);
699
- if (userArgs.includes('--help')) {
700
- printHelp(pkg.name, collectAllTags(allEntries));
701
- return;
702
- }
703
- // Default to 'extract' when no action is provided or the first arg is a flag.
704
- const action = userArgs.length === 0 || userArgs[0].startsWith('-') ? 'extract' : userArgs[0];
705
- if (!['extract', 'check', 'list', 'purge'].includes(action)) {
706
- process.stderr.write(`Error: unknown action '${action}'. Use 'extract', 'check', 'list', or 'purge'.\n\n`);
707
- printHelp(pkg.name, collectAllTags(allEntries));
708
- return;
709
- }
710
- const requestedTags = parseTagsFromArgv(argv);
711
- const entries = filterEntriesByTags(allEntries, requestedTags);
712
- const excludedEntries = requestedTags.length > 0 ? allEntries.filter((e) => !entries.includes(e)) : [];
713
- const cliPath = require.resolve('npmdata/dist/main.js', { paths: [binDir] });
714
- const parsedOutput = parseOutputFromArgv(userArgs);
715
- const runCwd = parsedOutput ? node_path_1.default.resolve(process.cwd(), parsedOutput) : process.cwd();
716
- const dryRunFromArgv = parseDryRunFromArgv(userArgs);
717
- const silentFromArgv = parseSilentFromArgv(userArgs);
718
- const verboseFromArgv = parseVerboseFromArgv(userArgs);
719
- const noGitignoreFromArgv = parseNoGitignoreFromArgv(userArgs);
720
- const unmanagedFromArgv = parseUnmanagedFromArgv(userArgs);
721
- if (verboseFromArgv) {
722
- // eslint-disable-next-line no-console
723
- console.log(`[verbose] runner: action=${action} entries=${entries.length} cwd=${runCwd}`);
724
- }
725
- // eslint-disable-next-line functional/no-try-statements
726
- try {
727
- if (action === 'extract') {
728
- runExtract(entries, excludedEntries, cliPath, runCwd, dryRunFromArgv, silentFromArgv, verboseFromArgv, noGitignoreFromArgv, unmanagedFromArgv);
729
- }
730
- else if (action === 'check') {
731
- runCheck(entries, cliPath, runCwd, verboseFromArgv);
732
- }
733
- else if (action === 'list') {
734
- runList(allEntries, cliPath, runCwd, verboseFromArgv);
735
- }
736
- else if (action === 'purge') {
737
- runPurge(entries, cliPath, runCwd, dryRunFromArgv, silentFromArgv, verboseFromArgv);
738
- }
739
- }
740
- catch (error) {
741
- // The child process already printed the error via stdio:inherit.
742
- // Exit with the child's exit code to suppress the Node.js stack trace.
743
- const status = error?.status;
744
- // eslint-disable-next-line unicorn/no-process-exit
745
- process.exit(status ?? 1);
746
- }
747
- }
748
- //# sourceMappingURL=runner.js.map