ci: test on Windows

close #3023
This commit is contained in:
Zoltan Kochan
2020-12-18 23:32:37 +02:00
parent efd81fb105
commit 1309703932
24 changed files with 117 additions and 44 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/headless": patch
---
Don't link skipped optional dependencies to the node_modules root.

View File

@@ -0,0 +1,5 @@
---
"@pnpm/modules-cleaner": patch
---
Symlinks to hoisted dependencies should be removed during pruning.

View File

@@ -0,0 +1,5 @@
---
"pnpm": patch
---
Fixed regression with pnpm server on Windows.

View File

@@ -4,9 +4,6 @@ on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
@@ -14,8 +11,13 @@ jobs:
- '10.16'
- '12'
- '14'
platform:
- ubuntu-latest
- windows-latest
name: '${{matrix.platform}} / Node.js ${{ matrix.node }}'
runs-on: ${{matrix.platform}}
name: ${{ matrix.node }} (Linux)
steps:
- name: Configure Git
run: |

View File

@@ -9,6 +9,9 @@
"../../typings/**/*.d.ts"
],
"references": [
{
"path": "../../privatePackages/prepare"
},
{
"path": "../constants"
},

View File

@@ -83,6 +83,7 @@
"@pnpm/lockfile-utils": "workspace:2.0.20",
"@pnpm/modules-cleaner": "workspace:10.0.16",
"@pnpm/modules-yaml": "workspace:8.0.5",
"@pnpm/package-is-installable": "workspace:^4.0.18",
"@pnpm/package-requester": "workspace:12.2.0",
"@pnpm/pkgid-to-filename": "^3.0.0",
"@pnpm/read-package-json": "workspace:3.1.8",

View File

@@ -46,6 +46,7 @@ import {
IncludedDependencies,
write as writeModulesYaml,
} from '@pnpm/modules-yaml'
import packageIsInstallable from '@pnpm/package-is-installable'
import pkgIdToFilename from '@pnpm/pkgid-to-filename'
import { fromDir as readPackageFromDir } from '@pnpm/read-package-json'
import { readProjectManifestOnly, safeReadProjectManifestOnly } from '@pnpm/read-project-manifest'
@@ -218,6 +219,8 @@ export default async (opts: HeadlessOptions) => {
lockfileDir,
skipped,
virtualStoreDir,
nodeVersion: opts.currentEngine.nodeVersion,
pnpmVersion: opts.currentEngine.pnpmVersion,
} as LockfileToDepGraphOptions
)
if (opts.enablePnp) {
@@ -508,15 +511,18 @@ function linkRootPackages (
}
interface LockfileToDepGraphOptions {
engineStrict: boolean
force: boolean
include: IncludedDependencies
importerIds: string[]
include: IncludedDependencies
lockfileDir: string
nodeVersion: string
pnpmVersion: string
registries: Registries
sideEffectsCacheRead: boolean
skipped: Set<string>
storeController: StoreController
storeDir: string
registries: Registries
sideEffectsCacheRead: boolean
virtualStoreDir: string
}
@@ -535,10 +541,29 @@ async function lockfileToDepGraph (
if (opts.skipped.has(depPath)) return
const pkgSnapshot = lockfile.packages![depPath]
// TODO: optimize. This info can be already returned by pkgSnapshotToResolution()
const pkgName = nameVerFromPkgSnapshot(depPath, pkgSnapshot).name
const { name: pkgName, version: pkgVersion } = nameVerFromPkgSnapshot(depPath, pkgSnapshot)
const modules = path.join(opts.virtualStoreDir, pkgIdToFilename(depPath, opts.lockfileDir), 'node_modules')
const packageId = packageIdFromSnapshot(depPath, pkgSnapshot, opts.registries)
const pkg = {
name: pkgName,
version: pkgVersion,
engines: pkgSnapshot.engines,
cpu: pkgSnapshot.cpu,
os: pkgSnapshot.os,
}
if (!opts.force &&
packageIsInstallable(packageId, pkg, {
engineStrict: opts.engineStrict,
lockfileDir: opts.lockfileDir,
nodeVersion: opts.nodeVersion,
optional: pkgSnapshot.optional === true,
pnpmVersion: opts.pnpmVersion,
}) === false
) {
opts.skipped.add(depPath)
return
}
const dir = path.join(modules, pkgName)
if (
currentPackages[depPath] && R.equals(currentPackages[depPath].dependencies, lockfile.packages![depPath].dependencies) &&
@@ -660,6 +685,7 @@ async function getChildrenPaths (
if (ctx.graph[childRelDepPath]) {
children[alias] = ctx.graph[childRelDepPath].dir
} else if (childPkgSnapshot) {
if (ctx.skipped.has(childRelDepPath)) continue
const pkgName = nameVerFromPkgSnapshot(childRelDepPath, childPkgSnapshot).name
children[alias] = path.join(ctx.virtualStoreDir, pkgIdToFilename(childRelDepPath, ctx.lockfileDir), 'node_modules', pkgName)
} else if (allDeps[alias].indexOf('file:') === 0) {

View File

@@ -60,6 +60,9 @@
{
"path": "../modules-yaml"
},
{
"path": "../package-is-installable"
},
{
"path": "../package-requester"
},

View File

@@ -118,20 +118,19 @@ export default async function prune (
if (orphanDepPaths.length) {
if (
opts.currentLockfile.packages &&
opts.hoistedModulesDir &&
opts.publicHoistedModulesDir
(opts.hoistedModulesDir != null || opts.publicHoistedModulesDir != null)
) {
const binsDir = path.join(opts.hoistedModulesDir, '.bin')
const prefix = path.join(opts.virtualStoreDir, '../..')
await Promise.all(orphanDepPaths.map(async (orphanDepPath) => {
if (opts.hoistedDependencies[orphanDepPath]) {
await Promise.all(Object.entries(opts.hoistedDependencies[orphanDepPath]).map(([alias, hoistType]) => {
const modulesDir = hoistType === 'public'
? opts.publicHoistedModulesDir! : opts.hoistedModulesDir!
if (!modulesDir) return
return removeDirectDependency({
name: alias,
}, {
binsDir,
binsDir: path.join(modulesDir, '.bin'),
modulesDir,
muteLogs: true,
rootDir: prefix,

View File

@@ -10,5 +10,6 @@ module.exports = {
'<rootDir>/test/utils/retryLoadJsonFile.ts',
'<rootDir>/test/utils/testDefaults.ts',
],
maxWorkers: 1,
}

View File

@@ -94,6 +94,7 @@
"path-exists": "^4.0.0",
"path-name": "^1.0.0",
"pkgs-graph": "workspace:5.2.0",
"ps-list": "^6.3.0",
"ramda": "^0.27.1",
"read-yaml-file": "^2.0.0",
"render-help": "^1.0.1",
@@ -146,8 +147,8 @@
"url": "git+https://github.com/pnpm/pnpm.git"
},
"scripts": {
"bundle:pnpm": "esbuild lib/bin/pnpm.js --bundle --platform=node --outfile=dist/pnpm.js --external:update-notifier --external:node-gyp",
"bundle:pnpx": "esbuild lib/bin/pnpx.js --bundle --platform=node --outfile=dist/pnpx.js",
"bundle:pnpm": "esbuild lib/pnpm.js --bundle --platform=node --outfile=dist/pnpm.js --external:update-notifier --external:node-gyp",
"bundle:pnpx": "esbuild lib/pnpx.js --bundle --platform=node --outfile=dist/pnpx.js",
"bundle": "pnpm bundle:pnpm && pnpm bundle:pnpx",
"start": "pnpm tsc -- --watch",
"lint": "eslint -c ../../eslint.json src/**/*.ts test/**/*.ts",
@@ -159,7 +160,7 @@
"test": "pnpm run compile && pnpm run _test",
"prepublishOnly": "pnpm compile && npm cache clear --force && publish-packed --prune --npm-client yarn --dest dist",
"postpublish": "publish-packed",
"compile": "rimraf lib tsconfig.tsbuildinfo && tsc --build && rimraf dist && pnpm run bundle && shx cp -r node-gyp-bin dist/node-gyp-bin && shx cp -r node_modules/@pnpm/tabtab/lib/scripts dist/scripts"
"compile": "rimraf lib tsconfig.tsbuildinfo && tsc --build && rimraf dist && pnpm run bundle && shx cp -r node-gyp-bin dist/node-gyp-bin && shx cp -r node_modules/@pnpm/tabtab/lib/scripts dist/scripts && shx cp node_modules/ps-list/fastlist.exe dist/fastlist.exe"
},
"publishconfig": {
"tag": "next"

View File

@@ -68,9 +68,9 @@ const argv = process.argv.slice(2)
})()
async function runPnpm () {
const errorHandler = (await import('../err')).default
const errorHandler = (await import('./err')).default
try {
const main = (await import('../main')).default
const main = (await import('./main')).default
await main(argv)
} catch (err) {
errorHandler(err)
@@ -78,7 +78,7 @@ async function runPnpm () {
}
async function passThruToNpm () {
const runNpm = (await import('../runNpm')).default
const runNpm = (await import('./runNpm')).default
const { status } = await runNpm(argv)
process.exit(status!)
}

View File

@@ -13,11 +13,14 @@ import {
import path = require('path')
import rimraf = require('@zkochan/rimraf')
import crossSpawn = require('cross-spawn')
import isWindows = require('is-windows')
import loadJsonFile = require('load-json-file')
import fs = require('mz/fs')
import exists = require('path-exists')
import semver = require('semver')
const skipOnWindows = isWindows() ? test.skip : test
test('bin files are found by lifecycle scripts', () => {
prepare({
dependencies: {
@@ -34,7 +37,7 @@ test('bin files are found by lifecycle scripts', () => {
expect(result.stdout.toString().includes('Hello world!')).toBeTruthy()
})
test('create a "node_modules/.pnpm-debug.log" file when the command fails', async () => {
skipOnWindows('create a "node_modules/.pnpm-debug.log" file when the command fails', async () => {
prepare()
const result = execPnpmSync(['install', '@zkochan/i-do-not-exist'])
@@ -44,7 +47,7 @@ test('create a "node_modules/.pnpm-debug.log" file when the command fails', asyn
expect(await exists('node_modules/.pnpm-debug.log')).toBeTruthy()
})
test('install --lockfile-only', async () => {
skipOnWindows('install --lockfile-only', async () => {
const project = prepare()
await execPnpm(['install', 'rimraf@2.5.1', '--lockfile-only'])

View File

@@ -4,10 +4,13 @@ import {
retryLoadJsonFile,
spawnPnpm,
} from '../utils'
import isWindows = require('is-windows')
import path = require('path')
import pathExists = require('path-exists')
test('self-update stops the store server', async () => {
const skipOnWindows = isWindows() ? test.skip : test
skipOnWindows('self-update stops the store server', async () => {
prepare()
spawnPnpm(['server', 'start'])

View File

@@ -13,6 +13,8 @@ import isWindows = require('is-windows')
import fs = require('mz/fs')
import writeYamlFile = require('write-yaml-file')
const skipOnWindows = isWindows() ? test.skip : test
test('recursive installation with package-specific .npmrc', async () => {
const projects = preparePackages([
{
@@ -97,7 +99,7 @@ test('workspace .npmrc is always read', async () => {
expect(modulesYaml2?.hoistPattern).toBeFalsy()
})
test('recursive installation using server', async () => {
skipOnWindows('recursive installation using server', async () => {
const projects = preparePackages([
{
name: 'project-1',

View File

@@ -1,11 +1,13 @@
import { LAYOUT_VERSION } from '@pnpm/constants'
import { tempDir } from '@pnpm/prepare'
import { execPnpmSync } from './utils'
import fs = require('mz/fs')
import path = require('path')
import isWindows = require('is-windows')
test('pnpm root', async () => {
tempDir()
await fs.writeFile('package.json', '{}', 'utf8')
const result = execPnpmSync(['root'])
@@ -27,7 +29,7 @@ test('pnpm root -g', async () => {
expect(result.status).toBe(0)
if (isWindows()) {
expect(result.stdout.toString()).toBe(path.join(global, `npm/pnpm-global/${LAYOUT_VERSION}/node_modules`) + '\n')
expect(result.stdout.toString()).toBe(path.join(global, `pnpm-global/${LAYOUT_VERSION}/node_modules`) + '\n')
} else {
expect(result.stdout.toString()).toBe(path.join(global, `pnpm-global/${LAYOUT_VERSION}/node_modules`) + '\n')
}

View File

@@ -13,12 +13,15 @@ import delay, { ClearablePromise } from 'delay'
import { DeferredPromise } from 'p-defer'
import path = require('path')
import byline = require('byline')
import isWindows = require('is-windows')
import pAny = require('p-any')
import pDefer = require('p-defer')
import pathExists = require('path-exists')
import killcb = require('tree-kill')
import writeJsonFile = require('write-json-file')
const skipOnWindows = isWindows() ? test.skip : test
// Third element is true if and only if we attempted to kill the process via a signal.
interface ServerProcess {
childProcess: ChildProcess
@@ -28,7 +31,7 @@ interface ServerProcess {
const kill = promisify(killcb) as (pid: number, signal: string) => Promise<void>
test('installation using pnpm server', async () => {
skipOnWindows('installation using pnpm server', async () => {
const project = prepare()
spawnPnpm(['server', 'start'])
@@ -50,7 +53,7 @@ test('installation using pnpm server', async () => {
expect(await pathExists(serverJsonPath)).toBeFalsy()
})
test('store server: headless installation', async () => {
skipOnWindows('store server: headless installation', async () => {
const project = prepare()
spawnPnpm(['server', 'start'])
@@ -71,7 +74,7 @@ test('store server: headless installation', async () => {
expect(await pathExists(serverJsonPath)).toBeFalsy()
})
test('installation using pnpm server that runs in the background', async () => {
skipOnWindows('installation using pnpm server that runs in the background', async () => {
const project = prepare()
await execPnpm(['server', 'start', '--background'])
@@ -92,7 +95,7 @@ test('installation using pnpm server that runs in the background', async () => {
expect(await pathExists(serverJsonPath)).toBeFalsy()
})
test('installation using pnpm server via TCP', async () => {
skipOnWindows('installation using pnpm server via TCP', async () => {
const project = prepare()
spawnPnpm(['server', 'start', '--protocol', 'tcp'])
@@ -113,7 +116,7 @@ test('installation using pnpm server via TCP', async () => {
expect(await pathExists(serverJsonPath)).toBeFalsy()
})
test('pnpm server uses TCP when port specified', async () => {
skipOnWindows('pnpm server uses TCP when port specified', async () => {
prepare()
spawnPnpm(['server', 'start', '--port', '7856'])
@@ -146,7 +149,7 @@ test('stopping server fails when the server disallows stopping via remote call',
await kill(server.pid, 'SIGINT')
})
test('uploading cache can be disabled without breaking install', async () => {
skipOnWindows('uploading cache can be disabled without breaking install', async () => {
const project = prepare()
spawnPnpm(['server', 'start', '--ignore-upload-requests'])
@@ -168,7 +171,7 @@ test('uploading cache can be disabled without breaking install', async () => {
await execPnpm(['server', 'stop'])
})
test('installation using store server started in the background', async () => {
skipOnWindows('installation using store server started in the background', async () => {
const project = prepare()
await execPnpm(['install', 'is-positive@1.0.0', '--use-store-server'])
@@ -187,7 +190,7 @@ test('installation using store server started in the background', async () => {
expect(await pathExists(serverJsonPath)).toBeFalsy()
})
test('store server started in the background should use store location wanted by install', async () => {
skipOnWindows('store server started in the background should use store location wanted by install', async () => {
const project = prepare()
await execPnpm(['add', 'is-positive@1.0.0', '--use-store-server', '--store-dir', '../store2'])
@@ -305,7 +308,7 @@ test('parallel server starts against the same store should result in only one se
expect(await pathExists(serverJsonPath)).toBeFalsy()
})
test('installation without store server running in the background', async () => {
skipOnWindows('installation without store server running in the background', async () => {
const project = prepare()
await execPnpm(['install', 'is-positive@1.0.0', '--no-use-store-server'])
@@ -332,7 +335,7 @@ test.skip('fail if the store server is run by a different version of pnpm', asyn
expect(result.stdout.toString()).toMatch(/The store server runs on pnpm v2.0.0. The same pnpm version should be used to connect (current is/)
})
test('print server status', async () => {
skipOnWindows('print server status', async () => {
prepare()
spawnPnpm(['server', 'start'])

View File

@@ -1,9 +1,10 @@
import { ChildProcess as NodeChildProcess } from 'child_process'
import { REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
import isWindows = require('is-windows')
import path = require('path')
import crossSpawn = require('cross-spawn')
const binDir = path.join(__dirname, '..', '..', 'bin')
const binDir = path.join(__dirname, '../..', isWindows() ? 'lib' : 'bin')
const pnpmBinLocation = path.join(binDir, 'pnpm.js')
const pnpxBinLocation = path.join(binDir, 'pnpx.js')
@@ -32,7 +33,7 @@ export function spawnPnpm (
storeDir?: string
}
): NodeChildProcess {
return crossSpawn.spawn('node', [pnpmBinLocation, ...args], {
return crossSpawn.spawn(process.execPath, [pnpmBinLocation, ...args], {
env: {
...createEnv(opts),
...opts?.env,
@@ -55,7 +56,7 @@ export async function execPnpx (args: string[]): Promise<void> {
}
export function spawnPnpx (args: string[], opts?: {storeDir?: string}): NodeChildProcess {
return crossSpawn.spawn('node', [pnpxBinLocation, ...args], {
return crossSpawn.spawn(process.execPath, [pnpxBinLocation, ...args], {
env: createEnv(opts),
stdio: 'inherit',
})
@@ -68,7 +69,7 @@ export interface ChildProcess {
}
export function execPnpmSync (args: string[], opts?: { env: Object }): ChildProcess {
return crossSpawn.sync('node', [pnpmBinLocation, ...args], {
return crossSpawn.sync(process.execPath, [pnpmBinLocation, ...args], {
env: {
...createEnv(),
...opts?.env,
@@ -77,7 +78,7 @@ export function execPnpmSync (args: string[], opts?: { env: Object }): ChildProc
}
export function execPnpxSync (args: string[]): ChildProcess {
return crossSpawn.sync('node', [pnpxBinLocation, ...args], {
return crossSpawn.sync(process.execPath, [pnpxBinLocation, ...args], {
env: createEnv(),
}) as ChildProcess
}

View File

@@ -840,6 +840,7 @@ test('reinstalls missing packages to node_modules', async () => {
await rimraf('pnpm-lock.yaml')
await rimraf(depLocation)
await rimraf('node_modules/is-positive')
await project.hasNot('is-positive')
@@ -868,6 +869,7 @@ test('reinstalls missing packages to node_modules during headless install', asyn
expect(reporter.calledWithMatch(missingDepLog)).toBeFalsy()
await rimraf(depLocation)
await rimraf('node_modules/is-positive')
await project.hasNot('is-positive')

View File

@@ -356,14 +356,14 @@ test('only that package is skipped which is an optional dependency only and not
const reporter = sinon.spy()
const manifest = await addDependenciesToPackage({}, [
'peer-c@1.0.1',
'peer-c@1.0.0',
'has-optional-dep-with-peer',
'not-compatible-with-any-os-and-has-peer',
], await testDefaults({ reporter }))
{
const modulesInfo = await readYamlFile<{ skipped: string[] }>(path.join('node_modules', '.modules.yaml'))
expect(modulesInfo.skipped).toStrictEqual(['/not-compatible-with-any-os-and-has-peer/1.0.0_peer-c@1.0.0'])
expect(modulesInfo.skipped).toStrictEqual([])
}
const lockfile = await project.readLockfile()
@@ -387,7 +387,7 @@ test('only that package is skipped which is an optional dependency only and not
{
const modulesInfo = await readYamlFile<{ skipped: string[] }>(path.join('node_modules', '.modules.yaml'))
expect(modulesInfo.skipped).toStrictEqual(['/not-compatible-with-any-os-and-has-peer/1.0.0_peer-c@1.0.0'])
expect(modulesInfo.skipped).toStrictEqual([])
}
})

View File

@@ -6,12 +6,14 @@ import { PackageFilesIndex } from '@pnpm/cafs'
import { testDefaults } from '../utils'
import path = require('path')
import rimraf = require('@zkochan/rimraf')
import isWindows = require('is-windows')
import loadJsonFile = require('load-json-file')
import fs = require('mz/fs')
import exists = require('path-exists')
import writeJsonFile = require('write-json-file')
const ENGINE_DIR = `${process.platform}-${process.arch}-node-${process.version.split('.')[0]}`
const skipOnWindows = isWindows() ? test.skip : test
test.skip('caching side effects of native package', async () => {
prepareEmpty()
@@ -69,7 +71,7 @@ test.skip('caching side effects of native package when hoisting is used', async
await project.has('.pnpm/node_modules/es6-promise') // verifying that a flat node_modules was created
})
test('using side effects cache', async () => {
skipOnWindows('using side effects cache', async () => {
prepareEmpty()
// Right now, hardlink does not work with side effects, so we specify copy as the packageImportMethod

5
pnpm-lock.yaml generated
View File

@@ -670,6 +670,7 @@ importers:
'@pnpm/lockfile-utils': 'link:../lockfile-utils'
'@pnpm/modules-cleaner': 'link:../modules-cleaner'
'@pnpm/modules-yaml': 'link:../modules-yaml'
'@pnpm/package-is-installable': 'link:../package-is-installable'
'@pnpm/package-requester': 'link:../package-requester'
'@pnpm/pkgid-to-filename': 3.0.0
'@pnpm/read-package-json': 'link:../read-package-json'
@@ -724,6 +725,7 @@ importers:
'@pnpm/logger': ^3.2.3
'@pnpm/modules-cleaner': 'workspace:10.0.16'
'@pnpm/modules-yaml': 'workspace:8.0.5'
'@pnpm/package-is-installable': 'workspace:^4.0.18'
'@pnpm/package-requester': 'workspace:12.2.0'
'@pnpm/package-store': 'workspace:*'
'@pnpm/pkgid-to-filename': ^3.0.0
@@ -2248,6 +2250,7 @@ importers:
path-exists: 4.0.0
path-name: 1.0.0
pkgs-graph: 'link:../pkgs-graph'
ps-list: 6.3.0
ramda: 0.27.1
read-yaml-file: 2.0.0
render-help: 1.0.1
@@ -2336,6 +2339,7 @@ importers:
path-exists: ^4.0.0
path-name: ^1.0.0
pkgs-graph: 'workspace:5.2.0'
ps-list: ^6.3.0
ramda: ^0.27.1
read-yaml-file: ^2.0.0
render-help: ^1.0.1
@@ -11738,7 +11742,6 @@ packages:
resolution:
integrity: sha1-0/wRS6BplaRexok/SEzrHXj19HY=
/ps-list/6.3.0:
dev: false
engines:
node: '>=8'
resolution:

View File

@@ -117,7 +117,8 @@ async function updateManifest (dir: string, manifest: ProjectManifest) {
}
scripts.compile += ' && rimraf dist && pnpm run bundle \
&& shx cp -r node-gyp-bin dist/node-gyp-bin \
&& shx cp -r node_modules/@pnpm/tabtab/lib/scripts dist/scripts'
&& shx cp -r node_modules/@pnpm/tabtab/lib/scripts dist/scripts \
&& shx cp node_modules/ps-list/fastlist.exe dist/fastlist.exe'
} else {
scripts.prepublishOnly = 'pnpm run compile'
homepage = `https://github.com/pnpm/pnpm/blob/master/${relative}#readme`