@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/shared-process",
3
- "version": "0.115.27",
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.1",
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', 'c2f78b2', 'extensions')
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.115.27'
59
+ export const version = '0.116.4'
60
60
 
61
- export const commit = 'c2f78b2'
61
+ export const commit = '3ccf100'
62
62
 
63
- export const date = '2026-09-19T13:51:30.000Z'
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', 'c2f78b2', 'packages', 'preload', 'dist', 'index.js')
5
+ return join(Root.root, 'static', '3ccf100', 'packages', 'preload', 'dist', 'index.js')
6
6
  }
@@ -1,8 +1,8 @@
1
- import { execFile, spawn } from 'node:child_process'
1
+ import { execFile } from 'node:child_process'
2
2
  import { createHash, randomBytes } from 'node:crypto'
3
- import { closeSync, createReadStream, createWriteStream } from 'node:fs'
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-Update-v${version}-${arch}.zip`
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}.zip`),
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)(powershell(), argumentsFor(WindowsUpdateScripts.extract), {
102
- env: { ...process.env, LVCE_UPDATE_PLAN: path },
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 output = UpdateLog.openOutput()
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 (child.exitCode !== null) {
151
- throw new Error(`Update helper exited before startup (${child.exitCode})`)
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
- child.kill()
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 asset = release.assets.find((item) => item.name === `Lvce-Update-v${version}-${process.arch}.zip`)
180
- if (!asset) {
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
- await writeFile(join(`${install}.updates`, `${token}.ready`), token)
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
- $newProcess.Refresh()
67
- if ($newProcess.HasExited) { throw 'Updated application exited before confirming startup' }
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
- if ((Get-Content -LiteralPath $plan.ready -Raw) -ne $plan.token) { throw 'Invalid startup acknowledgment' }
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 + '.zip'))) {
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": [] },