import assert from 'node:assert/strict' import { testTerminalHostPlugin } from './test-terminal-host-plugin' import { createServer } from 'node:http' import { mkdtemp, mkdir, writeFile, readFile, rm, symlink } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { pathToFileURL } from 'node:url' import { build } from 'esbuild' import { classifyWorkspacePath, decodeDownload, imageDownloadPayload, readWorkspaceResource, readWorkspaceText, resolveWorkspaceImage, resolveWorkspacePath } from '../src/main/resources' async function main(): Promise { const root = await mkdtemp(join(tmpdir(), 'ycode-resource-bridge-')) const workspace = join(root, 'workspace') await mkdir(workspace) const state = { workspace, handlers: new Map Promise>(), savePath: undefined as string | undefined, dialogCalls: 0 } const globals = globalThis as typeof globalThis & { __YCODE_RESOURCE_TEST__?: typeof state } globals.__YCODE_RESOURCE_TEST__ = state const server = createServer((_req, res) => { res.setHeader('content-type', 'image/png'); res.end(Buffer.from([137, 80, 78, 71])) }) try { await writeFile(join(workspace, 'example.ts'), Array.from({ length: 1000 }, (_, index) => `line ${index + 1}`).join('\n')) await writeFile(join(workspace, 'preview.png'), Buffer.from([137, 80, 78, 71])) await writeFile(join(workspace, 'binary.bin'), Buffer.from([0, 1, 2])) await mkdir(join(workspace, 'folder')) await writeFile(join(root, 'outside.txt'), 'must stay outside') await symlink(root, join(workspace, 'outside-link'), process.platform === 'win32' ? 'junction' : 'dir') assert.equal(await resolveWorkspacePath(workspace, 'example.ts'), join(workspace, 'example.ts')) assert.equal(await classifyWorkspacePath(workspace, 'example.ts'), 'file') assert.equal(await classifyWorkspacePath(workspace, 'folder'), 'directory') assert.equal(await classifyWorkspacePath(workspace, 'missing'), 'unknown') await assert.rejects(resolveWorkspacePath(workspace, '../outside.txt'), /超出工作目录/) await assert.rejects(resolveWorkspacePath(workspace, 'outside-link/outside.txt'), /超出工作目录/) assert.equal(await resolveWorkspaceImage(workspace, '../outside.txt'), undefined) const file = await readWorkspaceResource(workspace, 'example.ts', { start: 600, end: 605 }) assert.equal(file.kind, 'text') if (file.kind !== 'text') throw new Error('Expected text') assert.equal(file.firstLine, 580) assert.equal(file.totalLines, 1000) assert.deepEqual(file.range, { start: 600, end: 605 }) assert.equal(file.content.split('\n')[20], 'line 600') assert.equal(file.truncated, true) const fullText = await readWorkspaceText(workspace, 'example.ts') assert.equal(fullText.split('\n').length, 1000, 'Artifact content must not use the 400-line window') await assert.rejects(readWorkspaceText(workspace, '../outside.txt'), /超出工作目录/) await assert.rejects(readWorkspaceText(workspace, 'outside-link/outside.txt'), /超出工作目录/) await assert.rejects(readWorkspaceText(workspace, 'binary.bin'), /暂不支持文本预览/) const directory = await readWorkspaceResource(workspace, '.') assert.equal(directory.kind, 'directory') await assert.rejects(readWorkspaceResource(workspace, 'binary.bin'), /暂不支持文本预览/) const image = await resolveWorkspaceImage(workspace, 'preview.png') assert.equal(image, 'data:image/png;base64,iVBORw==') assert.equal(await resolveWorkspaceImage(workspace, pathToFileURL(join(workspace, 'preview.png')).href), image) assert.equal(await resolveWorkspaceImage(workspace, pathToFileURL(join(workspace, 'preview.png')).href.replace('file:', 'local-file:')), image) const payload = await imageDownloadPayload(workspace, image!) assert.equal(payload.encoding, 'base64') assert.deepEqual(decodeDownload(payload).bytes, Buffer.from([137, 80, 78, 71])) assert.equal(decodeDownload({ ...payload, filename: '../nested/result.png' }).filename, 'result.png') assert.throws(() => decodeDownload({ ...payload, mimeType: 'application/javascript' }), /不支持/) await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)) const address = server.address() assert.ok(address && typeof address !== 'string') const remote = await imageDownloadPayload(workspace, `http://127.0.0.1:${address.port}/remote.png`) assert.equal(remote.filename, 'remote.png') assert.deepEqual(decodeDownload(remote).bytes, Buffer.from([137, 80, 78, 71])) const outfile = join(root, 'ipc.mjs') await build({ entryPoints: ['src/main/ipc.ts'], outfile, bundle: true, external: ['node-pty'], platform: 'node', format: 'esm', logLevel: 'silent', plugins: [testTerminalHostPlugin, { name: 'resource-ipc-adapters', setup(builder) { builder.onResolve({ filter: /^electron$/ }, () => ({ path: 'electron', namespace: 'resource-test' })) builder.onLoad({ filter: /.*/, namespace: 'resource-test' }, () => ({ loader: 'js', contents: ` const state=globalThis.__YCODE_RESOURCE_TEST__; export const app={getPath:()=>state.workspace,getName:()=> 'YCode',getVersion:()=> 'test'}; export const ipcMain={handle:(name,handler)=>state.handlers.set(name,handler)}; export const dialog={showSaveDialog:async()=>{state.dialogCalls++;return {canceled:!state.savePath,filePath:state.savePath}},showOpenDialog:async()=>({canceled:true,filePaths:[]})}; export const shell={openPath:async()=>''}; export const safeStorage={isEncryptionAvailable:()=>false}; export class BrowserWindow{} export class WebContentsView{} ` })) builder.onResolve({ filter: /^\.\/config$/ }, () => ({ path: 'config', namespace: 'resource-config' })) builder.onLoad({ filter: /.*/, namespace: 'resource-config' }, () => ({ loader: 'js', contents: ` export const getWorkspaceRoot=async()=>globalThis.__YCODE_RESOURCE_TEST__.workspace; export const getConfigView=async()=>({}); export const updateConfig=async()=>({}); export const getGatewayConfig=async()=>({}); export const setWorkspaceRoot=async()=>{}; ` })) } }] }) const { registerIpc } = await import(pathToFileURL(outfile).href) registerIpc(() => null) const invoke = (name: string, ...args: unknown[]): Promise => state.handlers.get(name)!({}, ...args) assert.equal(await invoke('resources:classify', 'folder'), 'directory') assert.equal(await invoke('resources:read-text', 'example.ts'), fullText) assert.equal(await invoke('resources:resolveImage', 'preview.png'), image) // Match the restored Mermaid export contract, including its charset parameter. const svg = { filename: 'diagram.svg', mimeType: 'image/svg+xml;charset=utf-8', data: '', encoding: 'utf8' } assert.deepEqual(await invoke('resources:download', svg), { saved: false }) state.savePath = join(root, 'saved.svg') assert.deepEqual(await invoke('resources:download', svg), { saved: true, path: state.savePath }) assert.equal(await readFile(state.savePath, 'utf8'), svg.data) state.savePath = join(root, 'saved.png') await invoke('resources:downloadImage', image) assert.deepEqual(await readFile(state.savePath), Buffer.from([137, 80, 78, 71])) state.savePath = join(root, 'plan.md') await invoke('resources:download', { filename: 'plan.md', mimeType: 'text/markdown', data: '# Plan\n\n- [ ] Work', encoding: 'utf8' }) assert.equal(await readFile(state.savePath, 'utf8'), '# Plan\n\n- [ ] Work') assert.equal(state.dialogCalls, 4) console.log('resource-bridge-regression: workspace paths, ranges, images, downloads and actual IPC checks passed') } finally { delete globals.__YCODE_RESOURCE_TEST__ await new Promise((resolve) => server.close(() => resolve())) await rm(root, { recursive: true, force: true }) } } void main().catch((error) => { console.error(error); process.exitCode = 1 })