sitevision-cli 0.4.0-beta.2 → 0.6.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +24 -4
- package/dist/commands/build.js +52 -4
- package/dist/commands/dev.js +139 -22
- package/dist/components/AnimatedLogo.d.ts +9 -0
- package/dist/components/AnimatedLogo.js +79 -0
- package/dist/components/InfoScreen.js +3 -1
- package/dist/utils/branding.d.ts +9 -0
- package/dist/utils/branding.js +18 -0
- package/dist/utils/process-runner.d.ts +0 -8
- package/dist/utils/process-runner.js +0 -15
- package/dist/utils/sitevision-api.d.ts +32 -0
- package/dist/utils/sitevision-api.js +111 -42
- package/dist/utils/sitevision-scripts-runner.d.ts +83 -0
- package/dist/utils/sitevision-scripts-runner.js +187 -0
- package/dist/utils/webpack-runner.d.ts +9 -0
- package/dist/utils/webpack-runner.js +30 -15
- package/dist/utils/zip.d.ts +10 -3
- package/dist/utils/zip.js +171 -59
- package/package.json +1 -1
package/dist/utils/zip.js
CHANGED
|
@@ -6,16 +6,23 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import path from 'path';
|
|
9
|
-
import
|
|
9
|
+
import zlib from 'zlib';
|
|
10
10
|
import { ensureDistDir } from './project-detection.js';
|
|
11
11
|
// =============================================================================
|
|
12
12
|
// ZIP CREATION
|
|
13
13
|
// =============================================================================
|
|
14
14
|
/**
|
|
15
|
-
* Create a zip archive of a directory
|
|
15
|
+
* Create a zip archive of a directory.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* In-house, dependency-free implementation: walks the directory, deflates each
|
|
18
|
+
* file with Node's built-in zlib, and assembles a standard ZIP container (local
|
|
19
|
+
* file headers + central directory + end-of-central-directory record). This
|
|
20
|
+
* removes the previous reliance on the external `zip`/`tar`/PowerShell binaries
|
|
21
|
+
* and behaves identically across macOS, Linux, and Windows.
|
|
22
|
+
*
|
|
23
|
+
* Mirrors `zip -r <out> .` run from inside `sourceDir`: archive paths are
|
|
24
|
+
* relative to `sourceDir`, use forward slashes, and directory entries are
|
|
25
|
+
* emitted so empty directories are preserved.
|
|
19
26
|
*
|
|
20
27
|
* @param sourceDir - Directory to zip
|
|
21
28
|
* @param outputPath - Path for the output zip file
|
|
@@ -31,77 +38,182 @@ export async function createZip(sourceDir, outputPath) {
|
|
|
31
38
|
if (fs.existsSync(outputPath)) {
|
|
32
39
|
fs.unlinkSync(outputPath);
|
|
33
40
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
41
|
+
const entries = collectZipEntries(sourceDir);
|
|
42
|
+
const buffer = await buildZipBuffer(entries);
|
|
43
|
+
fs.writeFileSync(outputPath, buffer);
|
|
44
|
+
return outputPath;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Recursively collect file and directory entries for the archive.
|
|
48
|
+
* Directories are emitted before their contents, matching `zip -r`.
|
|
49
|
+
*/
|
|
50
|
+
function collectZipEntries(sourceDir) {
|
|
51
|
+
const entries = [];
|
|
52
|
+
const walk = (dir, prefix) => {
|
|
53
|
+
const dirEntries = fs.readdirSync(dir, { withFileTypes: true });
|
|
54
|
+
for (const entry of dirEntries) {
|
|
55
|
+
const absolutePath = path.join(dir, entry.name);
|
|
56
|
+
const archiveName = prefix + entry.name;
|
|
57
|
+
if (entry.isDirectory()) {
|
|
58
|
+
const stat = fs.statSync(absolutePath);
|
|
59
|
+
entries.push({
|
|
60
|
+
name: archiveName + '/',
|
|
61
|
+
isDirectory: true,
|
|
62
|
+
mtime: stat.mtime,
|
|
63
|
+
});
|
|
64
|
+
walk(absolutePath, archiveName + '/');
|
|
57
65
|
}
|
|
58
|
-
else {
|
|
59
|
-
|
|
66
|
+
else if (entry.isFile()) {
|
|
67
|
+
const stat = fs.statSync(absolutePath);
|
|
68
|
+
entries.push({
|
|
69
|
+
name: archiveName,
|
|
70
|
+
isDirectory: false,
|
|
71
|
+
absolutePath,
|
|
72
|
+
mtime: stat.mtime,
|
|
73
|
+
});
|
|
60
74
|
}
|
|
61
|
-
|
|
62
|
-
|
|
75
|
+
// Symlinks and special files are skipped (matches prior `zip` defaults
|
|
76
|
+
// closely enough for Sitevision build output, which has neither).
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
walk(sourceDir, '');
|
|
80
|
+
return entries;
|
|
63
81
|
}
|
|
64
82
|
/**
|
|
65
|
-
*
|
|
66
|
-
* This is a fallback for systems without the zip command.
|
|
83
|
+
* Assemble the full ZIP byte buffer from collected entries.
|
|
67
84
|
*/
|
|
68
|
-
async function
|
|
69
|
-
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
85
|
+
async function buildZipBuffer(entries) {
|
|
86
|
+
const localChunks = [];
|
|
87
|
+
const centralChunks = [];
|
|
88
|
+
let offset = 0;
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
const nameBuffer = Buffer.from(entry.name, 'utf8');
|
|
91
|
+
const { dosTime, dosDate } = toDosDateTime(entry.mtime);
|
|
92
|
+
let rawData;
|
|
93
|
+
let compressed;
|
|
94
|
+
let method;
|
|
95
|
+
if (entry.isDirectory) {
|
|
96
|
+
rawData = Buffer.alloc(0);
|
|
97
|
+
compressed = Buffer.alloc(0);
|
|
98
|
+
method = 0; // stored
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
rawData = fs.readFileSync(entry.absolutePath);
|
|
102
|
+
if (rawData.length === 0) {
|
|
103
|
+
compressed = Buffer.alloc(0);
|
|
104
|
+
method = 0; // stored (deflating empty data is wasteful)
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
compressed = await deflateRaw(rawData);
|
|
108
|
+
method = 8; // deflate
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const crc = crc32(rawData);
|
|
112
|
+
const localHeaderOffset = offset;
|
|
113
|
+
// Local file header (signature 0x04034b50)
|
|
114
|
+
const localHeader = Buffer.alloc(30);
|
|
115
|
+
localHeader.writeUInt32LE(0x04034b50, 0);
|
|
116
|
+
localHeader.writeUInt16LE(20, 4); // version needed to extract
|
|
117
|
+
localHeader.writeUInt16LE(0, 6); // general purpose flag
|
|
118
|
+
localHeader.writeUInt16LE(method, 8);
|
|
119
|
+
localHeader.writeUInt16LE(dosTime, 10);
|
|
120
|
+
localHeader.writeUInt16LE(dosDate, 12);
|
|
121
|
+
localHeader.writeUInt32LE(crc, 14);
|
|
122
|
+
localHeader.writeUInt32LE(compressed.length, 18);
|
|
123
|
+
localHeader.writeUInt32LE(rawData.length, 22);
|
|
124
|
+
localHeader.writeUInt16LE(nameBuffer.length, 26);
|
|
125
|
+
localHeader.writeUInt16LE(0, 28); // extra field length
|
|
126
|
+
localChunks.push(localHeader, nameBuffer, compressed);
|
|
127
|
+
offset += localHeader.length + nameBuffer.length + compressed.length;
|
|
128
|
+
// Central directory header (signature 0x02014b50)
|
|
129
|
+
const centralHeader = Buffer.alloc(46);
|
|
130
|
+
centralHeader.writeUInt32LE(0x02014b50, 0);
|
|
131
|
+
centralHeader.writeUInt16LE(20, 4); // version made by
|
|
132
|
+
centralHeader.writeUInt16LE(20, 6); // version needed
|
|
133
|
+
centralHeader.writeUInt16LE(0, 8); // general purpose flag
|
|
134
|
+
centralHeader.writeUInt16LE(method, 10);
|
|
135
|
+
centralHeader.writeUInt16LE(dosTime, 12);
|
|
136
|
+
centralHeader.writeUInt16LE(dosDate, 14);
|
|
137
|
+
centralHeader.writeUInt32LE(crc, 16);
|
|
138
|
+
centralHeader.writeUInt32LE(compressed.length, 20);
|
|
139
|
+
centralHeader.writeUInt32LE(rawData.length, 24);
|
|
140
|
+
centralHeader.writeUInt16LE(nameBuffer.length, 28);
|
|
141
|
+
centralHeader.writeUInt16LE(0, 30); // extra field length
|
|
142
|
+
centralHeader.writeUInt16LE(0, 32); // comment length
|
|
143
|
+
centralHeader.writeUInt16LE(0, 34); // disk number start
|
|
144
|
+
centralHeader.writeUInt16LE(0, 36); // internal attributes
|
|
145
|
+
// External attributes: directory vs file unix-ish mode in high bytes.
|
|
146
|
+
centralHeader.writeUInt32LE(entry.isDirectory ? 0x41ed0010 : 0x81a40000, 38);
|
|
147
|
+
centralHeader.writeUInt32LE(localHeaderOffset, 42);
|
|
148
|
+
centralChunks.push(centralHeader, nameBuffer);
|
|
73
149
|
}
|
|
74
|
-
|
|
75
|
-
|
|
150
|
+
const centralDirectory = Buffer.concat(centralChunks);
|
|
151
|
+
const centralDirectoryOffset = offset;
|
|
152
|
+
// End of central directory record (signature 0x06054b50)
|
|
153
|
+
const eocd = Buffer.alloc(22);
|
|
154
|
+
eocd.writeUInt32LE(0x06054b50, 0);
|
|
155
|
+
eocd.writeUInt16LE(0, 4); // disk number
|
|
156
|
+
eocd.writeUInt16LE(0, 6); // disk with central directory
|
|
157
|
+
eocd.writeUInt16LE(entries.length, 8); // entries on this disk
|
|
158
|
+
eocd.writeUInt16LE(entries.length, 10); // total entries
|
|
159
|
+
eocd.writeUInt32LE(centralDirectory.length, 12);
|
|
160
|
+
eocd.writeUInt32LE(centralDirectoryOffset, 16);
|
|
161
|
+
eocd.writeUInt16LE(0, 20); // comment length
|
|
162
|
+
return Buffer.concat([...localChunks, centralDirectory, eocd]);
|
|
76
163
|
}
|
|
77
164
|
/**
|
|
78
|
-
*
|
|
165
|
+
* Deflate (raw, no zlib header) a buffer.
|
|
79
166
|
*/
|
|
80
|
-
async function
|
|
167
|
+
async function deflateRaw(data) {
|
|
81
168
|
return new Promise((resolve, reject) => {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const psProcess = spawn('powershell', ['-Command', command], {
|
|
86
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
87
|
-
});
|
|
88
|
-
let stderr = '';
|
|
89
|
-
psProcess.stderr?.on('data', (data) => {
|
|
90
|
-
stderr += data.toString();
|
|
91
|
-
});
|
|
92
|
-
psProcess.on('error', error => {
|
|
93
|
-
reject(new Error(`PowerShell error: ${error.message}`));
|
|
94
|
-
});
|
|
95
|
-
psProcess.on('close', code => {
|
|
96
|
-
if (code === 0) {
|
|
97
|
-
resolve(absoluteOutputPath);
|
|
169
|
+
zlib.deflateRaw(data, (error, result) => {
|
|
170
|
+
if (error) {
|
|
171
|
+
reject(error);
|
|
98
172
|
}
|
|
99
173
|
else {
|
|
100
|
-
|
|
174
|
+
resolve(result);
|
|
101
175
|
}
|
|
102
176
|
});
|
|
103
177
|
});
|
|
104
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Convert a Date to DOS date/time fields used by the ZIP format.
|
|
181
|
+
* ZIP timestamps only span 1980–2107 with 2-second resolution.
|
|
182
|
+
*/
|
|
183
|
+
function toDosDateTime(date) {
|
|
184
|
+
const year = date.getFullYear();
|
|
185
|
+
if (year < 1980) {
|
|
186
|
+
// Clamp to the ZIP epoch (1980-01-01 00:00:00).
|
|
187
|
+
return { dosTime: 0, dosDate: (1 << 5) | 1 };
|
|
188
|
+
}
|
|
189
|
+
const dosTime = (date.getHours() << 11) |
|
|
190
|
+
(date.getMinutes() << 5) |
|
|
191
|
+
Math.floor(date.getSeconds() / 2);
|
|
192
|
+
const dosDate = ((year - 1980) << 9) | ((date.getMonth() + 1) << 5) | date.getDate();
|
|
193
|
+
return { dosTime, dosDate };
|
|
194
|
+
}
|
|
195
|
+
// CRC-32 table (IEEE polynomial 0xEDB88320), built once and reused.
|
|
196
|
+
const crc32Table = (() => {
|
|
197
|
+
const table = new Uint32Array(256);
|
|
198
|
+
for (let n = 0; n < 256; n++) {
|
|
199
|
+
let c = n;
|
|
200
|
+
for (let k = 0; k < 8; k++) {
|
|
201
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
202
|
+
}
|
|
203
|
+
table[n] = c >>> 0;
|
|
204
|
+
}
|
|
205
|
+
return table;
|
|
206
|
+
})();
|
|
207
|
+
/**
|
|
208
|
+
* Compute the CRC-32 checksum of a buffer.
|
|
209
|
+
*/
|
|
210
|
+
function crc32(data) {
|
|
211
|
+
let crc = 0xffffffff;
|
|
212
|
+
for (const byte of data) {
|
|
213
|
+
crc = crc32Table[(crc ^ byte) & 0xff] ^ (crc >>> 8);
|
|
214
|
+
}
|
|
215
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
216
|
+
}
|
|
105
217
|
/**
|
|
106
218
|
* Create a zip of the build directory for deployment
|
|
107
219
|
*
|