resuml 1.5.2 → 1.7.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/package.json +3 -1
- package/scripts/bundle-themes.js +534 -129
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "resuml",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Generate JSON resumes from YAML with theme support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/api.js",
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"dev:builder": "node scripts/dev-server.js",
|
|
29
29
|
"prepublishOnly": "npm run generate:types && npm run build:lib",
|
|
30
30
|
"generate:types": "node scripts/generate-types.cjs",
|
|
31
|
+
"bundle:themes": "node scripts/bundle-themes.js",
|
|
32
|
+
"bundle:themes:all": "node scripts/bundle-themes.js --all",
|
|
31
33
|
"test": "vitest run",
|
|
32
34
|
"test:watch": "vitest",
|
|
33
35
|
"lint": "eslint src api --ext .ts,.tsx",
|
package/scripts/bundle-themes.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Theme Bundling Script
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Bundles jsonresume-theme packages for the browser with:
|
|
5
|
+
* - Per-theme fs shim with embedded assets (CSS, templates, etc.)
|
|
6
|
+
* - CSS extraction (separate .css files)
|
|
7
|
+
* - Build-time snapshots (pre-rendered HTML for instant preview)
|
|
8
|
+
* - Node.js built-in shims (buffer, stream, util, events, os, etc.)
|
|
6
9
|
*
|
|
7
10
|
* Output:
|
|
8
|
-
* docs/themes/{name}.js
|
|
9
|
-
* docs/themes/
|
|
11
|
+
* docs/themes/{name}.js — bundled render module
|
|
12
|
+
* docs/themes/{name}.css — extracted CSS (if any)
|
|
13
|
+
* docs/themes/{name}.snapshot.html— pre-rendered HTML with sample data
|
|
14
|
+
* docs/themes/manifest.json — theme metadata registry
|
|
10
15
|
*
|
|
11
16
|
* Usage:
|
|
12
17
|
* node scripts/bundle-themes.js # Bundle popular themes
|
|
@@ -19,8 +24,10 @@ import { resolve, dirname, join } from 'path';
|
|
|
19
24
|
import { fileURLToPath } from 'url';
|
|
20
25
|
import { execSync } from 'child_process';
|
|
21
26
|
import { mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync } from 'fs';
|
|
27
|
+
import { createRequire } from 'module';
|
|
22
28
|
|
|
23
29
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
+
const require = createRequire(import.meta.url);
|
|
24
31
|
const THEMES_DIR = resolve(__dirname, '../docs/themes');
|
|
25
32
|
|
|
26
33
|
// Popular themes that are known to work in the browser
|
|
@@ -43,45 +50,113 @@ const POPULAR_THEMES = [
|
|
|
43
50
|
'compact',
|
|
44
51
|
];
|
|
45
52
|
|
|
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
|
-
|
|
53
|
+
// File extensions to embed in the fs shim
|
|
54
|
+
const TEXT_EXTS = new Set([
|
|
55
|
+
'css', 'hbs', 'html', 'json', 'txt', 'handlebars', 'mustache', 'template',
|
|
56
|
+
'pug', 'jade', 'ejs', 'svg', 'less', 'scss',
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
// Sample resume for generating snapshots
|
|
60
|
+
const SAMPLE_RESUME = {
|
|
61
|
+
basics: {
|
|
62
|
+
name: 'Jane Smith',
|
|
63
|
+
label: 'Senior Software Engineer',
|
|
64
|
+
image: '',
|
|
65
|
+
email: 'jane@example.com',
|
|
66
|
+
phone: '+1-555-987-6543',
|
|
67
|
+
url: 'https://janesmith.dev',
|
|
68
|
+
summary: 'Full-stack engineer with 8+ years of experience building scalable web applications. Passionate about clean architecture, performance optimization, and mentoring teams.',
|
|
69
|
+
location: { address: '', postalCode: '', city: 'San Francisco', countryCode: 'US', region: 'California' },
|
|
70
|
+
profiles: [
|
|
71
|
+
{ network: 'LinkedIn', username: 'janesmith', url: 'https://linkedin.com/in/janesmith' },
|
|
72
|
+
{ network: 'GitHub', username: 'janesmith', url: 'https://github.com/janesmith' },
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
work: [
|
|
76
|
+
{
|
|
77
|
+
name: 'Tech Corp',
|
|
78
|
+
position: 'Senior Software Engineer',
|
|
79
|
+
url: 'https://techcorp.com',
|
|
80
|
+
startDate: '2020-03-01',
|
|
81
|
+
summary: 'Lead engineer for the platform team, owning core API infrastructure.',
|
|
82
|
+
highlights: [
|
|
83
|
+
'Reduced API latency by 45% through caching and query optimization',
|
|
84
|
+
'Designed microservices architecture serving 2M daily requests',
|
|
85
|
+
'Mentored 4 junior engineers through technical growth plans',
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'StartupXYZ',
|
|
90
|
+
position: 'Full Stack Developer',
|
|
91
|
+
startDate: '2017-06-01',
|
|
92
|
+
endDate: '2020-02-28',
|
|
93
|
+
summary: 'Built customer-facing web applications from prototype to production.',
|
|
94
|
+
highlights: [
|
|
95
|
+
'Built real-time dashboard used by 15,000+ daily active users',
|
|
96
|
+
'Implemented CI/CD pipeline reducing deploy time from 2 hours to 8 minutes',
|
|
97
|
+
],
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
education: [
|
|
101
|
+
{
|
|
102
|
+
institution: 'University of Technology',
|
|
103
|
+
area: 'Computer Science',
|
|
104
|
+
studyType: 'Bachelor of Science',
|
|
105
|
+
startDate: '2013-09-01',
|
|
106
|
+
endDate: '2017-05-15',
|
|
107
|
+
score: '3.8',
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
skills: [
|
|
111
|
+
{ name: 'Languages', level: 'Expert', keywords: ['TypeScript', 'JavaScript', 'Python', 'Go'] },
|
|
112
|
+
{ name: 'Frontend', level: 'Expert', keywords: ['React', 'Next.js', 'HTML/CSS', 'Tailwind'] },
|
|
113
|
+
{ name: 'Backend & Cloud', level: 'Advanced', keywords: ['Node.js', 'PostgreSQL', 'AWS', 'Docker'] },
|
|
114
|
+
],
|
|
115
|
+
projects: [
|
|
116
|
+
{
|
|
117
|
+
name: 'Open Source CLI Tool',
|
|
118
|
+
description: 'Developer productivity tool with 2,000+ GitHub stars',
|
|
119
|
+
url: 'https://github.com/janesmith/cli-tool',
|
|
120
|
+
startDate: '2022-01-01',
|
|
121
|
+
highlights: ['Published to npm with 5,000+ weekly downloads'],
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
volunteer: [],
|
|
125
|
+
awards: [],
|
|
126
|
+
certificates: [],
|
|
127
|
+
publications: [],
|
|
128
|
+
languages: [],
|
|
129
|
+
interests: [],
|
|
130
|
+
references: [],
|
|
131
|
+
};
|
|
75
132
|
|
|
76
|
-
|
|
77
|
-
|
|
133
|
+
/**
|
|
134
|
+
* Pad resume with safe defaults so themes don't crash on missing fields.
|
|
135
|
+
* Ported from api/_lib/themeInstaller.ts.
|
|
136
|
+
*/
|
|
137
|
+
function padResume(r) {
|
|
138
|
+
const basics = r.basics ?? {};
|
|
139
|
+
const location = basics.location ?? {};
|
|
140
|
+
const safe = {
|
|
141
|
+
...r,
|
|
142
|
+
basics: {
|
|
143
|
+
name: '', label: '', image: '', email: '', phone: '', url: '', summary: '',
|
|
144
|
+
...basics,
|
|
145
|
+
location: { address: '', postalCode: '', city: '', countryCode: '', region: '', ...location },
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
const arraySections = ['work','volunteer','education','awards','certificates','publications','skills','languages','interests','references','projects'];
|
|
149
|
+
for (const key of arraySections) {
|
|
150
|
+
safe[key] = Array.isArray(safe[key]) ? safe[key] : [];
|
|
78
151
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return
|
|
152
|
+
const safeBasics = safe.basics;
|
|
153
|
+
safeBasics.profiles = Array.isArray(safeBasics.profiles) ? safeBasics.profiles : [];
|
|
154
|
+
return safe;
|
|
82
155
|
}
|
|
83
156
|
|
|
84
|
-
|
|
157
|
+
// ── Theme file collection ────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
/** Recursively collect text files from a theme directory for the fs shim. */
|
|
85
160
|
function collectThemeFiles(themeDir) {
|
|
86
161
|
const files = {};
|
|
87
162
|
const dirs = {};
|
|
@@ -102,9 +177,9 @@ function collectThemeFiles(themeDir) {
|
|
|
102
177
|
walk(full, rel);
|
|
103
178
|
} else {
|
|
104
179
|
const ext = (entry.name.split('.').pop() || '').toLowerCase();
|
|
105
|
-
if (
|
|
180
|
+
if (TEXT_EXTS.has(ext)) {
|
|
106
181
|
try { files[rel] = readFileSync(full, 'utf-8'); }
|
|
107
|
-
catch { /* skip
|
|
182
|
+
catch { /* skip */ }
|
|
108
183
|
}
|
|
109
184
|
}
|
|
110
185
|
}
|
|
@@ -115,53 +190,168 @@ function collectThemeFiles(themeDir) {
|
|
|
115
190
|
return { files, dirs };
|
|
116
191
|
}
|
|
117
192
|
|
|
118
|
-
/** Generate an fs shim that embeds the theme's files
|
|
193
|
+
/** Generate an fs shim that embeds the theme's files. */
|
|
119
194
|
function generateThemeFsShim(themeFiles) {
|
|
120
195
|
const { files, dirs } = themeFiles;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
196
|
+
const lines = [
|
|
197
|
+
`var __files = ${JSON.stringify(files)};`,
|
|
198
|
+
`var __dirs = ${JSON.stringify(dirs)};`,
|
|
199
|
+
'',
|
|
200
|
+
'function normalizePath(p) {',
|
|
201
|
+
' return String(p).replace(/[\\\\]+/g, "/").replace(/^\\/+/, "");',
|
|
202
|
+
'}',
|
|
203
|
+
'',
|
|
204
|
+
'function matchFile(p) {',
|
|
205
|
+
' var clean = normalizePath(p);',
|
|
206
|
+
' if (__files[clean] !== undefined) return __files[clean];',
|
|
207
|
+
' var keys = Object.keys(__files);',
|
|
208
|
+
' for (var i = 0; i < keys.length; i++) {',
|
|
209
|
+
' if (clean.endsWith("/" + keys[i]) || clean === keys[i]) return __files[keys[i]];',
|
|
210
|
+
' }',
|
|
211
|
+
' return undefined;',
|
|
212
|
+
'}',
|
|
213
|
+
'',
|
|
214
|
+
'function matchDir(p) {',
|
|
215
|
+
' var clean = normalizePath(p);',
|
|
216
|
+
' if (__dirs[clean] !== undefined) return __dirs[clean];',
|
|
217
|
+
' var keys = Object.keys(__dirs);',
|
|
218
|
+
' for (var i = 0; i < keys.length; i++) {',
|
|
219
|
+
' if (clean.endsWith("/" + keys[i]) || clean === keys[i]) return __dirs[keys[i]];',
|
|
220
|
+
' }',
|
|
221
|
+
' return undefined;',
|
|
222
|
+
'}',
|
|
223
|
+
'',
|
|
224
|
+
'export var readFileSync = function(p, encoding) {',
|
|
225
|
+
' var r = matchFile(p);',
|
|
226
|
+
' if (r !== undefined) return r;',
|
|
227
|
+
' return typeof encoding === "string" ? "" : "";',
|
|
228
|
+
'};',
|
|
229
|
+
'',
|
|
230
|
+
'export var readdirSync = function(p, opts) {',
|
|
231
|
+
' var r = matchDir(p);',
|
|
232
|
+
' if (r === undefined) r = [];',
|
|
233
|
+
' if (opts && opts.withFileTypes) {',
|
|
234
|
+
' return r.map(function(name) {',
|
|
235
|
+
' var childPath = normalizePath(p) + "/" + name;',
|
|
236
|
+
' var isDir = matchDir(childPath) !== undefined;',
|
|
237
|
+
' return { name: name, isFile: function() { return !isDir; }, isDirectory: function() { return isDir; } };',
|
|
238
|
+
' });',
|
|
239
|
+
' }',
|
|
240
|
+
' return r;',
|
|
241
|
+
'};',
|
|
242
|
+
'',
|
|
243
|
+
'export var existsSync = function(p) { return matchFile(p) !== undefined || matchDir(p) !== undefined; };',
|
|
244
|
+
'export var writeFileSync = function() {};',
|
|
245
|
+
'export var mkdirSync = function() {};',
|
|
246
|
+
'export var statSync = function() { return { isFile: function() { return true; }, isDirectory: function() { return false; }, size: 0, mtime: new Date() }; };',
|
|
247
|
+
'export var lstatSync = statSync;',
|
|
248
|
+
'export var unlinkSync = function() {};',
|
|
249
|
+
'export var rmdirSync = function() {};',
|
|
250
|
+
'export var createReadStream = function() { return { pipe: function(d) { return d; }, on: function() { return this; } }; };',
|
|
251
|
+
'export var createWriteStream = function() { return { write: function() {}, end: function() {}, on: function() { return this; } }; };',
|
|
252
|
+
'export default { readFileSync: readFileSync, readdirSync: readdirSync, existsSync: existsSync, writeFileSync: writeFileSync, mkdirSync: mkdirSync, statSync: statSync, lstatSync: lstatSync, unlinkSync: unlinkSync, rmdirSync: rmdirSync, createReadStream: createReadStream, createWriteStream: createWriteStream };',
|
|
253
|
+
];
|
|
254
|
+
return lines.join('\n');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ── CSS extraction ───────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
/** Extract <style> blocks from rendered HTML and write to a .css file. */
|
|
260
|
+
function extractCss(html, shortName) {
|
|
261
|
+
const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
262
|
+
let css = '';
|
|
263
|
+
let match;
|
|
264
|
+
while ((match = styleRegex.exec(html)) !== null) {
|
|
265
|
+
css += match[1] + '\n';
|
|
266
|
+
}
|
|
267
|
+
if (css.trim()) {
|
|
268
|
+
writeFileSync(resolve(THEMES_DIR, `${shortName}.css`), css.trim());
|
|
269
|
+
return true;
|
|
130
270
|
}
|
|
131
|
-
return
|
|
271
|
+
return false;
|
|
132
272
|
}
|
|
133
273
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
274
|
+
// ── Snapshot generation ──────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
/** Render a theme with sample data in Node.js and save as snapshot. */
|
|
277
|
+
function generateSnapshot(shortName, packageName) {
|
|
278
|
+
try {
|
|
279
|
+
// Clear require cache to get a fresh instance
|
|
280
|
+
const pkgPath = require.resolve(packageName);
|
|
281
|
+
delete require.cache[pkgPath];
|
|
282
|
+
|
|
283
|
+
const theme = require(packageName);
|
|
284
|
+
const render = theme.render || (theme.default && theme.default.render);
|
|
285
|
+
if (typeof render !== 'function') return null;
|
|
286
|
+
|
|
287
|
+
const paddedResume = padResume(SAMPLE_RESUME);
|
|
288
|
+
const result = render(paddedResume);
|
|
289
|
+
// render() may return a string or a Promise
|
|
290
|
+
if (typeof result === 'string') {
|
|
291
|
+
writeFileSync(resolve(THEMES_DIR, `${shortName}.snapshot.html`), result);
|
|
292
|
+
extractCss(result, shortName);
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
if (result && typeof result.then === 'function') {
|
|
296
|
+
return result.then((html) => {
|
|
297
|
+
if (typeof html === 'string') {
|
|
298
|
+
writeFileSync(resolve(THEMES_DIR, `${shortName}.snapshot.html`), html);
|
|
299
|
+
extractCss(html, shortName);
|
|
300
|
+
return html;
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}).catch(() => null);
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
} catch (e) {
|
|
307
|
+
console.log(` (snapshot failed: ${e.message})`);
|
|
308
|
+
return null;
|
|
139
309
|
}
|
|
140
|
-
return undefined;
|
|
141
310
|
}
|
|
142
311
|
|
|
143
|
-
|
|
144
|
-
const r = matchFile(path);
|
|
145
|
-
return r !== undefined ? r : '';
|
|
146
|
-
};
|
|
312
|
+
// ── npm discovery ────────────────────────────────────────────────────
|
|
147
313
|
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
314
|
+
async function discoverThemes() {
|
|
315
|
+
const themes = [];
|
|
316
|
+
let from = 0;
|
|
317
|
+
const size = 250;
|
|
152
318
|
|
|
153
|
-
|
|
154
|
-
return matchFile(path) !== undefined || matchDir(path) !== undefined;
|
|
155
|
-
};
|
|
319
|
+
console.log('🔍 Discovering themes from npm...');
|
|
156
320
|
|
|
157
|
-
|
|
158
|
-
`;
|
|
321
|
+
for (let page = 0; page < 4; page++) {
|
|
322
|
+
const url = `https://registry.npmjs.org/-/v1/search?text=jsonresume-theme&size=${size}&from=${from}`;
|
|
323
|
+
const res = await fetch(url);
|
|
324
|
+
if (!res.ok) break;
|
|
325
|
+
const data = await res.json();
|
|
326
|
+
|
|
327
|
+
for (const pkg of data.objects) {
|
|
328
|
+
const name = pkg.package.name;
|
|
329
|
+
if (!name.startsWith('jsonresume-theme-')) continue;
|
|
330
|
+
const shortName = name.replace('jsonresume-theme-', '');
|
|
331
|
+
if (pkg.score?.detail?.popularity === 0) continue;
|
|
332
|
+
|
|
333
|
+
themes.push({
|
|
334
|
+
name: shortName,
|
|
335
|
+
packageName: name,
|
|
336
|
+
description: pkg.package.description || '',
|
|
337
|
+
version: pkg.package.version,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (data.objects.length < size) break;
|
|
342
|
+
from += size;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
console.log(` Found ${themes.length} themes`);
|
|
346
|
+
return themes;
|
|
159
347
|
}
|
|
160
348
|
|
|
349
|
+
// ── esbuild bundling ─────────────────────────────────────────────────
|
|
350
|
+
|
|
161
351
|
async function bundleTheme(shortName, packageName, shimsDir) {
|
|
162
|
-
// Generate a theme-specific fs shim with embedded file contents so that
|
|
163
|
-
// readFileSync / readdirSync return real CSS, templates, and partials at runtime.
|
|
164
352
|
const themeDir = resolve(__dirname, `../node_modules/${packageName}`);
|
|
353
|
+
|
|
354
|
+
// Generate per-theme fs shim with embedded files
|
|
165
355
|
const themeFiles = collectThemeFiles(themeDir);
|
|
166
356
|
writeFileSync(resolve(shimsDir, 'fs.js'), generateThemeFsShim(themeFiles));
|
|
167
357
|
|
|
@@ -183,44 +373,264 @@ async function bundleTheme(shortName, packageName, shimsDir) {
|
|
|
183
373
|
format: 'esm',
|
|
184
374
|
target: 'es2022',
|
|
185
375
|
platform: 'browser',
|
|
186
|
-
// Use 'require' condition so packages like underscore/lodash resolve to their
|
|
187
|
-
// CJS/UMD builds (which export a callable function) instead of their ESM builds
|
|
188
|
-
// (which export a namespace object that breaks _(collection) call syntax).
|
|
189
376
|
conditions: ['browser', 'require', 'default'],
|
|
190
|
-
// Prefer the CJS 'main' field over the ESM 'module' field for packages
|
|
191
|
-
// that don't use the exports map (older packages).
|
|
192
377
|
mainFields: ['browser', 'main'],
|
|
193
378
|
outfile: resolve(THEMES_DIR, `${shortName}.js`),
|
|
194
379
|
define: {
|
|
195
380
|
'process.env.NODE_ENV': '"production"',
|
|
381
|
+
'process.env.LANG': '""',
|
|
196
382
|
'global': 'globalThis',
|
|
197
383
|
'__dirname': '"/"',
|
|
198
384
|
'__filename': '"/index.js"',
|
|
199
385
|
'process.browser': 'true',
|
|
200
386
|
'process.platform': '"browser"',
|
|
201
|
-
'process.version': '"
|
|
387
|
+
'process.version': '"v20.0.0"',
|
|
388
|
+
'process.versions': '{}',
|
|
389
|
+
'process.stdout': 'false',
|
|
390
|
+
'process.stderr': 'false',
|
|
202
391
|
},
|
|
203
|
-
// Polyfill Node.js built-ins as no-ops for browser
|
|
204
392
|
alias: {
|
|
205
|
-
'path': resolve(
|
|
206
|
-
'fs': resolve(
|
|
207
|
-
'url': resolve(
|
|
208
|
-
'
|
|
209
|
-
'
|
|
210
|
-
'
|
|
393
|
+
'path': resolve(shimsDir, 'path.js'),
|
|
394
|
+
'fs': resolve(shimsDir, 'fs.js'),
|
|
395
|
+
'url': resolve(shimsDir, 'url.js'),
|
|
396
|
+
'assert': resolve(shimsDir, 'assert.js'),
|
|
397
|
+
'buffer': resolve(shimsDir, 'buffer.js'),
|
|
398
|
+
'stream': resolve(shimsDir, 'stream.js'),
|
|
399
|
+
'util': resolve(shimsDir, 'util.js'),
|
|
400
|
+
'events': resolve(shimsDir, 'events.js'),
|
|
401
|
+
'os': resolve(shimsDir, 'os.js'),
|
|
402
|
+
// node: prefixed variants
|
|
403
|
+
'node:fs': resolve(shimsDir, 'fs.js'),
|
|
404
|
+
'node:path': resolve(shimsDir, 'path.js'),
|
|
405
|
+
'node:url': resolve(shimsDir, 'url.js'),
|
|
406
|
+
'node:crypto': resolve(shimsDir, 'crypto.js'),
|
|
407
|
+
'node:buffer': resolve(shimsDir, 'buffer.js'),
|
|
408
|
+
'node:stream': resolve(shimsDir, 'stream.js'),
|
|
409
|
+
'node:util': resolve(shimsDir, 'util.js'),
|
|
410
|
+
'node:events': resolve(shimsDir, 'events.js'),
|
|
411
|
+
'node:os': resolve(shimsDir, 'os.js'),
|
|
412
|
+
'node:assert': resolve(shimsDir, 'assert.js'),
|
|
211
413
|
},
|
|
212
414
|
logLevel: 'silent',
|
|
213
415
|
});
|
|
214
416
|
|
|
215
|
-
// Clean up entry file
|
|
216
417
|
execSync(`rm -f "${entryFile}"`);
|
|
217
418
|
return true;
|
|
218
419
|
} catch (e) {
|
|
420
|
+
console.log(` (bundle error: ${e.message})`);
|
|
219
421
|
execSync(`rm -f "${entryFile}"`);
|
|
220
422
|
return false;
|
|
221
423
|
}
|
|
222
424
|
}
|
|
223
425
|
|
|
426
|
+
// ── Shim file writers ────────────────────────────────────────────────
|
|
427
|
+
|
|
428
|
+
function writeShims(shimsDir) {
|
|
429
|
+
writeFileSync(resolve(shimsDir, 'path.js'), [
|
|
430
|
+
'export var join = function() { return [].slice.call(arguments).join("/"); };',
|
|
431
|
+
'export var resolve = function() { return [].slice.call(arguments).join("/"); };',
|
|
432
|
+
'export var dirname = function(p) { return p.split("/").slice(0, -1).join("/"); };',
|
|
433
|
+
'export var basename = function(p, ext) { var b = p.split("/").pop() || ""; return ext && b.endsWith(ext) ? b.slice(0, -ext.length) : b; };',
|
|
434
|
+
'export var extname = function(p) { var m = p.match(/\\.[^.]+$/); return m ? m[0] : ""; };',
|
|
435
|
+
'export var sep = "/";',
|
|
436
|
+
'export var isAbsolute = function(p) { return p.charAt(0) === "/"; };',
|
|
437
|
+
'export var normalize = function(p) { return p; };',
|
|
438
|
+
'export var relative = function(from, to) { return to; };',
|
|
439
|
+
'export var parse = function(p) { return { root: "", dir: dirname(p), base: basename(p), ext: extname(p), name: basename(p, extname(p)) }; };',
|
|
440
|
+
'export default { join: join, resolve: resolve, dirname: dirname, basename: basename, extname: extname, sep: sep, isAbsolute: isAbsolute, normalize: normalize, relative: relative, parse: parse };',
|
|
441
|
+
].join('\n'));
|
|
442
|
+
|
|
443
|
+
// fs shim is generated per-theme in bundleTheme()
|
|
444
|
+
|
|
445
|
+
writeFileSync(resolve(shimsDir, 'url.js'), [
|
|
446
|
+
'export var URL = globalThis.URL;',
|
|
447
|
+
'export var URLSearchParams = globalThis.URLSearchParams;',
|
|
448
|
+
'export var fileURLToPath = function(u) { return u.replace(/^file:\\/\\//, ""); };',
|
|
449
|
+
'export var pathToFileURL = function(p) { return new globalThis.URL("file://" + p); };',
|
|
450
|
+
'export var format = function(u) { return typeof u === "string" ? u : u.href; };',
|
|
451
|
+
'export var parse = function(u) { return new globalThis.URL(u); };',
|
|
452
|
+
'export default { URL: URL, URLSearchParams: URLSearchParams, fileURLToPath: fileURLToPath, pathToFileURL: pathToFileURL, format: format, parse: parse };',
|
|
453
|
+
].join('\n'));
|
|
454
|
+
|
|
455
|
+
writeFileSync(resolve(shimsDir, 'crypto.js'), [
|
|
456
|
+
'export var createHash = function() { return { update: function() { return this; }, digest: function() { return ""; } }; };',
|
|
457
|
+
'export var randomBytes = function(n) { return new Uint8Array(n); };',
|
|
458
|
+
'export var createHmac = function() { return { update: function() { return this; }, digest: function() { return ""; } }; };',
|
|
459
|
+
'export default { createHash: createHash, randomBytes: randomBytes, createHmac: createHmac };',
|
|
460
|
+
].join('\n'));
|
|
461
|
+
|
|
462
|
+
writeFileSync(resolve(shimsDir, 'assert.js'), [
|
|
463
|
+
'var assert = function(v, msg) { if (!v) throw new Error(msg || "Assertion failed"); };',
|
|
464
|
+
'assert.ok = assert;',
|
|
465
|
+
'assert.strictEqual = function(a, b) { if (a !== b) throw new Error("Not equal"); };',
|
|
466
|
+
'assert.deepStrictEqual = function() {};',
|
|
467
|
+
'assert.fail = function(msg) { throw new Error(msg); };',
|
|
468
|
+
'export default assert;',
|
|
469
|
+
'export var ok = assert;',
|
|
470
|
+
'export var strictEqual = assert.strictEqual;',
|
|
471
|
+
].join('\n'));
|
|
472
|
+
|
|
473
|
+
writeFileSync(resolve(shimsDir, 'buffer.js'), [
|
|
474
|
+
'var _enc = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;',
|
|
475
|
+
'var _dec = typeof TextDecoder !== "undefined" ? new TextDecoder() : null;',
|
|
476
|
+
'',
|
|
477
|
+
'function BufferShim(arg) {',
|
|
478
|
+
' if (typeof arg === "number") return new Uint8Array(arg);',
|
|
479
|
+
' if (arg instanceof Uint8Array) return arg;',
|
|
480
|
+
' if (arg instanceof ArrayBuffer) return new Uint8Array(arg);',
|
|
481
|
+
' return new Uint8Array(0);',
|
|
482
|
+
'}',
|
|
483
|
+
'',
|
|
484
|
+
'BufferShim.from = function(data, encoding) {',
|
|
485
|
+
' if (typeof data === "string") {',
|
|
486
|
+
' if (encoding === "base64") {',
|
|
487
|
+
' var bin = atob(data);',
|
|
488
|
+
' var bytes = new Uint8Array(bin.length);',
|
|
489
|
+
' for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);',
|
|
490
|
+
' return bytes;',
|
|
491
|
+
' }',
|
|
492
|
+
' return _enc ? _enc.encode(data) : new Uint8Array(0);',
|
|
493
|
+
' }',
|
|
494
|
+
' if (Array.isArray(data)) return new Uint8Array(data);',
|
|
495
|
+
' if (data instanceof ArrayBuffer) return new Uint8Array(data);',
|
|
496
|
+
' if (data instanceof Uint8Array) return new Uint8Array(data);',
|
|
497
|
+
' return new Uint8Array(0);',
|
|
498
|
+
'};',
|
|
499
|
+
'BufferShim.alloc = function(size) { return new Uint8Array(size); };',
|
|
500
|
+
'BufferShim.allocUnsafe = function(size) { return new Uint8Array(size); };',
|
|
501
|
+
'BufferShim.isBuffer = function(obj) { return obj instanceof Uint8Array; };',
|
|
502
|
+
'BufferShim.isEncoding = function() { return true; };',
|
|
503
|
+
'BufferShim.byteLength = function(str) { return _enc ? _enc.encode(str).length : str.length; };',
|
|
504
|
+
'BufferShim.concat = function(list) {',
|
|
505
|
+
' var total = 0;',
|
|
506
|
+
' for (var i = 0; i < list.length; i++) total += list[i].length;',
|
|
507
|
+
' var result = new Uint8Array(total);',
|
|
508
|
+
' var offset = 0;',
|
|
509
|
+
' for (var j = 0; j < list.length; j++) { result.set(list[j], offset); offset += list[j].length; }',
|
|
510
|
+
' return result;',
|
|
511
|
+
'};',
|
|
512
|
+
'',
|
|
513
|
+
'export var Buffer = BufferShim;',
|
|
514
|
+
'export var SlowBuffer = BufferShim;',
|
|
515
|
+
'export var INSPECT_MAX_BYTES = 50;',
|
|
516
|
+
'export var kMaxLength = 2147483647;',
|
|
517
|
+
'export default { Buffer: BufferShim, SlowBuffer: BufferShim, INSPECT_MAX_BYTES: 50, kMaxLength: 2147483647 };',
|
|
518
|
+
].join('\n'));
|
|
519
|
+
|
|
520
|
+
writeFileSync(resolve(shimsDir, 'stream.js'), [
|
|
521
|
+
'function EventBase() { this._e = {}; }',
|
|
522
|
+
'EventBase.prototype.on = function(ev, fn) { (this._e[ev] = this._e[ev] || []).push(fn); return this; };',
|
|
523
|
+
'EventBase.prototype.addListener = EventBase.prototype.on;',
|
|
524
|
+
'EventBase.prototype.once = function(ev, fn) { var self = this; var w = function() { self.removeListener(ev, w); fn.apply(null, arguments); }; return this.on(ev, w); };',
|
|
525
|
+
'EventBase.prototype.emit = function(ev) { var args = [].slice.call(arguments, 1); (this._e[ev] || []).forEach(function(fn) { fn.apply(null, args); }); return true; };',
|
|
526
|
+
'EventBase.prototype.removeListener = function(ev, fn) { var l = this._e[ev]; if (l) this._e[ev] = l.filter(function(f) { return f !== fn; }); return this; };',
|
|
527
|
+
'EventBase.prototype.off = EventBase.prototype.removeListener;',
|
|
528
|
+
'EventBase.prototype.removeAllListeners = function(ev) { if (ev) delete this._e[ev]; else this._e = {}; return this; };',
|
|
529
|
+
'EventBase.prototype.setMaxListeners = function() { return this; };',
|
|
530
|
+
'EventBase.prototype.listeners = function(ev) { return this._e[ev] || []; };',
|
|
531
|
+
'',
|
|
532
|
+
'function Readable() { EventBase.call(this); } Readable.prototype = Object.create(EventBase.prototype);',
|
|
533
|
+
'Readable.prototype.read = function() { return null; };',
|
|
534
|
+
'Readable.prototype.pipe = function(d) { return d; };',
|
|
535
|
+
'Readable.prototype.unpipe = function() { return this; };',
|
|
536
|
+
'Readable.prototype.destroy = function() { return this; };',
|
|
537
|
+
'',
|
|
538
|
+
'function Writable() { EventBase.call(this); } Writable.prototype = Object.create(EventBase.prototype);',
|
|
539
|
+
'Writable.prototype.write = function() { return true; };',
|
|
540
|
+
'Writable.prototype.end = function() { return this; };',
|
|
541
|
+
'Writable.prototype.destroy = function() { return this; };',
|
|
542
|
+
'',
|
|
543
|
+
'function Transform() { EventBase.call(this); } Transform.prototype = Object.create(EventBase.prototype);',
|
|
544
|
+
'Transform.prototype.write = function() { return true; };',
|
|
545
|
+
'Transform.prototype.end = function() { return this; };',
|
|
546
|
+
'Transform.prototype.pipe = function(d) { return d; };',
|
|
547
|
+
'Transform.prototype.destroy = function() { return this; };',
|
|
548
|
+
'',
|
|
549
|
+
'function PassThrough() { Transform.call(this); } PassThrough.prototype = Object.create(Transform.prototype);',
|
|
550
|
+
'function Duplex() { EventBase.call(this); } Duplex.prototype = Object.create(EventBase.prototype);',
|
|
551
|
+
'Duplex.prototype.write = function() { return true; };',
|
|
552
|
+
'Duplex.prototype.end = function() { return this; };',
|
|
553
|
+
'Duplex.prototype.read = function() { return null; };',
|
|
554
|
+
'Duplex.prototype.pipe = function(d) { return d; };',
|
|
555
|
+
'Duplex.prototype.destroy = function() { return this; };',
|
|
556
|
+
'',
|
|
557
|
+
'function Stream() { EventBase.call(this); } Stream.prototype = Object.create(EventBase.prototype);',
|
|
558
|
+
'Stream.prototype.pipe = function(d) { return d; };',
|
|
559
|
+
'Stream.Readable = Readable; Stream.Writable = Writable; Stream.Transform = Transform;',
|
|
560
|
+
'Stream.PassThrough = PassThrough; Stream.Duplex = Duplex; Stream.Stream = Stream;',
|
|
561
|
+
'',
|
|
562
|
+
'export { Readable, Writable, Transform, PassThrough, Duplex, Stream };',
|
|
563
|
+
'export default Stream;',
|
|
564
|
+
].join('\n'));
|
|
565
|
+
|
|
566
|
+
writeFileSync(resolve(shimsDir, 'util.js'), [
|
|
567
|
+
'export var inherits = function(ctor, superCtor) { ctor.super_ = superCtor; Object.setPrototypeOf(ctor.prototype, superCtor.prototype); };',
|
|
568
|
+
'export var deprecate = function(fn) { return fn; };',
|
|
569
|
+
'export var promisify = function(fn) { return function() { var args = [].slice.call(arguments); return new Promise(function(ok, fail) { args.push(function(err, res) { err ? fail(err) : ok(res); }); fn.apply(null, args); }); }; };',
|
|
570
|
+
'export var inspect = function(obj) { try { return JSON.stringify(obj); } catch(e) { return String(obj); } };',
|
|
571
|
+
'export var format = function(f) { if (typeof f !== "string") return [].map.call(arguments, function(a) { return inspect(a); }).join(" "); var i = 1; var args = arguments; return f.replace(/%[sdj%]/g, function(m) { if (m === "%%") return "%"; if (i >= args.length) return m; var v = args[i++]; if (m === "%s") return String(v); if (m === "%d") return Number(v); if (m === "%j") try { return JSON.stringify(v); } catch(e) { return "[Circular]"; } return m; }); };',
|
|
572
|
+
'export var debuglog = function() { return function() {}; };',
|
|
573
|
+
'export var isArray = Array.isArray;',
|
|
574
|
+
'export var isBoolean = function(v) { return typeof v === "boolean"; };',
|
|
575
|
+
'export var isNull = function(v) { return v === null; };',
|
|
576
|
+
'export var isNumber = function(v) { return typeof v === "number"; };',
|
|
577
|
+
'export var isString = function(v) { return typeof v === "string"; };',
|
|
578
|
+
'export var isUndefined = function(v) { return v === undefined; };',
|
|
579
|
+
'export var isObject = function(v) { return typeof v === "object" && v !== null; };',
|
|
580
|
+
'export var isFunction = function(v) { return typeof v === "function"; };',
|
|
581
|
+
'export var isRegExp = function(v) { return v instanceof RegExp; };',
|
|
582
|
+
'export var isDate = function(v) { return v instanceof Date; };',
|
|
583
|
+
'export var isError = function(v) { return v instanceof Error; };',
|
|
584
|
+
'export var isPrimitive = function(v) { return v === null || typeof v !== "object" && typeof v !== "function"; };',
|
|
585
|
+
'export var TextEncoder = globalThis.TextEncoder;',
|
|
586
|
+
'export var TextDecoder = globalThis.TextDecoder;',
|
|
587
|
+
'export var types = { isAnyArrayBuffer: function() { return false; }, isTypedArray: function(v) { return ArrayBuffer.isView(v); } };',
|
|
588
|
+
'export default { inherits: inherits, deprecate: deprecate, promisify: promisify, inspect: inspect, format: format, debuglog: debuglog, isArray: isArray, isBoolean: isBoolean, isNull: isNull, isNumber: isNumber, isString: isString, isUndefined: isUndefined, isObject: isObject, isFunction: isFunction, isRegExp: isRegExp, isDate: isDate, isError: isError, isPrimitive: isPrimitive, TextEncoder: TextEncoder, TextDecoder: TextDecoder, types: types };',
|
|
589
|
+
].join('\n'));
|
|
590
|
+
|
|
591
|
+
writeFileSync(resolve(shimsDir, 'events.js'), [
|
|
592
|
+
'function EventEmitter() { this._events = {}; this._maxListeners = 10; }',
|
|
593
|
+
'EventEmitter.prototype.on = function(ev, fn) { (this._events[ev] = this._events[ev] || []).push(fn); return this; };',
|
|
594
|
+
'EventEmitter.prototype.addListener = EventEmitter.prototype.on;',
|
|
595
|
+
'EventEmitter.prototype.once = function(ev, fn) { var self = this; var w = function() { self.removeListener(ev, w); fn.apply(null, arguments); }; return this.on(ev, w); };',
|
|
596
|
+
'EventEmitter.prototype.emit = function(ev) { var args = [].slice.call(arguments, 1); (this._events[ev] || []).forEach(function(fn) { fn.apply(null, args); }); return true; };',
|
|
597
|
+
'EventEmitter.prototype.removeListener = function(ev, fn) { var l = this._events[ev]; if (l) this._events[ev] = l.filter(function(f) { return f !== fn; }); return this; };',
|
|
598
|
+
'EventEmitter.prototype.off = EventEmitter.prototype.removeListener;',
|
|
599
|
+
'EventEmitter.prototype.removeAllListeners = function(ev) { if (ev) delete this._events[ev]; else this._events = {}; return this; };',
|
|
600
|
+
'EventEmitter.prototype.setMaxListeners = function(n) { this._maxListeners = n; return this; };',
|
|
601
|
+
'EventEmitter.prototype.getMaxListeners = function() { return this._maxListeners; };',
|
|
602
|
+
'EventEmitter.prototype.listeners = function(ev) { return this._events[ev] || []; };',
|
|
603
|
+
'EventEmitter.prototype.listenerCount = function(ev) { return (this._events[ev] || []).length; };',
|
|
604
|
+
'EventEmitter.prototype.prependListener = EventEmitter.prototype.on;',
|
|
605
|
+
'EventEmitter.prototype.prependOnceListener = EventEmitter.prototype.once;',
|
|
606
|
+
'EventEmitter.prototype.eventNames = function() { return Object.keys(this._events); };',
|
|
607
|
+
'EventEmitter.EventEmitter = EventEmitter;',
|
|
608
|
+
'EventEmitter.defaultMaxListeners = 10;',
|
|
609
|
+
'export { EventEmitter };',
|
|
610
|
+
'export default EventEmitter;',
|
|
611
|
+
].join('\n'));
|
|
612
|
+
|
|
613
|
+
writeFileSync(resolve(shimsDir, 'os.js'), [
|
|
614
|
+
'export var platform = function() { return "browser"; };',
|
|
615
|
+
'export var tmpdir = function() { return "/tmp"; };',
|
|
616
|
+
'export var homedir = function() { return "/"; };',
|
|
617
|
+
'export var hostname = function() { return "localhost"; };',
|
|
618
|
+
'export var type = function() { return "Browser"; };',
|
|
619
|
+
'export var arch = function() { return "wasm"; };',
|
|
620
|
+
'export var release = function() { return "0.0.0"; };',
|
|
621
|
+
'export var EOL = "\\n";',
|
|
622
|
+
'export var endianness = function() { return "LE"; };',
|
|
623
|
+
'export var cpus = function() { return []; };',
|
|
624
|
+
'export var totalmem = function() { return 0; };',
|
|
625
|
+
'export var freemem = function() { return 0; };',
|
|
626
|
+
'export var networkInterfaces = function() { return {}; };',
|
|
627
|
+
'export var userInfo = function() { return { username: "browser", uid: 0, gid: 0, shell: "", homedir: "/" }; };',
|
|
628
|
+
'export default { platform: platform, tmpdir: tmpdir, homedir: homedir, hostname: hostname, type: type, arch: arch, release: release, EOL: EOL, endianness: endianness, cpus: cpus, totalmem: totalmem, freemem: freemem, networkInterfaces: networkInterfaces, userInfo: userInfo };',
|
|
629
|
+
].join('\n'));
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// ── Main ─────────────────────────────────────────────────────────────
|
|
633
|
+
|
|
224
634
|
async function main() {
|
|
225
635
|
const args = process.argv.slice(2);
|
|
226
636
|
const doAll = args.includes('--all');
|
|
@@ -229,43 +639,10 @@ async function main() {
|
|
|
229
639
|
|
|
230
640
|
mkdirSync(THEMES_DIR, { recursive: true });
|
|
231
641
|
|
|
232
|
-
// Create shims directory
|
|
642
|
+
// Create shims directory and write all shims
|
|
233
643
|
const shimsDir = resolve(__dirname, 'shims');
|
|
234
644
|
mkdirSync(shimsDir, { recursive: true });
|
|
235
|
-
|
|
236
|
-
export const join = (...parts) => parts.join('/');
|
|
237
|
-
export const resolve = (...parts) => parts.join('/');
|
|
238
|
-
export const dirname = (p) => p.split('/').slice(0, -1).join('/');
|
|
239
|
-
export const basename = (p) => p.split('/').pop();
|
|
240
|
-
export const extname = (p) => { const m = p.match(/\\.[^.]+$/); return m ? m[0] : ''; };
|
|
241
|
-
export default { join, resolve, dirname, basename, extname };
|
|
242
|
-
`);
|
|
243
|
-
// fs shim is generated per-theme in bundleTheme() with embedded file contents
|
|
244
|
-
writeFileSync(resolve(shimsDir, 'url.js'), `
|
|
245
|
-
export const URL = globalThis.URL;
|
|
246
|
-
export const URLSearchParams = globalThis.URLSearchParams;
|
|
247
|
-
export const fileURLToPath = (u) => u.replace(/^file:\\/\\//, '');
|
|
248
|
-
export const pathToFileURL = (p) => new globalThis.URL('file://' + p);
|
|
249
|
-
export const format = (u) => (typeof u === 'string' ? u : u.href);
|
|
250
|
-
export const parse = (u) => new globalThis.URL(u);
|
|
251
|
-
export default { URL, URLSearchParams, fileURLToPath, pathToFileURL, format, parse };
|
|
252
|
-
`);
|
|
253
|
-
writeFileSync(resolve(shimsDir, 'crypto.js'), `
|
|
254
|
-
export const createHash = () => ({ update: function() { return this; }, digest: () => '' });
|
|
255
|
-
export const randomBytes = (n) => new Uint8Array(n);
|
|
256
|
-
export const createHmac = () => ({ update: function() { return this; }, digest: () => '' });
|
|
257
|
-
export default { createHash, randomBytes, createHmac };
|
|
258
|
-
`);
|
|
259
|
-
writeFileSync(resolve(shimsDir, 'assert.js'), `
|
|
260
|
-
const assert = (v, msg) => { if (!v) throw new Error(msg || 'Assertion failed'); };
|
|
261
|
-
assert.ok = assert;
|
|
262
|
-
assert.strictEqual = (a, b) => { if (a !== b) throw new Error('Not equal'); };
|
|
263
|
-
assert.deepStrictEqual = () => {};
|
|
264
|
-
assert.fail = (msg) => { throw new Error(msg); };
|
|
265
|
-
export default assert;
|
|
266
|
-
export const ok = assert;
|
|
267
|
-
export const strictEqual = assert.strictEqual;
|
|
268
|
-
`);
|
|
645
|
+
writeShims(shimsDir);
|
|
269
646
|
|
|
270
647
|
let themes;
|
|
271
648
|
if (specificThemes) {
|
|
@@ -295,16 +672,16 @@ async function main() {
|
|
|
295
672
|
|
|
296
673
|
// Install the theme
|
|
297
674
|
try {
|
|
298
|
-
execSync(`npm install --no-save ${theme.packageName} 2>/dev/null`, {
|
|
675
|
+
execSync(`npm install --no-save --prefer-offline ${theme.packageName} 2>/dev/null`, {
|
|
299
676
|
cwd: resolve(__dirname, '..'),
|
|
300
|
-
timeout:
|
|
677
|
+
timeout: 60000,
|
|
301
678
|
});
|
|
302
679
|
} catch {
|
|
303
680
|
console.log('❌ install failed');
|
|
304
681
|
continue;
|
|
305
682
|
}
|
|
306
683
|
|
|
307
|
-
//
|
|
684
|
+
// Read package metadata
|
|
308
685
|
try {
|
|
309
686
|
const pkgPath = resolve(__dirname, `../node_modules/${theme.packageName}/package.json`);
|
|
310
687
|
if (existsSync(pkgPath)) {
|
|
@@ -314,21 +691,44 @@ async function main() {
|
|
|
314
691
|
}
|
|
315
692
|
} catch {}
|
|
316
693
|
|
|
317
|
-
//
|
|
694
|
+
// Step 1: Generate snapshot (pre-render in Node.js)
|
|
695
|
+
let hasSnapshot = false;
|
|
696
|
+
let hasCss = false;
|
|
697
|
+
const snapshotResult = generateSnapshot(theme.name, theme.packageName);
|
|
698
|
+
if (snapshotResult && typeof snapshotResult.then === 'function') {
|
|
699
|
+
const html = await snapshotResult;
|
|
700
|
+
hasSnapshot = !!html;
|
|
701
|
+
hasCss = existsSync(resolve(THEMES_DIR, `${theme.name}.css`));
|
|
702
|
+
} else {
|
|
703
|
+
hasSnapshot = !!snapshotResult;
|
|
704
|
+
hasCss = existsSync(resolve(THEMES_DIR, `${theme.name}.css`));
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// Step 2: Bundle for browser
|
|
318
708
|
const success = await bundleTheme(theme.name, theme.packageName, shimsDir);
|
|
319
709
|
|
|
320
710
|
if (success) {
|
|
321
711
|
const outFile = resolve(THEMES_DIR, `${theme.name}.js`);
|
|
322
712
|
const stat = existsSync(outFile) ? readFileSync(outFile).length : 0;
|
|
713
|
+
const cssFile = resolve(THEMES_DIR, `${theme.name}.css`);
|
|
714
|
+
const cssSize = hasCss && existsSync(cssFile) ? readFileSync(cssFile).length : 0;
|
|
715
|
+
|
|
323
716
|
manifest.push({
|
|
324
717
|
name: theme.name,
|
|
325
718
|
displayName: theme.name.charAt(0).toUpperCase() + theme.name.slice(1).replace(/-/g, ' '),
|
|
326
719
|
description: theme.description,
|
|
327
720
|
version: theme.version || '',
|
|
328
721
|
browserCompatible: true,
|
|
722
|
+
hasSnapshot,
|
|
723
|
+
hasCss,
|
|
329
724
|
fileSize: stat,
|
|
725
|
+
cssSize,
|
|
330
726
|
});
|
|
331
|
-
|
|
727
|
+
|
|
728
|
+
const parts = [`✅ (${(stat / 1024).toFixed(0)}KB`];
|
|
729
|
+
if (hasCss) parts.push(`css:${(cssSize / 1024).toFixed(0)}KB`);
|
|
730
|
+
if (hasSnapshot) parts.push('snapshot');
|
|
731
|
+
console.log(parts.join(', ') + ')');
|
|
332
732
|
} else {
|
|
333
733
|
manifest.push({
|
|
334
734
|
name: theme.name,
|
|
@@ -336,9 +736,13 @@ async function main() {
|
|
|
336
736
|
description: theme.description,
|
|
337
737
|
version: theme.version || '',
|
|
338
738
|
browserCompatible: false,
|
|
739
|
+
hasSnapshot,
|
|
740
|
+
hasCss: false,
|
|
339
741
|
fileSize: 0,
|
|
742
|
+
cssSize: 0,
|
|
340
743
|
});
|
|
341
|
-
|
|
744
|
+
const note = hasSnapshot ? '⚠️ browser incompatible (has snapshot)' : '⚠️ browser incompatible';
|
|
745
|
+
console.log(note);
|
|
342
746
|
}
|
|
343
747
|
}
|
|
344
748
|
|
|
@@ -346,7 +750,8 @@ async function main() {
|
|
|
346
750
|
writeFileSync(resolve(THEMES_DIR, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
347
751
|
|
|
348
752
|
const successful = manifest.filter(t => t.browserCompatible).length;
|
|
349
|
-
|
|
753
|
+
const snapshots = manifest.filter(t => t.hasSnapshot).length;
|
|
754
|
+
console.log(`\n✅ Done! ${successful}/${manifest.length} themes bundled, ${snapshots} snapshots generated`);
|
|
350
755
|
console.log(`📁 Output: docs/themes/`);
|
|
351
756
|
|
|
352
757
|
// Clean up shims
|