feat: complete WorkBuddy parity and Windows release workflow
This commit is contained in:
1 parent
143516b3a8
commit
d5512df549
434 files changed
+54977
-1854
No files matched your search
@@ -0,0 +1,134 @@
|
||||
'use strict'
|
||||
const fs = require('node:fs')
|
||||
const { randomUUID, createHash } = require('node:crypto')
|
||||
const prefix = '\x1eYCODE_WORKSPACE_READ_V1 '
|
||||
let selected = false
|
||||
function error(code, message) { return Object.assign(new Error(message), { code }) }
|
||||
function frame(operation, args, id) { return '\n' + prefix + JSON.stringify({ version: 1, requestId: id, operation, args }) + '\n' }
|
||||
function send(operation, args, id) {
|
||||
const bytes = Buffer.from(frame(operation, args, id))
|
||||
let offset = 0
|
||||
while (offset < bytes.length) offset += fs.writeSync(1, bytes, offset, bytes.length - offset)
|
||||
}
|
||||
function unpack(line, id) {
|
||||
let response
|
||||
try { response = JSON.parse(line) } catch { throw error('EPROTO', 'Invalid file bridge reply') }
|
||||
if (response.version !== 1 || response.requestId !== id || typeof response.ok !== 'boolean') throw error('EPROTO', 'Unexpected file bridge reply')
|
||||
if (!response.ok) throw error(response.error?.code || 'EACCES', response.error?.message || 'Workspace read denied')
|
||||
return response.value
|
||||
}
|
||||
function claim() {
|
||||
if (selected) throw error('EBUSY', 'Choose one bridge client and mode per process; stdin belongs to that client')
|
||||
selected = true
|
||||
}
|
||||
function decode(part, initial, offset) {
|
||||
if (!part || part.snapshotId !== initial.snapshotId || part.sha256 !== initial.sha256 || part.size !== initial.size || part.offset !== offset || typeof part.data !== 'string') throw error('EPROTO', 'Inconsistent read snapshot')
|
||||
const bytes = Buffer.from(part.data, 'base64')
|
||||
if (bytes.length > 16384 || !bytes.length && offset < initial.size || offset + bytes.length > initial.size || part.eof !== (offset + bytes.length === initial.size)) throw error('EPROTO', 'Invalid read chunk')
|
||||
return bytes
|
||||
}
|
||||
function complete(chunks, initial, encoding) {
|
||||
const bytes = Buffer.concat(chunks)
|
||||
if (bytes.length !== initial.size || createHash('sha256').update(bytes).digest('hex') !== initial.sha256) throw error('EPROTO', 'Read snapshot digest mismatch')
|
||||
return encoding ? bytes.toString(encoding) : bytes
|
||||
}
|
||||
function connectSync() {
|
||||
claim()
|
||||
let buffered = Buffer.alloc(0), closed = false
|
||||
function call(operation, args, id = randomUUID()) {
|
||||
if (closed) throw error('EPIPE', 'File bridge client closed')
|
||||
send(operation, args, id)
|
||||
for (;;) {
|
||||
const end = buffered.indexOf(10)
|
||||
if (end >= 0) {
|
||||
const line = buffered.subarray(0, end).toString('utf8'); buffered = buffered.subarray(end + 1)
|
||||
return unpack(line, id)
|
||||
}
|
||||
if (buffered.length > 32768) throw error('EPROTO', 'File bridge reply exceeds limit')
|
||||
const bytes = Buffer.alloc(32768), length = fs.readSync(0, bytes, 0, bytes.length, null)
|
||||
if (!length) { closed = true; throw error('EPIPE', 'File bridge disconnected') }
|
||||
buffered = Buffer.concat([buffered, bytes.subarray(0, length)])
|
||||
}
|
||||
}
|
||||
return {
|
||||
call,
|
||||
readFileSync(path, encoding) {
|
||||
const initial = call('workspace.readFile', { path }), chunks = []
|
||||
try {
|
||||
let part = initial, offset = 0
|
||||
for (;;) {
|
||||
const bytes = decode(part, initial, offset); chunks.push(bytes); offset += bytes.length
|
||||
if (part.eof) return complete(chunks, initial, encoding)
|
||||
part = call('workspace.readChunk', { snapshotId: initial.snapshotId, offset })
|
||||
}
|
||||
} finally { call('workspace.closeRead', { snapshotId: initial.snapshotId }) }
|
||||
},
|
||||
statSync: path => call('workspace.stat', { path }),
|
||||
readdirSync: path => call('workspace.readdir', { path }).entries,
|
||||
querySync: requestId => call('operation.query', { requestId }),
|
||||
close() { closed = true }
|
||||
}
|
||||
}
|
||||
function connect() {
|
||||
claim()
|
||||
const waiting = new Map(), queue = []
|
||||
let buffered = Buffer.alloc(0), closed = false, failure
|
||||
function shutdown(reason) {
|
||||
if (closed) return
|
||||
closed = true; failure = reason
|
||||
process.stdin.off('data', receive); process.stdin.off('end', ended); process.stdin.off('error', failed); process.stdin.pause()
|
||||
process.stdin.destroy()
|
||||
for (const item of [...waiting.values(), ...queue]) item.reject(reason)
|
||||
waiting.clear(); queue.length = 0
|
||||
}
|
||||
function flush() {
|
||||
while (!closed && waiting.size < 8 && queue.length) {
|
||||
const item = queue.shift(); waiting.set(item.id, item)
|
||||
try { send(item.operation, item.args, item.id) } catch (reason) { shutdown(reason) }
|
||||
}
|
||||
if (waiting.size) { process.stdin.ref?.(); process.stdin.resume() }
|
||||
else { process.stdin.pause(); process.stdin.unref?.() }
|
||||
}
|
||||
function receive(bytes) {
|
||||
buffered = Buffer.concat([buffered, bytes])
|
||||
for (;;) {
|
||||
const end = buffered.indexOf(10)
|
||||
if (end < 0) { if (buffered.length > 32768) shutdown(error('EPROTO', 'File bridge reply exceeds limit')); return }
|
||||
const line = buffered.subarray(0, end).toString('utf8'); buffered = buffered.subarray(end + 1)
|
||||
let id
|
||||
try { id = JSON.parse(line).requestId } catch { shutdown(error('EPROTO', 'Invalid file bridge reply')); return }
|
||||
const item = waiting.get(id)
|
||||
if (!item) { shutdown(error('EPROTO', 'Unexpected file bridge reply')); return }
|
||||
waiting.delete(id)
|
||||
try { item.resolve(unpack(line, id)) } catch (reason) { item.reject(reason) }
|
||||
flush()
|
||||
}
|
||||
}
|
||||
const ended = () => shutdown(error('EPIPE', 'File bridge disconnected'))
|
||||
const failed = reason => shutdown(reason)
|
||||
process.stdin.on('data', receive); process.stdin.on('end', ended); process.stdin.on('error', failed); process.stdin.pause()
|
||||
function call(operation, args, id = randomUUID()) {
|
||||
if (closed) return Promise.reject(failure || error('EPIPE', 'File bridge client closed'))
|
||||
if (queue.length + waiting.size >= 32) return Promise.reject(error('EBUSY', 'File bridge client queue full'))
|
||||
return new Promise((resolve, reject) => { queue.push({ id, operation, args, resolve, reject }); flush() })
|
||||
}
|
||||
return {
|
||||
call,
|
||||
async readFile(path, encoding) {
|
||||
const initial = await call('workspace.readFile', { path }), chunks = []
|
||||
try {
|
||||
let part = initial, offset = 0
|
||||
for (;;) {
|
||||
const bytes = decode(part, initial, offset); chunks.push(bytes); offset += bytes.length
|
||||
if (part.eof) return complete(chunks, initial, encoding)
|
||||
part = await call('workspace.readChunk', { snapshotId: initial.snapshotId, offset })
|
||||
}
|
||||
} finally { await call('workspace.closeRead', { snapshotId: initial.snapshotId }) }
|
||||
},
|
||||
stat: path => call('workspace.stat', { path }),
|
||||
readdir: async path => (await call('workspace.readdir', { path })).entries,
|
||||
query: requestId => call('operation.query', { requestId }),
|
||||
close() { shutdown(error('EPIPE', 'File bridge client closed')) }
|
||||
}
|
||||
}
|
||||
module.exports = { connect, connectSync }
|
||||
Reference in new issue
Block a user