├── screencast.gif
├── app-screencast.gif
├── .npmignore
├── app
├── assets
│ ├── kiss-my-resume.icns
│ ├── kiss-my-resume.ico
│ └── kiss-my-resume.png
├── blacklisted-themes.json
├── renderer
│ ├── components
│ │ └── App
│ │ │ ├── App.css
│ │ │ └── App.tsx
│ ├── hooks
│ │ └── useThemeList.tsx
│ └── renderer.tsx
├── index.html
├── index.css
├── bootstrap-override.css
├── preload.ts
├── definitions.ts
└── main
│ ├── preview.ts
│ ├── index.ts
│ ├── theme-helpers.ts
│ └── ipc-event-listeners.ts
├── declarations.d.ts
├── webpack.main.config.js
├── tsconfig.json
├── .eslintrc.json
├── webpack.rules.js
├── lib
├── convert.js
├── log.js
├── parse.js
├── validate.js
├── serve.js
├── build.js
└── cli.js
├── LICENSE
├── webpack.renderer.config.js
├── .gitignore
├── resume
└── empty-json-resume.json
├── webpack.plugins.js
├── package.json
├── README.md
└── schemes
├── json-resume-schema_0.0.0.json
└── fresh-resume-schema_1.0.0-beta.json
/screencast.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlitos/KissMyResume/HEAD/screencast.gif
--------------------------------------------------------------------------------
/app-screencast.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlitos/KissMyResume/HEAD/app-screencast.gif
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | resume.html
4 | resume.pdf
5 | resume.json
6 | .DS_Store
7 | .idea
8 |
--------------------------------------------------------------------------------
/app/assets/kiss-my-resume.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlitos/KissMyResume/HEAD/app/assets/kiss-my-resume.icns
--------------------------------------------------------------------------------
/app/assets/kiss-my-resume.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlitos/KissMyResume/HEAD/app/assets/kiss-my-resume.ico
--------------------------------------------------------------------------------
/app/assets/kiss-my-resume.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/karlitos/KissMyResume/HEAD/app/assets/kiss-my-resume.png
--------------------------------------------------------------------------------
/app/blacklisted-themes.json:
--------------------------------------------------------------------------------
1 | [
2 | "jsonresume-theme-moon",
3 | "jsonresume-theme-briefstrap",
4 | "jsonresume-theme-elite",
5 | "jsonresume-theme-qimia"
6 | ]
7 |
--------------------------------------------------------------------------------
/app/renderer/components/App/App.css:
--------------------------------------------------------------------------------
1 | .notification-area {
2 | height: 3.5em;
3 | overflow-y: auto;
4 | border: 1px ridge #ccc;
5 | border-radius: 4px;
6 | }
7 |
--------------------------------------------------------------------------------
/declarations.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css' {
2 | const styles: { [key: string]: string };
3 | export default styles;
4 | }
5 |
6 | declare module '*.svg' {
7 | const content: string;
8 | export default content;
9 | }
10 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Kiss My Resume
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/index.css:
--------------------------------------------------------------------------------
1 | @import './bootstrap-override.css';
2 |
3 | body {
4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif !important;
5 | margin: auto !important;
6 | padding: 2rem !important;
7 | }
8 |
9 | /* Some generic global classes/helpers */
10 | .float-left {
11 | float: left;
12 | }
13 |
14 | .float-right {
15 | float: right;
16 | }
17 |
--------------------------------------------------------------------------------
/app/bootstrap-override.css:
--------------------------------------------------------------------------------
1 | /* Globally overrides bootstrap classes */
2 |
3 | .alert-slim { /* Less margin and vertical padding */
4 | margin: 5px;
5 | padding: 5px 15px;
6 | }
7 |
8 | .row-no-gutter {
9 | margin-right: 0;
10 | margin-left: 0;
11 | }
12 |
13 | .form-control {
14 | height: 36px;
15 | }
16 |
17 | .btn .caret, .dropdown-menu > li > .checkbox {
18 | margin-left: 1em;
19 | }
20 |
--------------------------------------------------------------------------------
/app/preload.ts:
--------------------------------------------------------------------------------
1 | import { contextBridge, ipcRenderer } from 'electron';
2 | import { VALID_INVOKE_CHANNELS } from './definitions';
3 |
4 | // see https://stackoverflow.com/a/59888788/1991697
5 | contextBridge.exposeInMainWorld('api', {
6 | invoke: (chanel: string, ...data: any[]) => {
7 | if (chanel in VALID_INVOKE_CHANNELS) {
8 | return ipcRenderer.invoke(chanel, ...data);
9 | }
10 | return Promise.reject('Invoke not allowed');
11 | }
12 | }
13 | );
14 |
--------------------------------------------------------------------------------
/webpack.main.config.js:
--------------------------------------------------------------------------------
1 | const plugins = require('./webpack.plugins');
2 |
3 | module.exports = {
4 | /**
5 | * This is the main entry point for your application, it's the first file
6 | * that runs in the main process.
7 | */
8 | entry: './app/main/index.ts',
9 | // Put your normal webpack config below here
10 | module: {
11 | rules: require('./webpack.rules'),
12 | },
13 | plugins: [plugins.copyPlugin,],
14 | resolve: {
15 | extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json']
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "module": "commonjs",
5 | "skipLibCheck": true,
6 | "esModuleInterop": true,
7 | "noImplicitAny": true,
8 | "sourceMap": true,
9 | "baseUrl": ".",
10 | "outDir": "dist",
11 | "moduleResolution": "node",
12 | "resolveJsonModule": true,
13 | "paths": {
14 | "*": ["node_modules/*"]
15 | },
16 | "jsx": "react"
17 | },
18 | "include": [
19 | "app/**/*"
20 | ],
21 | "files": [
22 | "declarations.d.ts"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": [
8 | "eslint:recommended",
9 | "plugin:@typescript-eslint/eslint-recommended",
10 | "plugin:@typescript-eslint/recommended",
11 | "plugin:import/errors",
12 | "plugin:import/warnings"
13 | ],
14 | "parser": "@typescript-eslint/parser",
15 | "settings": {
16 | "import/resolver": {
17 | "node": {
18 | "extensions": [".js", ".jsx", ".ts", ".tsx"]
19 | }
20 | }
21 | },
22 | "rules": {
23 | "object-curly-spacing": ["error", "always"]
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/webpack.rules.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | // Add support for native node modules
3 | {
4 | test: /\.node$/,
5 | use: 'node-loader',
6 | },
7 | /*
8 | {
9 | test: /\.(m?js|node)$/,
10 | parser: { amd: false },
11 | use: {
12 | loader: '@marshallofsound/webpack-asset-relocator-loader',
13 | options: {
14 | outputAssetBase: 'native_modules',
15 | },
16 | },
17 | },
18 | */
19 | {
20 | test: /\.tsx?$/,
21 | exclude: /(node_modules|\.webpack)/,
22 | use: {
23 | loader: 'ts-loader',
24 | options: {
25 | transpileOnly: true
26 | }
27 | }
28 | },
29 | ];
30 |
--------------------------------------------------------------------------------
/app/definitions.ts:
--------------------------------------------------------------------------------
1 | // Fixes the typescript errors cause by having the api property on the window object
2 | declare global {
3 | interface Window {
4 | api: Record;
5 | }
6 | }
7 |
8 | // The enum keys and values has to be same, otherwise the reverse lookup and the 'in' operator won't work
9 | export enum VALID_INVOKE_CHANNELS {
10 | 'open-cv' = 'open-cv',
11 | 'process-cv' = 'process-cv',
12 | 'save-cv' = 'save-cv',
13 | 'get-theme-list' = 'get-theme-list',
14 | 'fetch-theme' = 'fetch-theme',
15 | }
16 |
17 | export interface INotification {
18 | type: 'success' | 'info' | 'warning' | 'danger';
19 | text: string;
20 | }
21 |
22 | export interface IThemeEntry {
23 | name: string ,
24 | description: string,
25 | version: string,
26 | downloadLink: string,
27 | present: boolean,
28 | }
29 |
--------------------------------------------------------------------------------
/lib/convert.js:
--------------------------------------------------------------------------------
1 | const converter = require('fresh-jrs-converter');
2 | const { getResumeType, RESUME_TYPE_JSON, RESUME_TYPE_FRESH, RESUME_TYPE_UNKNOWN } = require('./validate');
3 |
4 | const convertToJsonResume = (resume) => {
5 | const resumeType = getResumeType(resume);
6 | if ( resumeType === RESUME_TYPE_JSON || resumeType === RESUME_TYPE_UNKNOWN ) {
7 | throw new Error('Cannot convert Json-resume or unsupported resume type to Json-resume!')
8 | }
9 | return converter.toJSR(resume);
10 | };
11 |
12 | const convertToFreshResume = (resume) => {
13 | const resumeType = getResumeType(resume);
14 | if ( resumeType === RESUME_TYPE_FRESH || resumeType === RESUME_TYPE_UNKNOWN ) {
15 | throw new Error('Cannot convert FRESH resume or unsupported resume type to FRESH resume!')
16 | }
17 | return converter.toFRESH(resume);
18 | };
19 |
20 | module.exports = {
21 | convertToJsonResume,
22 | convertToFreshResume,
23 | };
24 |
--------------------------------------------------------------------------------
/app/main/preview.ts:
--------------------------------------------------------------------------------
1 | export const PREVIEW_DEFAULT_MARKUP =`
2 |
3 |
4 |
5 |
6 |
29 |
30 |
31 |
32 | preview
33 |
34 |
35 | `;
36 |
--------------------------------------------------------------------------------
/app/renderer/hooks/useThemeList.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, Dispatch, SetStateAction } from 'react';
2 | import {INotification, IThemeEntry, VALID_INVOKE_CHANNELS} from "../../definitions";
3 |
4 | export const useThemeList = (): [INotification, IThemeEntry[], Dispatch>] => {
5 | const [themeList, setThemeList] = useState([]);
6 | let err: INotification = null;
7 |
8 | useEffect(() => {
9 | const fetchThemeList = async () => {
10 | try {
11 | // set result as theme list
12 | setThemeList( await window.api.invoke(VALID_INVOKE_CHANNELS['get-theme-list']));
13 | } catch (e) {
14 | // pass the exception message as a warning-type notification
15 | err = { type: 'warning', text: e.message }
16 |
17 | }
18 | };
19 | // calling async functions in useEffect prevents warnings, see: https://www.robinwieruch.de/react-hooks-fetch-data
20 | fetchThemeList();
21 | }, []);
22 |
23 | return [err, themeList, setThemeList];
24 | };
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Karel Mácha
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/lib/log.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 | const log = console.log;
3 | const error = console.error;
4 |
5 | let includeStacktrace = false;
6 | let errorCounter = 0;
7 |
8 | /**
9 | * Chalk wrapper for console info output
10 | * @param text {String} The text to be logged
11 | */
12 | const logInfo = (text) => log(chalk.white(`\n${text}`));
13 |
14 | /**
15 | * Chalk wrapper for console success output
16 | * @param text {String} The text to be logged
17 | */
18 | const logSuccess = (text) => {
19 | log(chalk.green(`\n${text}`));
20 | }
21 |
22 | /**
23 | * Chalk wrapper for console error output
24 | * @param text {String} The text to be logged
25 | */
26 | const logError = (err) => {
27 |
28 | errorCounter ++;
29 | const debugOutput = includeStacktrace && err.stack ? `\nReason: ${err.stack}` : '';
30 | error(chalk.red(`\n${err}${debugOutput}`));
31 | };
32 |
33 | /**
34 | * Chalk wrapper for console server output
35 | * @param text {String} The text to be logged
36 | */
37 | const logServer = (text) => log(chalk.blue(`\n${text}`));
38 |
39 | module.exports = {
40 | logInfo,
41 | logSuccess,
42 | logError,
43 | logServer,
44 | setLogLevelToDebug: () => includeStacktrace = true,
45 | getErrorCount: () => errorCounter
46 | };
--------------------------------------------------------------------------------
/webpack.renderer.config.js:
--------------------------------------------------------------------------------
1 | const rules = require('./webpack.rules');
2 | const plugins = require('./webpack.plugins');
3 |
4 | rules.push(
5 | { // see: https://github.com/css-modules/css-modules/pull/65#issuecomment-354712147
6 | test: /\.css$/,
7 | oneOf: [
8 | {
9 | resourceQuery: /^\?raw$/,
10 | use: [{ loader: 'style-loader' }, { loader: 'css-loader',}]
11 | },
12 | {
13 | test: /\.css$/,
14 | use: [
15 | { loader: 'style-loader' },
16 | { loader: 'css-loader', options: { modules: true }}
17 | ],
18 | },
19 | ]
20 | },
21 | { // see: https://chriscourses.com/blog/loading-fonts-webpack
22 | test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
23 | use: [{
24 | loader: 'file-loader',
25 | options: {
26 | name: '[name].[ext]',
27 | outputPath: 'fonts/'
28 | }
29 | }]
30 | }
31 | );
32 |
33 | module.exports = {
34 | module: {
35 | rules,
36 | },
37 | output: {
38 | publicPath: './../',
39 | },
40 | plugins: [plugins.forkTsCheckerWebpackPlugin, plugins.optimizeCssnanoPlugin, plugins.provideJqueryPlugin],
41 | resolve: {
42 | extensions: ['.js', '.ts', '.jsx', '.tsx', '.css']
43 | },
44 | };
45 |
--------------------------------------------------------------------------------
/app/renderer/renderer.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * This file will automatically be loaded by webpack and run in the "renderer" context.
3 | * To learn more about the differences between the "main" and the "renderer" context in
4 | * Electron, visit:
5 | *
6 | * https://electronjs.org/docs/tutorial/application-architecture#main-and-renderer-processes
7 | *
8 | * By default, Node.js integration in this file is disabled. When enabling Node.js integration
9 | * in a renderer process, please be aware of potential security implications. You can read
10 | * more about security risks here:
11 | *
12 | * https://electronjs.org/docs/tutorial/security
13 | *
14 | * To enable Node.js integration in this file, open up `main.js` and enable the `nodeIntegration`
15 | * flag:
16 | *
17 | * ```
18 | * // Create the browser window.
19 | * mainWindow = new BrowserWindow({
20 | * width: 800,
21 | * height: 600,
22 | * webPreferences: {
23 | * nodeIntegration: true
24 | * }
25 | * });
26 | * ```
27 | */
28 |
29 | import '../index.css?raw';
30 | // Don't change the order, override has to come after the main bootstrap
31 | import 'bootstrap3/dist/js/bootstrap';
32 | import 'bootstrap3/dist/css/bootstrap.min.css?raw';
33 | import '../bootstrap-override.css?raw';
34 | import 'ez-space-css/css/ez-space.css?raw';
35 |
36 | import React from 'react';
37 | import ReactDOM from 'react-dom';
38 |
39 | import App from './components/App/App';
40 |
41 | ReactDOM.render(, document.querySelector('#root'));
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | resume.html
4 | resume.pdf
5 | resume.json
6 | .DS_Store
7 | .idea
8 | .vscode
9 | .npmrc
10 | resume out/
11 |
12 | # Logs
13 | logs
14 | *.log
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 | lerna-debug.log*
19 |
20 | # Diagnostic reports (https://nodejs.org/api/report.html)
21 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
22 |
23 | # Runtime data
24 | pids
25 | *.pid
26 | *.seed
27 | *.pid.lock
28 | .DS_Store
29 |
30 | # Directory for instrumented libs generated by jscoverage/JSCover
31 | lib-cov
32 |
33 | # Coverage directory used by tools like istanbul
34 | coverage
35 | *.lcov
36 |
37 | # nyc test coverage
38 | .nyc_output
39 |
40 | # node-waf configuration
41 | .lock-wscript
42 |
43 | # Compiled binary addons (https://nodejs.org/api/addons.html)
44 | build/Release
45 |
46 | # Dependency directories
47 | node_modules/
48 | jspm_packages/
49 |
50 | # TypeScript v1 declaration files
51 | typings/
52 |
53 | # TypeScript cache
54 | *.tsbuildinfo
55 |
56 | # Optional npm cache directory
57 | .npm
58 |
59 | # Optional eslint cache
60 | .eslintcache
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # next.js build output
79 | .next
80 |
81 | # nuxt.js build output
82 | .nuxt
83 |
84 | # vuepress build output
85 | .vuepress/dist
86 |
87 | # Serverless directories
88 | .serverless/
89 |
90 | # FuseBox cache
91 | .fusebox/
92 |
93 | # DynamoDB Local files
94 | .dynamodb/
95 |
96 | # Webpack
97 | .webpack/
98 |
99 | # Electron-Forge
100 | out/
101 |
--------------------------------------------------------------------------------
/lib/parse.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | const { logInfo, logSuccess } = require('./log');
4 | const { getResumeType, RESUME_TYPE_JSON, RESUME_TYPE_FRESH, RESUME_TYPE_UNKNOWN } = require('./validate');
5 |
6 | /**
7 | * Parse the source JSON file and do some type checking
8 | * @param sourcePath {string} Source path to the resume to be parsed
9 | * @param logging {boolean} Whether the method should do any logging or not
10 | * @returns {{resume: Object, type: (string|*)}} Returns an object containing the parsed resume and its type
11 | */
12 | const parseResumeFromSource = (sourcePath, logging = true) => {
13 | // do some logging
14 | if (logging) logInfo(`Parsing resume file from ${sourcePath}`);
15 |
16 | try {
17 | const resume = JSON.parse(fs.readFileSync(sourcePath));
18 | return parseResume(resume, logging)
19 | } catch (err) {
20 | throw new Error(`There was an problem when loading ${sourcePath}. Reason: ${err}.`);
21 | }
22 | };
23 |
24 | /**
25 | * Parse resume data from an object
26 | * @param resume {Object} The resume data as a object
27 | * @param logging {boolean} Whether the method should do any logging or not
28 | * @returns {{resume: Object, type: (string|*)}} Returns an object containing the parsed resume and its type
29 | */
30 | const parseResume = (resume, logging = true) => {
31 | try {
32 | // // Do some validation
33 | const type = getResumeType(resume);
34 | switch (type) {
35 | case RESUME_TYPE_JSON:
36 | if (logging) logSuccess('Succesfully parsed resume in JSON-Resume format.');
37 | break;
38 | case RESUME_TYPE_FRESH:
39 | if (logging) logSuccess('Succesfully parsed resume in FRESH format.');
40 | break;
41 | case RESUME_TYPE_UNKNOWN:
42 | default:
43 | throw new Error(`Invalid or unknown resume format detected!`);
44 | }
45 | return { resume, type, };
46 | } catch (err) {
47 | throw new Error(`There was an problem when parsing ${resume}. Reason: ${err}.`);
48 | }
49 | };
50 |
51 | module.exports = {
52 | parseResumeFromSource,
53 | parseResume,
54 | };
55 |
--------------------------------------------------------------------------------
/resume/empty-json-resume.json:
--------------------------------------------------------------------------------
1 | {
2 | "basics": {
3 | "name": "",
4 | "label": "",
5 | "picture": "",
6 | "email": "",
7 | "phone": "",
8 | "website": "",
9 | "summary": "",
10 | "location": {
11 | "address": "",
12 | "postalCode": "",
13 | "city": "",
14 | "countryCode": "",
15 | "region": ""
16 | },
17 | "profiles": [
18 | {
19 | "network": "",
20 | "username": "",
21 | "url": ""
22 | }
23 | ]
24 | },
25 | "work": [
26 | {
27 | "company": "",
28 | "position": "",
29 | "website": "",
30 | "startDate": "",
31 | "endDate": "",
32 | "summary": "",
33 | "highlights": [
34 | "",
35 | "",
36 | ""
37 | ]
38 | }
39 | ],
40 | "volunteer": [
41 | {
42 | "organization": "",
43 | "position": "",
44 | "website": "",
45 | "startDate": "",
46 | "endDate": "",
47 | "summary": "",
48 | "highlights": [
49 | "",
50 | "",
51 | ""
52 | ]
53 | }
54 | ],
55 | "education": [
56 | {
57 | "institution": "",
58 | "area": "",
59 | "studyType": "",
60 | "startDate": "",
61 | "endDate": "",
62 | "gpa": "",
63 | "courses": [
64 | "",
65 | "",
66 | ""
67 | ]
68 | }
69 | ],
70 | "awards": [
71 | {
72 | "title": "",
73 | "date": "",
74 | "awarder": "",
75 | "summary": ""
76 | }
77 | ],
78 | "publications": [
79 | {
80 | "name": "",
81 | "publisher": "",
82 | "releaseDate": "",
83 | "website": "",
84 | "summary": ""
85 | }
86 | ],
87 | "skills": [
88 | {
89 | "name": "",
90 | "level": "",
91 | "keywords": [
92 | "",
93 | "",
94 | ""
95 | ]
96 | },
97 | {
98 | "name": "",
99 | "level": "",
100 | "keywords": [
101 | "",
102 | "",
103 | ""
104 | ]
105 | }
106 | ],
107 | "languages": [
108 | {
109 | "language": "",
110 | "fluency": ""
111 | }
112 | ],
113 | "interests": [
114 | {
115 | "name": "",
116 | "keywords": [
117 | "",
118 | ""
119 | ]
120 | }
121 | ],
122 | "references": [
123 | {
124 | "name": "",
125 | "reference": ""
126 | }
127 | ]
128 | }
129 |
--------------------------------------------------------------------------------
/lib/validate.js:
--------------------------------------------------------------------------------
1 | const jsonResumeSchema = require('../schemes/json-resume-schema_0.0.0');
2 | const freshResumeSchema = require('../schemes/fresh-resume-schema_1.0.0-beta');
3 | const ZSchema = require('z-schema');
4 | const { logInfo, logSuccess, logError } = require('./log');
5 |
6 | // TODO: consider normalizing error messages from Z-Schema
7 | // https://github.com/dschenkelman/z-schema-errors
8 |
9 | // Constants identifying the different resume types
10 | const RESUME_TYPE_JSON = 'jrs';
11 | const RESUME_TYPE_FRESH = 'fresh';
12 | const RESUME_TYPE_UNKNOWN = 'unk';
13 |
14 | // Instantiate new Z-schema validator
15 | const validator = new ZSchema({ breakOnFirstError: false });
16 |
17 | /**
18 | * Method determining the type of parsed resume
19 | * @param resume {Object} The parsed resume
20 | * @returns {(RESUME_TYPE_JSON|RESUME_TYPE_FRESH|RESUME_TYPE_UNKNOWN)} The type of the resume
21 | */
22 | const getResumeType = (resume) => {
23 | if (resume.meta && resume.meta.format) { //&& resume.meta.format.substr(0, 5).toUpperCase() == 'FRESH'
24 | return RESUME_TYPE_FRESH;
25 | } else if (resume.basics) {
26 | return RESUME_TYPE_JSON;
27 | } else {
28 | return RESUME_TYPE_UNKNOWN;
29 | }
30 | };
31 |
32 | /**
33 | * Validates resume in Json-resume format. Logs an success message in case of a valid resume or a list of validation
34 | * errors otherwise
35 | * @param resume {Object} The parsed resume
36 | */
37 | const validateJsonResume = (resume) => {
38 | const valid = validator.validate(resume, jsonResumeSchema);
39 | if (!valid) {
40 | logInfo('--- Your resume contains errors ---');
41 | for (const validationError of validator.getLastErrors()) {
42 | logError(`# ${validationError.message} in ${validationError.path}`);
43 | }
44 | } else {
45 | logSuccess('Valid resume in Json-resume format.')
46 | }
47 |
48 | };
49 |
50 | /**
51 | * Validates resume in FRESH format. Logs an success message in case of a valid resume or a list of validation
52 | * errors otherwise
53 | * @param resume {Object} The parsed resume
54 | */
55 | const validateFreshResume = (resume) => {
56 | const valid = validator.validate(resume, freshResumeSchema);
57 | if (!valid) {
58 | logInfo('--- Your resume contains errors ---');
59 | for (const validationError of validator.getLastErrors()) {
60 | logError(`# ${validationError.message} in ${validationError.path}`);
61 | }
62 | } else {
63 | logSuccess('Valid resume in FRESH format.')
64 | }
65 | };
66 |
67 |
68 | module.exports = {
69 | RESUME_TYPE_JSON,
70 | RESUME_TYPE_FRESH,
71 | RESUME_TYPE_UNKNOWN,
72 | getResumeType,
73 | validateResume: (resume, type) => {
74 | if (type === RESUME_TYPE_JSON) {
75 | validateJsonResume(resume);
76 | } else if (type === RESUME_TYPE_FRESH) {
77 | validateFreshResume(resume)
78 | } else {
79 | throw new Error('Unsupported resume type!')
80 | }
81 | },
82 | };
83 |
--------------------------------------------------------------------------------
/webpack.plugins.js:
--------------------------------------------------------------------------------
1 | const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
2 | const OptimizeCssnanoPlugin = require('@intervolga/optimize-cssnano-plugin');
3 | const CopyPlugin = require('copy-webpack-plugin');
4 | const { DefinePlugin, ProvidePlugin } = require('webpack');
5 |
6 | const path = require('path');
7 | // const puppeteer = require('puppeteer');
8 | // const exec = require('child_process').exec;
9 |
10 | /**
11 | * The copyPlugin is used to add some necessary assets to the final bundle. The downloading and bundling puppeteer was
12 | * a way to overcome the limitations of electron and puppeteer-in-electron, but resulted in a huge app with several
13 | * hundred MB size. After a puppeteer-independent solution for the PDF/PNG export was found the puppeteer the
14 | * .local-chromium instance does not need to be included any more, the plugin-setup will be left here first for
15 | * reference.
16 | */
17 | module.exports = {
18 | forkTsCheckerWebpackPlugin: new ForkTsCheckerWebpackPlugin(),
19 | optimizeCssnanoPlugin: new OptimizeCssnanoPlugin({}),
20 | provideJqueryPlugin: new ProvidePlugin({
21 | $: 'jquery',
22 | jquery: 'jquery',
23 | 'window.jQuery': 'jquery',
24 | jQuery:'jquery'
25 | }),
26 | copyPlugin: new CopyPlugin({
27 | patterns: [
28 | // This fix missing assets for html-docx-js
29 | {
30 | from: path.resolve(__dirname, 'node_modules/html-docx-js/build/assets'),
31 | to: path.resolve(__dirname, '.webpack/main/assets'),
32 | },
33 | // This fix missing styling and template for jsonresume-theme-flat
34 | {
35 | from: path.resolve(__dirname, 'node_modules/jsonresume-theme-flat/style.css'),
36 | to: path.resolve(__dirname, '.webpack/main/'),
37 | },
38 | {
39 | from: path.resolve(__dirname, 'node_modules/jsonresume-theme-flat/resume.template'),
40 | to: path.resolve(__dirname, '.webpack/main/'),
41 | },
42 | /*
43 | {
44 | from: path.resolve(__dirname, puppeteer.executablePath().split('/.local-chromium')[0], '.local-chromium'),
45 | to: path.resolve(__dirname, '.webpack/main/chromium'),
46 | globOptions: {
47 | followSymbolicLinks: false,
48 | }
49 | },
50 | */
51 | ],
52 | }),
53 | runShellAfterEmitPlugin: {
54 | /*
55 | apply: (compiler) => {
56 | compiler.hooks.afterEmit.tap('AfterEmitPlugin', (compilation) => {
57 | exec(`chmod -R 755 ${path.resolve(__dirname, '.webpack/main/chromium/')}`, (err, stdout, stderr) => {
58 | if (stdout) process.stdout.write(stdout);
59 | if (stderr) process.stderr.write(stderr);
60 | });
61 | });
62 | },
63 | */
64 | },
65 | definePlugin: new DefinePlugin({
66 | // CHROMIUM_BINARY: JSON.stringify(path.join('chromium', puppeteer.executablePath().split('/.local-chromium/')[1])),
67 | }),
68 | };
69 |
--------------------------------------------------------------------------------
/lib/serve.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const fs = require('fs');
3 | const path = require('path');
4 | const reload = require('reload');
5 | const open = require('open');
6 |
7 | /**
8 | * The port, the webserver will be listening on
9 | */
10 | const DEFAULT_PORT = 3000;
11 | /**
12 | * The polling rate with which the watcher will be looking for resume changes
13 | */
14 | const POLLING_RATE = 2000;
15 | /**
16 | * server The server instance created with the express framework.
17 | */
18 | let server;
19 | let markup;
20 |
21 | const { logInfo, logSuccess, logServer } = require('./log');
22 | const { createMarkupFromSource } = require('./build');
23 |
24 | const app = express();
25 |
26 | const serveResume = async (sourcePath, theme, port = DEFAULT_PORT) => {
27 | try {
28 | // Try to generate the markup in the first place to see whether to continue
29 | markup = await createMarkupFromSource(sourcePath, theme, false);
30 | // Set up the port
31 | app.set('port', port);
32 | // Set up the main path
33 | app.get('/', async (req, res) => {
34 | // Do not create the markup when it already exist (for whatever reason)
35 | // TODO: improve (fix) error handling when createMarkupFromSource throws an error
36 | markup = !!markup ? markup : await createMarkupFromSource(sourcePath, theme, false);
37 | // Add the script tag with the replace javascript link to the end of the Html body to enable hot-reloading
38 | res.send(markup.replace(/(<\/body>)/i, '\n