@thegetty/quire-cli 1.0.0-rc.2 → 1.0.0-rc.20

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +87 -7
  2. package/README.md +14 -0
  3. package/bin/cli.js +20 -8
  4. package/package.json +16 -9
  5. package/patches/README.md +19 -0
  6. package/patches/install-npm-version+1.0.9.patch +12119 -0
  7. package/src/Command.js +14 -0
  8. package/src/LOGGING.md +16 -0
  9. package/src/commands/README.md +28 -8
  10. package/src/commands/conf.js +43 -0
  11. package/src/commands/config/config-get.js +35 -0
  12. package/src/commands/config/config-reset.js +34 -0
  13. package/src/commands/config/config-set.js +37 -0
  14. package/src/commands/create.js +25 -7
  15. package/src/commands/epub.js +1 -2
  16. package/src/commands/info.js +128 -0
  17. package/src/commands/pdf.js +59 -10
  18. package/src/helpers/clean.js +5 -0
  19. package/src/helpers/is-empty.js +4 -0
  20. package/src/helpers/is-quire.js +4 -0
  21. package/src/helpers/os-utils.js +4 -0
  22. package/src/helpers/test-cwd.js +4 -0
  23. package/src/helpers/which.js +4 -0
  24. package/src/lib/conf/README.md +104 -0
  25. package/src/lib/conf/config.js +41 -0
  26. package/src/lib/conf/defaults.js +37 -0
  27. package/src/lib/conf/migrations.js +11 -0
  28. package/src/lib/conf/schema.js +30 -0
  29. package/src/lib/epub/index.js +3 -1
  30. package/src/lib/pdf/README.md +11 -4
  31. package/src/lib/pdf/index.js +7 -5
  32. package/src/lib/pdf/paged.js +88 -9
  33. package/src/lib/pdf/pagedPlugin.js +49 -0
  34. package/src/lib/pdf/prince.js +78 -4
  35. package/src/lib/pdf/princePlugin.js +35 -0
  36. package/src/lib/pdf/split.js +61 -0
  37. package/src/lib/quire/index.js +68 -58
  38. package/src/main.js +26 -6
  39. package/src/lib/11ty/plugins/before.js +0 -16
  40. package/src/lib/config/README.md +0 -3
  41. package/src/lib/config/index.js +0 -1
  42. package/src/lib/quire/constants.js +0 -6
  43. package/src/lib/quire/project.js +0 -104
@@ -3,18 +3,22 @@ import { chdir, cwd } from 'node:process'
3
3
  import { execa, execaCommand } from 'execa'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  import { isEmpty } from '#helpers/is-empty.js'
6
+ import fetch from 'node-fetch'
6
7
  import fs from 'fs-extra'
7
8
  import git from '#src/lib/git/index.js'
8
9
  import inv from 'install-npm-version'
9
10
  import path from 'node:path'
10
11
  import semver from 'semver'
11
12
 
12
- const __filename = fileURLToPath(import.meta.url)
13
- const __dirname = path.dirname(__filename)
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
14
+
15
+ const packagePath = path.join(__dirname, 'package.json')
16
+ const packageConfig = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
17
+
18
+ const { name: PACKAGE_NAME } = packageConfig
14
19
 
15
20
  // Version install path is relative to process working directory
16
21
  const INSTALL_PATH = path.join('src', 'lib', 'quire', 'versions')
17
- const PACKAGE_NAME = '@thegetty/quire-11ty'
18
22
  const VERSION_FILE = '.quire'
19
23
 
20
24
  /**
@@ -48,25 +52,19 @@ function getVersion(projectPath) {
48
52
  }
49
53
 
50
54
  /**
51
- * Read the required `quire-11ty` version from starter `package.json` `peerDependencies`
55
+ * Read the required `quire-11ty` and starter versions from starter `package.json` `peerDependencies`
52
56
  *
53
57
  * @param {String} projectPath Absolute system path to the project root
54
58
  *
55
- * @return {String} version Quire-11ty semantic version with caret or other
56
- * comparators trimmed off the beginning
57
- *
58
- * @TODO refactor `latest()` function to programmatically return a specific
59
- * version of `@thegetty/quire-11ty` from a semantic version string
60
- * (i.e `^1.0.0-pre-release.0` => `1.0.0-pre-release.2`) so this string-trimming
61
- * logic can be removed
59
+ * @return {Object}
60
+ * @property {String} quire11tyVersion Latest compatible Quire-11ty semantic version
61
+ * @property {String} starterVersion Starter project version defined in the starter package.json
62
62
  */
63
- async function getVersionFromStarter(projectPath) {
64
- const packageConfig = fs.readFileSync(path.join(projectPath, 'package.json'), { encoding:'utf8' })
65
- const { peerDependencies } = JSON.parse(packageConfig)
66
- const version = peerDependencies[PACKAGE_NAME]
67
- return version === 'latest'
68
- ? await latest()
69
- : version.substr(version.search(/\d/))
63
+ async function getVersionsFromStarter(projectPath) {
64
+ const projectPackageConfig = fs.readFileSync(path.join(projectPath, 'package.json'), { encoding:'utf8' })
65
+ const { peerDependencies, version: starterVersion } = JSON.parse(projectPackageConfig)
66
+ const quire11tyVersion = peerDependencies[PACKAGE_NAME]
67
+ return { quire11tyVersion, starterVersion }
70
68
  }
71
69
 
72
70
  /**
@@ -79,7 +77,7 @@ async function getVersionFromStarter(projectPath) {
79
77
  * @return {String} quireVersion A string indicating the current version
80
78
  * of quire being used with a new project
81
79
  */
82
- async function initStarter (starter, projectPath) {
80
+ async function initStarter (starter, projectPath, options) {
83
81
  projectPath = projectPath || cwd()
84
82
 
85
83
  // ensure that the target path exists
@@ -93,8 +91,6 @@ async function initStarter (starter, projectPath) {
93
91
  return
94
92
  }
95
93
 
96
- starter = starter || 'https://github.com/thegetty/quire-starter-default'
97
-
98
94
  console.debug('[CLI:quire] init-starter',
99
95
  `\n project root: "${projectPath}"`,
100
96
  `\n starter: "${starter}"`
@@ -110,12 +106,24 @@ async function initStarter (starter, projectPath) {
110
106
  .catch((error) => console.error('[CLI:error] ', error))
111
107
 
112
108
  /**
113
- * Determine `quire-11ty` version required by the starter project
114
- * and write a `.quire` file with the semantic version string.
109
+ * Determine the `quire-11ty` version to use in the new project,
110
+ * from the `quireVersion` option or as required by the starter project.
111
+ *
112
+ * Uses `latest` to get the latest semantic version compatible with version ranges
115
113
  */
116
- const quireVersion = await getVersionFromStarter(projectPath)
114
+ const { quire11tyVersion, starterVersion } = await getVersionsFromStarter(projectPath)
115
+ const quireVersion = await latest(options.quireVersion || quire11tyVersion)
117
116
  setVersion(projectPath, quireVersion)
118
117
 
118
+ /**
119
+ * Write quire-11ty, cli, starter name and version to VERSION_FILE
120
+ */
121
+ const versionInfo = {
122
+ cli: packageConfig.version,
123
+ starter: `${starter}@${starterVersion}`,
124
+ }
125
+ fs.writeFileSync(path.join(projectPath, VERSION_FILE), JSON.stringify(versionInfo))
126
+
119
127
  // Re-initialize project directory as a new git repository
120
128
  await fs.remove(path.join(projectPath, '.git'))
121
129
 
@@ -135,18 +143,19 @@ async function initStarter (starter, projectPath) {
135
143
  */
136
144
  const projectFiles = fs.readdirSync(projectPath)
137
145
  await git.init().add(projectFiles).commit('Initial Commit')
138
-
139
146
  return quireVersion
140
147
  }
141
148
 
142
149
  /**
143
- * Install a specific version of `quire-11ty`
150
+ * Install `quire-11ty`, default to 'latest' version
151
+ *
152
+ * @TODO refactor this to be callable by the installInProject method
144
153
  *
145
- * @param {String} version Quire-11ty semantic version
146
154
  * @param {Object} options options passed from `quire new` command
147
155
  * @return {Promise}
148
156
  */
149
- async function install(version, options={}) {
157
+ async function install(options = {}) {
158
+ const version = options.quireVersion || 'latest'
150
159
  console.debug(`[CLI:quire] installing quire-11ty@${version}`)
151
160
  const absoluteInstallPath = path.join(__dirname, 'versions')
152
161
  fs.ensureDirSync(absoluteInstallPath)
@@ -185,19 +194,23 @@ async function install(version, options={}) {
185
194
  }
186
195
 
187
196
  /**
188
- * Install a specific version of `quire-11ty` directly into a quire project
197
+ * Install `quire-11ty` directly into a quire project
198
+ *
199
+ * @TODO refactor this to use the install method with pre and post-install hooks
200
+ * or steps to prepare the working directory and cleanup on error and completion
189
201
  *
190
202
  * @param {String} projectPath Absolute system path to the project root
191
- * @param {String} version Quire-11ty semantic version
192
203
  * @param {Object} options options passed from `quire new` command
193
204
  * @return {Promise}
194
205
  */
195
- async function installInProject(projectPath, version, options={}) {
196
- console.debug(`[CLI:quire] installing quire-11ty@${version} into ${projectPath}`)
206
+ async function installInProject(projectPath, quireVersion, options = {}) {
207
+ const { quirePath } = options
208
+ const quire11tyPackage = fs.existsSync(quirePath) ? quirePath : `${PACKAGE_NAME}@${quireVersion}`
209
+ console.debug(`[CLI:quire] installing ${quire11tyPackage} into ${projectPath}`)
197
210
 
198
211
  /**
199
- * delete `package.json` from starter project, as it will be replaced with
200
- * `package.json` from `@thegetty/quire-11ty`
212
+ * Delete the starter project package configuration so that it can be replaced
213
+ * with the `@thegetty/quire-11ty` configuration
201
214
  * @TODO If a user runs quire eject at a later date we may want to merge their
202
215
  * package.json with the `quire-11ty` dev dependencies, scripts, etc
203
216
  */
@@ -220,7 +233,7 @@ async function installInProject(projectPath, version, options={}) {
220
233
  Verbosity: options.debug ? 'Debug' : 'Silent',
221
234
  WorkingDirectory: projectPath
222
235
  }
223
- await inv.Install(`${PACKAGE_NAME}@${version}`, installOptions)
236
+ await inv.Install(quire11tyPackage, installOptions)
224
237
 
225
238
  // delete empty `node_modules` directory that `install-npm-version` creates
226
239
  const invNodeModulesDir = path.join(projectPath, 'node_modules')
@@ -262,25 +275,27 @@ async function installInProject(projectPath, version, options={}) {
262
275
 
263
276
  /**
264
277
  * Retrieve latest published version of the `quire-11ty` package
265
-
266
- * @todo refactor to programmatically return a specific version
267
- * of `@thegetty/quire-11ty` from a semantic version string
268
- * (i.e `^1.0.0-pre-release.0` => `1.0.0-pre-release.2`)
269
- * so that the latest function may be used like:
270
- * await latest('^1.0.0-pre-release.0') => '1.0.0-pre-release.2'
271
- *
272
- * Nota bene: `npm view [<@scope>/]<name>[@<version>] version`
273
- * @see https://docs.npmjs.com/cli/v7/commands/npm-view
274
- * returns a list of versions that satisfy the `<version>` range specifier,
275
- * piping this to execa `stdout` we get only the last line of output.
276
- * @todo use [`parse-columns`](https://github.com/sindresorhus/parse-columns)
277
- * to parse the column formated list of versions returned by `npm view`
278
- *
278
+ * or the latest compatible version with the provided semantic version string
279
+ *
280
+ * @param {String} version A semantic version string, i.e `^1.0.0-pre-release.0`
281
+ *
279
282
  * @return {String} `quire-11ty@latest` semantic version string
280
283
  */
281
- async function latest() {
282
- const { stdout: quireVersion } =
283
- await execa('npm', ['view', PACKAGE_NAME, 'version'])
284
+ async function latest(version) {
285
+ let quireVersion;
286
+ if (!version || version === 'latest') {
287
+ const { stdout } =
288
+ await execa('npm', ['view', PACKAGE_NAME, 'version'])
289
+ quireVersion = stdout
290
+ } else {
291
+ const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}`)
292
+ const json = await response.json()
293
+ const versions = Object.keys(json.versions)
294
+ quireVersion = semver.maxSatisfying(versions, version)
295
+ }
296
+ if (!quireVersion) {
297
+ throw new Error(`[CLI:quire] Sorry, we couldn't find a version of quire-11ty compatible with the version "${version}". You can set the quire-11ty version in the starter project's package.json or specify a version when running \`quire new\` with the \`--quire-version\` flag. You can run \`npm view @thegetty/quire-11ty versions\` to view all versions.`)
298
+ }
284
299
  return quireVersion
285
300
  }
286
301
 
@@ -314,16 +329,11 @@ async function remove(version) {
314
329
  /**
315
330
  * Sets the quire-11ty version for a project
316
331
  *
317
- * @param {String} version Quire-11ty semantic version
332
+ * @param {String} version a version identifier or distribution tag
318
333
  */
319
334
  function setVersion(projectPath, version) {
320
- if (!version) {
321
- console.error('[CLI] no version specified')
322
- }
323
335
  const projectName = path.basename(projectPath)
324
336
  console.info(`${projectName} set to use quire-11ty@${version}`)
325
- const versionFilePath = path.join(projectPath, VERSION_FILE)
326
- fs.writeFileSync(versionFilePath, version)
327
337
  }
328
338
 
329
339
  /**
package/src/main.js CHANGED
@@ -1,6 +1,14 @@
1
1
  import { Argument, Command, Option } from 'commander'
2
- import commands from './commands/index.js'
3
- import packageConfig from '../package.json' assert { type: 'json' }
2
+ import { dirname, join } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import fs from 'node:fs'
5
+ import commands from '#src/commands/index.js'
6
+ import config from '#lib/conf/config.js'
7
+
8
+ const packagePath = join(fileURLToPath(import.meta.url), 'package.json')
9
+ const packageConfig = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
10
+
11
+ const { version } = packageConfig
4
12
 
5
13
  /**
6
14
  * Quire CLI implements the command pattern.
@@ -12,9 +20,9 @@ import packageConfig from '../package.json' assert { type: 'json' }
12
20
  const program = new Command()
13
21
 
14
22
  program
15
- .name('quire-cli')
23
+ .name('quire')
16
24
  .description('Quire command-line interface')
17
- .version(packageConfig.version, '-v, --version', 'output quire version number')
25
+ .version(version, '-v, --version', 'output quire version number')
18
26
  .configureHelp({
19
27
  helpWidth: 80,
20
28
  sortOptions: false,
@@ -23,9 +31,12 @@ program
23
31
 
24
32
  /**
25
33
  * Register each command as a subcommand of this program
34
+ *
35
+ * @todo refactor command definition to allow for per-command custom help text
36
+ * @see https://github.com/tj/commander.js?tab=readme-ov-file#automated-help
26
37
  */
27
38
  commands.forEach((command) => {
28
- const { action, aliases, args, description, name, options } = command
39
+ const { action, alias, aliases, args, description, name, options } = command
29
40
 
30
41
  const subCommand = program
31
42
  .command(name)
@@ -33,8 +44,12 @@ commands.forEach((command) => {
33
44
  .addHelpCommand()
34
45
  .showHelpAfterError()
35
46
 
47
+ if (alias instanceof String) {
48
+ subCommand.alias(alias)
49
+ }
50
+
36
51
  if (Array.isArray(aliases)) {
37
- aliases.forEach((alias) => subCommand.alias(alias))
52
+ subCommand.aliases(aliases)
38
53
  }
39
54
 
40
55
  /**
@@ -103,6 +118,11 @@ commands.forEach((command) => {
103
118
 
104
119
  // subCommand.action((args) => action.apply(command, args))
105
120
  subCommand.action(action)
121
+
122
+ /**
123
+ * Inject the CLI configuration into commands
124
+ */
125
+ subCommand.config = config
106
126
  })
107
127
 
108
128
  /**
@@ -1,16 +0,0 @@
1
- /**
2
- * { item_description }
3
- */
4
- module.export = (eleventyConfig) => {
5
- /**
6
- * The `eleventy.before` event runs every time Eleventy starts building,
7
- * it will run before the start of each stand-alone build, as well as
8
- * each time building starts as either part of `--watch` or `--serve`.
9
- * @see https://www.11ty.dev/docs/events/#eleventy.before
10
- */
11
- eleventyConfig.on('eleventy.before', async () => {
12
- console.debug('[11ty] before build')
13
- })
14
-
15
- return eleventyConfig
16
- }
@@ -1,3 +0,0 @@
1
- ## CLI Configuration Manager
2
-
3
- This `quire-cli/lib/config` module orchestrates creation of new projects and management of existing project configuration.
@@ -1 +0,0 @@
1
- import semver from 'semver'
@@ -1,6 +0,0 @@
1
- /**
2
- * Quire constants
3
- */
4
- export const CONSTANTS = Object.freeze({
5
- PACKAGE_NAME: '@thegetty/quire-11ty'
6
- })
@@ -1,104 +0,0 @@
1
- import { CONSTANT } from 'constants.js'
2
- import fs from 'fs-extra'
3
- import path from 'node:path'
4
-
5
- const { PACKAGE_NAME } = CONSTANT
6
-
7
- /**
8
- * Read the required `quire-11ty` version from starter `package.json` `peerDependencies`
9
- *
10
- * @param {String} projectPath Absolute system path to the project root
11
- *
12
- * @return {String} version Quire-11ty semantic version with caret or other
13
- * comparators trimmed off the beginning
14
- *
15
- * @TODO refactor `latest()` function to programmatically return a specific
16
- * version of `@thegetty/quire-11ty` from a semantic version string
17
- * (i.e `^1.0.0-pre-release.0` => `1.0.0-pre-release.2`) so this string-trimming
18
- * logic can be removed
19
- */
20
- async function getVersion(projectPath) {
21
- const packageConfig = fs.readFileSync(path.join(projectPath, 'package.json'), { encoding:'utf8' })
22
- const { peerDependencies } = JSON.parse(packageConfig)
23
- const version = peerDependencies[PACKAGE_NAME]
24
- return version === 'latest'
25
- ? await latest()
26
- : version.substr(version.search(/\d/))
27
- }
28
-
29
- /**
30
- * Clone or copy a Quire starter project
31
- *
32
- * @param {String} starter A repository URL or an absolute path to local starter
33
- * @TODO resolve both absolute and relative paths to local starter repositories
34
- * @param {String} projectPath Absolute system path to the project root
35
- *
36
- * @return {String} quireVersion A string indicating the current version
37
- * of quire being used with a new project
38
- */
39
- async function init (starter, projectPath) {
40
- projectPath = projectPath || cwd()
41
-
42
- // ensure that the target path exists
43
- fs.ensureDirSync(projectPath)
44
-
45
- // if the target directory exists it must be empty
46
- if (!isEmpty(projectPath)) {
47
- const location = projectPath === '.' ? 'the current directory' : projectPath
48
- console.error(`[CLI] cannot create a starter project in ${location} because it is not empty`)
49
- // @TODO cleanup directories from failed new command
50
- return
51
- }
52
-
53
- starter = starter || 'https://github.com/thegetty/quire-starter-default'
54
-
55
- console.debug('[CLI:quire] init-starter',
56
- `\n project root: "${projectPath}"`,
57
- `\n starter: "${starter}"`
58
- )
59
-
60
- /**
61
- * Clone starter project repository
62
- * @todo pipe `git clone` status to stdout for better UX
63
- */
64
- await git
65
- .cwd(projectPath)
66
- .clone(starter, '.')
67
- .catch((error) => console.error('[CLI:error] ', error))
68
-
69
- /**
70
- * Determine `quire-11ty` version required by the starter project
71
- * and write a `.quire` file with the semantic version string.
72
- */
73
- const quireVersion = await getVersion(projectPath)
74
- setVersion(projectPath, quireVersion)
75
-
76
- // Re-initialize project directory as a new git repository
77
- await fs.remove(path.join(projectPath, '.git'))
78
-
79
- /**
80
- * Remove the starter repository package config file
81
- * and create a new package config for the project
82
- * (this is necessary for Eleventy to resolve project paths)
83
- * @todo allow interactive init using a custom questionnaire
84
- * @see https://docs.npmjs.com/creating-a-package-json-file#customizing-the-packagejson-questionnaire
85
- */
86
- await fs.remove(path.join(projectPath, 'package.json'))
87
- await execaCommand('npm init --yes', { cwd: projectPath })
88
-
89
- /**
90
- * Create an initial commit of files in new repository
91
- * @todo use a localized string for the commit message
92
- */
93
- const projectFiles = fs.readdirSync(projectPath)
94
- await git.init().add(projectFiles).commit('Initial Commit')
95
-
96
- return quireVersion
97
- }
98
-
99
- /**
100
- * Update quire-11ty
101
- *
102
- * @return {Promise} { description_of_the_return_value }
103
- */
104
- async function update () {}