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' ;
87import path from 'path' ;
8+ import { deflateRawSync } from 'zlib' ;
99import * as logger from '../utils/logger.js' ;
1010
1111const EXAMPLES = [
@@ -14,6 +14,111 @@ const EXAMPLES = [
1414
1515const 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+
17122export 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