feat: complete WorkBuddy parity and Windows release workflow

This commit is contained in:
xiaohan.yuan committed 2026-10-18 12:45:21 +08:00
1 parent 143516b3a8
commit d5512df549
434 files changed
+54977 -1854

No files matched your search

+65
View File
@@ -0,0 +1,65 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import { spawn, execFileSync } from 'node:child_process'
import { createHash } from 'node:crypto'
import { verifyBundledGit } from './vendor-sandbox-git.mjs'
export async function buildSandbox({ outputDirectory = path.resolve('resources/sandbox/win32-x64'), probe = false } = {}) {
if (process.platform !== 'win32' || process.arch !== 'x64') throw new Error('The sandbox native build currently requires Windows x64')
const bundledGit = !probe ? await verifyBundledGit(path.join(outputDirectory, 'git')).catch(error => { throw new Error(`Bundled Git is missing or invalid; run npm run sandbox:vendor-git. ${error.message}`) }) : undefined
const programFiles = process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)'
const vswhere = path.join(programFiles, 'Microsoft Visual Studio/Installer/vswhere.exe')
const installation = execFileSync(vswhere, ['-latest', '-products', '*', '-requires', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', '-property', 'installationPath'], { encoding: 'utf8', windowsHide: true }).trim()
if (!installation) throw new Error('Visual Studio C++ Build Tools are required; see native/sandbox/README.md')
const versions = async directory => (await fs.readdir(directory)).filter(name => /^\d+(?:\.\d+)+$/.test(name)).sort((a, b) => b.localeCompare(a, undefined, { numeric: true }))
const msvcRoot = path.join(installation, 'VC/Tools/MSVC')
const msvc = path.join(msvcRoot, (await versions(msvcRoot))[0])
const sdkRoot = path.join(programFiles, 'Windows Kits/10')
const sdk = (await versions(path.join(sdkRoot, 'Include')))[0]
if (!sdk) throw new Error('Windows 10/11 SDK is required')
const compilerDirectory = path.join(msvc, 'bin/Hostx64/x64')
const environment = { ...process.env,
PATH: `${compilerDirectory};${process.env.PATH || ''}`,
INCLUDE: [path.join(msvc, 'include'), ...['ucrt', 'shared', 'um', 'winrt'].map(part => path.join(sdkRoot, 'Include', sdk, part))].join(';'),
LIB: [path.join(msvc, 'lib/x64'), ...['ucrt', 'um'].map(part => path.join(sdkRoot, 'Lib', sdk, part, 'x64'))].join(';') }
await fs.mkdir(outputDirectory, { recursive: true })
const objects = await fs.mkdtemp(path.resolve('.tmp/sandbox-native-build-'))
const source = path.resolve(probe ? 'native/sandbox/probe.cpp' : 'native/sandbox/runner.cpp')
const binary = path.join(outputDirectory, probe ? 'sandbox-probe.exe' : 'ycode-sandbox.exe')
const args = ['/nologo', '/std:c++17', '/EHsc', '/W4', '/WX', '/MT', '/O2', '/Brepro', '/utf-8', '/DUNICODE', '/D_UNICODE', '/D_WIN32_WINNT=0x0A00', `/Fo${objects}${path.sep}`, `/Fe${binary}`, source, '/link', '/INCREMENTAL:NO', 'advapi32.lib', 'userenv.lib', 'ws2_32.lib', 'bcrypt.lib']
await new Promise((resolve, reject) => {
const child = spawn(path.join(compilerDirectory, 'cl.exe'), args, { env: environment, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] })
let log = ''
child.stdout.on('data', bytes => { log += bytes })
child.stderr.on('data', bytes => { log += bytes })
child.once('error', reject)
child.once('close', code => code === 0 ? resolve() : reject(new Error(`Sandbox compiler exited ${code}\n${log}`)))
})
const hash = bytes => createHash('sha256').update(bytes).digest('hex')
const manifest = { protocol: 1, backend: 'windows-appcontainer', version: '0.7.0', architecture: 'x64',
sourceSha256: hash(await fs.readFile(source)), binarySha256: hash(await fs.readFile(binary)), compiler: path.basename(msvc), sdk }
if (!probe) {
manifest.workspaceTransactions = 1
manifest.workspaceState = 1
manifest.gitMetadataTransactions = 1
manifest.bundledGit = bundledGit
manifest.sources = Object.fromEntries(await Promise.all(['runner.cpp', 'filesystem-read.h', 'filesystem-write.h', 'workspace-state.h', 'git-control.h', 'node-bridge.cjs', 'node-process.cjs', 'python-sitecustomize.py'].map(async name => [name, hash(await fs.readFile(path.resolve('native/sandbox', name)))])))
const adapter = await fs.readFile(path.resolve('native/sandbox/node-bridge.cjs'))
await fs.writeFile(path.join(outputDirectory, 'node-bridge.cjs'), adapter)
manifest.nodeBridgeSha256 = hash(adapter)
const processes = await fs.readFile(path.resolve('native/sandbox/node-process.cjs'))
await fs.writeFile(path.join(outputDirectory, 'node-process.cjs'), processes)
manifest.nodeProcessSha256 = hash(processes)
const pythonAdapter = await fs.readFile(path.resolve('native/sandbox/python-sitecustomize.py'))
await fs.writeFile(path.join(outputDirectory, 'python-sitecustomize.py'), pythonAdapter)
manifest.pythonAdapterSha256 = hash(pythonAdapter)
}
if (!probe) await fs.writeFile(path.join(outputDirectory, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n')
console.log(`SANDBOX_BUILD_PASS ${path.relative(process.cwd(), binary)}`)
return binary
}
if (process.argv[1] && path.resolve(process.argv[1]) === import.meta.filename) {
await fs.mkdir(path.resolve('.tmp'), { recursive: true })
await buildSandbox({ probe: process.argv.includes('--probe') })
}