Ctrl K
ComunidadGuías…

scripts/build-zip-fixtures.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node
// Writes tests/fixtures/zip/*.zip: one small kit of synthetic files archived
// by real tools, so the zip reader is tested against what people send rather
// than only against tests/helpers/zip.ts. macOS only (zip, ditto, python3).
// Prints each archive's SHA-256 for allowedBinaryFiles in
// scripts/lib/source-export-policy.mjs, which must be updated after a run.
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { mkdirSync, mkdtempSync, readFileSync, rmSync, utimesSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { crc32, deflateSync } from 'node:zlib';

const ROOT = fileURLToPath(new URL('..', import.meta.url));
const OUT = join(ROOT, 'tests', 'fixtures', 'zip');
const FIXED_TIME = new Date('2026-01-01T00:00:00Z');
const TOOL_ENV = { ...process.env, TZ: 'UTC' };

const KIT = {
  'kit/notes.md': '# Synthetic plan\n\nThis file is synthetic test content.\n',
  'kit/data/table.csv': 'region,units\nnorth,12\nsouth,7\n',
  'kit/report.pdf': textPdf('Synthetic report: 42 units'),
  'kit/logo.png': png(2, 2),
  'kit/.DS_Store': Buffer.concat([Buffer.from('\0\0\0\u0001Bud1'), Buffer.alloc(24)]),
};

const PYTHON_WRITER = `
import os, sys, zipfile
out = sys.argv[1]
names = ['kit/notes.md', 'kit/data/table.csv', 'kit/report.pdf', 'kit/logo.png']
def write(name, compression, comment=b'', entries=names):
    with zipfile.ZipFile(os.path.join(out, name), 'w', compression) as archive:
        archive.comment = comment
        for entry in entries:
            info = zipfile.ZipInfo(entry, date_time=(2026, 1, 1, 0, 0, 0))
            info.compress_type = compression
            with open(entry, 'rb') as source:
                archive.writestr(info, source.read())
write('python-stored.zip', zipfile.ZIP_STORED)
write('python-deflated.zip', zipfile.ZIP_DEFLATED)
write('python-comment.zip', zipfile.ZIP_DEFLATED, comment=b'Synthetic archive comment.')
write('python-empty.zip', zipfile.ZIP_DEFLATED, entries=[])
`;

const work = mkdtempSync(join(tmpdir(), 'chickpea-zip-fixtures-'));
try {
  const source = join(work, 'source');
  for (const [path, content] of Object.entries(KIT)) {
    mkdirSync(dirname(join(source, path)), { recursive: true });
    writeFileSync(join(source, path), content);
  }
  // An extended attribute makes ditto write an AppleDouble file under __MACOSX/, as Finder does.
  execFileSync('/usr/bin/xattr', ['-w', 'com.example.synthetic', 'fixture', join(source, 'kit/notes.md')]);
  for (const path of [...Object.keys(KIT), 'kit/data', 'kit']) utimesSync(join(source, path), FIXED_TIME, FIXED_TIME);

  mkdirSync(OUT, { recursive: true });
  const run = (command, args) => execFileSync(command, args, { cwd: source, env: TOOL_ENV, stdio: 'inherit' });
  const output = (name) => {
    const path = join(OUT, name);
    rmSync(path, { force: true });
    return path;
  };
  run('/usr/bin/zip', ['-r', '-q', output('zip-r.zip'), 'kit']);
  run('/usr/bin/zip', ['-r', '-q', '-P', 'synthetic', output('zip-encrypted.zip'), 'kit']);
  run('/usr/bin/ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', 'kit', output('ditto.zip')]);
  run('python3', ['-I', '-c', PYTHON_WRITER, OUT]);

  for (const name of ['ditto.zip', 'python-comment.zip', 'python-deflated.zip', 'python-empty.zip', 'python-stored.zip', 'zip-encrypted.zip', 'zip-r.zip']) {
    const hash = createHash('sha256').update(readFileSync(join(OUT, name))).digest('hex');
    console.log(`  [exportPath('tests', 'fixtures', 'zip', '${name}'), '${hash}'],`);
  }
} finally {
  rmSync(work, { recursive: true, force: true });
}

function textPdf(text) {
  const stream = `BT /F1 12 Tf 72 720 Td (${text}) Tj ET`;
  const objects = [
    '<< /Type /Catalog /Pages 2 0 R >>',
    '<< /Type /Pages /Kids [4 0 R] /Count 1 >>',
    '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>',
    '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 3 0 R >> >> /Contents 5 0 R >>',
    `<< /Length ${stream.length} >>\nstream\n${stream}\nendstream`,
  ];
  let body = '%PDF-1.4\n';
  const offsets = objects.map((object, index) => {
    const offset = body.length;
    body += `${index + 1} 0 obj\n${object}\nendobj\n`;
    return offset;
  });
  const xref = body.length;
  body += `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`;
  body += offsets.map((offset) => `${String(offset).padStart(10, '0')} 00000 n \n`).join('');
  body += `trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\nstartxref\n${xref}\n%%EOF\n`;
  return Buffer.from(body, 'latin1');
}

function png(width, height) {
  const chunk = (type, data) => {
    const length = Buffer.alloc(4);
    length.writeUInt32BE(data.length);
    const checksum = Buffer.alloc(4);
    checksum.writeUInt32BE(crc32(Buffer.concat([Buffer.from(type), data])));
    return Buffer.concat([length, Buffer.from(type), data, checksum]);
  };
  const header = Buffer.alloc(13);
  header.writeUInt32BE(width, 0);
  header.writeUInt32BE(height, 4);
  header[8] = 8;
  header[9] = 2;
  const row = Buffer.concat([Buffer.from([0]), Buffer.alloc(width * 3, 0x7f)]);
  return Buffer.concat([
    Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
    chunk('IHDR', header),
    chunk('IDAT', deflateSync(Buffer.concat(Array.from({ length: height }, () => row)))),
    chunk('IEND', Buffer.alloc(0)),
  ]);
}