File size: 10,481 Bytes
58a9693 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 | /** Relocatable, integrity-recorded production packages carried by one Desktop release. */
import { createHash } from 'node:crypto'
import { lstatSync, readdirSync, readFile, readFileSync, writeFileSync } from 'node:fs'
import { isAbsolute, join, relative, sep } from 'node:path'
import { promisify } from 'node:util'
import { valid } from 'semver'
import { DESKTOP_HOST_PACKAGE } from './core-package-set.ts'
import { parseDesktopRelease, type DesktopRelease } from './release.ts'
/** Descriptor at the root of the immutable Desktop resource tree. */
export const DESKTOP_RUNTIME_FILE = 'desktop-runtime.json'
/** Host-owned package available to external plugins through a directory link. */
export interface DesktopSharedPackage {
readonly name: string
readonly version: string
readonly path: string
}
/** Final bytes and executable permissions of a runtime file. */
export interface DesktopRuntimeFile {
readonly path: string
readonly bytes: number
readonly sha256: string
readonly executable: boolean
}
/** One signed application's production dependency tree. */
export interface DesktopRuntimeDescriptor {
readonly schemaVersion: 1
readonly release: DesktopRelease
readonly platform: NodeJS.Platform
readonly arch: string
readonly sharedPackages: readonly DesktopSharedPackage[]
readonly files: readonly DesktopRuntimeFile[]
}
const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/[a-z0-9][a-z0-9._~-]*|[a-z0-9][a-z0-9._~-]*)$/u
const readRuntimeFile = promisify(readFile)
function record(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
/**
* Resolve one portable resource path without permitting traversal or absolute paths.
* @param root - Runtime root.
* @param path - Slash-separated relative path from durable metadata.
* @returns Absolute resource path.
*/
export function runtimePath(root: string, path: string): string {
if (path === '' || isAbsolute(path) || path.includes('\\') || path.includes(':')
|| path.split('/').some(part => part === '' || part === '.' || part === '..')) {
throw new Error(`desktop runtime: invalid relative path ${JSON.stringify(path)}`)
}
return join(root, ...path.split('/'))
}
function runtimeFiles(root: string): { path: string; name: string }[] {
const files: { path: string; name: string }[] = []
const visit = (directory: string): void => {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
const path = join(directory, entry.name)
const name = relative(root, path).split(sep).join('/')
if (name === DESKTOP_RUNTIME_FILE) continue
if (entry.isDirectory()) visit(path)
else if (entry.isFile()) files.push({ path, name })
else throw new Error(`desktop runtime: unsupported filesystem entry ${name}`)
}
}
visit(root)
return files
}
function runtimeFile(path: string, name: string, body: Buffer): DesktopRuntimeFile {
return { path: name, bytes: body.byteLength, sha256: createHash('sha256').update(body).digest('hex'),
// Windows has no portable Unix executable permission bits.
executable: process.platform !== 'win32' && (lstatSync(path).mode & 0o111) !== 0 }
}
/**
* Inventory a materialized runtime without following links or including its descriptor.
* @param root - Self-contained runtime directory.
* @returns Sorted final-file inventory; executable permissions are false on Windows.
*/
export function inventoryDesktopRuntime(root: string): DesktopRuntimeFile[] {
return runtimeFiles(root).map(({ path, name }) => runtimeFile(path, name, readFileSync(path)))
.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
}
async function inventoryRuntimeForVerification(root: string): Promise<DesktopRuntimeFile[]> {
const entries = runtimeFiles(root)
const files: DesktopRuntimeFile[] = []
const remaining = entries.values()
let failed = false
const workers = Array.from({ length: Math.min(8, entries.length) }, async () => {
while (!failed) {
const next = remaining.next()
if (next.done) return
const { path, name } = next.value
try {
files.push(runtimeFile(path, name, await readRuntimeFile(path)))
} catch (error) {
failed = true
throw error
}
}
})
// Failure returns only after every outstanding file read has closed its descriptor.
const results = await Promise.allSettled(workers)
for (const result of results) if (result.status === 'rejected') throw result.reason
return files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
}
/**
* Seal the final runtime tree after materialization and native signing.
* @param root - Runtime output directory.
* @param release - Matching shell, dsh, Host, and executable versions.
* @param sharedNames - Release-owned packages supplied to plugins.
* @param target - Platform and architecture selected by runtime preparation.
* @returns Descriptor written beside the production packages.
*/
export function writeDesktopRuntime(
root: string, release: DesktopRelease, sharedNames: readonly string[],
target: { platform: NodeJS.Platform; arch: string } = process,
): DesktopRuntimeDescriptor {
const sharedPackages = [...new Set(sharedNames)].sort().map((name) => {
if (!PACKAGE_NAME.test(name)) throw new Error(`desktop runtime: invalid shared package ${name}`)
const path = `node_modules/${name}`
const manifest = JSON.parse(readFileSync(join(runtimePath(root, path), 'package.json'), 'utf8')) as unknown
if (!record(manifest) || manifest.name !== name || typeof manifest.version !== 'string' || valid(manifest.version) === null) {
throw new Error(`desktop runtime: invalid shared package manifest ${name}`)
}
return { name, version: manifest.version, path }
})
const descriptor: DesktopRuntimeDescriptor = {
schemaVersion: 1, release, platform: target.platform, arch: target.arch,
sharedPackages, files: inventoryDesktopRuntime(root),
}
writeFileSync(join(root, DESKTOP_RUNTIME_FILE), `${JSON.stringify(descriptor, undefined, 2)}\n`)
return descriptor
}
/**
* Read packaged metadata and check shared package records.
* @param root - Current application's runtime resources.
* @returns Runtime metadata whose release compatibility is verified during packaging.
*/
export function readDesktopRuntime(root: string): DesktopRuntimeDescriptor {
const value: unknown = JSON.parse(readFileSync(join(root, DESKTOP_RUNTIME_FILE), 'utf8'))
if (!record(value) || typeof value.platform !== 'string' || typeof value.arch !== 'string'
|| !Array.isArray(value.sharedPackages) || !Array.isArray(value.files)) {
throw new Error('desktop runtime: invalid descriptor')
}
if (!record(value.release) || typeof value.release.version !== 'string'
|| typeof value.release.nodeVersion !== 'string' || typeof value.release.pnpmVersion !== 'string') {
throw new Error('desktop runtime: invalid release fields')
}
const release = value.release as unknown as DesktopRelease
const sharedPackages = value.sharedPackages.map((entry: unknown): DesktopSharedPackage => {
if (!record(entry) || typeof entry.name !== 'string' || !PACKAGE_NAME.test(entry.name)
|| typeof entry.version !== 'string' || valid(entry.version) === null || entry.path !== `node_modules/${entry.name}`) {
throw new Error('desktop runtime: invalid shared package record')
}
return { name: entry.name, version: entry.version, path: entry.path }
})
if (new Set(sharedPackages.map(entry => entry.name)).size !== sharedPackages.length) {
throw new Error('desktop runtime: duplicate shared package')
}
const files = value.files as DesktopRuntimeFile[]
for (const name of ['@deepseek-ai/dsh', DESKTOP_HOST_PACKAGE]) {
if (sharedPackages.find(entry => entry.name === name)?.version !== release.version) {
throw new Error(`desktop runtime: missing or mismatched ${name}`)
}
}
return { schemaVersion: value.schemaVersion as 1, release, platform: value.platform as NodeJS.Platform,
arch: value.arch, sharedPackages, files }
}
/**
* Verify every packaged runtime file against its recorded bytes and permissions at build time.
* @param root - Materialized runtime resources.
* @param electronVersion - Expected shell version.
* @param target - Required execution target; defaults to the current process.
* @returns Validated runtime descriptor.
*/
export async function verifyDesktopRuntime(
root: string, electronVersion: string, target: { platform: NodeJS.Platform; arch: string } = process,
): Promise<DesktopRuntimeDescriptor> {
const descriptor = readDesktopRuntime(root)
// readDesktopRuntime preserves the disk schema value without validating release compatibility.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (descriptor.schemaVersion !== 1 || descriptor.platform !== target.platform || descriptor.arch !== target.arch) {
throw new Error('desktop runtime: invalid descriptor or incompatible platform/architecture')
}
const release = parseDesktopRelease(descriptor.release)
if (release.version !== electronVersion) throw new Error(`desktop runtime: ${release.version} does not match Electron ${electronVersion}`)
for (const entry of descriptor.sharedPackages) {
const manifest: unknown = JSON.parse(readFileSync(join(runtimePath(root, entry.path), 'package.json'), 'utf8'))
if (!record(manifest) || manifest.name !== entry.name || manifest.version !== entry.version) {
throw new Error(`desktop runtime: shared package metadata mismatch for ${entry.name}`)
}
}
const actual = await inventoryRuntimeForVerification(root)
// Windows has no portable Unix executable permission bits.
const comparable = (items: readonly DesktopRuntimeFile[]): unknown => process.platform === 'win32'
? items.map(({ executable: _executable, ...item }) => item) : items
if (JSON.stringify(comparable(descriptor.files)) !== JSON.stringify(comparable(actual))) {
throw new Error('desktop runtime: integrity verification failed')
}
return descriptor
}
/**
* Identify exact runtime content independently of its installation path.
* @param descriptor - Validated runtime metadata.
* @returns SHA-256 runtime identity.
*/
export function desktopRuntimeId(descriptor: DesktopRuntimeDescriptor): string {
return createHash('sha256').update(JSON.stringify(descriptor)).digest('hex')
}
|