diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3743654fa..14e0624ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,9 @@ jobs: env: TARGET_BRANCH: ${{github.event.pull_request.base.ref}} + - name: 'Check for type errors' + run: yarn typecheck + build: strategy: fail-fast: false diff --git a/.pnp.cjs b/.pnp.cjs index c7ff741ff..406619db7 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -43,7 +43,8 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@babel/preset-typescript", "virtual:0bda696f47fa4339976f909c007f0dc954386da51b8923bf264010d9929618071aa538c29b76d4b6c53f7388a7d83508f2a5027eb81cfc74b39b9d4a0b1a8c5a#npm:7.13.0"], ["@types/debug", "npm:4.1.5"], ["@types/jest", "npm:26.0.23"], - ["@types/node", "npm:13.9.2"], + ["@types/node", "npm:17.0.10"], + ["@types/rimraf", "npm:3.0.2"], ["@types/semver", "npm:7.1.0"], ["@types/tar", "npm:4.0.3"], ["@types/which", "npm:1.3.2"], @@ -59,6 +60,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["eslint-plugin-arca", "npm:0.9.5"], ["jest", "npm:26.6.3"], ["nock", "npm:13.0.4"], + ["rimraf", "npm:3.0.2"], ["semver", "npm:7.1.3"], ["supports-color", "npm:7.1.0"], ["tar", "npm:6.0.1"], @@ -1461,6 +1463,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["@types/glob", [ + ["npm:7.2.0", { + "packageLocation": "./.yarn/cache/@types-glob-npm-7.2.0-772334bf9a-6ae717fedf.zip/node_modules/@types/glob/", + "packageDependencies": [ + ["@types/glob", "npm:7.2.0"], + ["@types/minimatch", "npm:3.0.5"], + ["@types/node", "npm:13.9.0"] + ], + "linkType": "HARD", + }] + ]], ["@types/graceful-fs", [ ["npm:4.1.5", { "packageLocation": "./.yarn/cache/@types-graceful-fs-npm-4.1.5-91d62e1050-d076bb61f4.zip/node_modules/@types/graceful-fs/", @@ -1527,6 +1540,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["@types/minimatch", [ + ["npm:3.0.5", { + "packageLocation": "./.yarn/cache/@types-minimatch-npm-3.0.5-802bb0797f-c41d136f67.zip/node_modules/@types/minimatch/", + "packageDependencies": [ + ["@types/minimatch", "npm:3.0.5"] + ], + "linkType": "HARD", + }] + ]], ["@types/minipass", [ ["npm:2.2.0", { "packageLocation": "./.yarn/cache/@types-minipass-npm-2.2.0-78f6142b4d-a6e7d103ce.zip/node_modules/@types/minipass/", @@ -1545,10 +1567,10 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ], "linkType": "HARD", }], - ["npm:13.9.2", { - "packageLocation": "./.yarn/cache/@types-node-npm-13.9.2-d069dc6c73-58e82c03d3.zip/node_modules/@types/node/", + ["npm:17.0.10", { + "packageLocation": "./.yarn/cache/@types-node-npm-17.0.10-c0e46c1462-979e83d642.zip/node_modules/@types/node/", "packageDependencies": [ - ["@types/node", "npm:13.9.2"] + ["@types/node", "npm:17.0.10"] ], "linkType": "HARD", }] @@ -1571,6 +1593,17 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["@types/rimraf", [ + ["npm:3.0.2", { + "packageLocation": "./.yarn/cache/@types-rimraf-npm-3.0.2-dd6eb5de99-b47fa302f4.zip/node_modules/@types/rimraf/", + "packageDependencies": [ + ["@types/rimraf", "npm:3.0.2"], + ["@types/glob", "npm:7.2.0"], + ["@types/node", "npm:13.9.0"] + ], + "linkType": "HARD", + }] + ]], ["@types/semver", [ ["npm:7.1.0", { "packageLocation": "./.yarn/cache/@types-semver-npm-7.1.0-ce58bbc3b4-1f10d876b1.zip/node_modules/@types/semver/", @@ -3059,7 +3092,8 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@babel/preset-typescript", "virtual:0bda696f47fa4339976f909c007f0dc954386da51b8923bf264010d9929618071aa538c29b76d4b6c53f7388a7d83508f2a5027eb81cfc74b39b9d4a0b1a8c5a#npm:7.13.0"], ["@types/debug", "npm:4.1.5"], ["@types/jest", "npm:26.0.23"], - ["@types/node", "npm:13.9.2"], + ["@types/node", "npm:17.0.10"], + ["@types/rimraf", "npm:3.0.2"], ["@types/semver", "npm:7.1.0"], ["@types/tar", "npm:4.0.3"], ["@types/which", "npm:1.3.2"], @@ -3075,6 +3109,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["eslint-plugin-arca", "npm:0.9.5"], ["jest", "npm:26.6.3"], ["nock", "npm:13.0.4"], + ["rimraf", "npm:3.0.2"], ["semver", "npm:7.1.3"], ["supports-color", "npm:7.1.0"], ["tar", "npm:6.0.1"], diff --git a/.yarn/cache/@types-glob-npm-7.2.0-772334bf9a-6ae717fedf.zip b/.yarn/cache/@types-glob-npm-7.2.0-772334bf9a-6ae717fedf.zip new file mode 100644 index 000000000..f3ad9aedc Binary files /dev/null and b/.yarn/cache/@types-glob-npm-7.2.0-772334bf9a-6ae717fedf.zip differ diff --git a/.yarn/cache/@types-minimatch-npm-3.0.5-802bb0797f-c41d136f67.zip b/.yarn/cache/@types-minimatch-npm-3.0.5-802bb0797f-c41d136f67.zip new file mode 100644 index 000000000..11730d3c3 Binary files /dev/null and b/.yarn/cache/@types-minimatch-npm-3.0.5-802bb0797f-c41d136f67.zip differ diff --git a/.yarn/cache/@types-node-npm-13.9.2-d069dc6c73-58e82c03d3.zip b/.yarn/cache/@types-node-npm-13.9.2-d069dc6c73-58e82c03d3.zip deleted file mode 100644 index a007e2981..000000000 Binary files a/.yarn/cache/@types-node-npm-13.9.2-d069dc6c73-58e82c03d3.zip and /dev/null differ diff --git a/.yarn/cache/@types-node-npm-17.0.10-c0e46c1462-979e83d642.zip b/.yarn/cache/@types-node-npm-17.0.10-c0e46c1462-979e83d642.zip new file mode 100644 index 000000000..7b54e51a9 Binary files /dev/null and b/.yarn/cache/@types-node-npm-17.0.10-c0e46c1462-979e83d642.zip differ diff --git a/.yarn/cache/@types-rimraf-npm-3.0.2-dd6eb5de99-b47fa302f4.zip b/.yarn/cache/@types-rimraf-npm-3.0.2-dd6eb5de99-b47fa302f4.zip new file mode 100644 index 000000000..5cf59c270 Binary files /dev/null and b/.yarn/cache/@types-rimraf-npm-3.0.2-dd6eb5de99-b47fa302f4.zip differ diff --git a/mkshims.ts b/mkshims.ts index 869a763f9..992f85050 100644 --- a/mkshims.ts +++ b/mkshims.ts @@ -5,9 +5,14 @@ import path from 'path'; import {Engine} from './sources/Engine'; import {SupportedPackageManagerSet} from './sources/types'; -const EXCLUDE_SHIMS = new Set([ - `vcc.js`, -]); +function shouldGenerateShim(name: string) { + if (name === 'vcc.js') { + return false; + } else if (name.startsWith('vendors')) { + return false; + } + return true; +} const engine = new Engine(); @@ -38,7 +43,7 @@ async function main() { } for (const binaryName of fs.readdirSync(distDir)) { - if (EXCLUDE_SHIMS.has(binaryName)) + if (shouldGenerateShim(binaryName) === false) continue; await cmdShim(path.join(distDir, binaryName), path.join(shimsDir, path.basename(binaryName, `.js`)), {createCmdFile: true}); @@ -55,12 +60,16 @@ async function main() { const remapPath = (p: string) => path.resolve(__dirname, path.relative(virtualNodewinDir, p)); const easyStatFs = Object.assign(Object.create(fs), { - readFile: (p: string, encoding: string, cb: (err: any, str: string) => void) => fs.readFile(remapPath(p), encoding, cb), + readFile: (p: string, encoding: BufferEncoding, cb: (err: any, str: string) => void) => fs.readFile(remapPath(p), encoding, cb), stat: (p: string, cb: () => void) => fs.stat(remapPath(p), cb), }); - for (const binaryName of fs.readdirSync(distDir)) + for (const binaryName of fs.readdirSync(distDir)) { + if (shouldGenerateShim(binaryName) === false) + continue; + await cmdShim(path.join(virtualNodewinDir, `dist/${binaryName}`), path.join(physicalNodewinDir, path.basename(binaryName, `.js`)), {createCmdFile: true, fs: easyStatFs}); + } console.log(`All shims have been generated.`); } diff --git a/package.json b/package.json index 37c981139..a57195034 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "@babel/preset-typescript": "^7.13.0", "@types/debug": "^4.1.5", "@types/jest": "^26.0.23", - "@types/node": "^13.9.2", + "@types/node": "^17.0.10", + "@types/rimraf": "^3.0.2", "@types/semver": "^7.1.0", "@types/tar": "^4.0.3", "@types/which": "^1.3.2", @@ -43,6 +44,7 @@ "eslint-plugin-arca": "^0.9.5", "jest": "^26.0.0", "nock": "^13.0.4", + "rimraf": "^3.0.2", "semver": "^7.1.3", "supports-color": "^7.1.0", "tar": "^6.0.1", @@ -56,10 +58,11 @@ "which": "^2.0.2" }, "scripts": { - "build": "rm -rf dist && webpack && ts-node ./mkshims.ts", + "build": "rm -rf dist shims && webpack && ts-node ./mkshims.ts", "corepack": "ts-node ./sources/main.ts", "prepack": "node ./.yarn/releases/*.*js build", "postpack": "rm -rf dist shims", + "typecheck": "tsc --noEmit", "test": "yarn jest" }, "files": [ diff --git a/sources/corepackUtils.ts b/sources/corepackUtils.ts index 7def4c802..f2359b0e8 100644 --- a/sources/corepackUtils.ts +++ b/sources/corepackUtils.ts @@ -87,39 +87,50 @@ export async function installVersion(installTarget: string, locator: Locator, {s const url = spec.url.replace(`{}`, locator.reference); debugUtils.log(`Installing ${locator.name}@${locator.reference} from ${url}`); - return await fsUtils.mutex(installFolder, async () => { - // Creating a temporary folder inside the install folder means that we - // are sure it'll be in the same drive as the destination, so we can - // just move it there atomically once we are done + // Creating a temporary folder inside the install folder means that we + // are sure it'll be in the same drive as the destination, so we can + // just move it there atomically once we are done - const tmpFolder = folderUtils.getTemporaryFolder(installTarget); - const stream = await httpUtils.fetchUrlStream(url); + const tmpFolder = folderUtils.getTemporaryFolder(installTarget); + const stream = await httpUtils.fetchUrlStream(url); - const parsedUrl = new URL(url); - const ext = path.posix.extname(parsedUrl.pathname); + const parsedUrl = new URL(url); + const ext = path.posix.extname(parsedUrl.pathname); - let outputFile: string | null = null; + let outputFile: string | null = null; - let sendTo: any; - if (ext === `.tgz`) { - sendTo = tar.x({strip: 1, cwd: tmpFolder}); - } else if (ext === `.js`) { - outputFile = path.join(tmpFolder, path.posix.basename(parsedUrl.pathname)); - sendTo = fs.createWriteStream(outputFile); - } + let sendTo: any; + if (ext === `.tgz`) { + sendTo = tar.x({strip: 1, cwd: tmpFolder}); + } else if (ext === `.js`) { + outputFile = path.join(tmpFolder, path.posix.basename(parsedUrl.pathname)); + sendTo = fs.createWriteStream(outputFile); + } - stream.pipe(sendTo); + stream.pipe(sendTo); - await new Promise(resolve => { - sendTo.on(`finish`, resolve); - }); + await new Promise(resolve => { + sendTo.on(`finish`, resolve); + }); - await fs.promises.mkdir(path.dirname(installFolder), {recursive: true}); + await fs.promises.mkdir(path.dirname(installFolder), {recursive: true}); + try { await fs.promises.rename(tmpFolder, installFolder); + } catch (err) { + if ( + err.code === `ENOTEMPTY` || + // On Windows the error code is EPERM so we check if it is a directory + (err.code === `EPERM` && (await fs.promises.stat(installFolder)).isDirectory()) + ) { + debugUtils.log(`Another instance of corepack installed ${locator.name}@${locator.reference}`); + await fsUtils.rimraf(tmpFolder); + } else { + throw err; + } + } - debugUtils.log(`Install finished`); - return installFolder; - }); + debugUtils.log(`Install finished`); + return installFolder; } export async function runVersion(installSpec: { location: string, spec: PackageManagerSpec }, locator: Locator, binName: string, args: Array, context: Context) { diff --git a/sources/fsUtils.ts b/sources/fsUtils.ts index 660052f58..e9f241964 100644 --- a/sources/fsUtils.ts +++ b/sources/fsUtils.ts @@ -1,3 +1,24 @@ -export async function mutex(p: string, cb: () => Promise) { - return await cb(); +import fs from 'fs'; + +export async function rimraf(path: string) { + const [major, minor] = process.versions.node.split(`.`).map(section => Number(section)); + + if (major > 14 || (major === 14 && minor >= 14)) { + // rm was added in v14.14.0 + return fs.promises.rm(path, {recursive: true}); + } else if (major > 12 || (major === 12 && minor >= 10)) { + // rmdir got support for recursive in v12.10.0 and was deprecated in v14.14.0 + return fs.promises.rmdir(path, {recursive: true}); + } else { + const rimraf = await import(`rimraf`); + return new Promise((resolve, reject) => { + rimraf.default(path, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } } diff --git a/tests/main.test.ts b/tests/main.test.ts index ec83898e3..13f288db3 100644 --- a/tests/main.test.ts +++ b/tests/main.test.ts @@ -328,3 +328,30 @@ it(`should support running package managers with bin array`, async () => { }); }); }); + +it(`should handle parallel installs`, async () => { + await xfs.mktempPromise(async cwd => { + await xfs.writeJsonPromise(ppath.join(cwd, `package.json` as Filename), { + packageManager: `yarn@2.2.2`, + }); + + await expect(Promise.all([ + runCli(cwd, [`yarn`, `--version`]), + runCli(cwd, [`yarn`, `--version`]), + runCli(cwd, [`yarn`, `--version`]), + ])).resolves.toMatchObject([ + { + stdout: `2.2.2\n`, + exitCode: 0, + }, + { + stdout: `2.2.2\n`, + exitCode: 0, + }, + { + stdout: `2.2.2\n`, + exitCode: 0, + }, + ]); + }); +}); diff --git a/tests/nock/5ed9c2e2a56a83b54950e0282b2c406c.dat b/tests/nock/5ed9c2e2a56a83b54950e0282b2c406c.dat new file mode 100644 index 000000000..d71911b1f Binary files /dev/null and b/tests/nock/5ed9c2e2a56a83b54950e0282b2c406c.dat differ diff --git a/tsconfig.json b/tsconfig.json index a47b6e082..ec82ea499 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "lib": ["dom", "es2017", "esnext.asynciterable"], "module": "commonjs", "resolveJsonModule": true, + "skipLibCheck": true, "target": "es2017" } } diff --git a/yarn.lock b/yarn.lock index 581482233..b3886c6f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1065,6 +1065,16 @@ __metadata: languageName: node linkType: hard +"@types/glob@npm:*": + version: 7.2.0 + resolution: "@types/glob@npm:7.2.0" + dependencies: + "@types/minimatch": "*" + "@types/node": "*" + checksum: 6ae717fedfdfdad25f3d5a568323926c64f52ef35897bcac8aca8e19bc50c0bd84630bbd063e5d52078b2137d8e7d3c26eabebd1a2f03ff350fff8a91e79fc19 + languageName: node + linkType: hard + "@types/graceful-fs@npm:^4.1.2": version: 4.1.5 resolution: "@types/graceful-fs@npm:4.1.5" @@ -1123,6 +1133,13 @@ __metadata: languageName: node linkType: hard +"@types/minimatch@npm:*": + version: 3.0.5 + resolution: "@types/minimatch@npm:3.0.5" + checksum: c41d136f67231c3131cf1d4ca0b06687f4a322918a3a5adddc87ce90ed9dbd175a3610adee36b106ae68c0b92c637c35e02b58c8a56c424f71d30993ea220b92 + languageName: node + linkType: hard + "@types/minipass@npm:*": version: 2.2.0 resolution: "@types/minipass@npm:2.2.0" @@ -1139,10 +1156,10 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^13.9.2": - version: 13.9.2 - resolution: "@types/node@npm:13.9.2" - checksum: 58e82c03d3e51981520119ec5fd104c43762d180106a3ebc7c2c28f1d567b5f505d3d527acc22e7dae8ca9fdaaeb562d3cd77f801f55425eb664afb73c63039b +"@types/node@npm:^17.0.10": + version: 17.0.10 + resolution: "@types/node@npm:17.0.10" + checksum: 979e83d642a2b4f18fa1a4233f884822c05abc7acd0836024aa964187f8446432b21f7913e72fe2b3927c4a811c27a0b6cd60ac7c4ac6a6762cfbab70782aa6a languageName: node linkType: hard @@ -1160,6 +1177,16 @@ __metadata: languageName: node linkType: hard +"@types/rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "@types/rimraf@npm:3.0.2" + dependencies: + "@types/glob": "*" + "@types/node": "*" + checksum: b47fa302f46434cba704d20465861ad250df79467d3d289f9d6490d3aeeb41e8cb32dd80bd1a8fd833d1e185ac719fbf9be12e05ad9ce9be094d8ee8f1405347 + languageName: node + linkType: hard + "@types/semver@npm:^7.1.0": version: 7.1.0 resolution: "@types/semver@npm:7.1.0" @@ -2365,7 +2392,8 @@ __metadata: "@babel/preset-typescript": ^7.13.0 "@types/debug": ^4.1.5 "@types/jest": ^26.0.23 - "@types/node": ^13.9.2 + "@types/node": ^17.0.10 + "@types/rimraf": ^3.0.2 "@types/semver": ^7.1.0 "@types/tar": ^4.0.3 "@types/which": ^1.3.2 @@ -2381,6 +2409,7 @@ __metadata: eslint-plugin-arca: ^0.9.5 jest: ^26.0.0 nock: ^13.0.4 + rimraf: ^3.0.2 semver: ^7.1.3 supports-color: ^7.1.0 tar: ^6.0.1 @@ -6116,7 +6145,7 @@ resolve@^1.3.2: languageName: node linkType: hard -"rimraf@npm:^3.0.0": +"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" dependencies: