@thegetty/quire-cli 1.0.0-rc.44 → 1.0.0-rc.46

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 (243) hide show
  1. package/CHANGELOG.md +0 -36
  2. package/README.md +0 -9
  3. package/bin/cli.js +1 -46
  4. package/package.json +9 -25
  5. package/src/Command.js +6 -26
  6. package/src/commands/README.md +122 -213
  7. package/src/commands/build.js +37 -45
  8. package/src/commands/clean.js +19 -33
  9. package/src/commands/conf.js +43 -0
  10. package/src/commands/create.js +14 -42
  11. package/src/commands/epub.js +31 -67
  12. package/src/commands/index.js +2 -9
  13. package/src/commands/info.js +86 -88
  14. package/src/commands/pdf.js +82 -63
  15. package/src/commands/preview.js +31 -32
  16. package/src/commands/validate.js +20 -64
  17. package/src/commands/version.js +43 -0
  18. package/src/errors/validation/validation-error.js +11 -20
  19. package/src/helpers/clean.js +1 -1
  20. package/src/helpers/is-quire.js +31 -0
  21. package/src/helpers/test-cwd.js +6 -5
  22. package/src/helpers/which.js +4 -10
  23. package/src/lib/11ty/README.md +19 -135
  24. package/src/lib/11ty/api.js +93 -235
  25. package/src/lib/11ty/cli.js +37 -112
  26. package/src/lib/11ty/index.js +5 -64
  27. package/src/lib/11ty/paths.js +103 -0
  28. package/src/lib/README.md +0 -330
  29. package/src/lib/conf/README.md +69 -86
  30. package/src/lib/conf/config.js +3 -5
  31. package/src/lib/conf/defaults.js +0 -65
  32. package/src/lib/conf/schema.js +9 -103
  33. package/src/lib/epub/README.md +2 -133
  34. package/src/lib/epub/epub.js +11 -36
  35. package/src/lib/epub/index.js +21 -111
  36. package/src/lib/epub/pandoc.js +4 -33
  37. package/src/lib/git/README.md +2 -151
  38. package/src/lib/git/index.js +13 -217
  39. package/src/lib/i18n/README.md +3 -0
  40. package/src/lib/i18n/config.js +53 -0
  41. package/src/lib/i18n/index.js +43 -0
  42. package/src/lib/i18n/localeService.js +58 -0
  43. package/src/lib/pdf/README.md +0 -131
  44. package/src/lib/pdf/index.js +21 -124
  45. package/src/lib/pdf/paged.js +52 -100
  46. package/src/lib/pdf/prince.js +37 -115
  47. package/src/lib/pdf/split.js +33 -61
  48. package/src/lib/quire/README.md +19 -0
  49. package/src/lib/quire/index.js +353 -0
  50. package/src/lib/reporter/README.md +2 -211
  51. package/src/lib/reporter/index.js +0 -607
  52. package/src/main.js +48 -221
  53. package/src/packageConfig.js +0 -17
  54. package/src/validators/utils.js +1 -2
  55. package/schemas/config.schema.json +0 -194
  56. package/schemas/figures.schema.json +0 -56
  57. package/schemas/layout.schema.json +0 -5
  58. package/schemas/objects.schema.json +0 -62
  59. package/schemas/publication.schema.json +0 -140
  60. package/schemas/references.schema.json +0 -29
  61. package/src/Command.spec.js +0 -99
  62. package/src/commands/build.spec.js +0 -113
  63. package/src/commands/build.test.js +0 -397
  64. package/src/commands/clean.spec.js +0 -118
  65. package/src/commands/clean.test.js +0 -382
  66. package/src/commands/config.js +0 -238
  67. package/src/commands/config.spec.js +0 -107
  68. package/src/commands/config.test.js +0 -845
  69. package/src/commands/create.spec.js +0 -112
  70. package/src/commands/create.test.js +0 -415
  71. package/src/commands/doctor.js +0 -251
  72. package/src/commands/doctor.spec.js +0 -115
  73. package/src/commands/doctor.test.js +0 -1409
  74. package/src/commands/epub.spec.js +0 -114
  75. package/src/commands/epub.test.js +0 -503
  76. package/src/commands/help.js +0 -60
  77. package/src/commands/help.test.js +0 -132
  78. package/src/commands/info.spec.js +0 -72
  79. package/src/commands/info.test.js +0 -512
  80. package/src/commands/pdf.spec.js +0 -114
  81. package/src/commands/pdf.test.js +0 -464
  82. package/src/commands/preview.spec.js +0 -97
  83. package/src/commands/preview.test.js +0 -250
  84. package/src/commands/use.js +0 -56
  85. package/src/commands/use.spec.js +0 -61
  86. package/src/commands/use.test.js +0 -280
  87. package/src/commands/validate.spec.js +0 -90
  88. package/src/commands/validate.test.js +0 -378
  89. package/src/commands/workflows.js +0 -70
  90. package/src/errors/build/build-failed-error.js +0 -19
  91. package/src/errors/build/config-field-missing-error.js +0 -20
  92. package/src/errors/build/config-file-not-found-error.js +0 -20
  93. package/src/errors/build/index.js +0 -11
  94. package/src/errors/help/help-topic-not-found-error.js +0 -30
  95. package/src/errors/help/index.js +0 -8
  96. package/src/errors/index.js +0 -58
  97. package/src/errors/input/index.js +0 -8
  98. package/src/errors/input/invalid-input-error.js +0 -21
  99. package/src/errors/install/dependency-install-error.js +0 -19
  100. package/src/errors/install/directory-not-empty-error.js +0 -25
  101. package/src/errors/install/index.js +0 -12
  102. package/src/errors/install/invalid-path-error.js +0 -31
  103. package/src/errors/install/invalid-starter-error.js +0 -27
  104. package/src/errors/install/version-not-found-error.js +0 -35
  105. package/src/errors/output/epub-generation-error.js +0 -19
  106. package/src/errors/output/index.js +0 -14
  107. package/src/errors/output/invalid-epub-library-error.js +0 -20
  108. package/src/errors/output/invalid-pdf-library-error.js +0 -20
  109. package/src/errors/output/missing-build-output-error.js +0 -21
  110. package/src/errors/output/pdf-generation-error.js +0 -24
  111. package/src/errors/output/tool-not-found-error.js +0 -37
  112. package/src/errors/project/index.js +0 -10
  113. package/src/errors/project/not-in-project-error.js +0 -20
  114. package/src/errors/project/project-create-error.js +0 -21
  115. package/src/errors/quire-error.js +0 -27
  116. package/src/errors/validation/index.js +0 -3
  117. package/src/errors/validation/unknown-config-key-error.js +0 -27
  118. package/src/errors/validation/unknown-config-operation-error.js +0 -24
  119. package/src/helpers/docs-url.js +0 -32
  120. package/src/helpers/pager.js +0 -59
  121. package/src/helpers/pager.test.js +0 -85
  122. package/src/helpers/suggest-similar.js +0 -124
  123. package/src/helpers/suggest-similar.test.js +0 -108
  124. package/src/helpers/test-cwd.test.js +0 -192
  125. package/src/lib/11ty/index.test.js +0 -655
  126. package/src/lib/commander/index.js +0 -103
  127. package/src/lib/commander/index.test.js +0 -86
  128. package/src/lib/commander/options.js +0 -237
  129. package/src/lib/commander/options.test.js +0 -205
  130. package/src/lib/conf/build-status.js +0 -103
  131. package/src/lib/conf/build-status.test.js +0 -247
  132. package/src/lib/conf/config.test.js +0 -281
  133. package/src/lib/conf/format.js +0 -60
  134. package/src/lib/conf/format.test.js +0 -106
  135. package/src/lib/conf/helpers.js +0 -91
  136. package/src/lib/conf/helpers.test.js +0 -136
  137. package/src/lib/conf/index.js +0 -22
  138. package/src/lib/constants.js +0 -28
  139. package/src/lib/doctor/README.md +0 -667
  140. package/src/lib/doctor/checks/environment/cli-version.js +0 -62
  141. package/src/lib/doctor/checks/environment/cli-version.test.js +0 -132
  142. package/src/lib/doctor/checks/environment/git-available.js +0 -77
  143. package/src/lib/doctor/checks/environment/git-available.test.js +0 -52
  144. package/src/lib/doctor/checks/environment/index.js +0 -13
  145. package/src/lib/doctor/checks/environment/node-version.js +0 -66
  146. package/src/lib/doctor/checks/environment/node-version.test.js +0 -17
  147. package/src/lib/doctor/checks/environment/npm-available.js +0 -66
  148. package/src/lib/doctor/checks/environment/npm-available.test.js +0 -52
  149. package/src/lib/doctor/checks/environment/os-info.js +0 -48
  150. package/src/lib/doctor/checks/environment/os-info.test.js +0 -81
  151. package/src/lib/doctor/checks/environment/runtime-info.js +0 -51
  152. package/src/lib/doctor/checks/environment/runtime-info.test.js +0 -132
  153. package/src/lib/doctor/checks/outputs/epub-output.js +0 -119
  154. package/src/lib/doctor/checks/outputs/epub-output.test.js +0 -277
  155. package/src/lib/doctor/checks/outputs/index.js +0 -10
  156. package/src/lib/doctor/checks/outputs/pdf-output.js +0 -144
  157. package/src/lib/doctor/checks/outputs/pdf-output.test.js +0 -377
  158. package/src/lib/doctor/checks/outputs/stale-build.js +0 -122
  159. package/src/lib/doctor/checks/outputs/stale-build.test.js +0 -282
  160. package/src/lib/doctor/checks/project/data-files.js +0 -56
  161. package/src/lib/doctor/checks/project/data-files.test.js +0 -125
  162. package/src/lib/doctor/checks/project/dependencies.js +0 -53
  163. package/src/lib/doctor/checks/project/dependencies.test.js +0 -71
  164. package/src/lib/doctor/checks/project/index.js +0 -11
  165. package/src/lib/doctor/checks/project/quire-11ty.js +0 -98
  166. package/src/lib/doctor/checks/project/quire-11ty.test.js +0 -170
  167. package/src/lib/doctor/checks/project/quire-project.js +0 -38
  168. package/src/lib/doctor/checks/project/quire-project.test.js +0 -47
  169. package/src/lib/doctor/checks/tools/index.js +0 -10
  170. package/src/lib/doctor/checks/tools/pandoc-available.js +0 -82
  171. package/src/lib/doctor/checks/tools/pandoc-available.test.js +0 -73
  172. package/src/lib/doctor/checks/tools/prince-available.js +0 -81
  173. package/src/lib/doctor/checks/tools/prince-available.test.js +0 -73
  174. package/src/lib/doctor/constants.js +0 -39
  175. package/src/lib/doctor/formatDuration.js +0 -108
  176. package/src/lib/doctor/formatDuration.test.js +0 -76
  177. package/src/lib/doctor/formatters/human.js +0 -257
  178. package/src/lib/doctor/formatters/human.test.js +0 -463
  179. package/src/lib/doctor/formatters/index.js +0 -8
  180. package/src/lib/doctor/formatters/json.js +0 -78
  181. package/src/lib/doctor/formatters/json.test.js +0 -174
  182. package/src/lib/doctor/formatters/shared.js +0 -129
  183. package/src/lib/doctor/formatters/shared.test.js +0 -194
  184. package/src/lib/doctor/index.js +0 -271
  185. package/src/lib/doctor/index.test.js +0 -797
  186. package/src/lib/epub/engines.js +0 -46
  187. package/src/lib/epub/index.test.js +0 -518
  188. package/src/lib/epub/pandoc.test.js +0 -122
  189. package/src/lib/epub/schema.js +0 -21
  190. package/src/lib/error/README.md +0 -170
  191. package/src/lib/error/handler.js +0 -107
  192. package/src/lib/git/index.spec.js +0 -80
  193. package/src/lib/git/index.test.js +0 -453
  194. package/src/lib/help/frontmatter.js +0 -77
  195. package/src/lib/help/frontmatter.test.js +0 -139
  196. package/src/lib/help/index.js +0 -146
  197. package/src/lib/help/index.test.js +0 -188
  198. package/src/lib/help/topics/configuration.md +0 -77
  199. package/src/lib/help/topics/debugging.md +0 -69
  200. package/src/lib/help/topics/epub.md +0 -74
  201. package/src/lib/help/topics/pdf.md +0 -74
  202. package/src/lib/help/topics/publishing.md +0 -80
  203. package/src/lib/help/topics/workflows.md +0 -50
  204. package/src/lib/installer/index.js +0 -309
  205. package/src/lib/installer/index.spec.js +0 -83
  206. package/src/lib/installer/index.test.js +0 -545
  207. package/src/lib/logger/README.md +0 -424
  208. package/src/lib/logger/debug.js +0 -132
  209. package/src/lib/logger/debug.spec.js +0 -130
  210. package/src/lib/logger/debug.test.js +0 -128
  211. package/src/lib/logger/index.js +0 -228
  212. package/src/lib/logger/index.spec.js +0 -131
  213. package/src/lib/logger/index.test.js +0 -578
  214. package/src/lib/npm/README.md +0 -127
  215. package/src/lib/npm/index.js +0 -198
  216. package/src/lib/npm/index.spec.js +0 -60
  217. package/src/lib/npm/index.test.js +0 -355
  218. package/src/lib/pdf/engines.js +0 -46
  219. package/src/lib/pdf/index.test.js +0 -708
  220. package/src/lib/pdf/paged.test.js +0 -366
  221. package/src/lib/pdf/prince.test.js +0 -202
  222. package/src/lib/pdf/schema.js +0 -21
  223. package/src/lib/pdf/split.test.js +0 -445
  224. package/src/lib/platform.js +0 -95
  225. package/src/lib/process/manager.js +0 -110
  226. package/src/lib/process/manager.test.js +0 -55
  227. package/src/lib/project/build.js +0 -162
  228. package/src/lib/project/build.test.js +0 -275
  229. package/src/lib/project/config.js +0 -48
  230. package/src/lib/project/config.test.js +0 -134
  231. package/src/lib/project/detect.js +0 -47
  232. package/src/lib/project/detect.test.js +0 -157
  233. package/src/lib/project/index.js +0 -56
  234. package/src/lib/project/output-paths.js +0 -87
  235. package/src/lib/project/output-paths.test.js +0 -66
  236. package/src/lib/project/paths.js +0 -272
  237. package/src/lib/project/version.js +0 -110
  238. package/src/lib/project/version.test.js +0 -350
  239. package/src/lib/reporter/index.test.js +0 -862
  240. package/src/main.spec.js +0 -61
  241. package/src/main.test.js +0 -347
  242. package/src/validators/validate-data-files.js +0 -154
  243. package/src/validators/validate-data-files.test.js +0 -217
@@ -1,162 +0,0 @@
1
- /**
2
- * Build detection module
3
- *
4
- * Detects if build output exists and provides build status information.
5
- * Uses shared output path resolution to ensure consistency with the
6
- * doctor command and generation commands.
7
- *
8
- * @module lib/project/build
9
- */
10
- import fs from 'node:fs'
11
- import path from 'node:path'
12
- import paths from './paths.js'
13
- import { getPdfOutputPaths, getEpubOutputPaths, getEpubBuildDir } from './output-paths.js'
14
-
15
- /**
16
- * Get build output information
17
- *
18
- * @param {string} [projectRoot] - Project root directory (defaults to cwd)
19
- * @returns {Object} Build status information
20
- */
21
- export function getBuildInfo(projectRoot = paths.getProjectRoot()) {
22
- const sitePath = path.join(projectRoot, '_site')
23
- const epubBuildPath = getEpubBuildDir({ projectRoot })
24
-
25
- const info = {
26
- site: {
27
- exists: fs.existsSync(sitePath),
28
- path: sitePath,
29
- lastModified: null,
30
- },
31
- epub: {
32
- exists: false,
33
- paths: [],
34
- buildDir: epubBuildPath,
35
- buildDirExists: fs.existsSync(epubBuildPath),
36
- lastModified: null,
37
- },
38
- pdf: {
39
- exists: false,
40
- paths: [],
41
- lastModified: null,
42
- },
43
- }
44
-
45
- // Get last modified times if directories exist
46
- if (info.site.exists) {
47
- const { mtime: lastModified } = fs.statSync(sitePath)
48
- info.site.lastModified = lastModified
49
- }
50
- if (info.epub.buildDirExists) {
51
- const { mtime: lastModified } = fs.statSync(epubBuildPath)
52
- info.epub.lastModified = lastModified
53
- }
54
-
55
- // Check for EPUB files using shared path resolution
56
- const epubPaths = getEpubOutputPaths({ projectRoot })
57
- for (const epubPath of epubPaths) {
58
- if (fs.existsSync(epubPath)) {
59
- info.epub.exists = true
60
- info.epub.paths.push(epubPath)
61
- const { mtime: lastModified } = fs.statSync(epubPath)
62
- if (!info.epub.lastModified || lastModified > info.epub.lastModified) {
63
- info.epub.lastModified = lastModified
64
- }
65
- }
66
- }
67
-
68
- // Check for PDF files using shared path resolution
69
- const pdfPaths = getPdfOutputPaths({ projectRoot })
70
- for (const pdfPath of pdfPaths) {
71
- if (fs.existsSync(pdfPath)) {
72
- info.pdf.exists = true
73
- info.pdf.paths.push(pdfPath)
74
- const { mtime: lastModified } = fs.statSync(pdfPath)
75
- if (!info.pdf.lastModified || lastModified > info.pdf.lastModified) {
76
- info.pdf.lastModified = lastModified
77
- }
78
- }
79
- }
80
-
81
- return info
82
- }
83
-
84
- /**
85
- * Check if site build output exists
86
- *
87
- * @param {string} [projectRoot] - Project root directory (defaults to cwd)
88
- * @returns {boolean} True if _site directory exists
89
- */
90
- export function hasSiteOutput(projectRoot = paths.getProjectRoot()) {
91
- const sitePath = path.join(projectRoot, '_site')
92
- return fs.existsSync(sitePath)
93
- }
94
-
95
- /**
96
- * Check if epub build output exists
97
- *
98
- * Checks for the intermediate _epub build directory.
99
- *
100
- * @param {string} [projectRoot] - Project root directory (defaults to cwd)
101
- * @returns {boolean} True if _epub directory exists
102
- */
103
- export function hasEpubOutput(projectRoot = paths.getProjectRoot()) {
104
- const epubPath = getEpubBuildDir({ projectRoot })
105
- return fs.existsSync(epubPath)
106
- }
107
-
108
- /**
109
- * Check if PDF output exists
110
- *
111
- * Uses shared path resolution to check all possible PDF output locations,
112
- * including config-aware custom paths and default engine-named paths.
113
- *
114
- * @param {Object} [options]
115
- * @param {string} [options.lib] - Specific library to check ('pagedjs' or 'prince')
116
- * @param {string} [options.projectRoot] - Project root directory
117
- * @returns {boolean} True if PDF output exists
118
- */
119
- export function hasPdfOutput(options = {}) {
120
- const { lib, projectRoot = paths.getProjectRoot() } = options
121
-
122
- if (lib) {
123
- const pdfPath = path.join(projectRoot, `${lib}.pdf`)
124
- return fs.existsSync(pdfPath)
125
- }
126
-
127
- // Check all possible PDF output locations
128
- const pdfPaths = getPdfOutputPaths({ projectRoot })
129
- return pdfPaths.some((pdfPath) => fs.existsSync(pdfPath))
130
- }
131
-
132
- /**
133
- * Require build output to exist, throwing an error if missing
134
- *
135
- * @param {Object} options
136
- * @param {string} [options.type='site'] - Output type to check ('site' or 'epub')
137
- * @param {string} [options.projectRoot] - Project root directory
138
- * @throws {Error} If required build output does not exist
139
- */
140
- export function requireBuildOutput(options = {}) {
141
- const { type = 'site', projectRoot = paths.getProjectRoot() } = options
142
-
143
- if (type === 'site' && !hasSiteOutput(projectRoot)) {
144
- const error = new Error('Build output not found. Run "quire build" first.')
145
- error.code = 'ENOBUILD'
146
- throw error
147
- }
148
-
149
- if (type === 'epub' && !hasEpubOutput(projectRoot)) {
150
- const error = new Error('EPUB output not found. Run "quire build" first.')
151
- error.code = 'ENOBUILD'
152
- throw error
153
- }
154
- }
155
-
156
- export default {
157
- getBuildInfo,
158
- hasEpubOutput,
159
- hasPdfOutput,
160
- hasSiteOutput,
161
- requireBuildOutput,
162
- }
@@ -1,275 +0,0 @@
1
- import test from 'ava'
2
- import path from 'node:path'
3
- import { Volume, createFsFromVolume } from 'memfs'
4
- import esmock from 'esmock'
5
-
6
- /**
7
- * Helper to create cross-platform paths for memfs
8
- * memfs uses forward slashes internally, but we need to pass
9
- * platform-native paths to the functions under test
10
- */
11
- const testRoot = '/test-project'
12
- const nativePath = (...segments) => path.join(testRoot, ...segments)
13
- const memfsPath = (...segments) => [testRoot, ...segments].join('/')
14
-
15
- test.beforeEach((t) => {
16
- // Create in-memory file system
17
- t.context.vol = new Volume()
18
- t.context.fs = createFsFromVolume(t.context.vol)
19
- })
20
-
21
- test.afterEach.always((t) => {
22
- t.context.vol.reset()
23
- })
24
-
25
- test('hasSiteOutput returns true when _site exists', async (t) => {
26
- const { vol } = t.context
27
-
28
- vol.fromJSON({
29
- [memfsPath('_site', 'index.html')]: '<html></html>',
30
- })
31
-
32
- const { hasSiteOutput } = await esmock('./build.js', {
33
- 'node:fs': createFsFromVolume(vol),
34
- })
35
-
36
- t.true(hasSiteOutput(testRoot))
37
- })
38
-
39
- test('hasSiteOutput returns false when _site does not exist', async (t) => {
40
- const { vol } = t.context
41
-
42
- vol.fromJSON({
43
- [memfsPath('.quire')]: '',
44
- })
45
-
46
- const { hasSiteOutput } = await esmock('./build.js', {
47
- 'node:fs': createFsFromVolume(vol),
48
- })
49
-
50
- t.false(hasSiteOutput(testRoot))
51
- })
52
-
53
- test('hasEpubOutput returns true when _epub exists', async (t) => {
54
- const { vol } = t.context
55
-
56
- vol.fromJSON({
57
- [memfsPath('_epub', 'content.opf')]: '<package></package>',
58
- })
59
-
60
- const { hasEpubOutput } = await esmock('./build.js', {
61
- 'node:fs': createFsFromVolume(vol),
62
- })
63
-
64
- t.true(hasEpubOutput(testRoot))
65
- })
66
-
67
- test('hasEpubOutput returns false when _epub does not exist', async (t) => {
68
- const { vol } = t.context
69
-
70
- vol.fromJSON({
71
- [memfsPath('.quire')]: '',
72
- })
73
-
74
- const { hasEpubOutput } = await esmock('./build.js', {
75
- 'node:fs': createFsFromVolume(vol),
76
- })
77
-
78
- t.false(hasEpubOutput(testRoot))
79
- })
80
-
81
- test('getBuildInfo returns correct status for existing builds', async (t) => {
82
- const { vol } = t.context
83
-
84
- vol.fromJSON({
85
- [memfsPath('_site', 'index.html')]: '<html></html>',
86
- [memfsPath('_epub', 'content.opf')]: '<package></package>',
87
- [memfsPath('epubjs.epub')]: 'PK\x03\x04',
88
- [memfsPath('pagedjs.pdf')]: '%PDF-1.4',
89
- })
90
-
91
- const { getBuildInfo } = await esmock('./build.js', {
92
- 'node:fs': createFsFromVolume(vol),
93
- })
94
-
95
- const info = getBuildInfo(testRoot)
96
-
97
- t.true(info.site.exists)
98
- t.is(info.site.path, nativePath('_site'))
99
- t.truthy(info.site.lastModified)
100
-
101
- t.true(info.epub.exists)
102
- t.true(info.epub.buildDirExists)
103
- t.is(info.epub.buildDir, nativePath('_epub'))
104
- t.true(info.epub.paths.includes(nativePath('epubjs.epub')))
105
- t.truthy(info.epub.lastModified)
106
-
107
- t.true(info.pdf.exists)
108
- t.deepEqual(info.pdf.paths, [nativePath('pagedjs.pdf')])
109
- t.truthy(info.pdf.lastModified)
110
- })
111
-
112
- test('getBuildInfo returns correct status for missing builds', async (t) => {
113
- const { vol } = t.context
114
-
115
- vol.fromJSON({
116
- [memfsPath('.quire')]: '',
117
- })
118
-
119
- const { getBuildInfo } = await esmock('./build.js', {
120
- 'node:fs': createFsFromVolume(vol),
121
- })
122
-
123
- const info = getBuildInfo(testRoot)
124
-
125
- t.false(info.site.exists)
126
- t.is(info.site.lastModified, null)
127
-
128
- t.false(info.epub.exists)
129
- t.false(info.epub.buildDirExists)
130
- t.is(info.epub.lastModified, null)
131
-
132
- t.false(info.pdf.exists)
133
- t.deepEqual(info.pdf.paths, [])
134
- t.is(info.pdf.lastModified, null)
135
- })
136
-
137
- test('getBuildInfo returns multiple PDF paths when both exist', async (t) => {
138
- const { vol } = t.context
139
-
140
- vol.fromJSON({
141
- [memfsPath('pagedjs.pdf')]: '%PDF-1.4',
142
- [memfsPath('prince.pdf')]: '%PDF-1.4',
143
- })
144
-
145
- const { getBuildInfo } = await esmock('./build.js', {
146
- 'node:fs': createFsFromVolume(vol),
147
- })
148
-
149
- const info = getBuildInfo(testRoot)
150
-
151
- t.true(info.pdf.exists)
152
- t.is(info.pdf.paths.length, 2)
153
- t.true(info.pdf.paths.includes(nativePath('pagedjs.pdf')))
154
- t.true(info.pdf.paths.includes(nativePath('prince.pdf')))
155
- t.truthy(info.pdf.lastModified)
156
- })
157
-
158
- test('getBuildInfo detects epub build dir without .epub files', async (t) => {
159
- const { vol } = t.context
160
-
161
- vol.fromJSON({
162
- [memfsPath('_epub', 'content.opf')]: '<package></package>',
163
- })
164
-
165
- const { getBuildInfo } = await esmock('./build.js', {
166
- 'node:fs': createFsFromVolume(vol),
167
- })
168
-
169
- const info = getBuildInfo(testRoot)
170
-
171
- t.false(info.epub.exists, 'no .epub files should mean exists is false')
172
- t.true(info.epub.buildDirExists, '_epub directory should be detected')
173
- t.truthy(info.epub.lastModified, 'lastModified from _epub dir should be present')
174
- })
175
-
176
- test('requireBuildOutput throws when site output missing', async (t) => {
177
- const { vol } = t.context
178
-
179
- vol.fromJSON({
180
- [memfsPath('.quire')]: '',
181
- })
182
-
183
- const { requireBuildOutput } = await esmock('./build.js', {
184
- 'node:fs': createFsFromVolume(vol),
185
- })
186
-
187
- const error = t.throws(() => {
188
- requireBuildOutput({ type: 'site', projectRoot: testRoot })
189
- })
190
-
191
- t.is(error.code, 'ENOBUILD')
192
- t.regex(error.message, /quire build/)
193
- })
194
-
195
- test('requireBuildOutput throws when epub output missing', async (t) => {
196
- const { vol } = t.context
197
-
198
- vol.fromJSON({
199
- [memfsPath('.quire')]: '',
200
- })
201
-
202
- const { requireBuildOutput } = await esmock('./build.js', {
203
- 'node:fs': createFsFromVolume(vol),
204
- })
205
-
206
- const error = t.throws(() => {
207
- requireBuildOutput({ type: 'epub', projectRoot: testRoot })
208
- })
209
-
210
- t.is(error.code, 'ENOBUILD')
211
- })
212
-
213
- test('requireBuildOutput does not throw when output exists', async (t) => {
214
- const { vol } = t.context
215
-
216
- vol.fromJSON({
217
- [memfsPath('_site', 'index.html')]: '<html></html>',
218
- [memfsPath('_epub', 'content.opf')]: '<package></package>',
219
- })
220
-
221
- const { requireBuildOutput } = await esmock('./build.js', {
222
- 'node:fs': createFsFromVolume(vol),
223
- })
224
-
225
- t.notThrows(() => {
226
- requireBuildOutput({ type: 'site', projectRoot: testRoot })
227
- })
228
-
229
- t.notThrows(() => {
230
- requireBuildOutput({ type: 'epub', projectRoot: testRoot })
231
- })
232
- })
233
-
234
- test('hasPdfOutput returns true when specific lib PDF exists', async (t) => {
235
- const { vol } = t.context
236
-
237
- vol.fromJSON({
238
- [memfsPath('pagedjs.pdf')]: '%PDF-1.4',
239
- })
240
-
241
- const { hasPdfOutput } = await esmock('./build.js', {
242
- 'node:fs': createFsFromVolume(vol),
243
- })
244
-
245
- t.true(hasPdfOutput({ lib: 'pagedjs', projectRoot: testRoot }))
246
- t.false(hasPdfOutput({ lib: 'prince', projectRoot: testRoot }))
247
- })
248
-
249
- test('hasPdfOutput returns false when no PDF exists', async (t) => {
250
- const { vol } = t.context
251
-
252
- vol.fromJSON({
253
- [memfsPath('.quire')]: '',
254
- })
255
-
256
- const { hasPdfOutput } = await esmock('./build.js', {
257
- 'node:fs': createFsFromVolume(vol),
258
- })
259
-
260
- t.false(hasPdfOutput({ projectRoot: testRoot }))
261
- })
262
-
263
- test('hasPdfOutput returns true when any common PDF exists', async (t) => {
264
- const { vol } = t.context
265
-
266
- vol.fromJSON({
267
- [memfsPath('prince.pdf')]: '%PDF-1.4',
268
- })
269
-
270
- const { hasPdfOutput } = await esmock('./build.js', {
271
- 'node:fs': createFsFromVolume(vol),
272
- })
273
-
274
- t.true(hasPdfOutput({ projectRoot: testRoot }))
275
- })
@@ -1,48 +0,0 @@
1
- /**
2
- * Project configuration module
3
- *
4
- * Loads and validates Quire project configuration files.
5
- *
6
- * @module lib/project/config
7
- */
8
- import fs from 'fs-extra'
9
- import path from 'node:path'
10
- import { pathToFileURL } from 'node:url'
11
- import yaml from 'js-yaml'
12
- import paths from './paths.js'
13
- import { ConfigFileNotFoundError } from '#src/errors/index.js'
14
-
15
- /**
16
- * Load and validate Quire project configuration
17
- *
18
- * @param {string} [projectRoot] - project root directory (defaults to cwd)
19
- * @returns {Promise<Object>} validated configuration object
20
- *
21
- * @todo consider hardcoding a version check against .quire / project's package.json ver
22
- */
23
- export async function loadProjectConfig(projectRoot) {
24
- const root = projectRoot || paths.getProjectRoot()
25
- const configPath = path.join(root, 'content', '_data', 'config.yaml')
26
-
27
- if (!fs.existsSync(configPath)) {
28
- throw new ConfigFileNotFoundError(configPath)
29
- }
30
-
31
- const data = fs.readFileSync(configPath)
32
- let config = yaml.load(data)
33
-
34
- // NB: Schemas and validators are specific to the 11ty version of the project being built
35
- const schemaPath = path.join(root, '_plugins', 'schemas', 'config.json')
36
- const validatorPath = path.join(root, '_plugins', 'globalData', 'validator.js')
37
-
38
- if (fs.existsSync(schemaPath) && fs.existsSync(validatorPath)) {
39
- const { validateUserConfig } = await import(pathToFileURL(validatorPath))
40
-
41
- const schemaJSON = fs.readFileSync(schemaPath)
42
- const schema = JSON.parse(schemaJSON)
43
-
44
- config = validateUserConfig('config', config, { config: schema })
45
- }
46
-
47
- return config
48
- }
@@ -1,134 +0,0 @@
1
- import test from 'ava'
2
- import { Volume, createFsFromVolume } from 'memfs'
3
- import sinon from 'sinon'
4
- import esmock from 'esmock'
5
-
6
- test.beforeEach((t) => {
7
- // Create sinon sandbox for mocking
8
- t.context.sandbox = sinon.createSandbox()
9
-
10
- // Create in-memory file system
11
- t.context.vol = new Volume()
12
- t.context.fs = createFsFromVolume(t.context.vol)
13
- })
14
-
15
- test.afterEach.always((t) => {
16
- // Restore all mocks
17
- if (t.context.sandbox) {
18
- t.context.sandbox.restore()
19
- }
20
-
21
- // Clear in-memory file system
22
- t.context.vol.reset()
23
- })
24
-
25
- test('loadProjectConfig should throw ConfigFileNotFoundError when config file does not exist', async (t) => {
26
- const { fs, vol } = t.context
27
-
28
- // Setup empty directory
29
- vol.fromJSON({
30
- '/project/content/_data/.gitkeep': ''
31
- })
32
-
33
- const { loadProjectConfig } = await esmock('./config.js', {
34
- 'fs-extra': {
35
- existsSync: (p) => fs.existsSync(p),
36
- readFileSync: (p) => fs.readFileSync(p)
37
- },
38
- './paths.js': {
39
- default: {
40
- getProjectRoot: () => '/project'
41
- }
42
- }
43
- })
44
-
45
- const error = await t.throwsAsync(() => loadProjectConfig('/project'))
46
-
47
- t.is(error.code, 'CONFIG_FILE_NOT_FOUND')
48
- t.true(error.message.includes('config.yaml'), 'error should include config path')
49
- })
50
-
51
- test('loadProjectConfig should load and parse YAML config file', async (t) => {
52
- const { fs, vol } = t.context
53
-
54
- // Setup config file
55
- vol.fromJSON({
56
- '/project/content/_data/config.yaml': 'title: Test Project\nauthor: Test Author'
57
- })
58
-
59
- // Mock fs-extra to use memfs
60
- const { loadProjectConfig } = await esmock('./config.js', {
61
- 'fs-extra': {
62
- existsSync: (p) => fs.existsSync(p),
63
- readFileSync: (p) => fs.readFileSync(p)
64
- },
65
- './paths.js': {
66
- default: {
67
- getProjectRoot: () => '/project'
68
- }
69
- }
70
- })
71
-
72
- const result = await loadProjectConfig('/project')
73
-
74
- t.truthy(result, 'should return config object')
75
- t.is(result.title, 'Test Project', 'should parse title')
76
- t.is(result.author, 'Test Author', 'should parse author')
77
- })
78
-
79
- test('loadProjectConfig should load PDF configuration', async (t) => {
80
- const { fs, vol } = t.context
81
-
82
- // Setup config file with PDF settings
83
- vol.fromJSON({
84
- '/project/content/_data/config.yaml': `title: Test Project
85
- pdf:
86
- filename: test-book
87
- outputDir: _pdf`
88
- })
89
-
90
- // Mock fs-extra to use memfs
91
- const { loadProjectConfig } = await esmock('./config.js', {
92
- 'fs-extra': {
93
- existsSync: (p) => fs.existsSync(p),
94
- readFileSync: (p) => fs.readFileSync(p)
95
- },
96
- './paths.js': {
97
- default: {
98
- getProjectRoot: () => '/project'
99
- }
100
- }
101
- })
102
-
103
- const result = await loadProjectConfig('/project')
104
-
105
- t.truthy(result.pdf, 'should have pdf config')
106
- t.is(result.pdf.filename, 'test-book', 'should parse pdf filename')
107
- t.is(result.pdf.outputDir, '_pdf', 'should parse pdf outputDir')
108
- })
109
-
110
- test('loadProjectConfig should use default project root when not provided', async (t) => {
111
- const { fs, vol } = t.context
112
-
113
- // Setup config file
114
- vol.fromJSON({
115
- '/default/project/content/_data/config.yaml': 'title: Default Project'
116
- })
117
-
118
- // Mock fs-extra to use memfs
119
- const { loadProjectConfig } = await esmock('./config.js', {
120
- 'fs-extra': {
121
- existsSync: (p) => fs.existsSync(p),
122
- readFileSync: (p) => fs.readFileSync(p)
123
- },
124
- './paths.js': {
125
- default: {
126
- getProjectRoot: () => '/default/project'
127
- }
128
- }
129
- })
130
-
131
- const result = await loadProjectConfig()
132
-
133
- t.is(result.title, 'Default Project', 'should use default project root')
134
- })
@@ -1,47 +0,0 @@
1
- /**
2
- * Project detection module
3
- *
4
- * Detects if a directory is a Quire project root by checking for marker files.
5
- *
6
- * @module lib/project/detect
7
- */
8
- import fs from 'node:fs'
9
-
10
- /**
11
- * Files that indicate a Quire project directory.
12
- *
13
- * These marker files are created by `quire new` or exist in Quire project templates.
14
- * The presence of any one of these files indicates the directory is a Quire project root.
15
- *
16
- * @constant {ReadonlyArray<string>}
17
- *
18
- * @example
19
- * // Check if a file is a project marker
20
- * if (PROJECT_MARKERS.includes(filename)) {
21
- * console.log('This is a Quire project root')
22
- * }
23
- */
24
- export const PROJECT_MARKERS = Object.freeze([
25
- '.eleventy.js',
26
- '.quire',
27
- '.quire-11ty',
28
- '.quire-version',
29
- 'eleventy.config.js',
30
- ])
31
-
32
- /**
33
- * Test if a directory is a Quire project root
34
- *
35
- * Nota bene: `dirpath` must be to the Quire project root to positively confirm
36
- * a directory is a Quire project, testing a Quire project subdirectory will
37
- * return falsely negative result.
38
- *
39
- * @todo refactor this to throw an Error NOTQUIRE
40
- *
41
- * @param {String} dirpath path to a local directory
42
- * @return {Promise}
43
- */
44
- export default function (dirpath) {
45
- return fs.readdirSync(dirpath)
46
- .find((entry) => PROJECT_MARKERS.includes(entry))
47
- }