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 function main() { const root = await fs.mkdtemp(path.join(os.tmpdir(), 'ycode-projects-')) try { const bundle = path.join(root, 'projects.cjs') await build({ entryPoints: [path.resolve('src/main/projects.ts')], bundle: true, platform: 'node', format: 'cjs', outfile: bundle, logLevel: 'silent' }) const { ProjectRepository, buildProjectPrompt } = require(bundle) const filename = path.join(root, 'data', 'projects.json') const repository = new ProjectRepository(filename) const input = { name: ' 项目测试 ', instructions: ' 项目约束 ', experts: [{ id: 'expert', source: 'local' }], skills: [{ id: 'skill', source: 'market', provider: 'skillhub' }], connectors: ['mcp', 'mcp'], templateId: 'custom' } assert.deepEqual(await repository.list(), []) const created = await Promise.all(Array.from({ length: 12 }, (_, i) => repository.create({ ...input, name: `项目${i}` }))) assert.equal(new Set(created.map(item => item.projectId)).size, 12) const reloaded = await new ProjectRepository(filename).list() assert.equal(reloaded.length, 12) assert.deepEqual(new Set(reloaded.map(item => item.projectId)), new Set(created.map(item => item.projectId))) assert.equal(reloaded[0].instructions, '项目约束') assert.deepEqual(reloaded[0].connectors, ['mcp']) assert.deepEqual(reloaded[0].experts, input.experts) assert.deepEqual(reloaded[0].skills, input.skills) assert.equal((await repository.get(created[0].projectId)).projectId, created[0].projectId) await assert.rejects(repository.get('missing-project')) assert.equal(buildProjectPrompt(undefined), '') assert.equal(buildProjectPrompt({ ...created[0], instructions: ' ' }), '') assert.equal(buildProjectPrompt(created[0]), '项目:项目0\n项目指令:\n项目约束') assert.ok(!buildProjectPrompt(created[0]).includes('skillhub')) await Promise.all([ repository.update(created[0].projectId, { name: '改名项目' }), repository.update(created[0].projectId, { instructions: '更新后的项目指令' }) ]) const edited = await new ProjectRepository(filename).get(created[0].projectId) assert.equal(edited.name, '改名项目') assert.equal(edited.instructions, '更新后的项目指令') assert.equal(edited.createdAt, created[0].createdAt) assert.deepEqual(edited.skills, input.skills) await assert.rejects(repository.update(created[0].projectId, { projectId: 'forged' })) await assert.rejects(repository.update('missing-project', { name: '不存在' })) await assert.rejects(repository.update(created[0].projectId, { name: '' })) assert.deepEqual(await repository.get(created[0].projectId), edited) await assert.rejects(repository.create({ ...input, name: ' ' })) await assert.rejects(repository.create({ ...input, name: '1234567890123456' })) await assert.rejects(repository.create({ ...input, experts: [{ id: '../x', source: 'invalid' }] })) assert.equal((await repository.list()).length, 12) await repository.create({ ...input, name: '恢复写入' }) assert.equal((await repository.list()).length, 13) await fs.writeFile(filename, '{broken') await assert.rejects(repository.list()) await assert.rejects(repository.create(input)) assert.equal(await fs.readFile(filename, 'utf8'), '{broken') console.log('PASS project persistence: concurrent creates, reload, references, validation, queue recovery, corruption preservation') } finally { await fs.rm(root, { recursive: true, force: true }) } } main().catch(error => { console.error(error); process.exitCode = 1 })