File size: 4,041 Bytes
04ec17f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Maël Nison @arcanis
*/

"use strict";

/** @typedef {import("./Resolver")} Resolver */
/** @typedef {import("./Resolver").ResolveStepHook} ResolveStepHook */
/** @typedef {import("./Resolver").ResolveRequest} ResolveRequest */
/**
 * @typedef {object} PnpApiImpl
 * @property {(packageName: string, issuer: string, options: { considerBuiltins: boolean }) => string | null} resolveToUnqualified resolve to unqualified
 */

module.exports = class PnpPlugin {
	/**
	 * @param {string | ResolveStepHook} source source
	 * @param {PnpApiImpl} pnpApi pnpApi
	 * @param {string | ResolveStepHook} target target
	 * @param {string | ResolveStepHook} alternateTarget alternateTarget
	 */
	constructor(source, pnpApi, target, alternateTarget) {
		this.source = source;
		this.pnpApi = pnpApi;
		this.target = target;
		this.alternateTarget = alternateTarget;
	}

	/**
	 * @param {Resolver} resolver the resolver
	 * @returns {void}
	 */
	apply(resolver) {
		/** @type {ResolveStepHook} */
		const target = resolver.ensureHook(this.target);
		const alternateTarget = resolver.ensureHook(this.alternateTarget);
		resolver
			.getHook(this.source)
			.tapAsync("PnpPlugin", (request, resolveContext, callback) => {
				const req = request.request;
				if (!req) return callback();

				// The trailing slash indicates to PnP that this value is a folder rather than a file
				const issuer = `${request.path}/`;

				const packageMatch = /^(@[^/]+\/)?[^/]+/.exec(req);
				if (!packageMatch) return callback();

				const [packageName] = packageMatch;
				const innerRequest = `.${req.slice(packageName.length)}`;

				/** @type {string|undefined|null} */
				let resolution;
				/** @type {string|undefined|null} */
				let apiResolution;
				try {
					resolution = this.pnpApi.resolveToUnqualified(packageName, issuer, {
						considerBuiltins: false,
					});

					if (resolution === null) {
						// This is either not a PnP managed issuer or it's a Node builtin
						// Try to continue resolving with our alternatives
						resolver.doResolve(
							alternateTarget,
							request,
							"issuer is not managed by a pnpapi",
							resolveContext,
							(err, result) => {
								if (err) return callback(err);
								if (result) return callback(null, result);
								// Skip alternatives
								return callback(null, null);
							},
						);
						return;
					}

					if (resolveContext.fileDependencies) {
						apiResolution = this.pnpApi.resolveToUnqualified("pnpapi", issuer, {
							considerBuiltins: false,
						});
					}
				} catch (/** @type {unknown} */ error) {
					if (
						/** @type {Error & { code: string }} */
						(error).code === "MODULE_NOT_FOUND" &&
						/** @type {Error & { pnpCode: string }} */
						(error).pnpCode === "UNDECLARED_DEPENDENCY"
					) {
						// This is not a PnP managed dependency.
						// Try to continue resolving with our alternatives
						if (resolveContext.log) {
							resolveContext.log("request is not managed by the pnpapi");
							for (const line of /** @type {Error} */ (error).message
								.split("\n")
								.filter(Boolean)) {
								resolveContext.log(`  ${line}`);
							}
						}
						return callback();
					}
					return callback(/** @type {Error} */ (error));
				}

				if (resolution === packageName) return callback();

				if (apiResolution && resolveContext.fileDependencies) {
					resolveContext.fileDependencies.add(apiResolution);
				}
				/** @type {ResolveRequest} */
				const obj = {
					...request,
					path: resolution,
					request: innerRequest,
					ignoreSymlinks: true,
					fullySpecified: request.fullySpecified && innerRequest !== ".",
				};
				resolver.doResolve(
					target,
					obj,
					`resolved by pnp to ${resolution}`,
					resolveContext,
					(err, result) => {
						if (err) return callback(err);
						if (result) return callback(null, result);
						// Skip alternatives
						return callback(null, null);
					},
				);
			});
	}
};