Skip to content

Commit 62b95a0

Browse files
authored
fix: allow hash character in paths (#5122)
* fix: allow link from path with hash character * fix: allow hash character in path in other places * Remove extra semicolon
1 parent de40c31 commit 62b95a0

File tree

12 files changed

+62
-21
lines changed

12 files changed

+62
-21
lines changed

lib/commands/diff.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class Diff extends BaseCommand {
106106
const pkgName = await this.packageName(this.prefix)
107107
return [
108108
`${pkgName}@${this.npm.config.get('tag')}`,
109-
`file:${this.prefix}`,
109+
`file:${this.prefix.replace(/#/g, '%23')}`,
110110
]
111111
}
112112

@@ -134,7 +134,7 @@ class Diff extends BaseCommand {
134134
}
135135
return [
136136
`${pkgName}@${a}`,
137-
`file:${this.prefix}`,
137+
`file:${this.prefix.replace(/#/g, '%23')}`,
138138
]
139139
}
140140

@@ -165,7 +165,7 @@ class Diff extends BaseCommand {
165165
}
166166
return [
167167
`${spec.name}@${spec.fetchSpec}`,
168-
`file:${this.prefix}`,
168+
`file:${this.prefix.replace(/#/g, '%23')}`,
169169
]
170170
}
171171

@@ -178,7 +178,7 @@ class Diff extends BaseCommand {
178178
}
179179
}
180180

181-
const aSpec = `file:${node.realpath}`
181+
const aSpec = `file:${node.realpath.replace(/#/g, '%23')}`
182182

183183
// finds what version of the package to compare against, if a exact
184184
// version or tag was passed than it should use that, otherwise
@@ -211,8 +211,8 @@ class Diff extends BaseCommand {
211211
]
212212
} else if (spec.type === 'directory') {
213213
return [
214-
`file:${spec.fetchSpec}`,
215-
`file:${this.prefix}`,
214+
`file:${spec.fetchSpec.replace(/#/g, '%23')}`,
215+
`file:${this.prefix.replace(/#/g, '%23')}`,
216216
]
217217
} else {
218218
throw this.usageError(`Spec type ${spec.type} not supported.`)
@@ -279,7 +279,7 @@ class Diff extends BaseCommand {
279279

280280
const res = !node || !node.package || !node.package.version
281281
? spec.fetchSpec
282-
: `file:${node.realpath}`
282+
: `file:${node.realpath.replace(/#/g, '%23')}`
283283

284284
return `${spec.name}@${res}`
285285
})

lib/commands/link.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ class Link extends ArboristWorkspaceCmd {
122122
...this.npm.flatOptions,
123123
prune: false,
124124
path: this.npm.prefix,
125-
add: names.map(l => `file:${resolve(globalTop, 'node_modules', l)}`),
125+
add: names.map(l => `file:${resolve(globalTop, 'node_modules', l).replace(/#/g, '%23')}`),
126126
save,
127127
workspaces: this.workspaceNames,
128128
})
@@ -133,7 +133,7 @@ class Link extends ArboristWorkspaceCmd {
133133
async linkPkg () {
134134
const wsp = this.workspacePaths
135135
const paths = wsp && wsp.length ? wsp : [this.npm.prefix]
136-
const add = paths.map(path => `file:${path}`)
136+
const add = paths.map(path => `file:${path.replace(/#/g, '%23')}`)
137137
const globalTop = resolve(this.npm.globalDir, '..')
138138
const arb = new Arborist({
139139
...this.npm.flatOptions,

tap-snapshots/test/lib/commands/link.js.test.cjs

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@
55
* Make sure to inspect the output below. Do not ignore changes!
66
*/
77
'use strict'
8+
exports[`test/lib/commands/link.js TAP hash character in working directory path > should create a global link to current pkg, even within path with hash 1`] = `
9+
{CWD}/test/lib/commands/tap-testdir-link-hash-character-in-working-directory-path/global-prefix/lib/node_modules/test-pkg-link -> {CWD}/test/lib/commands/tap-testdir-link-hash-character-in-working-directory-path/i_like_#_in_my_paths/test-pkg-link
10+
11+
`
12+
813
exports[`test/lib/commands/link.js TAP link global linked pkg to local nm when using args > should create a local symlink to global pkg 1`] = `
914
{CWD}/test/lib/commands/tap-testdir-link-link-global-linked-pkg-to-local-nm-when-using-args/my-project/node_modules/@myscope/bar -> {CWD}/test/lib/commands/tap-testdir-link-link-global-linked-pkg-to-local-nm-when-using-args/global-prefix/lib/node_modules/@myscope/bar
1015
{CWD}/test/lib/commands/tap-testdir-link-link-global-linked-pkg-to-local-nm-when-using-args/my-project/node_modules/@myscope/linked -> {CWD}/test/lib/commands/tap-testdir-link-link-global-linked-pkg-to-local-nm-when-using-args/scoped-linked

test/lib/commands/link.js

+36
Original file line numberDiff line numberDiff line change
@@ -514,3 +514,39 @@ t.test('--global option', async t => {
514514
'should throw an useful error'
515515
)
516516
})
517+
518+
t.test('hash character in working directory path', async t => {
519+
const testdir = t.testdir({
520+
'global-prefix': {
521+
lib: {
522+
node_modules: {
523+
a: {
524+
'package.json': JSON.stringify({
525+
name: 'a',
526+
version: '1.0.0',
527+
}),
528+
},
529+
},
530+
},
531+
},
532+
'i_like_#_in_my_paths': {
533+
'test-pkg-link': {
534+
'package.json': JSON.stringify({
535+
name: 'test-pkg-link',
536+
version: '1.0.0',
537+
}),
538+
},
539+
},
540+
})
541+
npm.globalDir = resolve(testdir, 'global-prefix', 'lib', 'node_modules')
542+
npm.prefix = resolve(testdir, 'i_like_#_in_my_paths', 'test-pkg-link')
543+
544+
link.workspacePaths = null
545+
await link.exec([])
546+
const links = await printLinks({
547+
path: resolve(npm.globalDir, '..'),
548+
global: true,
549+
})
550+
551+
t.matchSnapshot(links, 'should create a global link to current pkg, even within path with hash')
552+
})

workspaces/arborist/lib/arborist/build-ideal-tree.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ Try using the package name instead, e.g:
484484
.catch(/* istanbul ignore next */ er => null)
485485
if (st && st.isSymbolicLink()) {
486486
const target = await readlink(dir)
487-
const real = resolve(dirname(dir), target)
487+
const real = resolve(dirname(dir), target).replace(/#/g, '%23')
488488
tree.package.dependencies[name] = `file:${real}`
489489
} else {
490490
tree.package.dependencies[name] = '*'
@@ -603,7 +603,7 @@ Try using the package name instead, e.g:
603603
if (filepath) {
604604
const { name } = spec
605605
const tree = this.idealTree.target
606-
spec = npa(`file:${relpath(tree.path, filepath)}`, tree.path)
606+
spec = npa(`file:${relpath(tree.path, filepath).replace(/#/g, '%23')}`, tree.path)
607607
spec.name = name
608608
}
609609
return spec

workspaces/arborist/lib/arborist/load-actual.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ module.exports = cls => class ActualLoader extends cls {
196196
const actualRoot = tree.isLink ? tree.target : tree
197197
const { dependencies = {} } = actualRoot.package
198198
for (const [name, kid] of actualRoot.children.entries()) {
199-
const def = kid.isLink ? `file:${kid.realpath}` : '*'
199+
const def = kid.isLink ? `file:${kid.realpath.replace(/#/g, '%23')}` : '*'
200200
dependencies[name] = dependencies[name] || def
201201
}
202202
actualRoot.package = { ...actualRoot.package, dependencies }

workspaces/arborist/lib/arborist/load-virtual.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ module.exports = cls => class VirtualLoader extends cls {
162162
lockfile: s.data,
163163
})
164164
for (const [name, path] of workspaces.entries()) {
165-
lockWS.push(['workspace', name, `file:${path}`])
165+
lockWS.push(['workspace', name, `file:${path.replace(/#/g, '%23')}`])
166166
}
167167

168168
const lockEdges = [

workspaces/arborist/lib/arborist/reify.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1241,7 +1241,7 @@ module.exports = cls => class Reifier extends cls {
12411241
// path initially, in which case we can end up with the wrong
12421242
// thing, so just get the ultimate fetchSpec and relativize it.
12431243
const p = req.fetchSpec.replace(/^file:/, '')
1244-
const rel = relpath(addTree.realpath, p)
1244+
const rel = relpath(addTree.realpath, p).replace(/#/g, '%23')
12451245
newSpec = `file:${rel}`
12461246
}
12471247
} else {

workspaces/arborist/lib/consistent-resolve.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ const consistentResolve = (resolved, fromPath, toPath, relPaths = false) => {
2020
raw,
2121
} = npa(resolved, fromPath)
2222
const isPath = type === 'file' || type === 'directory'
23-
return isPath && !relPaths ? `file:${fetchSpec}`
24-
: isPath ? 'file:' + (toPath ? relpath(toPath, fetchSpec) : fetchSpec)
23+
return isPath && !relPaths ? `file:${fetchSpec.replace(/#/g, '%23')}`
24+
: isPath ? 'file:' + (toPath ? relpath(toPath, fetchSpec.replace(/#/g, '%23')) : fetchSpec.replace(/#/g, '%23'))
2525
: hosted ? `git+${
2626
hosted.auth ? hosted.https(hostedOpt) : hosted.sshurl(hostedOpt)
2727
}`

workspaces/arborist/lib/link.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class Link extends Node {
118118
// the path/realpath guard is there for the benefit of setting
119119
// these things in the "wrong" order
120120
return this.path && this.realpath
121-
? `file:${relpath(dirname(this.path), this.realpath)}`
121+
? `file:${relpath(dirname(this.path), this.realpath).replace(/#/g, '%23')}`
122122
: null
123123
}
124124

workspaces/arborist/lib/node.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,7 @@ class Node {
824824
}
825825

826826
for (const [name, path] of this[_workspaces].entries()) {
827-
new Edge({ from: this, name, spec: `file:${path}`, type: 'workspace' })
827+
new Edge({ from: this, name, spec: `file:${path.replace(/#/g, '%23')}`, type: 'workspace' })
828828
}
829829
}
830830

workspaces/arborist/lib/shrinkwrap.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -815,7 +815,7 @@ class Shrinkwrap {
815815
const pathFixed = !resolved ? null
816816
: !/^file:/.test(resolved) ? resolved
817817
// resolve onto the metadata path
818-
: `file:${resolve(this.path, resolved.slice(5))}`
818+
: `file:${resolve(this.path, resolved.slice(5)).replace(/#/g, '%23')}`
819819

820820
// if we have one, only set the other if it matches
821821
// otherwise it could be for a completely different thing.
@@ -996,7 +996,7 @@ class Shrinkwrap {
996996
: npa.resolve(node.name, edge.spec, edge.from.realpath)
997997

998998
if (node.isLink) {
999-
lock.version = `file:${relpath(this.path, node.realpath)}`
999+
lock.version = `file:${relpath(this.path, node.realpath).replace(/#/g, '%23')}`
10001000
} else if (spec && (spec.type === 'file' || spec.type === 'remote')) {
10011001
lock.version = spec.saveSpec
10021002
} else if (spec && spec.type === 'git' || rSpec.type === 'git') {
@@ -1074,7 +1074,7 @@ class Shrinkwrap {
10741074
// this especially shows up with workspace edges when the root
10751075
// node is also a workspace in the set.
10761076
const p = resolve(node.realpath, spec.slice('file:'.length))
1077-
set[k] = `file:${relpath(node.realpath, p)}`
1077+
set[k] = `file:${relpath(node.realpath, p).replace(/#/g, '%23')}`
10781078
} else {
10791079
set[k] = spec
10801080
}

0 commit comments

Comments
 (0)