Skip to content

Commit 39f88df

Browse files
committed
fix: replace shell zip with pure Node.js implementation for Windows CI
1 parent b9950e4 commit 39f88df

1 file changed

Lines changed: 108 additions & 15 deletions

File tree

build-tools/tasks/examples.js

Lines changed: 108 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
* @brief Pack example projects into zip files for editor
44
*/
55

6-
import { execSync } from 'child_process';
7-
import { existsSync, mkdirSync } from 'fs';
6+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'fs';
87
import path from 'path';
8+
import { deflateRawSync } from 'zlib';
99
import * as logger from '../utils/logger.js';
1010

1111
const EXAMPLES = [
@@ -14,6 +14,111 @@ const EXAMPLES = [
1414

1515
const OUTPUT_DIR = 'desktop/public/examples';
1616

17+
const EXCLUDE_PATTERNS = [
18+
'.DS_Store',
19+
'node_modules',
20+
'dist',
21+
'.esengine/cache',
22+
'tools',
23+
'__pycache__',
24+
];
25+
26+
function shouldExclude(relativePath) {
27+
for (const pattern of EXCLUDE_PATTERNS) {
28+
if (relativePath === pattern || relativePath.startsWith(pattern + '/') ||
29+
relativePath.includes('/' + pattern + '/') || relativePath.endsWith('/' + pattern)) {
30+
return true;
31+
}
32+
}
33+
return false;
34+
}
35+
36+
function collectFiles(dir, baseDir) {
37+
const results = [];
38+
for (const entry of readdirSync(dir)) {
39+
const fullPath = path.join(dir, entry);
40+
const relativePath = path.relative(baseDir, fullPath).split(path.sep).join('/');
41+
if (shouldExclude(relativePath)) continue;
42+
const stat = statSync(fullPath);
43+
if (stat.isDirectory()) {
44+
results.push(...collectFiles(fullPath, baseDir));
45+
} else {
46+
results.push({ fullPath, relativePath });
47+
}
48+
}
49+
return results;
50+
}
51+
52+
function crc32(buf) {
53+
let crc = 0xffffffff;
54+
for (let i = 0; i < buf.length; i++) {
55+
crc ^= buf[i];
56+
for (let j = 0; j < 8; j++) {
57+
crc = (crc >>> 1) ^ ((crc & 1) ? 0xedb88320 : 0);
58+
}
59+
}
60+
return (crc ^ 0xffffffff) >>> 0;
61+
}
62+
63+
function createZip(srcDir, outputPath) {
64+
const files = collectFiles(srcDir, srcDir);
65+
const localParts = [];
66+
const centralEntries = [];
67+
let offset = 0;
68+
69+
for (const file of files) {
70+
const content = readFileSync(file.fullPath);
71+
const compressed = deflateRawSync(content);
72+
const nameBytes = Buffer.from(file.relativePath, 'utf8');
73+
const crc = crc32(content);
74+
75+
const local = Buffer.alloc(30);
76+
local.writeUInt32LE(0x04034b50, 0);
77+
local.writeUInt16LE(20, 4);
78+
local.writeUInt16LE(0, 6);
79+
local.writeUInt16LE(8, 8);
80+
local.writeUInt16LE(0, 10);
81+
local.writeUInt16LE(0, 12);
82+
local.writeUInt32LE(crc, 14);
83+
local.writeUInt32LE(compressed.length, 18);
84+
local.writeUInt32LE(content.length, 22);
85+
local.writeUInt16LE(nameBytes.length, 26);
86+
local.writeUInt16LE(0, 28);
87+
88+
localParts.push(local, nameBytes, compressed);
89+
90+
const central = Buffer.alloc(46);
91+
central.writeUInt32LE(0x02014b50, 0);
92+
central.writeUInt16LE(20, 4);
93+
central.writeUInt16LE(20, 6);
94+
central.writeUInt16LE(0, 8);
95+
central.writeUInt16LE(8, 10);
96+
central.writeUInt16LE(0, 12);
97+
central.writeUInt16LE(0, 14);
98+
central.writeUInt32LE(crc, 16);
99+
central.writeUInt32LE(compressed.length, 20);
100+
central.writeUInt32LE(content.length, 24);
101+
central.writeUInt16LE(nameBytes.length, 28);
102+
central.writeUInt32LE(offset, 42);
103+
104+
centralEntries.push(central, nameBytes);
105+
offset += 30 + nameBytes.length + compressed.length;
106+
}
107+
108+
const centralDirOffset = offset;
109+
let centralDirSize = 0;
110+
for (const buf of centralEntries) centralDirSize += buf.length;
111+
112+
const eocd = Buffer.alloc(22);
113+
eocd.writeUInt32LE(0x06054b50, 0);
114+
eocd.writeUInt16LE(files.length, 8);
115+
eocd.writeUInt16LE(files.length, 10);
116+
eocd.writeUInt32LE(centralDirSize, 12);
117+
eocd.writeUInt32LE(centralDirOffset, 16);
118+
119+
writeFileSync(outputPath, Buffer.concat([...localParts, ...centralEntries, eocd]));
120+
}
121+
17122
export async function zipExamples(rootDir) {
18123
logger.step('Packing example projects...');
19124

@@ -31,20 +136,8 @@ export async function zipExamples(rootDir) {
31136
continue;
32137
}
33138

34-
const excludes = [
35-
'-x', '*.DS_Store',
36-
'-x', '*node_modules/*',
37-
'-x', '*dist/*',
38-
'-x', '*.esengine/cache/*',
39-
'-x', '*tools/*',
40-
'-x', '*__pycache__/*',
41-
];
42-
43139
try {
44-
execSync(
45-
`cd "${srcDir}" && zip -r "${path.resolve(zipPath)}" . ${excludes.join(' ')}`,
46-
{ stdio: 'pipe' }
47-
);
140+
createZip(srcDir, zipPath);
48141
logger.success(`${example.name}.zip`);
49142
} catch (err) {
50143
logger.error(`Failed to zip ${example.name}: ${err.message}`);

0 commit comments

Comments
 (0)