resuml 1.20.1 → 2.0.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/DOCS.md +314 -0
- package/README.md +7 -2
- package/dist/{chunk-KRJMZ2RQ.js → chunk-GRIYYG45.js} +242 -2
- package/dist/chunk-GRIYYG45.js.map +1 -0
- package/dist/index.d.ts +422 -3
- package/dist/index.js +119 -54
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +4 -8
- package/dist/mcp/server.js.map +1 -1
- package/package.json +26 -52
- package/dist/api.d.ts +0 -9
- package/dist/api.js +0 -20
- package/dist/api.js.map +0 -1
- package/dist/chunk-4ZOTZUAW.js +0 -6666
- package/dist/chunk-4ZOTZUAW.js.map +0 -1
- package/dist/chunk-JP7UCR3P.js +0 -182
- package/dist/chunk-JP7UCR3P.js.map +0 -1
- package/dist/chunk-KRJMZ2RQ.js.map +0 -1
- package/dist/chunk-ZLA7NFYP.js +0 -90
- package/dist/chunk-ZLA7NFYP.js.map +0 -1
- package/dist/index-yHdKpxms.d.ts +0 -422
- package/dist/themeLoader-ZGWEGYXG.js +0 -7
- package/dist/themeLoader-ZGWEGYXG.js.map +0 -1
- package/scripts/build-builder.js +0 -25
- package/scripts/build-skills-db.js +0 -314
- package/scripts/bundle-themes.js +0 -1104
- package/scripts/dev-server.js +0 -392
- package/scripts/enrich-themes-manifest.mjs +0 -156
- package/scripts/generate-types.cjs +0 -55
- package/scripts/mcp-call.mjs +0 -99
- package/scripts/quick-bundle.cjs +0 -129
- package/scripts/render-theme-thumbs.mjs +0 -117
- package/scripts/test-mcp.mjs +0 -583
package/scripts/bundle-themes.js
DELETED
|
@@ -1,1104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Theme Bundling Script
|
|
3
|
-
*
|
|
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.)
|
|
9
|
-
*
|
|
10
|
-
* Output:
|
|
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
|
|
15
|
-
*
|
|
16
|
-
* Usage:
|
|
17
|
-
* node scripts/bundle-themes.js # Bundle popular themes
|
|
18
|
-
* node scripts/bundle-themes.js --all # Discover + bundle all from npm
|
|
19
|
-
* node scripts/bundle-themes.js --themes elegant,even,kendall # Specific themes
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { build } from 'esbuild';
|
|
23
|
-
import { resolve, dirname, join } from 'path';
|
|
24
|
-
import { fileURLToPath } from 'url';
|
|
25
|
-
import { execSync } from 'child_process';
|
|
26
|
-
import { mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync } from 'fs';
|
|
27
|
-
import { createRequire } from 'module';
|
|
28
|
-
|
|
29
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
|
-
const require = createRequire(import.meta.url);
|
|
31
|
-
const THEMES_DIR = resolve(__dirname, '../docs/themes');
|
|
32
|
-
|
|
33
|
-
// Popular themes that are known to work in the browser
|
|
34
|
-
const POPULAR_THEMES = [
|
|
35
|
-
'stackoverflow',
|
|
36
|
-
'elegant',
|
|
37
|
-
'even',
|
|
38
|
-
'kendall',
|
|
39
|
-
'flat',
|
|
40
|
-
'macchiato',
|
|
41
|
-
'class',
|
|
42
|
-
'paper',
|
|
43
|
-
'spartan',
|
|
44
|
-
'short',
|
|
45
|
-
'caffeine',
|
|
46
|
-
'onepage',
|
|
47
|
-
'kwan',
|
|
48
|
-
'autumn',
|
|
49
|
-
'relaxed',
|
|
50
|
-
'compact',
|
|
51
|
-
];
|
|
52
|
-
|
|
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
|
-
};
|
|
132
|
-
|
|
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 meta = r.meta ?? {};
|
|
141
|
-
const palette = meta.palette ?? {};
|
|
142
|
-
const safe = {
|
|
143
|
-
...r,
|
|
144
|
-
basics: {
|
|
145
|
-
name: '', label: '', image: '', email: '', phone: '', url: '', summary: '',
|
|
146
|
-
...basics,
|
|
147
|
-
location: { address: '', postalCode: '', city: '', countryCode: '', region: '', ...location },
|
|
148
|
-
},
|
|
149
|
-
// Guard against themes like material that read meta.palette.primary without
|
|
150
|
-
// defensive checks.
|
|
151
|
-
meta: { ...meta, palette: { primary: '', secondary: '', ...palette } },
|
|
152
|
-
};
|
|
153
|
-
const arraySections = ['work','volunteer','education','awards','certificates','publications','skills','languages','interests','references','projects'];
|
|
154
|
-
for (const key of arraySections) {
|
|
155
|
-
safe[key] = Array.isArray(safe[key]) ? safe[key] : [];
|
|
156
|
-
}
|
|
157
|
-
const safeBasics = safe.basics;
|
|
158
|
-
safeBasics.profiles = Array.isArray(safeBasics.profiles) ? safeBasics.profiles : [];
|
|
159
|
-
return safe;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ── Theme file collection ────────────────────────────────────────────
|
|
163
|
-
|
|
164
|
-
/** Recursively collect text files from a theme directory for the fs shim. */
|
|
165
|
-
function collectThemeFiles(themeDir) {
|
|
166
|
-
const files = {};
|
|
167
|
-
const dirs = {};
|
|
168
|
-
|
|
169
|
-
function walk(dir, relPrefix = '') {
|
|
170
|
-
let entries;
|
|
171
|
-
try { entries = readdirSync(dir, { withFileTypes: true }); }
|
|
172
|
-
catch { return; }
|
|
173
|
-
|
|
174
|
-
const childNames = [];
|
|
175
|
-
for (const entry of entries) {
|
|
176
|
-
if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
|
|
177
|
-
childNames.push(entry.name);
|
|
178
|
-
const full = join(dir, entry.name);
|
|
179
|
-
const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
|
|
180
|
-
|
|
181
|
-
if (entry.isDirectory()) {
|
|
182
|
-
walk(full, rel);
|
|
183
|
-
} else {
|
|
184
|
-
const ext = (entry.name.split('.').pop() || '').toLowerCase();
|
|
185
|
-
if (TEXT_EXTS.has(ext)) {
|
|
186
|
-
try { files[rel] = readFileSync(full, 'utf-8'); }
|
|
187
|
-
catch { /* skip */ }
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
dirs[relPrefix || '.'] = childNames;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
walk(themeDir);
|
|
195
|
-
return { files, dirs };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/** Generate an fs shim that embeds the theme's files. */
|
|
199
|
-
function generateThemeFsShim(themeFiles) {
|
|
200
|
-
const { files, dirs } = themeFiles;
|
|
201
|
-
const lines = [
|
|
202
|
-
`var __files = ${JSON.stringify(files)};`,
|
|
203
|
-
`var __dirs = ${JSON.stringify(dirs)};`,
|
|
204
|
-
'',
|
|
205
|
-
'function normalizePath(p) {',
|
|
206
|
-
' return String(p).replace(/[\\\\]+/g, "/").replace(/^\\/+/, "");',
|
|
207
|
-
'}',
|
|
208
|
-
'',
|
|
209
|
-
'function matchFile(p) {',
|
|
210
|
-
' var clean = normalizePath(p);',
|
|
211
|
-
' if (__files[clean] !== undefined) return __files[clean];',
|
|
212
|
-
' var keys = Object.keys(__files);',
|
|
213
|
-
' for (var i = 0; i < keys.length; i++) {',
|
|
214
|
-
' if (clean.endsWith("/" + keys[i]) || clean === keys[i]) return __files[keys[i]];',
|
|
215
|
-
' }',
|
|
216
|
-
' return undefined;',
|
|
217
|
-
'}',
|
|
218
|
-
'',
|
|
219
|
-
'function matchDir(p) {',
|
|
220
|
-
' var clean = normalizePath(p);',
|
|
221
|
-
' // Root ("/"/".") after stripping leading slash is "", look up "." which',
|
|
222
|
-
' // the collector uses for the top-level directory listing.',
|
|
223
|
-
' if (clean === "" || clean === ".") {',
|
|
224
|
-
' if (__dirs["."] !== undefined) return __dirs["."];',
|
|
225
|
-
' }',
|
|
226
|
-
' if (__dirs[clean] !== undefined) return __dirs[clean];',
|
|
227
|
-
' var keys = Object.keys(__dirs);',
|
|
228
|
-
' for (var i = 0; i < keys.length; i++) {',
|
|
229
|
-
' if (clean.endsWith("/" + keys[i]) || clean === keys[i]) return __dirs[keys[i]];',
|
|
230
|
-
' }',
|
|
231
|
-
' return undefined;',
|
|
232
|
-
'}',
|
|
233
|
-
'',
|
|
234
|
-
'export var readFileSync = function(p, encoding) {',
|
|
235
|
-
' var r = matchFile(p);',
|
|
236
|
-
' if (r !== undefined) return r;',
|
|
237
|
-
' return typeof encoding === "string" ? "" : "";',
|
|
238
|
-
'};',
|
|
239
|
-
'',
|
|
240
|
-
'export var readdirSync = function(p, opts) {',
|
|
241
|
-
' var r = matchDir(p);',
|
|
242
|
-
' if (r === undefined) r = [];',
|
|
243
|
-
' if (opts && opts.withFileTypes) {',
|
|
244
|
-
' return r.map(function(name) {',
|
|
245
|
-
' var childPath = normalizePath(p) + "/" + name;',
|
|
246
|
-
' var isDir = matchDir(childPath) !== undefined;',
|
|
247
|
-
' return { name: name, isFile: function() { return !isDir; }, isDirectory: function() { return isDir; } };',
|
|
248
|
-
' });',
|
|
249
|
-
' }',
|
|
250
|
-
' return r;',
|
|
251
|
-
'};',
|
|
252
|
-
'',
|
|
253
|
-
'export var existsSync = function(p) { return matchFile(p) !== undefined || matchDir(p) !== undefined; };',
|
|
254
|
-
'export var writeFileSync = function() {};',
|
|
255
|
-
'export var mkdirSync = function() {};',
|
|
256
|
-
'export var statSync = function() { return { isFile: function() { return true; }, isDirectory: function() { return false; }, size: 0, mtime: new Date() }; };',
|
|
257
|
-
'export var lstatSync = statSync;',
|
|
258
|
-
'export var unlinkSync = function() {};',
|
|
259
|
-
'export var rmdirSync = function() {};',
|
|
260
|
-
'export var createReadStream = function() { return { pipe: function(d) { return d; }, on: function() { return this; } }; };',
|
|
261
|
-
'export var createWriteStream = function() { return { write: function() {}, end: function() {}, on: function() { return this; } }; };',
|
|
262
|
-
'export default { readFileSync: readFileSync, readdirSync: readdirSync, existsSync: existsSync, writeFileSync: writeFileSync, mkdirSync: mkdirSync, statSync: statSync, lstatSync: lstatSync, unlinkSync: unlinkSync, rmdirSync: rmdirSync, createReadStream: createReadStream, createWriteStream: createWriteStream };',
|
|
263
|
-
];
|
|
264
|
-
return lines.join('\n');
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// ── CSS extraction ───────────────────────────────────────────────────
|
|
268
|
-
|
|
269
|
-
/** Extract <style> blocks from rendered HTML and write to a .css file. */
|
|
270
|
-
function extractCss(html, shortName) {
|
|
271
|
-
const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
272
|
-
let css = '';
|
|
273
|
-
let match;
|
|
274
|
-
while ((match = styleRegex.exec(html)) !== null) {
|
|
275
|
-
css += match[1] + '\n';
|
|
276
|
-
}
|
|
277
|
-
if (css.trim()) {
|
|
278
|
-
writeFileSync(resolve(THEMES_DIR, `${shortName}.css`), css.trim());
|
|
279
|
-
return true;
|
|
280
|
-
}
|
|
281
|
-
return false;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// ── Snapshot generation ──────────────────────────────────────────────
|
|
285
|
-
|
|
286
|
-
/** Render a theme with sample data in Node.js and save as snapshot. */
|
|
287
|
-
function generateSnapshot(shortName, packageName) {
|
|
288
|
-
try {
|
|
289
|
-
// Clear require cache to get a fresh instance
|
|
290
|
-
const pkgPath = require.resolve(packageName);
|
|
291
|
-
delete require.cache[pkgPath];
|
|
292
|
-
|
|
293
|
-
const theme = require(packageName);
|
|
294
|
-
const render = theme.render || (theme.default && theme.default.render);
|
|
295
|
-
if (typeof render !== 'function') return null;
|
|
296
|
-
|
|
297
|
-
// Deep-clone SAMPLE_RESUME, some themes preprocess (e.g. markdown-ify)
|
|
298
|
-
// fields and mutate the input, which would then pollute subsequent
|
|
299
|
-
// themes' snapshots (keywords end up as "<p>TypeScript</p>" etc.).
|
|
300
|
-
const paddedResume = padResume(JSON.parse(JSON.stringify(SAMPLE_RESUME)));
|
|
301
|
-
const result = render(paddedResume);
|
|
302
|
-
// render() may return a string or a Promise
|
|
303
|
-
if (typeof result === 'string') {
|
|
304
|
-
writeFileSync(resolve(THEMES_DIR, `${shortName}.snapshot.html`), result);
|
|
305
|
-
extractCss(result, shortName);
|
|
306
|
-
return result;
|
|
307
|
-
}
|
|
308
|
-
if (result && typeof result.then === 'function') {
|
|
309
|
-
return result.then((html) => {
|
|
310
|
-
if (typeof html === 'string') {
|
|
311
|
-
writeFileSync(resolve(THEMES_DIR, `${shortName}.snapshot.html`), html);
|
|
312
|
-
extractCss(html, shortName);
|
|
313
|
-
return html;
|
|
314
|
-
}
|
|
315
|
-
return null;
|
|
316
|
-
}).catch(() => null);
|
|
317
|
-
}
|
|
318
|
-
return null;
|
|
319
|
-
} catch (e) {
|
|
320
|
-
console.log(` (snapshot failed: ${e.message})`);
|
|
321
|
-
return null;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// ── npm discovery ────────────────────────────────────────────────────
|
|
326
|
-
|
|
327
|
-
async function discoverThemes() {
|
|
328
|
-
const themes = [];
|
|
329
|
-
let from = 0;
|
|
330
|
-
const size = 250;
|
|
331
|
-
|
|
332
|
-
console.log('🔍 Discovering themes from npm...');
|
|
333
|
-
|
|
334
|
-
for (let page = 0; page < 4; page++) {
|
|
335
|
-
const url = `https://registry.npmjs.org/-/v1/search?text=jsonresume-theme&size=${size}&from=${from}`;
|
|
336
|
-
const res = await fetch(url);
|
|
337
|
-
if (!res.ok) break;
|
|
338
|
-
const data = await res.json();
|
|
339
|
-
|
|
340
|
-
for (const pkg of data.objects) {
|
|
341
|
-
const name = pkg.package.name;
|
|
342
|
-
if (!name.startsWith('jsonresume-theme-')) continue;
|
|
343
|
-
const shortName = name.replace('jsonresume-theme-', '');
|
|
344
|
-
if (pkg.score?.detail?.popularity === 0) continue;
|
|
345
|
-
|
|
346
|
-
themes.push({
|
|
347
|
-
name: shortName,
|
|
348
|
-
packageName: name,
|
|
349
|
-
description: pkg.package.description || '',
|
|
350
|
-
version: pkg.package.version,
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (data.objects.length < size) break;
|
|
355
|
-
from += size;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
console.log(` Found ${themes.length} themes`);
|
|
359
|
-
return themes;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// ── esbuild bundling ─────────────────────────────────────────────────
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* esbuild plugin that fixes legacy CJS patterns that break under ESM's
|
|
366
|
-
* implicit strict mode. Specifically: top-level implicit-global assignments
|
|
367
|
-
* like `COURSES_COLUMNS = 3;` (onepage, several older themes) which throw
|
|
368
|
-
* `ReferenceError` in strict mode. Rewrite them to `var` declarations.
|
|
369
|
-
*
|
|
370
|
-
* Scope: applied to any .js file inside jsonresume-theme-* packages. Keep the
|
|
371
|
-
* regex conservative, only match top-level (start-of-line) SCREAMING_SNAKE
|
|
372
|
-
* identifier assignments. This misses camelCase globals but avoids rewriting
|
|
373
|
-
* legitimate property assignments inside functions.
|
|
374
|
-
*/
|
|
375
|
-
/**
|
|
376
|
-
* Known-bad CJS patterns in specific dependencies. Applied via esbuild onLoad.
|
|
377
|
-
* Keep patches minimal and scoped, a regex that's too broad breaks working
|
|
378
|
-
* themes that happen to match.
|
|
379
|
-
*/
|
|
380
|
-
const LEGACY_PATCHES = [
|
|
381
|
-
// swag 0.7.x branches on `typeof window` and, in a browser, assigns to
|
|
382
|
-
// `window.Swag` instead of `module.exports`. After ESM bundling, the theme
|
|
383
|
-
// then gets `undefined` from `require('swag')`. Force the module-exports
|
|
384
|
-
// branch by hiding `window` only for this file.
|
|
385
|
-
{
|
|
386
|
-
filter: /node_modules\/swag\/lib\/swag\.js$/,
|
|
387
|
-
transform: (src) => src.replace(
|
|
388
|
-
/typeof\s+window\s*!==\s*["']undefined["']\s*&&\s*window\s*!==\s*null/g,
|
|
389
|
-
'false',
|
|
390
|
-
),
|
|
391
|
-
},
|
|
392
|
-
// onepage uses implicit globals inside its render function: `splitCourses`,
|
|
393
|
-
// `columnIndex`. Declare them up-front so strict mode doesn't reject them.
|
|
394
|
-
{
|
|
395
|
-
filter: /node_modules\/jsonresume-theme-onepage\/[^/]*\.js$/,
|
|
396
|
-
transform: (src) => {
|
|
397
|
-
// Only insert declarations once at top of render() body
|
|
398
|
-
return src.replace(
|
|
399
|
-
/(function\s+render\s*\([^)]*\)\s*\{)/,
|
|
400
|
-
'$1\n var splitCourses, columnIndex;\n',
|
|
401
|
-
);
|
|
402
|
-
},
|
|
403
|
-
},
|
|
404
|
-
// jsonresume-theme-react tries to assign `window` and `document` which are
|
|
405
|
-
// read-only in the browser (we're already in a browser, no need to
|
|
406
|
-
// polyfill). Guard the assignments so they don't throw. It also uses a
|
|
407
|
-
// pattern not covered by the generic rewrite:
|
|
408
|
-
// const modulePath = path.join(__dirname, 'dist/index.cjs');
|
|
409
|
-
// require(modulePath);
|
|
410
|
-
// Inline the variable so esbuild can statically bundle dist/index.cjs.
|
|
411
|
-
{
|
|
412
|
-
filter: /node_modules\/jsonresume-theme-react\/index\.cjs$/,
|
|
413
|
-
transform: (src) => src
|
|
414
|
-
.replace(
|
|
415
|
-
/global\.window\s*=\s*global\.window\s*\|\|\s*\{[^}]*\};?/g,
|
|
416
|
-
'try { if (typeof window === "undefined") globalThis.window = {}; } catch (_) {}',
|
|
417
|
-
)
|
|
418
|
-
.replace(
|
|
419
|
-
/global\.document\s*=\s*global\.document\s*\|\|\s*\{[\s\S]*?\};/g,
|
|
420
|
-
'try { if (typeof document === "undefined") globalThis.document = { createElement: () => ({}), addEventListener: () => {} }; } catch (_) {}',
|
|
421
|
-
)
|
|
422
|
-
.replace(
|
|
423
|
-
/const\s+modulePath\s*=\s*path\.join\(\s*__dirname\s*,\s*['"]([^'"]+)['"]\s*\);([\s\S]*?)require\(modulePath\)/,
|
|
424
|
-
(_, rel, middle) => `const modulePath = './${rel}';${middle}require('./${rel}')`,
|
|
425
|
-
),
|
|
426
|
-
},
|
|
427
|
-
];
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* esbuild plugin that fixes legacy CJS patterns that break under ESM's
|
|
431
|
-
* implicit strict mode. Two layers:
|
|
432
|
-
* 1. Targeted LEGACY_PATCHES for known bad packages (swag, specific themes).
|
|
433
|
-
* 2. Generic: rewrite top-level `SCREAMING_SNAKE = ...` assignments to
|
|
434
|
-
* `var SCREAMING_SNAKE = ...` so implicit globals work under strict.
|
|
435
|
-
*/
|
|
436
|
-
function legacyThemeGlobalsPlugin() {
|
|
437
|
-
return {
|
|
438
|
-
name: 'legacy-theme-globals',
|
|
439
|
-
setup(build) {
|
|
440
|
-
build.onLoad({ filter: /\.c?js$/ }, async (args) => {
|
|
441
|
-
const { readFile } = await import('node:fs/promises');
|
|
442
|
-
const isThemePkg = /node_modules\/jsonresume-theme-[^/]+\/[^/]*\.js$/.test(args.path);
|
|
443
|
-
const matchingPatches = LEGACY_PATCHES.filter((p) => p.filter.test(args.path));
|
|
444
|
-
if (!isThemePkg && matchingPatches.length === 0) return null;
|
|
445
|
-
|
|
446
|
-
const original = await readFile(args.path, 'utf8');
|
|
447
|
-
let contents = original;
|
|
448
|
-
|
|
449
|
-
// 1. Generic rewrite for theme packages: a line-leading `ident = expr`
|
|
450
|
-
// (where `ident` is not a JS keyword) becomes `var ident = expr`. ESM
|
|
451
|
-
// modules are strict-mode-only and throw `ReferenceError` on implicit
|
|
452
|
-
// global assignments, themes that skip `var` (e.g. onepage's
|
|
453
|
-
// `COURSES_COLUMNS = 3` at module scope, riga's `w = resume.work[i]`
|
|
454
|
-
// inside a for-loop) die at runtime without this.
|
|
455
|
-
//
|
|
456
|
-
// Property-chain assignments (`foo.bar = x`) don't match, the dot
|
|
457
|
-
// breaks the identifier before we reach `=`. The `(?!=)` guard skips
|
|
458
|
-
// `==` comparisons.
|
|
459
|
-
if (isThemePkg) {
|
|
460
|
-
const RESERVED = new Set([
|
|
461
|
-
'var', 'let', 'const', 'function', 'return', 'if', 'else', 'for',
|
|
462
|
-
'while', 'do', 'switch', 'case', 'default', 'break', 'continue',
|
|
463
|
-
'throw', 'try', 'catch', 'finally', 'new', 'delete', 'typeof',
|
|
464
|
-
'void', 'in', 'of', 'instanceof', 'class', 'extends', 'super',
|
|
465
|
-
'this', 'import', 'export', 'from', 'as', 'async', 'await',
|
|
466
|
-
'yield', 'true', 'false', 'null', 'undefined', 'debugger',
|
|
467
|
-
]);
|
|
468
|
-
contents = contents.replace(
|
|
469
|
-
/^([ \t]*)([a-zA-Z_$][\w$]*)\s*=(?!=)/gm,
|
|
470
|
-
(match, indent, name, offset, full) => {
|
|
471
|
-
if (RESERVED.has(name)) return match;
|
|
472
|
-
// Skip expression-context assignments (not a new statement):
|
|
473
|
-
// - `var a, b,\n c = ...` (multi-var continuation)
|
|
474
|
-
// - `foo(\n bar = 1\n)` (argument expression)
|
|
475
|
-
// - `[\n x = 1\n]` (array element assignment)
|
|
476
|
-
// - `a ?\n b = 1 :\n c = 2` (ternary branch)
|
|
477
|
-
// - `a ||\n b = 1` (short-circuit)
|
|
478
|
-
// A new statement context, on the other hand, lives after
|
|
479
|
-
// `{` `}` `;`, in those cases the rewrite is correct.
|
|
480
|
-
const before = full.slice(0, offset).replace(/\s+$/, '');
|
|
481
|
-
const prevChar = before.charAt(before.length - 1);
|
|
482
|
-
if (prevChar === ',' || prevChar === '(' || prevChar === '[' ||
|
|
483
|
-
prevChar === '?' || prevChar === ':') return match;
|
|
484
|
-
const prev2 = before.slice(-2);
|
|
485
|
-
if (prev2 === '||' || prev2 === '&&' || prev2 === '=>') return match;
|
|
486
|
-
// Inside `for (` init clause, rare but valid (var isn't allowed
|
|
487
|
-
// there unless at the very start, and esbuild will handle the
|
|
488
|
-
// already-correct `for (var i = 0; ...)` form).
|
|
489
|
-
const lineStart = full.lastIndexOf('\n', offset - 1) + 1;
|
|
490
|
-
const lineBefore = full.slice(lineStart, offset);
|
|
491
|
-
if (/\bfor\s*\(\s*$/.test(lineBefore)) return match;
|
|
492
|
-
return `${indent}var ${name} =`;
|
|
493
|
-
}
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// 2. Targeted source patches (layered on top of the generic rewrite)
|
|
498
|
-
for (const patch of matchingPatches) contents = patch.transform(contents);
|
|
499
|
-
|
|
500
|
-
// 3. Resolve dynamic requires statically so esbuild can bundle them.
|
|
501
|
-
// Two common patterns in themes:
|
|
502
|
-
// a) const HELPERS = join(__dirname, 'theme/hbs-helpers');
|
|
503
|
-
// require(join(HELPERS, 'file.js'))
|
|
504
|
-
// b) require(path.join(__dirname, 'dist/index.cjs'))
|
|
505
|
-
// esbuild leaves computed requires as runtime calls, which our
|
|
506
|
-
// browser shim rejects. Rewrite to static relative paths.
|
|
507
|
-
if (isThemePkg) {
|
|
508
|
-
// Pattern (b): inline `require((path.)?join(__dirname, 'literal'))`
|
|
509
|
-
contents = contents.replace(
|
|
510
|
-
/require\(\s*(?:\w+\.)?join\(\s*__dirname\s*,\s*['"]([^'"]+)['"]\s*\)\s*\)/g,
|
|
511
|
-
(_, rel) => `require(${JSON.stringify('./' + rel)})`,
|
|
512
|
-
);
|
|
513
|
-
// Pattern (a): resolve via tracked join-constants.
|
|
514
|
-
const joinConsts = {};
|
|
515
|
-
for (const m of contents.matchAll(
|
|
516
|
-
/\b(?:const|var|let)\s+(\w+)\s*=\s*(?:\w+\.)?join\(\s*__dirname\s*,\s*['"]([^'"]+)['"]\s*\)/g
|
|
517
|
-
)) {
|
|
518
|
-
joinConsts[m[1]] = m[2];
|
|
519
|
-
}
|
|
520
|
-
if (Object.keys(joinConsts).length > 0) {
|
|
521
|
-
contents = contents.replace(
|
|
522
|
-
/require\(\s*(?:\w+\.)?join\(\s*(\w+)\s*,\s*['"]([^'"]+)['"]\s*\)\s*\)/g,
|
|
523
|
-
(match, name, file) => {
|
|
524
|
-
const prefix = joinConsts[name];
|
|
525
|
-
if (prefix === undefined) return match;
|
|
526
|
-
const rel = `./${prefix}/${file}`.replace(/\/+/g, '/');
|
|
527
|
-
return `require(${JSON.stringify(rel)})`;
|
|
528
|
-
}
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
return contents !== original ? { contents, loader: 'js' } : null;
|
|
534
|
-
});
|
|
535
|
-
},
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
async function bundleTheme(shortName, packageName, shimsDir) {
|
|
540
|
-
const themeDir = resolve(__dirname, `../node_modules/${packageName}`);
|
|
541
|
-
|
|
542
|
-
// Generate per-theme fs shim with embedded files
|
|
543
|
-
const themeFiles = collectThemeFiles(themeDir);
|
|
544
|
-
writeFileSync(resolve(shimsDir, 'fs.js'), generateThemeFsShim(themeFiles));
|
|
545
|
-
|
|
546
|
-
const entryContent = `
|
|
547
|
-
import * as themeNs from '${packageName}';
|
|
548
|
-
const _t = themeNs.default ?? themeNs;
|
|
549
|
-
export const render = _t.render ?? themeNs.render;
|
|
550
|
-
export const pdfRenderOptions = _t.pdfRenderOptions ?? themeNs.pdfRenderOptions;
|
|
551
|
-
`;
|
|
552
|
-
|
|
553
|
-
const entryFile = resolve(THEMES_DIR, `_entry_${shortName}.js`);
|
|
554
|
-
writeFileSync(entryFile, entryContent);
|
|
555
|
-
|
|
556
|
-
try {
|
|
557
|
-
await build({
|
|
558
|
-
entryPoints: [entryFile],
|
|
559
|
-
bundle: true,
|
|
560
|
-
minify: true,
|
|
561
|
-
format: 'esm',
|
|
562
|
-
target: 'es2022',
|
|
563
|
-
platform: 'browser',
|
|
564
|
-
conditions: ['browser', 'require', 'default'],
|
|
565
|
-
mainFields: ['browser', 'main'],
|
|
566
|
-
outfile: resolve(THEMES_DIR, `${shortName}.js`),
|
|
567
|
-
// Runtime prelude prepended to every bundled theme:
|
|
568
|
-
// - `require.extensions` is a Node CJS API that handlebars-wax uses
|
|
569
|
-
// unconditionally (to intercept .handlebars/.hbs during require).
|
|
570
|
-
// In browser ESM, `require` is undefined, so we stub it.
|
|
571
|
-
// - We deliberately do NOT alias `window = globalThis`. Legacy CJS
|
|
572
|
-
// packages like `swag` branch on `typeof window`, if window is
|
|
573
|
-
// defined they assign `window.Swag = Swag = {}` and skip setting
|
|
574
|
-
// `module.exports`, leaving the theme with `Swag.registerHelpers`
|
|
575
|
-
// undefined. We want the module-exports branch in workers.
|
|
576
|
-
banner: {
|
|
577
|
-
js: [
|
|
578
|
-
// `require.extensions` is read by handlebars-wax at module load; no
|
|
579
|
-
// ESM equivalent, so we provide a stub.
|
|
580
|
-
'var require = globalThis.require || (function(){ var r = function(){ throw new Error("require not available in browser"); }; r.extensions = {}; r.cache = {}; return r; })();',
|
|
581
|
-
'if (!require.extensions) require.extensions = {};',
|
|
582
|
-
// Runtime process shim: `process.env.X` literal access is handled
|
|
583
|
-
// by esbuild's `define`, but dynamic access like `process.cwd()` or
|
|
584
|
-
// `process.stdout.write` needs an actual object at runtime.
|
|
585
|
-
'if (typeof globalThis.process === "undefined") globalThis.process = { env: { NODE_ENV: "production" }, browser: true, platform: "browser", version: "v20.0.0", versions: { node: "20.0.0", v8: "11.3.0" }, stdout: { write: function(){} }, stderr: { write: function(){} }, cwd: function(){ return "/"; }, chdir: function(){}, nextTick: function(fn){ Promise.resolve().then(fn); }, argv: [], pid: 1, title: "browser" };',
|
|
586
|
-
].join(''),
|
|
587
|
-
},
|
|
588
|
-
define: {
|
|
589
|
-
'process.env.NODE_ENV': '"production"',
|
|
590
|
-
'process.env.LANG': '""',
|
|
591
|
-
'global': 'globalThis',
|
|
592
|
-
'__dirname': '"/"',
|
|
593
|
-
'__filename': '"/index.js"',
|
|
594
|
-
'process.browser': 'true',
|
|
595
|
-
'process.platform': '"browser"',
|
|
596
|
-
'process.version': '"v20.0.0"',
|
|
597
|
-
'process.versions': '{}',
|
|
598
|
-
'process.stdout': 'false',
|
|
599
|
-
'process.stderr': 'false',
|
|
600
|
-
},
|
|
601
|
-
alias: {
|
|
602
|
-
'path': resolve(shimsDir, 'path.js'),
|
|
603
|
-
'fs': resolve(shimsDir, 'fs.js'),
|
|
604
|
-
'url': resolve(shimsDir, 'url.js'),
|
|
605
|
-
'assert': resolve(shimsDir, 'assert.js'),
|
|
606
|
-
'buffer': resolve(shimsDir, 'buffer.js'),
|
|
607
|
-
'stream': resolve(shimsDir, 'stream.js'),
|
|
608
|
-
'util': resolve(shimsDir, 'util.js'),
|
|
609
|
-
'events': resolve(shimsDir, 'events.js'),
|
|
610
|
-
'os': resolve(shimsDir, 'os.js'),
|
|
611
|
-
// node: prefixed variants
|
|
612
|
-
'node:fs': resolve(shimsDir, 'fs.js'),
|
|
613
|
-
'node:path': resolve(shimsDir, 'path.js'),
|
|
614
|
-
'node:url': resolve(shimsDir, 'url.js'),
|
|
615
|
-
'node:crypto': resolve(shimsDir, 'crypto.js'),
|
|
616
|
-
'node:buffer': resolve(shimsDir, 'buffer.js'),
|
|
617
|
-
'node:stream': resolve(shimsDir, 'stream.js'),
|
|
618
|
-
'node:util': resolve(shimsDir, 'util.js'),
|
|
619
|
-
'node:events': resolve(shimsDir, 'events.js'),
|
|
620
|
-
'node:os': resolve(shimsDir, 'os.js'),
|
|
621
|
-
'node:assert': resolve(shimsDir, 'assert.js'),
|
|
622
|
-
// Glob shims for themes that use glob-based partial loading
|
|
623
|
-
'glob': resolve(shimsDir, 'glob.js'),
|
|
624
|
-
'globby': resolve(shimsDir, 'globby.js'),
|
|
625
|
-
'require-glob': resolve(shimsDir, 'require-glob.js'),
|
|
626
|
-
'fast-glob': resolve(shimsDir, 'globby.js'),
|
|
627
|
-
},
|
|
628
|
-
plugins: [legacyThemeGlobalsPlugin()],
|
|
629
|
-
logLevel: 'silent',
|
|
630
|
-
});
|
|
631
|
-
|
|
632
|
-
execSync(`rm -f "${entryFile}"`);
|
|
633
|
-
return true;
|
|
634
|
-
} catch (e) {
|
|
635
|
-
console.log(` (bundle error: ${e.message})`);
|
|
636
|
-
execSync(`rm -f "${entryFile}"`);
|
|
637
|
-
return false;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// ── Shim file writers ────────────────────────────────────────────────
|
|
642
|
-
|
|
643
|
-
function writeShims(shimsDir) {
|
|
644
|
-
writeFileSync(resolve(shimsDir, 'path.js'), [
|
|
645
|
-
'export var join = function() { return [].slice.call(arguments).join("/"); };',
|
|
646
|
-
'export var resolve = function() { return [].slice.call(arguments).join("/"); };',
|
|
647
|
-
'export var dirname = function(p) { return p.split("/").slice(0, -1).join("/"); };',
|
|
648
|
-
'export var basename = function(p, ext) { var b = p.split("/").pop() || ""; return ext && b.endsWith(ext) ? b.slice(0, -ext.length) : b; };',
|
|
649
|
-
'export var extname = function(p) { var m = p.match(/\\.[^.]+$/); return m ? m[0] : ""; };',
|
|
650
|
-
'export var sep = "/";',
|
|
651
|
-
'export var isAbsolute = function(p) { return p.charAt(0) === "/"; };',
|
|
652
|
-
'export var normalize = function(p) { return p; };',
|
|
653
|
-
'export var relative = function(from, to) { return to; };',
|
|
654
|
-
'export var parse = function(p) { return { root: "", dir: dirname(p), base: basename(p), ext: extname(p), name: basename(p, extname(p)) }; };',
|
|
655
|
-
'export default { join: join, resolve: resolve, dirname: dirname, basename: basename, extname: extname, sep: sep, isAbsolute: isAbsolute, normalize: normalize, relative: relative, parse: parse };',
|
|
656
|
-
].join('\n'));
|
|
657
|
-
|
|
658
|
-
// fs shim is generated per-theme in bundleTheme()
|
|
659
|
-
|
|
660
|
-
writeFileSync(resolve(shimsDir, 'url.js'), [
|
|
661
|
-
'export var URL = globalThis.URL;',
|
|
662
|
-
'export var URLSearchParams = globalThis.URLSearchParams;',
|
|
663
|
-
'export var fileURLToPath = function(u) { return u.replace(/^file:\\/\\//, ""); };',
|
|
664
|
-
'export var pathToFileURL = function(p) { return new globalThis.URL("file://" + p); };',
|
|
665
|
-
'export var format = function(u) { return typeof u === "string" ? u : u.href; };',
|
|
666
|
-
'export var parse = function(u) { return new globalThis.URL(u); };',
|
|
667
|
-
'export default { URL: URL, URLSearchParams: URLSearchParams, fileURLToPath: fileURLToPath, pathToFileURL: pathToFileURL, format: format, parse: parse };',
|
|
668
|
-
].join('\n'));
|
|
669
|
-
|
|
670
|
-
writeFileSync(resolve(shimsDir, 'crypto.js'), [
|
|
671
|
-
'export var createHash = function() { return { update: function() { return this; }, digest: function() { return ""; } }; };',
|
|
672
|
-
'export var randomBytes = function(n) { return new Uint8Array(n); };',
|
|
673
|
-
'export var createHmac = function() { return { update: function() { return this; }, digest: function() { return ""; } }; };',
|
|
674
|
-
'export default { createHash: createHash, randomBytes: randomBytes, createHmac: createHmac };',
|
|
675
|
-
].join('\n'));
|
|
676
|
-
|
|
677
|
-
writeFileSync(resolve(shimsDir, 'assert.js'), [
|
|
678
|
-
'var assert = function(v, msg) { if (!v) throw new Error(msg || "Assertion failed"); };',
|
|
679
|
-
'assert.ok = assert;',
|
|
680
|
-
'assert.strictEqual = function(a, b) { if (a !== b) throw new Error("Not equal"); };',
|
|
681
|
-
'assert.deepStrictEqual = function() {};',
|
|
682
|
-
'assert.fail = function(msg) { throw new Error(msg); };',
|
|
683
|
-
'export default assert;',
|
|
684
|
-
'export var ok = assert;',
|
|
685
|
-
'export var strictEqual = assert.strictEqual;',
|
|
686
|
-
].join('\n'));
|
|
687
|
-
|
|
688
|
-
writeFileSync(resolve(shimsDir, 'buffer.js'), [
|
|
689
|
-
'var _enc = typeof TextEncoder !== "undefined" ? new TextEncoder() : null;',
|
|
690
|
-
'var _dec = typeof TextDecoder !== "undefined" ? new TextDecoder() : null;',
|
|
691
|
-
'',
|
|
692
|
-
'function BufferShim(arg) {',
|
|
693
|
-
' if (typeof arg === "number") return new Uint8Array(arg);',
|
|
694
|
-
' if (arg instanceof Uint8Array) return arg;',
|
|
695
|
-
' if (arg instanceof ArrayBuffer) return new Uint8Array(arg);',
|
|
696
|
-
' return new Uint8Array(0);',
|
|
697
|
-
'}',
|
|
698
|
-
'',
|
|
699
|
-
'BufferShim.from = function(data, encoding) {',
|
|
700
|
-
' if (typeof data === "string") {',
|
|
701
|
-
' if (encoding === "base64") {',
|
|
702
|
-
' var bin = atob(data);',
|
|
703
|
-
' var bytes = new Uint8Array(bin.length);',
|
|
704
|
-
' for (var i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);',
|
|
705
|
-
' return bytes;',
|
|
706
|
-
' }',
|
|
707
|
-
' return _enc ? _enc.encode(data) : new Uint8Array(0);',
|
|
708
|
-
' }',
|
|
709
|
-
' if (Array.isArray(data)) return new Uint8Array(data);',
|
|
710
|
-
' if (data instanceof ArrayBuffer) return new Uint8Array(data);',
|
|
711
|
-
' if (data instanceof Uint8Array) return new Uint8Array(data);',
|
|
712
|
-
' return new Uint8Array(0);',
|
|
713
|
-
'};',
|
|
714
|
-
'BufferShim.alloc = function(size) { return new Uint8Array(size); };',
|
|
715
|
-
'BufferShim.allocUnsafe = function(size) { return new Uint8Array(size); };',
|
|
716
|
-
'BufferShim.isBuffer = function(obj) { return obj instanceof Uint8Array; };',
|
|
717
|
-
'BufferShim.isEncoding = function() { return true; };',
|
|
718
|
-
'BufferShim.byteLength = function(str) { return _enc ? _enc.encode(str).length : str.length; };',
|
|
719
|
-
'BufferShim.concat = function(list) {',
|
|
720
|
-
' var total = 0;',
|
|
721
|
-
' for (var i = 0; i < list.length; i++) total += list[i].length;',
|
|
722
|
-
' var result = new Uint8Array(total);',
|
|
723
|
-
' var offset = 0;',
|
|
724
|
-
' for (var j = 0; j < list.length; j++) { result.set(list[j], offset); offset += list[j].length; }',
|
|
725
|
-
' return result;',
|
|
726
|
-
'};',
|
|
727
|
-
'',
|
|
728
|
-
'export var Buffer = BufferShim;',
|
|
729
|
-
'export var SlowBuffer = BufferShim;',
|
|
730
|
-
'export var INSPECT_MAX_BYTES = 50;',
|
|
731
|
-
'export var kMaxLength = 2147483647;',
|
|
732
|
-
'export default { Buffer: BufferShim, SlowBuffer: BufferShim, INSPECT_MAX_BYTES: 50, kMaxLength: 2147483647 };',
|
|
733
|
-
].join('\n'));
|
|
734
|
-
|
|
735
|
-
writeFileSync(resolve(shimsDir, 'stream.js'), [
|
|
736
|
-
'function EventBase() { this._e = {}; }',
|
|
737
|
-
'EventBase.prototype.on = function(ev, fn) { (this._e[ev] = this._e[ev] || []).push(fn); return this; };',
|
|
738
|
-
'EventBase.prototype.addListener = EventBase.prototype.on;',
|
|
739
|
-
'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); };',
|
|
740
|
-
'EventBase.prototype.emit = function(ev) { var args = [].slice.call(arguments, 1); (this._e[ev] || []).forEach(function(fn) { fn.apply(null, args); }); return true; };',
|
|
741
|
-
'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; };',
|
|
742
|
-
'EventBase.prototype.off = EventBase.prototype.removeListener;',
|
|
743
|
-
'EventBase.prototype.removeAllListeners = function(ev) { if (ev) delete this._e[ev]; else this._e = {}; return this; };',
|
|
744
|
-
'EventBase.prototype.setMaxListeners = function() { return this; };',
|
|
745
|
-
'EventBase.prototype.listeners = function(ev) { return this._e[ev] || []; };',
|
|
746
|
-
'',
|
|
747
|
-
'function Readable() { EventBase.call(this); } Readable.prototype = Object.create(EventBase.prototype);',
|
|
748
|
-
'Readable.prototype.read = function() { return null; };',
|
|
749
|
-
'Readable.prototype.pipe = function(d) { return d; };',
|
|
750
|
-
'Readable.prototype.unpipe = function() { return this; };',
|
|
751
|
-
'Readable.prototype.destroy = function() { return this; };',
|
|
752
|
-
'',
|
|
753
|
-
'function Writable() { EventBase.call(this); } Writable.prototype = Object.create(EventBase.prototype);',
|
|
754
|
-
'Writable.prototype.write = function() { return true; };',
|
|
755
|
-
'Writable.prototype.end = function() { return this; };',
|
|
756
|
-
'Writable.prototype.destroy = function() { return this; };',
|
|
757
|
-
'',
|
|
758
|
-
'function Transform() { EventBase.call(this); } Transform.prototype = Object.create(EventBase.prototype);',
|
|
759
|
-
'Transform.prototype.write = function() { return true; };',
|
|
760
|
-
'Transform.prototype.end = function() { return this; };',
|
|
761
|
-
'Transform.prototype.pipe = function(d) { return d; };',
|
|
762
|
-
'Transform.prototype.destroy = function() { return this; };',
|
|
763
|
-
'',
|
|
764
|
-
'function PassThrough() { Transform.call(this); } PassThrough.prototype = Object.create(Transform.prototype);',
|
|
765
|
-
'function Duplex() { EventBase.call(this); } Duplex.prototype = Object.create(EventBase.prototype);',
|
|
766
|
-
'Duplex.prototype.write = function() { return true; };',
|
|
767
|
-
'Duplex.prototype.end = function() { return this; };',
|
|
768
|
-
'Duplex.prototype.read = function() { return null; };',
|
|
769
|
-
'Duplex.prototype.pipe = function(d) { return d; };',
|
|
770
|
-
'Duplex.prototype.destroy = function() { return this; };',
|
|
771
|
-
'',
|
|
772
|
-
'function Stream() { EventBase.call(this); } Stream.prototype = Object.create(EventBase.prototype);',
|
|
773
|
-
'Stream.prototype.pipe = function(d) { return d; };',
|
|
774
|
-
'Stream.Readable = Readable; Stream.Writable = Writable; Stream.Transform = Transform;',
|
|
775
|
-
'Stream.PassThrough = PassThrough; Stream.Duplex = Duplex; Stream.Stream = Stream;',
|
|
776
|
-
'',
|
|
777
|
-
'export { Readable, Writable, Transform, PassThrough, Duplex, Stream };',
|
|
778
|
-
'export default Stream;',
|
|
779
|
-
].join('\n'));
|
|
780
|
-
|
|
781
|
-
writeFileSync(resolve(shimsDir, 'util.js'), [
|
|
782
|
-
'export var inherits = function(ctor, superCtor) { ctor.super_ = superCtor; Object.setPrototypeOf(ctor.prototype, superCtor.prototype); };',
|
|
783
|
-
'export var deprecate = function(fn) { return fn; };',
|
|
784
|
-
'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); }); }; };',
|
|
785
|
-
'export var inspect = function(obj) { try { return JSON.stringify(obj); } catch(e) { return String(obj); } };',
|
|
786
|
-
'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; }); };',
|
|
787
|
-
'export var debuglog = function() { return function() {}; };',
|
|
788
|
-
'export var isArray = Array.isArray;',
|
|
789
|
-
'export var isBoolean = function(v) { return typeof v === "boolean"; };',
|
|
790
|
-
'export var isNull = function(v) { return v === null; };',
|
|
791
|
-
'export var isNumber = function(v) { return typeof v === "number"; };',
|
|
792
|
-
'export var isString = function(v) { return typeof v === "string"; };',
|
|
793
|
-
'export var isUndefined = function(v) { return v === undefined; };',
|
|
794
|
-
'export var isObject = function(v) { return typeof v === "object" && v !== null; };',
|
|
795
|
-
'export var isFunction = function(v) { return typeof v === "function"; };',
|
|
796
|
-
'export var isRegExp = function(v) { return v instanceof RegExp; };',
|
|
797
|
-
'export var isDate = function(v) { return v instanceof Date; };',
|
|
798
|
-
'export var isError = function(v) { return v instanceof Error; };',
|
|
799
|
-
'export var isPrimitive = function(v) { return v === null || typeof v !== "object" && typeof v !== "function"; };',
|
|
800
|
-
'export var TextEncoder = globalThis.TextEncoder;',
|
|
801
|
-
'export var TextDecoder = globalThis.TextDecoder;',
|
|
802
|
-
'export var types = { isAnyArrayBuffer: function() { return false; }, isTypedArray: function(v) { return ArrayBuffer.isView(v); } };',
|
|
803
|
-
'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 };',
|
|
804
|
-
].join('\n'));
|
|
805
|
-
|
|
806
|
-
writeFileSync(resolve(shimsDir, 'events.js'), [
|
|
807
|
-
'function EventEmitter() { this._events = {}; this._maxListeners = 10; }',
|
|
808
|
-
'EventEmitter.prototype.on = function(ev, fn) { (this._events[ev] = this._events[ev] || []).push(fn); return this; };',
|
|
809
|
-
'EventEmitter.prototype.addListener = EventEmitter.prototype.on;',
|
|
810
|
-
'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); };',
|
|
811
|
-
'EventEmitter.prototype.emit = function(ev) { var args = [].slice.call(arguments, 1); (this._events[ev] || []).forEach(function(fn) { fn.apply(null, args); }); return true; };',
|
|
812
|
-
'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; };',
|
|
813
|
-
'EventEmitter.prototype.off = EventEmitter.prototype.removeListener;',
|
|
814
|
-
'EventEmitter.prototype.removeAllListeners = function(ev) { if (ev) delete this._events[ev]; else this._events = {}; return this; };',
|
|
815
|
-
'EventEmitter.prototype.setMaxListeners = function(n) { this._maxListeners = n; return this; };',
|
|
816
|
-
'EventEmitter.prototype.getMaxListeners = function() { return this._maxListeners; };',
|
|
817
|
-
'EventEmitter.prototype.listeners = function(ev) { return this._events[ev] || []; };',
|
|
818
|
-
'EventEmitter.prototype.listenerCount = function(ev) { return (this._events[ev] || []).length; };',
|
|
819
|
-
'EventEmitter.prototype.prependListener = EventEmitter.prototype.on;',
|
|
820
|
-
'EventEmitter.prototype.prependOnceListener = EventEmitter.prototype.once;',
|
|
821
|
-
'EventEmitter.prototype.eventNames = function() { return Object.keys(this._events); };',
|
|
822
|
-
'EventEmitter.EventEmitter = EventEmitter;',
|
|
823
|
-
'EventEmitter.defaultMaxListeners = 10;',
|
|
824
|
-
'export { EventEmitter };',
|
|
825
|
-
'export default EventEmitter;',
|
|
826
|
-
].join('\n'));
|
|
827
|
-
|
|
828
|
-
writeFileSync(resolve(shimsDir, 'os.js'), [
|
|
829
|
-
'export var platform = function() { return "browser"; };',
|
|
830
|
-
'export var tmpdir = function() { return "/tmp"; };',
|
|
831
|
-
'export var homedir = function() { return "/"; };',
|
|
832
|
-
'export var hostname = function() { return "localhost"; };',
|
|
833
|
-
'export var type = function() { return "Browser"; };',
|
|
834
|
-
'export var arch = function() { return "wasm"; };',
|
|
835
|
-
'export var release = function() { return "0.0.0"; };',
|
|
836
|
-
'export var EOL = "\\n";',
|
|
837
|
-
'export var endianness = function() { return "LE"; };',
|
|
838
|
-
'export var cpus = function() { return []; };',
|
|
839
|
-
'export var totalmem = function() { return 0; };',
|
|
840
|
-
'export var freemem = function() { return 0; };',
|
|
841
|
-
'export var networkInterfaces = function() { return {}; };',
|
|
842
|
-
'export var userInfo = function() { return { username: "browser", uid: 0, gid: 0, shell: "", homedir: "/" }; };',
|
|
843
|
-
'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 };',
|
|
844
|
-
].join('\n'));
|
|
845
|
-
|
|
846
|
-
// glob/globby/require-glob shims, these use the fs shim's embedded file list
|
|
847
|
-
writeFileSync(resolve(shimsDir, 'glob.js'), [
|
|
848
|
-
'import { readdirSync, readFileSync, existsSync } from "fs";',
|
|
849
|
-
'',
|
|
850
|
-
'// Minimal glob pattern matching against embedded file list',
|
|
851
|
-
'function collapseSlashes(p) { return String(p).replace(/\\/+/g, "/"); }',
|
|
852
|
-
'function minimatch(filepath, pattern) {',
|
|
853
|
-
' filepath = collapseSlashes(filepath);',
|
|
854
|
-
' pattern = collapseSlashes(pattern);',
|
|
855
|
-
' // Step 1: expand `{a,b,c}` alternatives into placeholders BEFORE',
|
|
856
|
-
' // escaping, otherwise the escape step turns `{hbs,js}` into',
|
|
857
|
-
' // `\\{hbs,js\\}` and the alternation regex no longer sees braces.',
|
|
858
|
-
' var alts = [];',
|
|
859
|
-
' pattern = pattern.replace(/\\{([^}]+)\\}/g, function(_, list) {',
|
|
860
|
-
' alts.push("(" + list.split(",").join("|") + ")");',
|
|
861
|
-
' return "___ALT" + (alts.length - 1) + "___";',
|
|
862
|
-
' });',
|
|
863
|
-
' // Step 2: handle globstars. `**/` matches zero or more full path',
|
|
864
|
-
' // segments (so `a/**/b` matches `a/b`, `a/x/b`, `a/x/y/b`). Lone `**`',
|
|
865
|
-
' // (no trailing slash) matches any chars including `/`.',
|
|
866
|
-
' pattern = pattern.replace(/\\*\\*\\//g, "___GLOBSTAR_SEG___");',
|
|
867
|
-
' pattern = pattern.replace(/\\*\\*/g, "___GLOBSTAR___");',
|
|
868
|
-
' // Step 3: escape remaining regex metachars (but not *, ?, /)',
|
|
869
|
-
' var re = pattern',
|
|
870
|
-
' .replace(/[.+^$()|[\\]\\\\]/g, "\\\\$&")',
|
|
871
|
-
' .replace(/\\*/g, "[^/]*")',
|
|
872
|
-
' .replace(/\\?/g, "[^/]");',
|
|
873
|
-
' re = re.replace(/___GLOBSTAR_SEG___/g, "(?:.*/)?");',
|
|
874
|
-
' re = re.replace(/___GLOBSTAR___/g, ".*");',
|
|
875
|
-
' // Step 4: restore alternations',
|
|
876
|
-
' re = re.replace(/___ALT(\\d+)___/g, function(_, i) { return alts[Number(i)]; });',
|
|
877
|
-
' try { return new RegExp("^" + re + "$").test(filepath); }',
|
|
878
|
-
' catch (e) { return false; }',
|
|
879
|
-
'}',
|
|
880
|
-
'',
|
|
881
|
-
'function joinPath(dir, name) {',
|
|
882
|
-
' if (!dir || dir === "/") return "/" + name;',
|
|
883
|
-
' if (dir.charAt(dir.length - 1) === "/") return dir + name;',
|
|
884
|
-
' return dir + "/" + name;',
|
|
885
|
-
'}',
|
|
886
|
-
'function getAllPaths(dir) {',
|
|
887
|
-
' var results = [];',
|
|
888
|
-
' try {',
|
|
889
|
-
' var entries = readdirSync(dir);',
|
|
890
|
-
' for (var i = 0; i < entries.length; i++) {',
|
|
891
|
-
' var full = joinPath(dir, entries[i]);',
|
|
892
|
-
' results.push(full);',
|
|
893
|
-
' var sub = getAllPaths(full);',
|
|
894
|
-
' for (var j = 0; j < sub.length; j++) results.push(sub[j]);',
|
|
895
|
-
' }',
|
|
896
|
-
' } catch(e) {}',
|
|
897
|
-
' return results;',
|
|
898
|
-
'}',
|
|
899
|
-
'',
|
|
900
|
-
'export function sync(pattern, opts) {',
|
|
901
|
-
' var cwd = (opts && opts.cwd) || "/";',
|
|
902
|
-
' var allPaths = getAllPaths(cwd);',
|
|
903
|
-
' return allPaths.filter(function(p) { return minimatch(p, pattern); });',
|
|
904
|
-
'}',
|
|
905
|
-
'',
|
|
906
|
-
'export function globSync(pattern, opts) { return sync(pattern, opts); }',
|
|
907
|
-
'',
|
|
908
|
-
'export default function glob(pattern, opts, cb) {',
|
|
909
|
-
' if (typeof opts === "function") { cb = opts; opts = {}; }',
|
|
910
|
-
' try { var r = sync(pattern, opts); if (cb) cb(null, r); return Promise.resolve(r); }',
|
|
911
|
-
' catch(e) { if (cb) cb(e); return Promise.reject(e); }',
|
|
912
|
-
'};',
|
|
913
|
-
'glob.sync = sync;',
|
|
914
|
-
'glob.globSync = sync;',
|
|
915
|
-
].join('\n'));
|
|
916
|
-
|
|
917
|
-
writeFileSync(resolve(shimsDir, 'globby.js'), [
|
|
918
|
-
'import { sync as globSync } from "glob";',
|
|
919
|
-
'export function globby(patterns, opts) {',
|
|
920
|
-
' var pats = Array.isArray(patterns) ? patterns : [patterns];',
|
|
921
|
-
' var results = [];',
|
|
922
|
-
' for (var i = 0; i < pats.length; i++) {',
|
|
923
|
-
' var matches = globSync(pats[i], opts);',
|
|
924
|
-
' for (var j = 0; j < matches.length; j++) {',
|
|
925
|
-
' if (results.indexOf(matches[j]) === -1) results.push(matches[j]);',
|
|
926
|
-
' }',
|
|
927
|
-
' }',
|
|
928
|
-
' return Promise.resolve(results);',
|
|
929
|
-
'}',
|
|
930
|
-
'export function globbySync(patterns, opts) {',
|
|
931
|
-
' var pats = Array.isArray(patterns) ? patterns : [patterns];',
|
|
932
|
-
' var results = [];',
|
|
933
|
-
' for (var i = 0; i < pats.length; i++) {',
|
|
934
|
-
' var matches = globSync(pats[i], opts);',
|
|
935
|
-
' for (var j = 0; j < matches.length; j++) {',
|
|
936
|
-
' if (results.indexOf(matches[j]) === -1) results.push(matches[j]);',
|
|
937
|
-
' }',
|
|
938
|
-
' }',
|
|
939
|
-
' return results;',
|
|
940
|
-
'}',
|
|
941
|
-
'export default globby;',
|
|
942
|
-
].join('\n'));
|
|
943
|
-
|
|
944
|
-
// require-glob: export as a function with a `.sync` PROPERTY. Consumers do
|
|
945
|
-
// `var requireGlob = require('require-glob'); requireGlob.sync(pattern)`,
|
|
946
|
-
// the property must survive esbuild's CJS/ESM interop. Default-only export
|
|
947
|
-
// would strip `.sync`, so we use a proxy-like object pattern: default is
|
|
948
|
-
// the function, and the function has `.sync` set before export.
|
|
949
|
-
writeFileSync(resolve(shimsDir, 'require-glob.js'), [
|
|
950
|
-
'import { sync as globSync } from "glob";',
|
|
951
|
-
'import { readFileSync } from "fs";',
|
|
952
|
-
'',
|
|
953
|
-
'function requireGlob(pattern, opts) {',
|
|
954
|
-
' var cwd = (opts && opts.cwd) || "/";',
|
|
955
|
-
' var matches = globSync(pattern, { cwd: cwd });',
|
|
956
|
-
' var result = {};',
|
|
957
|
-
' for (var i = 0; i < matches.length; i++) {',
|
|
958
|
-
' var name = matches[i].replace(/.*\\//, "").replace(/\\.[^.]+$/, "");',
|
|
959
|
-
' try {',
|
|
960
|
-
' var content = readFileSync(matches[i], "utf-8");',
|
|
961
|
-
' result[name] = content;',
|
|
962
|
-
' } catch(e) {}',
|
|
963
|
-
' }',
|
|
964
|
-
' return result;',
|
|
965
|
-
'}',
|
|
966
|
-
'requireGlob.sync = requireGlob;',
|
|
967
|
-
'// Named export preserves `.sync` through esbuild\'s CJS interop,',
|
|
968
|
-
'// consumers that `require(\'require-glob\')` receive the function itself.',
|
|
969
|
-
'export { requireGlob as default };',
|
|
970
|
-
'export { requireGlob };',
|
|
971
|
-
'export var sync = requireGlob;',
|
|
972
|
-
].join('\n'));
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
// ── Main ─────────────────────────────────────────────────────────────
|
|
976
|
-
|
|
977
|
-
async function main() {
|
|
978
|
-
const args = process.argv.slice(2);
|
|
979
|
-
const doAll = args.includes('--all');
|
|
980
|
-
const themesArg = args.find(a => a.startsWith('--themes='));
|
|
981
|
-
const specificThemes = themesArg ? themesArg.split('=')[1].split(',') : null;
|
|
982
|
-
|
|
983
|
-
mkdirSync(THEMES_DIR, { recursive: true });
|
|
984
|
-
|
|
985
|
-
// Create shims directory and write all shims
|
|
986
|
-
const shimsDir = resolve(__dirname, 'shims');
|
|
987
|
-
mkdirSync(shimsDir, { recursive: true });
|
|
988
|
-
writeShims(shimsDir);
|
|
989
|
-
|
|
990
|
-
let themes;
|
|
991
|
-
if (specificThemes) {
|
|
992
|
-
themes = specificThemes.map(name => ({
|
|
993
|
-
name,
|
|
994
|
-
packageName: `jsonresume-theme-${name}`,
|
|
995
|
-
description: '',
|
|
996
|
-
version: '',
|
|
997
|
-
}));
|
|
998
|
-
} else if (doAll) {
|
|
999
|
-
themes = await discoverThemes();
|
|
1000
|
-
} else {
|
|
1001
|
-
themes = POPULAR_THEMES.map(name => ({
|
|
1002
|
-
name,
|
|
1003
|
-
packageName: `jsonresume-theme-${name}`,
|
|
1004
|
-
description: '',
|
|
1005
|
-
version: '',
|
|
1006
|
-
}));
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
console.log(`📦 Bundling ${themes.length} themes...\n`);
|
|
1010
|
-
|
|
1011
|
-
const manifest = [];
|
|
1012
|
-
|
|
1013
|
-
for (const theme of themes) {
|
|
1014
|
-
process.stdout.write(` ${theme.name}... `);
|
|
1015
|
-
|
|
1016
|
-
// Install the theme
|
|
1017
|
-
try {
|
|
1018
|
-
execSync(`npm install --no-save --prefer-offline ${theme.packageName} 2>/dev/null`, {
|
|
1019
|
-
cwd: resolve(__dirname, '..'),
|
|
1020
|
-
timeout: 60000,
|
|
1021
|
-
});
|
|
1022
|
-
} catch {
|
|
1023
|
-
console.log('❌ install failed');
|
|
1024
|
-
continue;
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
// Read package metadata
|
|
1028
|
-
try {
|
|
1029
|
-
const pkgPath = resolve(__dirname, `../node_modules/${theme.packageName}/package.json`);
|
|
1030
|
-
if (existsSync(pkgPath)) {
|
|
1031
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
1032
|
-
theme.description = theme.description || pkg.description || '';
|
|
1033
|
-
theme.version = theme.version || pkg.version || '';
|
|
1034
|
-
}
|
|
1035
|
-
} catch {}
|
|
1036
|
-
|
|
1037
|
-
// Step 1: Generate snapshot (pre-render in Node.js)
|
|
1038
|
-
let hasSnapshot = false;
|
|
1039
|
-
let hasCss = false;
|
|
1040
|
-
const snapshotResult = generateSnapshot(theme.name, theme.packageName);
|
|
1041
|
-
if (snapshotResult && typeof snapshotResult.then === 'function') {
|
|
1042
|
-
const html = await snapshotResult;
|
|
1043
|
-
hasSnapshot = !!html;
|
|
1044
|
-
hasCss = existsSync(resolve(THEMES_DIR, `${theme.name}.css`));
|
|
1045
|
-
} else {
|
|
1046
|
-
hasSnapshot = !!snapshotResult;
|
|
1047
|
-
hasCss = existsSync(resolve(THEMES_DIR, `${theme.name}.css`));
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
// Step 2: Bundle for browser
|
|
1051
|
-
const success = await bundleTheme(theme.name, theme.packageName, shimsDir);
|
|
1052
|
-
|
|
1053
|
-
if (success) {
|
|
1054
|
-
const outFile = resolve(THEMES_DIR, `${theme.name}.js`);
|
|
1055
|
-
const stat = existsSync(outFile) ? readFileSync(outFile).length : 0;
|
|
1056
|
-
const cssFile = resolve(THEMES_DIR, `${theme.name}.css`);
|
|
1057
|
-
const cssSize = hasCss && existsSync(cssFile) ? readFileSync(cssFile).length : 0;
|
|
1058
|
-
|
|
1059
|
-
manifest.push({
|
|
1060
|
-
name: theme.name,
|
|
1061
|
-
displayName: theme.name.charAt(0).toUpperCase() + theme.name.slice(1).replace(/-/g, ' '),
|
|
1062
|
-
description: theme.description,
|
|
1063
|
-
version: theme.version || '',
|
|
1064
|
-
browserCompatible: true,
|
|
1065
|
-
hasSnapshot,
|
|
1066
|
-
hasCss,
|
|
1067
|
-
fileSize: stat,
|
|
1068
|
-
cssSize,
|
|
1069
|
-
});
|
|
1070
|
-
|
|
1071
|
-
const parts = [`✅ (${(stat / 1024).toFixed(0)}KB`];
|
|
1072
|
-
if (hasCss) parts.push(`css:${(cssSize / 1024).toFixed(0)}KB`);
|
|
1073
|
-
if (hasSnapshot) parts.push('snapshot');
|
|
1074
|
-
console.log(parts.join(', ') + ')');
|
|
1075
|
-
} else {
|
|
1076
|
-
manifest.push({
|
|
1077
|
-
name: theme.name,
|
|
1078
|
-
displayName: theme.name.charAt(0).toUpperCase() + theme.name.slice(1).replace(/-/g, ' '),
|
|
1079
|
-
description: theme.description,
|
|
1080
|
-
version: theme.version || '',
|
|
1081
|
-
browserCompatible: false,
|
|
1082
|
-
hasSnapshot,
|
|
1083
|
-
hasCss: false,
|
|
1084
|
-
fileSize: 0,
|
|
1085
|
-
cssSize: 0,
|
|
1086
|
-
});
|
|
1087
|
-
const note = hasSnapshot ? '⚠️ browser incompatible (has snapshot)' : '⚠️ browser incompatible';
|
|
1088
|
-
console.log(note);
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
// Write manifest
|
|
1093
|
-
writeFileSync(resolve(THEMES_DIR, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
1094
|
-
|
|
1095
|
-
const successful = manifest.filter(t => t.browserCompatible).length;
|
|
1096
|
-
const snapshots = manifest.filter(t => t.hasSnapshot).length;
|
|
1097
|
-
console.log(`\n✅ Done! ${successful}/${manifest.length} themes bundled, ${snapshots} snapshots generated`);
|
|
1098
|
-
console.log(`📁 Output: docs/themes/`);
|
|
1099
|
-
|
|
1100
|
-
// Clean up shims
|
|
1101
|
-
execSync(`rm -rf "${shimsDir}"`);
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
main().catch(console.error);
|