resuml 2.0.0 → 3.1.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/dist/index.js CHANGED
@@ -1,694 +1,16 @@
1
- #!/usr/bin/env node
2
1
  import {
3
- KNOWN_THEMES,
4
- __export,
5
- analyzeAts,
6
- generateResumeYaml,
7
- getInstalledVersion,
8
- isThemeInstalled,
2
+ loadResumeFiles,
3
+ program,
4
+ themeRender_exports
5
+ } from "./chunk-M6JY5UDJ.js";
6
+ import {
9
7
  loadTheme,
10
8
  processResumeData
11
- } from "./chunk-GRIYYG45.js";
12
-
13
- // src/index.ts
14
- import { Command } from "commander";
15
- import path6 from "path";
16
- import fs9 from "fs";
17
- import { fileURLToPath } from "url";
18
-
19
- // src/commands/validate.ts
20
- import fs3 from "fs";
21
-
22
- // src/utils/loadResume.ts
23
- import fs2 from "fs/promises";
24
- import YAML from "yaml";
25
-
26
- // src/utils/fileUtils.ts
27
- import fs from "fs/promises";
28
- import path from "path";
29
- import { glob } from "glob";
30
- async function findInputFiles(inputPath) {
31
- if (!inputPath) {
32
- return [];
33
- }
34
- if (inputPath.includes("*")) {
35
- try {
36
- const matchedFiles = await glob(inputPath);
37
- if (matchedFiles.length === 0) {
38
- throw new Error(`No files found matching pattern: ${inputPath}`);
39
- }
40
- return matchedFiles;
41
- } catch (err) {
42
- throw new Error(`Error matching files: ${err instanceof Error ? err.message : String(err)}`);
43
- }
44
- }
45
- try {
46
- const stat = await fs.stat(inputPath);
47
- if (stat.isFile()) {
48
- return [inputPath];
49
- } else if (stat.isDirectory()) {
50
- const pattern = path.join(inputPath, "*.{yaml,yml}");
51
- try {
52
- const yamlFiles = await glob(pattern);
53
- if (yamlFiles.length === 0) {
54
- throw new Error(`No YAML files found in directory: ${inputPath}`);
55
- }
56
- return yamlFiles;
57
- } catch (_) {
58
- throw new Error(`No YAML files found in directory: ${inputPath}`);
59
- }
60
- }
61
- } catch (e) {
62
- if (e instanceof Error && e.message.includes("ENOENT")) {
63
- throw new Error("Input path not found");
64
- }
65
- throw e;
66
- }
67
- return [];
68
- }
69
-
70
- // src/utils/loadResume.ts
71
- async function loadResumeFiles(inputPath) {
72
- const files = await findInputFiles(inputPath);
73
- if (files.length === 0) {
74
- throw new Error("No resume files found");
75
- }
76
- const yamlContents = [];
77
- for (const file of files) {
78
- try {
79
- const content = await fs2.readFile(file, "utf-8");
80
- const parsed = YAML.parse(content);
81
- if (parsed && typeof parsed === "object") {
82
- yamlContents.push(content);
83
- }
84
- } catch (error) {
85
- throw new Error(`Failed to parse ${file}: ${error.message}`);
86
- }
87
- }
88
- if (yamlContents.length === 0) {
89
- throw new Error("No valid data found in any of the input files");
90
- }
91
- return { files, yamlContents };
92
- }
93
-
94
- // src/utils/errorHandler.ts
95
- function handleCommandError(error, command, debug = false) {
96
- const errorMessage = error instanceof Error ? error.message : String(error);
97
- console.error(`\u274C Error during ${command} command: ${errorMessage}`);
98
- const potentialValidationError = error;
99
- if (potentialValidationError instanceof Error && potentialValidationError.name === "SchemaValidationError" && Array.isArray(potentialValidationError.errors)) {
100
- const errors = potentialValidationError.errors;
101
- if (debug) {
102
- console.error("\nValidation failed with the following errors:");
103
- errors.forEach((err, index) => {
104
- const path7 = err.instancePath || "root";
105
- console.error(`${index + 1}. Path: ${path7}`);
106
- console.error(` Error: ${err.message || "Unknown validation error"}`);
107
- if (err.params) {
108
- console.error(` Params: ${JSON.stringify(err.params)}`);
109
- }
110
- });
111
- } else {
112
- console.error("\nSome validation errors were found:");
113
- const maxErrors = 5;
114
- errors.slice(0, maxErrors).forEach((err, index) => {
115
- const path7 = err.instancePath || "root";
116
- console.error(`${index + 1}. Field: ${path7}`);
117
- console.error(` Error: ${err.message || "Unknown validation error"}`);
118
- });
119
- if (errors.length > maxErrors) {
120
- console.error(`
121
- ...and ${errors.length - maxErrors} more errors.`);
122
- console.error("Use the --debug flag for complete error details.");
123
- }
124
- }
125
- }
126
- if (process.env["NODE_ENV"] !== "test") {
127
- process.exit(1);
128
- }
129
- }
130
-
131
- // src/commands/validate.ts
132
- function formatAtsReport(result, debug, chalk6) {
133
- const scoreColor = result.score >= 75 ? chalk6.green : result.score >= 60 ? chalk6.yellow : chalk6.red;
134
- console.log("");
135
- console.log(chalk6.bold("\u2550\u2550\u2550 ATS Analysis Report \u2550\u2550\u2550"));
136
- console.log("");
137
- console.log(` Score: ${scoreColor(chalk6.bold(`${result.score}/100`))} (${result.rating.replace("-", " ")})`);
138
- console.log(` ${result.summary}`);
139
- console.log("");
140
- const categories = {};
141
- for (const check of result.checks) {
142
- const list = categories[check.category];
143
- if (!list) {
144
- categories[check.category] = [check];
145
- } else {
146
- list.push(check);
147
- }
148
- }
149
- const categoryLabels = {
150
- contact: "Contact Information",
151
- content: "Content Quality",
152
- structure: "Resume Structure",
153
- keywords: "Keywords"
154
- };
155
- for (const [cat, checks] of Object.entries(categories)) {
156
- const label = categoryLabels[cat] || cat;
157
- console.log(chalk6.bold(` ${label}`));
158
- for (const check of checks) {
159
- if (!debug && check.passed) continue;
160
- const icon = check.passed ? chalk6.green("\u2713") : chalk6.red("\u2717");
161
- const scoreText = chalk6.dim(`[${check.score}]`);
162
- console.log(` ${icon} ${check.message} ${scoreText}`);
163
- if (!check.passed && check.suggestion) {
164
- console.log(chalk6.dim(` \u2192 ${check.suggestion}`));
165
- }
166
- }
167
- console.log("");
168
- }
169
- if (result.keywords) {
170
- console.log(chalk6.bold(" Job Description Match"));
171
- const kw = result.keywords;
172
- const matchColor = kw.matchPercentage >= 70 ? chalk6.green : kw.matchPercentage >= 50 ? chalk6.yellow : chalk6.red;
173
- console.log(` Match: ${matchColor(`${kw.matchPercentage}%`)} (${kw.matched.length}/${kw.matched.length + kw.missing.length} keywords)`);
174
- if (kw.matched.length > 0) {
175
- console.log(chalk6.green(` \u2713 Matched: ${kw.matched.join(", ")}`));
176
- }
177
- if (kw.missing.length > 0) {
178
- console.log(chalk6.red(` \u2717 Missing: ${kw.missing.join(", ")}`));
179
- console.log(chalk6.dim(" \u2192 Consider incorporating these keywords into your resume where relevant."));
180
- }
181
- console.log("");
182
- }
183
- console.log(chalk6.dim("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
184
- }
185
- async function validateAction(options) {
186
- const chalk6 = (await import("chalk")).default;
187
- console.log(chalk6.blue("Starting resuml validate..."));
188
- try {
189
- const inputPath = options.resume;
190
- const { yamlContents } = await loadResumeFiles(inputPath);
191
- console.log(chalk6.blue("Validating resume data..."));
192
- let resumeData;
193
- try {
194
- resumeData = await processResumeData(yamlContents);
195
- console.log(chalk6.green("\u2713 Resume data is valid against the schema!"));
196
- } catch (error) {
197
- handleCommandError(error, "validate", options.debug);
198
- return;
199
- }
200
- if (options.ats) {
201
- console.log(chalk6.blue("Running ATS analysis..."));
202
- let jobDescription;
203
- if (options.jd) {
204
- try {
205
- jobDescription = fs3.readFileSync(options.jd, "utf8");
206
- } catch {
207
- console.error(chalk6.red(`Failed to read job description file: ${options.jd}`));
208
- return;
209
- }
210
- }
211
- const result = analyzeAts(resumeData, {
212
- language: "en",
213
- jobDescription
214
- });
215
- if (options.format === "json") {
216
- console.log(JSON.stringify(result, null, 2));
217
- } else {
218
- formatAtsReport(result, !!options.debug, chalk6);
219
- }
220
- const threshold = options.atsThreshold ? parseInt(options.atsThreshold, 10) : void 0;
221
- if (threshold !== void 0 && result.score < threshold) {
222
- console.error(chalk6.red(`
223
- ATS score ${result.score} is below threshold ${threshold}.`));
224
- process.exit(1);
225
- }
226
- }
227
- } catch (error) {
228
- handleCommandError(error, "validate", options.debug);
229
- }
230
- }
231
-
232
- // src/commands/tojson.ts
233
- import fs4 from "fs";
234
- async function toJsonAction(options) {
235
- const chalk6 = (await import("chalk")).default;
236
- console.log(chalk6.blue("Starting resuml tojson..."));
237
- try {
238
- const inputPath = options.resume;
239
- const { yamlContents } = await loadResumeFiles(inputPath);
240
- console.log(chalk6.blue("Processing and validating data..."));
241
- const resumeData = await processResumeData(yamlContents);
242
- console.log(chalk6.green("Processing and validation successful!"));
243
- const jsonOutput = JSON.stringify(resumeData, null, 2);
244
- fs4.writeFileSync(options.output, jsonOutput, "utf8");
245
- console.log(chalk6.green(`Successfully wrote output to ${options.output}`));
246
- } catch (error) {
247
- handleCommandError(error, "tojson", options.debug);
248
- }
249
- }
250
-
251
- // src/commands/render.ts
252
- import fs5 from "fs";
253
- import path2 from "path";
254
- import chalk from "chalk";
255
- async function renderAction(options) {
256
- if (!options.theme) {
257
- throw new Error(
258
- "--theme option is required. Please specify a theme name (e.g., stackoverflow, react)."
259
- );
260
- }
261
- console.log(chalk.blue("Starting resuml render..."));
262
- try {
263
- const inputPath = options.resume;
264
- const { yamlContents } = await loadResumeFiles(inputPath);
265
- console.log(chalk.blue("Processing and validating resume data..."));
266
- const resumeData = await processResumeData(yamlContents);
267
- console.log(chalk.green("Resume data processing and validation successful!"));
268
- const theme = loadTheme(options.theme);
269
- const htmlOutput = await theme.render(resumeData, {
270
- locale: options.language
271
- });
272
- const defaultExtension = options.format;
273
- const defaultFilename = `resume.${defaultExtension}`;
274
- const outputPath = options.output || defaultFilename;
275
- if (options.format === "pdf") {
276
- console.log(chalk.blue(`Generating PDF output at ${outputPath}...`));
277
- const { chromium } = await import("playwright");
278
- const browser = await chromium.launch();
279
- const page = await browser.newPage();
280
- await page.setContent(htmlOutput, { waitUntil: "networkidle" });
281
- const pdfBuffer = await page.pdf({
282
- path: outputPath,
283
- format: "A4",
284
- printBackground: true,
285
- margin: {
286
- top: "1cm",
287
- right: "1cm",
288
- bottom: "1cm",
289
- left: "1cm"
290
- }
291
- });
292
- await browser.close();
293
- fs5.writeFileSync(outputPath, pdfBuffer);
294
- console.log(chalk.green(`Successfully wrote PDF output to ${outputPath}`));
295
- } else {
296
- console.log(chalk.blue(`Writing HTML output to ${outputPath}...`));
297
- fs5.mkdirSync(path2.dirname(outputPath), { recursive: true });
298
- fs5.writeFileSync(outputPath, htmlOutput, "utf8");
299
- console.log(chalk.green(`Successfully wrote HTML output to ${outputPath}`));
300
- }
301
- } catch (error) {
302
- handleCommandError(error, "render", options.debug);
303
- }
304
- }
305
-
306
- // src/commands/dev.ts
307
- import fs6 from "fs";
308
- import path3 from "path";
309
- import chalk2 from "chalk";
310
- async function devAction(options) {
311
- if (!options.theme) {
312
- throw new Error(
313
- "--theme option is required. Please specify a theme name (e.g., stackoverflow, react)."
314
- );
315
- }
316
- console.log(chalk2.blue("Starting resuml development server..."));
317
- const port = options.port || 3e3;
318
- const inputPath = options.resume;
319
- if (!inputPath) {
320
- throw new Error("Resume path is required. Use -r or --resume option.");
321
- }
322
- try {
323
- await renderResume(options);
324
- console.log(chalk2.green(`\u{1F680} Development server running at http://localhost:${port}`));
325
- console.log(chalk2.blue("Watching for file changes..."));
326
- if (fs6.existsSync(inputPath) && fs6.statSync(inputPath).isDirectory()) {
327
- watchDirectory(inputPath, () => {
328
- void renderResume(options);
329
- });
330
- } else if (fs6.existsSync(inputPath)) {
331
- watchFile(inputPath, () => {
332
- void renderResume(options);
333
- });
334
- }
335
- await startDevServer(port);
336
- } catch (error) {
337
- handleCommandError(error, "dev", options.debug);
338
- }
339
- }
340
- async function renderResume(options) {
341
- try {
342
- const inputPath = options.resume;
343
- if (!inputPath) {
344
- throw new Error("Resume path is required");
345
- }
346
- const { yamlContents } = await loadResumeFiles(inputPath);
347
- console.log(chalk2.blue("\u{1F504} Processing resume data..."));
348
- const resumeData = await processResumeData(yamlContents);
349
- const theme = loadTheme(options.theme ?? "stackoverflow");
350
- const htmlOutput = await theme.render(resumeData, {
351
- locale: options.language
352
- });
353
- const outputPath = path3.join(process.cwd(), ".resuml-dev", "index.html");
354
- fs6.mkdirSync(path3.dirname(outputPath), { recursive: true });
355
- fs6.writeFileSync(outputPath, htmlOutput, "utf8");
356
- console.log(chalk2.green("\u2705 Resume updated!"));
357
- } catch (error) {
358
- console.error(chalk2.red("\u274C Error rendering resume:"), error.message);
359
- }
360
- }
361
- function watchDirectory(dirPath, callback) {
362
- fs6.watch(dirPath, { recursive: true }, (_eventType, filename) => {
363
- if (filename && (filename.endsWith(".yaml") || filename.endsWith(".yml"))) {
364
- console.log(chalk2.blue(`\u{1F4C1} File changed: ${filename}`));
365
- callback();
366
- }
367
- });
368
- }
369
- function watchFile(filePath, callback) {
370
- fs6.watch(filePath, (eventType) => {
371
- if (eventType === "change") {
372
- console.log(chalk2.blue(`\u{1F4C4} File changed: ${path3.basename(filePath)}`));
373
- callback();
374
- }
375
- });
376
- }
377
- async function startDevServer(port) {
378
- const http = await import("http");
379
- const url = await import("url");
380
- const server = http.createServer((req, res) => {
381
- const parsedUrl = url.parse(req.url || "", true);
382
- const pathname = parsedUrl.pathname || "/";
383
- if (pathname === "/" || pathname === "/index.html") {
384
- const htmlPath = path3.join(process.cwd(), ".resuml-dev", "index.html");
385
- if (fs6.existsSync(htmlPath)) {
386
- const html = fs6.readFileSync(htmlPath, "utf8");
387
- const liveReloadScript = `
388
- <script>
389
- setInterval(() => {
390
- fetch('/health').then(() => {
391
- location.reload();
392
- }).catch(() => {
393
- // Server might be restarting
394
- });
395
- }, 1000);
396
- </script>
397
- `;
398
- const modifiedHtml = html.replace("</body>", `${liveReloadScript}</body>`);
399
- res.writeHead(200, { "Content-Type": "text/html" });
400
- res.end(modifiedHtml);
401
- } else {
402
- res.writeHead(404, { "Content-Type": "text/plain" });
403
- res.end("Resume not found. Make sure to provide a valid resume path.");
404
- }
405
- } else if (pathname === "/health") {
406
- res.writeHead(200, { "Content-Type": "application/json" });
407
- res.end('{"status":"ok"}');
408
- } else {
409
- res.writeHead(404, { "Content-Type": "text/plain" });
410
- res.end("Not found");
411
- }
412
- });
413
- server.listen(port, () => {
414
- console.log(chalk2.green(`\u{1F310} Server listening on port ${port}`));
415
- });
416
- }
417
-
418
- // src/commands/init.ts
419
- import fs7 from "fs";
420
- import path4 from "path";
421
- import readline from "readline";
422
- import chalk3 from "chalk";
423
- function createReadlineInterface() {
424
- return readline.createInterface({
425
- input: process.stdin,
426
- output: process.stdout
427
- });
428
- }
429
- function ask(rl, question, defaultValue) {
430
- const prompt = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
431
- return new Promise((resolve) => {
432
- rl.question(prompt, (answer) => {
433
- resolve(answer.trim() || defaultValue || "");
434
- });
435
- });
436
- }
437
- async function initAction(options) {
438
- const outputPath = options.output || "resume.yaml";
439
- const fullPath = path4.resolve(outputPath);
440
- const rl = createReadlineInterface();
441
- try {
442
- if (fs7.existsSync(fullPath)) {
443
- const overwrite = await ask(
444
- rl,
445
- `${chalk3.yellow("\u26A0")} ${outputPath} already exists. Overwrite? (y/N)`,
446
- "N"
447
- );
448
- if (overwrite.toLowerCase() !== "y") {
449
- console.log(chalk3.blue("Aborted. No files were changed."));
450
- return;
451
- }
452
- }
453
- console.log(chalk3.blue("\n\u{1F4DD} Let's set up your resume!\n"));
454
- const name = await ask(rl, "Your full name", "John Doe");
455
- const email = await ask(rl, "Email address", "john@example.com");
456
- const label = await ask(rl, "Professional title/label", "Software Engineer");
457
- const yaml = generateResumeYaml(name, email, label);
458
- fs7.mkdirSync(path4.dirname(fullPath), { recursive: true });
459
- fs7.writeFileSync(fullPath, yaml, "utf8");
460
- console.log(chalk3.green(`
461
- \u2705 Created ${outputPath}`));
462
- console.log(chalk3.blue("\nNext steps:"));
463
- console.log(` 1. Edit ${outputPath} to fill in your details`);
464
- console.log(" 2. Run " + chalk3.cyan("resuml validate --resume " + outputPath));
465
- console.log(" 3. Run " + chalk3.cyan("resuml render --resume " + outputPath + " --theme stackoverflow"));
466
- } finally {
467
- rl.close();
468
- }
469
- }
470
-
471
- // src/commands/pdf.ts
472
- import fs8 from "fs";
473
- import path5 from "path";
474
- import chalk4 from "chalk";
475
- async function loadPlaywright() {
476
- try {
477
- const { chromium } = await import("playwright");
478
- return chromium;
479
- } catch {
480
- throw new Error(
481
- `Playwright is required for PDF export but is not installed.
482
- Install it with: ${chalk4.cyan("npm install playwright")}`
483
- );
484
- }
485
- }
486
- function parseMargin(margin) {
487
- const defaultMargin = { top: "10mm", right: "10mm", bottom: "10mm", left: "10mm" };
488
- if (!margin) return defaultMargin;
489
- const parts = margin.split(",").map((s) => s.trim());
490
- if (parts.length === 1 && parts[0]) {
491
- return { top: parts[0], right: parts[0], bottom: parts[0], left: parts[0] };
492
- }
493
- if (parts.length === 2 && parts[0] && parts[1]) {
494
- return { top: parts[0], right: parts[1], bottom: parts[0], left: parts[1] };
495
- }
496
- if (parts.length === 4 && parts[0] && parts[1] && parts[2] && parts[3]) {
497
- return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[3] };
498
- }
499
- return defaultMargin;
500
- }
501
- async function pdfAction(options) {
502
- if (!options.theme) {
503
- throw new Error(
504
- "--theme option is required. Please specify a theme name (e.g., stackoverflow, react)."
505
- );
506
- }
507
- console.log(chalk4.blue("Starting resuml PDF export..."));
508
- try {
509
- const inputPath = options.resume;
510
- const { yamlContents } = await loadResumeFiles(inputPath);
511
- console.log(chalk4.blue("Processing and validating resume data..."));
512
- const resumeData = await processResumeData(yamlContents);
513
- console.log(chalk4.green("Resume data processing and validation successful!"));
514
- const theme = loadTheme(options.theme);
515
- const htmlOutput = await theme.render(resumeData, {
516
- locale: options.language
517
- });
518
- console.log(chalk4.blue("Loading Playwright..."));
519
- const chromium = await loadPlaywright();
520
- const outputPath = options.output || "resume.pdf";
521
- const format = options.format === "Letter" ? "Letter" : "A4";
522
- const margin = parseMargin(options.margin);
523
- console.log(chalk4.blue(`Generating PDF (${format} format)...`));
524
- const browser = await chromium.launch({ headless: true });
525
- try {
526
- const page = await browser.newPage();
527
- await page.setContent(htmlOutput, { waitUntil: "networkidle" });
528
- const pdfBuffer = await page.pdf({
529
- format,
530
- margin,
531
- printBackground: true,
532
- preferCSSPageSize: true
533
- });
534
- fs8.mkdirSync(path5.dirname(path5.resolve(outputPath)), { recursive: true });
535
- fs8.writeFileSync(outputPath, pdfBuffer);
536
- console.log(chalk4.green(`\u2705 Successfully generated ${outputPath}`));
537
- } finally {
538
- await browser.close();
539
- }
540
- } catch (error) {
541
- handleCommandError(error, "pdf", options.debug);
542
- }
543
- }
544
-
545
- // src/commands/themes.ts
546
- import chalk5 from "chalk";
547
- import { execSync } from "child_process";
548
- function listThemes() {
549
- console.log(chalk5.blue("\n\u{1F4E6} Compatible JSON Resume Themes\n"));
550
- const nameWidth = 16;
551
- const pkgWidth = 38;
552
- console.log(
553
- ` ${"Status".padEnd(10)}${"Name".padEnd(nameWidth)}${"Package".padEnd(pkgWidth)}Description`
554
- );
555
- console.log(` ${"\u2500".repeat(10)}${"\u2500".repeat(nameWidth)}${"\u2500".repeat(pkgWidth)}${"\u2500".repeat(30)}`);
556
- for (const theme of KNOWN_THEMES) {
557
- const installed = isThemeInstalled(theme.pkg);
558
- const version = installed ? getInstalledVersion(theme.pkg) : null;
559
- const status = installed ? chalk5.green(`\u2713 ${version || "yes"}`.padEnd(10)) : chalk5.yellow("not installed".substring(0, 10).padEnd(10));
560
- console.log(
561
- ` ${status}${theme.name.padEnd(nameWidth)}${chalk5.blue(theme.pkg.padEnd(pkgWidth))}${theme.description}`
562
- );
563
- }
564
- console.log(chalk5.blue("\nInstall a theme:"));
565
- console.log(` ${chalk5.cyan("resuml themes --install <name>")}`);
566
- console.log(` ${chalk5.cyan("resuml themes --install stackoverflow")}
567
- `);
568
- console.log(
569
- chalk5.blue("Browse all themes: ") + "https://www.npmjs.com/search?q=jsonresume-theme\n"
570
- );
571
- }
572
- function installTheme(name) {
573
- const known = KNOWN_THEMES.find((t) => t.name === name);
574
- const pkg = known ? known.pkg : name.startsWith("jsonresume-theme-") ? name : `jsonresume-theme-${name}`;
575
- console.log(chalk5.blue(`
576
- \u{1F4E6} Installing ${pkg}...
577
- `));
578
- try {
579
- execSync(`npm install ${pkg}`, { stdio: "inherit" });
580
- console.log(chalk5.green(`
581
- \u2705 Successfully installed ${pkg}`));
582
- console.log(chalk5.blue(`
583
- Use it with: ${chalk5.cyan(`resuml render --theme ${known?.name || name}`)}
584
- `));
585
- } catch {
586
- console.error(chalk5.red(`
587
- \u274C Failed to install ${pkg}`));
588
- console.error(chalk5.yellow(`Make sure the package exists: https://www.npmjs.com/package/${pkg}
589
- `));
590
- }
591
- }
592
- function themesAction(options) {
593
- if (options.install) {
594
- installTheme(options.install);
595
- } else {
596
- listThemes();
597
- }
598
- }
599
-
600
- // src/commands/mcp.ts
601
- async function mcpAction() {
602
- const { startMcpServer } = await import("./mcp/server.js");
603
- await startMcpServer();
604
- }
605
-
606
- // src/utils/themeRender.ts
607
- var themeRender_exports = {};
608
- __export(themeRender_exports, {
609
- injectCss: () => injectCss,
610
- renderTheme: () => renderTheme
611
- });
612
- async function renderTheme(themeName, resumeData, themeConfig = {}, inlineCss, language = "en") {
613
- try {
614
- if (themeName.startsWith("jsonresume-")) {
615
- return await renderJsonResumeTheme(themeName, resumeData, inlineCss);
616
- } else {
617
- return await renderRyamlTheme(themeName, resumeData, themeConfig, inlineCss, language);
618
- }
619
- } catch (error) {
620
- if (error instanceof Error) {
621
- throw new Error(`Error rendering theme: ${error.message}`);
622
- }
623
- throw new Error("Unknown error rendering theme");
624
- }
625
- }
626
- async function renderJsonResumeTheme(themeName, resumeData, inlineCss) {
627
- let themePackageName;
628
- if (themeName.startsWith("jsonresume-theme-")) {
629
- themePackageName = themeName;
630
- } else if (themeName.startsWith("jsonresume-")) {
631
- themePackageName = `jsonresume-theme-${themeName.replace("jsonresume-", "")}`;
632
- } else {
633
- themePackageName = `jsonresume-theme-${themeName}`;
634
- }
635
- const jsonResumeData = resumeData;
636
- const themePackage = await import(themePackageName);
637
- const renderedHTML = themePackage.default.render(jsonResumeData);
638
- return {
639
- htmlOutput: injectCss(renderedHTML, inlineCss)
640
- };
641
- }
642
- async function renderRyamlTheme(themeName, resumeData, themeConfig, inlineCss, language = "en") {
643
- if (themeName === "default") {
644
- throw new Error("No default theme available. Please specify a specific theme name.");
645
- }
646
- const themePackageName = `@resuml/theme-${themeName}`;
647
- const themePackage = await import(themePackageName);
648
- const renderedHTML = themePackage.default.render(resumeData, themeConfig, language);
649
- return {
650
- htmlOutput: injectCss(renderedHTML, inlineCss)
651
- };
652
- }
653
- function injectCss(html, css) {
654
- if (!css) return html;
655
- return html.replace("</head>", `<style>${css}</style></head>`);
656
- }
657
-
658
- // src/index.ts
659
- var currentDir = path6.dirname(fileURLToPath(import.meta.url));
660
- function getCliVersion() {
661
- const packageJsonPath = path6.resolve(currentDir, "../package.json");
662
- if (fs9.existsSync(packageJsonPath)) {
663
- try {
664
- const packageJson = JSON.parse(fs9.readFileSync(packageJsonPath, "utf8"));
665
- return packageJson.version ?? "0.0.0";
666
- } catch {
667
- return "0.0.0";
668
- }
669
- }
670
- return "0.0.0";
671
- }
672
- var program = new Command();
673
- program.name("resuml").description("CLI tool for managing resuml resume files.").version(getCliVersion());
674
- program.command("validate").description("Validates resume data against the schema.").option("-r, --resume <path>", "Input YAML file, directory, or glob pattern.").option("--debug", "Show detailed validation errors.").option("--ats", "Run ATS (Applicant Tracking System) compatibility analysis.").option("--jd <path>", "Path to a job description file for keyword matching (requires --ats).").option("--ats-threshold <score>", "Minimum ATS score (0-100). Exit with code 1 if below threshold.").option("--format <type>", "Output format for ATS results (text or json).", "text").action(validateAction);
675
- program.command("tojson").description("Converts YAML resume data to JSON format.").option("-r, --resume <path>", "Input YAML file, directory, or glob pattern.").option("-o, --output <file>", "Output JSON file path.", "resume.json").option("--debug", "Show detailed validation and processing information.").action(toJsonAction);
676
- program.command("render").description("Renders the resume data using a specified theme.").option("-r, --resume <path>", "Input YAML file, directory, or glob pattern.").option("-t, --theme <name>", "Theme name (e.g., stackoverflow, react).").option("-o, --output <file>", "Output file path.").option("--format <type>", "Output format (html or pdf).", "html").option("--language <code>", "Language code for localization.", "en").option("--debug", "Show detailed validation and processing information.").action(renderAction);
677
- program.command("dev").description("Start development server with hot-reload.").option("-r, --resume <path>", "Input YAML file, directory, or glob pattern.").option("-t, --theme <name>", "Theme name (e.g., stackoverflow, react).").option("--port <number>", "Port for development server.", "3000").option("--language <code>", "Language code for localization.", "en").option("--debug", "Show detailed validation and processing information.").action(devAction);
678
- program.command("init").description("Scaffold a starter resume.yaml file with all sections.").option("-o, --output <file>", "Output YAML file path.", "resume.yaml").action(initAction);
679
- program.command("pdf").description("Export resume as PDF using Playwright.").option("-r, --resume <path>", "Input YAML file, directory, or glob pattern.").option("-t, --theme <name>", "Theme name (e.g., stackoverflow, react).").option("-o, --output <file>", "Output PDF file path.", "resume.pdf").option("--language <code>", "Language code for localization.", "en").option("--format <size>", "Page format: A4 or Letter.", "A4").option("--margin <values>", 'Page margins (e.g., "10mm" or "10mm,15mm,10mm,15mm").').option("--debug", "Show detailed validation and processing information.").action(pdfAction);
680
- program.command("themes").description("List available JSON Resume themes and install them.").option("--install <name>", "Install a theme by name (e.g., stackoverflow, elegant).").action(themesAction);
681
- program.command("mcp").description("Start MCP server for AI agent integration (stdio transport).").action(mcpAction);
682
- if (process.env["NODE_ENV"] !== "test") {
683
- void (async () => {
684
- try {
685
- await program.parseAsync(process.argv);
686
- } catch (e) {
687
- console.error("Command line error:", e.message);
688
- process.exit(1);
689
- }
690
- })();
691
- }
9
+ } from "./chunk-G4AN2EMI.js";
10
+ import {
11
+ analyzeAts
12
+ } from "./chunk-N55EPZ2N.js";
13
+ import "./chunk-QR77BRMN.js";
692
14
  export {
693
15
  analyzeAts,
694
16
  loadResumeFiles,