@yamlresume/node 0.15.1 → 0.15.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,10 +16,10 @@ npm install @yamlresume/node
16
16
  ## Usage
17
17
 
18
18
  ```typescript
19
- import { buildResume, readResume } from '@yamlresume/node'
19
+ import { buildResumeFile, readResumeFile } from '@yamlresume/node'
20
20
 
21
- const { resume, validated } = readResume('resume.yaml')
22
- const { outputs } = await buildResume('resume.yaml')
21
+ const { resume, validated } = readResumeFile('resume.yaml')
22
+ const { outputs } = await buildResumeFile('resume.yaml')
23
23
  ```
24
24
 
25
25
  For command-line usage, see the
@@ -27,12 +27,12 @@ For command-line usage, see the
27
27
 
28
28
  ## API
29
29
 
30
- ### `readResume`
30
+ ### `readResumeFile`
31
31
 
32
32
  ```typescript
33
- function readResume(
33
+ function readResumeFile(
34
34
  resumePath: string,
35
- options?: ReadResumeOptions
35
+ options?: ReadResumeFileOptions
36
36
  ): ReadResumeResult
37
37
  ```
38
38
 
@@ -42,7 +42,7 @@ validation status (`'success' | 'failed' | 'unknown'`), and positional errors
42
42
  with line and column numbers if validation failed.
43
43
 
44
44
  ```typescript
45
- const { resume, validated, errors } = readResume('resume.yaml')
45
+ const { resume, validated, errors } = readResumeFile('resume.yaml')
46
46
 
47
47
  if (validated === 'failed') {
48
48
  for (const error of errors ?? []) {
@@ -63,12 +63,12 @@ function validateResume(
63
63
  Validate a raw YAML string against the resume schema. Returns positional
64
64
  errors sorted by line number, or an empty array if validation succeeds.
65
65
 
66
- ### `buildResume`
66
+ ### `buildResumeFile`
67
67
 
68
68
  ```typescript
69
- function buildResume(
69
+ function buildResumeFile(
70
70
  resumePath: string,
71
- options?: BuildResumeOptions
71
+ options?: BuildResumeFileOptions
72
72
  ): Promise<BuildResumeResult>
73
73
  ```
74
74
 
@@ -79,37 +79,37 @@ LaTeX compilation timeout, and an optional logger. Returns the list of
79
79
  generated file paths.
80
80
 
81
81
  ```typescript
82
- const { outputs } = await buildResume('resume.yaml', {
82
+ const { outputs } = await buildResumeFile('resume.yaml', {
83
83
  pdf: true,
84
84
  output: 'dist',
85
85
  })
86
86
  ```
87
87
 
88
- ### `newResume`
88
+ ### `newResumeFile`
89
89
 
90
90
  ```typescript
91
- function newResume(
92
- filename: string,
91
+ function newResumeFile(
92
+ resumePath: string,
93
93
  sampleId: string,
94
94
  language: LocaleLanguage,
95
- options?: NewResumeOptions
95
+ options?: NewResumeFileOptions
96
96
  ): void
97
97
  ```
98
98
 
99
99
  Create a new resume file from a curated sample resume.
100
100
 
101
101
  ```typescript
102
- newResume('resume.yaml', 'software-engineer', 'en')
102
+ newResumeFile('resume.yaml', 'software-engineer', 'en')
103
103
  ```
104
104
 
105
- ### `generateResume`
105
+ ### `generateResumeFile`
106
106
 
107
107
  ```typescript
108
- async function generateResume(
109
- filename: string,
108
+ async function generateResumeFile(
109
+ resumePath: string,
110
110
  position: string,
111
111
  language: string,
112
- options?: GenerateResumeOptions
112
+ options?: GenerateResumeFileOptions
113
113
  ): Promise<void>
114
114
  ```
115
115
 
@@ -117,12 +117,12 @@ Generate a new resume file with AI for a given position and language.
117
117
  Supports model selection, retries, streaming chunks via callback, and an
118
118
  optional logger.
119
119
 
120
- ### `watchResume`
120
+ ### `watchResumeFile`
121
121
 
122
122
  ```typescript
123
- function watchResume(
123
+ function watchResumeFile(
124
124
  resumePath: string,
125
- options?: BuildResumeOptions
125
+ options?: BuildResumeFileOptions
126
126
  ): chokidar.Watcher
127
127
  ```
128
128
 
@@ -138,7 +138,7 @@ so you can catch and inspect them uniformly:
138
138
  import { YAMLResumeError } from '@yamlresume/core'
139
139
 
140
140
  try {
141
- await buildResume('missing.yaml')
141
+ await buildResumeFile('missing.yaml')
142
142
  } catch (error) {
143
143
  if (error instanceof YAMLResumeError) {
144
144
  console.error(error.code, error.message)
package/dist/index.d.ts CHANGED
@@ -28,7 +28,7 @@ import * as chokidar from 'chokidar';
28
28
  /**
29
29
  * Options for building resume outputs.
30
30
  */
31
- interface BuildResumeOptions {
31
+ interface BuildResumeFileOptions {
32
32
  pdf?: boolean;
33
33
  validate?: boolean;
34
34
  output?: string;
@@ -52,7 +52,7 @@ interface BuildResumeResult {
52
52
  * output directory, and LaTeX compilation timeout.
53
53
  * @returns The list of generated output file paths.
54
54
  */
55
- declare function buildResume(resumePath: string, options?: BuildResumeOptions): Promise<BuildResumeResult>;
55
+ declare function buildResumeFile(resumePath: string, options?: BuildResumeFileOptions): Promise<BuildResumeResult>;
56
56
 
57
57
  /**
58
58
  * MIT License
@@ -81,7 +81,7 @@ declare function buildResume(resumePath: string, options?: BuildResumeOptions):
81
81
  /**
82
82
  * Options for generating a resume with AI.
83
83
  */
84
- interface GenerateResumeOptions {
84
+ interface GenerateResumeFileOptions {
85
85
  model?: string;
86
86
  baseURL?: string;
87
87
  maxRetries?: number;
@@ -91,13 +91,13 @@ interface GenerateResumeOptions {
91
91
  /**
92
92
  * Generate a new resume file with AI for a given position and language.
93
93
  *
94
- * @param filename - The output resume file path.
94
+ * @param resumePath - The output resume file path.
95
95
  * @param position - The target position or job title.
96
96
  * @param language - The target locale language.
97
97
  * @param options - Optional model, base URL, retry and callback settings.
98
98
  * @throws {YAMLResumeError} When the file already exists or writing fails.
99
99
  */
100
- declare function generateResume(filename: string, position: string, language: string, options?: GenerateResumeOptions): Promise<void>;
100
+ declare function generateResumeFile(resumePath: string, position: string, language: string, options?: GenerateResumeFileOptions): Promise<void>;
101
101
 
102
102
  /**
103
103
  * MIT License
@@ -126,14 +126,14 @@ declare function generateResume(filename: string, position: string, language: st
126
126
  /**
127
127
  * Options for creating a new resume from a sample.
128
128
  */
129
- interface NewResumeOptions {
129
+ interface NewResumeFileOptions {
130
130
  showSampleSource?: boolean;
131
131
  logger?: Logger;
132
132
  }
133
133
  /**
134
134
  * Creates a new resume file from a curated sample resume.
135
135
  *
136
- * @param filename - The name of the resume file to create.
136
+ * @param resumePath - The path of the resume file to create.
137
137
  * @param sampleId - The identifier of the sample resume to use.
138
138
  * @param language - The locale language of the sample resume.
139
139
  * @param options - Optional settings.
@@ -141,7 +141,7 @@ interface NewResumeOptions {
141
141
  * - FILE_CONFLICT: When the file already exists
142
142
  * - FILE_WRITE_ERROR: When there is an error writing the file
143
143
  */
144
- declare function newResume(filename: string, sampleId: string, language: LocaleLanguage, options?: NewResumeOptions): void;
144
+ declare function newResumeFile(resumePath: string, sampleId: string, language: LocaleLanguage, options?: NewResumeFileOptions): void;
145
145
 
146
146
  /**
147
147
  * MIT License
@@ -179,7 +179,7 @@ interface PositionalError {
179
179
  /**
180
180
  * Options for reading a resume file.
181
181
  */
182
- interface ReadResumeOptions {
182
+ interface ReadResumeFileOptions {
183
183
  validate?: boolean;
184
184
  }
185
185
  /**
@@ -214,7 +214,7 @@ declare function validateResume(yamlStr: string, schema: typeof ResumeSchema): P
214
214
  * @returns The resume object.
215
215
  * @throws {Error} If the source file cannot be read or is invalid.
216
216
  */
217
- declare function readResume(resumePath: string, options?: ReadResumeOptions): ReadResumeResult;
217
+ declare function readResumeFile(resumePath: string, options?: ReadResumeFileOptions): ReadResumeResult;
218
218
 
219
219
  /**
220
220
  * MIT License
@@ -257,6 +257,6 @@ declare const LATEX_COMPILE_TIMEOUT = 30;
257
257
  * @param options - Build and watch options
258
258
  * @returns Chokidar watcher instance
259
259
  */
260
- declare function watchResume(resumePath: string, options?: BuildResumeOptions): chokidar.FSWatcher;
260
+ declare function watchResumeFile(resumePath: string, options?: BuildResumeFileOptions): chokidar.FSWatcher;
261
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 };
262
+ export { type BuildResumeFileOptions, type BuildResumeResult, type GenerateResumeFileOptions, LATEX_COMPILE_TIMEOUT, type NewResumeFileOptions, type PositionalError, type ReadResumeFileOptions, type ReadResumeResult, buildResumeFile, generateResumeFile, newResumeFile, readResumeFile, validateResume, watchResumeFile };
package/dist/index.js CHANGED
@@ -1,2 +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
1
+ import F from'fs';import p from'path';import {YAMLResumeError,getErrorMessage,ResumeSchema,joinNonEmptyString,DEFAULT_RESUME_LAYOUTS,toCodeBlock,getResumeRenderer,LOCALE_LANGUAGE_OPTIONS}from'@yamlresume/core';import V,{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 pe from'chokidar';import {coalesce}from'coalescifn';function I(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 u=o.path,c=t.getIn(u,true),l=1,m=1;if(isNode(c)&&c.range){let d=c.range[0],g=a.linePos(d);l=g.line,m=g.col;}return {message:o.message,line:l,column:m,path:u}}).sort((o,u)=>o.line-u.line)}function h(e,n={}){let{validate:a=true}=n,t;try{t=F.readFileSync(e,"utf8");}catch{throw new YAMLResumeError("FILE_READ_ERROR",{path:e})}let i;try{i=V.parse(t);}catch(r){throw new YAMLResumeError("INVALID_YAML",{error:getErrorMessage(r)})}if(a){let r=I(t,ResumeSchema);return r.length>0?{resume:i,validated:"failed",errors:r}:{resume:i,validated:"success"}}return {resume:i,validated:"unknown"}}function v(e){try{return !!H.sync(e)}catch{return false}}function K(){if(v("xelatex"))return "xelatex";if(v("tectonic"))return "tectonic";throw new YAMLResumeError("LATEX_NOT_FOUND",{})}function L(e){return e.replace(/\.tex$/,".pdf")}function k(e,n){let a=p.extname(e);if(e.endsWith(".yaml")||e.endsWith(".yml")||e.endsWith(".json")){let t=p.basename(e.replace(/\.(yaml|yml|json)$/,".tex"));return n?p.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",p.basename(t)];break;case "tectonic":i="tectonic",r=[p.basename(t)];break}let o=n?p.resolve(n):p.dirname(p.resolve(t));return {command:i,args:r,cwd:o}}var f=30;function C(e,n){let a=n?p.resolve(n):p.dirname(p.resolve(e));return p.join(a,`${p.basename(e,".tex")}.aux`)}function S(e){try{return F.readFileSync(e,"utf8")}catch{return null}}async function O(e,n,a=f,t){let{command:i,args:r,cwd:o}=Q(e,n),u=C(e,n);t?.start(`Generating resume pdf file with command: \`${i} ${r.join(" ")}\`...`);let c=a===0?void 0:a*1e3,l=S(u),m=2;for(let d=0;d<m;d++){t?.debug(`Running LaTeX pass ${d+1}/${m}`);try{let s=await execa(i,r,{cwd:o,encoding:"utf8",timeout:c});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 g=S(u);if(l===g){t?.debug(`LaTeX compilation stabilized after ${d+1} pass(es)`);break}t?.debug("Auxiliary file changed, running LaTeX again..."),l=g;}t?.success(`Generated resume pdf file successfully: ${L(e)}`);}function re(e,n,a,t,i){let r=p.basename(e.replace(/\.(yaml|yml|json)$/,"")),o=t>1?`${r}.${a}${n}`:`${r}${n}`;return i?p.join(i,o):p.join(p.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 w(e,n,a,t,i,r,o,u){let c=re(e,r,a,t,i),l=p.dirname(c);F.existsSync(l)||F.mkdirSync(l,{recursive:true});let d=await getResumeRenderer(n,o).render();try{F.writeFileSync(c,d),u?.success(joinNonEmptyString([`Generated resume ${ne(r)} file successfully:`,c]," "));}catch{throw new YAMLResumeError("FILE_WRITE_ERROR",{path:c})}return c}async function T(e,n={}){let{pdf:a=true,validate:t=true,timeout:i=f,logger:r}=n,{resume:o,validated:u,errors:c}=h(e,{validate:t});if(u==="failed"&&c){r?.warn(joinNonEmptyString(["Resume schema validation failed for",e,"continuing to build anyway."]," "));for(let s of c)r?.warn(`${s.path.join(".")}: ${s.message}`);}let l=o.layouts??DEFAULT_RESUME_LAYOUTS;o.layouts||(o.layouts=l);let m={docx:l.filter(s=>s.engine==="docx").length,html:l.filter(s=>s.engine==="html").length,latex:l.filter(s=>s.engine==="latex").length,markdown:l.filter(s=>s.engine==="markdown").length},d={docx:0,html:0,latex:0,markdown:0},g=[];for(let s=0;s<l.length;s++)switch(l[s].engine){case "docx":{g.push(await w(e,o,d.docx++,m.docx,n.output,".docx",s,r));break}case "html":{g.push(await w(e,o,d.html++,m.html,n.output,".html",s,r));break}case "latex":{let y=await w(e,o,d.latex++,m.latex,n.output,".tex",s,r);g.push(y),a===true&&(await O(y,n.output,i,r),g.push(L(y)));break}case "markdown":{g.push(await w(e,o,d.markdown++,m.markdown,n.output,".md",s,r));break}}return {outputs:g}}function ae(e){if(!LOCALE_LANGUAGE_OPTIONS.includes(e))throw new YAMLResumeError("INVALID_LANGUAGE",{language:e})}async function ue(e,n,a,t={}){if(F.existsSync(e))throw new YAMLResumeError("FILE_CONFLICT",{path:e});ae(a);let{model:i,baseURL:r,maxRetries:o,onChunk:u,logger:c}=t;c?.start("Generating resume...");let l;try{l=await generateResume(n,a,{model:getModelFromEnv({...i&&{model:i},...r&&{baseURL:r}}),...o!==void 0&&{maxRetries:o},...u&&{onChunk:u}});}catch(m){throw c?.debug(joinNonEmptyString(["Error generating resume: ",toCodeBlock(getErrorMessage(m))])),m}try{F.writeFileSync(e,l),c?.success(`Generated ${e} successfully.`);}catch(m){throw c?.debug(joinNonEmptyString(["Error writing resume file: ",toCodeBlock(getErrorMessage(m))])),new YAMLResumeError("FILE_WRITE_ERROR",{path:e})}}function ge(e,n,a,t={}){let{showSampleSource:i=false,logger:r}=t;if(F.existsSync(e))throw new YAMLResumeError("FILE_CONFLICT",{path:e});let o=getSampleResume(n,a,{withLayouts:true,withComments:true});try{F.writeFileSync(e,o);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 Re(e,n={pdf:true,validate:true}){let{pdf:a,validate:t,output:i,logger:r}=n,o=coalesce(()=>T(e,{pdf:a,validate:t,output:i,logger:r}));o(),r?.start(`Watching file changes: ${e}...`);let u=pe.watch(e,{awaitWriteFinish:{stabilityThreshold:200,pollInterval:200},ignoreInitial:true});return u.on("change",()=>o()),u.on("add",()=>o()),u}export{f as LATEX_COMPILE_TIMEOUT,T as buildResumeFile,ue as generateResumeFile,ge as newResumeFile,h as readResumeFile,I as validateResume,Re as watchResumeFile};//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +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"]}
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","readResumeFile","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","buildResumeFile","pdf","validated","allLayouts","DEFAULT_RESUME_LAYOUTS","totals","l","indices","outputs","validateLocaleLanguage","language","LOCALE_LANGUAGE_OPTIONS","generateResumeFile","position","model","baseURL","maxRetries","onChunk","generateResume","getModelFromEnv","newResumeFile","sampleId","showSampleSource","sampleContent","getSampleResume","successMessage","watchResumeFile","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 ReadResumeFileOptions {\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 readResumeFile(\n resumePath: string,\n options: ReadResumeFileOptions = {}\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 { readResumeFile } from './read'\nimport { compileLaTeX, getPdfPath, LATEX_COMPILE_TIMEOUT } from './utils'\n\n/**\n * Options for building resume outputs.\n */\nexport interface BuildResumeFileOptions {\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 buildResumeFile(\n resumePath: string,\n options: BuildResumeFileOptions = {}\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 } = readResumeFile(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 { generateResume, getModelFromEnv } 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 GenerateResumeFileOptions {\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 resumePath - 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 generateResumeFile(\n resumePath: string,\n position: string,\n language: string,\n options: GenerateResumeFileOptions = {}\n): Promise<void> {\n if (fs.existsSync(resumePath)) {\n throw new YAMLResumeError('FILE_CONFLICT', { path: resumePath })\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 generateResume(position, language as LocaleLanguage, {\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(resumePath, content)\n logger?.success(`Generated ${resumePath} 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: resumePath })\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 getErrorMessage,\n joinNonEmptyString,\n type LocaleLanguage,\n type Logger,\n toCodeBlock,\n YAMLResumeError,\n} from '@yamlresume/core'\nimport { getSampleResume } from '@yamlresume/samples'\n\n/**\n * Options for creating a new resume from a sample.\n */\nexport interface NewResumeFileOptions {\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 resumePath - The path 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 newResumeFile(\n resumePath: string,\n sampleId: string,\n language: LocaleLanguage,\n options: NewResumeFileOptions = {}\n) {\n const { showSampleSource = false, logger } = options\n\n if (fs.existsSync(resumePath)) {\n throw new YAMLResumeError('FILE_CONFLICT', { path: resumePath })\n }\n\n const sampleContent = getSampleResume(sampleId, language, {\n withLayouts: true,\n withComments: true,\n })\n\n try {\n fs.writeFileSync(resumePath, sampleContent)\n\n const successMessage = showSampleSource\n ? `Created ${resumePath} from sample \"${sampleId}\" successfully.`\n : `Created ${resumePath} 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: resumePath })\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 BuildResumeFileOptions, buildResumeFile } 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 watchResumeFile(\n resumePath: string,\n options: BuildResumeFileOptions = { 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 buildResumeFile(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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yamlresume/node",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "description": "Node.js runtime support for YAMLResume",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -39,14 +39,14 @@
39
39
  "execa": "^10.0.0",
40
40
  "which": "^7.0.0",
41
41
  "yaml": "^2.9.0",
42
- "@yamlresume/ai": "0.15.1",
43
- "@yamlresume/samples": "0.15.1",
44
- "@yamlresume/core": "0.15.1"
42
+ "@yamlresume/ai": "0.15.2",
43
+ "@yamlresume/core": "0.15.2",
44
+ "@yamlresume/samples": "0.15.2"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^26.1.1",
48
48
  "@types/which": "^3.0.4",
49
- "@yamlresume/testing": "0.15.1"
49
+ "@yamlresume/testing": "0.15.2"
50
50
  },
51
51
  "publishConfig": {
52
52
  "access": "public"