@lvce-editor/shared-process 0.115.27 → 0.116.4
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/package.json +2 -2
- package/src/parts/BuiltinExtensionsPath/BuiltinExtensionsPath.js +1 -1
- package/src/parts/Platform/Platform.js +3 -3
- package/src/parts/PreloadUrl/PreloadUrl.js +1 -1
- package/src/parts/WindowsStagedUpdate/WindowsStagedUpdate.js +24 -36
- package/src/parts/WindowsUpdateHelper/WindowsUpdateHelper.js +44 -0
- package/src/parts/WindowsUpdateScripts/WindowsUpdateScripts.js +10 -16
- package/src/parts/Workers/Workers.json +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lvce-editor/shared-process",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.116.4",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@lvce-editor/json-rpc": "8.8.1",
|
|
24
24
|
"@lvce-editor/jsonc-parser": "1.5.2",
|
|
25
25
|
"@lvce-editor/pretty-error": "2.0.2",
|
|
26
|
-
"@lvce-editor/process-explorer": "3.30.
|
|
26
|
+
"@lvce-editor/process-explorer": "3.30.2",
|
|
27
27
|
"@lvce-editor/rpc-registry": "9.64.0",
|
|
28
28
|
"@lvce-editor/verror": "1.7.0",
|
|
29
29
|
"is-object": "^1.0.2",
|
|
@@ -3,6 +3,6 @@ import { fileURLToPath } from 'url'
|
|
|
3
3
|
|
|
4
4
|
export const getBuiltinExtensionsPath = () => {
|
|
5
5
|
const staticServerPath = fileURLToPath(import.meta.resolve('@lvce-editor/static-server'))
|
|
6
|
-
const builtinExtensionsPath = join(staticServerPath, '..', '..', 'static', '
|
|
6
|
+
const builtinExtensionsPath = join(staticServerPath, '..', '..', 'static', '3ccf100', 'extensions')
|
|
7
7
|
return builtinExtensionsPath
|
|
8
8
|
}
|
|
@@ -56,11 +56,11 @@ export const getSetupName = () => {
|
|
|
56
56
|
return 'Lvce-Setup'
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
export const version = '0.
|
|
59
|
+
export const version = '0.116.4'
|
|
60
60
|
|
|
61
|
-
export const commit = '
|
|
61
|
+
export const commit = '3ccf100'
|
|
62
62
|
|
|
63
|
-
export const date = '2026-09-
|
|
63
|
+
export const date = '2026-09-19T18:16:04.000Z'
|
|
64
64
|
|
|
65
65
|
export const getVersion = () => {
|
|
66
66
|
return version
|
|
@@ -2,5 +2,5 @@ import { join } from 'node:path'
|
|
|
2
2
|
import * as Root from '../Root/Root.js'
|
|
3
3
|
|
|
4
4
|
export const getPreloadUrl = () => {
|
|
5
|
-
return join(Root.root, 'static', '
|
|
5
|
+
return join(Root.root, 'static', '3ccf100', 'packages', 'preload', 'dist', 'index.js')
|
|
6
6
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { execFile
|
|
1
|
+
import { execFile } from 'node:child_process'
|
|
2
2
|
import { createHash, randomBytes } from 'node:crypto'
|
|
3
|
-
import {
|
|
4
|
-
import { access, copyFile, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
|
|
5
|
-
import { basename, dirname, join } from 'node:path'
|
|
3
|
+
import { createReadStream, createWriteStream } from 'node:fs'
|
|
4
|
+
import { access, copyFile, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
|
|
5
|
+
import { basename, dirname, join, toNamespacedPath } from 'node:path'
|
|
6
6
|
import { Readable } from 'node:stream'
|
|
7
7
|
import { pipeline } from 'node:stream/promises'
|
|
8
8
|
import { promisify } from 'node:util'
|
|
@@ -11,6 +11,7 @@ import * as ElectronDialog from '../ElectronDialog/ElectronDialog.js'
|
|
|
11
11
|
import * as Exit from '../Exit/Exit.js'
|
|
12
12
|
import * as Platform from '../Platform/Platform.js'
|
|
13
13
|
import * as UpdateLog from '../UpdateLog/UpdateLog.js'
|
|
14
|
+
import * as WindowsUpdateHelper from '../WindowsUpdateHelper/WindowsUpdateHelper.js'
|
|
14
15
|
import * as WindowsUpdateScripts from '../WindowsUpdateScripts/WindowsUpdateScripts.js'
|
|
15
16
|
|
|
16
17
|
|
|
@@ -33,19 +34,11 @@ const RE_DIGEST = /^sha256:[a-f0-9]{64}$/
|
|
|
33
34
|
const RE_TAG = /^v/
|
|
34
35
|
const RE_TOKEN = /^[a-f0-9]{32}$/
|
|
35
36
|
|
|
36
|
-
const powershell = () => join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe')
|
|
37
|
-
const argumentsFor = (script) => [
|
|
38
|
-
'-NoProfile',
|
|
39
|
-
'-NonInteractive',
|
|
40
|
-
'-EncodedCommand',
|
|
41
|
-
Buffer.from(script, 'utf16le').toString('base64'),
|
|
42
|
-
]
|
|
43
|
-
|
|
44
37
|
export const validateAsset = (asset, version, arch) => {
|
|
45
38
|
if (!RE_VERSION.test(version) || !['x64', 'arm64'].includes(arch)) {
|
|
46
39
|
throw new Error('Unsupported Windows update version or architecture')
|
|
47
40
|
}
|
|
48
|
-
const name = `Lvce-
|
|
41
|
+
const name = `Lvce-Setup-v${version}-${arch}.exe`
|
|
49
42
|
if (asset.name !== name || asset.browser_download_url !== `https://github.com/lvce-editor/lvce-editor/releases/download/v${version}/${name}`) {
|
|
50
43
|
throw new Error('Unexpected Windows update asset')
|
|
51
44
|
}
|
|
@@ -78,7 +71,7 @@ const prepare = async (asset, version) => {
|
|
|
78
71
|
await mkdir(work, { recursive: true })
|
|
79
72
|
const path = join(work, `${token}.json`)
|
|
80
73
|
const plan = {
|
|
81
|
-
archive: join(work, `${token}.
|
|
74
|
+
archive: join(work, `${token}.exe`),
|
|
82
75
|
backup: `${install}.backup-${token}`,
|
|
83
76
|
exe: basename(process.execPath),
|
|
84
77
|
install,
|
|
@@ -98,11 +91,14 @@ const prepare = async (asset, version) => {
|
|
|
98
91
|
await pipeline(Readable.fromWeb(response.body), createWriteStream(plan.archive, { flags: 'wx' }))
|
|
99
92
|
await verifyDigest(plan.archive, asset.digest)
|
|
100
93
|
UpdateLog.write(`Staged update download verified version=${version}; extraction started`)
|
|
101
|
-
await promisify(execFile)(
|
|
102
|
-
env: { ...process.env,
|
|
94
|
+
await promisify(execFile)(plan.archive, ['/S', '/LVCESTAGE'], {
|
|
95
|
+
env: { ...process.env, LVCE_UPDATE_STAGE: toNamespacedPath(stage) },
|
|
103
96
|
timeout: 600_000,
|
|
104
97
|
windowsHide: true,
|
|
105
98
|
})
|
|
99
|
+
if ((await readFile(join(stage, '.lvce-stage-complete'), 'utf8')) !== 'complete') {
|
|
100
|
+
throw new Error('Installer did not confirm update preparation')
|
|
101
|
+
}
|
|
106
102
|
const config = JSON.parse(await readFile(join(stage, 'resources', 'app', 'config.json'), 'utf8'))
|
|
107
103
|
if (config.version !== version) {
|
|
108
104
|
throw new Error('Extracted application version does not match release')
|
|
@@ -126,20 +122,7 @@ const restart = async ({ path, plan } ) => {
|
|
|
126
122
|
await writeFile(path, JSON.stringify({ ...plan, parentPid: process.ppid }))
|
|
127
123
|
await rm(`${path}.started`, { force: true })
|
|
128
124
|
await writeFile(`${path}.helper.ps1`, WindowsUpdateScripts.activate)
|
|
129
|
-
const
|
|
130
|
-
const child = spawn(powershell(), argumentsFor(WindowsUpdateScripts.activate), {
|
|
131
|
-
cwd: dirname(plan.install),
|
|
132
|
-
detached: true,
|
|
133
|
-
env: { ...process.env, LVCE_UPDATE_PLAN: path },
|
|
134
|
-
stdio: ['ignore', output, output],
|
|
135
|
-
windowsHide: true,
|
|
136
|
-
})
|
|
137
|
-
closeSync(output)
|
|
138
|
-
await new Promise ((resolve, reject) => {
|
|
139
|
-
child.once('spawn', resolve)
|
|
140
|
-
child.once('error', reject)
|
|
141
|
-
})
|
|
142
|
-
child.unref()
|
|
125
|
+
const helperPid = await WindowsUpdateHelper.launch(WindowsUpdateScripts.activate, path, dirname(plan.install))
|
|
143
126
|
// Do not exit if PowerShell is blocked or the helper cannot initialize.
|
|
144
127
|
for (let attempt = 0; attempt < 300; attempt++) {
|
|
145
128
|
if ((await readFile(`${path}.started`, 'utf8').catch(() => '')) === 'ready') {
|
|
@@ -147,12 +130,14 @@ const restart = async ({ path, plan } ) => {
|
|
|
147
130
|
await Exit.exit()
|
|
148
131
|
return
|
|
149
132
|
}
|
|
150
|
-
if (
|
|
151
|
-
throw new Error(`Update helper exited before startup
|
|
133
|
+
if (!WindowsUpdateHelper.isRunning(helperPid)) {
|
|
134
|
+
throw new Error(`Update helper exited before startup; see ${path}.output.log`)
|
|
152
135
|
}
|
|
153
136
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
154
137
|
}
|
|
155
|
-
|
|
138
|
+
if (WindowsUpdateHelper.isRunning(helperPid)) {
|
|
139
|
+
process.kill(helperPid)
|
|
140
|
+
}
|
|
156
141
|
throw new Error('Update helper did not acknowledge startup; editor left running')
|
|
157
142
|
}
|
|
158
143
|
|
|
@@ -176,8 +161,9 @@ export const check = async (silent, windowId) => {
|
|
|
176
161
|
}
|
|
177
162
|
return true
|
|
178
163
|
}
|
|
179
|
-
const
|
|
180
|
-
|
|
164
|
+
const capability = release.assets.some((item) => item.name === `Lvce-Stage-v${version}-${process.arch}-v2.json`)
|
|
165
|
+
const asset = release.assets.find((item) => item.name === `Lvce-Setup-v${version}-${process.arch}.exe`)
|
|
166
|
+
if (!capability || !asset) {
|
|
181
167
|
return false
|
|
182
168
|
}
|
|
183
169
|
if (!silent) {
|
|
@@ -225,6 +211,8 @@ export const confirmStartup = async () => {
|
|
|
225
211
|
if (!RE_TOKEN.test(token) || version !== Platform.version) {
|
|
226
212
|
throw new Error('Invalid staged update startup marker')
|
|
227
213
|
}
|
|
228
|
-
|
|
214
|
+
const ready = join(`${install}.updates`, `${token}.ready`)
|
|
215
|
+
await writeFile(`${ready}.tmp`, JSON.stringify({ pid: process.ppid, token, version }))
|
|
216
|
+
await rename(`${ready}.tmp`, ready)
|
|
229
217
|
UpdateLog.write(`Staged update startup confirmed version=${version}`)
|
|
230
218
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { promisify } from 'node:util'
|
|
4
|
+
|
|
5
|
+
const encode = (script) => Buffer.from(script, 'utf16le').toString('base64')
|
|
6
|
+
|
|
7
|
+
const bootstrap = String.raw`
|
|
8
|
+
$ErrorActionPreference = 'Stop'
|
|
9
|
+
$child = Start-Process -FilePath (Join-Path $PSHOME 'powershell.exe') -ArgumentList @('-NoProfile', '-NonInteractive', '-EncodedCommand', $env:LVCE_UPDATE_SCRIPT) -WorkingDirectory (Get-Location).Path -WindowStyle Hidden -PassThru
|
|
10
|
+
Write-Output $child.Id
|
|
11
|
+
`
|
|
12
|
+
|
|
13
|
+
export const launch = async (script, planPath, cwd) => {
|
|
14
|
+
// Windows PowerShell exits without executing when launched with Node's
|
|
15
|
+
// DETACHED_PROCESS flag. Start-Process gives it its own hidden console so it
|
|
16
|
+
// executes normally and survives even a console parent's termination.
|
|
17
|
+
// Output must be separate from the phase log: .NET AppendAllText cannot open
|
|
18
|
+
// a file also held open as redirected stdout/stderr on Windows.
|
|
19
|
+
const wrapped = `$ErrorActionPreference = 'Stop'\ntry { & {\n${script}\n} *>&1 | Out-File -LiteralPath ($env:LVCE_UPDATE_PLAN + '.output.log') -Encoding utf8 } catch { [IO.File]::AppendAllText($env:LVCE_UPDATE_PLAN + '.output.log', ($_ | Out-String)); exit 1 }`
|
|
20
|
+
const { stdout } = await promisify(execFile)(
|
|
21
|
+
join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'),
|
|
22
|
+
['-NoProfile', '-NonInteractive', '-EncodedCommand', encode(bootstrap)],
|
|
23
|
+
{
|
|
24
|
+
cwd,
|
|
25
|
+
env: { ...process.env, LVCE_UPDATE_PLAN: planPath, LVCE_UPDATE_SCRIPT: encode(wrapped) },
|
|
26
|
+
timeout: 15000,
|
|
27
|
+
windowsHide: true,
|
|
28
|
+
},
|
|
29
|
+
)
|
|
30
|
+
const pid = Number(stdout.trim())
|
|
31
|
+
if (!Number.isSafeInteger(pid) || pid <= 0) {
|
|
32
|
+
throw new Error('Windows update helper did not return a valid process id')
|
|
33
|
+
}
|
|
34
|
+
return pid
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const isRunning = (pid) => {
|
|
38
|
+
try {
|
|
39
|
+
process.kill(pid, 0)
|
|
40
|
+
return true
|
|
41
|
+
} catch {
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -61,14 +61,20 @@ try {
|
|
|
61
61
|
$movedNew = $true
|
|
62
62
|
Journal 'launching-new'
|
|
63
63
|
$newProcess = Start-Process -FilePath (Join-Path $install $plan.exe) -ArgumentList '--updated' -WorkingDirectory $install -PassThru -WindowStyle Hidden
|
|
64
|
+
Log ('Launcher process id=' + $newProcess.Id)
|
|
64
65
|
$deadline = [DateTime]::UtcNow.AddSeconds(60)
|
|
65
66
|
while (!(Test-Path -LiteralPath $plan.ready)) {
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
# ShellExecute can return a launcher that exits before the actual editor
|
|
68
|
+
# acknowledges startup. The acknowledgment identifies the real main process.
|
|
68
69
|
if ([DateTime]::UtcNow -gt $deadline) { throw 'Updated application did not confirm startup' }
|
|
69
70
|
Start-Sleep -Milliseconds 100
|
|
70
71
|
}
|
|
71
|
-
|
|
72
|
+
$ack = Get-Content -LiteralPath $plan.ready -Raw | ConvertFrom-Json
|
|
73
|
+
if ($ack.token -ne $plan.token -or $ack.version -ne $plan.version -or $ack.pid -le 0) { throw 'Invalid startup acknowledgment' }
|
|
74
|
+
$confirmedProcess = Get-Process -Id $ack.pid -ErrorAction Stop
|
|
75
|
+
if ($confirmedProcess.Path -ne (Join-Path $install $plan.exe)) { throw 'Startup acknowledgment belongs to another executable' }
|
|
76
|
+
$newProcess = $confirmedProcess
|
|
77
|
+
Log ('Confirmed editor process id=' + $newProcess.Id)
|
|
72
78
|
Journal 'startup-confirmed'
|
|
73
79
|
try {
|
|
74
80
|
Get-ChildItem 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall' | ForEach-Object {
|
|
@@ -89,7 +95,7 @@ try {
|
|
|
89
95
|
$longBackup = '\\?\' + $backup
|
|
90
96
|
if ($backup.StartsWith('\\')) { $longBackup = '\\?\UNC\' + $backup.Substring(2) }
|
|
91
97
|
Remove-Item -LiteralPath $longBackup -Recurse -Force
|
|
92
|
-
if ($plan.archive -eq (Join-Path ($install + '.updates') ($plan.token + '.
|
|
98
|
+
if ($plan.archive -eq (Join-Path ($install + '.updates') ($plan.token + '.exe'))) {
|
|
93
99
|
Remove-Item -LiteralPath $plan.archive -Force -ErrorAction SilentlyContinue
|
|
94
100
|
}
|
|
95
101
|
Journal 'complete'
|
|
@@ -116,15 +122,3 @@ try {
|
|
|
116
122
|
exit 1
|
|
117
123
|
}
|
|
118
124
|
`
|
|
119
|
-
|
|
120
|
-
export const extract = String.raw`
|
|
121
|
-
$ErrorActionPreference = 'Stop'
|
|
122
|
-
$plan = Get-Content -LiteralPath $env:LVCE_UPDATE_PLAN -Raw | ConvertFrom-Json
|
|
123
|
-
# Windows PowerShell's .NET Framework ZIP extraction fails on deeply nested
|
|
124
|
-
# extension paths. The Windows inbox bsdtar supports these long paths and
|
|
125
|
-
# rejects parent-directory traversal by default. Never reuse a partial stage.
|
|
126
|
-
if (Test-Path -LiteralPath $plan.stage) { throw 'Staging directory already exists' }
|
|
127
|
-
[IO.Directory]::CreateDirectory($plan.stage) | Out-Null
|
|
128
|
-
& (Join-Path $env:SystemRoot 'System32\tar.exe') -xf $plan.archive -C $plan.stage
|
|
129
|
-
if ($LASTEXITCODE -ne 0) { throw "Windows update extraction failed ($LASTEXITCODE)" }
|
|
130
|
-
`
|
|
@@ -191,6 +191,8 @@
|
|
|
191
191
|
"diff": { "name": "ActivityBar.diff2", "parameters": [{ "source": "state", "name": "uid" }] },
|
|
192
192
|
"render": { "name": "ActivityBar.render2", "parameters": [{ "source": "state", "name": "uid" }, { "source": "result", "name": "diff" }] },
|
|
193
193
|
"saveState": { "name": "ActivityBar.saveState", "parameters": [{ "source": "state", "name": "uid" }] },
|
|
194
|
+
"getComponentState": { "name": "ActivityBar.getComponentState", "parameters": [{ "source": "state", "name": "uid" }] },
|
|
195
|
+
"setComponentState": { "name": "ActivityBar.setComponentState", "parameters": [{ "source": "state", "name": "uid" }, { "source": "argument", "name": "componentState" }] },
|
|
194
196
|
"terminate": { "name": "ActivityBar.terminate", "parameters": [] },
|
|
195
197
|
"getCommandIds": { "name": "ActivityBar.getCommandIds", "parameters": [] },
|
|
196
198
|
"getMenuEntryIds": { "name": "ActivityBar.getMenuEntryIds", "parameters": [] },
|