@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.
Files changed (45) hide show
  1. package/CHANGELOG.md +154 -7
  2. package/README.md +15 -0
  3. package/bin/cli.js +13 -7
  4. package/package.json +17 -11
  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/commands/README.md +28 -8
  9. package/src/commands/conf.js +43 -0
  10. package/src/commands/create.js +22 -10
  11. package/src/commands/epub.js +1 -2
  12. package/src/commands/info.js +130 -0
  13. package/src/commands/pdf.js +60 -10
  14. package/src/commands/validate.js +64 -0
  15. package/src/errors/validation/validation-error.js +13 -0
  16. package/src/errors/validation/yaml-duplicate-error.js +11 -0
  17. package/src/errors/validation/yaml-parse-error.js +11 -0
  18. package/src/errors/validation/yaml-validation-error.js +11 -0
  19. package/src/helpers/clean.js +5 -0
  20. package/src/helpers/is-empty.js +4 -0
  21. package/src/helpers/is-quire.js +4 -0
  22. package/src/helpers/os-utils.js +4 -0
  23. package/src/helpers/test-cwd.js +4 -0
  24. package/src/helpers/which.js +4 -0
  25. package/src/lib/11ty/cli.js +26 -4
  26. package/src/lib/conf/README.md +104 -0
  27. package/src/lib/conf/config.js +35 -0
  28. package/src/lib/conf/defaults.js +37 -0
  29. package/src/lib/conf/migrations.js +11 -0
  30. package/src/lib/conf/schema.js +30 -0
  31. package/src/lib/epub/index.js +3 -1
  32. package/src/lib/pdf/README.md +11 -4
  33. package/src/lib/pdf/index.js +7 -5
  34. package/src/lib/pdf/paged.js +88 -9
  35. package/src/lib/pdf/pagedPlugin.js +49 -0
  36. package/src/lib/pdf/prince.js +79 -4
  37. package/src/lib/pdf/princePlugin.js +35 -0
  38. package/src/lib/pdf/split.js +61 -0
  39. package/src/lib/quire/index.js +89 -143
  40. package/src/main.js +21 -6
  41. package/src/packageConfig.js +15 -0
  42. package/src/validators/utils.js +98 -0
  43. package/src/validators/validate-yaml.js +38 -0
  44. package/src/lib/config/README.md +0 -3
  45. package/src/lib/config/index.js +0 -1
@@ -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 '#src/lib/git/index.js'
8
- import inv from 'install-npm-version'
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
- const VERSION_FILE = '.quire'
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 `quire-11ty` version
25
+ * Return an absolute path to an installed quire-11ty version
22
26
  *
23
- * @return {String} path to installed `quire-11ty` version
27
+ * @return {String} path to installed quire-11ty version
24
28
  */
25
- function getPath(version='latest') {
26
- const absolutePath = path.relative('/', `${INSTALL_PATH}/${version}`)
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] \`quire-11ty@${version}\` is not installed`)
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 `quire-11ty` version for the project `.quire` file
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 the required `quire-11ty` version from starter `package.json` `peerDependencies`
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 {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,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 root: "${projectPath}"`,
100
- `\n starter: "${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 `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, 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 a specific version of `quire-11ty`
150
+ * Install `quire-11ty` directly into a quire project
144
151
  *
145
- * @param {String} version Quire-11ty semantic version
146
- * @param {Object} options options passed from `quire new` command
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, version, options={}) {
196
- console.debug(`[CLI:quire] installing quire-11ty@${version} into ${projectPath}`)
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
- * delete `package.json` from starter project, as it will be replaced with
200
- * `package.json` from `@thegetty/quire-11ty`
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
- * `Destination` is relative to `node_modules` of the working-directory
212
- * so we have included a relative path to parent directory in order to
213
- * install versions to a different local path.
214
- * @see https://github.com/scott-lin/install-npm-version
215
- */
216
- const installOptions = {
217
- Destination: path.join('..', temp11tyDirectory),
218
- Debug: false,
219
- Overwrite: options.force || options.overwrite || false,
220
- Verbosity: options.debug ? 'Debug' : 'Silent',
221
- WorkingDirectory: projectPath
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
- // delete empty `node_modules` directory that `install-npm-version` creates
226
- const invNodeModulesDir = path.join(projectPath, 'node_modules')
227
- if (fs.existsSync(invNodeModulesDir)) fs.rmdir(invNodeModulesDir)
190
+ fs.rmSync(tarballPath)
191
+ }
228
192
 
229
- // Copy all files installed in `.temp` to projectPath
230
- fs.copySync(path.join(projectPath, '.temp'), projectPath)
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.removeSync(projectPath)
207
+ fs.rmSync(projectPath, {recursive: true})
244
208
  return
245
209
  }
246
210
 
247
211
  const eleventyFilesToCommit = fs
248
- .readdirSync(path.join(projectPath, temp11tyDirectory))
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.removeSync(path.join(projectPath, temp11tyDirectory))
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
- * @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
- *
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
- const { stdout: quireVersion } =
283
- await execa('npm', ['view', PACKAGE_NAME, 'version'])
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 Quire-11ty semantic 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 './commands/index.js'
3
- import packageConfig from '../package.json' assert { type: 'json' }
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-cli')
18
+ .name('quire')
16
19
  .description('Quire command-line interface')
17
- .version(packageConfig.version, '-v, --version', 'output quire version number')
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
- aliases.forEach((alias) => subCommand.alias(alias))
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
+ }
@@ -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'