resuml 1.5.2 → 1.6.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 +1 -1
- package/scripts/bundle-themes.js +259 -95
package/package.json
CHANGED
package/scripts/bundle-themes.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Theme Bundling Script
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Bundles jsonresume-theme packages for the browser with:
|
|
5
|
+
* - CSS extraction (separate .css files)
|
|
6
|
+
* - Build-time snapshots (pre-rendered HTML for instant preview)
|
|
7
|
+
* - Per-theme fs shim with embedded assets
|
|
6
8
|
*
|
|
7
9
|
* Output:
|
|
8
|
-
* docs/themes/{name}.js
|
|
9
|
-
* docs/themes/
|
|
10
|
+
* docs/themes/{name}.js — bundled render module
|
|
11
|
+
* docs/themes/{name}.css — extracted CSS (if any)
|
|
12
|
+
* docs/themes/{name}.snapshot.html— pre-rendered HTML with sample data
|
|
13
|
+
* docs/themes/manifest.json — theme metadata registry
|
|
10
14
|
*
|
|
11
15
|
* Usage:
|
|
12
16
|
* node scripts/bundle-themes.js # Bundle popular themes
|
|
@@ -19,8 +23,10 @@ import { resolve, dirname, join } from 'path';
|
|
|
19
23
|
import { fileURLToPath } from 'url';
|
|
20
24
|
import { execSync } from 'child_process';
|
|
21
25
|
import { mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync } from 'fs';
|
|
26
|
+
import { createRequire } from 'module';
|
|
22
27
|
|
|
23
28
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const require = createRequire(import.meta.url);
|
|
24
30
|
const THEMES_DIR = resolve(__dirname, '../docs/themes');
|
|
25
31
|
|
|
26
32
|
// Popular themes that are known to work in the browser
|
|
@@ -43,45 +49,83 @@ const POPULAR_THEMES = [
|
|
|
43
49
|
'compact',
|
|
44
50
|
];
|
|
45
51
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
52
|
+
// Sample resume for generating snapshots
|
|
53
|
+
const SAMPLE_RESUME = {
|
|
54
|
+
basics: {
|
|
55
|
+
name: 'Jane Smith',
|
|
56
|
+
label: 'Senior Software Engineer',
|
|
57
|
+
email: 'jane@example.com',
|
|
58
|
+
phone: '+1-555-987-6543',
|
|
59
|
+
url: 'https://janesmith.dev',
|
|
60
|
+
summary: 'Full-stack engineer with 8+ years of experience building scalable web applications. Passionate about clean architecture, performance optimization, and mentoring teams.',
|
|
61
|
+
location: { city: 'San Francisco', countryCode: 'US', region: 'California' },
|
|
62
|
+
profiles: [
|
|
63
|
+
{ network: 'LinkedIn', username: 'janesmith', url: 'https://linkedin.com/in/janesmith' },
|
|
64
|
+
{ network: 'GitHub', username: 'janesmith', url: 'https://github.com/janesmith' },
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
work: [
|
|
68
|
+
{
|
|
69
|
+
name: 'Tech Corp',
|
|
70
|
+
position: 'Senior Software Engineer',
|
|
71
|
+
url: 'https://techcorp.com',
|
|
72
|
+
startDate: '2020-03-01',
|
|
73
|
+
summary: 'Lead engineer for the platform team, owning core API infrastructure.',
|
|
74
|
+
highlights: [
|
|
75
|
+
'Reduced API latency by 45% through caching and query optimization',
|
|
76
|
+
'Designed microservices architecture serving 2M daily requests',
|
|
77
|
+
'Mentored 4 junior engineers through technical growth plans',
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'StartupXYZ',
|
|
82
|
+
position: 'Full Stack Developer',
|
|
83
|
+
startDate: '2017-06-01',
|
|
84
|
+
endDate: '2020-02-28',
|
|
85
|
+
summary: 'Built customer-facing web applications from prototype to production.',
|
|
86
|
+
highlights: [
|
|
87
|
+
'Built real-time dashboard used by 15,000+ daily active users',
|
|
88
|
+
'Implemented CI/CD pipeline reducing deploy time from 2 hours to 8 minutes',
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
education: [
|
|
93
|
+
{
|
|
94
|
+
institution: 'University of Technology',
|
|
95
|
+
area: 'Computer Science',
|
|
96
|
+
studyType: 'Bachelor of Science',
|
|
97
|
+
startDate: '2013-09-01',
|
|
98
|
+
endDate: '2017-05-15',
|
|
99
|
+
score: '3.8',
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
skills: [
|
|
103
|
+
{ name: 'Languages', level: 'Expert', keywords: ['TypeScript', 'JavaScript', 'Python', 'Go'] },
|
|
104
|
+
{ name: 'Frontend', level: 'Expert', keywords: ['React', 'Next.js', 'HTML/CSS', 'Tailwind'] },
|
|
105
|
+
{ name: 'Backend & Cloud', level: 'Advanced', keywords: ['Node.js', 'PostgreSQL', 'AWS', 'Docker'] },
|
|
106
|
+
],
|
|
107
|
+
projects: [
|
|
108
|
+
{
|
|
109
|
+
name: 'Open Source CLI Tool',
|
|
110
|
+
description: 'Developer productivity tool with 2,000+ GitHub stars',
|
|
111
|
+
url: 'https://github.com/janesmith/cli-tool',
|
|
112
|
+
startDate: '2022-01-01',
|
|
113
|
+
highlights: ['Published to npm with 5,000+ weekly downloads'],
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
// Provide empty arrays for sections themes might iterate over
|
|
117
|
+
volunteer: [],
|
|
118
|
+
awards: [],
|
|
119
|
+
certificates: [],
|
|
120
|
+
publications: [],
|
|
121
|
+
languages: [],
|
|
122
|
+
interests: [],
|
|
123
|
+
references: [],
|
|
124
|
+
};
|
|
79
125
|
|
|
80
|
-
|
|
81
|
-
return themes;
|
|
82
|
-
}
|
|
126
|
+
// ── Theme file collection ────────────────────────────────────────────
|
|
83
127
|
|
|
84
|
-
/** Recursively collect text files from a theme directory for
|
|
128
|
+
/** Recursively collect text files from a theme directory for the fs shim. */
|
|
85
129
|
function collectThemeFiles(themeDir) {
|
|
86
130
|
const files = {};
|
|
87
131
|
const dirs = {};
|
|
@@ -102,9 +146,9 @@ function collectThemeFiles(themeDir) {
|
|
|
102
146
|
walk(full, rel);
|
|
103
147
|
} else {
|
|
104
148
|
const ext = (entry.name.split('.').pop() || '').toLowerCase();
|
|
105
|
-
if (['css', 'hbs', 'html', 'json', 'txt', 'handlebars', 'mustache'].includes(ext)) {
|
|
149
|
+
if (['css', 'hbs', 'html', 'json', 'txt', 'handlebars', 'mustache', 'template'].includes(ext)) {
|
|
106
150
|
try { files[rel] = readFileSync(full, 'utf-8'); }
|
|
107
|
-
catch { /* skip
|
|
151
|
+
catch { /* skip */ }
|
|
108
152
|
}
|
|
109
153
|
}
|
|
110
154
|
}
|
|
@@ -115,53 +159,145 @@ function collectThemeFiles(themeDir) {
|
|
|
115
159
|
return { files, dirs };
|
|
116
160
|
}
|
|
117
161
|
|
|
118
|
-
/** Generate an fs shim that embeds the theme's files
|
|
162
|
+
/** Generate an fs shim that embeds the theme's files. */
|
|
119
163
|
function generateThemeFsShim(themeFiles) {
|
|
120
164
|
const { files, dirs } = themeFiles;
|
|
121
|
-
|
|
122
|
-
const __files = ${JSON.stringify(files)}
|
|
123
|
-
const __dirs = ${JSON.stringify(dirs)}
|
|
124
|
-
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
165
|
+
const lines = [
|
|
166
|
+
`const __files = ${JSON.stringify(files)};`,
|
|
167
|
+
`const __dirs = ${JSON.stringify(dirs)};`,
|
|
168
|
+
'',
|
|
169
|
+
'function normalizePath(p) {',
|
|
170
|
+
' return p.replace(/[\\\\]+/g, "/").replace(/^\\/+/, "");',
|
|
171
|
+
'}',
|
|
172
|
+
'',
|
|
173
|
+
'function matchFile(p) {',
|
|
174
|
+
' var clean = normalizePath(p);',
|
|
175
|
+
' if (__files[clean] !== undefined) return __files[clean];',
|
|
176
|
+
' var keys = Object.keys(__files);',
|
|
177
|
+
' for (var i = 0; i < keys.length; i++) {',
|
|
178
|
+
' if (clean.endsWith("/" + keys[i]) || clean.endsWith(keys[i])) return __files[keys[i]];',
|
|
179
|
+
' }',
|
|
180
|
+
' return undefined;',
|
|
181
|
+
'}',
|
|
182
|
+
'',
|
|
183
|
+
'function matchDir(p) {',
|
|
184
|
+
' var clean = normalizePath(p);',
|
|
185
|
+
' if (__dirs[clean] !== undefined) return __dirs[clean];',
|
|
186
|
+
' var keys = Object.keys(__dirs);',
|
|
187
|
+
' for (var i = 0; i < keys.length; i++) {',
|
|
188
|
+
' if (clean.endsWith("/" + keys[i]) || clean.endsWith(keys[i])) return __dirs[keys[i]];',
|
|
189
|
+
' }',
|
|
190
|
+
' return undefined;',
|
|
191
|
+
'}',
|
|
192
|
+
'',
|
|
193
|
+
'export var readFileSync = function(p) { var r = matchFile(p); return r !== undefined ? r : ""; };',
|
|
194
|
+
'export var readdirSync = function(p) { var r = matchDir(p); return r !== undefined ? r : []; };',
|
|
195
|
+
'export var existsSync = function(p) { return matchFile(p) !== undefined || matchDir(p) !== undefined; };',
|
|
196
|
+
'export var writeFileSync = function() {};',
|
|
197
|
+
'export var mkdirSync = function() {};',
|
|
198
|
+
'export default { readFileSync: readFileSync, readdirSync: readdirSync, existsSync: existsSync, writeFileSync: writeFileSync, mkdirSync: mkdirSync };',
|
|
199
|
+
];
|
|
200
|
+
return lines.join('\n');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ── CSS extraction ───────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
/** Extract <style> blocks from rendered HTML and write to a .css file. */
|
|
206
|
+
function extractCss(html, shortName) {
|
|
207
|
+
const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
208
|
+
let css = '';
|
|
209
|
+
let match;
|
|
210
|
+
while ((match = styleRegex.exec(html)) !== null) {
|
|
211
|
+
css += match[1] + '\n';
|
|
212
|
+
}
|
|
213
|
+
if (css.trim()) {
|
|
214
|
+
writeFileSync(resolve(THEMES_DIR, `${shortName}.css`), css.trim());
|
|
215
|
+
return true;
|
|
130
216
|
}
|
|
131
|
-
return
|
|
217
|
+
return false;
|
|
132
218
|
}
|
|
133
219
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
220
|
+
// ── Snapshot generation ──────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
/** Render a theme with sample data in Node.js and save as snapshot. */
|
|
223
|
+
function generateSnapshot(shortName, packageName) {
|
|
224
|
+
try {
|
|
225
|
+
// Clear require cache to get a fresh instance
|
|
226
|
+
const pkgPath = require.resolve(packageName);
|
|
227
|
+
delete require.cache[pkgPath];
|
|
228
|
+
|
|
229
|
+
const theme = require(packageName);
|
|
230
|
+
const render = theme.render || (theme.default && theme.default.render);
|
|
231
|
+
if (typeof render !== 'function') return null;
|
|
232
|
+
|
|
233
|
+
const result = render(SAMPLE_RESUME);
|
|
234
|
+
// render() may return a string or a Promise
|
|
235
|
+
if (typeof result === 'string') {
|
|
236
|
+
writeFileSync(resolve(THEMES_DIR, `${shortName}.snapshot.html`), result);
|
|
237
|
+
extractCss(result, shortName);
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
// If it's a promise, we need to await it
|
|
241
|
+
if (result && typeof result.then === 'function') {
|
|
242
|
+
return result.then((html) => {
|
|
243
|
+
if (typeof html === 'string') {
|
|
244
|
+
writeFileSync(resolve(THEMES_DIR, `${shortName}.snapshot.html`), html);
|
|
245
|
+
extractCss(html, shortName);
|
|
246
|
+
return html;
|
|
247
|
+
}
|
|
248
|
+
return null;
|
|
249
|
+
}).catch(() => null);
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
} catch (e) {
|
|
253
|
+
console.log(` (snapshot failed: ${e.message})`);
|
|
254
|
+
return null;
|
|
139
255
|
}
|
|
140
|
-
return undefined;
|
|
141
256
|
}
|
|
142
257
|
|
|
143
|
-
|
|
144
|
-
const r = matchFile(path);
|
|
145
|
-
return r !== undefined ? r : '';
|
|
146
|
-
};
|
|
258
|
+
// ── npm discovery ────────────────────────────────────────────────────
|
|
147
259
|
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
260
|
+
async function discoverThemes() {
|
|
261
|
+
const themes = [];
|
|
262
|
+
let from = 0;
|
|
263
|
+
const size = 250;
|
|
152
264
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
265
|
+
console.log('🔍 Discovering themes from npm...');
|
|
266
|
+
|
|
267
|
+
for (let page = 0; page < 4; page++) {
|
|
268
|
+
const url = `https://registry.npmjs.org/-/v1/search?text=jsonresume-theme&size=${size}&from=${from}`;
|
|
269
|
+
const res = await fetch(url);
|
|
270
|
+
if (!res.ok) break;
|
|
271
|
+
const data = await res.json();
|
|
272
|
+
|
|
273
|
+
for (const pkg of data.objects) {
|
|
274
|
+
const name = pkg.package.name;
|
|
275
|
+
if (!name.startsWith('jsonresume-theme-')) continue;
|
|
276
|
+
const shortName = name.replace('jsonresume-theme-', '');
|
|
277
|
+
if (pkg.score?.detail?.popularity === 0) continue;
|
|
278
|
+
|
|
279
|
+
themes.push({
|
|
280
|
+
name: shortName,
|
|
281
|
+
packageName: name,
|
|
282
|
+
description: pkg.package.description || '',
|
|
283
|
+
version: pkg.package.version,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (data.objects.length < size) break;
|
|
288
|
+
from += size;
|
|
289
|
+
}
|
|
156
290
|
|
|
157
|
-
|
|
158
|
-
|
|
291
|
+
console.log(` Found ${themes.length} themes`);
|
|
292
|
+
return themes;
|
|
159
293
|
}
|
|
160
294
|
|
|
295
|
+
// ── esbuild bundling ─────────────────────────────────────────────────
|
|
296
|
+
|
|
161
297
|
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
298
|
const themeDir = resolve(__dirname, `../node_modules/${packageName}`);
|
|
299
|
+
|
|
300
|
+
// Generate per-theme fs shim with embedded files
|
|
165
301
|
const themeFiles = collectThemeFiles(themeDir);
|
|
166
302
|
writeFileSync(resolve(shimsDir, 'fs.js'), generateThemeFsShim(themeFiles));
|
|
167
303
|
|
|
@@ -183,12 +319,7 @@ async function bundleTheme(shortName, packageName, shimsDir) {
|
|
|
183
319
|
format: 'esm',
|
|
184
320
|
target: 'es2022',
|
|
185
321
|
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
322
|
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
323
|
mainFields: ['browser', 'main'],
|
|
193
324
|
outfile: resolve(THEMES_DIR, `${shortName}.js`),
|
|
194
325
|
define: {
|
|
@@ -200,27 +331,28 @@ async function bundleTheme(shortName, packageName, shimsDir) {
|
|
|
200
331
|
'process.platform': '"browser"',
|
|
201
332
|
'process.version': '"v18.0.0"',
|
|
202
333
|
},
|
|
203
|
-
// Polyfill Node.js built-ins as no-ops for browser
|
|
204
334
|
alias: {
|
|
205
|
-
'path': resolve(
|
|
206
|
-
'fs': resolve(
|
|
207
|
-
'url': resolve(
|
|
208
|
-
'node:url': resolve(
|
|
209
|
-
'node:crypto': resolve(
|
|
210
|
-
'assert': resolve(
|
|
335
|
+
'path': resolve(shimsDir, 'path.js'),
|
|
336
|
+
'fs': resolve(shimsDir, 'fs.js'),
|
|
337
|
+
'url': resolve(shimsDir, 'url.js'),
|
|
338
|
+
'node:url': resolve(shimsDir, 'url.js'),
|
|
339
|
+
'node:crypto': resolve(shimsDir, 'crypto.js'),
|
|
340
|
+
'assert': resolve(shimsDir, 'assert.js'),
|
|
211
341
|
},
|
|
212
342
|
logLevel: 'silent',
|
|
213
343
|
});
|
|
214
344
|
|
|
215
|
-
// Clean up entry file
|
|
216
345
|
execSync(`rm -f "${entryFile}"`);
|
|
217
346
|
return true;
|
|
218
347
|
} catch (e) {
|
|
348
|
+
console.log(` (bundle error: ${e.message})`);
|
|
219
349
|
execSync(`rm -f "${entryFile}"`);
|
|
220
350
|
return false;
|
|
221
351
|
}
|
|
222
352
|
}
|
|
223
353
|
|
|
354
|
+
// ── Main ─────────────────────────────────────────────────────────────
|
|
355
|
+
|
|
224
356
|
async function main() {
|
|
225
357
|
const args = process.argv.slice(2);
|
|
226
358
|
const doAll = args.includes('--all');
|
|
@@ -232,15 +364,17 @@ async function main() {
|
|
|
232
364
|
// Create shims directory
|
|
233
365
|
const shimsDir = resolve(__dirname, 'shims');
|
|
234
366
|
mkdirSync(shimsDir, { recursive: true });
|
|
367
|
+
|
|
235
368
|
writeFileSync(resolve(shimsDir, 'path.js'), `
|
|
236
369
|
export const join = (...parts) => parts.join('/');
|
|
237
370
|
export const resolve = (...parts) => parts.join('/');
|
|
238
371
|
export const dirname = (p) => p.split('/').slice(0, -1).join('/');
|
|
239
372
|
export const basename = (p) => p.split('/').pop();
|
|
240
373
|
export const extname = (p) => { const m = p.match(/\\.[^.]+$/); return m ? m[0] : ''; };
|
|
241
|
-
export
|
|
374
|
+
export const sep = '/';
|
|
375
|
+
export default { join, resolve, dirname, basename, extname, sep };
|
|
242
376
|
`);
|
|
243
|
-
// fs shim is generated per-theme in bundleTheme()
|
|
377
|
+
// fs shim is generated per-theme in bundleTheme()
|
|
244
378
|
writeFileSync(resolve(shimsDir, 'url.js'), `
|
|
245
379
|
export const URL = globalThis.URL;
|
|
246
380
|
export const URLSearchParams = globalThis.URLSearchParams;
|
|
@@ -304,7 +438,7 @@ async function main() {
|
|
|
304
438
|
continue;
|
|
305
439
|
}
|
|
306
440
|
|
|
307
|
-
//
|
|
441
|
+
// Read package metadata
|
|
308
442
|
try {
|
|
309
443
|
const pkgPath = resolve(__dirname, `../node_modules/${theme.packageName}/package.json`);
|
|
310
444
|
if (existsSync(pkgPath)) {
|
|
@@ -314,31 +448,60 @@ async function main() {
|
|
|
314
448
|
}
|
|
315
449
|
} catch {}
|
|
316
450
|
|
|
317
|
-
//
|
|
451
|
+
// Step 1: Generate snapshot (pre-render in Node.js)
|
|
452
|
+
let hasSnapshot = false;
|
|
453
|
+
let hasCss = false;
|
|
454
|
+
const snapshotResult = generateSnapshot(theme.name, theme.packageName);
|
|
455
|
+
// Handle promise if async render
|
|
456
|
+
if (snapshotResult && typeof snapshotResult.then === 'function') {
|
|
457
|
+
const html = await snapshotResult;
|
|
458
|
+
hasSnapshot = !!html;
|
|
459
|
+
hasCss = existsSync(resolve(THEMES_DIR, `${theme.name}.css`));
|
|
460
|
+
} else {
|
|
461
|
+
hasSnapshot = !!snapshotResult;
|
|
462
|
+
hasCss = existsSync(resolve(THEMES_DIR, `${theme.name}.css`));
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Step 2: Bundle for browser
|
|
318
466
|
const success = await bundleTheme(theme.name, theme.packageName, shimsDir);
|
|
319
467
|
|
|
320
468
|
if (success) {
|
|
321
469
|
const outFile = resolve(THEMES_DIR, `${theme.name}.js`);
|
|
322
470
|
const stat = existsSync(outFile) ? readFileSync(outFile).length : 0;
|
|
471
|
+
const cssFile = resolve(THEMES_DIR, `${theme.name}.css`);
|
|
472
|
+
const cssSize = hasCss && existsSync(cssFile) ? readFileSync(cssFile).length : 0;
|
|
473
|
+
|
|
323
474
|
manifest.push({
|
|
324
475
|
name: theme.name,
|
|
325
476
|
displayName: theme.name.charAt(0).toUpperCase() + theme.name.slice(1).replace(/-/g, ' '),
|
|
326
477
|
description: theme.description,
|
|
327
478
|
version: theme.version || '',
|
|
328
479
|
browserCompatible: true,
|
|
480
|
+
hasSnapshot,
|
|
481
|
+
hasCss,
|
|
329
482
|
fileSize: stat,
|
|
483
|
+
cssSize,
|
|
330
484
|
});
|
|
331
|
-
|
|
485
|
+
|
|
486
|
+
const parts = [`✅ (${(stat / 1024).toFixed(0)}KB`];
|
|
487
|
+
if (hasCss) parts.push(`css:${(cssSize / 1024).toFixed(0)}KB`);
|
|
488
|
+
if (hasSnapshot) parts.push('snapshot');
|
|
489
|
+
console.log(parts.join(', ') + ')');
|
|
332
490
|
} else {
|
|
491
|
+
// Bundle failed — still useful if we have a snapshot (server fallback)
|
|
333
492
|
manifest.push({
|
|
334
493
|
name: theme.name,
|
|
335
494
|
displayName: theme.name.charAt(0).toUpperCase() + theme.name.slice(1).replace(/-/g, ' '),
|
|
336
495
|
description: theme.description,
|
|
337
496
|
version: theme.version || '',
|
|
338
497
|
browserCompatible: false,
|
|
498
|
+
hasSnapshot,
|
|
499
|
+
hasCss: false,
|
|
339
500
|
fileSize: 0,
|
|
501
|
+
cssSize: 0,
|
|
340
502
|
});
|
|
341
|
-
|
|
503
|
+
const note = hasSnapshot ? '⚠️ browser incompatible (has snapshot)' : '⚠️ browser incompatible';
|
|
504
|
+
console.log(note);
|
|
342
505
|
}
|
|
343
506
|
}
|
|
344
507
|
|
|
@@ -346,7 +509,8 @@ async function main() {
|
|
|
346
509
|
writeFileSync(resolve(THEMES_DIR, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
347
510
|
|
|
348
511
|
const successful = manifest.filter(t => t.browserCompatible).length;
|
|
349
|
-
|
|
512
|
+
const snapshots = manifest.filter(t => t.hasSnapshot).length;
|
|
513
|
+
console.log(`\n✅ Done! ${successful}/${manifest.length} themes bundled, ${snapshots} snapshots generated`);
|
|
350
514
|
console.log(`📁 Output: docs/themes/`);
|
|
351
515
|
|
|
352
516
|
// Clean up shims
|