resuml 1.14.0 → 1.14.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "resuml",
3
- "version": "1.14.0",
3
+ "version": "1.14.2",
4
4
  "description": "Generate JSON resumes from YAML with theme support",
5
5
  "type": "module",
6
6
  "main": "./dist/api.js",
@@ -137,6 +137,8 @@ const SAMPLE_RESUME = {
137
137
  function padResume(r) {
138
138
  const basics = r.basics ?? {};
139
139
  const location = basics.location ?? {};
140
+ const meta = r.meta ?? {};
141
+ const palette = meta.palette ?? {};
140
142
  const safe = {
141
143
  ...r,
142
144
  basics: {
@@ -144,6 +146,9 @@ function padResume(r) {
144
146
  ...basics,
145
147
  location: { address: '', postalCode: '', city: '', countryCode: '', region: '', ...location },
146
148
  },
149
+ // Guard against themes like material that read meta.palette.primary without
150
+ // defensive checks.
151
+ meta: { ...meta, palette: { primary: '', secondary: '', ...palette } },
147
152
  };
148
153
  const arraySections = ['work','volunteer','education','awards','certificates','publications','skills','languages','interests','references','projects'];
149
154
  for (const key of arraySections) {
@@ -393,6 +398,29 @@ const LEGACY_PATCHES = [
393
398
  );
394
399
  },
395
400
  },
401
+ // jsonresume-theme-react tries to assign `window` and `document` which are
402
+ // read-only in the browser (we're already in a browser — no need to
403
+ // polyfill). Guard the assignments so they don't throw. It also uses a
404
+ // pattern not covered by the generic rewrite:
405
+ // const modulePath = path.join(__dirname, 'dist/index.cjs');
406
+ // require(modulePath);
407
+ // Inline the variable so esbuild can statically bundle dist/index.cjs.
408
+ {
409
+ filter: /node_modules\/jsonresume-theme-react\/index\.cjs$/,
410
+ transform: (src) => src
411
+ .replace(
412
+ /global\.window\s*=\s*global\.window\s*\|\|\s*\{[^}]*\};?/g,
413
+ 'try { if (typeof window === "undefined") globalThis.window = {}; } catch (_) {}',
414
+ )
415
+ .replace(
416
+ /global\.document\s*=\s*global\.document\s*\|\|\s*\{[\s\S]*?\};/g,
417
+ 'try { if (typeof document === "undefined") globalThis.document = { createElement: () => ({}), addEventListener: () => {} }; } catch (_) {}',
418
+ )
419
+ .replace(
420
+ /const\s+modulePath\s*=\s*path\.join\(\s*__dirname\s*,\s*['"]([^'"]+)['"]\s*\);([\s\S]*?)require\(modulePath\)/,
421
+ (_, rel, middle) => `const modulePath = './${rel}';${middle}require('./${rel}')`,
422
+ ),
423
+ },
396
424
  ];
397
425
 
398
426
  /**
@@ -406,7 +434,7 @@ function legacyThemeGlobalsPlugin() {
406
434
  return {
407
435
  name: 'legacy-theme-globals',
408
436
  setup(build) {
409
- build.onLoad({ filter: /\.js$/ }, async (args) => {
437
+ build.onLoad({ filter: /\.c?js$/ }, async (args) => {
410
438
  const { readFile } = await import('node:fs/promises');
411
439
  const isThemePkg = /node_modules\/jsonresume-theme-[^/]+\/[^/]*\.js$/.test(args.path);
412
440
  const matchingPatches = LEGACY_PATCHES.filter((p) => p.filter.test(args.path));
@@ -436,8 +464,28 @@ function legacyThemeGlobalsPlugin() {
436
464
  ]);
437
465
  contents = contents.replace(
438
466
  /^([ \t]*)([a-zA-Z_$][\w$]*)\s*=(?!=)/gm,
439
- (match, indent, name) => {
467
+ (match, indent, name, offset, full) => {
440
468
  if (RESERVED.has(name)) return match;
469
+ // Skip expression-context assignments (not a new statement):
470
+ // - `var a, b,\n c = ...` (multi-var continuation)
471
+ // - `foo(\n bar = 1\n)` (argument expression)
472
+ // - `[\n x = 1\n]` (array element assignment)
473
+ // - `a ?\n b = 1 :\n c = 2` (ternary branch)
474
+ // - `a ||\n b = 1` (short-circuit)
475
+ // A new statement context, on the other hand, lives after
476
+ // `{` `}` `;` — in those cases the rewrite is correct.
477
+ const before = full.slice(0, offset).replace(/\s+$/, '');
478
+ const prevChar = before.charAt(before.length - 1);
479
+ if (prevChar === ',' || prevChar === '(' || prevChar === '[' ||
480
+ prevChar === '?' || prevChar === ':') return match;
481
+ const prev2 = before.slice(-2);
482
+ if (prev2 === '||' || prev2 === '&&' || prev2 === '=>') return match;
483
+ // Inside `for (` init clause — rare but valid (var isn't allowed
484
+ // there unless at the very start, and esbuild will handle the
485
+ // already-correct `for (var i = 0; ...)` form).
486
+ const lineStart = full.lastIndexOf('\n', offset - 1) + 1;
487
+ const lineBefore = full.slice(lineStart, offset);
488
+ if (/\bfor\s*\(\s*$/.test(lineBefore)) return match;
441
489
  return `${indent}var ${name} =`;
442
490
  }
443
491
  );
@@ -446,6 +494,39 @@ function legacyThemeGlobalsPlugin() {
446
494
  // 2. Targeted source patches (layered on top of the generic rewrite)
447
495
  for (const patch of matchingPatches) contents = patch.transform(contents);
448
496
 
497
+ // 3. Resolve dynamic requires statically so esbuild can bundle them.
498
+ // Two common patterns in themes:
499
+ // a) const HELPERS = join(__dirname, 'theme/hbs-helpers');
500
+ // require(join(HELPERS, 'file.js'))
501
+ // b) require(path.join(__dirname, 'dist/index.cjs'))
502
+ // esbuild leaves computed requires as runtime calls, which our
503
+ // browser shim rejects. Rewrite to static relative paths.
504
+ if (isThemePkg) {
505
+ // Pattern (b): inline `require((path.)?join(__dirname, 'literal'))`
506
+ contents = contents.replace(
507
+ /require\(\s*(?:\w+\.)?join\(\s*__dirname\s*,\s*['"]([^'"]+)['"]\s*\)\s*\)/g,
508
+ (_, rel) => `require(${JSON.stringify('./' + rel)})`,
509
+ );
510
+ // Pattern (a): resolve via tracked join-constants.
511
+ const joinConsts = {};
512
+ for (const m of contents.matchAll(
513
+ /\b(?:const|var|let)\s+(\w+)\s*=\s*(?:\w+\.)?join\(\s*__dirname\s*,\s*['"]([^'"]+)['"]\s*\)/g
514
+ )) {
515
+ joinConsts[m[1]] = m[2];
516
+ }
517
+ if (Object.keys(joinConsts).length > 0) {
518
+ contents = contents.replace(
519
+ /require\(\s*(?:\w+\.)?join\(\s*(\w+)\s*,\s*['"]([^'"]+)['"]\s*\)\s*\)/g,
520
+ (match, name, file) => {
521
+ const prefix = joinConsts[name];
522
+ if (prefix === undefined) return match;
523
+ const rel = `./${prefix}/${file}`.replace(/\/+/g, '/');
524
+ return `require(${JSON.stringify(rel)})`;
525
+ }
526
+ );
527
+ }
528
+ }
529
+
449
530
  return contents !== original ? { contents, loader: 'js' } : null;
450
531
  });
451
532
  },
@@ -498,7 +579,7 @@ async function bundleTheme(shortName, packageName, shimsDir) {
498
579
  // Runtime process shim: `process.env.X` literal access is handled
499
580
  // by esbuild's `define`, but dynamic access like `process.cwd()` or
500
581
  // `process.stdout.write` needs an actual object at runtime.
501
- 'if (typeof globalThis.process === "undefined") globalThis.process = { env: { NODE_ENV: "production" }, browser: true, platform: "browser", version: "v20.0.0", versions: {}, stdout: { write: function(){} }, stderr: { write: function(){} }, cwd: function(){ return "/"; }, chdir: function(){}, nextTick: function(fn){ Promise.resolve().then(fn); }, argv: [], pid: 1, title: "browser" };',
582
+ '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" };',
502
583
  ].join(''),
503
584
  },
504
585
  define: {
@@ -97,7 +97,10 @@ async function enrichRender(manifest) {
97
97
  certificates: [{ name: 'C', date: '2020', issuer: 'Is' }],
98
98
  awards: [{ title: 'A', date: '2020', awarder: 'Aw', summary: 'S' }],
99
99
  volunteer: [{ organization: 'V', position: 'P', startDate: '2020', summary: 'S', highlights: ['H'] }],
100
- meta: { theme: 'test' },
100
+ // Include `meta.palette` so themes like material (which set
101
+ // meta.palette.primary without defensive checks) don't trip the probe
102
+ // — matches what the runtime padResume provides.
103
+ meta: { theme: 'test', palette: { primary: '', secondary: '' } },
101
104
  };
102
105
 
103
106
  for (let i = 0; i < manifest.length; i++) {