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); });