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 +21 -0
- package/README.md +96 -0
- package/build/builder.js +53 -0
- package/build/export-resume.js +122 -0
- package/build/get-resume.js +49 -0
- package/build/get-resume.test.js +82 -0
- package/build/get-schema.js +21 -0
- package/build/init.js +53 -0
- package/build/main.js +80 -0
- package/build/main.test.js +136 -0
- package/build/render-html.js +45 -0
- package/build/render-html.test.js +49 -0
- package/build/serve.js +51 -0
- package/build/test-utils/cli-test-entry.js +21 -0
- package/build/test-utils/mocked-volume-builder.js +47 -0
- package/build/validate.js +28 -0
- package/build/validate.test.js +41 -0
- package/package.json +79 -0
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
|
+
[](https://matrix.to/#/#json-resume:one.ems.host)
|
|
4
|
+
[](https://github.com/jsonresume/resume-cli/actions)
|
|
5
|
+
[](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).
|
package/build/builder.js
ADDED
|
@@ -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
|
+
}
|