@thegetty/quire-cli 1.0.0-rc.5 → 1.0.0-rc.50
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/CHANGELOG.md +154 -7
- package/README.md +15 -0
- package/bin/cli.js +13 -7
- package/package.json +17 -11
- package/patches/README.md +19 -0
- package/patches/install-npm-version+1.0.9.patch +12119 -0
- package/src/Command.js +14 -0
- package/src/commands/README.md +28 -8
- package/src/commands/conf.js +43 -0
- package/src/commands/create.js +22 -10
- package/src/commands/epub.js +1 -2
- package/src/commands/info.js +130 -0
- package/src/commands/pdf.js +60 -10
- package/src/commands/validate.js +64 -0
- package/src/errors/validation/validation-error.js +13 -0
- package/src/errors/validation/yaml-duplicate-error.js +11 -0
- package/src/errors/validation/yaml-parse-error.js +11 -0
- package/src/errors/validation/yaml-validation-error.js +11 -0
- package/src/helpers/clean.js +5 -0
- package/src/helpers/is-empty.js +4 -0
- package/src/helpers/is-quire.js +4 -0
- package/src/helpers/os-utils.js +4 -0
- package/src/helpers/test-cwd.js +4 -0
- package/src/helpers/which.js +4 -0
- package/src/lib/11ty/cli.js +26 -4
- package/src/lib/conf/README.md +104 -0
- package/src/lib/conf/config.js +35 -0
- package/src/lib/conf/defaults.js +37 -0
- package/src/lib/conf/migrations.js +11 -0
- package/src/lib/conf/schema.js +30 -0
- package/src/lib/epub/index.js +3 -1
- package/src/lib/pdf/README.md +11 -4
- package/src/lib/pdf/index.js +7 -5
- package/src/lib/pdf/paged.js +88 -9
- package/src/lib/pdf/pagedPlugin.js +49 -0
- package/src/lib/pdf/prince.js +79 -4
- package/src/lib/pdf/princePlugin.js +35 -0
- package/src/lib/pdf/split.js +61 -0
- package/src/lib/quire/index.js +89 -143
- package/src/main.js +21 -6
- package/src/packageConfig.js +15 -0
- package/src/validators/utils.js +98 -0
- package/src/validators/validate-yaml.js +38 -0
- package/src/lib/config/README.md +0 -3
- package/src/lib/config/index.js +0 -1
package/src/lib/quire/index.js
CHANGED
|
@@ -3,9 +3,11 @@ 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 config from '#lib/conf/config.js'
|
|
7
|
+
import fetch from 'node-fetch'
|
|
6
8
|
import fs from 'fs-extra'
|
|
7
|
-
import git from '#
|
|
8
|
-
import
|
|
9
|
+
import git from '#lib/git/index.js'
|
|
10
|
+
import packageConfig from '#src/packageConfig.js'
|
|
9
11
|
import path from 'node:path'
|
|
10
12
|
import semver from 'semver'
|
|
11
13
|
|
|
@@ -15,17 +17,19 @@ const __dirname = path.dirname(__filename)
|
|
|
15
17
|
// Version install path is relative to process working directory
|
|
16
18
|
const INSTALL_PATH = path.join('src', 'lib', 'quire', 'versions')
|
|
17
19
|
const PACKAGE_NAME = '@thegetty/quire-11ty'
|
|
18
|
-
|
|
20
|
+
|
|
21
|
+
const QUIRE_VERSION = config.get('quireVersion')
|
|
22
|
+
const VERSION_FILE = config.get('versionFile')
|
|
19
23
|
|
|
20
24
|
/**
|
|
21
|
-
* Return an absolute path to an installed
|
|
25
|
+
* Return an absolute path to an installed quire-11ty version
|
|
22
26
|
*
|
|
23
|
-
* @return {String} path to installed
|
|
27
|
+
* @return {String} path to installed quire-11ty version
|
|
24
28
|
*/
|
|
25
|
-
function getPath(version=
|
|
26
|
-
const absolutePath = path.relative('/',
|
|
29
|
+
function getPath(version=QUIRE_VERSION) {
|
|
30
|
+
const absolutePath = path.relative('/', path.join(INSTALL_PATH, version))
|
|
27
31
|
if (!fs.existsSync(absolutePath)) {
|
|
28
|
-
console.error(`[CLI:quire]
|
|
32
|
+
console.error(`[CLI:quire] quire-11ty@${version} is not installed`)
|
|
29
33
|
return null
|
|
30
34
|
}
|
|
31
35
|
console.debug(`[CLI:quire] %s`, absolutePath)
|
|
@@ -33,7 +37,7 @@ function getPath(version='latest') {
|
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
/**
|
|
36
|
-
* Read the required
|
|
40
|
+
* Read the required quire-11ty version for the quire version file
|
|
37
41
|
*
|
|
38
42
|
* @param {String} projectPath Absolute system path to the project root
|
|
39
43
|
*
|
|
@@ -48,25 +52,19 @@ function getVersion(projectPath) {
|
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
/**
|
|
51
|
-
* Read
|
|
55
|
+
* Read required quire-11ty and starter versions from starter peerDependencies
|
|
52
56
|
*
|
|
53
57
|
* @param {String} projectPath Absolute system path to the project root
|
|
54
58
|
*
|
|
55
|
-
* @return {
|
|
56
|
-
*
|
|
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
|
|
64
|
-
const
|
|
65
|
-
const { peerDependencies } = JSON.parse(
|
|
66
|
-
const
|
|
67
|
-
return
|
|
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,11 +91,9 @@ 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
|
-
`\n project
|
|
100
|
-
`\n starter:
|
|
95
|
+
`\n project: ${path.join(__dirname, projectPath)}`,
|
|
96
|
+
`\n starter: ${starter}`
|
|
101
97
|
)
|
|
102
98
|
|
|
103
99
|
/**
|
|
@@ -110,12 +106,24 @@ async function initStarter (starter, projectPath) {
|
|
|
110
106
|
.catch((error) => console.error('[CLI:error] ', error))
|
|
111
107
|
|
|
112
108
|
/**
|
|
113
|
-
* Determine
|
|
114
|
-
*
|
|
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
|
|
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, quire-cli, starter versions to the 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,69 +143,28 @@ 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
|
|
150
|
+
* Install `quire-11ty` directly into a quire project
|
|
144
151
|
*
|
|
145
|
-
* @
|
|
146
|
-
*
|
|
147
|
-
* @return {Promise}
|
|
148
|
-
*/
|
|
149
|
-
async function install(version, options={}) {
|
|
150
|
-
console.debug(`[CLI:quire] installing quire-11ty@${version}`)
|
|
151
|
-
const absoluteInstallPath = path.join(__dirname, 'versions')
|
|
152
|
-
fs.ensureDirSync(absoluteInstallPath)
|
|
153
|
-
/**
|
|
154
|
-
* `Destination` is relative to `node_modules` of the working-directory
|
|
155
|
-
* so we have included a relative path to parent directory in order to
|
|
156
|
-
* install versions to a different local path.
|
|
157
|
-
* @see https://github.com/scott-lin/install-npm-version
|
|
158
|
-
*/
|
|
159
|
-
const installOptions = {
|
|
160
|
-
Destination: path.join('..', version),
|
|
161
|
-
Debug: false,
|
|
162
|
-
Overwrite: options.force || options.overwrite || false,
|
|
163
|
-
Verbosity: options.debug ? 'Debug' : 'Silent',
|
|
164
|
-
WorkingDirectory: absoluteInstallPath
|
|
165
|
-
}
|
|
166
|
-
await inv.Install(`${PACKAGE_NAME}@${version}`, installOptions)
|
|
167
|
-
|
|
168
|
-
// delete empty `node_modules` directory that `install-npm-version` creates
|
|
169
|
-
const invNodeModulesDir = path.join(absoluteInstallPath, 'node_modules')
|
|
170
|
-
if (fs.existsSync(invNodeModulesDir)) fs.rmdir(invNodeModulesDir)
|
|
171
|
-
|
|
172
|
-
symlinkLatest()
|
|
173
|
-
|
|
174
|
-
console.debug('[CLI:quire] installing dev dependencies')
|
|
175
|
-
/**
|
|
176
|
-
* Manually install necessary dev dependencies to run 11ty;
|
|
177
|
-
* these must be `devDependencies` so that they are not bundled into
|
|
178
|
-
* the final `_site` package when running `quire build`
|
|
179
|
-
*/
|
|
180
|
-
const currentWorkingDirectory = cwd()
|
|
181
|
-
const versionDir = path.join(absoluteInstallPath, version)
|
|
182
|
-
chdir(versionDir)
|
|
183
|
-
await execaCommand('npm cache clean --force')
|
|
184
|
-
await execaCommand('npm install --save-dev')
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Install a specific version of `quire-11ty` directly into a quire project
|
|
152
|
+
* @TODO refactor this to use the install method with pre and post-install hooks
|
|
153
|
+
* or steps to prepare the working directory and cleanup on error and completion
|
|
189
154
|
*
|
|
190
155
|
* @param {String} projectPath Absolute system path to the project root
|
|
191
|
-
* @param {String} version Quire-11ty semantic version
|
|
192
156
|
* @param {Object} options options passed from `quire new` command
|
|
193
157
|
* @return {Promise}
|
|
194
158
|
*/
|
|
195
|
-
async function installInProject(projectPath,
|
|
196
|
-
|
|
159
|
+
async function installInProject(projectPath, quireVersion, options = {}) {
|
|
160
|
+
const { quirePath } = options
|
|
161
|
+
const quire11tyPackage = `${PACKAGE_NAME}@${quireVersion}`
|
|
162
|
+
|
|
163
|
+
console.debug(`[CLI:quire] installing ${quire11tyPackage} into ${projectPath}`)
|
|
197
164
|
|
|
198
165
|
/**
|
|
199
|
-
*
|
|
200
|
-
*
|
|
166
|
+
* Delete the starter project package configuration so that it can be replaced
|
|
167
|
+
* with the `@thegetty/quire-11ty` configuration
|
|
201
168
|
* @TODO If a user runs quire eject at a later date we may want to merge their
|
|
202
169
|
* package.json with the `quire-11ty` dev dependencies, scripts, etc
|
|
203
170
|
*/
|
|
@@ -207,27 +174,24 @@ async function installInProject(projectPath, version, options={}) {
|
|
|
207
174
|
.catch((error) => console.error('[CLI:error] ', error))
|
|
208
175
|
|
|
209
176
|
const temp11tyDirectory = '.temp'
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
await inv.Install(`${PACKAGE_NAME}@${version}`, installOptions)
|
|
177
|
+
const tempDir = path.join(projectPath,temp11tyDirectory)
|
|
178
|
+
fs.mkdirSync(tempDir)
|
|
179
|
+
|
|
180
|
+
// Copy if passed a path and it exists, otherwise attempt to download the tarball for this pathspec
|
|
181
|
+
if (fs.existsSync(quirePath)) {
|
|
182
|
+
fs.cpSync(quirePath, tempDir, {recursive: true})
|
|
183
|
+
} else {
|
|
184
|
+
await execaCommand(`npm pack ${ options.debug ? '--debug' : '--quiet' } --pack-destination ${tempDir} ${quire11tyPackage}`)
|
|
185
|
+
|
|
186
|
+
// Extract only the package dir from the tar bar and strip it from the extracted path
|
|
187
|
+
const tarballPath = path.join(tempDir, `thegetty-quire-11ty-${quireVersion}.tgz`)
|
|
188
|
+
await execaCommand(`tar -xzf ${tarballPath} -C ${tempDir} --strip-components=1 package/`)
|
|
224
189
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
if (fs.existsSync(invNodeModulesDir)) fs.rmdir(invNodeModulesDir)
|
|
190
|
+
fs.rmSync(tarballPath)
|
|
191
|
+
}
|
|
228
192
|
|
|
229
|
-
// Copy
|
|
230
|
-
fs.
|
|
193
|
+
// Copy `.temp` to projectPath
|
|
194
|
+
fs.cpSync(tempDir, projectPath, {recursive: true})
|
|
231
195
|
|
|
232
196
|
console.debug('[CLI:quire] installing dev dependencies into quire project')
|
|
233
197
|
/**
|
|
@@ -240,12 +204,12 @@ async function installInProject(projectPath, version, options={}) {
|
|
|
240
204
|
await execaCommand('npm install --save-dev', { cwd: projectPath })
|
|
241
205
|
} catch(error) {
|
|
242
206
|
console.warn(`[CLI:error]`, error)
|
|
243
|
-
fs.
|
|
207
|
+
fs.rmSync(projectPath, {recursive: true})
|
|
244
208
|
return
|
|
245
209
|
}
|
|
246
210
|
|
|
247
211
|
const eleventyFilesToCommit = fs
|
|
248
|
-
.readdirSync(
|
|
212
|
+
.readdirSync(tempDir)
|
|
249
213
|
.filter((filePath) => filePath !== 'node_modules')
|
|
250
214
|
|
|
251
215
|
eleventyFilesToCommit.push('package-lock.json')
|
|
@@ -257,30 +221,32 @@ async function installInProject(projectPath, version, options={}) {
|
|
|
257
221
|
await git.add(eleventyFilesToCommit).commit('Adds `@thegetty/quire-11ty` files')
|
|
258
222
|
|
|
259
223
|
// remove temporary 11ty install directory
|
|
260
|
-
fs.
|
|
224
|
+
fs.rmSync(path.join(projectPath, temp11tyDirectory), {recursive: true})
|
|
261
225
|
}
|
|
262
226
|
|
|
263
227
|
/**
|
|
264
228
|
* Retrieve latest published version of the `quire-11ty` package
|
|
265
|
-
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
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
|
-
*
|
|
229
|
+
* or the latest compatible version with the provided semantic version string
|
|
230
|
+
*
|
|
231
|
+
* @param {String} version A semantic version string, i.e `^1.0.0-pre-release.0`
|
|
232
|
+
*
|
|
279
233
|
* @return {String} `quire-11ty@latest` semantic version string
|
|
280
234
|
*/
|
|
281
|
-
async function latest() {
|
|
282
|
-
|
|
283
|
-
|
|
235
|
+
async function latest(version) {
|
|
236
|
+
let quireVersion;
|
|
237
|
+
if (!version || version === 'latest') {
|
|
238
|
+
const { stdout } =
|
|
239
|
+
await execa('npm', ['view', PACKAGE_NAME, 'version'])
|
|
240
|
+
quireVersion = stdout
|
|
241
|
+
} else {
|
|
242
|
+
const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}`)
|
|
243
|
+
const json = await response.json()
|
|
244
|
+
const versions = Object.keys(json.versions)
|
|
245
|
+
quireVersion = semver.maxSatisfying(versions, version)
|
|
246
|
+
}
|
|
247
|
+
if (!quireVersion) {
|
|
248
|
+
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.`)
|
|
249
|
+
}
|
|
284
250
|
return quireVersion
|
|
285
251
|
}
|
|
286
252
|
|
|
@@ -314,16 +280,11 @@ async function remove(version) {
|
|
|
314
280
|
/**
|
|
315
281
|
* Sets the quire-11ty version for a project
|
|
316
282
|
*
|
|
317
|
-
* @param {String} version
|
|
283
|
+
* @param {String} version a version identifier or distribution tag
|
|
318
284
|
*/
|
|
319
285
|
function setVersion(projectPath, version) {
|
|
320
|
-
if (!version) {
|
|
321
|
-
console.error('[CLI] no version specified')
|
|
322
|
-
}
|
|
323
286
|
const projectName = path.basename(projectPath)
|
|
324
287
|
console.info(`${projectName} set to use quire-11ty@${version}`)
|
|
325
|
-
const versionFilePath = path.join(projectPath, VERSION_FILE)
|
|
326
|
-
fs.writeFileSync(versionFilePath, version)
|
|
327
288
|
}
|
|
328
289
|
|
|
329
290
|
/**
|
|
@@ -370,19 +331,6 @@ function symlinkLatest() {
|
|
|
370
331
|
return fs.symlinkSync(target, source, type)
|
|
371
332
|
}
|
|
372
333
|
|
|
373
|
-
/**
|
|
374
|
-
* Tests if a `quire-11ty` version is already installed
|
|
375
|
-
* and installs the version if it is not already installed.
|
|
376
|
-
*
|
|
377
|
-
* @param {String} version `quire-11ty` semantic version
|
|
378
|
-
*/
|
|
379
|
-
function testVersion(version) {
|
|
380
|
-
version ||= getVersion()
|
|
381
|
-
if (!versions.includes(version)) {
|
|
382
|
-
install(version)
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
334
|
/**
|
|
387
335
|
* Get an array of published `quire-11ty` package versions
|
|
388
336
|
*
|
|
@@ -396,12 +344,10 @@ export const quire = {
|
|
|
396
344
|
getPath,
|
|
397
345
|
getVersion,
|
|
398
346
|
initStarter,
|
|
399
|
-
install,
|
|
400
347
|
installInProject,
|
|
401
348
|
latest,
|
|
402
349
|
list,
|
|
403
350
|
remove,
|
|
404
351
|
setVersion,
|
|
405
|
-
testVersion,
|
|
406
352
|
versions,
|
|
407
353
|
}
|
package/src/main.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Argument, Command, Option } from 'commander'
|
|
2
|
-
import commands from '
|
|
3
|
-
import
|
|
2
|
+
import commands from '#src/commands/index.js'
|
|
3
|
+
import config from '#lib/conf/config.js'
|
|
4
|
+
import packageConfig from '#src/packageConfig.js'
|
|
5
|
+
|
|
6
|
+
const { version } = packageConfig
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* Quire CLI implements the command pattern.
|
|
@@ -12,9 +15,9 @@ import packageConfig from '../package.json' assert { type: 'json' }
|
|
|
12
15
|
const program = new Command()
|
|
13
16
|
|
|
14
17
|
program
|
|
15
|
-
.name('quire
|
|
18
|
+
.name('quire')
|
|
16
19
|
.description('Quire command-line interface')
|
|
17
|
-
.version(
|
|
20
|
+
.version(version, '-v, --version', 'output quire version number')
|
|
18
21
|
.configureHelp({
|
|
19
22
|
helpWidth: 80,
|
|
20
23
|
sortOptions: false,
|
|
@@ -23,9 +26,12 @@ program
|
|
|
23
26
|
|
|
24
27
|
/**
|
|
25
28
|
* Register each command as a subcommand of this program
|
|
29
|
+
*
|
|
30
|
+
* @todo refactor command definition to allow for per-command custom help text
|
|
31
|
+
* @see https://github.com/tj/commander.js?tab=readme-ov-file#automated-help
|
|
26
32
|
*/
|
|
27
33
|
commands.forEach((command) => {
|
|
28
|
-
const { action, aliases, args, description, name, options } = command
|
|
34
|
+
const { action, alias, aliases, args, description, name, options } = command
|
|
29
35
|
|
|
30
36
|
const subCommand = program
|
|
31
37
|
.command(name)
|
|
@@ -33,8 +39,12 @@ commands.forEach((command) => {
|
|
|
33
39
|
.addHelpCommand()
|
|
34
40
|
.showHelpAfterError()
|
|
35
41
|
|
|
42
|
+
if (alias instanceof String) {
|
|
43
|
+
subCommand.alias(alias)
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
if (Array.isArray(aliases)) {
|
|
37
|
-
|
|
47
|
+
subCommand.aliases(aliases)
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
/**
|
|
@@ -103,6 +113,11 @@ commands.forEach((command) => {
|
|
|
103
113
|
|
|
104
114
|
// subCommand.action((args) => action.apply(command, args))
|
|
105
115
|
subCommand.action(action)
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Inject the CLI configuration into commands
|
|
119
|
+
*/
|
|
120
|
+
subCommand.config = config
|
|
106
121
|
})
|
|
107
122
|
|
|
108
123
|
/**
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { dirname } from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { readPackageUpSync } from 'read-package-up'
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
6
|
+
const __dirname = dirname(__filename)
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Nota bene: current working directory will be that from which cli commands
|
|
10
|
+
* are being, readPackageUpSync is passed the directory of this module as the
|
|
11
|
+
* starting path to search for the quire-cli package config file.
|
|
12
|
+
*/
|
|
13
|
+
const { packageJson } = readPackageUpSync({ cwd: __dirname, normalize: true })
|
|
14
|
+
|
|
15
|
+
export default packageJson
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import YamlDuplicateIdError from '../errors/validation/yaml-duplicate-error.js'
|
|
2
|
+
import { fileURLToPath } from 'url'
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import { projectRoot } from '#lib/11ty/index.js'
|
|
6
|
+
|
|
7
|
+
const IMAGE_KEYS = new Set(['src', 'image', 'logo'])
|
|
8
|
+
|
|
9
|
+
export function getSchemaForDocument(file) {
|
|
10
|
+
const schemaName = path.basename(file, path.extname(file))
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
12
|
+
const schemaPath = path.join(__dirname,'..','..','schemas', `${schemaName}.schema.json`)
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(fs.readFileSync(schemaPath, 'utf8'))
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.warn(`Warning: No schema found for document ${schemaName} at path: ${schemaPath}.`)
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Recursive helper to check image paths in nested figure list structures
|
|
23
|
+
function collectImagePaths(node, paths=[]) {
|
|
24
|
+
if(!node || typeof node !== 'object') return
|
|
25
|
+
|
|
26
|
+
for (const key in node) {
|
|
27
|
+
const value = node[key]
|
|
28
|
+
if (IMAGE_KEYS.has(key) && typeof value === 'string') {
|
|
29
|
+
paths.push(value)
|
|
30
|
+
}
|
|
31
|
+
collectImagePaths(value, paths)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return paths
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function validateImage(label, src) {
|
|
38
|
+
if(!src) return
|
|
39
|
+
|
|
40
|
+
let assetPath = ''
|
|
41
|
+
if(src.endsWith('.html')) {
|
|
42
|
+
assetPath = path.join(projectRoot, 'content', '_assets', src)
|
|
43
|
+
} else {
|
|
44
|
+
assetPath = path.join(projectRoot, 'content', '_assets', 'images', src)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if(!fs.existsSync(assetPath)) {
|
|
48
|
+
console.warn(`Warning: ${label} source not found at path: ${assetPath}`)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function validateImagePaths(doc) {
|
|
53
|
+
validateImage('Cover image', doc?.epub?.defaultCoverImage)
|
|
54
|
+
validateImage('Promo image', doc?.promo_image)
|
|
55
|
+
|
|
56
|
+
for (const figure of doc?.figure_list || []) {
|
|
57
|
+
let paths = []
|
|
58
|
+
const imagePaths = collectImagePaths(figure, paths)
|
|
59
|
+
for (const imgPath of imagePaths) {
|
|
60
|
+
validateImage(`Figure id ${figure.id}`, imgPath)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const publisher of doc?.publisher || []) {
|
|
65
|
+
validateImage('Logo', publisher.logo)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (const contributor of doc?.contributor || []) {
|
|
69
|
+
validateImage('Contributor', contributor.image)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Lifted from packages/11ty/_plugins/globalData
|
|
75
|
+
* Throws an error if data contains duplicate ids
|
|
76
|
+
* @param {Object|Array} data
|
|
77
|
+
*/
|
|
78
|
+
export const checkForDuplicateIds = function (data, file) {
|
|
79
|
+
if (!data) return
|
|
80
|
+
|
|
81
|
+
if (Array.isArray(data)) {
|
|
82
|
+
if (data.every((item) => Object.hasOwn(item, 'id'))) {
|
|
83
|
+
const duplicates = data.filter((a, index) => {
|
|
84
|
+
return index !== data.findIndex((b) => b.id === a.id)
|
|
85
|
+
})
|
|
86
|
+
if (duplicates.length) {
|
|
87
|
+
const ids = duplicates.map(({ id }) => id)
|
|
88
|
+
throw new YamlDuplicateIdError(file, `Error in ${file}: Duplicate IDs found: ${ids.join(', ')}`)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (typeof data === 'object') {
|
|
94
|
+
Object.keys(data).forEach((key) => {
|
|
95
|
+
checkForDuplicateIds(data[key], file)
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import Ajv from 'ajv'
|
|
2
|
+
import addFormats from 'ajv-formats'
|
|
3
|
+
import { validateImagePaths ,getSchemaForDocument, checkForDuplicateIds } from './utils.js'
|
|
4
|
+
import YamlValidationError from '../errors/validation/yaml-validation-error.js'
|
|
5
|
+
import fs from 'fs'
|
|
6
|
+
import yaml from 'js-yaml'
|
|
7
|
+
|
|
8
|
+
export default function yamlValidation(file) {
|
|
9
|
+
|
|
10
|
+
const fileContent = fs.readFileSync(file, 'utf8')
|
|
11
|
+
|
|
12
|
+
let doc
|
|
13
|
+
try {
|
|
14
|
+
doc = yaml.load(fileContent)
|
|
15
|
+
} catch (error) {
|
|
16
|
+
const message = `Error in ${file}: ${error.reason} at line ${error.mark.line} column ${error.mark.column}`
|
|
17
|
+
throw new YamlValidationError(file, `${message}`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const schema = getSchemaForDocument(file)
|
|
21
|
+
if(!schema){ return }
|
|
22
|
+
|
|
23
|
+
const ajv = new Ajv({allErrors:true})
|
|
24
|
+
addFormats(ajv)
|
|
25
|
+
const validate = ajv.compile(schema)
|
|
26
|
+
const valid = validate(doc)
|
|
27
|
+
if(!valid) {
|
|
28
|
+
const messages = validate.errors
|
|
29
|
+
.map(err => `${err.instancePath || '(root)'} ${err.message}`)
|
|
30
|
+
.join('\n')
|
|
31
|
+
|
|
32
|
+
const fullMessage = `Error in ${file}:\n${messages}`
|
|
33
|
+
throw new YamlValidationError(file, fullMessage)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
validateImagePaths(doc)
|
|
37
|
+
checkForDuplicateIds(doc, file)
|
|
38
|
+
}
|
package/src/lib/config/README.md
DELETED
package/src/lib/config/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import semver from 'semver'
|