dsh-code-index 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -20
- package/README.md +142 -129
- package/README.zh.md +138 -125
- package/cordis.patch.yml +4 -4
- package/dist/index.js +62 -19
- package/dist/index.js.map +1 -1
- package/package.json +13 -5
- package/scripts/fix-wsl-links.mjs +154 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* fix-wsl-links — repair pnpm symlinks broken by WSL→Windows installs.
|
|
4
|
+
*
|
|
5
|
+
* Running `pnpm install` from WSL against a checkout on /mnt/c lays down
|
|
6
|
+
* Linux-style symlinks that Windows cannot traverse (EACCES / "cannot find
|
|
7
|
+
* package"). This script walks node_modules, finds every such dead link,
|
|
8
|
+
* and re-points it at its real target inside .pnpm as a Windows junction.
|
|
9
|
+
*
|
|
10
|
+
* Run from Windows-side Node (the dsh-bundled node.exe works):
|
|
11
|
+
*
|
|
12
|
+
* node.exe scripts\fix-wsl-links.mjs [root]
|
|
13
|
+
*
|
|
14
|
+
* `root` defaults to the repo this script lives in. Pass a dsh profile
|
|
15
|
+
* directory (…\.dsh\profiles\web) to fix a plugin install instead.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync, readdirSync, readlinkSync, realpathSync, rmSync, symlinkSync, lstatSync } from 'node:fs'
|
|
19
|
+
import path from 'node:path'
|
|
20
|
+
import { fileURLToPath } from 'node:url'
|
|
21
|
+
|
|
22
|
+
const defaultRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
23
|
+
const root = path.resolve(process.argv[2] ?? defaultRoot)
|
|
24
|
+
const nm = path.join(root, 'node_modules')
|
|
25
|
+
const pnpm = path.join(nm, '.pnpm')
|
|
26
|
+
|
|
27
|
+
if (!existsSync(nm)) {
|
|
28
|
+
console.error(`no node_modules under ${root} — run pnpm install first`)
|
|
29
|
+
process.exit(1)
|
|
30
|
+
}
|
|
31
|
+
if (process.platform === 'win32' && !existsSync(pnpm)) {
|
|
32
|
+
console.error(`no .pnpm store under ${nm} — nothing to re-link against`)
|
|
33
|
+
process.exit(1)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
let fixed = 0
|
|
37
|
+
let checked = 0
|
|
38
|
+
|
|
39
|
+
/** True for a link entry whose target Windows Node cannot resolve. */
|
|
40
|
+
function broken(link) {
|
|
41
|
+
try {
|
|
42
|
+
realpathSync(link)
|
|
43
|
+
return false
|
|
44
|
+
} catch {
|
|
45
|
+
return true
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Replace `link` with a junction to `target`; report and count. */
|
|
50
|
+
function junction(link, target) {
|
|
51
|
+
rmSync(link, { force: true, recursive: true })
|
|
52
|
+
try {
|
|
53
|
+
symlinkSync(target, link, 'junction')
|
|
54
|
+
} catch {
|
|
55
|
+
try {
|
|
56
|
+
rmSync(link, { force: true, recursive: true })
|
|
57
|
+
} catch {}
|
|
58
|
+
console.error(` FAIL ${rel(link)} → ${target}`)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
fixed++
|
|
62
|
+
console.log(` fixed ${rel(link)}`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function rel(p) {
|
|
66
|
+
return path.relative(root, p) || p
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Resolve the canonical store dir for a package: .pnpm/<+name>@<ver>/node_modules</name>. */
|
|
70
|
+
function storeDir(pkgName, versionHint) {
|
|
71
|
+
const storeName = pkgName.replace('/', '+')
|
|
72
|
+
if (versionHint) {
|
|
73
|
+
const direct = path.join(pnpm, `${storeName}@${versionHint}`)
|
|
74
|
+
if (existsSync(direct)) return direct
|
|
75
|
+
}
|
|
76
|
+
const prefix = `${storeName}@`
|
|
77
|
+
const hit = readdirSync(pnpm).find(
|
|
78
|
+
(d) => d.startsWith(prefix) && existsSync(path.join(pnpm, d, 'node_modules', pkgName)),
|
|
79
|
+
)
|
|
80
|
+
return hit ? path.join(pnpm, hit) : null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Fix every dead scoped/unscoped link directly under one node_modules. */
|
|
84
|
+
function fixLevel(levelNm, scope = null) {
|
|
85
|
+
for (const entry of readdirSync(levelNm)) {
|
|
86
|
+
const link = path.join(levelNm, entry)
|
|
87
|
+
let st
|
|
88
|
+
try {
|
|
89
|
+
st = lstatSync(link)
|
|
90
|
+
} catch {
|
|
91
|
+
continue
|
|
92
|
+
}
|
|
93
|
+
if (!st.isSymbolicLink()) {
|
|
94
|
+
if (st.isDirectory() && entry.startsWith('@')) fixLevel(link, entry)
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
checked++
|
|
98
|
+
if (!broken(link)) continue
|
|
99
|
+
|
|
100
|
+
const target = readlinkSync(link)
|
|
101
|
+
const pkgName = scope ? `${scope}/${entry}` : entry
|
|
102
|
+
const storeName = pkgName.replace('/', '+')
|
|
103
|
+
|
|
104
|
+
// pnpm layout: <link> → .pnpm/<pkg>@<ver[_peer]>…/node_modules/<pkg>.
|
|
105
|
+
// Re-derive that dir; the old target string is Linux-shaped and useless.
|
|
106
|
+
const base = target.split('node_modules/')[0]?.split('/').pop() ?? ''
|
|
107
|
+
const versionHint = base.startsWith(`${storeName}@`) ? base : null
|
|
108
|
+
const store = storeDir(pkgName, versionHint)
|
|
109
|
+
let resolved = store ? path.join(store, 'node_modules', pkgName) : null
|
|
110
|
+
|
|
111
|
+
// Scoped aliases link a nested path (e.g. @standard-schema/spec lives in
|
|
112
|
+
// the @standard-schema+spec store); fall back to matching by the target's
|
|
113
|
+
// node_modules tail — or, when the target names no node_modules at all
|
|
114
|
+
// (dangling junk), by the package name itself.
|
|
115
|
+
if (!resolved) {
|
|
116
|
+
const tail = target.includes('node_modules/')
|
|
117
|
+
? target.split('node_modules/').pop()
|
|
118
|
+
: pkgName
|
|
119
|
+
for (const dir of readdirSync(pnpm)) {
|
|
120
|
+
const candidate = path.join(pnpm, dir, 'node_modules', tail)
|
|
121
|
+
if (existsSync(candidate)) {
|
|
122
|
+
resolved = candidate
|
|
123
|
+
break
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (resolved && existsSync(resolved)) {
|
|
129
|
+
junction(link, resolved)
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Non-store link (workspace `link:` deps, profile installs): re-point at
|
|
134
|
+
// the original Windows path when that still exists.
|
|
135
|
+
const asWin = target.replaceAll('/', '\\')
|
|
136
|
+
if (existsSync(asWin)) {
|
|
137
|
+
junction(link, asWin)
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
console.error(` no target for ${rel(link)} (${target}) — remove or reinstall`)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (existsSync(pnpm)) {
|
|
145
|
+
for (const dir of readdirSync(pnpm)) {
|
|
146
|
+
const levelNm = path.join(pnpm, dir, 'node_modules')
|
|
147
|
+
if (existsSync(levelNm)) fixLevel(levelNm)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
fixLevel(nm)
|
|
151
|
+
|
|
152
|
+
console.log(`\n${root}`)
|
|
153
|
+
console.log(`links checked: ${checked}, fixed: ${fixed}`)
|
|
154
|
+
process.exit(fixed > 0 ? 0 : 0)
|