- добавлен агент frontend-architect с манифестом и reference-картой - добавлен скрипт сборки архива агента в public/agents - добавлена сборка агентов в CI и Docker-сборку - исключены generated-директории public/agents и public/template-sync-strategy - удалены сгенерированные файлы Template Sync Strategy из git
190 lines
5.0 KiB
TypeScript
190 lines
5.0 KiB
TypeScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
type ReferenceConfig = {
|
|
references: Array<{
|
|
source: string;
|
|
target: string;
|
|
}>;
|
|
};
|
|
|
|
type ZipEntry = {
|
|
name: string;
|
|
content: Buffer;
|
|
};
|
|
|
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
const agentDir = path.resolve(scriptDir, '..');
|
|
const rootDir = path.resolve(agentDir, '../..');
|
|
const sourceDir = path.join(agentDir, 'source');
|
|
const tempDir = path.join(agentDir, '.tmp');
|
|
const packageName = 'frontend-architect';
|
|
const packageDir = path.join(tempDir, packageName);
|
|
const publicAgentsDir = path.join(rootDir, 'public', 'agents');
|
|
const zipPath = path.join(publicAgentsDir, `${packageName}.zip`);
|
|
|
|
function readJson<T>(filePath: string): T {
|
|
return JSON.parse(fs.readFileSync(filePath, 'utf8')) as T;
|
|
}
|
|
|
|
function copyFile(sourcePath: string, targetPath: string) {
|
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
fs.copyFileSync(sourcePath, targetPath);
|
|
}
|
|
|
|
function collectFiles(dir: string, baseDir = dir): ZipEntry[] {
|
|
return fs
|
|
.readdirSync(dir, { withFileTypes: true })
|
|
.flatMap((entry) => {
|
|
const entryPath = path.join(dir, entry.name);
|
|
|
|
if (entry.isDirectory()) {
|
|
return collectFiles(entryPath, baseDir);
|
|
}
|
|
|
|
const relativePath = path.relative(baseDir, entryPath).split(path.sep).join('/');
|
|
|
|
return [
|
|
{
|
|
name: `${packageName}/${relativePath}`,
|
|
content: fs.readFileSync(entryPath),
|
|
},
|
|
];
|
|
})
|
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
}
|
|
|
|
function createCrc32Table() {
|
|
return Array.from({ length: 256 }, (_, index) => {
|
|
let value = index;
|
|
|
|
for (let bit = 0; bit < 8; bit += 1) {
|
|
value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1;
|
|
}
|
|
|
|
return value >>> 0;
|
|
});
|
|
}
|
|
|
|
const crc32Table = createCrc32Table();
|
|
|
|
function crc32(buffer: Buffer) {
|
|
let crc = 0xffffffff;
|
|
|
|
for (const byte of buffer) {
|
|
crc = crc32Table[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
|
}
|
|
|
|
return (crc ^ 0xffffffff) >>> 0;
|
|
}
|
|
|
|
function writeUInt16(value: number) {
|
|
const buffer = Buffer.alloc(2);
|
|
buffer.writeUInt16LE(value, 0);
|
|
return buffer;
|
|
}
|
|
|
|
function writeUInt32(value: number) {
|
|
const buffer = Buffer.alloc(4);
|
|
buffer.writeUInt32LE(value >>> 0, 0);
|
|
return buffer;
|
|
}
|
|
|
|
function createZip(entries: ZipEntry[]) {
|
|
const localParts: Buffer[] = [];
|
|
const centralParts: Buffer[] = [];
|
|
const dosTime = 0;
|
|
const dosDate = ((2026 - 1980) << 9) | (1 << 5) | 1;
|
|
let offset = 0;
|
|
|
|
for (const entry of entries) {
|
|
const fileName = Buffer.from(entry.name, 'utf8');
|
|
const checksum = crc32(entry.content);
|
|
const size = entry.content.length;
|
|
|
|
const localHeader = Buffer.concat([
|
|
writeUInt32(0x04034b50),
|
|
writeUInt16(20),
|
|
writeUInt16(0),
|
|
writeUInt16(0),
|
|
writeUInt16(dosTime),
|
|
writeUInt16(dosDate),
|
|
writeUInt32(checksum),
|
|
writeUInt32(size),
|
|
writeUInt32(size),
|
|
writeUInt16(fileName.length),
|
|
writeUInt16(0),
|
|
fileName,
|
|
]);
|
|
|
|
localParts.push(localHeader, entry.content);
|
|
|
|
const centralHeader = Buffer.concat([
|
|
writeUInt32(0x02014b50),
|
|
writeUInt16(20),
|
|
writeUInt16(20),
|
|
writeUInt16(0),
|
|
writeUInt16(0),
|
|
writeUInt16(dosTime),
|
|
writeUInt16(dosDate),
|
|
writeUInt32(checksum),
|
|
writeUInt32(size),
|
|
writeUInt32(size),
|
|
writeUInt16(fileName.length),
|
|
writeUInt16(0),
|
|
writeUInt16(0),
|
|
writeUInt16(0),
|
|
writeUInt16(0),
|
|
writeUInt32(0),
|
|
writeUInt32(offset),
|
|
fileName,
|
|
]);
|
|
|
|
centralParts.push(centralHeader);
|
|
offset += localHeader.length + size;
|
|
}
|
|
|
|
const centralDirectory = Buffer.concat(centralParts);
|
|
const endOfCentralDirectory = Buffer.concat([
|
|
writeUInt32(0x06054b50),
|
|
writeUInt16(0),
|
|
writeUInt16(0),
|
|
writeUInt16(entries.length),
|
|
writeUInt16(entries.length),
|
|
writeUInt32(centralDirectory.length),
|
|
writeUInt32(offset),
|
|
writeUInt16(0),
|
|
]);
|
|
|
|
return Buffer.concat([...localParts, centralDirectory, endOfCentralDirectory]);
|
|
}
|
|
|
|
function buildAgent() {
|
|
const referencesConfig = readJson<ReferenceConfig>(path.join(sourceDir, 'references.json'));
|
|
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
fs.mkdirSync(packageDir, { recursive: true });
|
|
|
|
copyFile(path.join(sourceDir, 'AGENT.md'), path.join(packageDir, 'AGENT.md'));
|
|
copyFile(path.join(sourceDir, 'manifest.json'), path.join(packageDir, 'manifest.json'));
|
|
|
|
for (const reference of referencesConfig.references) {
|
|
const sourcePath = path.join(rootDir, reference.source);
|
|
|
|
if (!fs.existsSync(sourcePath)) {
|
|
throw new Error(`Reference not found: ${reference.source}`);
|
|
}
|
|
|
|
copyFile(sourcePath, path.join(packageDir, reference.target));
|
|
}
|
|
|
|
fs.mkdirSync(publicAgentsDir, { recursive: true });
|
|
fs.writeFileSync(zipPath, createZip(collectFiles(packageDir)));
|
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
|
|
console.log(`Built ${path.relative(rootDir, zipPath)}`);
|
|
}
|
|
|
|
buildAgent();
|