Files
docs/projects/_shared/lib/zip.ts
S.Gromov 9a962f37b5
All checks were successful
CI/CD Pipeline / build (push) Successful in 43s
CI/CD Pipeline / docker (push) Successful in 1m18s
CI/CD Pipeline / deploy (push) Successful in 6s
feat: добавить skill для SLM Design
- добавлена сборка self-contained skill для Claude Code и opencode

- добавлен install-ready архив skill в public/slm-design/skill

- обновлена карточка SLM Design с меню действий открыть/скачать

- добавлен static fallback главной страницы из общего конфига

- подключены Mantine Menu и Phosphor Icons для действий карточки
2026-05-22 23:23:14 +03:00

137 lines
3.4 KiB
TypeScript

import fs from 'node:fs';
import path from 'node:path';
type ZipEntry = {
name: string;
content: Buffer;
};
function collectFiles(dir: string, baseDir = dir, archiveRoot = path.basename(dir)): ZipEntry[] {
return fs
.readdirSync(dir, { withFileTypes: true })
.flatMap((entry) => {
const entryPath = path.join(dir, entry.name);
if (entry.isDirectory()) return collectFiles(entryPath, baseDir, archiveRoot);
const relativePath = path.relative(baseDir, entryPath).split(path.sep).join('/');
return [
{
name: archiveRoot ? `${archiveRoot}/${relativePath}` : 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);
centralParts.push(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,
]));
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]);
}
export function writeZipFromDirectory(sourceDir: string, zipPath: string, archiveRoot = path.basename(sourceDir)) {
fs.mkdirSync(path.dirname(zipPath), { recursive: true });
fs.writeFileSync(zipPath, createZip(collectFiles(sourceDir, sourceDir, archiveRoot)));
}