resume-cli 3.0.8 → 3.1.0

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/README.md CHANGED
@@ -1,21 +1,18 @@
1
1
  # resume-cli
2
2
 
3
- [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/jsonresume/public?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4
- [![Build status](https://img.shields.io/github/workflow/status/jsonresume/resume-cli/Main)](https://github.com/jsonresume/resume-cli/actions)
5
- [![Dependency status](https://david-dm.org/jsonresume/resume-cli.svg)](https://david-dm.org/jsonresume/resume-cli)
6
- [![devDependency status](https://david-dm.org/jsonresume/resume-cli/dev-status.svg)](https://david-dm.org/jsonresume/resume-cli#info=devDependencies)
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)
7
5
  [![npm package](https://badge.fury.io/js/resume-cli.svg)](https://www.npmjs.org/package/resume-cli)
8
6
 
9
- This is the command line tool for [JSON Resume](https://jsonresume.org), the open source initiative to create a JSON-based standard for resumes.
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.
10
8
 
11
- [Read more...](https://jsonresume.org/schema/)
9
+ ## Project Status
12
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:
13
12
 
14
- Alternatives: The Resume CLI tool works as it is so there isn't a huge amount of active development on it, try these alternatives if it doesn't work for you;
15
- - [Resumed](https://github.com/rbardini/resumed)
13
+ * [Resumed](https://github.com/rbardini/resumed)
16
14
 
17
-
18
- # Getting Started
15
+ ## Getting Started
19
16
 
20
17
  Install the command-line tool:
21
18
 
@@ -23,84 +20,77 @@ Install the command-line tool:
23
20
  npm install -g resume-cli
24
21
  ```
25
22
 
26
- ## Commands at a glance
23
+ ## Usage
27
24
 
28
- | command | description |
29
- | ---------------------- | ----------------------------------------- |
30
- | init | Initialize a `resume.json` file |
31
- | validate | Schema validation test your `resume.json` |
32
- | export [fileName.html] | Export locally to `.html` |
33
- | serve | Serve resume at `http://localhost:4000/` |
25
+ ### Commands at a Glance
34
26
 
35
- # Usage
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/`. |
36
33
 
37
- ## `resume --help`
34
+ ### `resume --help`
38
35
 
39
- Show a list of options and commands for the <abbr title="Command Line Interface">CLI</abbr>.
36
+ Show a list of options and commands for the <abbr title="Command-line Interface">CLI</abbr>.
40
37
 
41
- ## `resume init`
38
+ ### `resume init`
42
39
 
43
40
  Creates a new `resume.json` file in your current working directory.
44
41
 
45
- Complete the `resume.json` with your text editor. Be sure to follow the schema
46
- (available at http://jsonresume.org).
42
+ Complete the `resume.json` with your text editor. Be sure to follow the schema (available at https://jsonresume.org/schema/).
47
43
 
48
- ## `resume validate`
44
+ ### `resume validate`
49
45
 
50
- Validates your `resume.json` against our schema tests to ensure it complies with
51
- the standard. Tries to identify where any errors may be occurring.
46
+ Validates your `resume.json` against our schema to ensure it complies with the standard. Tries to identify where any errors may be occurring.
52
47
 
53
- ## `resume export [fileName]`
48
+ ### `resume export [fileName]`
54
49
 
55
- Exports your resume locally in a stylized HTML or PDF format.
50
+ Exports your resume in a stylized HTML or PDF format.
56
51
 
57
- A list of available themes can be found here: http://jsonresume.org/themes/
52
+ A list of available themes can be found here:
53
+ https://jsonresume.org/themes/
58
54
 
59
- Please npm install the theme you wish to use locally before attempting to export it.
55
+ Please npm install the theme you wish to use before attempting to export it.
60
56
 
61
57
  Options:
62
58
 
63
59
  - `--format <file type>` Example: `--format pdf`
64
60
  - `--theme <name>` Example: `--theme even`
65
61
 
66
- ## `resume serve`
62
+ ### `resume serve`
67
63
 
68
- Starts a web server that serves your local `resume.json`. It will live reload when you make edits to your `resume.json`.
64
+ Starts a web server that serves your local `resume.json`. It will live reload when you make changes to your `resume.json`.
69
65
 
70
66
  Options:
71
67
 
72
68
  - `--port <port>`
73
69
  - `--theme <name>`
74
70
 
75
- When developing themes, simply change into your theme directory and run `resume serve --theme .` (which tells it to run the local folder as the specified theme)
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.
76
74
 
77
- # supported resume input types
75
+ ## Supported Resume Input Types
78
76
 
79
77
  - [`json`](https://www.json.org/json-en.html): via `JSON.parse`.
80
78
  - [`yaml`](https://yaml.org/): via [`yaml-js`](https://www.npmjs.com/package/yaml-js)
81
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.
82
80
 
83
- # resume data
81
+ ## Resume Data
84
82
 
85
- - Setting `--resume -` tells the cli to read resume data from standard input (`stdin`), and defaults `--type` to `application/json`.
83
+ - Setting `--resume -` tells the CLI to read resume data from standard input (`STDIN`), and defaults `--type` to `application/json`.
86
84
  - Setting `--resume <path>` reads resume data from `path`.
87
85
  - Leaving `--resume` unset defaults to reading from `resume.json` on the current working directory.
88
86
 
89
- # resume mime types
87
+ ## Resume MIME Types
90
88
 
91
- Supported resume data mime types are:
89
+ Supported resume data MIME types are:
92
90
 
93
91
  - `application/json`
94
92
  - `text/yaml`
95
93
 
96
- # Development
97
-
98
- to test the cli, run the dev script:
99
-
100
- ```sh
101
- npm run dev -- [cli arguments can be passed after the double-dash]
102
- ```
103
-
104
- # License
94
+ ## License
105
95
 
106
96
  Available under [the MIT license](http://mths.be/mit).
package/build/builder.js CHANGED
@@ -1,19 +1,13 @@
1
1
  "use strict";
2
2
 
3
3
  const themeServer = process.env.THEME_SERVER || 'https://themes.jsonresume.org/theme/';
4
-
5
4
  const fs = require('fs');
6
-
7
5
  const request = require('superagent');
8
-
9
6
  const chalk = require('chalk');
10
-
11
- const renderHtml = require('./render-html').default;
12
-
7
+ const renderHtml = require('./render-html');
13
8
  const denormalizeTheme = value => {
14
9
  return value.match(/jsonresume-theme-(.*)/)[1];
15
10
  };
16
-
17
11
  const sendExportHTML = (resumeJson, theme, callback) => {
18
12
  console.log(resumeJson, theme);
19
13
  console.log('Requesting theme from server...');
@@ -29,7 +23,6 @@ const sendExportHTML = (resumeJson, theme, callback) => {
29
23
  }
30
24
  });
31
25
  };
32
-
33
26
  module.exports = function resumeBuilder(theme, dir, resumeFilename, cb) {
34
27
  fs.readFile(resumeFilename, async (err, resumeJson) => {
35
28
  if (err) {
@@ -45,7 +38,6 @@ module.exports = function resumeBuilder(theme, dir, resumeFilename, cb) {
45
38
  return cb(err);
46
39
  }
47
40
  }
48
-
49
41
  try {
50
42
  const html = await renderHtml({
51
43
  resume: resumeJson,
@@ -1,21 +1,13 @@
1
1
  "use strict";
2
2
 
3
3
  var _renderHtml = _interopRequireDefault(require("./render-html"));
4
-
5
4
  var _util = require("util");
6
-
7
5
  var _fs = _interopRequireDefault(require("fs"));
8
-
9
6
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
10
-
11
7
  const writeFile = (0, _util.promisify)(_fs.default.writeFile);
12
-
13
8
  const path = require('path');
14
-
15
9
  const puppeteer = require('puppeteer');
16
-
17
10
  const btoa = require('btoa');
18
-
19
11
  module.exports = ({
20
12
  resume: resumeJson,
21
13
  fileName,
@@ -26,18 +18,15 @@ module.exports = ({
26
18
  console.error('Please enter a export destination.');
27
19
  process.exit(1);
28
20
  }
29
-
30
21
  const fileNameAndFormat = getFileNameAndFormat(fileName, format);
31
22
  fileName = fileNameAndFormat.fileName;
32
23
  const fileFormatToUse = fileNameAndFormat.fileFormatToUse;
33
24
  const formatToUse = '.' + fileFormatToUse;
34
-
35
25
  if (formatToUse === '.html') {
36
26
  createHtml(resumeJson, fileName, theme, formatToUse, error => {
37
27
  if (error) {
38
28
  console.error(error, '`createHtml` errored out');
39
29
  }
40
-
41
30
  callback(error, fileName, formatToUse);
42
31
  });
43
32
  } else if (formatToUse === '.pdf') {
@@ -45,7 +34,6 @@ module.exports = ({
45
34
  if (error) {
46
35
  console.error(error, '`createPdf` errored out');
47
36
  }
48
-
49
37
  callback(error, fileName, formatToUse);
50
38
  });
51
39
  } else {
@@ -53,27 +41,21 @@ module.exports = ({
53
41
  process.exit(1);
54
42
  }
55
43
  };
56
-
57
44
  const extractFileFormat = fileName => {
58
45
  const dotPos = fileName.lastIndexOf('.');
59
-
60
46
  if (dotPos === -1) {
61
47
  return null;
62
48
  }
63
-
64
49
  return fileName.substring(dotPos + 1).toLowerCase();
65
50
  };
66
-
67
51
  const getThemePkg = theme => {
68
52
  if (theme[0] === '.') {
69
53
  theme = path.join(process.cwd(), theme, 'index.js');
70
54
  } else {
71
55
  theme = path.join(process.cwd(), 'node_modules', theme, 'index.js');
72
56
  }
73
-
74
57
  try {
75
58
  const themePkg = require(theme);
76
-
77
59
  return themePkg;
78
60
  } catch (err) {
79
61
  // Theme not installed
@@ -81,7 +63,6 @@ const getThemePkg = theme => {
81
63
  process.exit();
82
64
  }
83
65
  };
84
-
85
66
  async function createHtml(resumeJson, fileName, themePath, format, callback) {
86
67
  const html = await (0, _renderHtml.default)({
87
68
  resume: resumeJson,
@@ -89,23 +70,18 @@ async function createHtml(resumeJson, fileName, themePath, format, callback) {
89
70
  });
90
71
  const pathToStream = path.resolve(process.cwd(), fileName + format);
91
72
  await writeFile(pathToStream, ''); // workaround for https://github.com/streamich/unionfs/issues/428
92
-
93
73
  const stream = _fs.default.createWriteStream(pathToStream);
94
-
95
74
  stream.write(html, () => {
96
75
  stream.close(callback);
97
76
  });
98
77
  }
99
-
100
78
  const createPdf = (resumeJson, fileName, theme, format, callback) => {
101
79
  (async () => {
102
80
  const themePkg = getThemePkg(theme);
103
81
  const puppeteerLaunchArgs = [];
104
-
105
82
  if (process.env.RESUME_PUPPETEER_NO_SANDBOX) {
106
83
  puppeteerLaunchArgs.push('--no-sandbox');
107
84
  }
108
-
109
85
  const html = await (0, _renderHtml.default)({
110
86
  resume: resumeJson,
111
87
  themePath: theme
@@ -118,11 +94,9 @@ const createPdf = (resumeJson, fileName, theme, format, callback) => {
118
94
  await page.goto(`data:text/html;base64,${btoa(unescape(encodeURIComponent(html)))}`, {
119
95
  waitUntil: 'networkidle0'
120
96
  });
121
-
122
97
  if (themePkg.pdfViewport) {
123
98
  await page.setViewport(themePkg.pdfViewport);
124
99
  }
125
-
126
100
  await page.pdf({
127
101
  path: fileName + format,
128
102
  format: 'Letter',
@@ -132,18 +106,15 @@ const createPdf = (resumeJson, fileName, theme, format, callback) => {
132
106
  await browser.close();
133
107
  })().then(callback).catch(callback);
134
108
  };
135
-
136
109
  const getFileNameAndFormat = (fileName, format) => {
137
110
  const fileFormatFound = extractFileFormat(fileName);
138
111
  let fileFormatToUse = format;
139
-
140
112
  if (format && fileFormatFound && format === fileFormatFound) {
141
113
  fileName = fileName.substring(0, fileName.lastIndexOf('.'));
142
114
  } else if (fileFormatFound) {
143
115
  fileFormatToUse = fileFormatFound;
144
116
  fileName = fileName.substring(0, fileName.lastIndexOf('.'));
145
117
  }
146
-
147
118
  return {
148
119
  fileName: fileName,
149
120
  fileFormatToUse: fileFormatToUse
@@ -4,23 +4,14 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
-
8
7
  var _fs = _interopRequireDefault(require("fs"));
9
-
10
8
  var _mimeTypes = require("mime-types");
11
-
12
9
  var _path = require("path");
13
-
14
10
  var _quaff = _interopRequireDefault(require("quaff"));
15
-
16
11
  var _streamToString = _interopRequireDefault(require("stream-to-string"));
17
-
18
12
  var _yamlJs = _interopRequireDefault(require("yaml-js"));
19
-
20
13
  var _util = require("util");
21
-
22
14
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
23
-
24
15
  const {
25
16
  createReadStream
26
17
  } = _fs.default;
@@ -29,38 +20,30 @@ const parsers = {
29
20
  'text/yaml': string => _yamlJs.default.load(string),
30
21
  'application/json': string => JSON.parse(string)
31
22
  };
32
-
33
23
  var _default = async ({
34
24
  path,
35
25
  mime: inputMime
36
26
  }) => {
37
27
  let input;
38
28
  let mime;
39
-
40
29
  if ('-' === path) {
41
30
  mime = inputMime || (0, _mimeTypes.lookup)('.json');
42
31
  input = process.stdin;
43
32
  } else if (path && (await stat(path)).isDirectory()) {
44
33
  return (0, _quaff.default)(path);
45
34
  }
46
-
47
35
  if (!input) {
48
36
  mime = inputMime || (0, _mimeTypes.lookup)(path);
49
37
  input = createReadStream((0, _path.resolve)(process.cwd(), path));
50
38
  }
51
-
52
39
  if (!input) {
53
40
  throw new Error('resume could not be gotten from path or stdin');
54
41
  }
55
-
56
42
  const resumeString = await (0, _streamToString.default)(input);
57
43
  const parser = parsers[mime];
58
-
59
44
  if (!parser) {
60
45
  throw new Error(`no parser available for detected mime type ${mime}`);
61
46
  }
62
-
63
47
  return parser(resumeString);
64
48
  };
65
-
66
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
+ });
@@ -4,25 +4,18 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
-
8
7
  var _fs = require("fs");
9
-
10
8
  var _util = require("util");
11
-
12
9
  const readFile = (0, _util.promisify)(_fs.readFile);
13
-
14
10
  var _default = async ({
15
11
  path: pathArg
16
12
  } = {}) => {
17
13
  let path = pathArg;
18
-
19
14
  if (!path) {
20
15
  path = require.resolve('resume-schema/schema.json');
21
16
  }
22
-
23
17
  return JSON.parse(await readFile(path, {
24
18
  encoding: 'utf-8'
25
19
  }));
26
20
  };
27
-
28
21
  exports.default = _default;
package/build/init.js CHANGED
@@ -4,41 +4,28 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
-
8
7
  var _util = require("util");
9
-
10
8
  var _fs = _interopRequireDefault(require("fs"));
11
-
12
9
  var _chalk = _interopRequireDefault(require("chalk"));
13
-
14
10
  var _yesno = _interopRequireDefault(require("yesno"));
15
-
16
11
  var _objectPathImmutable = require("object-path-immutable");
17
-
18
12
  var _fileExists = _interopRequireDefault(require("file-exists"));
19
-
20
13
  var _read = _interopRequireDefault(require("read"));
21
-
22
14
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
23
-
24
15
  const writeFile = (0, _util.promisify)(_fs.default.writeFile);
25
16
  const read = (0, _util.promisify)(_read.default);
26
-
27
17
  const resume = require('resume-schema/sample.resume.json');
28
-
29
18
  var _default = async ({
30
19
  resumePath
31
20
  }) => {
32
21
  if (await (0, _fileExists.default)(resumePath)) {
33
22
  console.log(_chalk.default.yellow('There is already a resume.json file in this directory.'));
34
-
35
23
  if (!(await (0, _yesno.default)({
36
24
  question: 'Do you want to override?'
37
25
  }))) {
38
26
  return;
39
27
  }
40
28
  }
41
-
42
29
  console.log(`This utility will generate a file at ${resumePath}.`);
43
30
  console.log('Fill out your name and email to get started, or leave the fields blank.');
44
31
  console.log('All fields are optional.\n');
@@ -63,5 +50,4 @@ var _default = async ({
63
50
  console.log('Or simply type: `resume export` and follow the prompts.');
64
51
  console.log('');
65
52
  };
66
-
67
53
  exports.default = _default;
package/build/main.js CHANGED
@@ -2,39 +2,25 @@
2
2
  "use strict";
3
3
 
4
4
  require("dotenv/config");
5
-
6
5
  var _init = _interopRequireDefault(require("./init"));
7
-
8
6
  var _getResume = _interopRequireDefault(require("./get-resume"));
9
-
10
7
  var _getSchema = _interopRequireDefault(require("./get-schema"));
11
-
12
8
  var _validate = _interopRequireDefault(require("./validate"));
13
-
14
9
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
-
16
10
  const pkg = require('../package.json');
17
-
18
11
  const exportResume = require('./export-resume');
19
-
20
12
  const serve = require('./serve');
21
-
22
13
  const program = require('commander');
23
-
24
14
  const chalk = require('chalk');
25
-
26
15
  const path = require('path');
27
-
28
16
  const normalizeTheme = (value, defaultValue) => {
29
- const theme = value || defaultValue; // TODO - This is not great, but bypasses this function if it is a relative path
30
-
17
+ const theme = value || defaultValue;
18
+ // TODO - This is not great, but bypasses this function if it is a relative path
31
19
  if (theme[0] === '.') {
32
20
  return theme;
33
21
  }
34
-
35
22
  return theme.match('jsonresume-theme-.*') ? theme : `jsonresume-theme-${theme}`;
36
23
  };
37
-
38
24
  (async () => {
39
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.');
40
26
  program.command('init').description('Initialize a resume.json file').action(async () => {
@@ -49,7 +35,6 @@ const normalizeTheme = (value, defaultValue) => {
49
35
  const schema = await (0, _getSchema.default)({
50
36
  path: program.schema
51
37
  });
52
-
53
38
  try {
54
39
  await (0, _validate.default)({
55
40
  resume,
@@ -64,7 +49,8 @@ const normalizeTheme = (value, defaultValue) => {
64
49
  const resume = await (0, _getResume.default)({
65
50
  path: program.resume
66
51
  });
67
- exportResume({ ...program,
52
+ exportResume({
53
+ ...program,
68
54
  resume,
69
55
  fileName
70
56
  }, (err, fileName, format) => {
@@ -72,15 +58,17 @@ const normalizeTheme = (value, defaultValue) => {
72
58
  });
73
59
  });
74
60
  program.command('serve').description('Serve resume at http://localhost:4000/').action(async () => {
75
- serve({ ...program,
61
+ serve({
62
+ ...program,
76
63
  resumeFilename: program.resume
77
64
  });
78
65
  });
79
66
  await program.parseAsync(process.argv);
80
67
  const validCommands = program.commands.map(cmd => {
81
68
  return cmd._name;
82
- }); // https://github.com/tj/commander.js/blob/master/CHANGELOG.md#testing-for-no-arguments
69
+ });
83
70
 
71
+ // https://github.com/tj/commander.js/blob/master/CHANGELOG.md#testing-for-no-arguments
84
72
  if (program.rawArgs.length < 3) {
85
73
  console.log(chalk.cyan('resume-cli:'), 'https://jsonresume.org', '\n');
86
74
  program.help();
@@ -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
+ });
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
-
8
7
  const tryResolve = (...args) => {
9
8
  try {
10
9
  return require.resolve(...args);
@@ -12,47 +11,35 @@ const tryResolve = (...args) => {
12
11
  return false;
13
12
  }
14
13
  };
15
-
16
14
  var _default = async ({
17
15
  resume,
18
16
  themePath
19
17
  }) => {
20
18
  const cwd = process.cwd();
21
19
  let path;
22
-
23
20
  if (themePath[0] === '.') {
24
- path = tryResolve(require('path').join(cwd, themePath), {
21
+ path = tryResolve(path.join(cwd, themePath), {
25
22
  paths: [cwd]
26
23
  });
27
-
28
- if (!path) {
29
- throw new Error(`Theme ${themePath} could not be resolved relative to ${cwd}`);
30
- }
24
+ throw new Error(`Theme ${themePath} could not be resolved relative to ${cwd}`);
31
25
  }
32
-
33
26
  if (!path) {
34
27
  path = tryResolve(themePath, {
35
28
  paths: [cwd]
36
29
  });
37
30
  }
38
-
39
31
  if (!path && /^[a-z0-9]/i.test(path)) {
40
32
  path = tryResolve(`jsonresume-theme-${themePath}`, {
41
33
  paths: [cwd]
42
34
  });
43
35
  }
44
-
45
36
  if (!path) {
46
37
  throw new Error(`theme path ${themePath} could not be resolved from current working directory`);
47
38
  }
48
-
49
39
  const theme = require(path);
50
-
51
40
  if (typeof (theme === null || theme === void 0 ? void 0 : theme.render) !== 'function') {
52
41
  throw new Error('theme.render is not a function');
53
42
  }
54
-
55
43
  return theme.render(resume);
56
44
  };
57
-
58
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 CHANGED
@@ -1,15 +1,10 @@
1
1
  "use strict";
2
2
 
3
3
  const fs = require('fs');
4
-
5
4
  const path = require('path');
6
-
7
5
  const readline = require('readline');
8
-
9
6
  const bs = require('browser-sync').create();
10
-
11
7
  const builder = require('./builder');
12
-
13
8
  const reBuildResume = (theme, dir, resumeFilename, cb) => {
14
9
  builder(theme, dir, resumeFilename, (err, html) => {
15
10
  if (err) {
@@ -17,17 +12,14 @@ const reBuildResume = (theme, dir, resumeFilename, cb) => {
17
12
  console.log(err);
18
13
  html = err;
19
14
  }
20
-
21
15
  fs.writeFile(path.join(process.cwd(), dir, 'index.html'), html, err => {
22
16
  if (err) {
23
17
  console.log(err);
24
18
  }
25
-
26
19
  cb();
27
20
  });
28
21
  });
29
22
  };
30
-
31
23
  module.exports = function ({
32
24
  port,
33
25
  theme,
@@ -38,7 +30,6 @@ module.exports = function ({
38
30
  if (!fs.existsSync(dir)) {
39
31
  fs.mkdirSync(dir);
40
32
  }
41
-
42
33
  bs.watch(resumeFilename).on('change', () => {
43
34
  reBuildResume(theme, dir, resumeFilename, () => {
44
35
  bs.reload();
@@ -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
+ };
package/build/validate.js CHANGED
@@ -4,20 +4,13 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.default = void 0;
7
-
8
7
  var _util = require("util");
9
-
10
8
  var _zSchema = _interopRequireDefault(require("z-schema"));
11
-
12
9
  var _zSchemaErrors = _interopRequireDefault(require("z-schema-errors"));
13
-
14
10
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
-
16
11
  const reporter = _zSchemaErrors.default.init();
17
-
18
12
  const validator = new _zSchema.default();
19
13
  const validate = (0, _util.promisify)((...args) => validator.validate(...args)); // maintains context
20
-
21
14
  var _default = async ({
22
15
  resume,
23
16
  schema
@@ -32,5 +25,4 @@ var _default = async ({
32
25
  }));
33
26
  }
34
27
  };
35
-
36
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 CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "resume-cli",
3
- "version": "3.0.8",
3
+ "version": "3.1.0",
4
4
  "description": "The JSON Resume command line interface",
5
5
  "main": "index.js",
6
6
  "engines": {
7
- "node": ">=10.18.1"
7
+ "node": ">=12 <18"
8
8
  },
9
9
  "files": [
10
10
  "build/*",
@@ -30,24 +30,22 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "async": "^3.2.0",
33
- "browser-sync": "^2.26.7",
33
+ "browser-sync": "^2.29.3",
34
34
  "btoa": "^1.2.1",
35
35
  "chalk": "^4.1.0",
36
36
  "commander": "^6.0.0",
37
37
  "dotenv": "^8.2.0",
38
38
  "file-exists": "^5.0.1",
39
39
  "jest-extended": "^0.11.5",
40
- "jsonlint": "^1.6.3",
41
40
  "jsonresume-theme-even": "^0.6.0",
42
41
  "mime-types": "^2.1.27",
43
42
  "object-path-immutable": "^4.1.1",
44
- "puppeteer": "^16.1.0",
43
+ "puppeteer": "^18.2.1",
45
44
  "quaff": "^4.2.0",
46
45
  "read": "^1.0.7",
47
46
  "resume-schema": "^1.0.0",
48
- "stream-to-string": "^1.2.0",
47
+ "stream-to-string": "^1.2.1",
49
48
  "superagent": "^6.0.0",
50
- "util.promisify": "^1.0.1",
51
49
  "yaml-js": "^0.2.3",
52
50
  "yesno": "^0.3.1",
53
51
  "z-schema": "^5.0.0",
@@ -61,7 +59,7 @@
61
59
  "@babel/plugin-proposal-optional-chaining": "7.12.7",
62
60
  "@babel/preset-env": "7.12.11",
63
61
  "babel-eslint": "10.1.0",
64
- "babel-jest": "^26.6.3",
62
+ "babel-jest": "28.1.2",
65
63
  "dedent": "0.7.0",
66
64
  "eslint": "7.15.0",
67
65
  "eslint-config-prettier": "7.0.0",
@@ -70,12 +68,10 @@
70
68
  "flat": "5.0.2",
71
69
  "fs-monkey": "1.0.1",
72
70
  "husky": "5.0.6",
73
- "jest": "26.6.3",
74
- "jest-extended": "0.11.5",
71
+ "jest": "28.1.2",
75
72
  "lint-staged": "10.5.3",
76
73
  "memfs": "3.2.0",
77
74
  "mock-stdin": "1.0.0",
78
- "pinst": "2.1.1",
79
75
  "prettier": "2.2.1",
80
76
  "unionfs": "4.4.0",
81
77
  "waait": "1.0.5"