resume-cli 0.0.0-development

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) [year] [fullname]
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
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # resume-cli
2
+
3
+ [![matrix](https://img.shields.io/badge/matrix-join%20chat-%230dbd8b)](https://matrix.to/#/#json-resume:one.ems.host)
4
+ [![Build status](https://img.shields.io/github/actions/workflow/status/jsonresume/resume-cli/test.yml?branch=master)](https://github.com/jsonresume/resume-cli/actions)
5
+ [![npm package](https://badge.fury.io/js/resume-cli.svg)](https://www.npmjs.org/package/resume-cli)
6
+
7
+ This is the command line tool for [JSON Resume](https://jsonresume.org), the open-source initiative to create a JSON-based standard for resumes.
8
+
9
+ ## Project Status
10
+
11
+ This repository is not actively maintained. It's recommended to use one of the third-party clients that support the JSON Resume standard instead:
12
+
13
+ * [Resumed](https://github.com/rbardini/resumed)
14
+
15
+ ## Getting Started
16
+
17
+ Install the command-line tool:
18
+
19
+ ```
20
+ npm install -g resume-cli
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### Commands at a Glance
26
+
27
+ | Command | Description |
28
+ |---|---|
29
+ | init | Initialize a `resume.json` file. |
30
+ | validate | Schema validation test your `resume.json`. |
31
+ | export path/to/file.html | Export to `.html`. |
32
+ | serve | Serve resume at `http://localhost:4000/`. |
33
+
34
+ ### `resume --help`
35
+
36
+ Show a list of options and commands for the <abbr title="Command-line Interface">CLI</abbr>.
37
+
38
+ ### `resume init`
39
+
40
+ Creates a new `resume.json` file in your current working directory.
41
+
42
+ Complete the `resume.json` with your text editor. Be sure to follow the schema (available at https://jsonresume.org/schema/).
43
+
44
+ ### `resume validate`
45
+
46
+ Validates your `resume.json` against our schema to ensure it complies with the standard. Tries to identify where any errors may be occurring.
47
+
48
+ ### `resume export [fileName]`
49
+
50
+ Exports your resume in a stylized HTML or PDF format.
51
+
52
+ A list of available themes can be found here:
53
+ https://jsonresume.org/themes/
54
+
55
+ Please npm install the theme you wish to use before attempting to export it.
56
+
57
+ Options:
58
+
59
+ - `--format <file type>` Example: `--format pdf`
60
+ - `--theme <name>` Example: `--theme even`
61
+
62
+ ### `resume serve`
63
+
64
+ Starts a web server that serves your local `resume.json`. It will live reload when you make changes to your `resume.json`.
65
+
66
+ Options:
67
+
68
+ - `--port <port>`
69
+ - `--theme <name>`
70
+
71
+ When developing themes, change into your theme directory and run `resume serve --theme .`, which tells it to run the local folder as the specified theme.
72
+
73
+ This is not intended for production use, it's a convenience for theme development or to visualize changes to your resume while editing it.
74
+
75
+ ## Supported Resume Input Types
76
+
77
+ - [`json`](https://www.json.org/json-en.html): via `JSON.parse`.
78
+ - [`yaml`](https://yaml.org/): via [`yaml-js`](https://www.npmjs.com/package/yaml-js)
79
+ - `quaff`: if `--resume` is a directory, then the path is passed to [`quaff`](https://www.npmjs.com/package/quaff) and the resulting json is used as the resume. quaff supports a variety of formats in the directory, including javascript modules.
80
+
81
+ ## Resume Data
82
+
83
+ - Setting `--resume -` tells the CLI to read resume data from standard input (`STDIN`), and defaults `--type` to `application/json`.
84
+ - Setting `--resume <path>` reads resume data from `path`.
85
+ - Leaving `--resume` unset defaults to reading from `resume.json` on the current working directory.
86
+
87
+ ## Resume MIME Types
88
+
89
+ Supported resume data MIME types are:
90
+
91
+ - `application/json`
92
+ - `text/yaml`
93
+
94
+ ## License
95
+
96
+ Available under [the MIT license](http://mths.be/mit).
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ const themeServer = process.env.THEME_SERVER || 'https://themes.jsonresume.org/theme/';
4
+ const fs = require('fs');
5
+ const request = require('superagent');
6
+ const chalk = require('chalk');
7
+ const renderHtml = require('./render-html');
8
+ const denormalizeTheme = value => {
9
+ return value.match(/jsonresume-theme-(.*)/)[1];
10
+ };
11
+ const sendExportHTML = (resumeJson, theme, callback) => {
12
+ console.log(resumeJson, theme);
13
+ console.log('Requesting theme from server...');
14
+ request.post(themeServer + denormalizeTheme(theme)).send({
15
+ resume: resumeJson
16
+ }).set('Accept', 'application/json').end((err, response) => {
17
+ if (err) {
18
+ callback('There was an error downloading your generated html resume from our server: ' + err);
19
+ } else if (response.text) {
20
+ callback(null, response.text);
21
+ } else {
22
+ callback('There was an error downloading your generated html resume from our server.');
23
+ }
24
+ });
25
+ };
26
+ module.exports = function resumeBuilder(theme, dir, resumeFilename, cb) {
27
+ fs.readFile(resumeFilename, async (err, resumeJson) => {
28
+ if (err) {
29
+ console.log(chalk.yellow('Could not find:'), resumeFilename);
30
+ console.log(chalk.cyan('Using example resume.json from resume-schema instead...'));
31
+ resumeJson = require('resume-schema').resumeJson;
32
+ } else {
33
+ try {
34
+ // todo: test resume schema
35
+ resumeJson = JSON.parse(resumeJson);
36
+ } catch (e) {
37
+ err = 'Parse error: ' + resumeFilename;
38
+ return cb(err);
39
+ }
40
+ }
41
+ try {
42
+ const html = await renderHtml({
43
+ resume: resumeJson,
44
+ themePath: theme
45
+ });
46
+ cb(null, html);
47
+ } catch (err) {
48
+ console.log(err);
49
+ console.log(chalk.yellow('Could not run the render function from local theme.'));
50
+ sendExportHTML(resumeJson, theme, cb);
51
+ }
52
+ });
53
+ };
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+
3
+ var _renderHtml = _interopRequireDefault(require("./render-html"));
4
+ var _util = require("util");
5
+ var _fs = _interopRequireDefault(require("fs"));
6
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
7
+ const writeFile = (0, _util.promisify)(_fs.default.writeFile);
8
+ const path = require('path');
9
+ const puppeteer = require('puppeteer');
10
+ const btoa = require('btoa');
11
+ module.exports = ({
12
+ resume: resumeJson,
13
+ fileName,
14
+ theme,
15
+ format
16
+ }, callback) => {
17
+ if (!fileName) {
18
+ console.error('Please enter a export destination.');
19
+ process.exit(1);
20
+ }
21
+ const fileNameAndFormat = getFileNameAndFormat(fileName, format);
22
+ fileName = fileNameAndFormat.fileName;
23
+ const fileFormatToUse = fileNameAndFormat.fileFormatToUse;
24
+ const formatToUse = '.' + fileFormatToUse;
25
+ if (formatToUse === '.html') {
26
+ createHtml(resumeJson, fileName, theme, formatToUse, error => {
27
+ if (error) {
28
+ console.error(error, '`createHtml` errored out');
29
+ }
30
+ callback(error, fileName, formatToUse);
31
+ });
32
+ } else if (formatToUse === '.pdf') {
33
+ createPdf(resumeJson, fileName, theme, formatToUse, error => {
34
+ if (error) {
35
+ console.error(error, '`createPdf` errored out');
36
+ }
37
+ callback(error, fileName, formatToUse);
38
+ });
39
+ } else {
40
+ console.error(`JSON Resume does not support the ${formatToUse} format`);
41
+ process.exit(1);
42
+ }
43
+ };
44
+ const extractFileFormat = fileName => {
45
+ const dotPos = fileName.lastIndexOf('.');
46
+ if (dotPos === -1) {
47
+ return null;
48
+ }
49
+ return fileName.substring(dotPos + 1).toLowerCase();
50
+ };
51
+ const getThemePkg = theme => {
52
+ if (theme[0] === '.') {
53
+ theme = path.join(process.cwd(), theme, 'index.js');
54
+ } else {
55
+ theme = path.join(process.cwd(), 'node_modules', theme, 'index.js');
56
+ }
57
+ try {
58
+ const themePkg = require(theme);
59
+ return themePkg;
60
+ } catch (err) {
61
+ // Theme not installed
62
+ console.log('You have to install this theme relative to the folder to use it e.g. `npm install ' + theme + '`');
63
+ process.exit();
64
+ }
65
+ };
66
+ async function createHtml(resumeJson, fileName, themePath, format, callback) {
67
+ const html = await (0, _renderHtml.default)({
68
+ resume: resumeJson,
69
+ themePath
70
+ });
71
+ const pathToStream = path.resolve(process.cwd(), fileName + format);
72
+ await writeFile(pathToStream, ''); // workaround for https://github.com/streamich/unionfs/issues/428
73
+ const stream = _fs.default.createWriteStream(pathToStream);
74
+ stream.write(html, () => {
75
+ stream.close(callback);
76
+ });
77
+ }
78
+ const createPdf = (resumeJson, fileName, theme, format, callback) => {
79
+ (async () => {
80
+ const themePkg = getThemePkg(theme);
81
+ const puppeteerLaunchArgs = [];
82
+ if (process.env.RESUME_PUPPETEER_NO_SANDBOX) {
83
+ puppeteerLaunchArgs.push('--no-sandbox');
84
+ }
85
+ const html = await (0, _renderHtml.default)({
86
+ resume: resumeJson,
87
+ themePath: theme
88
+ });
89
+ const browser = await puppeteer.launch({
90
+ args: puppeteerLaunchArgs
91
+ });
92
+ const page = await browser.newPage();
93
+ await page.emulateMediaType(themePkg.pdfRenderOptions && themePkg.pdfRenderOptions.mediaType || 'screen');
94
+ await page.goto(`data:text/html;base64,${btoa(unescape(encodeURIComponent(html)))}`, {
95
+ waitUntil: 'networkidle0'
96
+ });
97
+ if (themePkg.pdfViewport) {
98
+ await page.setViewport(themePkg.pdfViewport);
99
+ }
100
+ await page.pdf({
101
+ path: fileName + format,
102
+ format: 'Letter',
103
+ printBackground: true,
104
+ ...themePkg.pdfRenderOptions
105
+ });
106
+ await browser.close();
107
+ })().then(callback).catch(callback);
108
+ };
109
+ const getFileNameAndFormat = (fileName, format) => {
110
+ const fileFormatFound = extractFileFormat(fileName);
111
+ let fileFormatToUse = format;
112
+ if (format && fileFormatFound && format === fileFormatFound) {
113
+ fileName = fileName.substring(0, fileName.lastIndexOf('.'));
114
+ } else if (fileFormatFound) {
115
+ fileFormatToUse = fileFormatFound;
116
+ fileName = fileName.substring(0, fileName.lastIndexOf('.'));
117
+ }
118
+ return {
119
+ fileName: fileName,
120
+ fileFormatToUse: fileFormatToUse
121
+ };
122
+ };
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _fs = _interopRequireDefault(require("fs"));
8
+ var _mimeTypes = require("mime-types");
9
+ var _path = require("path");
10
+ var _quaff = _interopRequireDefault(require("quaff"));
11
+ var _streamToString = _interopRequireDefault(require("stream-to-string"));
12
+ var _yamlJs = _interopRequireDefault(require("yaml-js"));
13
+ var _util = require("util");
14
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+ const {
16
+ createReadStream
17
+ } = _fs.default;
18
+ const stat = (0, _util.promisify)(_fs.default.stat);
19
+ const parsers = {
20
+ 'text/yaml': string => _yamlJs.default.load(string),
21
+ 'application/json': string => JSON.parse(string)
22
+ };
23
+ var _default = async ({
24
+ path,
25
+ mime: inputMime
26
+ }) => {
27
+ let input;
28
+ let mime;
29
+ if ('-' === path) {
30
+ mime = inputMime || (0, _mimeTypes.lookup)('.json');
31
+ input = process.stdin;
32
+ } else if (path && (await stat(path)).isDirectory()) {
33
+ return (0, _quaff.default)(path);
34
+ }
35
+ if (!input) {
36
+ mime = inputMime || (0, _mimeTypes.lookup)(path);
37
+ input = createReadStream((0, _path.resolve)(process.cwd(), path));
38
+ }
39
+ if (!input) {
40
+ throw new Error('resume could not be gotten from path or stdin');
41
+ }
42
+ const resumeString = await (0, _streamToString.default)(input);
43
+ const parser = parsers[mime];
44
+ if (!parser) {
45
+ throw new Error(`no parser available for detected mime type ${mime}`);
46
+ }
47
+ return parser(resumeString);
48
+ };
49
+ exports.default = _default;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+
3
+ var _waait = _interopRequireDefault(require("waait"));
4
+ var _getResume = _interopRequireDefault(require("./get-resume"));
5
+ var _mockStdin = require("mock-stdin");
6
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
7
+ jest.mock('fs', () => {
8
+ const build = require('./test-utils/mocked-volume-builder');
9
+ const {
10
+ createFsFromVolume
11
+ } = require('memfs');
12
+ const vol = build();
13
+ return createFsFromVolume(vol);
14
+ });
15
+ describe('get-resume', () => {
16
+ it('should consume yaml', async () => {
17
+ expect(await (0, _getResume.default)({
18
+ path: '/resume.yaml'
19
+ })).toMatchInlineSnapshot(`
20
+ Object {
21
+ "basics": Object {
22
+ "email": "thomas@example.com",
23
+ "name": "thomas",
24
+ },
25
+ }
26
+ `);
27
+ });
28
+ it('should consume json', async () => {
29
+ expect(await (0, _getResume.default)({
30
+ path: '/resume.json'
31
+ })).toMatchInlineSnapshot(`
32
+ Object {
33
+ "basics": Object {
34
+ "email": "thomas@example.com",
35
+ "name": "thomas",
36
+ },
37
+ }
38
+ `);
39
+ });
40
+ it('should consume an entire directory as if it were a json object', async () => {
41
+ expect(await (0, _getResume.default)({
42
+ path: '/quaff'
43
+ })).toMatchInlineSnapshot(`
44
+ Object {
45
+ "basics": Object {
46
+ "email": "thomas@example.com",
47
+ "name": "thomas",
48
+ },
49
+ "work": Array [
50
+ Object {
51
+ "company": "Pied Piper",
52
+ "endDate": "2014-12-01",
53
+ "position": "CEO/President",
54
+ "startDate": "2013-12-01",
55
+ },
56
+ ],
57
+ }
58
+ `);
59
+ });
60
+ it('should read from process.stdin when path is a dash', async () => {
61
+ const stdin = (0, _mockStdin.stdin)();
62
+ const gotResume = (0, _getResume.default)({
63
+ path: '-'
64
+ });
65
+ await (0, _waait.default)();
66
+ stdin.send(JSON.stringify({
67
+ basics: {
68
+ name: 'thomas',
69
+ email: 'thomas@example.com'
70
+ }
71
+ }));
72
+ stdin.send(null);
73
+ expect(await gotResume).toMatchInlineSnapshot(`
74
+ Object {
75
+ "basics": Object {
76
+ "email": "thomas@example.com",
77
+ "name": "thomas",
78
+ },
79
+ }
80
+ `);
81
+ });
82
+ });
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _fs = require("fs");
8
+ var _util = require("util");
9
+ const readFile = (0, _util.promisify)(_fs.readFile);
10
+ var _default = async ({
11
+ path: pathArg
12
+ } = {}) => {
13
+ let path = pathArg;
14
+ if (!path) {
15
+ path = require.resolve('resume-schema/schema.json');
16
+ }
17
+ return JSON.parse(await readFile(path, {
18
+ encoding: 'utf-8'
19
+ }));
20
+ };
21
+ exports.default = _default;
package/build/init.js ADDED
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _util = require("util");
8
+ var _fs = _interopRequireDefault(require("fs"));
9
+ var _chalk = _interopRequireDefault(require("chalk"));
10
+ var _yesno = _interopRequireDefault(require("yesno"));
11
+ var _objectPathImmutable = require("object-path-immutable");
12
+ var _fileExists = _interopRequireDefault(require("file-exists"));
13
+ var _read = _interopRequireDefault(require("read"));
14
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
+ const writeFile = (0, _util.promisify)(_fs.default.writeFile);
16
+ const read = (0, _util.promisify)(_read.default);
17
+ const resume = require('resume-schema/sample.resume.json');
18
+ var _default = async ({
19
+ resumePath
20
+ }) => {
21
+ if (await (0, _fileExists.default)(resumePath)) {
22
+ console.log(_chalk.default.yellow('There is already a resume.json file in this directory.'));
23
+ if (!(await (0, _yesno.default)({
24
+ question: 'Do you want to override?'
25
+ }))) {
26
+ return;
27
+ }
28
+ }
29
+ console.log(`This utility will generate a file at ${resumePath}.`);
30
+ console.log('Fill out your name and email to get started, or leave the fields blank.');
31
+ console.log('All fields are optional.\n');
32
+ console.log('Press ^C at any time to quit.');
33
+ const name = await read({
34
+ prompt: 'name: '
35
+ });
36
+ const email = await read({
37
+ prompt: 'email: '
38
+ });
39
+ const personalizedResume = Object.entries({
40
+ name,
41
+ email
42
+ }).reduce((acc, [key, value]) => (0, _objectPathImmutable.set)(acc, `basics.${key}`, value), resume);
43
+ await writeFile(resumePath, JSON.stringify(personalizedResume, undefined, 2));
44
+ console.log(`Your resume has been created at ${resumePath}`);
45
+ console.log('');
46
+ console.log('To generate a formatted .html .md .txt or .pdf resume from your resume');
47
+ console.log('type: `resume export [someFileName]` including file extension eg: `resume export myresume.html`');
48
+ console.log('\nYou can optionally specify an available theme for html and pdf resumes using the --theme flag.');
49
+ console.log('Example: `resume export myresume.pdf --theme even`');
50
+ console.log('Or simply type: `resume export` and follow the prompts.');
51
+ console.log('');
52
+ };
53
+ exports.default = _default;
package/build/main.js ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ require("dotenv/config");
5
+ var _init = _interopRequireDefault(require("./init"));
6
+ var _getResume = _interopRequireDefault(require("./get-resume"));
7
+ var _getSchema = _interopRequireDefault(require("./get-schema"));
8
+ var _validate = _interopRequireDefault(require("./validate"));
9
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
+ const pkg = require('../package.json');
11
+ const exportResume = require('./export-resume');
12
+ const serve = require('./serve');
13
+ const program = require('commander');
14
+ const chalk = require('chalk');
15
+ const path = require('path');
16
+ const normalizeTheme = (value, defaultValue) => {
17
+ const theme = value || defaultValue;
18
+ // TODO - This is not great, but bypasses this function if it is a relative path
19
+ if (theme[0] === '.') {
20
+ return theme;
21
+ }
22
+ return theme.match('jsonresume-theme-.*') ? theme : `jsonresume-theme-${theme}`;
23
+ };
24
+ (async () => {
25
+ program.name('resume').usage('[command] [options]').version(pkg.version).option('-F, --force', 'Used by `publish` and `export` - bypasses schema testing.').option('-t, --theme <theme name>', 'Specify theme used by `export` and `serve` or specify a path starting with . (use . for current directory or ../some/other/dir)', normalizeTheme, 'jsonresume-theme-even').option('-f, --format <file type extension>', 'Used by `export`.').option('-r, --resume <resume filename>', "path to the resume in json format. Use '-' to read from stdin", 'resume.json').option('-p, --port <port>', 'Used by `serve` (default: 4000)', 4000).option('-s, --silent', 'Used by `serve` to tell it if open browser auto or not.', false).option('-d, --dir <path>', 'Used by `serve` to indicate a public directory path.', 'public').option('--schema <relativePath>', 'Used by `validate` to validate against a custom schema.');
26
+ program.command('init').description('Initialize a resume.json file').action(async () => {
27
+ await (0, _init.default)({
28
+ resumePath: program.resume
29
+ });
30
+ });
31
+ program.command('validate').description("Validate your resume's schema").action(async () => {
32
+ const resume = await (0, _getResume.default)({
33
+ path: program.resume
34
+ });
35
+ const schema = await (0, _getSchema.default)({
36
+ path: program.schema
37
+ });
38
+ try {
39
+ await (0, _validate.default)({
40
+ resume,
41
+ schema
42
+ });
43
+ } catch (e) {
44
+ console.error(e.message);
45
+ process.exitCode = 1;
46
+ }
47
+ });
48
+ program.command('export [fileName]').description('Export locally to .html or .pdf. Supply a --format <file format> flag and argument to specify export format.').action(async fileName => {
49
+ const resume = await (0, _getResume.default)({
50
+ path: program.resume
51
+ });
52
+ exportResume({
53
+ ...program,
54
+ resume,
55
+ fileName
56
+ }, (err, fileName, format) => {
57
+ console.log(chalk.green('\nDone! Find your new', format, 'resume at:\n', path.resolve(process.cwd(), fileName + format)));
58
+ });
59
+ });
60
+ program.command('serve').description('Serve resume at http://localhost:4000/').action(async () => {
61
+ serve({
62
+ ...program,
63
+ resumeFilename: program.resume
64
+ });
65
+ });
66
+ await program.parseAsync(process.argv);
67
+ const validCommands = program.commands.map(cmd => {
68
+ return cmd._name;
69
+ });
70
+
71
+ // https://github.com/tj/commander.js/blob/master/CHANGELOG.md#testing-for-no-arguments
72
+ if (program.rawArgs.length < 3) {
73
+ console.log(chalk.cyan('resume-cli:'), 'https://jsonresume.org', '\n');
74
+ program.help();
75
+ } else if (validCommands.indexOf(process.argv[2]) === -1) {
76
+ console.log(chalk.red('Invalid argument:'), process.argv[2]);
77
+ console.log(chalk.cyan('resume-cli:'), 'https://jsonresume.org', '\n');
78
+ program.help();
79
+ }
80
+ })();
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+
3
+ var _child_process = require("child_process");
4
+ var _streamToString = _interopRequireDefault(require("stream-to-string"));
5
+ var _util = require("util");
6
+ var _package = _interopRequireDefault(require("../package.json"));
7
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
8
+ const exec = (0, _util.promisify)(_child_process.exec);
9
+ const run = async (argv, {
10
+ waitForVolumeExport = true,
11
+ stdin = ''
12
+ } = {}) => {
13
+ let volume;
14
+ let exitCode;
15
+ const child = (0, _child_process.spawn)(process.execPath, ['build/test-utils/cli-test-entry.js', ...argv], {
16
+ stdio: ['pipe', 'pipe', 2, 'ipc']
17
+ });
18
+ const allChecks = Promise.all([waitForVolumeExport ? new Promise(volumeSet => {
19
+ child.on('message', async message => {
20
+ if (message.type === 'volumeExport') {
21
+ volume = message.data;
22
+ volumeSet();
23
+ }
24
+ });
25
+ }) : true, new Promise(processExited => {
26
+ child.on('exit', code => {
27
+ exitCode = code;
28
+ processExited();
29
+ });
30
+ })]);
31
+ child.stdin.write(stdin);
32
+ child.stdin.end();
33
+ const stdout = await (0, _streamToString.default)(child.stdout);
34
+ await allChecks;
35
+ return {
36
+ volume,
37
+ code: exitCode,
38
+ stdout
39
+ };
40
+ };
41
+ describe('cli configuration', () => {
42
+ beforeAll(() => exec(_package.default.scripts.prepare));
43
+ it('should show help', async () => {
44
+ const {
45
+ stdout
46
+ } = await run(['help'], {
47
+ waitForVolumeExport: false
48
+ });
49
+ expect(stdout).toMatchInlineSnapshot(`
50
+ "Usage: resume [command] [options]
51
+
52
+ Options:
53
+ -V, --version output the version number
54
+ -F, --force Used by \`publish\` and \`export\` - bypasses
55
+ schema testing.
56
+ -t, --theme <theme name> Specify theme used by \`export\` and
57
+ \`serve\` or specify a path starting with .
58
+ (use . for current directory or
59
+ ../some/other/dir) (default:
60
+ \\"jsonresume-theme-even\\")
61
+ -f, --format <file type extension> Used by \`export\`.
62
+ -r, --resume <resume filename> path to the resume in json format. Use
63
+ '-' to read from stdin (default:
64
+ \\"resume.json\\")
65
+ -p, --port <port> Used by \`serve\` (default: 4000) (default:
66
+ 4000)
67
+ -s, --silent Used by \`serve\` to tell it if open
68
+ browser auto or not. (default: false)
69
+ -d, --dir <path> Used by \`serve\` to indicate a public
70
+ directory path. (default: \\"public\\")
71
+ --schema <relativePath> Used by \`validate\` to validate against a
72
+ custom schema.
73
+ -h, --help display help for command
74
+
75
+ Commands:
76
+ init Initialize a resume.json file
77
+ validate Validate your resume's schema
78
+ export [fileName] Export locally to .html or .pdf. Supply
79
+ a --format <file format> flag and
80
+ argument to specify export format.
81
+ serve Serve resume at http://localhost:4000/
82
+ help [command] display help for command
83
+ "
84
+ `);
85
+ });
86
+ describe('validate', () => {
87
+ it('should use the schema override arg', async () => {
88
+ const {
89
+ stdout
90
+ } = await run(['validate', '--schema', '/test-resumes/only-number-schema.json', '--resume', '/test-resumes/only-number.json']);
91
+ expect(stdout).toMatchInlineSnapshot(`""`);
92
+ });
93
+ it('should fail when trying to validate an invalid resume specified by the --resume option', async () => {
94
+ expect((await run(['validate', '--resume', '/test-resumes/invalid-resume.json'])).code).toEqual(1);
95
+ });
96
+ it('should validate a resume specified by the --resume option', async () => {
97
+ const {
98
+ stdout
99
+ } = await run(['validate', '--resume', '/test-resumes/resume.json']);
100
+ expect(stdout).toMatchInlineSnapshot(`""`);
101
+ });
102
+ });
103
+ describe('export', () => {
104
+ it('should read from stdin when path is a dash', async () => {
105
+ const {
106
+ stdout,
107
+ volume
108
+ } = await run(['export', '/test-resumes/exported-resume-from-stdin.html', '--resume', '-' // this is the dash
109
+ ], {
110
+ stdin: JSON.stringify({
111
+ basics: {
112
+ name: 'thomas-from-stdin'
113
+ }
114
+ })
115
+ });
116
+ expect(volume['/test-resumes/exported-resume-from-stdin.html']).toEqual(expect.stringContaining('thomas-from-stdin'));
117
+ expect(stdout).toMatchInlineSnapshot(`
118
+ "
119
+ Done! Find your new .html resume at:
120
+ /test-resumes/exported-resume-from-stdin.html
121
+ "
122
+ `);
123
+ });
124
+ it('should export a resume from the path specified by --resume to the path specified immediately after the export command', async () => {
125
+ const {
126
+ stdout
127
+ } = await run(['export', '/test-resumes/exported-resume.html', '--resume', '/test-resumes/resume.json']);
128
+ expect(stdout).toMatchInlineSnapshot(`
129
+ "
130
+ Done! Find your new .html resume at:
131
+ /test-resumes/exported-resume.html
132
+ "
133
+ `);
134
+ });
135
+ });
136
+ });
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ const tryResolve = (...args) => {
8
+ try {
9
+ return require.resolve(...args);
10
+ } catch (err) {
11
+ return false;
12
+ }
13
+ };
14
+ var _default = async ({
15
+ resume,
16
+ themePath
17
+ }) => {
18
+ const cwd = process.cwd();
19
+ let path;
20
+ if (themePath[0] === '.') {
21
+ path = tryResolve(path.join(cwd, themePath), {
22
+ paths: [cwd]
23
+ });
24
+ throw new Error(`Theme ${themePath} could not be resolved relative to ${cwd}`);
25
+ }
26
+ if (!path) {
27
+ path = tryResolve(themePath, {
28
+ paths: [cwd]
29
+ });
30
+ }
31
+ if (!path && /^[a-z0-9]/i.test(path)) {
32
+ path = tryResolve(`jsonresume-theme-${themePath}`, {
33
+ paths: [cwd]
34
+ });
35
+ }
36
+ if (!path) {
37
+ throw new Error(`theme path ${themePath} could not be resolved from current working directory`);
38
+ }
39
+ const theme = require(path);
40
+ if (typeof (theme === null || theme === void 0 ? void 0 : theme.render) !== 'function') {
41
+ throw new Error('theme.render is not a function');
42
+ }
43
+ return theme.render(resume);
44
+ };
45
+ exports.default = _default;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+
3
+ var _renderHtml = _interopRequireDefault(require("./render-html"));
4
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
5
+ describe('renderHTML', () => {
6
+ beforeAll(() => {
7
+ const originalRequireResolve = require.resolve;
8
+ const mockThemePath = 'mock/path/to/jsonresume-theme-even';
9
+ require.resolve = (...args) => {
10
+ if (args[0] === 'jsonresume-theme-even') {
11
+ return mockThemePath;
12
+ }
13
+ if (args[0] === 'jsonresume-theme-even') {
14
+ return mockThemePath;
15
+ }
16
+ return originalRequireResolve.apply(require, ...args);
17
+ };
18
+ require.cache[mockThemePath] = {
19
+ render: () => 'here-is-your-mocked-theme'
20
+ };
21
+ });
22
+ const resume = {
23
+ basics: {
24
+ name: 'test',
25
+ label: 'Programmer',
26
+ email: 'test4@test.com'
27
+ }
28
+ };
29
+ it('should reject when theme is not availlable', async () => {
30
+ await expect((0, _renderHtml.default)({
31
+ resume,
32
+ themePath: 'unknown'
33
+ })).rejects.toBeTruthy();
34
+ });
35
+ describe('should render html when theme is availlable', () => {
36
+ it('with long theme name', async () => {
37
+ expect(await (0, _renderHtml.default)({
38
+ resume,
39
+ themePath: 'jsonresume-theme-even'
40
+ })).toStartWith('<!doctype html>');
41
+ });
42
+ it('with short theme name', async () => {
43
+ expect(await (0, _renderHtml.default)({
44
+ resume,
45
+ themePath: 'even'
46
+ })).toStartWith('<!doctype html>');
47
+ });
48
+ });
49
+ });
package/build/serve.js ADDED
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const readline = require('readline');
6
+ const bs = require('browser-sync').create();
7
+ const builder = require('./builder');
8
+ const reBuildResume = (theme, dir, resumeFilename, cb) => {
9
+ builder(theme, dir, resumeFilename, (err, html) => {
10
+ if (err) {
11
+ readline.cursorTo(process.stdout, 0);
12
+ console.log(err);
13
+ html = err;
14
+ }
15
+ fs.writeFile(path.join(process.cwd(), dir, 'index.html'), html, err => {
16
+ if (err) {
17
+ console.log(err);
18
+ }
19
+ cb();
20
+ });
21
+ });
22
+ };
23
+ module.exports = function ({
24
+ port,
25
+ theme,
26
+ silent,
27
+ dir,
28
+ resumeFilename
29
+ }) {
30
+ if (!fs.existsSync(dir)) {
31
+ fs.mkdirSync(dir);
32
+ }
33
+ bs.watch(resumeFilename).on('change', () => {
34
+ reBuildResume(theme, dir, resumeFilename, () => {
35
+ bs.reload();
36
+ });
37
+ });
38
+ reBuildResume(theme, dir, resumeFilename, () => {
39
+ bs.init({
40
+ server: dir,
41
+ port: port,
42
+ open: !silent && 'local',
43
+ ui: false
44
+ });
45
+ });
46
+ console.log('');
47
+ const previewUrl = 'http://localhost:' + port;
48
+ console.log('Preview: ' + previewUrl);
49
+ console.log('Press ctrl-c to stop');
50
+ console.log('');
51
+ };
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+
3
+ var _mockedVolumeBuilder = _interopRequireDefault(require("./mocked-volume-builder"));
4
+ var _fsMonkey = require("fs-monkey");
5
+ var _unionfs = require("unionfs");
6
+ var fs = _interopRequireWildcard(require("fs"));
7
+ function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }
8
+ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
9
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
+ const mockVolume = (0, _mockedVolumeBuilder.default)({
11
+ mount: '/test-resumes'
12
+ });
13
+ const vol = _unionfs.ufs.use(mockVolume).use(fs);
14
+ (0, _fsMonkey.patchFs)(vol);
15
+ require('../main.js');
16
+ process.once('beforeExit', () => {
17
+ process.send({
18
+ data: mockVolume.toJSON(),
19
+ type: 'volumeExport'
20
+ });
21
+ });
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+
3
+ module.exports = ({
4
+ mount = '/'
5
+ } = {}) => {
6
+ const dedent = require('dedent');
7
+ const flat = require('flat');
8
+ const {
9
+ Volume
10
+ } = require('memfs');
11
+ return Volume.fromJSON(flat({
12
+ 'only-number-schema.json': JSON.stringify({
13
+ type: 'number'
14
+ }),
15
+ 'only-number.json': '123',
16
+ 'invalid-resume.json': JSON.stringify({
17
+ notAValidKey: {
18
+ foo: 'bar'
19
+ }
20
+ }),
21
+ 'resume.json': JSON.stringify({
22
+ basics: {
23
+ name: 'thomas',
24
+ email: 'thomas@example.com'
25
+ }
26
+ }),
27
+ 'resume.yaml': dedent`
28
+ basics:
29
+ name: thomas
30
+ email: thomas@example.com
31
+ `,
32
+ quaff: {
33
+ 'basics.yaml': dedent`
34
+ name: thomas
35
+ email: thomas@example.com
36
+ `,
37
+ 'work.json': JSON.stringify([{
38
+ company: 'Pied Piper',
39
+ endDate: '2014-12-01',
40
+ position: 'CEO/President',
41
+ startDate: '2013-12-01'
42
+ }])
43
+ }
44
+ }, {
45
+ delimiter: '/'
46
+ }), mount);
47
+ };
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _util = require("util");
8
+ var _zSchema = _interopRequireDefault(require("z-schema"));
9
+ var _zSchemaErrors = _interopRequireDefault(require("z-schema-errors"));
10
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
11
+ const reporter = _zSchemaErrors.default.init();
12
+ const validator = new _zSchema.default();
13
+ const validate = (0, _util.promisify)((...args) => validator.validate(...args)); // maintains context
14
+ var _default = async ({
15
+ resume,
16
+ schema
17
+ }) => {
18
+ try {
19
+ return await validate(resume, schema);
20
+ } catch (errors) {
21
+ throw new Error(reporter.extractMessage({
22
+ report: {
23
+ errors
24
+ }
25
+ }));
26
+ }
27
+ };
28
+ exports.default = _default;
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+
3
+ var _validate = _interopRequireDefault(require("./validate"));
4
+ var _getSchema = _interopRequireDefault(require("./get-schema"));
5
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
6
+ describe('validate', () => {
7
+ let defaultSchema;
8
+ beforeEach(async () => {
9
+ defaultSchema = await (0, _getSchema.default)();
10
+ });
11
+ it('should not throw an error for a valid resume object', async () => {
12
+ await (0, _validate.default)({
13
+ resume: {
14
+ basics: {}
15
+ },
16
+ schema: defaultSchema
17
+ });
18
+ });
19
+ it('should throw an error for an invalid resume object', async () => {
20
+ await expect((0, _validate.default)({
21
+ resume: {
22
+ notInTheSchema: true
23
+ },
24
+ schema: defaultSchema
25
+ })).rejects.toMatchInlineSnapshot(`[Error: An error occurred 'Additional properties not allowed: notInTheSchema'.]`);
26
+ });
27
+ it('should accept a schema override', async () => {
28
+ await (0, _validate.default)({
29
+ resume: 123,
30
+ schema: {
31
+ type: 'number'
32
+ }
33
+ });
34
+ await expect((0, _validate.default)({
35
+ resume: 'thomas',
36
+ schema: {
37
+ type: 'number'
38
+ }
39
+ })).rejects.toMatchInlineSnapshot(`[Error: An error occurred 'Expected type number but found type string'.]`);
40
+ });
41
+ });
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "resume-cli",
3
+ "version": "0.0.0-development",
4
+ "description": "The JSON Resume command line interface",
5
+ "main": "index.js",
6
+ "engines": {
7
+ "node": ">=12 <18"
8
+ },
9
+ "files": [
10
+ "build/*",
11
+ "!test-utils",
12
+ "!*.test.js"
13
+ ],
14
+ "scripts": {
15
+ "dev": "babel-node lib/main.js",
16
+ "lint": "eslint --ignore-path .gitignore .",
17
+ "_postinstall": "husky install",
18
+ "prepublishOnly": "pinst --disable",
19
+ "postpublish": "pinst --enable",
20
+ "prepare": "babel lib -d build --copy-files",
21
+ "test": "jest"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/jsonresume/resume-cli.git"
26
+ },
27
+ "license": "MIT",
28
+ "bin": {
29
+ "resume": "build/main.js"
30
+ },
31
+ "dependencies": {
32
+ "async": "^3.2.0",
33
+ "browser-sync": "^2.29.3",
34
+ "btoa": "^1.2.1",
35
+ "chalk": "^4.1.0",
36
+ "commander": "^6.0.0",
37
+ "dotenv": "^8.2.0",
38
+ "file-exists": "^5.0.1",
39
+ "jest-extended": "^0.11.5",
40
+ "jsonresume-theme-even": "^0.6.0",
41
+ "mime-types": "^2.1.27",
42
+ "object-path-immutable": "^4.1.1",
43
+ "puppeteer": "^18.2.1",
44
+ "quaff": "^4.2.0",
45
+ "read": "^1.0.7",
46
+ "resume-schema": "^1.0.0",
47
+ "stream-to-string": "^1.2.1",
48
+ "superagent": "^6.0.0",
49
+ "yaml-js": "^0.2.3",
50
+ "yesno": "^0.3.1",
51
+ "z-schema": "^5.0.0",
52
+ "z-schema-errors": "^0.2.1"
53
+ },
54
+ "devDependencies": {
55
+ "@babel/cli": "7.12.10",
56
+ "@babel/core": "7.12.10",
57
+ "@babel/eslint-parser": "7.12.1",
58
+ "@babel/node": "7.12.10",
59
+ "@babel/plugin-proposal-optional-chaining": "7.12.7",
60
+ "@babel/preset-env": "7.12.11",
61
+ "babel-eslint": "10.1.0",
62
+ "babel-jest": "28.1.2",
63
+ "dedent": "0.7.0",
64
+ "eslint": "7.15.0",
65
+ "eslint-config-prettier": "7.0.0",
66
+ "eslint-plugin-jest": "24.1.3",
67
+ "eslint-plugin-prettier": "3.2.0",
68
+ "flat": "5.0.2",
69
+ "fs-monkey": "1.0.1",
70
+ "husky": "5.0.6",
71
+ "jest": "28.1.2",
72
+ "lint-staged": "10.5.3",
73
+ "memfs": "3.2.0",
74
+ "mock-stdin": "1.0.0",
75
+ "prettier": "2.2.1",
76
+ "unionfs": "4.4.0",
77
+ "waait": "1.0.5"
78
+ }
79
+ }