@thegetty/quire-cli 1.0.0-pre-release.13 → 1.0.0-pre-release.15

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@thegetty/quire-cli",
3
3
  "description": "Quire command-line interface",
4
- "version": "1.0.0-pre-release.13",
4
+ "version": "1.0.0-pre-release.15",
5
5
  "author": "Getty Digital",
6
6
  "license": "SEE LICENSE IN https://github.com/thegetty/quire/blob/main/LICENSE",
7
7
  "bugs": {
@@ -51,6 +51,7 @@
51
51
  "conf": "^10.2.0",
52
52
  "cross-env": "^7.0.3",
53
53
  "del": "^7.0.0",
54
+ "epubjs": "^0.4.2",
54
55
  "execa": "^6.1.0",
55
56
  "fs-extra": "^11.0.0",
56
57
  "hosted-git-info": "^6.1.1",
@@ -58,7 +59,9 @@
58
59
  "inquirer": "^9.1.4",
59
60
  "install-npm-version": "^1.0.8",
60
61
  "loglevel": "^1.8.0",
62
+ "open": "^8.4.0",
61
63
  "ora": "^6.1.2",
64
+ "pagedjs-cli": "^0.3.1",
62
65
  "semver": "^7.3.8",
63
66
  "simple-git": "^3.15.0",
64
67
  "update-notifier": "^6.0.2"
@@ -19,10 +19,9 @@ export default class BuildCommand extends Command {
19
19
  version: '1.0.0',
20
20
  args: [
21
21
  // [
22
- // '[formats]', 'output formats',
22
+ // '[formats...]', 'output formats',
23
23
  // {
24
- // choices: ['html', 'pdf', 'epub'],
25
- // default: ['html', 'html only']
24
+ // choices: ['pdf', 'epub'],
26
25
  // }
27
26
  // ],
28
27
  ],
@@ -20,15 +20,7 @@ export default class CleanCommand extends Command {
20
20
  description: 'Remove build outputs',
21
21
  summary: 'remove build outputs',
22
22
  version: '1.0.0',
23
- args: [
24
- // [
25
- // '[formats]', 'output formats',
26
- // {
27
- // choices: ['html', 'pdf', 'epub'],
28
- // default: ['html', 'html only']
29
- // }
30
- // ],
31
- ],
23
+ args: [],
32
24
  options: [
33
25
  [ '-d', '--dry-run', 'show paths to be cleaned without deleting files' ],
34
26
  [ '-p', '--progress', 'display progress of removing files' ],
@@ -52,7 +44,7 @@ export default class CleanCommand extends Command {
52
44
  ? `the following files ${options.dryRun ? 'will be' : 'have been'} deleted:`
53
45
  : 'no files to delete'
54
46
 
55
- console.debug(`[CLI] ${message} ${deletedPaths}`)
47
+ console.debug(`[CLI] ${message}\n${deletedPaths.join('\n')}`)
56
48
  }
57
49
 
58
50
  preAction(command) {
@@ -0,0 +1,67 @@
1
+ import Command from '#src/Command.js'
2
+ import { paths, projectRoot } from '#lib/11ty/index.js'
3
+ import fs from 'fs-extra'
4
+ import libEpub from '#lib/epub/index.js'
5
+ import open from 'open'
6
+ import path from 'node:path'
7
+
8
+ /**
9
+ * Quire CLI `build epub` Command
10
+ *
11
+ * Generate EPUB from Eleventy `build` output.
12
+ *
13
+ * @class EpubCommand
14
+ * @extends {Command}
15
+ */
16
+ export default class EpubCommand extends Command {
17
+ static definition = {
18
+ name: 'epub',
19
+ description: 'Generate publication EPUB',
20
+ summary: 'run build epub',
21
+ version: '1.0.0',
22
+ args: [],
23
+ options: [
24
+ [
25
+ '--lib <module>', 'use the specified epub library', 'epubjs',
26
+ { choices: ['epubjs', 'pandoc'], default: 'epubjs' }
27
+ ],
28
+ [ '--open', 'open EPUB in default application' ],
29
+ [ '--debug', 'run epub with debug output' ],
30
+ ],
31
+ }
32
+
33
+ constructor() {
34
+ super(EpubCommand.definition)
35
+ }
36
+
37
+ async action(options, command) {
38
+ if (options.debug) {
39
+ console.debug('[CLI] Command \'%s\' called with options %o', this.name(), options)
40
+ }
41
+
42
+ const input = path.join(projectRoot, paths.epub)
43
+
44
+ if (!fs.existsSync(input)) {
45
+ console.error(`Unable to find Epub input at ${input}\nPlease first run the 'quire build' command.`)
46
+ return
47
+ }
48
+
49
+ const output = path.join(projectRoot, `${options.lib}.epub`)
50
+
51
+ const epubLib = await libEpub(options.lib, { ...options.debug })
52
+ await epubLib(input, output, { ...options.debug })
53
+
54
+ if (fs.existsSync(output) && options.open) open(output)
55
+ }
56
+
57
+ /**
58
+ * test if build site has already be run and output can be reused
59
+ * @todo
60
+ */
61
+ preAction(command) {
62
+ const options = command.opts()
63
+ if (options.debug) {
64
+ console.debug('[CLI] Calling \'epub\' command pre-action with options', options)
65
+ }
66
+ }
67
+ }
@@ -1,5 +1,5 @@
1
- import { IS_WINDOWS } from '#helpers/os-utils.js'
2
- import { fileURLToPath, pathToFileURL } from 'node:url'
1
+ import { dynamicImport } from '#helpers/os-utils.js'
2
+ import { fileURLToPath } from 'node:url'
3
3
  import fs from 'fs-extra'
4
4
  import path from 'node:path'
5
5
 
@@ -15,10 +15,7 @@ const thisfile = path.basename(__filename)
15
15
  const commands = await Promise.all(
16
16
  fs.readdirSync(__dirname)
17
17
  .filter((file) => file !== thisfile && file.match(/^.*\.js$/))
18
- .map((file) => IS_WINDOWS
19
- ? import(pathToFileURL(path.resolve(__dirname, file)))
20
- : import(path.resolve(__dirname, file))
21
- )
18
+ .map(async (file) => await dynamicImport(path.resolve(__dirname, file)))
22
19
  ).then((modules) => {
23
20
  return modules.map(({ default: CommandClass }) => new CommandClass())
24
21
  })
@@ -0,0 +1,70 @@
1
+ import Command from '#src/Command.js'
2
+ import { paths, projectRoot } from '#lib/11ty/index.js'
3
+ import fs from 'fs-extra'
4
+ import libPdf from '#lib/pdf/index.js'
5
+ import open from 'open'
6
+ import path from 'node:path'
7
+ // import testcwd from '#helpers/test-cwd.js'
8
+
9
+ /**
10
+ * Quire CLI `pdf` Command
11
+ *
12
+ * Generate PDF from Eleventy `build` output.
13
+ *
14
+ * @class PDFCommand
15
+ * @extends {Command}
16
+ */
17
+ export default class PDFCommand extends Command {
18
+ static definition = {
19
+ name: 'pdf',
20
+ description: 'Generate publication PDF',
21
+ summary: 'run build pdf',
22
+ version: '1.0.0',
23
+ args: [
24
+ ],
25
+ options: [
26
+ [
27
+ '--lib <module>', 'use the specified pdf module', 'pagedjs',
28
+ { choices: ['pagedjs', 'prince'], default: 'pagedjs' }
29
+ ],
30
+ [ '--open', 'open PDF in default application' ],
31
+ [ '--debug', 'run build with debug output to console' ],
32
+ ],
33
+ }
34
+
35
+ constructor() {
36
+ super(PDFCommand.definition)
37
+ }
38
+
39
+ async action(options, command) {
40
+ if (options.debug) {
41
+ console.debug('[CLI] Command \'%s\' called with options %o', this.name(), options)
42
+ }
43
+
44
+ const input = path.join(projectRoot, paths.output, 'pdf.html')
45
+
46
+ if (!fs.existsSync(input)) {
47
+ console.error(`Unable to find PDF input at ${input}\nPlease first run the 'quire build' command.`)
48
+ return
49
+ }
50
+
51
+ const output = path.join(projectRoot, `${options.lib}.pdf`)
52
+
53
+ const pdfLib = await libPdf(options.lib, { ...options.debug })
54
+ await pdfLib(input, output, { ...options.debug })
55
+
56
+ try {
57
+ if (fs.existsSync(output) && options.open) open(output)
58
+ } catch (error) {
59
+ console.error(error)
60
+ process.exit(1)
61
+ }
62
+ }
63
+
64
+ /**
65
+ * @todo test if build has already be run and output can be reused
66
+ */
67
+ preAction(command) {
68
+ // testcwd(command)
69
+ }
70
+ }
@@ -18,10 +18,9 @@ export default class PreviewCommand extends Command {
18
18
  version: '1.0.0',
19
19
  args: [
20
20
  // [
21
- // '[formats]', 'output formats',
21
+ // '[formats...]', 'output formats',
22
22
  // {
23
- // choices: ['html', 'pdf', 'epub'],
24
- // default: ['html', 'html only']
23
+ // choices: ['pdf', 'epub'],
25
24
  // }
26
25
  // ],
27
26
  ],
@@ -41,9 +40,9 @@ export default class PreviewCommand extends Command {
41
40
  super(PreviewCommand.definition)
42
41
  }
43
42
 
44
- action(options = {}) {
43
+ async action(args, options, command) {
45
44
  if (options.debug) {
46
- console.info('Command \'%s\' called with options %o', this.name(), options)
45
+ console.debug('[CLI] Command \'%s\' called with arguments [%o] and options %o', this.name(), args.join(', '), options)
47
46
  }
48
47
 
49
48
  if (options['11ty'] === 'cli') {
@@ -9,8 +9,10 @@ import path from 'node:path'
9
9
  */
10
10
  export async function clean (projectRoot, paths, options = {}) {
11
11
  const pathsToClean = [
12
+ path.join(projectRoot, paths.epub),
12
13
  path.join(projectRoot, paths.output),
13
- path.join(projectRoot, paths.public),
14
+ path.join(projectRoot, '*.epub'),
15
+ path.join(projectRoot, '*.pdf'),
14
16
  ]
15
17
 
16
18
  /**
@@ -1,5 +1,25 @@
1
+ import { pathToFileURL } from 'node:url'
2
+
1
3
  /**
2
4
  * Determine if the process is running on Windows operating system
3
5
  */
4
6
  export const IS_WINDOWS =
5
7
  process.platform === 'win32' || /^(cygwin|msys)$/.test(process.env.OSTYPE)
8
+
9
+ /**
10
+ * Cross platform dynamic module import
11
+ * On the Windows platform ESM modules are loaded using the file:// scheme.
12
+ *
13
+ * Nota bene: a relative `path` argument is relative to this module!
14
+ *
15
+ *
16
+ * @param {String} path An aboslute path for the module to import
17
+ * @return {Object} Imported module
18
+ */
19
+ export const dynamicImport = async (path) => {
20
+ try {
21
+ return IS_WINDOWS ? await import(pathToFileURL(path)) : await import(path)
22
+ } catch (error) {
23
+ console.error(`[CLI] unable to import ${path}`, error)
24
+ }
25
+ }
@@ -32,7 +32,7 @@ const factory = async (options = {}) => {
32
32
  */
33
33
  const copyOptions = {
34
34
  debug: options.debug || false,
35
- expand: true,
35
+ expand: false,
36
36
  }
37
37
 
38
38
  /**
@@ -83,11 +83,19 @@ const factory = async (options = {}) => {
83
83
  return addPassthroughCopy(entry, copyOptions)
84
84
  }
85
85
  }
86
+
87
+ /**
88
+ * Event callback when a build completes
89
+ * @see https://www.11ty.dev/docs/events/#eleventy.after
90
+ */
91
+ eleventyConfig.on('eleventy.after', async () => {
92
+ console.debug('[11ty:API] build complete')
93
+ })
94
+
86
95
  return eleventyConfig
87
96
  },
88
97
  configPath: options.config || config,
89
98
  quietMode: options.quiet || false,
90
- // source: 'script',
91
99
  })
92
100
 
93
101
  return eleventy
@@ -111,9 +119,11 @@ export default {
111
119
 
112
120
  eleventy.setDryRun(options.dryrun)
113
121
 
114
- await eleventy.executeBuild()
122
+ await eleventy.write()
115
123
  },
116
124
  serve: async (options = {}) => {
125
+ process.cwd(projectRoot)
126
+
117
127
  console.info('[CLI:11ty] running development server')
118
128
 
119
129
  process.env.ELEVENTY_ENV = 'development'
@@ -82,10 +82,10 @@ export default {
82
82
 
83
83
  command.push('--serve')
84
84
 
85
- env.ELEVENTY_ENV = 'development'
86
-
87
85
  if (options.port) command.push(`--port=${options.port}`)
88
86
 
87
+ env.ELEVENTY_ENV = 'development'
88
+
89
89
  await execa('node', command, {
90
90
  all: true,
91
91
  cwd: projectRoot,
@@ -23,19 +23,8 @@ const cliRoot = path.resolve(__dirname, path.join('..', '..'))
23
23
  const eleventyConfig = '.eleventy.js'
24
24
  const version = 'latest'
25
25
 
26
- // Test project directory for an eleventy configuration file
27
- const hasEleventyConfig = (dir) => {
28
- try {
29
- return fs.readdirSync(dir).includes(eleventyConfig)
30
- } catch (error) {
31
- throw new Error(`[CLI:11ty] Unable to read project directory for eleventy config ${error}`)
32
- }
33
- }
34
-
35
26
  export const projectRoot = process.cwd()
36
27
 
37
- const inputDir = path.join(projectRoot, 'content')
38
-
39
28
  /**
40
29
  * Absolute path to the latest installed version of `quire-11ty`
41
30
  * @todo use version read from the project `.quire-version` file
@@ -47,13 +36,55 @@ const libQuirePath = path.resolve(__dirname, path.join(cliRoot, 'lib', 'quire',
47
36
  * Nota bene: to get a relative path to the `eleventyRoot`,
48
37
  * for example when the version is specified is 'latest',
49
38
  * it must be set to the real path to the symlink target.
39
+ *
40
+ * @todo refactor how and *when* the eleventyRoot is determined:
41
+ * - we only need eleventyRoot for cli commands that run 11ty
42
+ * - paths module is a concern of the `quire` module
43
+ * - global quire-cli installation
44
+ * - local quire-cli installation
45
+ *
46
+ * For an excellent developer experience, the quire-11ty code should be
47
+ * installed into an `11ty` or `quire-11ty` directory in the `projectRoot`
48
+ * this will allow us to more easily manage symlinks for local development
49
+ * using unpublished `quire-11ty` code.
50
+ * @example
51
+ * ```sh
52
+ * blargh/
53
+ * .git/
54
+ * .gitignore
55
+ * .node-version
56
+ * .quire-version
57
+ * 11ty@ --> /Users/mph/Code/Getty/quire/packages/11ty
58
+ * content/
59
+ * CHANGELOG.md
60
+ * LICENSE
61
+ * README.md
62
+ * package.json
63
+ * package-lock.json
64
+ * ```
65
+ * Installing to a directory will allow more cleanly `eject` and `uneject`
66
+ * using a symlink or single directory `rm -rf`.
67
+ * Should the `11ty` directory be a dot directory to hide the complexity from
68
+ * users or should it be visible to be more explicit when project is ejected?
50
69
  */
51
- export const eleventyRoot = hasEleventyConfig(projectRoot)
52
- ? projectRoot
53
- : fs.realpathSync(libQuirePath)
70
+ const getEleventyRoot = () => {
71
+ // try {
72
+ // return fs.readdirSync(projectRoot).includes(eleventyConfig)
73
+ // ? projectRoot
74
+ // : fs.realpathSync(libQuirePath)
75
+ // } catch (error) {
76
+ // throw new Error(`[CLI:11ty] Unable to read project directory for eleventy config ${error}`)
77
+ // }
78
+ return projectRoot
79
+ }
80
+
81
+ export const eleventyRoot = getEleventyRoot()
82
+
83
+ const inputDir = path.join(projectRoot, 'content')
54
84
 
55
85
  export default {
56
86
  config: path.join(eleventyRoot, '.eleventy.js'),
87
+ epub: path.relative(eleventyRoot, path.join(projectRoot, '_epub')),
57
88
  input: path.relative(eleventyRoot, inputDir),
58
89
  output: path.relative(eleventyRoot, path.join(projectRoot, '_site')),
59
90
  data: path.relative(inputDir, path.join(eleventyRoot, '_computed')),
@@ -0,0 +1,7 @@
1
+ ## CLI lib/EPUB Module
2
+
3
+ This module implements a façade for EPUB e-book file format generation libries. The module exports a single method that accepts a `lib` option and delegates to a façade for the specified EPUB library.
4
+
5
+ ### Epub.js Façade
6
+
7
+ See the [`Epub.js` documentation](https://github.com/futurepress/epub.js).
@@ -0,0 +1,9 @@
1
+ // import epub from 'epubjs'
2
+
3
+ /**
4
+ * A façade module for the EPUB.js library
5
+ * @see https://github.com/futurepress/epub.js
6
+ */
7
+ export default async (input, output, options) => {
8
+ console.info('[CLI:lib/epubjs] integration with epub.js is not yet implemented.')
9
+ }
@@ -0,0 +1,36 @@
1
+ import { dynamicImport } from '#helpers/os-utils.js'
2
+ import { fileURLToPath } from 'node:url'
3
+ import path from 'node:path'
4
+
5
+ const __filename = fileURLToPath(import.meta.url)
6
+ const __dirname = path.dirname(__filename)
7
+
8
+ /**
9
+ * A façade delegation module for EPUB libraries
10
+ */
11
+ export default async (lib = 'epubjs', options = {}) => {
12
+ let libName, libPath
13
+
14
+ switch (lib.toLowerCase()) {
15
+ case 'epubjs': {
16
+ libName = 'Epub.js'
17
+ libPath = path.join(__dirname, 'epub.js')
18
+ break
19
+ }
20
+ case 'pandoc': {
21
+ libName = 'Pandoc'
22
+ libPath = path.join(__dirname, 'pandoc.js')
23
+ break
24
+ }
25
+ default:
26
+ console.error(`[CLI:lib/pdf] Unrecognized EPUB library '${lib}'`)
27
+ return
28
+ }
29
+
30
+ const { default: epubLib } = await dynamicImport(libPath)
31
+
32
+ return async (input, output, options = {}) => {
33
+ console.info(`[CLI:lib/epub] generating EPUB using ${libName}`)
34
+ return await epubLib(input, output, options)
35
+ }
36
+ }
@@ -0,0 +1,42 @@
1
+ import { execa } from 'execa'
2
+ import fs from 'fs-extra'
3
+ import path from 'node:path'
4
+ import paths, { projectRoot } from '#lib/11ty/paths.js'
5
+ import which from '#helpers/which.js'
6
+
7
+ /**
8
+ * Filter directory entries for XHTML files
9
+ *
10
+ * @param {fs.Dirent} dirent directory entry
11
+ * @return {fs.Dirent} XHTML entries
12
+ */
13
+ const xhtmlFiles = (dirent) => {
14
+ const stats = fs.lstatSync(dirent)
15
+ const { ext } = path.parse(dirent)
16
+ return stats.isFile() && ext === '.xhtml'
17
+ }
18
+
19
+ /**
20
+ * A façade module for interacting with Pandoc CLI.
21
+ * @see https://pandoc.org/MANUAL.html#general-options
22
+ */
23
+ export default async (input, output, options) => {
24
+ which('pandoc')
25
+
26
+ const inputs = fs.readdirSync(input)
27
+ .map((entry) => path.join(input, entry))
28
+ .filter(xhtmlFiles)
29
+
30
+ const cmdOptions = [
31
+ `--from=html-native_divs+native_spans`,
32
+ `--to=epub`,
33
+ `--output=${output}`,
34
+ // `--epub-metadata=${path.join(input, 'dc.xml')}`,
35
+ // `--epub-cover-image=${coverImage}`,
36
+ // `--template=${path.join(input, 'template.xhtml')}`,
37
+ `--css=${path.join(input, '_assets', 'epub.css')}`,
38
+ `--standalone`,
39
+ ]
40
+
41
+ await execa('pandoc', [...cmdOptions, ...inputs])
42
+ }
@@ -0,0 +1,12 @@
1
+ ## CLI lib/PDF Module
2
+
3
+ This module implements a façade for PDF generation libries.
4
+ The module exports a single method that accepts a `lib` option and delegates to a façade for the specified PDF library.
5
+
6
+ ### Paged.js Façade
7
+
8
+ See the [`Paged.js` documentation](https://gitlab.coko.foundation/pagedjs/).
9
+
10
+ ### Prince XML Façade
11
+
12
+ See the [Prince Command-line Reference](https://www.princexml.com/doc/command-line/).
@@ -0,0 +1,38 @@
1
+ import { dynamicImport } from '#helpers/os-utils.js'
2
+ import { fileURLToPath } from 'node:url'
3
+ import path from 'node:path'
4
+
5
+ const __filename = fileURLToPath(import.meta.url)
6
+ const __dirname = path.dirname(__filename)
7
+
8
+ /**
9
+ * A façade delegation module for PDF generation libraries
10
+ */
11
+ export default async (lib = 'pagedjs', options = {}) => {
12
+ let libName, libPath
13
+
14
+ switch (lib.toLowerCase()) {
15
+ case 'paged':
16
+ case 'pagedjs': {
17
+ libName = 'Paged.js'
18
+ libPath = path.join(__dirname, 'paged.js')
19
+ break
20
+ }
21
+ case 'prince':
22
+ case 'princexml': {
23
+ libName = 'Prince'
24
+ libPath = path.join(__dirname, 'prince.js')
25
+ break
26
+ }
27
+ default:
28
+ console.error(`[CLI:lib/pdf] Unrecognized PDF library '${lib}'`)
29
+ return
30
+ }
31
+
32
+ const { default: pdfLib } = await dynamicImport(libPath)
33
+
34
+ return async (input, output, options = {}) => {
35
+ console.info(`[CLI:lib/pdf] generating PDF using ${libName}`)
36
+ return await pdfLib(input, output, options)
37
+ }
38
+ }
@@ -0,0 +1,42 @@
1
+ import Printer from 'pagedjs-cli'
2
+ import fs from 'fs-extra'
3
+
4
+ /**
5
+ * A façade module for interacting with Paged.js
6
+ * @see https://gitlab.coko.foundation/pagedjs/
7
+ */
8
+ export default async (input, output, options = {}) => {
9
+ /**
10
+ * Configure the Paged.js Printer options
11
+ * @see https://gitlab.coko.foundation/pagedjs/pagedjs-cli/-/blob/main/src/cli.js
12
+ */
13
+ const printerOptions = {
14
+ allowLocal: true,
15
+ debug: options.debug || false,
16
+ }
17
+
18
+ const printer = new Printer(printerOptions)
19
+
20
+ /**
21
+ * Configure the Paged.js PDF options
22
+ * @see
23
+ *
24
+ * @todo verify options before setting them
25
+ * @todo catch errors thrown by the printer
26
+ */
27
+ const pdfOptions = {
28
+ outlineTags: options.outlineTags || ['h1'],
29
+ }
30
+
31
+ try {
32
+ console.info(`[CLI:lib/pdf/pagedjs] printing ${input}`)
33
+ const file = await printer.pdf(input, pdfOptions)
34
+ .catch((error) => console.error(error))
35
+
36
+ if (file && output) {
37
+ fs.writeFile(output, file, (error) => { if (error) throw error })
38
+ }
39
+ } catch (ERR_FILE_NOT_FOUND) {
40
+ console.error(`[CLI:lib/pdf/pagedjs] file not found ${input}`)
41
+ }
42
+ }
@@ -0,0 +1,17 @@
1
+ import { execa } from 'execa'
2
+ import which from '#helpers/which.js'
3
+
4
+ /**
5
+ * A façade module for interacting with the Prince CLI.
6
+ * @see https://www.princexml.com/doc/command-line/
7
+ */
8
+ export default async (input, output, options = {}) => {
9
+ which('prince')
10
+
11
+ const cmdOptions = [
12
+ `--output=${output}`,
13
+ ]
14
+
15
+ const { stderror, stdout } = await execa('prince', [...cmdOptions, input])
16
+ return { stderror, stdout }
17
+ }
package/src/main.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Command } from 'commander'
1
+ import { Argument, Command, Option } from 'commander'
2
2
  import commands from './commands/index.js'
3
3
  import packageConfig from '../package.json' assert { type: 'json' }
4
4
 
@@ -37,9 +37,15 @@ commands.forEach((command) => {
37
37
  aliases.forEach((alias) => subCommand.alias(alias))
38
38
  }
39
39
 
40
+ /**
41
+ * @see https://github.com/tj/commander.js#more-configuration-1
42
+ */
40
43
  if (Array.isArray(args)) {
41
- args.forEach(([ arg, value, callback ]) => {
42
- subCommand.argument(arg, value, callback)
44
+ args.forEach(([ name, description, configuration = {} ]) => {
45
+ const argument = new Argument(name, description)
46
+ if (configuration.choices) argument.choices(configuration.choices)
47
+ if (configuration.default) argument.default(configuration.default)
48
+ subCommand.addArgument(argument)
43
49
  })
44
50
  }
45
51
 
@@ -47,16 +53,16 @@ commands.forEach((command) => {
47
53
  * @see https://github.com/tj/commander.js/#options
48
54
  */
49
55
  if (Array.isArray(options)) {
50
- options.forEach((option) => {
51
- if (Array.isArray(option)) {
56
+ options.forEach((entry) => {
57
+ if (Array.isArray(entry)) {
52
58
  // ensure we can join the first two attributes as the option name
53
59
  // when only the short or the long flag is defined in the array
54
- if (option[0].startsWith('--')) option.unshift('\u0020'.repeat(4))
55
- if (option[0].startsWith('-') && !option[1].startsWith('--')) {
56
- option.splice(1, 0, '')
60
+ if (entry[0].startsWith('--')) entry.unshift('\u0020'.repeat(4))
61
+ if (entry[0].startsWith('-') && !entry[1].startsWith('--')) {
62
+ entry.splice(1, 0, '')
57
63
  }
58
64
  // assign attribute names to the array of option attributes
59
- const [ short, long, description, defaultValue ] = option
65
+ const [ short, long, description, defaultValue ] = entry
60
66
  // join short and long flags as the option name attribute
61
67
  const name = /^-\w/.test(short) && /^--\w/.test(long)
62
68
  ? [short, long].join(', ')
@@ -64,11 +70,13 @@ commands.forEach((command) => {
64
70
  subCommand.option(name, description, defaultValue)
65
71
  } else {
66
72
  /**
67
- * @todo allow option attributes to be defined using an object
73
+ * @todo allow options to be defined by a configuration object
68
74
  * @see https://github.com/tj/commander.js/#more-configuration
69
75
  */
70
- // const option = new Option(name, description, defaultValue)
71
- // if (attributes.blarg) option.blagh(attributes.blarg)
76
+ // const option = new Option(name, description)
77
+ // for (const property of configuration) {
78
+ // option[property](configuration[property])
79
+ // }
72
80
  // subCommand.addOption(option)
73
81
  console.error('@TODO please use an array to define option attributes')
74
82
  }