@rushstack/package-extractor 0.1.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.
@@ -0,0 +1,576 @@
1
+ "use strict";
2
+ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
3
+ // See LICENSE in the project root for license information.
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || function (mod) {
21
+ if (mod && mod.__esModule) return mod;
22
+ var result = {};
23
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
24
+ __setModuleDefault(result, mod);
25
+ return result;
26
+ };
27
+ var __importDefault = (this && this.__importDefault) || function (mod) {
28
+ return (mod && mod.__esModule) ? mod : { "default": mod };
29
+ };
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.PackageExtractor = void 0;
32
+ const path = __importStar(require("path"));
33
+ const fs = __importStar(require("fs"));
34
+ const npm_packlist_1 = __importDefault(require("npm-packlist"));
35
+ const link_bins_1 = __importDefault(require("@pnpm/link-bins"));
36
+ const ignore_1 = __importDefault(require("ignore"));
37
+ const node_core_library_1 = require("@rushstack/node-core-library");
38
+ const ArchiveManager_1 = require("./ArchiveManager");
39
+ const SymlinkAnalyzer_1 = require("./SymlinkAnalyzer");
40
+ const Utils_1 = require("./Utils");
41
+ const PathConstants_1 = require("./PathConstants");
42
+ /**
43
+ * Manages the business logic for the "rush deploy" command.
44
+ *
45
+ * @public
46
+ */
47
+ class PackageExtractor {
48
+ /**
49
+ * Extract a package using the provided options
50
+ */
51
+ async extractAsync(options) {
52
+ const { terminal, projectConfigurations, sourceRootFolder, targetRootFolder, mainProjectName, overwriteExisting, createArchiveFilePath, createArchiveOnly } = options;
53
+ if (createArchiveOnly) {
54
+ if (options.linkCreation !== 'script' && options.linkCreation !== 'none') {
55
+ throw new Error('createArchiveOnly is only supported when linkCreation is "script" or "none"');
56
+ }
57
+ if (!createArchiveFilePath) {
58
+ throw new Error('createArchiveOnly is only supported when createArchiveFilePath is specified');
59
+ }
60
+ }
61
+ let archiver;
62
+ let archiveFilePath;
63
+ if (createArchiveFilePath) {
64
+ if (path.extname(createArchiveFilePath) !== '.zip') {
65
+ throw new Error('Only archives with the .zip file extension are currently supported.');
66
+ }
67
+ archiveFilePath = path.resolve(targetRootFolder, createArchiveFilePath);
68
+ archiver = new ArchiveManager_1.ArchiveManager();
69
+ }
70
+ await node_core_library_1.FileSystem.ensureFolderAsync(targetRootFolder);
71
+ terminal.writeLine(node_core_library_1.Colors.cyan(`Extracting to target folder: ${targetRootFolder}`));
72
+ terminal.writeLine(node_core_library_1.Colors.cyan(`Main project for extraction: ${mainProjectName}`));
73
+ try {
74
+ const existingExtraction = (await node_core_library_1.FileSystem.readFolderItemNamesAsync(targetRootFolder)).length > 0;
75
+ if (existingExtraction) {
76
+ if (!overwriteExisting) {
77
+ throw new Error('The extraction target folder is not empty. Overwrite must be explicitly requested');
78
+ }
79
+ else {
80
+ terminal.writeLine('Deleting target folder contents...');
81
+ terminal.writeLine('');
82
+ await node_core_library_1.FileSystem.ensureEmptyFolderAsync(targetRootFolder);
83
+ }
84
+ }
85
+ }
86
+ catch (error) {
87
+ if (!node_core_library_1.FileSystem.isFolderDoesNotExistError(error)) {
88
+ throw error;
89
+ }
90
+ }
91
+ // Create a new state for each run
92
+ const state = {
93
+ foldersToCopy: new Set(),
94
+ projectConfigurationsByName: new Map(projectConfigurations.map((p) => [p.projectName, p])),
95
+ projectConfigurationsByPath: new Map(projectConfigurations.map((p) => [p.projectFolder, p])),
96
+ symlinkAnalyzer: new SymlinkAnalyzer_1.SymlinkAnalyzer({ requiredSourceParentPath: sourceRootFolder }),
97
+ archiver
98
+ };
99
+ await this._performExtractionAsync(options, state);
100
+ if (archiver && archiveFilePath) {
101
+ terminal.writeLine(`Creating archive at "${archiveFilePath}"`);
102
+ await archiver.createArchiveAsync(archiveFilePath);
103
+ }
104
+ }
105
+ async _performExtractionAsync(options, state) {
106
+ var _a;
107
+ const { terminal, mainProjectName, sourceRootFolder, targetRootFolder, folderToCopy: addditionalFolderToCopy, linkCreation } = options;
108
+ const { projectConfigurationsByName, foldersToCopy, symlinkAnalyzer } = state;
109
+ const mainProjectConfiguration = projectConfigurationsByName.get(mainProjectName);
110
+ if (!mainProjectConfiguration) {
111
+ throw new Error(`Main project "${mainProjectName}" was not found in the list of projects`);
112
+ }
113
+ // Calculate the set with additionalProjectsToInclude
114
+ const includedProjectsSet = new Set([mainProjectConfiguration]);
115
+ for (const { additionalProjectsToInclude } of includedProjectsSet) {
116
+ if (additionalProjectsToInclude) {
117
+ for (const additionalProjectNameToInclude of additionalProjectsToInclude) {
118
+ const additionalProjectToInclude = projectConfigurationsByName.get(additionalProjectNameToInclude);
119
+ if (!additionalProjectToInclude) {
120
+ throw new Error(`Project "${additionalProjectNameToInclude}" was not found in the list of projects.`);
121
+ }
122
+ includedProjectsSet.add(additionalProjectToInclude);
123
+ }
124
+ }
125
+ }
126
+ for (const { projectName, projectFolder } of includedProjectsSet) {
127
+ terminal.writeLine(node_core_library_1.Colors.cyan(`Analyzing project: ${projectName}`));
128
+ await this._collectFoldersAsync(projectFolder, options, state);
129
+ }
130
+ if (!options.createArchiveOnly) {
131
+ terminal.writeLine(`Copying folders to target folder "${targetRootFolder}"`);
132
+ }
133
+ await node_core_library_1.Async.forEachAsync(foldersToCopy, async (folderToCopy) => {
134
+ await this._extractFolderAsync(folderToCopy, options, state);
135
+ }, {
136
+ concurrency: 10
137
+ });
138
+ switch (linkCreation) {
139
+ case 'script': {
140
+ const sourceFilePath = path.join(PathConstants_1.scriptsFolderPath, PathConstants_1.createLinksScriptFilename);
141
+ if (!options.createArchiveOnly) {
142
+ terminal.writeLine(`Creating ${PathConstants_1.createLinksScriptFilename}`);
143
+ await node_core_library_1.FileSystem.copyFileAsync({
144
+ sourcePath: sourceFilePath,
145
+ destinationPath: path.join(targetRootFolder, PathConstants_1.createLinksScriptFilename),
146
+ alreadyExistsBehavior: node_core_library_1.AlreadyExistsBehavior.Error
147
+ });
148
+ }
149
+ await ((_a = state.archiver) === null || _a === void 0 ? void 0 : _a.addToArchiveAsync({
150
+ filePath: sourceFilePath,
151
+ archivePath: PathConstants_1.createLinksScriptFilename
152
+ }));
153
+ break;
154
+ }
155
+ case 'default': {
156
+ terminal.writeLine('Creating symlinks');
157
+ const linksToCopy = symlinkAnalyzer.reportSymlinks();
158
+ await node_core_library_1.Async.forEachAsync(linksToCopy, async (linkToCopy) => {
159
+ await this._extractSymlinkAsync(linkToCopy, options, state);
160
+ });
161
+ await this._makeBinLinksAsync(options, state);
162
+ break;
163
+ }
164
+ default: {
165
+ break;
166
+ }
167
+ }
168
+ terminal.writeLine('Creating extractor-metadata.json');
169
+ await this._writeExtractorMetadataAsync(options, state);
170
+ if (addditionalFolderToCopy) {
171
+ const sourceFolderPath = path.resolve(sourceRootFolder, addditionalFolderToCopy);
172
+ await node_core_library_1.FileSystem.copyFilesAsync({
173
+ sourcePath: sourceFolderPath,
174
+ destinationPath: targetRootFolder,
175
+ alreadyExistsBehavior: node_core_library_1.AlreadyExistsBehavior.Error
176
+ });
177
+ }
178
+ }
179
+ /**
180
+ * Recursively crawl the node_modules dependencies and collect the result in IExtractorState.foldersToCopy.
181
+ */
182
+ async _collectFoldersAsync(packageJsonFolder, options, state) {
183
+ const { terminal, pnpmInstallFolder, transformPackageJson } = options;
184
+ const { projectConfigurationsByPath } = state;
185
+ const packageJsonFolderPathQueue = new node_core_library_1.AsyncQueue([packageJsonFolder]);
186
+ await node_core_library_1.Async.forEachAsync(packageJsonFolderPathQueue, async ([packageJsonFolderPath, callback]) => {
187
+ var _a;
188
+ const packageJsonRealFolderPath = await node_core_library_1.FileSystem.getRealPathAsync(packageJsonFolderPath);
189
+ if (state.foldersToCopy.has(packageJsonRealFolderPath)) {
190
+ // we've already seen this folder
191
+ callback();
192
+ return;
193
+ }
194
+ state.foldersToCopy.add(packageJsonRealFolderPath);
195
+ const originalPackageJson = await node_core_library_1.JsonFile.loadAsync(path.join(packageJsonRealFolderPath, 'package.json'));
196
+ // Transform packageJson using the provided transformer, if requested
197
+ const packageJson = (_a = transformPackageJson === null || transformPackageJson === void 0 ? void 0 : transformPackageJson(originalPackageJson)) !== null && _a !== void 0 ? _a : originalPackageJson;
198
+ // Union of keys from regular dependencies, peerDependencies, optionalDependencies
199
+ // (and possibly devDependencies if includeDevDependencies=true)
200
+ const dependencyNamesToProcess = new Set();
201
+ // Just the keys from optionalDependencies and peerDependencies
202
+ const optionalDependencyNames = new Set();
203
+ for (const name of Object.keys(packageJson.dependencies || {})) {
204
+ dependencyNamesToProcess.add(name);
205
+ }
206
+ for (const name of Object.keys(packageJson.peerDependencies || {})) {
207
+ dependencyNamesToProcess.add(name);
208
+ optionalDependencyNames.add(name); // consider peers optional, since they are so frequently broken
209
+ }
210
+ for (const name of Object.keys(packageJson.optionalDependencies || {})) {
211
+ dependencyNamesToProcess.add(name);
212
+ optionalDependencyNames.add(name);
213
+ }
214
+ // Check to see if this is a local project
215
+ const projectConfiguration = projectConfigurationsByPath.get(packageJsonRealFolderPath);
216
+ if (projectConfiguration) {
217
+ if (options.includeDevDependencies) {
218
+ for (const name of Object.keys(packageJson.devDependencies || {})) {
219
+ dependencyNamesToProcess.add(name);
220
+ }
221
+ }
222
+ this._applyDependencyFilters(terminal, dependencyNamesToProcess, projectConfiguration.additionalDependenciesToInclude, projectConfiguration.dependenciesToExclude);
223
+ }
224
+ for (const dependencyPackageName of dependencyNamesToProcess) {
225
+ try {
226
+ const dependencyPackageFolderPath = await node_core_library_1.Import.resolvePackageAsync({
227
+ packageName: dependencyPackageName,
228
+ baseFolderPath: packageJsonRealFolderPath,
229
+ getRealPathAsync: async (filePath) => {
230
+ try {
231
+ return (await state.symlinkAnalyzer.analyzePathAsync(filePath)).nodePath;
232
+ }
233
+ catch (error) {
234
+ if (node_core_library_1.FileSystem.isFileDoesNotExistError(error)) {
235
+ return filePath;
236
+ }
237
+ throw error;
238
+ }
239
+ }
240
+ });
241
+ packageJsonFolderPathQueue.push(dependencyPackageFolderPath);
242
+ }
243
+ catch (resolveErr) {
244
+ if (optionalDependencyNames.has(dependencyPackageName)) {
245
+ // Ignore missing optional dependency
246
+ continue;
247
+ }
248
+ throw resolveErr;
249
+ }
250
+ }
251
+ // Replicate the links to the virtual store. Note that if the package has not been hoisted by
252
+ // PNPM, the package will not be resolvable from here.
253
+ // Only apply this logic for packages that were actually installed under the common/temp folder.
254
+ if (pnpmInstallFolder && node_core_library_1.Path.isUnder(packageJsonFolderPath, pnpmInstallFolder)) {
255
+ try {
256
+ // The PNPM virtual store links are created in this folder. We will resolve the current package
257
+ // from that location and collect any additional links encountered along the way.
258
+ // TODO: This can be configured via NPMRC. We should support that.
259
+ const pnpmDotFolderPath = path.join(pnpmInstallFolder, 'node_modules', '.pnpm');
260
+ // TODO: Investigate how package aliases are handled by PNPM in this case. For example:
261
+ //
262
+ // "dependencies": {
263
+ // "alias-name": "npm:real-name@^1.2.3"
264
+ // }
265
+ const dependencyPackageFolderPath = await node_core_library_1.Import.resolvePackageAsync({
266
+ packageName: packageJson.name,
267
+ baseFolderPath: pnpmDotFolderPath,
268
+ getRealPathAsync: async (filePath) => {
269
+ try {
270
+ return (await state.symlinkAnalyzer.analyzePathAsync(filePath)).nodePath;
271
+ }
272
+ catch (error) {
273
+ if (node_core_library_1.FileSystem.isFileDoesNotExistError(error)) {
274
+ return filePath;
275
+ }
276
+ throw error;
277
+ }
278
+ }
279
+ });
280
+ packageJsonFolderPathQueue.push(dependencyPackageFolderPath);
281
+ }
282
+ catch (resolveErr) {
283
+ // The virtual store link isn't guaranteed to exist, so ignore if it's missing
284
+ // NOTE: If you encounter this warning a lot, please report it to the Rush maintainers.
285
+ console.log('Ignoring missing PNPM virtual store link for ' + packageJsonFolderPath);
286
+ }
287
+ }
288
+ callback();
289
+ }, {
290
+ concurrency: 10
291
+ });
292
+ }
293
+ _applyDependencyFilters(terminal, allDependencyNames, additionalDependenciesToInclude = [], dependenciesToExclude = []) {
294
+ // Track packages that got added/removed for reporting purposes
295
+ const extraIncludedPackageNames = [];
296
+ const extraExcludedPackageNames = [];
297
+ for (const patternWithStar of dependenciesToExclude) {
298
+ for (const dependency of allDependencyNames) {
299
+ if ((0, Utils_1.matchesWithStar)(patternWithStar, dependency)) {
300
+ if (allDependencyNames.delete(dependency)) {
301
+ extraExcludedPackageNames.push(dependency);
302
+ }
303
+ }
304
+ }
305
+ }
306
+ for (const dependencyToInclude of additionalDependenciesToInclude) {
307
+ if (!allDependencyNames.has(dependencyToInclude)) {
308
+ allDependencyNames.add(dependencyToInclude);
309
+ extraIncludedPackageNames.push(dependencyToInclude);
310
+ }
311
+ }
312
+ if (extraIncludedPackageNames.length > 0) {
313
+ extraIncludedPackageNames.sort();
314
+ terminal.writeLine(`Extra dependencies included by settings: ${extraIncludedPackageNames.join(', ')}`);
315
+ }
316
+ if (extraExcludedPackageNames.length > 0) {
317
+ extraExcludedPackageNames.sort();
318
+ terminal.writeLine(`Extra dependencies excluded by settings: ${extraExcludedPackageNames.join(', ')}`);
319
+ }
320
+ return allDependencyNames;
321
+ }
322
+ /**
323
+ * Maps a file path from IExtractorOptions.sourceRootFolder to IExtractorOptions.targetRootFolder
324
+ *
325
+ * Example input: "C:\\MyRepo\\libraries\\my-lib"
326
+ * Example output: "C:\\MyRepo\\common\\deploy\\libraries\\my-lib"
327
+ */
328
+ _remapPathForExtractorFolder(absolutePathInSourceFolder, options) {
329
+ const { sourceRootFolder, targetRootFolder } = options;
330
+ const relativePath = path.relative(sourceRootFolder, absolutePathInSourceFolder);
331
+ if (relativePath.startsWith('..')) {
332
+ throw new Error(`Source path "${absolutePathInSourceFolder}" is not under "${sourceRootFolder}"`);
333
+ }
334
+ const absolutePathInTargetFolder = path.join(targetRootFolder, relativePath);
335
+ return absolutePathInTargetFolder;
336
+ }
337
+ /**
338
+ * Maps a file path from IExtractorOptions.sourceRootFolder to relative path
339
+ *
340
+ * Example input: "C:\\MyRepo\\libraries\\my-lib"
341
+ * Example output: "libraries/my-lib"
342
+ */
343
+ _remapPathForExtractorMetadata(absolutePathInSourceFolder, options) {
344
+ const { sourceRootFolder } = options;
345
+ const relativePath = path.relative(sourceRootFolder, absolutePathInSourceFolder);
346
+ if (relativePath.startsWith('..')) {
347
+ throw new Error(`Source path "${absolutePathInSourceFolder}" is not under "${sourceRootFolder}"`);
348
+ }
349
+ return node_core_library_1.Path.convertToSlashes(relativePath);
350
+ }
351
+ /**
352
+ * Copy one package folder to the extractor target folder.
353
+ */
354
+ async _extractFolderAsync(sourceFolderPath, options, state) {
355
+ const { includeNpmIgnoreFiles, targetRootFolder } = options;
356
+ const { projectConfigurationsByPath, archiver } = state;
357
+ let useNpmIgnoreFilter = false;
358
+ if (!includeNpmIgnoreFiles) {
359
+ const sourceFolderRealPath = await node_core_library_1.FileSystem.getRealPathAsync(sourceFolderPath);
360
+ const sourceProjectConfiguration = projectConfigurationsByPath.get(sourceFolderRealPath);
361
+ if (sourceProjectConfiguration) {
362
+ useNpmIgnoreFilter = true;
363
+ }
364
+ }
365
+ const targetFolderPath = this._remapPathForExtractorFolder(sourceFolderPath, options);
366
+ if (useNpmIgnoreFilter) {
367
+ // Use npm-packlist to filter the files. Using the Walker class (instead of the default API) ensures
368
+ // that "bundledDependencies" are not included.
369
+ const walkerPromise = new Promise((resolve, reject) => {
370
+ const walker = new npm_packlist_1.default.Walker({
371
+ path: sourceFolderPath
372
+ });
373
+ walker.on('done', resolve).on('error', reject).start();
374
+ });
375
+ const npmPackFiles = await walkerPromise;
376
+ const alreadyCopiedSourcePaths = new Set();
377
+ await node_core_library_1.Async.forEachAsync(npmPackFiles, async (npmPackFile) => {
378
+ // In issue https://github.com/microsoft/rushstack/issues/2121 we found that npm-packlist sometimes returns
379
+ // duplicate file paths, for example:
380
+ //
381
+ // 'dist//index.js'
382
+ // 'dist/index.js'
383
+ //
384
+ // We can detect the duplicates by comparing the path.resolve() result.
385
+ const copySourcePath = path.resolve(sourceFolderPath, npmPackFile);
386
+ if (alreadyCopiedSourcePaths.has(copySourcePath)) {
387
+ return;
388
+ }
389
+ alreadyCopiedSourcePaths.add(copySourcePath);
390
+ const copyDestinationPath = path.join(targetFolderPath, npmPackFile);
391
+ const copySourcePathNode = await state.symlinkAnalyzer.analyzePathAsync(copySourcePath);
392
+ if (copySourcePathNode.kind !== 'link') {
393
+ if (!options.createArchiveOnly) {
394
+ await node_core_library_1.FileSystem.ensureFolderAsync(path.dirname(copyDestinationPath));
395
+ // Use the fs.copyFile API instead of FileSystem.copyFileAsync() since copyFileAsync performs
396
+ // a needless stat() call to determine if it's a file or folder, and we already know it's a file.
397
+ await fs.promises.copyFile(copySourcePath, copyDestinationPath, fs.constants.COPYFILE_EXCL);
398
+ }
399
+ if (archiver) {
400
+ const archivePath = path.relative(targetRootFolder, copyDestinationPath);
401
+ await archiver.addToArchiveAsync({
402
+ filePath: copySourcePath,
403
+ archivePath,
404
+ stats: copySourcePathNode.linkStats
405
+ });
406
+ }
407
+ }
408
+ }, {
409
+ concurrency: 10
410
+ });
411
+ }
412
+ else {
413
+ // use a simplistic "ignore" ruleset to filter the files
414
+ const ignoreFilter = (0, ignore_1.default)();
415
+ ignoreFilter.add([
416
+ // The top-level node_modules folder is always excluded
417
+ '/node_modules',
418
+ // Also exclude well-known folders that can contribute a lot of unnecessary files
419
+ '**/.git',
420
+ '**/.svn',
421
+ '**/.hg',
422
+ '**/.DS_Store'
423
+ ]);
424
+ // Do a breadth-first search of the source folder, copying each file to the target folder
425
+ const queue = new node_core_library_1.AsyncQueue([sourceFolderPath]);
426
+ await node_core_library_1.Async.forEachAsync(queue, async ([sourcePath, callback]) => {
427
+ const relativeSourcePath = path.relative(sourceFolderPath, sourcePath);
428
+ if (relativeSourcePath !== '' && ignoreFilter.ignores(relativeSourcePath)) {
429
+ callback();
430
+ return;
431
+ }
432
+ const sourcePathNode = await state.symlinkAnalyzer.analyzePathAsync(sourcePath);
433
+ if (sourcePathNode.kind === 'file') {
434
+ const targetPath = path.join(targetFolderPath, relativeSourcePath);
435
+ if (!options.createArchiveOnly) {
436
+ // Manually call fs.copyFile to avoid unnecessary stat calls.
437
+ await fs.promises.copyFile(sourcePath, targetPath, fs.constants.COPYFILE_EXCL);
438
+ }
439
+ // Add the file to the archive. Only need to add files since directories will be auto-created
440
+ if (archiver) {
441
+ const archivePath = path.relative(targetRootFolder, targetPath);
442
+ await archiver.addToArchiveAsync({
443
+ filePath: sourcePath,
444
+ archivePath: archivePath,
445
+ stats: sourcePathNode.linkStats
446
+ });
447
+ }
448
+ }
449
+ else if (sourcePathNode.kind === 'folder') {
450
+ if (!options.createArchiveOnly) {
451
+ const targetPath = path.join(targetFolderPath, relativeSourcePath);
452
+ await node_core_library_1.FileSystem.ensureFolderAsync(targetPath);
453
+ }
454
+ const children = await node_core_library_1.FileSystem.readFolderItemNamesAsync(sourcePath);
455
+ for (const child of children) {
456
+ queue.push(path.join(sourcePath, child));
457
+ }
458
+ }
459
+ callback();
460
+ }, {
461
+ concurrency: 10
462
+ });
463
+ }
464
+ }
465
+ /**
466
+ * Create a symlink as described by the ILinkInfo object.
467
+ */
468
+ async _extractSymlinkAsync(originalLinkInfo, options, state) {
469
+ var _a;
470
+ const linkInfo = {
471
+ kind: originalLinkInfo.kind,
472
+ linkPath: this._remapPathForExtractorFolder(originalLinkInfo.linkPath, options),
473
+ targetPath: this._remapPathForExtractorFolder(originalLinkInfo.targetPath, options)
474
+ };
475
+ const newLinkFolder = path.dirname(linkInfo.linkPath);
476
+ await node_core_library_1.FileSystem.ensureFolderAsync(newLinkFolder);
477
+ // Link to the relative path for symlinks
478
+ const relativeTargetPath = path.relative(newLinkFolder, linkInfo.targetPath);
479
+ // NOTE: This logic is based on NpmLinkManager._createSymlink()
480
+ if (linkInfo.kind === 'fileLink') {
481
+ // For files, we use a Windows "hard link", because creating a symbolic link requires
482
+ // administrator permission. However hard links seem to cause build failures on Mac,
483
+ // so for all other operating systems we use symbolic links for this case.
484
+ if (process.platform === 'win32') {
485
+ await node_core_library_1.FileSystem.createHardLinkAsync({
486
+ linkTargetPath: relativeTargetPath,
487
+ newLinkPath: linkInfo.linkPath
488
+ });
489
+ }
490
+ else {
491
+ await node_core_library_1.FileSystem.createSymbolicLinkFileAsync({
492
+ linkTargetPath: relativeTargetPath,
493
+ newLinkPath: linkInfo.linkPath
494
+ });
495
+ }
496
+ }
497
+ else {
498
+ // Junctions are only supported on Windows. This will create a symbolic link on other platforms.
499
+ await node_core_library_1.FileSystem.createSymbolicLinkJunctionAsync({
500
+ linkTargetPath: relativeTargetPath,
501
+ newLinkPath: linkInfo.linkPath
502
+ });
503
+ }
504
+ // Since the created symlinks have the required relative paths, they can be added directly to
505
+ // the archive.
506
+ await ((_a = state.archiver) === null || _a === void 0 ? void 0 : _a.addToArchiveAsync({
507
+ filePath: linkInfo.linkPath,
508
+ archivePath: path.relative(options.targetRootFolder, linkInfo.linkPath)
509
+ }));
510
+ }
511
+ /**
512
+ * Write the common/deploy/deploy-metadata.json file.
513
+ */
514
+ async _writeExtractorMetadataAsync(options, state) {
515
+ var _a;
516
+ const { mainProjectName, targetRootFolder } = options;
517
+ const { projectConfigurationsByPath } = state;
518
+ const extractorMetadataFileName = 'extractor-metadata.json';
519
+ const extractorMetadataFilePath = path.join(targetRootFolder, extractorMetadataFileName);
520
+ const extractorMetadataJson = {
521
+ mainProjectName,
522
+ projects: [],
523
+ links: []
524
+ };
525
+ for (const projectFolder of projectConfigurationsByPath.keys()) {
526
+ if (state.foldersToCopy.has(projectFolder)) {
527
+ extractorMetadataJson.projects.push({
528
+ path: this._remapPathForExtractorMetadata(projectFolder, options)
529
+ });
530
+ }
531
+ }
532
+ // Remap the links to be relative to target folder
533
+ for (const absoluteLinkInfo of state.symlinkAnalyzer.reportSymlinks()) {
534
+ const relativeInfo = {
535
+ kind: absoluteLinkInfo.kind,
536
+ linkPath: this._remapPathForExtractorMetadata(absoluteLinkInfo.linkPath, options),
537
+ targetPath: this._remapPathForExtractorMetadata(absoluteLinkInfo.targetPath, options)
538
+ };
539
+ extractorMetadataJson.links.push(relativeInfo);
540
+ }
541
+ const extractorMetadataFileContent = JSON.stringify(extractorMetadataJson, undefined, 0);
542
+ if (!options.createArchiveOnly) {
543
+ await node_core_library_1.FileSystem.writeFileAsync(extractorMetadataFilePath, extractorMetadataFileContent);
544
+ }
545
+ await ((_a = state.archiver) === null || _a === void 0 ? void 0 : _a.addToArchiveAsync({
546
+ fileData: extractorMetadataFileContent,
547
+ archivePath: extractorMetadataFileName
548
+ }));
549
+ }
550
+ async _makeBinLinksAsync(options, state) {
551
+ const { terminal } = options;
552
+ const extractedProjectFolders = Array.from(state.projectConfigurationsByPath.keys()).filter((folderPath) => state.foldersToCopy.has(folderPath));
553
+ await node_core_library_1.Async.forEachAsync(extractedProjectFolders, async (projectFolder) => {
554
+ const extractedProjectFolder = this._remapPathForExtractorFolder(projectFolder, options);
555
+ const extractedProjectNodeModulesFolder = path.join(extractedProjectFolder, 'node_modules');
556
+ const extractedProjectBinFolder = path.join(extractedProjectNodeModulesFolder, '.bin');
557
+ const linkedBinPackageNames = await (0, link_bins_1.default)(extractedProjectNodeModulesFolder, extractedProjectBinFolder, {
558
+ warn: (msg) => terminal.writeLine(node_core_library_1.Colors.yellow(msg))
559
+ });
560
+ if (linkedBinPackageNames.length && state.archiver) {
561
+ const binFolderItems = await node_core_library_1.FileSystem.readFolderItemNamesAsync(extractedProjectBinFolder);
562
+ for (const binFolderItem of binFolderItems) {
563
+ const binFilePath = path.join(extractedProjectBinFolder, binFolderItem);
564
+ await state.archiver.addToArchiveAsync({
565
+ filePath: binFilePath,
566
+ archivePath: path.relative(options.targetRootFolder, binFilePath)
567
+ });
568
+ }
569
+ }
570
+ }, {
571
+ concurrency: 10
572
+ });
573
+ }
574
+ }
575
+ exports.PackageExtractor = PackageExtractor;
576
+ //# sourceMappingURL=PackageExtractor.js.map