diff --git a/CHANGELOG.md b/CHANGELOG.md index 6117ff09e9..3013ef545b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Please add one entry in this file for each change in Yarn's behavior. Use the sa ## Master +- Adds initial support for PnP on Windows + + [#6447](https://github.com/yarnpkg/yarn/pull/6447) - [**John-David Dalton**](https://twitter.com/jdalton) + - Adds a special logic to PnP for ESLint compatibility (temporary, until [eslint/eslint#10125](https://github.com/eslint/eslint/issues/10125) is fixed) [#6449](https://github.com/yarnpkg/yarn/pull/6449) - [**Maƫl Nison**](https://twitter.com/arcanis) diff --git a/src/config.js b/src/config.js index 4374375b9b..225d41066f 100644 --- a/src/config.js +++ b/src/config.js @@ -396,11 +396,16 @@ export default class Config { } if (process.platform === 'win32') { - if (this.plugnplayEnabled) { - this.reporter.warn(this.reporter.lang('plugnplayWindowsSupport')); + const cacheRootFolderDrive = path.parse(this._cacheRootFolder).root; + const lockfileFolderDrive = path.parse(this.lockfileFolder).root; + + if (cacheRootFolderDrive !== lockfileFolderDrive) { + if (this.plugnplayEnabled) { + this.reporter.warn(this.reporter.lang('plugnplayWindowsSupport')); + } + this.plugnplayEnabled = false; + this.plugnplayPersist = false; } - this.plugnplayEnabled = false; - this.plugnplayPersist = false; } this.plugnplayShebang = String(this.getOption('plugnplay-shebang') || '') || '/usr/bin/env node'; diff --git a/src/reporters/lang/en.js b/src/reporters/lang/en.js index 9e5f679069..1f0468c23b 100644 --- a/src/reporters/lang/en.js +++ b/src/reporters/lang/en.js @@ -357,8 +357,7 @@ const messages = { unplugDisabled: "Packages can only be unplugged when Plug'n'Play is enabled.", - plugnplayWindowsSupport: - "Plug'n'Play is ignored on Windows for now - contributions welcome! https://github.com/yarnpkg/yarn/issues/6402", + plugnplayWindowsSupport: "Plug'n'Play on Windows doesn't support the cache and project to be kept on separate drives", packageInstalledWithBinaries: 'Installed $0 with binaries:', packageHasBinaries: '$0 has binaries:', diff --git a/src/util/generate-pnp-map-api.tpl.js b/src/util/generate-pnp-map-api.tpl.js index ece2496ce8..1eeeab5cfa 100644 --- a/src/util/generate-pnp-map-api.tpl.js +++ b/src/util/generate-pnp-map-api.tpl.js @@ -22,15 +22,18 @@ const blacklistedLocator = {name: NaN, reference: NaN}; const patchedModules = new Map(); const fallbackLocators = [topLevelLocator]; -// Splits a require request into its components, or return null if the request is a file path -const pathRegExp = /^(?!\.{0,2}(?:\/|$))((?:@[^\/]+\/)?[^\/]+)\/?(.*|)$/; +// Matches backslashes of Windows paths +const backwardSlashRegExp = /\\/g; + +// Matches if the path must point to a directory (ie ends with /) +const isDirRegExp = /\/$/; // Matches if the path starts with a valid path qualifier (./, ../, /) // eslint-disable-next-line no-unused-vars const isStrictRegExp = /^\.{0,2}\//; -// Matches if the path must point to a directory (ie ends with /) -const isDirRegExp = /\/$/; +// Splits a require request into its components, or return null if the request is a file path +const pathRegExp = /^(?!\.{0,2}(?:\/|$))((?:@[^\/]+\/)?[^\/]+)\/?(.*|)$/; // Keep a reference around ("module" is a common name in this context, so better rename it to something more significant) const pnpModule = module; @@ -229,6 +232,15 @@ function makeFakeModule(path) { return fakeModule; } +/** + * Normalize path to posix format. + */ + +// eslint-disable-next-line no-unused-vars +function normalizePath(fsPath) { + return process.platform === 'win32' ? fsPath.replace(backwardSlashRegExp, '/') : fsPath; +} + /** * Forward the resolution to the next resolver (usually the native one) */ diff --git a/src/util/generate-pnp-map.js b/src/util/generate-pnp-map.js index 3e78cdba74..7956721db9 100644 --- a/src/util/generate-pnp-map.js +++ b/src/util/generate-pnp-map.js @@ -11,6 +11,8 @@ const crypto = require('crypto'); const invariant = require('invariant'); const path = require('path'); +const backwardSlashRegExp = /\\/g; + const OFFLINE_CACHE_EXTENSION = `.zip`; type PackageInformation = {| @@ -100,7 +102,7 @@ function generateFindPackageLocator(packageInformationStores: PackageInformation // Generate a function that, given a file path, returns the associated package name code += `exports.findPackageLocator = function findPackageLocator(location) {\n`; - code += ` let relativeLocation = path.relative(__dirname, location);\n`; + code += ` let relativeLocation = normalizePath(path.relative(__dirname, location));\n`; code += `\n`; code += ` if (!relativeLocation.match(isStrictRegExp))\n`; code += ` relativeLocation = \`./\${relativeLocation}\`;\n`; @@ -136,7 +138,7 @@ async function getPackageInformationStores( const blacklistedLocations: Set = new Set(); const getCachePath = (fsPath: string) => { - const cacheRelativePath = path.relative(config.cacheFolder, fsPath); + const cacheRelativePath = normalizePath(path.relative(config.cacheFolder, fsPath)); // if fsPath is not inside cacheRelativePath, we just skip it if (cacheRelativePath.match(/^\.\.\//)) { @@ -164,8 +166,12 @@ async function getPackageInformationStores( return path.resolve(offlineCacheFolder, `${cacheEntry}${OFFLINE_CACHE_EXTENSION}`, internalPath.join('/')); }; + const normalizePath = (fsPath: string) => { + return process.platform === 'win32' ? fsPath.replace(backwardSlashRegExp, '/') : fsPath; + }; + const normalizeDirectoryPath = (fsPath: string) => { - let relativePath = path.relative(targetDirectory, resolveOfflineCacheFolder(fsPath)); + let relativePath = normalizePath(path.relative(targetDirectory, resolveOfflineCacheFolder(fsPath))); if (!relativePath.match(/^\.{0,2}\//)) { relativePath = `./${relativePath}`;