@yarnpkg/plugin-pnpm 1.0.0 → 1.1.0-rc.10

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,42 @@
1
+ import { Descriptor, FetchResult, Installer, InstallPackageExtraApi, Linker, LinkOptions, Locator, LocatorHash, MinimalLinkOptions, Package } from '@yarnpkg/core';
2
+ import { PortablePath } from '@yarnpkg/fslib';
3
+ export declare type PnpmCustomData = {
4
+ pathByLocator: Map<LocatorHash, PortablePath>;
5
+ locatorByPath: Map<PortablePath, string>;
6
+ };
7
+ export declare class PnpmLinker implements Linker {
8
+ supportsPackage(pkg: Package, opts: MinimalLinkOptions): boolean;
9
+ findPackageLocation(locator: Locator, opts: LinkOptions): Promise<PortablePath>;
10
+ findPackageLocator(location: PortablePath, opts: LinkOptions): Promise<Locator | null>;
11
+ makeInstaller(opts: LinkOptions): PnpmInstaller;
12
+ private isEnabled;
13
+ }
14
+ declare class PnpmInstaller implements Installer {
15
+ private opts;
16
+ private readonly asyncActions;
17
+ constructor(opts: LinkOptions);
18
+ getCustomDataKey(): string;
19
+ private customData;
20
+ attachCustomData(customData: any): void;
21
+ installPackage(pkg: Package, fetchResult: FetchResult, api: InstallPackageExtraApi): Promise<{
22
+ packageLocation: PortablePath;
23
+ buildDirective: null;
24
+ } | {
25
+ packageLocation: PortablePath;
26
+ buildDirective: import("@yarnpkg/core").BuildDirective[];
27
+ }>;
28
+ installPackageSoft(pkg: Package, fetchResult: FetchResult, api: InstallPackageExtraApi): Promise<{
29
+ packageLocation: PortablePath;
30
+ buildDirective: null;
31
+ }>;
32
+ installPackageHard(pkg: Package, fetchResult: FetchResult, api: InstallPackageExtraApi): Promise<{
33
+ packageLocation: PortablePath;
34
+ buildDirective: import("@yarnpkg/core").BuildDirective[];
35
+ }>;
36
+ attachInternalDependencies(locator: Locator, dependencies: Array<[Descriptor, Locator]>): Promise<void>;
37
+ attachExternalDependents(locator: Locator, dependentPaths: Array<PortablePath>): Promise<void>;
38
+ finalizeInstall(): Promise<{
39
+ customData: PnpmCustomData;
40
+ }>;
41
+ }
42
+ export {};
@@ -0,0 +1,326 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PnpmLinker = void 0;
4
+ const core_1 = require("@yarnpkg/core");
5
+ const fslib_1 = require("@yarnpkg/fslib");
6
+ const plugin_pnp_1 = require("@yarnpkg/plugin-pnp");
7
+ const clipanion_1 = require("clipanion");
8
+ class PnpmLinker {
9
+ supportsPackage(pkg, opts) {
10
+ return this.isEnabled(opts);
11
+ }
12
+ async findPackageLocation(locator, opts) {
13
+ if (!this.isEnabled(opts))
14
+ throw new Error(`Assertion failed: Expected the pnpm linker to be enabled`);
15
+ const customDataKey = getCustomDataKey();
16
+ const customData = opts.project.installersCustomData.get(customDataKey);
17
+ if (!customData)
18
+ throw new clipanion_1.UsageError(`The project in ${core_1.formatUtils.pretty(opts.project.configuration, `${opts.project.cwd}/package.json`, core_1.formatUtils.Type.PATH)} doesn't seem to have been installed - running an install there might help`);
19
+ const packageLocation = customData.pathByLocator.get(locator.locatorHash);
20
+ if (typeof packageLocation === `undefined`)
21
+ throw new clipanion_1.UsageError(`Couldn't find ${core_1.structUtils.prettyLocator(opts.project.configuration, locator)} in the currently installed pnpm map - running an install might help`);
22
+ return packageLocation;
23
+ }
24
+ async findPackageLocator(location, opts) {
25
+ if (!this.isEnabled(opts))
26
+ return null;
27
+ const customDataKey = getCustomDataKey();
28
+ const customData = opts.project.installersCustomData.get(customDataKey);
29
+ if (!customData)
30
+ throw new clipanion_1.UsageError(`The project in ${core_1.formatUtils.pretty(opts.project.configuration, `${opts.project.cwd}/package.json`, core_1.formatUtils.Type.PATH)} doesn't seem to have been installed - running an install there might help`);
31
+ const nmRootLocation = location.match(/(^.*\/node_modules\/(@[^/]*\/)?[^/]+)(\/.*$)/);
32
+ if (nmRootLocation) {
33
+ const nmLocator = customData.locatorByPath.get(nmRootLocation[1]);
34
+ if (nmLocator) {
35
+ return nmLocator;
36
+ }
37
+ }
38
+ let nextPath = location;
39
+ let currentPath = location;
40
+ do {
41
+ currentPath = nextPath;
42
+ nextPath = fslib_1.ppath.dirname(currentPath);
43
+ const locator = customData.locatorByPath.get(currentPath);
44
+ if (locator) {
45
+ return locator;
46
+ }
47
+ } while (nextPath !== currentPath);
48
+ return null;
49
+ }
50
+ makeInstaller(opts) {
51
+ return new PnpmInstaller(opts);
52
+ }
53
+ isEnabled(opts) {
54
+ return opts.project.configuration.get(`nodeLinker`) === `pnpm`;
55
+ }
56
+ }
57
+ exports.PnpmLinker = PnpmLinker;
58
+ class PnpmInstaller {
59
+ constructor(opts) {
60
+ this.opts = opts;
61
+ this.asyncActions = new core_1.miscUtils.AsyncActions(10);
62
+ this.customData = {
63
+ pathByLocator: new Map(),
64
+ locatorByPath: new Map(),
65
+ };
66
+ // Nothing to do
67
+ }
68
+ getCustomDataKey() {
69
+ return getCustomDataKey();
70
+ }
71
+ attachCustomData(customData) {
72
+ // We don't want to attach the data because it's only used in the Linker and we'll recompute it anyways in the Installer,
73
+ // it needs to be invalidated because otherwise we'll never prune the store or we might run into various issues.
74
+ }
75
+ async installPackage(pkg, fetchResult, api) {
76
+ switch (pkg.linkType) {
77
+ case core_1.LinkType.SOFT: return this.installPackageSoft(pkg, fetchResult, api);
78
+ case core_1.LinkType.HARD: return this.installPackageHard(pkg, fetchResult, api);
79
+ }
80
+ throw new Error(`Assertion failed: Unsupported package link type`);
81
+ }
82
+ async installPackageSoft(pkg, fetchResult, api) {
83
+ const pkgPath = fslib_1.ppath.resolve(fetchResult.packageFs.getRealPath(), fetchResult.prefixPath);
84
+ this.customData.pathByLocator.set(pkg.locatorHash, pkgPath);
85
+ return {
86
+ packageLocation: pkgPath,
87
+ buildDirective: null,
88
+ };
89
+ }
90
+ async installPackageHard(pkg, fetchResult, api) {
91
+ var _a;
92
+ const pkgPath = getPackageLocation(pkg, { project: this.opts.project });
93
+ this.customData.locatorByPath.set(pkgPath, core_1.structUtils.stringifyLocator(pkg));
94
+ this.customData.pathByLocator.set(pkg.locatorHash, pkgPath);
95
+ api.holdFetchResult(this.asyncActions.set(pkg.locatorHash, async () => {
96
+ await fslib_1.xfs.mkdirPromise(pkgPath, { recursive: true });
97
+ // Copy the package source into the <root>/n_m/.store/<hash> directory, so
98
+ // that we can then create symbolic links to it later.
99
+ await fslib_1.xfs.copyPromise(pkgPath, fetchResult.prefixPath, {
100
+ baseFs: fetchResult.packageFs,
101
+ overwrite: false,
102
+ });
103
+ }));
104
+ const isVirtual = core_1.structUtils.isVirtualLocator(pkg);
105
+ const devirtualizedLocator = isVirtual ? core_1.structUtils.devirtualizeLocator(pkg) : pkg;
106
+ const buildConfig = {
107
+ manifest: (_a = await core_1.Manifest.tryFind(fetchResult.prefixPath, { baseFs: fetchResult.packageFs })) !== null && _a !== void 0 ? _a : new core_1.Manifest(),
108
+ misc: {
109
+ hasBindingGyp: plugin_pnp_1.jsInstallUtils.hasBindingGyp(fetchResult),
110
+ },
111
+ };
112
+ const dependencyMeta = this.opts.project.getDependencyMeta(devirtualizedLocator, pkg.version);
113
+ const buildScripts = plugin_pnp_1.jsInstallUtils.extractBuildScripts(pkg, buildConfig, dependencyMeta, { configuration: this.opts.project.configuration, report: this.opts.report });
114
+ return {
115
+ packageLocation: pkgPath,
116
+ buildDirective: buildScripts,
117
+ };
118
+ }
119
+ async attachInternalDependencies(locator, dependencies) {
120
+ if (this.opts.project.configuration.get(`nodeLinker`) !== `pnpm`)
121
+ return;
122
+ // We don't install those packages at all, because they can't be used anyway
123
+ if (!isPnpmVirtualCompatible(locator, { project: this.opts.project }))
124
+ return;
125
+ this.asyncActions.reduce(locator.locatorHash, async (action) => {
126
+ // Wait that the package is properly installed before starting to copy things into it
127
+ await action;
128
+ const pkgPath = this.customData.pathByLocator.get(locator.locatorHash);
129
+ if (typeof pkgPath === `undefined`)
130
+ throw new Error(`Assertion failed: Expected the package to have been registered (${core_1.structUtils.stringifyLocator(locator)})`);
131
+ const nmPath = fslib_1.ppath.join(pkgPath, fslib_1.Filename.nodeModules);
132
+ const concurrentPromises = [];
133
+ // Retrieve what's currently inside the package's true nm folder. We
134
+ // will use that to figure out what are the extraneous entries we'll
135
+ // need to remove.
136
+ const extraneous = await getNodeModulesListing(nmPath);
137
+ for (const [descriptor, dependency] of dependencies) {
138
+ // Downgrade virtual workspaces (cf isPnpmVirtualCompatible's documentation)
139
+ let targetDependency = dependency;
140
+ if (!isPnpmVirtualCompatible(dependency, { project: this.opts.project })) {
141
+ this.opts.report.reportWarning(core_1.MessageName.UNNAMED, `The pnpm linker doesn't support providing different versions to workspaces' peer dependencies`);
142
+ targetDependency = core_1.structUtils.devirtualizeLocator(dependency);
143
+ }
144
+ const depSrcPath = this.customData.pathByLocator.get(targetDependency.locatorHash);
145
+ if (typeof depSrcPath === `undefined`)
146
+ throw new Error(`Assertion failed: Expected the package to have been registered (${core_1.structUtils.stringifyLocator(dependency)})`);
147
+ const name = core_1.structUtils.stringifyIdent(descriptor);
148
+ const depDstPath = fslib_1.ppath.join(nmPath, name);
149
+ const depLinkPath = fslib_1.ppath.relative(fslib_1.ppath.dirname(depDstPath), depSrcPath);
150
+ const existing = extraneous.get(name);
151
+ extraneous.delete(name);
152
+ concurrentPromises.push(Promise.resolve().then(async () => {
153
+ // No need to update the symlink if it's already the correct one
154
+ if (existing) {
155
+ if (existing.isSymbolicLink() && await fslib_1.xfs.readlinkPromise(depDstPath) === depLinkPath) {
156
+ return;
157
+ }
158
+ else {
159
+ await fslib_1.xfs.removePromise(depDstPath);
160
+ }
161
+ }
162
+ await fslib_1.xfs.mkdirpPromise(fslib_1.ppath.dirname(depDstPath));
163
+ if (process.platform == `win32`) {
164
+ await fslib_1.xfs.symlinkPromise(depSrcPath, depDstPath, `junction`);
165
+ }
166
+ else {
167
+ await fslib_1.xfs.symlinkPromise(depLinkPath, depDstPath);
168
+ }
169
+ }));
170
+ }
171
+ concurrentPromises.push(cleanNodeModules(nmPath, extraneous));
172
+ await Promise.all(concurrentPromises);
173
+ });
174
+ }
175
+ async attachExternalDependents(locator, dependentPaths) {
176
+ throw new Error(`External dependencies haven't been implemented for the pnpm linker`);
177
+ }
178
+ async finalizeInstall() {
179
+ const storeLocation = getStoreLocation(this.opts.project);
180
+ if (this.opts.project.configuration.get(`nodeLinker`) !== `pnpm`) {
181
+ await fslib_1.xfs.removePromise(storeLocation);
182
+ }
183
+ else {
184
+ const removals = [];
185
+ const expectedEntries = new Set();
186
+ for (const packageLocation of this.customData.pathByLocator.values()) {
187
+ const subpath = fslib_1.ppath.contains(storeLocation, packageLocation);
188
+ if (subpath !== null) {
189
+ const [storeEntry, /* Filename.nodeModules */ , ...identComponents] = subpath.split(fslib_1.ppath.sep);
190
+ expectedEntries.add(storeEntry);
191
+ const storeEntryPath = fslib_1.ppath.join(storeLocation, storeEntry);
192
+ removals.push(fslib_1.xfs.readdirPromise(storeEntryPath)
193
+ .then(entries => {
194
+ return Promise.all(entries.map(async (entry) => {
195
+ const p = fslib_1.ppath.join(storeEntryPath, entry);
196
+ if (entry === fslib_1.Filename.nodeModules) {
197
+ const extraneous = await getNodeModulesListing(p);
198
+ extraneous.delete(identComponents.join(fslib_1.ppath.sep));
199
+ return cleanNodeModules(p, extraneous);
200
+ }
201
+ else {
202
+ return fslib_1.xfs.removePromise(p);
203
+ }
204
+ }));
205
+ })
206
+ .catch(error => {
207
+ if (error.code !== `ENOENT`) {
208
+ throw error;
209
+ }
210
+ }));
211
+ }
212
+ }
213
+ let storeRecords;
214
+ try {
215
+ storeRecords = await fslib_1.xfs.readdirPromise(storeLocation);
216
+ }
217
+ catch {
218
+ storeRecords = [];
219
+ }
220
+ for (const record of storeRecords)
221
+ if (!expectedEntries.has(record))
222
+ removals.push(fslib_1.xfs.removePromise(fslib_1.ppath.join(storeLocation, record)));
223
+ await Promise.all(removals);
224
+ }
225
+ // Wait for the package installs to catch up
226
+ await this.asyncActions.wait(),
227
+ await removeIfEmpty(storeLocation);
228
+ await removeIfEmpty(getNodeModulesLocation(this.opts.project));
229
+ return {
230
+ customData: this.customData,
231
+ };
232
+ }
233
+ }
234
+ function getCustomDataKey() {
235
+ return JSON.stringify({
236
+ name: `PnpmInstaller`,
237
+ version: 2,
238
+ });
239
+ }
240
+ function getNodeModulesLocation(project) {
241
+ return fslib_1.ppath.join(project.cwd, fslib_1.Filename.nodeModules);
242
+ }
243
+ function getStoreLocation(project) {
244
+ return fslib_1.ppath.join(getNodeModulesLocation(project), `.store`);
245
+ }
246
+ function getPackageLocation(locator, { project }) {
247
+ const pkgKey = core_1.structUtils.slugifyLocator(locator);
248
+ const prefixPath = core_1.structUtils.getIdentVendorPath(locator);
249
+ const pkgPath = fslib_1.ppath.join(getStoreLocation(project), pkgKey, prefixPath);
250
+ return pkgPath;
251
+ }
252
+ function isPnpmVirtualCompatible(locator, { project }) {
253
+ // The pnpm install strategy has a limitation: because Node would always
254
+ // resolve symbolic path to their true location, and because we can't just
255
+ // copy-paste workspaces like we do with normal dependencies, we can't give
256
+ // multiple dependency sets to the same workspace based on how its peer
257
+ // dependencies are satisfied by its dependents (like PnP can).
258
+ //
259
+ // For this reason, we ignore all virtual instances of workspaces, and
260
+ // instead have to rely on the user being aware of this caveat.
261
+ //
262
+ // TODO: Perhaps we could implement an error message when we detect multiple
263
+ // sets in a way that can't be reproduced on disk?
264
+ return !core_1.structUtils.isVirtualLocator(locator) || !project.tryWorkspaceByLocator(locator);
265
+ }
266
+ async function getNodeModulesListing(nmPath) {
267
+ const listing = new Map();
268
+ let fsListing = [];
269
+ try {
270
+ fsListing = await fslib_1.xfs.readdirPromise(nmPath, { withFileTypes: true });
271
+ }
272
+ catch (err) {
273
+ if (err.code !== `ENOENT`) {
274
+ throw err;
275
+ }
276
+ }
277
+ try {
278
+ for (const entry of fsListing) {
279
+ if (entry.name.startsWith(`.`))
280
+ continue;
281
+ if (entry.name.startsWith(`@`)) {
282
+ const scopeListing = await fslib_1.xfs.readdirPromise(fslib_1.ppath.join(nmPath, entry.name), { withFileTypes: true });
283
+ if (scopeListing.length === 0) {
284
+ listing.set(entry.name, entry);
285
+ }
286
+ else {
287
+ for (const subEntry of scopeListing) {
288
+ listing.set(`${entry.name}/${subEntry.name}`, subEntry);
289
+ }
290
+ }
291
+ }
292
+ else {
293
+ listing.set(entry.name, entry);
294
+ }
295
+ }
296
+ }
297
+ catch (err) {
298
+ if (err.code !== `ENOENT`) {
299
+ throw err;
300
+ }
301
+ }
302
+ return listing;
303
+ }
304
+ async function cleanNodeModules(nmPath, extraneous) {
305
+ var _a;
306
+ const removeNamePromises = [];
307
+ const scopesToRemove = new Set();
308
+ for (const name of extraneous.keys()) {
309
+ removeNamePromises.push(fslib_1.xfs.removePromise(fslib_1.ppath.join(nmPath, name)));
310
+ const scope = (_a = core_1.structUtils.tryParseIdent(name)) === null || _a === void 0 ? void 0 : _a.scope;
311
+ if (scope) {
312
+ scopesToRemove.add(`@${scope}`);
313
+ }
314
+ }
315
+ return Promise.all(removeNamePromises).then(() => Promise.all([...scopesToRemove].map(scope => removeIfEmpty(fslib_1.ppath.join(nmPath, scope)))));
316
+ }
317
+ async function removeIfEmpty(dir) {
318
+ try {
319
+ await fslib_1.xfs.rmdirPromise(dir);
320
+ }
321
+ catch (error) {
322
+ if (error.code !== `ENOENT` && error.code !== `ENOTEMPTY`) {
323
+ throw error;
324
+ }
325
+ }
326
+ }
package/lib/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { Plugin } from '@yarnpkg/core';
2
+ declare const plugin: Plugin;
3
+ export default plugin;
package/lib/index.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const PnpmLinker_1 = require("./PnpmLinker");
4
+ const plugin = {
5
+ linkers: [
6
+ PnpmLinker_1.PnpmLinker,
7
+ ],
8
+ };
9
+ // eslint-disable-next-line arca/no-default-export
10
+ exports.default = plugin;
package/package.json CHANGED
@@ -1,23 +1,23 @@
1
1
  {
2
2
  "name": "@yarnpkg/plugin-pnpm",
3
- "version": "1.0.0",
3
+ "version": "1.1.0-rc.10",
4
4
  "license": "BSD-2-Clause",
5
5
  "main": "./lib/index.js",
6
6
  "dependencies": {
7
- "@yarnpkg/fslib": "^2.6.0",
8
- "@yarnpkg/plugin-pnp": "^3.1.0",
9
- "@yarnpkg/plugin-stage": "^3.1.0",
10
- "clipanion": "^3.0.1",
7
+ "@yarnpkg/fslib": "^2.6.1-rc.7",
8
+ "@yarnpkg/plugin-pnp": "^3.2.0-rc.2",
9
+ "@yarnpkg/plugin-stage": "^3.1.1",
10
+ "clipanion": "^3.2.0-rc.4",
11
11
  "p-limit": "^2.2.0",
12
12
  "tslib": "^1.13.0"
13
13
  },
14
14
  "peerDependencies": {
15
- "@yarnpkg/cli": "^3.1.0",
16
- "@yarnpkg/core": "^3.1.0"
15
+ "@yarnpkg/cli": "^3.2.0-rc.12",
16
+ "@yarnpkg/core": "^3.2.0-rc.12"
17
17
  },
18
18
  "devDependencies": {
19
- "@yarnpkg/cli": "^3.1.0",
20
- "@yarnpkg/core": "^3.1.0"
19
+ "@yarnpkg/cli": "^3.2.0-rc.12",
20
+ "@yarnpkg/core": "^3.2.0-rc.12"
21
21
  },
22
22
  "repository": {
23
23
  "type": "git",
@@ -38,5 +38,6 @@
38
38
  "engines": {
39
39
  "node": ">=12 <14 || 14.2 - 14.9 || >14.10.0"
40
40
  },
41
+ "stableVersion": "1.0.1",
41
42
  "typings": "./lib/index.d.ts"
42
43
  }