@thegetty/quire-cli 1.0.0-rc.9 → 1.0.0
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 +174 -7
- package/README.md +15 -0
- package/bin/cli.js +13 -7
- package/package.json +19 -14
- 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/conf.js +43 -0
- package/src/commands/create.js +13 -9
- package/src/commands/epub.js +1 -2
- package/src/commands/info.js +15 -10
- 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/api.js +1 -0
- package/src/lib/11ty/cli.js +29 -6
- 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/pdf/README.md +11 -4
- package/src/lib/pdf/index.js +4 -4
- 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 +67 -144
- 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,10 +3,10 @@ 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'
|
|
6
7
|
import fs from 'fs-extra'
|
|
7
|
-
import git from '#
|
|
8
|
-
import
|
|
9
|
-
import packageConfig from '#root/package.json' assert { type: 'json' }
|
|
8
|
+
import git from '#lib/git/index.js'
|
|
9
|
+
import packageConfig from '#src/packageConfig.js'
|
|
10
10
|
import path from 'node:path'
|
|
11
11
|
import semver from 'semver'
|
|
12
12
|
|
|
@@ -16,17 +16,19 @@ const __dirname = path.dirname(__filename)
|
|
|
16
16
|
// Version install path is relative to process working directory
|
|
17
17
|
const INSTALL_PATH = path.join('src', 'lib', 'quire', 'versions')
|
|
18
18
|
const PACKAGE_NAME = '@thegetty/quire-11ty'
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
const QUIRE_VERSION = config.get('quireVersion')
|
|
21
|
+
const VERSION_FILE = config.get('versionFile')
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
|
-
* Return an absolute path to an installed
|
|
24
|
+
* Return an absolute path to an installed quire-11ty version
|
|
23
25
|
*
|
|
24
|
-
* @return {String} path to installed
|
|
26
|
+
* @return {String} path to installed quire-11ty version
|
|
25
27
|
*/
|
|
26
|
-
function getPath(version=
|
|
27
|
-
const absolutePath = path.relative('/',
|
|
28
|
+
function getPath(version=QUIRE_VERSION) {
|
|
29
|
+
const absolutePath = path.relative('/', path.join(INSTALL_PATH, version))
|
|
28
30
|
if (!fs.existsSync(absolutePath)) {
|
|
29
|
-
console.error(`[CLI:quire]
|
|
31
|
+
console.error(`[CLI:quire] quire-11ty@${version} is not installed`)
|
|
30
32
|
return null
|
|
31
33
|
}
|
|
32
34
|
console.debug(`[CLI:quire] %s`, absolutePath)
|
|
@@ -34,7 +36,7 @@ function getPath(version='latest') {
|
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/**
|
|
37
|
-
* Read the required
|
|
39
|
+
* Read the required quire-11ty version for the quire version file
|
|
38
40
|
*
|
|
39
41
|
* @param {String} projectPath Absolute system path to the project root
|
|
40
42
|
*
|
|
@@ -49,25 +51,18 @@ function getVersion(projectPath) {
|
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
/**
|
|
52
|
-
* Read
|
|
54
|
+
* Read required quire-11ty and starter versions from starter peerDependencies
|
|
53
55
|
*
|
|
54
56
|
* @param {String} projectPath Absolute system path to the project root
|
|
55
57
|
*
|
|
56
|
-
* @return {
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
* @TODO refactor `latest()` function to programmatically return a specific
|
|
60
|
-
* version of `@thegetty/quire-11ty` from a semantic version string
|
|
61
|
-
* (i.e `^1.0.0-pre-release.0` => `1.0.0-pre-release.2`) so this string-trimming
|
|
62
|
-
* logic can be removed
|
|
58
|
+
* @return {Object}
|
|
59
|
+
* @property {String} quire11tyVersion Latest compatible Quire-11ty semantic version
|
|
60
|
+
* @property {String} starterVersion Starter project version defined in the starter package.json
|
|
63
61
|
*/
|
|
64
62
|
async function getVersionsFromStarter(projectPath) {
|
|
65
63
|
const projectPackageConfig = fs.readFileSync(path.join(projectPath, 'package.json'), { encoding:'utf8' })
|
|
66
64
|
const { peerDependencies, version: starterVersion } = JSON.parse(projectPackageConfig)
|
|
67
|
-
const
|
|
68
|
-
const quire11tyVersion = quire11ty === 'latest'
|
|
69
|
-
? await latest()
|
|
70
|
-
: quire11ty.substr(quire11ty.search(/\d/))
|
|
65
|
+
const quire11tyVersion = peerDependencies[PACKAGE_NAME]
|
|
71
66
|
return { quire11tyVersion, starterVersion }
|
|
72
67
|
}
|
|
73
68
|
|
|
@@ -95,11 +90,9 @@ async function initStarter (starter, projectPath, options) {
|
|
|
95
90
|
return
|
|
96
91
|
}
|
|
97
92
|
|
|
98
|
-
starter = starter || 'https://github.com/thegetty/quire-starter-default'
|
|
99
|
-
|
|
100
93
|
console.debug('[CLI:quire] init-starter',
|
|
101
|
-
`\n project
|
|
102
|
-
`\n starter:
|
|
94
|
+
`\n project: ${path.join(__dirname, projectPath)}`,
|
|
95
|
+
`\n starter: ${starter}`
|
|
103
96
|
)
|
|
104
97
|
|
|
105
98
|
/**
|
|
@@ -112,20 +105,19 @@ async function initStarter (starter, projectPath, options) {
|
|
|
112
105
|
.catch((error) => console.error('[CLI:error] ', error))
|
|
113
106
|
|
|
114
107
|
/**
|
|
115
|
-
* Determine
|
|
108
|
+
* Determine the quire-11ty version to use in the new project,
|
|
109
|
+
* from the quireVersion option or as required by the starter project.
|
|
116
110
|
*
|
|
117
|
-
*
|
|
118
|
-
* project `package.json`.
|
|
111
|
+
* Uses 'latest' to get the latest semantic version compatible with version ranges
|
|
119
112
|
*/
|
|
120
113
|
const { quire11tyVersion, starterVersion } = await getVersionsFromStarter(projectPath)
|
|
121
|
-
const quireVersion = options.quireVersion || quire11tyVersion
|
|
122
|
-
|
|
114
|
+
const quireVersion = await latest(options.quireVersion || quire11tyVersion)
|
|
123
115
|
setVersion(projectPath, quireVersion)
|
|
124
116
|
|
|
125
117
|
/**
|
|
126
|
-
* Write quire-11ty, cli, starter
|
|
118
|
+
* Write quire-11ty, quire-cli, starter versions to the version file
|
|
127
119
|
*/
|
|
128
|
-
const versionInfo = {
|
|
120
|
+
const versionInfo = {
|
|
129
121
|
cli: packageConfig.version,
|
|
130
122
|
starter: `${starter}@${starterVersion}`,
|
|
131
123
|
}
|
|
@@ -150,57 +142,9 @@ async function initStarter (starter, projectPath, options) {
|
|
|
150
142
|
*/
|
|
151
143
|
const projectFiles = fs.readdirSync(projectPath)
|
|
152
144
|
await git.init().add(projectFiles).commit('Initial Commit')
|
|
153
|
-
|
|
154
145
|
return quireVersion
|
|
155
146
|
}
|
|
156
147
|
|
|
157
|
-
/**
|
|
158
|
-
* Install `quire-11ty`, default to 'latest' version
|
|
159
|
-
*
|
|
160
|
-
* @TODO refactor this to be callable by the installInProject method
|
|
161
|
-
*
|
|
162
|
-
* @param {Object} options options passed from `quire new` command
|
|
163
|
-
* @return {Promise}
|
|
164
|
-
*/
|
|
165
|
-
async function install(options = {}) {
|
|
166
|
-
const version = options.quireVersion || 'latest'
|
|
167
|
-
console.debug(`[CLI:quire] installing quire-11ty@${version}`)
|
|
168
|
-
const absoluteInstallPath = path.join(__dirname, 'versions')
|
|
169
|
-
fs.ensureDirSync(absoluteInstallPath)
|
|
170
|
-
/**
|
|
171
|
-
* `Destination` is relative to `node_modules` of the working-directory
|
|
172
|
-
* so we have included a relative path to parent directory in order to
|
|
173
|
-
* install versions to a different local path.
|
|
174
|
-
* @see https://github.com/scott-lin/install-npm-version
|
|
175
|
-
*/
|
|
176
|
-
const installOptions = {
|
|
177
|
-
Destination: path.join('..', version),
|
|
178
|
-
Debug: false,
|
|
179
|
-
Overwrite: options.force || options.overwrite || false,
|
|
180
|
-
Verbosity: options.debug ? 'Debug' : 'Silent',
|
|
181
|
-
WorkingDirectory: absoluteInstallPath
|
|
182
|
-
}
|
|
183
|
-
await inv.Install(`${PACKAGE_NAME}@${version}`, installOptions)
|
|
184
|
-
|
|
185
|
-
// delete empty `node_modules` directory that `install-npm-version` creates
|
|
186
|
-
const invNodeModulesDir = path.join(absoluteInstallPath, 'node_modules')
|
|
187
|
-
if (fs.existsSync(invNodeModulesDir)) fs.rmdir(invNodeModulesDir)
|
|
188
|
-
|
|
189
|
-
symlinkLatest()
|
|
190
|
-
|
|
191
|
-
console.debug('[CLI:quire] installing dev dependencies')
|
|
192
|
-
/**
|
|
193
|
-
* Manually install necessary dev dependencies to run 11ty;
|
|
194
|
-
* these must be `devDependencies` so that they are not bundled into
|
|
195
|
-
* the final `_site` package when running `quire build`
|
|
196
|
-
*/
|
|
197
|
-
const currentWorkingDirectory = cwd()
|
|
198
|
-
const versionDir = path.join(absoluteInstallPath, version)
|
|
199
|
-
chdir(versionDir)
|
|
200
|
-
await execaCommand('npm cache clean --force')
|
|
201
|
-
await execaCommand('npm install --save-dev')
|
|
202
|
-
}
|
|
203
|
-
|
|
204
148
|
/**
|
|
205
149
|
* Install `quire-11ty` directly into a quire project
|
|
206
150
|
*
|
|
@@ -211,9 +155,10 @@ async function install(options = {}) {
|
|
|
211
155
|
* @param {Object} options options passed from `quire new` command
|
|
212
156
|
* @return {Promise}
|
|
213
157
|
*/
|
|
214
|
-
async function installInProject(projectPath, options = {}) {
|
|
215
|
-
const { quirePath
|
|
216
|
-
const quire11tyPackage =
|
|
158
|
+
async function installInProject(projectPath, quireVersion, options = {}) {
|
|
159
|
+
const { quirePath } = options
|
|
160
|
+
const quire11tyPackage = `${PACKAGE_NAME}@${quireVersion}`
|
|
161
|
+
|
|
217
162
|
console.debug(`[CLI:quire] installing ${quire11tyPackage} into ${projectPath}`)
|
|
218
163
|
|
|
219
164
|
/**
|
|
@@ -228,27 +173,24 @@ async function installInProject(projectPath, options = {}) {
|
|
|
228
173
|
.catch((error) => console.error('[CLI:error] ', error))
|
|
229
174
|
|
|
230
175
|
const temp11tyDirectory = '.temp'
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
Debug: false,
|
|
240
|
-
Overwrite: options.force || options.overwrite || false,
|
|
241
|
-
Verbosity: options.debug ? 'Debug' : 'Silent',
|
|
242
|
-
WorkingDirectory: projectPath
|
|
243
|
-
}
|
|
244
|
-
await inv.Install(quire11tyPackage, installOptions)
|
|
176
|
+
const tempDir = path.join(projectPath,temp11tyDirectory)
|
|
177
|
+
fs.mkdirSync(tempDir)
|
|
178
|
+
|
|
179
|
+
// Copy if passed a path and it exists, otherwise attempt to download the tarball for this pathspec
|
|
180
|
+
if (fs.existsSync(quirePath)) {
|
|
181
|
+
fs.cpSync(quirePath, tempDir, {recursive: true})
|
|
182
|
+
} else {
|
|
183
|
+
await execaCommand(`npm pack ${ options.debug ? '--debug' : '--quiet' } --pack-destination ${tempDir} ${quire11tyPackage}`)
|
|
245
184
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
185
|
+
// Extract only the package dir from the tar bar and strip it from the extracted path
|
|
186
|
+
const tarballPath = path.join(tempDir, `thegetty-quire-11ty-${quireVersion}.tgz`)
|
|
187
|
+
await execaCommand(`tar -xzf ${tarballPath} -C ${tempDir} --strip-components=1 package/`)
|
|
249
188
|
|
|
250
|
-
|
|
251
|
-
|
|
189
|
+
fs.rmSync(tarballPath)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Copy `.temp` to projectPath
|
|
193
|
+
fs.cpSync(tempDir, projectPath, {recursive: true})
|
|
252
194
|
|
|
253
195
|
console.debug('[CLI:quire] installing dev dependencies into quire project')
|
|
254
196
|
/**
|
|
@@ -256,17 +198,16 @@ async function installInProject(projectPath, options = {}) {
|
|
|
256
198
|
* these must be `devDependencies` so that they are not bundled into
|
|
257
199
|
* the final `_site` package when running `quire build`
|
|
258
200
|
*/
|
|
259
|
-
await execaCommand('npm cache clean --force', { cwd: projectPath })
|
|
260
201
|
try {
|
|
261
202
|
await execaCommand('npm install --save-dev', { cwd: projectPath })
|
|
262
203
|
} catch(error) {
|
|
263
204
|
console.warn(`[CLI:error]`, error)
|
|
264
|
-
fs.
|
|
205
|
+
fs.rmSync(projectPath, {recursive: true})
|
|
265
206
|
return
|
|
266
207
|
}
|
|
267
208
|
|
|
268
209
|
const eleventyFilesToCommit = fs
|
|
269
|
-
.readdirSync(
|
|
210
|
+
.readdirSync(tempDir)
|
|
270
211
|
.filter((filePath) => filePath !== 'node_modules')
|
|
271
212
|
|
|
272
213
|
eleventyFilesToCommit.push('package-lock.json')
|
|
@@ -278,30 +219,32 @@ async function installInProject(projectPath, options = {}) {
|
|
|
278
219
|
await git.add(eleventyFilesToCommit).commit('Adds `@thegetty/quire-11ty` files')
|
|
279
220
|
|
|
280
221
|
// remove temporary 11ty install directory
|
|
281
|
-
fs.
|
|
222
|
+
fs.rmSync(path.join(projectPath, temp11tyDirectory), {recursive: true})
|
|
282
223
|
}
|
|
283
224
|
|
|
284
225
|
/**
|
|
285
226
|
* Retrieve latest published version of the `quire-11ty` package
|
|
286
|
-
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
* so that the latest function may be used like:
|
|
291
|
-
* await latest('^1.0.0-pre-release.0') => '1.0.0-pre-release.2'
|
|
292
|
-
*
|
|
293
|
-
* Nota bene: `npm view [<@scope>/]<name>[@<version>] version`
|
|
294
|
-
* @see https://docs.npmjs.com/cli/v7/commands/npm-view
|
|
295
|
-
* returns a list of versions that satisfy the `<version>` range specifier,
|
|
296
|
-
* piping this to execa `stdout` we get only the last line of output.
|
|
297
|
-
* @todo use [`parse-columns`](https://github.com/sindresorhus/parse-columns)
|
|
298
|
-
* to parse the column formated list of versions returned by `npm view`
|
|
299
|
-
*
|
|
227
|
+
* or the latest compatible version with the provided semantic version string
|
|
228
|
+
*
|
|
229
|
+
* @param {String} version A semantic version string, i.e `^1.0.0-pre-release.0`
|
|
230
|
+
*
|
|
300
231
|
* @return {String} `quire-11ty@latest` semantic version string
|
|
301
232
|
*/
|
|
302
|
-
async function latest() {
|
|
303
|
-
|
|
304
|
-
|
|
233
|
+
async function latest(version) {
|
|
234
|
+
let quireVersion;
|
|
235
|
+
if (!version || version === 'latest') {
|
|
236
|
+
const { stdout } =
|
|
237
|
+
await execa('npm', ['view', PACKAGE_NAME, 'version'])
|
|
238
|
+
quireVersion = stdout
|
|
239
|
+
} else {
|
|
240
|
+
const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}`)
|
|
241
|
+
const json = await response.json()
|
|
242
|
+
const versions = Object.keys(json.versions)
|
|
243
|
+
quireVersion = semver.maxSatisfying(versions, version)
|
|
244
|
+
}
|
|
245
|
+
if (!quireVersion) {
|
|
246
|
+
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.`)
|
|
247
|
+
}
|
|
305
248
|
return quireVersion
|
|
306
249
|
}
|
|
307
250
|
|
|
@@ -338,11 +281,6 @@ async function remove(version) {
|
|
|
338
281
|
* @param {String} version a version identifier or distribution tag
|
|
339
282
|
*/
|
|
340
283
|
function setVersion(projectPath, version) {
|
|
341
|
-
if (!version) {
|
|
342
|
-
console.error('[CLI] no version specified')
|
|
343
|
-
return
|
|
344
|
-
}
|
|
345
|
-
|
|
346
284
|
const projectName = path.basename(projectPath)
|
|
347
285
|
console.info(`${projectName} set to use quire-11ty@${version}`)
|
|
348
286
|
}
|
|
@@ -391,19 +329,6 @@ function symlinkLatest() {
|
|
|
391
329
|
return fs.symlinkSync(target, source, type)
|
|
392
330
|
}
|
|
393
331
|
|
|
394
|
-
/**
|
|
395
|
-
* Tests if a `quire-11ty` version is already installed
|
|
396
|
-
* and installs the version if it is not already installed.
|
|
397
|
-
*
|
|
398
|
-
* @param {String} version `quire-11ty` semantic version
|
|
399
|
-
*/
|
|
400
|
-
function testVersion(version) {
|
|
401
|
-
version ||= getVersion()
|
|
402
|
-
if (!versions.includes(version)) {
|
|
403
|
-
install(version)
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
332
|
/**
|
|
408
333
|
* Get an array of published `quire-11ty` package versions
|
|
409
334
|
*
|
|
@@ -417,12 +342,10 @@ export const quire = {
|
|
|
417
342
|
getPath,
|
|
418
343
|
getVersion,
|
|
419
344
|
initStarter,
|
|
420
|
-
install,
|
|
421
345
|
installInProject,
|
|
422
346
|
latest,
|
|
423
347
|
list,
|
|
424
348
|
remove,
|
|
425
349
|
setVersion,
|
|
426
|
-
testVersion,
|
|
427
350
|
versions,
|
|
428
351
|
}
|
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'
|