Skip to content

Commit

Permalink
do not self-dep link if already linked
Browse files Browse the repository at this point in the history
Fix: #4
  • Loading branch information
isaacs committed Sep 18, 2023
1 parent 03ec814 commit 9835fd1
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 7 deletions.
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"resolve-import": "^1.4.1",
"rimraf": "^5.0.1",
"sync-content": "^1.0.2",
"typescript": "5.2"
"typescript": "5.2",
"walk-up-path": "^3.0.1"
},
"scripts": {
"postversion": "npm publish",
Expand Down
40 changes: 36 additions & 4 deletions src/self-dep.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
// link the package folder into ./target/node_modules/<pkgname>
import { symlinkSync } from 'fs'
import { readlinkSync, symlinkSync } from 'fs'
import { mkdirpSync } from 'mkdirp'
import { dirname, relative, resolve } from 'path'
import { dirname, relative, resolve, sep } from 'path'
import { rimrafSync } from 'rimraf'
import { walkUp } from 'walk-up-path'
import { Package } from './types.js'

const dirsMade = new Map<string, string>()

// if the cwd is in already linked to or living within node_modules,
// then skip the linking, because it's already done.
// This is typically the case in a workspaces setup, and
// creating yet *another* symlink to ourselves in src/node_modules
// will break nx's change detection logic with an ELOOP error.
let inNM: boolean | undefined = undefined

const linkedAlready = (pkg: Package) => {
if (inNM !== undefined) {
return inNM
}

const cwd = process.cwd()
const p = `${sep}node_modules${sep}${pkg.name}`.toLowerCase()
if (cwd.toLowerCase().endsWith(p)) {
return (inNM = true)
}

for (const p of walkUp(cwd)) {
const link = resolve(p, 'node_modules', pkg.name)
try {
const target = resolve(dirname(link), readlinkSync(link))
if (relative(target, cwd) === '') {
return (inNM = true)
}
} catch {}
}

return (inNM = false)
}

export const link = (pkg: Package, where: string) => {
if (!pkg.name) return
if (!pkg.name || linkedAlready(pkg)) return
const dest = resolve(where, 'node_modules', pkg.name)
const dir = dirname(dest)
const src = relative(dir, process.cwd())
Expand All @@ -18,7 +50,7 @@ export const link = (pkg: Package, where: string) => {
}

export const unlink = (pkg: Package, where: string) => {
if (!pkg.name) return
if (!pkg.name || linkedAlready(pkg)) return
const dest = resolve(where, 'node_modules', pkg.name)
rimrafSync(dest)
const made = dirsMade.get(dest)
Expand Down
68 changes: 67 additions & 1 deletion test/self-dep.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { posix as path } from 'node:path'
import { posix as path, resolve } from 'node:path'
import t from 'tap'
import { Package } from '../src/types.js'

Expand All @@ -12,8 +12,10 @@ const mkdirpCalls = t.capture(
mkdirp.mkdirpSync
).args

import * as FS from 'node:fs'
const fs = {
symlinkSync: () => {},
readlinkSync: FS.readlinkSync,
}
const symlinkCalls = t.capture(fs, 'symlinkSync', fs.symlinkSync).args

Expand Down Expand Up @@ -58,3 +60,67 @@ t.test('made dir, clean up', t => {
t.matchSnapshot(mkdirpCalls(), 'mkdirps')
t.end()
})

t.test('already in node_modules, do not create link', t => {
const readlinkCalls = t.capture(
fs,
'readlinkSync',
fs.readlinkSync
).args

const dir = t.testdir({
node_modules: {
installed: { src: {} },
linked: t.fixture('symlink', '../packages/linked'),
'@scope': {
linked: t.fixture('symlink', '../../packages/scopelinked'),
installed: { src: {} },
},
},
packages: {
linked: { src: {} },
scopelinked: { src: {} },
},
})

const cases: [string, string][] = [
['installed', 'node_modules/installed'],
['@scope/installed', 'node_modules/@scope/installed'],
['linked', 'node_modules/linked'],
['@scope/linked', 'node_modules/@scope/linked'],
['linked', 'packages/linked'],
['@scope/linked', 'packages/scopelinked'],
]

t.plan(cases.length)

const cwd = process.cwd()
t.afterEach(() => process.chdir(cwd))
for (const [name, d] of cases) {
t.test(d, async t => {
// need a separate import for each test, because this gets cached
// to save extra readlink and walkUp calls.
const { link, unlink } = (await t.mockImport(
'../dist/esm/self-dep.js',
{ mkdirp, fs, rimraf }
)) as typeof import('../dist/esm/self-dep.js')
process.chdir(resolve(dir, d))
link({ name } as unknown as Package, 'src')
unlink({ name } as unknown as Package, 'src')
const rl = readlinkCalls()
if (name.endsWith('linked')) {
t.strictSame(
rl.pop(),
[resolve(dir, 'node_modules', name)],
'found link'
)
} else {
t.strictSame(rl, [], 'did not need to check for links')
}
t.strictSame(symlinkCalls(), [])
t.strictSame(mkdirpCalls(), [])
t.strictSame(rimrafCalls(), [])
t.end()
})
}
})

0 comments on commit 9835fd1

Please sign in to comment.