feat: bundle private Windows runtimes with settings switches

This commit is contained in:
Codex committed 2026-10-21 16:31:24 +09:00
1 parent 759df7060d
commit bef87520c2
28 files changed
+902 -10

No files matched your search

+135
View File
@@ -0,0 +1,135 @@
const assert = require('node:assert/strict')
const fs = require('node:fs/promises')
const path = require('node:path')
const os = require('node:os')
const { build } = require('esbuild')
;(async () => {
const temp = await fs.mkdtemp(path.join(os.tmpdir(), 'ycode-private-runtimes-'))
let checks = 0
const check = (name, run) => { run(); checks++; console.log('PASS', name) }
try {
const pureFile = path.join(temp, 'pure.cjs')
await build({ entryPoints: ['src/main/private-runtime-environment.ts'], outfile: pureFile, bundle: true, platform: 'node', format: 'cjs', logLevel: 'silent' })
const { normalizePrivateRuntimePreferences: normalize, validatePrivateRuntimePatch: validate, privateRuntimeView: view, applyPrivateRuntimeEnvironment: apply } = require(pureFile)
const root = 'C:\\YCode Private\\resources\\runtimes\\win32-x64'
const defaults = normalize(undefined)
const all = view('win32', root, defaults, () => true)
const base = Object.freeze({ Path: 'C:\\System Tools;C:\\Windows', SystemRoot: 'C:\\Windows', TERM: 'xterm' })
check('defaults and complete bundle activate all tools', () => assert.ok(all.tools.every(tool => tool.active)))
check('private tools precede original system paths, including spaces', () => {
const result = apply(base, all)
assert.ok(result.PATH.startsWith(root + '\\node;'))
assert.ok(result.PATH.endsWith(base.Path))
assert.ok(result.PATH.includes(root + '\\python\\Scripts'))
assert.equal(result.CLAUDE_CODE_GIT_BASH_PATH, root + '\\git\\bin\\bash.exe')
assert.equal(result.Path, undefined)
assert.equal(base.Path, 'C:\\System Tools;C:\\Windows')
})
check('disabled global preference preserves original environment exactly', () => assert.deepEqual(apply(base, view('win32', root, normalize({ enabled: false }), () => true)), base))
check('per-tool disabled does not inject its paths', () => {
const result = apply(base, view('win32', root, normalize({ tools: { node: false, git: false } }), () => true))
assert.ok(!result.PATH.includes('\\node'))
assert.ok(!result.PATH.includes('\\git'))
assert.equal(result.CLAUDE_CODE_GIT_BASH_PATH, undefined)
assert.ok(result.PATH.startsWith(root + '\\python;'))
})
check('missing and incomplete bundle never reports active', () => {
for (const missing of ['node.exe', 'npm.cmd', 'npx.cmd', 'python.exe', 'pip.cmd', 'git.exe', 'bash.exe']) {
const state = view('win32', root, defaults, file => !file.endsWith(missing))
assert.equal(state.tools.filter(tool => !tool.available).length, 1)
assert.ok(state.tools.filter(tool => !tool.available).every(tool => !tool.active && tool.path === null))
}
assert.deepEqual(apply(base, view('win32', root, defaults, () => false)), base)
})
check('Linux and macOS preserve original environment', () => {
for (const platform of ['linux', 'darwin']) {
const state = view(platform, root, defaults, () => true)
assert.equal(state.supported, false)
assert.deepEqual(apply(base, state), base)
}
})
check('case-insensitive PATH becomes one canonical key and survives MCP SDK merge', () => {
const result = apply({ PATH: 'C:\\System', Path: 'C:\\Explicit', path: 'c:\\system', claude_code_git_bash_path: 'old' }, all)
assert.deepEqual(Object.keys(result).filter(key => key.toLowerCase() === 'path'), ['PATH'])
assert.equal(result.PATH.split(';').filter(item => item.toLowerCase() === 'c:\\system').length, 1)
assert.ok(({ PATH: 'C:\\System', ...result }).PATH.startsWith(root))
assert.equal(result.claude_code_git_bash_path, undefined)
})
check('overlay introduces no credentials or unrelated variables', () => {
const result = apply({ PATH: 'C:\\System' }, all)
assert.deepEqual(Object.keys(result).sort(), ['CLAUDE_CODE_GIT_BASH_PATH', 'PATH'])
})
check('writable private caches and prefixes are scoped to active tools', () => {
const dataRoot = 'C:\\Users\\test\\AppData\\YCode\\runtime-data'
const result = apply({ ...base, npm_config_prefix: 'system', pythonuserbase: 'system' }, all, dataRoot)
assert.equal(result.NPM_CONFIG_PREFIX, dataRoot + '\\node\\global')
assert.equal(result.NPM_CONFIG_CACHE, dataRoot + '\\node\\cache')
assert.equal(result.PYTHONUSERBASE, dataRoot + '\\python\\user')
assert.equal(result.PIP_CACHE_DIR, dataRoot + '\\python\\cache')
assert.ok(result.PATH.includes(dataRoot + '\\python\\user\\Python314\\Scripts'))
assert.equal(result.PYTHONHOME, undefined)
assert.equal(result.PIP_USER, undefined)
assert.equal(result.PIP_TARGET, undefined)
assert.equal(result.npm_config_prefix, undefined)
assert.equal(result.pythonuserbase, undefined)
assert.deepEqual(apply(base, view('win32', root, normalize({ enabled: false }), () => true), dataRoot), base)
const gitOnly = apply(base, view('win32', root, normalize({ tools: { node: false, python: false } }), () => true), dataRoot)
assert.equal(gitOnly.NPM_CONFIG_PREFIX, undefined)
assert.equal(gitOnly.PYTHONUSERBASE, undefined)
})
check('overlay idempotence', () => assert.deepEqual(apply(apply(base, all), all), apply(base, all)))
check('IPC patch validation rejects malformed and unknown values', () => {
for (const bad of [null, [], 'yes', { enabled: 'true' }, { tools: null }, { tools: [] }, { tools: { ruby: true } }, { tools: { node: 1 } }, { root: 'C:\\evil' }]) assert.throws(() => validate(bad))
assert.deepEqual(validate({ enabled: false, tools: { node: true } }), { enabled: false, tools: { node: true } })
})
const userData = path.join(temp, 'user-data')
await fs.mkdir(userData)
await fs.writeFile(path.join(userData, 'config.json'), JSON.stringify({ apiKeyPlain: 'persistence-only-test', unrelated: 'preserved' }))
async function service(name) {
const outfile = path.join(temp, name + '.cjs')
await build({ entryPoints: ['src/main/private-runtimes.ts'], outfile, bundle: true, platform: 'node', format: 'cjs', logLevel: 'silent', plugins: [{ name: 'isolated-electron', setup(build) {
build.onResolve({ filter: /^electron$/ }, () => ({ path: 'electron', namespace: 'mock' }))
build.onLoad({ filter: /.*/, namespace: 'mock' }, () => ({ contents: `export const app = { isPackaged:false,getPath:()=>${JSON.stringify(userData)},getAppPath:()=>${JSON.stringify(temp)} };export const safeStorage={};`, loader: 'js' }))
} }] })
return require(outfile)
}
const api = await service('service')
await Promise.all([api.updatePrivateRuntimes({ tools: { node: false } }), api.updatePrivateRuntimes({ tools: { git: false } }), api.updatePrivateRuntimes({ enabled: false })])
const saved = JSON.parse(await fs.readFile(path.join(userData, 'private-runtimes.json'), 'utf8'))
const unrelated = JSON.parse(await fs.readFile(path.join(userData, 'config.json'), 'utf8'))
check('serialized updates merge toggles and preserve unrelated config', () => {
assert.deepEqual(saved, { enabled: false, tools: { node: false, python: true, git: false } })
assert.equal(unrelated.unrelated, 'preserved')
assert.equal(unrelated.apiKeyPlain, 'persistence-only-test')
})
const fresh = await service('reloaded')
check('settings survive module restart without exposing config credentials', () => {
assert.ok(!JSON.stringify(all).includes('persistence-only-test'))
})
assert.deepEqual((await fresh.getPrivateRuntimes()).preferences, saved)
assert.ok(!JSON.stringify(await fresh.getPrivateRuntimes()).includes('persistence-only-test'))
// Force temporary-file creation failure; the committed preferences remain readable.
await fs.mkdir(path.join(userData, 'private-runtimes.json.tmp'))
await assert.rejects(api.updatePrivateRuntimes({ enabled: true }))
assert.equal((await api.getPrivateRuntimes()).preferences.enabled, false)
check('failed atomic save leaves the committed enable policy unchanged', () => {})
await fs.rm(path.join(userData, 'private-runtimes.json.tmp'), { recursive: true })
await api.updatePrivateRuntimes({ enabled: true })
assert.equal((await api.getPrivateRuntimes()).preferences.enabled, true)
const settingsPath = path.join(userData, 'private-runtimes.json')
const validSettings = await fs.readFile(settingsPath, 'utf8')
for (const corrupt of ['{broken', '{}', 'null', '{"enabled":"true","tools":{}}']) {
await fs.writeFile(settingsPath, corrupt)
await assert.rejects(api.getPrivateRuntimes())
await assert.rejects(api.updatePrivateRuntimes({ enabled: true }))
}
await fs.rm(settingsPath)
await fs.mkdir(settingsPath)
await assert.rejects(api.getPrivateRuntimes())
await fs.rm(settingsPath, { recursive: true })
await fs.writeFile(settingsPath, validSettings)
check('corrupt, incomplete, and unreadable stored policy never silently enables tools', () => {})
console.log(`Private runtimes: ${checks} regression groups passed; no Windows executables run.`)
} finally { await fs.rm(temp, { recursive: true, force: true }) }
})().catch(error => { console.error(error); process.exitCode = 1 })