forked from voideditor/void-builder
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathpreload-builtin-extensions.js
More file actions
153 lines (125 loc) · 5.23 KB
/
Copy pathpreload-builtin-extensions.js
File metadata and controls
153 lines (125 loc) · 5.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
'use strict';
// Pre-populates .build/builtInExtensions/<name>/ from GitHub before the gulp task runs.
// When these directories exist at the right version, getExtensionStream() in
// builtInExtensions.ts takes the fast path: vfs.src() instead of downloading+unzipping.
// This completely bypasses the gulp-vinyl-zip stream pipeline that stalls on Node 22.15.x.
const https = require('https');
const fs = require('fs');
const path = require('path');
// yauzl is already in node_modules from the VS Code npm install
const yauzlPath = path.join(process.cwd(), 'node_modules', 'yauzl');
const yauzl = require(yauzlPath);
const productJson = JSON.parse(fs.readFileSync('product.json', 'utf8'));
const builtInExtensions = productJson.builtInExtensions || [];
const GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
const headers = {
'Authorization': `Bearer ${GITHUB_TOKEN}`,
'User-Agent': 'CortexIDE-Builder/1.0',
};
function httpsGet(url, extraHeaders) {
return new Promise((resolve, reject) => {
const opts = new URL(url);
const reqHeaders = { ...headers, ...extraHeaders, 'Host': opts.hostname };
const req = https.get({ hostname: opts.hostname, path: opts.pathname + opts.search, headers: reqHeaders }, res => {
if (res.statusCode === 301 || res.statusCode === 302) {
return resolve(httpsGet(res.headers.location, extraHeaders));
}
const chunks = [];
res.on('data', c => chunks.push(c));
res.on('end', () => resolve({ status: res.statusCode, body: Buffer.concat(chunks) }));
res.on('error', reject);
});
req.on('error', reject);
req.end();
});
}
async function getVsixAssetUrl(repoPath, version) {
const url = `https://api.github.com/repos${repoPath}/releases/tags/v${version}`;
const res = await httpsGet(url, { 'Accept': 'application/vnd.github+json' });
if (res.status !== 200) throw new Error(`GitHub API returned ${res.status} for ${url}`);
const release = JSON.parse(res.body.toString('utf8'));
const asset = release.assets && release.assets.find(a => a.name.endsWith('.vsix'));
if (!asset) throw new Error(`No .vsix asset found in release ${version} of ${repoPath}`);
return asset.url;
}
async function downloadVsix(assetUrl) {
console.log(` Downloading: ${assetUrl}`);
const res = await httpsGet(assetUrl, { 'Accept': 'application/octet-stream' });
if (res.status !== 200) throw new Error(`Download failed with status ${res.status}`);
console.log(` Downloaded: ${res.body.length} bytes`);
return res.body;
}
function extractVsix(buffer, destDir) {
// Use lazyEntries: true so we control the entry reading pace (avoids race conditions)
return new Promise((resolve, reject) => {
yauzl.fromBuffer(buffer, { lazyEntries: true }, (err, zip) => {
if (err) return reject(err);
fs.mkdirSync(destDir, { recursive: true });
zip.on('error', reject);
zip.on('end', resolve);
zip.on('entry', entry => {
// Only extract files under extension/
if (!entry.fileName.startsWith('extension/') && !entry.fileName.startsWith('extension\\')) {
return zip.readEntry();
}
const relPath = entry.fileName.replace(/^extension[/\\]/, '').replace(/\\/g, path.sep);
const destPath = path.join(destDir, relPath);
if (entry.fileName.endsWith('/') || entry.fileName.endsWith('\\')) {
// Directory entry
fs.mkdirSync(destPath, { recursive: true });
return zip.readEntry();
}
fs.mkdirSync(path.dirname(destPath), { recursive: true });
zip.openReadStream(entry, (err, stream) => {
if (err) return reject(err);
const out = fs.createWriteStream(destPath);
out.on('error', reject);
out.on('finish', () => zip.readEntry());
stream.on('error', reject);
stream.pipe(out);
});
});
zip.readEntry();
});
});
}
async function preloadExtension(ext) {
const repoPath = new URL(ext.repo).pathname;
const destDir = path.join('.build', 'builtInExtensions', ext.name);
const pkgPath = path.join(destDir, 'package.json');
// Check if already cached at the right version
if (fs.existsSync(pkgPath)) {
try {
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
if (pkg.version === ext.version) {
console.log(`[preload] ${ext.name}@${ext.version} already cached — skipping`);
return;
}
} catch (_) { /* corrupt cache, re-download */ }
}
console.log(`[preload] Fetching ${ext.name}@${ext.version} from ${ext.repo}...`);
const assetUrl = await getVsixAssetUrl(repoPath, ext.version);
const vsixBuffer = await downloadVsix(assetUrl);
fs.rmSync(destDir, { recursive: true, force: true });
console.log(`[preload] Extracting to ${destDir}...`);
await extractVsix(vsixBuffer, destDir);
// Verify
if (!fs.existsSync(pkgPath)) {
throw new Error(`Extraction failed — ${pkgPath} not found after extract`);
}
console.log(`[preload] ${ext.name}@${ext.version} ready`);
}
async function main() {
if (!GITHUB_TOKEN) {
console.warn('[preload] Warning: GITHUB_TOKEN not set — GitHub API rate limits may apply');
}
console.log(`[preload] Pre-loading ${builtInExtensions.length} built-in extensions...`);
for (const ext of builtInExtensions) {
await preloadExtension(ext);
}
console.log('[preload] All built-in extensions cached. gulp-vinyl-zip will be bypassed.');
}
main().catch(err => {
console.error('[preload] Fatal error:', err.message);
process.exit(1);
});