@yamlresume/node 0.15.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023–Present PPResume (https://ppresume.com)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to
7
+ deal in the Software without restriction, including without limitation the
8
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9
+ sell copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # @yamlresume/node
2
+
3
+ Node.js runtime support for [YAMLResume](https://yamlresume.dev).
4
+
5
+ This package provides programmatic APIs for reading resume files and building
6
+ outputs (PDF, TeX, HTML, Markdown, Docx) from YAML/JSON resumes. It wraps
7
+ `@yamlresume/core` with Node.js-specific capabilities such as file system
8
+ access and LaTeX compilation.
9
+
10
+ ## Installation
11
+
12
+ ```sh
13
+ npm install @yamlresume/node
14
+ ```
15
+
16
+ ## Usage
17
+
18
+ ```typescript
19
+ import { buildResume, readResume } from '@yamlresume/node'
20
+
21
+ const { resume, validated } = readResume('resume.yaml')
22
+ const { outputs } = await buildResume('resume.yaml')
23
+ ```
24
+
25
+ For command-line usage, see the
26
+ [`yamlresume`](https://www.npmjs.com/package/yamlresume) package.
27
+
28
+ ## API
29
+
30
+ ### `readResume`
31
+
32
+ ```typescript
33
+ function readResume(
34
+ resumePath: string,
35
+ options?: ReadResumeOptions
36
+ ): ReadResumeResult
37
+ ```
38
+
39
+ Read the resume from the source file (YAML, YML, or JSON) and validate it
40
+ against the schema on request. The result includes the resume object, the
41
+ validation status (`'success' | 'failed' | 'unknown'`), and positional errors
42
+ with line and column numbers if validation failed.
43
+
44
+ ```typescript
45
+ const { resume, validated, errors } = readResume('resume.yaml')
46
+
47
+ if (validated === 'failed') {
48
+ for (const error of errors ?? []) {
49
+ console.log(`${error.path.join('.')}: ${error.message} (line ${error.line})`)
50
+ }
51
+ }
52
+ ```
53
+
54
+ ### `validateResume`
55
+
56
+ ```typescript
57
+ function validateResume(
58
+ yamlStr: string,
59
+ schema: typeof ResumeSchema
60
+ ): PositionalError[]
61
+ ```
62
+
63
+ Validate a raw YAML string against the resume schema. Returns positional
64
+ errors sorted by line number, or an empty array if validation succeeds.
65
+
66
+ ### `buildResume`
67
+
68
+ ```typescript
69
+ function buildResume(
70
+ resumePath: string,
71
+ options?: BuildResumeOptions
72
+ ): Promise<BuildResumeResult>
73
+ ```
74
+
75
+ Build a YAML resume into one or more outputs (`docx`, `html`, `tex`/`pdf`,
76
+ `markdown`) by iterating through the layouts configured in the resume's
77
+ `layouts` field. Options include PDF generation, validation, output directory,
78
+ LaTeX compilation timeout, and an optional logger. Returns the list of
79
+ generated file paths.
80
+
81
+ ```typescript
82
+ const { outputs } = await buildResume('resume.yaml', {
83
+ pdf: true,
84
+ output: 'dist',
85
+ })
86
+ ```
87
+
88
+ ### `newResume`
89
+
90
+ ```typescript
91
+ function newResume(
92
+ filename: string,
93
+ sampleId: string,
94
+ language: LocaleLanguage,
95
+ options?: NewResumeOptions
96
+ ): void
97
+ ```
98
+
99
+ Create a new resume file from a curated sample resume.
100
+
101
+ ```typescript
102
+ newResume('resume.yaml', 'software-engineer', 'en')
103
+ ```
104
+
105
+ ### `generateResume`
106
+
107
+ ```typescript
108
+ async function generateResume(
109
+ filename: string,
110
+ position: string,
111
+ language: string,
112
+ options?: GenerateResumeOptions
113
+ ): Promise<void>
114
+ ```
115
+
116
+ Generate a new resume file with AI for a given position and language.
117
+ Supports model selection, retries, streaming chunks via callback, and an
118
+ optional logger.
119
+
120
+ ### `watchResume`
121
+
122
+ ```typescript
123
+ function watchResume(
124
+ resumePath: string,
125
+ options?: BuildResumeOptions
126
+ ): chokidar.Watcher
127
+ ```
128
+
129
+ Watch a resume source file and rebuild outputs on changes. Uses `chokidar`
130
+ for robust watching (handles atomic saves from editors like vim), runs only
131
+ one build at a time, and coalesces bursts of change events into a single
132
+ follow-up build.
133
+
134
+ All functions throw `YAMLResumeError`s from `@yamlresume/core` on failure,
135
+ so you can catch and inspect them uniformly:
136
+
137
+ ```typescript
138
+ import { YAMLResumeError } from '@yamlresume/core'
139
+
140
+ try {
141
+ await buildResume('missing.yaml')
142
+ } catch (error) {
143
+ if (error instanceof YAMLResumeError) {
144
+ console.error(error.code, error.message)
145
+ }
146
+ }
147
+ ```
@@ -0,0 +1,262 @@
1
+ import { Logger, LocaleLanguage, Resume, ResumeSchema } from '@yamlresume/core';
2
+ import * as chokidar from 'chokidar';
3
+
4
+ /**
5
+ * MIT License
6
+ *
7
+ * Copyright (c) 2023–Present PPResume (https://ppresume.com)
8
+ *
9
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ * of this software and associated documentation files (the "Software"), to
11
+ * deal in the Software without restriction, including without limitation the
12
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
13
+ * sell copies of the Software, and to permit persons to whom the Software is
14
+ * furnished to do so, subject to the following conditions:
15
+ *
16
+ * The above copyright notice and this permission notice shall be included in
17
+ * all copies or substantial portions of the Software.
18
+ *
19
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
25
+ * IN THE SOFTWARE.
26
+ */
27
+
28
+ /**
29
+ * Options for building resume outputs.
30
+ */
31
+ interface BuildResumeOptions {
32
+ pdf?: boolean;
33
+ validate?: boolean;
34
+ output?: string;
35
+ timeout?: number;
36
+ logger?: Logger;
37
+ }
38
+ /**
39
+ * Result of building resume outputs.
40
+ */
41
+ interface BuildResumeResult {
42
+ outputs: string[];
43
+ }
44
+ /**
45
+ * Build a YAML resume to LaTeX & PDF and/or Markdown
46
+ *
47
+ * It first validates the resume against the schema (unless validation is
48
+ * disabled), then iterates through configured layouts to generate outputs.
49
+ *
50
+ * @param resumePath - The source resume file path (YAML, YML, or JSON).
51
+ * @param options - Build options including validation, PDF generation flags,
52
+ * output directory, and LaTeX compilation timeout.
53
+ * @returns The list of generated output file paths.
54
+ */
55
+ declare function buildResume(resumePath: string, options?: BuildResumeOptions): Promise<BuildResumeResult>;
56
+
57
+ /**
58
+ * MIT License
59
+ *
60
+ * Copyright (c) 2023–Present PPResume (https://ppresume.com)
61
+ *
62
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
63
+ * of this software and associated documentation files (the "Software"), to
64
+ * deal in the Software without restriction, including without limitation the
65
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
66
+ * sell copies of the Software, and to permit persons to whom the Software is
67
+ * furnished to do so, subject to the following conditions:
68
+ *
69
+ * The above copyright notice and this permission notice shall be included in
70
+ * all copies or substantial portions of the Software.
71
+ *
72
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
73
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
74
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
75
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
76
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
77
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
78
+ * IN THE SOFTWARE.
79
+ */
80
+
81
+ /**
82
+ * Options for generating a resume with AI.
83
+ */
84
+ interface GenerateResumeOptions {
85
+ model?: string;
86
+ baseURL?: string;
87
+ maxRetries?: number;
88
+ onChunk?: (chunk: string) => void;
89
+ logger?: Logger;
90
+ }
91
+ /**
92
+ * Generate a new resume file with AI for a given position and language.
93
+ *
94
+ * @param filename - The output resume file path.
95
+ * @param position - The target position or job title.
96
+ * @param language - The target locale language.
97
+ * @param options - Optional model, base URL, retry and callback settings.
98
+ * @throws {YAMLResumeError} When the file already exists or writing fails.
99
+ */
100
+ declare function generateResume(filename: string, position: string, language: string, options?: GenerateResumeOptions): Promise<void>;
101
+
102
+ /**
103
+ * MIT License
104
+ *
105
+ * Copyright (c) 2023–Present PPResume (https://ppresume.com)
106
+ *
107
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
108
+ * of this software and associated documentation files (the "Software"), to
109
+ * deal in the Software without restriction, including without limitation the
110
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
111
+ * sell copies of the Software, and to permit persons to whom the Software is
112
+ * furnished to do so, subject to the following conditions:
113
+ *
114
+ * The above copyright notice and this permission notice shall be included in
115
+ * all copies or substantial portions of the Software.
116
+ *
117
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
118
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
119
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
120
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
121
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
122
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
123
+ * IN THE SOFTWARE.
124
+ */
125
+
126
+ /**
127
+ * Options for creating a new resume from a sample.
128
+ */
129
+ interface NewResumeOptions {
130
+ showSampleSource?: boolean;
131
+ logger?: Logger;
132
+ }
133
+ /**
134
+ * Creates a new resume file from a curated sample resume.
135
+ *
136
+ * @param filename - The name of the resume file to create.
137
+ * @param sampleId - The identifier of the sample resume to use.
138
+ * @param language - The locale language of the sample resume.
139
+ * @param options - Optional settings.
140
+ * @throws {YAMLResumeError} When there are file-related errors:
141
+ * - FILE_CONFLICT: When the file already exists
142
+ * - FILE_WRITE_ERROR: When there is an error writing the file
143
+ */
144
+ declare function newResume(filename: string, sampleId: string, language: LocaleLanguage, options?: NewResumeOptions): void;
145
+
146
+ /**
147
+ * MIT License
148
+ *
149
+ * Copyright (c) 2023–Present PPResume (https://ppresume.com)
150
+ *
151
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
152
+ * of this software and associated documentation files (the "Software"), to
153
+ * deal in the Software without restriction, including without limitation the
154
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
155
+ * sell copies of the Software, and to permit persons to whom the Software is
156
+ * furnished to do so, subject to the following conditions:
157
+ *
158
+ * The above copyright notice and this permission notice shall be included in
159
+ * all copies or substantial portions of the Software.
160
+ *
161
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
162
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
163
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
164
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
165
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
166
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
167
+ * IN THE SOFTWARE.
168
+ */
169
+
170
+ /**
171
+ * A positional error with line number, column number, and path.
172
+ */
173
+ interface PositionalError {
174
+ message: string;
175
+ line: number;
176
+ column: number;
177
+ path: (string | number | symbol)[];
178
+ }
179
+ /**
180
+ * Options for reading a resume file.
181
+ */
182
+ interface ReadResumeOptions {
183
+ validate?: boolean;
184
+ }
185
+ /**
186
+ * The result of reading a resume file, including the resume object, validation
187
+ * status, and any validation errors.
188
+ */
189
+ interface ReadResumeResult {
190
+ resume: Resume;
191
+ validated: 'success' | 'failed' | 'unknown';
192
+ errors?: PositionalError[];
193
+ }
194
+ /**
195
+ * Validates a YAML string against a Zod schema and returns errors.
196
+ *
197
+ * @param yamlStr The YAML string to validate.
198
+ * @param schema The Zod schema to validate against.
199
+ * @returns A list of positional errors, or an empty array if validation is
200
+ * successful.
201
+ */
202
+ declare function validateResume(yamlStr: string, schema: typeof ResumeSchema): PositionalError[];
203
+ /**
204
+ * Read the resume from the source file and validate it on request.
205
+ *
206
+ * Steps:
207
+ *
208
+ * 1. read the resume from the source file
209
+ * 2. validate the resume with `yaml.parse`
210
+ * 3. if `validate` is true, validate the resume with `ResumeSchema`
211
+ *
212
+ * @param resumePath - The source resume file path (YAML, YML, or JSON).
213
+ * @param options - Options for reading and validating the resume.
214
+ * @returns The resume object.
215
+ * @throws {Error} If the source file cannot be read or is invalid.
216
+ */
217
+ declare function readResume(resumePath: string, options?: ReadResumeOptions): ReadResumeResult;
218
+
219
+ /**
220
+ * MIT License
221
+ *
222
+ * Copyright (c) 2023–Present PPResume (https://ppresume.com)
223
+ *
224
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
225
+ * of this software and associated documentation files (the "Software"), to
226
+ * deal in the Software without restriction, including without limitation the
227
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
228
+ * sell copies of the Software, and to permit persons to whom the Software is
229
+ * furnished to do so, subject to the following conditions:
230
+ *
231
+ * The above copyright notice and this permission notice shall be included in
232
+ * all copies or substantial portions of the Software.
233
+ *
234
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
235
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
236
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
237
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
238
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
239
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
240
+ * IN THE SOFTWARE.
241
+ */
242
+
243
+ /**
244
+ * Default timeout for LaTeX compilation in seconds
245
+ */
246
+ declare const LATEX_COMPILE_TIMEOUT = 30;
247
+
248
+ /**
249
+ * Watch a resume source file and rebuild on changes.
250
+ *
251
+ * - Only one build runs at a time.
252
+ * - If multiple events arrive during a build, run exactly one more build after
253
+ * it finishes (coalesce bursts).
254
+ * - Uses chokidar for robust file watching that handles editor operations.
255
+ *
256
+ * @param resumePath - The resume file to watch
257
+ * @param options - Build and watch options
258
+ * @returns Chokidar watcher instance
259
+ */
260
+ declare function watchResume(resumePath: string, options?: BuildResumeOptions): chokidar.FSWatcher;
261
+
262
+ export { type BuildResumeOptions, type BuildResumeResult, type GenerateResumeOptions, LATEX_COMPILE_TIMEOUT, type NewResumeOptions, type PositionalError, type ReadResumeOptions, type ReadResumeResult, buildResume, generateResume, newResume, readResume, validateResume, watchResume };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import T from'fs';import g from'path';import {YAMLResumeError,getErrorMessage,ResumeSchema,joinNonEmptyString,DEFAULT_RESUME_LAYOUTS,toCodeBlock,appendResumeLayouts,injectResumeComments,getResumeRenderer,LOCALE_LANGUAGE_OPTIONS}from'@yamlresume/core';import Y,{LineCounter,parseDocument,isNode}from'yaml';import {execa}from'execa';import H from'which';import {generateResume,getModelFromEnv}from'@yamlresume/ai';import {getSampleResume}from'@yamlresume/samples';import he from'chokidar';import {coalesce}from'coalescifn';function v(e,n){let a=new LineCounter,t=parseDocument(e,{lineCounter:a,keepSourceTokens:true}),i=n.safeParse(t.toJS());if(i.success)return [];let{error:{issues:r}}=i;return r.map(o=>{let c=o.path,m=t.getIn(c,true),u=1,l=1;if(isNode(m)&&m.range){let d=m.range[0],p=a.linePos(d);u=p.line,l=p.col;}return {message:o.message,line:u,column:l,path:c}}).sort((o,c)=>o.line-c.line)}function w(e,n={}){let{validate:a=true}=n,t;try{t=T.readFileSync(e,"utf8");}catch{throw new YAMLResumeError("FILE_READ_ERROR",{path:e})}let i;try{i=Y.parse(t);}catch(r){throw new YAMLResumeError("INVALID_YAML",{error:getErrorMessage(r)})}if(a){let r=v(t,ResumeSchema);return r.length>0?{resume:i,validated:"failed",errors:r}:{resume:i,validated:"success"}}return {resume:i,validated:"unknown"}}function S(e){try{return !!H.sync(e)}catch{return false}}function K(){if(S("xelatex"))return "xelatex";if(S("tectonic"))return "tectonic";throw new YAMLResumeError("LATEX_NOT_FOUND",{})}function x(e){return e.replace(/\.tex$/,".pdf")}function k(e,n){let a=g.extname(e);if(e.endsWith(".yaml")||e.endsWith(".yml")||e.endsWith(".json")){let t=g.basename(e.replace(/\.(yaml|yml|json)$/,".tex"));return n?g.join(n,t):e.replace(/\.(yaml|yml|json)$/,".tex")}throw new YAMLResumeError("INVALID_EXTNAME",{extname:a})}function Q(e,n){let a=K(),t=e.endsWith(".tex")?e:k(e,n),i="",r=[];switch(a){case "xelatex":i="xelatex",r=["-halt-on-error",g.basename(t)];break;case "tectonic":i="tectonic",r=[g.basename(t)];break}let o=n?g.resolve(n):g.dirname(g.resolve(t));return {command:i,args:r,cwd:o}}var f=30;function M(e,n){let a=n?g.resolve(n):g.dirname(g.resolve(e));return g.join(a,`${g.basename(e,".tex")}.aux`)}function C(e){try{return T.readFileSync(e,"utf8")}catch{return null}}async function O(e,n,a=f,t){let{command:i,args:r,cwd:o}=Q(e,n),c=M(e,n);t?.start(`Generating resume pdf file with command: \`${i} ${r.join(" ")}\`...`);let m=a===0?void 0:a*1e3,u=C(c),l=2;for(let d=0;d<l;d++){t?.debug(`Running LaTeX pass ${d+1}/${l}`);try{let s=await execa(i,r,{cwd:o,encoding:"utf8",timeout:m});t?.debug(joinNonEmptyString(["stdout: ",toCodeBlock(s.stdout)]));}catch(s){throw s.timedOut?(s.stdout&&(t?.info("LaTeX output before timeout:"),t?.log(s.stdout)),s.stderr&&(t?.info("LaTeX error output:"),t?.log(s.stderr)),new YAMLResumeError("LATEX_COMPILE_TIMEOUT",{timeout:String(a)})):(t?.debug(joinNonEmptyString(["stdout: ",toCodeBlock(s.stdout)])),t?.debug(joinNonEmptyString(["stderr: ",toCodeBlock(s.stderr)])),new YAMLResumeError("LATEX_COMPILE_ERROR",{error:s.message}))}let p=C(c);if(u===p){t?.debug(`LaTeX compilation stabilized after ${d+1} pass(es)`);break}t?.debug("Auxiliary file changed, running LaTeX again..."),u=p;}t?.success(`Generated resume pdf file successfully: ${x(e)}`);}function re(e,n,a,t,i){let r=g.basename(e.replace(/\.(yaml|yml|json)$/,"")),o=t>1?`${r}.${a}${n}`:`${r}${n}`;return i?g.join(i,o):g.join(g.dirname(e),o)}function ne(e){switch(e){case ".docx":return "docx";case ".html":return "html";case ".md":return "markdown";case ".tex":return "tex";default:return e.replace(".","")}}async function L(e,n,a,t,i,r,o,c){let m=re(e,r,a,t,i),u=g.dirname(m);T.existsSync(u)||T.mkdirSync(u,{recursive:true});let d=await getResumeRenderer(n,o).render();try{T.writeFileSync(m,d),c?.success(joinNonEmptyString([`Generated resume ${ne(r)} file successfully:`,m]," "));}catch{throw new YAMLResumeError("FILE_WRITE_ERROR",{path:m})}return m}async function A(e,n={}){let{pdf:a=true,validate:t=true,timeout:i=f,logger:r}=n,{resume:o,validated:c,errors:m}=w(e,{validate:t});if(c==="failed"&&m){r?.warn(joinNonEmptyString(["Resume schema validation failed for",e,"continuing to build anyway."]," "));for(let s of m)r?.warn(`${s.path.join(".")}: ${s.message}`);}let u=o.layouts??DEFAULT_RESUME_LAYOUTS;o.layouts||(o.layouts=u);let l={docx:u.filter(s=>s.engine==="docx").length,html:u.filter(s=>s.engine==="html").length,latex:u.filter(s=>s.engine==="latex").length,markdown:u.filter(s=>s.engine==="markdown").length},d={docx:0,html:0,latex:0,markdown:0},p=[];for(let s=0;s<u.length;s++)switch(u[s].engine){case "docx":{p.push(await L(e,o,d.docx++,l.docx,n.output,".docx",s,r));break}case "html":{p.push(await L(e,o,d.html++,l.html,n.output,".html",s,r));break}case "latex":{let y=await L(e,o,d.latex++,l.latex,n.output,".tex",s,r);p.push(y),a===true&&(await O(y,n.output,i,r),p.push(x(y)));break}case "markdown":{p.push(await L(e,o,d.markdown++,l.markdown,n.output,".md",s,r));break}}return {outputs:p}}function ae(e){if(!LOCALE_LANGUAGE_OPTIONS.includes(e))throw new YAMLResumeError("INVALID_LANGUAGE",{language:e})}async function ue(e,n,a,t={}){if(T.existsSync(e))throw new YAMLResumeError("FILE_CONFLICT",{path:e});ae(a);let{model:i,baseURL:r,maxRetries:o,onChunk:c,logger:m}=t;m?.start("Generating resume...");let u;try{u=await generateResume({position:n,language:a,model:getModelFromEnv({...i&&{model:i},...r&&{baseURL:r}}),...o!==void 0&&{maxRetries:o},...c&&{onChunk:c}});}catch(l){throw m?.debug(joinNonEmptyString(["Error generating resume: ",toCodeBlock(getErrorMessage(l))])),l}try{T.writeFileSync(e,u),m?.success(`Generated ${e} successfully.`);}catch(l){throw m?.debug(joinNonEmptyString(["Error writing resume file: ",toCodeBlock(getErrorMessage(l))])),new YAMLResumeError("FILE_WRITE_ERROR",{path:e})}}function Re(e,n,a,t={}){let{showSampleSource:i=false,logger:r}=t;if(T.existsSync(e))throw new YAMLResumeError("FILE_CONFLICT",{path:e});let o=getSampleResume(n,a),c=Y.parseDocument(o);appendResumeLayouts(c);let m=injectResumeComments(c);try{T.writeFileSync(e,m);let u=i?`Created ${e} from sample "${n}" successfully.`:`Created ${e} successfully.`;r?.success(u);}catch(u){throw r?.debug(joinNonEmptyString(["Error creating resume: ",toCodeBlock(getErrorMessage(u))])),new YAMLResumeError("FILE_WRITE_ERROR",{path:e})}}function Le(e,n={pdf:true,validate:true}){let{pdf:a,validate:t,output:i,logger:r}=n,o=coalesce(()=>A(e,{pdf:a,validate:t,output:i,logger:r}));o(),r?.start(`Watching file changes: ${e}...`);let c=he.watch(e,{awaitWriteFinish:{stabilityThreshold:200,pollInterval:200},ignoreInitial:true});return c.on("change",()=>o()),c.on("add",()=>o()),c}export{f as LATEX_COMPILE_TIMEOUT,A as buildResume,ue as generateResume,Re as newResume,w as readResume,v as validateResume,Le as watchResume};//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/read.ts","../src/utils/latex.ts","../src/build.ts","../src/generate.ts","../src/new.ts","../src/watch.ts"],"names":["validateResume","yamlStr","schema","lineCounter","LineCounter","resumeCST","parseDocument","validationResult","issues","issue","path","node","line","column","isNode","startOffset","pos","a","b","readResume","resumePath","options","validate","resumeStr","fs","YAMLResumeError","resume","yaml","error","getErrorMessage","errors","ResumeSchema","isCommandAvailable","command","which","inferLaTeXEnvironment","getPdfPath","texPath","inferOutput","outputDir","extname","baseName","inferLaTeXCommand","resumePathOrTexFile","environment","texFile","args","cwd","LATEX_COMPILE_TIMEOUT","getAuxPath","readAuxFile","auxPath","compileLaTeX","timeout","logger","execaTimeout","previousAux","maxRuns","run","result","execa","joinNonEmptyString","toCodeBlock","currentAux","getOutputPath","extension","index","total","fileName","normalizeExtension","generateOutput","layoutIndex","outputFile","dir","content","getResumeRenderer","buildResume","pdf","validated","allLayouts","DEFAULT_RESUME_LAYOUTS","totals","l","indices","outputs","validateLocaleLanguage","language","LOCALE_LANGUAGE_OPTIONS","generateResume","filename","position","model","baseURL","maxRetries","onChunk","generateResumeWithAI","getModelFromEnv","newResume","sampleId","showSampleSource","sampleContent","getSampleResume","doc","appendResumeLayouts","contentWithLayoutsAndComments","injectResumeComments","successMessage","watchResume","output","exclusiveBuild","coalesce","watcher","chokidar"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * MIT License\n *\n * Copyright (c) 2023–Present PPResume (https://ppresume.com)\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport fs from 'node:fs'\nimport {\n getErrorMessage,\n type Resume,\n ResumeSchema,\n YAMLResumeError,\n} from '@yamlresume/core'\nimport yaml, { isNode, LineCounter, parseDocument } from 'yaml'\n\n/**\n * A positional error with line number, column number, and path.\n */\nexport interface PositionalError {\n // The error message.\n message: string\n // The line number where the error occurred (1-based).\n line: number\n // The column number where the error occurred (1-based).\n column: number\n // The path to the property in the object where the error occurred.\n path: (string | number | symbol)[]\n}\n\n/**\n * Options for reading a resume file.\n */\nexport interface ReadResumeOptions {\n // Optional flag to validate the resume against the schema. Defaults to true.\n validate?: boolean\n}\n\n/**\n * The result of reading a resume file, including the resume object, validation\n * status, and any validation errors.\n */\nexport interface ReadResumeResult {\n // The resume object read from the file.\n resume: Resume\n // The validation status: 'success', 'failed', or 'unknown'.\n validated: 'success' | 'failed' | 'unknown'\n // An array of positional errors if validation failed, otherwise undefined.\n errors?: PositionalError[]\n}\n\n/**\n * Validates a YAML string against a Zod schema and returns errors.\n *\n * @param yamlStr The YAML string to validate.\n * @param schema The Zod schema to validate against.\n * @returns A list of positional errors, or an empty array if validation is\n * successful.\n */\nexport function validateResume(\n yamlStr: string,\n schema: typeof ResumeSchema\n): PositionalError[] {\n const lineCounter = new LineCounter()\n\n // CST: Concrete Syntax Tree\n const resumeCST = parseDocument(yamlStr, {\n lineCounter,\n keepSourceTokens: true,\n })\n\n const validationResult = schema.safeParse(resumeCST.toJS())\n\n if (validationResult.success) {\n return []\n }\n\n const {\n error: { issues },\n } = validationResult\n\n return issues\n .map((issue) => {\n const path = issue.path\n const node = resumeCST.getIn(path, true)\n\n let line = 1\n let column = 1\n\n if (isNode(node) && node.range) {\n const startOffset = node.range[0]\n const pos = lineCounter.linePos(startOffset)\n line = pos.line\n column = pos.col\n }\n\n return {\n message: issue.message,\n line,\n column,\n path,\n }\n })\n .sort((a, b) => a.line - b.line)\n}\n\n/**\n * Read the resume from the source file and validate it on request.\n *\n * Steps:\n *\n * 1. read the resume from the source file\n * 2. validate the resume with `yaml.parse`\n * 3. if `validate` is true, validate the resume with `ResumeSchema`\n *\n * @param resumePath - The source resume file path (YAML, YML, or JSON).\n * @param options - Options for reading and validating the resume.\n * @returns The resume object.\n * @throws {Error} If the source file cannot be read or is invalid.\n */\nexport function readResume(\n resumePath: string,\n options: ReadResumeOptions = {}\n): ReadResumeResult {\n const { validate = true } = options\n\n let resumeStr: string\n\n try {\n resumeStr = fs.readFileSync(resumePath, 'utf8')\n } catch (_error) {\n throw new YAMLResumeError('FILE_READ_ERROR', { path: resumePath })\n }\n\n let resume: Resume\n\n try {\n resume = yaml.parse(resumeStr) as Resume\n } catch (error) {\n throw new YAMLResumeError('INVALID_YAML', {\n error: getErrorMessage(error),\n })\n }\n\n if (validate) {\n const errors = validateResume(resumeStr, ResumeSchema)\n\n if (errors.length > 0) {\n return { resume, validated: 'failed', errors }\n }\n\n return { resume, validated: 'success' }\n }\n\n return { resume, validated: 'unknown' }\n}\n","/**\n * MIT License\n *\n * Copyright (c) 2023–Present PPResume (https://ppresume.com)\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport {\n joinNonEmptyString,\n type Logger,\n toCodeBlock,\n YAMLResumeError,\n} from '@yamlresume/core'\nimport { execa } from 'execa'\nimport which from 'which'\n\ntype LaTeXEnvironment = 'xelatex' | 'tectonic'\n\n/**\n * Check if a command is available\n *\n * @param command - The command to check\n * @returns True if the command is available, false otherwise\n */\nexport function isCommandAvailable(command: string): boolean {\n try {\n return !!which.sync(command)\n } catch {\n return false\n }\n}\n\n/**\n * Infer the LaTeX environment to use\n *\n * We support xelatex and tectonic, if both are installed we will prioritize\n * xelatex.\n *\n * @returns The LaTeX environment PATH.\n * @throws {Error} If neither 'xelatex' nor 'tectonic' is found in system PATH.\n */\nexport function inferLaTeXEnvironment(): LaTeXEnvironment {\n if (isCommandAvailable('xelatex')) {\n return 'xelatex'\n }\n\n if (isCommandAvailable('tectonic')) {\n return 'tectonic'\n }\n\n throw new YAMLResumeError('LATEX_NOT_FOUND', {})\n}\n\n/**\n * Get the PDF output path from a tex file path\n *\n * @param texPath - The tex file path\n * @returns The PDF file path\n */\nexport function getPdfPath(texPath: string): string {\n return texPath.replace(/\\.tex$/, '.pdf')\n}\n\n/**\n * Infer the output file name from the source file name\n *\n * For now we support yaml, yml and json file extensions, and the output file\n * will have a `.tex` extension based on the `layouts` config in the resume. The\n * output file will be placed in the same directory as the source file, or in\n * the specified output directory if provided.\n *\n * @param resumePath - The source resume file\n * @param outputDir - Optional output directory to place the file in\n * @returns The output file name\n * @throws {Error} If the source file has an unsupported extension.\n */\nexport function inferOutput(resumePath: string, outputDir?: string): string {\n const extname = path.extname(resumePath)\n\n if (\n resumePath.endsWith('.yaml') ||\n resumePath.endsWith('.yml') ||\n resumePath.endsWith('.json')\n ) {\n const baseName = path.basename(\n resumePath.replace(/\\.(yaml|yml|json)$/, '.tex')\n )\n if (outputDir) {\n return path.join(outputDir, baseName)\n }\n return resumePath.replace(/\\.(yaml|yml|json)$/, '.tex')\n }\n\n throw new YAMLResumeError('INVALID_EXTNAME', { extname })\n}\n\n/**\n * Infer the LaTeX command to use based on the LaTeX environment\n *\n * @param resumePathOrTexFile - The source resume file OR the target .tex file\n * @param outputDir - Optional output directory\n * @returns The LaTeX command\n * @throws {Error} If the LaTeX environment cannot be inferred or the source\n * file extension is unsupported.\n */\nexport function inferLaTeXCommand(\n resumePathOrTexFile: string,\n outputDir?: string\n): { command: string; args: string[]; cwd: string } {\n const environment = inferLaTeXEnvironment()\n\n // If the input is already a .tex file, use it directly; otherwise infer from .yaml/.json\n const texFile = resumePathOrTexFile.endsWith('.tex')\n ? resumePathOrTexFile\n : inferOutput(resumePathOrTexFile, outputDir)\n\n let command = ''\n let args: string[] = []\n\n switch (environment) {\n case 'xelatex':\n command = 'xelatex'\n args = ['-halt-on-error', path.basename(texFile)]\n break\n case 'tectonic':\n command = 'tectonic'\n args = [path.basename(texFile)]\n break\n }\n\n const cwd = outputDir\n ? path.resolve(outputDir)\n : path.dirname(path.resolve(texFile))\n\n return { command, args, cwd }\n}\n\n/**\n * Default timeout for LaTeX compilation in seconds\n */\nexport const LATEX_COMPILE_TIMEOUT = 30\n\n/**\n * Get the auxiliary file path for a tex file\n *\n * @param texFile - The TeX file path\n * @param outputDir - Optional output directory\n * @returns The auxiliary file path\n */\nexport function getAuxPath(texFile: string, outputDir?: string): string {\n const cwd = outputDir\n ? path.resolve(outputDir)\n : path.dirname(path.resolve(texFile))\n return path.join(cwd, `${path.basename(texFile, '.tex')}.aux`)\n}\n\n/**\n * Read the content of an auxiliary file\n *\n * @param auxPath - The auxiliary file path\n * @returns The file content, or null if the file does not exist\n */\nfunction readAuxFile(auxPath: string): string | null {\n try {\n return fs.readFileSync(auxPath, 'utf8')\n } catch {\n return null\n }\n}\n\n/**\n * Compile a TeX file to PDF\n *\n * Runs the LaTeX compiler repeatedly until auxiliary files stabilize, ensuring\n * correct page numbers and cross-references.\n *\n * @param texFile - The TeX file to compile.\n * @param outputDir - Optional output directory.\n * @param timeout - Timeout in seconds. 0 means no timeout.\n * @param logger - Optional logger for progress messages.\n */\nexport async function compileLaTeX(\n texFile: string,\n outputDir?: string,\n timeout: number = LATEX_COMPILE_TIMEOUT,\n logger?: Logger\n) {\n const { command, args, cwd } = inferLaTeXCommand(texFile, outputDir)\n const auxPath = getAuxPath(texFile, outputDir)\n\n logger?.start(\n `Generating resume pdf file with command: \\`${command} ${args.join(' ')}\\`...`\n )\n\n // When timeout is 0, disable timeout by setting it to undefined\n const execaTimeout = timeout === 0 ? undefined : timeout * 1000\n\n let previousAux = readAuxFile(auxPath)\n const maxRuns = 2\n\n for (let run = 0; run < maxRuns; run++) {\n logger?.debug(`Running LaTeX pass ${run + 1}/${maxRuns}`)\n\n try {\n const result = await execa(command, args, {\n cwd,\n encoding: 'utf8',\n timeout: execaTimeout,\n })\n logger?.debug(\n joinNonEmptyString(['stdout: ', toCodeBlock(result.stdout)])\n )\n } catch (error) {\n // Check if it's a timeout error\n if (error.timedOut) {\n // Show raw logs to help users diagnose the issue\n if (error.stdout) {\n logger?.info('LaTeX output before timeout:')\n logger?.log(error.stdout)\n }\n if (error.stderr) {\n logger?.info('LaTeX error output:')\n logger?.log(error.stderr)\n }\n throw new YAMLResumeError('LATEX_COMPILE_TIMEOUT', {\n timeout: String(timeout),\n })\n }\n\n logger?.debug(joinNonEmptyString(['stdout: ', toCodeBlock(error.stdout)]))\n logger?.debug(joinNonEmptyString(['stderr: ', toCodeBlock(error.stderr)]))\n throw new YAMLResumeError('LATEX_COMPILE_ERROR', { error: error.message })\n }\n\n const currentAux = readAuxFile(auxPath)\n if (previousAux === currentAux) {\n logger?.debug(`LaTeX compilation stabilized after ${run + 1} pass(es)`)\n break\n }\n\n logger?.debug('Auxiliary file changed, running LaTeX again...')\n previousAux = currentAux\n }\n\n logger?.success(\n `Generated resume pdf file successfully: ${getPdfPath(texFile)}`\n )\n}\n","/**\n * MIT License\n *\n * Copyright (c) 2023–Present PPResume (https://ppresume.com)\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport {\n DEFAULT_RESUME_LAYOUTS,\n getResumeRenderer,\n joinNonEmptyString,\n type Logger,\n type Resume,\n YAMLResumeError,\n} from '@yamlresume/core'\nimport { readResume } from './read'\nimport { compileLaTeX, getPdfPath, LATEX_COMPILE_TIMEOUT } from './utils'\n\n/**\n * Options for building resume outputs.\n */\nexport interface BuildResumeOptions {\n // Whether to generate PDF output from LaTeX. Defaults to true.\n pdf?: boolean\n // Whether to validate the resume against the schema before building. Defaults\n // to true.\n validate?: boolean\n // Optional output directory for generated files. If not specified, outputs to\n // current working directory.\n output?: string\n // Timeout in seconds for LaTeX compilation. Defaults to 30 seconds. Set to 0\n // to disable timeout.\n timeout?: number\n // Optional logger for progress messages. If not provided, no logs will be\n // shown.\n logger?: Logger\n}\n\n/**\n * Result of building resume outputs.\n */\nexport interface BuildResumeResult {\n outputs: string[]\n}\n\n/**\n * Get the output file path with support for multiple outputs and custom extension\n *\n * @param resumePath - The source resume file path\n * @param extension - The target file extension (e.g., '.tex', '.md')\n * @param index - The index of the current layout\n * @param total - The total number of layouts for this engine\n * @param outputDir - Optional output directory\n * @returns The determined output file path\n */\nfunction getOutputPath(\n resumePath: string,\n extension: string,\n index: number,\n total: number,\n outputDir?: string\n): string {\n const baseName = path.basename(resumePath.replace(/\\.(yaml|yml|json)$/, ''))\n\n // If there are multiple layouts, append the index to the filename\n // e.g., resume.0.tex, resume.1.tex\n // Otherwise, use the base filename\n // e.g., resume.tex\n const fileName =\n total > 1 ? `${baseName}.${index}${extension}` : `${baseName}${extension}`\n\n if (outputDir) {\n return path.join(outputDir, fileName)\n }\n return path.join(path.dirname(resumePath), fileName)\n}\n\n/**\n * Normalize the file extension that can be used in the output file name\n *\n * @param extension - file extension\n * @returns\n */\nexport function normalizeExtension(extension: string): string {\n switch (extension) {\n case '.docx':\n return 'docx'\n case '.html':\n return 'html'\n case '.md':\n return 'markdown'\n case '.tex':\n return 'tex'\n default:\n return extension.replace('.', '')\n }\n}\n\n/**\n * Shared helper to generate output file from a layout\n */\nasync function generateOutput(\n resumePath: string,\n resume: Resume,\n index: number,\n total: number,\n outputDir: string | undefined,\n extension: string,\n layoutIndex: number,\n logger?: Logger\n): Promise<string> {\n const outputFile = getOutputPath(\n resumePath,\n extension,\n index,\n total,\n outputDir\n )\n\n const dir = path.dirname(outputFile)\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true })\n }\n\n const renderer = getResumeRenderer(resume, layoutIndex)\n const content = await renderer.render()\n\n try {\n fs.writeFileSync(outputFile, content)\n logger?.success(\n joinNonEmptyString(\n [\n `Generated resume ${normalizeExtension(extension)} file successfully:`,\n outputFile,\n ],\n ' '\n )\n )\n } catch (_error) {\n throw new YAMLResumeError('FILE_WRITE_ERROR', { path: outputFile })\n }\n\n return outputFile\n}\n\n/**\n * Build a YAML resume to LaTeX & PDF and/or Markdown\n *\n * It first validates the resume against the schema (unless validation is\n * disabled), then iterates through configured layouts to generate outputs.\n *\n * @param resumePath - The source resume file path (YAML, YML, or JSON).\n * @param options - Build options including validation, PDF generation flags,\n * output directory, and LaTeX compilation timeout.\n * @returns The list of generated output file paths.\n */\nexport async function buildResume(\n resumePath: string,\n options: BuildResumeOptions = {}\n): Promise<BuildResumeResult> {\n const {\n pdf = true,\n validate = true,\n timeout = LATEX_COMPILE_TIMEOUT,\n logger,\n } = options\n\n const { resume, validated, errors } = readResume(resumePath, { validate })\n\n if (validated === 'failed' && errors) {\n logger?.warn(\n joinNonEmptyString(\n [\n 'Resume schema validation failed for',\n resumePath,\n 'continuing to build anyway.',\n ],\n ' '\n )\n )\n for (const error of errors) {\n logger?.warn(`${error.path.join('.')}: ${error.message}`)\n }\n }\n\n // Fallback to default layout if none provided\n const allLayouts = resume.layouts ?? DEFAULT_RESUME_LAYOUTS\n // Ensure resume has layouts for the renderer to use\n if (!resume.layouts) {\n resume.layouts = allLayouts\n }\n\n // Count totals for each engine to determine file naming strategy\n // (e.g. resume.0.tex vs resume.tex)\n const totals = {\n docx: allLayouts.filter((l) => l.engine === 'docx').length,\n html: allLayouts.filter((l) => l.engine === 'html').length,\n latex: allLayouts.filter((l) => l.engine === 'latex').length,\n markdown: allLayouts.filter((l) => l.engine === 'markdown').length,\n }\n\n // Track current index for each engine\n const indices = {\n docx: 0,\n html: 0,\n latex: 0,\n markdown: 0,\n }\n\n const outputs: string[] = []\n\n for (let layoutIndex = 0; layoutIndex < allLayouts.length; layoutIndex++) {\n const layout = allLayouts[layoutIndex]\n\n switch (layout.engine) {\n case 'docx': {\n outputs.push(\n await generateOutput(\n resumePath,\n resume,\n indices.docx++,\n totals.docx,\n options.output,\n '.docx',\n layoutIndex,\n logger\n )\n )\n break\n }\n case 'html': {\n outputs.push(\n await generateOutput(\n resumePath,\n resume,\n indices.html++,\n totals.html,\n options.output,\n '.html',\n layoutIndex,\n logger\n )\n )\n break\n }\n case 'latex': {\n const texFile = await generateOutput(\n resumePath,\n resume,\n indices.latex++,\n totals.latex,\n options.output,\n '.tex',\n layoutIndex,\n logger\n )\n outputs.push(texFile)\n\n if (pdf === true) {\n await compileLaTeX(texFile, options.output, timeout, logger)\n outputs.push(getPdfPath(texFile))\n }\n break\n }\n case 'markdown': {\n outputs.push(\n await generateOutput(\n resumePath,\n resume,\n indices.markdown++,\n totals.markdown,\n options.output,\n '.md',\n layoutIndex,\n logger\n )\n )\n break\n }\n }\n }\n\n return { outputs }\n}\n","/**\n * MIT License\n *\n * Copyright (c) 2023–Present PPResume (https://ppresume.com)\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport fs from 'node:fs'\nimport {\n generateResume as generateResumeWithAI,\n getModelFromEnv,\n} from '@yamlresume/ai'\nimport {\n getErrorMessage,\n joinNonEmptyString,\n LOCALE_LANGUAGE_OPTIONS,\n type LocaleLanguage,\n type Logger,\n toCodeBlock,\n YAMLResumeError,\n} from '@yamlresume/core'\n\n/**\n * Options for generating a resume with AI.\n */\nexport interface GenerateResumeOptions {\n // Optional settings for generating a resume with AI.\n model?: string\n // Optional base URL for the AI service.\n baseURL?: string\n // Optional maximum number of retries for AI generation.\n maxRetries?: number\n // Optional callback function to handle chunks of generated content.\n onChunk?: (chunk: string) => void\n // Optional logger for progress messages. If not provided, no logs will be\n // shown.\n logger?: Logger\n}\n\n/**\n * Validate that a locale language is supported by YAMLResume.\n *\n * @param language - The language code to validate.\n * @throws {YAMLResumeError} When the language is not supported.\n */\nexport function validateLocaleLanguage(\n language: string\n): asserts language is LocaleLanguage {\n if (\n !LOCALE_LANGUAGE_OPTIONS.includes(\n language as (typeof LOCALE_LANGUAGE_OPTIONS)[number]\n )\n ) {\n throw new YAMLResumeError('INVALID_LANGUAGE', { language })\n }\n}\n\n/**\n * Generate a new resume file with AI for a given position and language.\n *\n * @param filename - The output resume file path.\n * @param position - The target position or job title.\n * @param language - The target locale language.\n * @param options - Optional model, base URL, retry and callback settings.\n * @throws {YAMLResumeError} When the file already exists or writing fails.\n */\nexport async function generateResume(\n filename: string,\n position: string,\n language: string,\n options: GenerateResumeOptions = {}\n): Promise<void> {\n if (fs.existsSync(filename)) {\n throw new YAMLResumeError('FILE_CONFLICT', { path: filename })\n }\n\n validateLocaleLanguage(language)\n\n const { model, baseURL, maxRetries, onChunk, logger } = options\n\n logger?.start('Generating resume...')\n\n let content: string\n try {\n content = await generateResumeWithAI({\n position,\n language,\n model: getModelFromEnv({\n ...(model && { model }),\n ...(baseURL && { baseURL }),\n }),\n ...(maxRetries !== undefined && { maxRetries }),\n ...(onChunk && { onChunk }),\n })\n } catch (error) {\n logger?.debug(\n joinNonEmptyString([\n 'Error generating resume: ',\n toCodeBlock(getErrorMessage(error)),\n ])\n )\n throw error\n }\n\n try {\n fs.writeFileSync(filename, content)\n logger?.success(`Generated ${filename} successfully.`)\n } catch (error) {\n logger?.debug(\n joinNonEmptyString([\n 'Error writing resume file: ',\n toCodeBlock(getErrorMessage(error)),\n ])\n )\n throw new YAMLResumeError('FILE_WRITE_ERROR', { path: filename })\n }\n}\n","/**\n * MIT License\n *\n * Copyright (c) 2023–Present PPResume (https://ppresume.com)\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport fs from 'node:fs'\nimport {\n appendResumeLayouts,\n getErrorMessage,\n injectResumeComments,\n joinNonEmptyString,\n type LocaleLanguage,\n type Logger,\n toCodeBlock,\n YAMLResumeError,\n} from '@yamlresume/core'\nimport { getSampleResume } from '@yamlresume/samples'\nimport yaml from 'yaml'\n\n/**\n * Options for creating a new resume from a sample.\n */\nexport interface NewResumeOptions {\n // Optional flag to show the source of the sample resume in the success\n // message.\n showSampleSource?: boolean\n // Optional logger for progress messages. If not provided, no logs will be\n // shown.\n logger?: Logger\n}\n\n/**\n * Creates a new resume file from a curated sample resume.\n *\n * @param filename - The name of the resume file to create.\n * @param sampleId - The identifier of the sample resume to use.\n * @param language - The locale language of the sample resume.\n * @param options - Optional settings.\n * @throws {YAMLResumeError} When there are file-related errors:\n * - FILE_CONFLICT: When the file already exists\n * - FILE_WRITE_ERROR: When there is an error writing the file\n */\nexport function newResume(\n filename: string,\n sampleId: string,\n language: LocaleLanguage,\n options: NewResumeOptions = {}\n) {\n const { showSampleSource = false, logger } = options\n\n if (fs.existsSync(filename)) {\n throw new YAMLResumeError('FILE_CONFLICT', { path: filename })\n }\n\n const sampleContent = getSampleResume(sampleId, language)\n const doc = yaml.parseDocument(sampleContent)\n appendResumeLayouts(doc)\n const contentWithLayoutsAndComments = injectResumeComments(doc)\n\n try {\n fs.writeFileSync(filename, contentWithLayoutsAndComments)\n\n const successMessage = showSampleSource\n ? `Created ${filename} from sample \"${sampleId}\" successfully.`\n : `Created ${filename} successfully.`\n\n logger?.success(successMessage)\n } catch (error) {\n logger?.debug(\n joinNonEmptyString([\n 'Error creating resume: ',\n toCodeBlock(getErrorMessage(error)),\n ])\n )\n throw new YAMLResumeError('FILE_WRITE_ERROR', { path: filename })\n }\n}\n","/**\n * MIT License\n *\n * Copyright (c) 2023–Present PPResume (https://ppresume.com)\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport chokidar from 'chokidar'\nimport { coalesce } from 'coalescifn'\n\nimport { type BuildResumeOptions, buildResume } from './build'\n\n/**\n * Watch a resume source file and rebuild on changes.\n *\n * - Only one build runs at a time.\n * - If multiple events arrive during a build, run exactly one more build after\n * it finishes (coalesce bursts).\n * - Uses chokidar for robust file watching that handles editor operations.\n *\n * @param resumePath - The resume file to watch\n * @param options - Build and watch options\n * @returns Chokidar watcher instance\n */\nexport function watchResume(\n resumePath: string,\n options: BuildResumeOptions = { pdf: true, validate: true }\n) {\n const { pdf, validate, output, logger } = options\n\n // there should be only one build running at a time\n const exclusiveBuild = coalesce(() =>\n buildResume(resumePath, { pdf, validate, output, logger })\n )\n\n // initial build\n exclusiveBuild()\n\n logger?.start(`Watching file changes: ${resumePath}...`)\n\n // use chokidar for robust file watching that handles vim and other editors\n // properly.\n //\n // vim will save the file in a single atomic operation, that being said, it\n // will first create a temporary file (the '.swp' file), then rename it to the\n // final file.\n //\n // Node.js `fs.watch` has trouble with this, so we use chokidar instead.\n const watcher = chokidar.watch(resumePath, {\n awaitWriteFinish: {\n stabilityThreshold: 200, // wait 200ms after file stops changing\n pollInterval: 200, // check every 200ms\n },\n ignoreInitial: true, // don't trigger on initial file discovery\n })\n\n // handle file changes - chokidar's awaitWriteFinish already handles\n // debouncing\n watcher.on('change', () => exclusiveBuild())\n\n // handle file additions (in case file gets recreated)\n watcher.on('add', () => exclusiveBuild())\n\n return watcher\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@yamlresume/node",
3
+ "version": "0.15.1",
4
+ "description": "Node.js runtime support for YAMLResume",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "YAMLResume",
8
+ "email": "support@yamlresume.com",
9
+ "url": "https://yamlresume.dev"
10
+ },
11
+ "keywords": [
12
+ "YAMLResume",
13
+ "CV",
14
+ "Resume",
15
+ "LaTeX",
16
+ "Typesetting",
17
+ "PDF",
18
+ "YAML",
19
+ "JSON",
20
+ "Node.js"
21
+ ],
22
+ "type": "module",
23
+ "main": "./dist/index.js",
24
+ "types": "./dist/index.d.ts",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "dependencies": {
37
+ "chokidar": "^5.0.0",
38
+ "coalescifn": "^1.0.0",
39
+ "execa": "^10.0.0",
40
+ "which": "^7.0.0",
41
+ "yaml": "^2.9.0",
42
+ "@yamlresume/ai": "0.15.1",
43
+ "@yamlresume/samples": "0.15.1",
44
+ "@yamlresume/core": "0.15.1"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^26.1.1",
48
+ "@types/which": "^3.0.4",
49
+ "@yamlresume/testing": "0.15.1"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "https://github.com/yamlresume/yamlresume.git",
57
+ "directory": "packages/node"
58
+ },
59
+ "bugs": {
60
+ "url": "https://github.com/yamlresume/yamlresume/issues"
61
+ },
62
+ "homepage": "https://yamlresume.dev/docs/node",
63
+ "engines": {
64
+ "node": ">=20.0.0"
65
+ },
66
+ "scripts": {
67
+ "build": "tsup",
68
+ "build:watch": "tsup --watch",
69
+ "build:clean": "rm -rf dist",
70
+ "build:prod": "tsup --dts --minify --sourcemap",
71
+ "test": "vitest --run",
72
+ "test:cov": "vitest --coverage --run",
73
+ "test:watch": "vitest",
74
+ "typedoc": "typedoc"
75
+ }
76
+ }