Files
resume/generate_resume.js

383 lines
8.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const fs = require('fs');
const {
Document, Packer, Paragraph, TextRun, AlignmentType,
HeadingLevel, LevelFormat, ExternalHyperlink, BorderStyle
} = require('docx');
// ============================================
// CONFIGURATION - Modify these paths as needed
// ============================================
const INPUT_FILE = './resume.json';
const OUTPUT_FILE = `./Phillip_Tarrant_Resume_${new Date().getFullYear()}.docx`;
// Load resume data
const resumeData = JSON.parse(fs.readFileSync(INPUT_FILE, 'utf8'));
// ============================================
// HELPER FUNCTIONS
// ============================================
// Create a bullet point paragraph
function createBulletParagraph(text, reference) {
return new Paragraph({
numbering: { reference: reference, level: 0 },
spacing: { after: 60 },
children: [new TextRun({ text: text, size: 22, font: "Arial" })]
});
}
// Format date from YYYY-MM to "Mon YYYY"
function formatDate(dateStr) {
if (!dateStr) return '';
const date = new Date(dateStr + '-01');
return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
}
// ============================================
// BUILD DOCUMENT CONTENT
// ============================================
const children = [];
// --- HEADER SECTION ---
// Name - centered, bold, larger
children.push(new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 80 },
children: [new TextRun({
text: resumeData.basics.name,
bold: true,
size: 32, // 16pt
font: "Arial"
})]
}));
// Title/Label - centered
children.push(new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 80 },
children: [new TextRun({
text: resumeData.basics.label,
size: 24, // 12pt
font: "Arial"
})]
}));
// Contact line 1: email • phone
children.push(new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 40 },
children: [new TextRun({
text: `${resumeData.basics.email}${resumeData.basics.phone}`,
size: 22, // 11pt
font: "Arial"
})]
}));
// Contact line 2: LinkedIn • Location
const location = `${resumeData.basics.location.city}, ${resumeData.basics.location.region}`;
children.push(new Paragraph({
alignment: AlignmentType.CENTER,
spacing: { after: 200 },
children: [
new ExternalHyperlink({
link: resumeData.basics.url,
children: [new TextRun({
text: resumeData.basics.url.replace('https://', ''),
style: "Hyperlink",
size: 22,
font: "Arial"
})]
}),
new TextRun({
text: `${location}`,
size: 22,
font: "Arial"
})
]
}));
// --- SUMMARY SECTION ---
children.push(new Paragraph({
spacing: { after: 240 },
children: [new TextRun({
text: resumeData.basics.summary,
size: 22,
font: "Arial"
})]
}));
// --- CAREER EXPERIENCE SECTION ---
children.push(new Paragraph({
spacing: { before: 120, after: 200 },
children: [new TextRun({
text: "Career Experience",
bold: true,
size: 26, // 13pt
font: "Arial"
})]
}));
// Work experience entries
resumeData.work.forEach((job, index) => {
const startDate = formatDate(job.startDate);
const endDate = job.endDate ? formatDate(job.endDate) : 'Present';
const dateRange = `${startDate} ${endDate}`;
const companyLine = job.location ? `${job.name}, ${job.location}` : job.name;
// Job title and company
children.push(new Paragraph({
spacing: { before: 160, after: 60 },
children: [
new TextRun({
text: job.position,
bold: true,
size: 22,
font: "Arial"
}),
new TextRun({
text: `\n${companyLine}`,
size: 22,
font: "Arial"
}),
new TextRun({
text: ` ${dateRange}`,
size: 22,
font: "Arial",
italics: true
})
]
}));
// Job summary
if (job.summary) {
children.push(new Paragraph({
spacing: { after: 100 },
children: [new TextRun({
text: job.summary,
size: 22,
font: "Arial"
})]
}));
}
// Highlights as bullet points
if (job.highlights && job.highlights.length > 0) {
job.highlights.forEach(highlight => {
children.push(createBulletParagraph(highlight, "bullets"));
});
}
// Spacing after each job
children.push(new Paragraph({ spacing: { after: 120 }, children: [] }));
});
// --- EDUCATION, CERTIFICATIONS, AND AWARDS SECTION ---
children.push(new Paragraph({
spacing: { before: 200, after: 160 },
children: [new TextRun({
text: "Education, Certifications, and Awards",
bold: true,
size: 26,
font: "Arial"
})]
}));
// Certificates
if (resumeData.certificates) {
resumeData.certificates.forEach(cert => {
const year = cert.date ? new Date(cert.date).getFullYear() : '';
children.push(new Paragraph({
spacing: { after: 60 },
children: [
new TextRun({
text: cert.name,
bold: true,
size: 22,
font: "Arial"
}),
new TextRun({
text: `, ${cert.issuer}`,
size: 22,
font: "Arial"
}),
new TextRun({
text: year ? `, ${year}` : '',
size: 22,
font: "Arial"
})
]
}));
});
}
// Awards
if (resumeData.awards) {
resumeData.awards.forEach(award => {
const year = award.date ? new Date(award.date).getFullYear() : '';
children.push(new Paragraph({
spacing: { after: 60 },
children: [
new TextRun({
text: award.title,
bold: true,
size: 22,
font: "Arial"
}),
new TextRun({
text: `, ${award.awarder}`,
size: 22,
font: "Arial"
}),
new TextRun({
text: year ? `, ${year}` : '',
size: 22,
font: "Arial"
})
]
}));
if (award.summary) {
children.push(new Paragraph({
spacing: { after: 60 },
indent: { left: 360 },
children: [new TextRun({
text: award.summary,
size: 22,
font: "Arial",
italics: true
})]
}));
}
});
}
// Education
if (resumeData.education) {
resumeData.education.forEach(edu => {
const dateRange = edu.startDate && edu.endDate ? `${edu.startDate} ${edu.endDate}` : '';
children.push(new Paragraph({
spacing: { after: 60 },
children: [
new TextRun({
text: `${edu.studyType} in ${edu.area}`,
bold: true,
size: 22,
font: "Arial"
}),
new TextRun({
text: `, ${edu.institution}`,
size: 22,
font: "Arial"
}),
new TextRun({
text: dateRange ? `, ${dateRange}` : '',
size: 22,
font: "Arial"
})
]
}));
if (edu.score) {
children.push(new Paragraph({
spacing: { after: 60 },
indent: { left: 360 },
children: [new TextRun({
text: `GPA: ${edu.score}`,
size: 22,
font: "Arial",
italics: true
})]
}));
}
});
}
// --- TECHNICAL SKILLS SECTION ---
children.push(new Paragraph({
spacing: { before: 200, after: 160 },
children: [new TextRun({
text: "Technical Skills",
bold: true,
size: 26,
font: "Arial"
})]
}));
if (resumeData.skills) {
resumeData.skills.forEach(skillCategory => {
children.push(new Paragraph({
spacing: { after: 60 },
children: [
new TextRun({
text: `${skillCategory.name}: `,
bold: true,
size: 22,
font: "Arial"
}),
new TextRun({
text: skillCategory.keywords.join(", "),
size: 22,
font: "Arial"
})
]
}));
});
}
// ============================================
// CREATE AND SAVE DOCUMENT
// ============================================
const doc = new Document({
styles: {
default: {
document: {
run: { font: "Arial", size: 22 }
}
}
},
numbering: {
config: [
{
reference: "bullets",
levels: [{
level: 0,
format: LevelFormat.BULLET,
text: "•",
alignment: AlignmentType.LEFT,
style: {
paragraph: {
indent: { left: 720, hanging: 360 }
}
}
}]
}
]
},
sections: [{
properties: {
page: {
size: {
width: 12240, // 8.5 inches (1440 DXA = 1 inch)
height: 15840 // 11 inches
},
margin: {
top: 720, // 0.5 inch
right: 1080, // 0.75 inch
bottom: 720, // 0.5 inch
left: 1080 // 0.75 inch
}
}
},
children: children
}]
});
// Save the document
Packer.toBuffer(doc).then(buffer => {
fs.writeFileSync(OUTPUT_FILE, buffer);
console.log(`Resume created successfully: ${OUTPUT_FILE}`);
}).catch(err => {
console.error('Error creating document:', err);
process.exit(1);
});