Solid start at a resume generator. Should probably add an action to build the real thing so I can expose it

This commit is contained in:
2025-12-08 16:48:07 -06:00
commit 431e5d2999
12 changed files with 546 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules
generated_resume.html

13
.zed/tasks.json Normal file
View File

@@ -0,0 +1,13 @@
[
{
"label": "Build",
"command": "npm run build",
"use_new_terminal": false,
"allow_concurrent_runs": false,
"reveal": "always",
"reveal_target": "dock",
"hide": "never",
"shell": "system",
"tags": ["script", "build"]
}
]

20
build.js Normal file
View File

@@ -0,0 +1,20 @@
const ejs = require('ejs');
const fs = require('fs');
const path = require('path');
// File paths
const dataPath = path.join(__dirname, 'content.json');
const templatePath = path.join(__dirname, 'template.ejs');
const outputPath = path.join(__dirname, 'generated_resume.html');
// Read data and template
const jsonData = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
const template = fs.readFileSync(templatePath, 'utf-8');
// Render HTML
const html = ejs.render(template, jsonData);
// Write the output
fs.writeFileSync(outputPath, html, 'utf-8');
console.log(`Successfully generated resume at ${outputPath}`);

143
content.json Normal file
View File

@@ -0,0 +1,143 @@
{
"name": "Jared Kling",
"email": "jared@kling.dev",
"website": "https://portfolio.kling.dev",
"phone": "(630) 740-0130",
"header": "Experienced Engineer who transforms ideas into scalable products through technical leadership and team development. Successfully leading cross-functional teams, architecting major system migrations, and improving developer efficiency.",
"experience": {
"technologies": [
"JavaScript",
"TypeScript",
"NodeJS",
"React",
"MySQL",
"PHP",
"C#",
"ASP.NET",
"SQL Server",
"HTML",
"CSS",
"AWS",
"Git",
"GitLab",
"Windows",
"Linux",
"Docker",
"Kubernetes"
],
"positions": [
{
"company": "Suralink",
"website": "https://suralink.com",
"logo": {
"path": "./logos/suralink.png",
"includesName": false
},
"roles": [
{
"role": "Engineering Manager",
"dates": "2023 - 2025",
"bullets": [
"Managed 2 teams of 5 and oversaw the work of integrating with an offshore team.",
"Executed a migration to Auth0 for additional scalability and security, moving over 1M user accounts and having <10 support tickets.",
"Delivered key initiatives on time, such as: integrating with external partners, seamlessly transitioning a domain out of a monolith and into a microservice, increasing our scalability through proper metrics.",
"Built the platform to extract value of user uploaded files, enabling our users to work faster and more efficiently.",
"Reduced an external partner's API calls by 97% by introducing additional data points in our responses.",
"Assisted in revamping our processes around hiring, incident response, and deployment frequency."
]
},
{
"role": "Staff Software Engineer / Team Lead",
"dates": "2022 - 2023",
"bullets": [
"I led a team of 5 engineers through all sprint ceremonies and technical planning sessions",
"Responsible for a portion customer support, including doing video calls with customers",
"Mentored multiple engineers on a regular basis, leading to promotions for two of them"
]
},
{
"role": "Sr Software Engineer",
"dates": "2020 - 2022",
"bullets": [
"Improved our release cadence from a few times a year to deploying every sprint.",
"Increased developer efficiency by shifting the development environment locally and by increasing team documentation",
"Led many large projects such as migrating our Authentication to a microservice, introducing an API and creating a second product"
]
}
]
},
{
"company": "Livly",
"website": "https://www.livly.io",
"logo": {
"path": "./logos/livly.png",
"includesName": true
},
"roles": [
{
"role": "Sr Software Engineer",
"dates": "2019 - 2020",
"bullets": [
"Integrated getstream.io into the app to allow property managers to communicate with other members of the building.",
"Strengthened integration with 3rd party vendor to automate manual data cleansing efforts."
]
}
]
},
{
"company": "PayLease (now Zego)",
"website": "https://www.gozego.com",
"logo": {
"path": "./logos/paylease.png",
"includesName": true
},
"roles": [
{
"role": "Jr Software Engineer - Staff Software Engineer",
"dates": "2015 - 2019",
"bullets": [
"Progressed from Junior to Staff Software Engineer, eventually leading a squad of engineers and QA associates.",
"Architected and implemented major projects, including an in-house billing statement generation system and various partner integrations.",
"Introduced containerized testing environments using Docker and spearheaded the adoption of unit testing.",
"Managed development resources in AWS and integrated data from a company acquisition into the main platform."
]
}
]
},
{
"company": "Encompass Insurance (Allstate)",
"website": "https://www.encompassinsurance.com",
"logo": {
"path": "./logos/encompass.svg",
"includesName": true
},
"roles": [
{
"role": "Application Developer",
"dates": "2013 - 2014 (intern), 2014 - 2015",
"bullets": [
"Worked on a small team to build a user portal for viewing policy details",
"Built a support tool using internal APIs to search for policies"
]
}
]
}
]
},
"education": {
"university": "Illinois State University",
"location": "Normal, IL",
"dates": "2009-2010, 2011-2014",
"degree": "Bachelor of Science in Computer Science with Minor in Mathematics"
},
"links": [
{
"label": "Website",
"href": "https://portfolio.kling.dev"
},
{
"label": "Git",
"href": "https://git.kling.dev/jared"
}
]
}

Binary file not shown.

1
logos/encompass.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
logos/livly.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

BIN
logos/paylease.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
logos/suralink.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

95
package-lock.json generated Normal file
View File

@@ -0,0 +1,95 @@
{
"name": "resume-builder",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "resume-builder",
"version": "1.0.0",
"dependencies": {
"ejs": "^3.1.8"
}
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/ejs": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"license": "Apache-2.0",
"dependencies": {
"jake": "^10.8.5"
},
"bin": {
"ejs": "bin/cli.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/filelist": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
"license": "Apache-2.0",
"dependencies": {
"minimatch": "^5.0.1"
}
},
"node_modules/jake": {
"version": "10.9.4",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
"integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
"license": "Apache-2.0",
"dependencies": {
"async": "^3.2.6",
"filelist": "^1.0.4",
"picocolors": "^1.1.1"
},
"bin": {
"jake": "bin/cli.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
}
}
}

12
package.json Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "resume-builder",
"version": "1.0.0",
"description": "Builds an HTML resume from JSON data",
"main": "build.js",
"scripts": {
"build": "node build.js"
},
"dependencies": {
"ejs": "^3.1.8"
}
}

260
template.ejs Normal file
View File

@@ -0,0 +1,260 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= name %> - Resume</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.6;
color: #333;
background-color: #fff;
margin: 0;
padding: 0;
}
.container {
max-width: 900px;
margin: 1rem auto;
padding: 1rem;
}
header {
text-align: center;
border-bottom: 2px solid #e9ecef;
padding-bottom: 1.5rem;
margin-bottom: 2rem;
}
header h1 {
margin: 0;
font-size: 2.8rem;
color: #212529;
}
.contact-info {
margin-top: 0.5rem;
color: #6c757d;
}
.contact-info a {
color: #007bff;
text-decoration: none;
}
.contact-info a:hover {
text-decoration: underline;
}
.contact-info span {
margin: 0 0.75rem;
}
.links a {
color: #007bff;
text-decoration: none;
}
.links a:hover {
text-decoration: underline;
}
header p.summary {
margin-top: 1rem;
font-size: 1.1rem;
color: #495057;
max-width: 750px;
margin-left: auto;
margin-right: auto;
}
h2 {
font-size: 1.8rem;
color: #343a40;
border-bottom: 2px solid #007bff;
padding-bottom: 0.5rem;
margin-top: 2.5rem;
margin-bottom: 1.5rem;
}
.section {
margin-bottom: 2rem;
}
.technologies ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
.technologies li {
border: 1px solid #ddd;
color: #495057;
padding: 0.4rem 0.8rem;
border-radius: 4px;
font-size: 0.9rem;
font-weight: 500;
}
.job {
margin-bottom: 2rem;
padding-left: 1.5rem;
}
.job:last-child {
margin-bottom: 0;
}
.job-header {
display: flex;
}
.job-header h3 {
margin: 0;
margin-left: 10px;
font-size: 1.4rem;
color: #212529;
}
.logo {
max-height: 2rem;
}
.role {
margin-bottom: 1rem;
}
.role-title {
display: flex;
justify-content: space-between;
align-items: baseline;
flex-wrap: wrap;
}
.role-title h4 {
margin: 0;
font-size: 1.15rem;
font-weight: 600;
}
.role-title .dates {
font-style: italic;
color: #6c757d;
font-size: 0.95rem;
}
.role ul {
padding-left: 1.5rem;
margin-top: 0.5rem;
color: #495057;
}
.role ul li {
margin-bottom: 0.5rem;
}
.education-details {
padding: 1.5rem 0 1.5rem 1.5rem;
}
.education-details p {
margin: 0.25rem 0;
}
.education-details .university {
font-size: 1.2rem;
font-weight: 600;
color: #343a40;
}
.education-details .degree {
font-size: 1.1rem;
}
.education-details .dates {
color: #6c757d;
font-style: italic;
}
.links {
display: flex;
justify-content: center;
gap: 1rem;
}
@media (max-width: 768px) {
.container {
margin: 0;
padding: 1rem;
}
header h1 {
font-size: 2.2rem;
}
.contact-info {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
}
.contact-info span {
margin: 0.2rem 0;
}
.role-title {
flex-direction: column;
align-items: flex-start;
}
.role-title .dates {
margin-top: 0.25rem;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1><%= name %></h1>
<div class="contact-info">
<a href="mailto:<%= email %>"><%= email %></a>
<span>&bull;</span>
<span><%= phone %></span>
</div>
<p class="summary"><%= header %></p>
</header>
<main>
<section class="section technologies">
<h2>Core Technologies</h2>
<ul>
<% experience.technologies.forEach(function(tech) { %>
<li><%= tech %></li>
<% }); %>
</ul>
</section>
<section class="section experience">
<h2>Professional Experience</h2>
<% experience.positions.forEach(function(position) { %>
<div class="job">
<div class="job-header">
<% if (position.logo.path.length > 0) { %>
<a href="<%= position.website %>">
<img class="logo" src="<%= position.logo.path %>" alt="<%= position.company %>" title="<%= position.company %>" />
</a>
<% } %>
<% if (!position.logo.includesName) { %>
<h3 class="job-name"><%= position.company %></h3>
<% } %>
</div>
<% position.roles.forEach(function(role) { %>
<div class="role">
<div class="role-title">
<h4><%= role.role %></h4>
<span class="dates"><%= role.dates %></span>
</div>
<% if (role.bullets && role.bullets.join('').length > 0) { %>
<ul>
<% role.bullets.forEach(function(bullet) { %>
<li><%= bullet %></li>
<% }); %>
</ul>
<% } %>
</div>
<% }); %>
</div>
<% }); %>
</section>
<section class="section education">
<h2>Education</h2>
<div class="education-details">
<p class="university"><%= education.university %></p>
<p class="location"><%= education.location %></p>
<p class="degree"><%= education.degree %></p>
<p class="dates"><%= education.dates %></p>
</div>
</section>
<% if (links.length > 0) { %>
<section class="section links">
<% links.forEach(function(link) { %>
<a href="<%= link.href %>"><%= link.label %></a>
<% }); %>
</section>
<% } %>
</main>
</div>
</body>
</html>