dsh-clean-desktop-shell 0.1.4 → 0.1.6
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/README.en.md +38 -34
- package/README.md +35 -29
- package/electron/update.js +32 -4
- package/lib/common.js +22 -0
- package/lib/icon.js +63 -0
- package/lib/index.js +7 -276
- package/lib/runtime.js +213 -0
- package/package.json +10 -2
- package/scripts/build.mjs +3 -0
- package/src/host/common.js +22 -0
- package/src/host/icon.js +63 -0
- package/src/host/index.js +7 -276
- package/src/host/runtime.js +213 -0
- package/version.txt +1 -1
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Electron runtime provisioning for the plugin-market (branch 2) shell.
|
|
3
|
+
*
|
|
4
|
+
* The electron runtime is NOT an npm dependency (electron-builder forbids
|
|
5
|
+
* electron in "dependencies", and pnpm's allowBuilds would block its
|
|
6
|
+
* postinstall anyway). This module provisions it under
|
|
7
|
+
* $DSH_HOME/desktop-shell-runtime/electron-v<ver>/:
|
|
8
|
+
*
|
|
9
|
+
* 1. resolve the version (package.json → desktopShell.electronVersion)
|
|
10
|
+
* 2. if that version dir exists → reuse it
|
|
11
|
+
* 3. otherwise reuse a local electron package (DSH_SHELL_ELECTRON_DIR),
|
|
12
|
+
* else download the official zip from the fastest reachable source
|
|
13
|
+
* 4. drop stale electron-v* dirs (no unbounded disk growth)
|
|
14
|
+
*/
|
|
15
|
+
import { spawn } from 'node:child_process'
|
|
16
|
+
import {
|
|
17
|
+
cpSync,
|
|
18
|
+
existsSync,
|
|
19
|
+
mkdirSync,
|
|
20
|
+
readFileSync,
|
|
21
|
+
readdirSync,
|
|
22
|
+
renameSync,
|
|
23
|
+
rmSync,
|
|
24
|
+
symlinkSync,
|
|
25
|
+
} from 'node:fs'
|
|
26
|
+
import { join } from 'node:path'
|
|
27
|
+
import { PKG_ROOT, EXE_NAME, ARCH, PLATFORM, runtimeRoot } from './common.js'
|
|
28
|
+
|
|
29
|
+
function electronVersion() {
|
|
30
|
+
try {
|
|
31
|
+
const meta = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf8'))
|
|
32
|
+
return meta.desktopShell?.electronVersion || null
|
|
33
|
+
} catch {
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function versionDir(root, version) {
|
|
39
|
+
return join(root, `electron-v${version}`)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function zipName(version) {
|
|
43
|
+
return `electron-v${version}-${PLATFORM}-${ARCH}.zip`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Resolve (or provision) the runtime and return the electron exe path. */
|
|
47
|
+
export async function ensureRuntime(ctx) {
|
|
48
|
+
const version = electronVersion()
|
|
49
|
+
if (!version) throw new Error('desktopShell.electronVersion missing in package.json')
|
|
50
|
+
const root = runtimeRoot()
|
|
51
|
+
const dir = versionDir(root, version)
|
|
52
|
+
const exe = join(dir, EXE_NAME)
|
|
53
|
+
|
|
54
|
+
// 1) Already provisioned for this version?
|
|
55
|
+
if (existsSync(exe)) {
|
|
56
|
+
cleanupOldVersions(root, dir)
|
|
57
|
+
return exe
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 2) Local reuse: DSH_SHELL_ELECTRON_DIR → link/copy its dist/ (fast).
|
|
61
|
+
const localSrc = process.env.DSH_SHELL_ELECTRON_DIR
|
|
62
|
+
if (localSrc) {
|
|
63
|
+
const srcExe = join(localSrc, 'dist', EXE_NAME)
|
|
64
|
+
if (existsSync(srcExe) && provisionLocalDist(localSrc, dir)) {
|
|
65
|
+
ctx.logger.info(`[clean-desktop-shell] reused electron runtime from ${localSrc}`)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 3) Download + extract the official zip from a network-appropriate source.
|
|
70
|
+
if (!existsSync(exe)) {
|
|
71
|
+
mkdirSync(root, { recursive: true })
|
|
72
|
+
await downloadRuntime(ctx, version, root, dir)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!existsSync(exe)) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
'electron runtime provisioning failed — check network, or point DSH_SHELL_ELECTRON_DIR at an electron package',
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
cleanupOldVersions(root, dir)
|
|
81
|
+
ctx.logger.info(`[clean-desktop-shell] electron runtime ${version} ready at ${dir}`)
|
|
82
|
+
return exe
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function downloadRuntime(ctx, version, root, dir) {
|
|
86
|
+
const tmpZip = join(root, `.electron-${version}.zip.tmp`)
|
|
87
|
+
rmSync(tmpZip, { force: true })
|
|
88
|
+
const urls = await runtimeUrls(version)
|
|
89
|
+
for (const url of urls) {
|
|
90
|
+
ctx.logger.info(`[clean-desktop-shell] downloading electron ${version} from ${url}`)
|
|
91
|
+
if (await fetchFile(url, tmpZip)) {
|
|
92
|
+
// Zip extracts to an inner dir named like the zip basename.
|
|
93
|
+
const inner = join(root, zipName(version).replace(/\.zip$/, ''))
|
|
94
|
+
try {
|
|
95
|
+
await extractZip(tmpZip, root)
|
|
96
|
+
if (existsSync(join(inner, EXE_NAME)) && inner !== dir) {
|
|
97
|
+
rmSync(dir, { recursive: true, force: true })
|
|
98
|
+
renameSync(inner, dir)
|
|
99
|
+
}
|
|
100
|
+
rmSync(tmpZip, { force: true })
|
|
101
|
+
return
|
|
102
|
+
} catch (err) {
|
|
103
|
+
ctx.logger.warn(`[clean-desktop-shell] extract failed: ${err?.message ?? err}`)
|
|
104
|
+
rmSync(inner, { recursive: true, force: true })
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Failed download — drop the partial file so a later run starts clean.
|
|
108
|
+
rmSync(tmpZip, { force: true })
|
|
109
|
+
}
|
|
110
|
+
throw new Error('electron download failed from all sources')
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Pick the download source by racing a HEAD probe against each candidate
|
|
115
|
+
* (direct connection, 3s each). The fastest reachable source goes first —
|
|
116
|
+
* this naturally prefers the domestic npmmirror mirror on CN networks,
|
|
117
|
+
* the official GitHub source on international/well-proxied networks, and
|
|
118
|
+
* never wastes a full download on a dead source.
|
|
119
|
+
*/
|
|
120
|
+
async function runtimeUrls(version) {
|
|
121
|
+
const candidates = [
|
|
122
|
+
{ name: 'github', url: `https://github.com/electron/electron/releases/download/v${version}/${zipName(version)}` },
|
|
123
|
+
{ name: 'npmmirror', url: `https://npmmirror.com/mirrors/electron/${version}/${zipName(version)}` },
|
|
124
|
+
]
|
|
125
|
+
const results = await Promise.all(
|
|
126
|
+
candidates.map(async (c) => {
|
|
127
|
+
const t0 = Date.now()
|
|
128
|
+
try {
|
|
129
|
+
const ctrl = new AbortController()
|
|
130
|
+
const timer = setTimeout(() => ctrl.abort(), 3000)
|
|
131
|
+
const res = await fetch(c.url, { signal: ctrl.signal, method: 'HEAD' })
|
|
132
|
+
clearTimeout(timer)
|
|
133
|
+
if (res.status < 500) return { ...c, ms: Date.now() - t0 }
|
|
134
|
+
} catch {
|
|
135
|
+
// unreachable — drop
|
|
136
|
+
}
|
|
137
|
+
return null
|
|
138
|
+
}),
|
|
139
|
+
)
|
|
140
|
+
const ok = results.filter(Boolean).sort((a, b) => a.ms - b.ms)
|
|
141
|
+
if (ok.length === 0) {
|
|
142
|
+
// Probes all failed (offline?) — still try both, mirror first (cheap).
|
|
143
|
+
return [candidates[1].url, candidates[0].url]
|
|
144
|
+
}
|
|
145
|
+
const rest = candidates.map((c) => c.url).filter((u) => u !== ok[0].url)
|
|
146
|
+
return [ok[0].url, ...rest]
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function fetchFile(url, dest) {
|
|
150
|
+
return new Promise((resolve) => {
|
|
151
|
+
// curl is available on Windows 10+; streams to disk, honors proxy env.
|
|
152
|
+
// --max-time keeps a stalled download from hanging forever (a proxy
|
|
153
|
+
// stall previously left a half-written .zip.tmp and blocked the shell
|
|
154
|
+
// launch); --retry 2 rides out transient failures.
|
|
155
|
+
const child = spawn(
|
|
156
|
+
'curl',
|
|
157
|
+
['-L', '--fail', '--silent', '--show-error', '--retry', '2', '--max-time', '600', '-o', dest, url],
|
|
158
|
+
{ windowsHide: true, stdio: 'ignore' },
|
|
159
|
+
)
|
|
160
|
+
child.on('error', () => resolve(false))
|
|
161
|
+
child.on('exit', (code) => resolve(code === 0))
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function extractZip(zipPath, dest) {
|
|
166
|
+
// Windows ships bsdtar (tar.exe) which reads zip; fall back to
|
|
167
|
+
// PowerShell Expand-Archive if needed.
|
|
168
|
+
const child = spawn('tar', ['-xf', zipPath, '-C', dest], { windowsHide: true, stdio: 'ignore' })
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
child.on('error', reject)
|
|
171
|
+
child.on('exit', (code) => (code === 0 ? resolve() : reject(new Error(`tar exit ${code}`))))
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Remove version dirs older than the current one (dead weight). */
|
|
176
|
+
function cleanupOldVersions(root, currentDir) {
|
|
177
|
+
try {
|
|
178
|
+
for (const entry of readdirSync(root)) {
|
|
179
|
+
if (!entry.startsWith('electron-v')) continue
|
|
180
|
+
const full = join(root, entry)
|
|
181
|
+
if (full === currentDir) continue
|
|
182
|
+
rmSync(full, { recursive: true, force: true })
|
|
183
|
+
}
|
|
184
|
+
} catch {
|
|
185
|
+
// best-effort
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Provision a local electron package's dist/ as the version dir itself,
|
|
191
|
+
* so the layout matches a downloaded runtime: <dir>/electron(.exe) at the
|
|
192
|
+
* version-dir root. Windows: junction (zero-copy, instant) — a 269MB
|
|
193
|
+
* recursive cpSync can be killed by sandbox/AV on large trees, so only
|
|
194
|
+
* fall back to a copy.
|
|
195
|
+
*/
|
|
196
|
+
function provisionLocalDist(srcPkg, destDir) {
|
|
197
|
+
if (process.platform === 'win32') {
|
|
198
|
+
try {
|
|
199
|
+
rmSync(destDir, { recursive: true, force: true })
|
|
200
|
+
symlinkSync(join(srcPkg, 'dist'), destDir, 'junction')
|
|
201
|
+
return true
|
|
202
|
+
} catch {
|
|
203
|
+
// fall through to a real copy
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
rmSync(destDir, { recursive: true, force: true })
|
|
208
|
+
cpSync(join(srcPkg, 'dist'), destDir, { recursive: true })
|
|
209
|
+
return true
|
|
210
|
+
} catch {
|
|
211
|
+
return false
|
|
212
|
+
}
|
|
213
|
+
}
|
package/version.txt
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.
|
|
1
|
+
0.1.6
|