Skip to content

Commit 91bc0b7

Browse files
committed
test: add windows path support
1 parent 94de477 commit 91bc0b7

File tree

2 files changed

+107
-24
lines changed

2 files changed

+107
-24
lines changed

lib/install/installer/bin.js

Lines changed: 82 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,94 @@ const fs = require('fs-extra')
22
const path = require('path')
33
const nm = require('../../utils/nm')
44

5+
const isWindows = process.platform === 'win32'
6+
const removeIfExists = (filePath) => {
7+
try {
8+
fs.rmSync(filePath, { force: true })
9+
} catch (error) {
10+
if (error.code !== 'ENOENT') {
11+
throw error
12+
}
13+
}
14+
}
15+
16+
const makePosixLink = (source, dest) => {
17+
removeIfExists(dest)
18+
fs.symlinkSync(source, dest)
19+
try {
20+
fs.chmodSync(source, 0o755)
21+
} catch (error) {
22+
if (error.code !== 'ENOENT') throw error
23+
}
24+
}
25+
26+
const makeWindowsShims = (source, dest) => {
27+
removeIfExists(dest)
28+
let linked = false
29+
try {
30+
fs.symlinkSync(source, dest, 'file')
31+
linked = true
32+
} catch (error) {
33+
if (!['EACCES', 'EPERM', 'UNKNOWN'].includes(error.code)) {
34+
throw error
35+
}
36+
fs.copyFileSync(source, dest)
37+
}
38+
39+
try {
40+
fs.chmodSync(source, 0o755)
41+
} catch (error) {
42+
if (!['ENOENT', 'EPERM', 'EINVAL'].includes(error.code)) {
43+
throw error
44+
}
45+
}
46+
47+
const nodePath = process.execPath
48+
const cmdContent = `@ECHO OFF\r\n"${nodePath}" "${source}" %*\r\n`
49+
const psContent = `& "${nodePath}" "${source}" $args\r\n`
50+
51+
removeIfExists(`${dest}.cmd`)
52+
removeIfExists(`${dest}.ps1`)
53+
54+
fs.writeFileSync(`${dest}.cmd`, cmdContent, 'utf8')
55+
fs.writeFileSync(`${dest}.ps1`, psContent, 'utf8')
56+
57+
return linked
58+
}
59+
60+
const createBinEntry = (command, relativePath, targetDir) => {
61+
const binDir = path.join(nm, '.bin')
62+
const source = path.join(targetDir, relativePath)
63+
const dest = path.join(binDir, command)
64+
65+
fs.ensureDirSync(binDir)
66+
67+
if (isWindows) {
68+
makeWindowsShims(source, dest)
69+
} else {
70+
makePosixLink(source, dest)
71+
}
72+
73+
if (!isWindows) return
74+
75+
try {
76+
fs.chmodSync(dest, 0o755)
77+
} catch (error) {
78+
if (!['ENOENT', 'EPERM', 'EINVAL'].includes(error.code)) {
79+
throw error
80+
}
81+
}
82+
}
83+
584
const bin = (key, target) => {
685
const pkgJSON = require(path.join(target, 'package.json'))
786
if (!pkgJSON.bin) return
8-
fs.ensureDirSync(path.join(nm, '.bin'))
87+
988
if (typeof pkgJSON.bin === 'string') {
10-
try {
11-
fs.unlinkSync(path.join(nm, '.bin', key))
12-
} catch (e) {}
13-
fs.symlinkSync(
14-
path.join(target, pkgJSON.bin),
15-
path.join(nm, '.bin', key)
16-
)
17-
fs.chmodSync(path.join(target, pkgJSON.bin), '0755')
89+
createBinEntry(key, pkgJSON.bin, target)
1890
} else if (typeof pkgJSON.bin === 'object') {
1991
Object.keys(pkgJSON.bin).forEach((cmd) => {
20-
try {
21-
fs.unlinkSync(path.join(nm, '.bin', cmd))
22-
} catch (e) {}
23-
fs.symlinkSync(
24-
path.join(target, pkgJSON.bin[cmd]),
25-
path.join(nm, '.bin', cmd)
26-
)
27-
fs.chmodSync(path.join(target, pkgJSON.bin[cmd]), '0755')
92+
createBinEntry(cmd, pkgJSON.bin[cmd], target)
2893
})
2994
}
3095
}

test/12-install-bin.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,33 @@ test((t) => {
1818
const binPath = resolveBinPath()
1919
t.ok(fs.existsSync(binPath), `${packageJson.name}: happy-birthday bin exists`)
2020

21-
if (process.platform !== 'win32') {
22-
const linkStats = fs.lstatSync(binPath)
23-
t.ok(linkStats.isSymbolicLink(), `${packageJson.name}: bin entry is a symlink`)
24-
}
21+
const binStats = fs.lstatSync(binPath)
22+
23+
if (binStats.isSymbolicLink()) {
24+
const resolved = fs.realpathSync(binPath)
25+
t.equal(resolved, resolveBinTarget(), `${packageJson.name}: bin resolves to package script`)
2526

26-
const resolved = fs.realpathSync(binPath)
27-
t.equal(resolved, resolveBinTarget(), `${packageJson.name}: bin resolves to package script`)
27+
const stat = fs.statSync(binPath)
28+
t.ok(isExecutable(stat.mode), `${packageJson.name}: bin entry is executable`)
29+
} else if (process.platform === 'win32') {
30+
const cmdShim = `${binPath}.cmd`
31+
const psShim = `${binPath}.ps1`
32+
t.ok(fs.existsSync(cmdShim), `${packageJson.name}: cmd shim exists`)
33+
t.ok(fs.existsSync(psShim), `${packageJson.name}: powershell shim exists`)
34+
const target = resolveBinTarget()
35+
const shimContent = fs.readFileSync(cmdShim, 'utf8')
36+
t.match(
37+
shimContent.replace(/\\/g, '/'),
38+
target.replace(/\\/g, '/'),
39+
`${packageJson.name}: cmd shim points to target script`
40+
)
41+
const binContent = fs.readFileSync(binPath, 'utf8')
42+
const targetContent = fs.readFileSync(target, 'utf8')
43+
t.equal(binContent, targetContent, `${packageJson.name}: fallback bin matches target content`)
44+
} else {
45+
const resolved = fs.realpathSync(binPath)
46+
t.equal(resolved, resolveBinTarget(), `${packageJson.name}: bin resolves to package script`)
2847

29-
if (process.platform !== 'win32') {
3048
const stat = fs.statSync(binPath)
3149
t.ok(isExecutable(stat.mode), `${packageJson.name}: bin entry is executable`)
3250
}

0 commit comments

Comments
 (0)