build(types): provide type hints via JSDoc
This commit is contained in:
parent
6ecc806add
commit
dec4e36093
|
@ -17,6 +17,9 @@ jobs:
|
|||
- name: Install
|
||||
run: npm ci
|
||||
|
||||
- name: Type-check
|
||||
run: npm run type-check
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
|
|
|
@ -5,3 +5,4 @@ node_modules
|
|||
public
|
||||
npm-debug.log
|
||||
resume.html
|
||||
schema.d.ts
|
||||
|
|
|
@ -15,6 +15,7 @@ Inspired by [jsonresume-theme-flat](https://github.com/erming/jsonresume-theme-f
|
|||
- 🎨 Customizable colors
|
||||
- 🧩 Standalone CLI
|
||||
- 📦 ESM and CommonJS builds
|
||||
- 🤖 TypeScript typings
|
||||
|
||||
[View demo →](https://jsonresume-theme-even.rbrd.in)
|
||||
|
||||
|
|
|
@ -2,6 +2,10 @@ import html from '../utils/html.js'
|
|||
import markdown from '../utils/markdown.js'
|
||||
import Date from './date.js'
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['awards']} awards
|
||||
* @returns {string | false}
|
||||
*/
|
||||
export default function Awards(awards = []) {
|
||||
return awards.length > 0 && html`
|
||||
<section id="awards">
|
||||
|
|
|
@ -2,6 +2,10 @@ import html from '../utils/html.js'
|
|||
import Date from './date.js'
|
||||
import Link from './link.js'
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['certificates']} certificates
|
||||
* @returns {string | false}
|
||||
*/
|
||||
export default function Certificates(certificates = []) {
|
||||
return certificates.length > 0 && html`
|
||||
<section id="certificates">
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import html from '../utils/html.js'
|
||||
|
||||
/**
|
||||
* @param {string} dateString
|
||||
* @returns {string}
|
||||
*/
|
||||
const formatDate = dateString =>
|
||||
new Date(dateString).toLocaleDateString('en', {
|
||||
month: 'short',
|
||||
|
@ -7,6 +11,10 @@ const formatDate = dateString =>
|
|||
timeZone: 'UTC',
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {string} date
|
||||
* @returns {string}
|
||||
*/
|
||||
export default function Duration(date) {
|
||||
return html`<time datetime="${date}">${formatDate(date)}</time>`
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import html from '../utils/html.js'
|
||||
import Date from './date.js'
|
||||
|
||||
/**
|
||||
* @param {string} startDate
|
||||
* @param {string} [endDate]
|
||||
* @returns {string}
|
||||
*/
|
||||
export default function Duration(startDate, endDate) {
|
||||
return html`${Date(startDate)} – ${endDate ? Date(endDate) : 'Present'}`
|
||||
}
|
||||
|
|
|
@ -3,6 +3,10 @@ import markdown from '../utils/markdown.js'
|
|||
import Duration from './duration.js'
|
||||
import Link from './link.js'
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['education']} education
|
||||
* @returns {string | false}
|
||||
*/
|
||||
export default function Education(education = []) {
|
||||
return education.length > 0 && html`
|
||||
<section id="education">
|
||||
|
@ -14,7 +18,7 @@ export default function Education(education = []) {
|
|||
<h4>${Link(url, institution)}</h4>
|
||||
<div class="meta">
|
||||
${area && html`<strong>${area}</strong>`}
|
||||
<div>${Duration(startDate, endDate)}</div>
|
||||
${startDate && html`<div>${Duration(startDate, endDate)}</div>`}
|
||||
</div>
|
||||
</header>
|
||||
${studyType && markdown(studyType)}
|
||||
|
|
|
@ -3,10 +3,18 @@ import markdown from '../utils/markdown.js'
|
|||
import Icon from './icon.js'
|
||||
import Link from './link.js'
|
||||
|
||||
/**
|
||||
* @param {string} countryCode
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
const formatCountry = countryCode => Intl.DisplayNames
|
||||
? new Intl.DisplayNames(['en'], { type: 'region' }).of(countryCode)
|
||||
: countryCode
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['basics']} basics
|
||||
* @returns {string}
|
||||
*/
|
||||
export default function Header(basics = {}) {
|
||||
const { email, image, label, location, name, phone, profiles = [], summary, url } = basics
|
||||
|
||||
|
@ -45,7 +53,7 @@ export default function Header(basics = {}) {
|
|||
`}
|
||||
${profiles.map(({ network, url, username }) => html`
|
||||
<li>
|
||||
${Icon(network, 'user')}
|
||||
${network && Icon(network, 'user')}
|
||||
${Link(url, username)}
|
||||
${network && html`<span class="network">(${network})</span>`}
|
||||
</li>
|
||||
|
|
|
@ -1,7 +1,15 @@
|
|||
import feather from 'feather-icons'
|
||||
|
||||
/** @typedef {import('feather-icons').FeatherIconNames} FeatherIconNames */
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} [fallback]
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
export default function Icon(name, fallback) {
|
||||
const icon =
|
||||
feather.icons[name.toLowerCase()] || feather.icons[fallback.toLowerCase()]
|
||||
return icon.toSvg({ width: 16, height: 16 })
|
||||
feather.icons[/** @type {FeatherIconNames} */ (name.toLowerCase())] ||
|
||||
(fallback && feather.icons[/** @type {FeatherIconNames} */ (fallback.toLowerCase())])
|
||||
return icon?.toSvg({ width: 16, height: 16 })
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import html from '../utils/html.js'
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['interests']} interests
|
||||
* @returns {string | false}
|
||||
*/
|
||||
export default function Interests(interests = []) {
|
||||
return interests.length > 0 && html`
|
||||
<section id="interests">
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import html from '../utils/html.js'
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['languages']} languages
|
||||
* @returns {string | false}
|
||||
*/
|
||||
export default function Languages(languages = []) {
|
||||
return languages.length > 0 && html`
|
||||
<section id="languages">
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
import html from '../utils/html.js'
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @returns {string}
|
||||
*/
|
||||
const formatURL = url => url.replace(/^(https?:|)\/\//, '').replace(/\/$/, '')
|
||||
|
||||
/**
|
||||
* @param {string} [url]
|
||||
* @param {string} [name]
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
export default function Link(url, name) {
|
||||
return name
|
||||
? (url ? html`<a href="${url}">${name}</a>` : name)
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import html from '../utils/html.js'
|
||||
import markdown from '../utils/markdown.js'
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['basics']} basics
|
||||
* @returns {string}
|
||||
*/
|
||||
export default function Meta(basics = {}) {
|
||||
const { name, summary } = basics
|
||||
|
||||
|
|
|
@ -3,10 +3,18 @@ import markdown from '../utils/markdown.js'
|
|||
import Duration from './duration.js'
|
||||
import Link from './link.js'
|
||||
|
||||
const formatRoles = arr => Intl.ListFormat
|
||||
? new Intl.ListFormat('en').format(arr)
|
||||
: arr.join(', ')
|
||||
/**
|
||||
* @param {string[]} roles
|
||||
* @returns {string}
|
||||
*/
|
||||
const formatRoles = roles => Intl.ListFormat
|
||||
? new Intl.ListFormat('en').format(roles)
|
||||
: roles.join(', ')
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['projects']} projects
|
||||
* @returns {string | false}
|
||||
*/
|
||||
export default function Projects(projects = []) {
|
||||
return projects.length > 0 && html`
|
||||
<section id="projects">
|
||||
|
@ -21,7 +29,7 @@ export default function Projects(projects = []) {
|
|||
${roles.length > 0 && html`<strong>${formatRoles(roles)}</strong>`}
|
||||
${entity && html`at <strong>${entity}</strong>`}
|
||||
</div>
|
||||
<div>${Duration(startDate, endDate)}</div>
|
||||
${startDate && html`<div>${Duration(startDate, endDate)}</div>`}
|
||||
${type && html`<div>${type}</div>`}
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
@ -3,6 +3,10 @@ import markdown from '../utils/markdown.js'
|
|||
import Date from './date.js'
|
||||
import Link from './link.js'
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['publications']} publications
|
||||
* @returns {string | false}
|
||||
*/
|
||||
export default function Publications(publications = []) {
|
||||
return publications.length > 0 && html`
|
||||
<section id="publications">
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
import html from '../utils/html.js'
|
||||
import markdown from '../utils/markdown.js'
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['references']} references
|
||||
* @returns {string | false}
|
||||
*/
|
||||
export default function References(references = []) {
|
||||
return references.length > 0 && html`
|
||||
<section id="references">
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import html from '../utils/html.js'
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['skills']} skills
|
||||
* @returns {string | false}
|
||||
*/
|
||||
export default function Skills(skills = []) {
|
||||
return skills.length > 0 && html`
|
||||
<section id="skills">
|
||||
|
|
|
@ -3,6 +3,10 @@ import markdown from '../utils/markdown.js'
|
|||
import Duration from './duration.js'
|
||||
import Link from './link.js'
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['volunteer']} volunteer
|
||||
* @returns {string | false}
|
||||
*/
|
||||
export default function Volunteer(volunteer = []) {
|
||||
return volunteer.length > 0 && html`
|
||||
<section id="volunteer">
|
||||
|
@ -14,7 +18,7 @@ export default function Volunteer(volunteer = []) {
|
|||
<h4>${Link(url, organization)}</h4>
|
||||
<div class="meta">
|
||||
<strong>${position}</strong>
|
||||
<div>${Duration(startDate, endDate)}</div>
|
||||
${startDate && html`<div>${Duration(startDate, endDate)}</div>`}
|
||||
</div>
|
||||
</header>
|
||||
${summary && markdown(summary)}
|
||||
|
|
|
@ -3,13 +3,21 @@ import markdown from '../utils/markdown.js'
|
|||
import Duration from './duration.js'
|
||||
import Link from './link.js'
|
||||
|
||||
/** @typedef {NonNullable<import('../schema.d.ts').ResumeSchema['work']>[number]} Work */
|
||||
/** @typedef {Pick<Work, 'highlights' | 'location' | 'position' | 'startDate' | 'endDate' | 'summary'>} NestedWorkItem */
|
||||
/** @typedef {Pick<Work, 'description' | 'name' | 'url'> & { items: NestedWorkItem[] }} NestedWork */
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['work']} work
|
||||
* @returns {string | false}
|
||||
*/
|
||||
export default function Work(work = []) {
|
||||
const nestedWork = work.reduce((acc, { description, name, url, ...rest }) => {
|
||||
const prev = acc[acc.length - 1]
|
||||
if (prev && prev.name === name && prev.description === description && prev.url === url) prev.items.push(rest)
|
||||
else acc.push({ description, name, url, items: [rest] })
|
||||
return acc
|
||||
}, [])
|
||||
}, /** @type {NestedWork[]} */ ([]))
|
||||
|
||||
return work.length > 0 && html`
|
||||
<section id="work">
|
||||
|
@ -29,7 +37,7 @@ export default function Work(work = []) {
|
|||
<div>
|
||||
<h5>${position}</h5>
|
||||
<div class="meta">
|
||||
<div>${Duration(startDate, endDate)}</div>
|
||||
${startDate && html`<div>${Duration(startDate, endDate)}</div>`}
|
||||
${location && html`<div>${location}</div>`}
|
||||
</div>
|
||||
</div>
|
||||
|
|
5
index.js
5
index.js
|
@ -1,8 +1,13 @@
|
|||
import Resume from './resume.js'
|
||||
// @ts-expect-error `?inline` query
|
||||
import css from './style.css?inline'
|
||||
|
||||
export const pdfRenderOptions = { mediaType: 'print' }
|
||||
|
||||
/**
|
||||
* @param {import('./schema.d.ts').ResumeSchema} resume
|
||||
* @returns {string}
|
||||
*/
|
||||
export const render = resume => {
|
||||
return Resume(resume, css)
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
|
@ -28,6 +28,7 @@
|
|||
"main": "./dist/index.cjs",
|
||||
"unpkg": "./dist/index.umd.cjs",
|
||||
"module": "./dist/index.js",
|
||||
"typings": "./dist/index.d.ts",
|
||||
"source": "index.js",
|
||||
"bin": {
|
||||
"jsonresume-theme-even": "bin/cli.js"
|
||||
|
@ -41,9 +42,10 @@
|
|||
"dev": "vite",
|
||||
"format": "prettier .",
|
||||
"lint": "eslint --ignore-path .gitignore .",
|
||||
"prepare": "husky install",
|
||||
"prepare": "husky install && json2ts node_modules/resume-schema/schema.json schema.d.ts",
|
||||
"prepublishOnly": "npm run build",
|
||||
"test": "vitest"
|
||||
"test": "vitest",
|
||||
"type-check": "tsc"
|
||||
},
|
||||
"dependencies": {
|
||||
"feather-icons": "^4.28.0",
|
||||
|
@ -55,17 +57,22 @@
|
|||
"@codemirror/lang-json": "6.0.1",
|
||||
"@codemirror/theme-one-dark": "6.1.2",
|
||||
"@codemirror/view": "6.19.0",
|
||||
"@types/feather-icons": "4.29.2",
|
||||
"@vitest/coverage-v8": "0.34.3",
|
||||
"codemirror": "6.0.1",
|
||||
"debounce": "1.2.1",
|
||||
"eslint": "8.48.0",
|
||||
"html-validate": "8.3.0",
|
||||
"husky": "8.0.3",
|
||||
"json-schema-to-typescript": "13.1.1",
|
||||
"lint-staged": "14.0.1",
|
||||
"prettier": "3.0.2",
|
||||
"prettier-plugin-packagejson": "2.4.5",
|
||||
"resume-schema": "1.0.0",
|
||||
"typescript": "5.2.2",
|
||||
"vite": "4.4.9",
|
||||
"vite-plugin-dts": "3.5.3",
|
||||
"vite-plugin-static-copy": "0.17.0",
|
||||
"vitest": "0.34.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,11 @@ import Work from './components/work.js'
|
|||
import colors from './utils/colors.js'
|
||||
import html from './utils/html.js'
|
||||
|
||||
/**
|
||||
* @param {import('./schema.d.ts').ResumeSchema} resume
|
||||
* @param {string} css
|
||||
* @returns
|
||||
*/
|
||||
export default function Resume(resume, css) {
|
||||
return html`<!DOCTYPE html>
|
||||
<html lang="en" style="${colors(resume.meta)}">
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"include": ["./index.js"],
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"noEmit": true,
|
||||
"strict": true,
|
||||
"target": "esnext"
|
||||
}
|
||||
}
|
|
@ -1,5 +1,12 @@
|
|||
/** @typedef {Record<string, [light: string, dark?: string]>} ThemeColorOptions */
|
||||
/** @typedef {{ colors?: ThemeColorOptions }} ThemeOptions */
|
||||
|
||||
/**
|
||||
* @param {import('../schema.d.ts').ResumeSchema['meta'] & { themeOptions?: ThemeOptions }} meta
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
export default function colors(meta = {}) {
|
||||
const { colors } = meta.themeOptions || {}
|
||||
const colors = meta.themeOptions?.colors
|
||||
return (
|
||||
colors &&
|
||||
Object.entries(colors)
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
// Based on https://github.com/jimniels/html
|
||||
/**
|
||||
* @param {TemplateStringsArray} strings
|
||||
* @param {...any} values
|
||||
* @returns {string}
|
||||
*/
|
||||
export default function html(strings, ...values) {
|
||||
return strings.reduce((acc, string, i) => {
|
||||
const value = values[i]
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import micromark from 'micromark'
|
||||
import striptags from 'striptags'
|
||||
|
||||
/**
|
||||
* @param {string} doc
|
||||
* @param {boolean} [stripTags]
|
||||
* @returns
|
||||
*/
|
||||
export default function markdown(doc, stripTags = false) {
|
||||
const html = micromark(doc)
|
||||
// @ts-expect-error missing micromark types
|
||||
const html = /** @type {string} */ (micromark(doc))
|
||||
return stripTags ? striptags(html) : html
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import dts from 'vite-plugin-dts'
|
||||
import { viteStaticCopy } from 'vite-plugin-static-copy'
|
||||
import pkg from './package.json' assert { type: 'json' }
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
|
@ -25,9 +27,15 @@ export default defineConfig(({ mode }) => {
|
|||
external: [...Object.keys(pkg.dependencies), /^node:.*/],
|
||||
},
|
||||
target: 'esnext',
|
||||
test: {
|
||||
clearMocks: true,
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
dts(),
|
||||
viteStaticCopy({
|
||||
targets: [{ src: './schema.d.ts', dest: '.' }],
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
clearMocks: true,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue