@thegetty/quire-cli 1.0.0-rc.7 → 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 +187 -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/README.md +8 -8
- 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 +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/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 +79 -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,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
|
|
8
|
+
import git from '#lib/git/index.js'
|
|
9
|
+
import packageConfig from '#src/packageConfig.js'
|
|
9
10
|
import path from 'node:path'
|
|
10
11
|
import semver from 'semver'
|
|
11
12
|
|
|
@@ -15,17 +16,19 @@ const __dirname = path.dirname(__filename)
|
|
|
15
16
|
// Version install path is relative to process working directory
|
|
16
17
|
const INSTALL_PATH = path.join('src', 'lib', 'quire', 'versions')
|
|
17
18
|
const PACKAGE_NAME = '@thegetty/quire-11ty'
|
|
18
|
-
|
|
19
|
+
|
|
20
|
+
const QUIRE_VERSION = config.get('quireVersion')
|
|
21
|
+
const VERSION_FILE = config.get('versionFile')
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
|
-
* Return an absolute path to an installed
|
|
24
|
+
* Return an absolute path to an installed quire-11ty version
|
|
22
25
|
*
|
|
23
|
-
* @return {String} path to installed
|
|
26
|
+
* @return {String} path to installed quire-11ty version
|
|
24
27
|
*/
|
|
25
|
-
function getPath(version=
|
|
26
|
-
const absolutePath = path.relative('/',
|
|
28
|
+
function getPath(version=QUIRE_VERSION) {
|
|
29
|
+
const absolutePath = path.relative('/', path.join(INSTALL_PATH, version))
|
|
27
30
|
if (!fs.existsSync(absolutePath)) {
|
|
28
|
-
console.error(`[CLI:quire]
|
|
31
|
+
console.error(`[CLI:quire] quire-11ty@${version} is not installed`)
|
|
29
32
|
return null
|
|
30
33
|
}
|
|
31
34
|
console.debug(`[CLI:quire] %s`, absolutePath)
|
|
@@ -33,7 +36,7 @@ function getPath(version='latest') {
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
|
-
* Read the required
|
|
39
|
+
* Read the required quire-11ty version for the quire version file
|
|
37
40
|
*
|
|
38
41
|
* @param {String} projectPath Absolute system path to the project root
|
|
39
42
|
*
|
|
@@ -48,25 +51,19 @@ function getVersion(projectPath) {
|
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
/**
|
|
51
|
-
* Read
|
|
54
|
+
* Read required quire-11ty and starter versions from starter peerDependencies
|
|
52
55
|
*
|
|
53
56
|
* @param {String} projectPath Absolute system path to the project root
|
|
54
57
|
*
|
|
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
|
|
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
|
|
62
61
|
*/
|
|
63
|
-
async function
|
|
64
|
-
const
|
|
65
|
-
const { peerDependencies } = JSON.parse(
|
|
66
|
-
const
|
|
67
|
-
return
|
|
68
|
-
? await latest()
|
|
69
|
-
: version.substr(version.search(/\d/))
|
|
62
|
+
async function getVersionsFromStarter(projectPath) {
|
|
63
|
+
const projectPackageConfig = fs.readFileSync(path.join(projectPath, 'package.json'), { encoding:'utf8' })
|
|
64
|
+
const { peerDependencies, version: starterVersion } = JSON.parse(projectPackageConfig)
|
|
65
|
+
const quire11tyVersion = peerDependencies[PACKAGE_NAME]
|
|
66
|
+
return { quire11tyVersion, starterVersion }
|
|
70
67
|
}
|
|
71
68
|
|
|
72
69
|
/**
|
|
@@ -93,11 +90,9 @@ async function initStarter (starter, projectPath, options) {
|
|
|
93
90
|
return
|
|
94
91
|
}
|
|
95
92
|
|
|
96
|
-
starter = starter || 'https://github.com/thegetty/quire-starter-default'
|
|
97
|
-
|
|
98
93
|
console.debug('[CLI:quire] init-starter',
|
|
99
|
-
`\n project
|
|
100
|
-
`\n starter:
|
|
94
|
+
`\n project: ${path.join(__dirname, projectPath)}`,
|
|
95
|
+
`\n starter: ${starter}`
|
|
101
96
|
)
|
|
102
97
|
|
|
103
98
|
/**
|
|
@@ -110,14 +105,24 @@ async function initStarter (starter, projectPath, options) {
|
|
|
110
105
|
.catch((error) => console.error('[CLI:error] ', error))
|
|
111
106
|
|
|
112
107
|
/**
|
|
113
|
-
* 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.
|
|
114
110
|
*
|
|
115
|
-
*
|
|
116
|
-
* project `package.json`.
|
|
111
|
+
* Uses 'latest' to get the latest semantic version compatible with version ranges
|
|
117
112
|
*/
|
|
118
|
-
const
|
|
113
|
+
const { quire11tyVersion, starterVersion } = await getVersionsFromStarter(projectPath)
|
|
114
|
+
const quireVersion = await latest(options.quireVersion || quire11tyVersion)
|
|
119
115
|
setVersion(projectPath, quireVersion)
|
|
120
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Write quire-11ty, quire-cli, starter versions to the version file
|
|
119
|
+
*/
|
|
120
|
+
const versionInfo = {
|
|
121
|
+
cli: packageConfig.version,
|
|
122
|
+
starter: `${starter}@${starterVersion}`,
|
|
123
|
+
}
|
|
124
|
+
fs.writeFileSync(path.join(projectPath, VERSION_FILE), JSON.stringify(versionInfo))
|
|
125
|
+
|
|
121
126
|
// Re-initialize project directory as a new git repository
|
|
122
127
|
await fs.remove(path.join(projectPath, '.git'))
|
|
123
128
|
|
|
@@ -137,57 +142,9 @@ async function initStarter (starter, projectPath, options) {
|
|
|
137
142
|
*/
|
|
138
143
|
const projectFiles = fs.readdirSync(projectPath)
|
|
139
144
|
await git.init().add(projectFiles).commit('Initial Commit')
|
|
140
|
-
|
|
141
145
|
return quireVersion
|
|
142
146
|
}
|
|
143
147
|
|
|
144
|
-
/**
|
|
145
|
-
* Install `quire-11ty`, default to 'latest' version
|
|
146
|
-
*
|
|
147
|
-
* @TODO refactor this to be callable by the installInProject method
|
|
148
|
-
*
|
|
149
|
-
* @param {Object} options options passed from `quire new` command
|
|
150
|
-
* @return {Promise}
|
|
151
|
-
*/
|
|
152
|
-
async function install(options = {}) {
|
|
153
|
-
const version = options.quireVersion || 'latest'
|
|
154
|
-
console.debug(`[CLI:quire] installing quire-11ty@${version}`)
|
|
155
|
-
const absoluteInstallPath = path.join(__dirname, 'versions')
|
|
156
|
-
fs.ensureDirSync(absoluteInstallPath)
|
|
157
|
-
/**
|
|
158
|
-
* `Destination` is relative to `node_modules` of the working-directory
|
|
159
|
-
* so we have included a relative path to parent directory in order to
|
|
160
|
-
* install versions to a different local path.
|
|
161
|
-
* @see https://github.com/scott-lin/install-npm-version
|
|
162
|
-
*/
|
|
163
|
-
const installOptions = {
|
|
164
|
-
Destination: path.join('..', version),
|
|
165
|
-
Debug: false,
|
|
166
|
-
Overwrite: options.force || options.overwrite || false,
|
|
167
|
-
Verbosity: options.debug ? 'Debug' : 'Silent',
|
|
168
|
-
WorkingDirectory: absoluteInstallPath
|
|
169
|
-
}
|
|
170
|
-
await inv.Install(`${PACKAGE_NAME}@${version}`, installOptions)
|
|
171
|
-
|
|
172
|
-
// delete empty `node_modules` directory that `install-npm-version` creates
|
|
173
|
-
const invNodeModulesDir = path.join(absoluteInstallPath, 'node_modules')
|
|
174
|
-
if (fs.existsSync(invNodeModulesDir)) fs.rmdir(invNodeModulesDir)
|
|
175
|
-
|
|
176
|
-
symlinkLatest()
|
|
177
|
-
|
|
178
|
-
console.debug('[CLI:quire] installing dev dependencies')
|
|
179
|
-
/**
|
|
180
|
-
* Manually install necessary dev dependencies to run 11ty;
|
|
181
|
-
* these must be `devDependencies` so that they are not bundled into
|
|
182
|
-
* the final `_site` package when running `quire build`
|
|
183
|
-
*/
|
|
184
|
-
const currentWorkingDirectory = cwd()
|
|
185
|
-
const versionDir = path.join(absoluteInstallPath, version)
|
|
186
|
-
chdir(versionDir)
|
|
187
|
-
await execaCommand('npm cache clean --force')
|
|
188
|
-
await execaCommand('npm install --save-dev')
|
|
189
|
-
}
|
|
190
|
-
|
|
191
148
|
/**
|
|
192
149
|
* Install `quire-11ty` directly into a quire project
|
|
193
150
|
*
|
|
@@ -198,9 +155,10 @@ async function install(options = {}) {
|
|
|
198
155
|
* @param {Object} options options passed from `quire new` command
|
|
199
156
|
* @return {Promise}
|
|
200
157
|
*/
|
|
201
|
-
async function installInProject(projectPath, options = {}) {
|
|
202
|
-
const { quirePath
|
|
203
|
-
const quire11tyPackage =
|
|
158
|
+
async function installInProject(projectPath, quireVersion, options = {}) {
|
|
159
|
+
const { quirePath } = options
|
|
160
|
+
const quire11tyPackage = `${PACKAGE_NAME}@${quireVersion}`
|
|
161
|
+
|
|
204
162
|
console.debug(`[CLI:quire] installing ${quire11tyPackage} into ${projectPath}`)
|
|
205
163
|
|
|
206
164
|
/**
|
|
@@ -215,27 +173,24 @@ async function installInProject(projectPath, options = {}) {
|
|
|
215
173
|
.catch((error) => console.error('[CLI:error] ', error))
|
|
216
174
|
|
|
217
175
|
const temp11tyDirectory = '.temp'
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
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}`)
|
|
184
|
+
|
|
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/`)
|
|
232
188
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if (fs.existsSync(invNodeModulesDir)) fs.rmdir(invNodeModulesDir)
|
|
189
|
+
fs.rmSync(tarballPath)
|
|
190
|
+
}
|
|
236
191
|
|
|
237
|
-
// Copy
|
|
238
|
-
fs.
|
|
192
|
+
// Copy `.temp` to projectPath
|
|
193
|
+
fs.cpSync(tempDir, projectPath, {recursive: true})
|
|
239
194
|
|
|
240
195
|
console.debug('[CLI:quire] installing dev dependencies into quire project')
|
|
241
196
|
/**
|
|
@@ -243,17 +198,16 @@ async function installInProject(projectPath, options = {}) {
|
|
|
243
198
|
* these must be `devDependencies` so that they are not bundled into
|
|
244
199
|
* the final `_site` package when running `quire build`
|
|
245
200
|
*/
|
|
246
|
-
await execaCommand('npm cache clean --force', { cwd: projectPath })
|
|
247
201
|
try {
|
|
248
202
|
await execaCommand('npm install --save-dev', { cwd: projectPath })
|
|
249
203
|
} catch(error) {
|
|
250
204
|
console.warn(`[CLI:error]`, error)
|
|
251
|
-
fs.
|
|
205
|
+
fs.rmSync(projectPath, {recursive: true})
|
|
252
206
|
return
|
|
253
207
|
}
|
|
254
208
|
|
|
255
209
|
const eleventyFilesToCommit = fs
|
|
256
|
-
.readdirSync(
|
|
210
|
+
.readdirSync(tempDir)
|
|
257
211
|
.filter((filePath) => filePath !== 'node_modules')
|
|
258
212
|
|
|
259
213
|
eleventyFilesToCommit.push('package-lock.json')
|
|
@@ -265,30 +219,32 @@ async function installInProject(projectPath, options = {}) {
|
|
|
265
219
|
await git.add(eleventyFilesToCommit).commit('Adds `@thegetty/quire-11ty` files')
|
|
266
220
|
|
|
267
221
|
// remove temporary 11ty install directory
|
|
268
|
-
fs.
|
|
222
|
+
fs.rmSync(path.join(projectPath, temp11tyDirectory), {recursive: true})
|
|
269
223
|
}
|
|
270
224
|
|
|
271
225
|
/**
|
|
272
226
|
* Retrieve latest published version of the `quire-11ty` package
|
|
273
|
-
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
* so that the latest function may be used like:
|
|
278
|
-
* await latest('^1.0.0-pre-release.0') => '1.0.0-pre-release.2'
|
|
279
|
-
*
|
|
280
|
-
* Nota bene: `npm view [<@scope>/]<name>[@<version>] version`
|
|
281
|
-
* @see https://docs.npmjs.com/cli/v7/commands/npm-view
|
|
282
|
-
* returns a list of versions that satisfy the `<version>` range specifier,
|
|
283
|
-
* piping this to execa `stdout` we get only the last line of output.
|
|
284
|
-
* @todo use [`parse-columns`](https://github.com/sindresorhus/parse-columns)
|
|
285
|
-
* to parse the column formated list of versions returned by `npm view`
|
|
286
|
-
*
|
|
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
|
+
*
|
|
287
231
|
* @return {String} `quire-11ty@latest` semantic version string
|
|
288
232
|
*/
|
|
289
|
-
async function latest() {
|
|
290
|
-
|
|
291
|
-
|
|
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
|
+
}
|
|
292
248
|
return quireVersion
|
|
293
249
|
}
|
|
294
250
|
|
|
@@ -325,11 +281,6 @@ async function remove(version) {
|
|
|
325
281
|
* @param {String} version a version identifier or distribution tag
|
|
326
282
|
*/
|
|
327
283
|
function setVersion(projectPath, version) {
|
|
328
|
-
if (!version) {
|
|
329
|
-
console.error('[CLI] no version specified')
|
|
330
|
-
return
|
|
331
|
-
}
|
|
332
|
-
fs.writeFileSync(path.join(projectPath, VERSION_FILE), version)
|
|
333
284
|
const projectName = path.basename(projectPath)
|
|
334
285
|
console.info(`${projectName} set to use quire-11ty@${version}`)
|
|
335
286
|
}
|
|
@@ -378,19 +329,6 @@ function symlinkLatest() {
|
|
|
378
329
|
return fs.symlinkSync(target, source, type)
|
|
379
330
|
}
|
|
380
331
|
|
|
381
|
-
/**
|
|
382
|
-
* Tests if a `quire-11ty` version is already installed
|
|
383
|
-
* and installs the version if it is not already installed.
|
|
384
|
-
*
|
|
385
|
-
* @param {String} version `quire-11ty` semantic version
|
|
386
|
-
*/
|
|
387
|
-
function testVersion(version) {
|
|
388
|
-
version ||= getVersion()
|
|
389
|
-
if (!versions.includes(version)) {
|
|
390
|
-
install(version)
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
332
|
/**
|
|
395
333
|
* Get an array of published `quire-11ty` package versions
|
|
396
334
|
*
|
|
@@ -404,12 +342,10 @@ export const quire = {
|
|
|
404
342
|
getPath,
|
|
405
343
|
getVersion,
|
|
406
344
|
initStarter,
|
|
407
|
-
install,
|
|
408
345
|
installInProject,
|
|
409
346
|
latest,
|
|
410
347
|
list,
|
|
411
348
|
remove,
|
|
412
349
|
setVersion,
|
|
413
|
-
testVersion,
|
|
414
350
|
versions,
|
|
415
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'
|