resuml 1.3.0 → 1.4.1
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/README.md +93 -1
- package/bin/resuml +21 -0
- package/dist/{api.js → api.cjs} +50 -45
- package/dist/api.cjs.map +1 -0
- package/dist/api.d.cts +9 -0
- package/dist/{index.js → index.cjs} +1180 -118
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +88 -0
- package/dist/{loadResume-BFCirLAW.d.ts → themeLoader-C7CBqNiC.d.cts} +14 -1
- package/package.json +53 -47
- package/scripts/build-builder.js +25 -0
- package/scripts/bundle-themes.js +233 -0
- package/scripts/dev-server.js +392 -0
- package/scripts/quick-bundle.cjs +129 -0
- package/dist/api.d.ts +0 -8
- package/dist/api.js.map +0 -1
- package/dist/index.d.ts +0 -55
- package/dist/index.js.map +0 -1
- /package/scripts/{generate-types.js → generate-types.cjs} +0 -0
package/README.md
CHANGED
|
@@ -88,6 +88,7 @@ npm install -g resuml
|
|
|
88
88
|
| Command | Description |
|
|
89
89
|
|---------|-------------|
|
|
90
90
|
| `validate` | Validate resume data against the JSON Resume schema |
|
|
91
|
+
| `validate --ats` | Run ATS (Applicant Tracking System) compatibility analysis |
|
|
91
92
|
| `tojson` | Convert YAML resume data to JSON format |
|
|
92
93
|
| `render` | Render the resume using a specified theme |
|
|
93
94
|
| `dev` | Start a development server with hot-reload |
|
|
@@ -102,6 +103,82 @@ npm install -g resuml
|
|
|
102
103
|
| `--port` | `-p` | Port for dev server (default: 3000) |
|
|
103
104
|
| `--language` | | Language code for localization (default: `en`) |
|
|
104
105
|
| `--debug` | | Enable debug mode for detailed errors |
|
|
106
|
+
| `--ats` | | Run ATS compatibility analysis (with `validate`) |
|
|
107
|
+
| `--jd` | | Path to job description file for keyword matching (with `--ats`) |
|
|
108
|
+
| `--ats-threshold` | | Minimum ATS score (0-100); exits with code 1 if below |
|
|
109
|
+
|
|
110
|
+
## ATS Analysis
|
|
111
|
+
|
|
112
|
+
Resumls built-in ATS (Applicant Tracking System) analysis helps ensure your resume passes automated screening. Fully offline and deterministic — no API keys or LLMs required.
|
|
113
|
+
|
|
114
|
+
### Quick Start
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Basic ATS score
|
|
118
|
+
resuml validate --resume resume.yaml --ats
|
|
119
|
+
|
|
120
|
+
# Match against a specific job description
|
|
121
|
+
resuml validate --resume resume.yaml --ats --jd job-description.txt
|
|
122
|
+
|
|
123
|
+
# CI/CD gate: fail if score is below threshold
|
|
124
|
+
resuml validate --resume resume.yaml --ats --ats-threshold 75
|
|
125
|
+
|
|
126
|
+
# Machine-readable JSON output
|
|
127
|
+
resuml validate --resume resume.yaml --ats --format json
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### What It Checks
|
|
131
|
+
|
|
132
|
+
The ATS engine runs 11 deterministic checks across 3 categories:
|
|
133
|
+
|
|
134
|
+
**Contact Information**
|
|
135
|
+
- Complete contact details (name, email, phone, city)
|
|
136
|
+
- LinkedIn profile present
|
|
137
|
+
|
|
138
|
+
**Content Quality**
|
|
139
|
+
- Professional summary (length and presence)
|
|
140
|
+
- Work highlights (minimum 2 per entry)
|
|
141
|
+
- Action verbs (highlights start with strong verbs)
|
|
142
|
+
- Quantified impact (numbers, percentages, metrics in highlights)
|
|
143
|
+
- No first-person pronouns
|
|
144
|
+
|
|
145
|
+
**Resume Structure**
|
|
146
|
+
- Date consistency (no unexplained gaps > 6 months)
|
|
147
|
+
- Skills populated (≥ 3 categories with keywords)
|
|
148
|
+
- Education completeness
|
|
149
|
+
- Essential sections present (basics, work, education, skills)
|
|
150
|
+
|
|
151
|
+
### Job Description Matching
|
|
152
|
+
|
|
153
|
+
Provide a job description file to get keyword matching:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
resuml validate --resume resume.yaml --ats --jd job.txt
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The engine extracts keywords from the job description using TF-based ranking with stem matching, then compares them against your resume. You get:
|
|
160
|
+
- **Match percentage** — how many JD keywords appear in your resume
|
|
161
|
+
- **Matched keywords** — what you already cover
|
|
162
|
+
- **Missing keywords** — what to consider adding
|
|
163
|
+
|
|
164
|
+
### Scoring
|
|
165
|
+
|
|
166
|
+
| Score | Rating | Meaning |
|
|
167
|
+
|-------|--------|-------------|
|
|
168
|
+
| 90-100 | Excellent | Resume is well-optimized for ATS |
|
|
169
|
+
| 75-89 | Good | Minor improvements possible |
|
|
170
|
+
| 60-74 | Needs Work | Several issues to address |
|
|
171
|
+
| 0-59 | Poor | Significant improvements needed |
|
|
172
|
+
|
|
173
|
+
When a job description is provided, the final score is 60% generic checks + 40% keyword match.
|
|
174
|
+
|
|
175
|
+
### Multi-Language Support
|
|
176
|
+
|
|
177
|
+
ATS checks support English and German, with language-specific action verb lists and pronoun detection. Use the `--language` flag:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
resuml validate --resume lebenslauf.yaml --ats --language de
|
|
181
|
+
```
|
|
105
182
|
|
|
106
183
|
## Compatible Themes
|
|
107
184
|
|
|
@@ -174,6 +251,9 @@ jobs:
|
|
|
174
251
|
- run: npm install -g resuml
|
|
175
252
|
- run: npm install jsonresume-theme-stackoverflow
|
|
176
253
|
|
|
254
|
+
# Validate and check ATS score (fails if below 75)
|
|
255
|
+
- run: resuml validate --resume resume.yaml --ats --ats-threshold 75
|
|
256
|
+
|
|
177
257
|
- run: resuml render --resume resume.yaml --theme stackoverflow --output resume.html
|
|
178
258
|
- run: resuml tojson --resume resume.yaml --output resume.json
|
|
179
259
|
|
|
@@ -194,7 +274,8 @@ import {
|
|
|
194
274
|
processResumeData,
|
|
195
275
|
loadResumeFiles,
|
|
196
276
|
loadTheme,
|
|
197
|
-
themeRender
|
|
277
|
+
themeRender,
|
|
278
|
+
analyzeAts
|
|
198
279
|
} from 'resuml';
|
|
199
280
|
|
|
200
281
|
// Load YAML files
|
|
@@ -205,6 +286,17 @@ const resume = await processResumeData(yamlContents);
|
|
|
205
286
|
const theme = await loadTheme('stackoverflow');
|
|
206
287
|
// Render HTML
|
|
207
288
|
const html = await theme.render(resume, { locale: 'en' });
|
|
289
|
+
|
|
290
|
+
// ATS analysis
|
|
291
|
+
const atsResult = analyzeAts(resume, { language: 'en' });
|
|
292
|
+
console.log(`ATS Score: ${atsResult.score}/100`);
|
|
293
|
+
|
|
294
|
+
// With job description matching
|
|
295
|
+
const jdResult = analyzeAts(resume, {
|
|
296
|
+
language: 'en',
|
|
297
|
+
jobDescription: 'Looking for a senior TypeScript developer...'
|
|
298
|
+
});
|
|
299
|
+
console.log(`Matched keywords: ${jdResult.keywords?.matched.join(', ')}`);
|
|
208
300
|
```
|
|
209
301
|
|
|
210
302
|
See the CLI and API for more details.
|
package/bin/resuml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-env node */
|
|
3
|
+
|
|
4
|
+
const { existsSync } = require('fs');
|
|
5
|
+
const { join } = require('path');
|
|
6
|
+
const distPath = join(__dirname, '../dist/index.js');
|
|
7
|
+
|
|
8
|
+
function startCli() {
|
|
9
|
+
try {
|
|
10
|
+
if (existsSync(distPath)) {
|
|
11
|
+
require(distPath);
|
|
12
|
+
} else {
|
|
13
|
+
throw new Error('CLI not built. Please run "npm run build" first.');
|
|
14
|
+
}
|
|
15
|
+
} catch (err) {
|
|
16
|
+
console.error('Error starting resuml CLI:', err);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
startCli();
|
package/dist/{api.js → api.cjs}
RENAMED
|
@@ -38,7 +38,7 @@ var getImportMetaUrl, importMetaUrl;
|
|
|
38
38
|
var init_cjs_shims = __esm({
|
|
39
39
|
"node_modules/tsup/assets/cjs_shims.js"() {
|
|
40
40
|
"use strict";
|
|
41
|
-
getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.
|
|
41
|
+
getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.src || new URL("main.js", document.baseURI).href;
|
|
42
42
|
importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
43
43
|
}
|
|
44
44
|
});
|
|
@@ -180,7 +180,7 @@ var require_brace_expansion = __commonJS({
|
|
|
180
180
|
var isSequence = isNumericSequence || isAlphaSequence;
|
|
181
181
|
var isOptions = m.body.indexOf(",") >= 0;
|
|
182
182
|
if (!isSequence && !isOptions) {
|
|
183
|
-
if (m.post.match(
|
|
183
|
+
if (m.post.match(/,.*\}/)) {
|
|
184
184
|
str = m.pre + "{" + m.body + escClose + m.post;
|
|
185
185
|
return expand2(str);
|
|
186
186
|
}
|
|
@@ -259,7 +259,7 @@ var themeLoader_exports = {};
|
|
|
259
259
|
__export(themeLoader_exports, {
|
|
260
260
|
loadTheme: () => loadTheme
|
|
261
261
|
});
|
|
262
|
-
|
|
262
|
+
function installTheme(packageName) {
|
|
263
263
|
try {
|
|
264
264
|
(0, import_child_process.execFileSync)("npm", ["install", packageName], {
|
|
265
265
|
stdio: ["inherit", "pipe", "pipe"],
|
|
@@ -269,7 +269,7 @@ async function installTheme(packageName) {
|
|
|
269
269
|
throw new Error(`Failed to install ${packageName}: ${error.message}`);
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
|
-
|
|
272
|
+
function loadTheme(themeName, options) {
|
|
273
273
|
let jsonResumeThemeName;
|
|
274
274
|
let nativeThemeName;
|
|
275
275
|
const autoInstall = options?.autoInstall !== false;
|
|
@@ -290,7 +290,7 @@ Please install the theme package manually.`
|
|
|
290
290
|
}
|
|
291
291
|
console.log(`\u{1F4E6} Theme ${jsonResumeThemeName} not found. Installing...`);
|
|
292
292
|
try {
|
|
293
|
-
|
|
293
|
+
installTheme(jsonResumeThemeName);
|
|
294
294
|
console.log(`\u2705 Successfully installed ${jsonResumeThemeName}`);
|
|
295
295
|
return require2(jsonResumeThemeName);
|
|
296
296
|
} catch (installError) {
|
|
@@ -331,7 +331,7 @@ init_cjs_shims();
|
|
|
331
331
|
// src/core.ts
|
|
332
332
|
init_cjs_shims();
|
|
333
333
|
var import_yaml = require("yaml");
|
|
334
|
-
var import_lodash = __toESM(require("lodash.merge"));
|
|
334
|
+
var import_lodash = __toESM(require("lodash.merge"), 1);
|
|
335
335
|
var import_schema = require("@jsonresume/schema");
|
|
336
336
|
async function processResumeData(yamlContents) {
|
|
337
337
|
if (yamlContents.length === 0) {
|
|
@@ -370,13 +370,13 @@ async function processResumeData(yamlContents) {
|
|
|
370
370
|
|
|
371
371
|
// src/utils/loadResume.ts
|
|
372
372
|
init_cjs_shims();
|
|
373
|
-
var import_promises3 = __toESM(require("fs/promises"));
|
|
374
|
-
var import_yaml2 = __toESM(require("yaml"));
|
|
373
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
374
|
+
var import_yaml2 = __toESM(require("yaml"), 1);
|
|
375
375
|
|
|
376
376
|
// src/utils/fileUtils.ts
|
|
377
377
|
init_cjs_shims();
|
|
378
|
-
var import_promises2 = __toESM(require("fs/promises"));
|
|
379
|
-
var import_path = __toESM(require("path"));
|
|
378
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
379
|
+
var import_path = __toESM(require("path"), 1);
|
|
380
380
|
|
|
381
381
|
// node_modules/glob/dist/esm/index.js
|
|
382
382
|
init_cjs_shims();
|
|
@@ -1066,7 +1066,7 @@ var path = {
|
|
|
1066
1066
|
};
|
|
1067
1067
|
var sep = defaultPlatform === "win32" ? path.win32.sep : path.posix.sep;
|
|
1068
1068
|
minimatch.sep = sep;
|
|
1069
|
-
var GLOBSTAR =
|
|
1069
|
+
var GLOBSTAR = Symbol("globstar **");
|
|
1070
1070
|
minimatch.GLOBSTAR = GLOBSTAR;
|
|
1071
1071
|
var qmark2 = "[^/]";
|
|
1072
1072
|
var star2 = qmark2 + "*?";
|
|
@@ -1771,6 +1771,7 @@ if (typeof AC === "undefined") {
|
|
|
1771
1771
|
};
|
|
1772
1772
|
}
|
|
1773
1773
|
var shouldWarn = (code) => !warned.has(code);
|
|
1774
|
+
var TYPE = Symbol("type");
|
|
1774
1775
|
var isPosInt = (n) => n && n === Math.floor(n) && n > 0 && isFinite(n);
|
|
1775
1776
|
var getUintArray = (max) => !isPosInt(max) ? null : max <= Math.pow(2, 8) ? Uint8Array : max <= Math.pow(2, 16) ? Uint16Array : max <= Math.pow(2, 32) ? Uint32Array : max <= Number.MAX_SAFE_INTEGER ? ZeroArray : null;
|
|
1776
1777
|
var ZeroArray = class extends Array {
|
|
@@ -3115,37 +3116,37 @@ var isStream = (s) => !!s && typeof s === "object" && (s instanceof Minipass ||
|
|
|
3115
3116
|
var isReadable = (s) => !!s && typeof s === "object" && s instanceof import_node_events.EventEmitter && typeof s.pipe === "function" && // node core Writable streams have a pipe() method, but it throws
|
|
3116
3117
|
s.pipe !== import_node_stream.default.Writable.prototype.pipe;
|
|
3117
3118
|
var isWritable = (s) => !!s && typeof s === "object" && s instanceof import_node_events.EventEmitter && typeof s.write === "function" && typeof s.end === "function";
|
|
3118
|
-
var EOF =
|
|
3119
|
-
var MAYBE_EMIT_END =
|
|
3120
|
-
var EMITTED_END =
|
|
3121
|
-
var EMITTING_END =
|
|
3122
|
-
var EMITTED_ERROR =
|
|
3123
|
-
var CLOSED =
|
|
3124
|
-
var READ =
|
|
3125
|
-
var FLUSH =
|
|
3126
|
-
var FLUSHCHUNK =
|
|
3127
|
-
var ENCODING =
|
|
3128
|
-
var DECODER =
|
|
3129
|
-
var FLOWING =
|
|
3130
|
-
var PAUSED =
|
|
3131
|
-
var RESUME =
|
|
3132
|
-
var BUFFER =
|
|
3133
|
-
var PIPES =
|
|
3134
|
-
var BUFFERLENGTH =
|
|
3135
|
-
var BUFFERPUSH =
|
|
3136
|
-
var BUFFERSHIFT =
|
|
3137
|
-
var OBJECTMODE =
|
|
3138
|
-
var DESTROYED =
|
|
3139
|
-
var ERROR =
|
|
3140
|
-
var EMITDATA =
|
|
3141
|
-
var EMITEND =
|
|
3142
|
-
var EMITEND2 =
|
|
3143
|
-
var ASYNC =
|
|
3144
|
-
var ABORT =
|
|
3145
|
-
var ABORTED =
|
|
3146
|
-
var SIGNAL =
|
|
3147
|
-
var DATALISTENERS =
|
|
3148
|
-
var DISCARDED =
|
|
3119
|
+
var EOF = Symbol("EOF");
|
|
3120
|
+
var MAYBE_EMIT_END = Symbol("maybeEmitEnd");
|
|
3121
|
+
var EMITTED_END = Symbol("emittedEnd");
|
|
3122
|
+
var EMITTING_END = Symbol("emittingEnd");
|
|
3123
|
+
var EMITTED_ERROR = Symbol("emittedError");
|
|
3124
|
+
var CLOSED = Symbol("closed");
|
|
3125
|
+
var READ = Symbol("read");
|
|
3126
|
+
var FLUSH = Symbol("flush");
|
|
3127
|
+
var FLUSHCHUNK = Symbol("flushChunk");
|
|
3128
|
+
var ENCODING = Symbol("encoding");
|
|
3129
|
+
var DECODER = Symbol("decoder");
|
|
3130
|
+
var FLOWING = Symbol("flowing");
|
|
3131
|
+
var PAUSED = Symbol("paused");
|
|
3132
|
+
var RESUME = Symbol("resume");
|
|
3133
|
+
var BUFFER = Symbol("buffer");
|
|
3134
|
+
var PIPES = Symbol("pipes");
|
|
3135
|
+
var BUFFERLENGTH = Symbol("bufferLength");
|
|
3136
|
+
var BUFFERPUSH = Symbol("bufferPush");
|
|
3137
|
+
var BUFFERSHIFT = Symbol("bufferShift");
|
|
3138
|
+
var OBJECTMODE = Symbol("objectMode");
|
|
3139
|
+
var DESTROYED = Symbol("destroyed");
|
|
3140
|
+
var ERROR = Symbol("error");
|
|
3141
|
+
var EMITDATA = Symbol("emitData");
|
|
3142
|
+
var EMITEND = Symbol("emitEnd");
|
|
3143
|
+
var EMITEND2 = Symbol("emitEnd2");
|
|
3144
|
+
var ASYNC = Symbol("async");
|
|
3145
|
+
var ABORT = Symbol("abort");
|
|
3146
|
+
var ABORTED = Symbol("aborted");
|
|
3147
|
+
var SIGNAL = Symbol("signal");
|
|
3148
|
+
var DATALISTENERS = Symbol("dataListeners");
|
|
3149
|
+
var DISCARDED = Symbol("discarded");
|
|
3149
3150
|
var defer = (fn) => Promise.resolve().then(fn);
|
|
3150
3151
|
var nodefer = (fn) => fn();
|
|
3151
3152
|
var isEndish = (ev) => ev === "end" || ev === "finish" || ev === "prefinish";
|
|
@@ -3184,7 +3185,7 @@ var PipeProxyErrors = class extends Pipe {
|
|
|
3184
3185
|
}
|
|
3185
3186
|
constructor(src, dest, opts) {
|
|
3186
3187
|
super(src, dest, opts);
|
|
3187
|
-
this.proxyErrors = (er) => dest.emit("error", er);
|
|
3188
|
+
this.proxyErrors = (er) => this.dest.emit("error", er);
|
|
3188
3189
|
src.on("error", this.proxyErrors);
|
|
3189
3190
|
}
|
|
3190
3191
|
};
|
|
@@ -3898,6 +3899,8 @@ var Minipass = class extends import_node_events.EventEmitter {
|
|
|
3898
3899
|
return: stop,
|
|
3899
3900
|
[Symbol.asyncIterator]() {
|
|
3900
3901
|
return this;
|
|
3902
|
+
},
|
|
3903
|
+
[Symbol.asyncDispose]: async () => {
|
|
3901
3904
|
}
|
|
3902
3905
|
};
|
|
3903
3906
|
}
|
|
@@ -3933,6 +3936,8 @@ var Minipass = class extends import_node_events.EventEmitter {
|
|
|
3933
3936
|
return: stop,
|
|
3934
3937
|
[Symbol.iterator]() {
|
|
3935
3938
|
return this;
|
|
3939
|
+
},
|
|
3940
|
+
[Symbol.dispose]: () => {
|
|
3936
3941
|
}
|
|
3937
3942
|
};
|
|
3938
3943
|
}
|
|
@@ -4058,7 +4063,7 @@ var ChildrenCache = class extends LRUCache {
|
|
|
4058
4063
|
});
|
|
4059
4064
|
}
|
|
4060
4065
|
};
|
|
4061
|
-
var setAsCwd =
|
|
4066
|
+
var setAsCwd = Symbol("PathScurry setAsCwd");
|
|
4062
4067
|
var PathBase = class {
|
|
4063
4068
|
/**
|
|
4064
4069
|
* the basename of this path
|
|
@@ -6848,4 +6853,4 @@ async function loadTheme2(themeName) {
|
|
|
6848
6853
|
loadTheme,
|
|
6849
6854
|
processResumeData
|
|
6850
6855
|
});
|
|
6851
|
-
//# sourceMappingURL=api.
|
|
6856
|
+
//# sourceMappingURL=api.cjs.map
|