updating generation code to straight JS
This commit is contained in:
382
generate_resume.js
Normal file
382
generate_resume.js
Normal file
@@ -0,0 +1,382 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user