Skip to content

Commit e6a5e28

Browse files
authored
feat: Add CLI tool (#523)
Skip CI tests for Node.js < 20
1 parent 959f1cf commit e6a5e28

17 files changed

+503
-27
lines changed

README.md

+11-9
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ npm install yaml
3333
The API provided by `yaml` has three layers, depending on how deep you need to go: [Parse & Stringify](https://eemeli.org/yaml/#parse-amp-stringify), [Documents](https://eemeli.org/yaml/#documents), and the underlying [Lexer/Parser/Composer](https://eemeli.org/yaml/#parsing-yaml).
3434
The first has the simplest API and "just works", the second gets you all the bells and whistles supported by the library along with a decent [AST](https://eemeli.org/yaml/#content-nodes), and the third lets you get progressively closer to YAML source, if that's your thing.
3535

36+
A [command-line tool](https://eemeli.org/yaml/#command-line-tool) is also included.
37+
3638
```js
3739
import { parse, stringify } from 'yaml'
3840
// or
@@ -55,26 +57,26 @@ const YAML = require('yaml')
5557
- [`#directives`](https://eemeli.org/yaml/#stream-directives)
5658
- [`#errors`](https://eemeli.org/yaml/#errors)
5759
- [`#warnings`](https://eemeli.org/yaml/#errors)
58-
- [`isDocument(foo): boolean`](https://eemeli.org/yaml/#identifying-nodes)
60+
- [`isDocument(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types)
5961
- [`parseAllDocuments(str, options?): Document[]`](https://eemeli.org/yaml/#parsing-documents)
6062
- [`parseDocument(str, options?): Document`](https://eemeli.org/yaml/#parsing-documents)
6163

6264
### Content Nodes
6365

64-
- [`isAlias(foo): boolean`](https://eemeli.org/yaml/#identifying-nodes)
65-
- [`isCollection(foo): boolean`](https://eemeli.org/yaml/#identifying-nodes)
66-
- [`isMap(foo): boolean`](https://eemeli.org/yaml/#identifying-nodes)
67-
- [`isNode(foo): boolean`](https://eemeli.org/yaml/#identifying-nodes)
68-
- [`isPair(foo): boolean`](https://eemeli.org/yaml/#identifying-nodes)
69-
- [`isScalar(foo): boolean`](https://eemeli.org/yaml/#identifying-nodes)
70-
- [`isSeq(foo): boolean`](https://eemeli.org/yaml/#identifying-nodes)
66+
- [`isAlias(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types)
67+
- [`isCollection(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types)
68+
- [`isMap(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types)
69+
- [`isNode(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types)
70+
- [`isPair(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types)
71+
- [`isScalar(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types)
72+
- [`isSeq(foo): boolean`](https://eemeli.org/yaml/#identifying-node-types)
7173
- [`new Scalar(value)`](https://eemeli.org/yaml/#scalar-values)
7274
- [`new YAMLMap()`](https://eemeli.org/yaml/#collections)
7375
- [`new YAMLSeq()`](https://eemeli.org/yaml/#collections)
7476
- [`doc.createAlias(node, name?): Alias`](https://eemeli.org/yaml/#working-with-anchors)
7577
- [`doc.createNode(value, options?): Node`](https://eemeli.org/yaml/#creating-nodes)
7678
- [`doc.createPair(key, value): Pair`](https://eemeli.org/yaml/#creating-nodes)
77-
- [`visit(node, visitor)`](https://eemeli.org/yaml/#modifying-nodes)
79+
- [`visit(node, visitor)`](https://eemeli.org/yaml/#finding-and-modifying-nodes)
7880

7981
### Parsing YAML
8082

bin.mjs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env node
2+
3+
import { UserError, cli, help } from './dist/cli.mjs'
4+
5+
cli(process.stdin, error => {
6+
if (error instanceof UserError) {
7+
if (error.code === UserError.ARGS) console.error(`${help}\n`)
8+
console.error(error.message)
9+
process.exitCode = error.code
10+
} else if (error) throw error
11+
})

config/jest.config.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
let moduleNameMapper
22
const transform = {
3-
'[/\\\\]tests[/\\\\].*\\.(js|ts)$': [
3+
'[/\\\\]tests[/\\\\].*\\.(m?js|ts)$': [
44
'babel-jest',
55
{ configFile: './config/babel.config.js' }
66
]
@@ -12,16 +12,22 @@ switch (process.env.npm_lifecycle_event) {
1212
console.log('Testing build output from dist/')
1313
moduleNameMapper = {
1414
'^yaml$': '<rootDir>/dist/index.js',
15+
'^yaml/cli$': '<rootDir>/dist/cli.mjs',
1516
'^yaml/util$': '<rootDir>/dist/util.js',
1617
'^../src/test-events$': '<rootDir>/dist/test-events.js'
1718
}
19+
transform['[/\\\\]dist[/\\\\].*\\.mjs$'] = [
20+
'babel-jest',
21+
{ configFile: './config/babel.config.js' }
22+
]
1823
break
1924

2025
case 'test':
2126
default:
2227
process.env.TRACE_LEVEL = 'log'
2328
moduleNameMapper = {
2429
'^yaml$': '<rootDir>/src/index.ts',
30+
'^yaml/cli$': '<rootDir>/src/cli.ts',
2531
'^yaml/util$': '<rootDir>/src/util.ts'
2632
}
2733
transform['[/\\\\]src[/\\\\].*\\.ts$'] = [

config/rollup.node-config.mjs

+23-14
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
1+
import { chmod, stat } from 'node:fs/promises'
12
import typescript from '@rollup/plugin-typescript'
23

3-
export default {
4-
input: {
5-
index: 'src/index.ts',
6-
'test-events': 'src/test-events.ts',
7-
util: 'src/util.ts'
4+
export default [
5+
{
6+
input: {
7+
index: 'src/index.ts',
8+
'test-events': 'src/test-events.ts',
9+
util: 'src/util.ts'
10+
},
11+
output: {
12+
dir: 'dist',
13+
format: 'cjs',
14+
esModule: false,
15+
preserveModules: true
16+
},
17+
plugins: [typescript()],
18+
treeshake: { moduleSideEffects: false, propertyReadSideEffects: false }
819
},
9-
output: {
10-
dir: 'dist',
11-
format: 'cjs',
12-
esModule: false,
13-
preserveModules: true
14-
},
15-
plugins: [typescript()],
16-
treeshake: { moduleSideEffects: false, propertyReadSideEffects: false }
17-
}
20+
{
21+
input: 'src/cli.ts',
22+
output: { file: 'dist/cli.mjs' },
23+
external: () => true,
24+
plugins: [typescript()]
25+
}
26+
]

docs/01_intro.md

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ This requirement may be updated between minor versions of the library.
4343
The API provided by `yaml` has three layers, depending on how deep you need to go: [Parse & Stringify](#parse-amp-stringify), [Documents](#documents), and the underlying [Lexer/Parser/Composer](#parsing-yaml).
4444
The first has the simplest API and "just works", the second gets you all the bells and whistles supported by the library along with a decent [AST](#content-nodes), and the third lets you get progressively closer to YAML source, if that's your thing.
4545

46+
A [command-line tool](#command-line-tool) is also included.
47+
4648
<h3>Parse & Stringify</h3>
4749

4850
```js

docs/09_cli.md

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Command-line Tool
2+
3+
Available as `npx yaml` or `npm exec yaml`:
4+
5+
<pre id="cli-help" style="float: none">
6+
yaml: A command-line YAML processor and inspector
7+
8+
Reads stdin and writes output to stdout and errors & warnings to stderr.
9+
10+
Usage:
11+
yaml Process a YAML stream, outputting it as YAML
12+
yaml cst Parse the CST of a YAML stream
13+
yaml lex Parse the lexical tokens of a YAML stream
14+
yaml valid Validate a YAML stream, returning 0 on success
15+
16+
Options:
17+
--help, -h Show this message.
18+
--json, -j Output JSON.
19+
20+
Additional options for bare "yaml" command:
21+
--doc, -d Output pretty-printed JS Document objects.
22+
--single, -1 Require the input to consist of a single YAML document.
23+
--strict, -s Stop on errors.
24+
--visit, -v Apply a visitor to each document (requires a path to import)
25+
--yaml 1.1 Set the YAML version. (default: 1.2)
26+
</pre>
File renamed without changes.

docs/index.html.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ includes:
1515
- 06_custom_tags
1616
- 07_parsing_yaml
1717
- 08_errors
18-
- 09_yaml_syntax
18+
- 09_cli
19+
- 10_yaml_syntax
1920

2021
search: true
2122
---

docs/prepare-docs.mjs

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,30 @@
11
#!/usr/bin/env node
22

3-
import { lstat, mkdir, readdir, readFile, symlink, rm } from 'node:fs/promises'
3+
import {
4+
lstat,
5+
mkdir,
6+
readdir,
7+
readFile,
8+
symlink,
9+
rm,
10+
writeFile
11+
} from 'node:fs/promises'
412
import { resolve } from 'node:path'
13+
import { help } from '../dist/cli.mjs'
514
import { parseAllDocuments } from '../dist/index.js'
615

716
const source = 'docs'
817
const target = 'docs-slate/source'
918

19+
// Update CLI help
20+
const cli = resolve(source, '09_cli.md')
21+
const docs = await readFile(cli, 'utf-8')
22+
const update = docs.replace(
23+
/(<pre id="cli-help".*?>).*?(<\/pre>)/s,
24+
'$1\n' + help + '\n$2'
25+
)
26+
if (update !== docs) await writeFile(cli, update)
27+
1028
// Create symlink for index.html.md
1129
const indexSource = resolve(source, 'index.html.md')
1230
const indexTarget = resolve(target, 'index.html.md')

package-lock.json

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
],
1919
"type": "commonjs",
2020
"main": "./dist/index.js",
21+
"bin": "./bin.mjs",
2122
"browser": {
2223
"./dist/index.js": "./browser/index.js",
2324
"./dist/util.js": "./browser/dist/util.js",

0 commit comments

Comments
 (0)