@thegetty/quire-cli 1.0.0-rc.34 → 1.0.0-rc.36

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 (159) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +9 -0
  3. package/bin/cli.js +19 -1
  4. package/package.json +21 -8
  5. package/schemas/config.schema.json +194 -0
  6. package/schemas/figures.schema.json +56 -0
  7. package/schemas/layout.schema.json +5 -0
  8. package/schemas/objects.schema.json +62 -0
  9. package/schemas/publication.schema.json +140 -0
  10. package/schemas/references.schema.json +29 -0
  11. package/src/Command.js +26 -6
  12. package/src/Command.spec.js +99 -0
  13. package/src/commands/README.md +213 -122
  14. package/src/commands/build.js +46 -37
  15. package/src/commands/build.spec.js +113 -0
  16. package/src/commands/build.test.js +402 -0
  17. package/src/commands/clean.js +25 -19
  18. package/src/commands/clean.spec.js +108 -0
  19. package/src/commands/clean.test.js +260 -0
  20. package/src/commands/config.js +251 -0
  21. package/src/commands/config.spec.js +107 -0
  22. package/src/commands/config.test.js +715 -0
  23. package/src/commands/create.js +42 -14
  24. package/src/commands/create.spec.js +112 -0
  25. package/src/commands/create.test.js +415 -0
  26. package/src/commands/epub.js +59 -30
  27. package/src/commands/epub.spec.js +114 -0
  28. package/src/commands/epub.test.js +503 -0
  29. package/src/commands/index.js +9 -2
  30. package/src/commands/info.js +18 -24
  31. package/src/commands/info.spec.js +64 -0
  32. package/src/commands/info.test.js +415 -0
  33. package/src/commands/pdf.js +58 -84
  34. package/src/commands/pdf.spec.js +114 -0
  35. package/src/commands/pdf.test.js +464 -0
  36. package/src/commands/preview.js +26 -31
  37. package/src/commands/preview.spec.js +97 -0
  38. package/src/commands/preview.test.js +250 -0
  39. package/src/commands/use.js +56 -0
  40. package/src/commands/use.spec.js +61 -0
  41. package/src/commands/use.test.js +280 -0
  42. package/src/commands/validate.js +31 -19
  43. package/src/commands/validate.spec.js +82 -0
  44. package/src/commands/validate.test.js +234 -0
  45. package/src/commands/workflows.js +70 -0
  46. package/src/errors/build/build-failed-error.js +19 -0
  47. package/src/errors/build/config-field-missing-error.js +20 -0
  48. package/src/errors/build/config-file-not-found-error.js +20 -0
  49. package/src/errors/build/index.js +11 -0
  50. package/src/errors/index.js +48 -0
  51. package/src/errors/install/dependency-install-error.js +19 -0
  52. package/src/errors/install/directory-not-empty-error.js +25 -0
  53. package/src/errors/install/index.js +12 -0
  54. package/src/errors/install/invalid-path-error.js +31 -0
  55. package/src/errors/install/invalid-starter-error.js +27 -0
  56. package/src/errors/install/version-not-found-error.js +35 -0
  57. package/src/errors/output/epub-generation-error.js +19 -0
  58. package/src/errors/output/index.js +14 -0
  59. package/src/errors/output/invalid-epub-library-error.js +20 -0
  60. package/src/errors/output/invalid-pdf-library-error.js +20 -0
  61. package/src/errors/output/missing-build-output-error.js +21 -0
  62. package/src/errors/output/pdf-generation-error.js +24 -0
  63. package/src/errors/output/tool-not-found-error.js +37 -0
  64. package/src/errors/project/index.js +10 -0
  65. package/src/errors/project/not-in-project-error.js +20 -0
  66. package/src/errors/project/project-create-error.js +21 -0
  67. package/src/errors/quire-error.js +27 -0
  68. package/src/errors/validation/validation-error.js +20 -11
  69. package/src/helpers/clean.js +1 -1
  70. package/src/helpers/docs-url.js +32 -0
  71. package/src/helpers/test-cwd.js +5 -6
  72. package/src/helpers/test-cwd.test.js +192 -0
  73. package/src/helpers/which.js +10 -4
  74. package/src/lib/11ty/README.md +135 -19
  75. package/src/lib/11ty/api.js +176 -93
  76. package/src/lib/11ty/cli.js +77 -35
  77. package/src/lib/11ty/index.js +64 -5
  78. package/src/lib/11ty/index.test.js +655 -0
  79. package/src/lib/README.md +275 -0
  80. package/src/lib/commander/index.js +100 -0
  81. package/src/lib/commander/index.test.js +86 -0
  82. package/src/lib/commander/options.js +195 -0
  83. package/src/lib/commander/options.test.js +109 -0
  84. package/src/lib/conf/README.md +84 -73
  85. package/src/lib/conf/config.js +5 -3
  86. package/src/lib/conf/config.test.js +281 -0
  87. package/src/lib/conf/defaults.js +44 -0
  88. package/src/lib/conf/format.js +60 -0
  89. package/src/lib/conf/format.test.js +106 -0
  90. package/src/lib/conf/helpers.js +91 -0
  91. package/src/lib/conf/helpers.test.js +136 -0
  92. package/src/lib/conf/index.js +22 -0
  93. package/src/lib/conf/schema.js +53 -8
  94. package/src/lib/epub/README.md +133 -2
  95. package/src/lib/epub/engines.js +46 -0
  96. package/src/lib/epub/epub.js +36 -11
  97. package/src/lib/epub/index.js +111 -21
  98. package/src/lib/epub/index.test.js +518 -0
  99. package/src/lib/epub/pandoc.js +33 -4
  100. package/src/lib/epub/pandoc.test.js +122 -0
  101. package/src/lib/epub/schema.js +21 -0
  102. package/src/lib/error/README.md +170 -0
  103. package/src/lib/error/handler.js +107 -0
  104. package/src/lib/git/README.md +151 -2
  105. package/src/lib/git/index.js +217 -13
  106. package/src/lib/git/index.spec.js +80 -0
  107. package/src/lib/git/index.test.js +453 -0
  108. package/src/lib/installer/index.js +309 -0
  109. package/src/lib/installer/index.spec.js +83 -0
  110. package/src/lib/installer/index.test.js +545 -0
  111. package/src/lib/logger/README.md +424 -0
  112. package/src/lib/logger/debug.js +90 -0
  113. package/src/lib/logger/debug.spec.js +130 -0
  114. package/src/lib/logger/index.js +228 -0
  115. package/src/lib/logger/index.spec.js +131 -0
  116. package/src/lib/logger/index.test.js +477 -0
  117. package/src/lib/npm/README.md +127 -0
  118. package/src/lib/npm/index.js +198 -0
  119. package/src/lib/npm/index.spec.js +60 -0
  120. package/src/lib/npm/index.test.js +355 -0
  121. package/src/lib/pdf/README.md +131 -0
  122. package/src/lib/pdf/engines.js +46 -0
  123. package/src/lib/pdf/index.js +124 -21
  124. package/src/lib/pdf/index.test.js +708 -0
  125. package/src/lib/pdf/paged.js +100 -52
  126. package/src/lib/pdf/paged.test.js +366 -0
  127. package/src/lib/pdf/prince.js +115 -37
  128. package/src/lib/pdf/prince.test.js +202 -0
  129. package/src/lib/pdf/schema.js +21 -0
  130. package/src/lib/pdf/split.js +61 -33
  131. package/src/lib/pdf/split.test.js +445 -0
  132. package/src/lib/process/manager.js +110 -0
  133. package/src/lib/process/manager.test.js +55 -0
  134. package/src/lib/project/build.js +143 -0
  135. package/src/lib/project/build.test.js +253 -0
  136. package/src/lib/project/config.js +48 -0
  137. package/src/lib/project/config.test.js +134 -0
  138. package/src/{helpers/is-quire.js → lib/project/detect.js} +5 -3
  139. package/src/lib/project/detect.test.js +157 -0
  140. package/src/lib/project/index.js +40 -0
  141. package/src/lib/project/paths.js +224 -0
  142. package/src/lib/project/version.js +110 -0
  143. package/src/lib/project/version.test.js +350 -0
  144. package/src/lib/reporter/README.md +211 -2
  145. package/src/lib/reporter/index.js +512 -0
  146. package/src/lib/reporter/index.test.js +593 -0
  147. package/src/main.js +134 -47
  148. package/src/main.spec.js +61 -0
  149. package/src/main.test.js +347 -0
  150. package/src/validators/utils.js +2 -1
  151. package/src/commands/conf.js +0 -43
  152. package/src/commands/version.js +0 -43
  153. package/src/lib/11ty/paths.js +0 -103
  154. package/src/lib/i18n/README.md +0 -3
  155. package/src/lib/i18n/config.js +0 -53
  156. package/src/lib/i18n/index.js +0 -43
  157. package/src/lib/i18n/localeService.js +0 -58
  158. package/src/lib/quire/README.md +0 -19
  159. package/src/lib/quire/index.js +0 -350
@@ -0,0 +1,157 @@
1
+ import test from 'ava'
2
+ import { Volume, createFsFromVolume } from 'memfs'
3
+ import esmock from 'esmock'
4
+
5
+ test.beforeEach((t) => {
6
+ // Create in-memory file system
7
+ t.context.vol = new Volume()
8
+ t.context.fs = createFsFromVolume(t.context.vol)
9
+ })
10
+
11
+ test.afterEach.always((t) => {
12
+ // Clear in-memory file system
13
+ t.context.vol.reset()
14
+ })
15
+
16
+ test('detect should recognize .eleventy.js marker file', async (t) => {
17
+ const { fs, vol } = t.context
18
+
19
+ // Setup a Quire project directory with .eleventy.js
20
+ vol.fromJSON({
21
+ '/quire-project/.eleventy.js': 'module.exports = function() {}',
22
+ '/quire-project/content/_data/config.yaml': 'title: Test Project'
23
+ })
24
+
25
+ // Mock node:fs to use memfs
26
+ const detect = await esmock('./detect.js', {
27
+ 'node:fs': fs
28
+ })
29
+
30
+ const result = detect('/quire-project')
31
+
32
+ t.truthy(result, 'should return truthy value for directory with .eleventy.js')
33
+ t.is(result, '.eleventy.js', 'should return the marker file name')
34
+ })
35
+
36
+ test('detect should recognize eleventy.config.js marker file', async (t) => {
37
+ const { fs, vol } = t.context
38
+
39
+ // Setup a Quire project directory with eleventy.config.js
40
+ vol.fromJSON({
41
+ '/quire-project/eleventy.config.js': 'module.exports = function() {}',
42
+ '/quire-project/content/_data/config.yaml': 'title: Test Project'
43
+ })
44
+
45
+ // Mock node:fs to use memfs
46
+ const detect = await esmock('./detect.js', {
47
+ 'node:fs': fs
48
+ })
49
+
50
+ const result = detect('/quire-project')
51
+
52
+ t.truthy(result, 'should return truthy value for directory with eleventy.config.js')
53
+ t.is(result, 'eleventy.config.js', 'should return the marker file name')
54
+ })
55
+
56
+ test('detect should recognize .quire marker file', async (t) => {
57
+ const { fs, vol } = t.context
58
+
59
+ // Setup a Quire project directory with .quire marker
60
+ vol.fromJSON({
61
+ '/quire-project/.quire': '',
62
+ '/quire-project/content/index.md': '# Welcome'
63
+ })
64
+
65
+ // Mock node:fs to use memfs
66
+ const detect = await esmock('./detect.js', {
67
+ 'node:fs': fs
68
+ })
69
+
70
+ const result = detect('/quire-project')
71
+
72
+ t.truthy(result, 'should return truthy value for directory with .quire')
73
+ t.is(result, '.quire', 'should return the marker file name')
74
+ })
75
+
76
+ test('detect should recognize .quire-11ty marker file', async (t) => {
77
+ const { fs, vol } = t.context
78
+
79
+ // Setup a Quire project directory with .quire-11ty marker
80
+ vol.fromJSON({
81
+ '/quire-project/.quire-11ty': '',
82
+ '/quire-project/content/index.md': '# Welcome'
83
+ })
84
+
85
+ // Mock node:fs to use memfs
86
+ const detect = await esmock('./detect.js', {
87
+ 'node:fs': fs
88
+ })
89
+
90
+ const result = detect('/quire-project')
91
+
92
+ t.truthy(result, 'should return truthy value for directory with .quire-11ty')
93
+ t.is(result, '.quire-11ty', 'should return the marker file name')
94
+ })
95
+
96
+ test('detect should recognize .quire-version marker file', async (t) => {
97
+ const { fs, vol } = t.context
98
+
99
+ // Setup a Quire project directory with .quire-version marker
100
+ vol.fromJSON({
101
+ '/quire-project/.quire-version': '1.0.0',
102
+ '/quire-project/content/index.md': '# Welcome'
103
+ })
104
+
105
+ // Mock node:fs to use memfs
106
+ const detect = await esmock('./detect.js', {
107
+ 'node:fs': fs
108
+ })
109
+
110
+ const result = detect('/quire-project')
111
+
112
+ t.truthy(result, 'should return truthy value for directory with .quire-version')
113
+ t.is(result, '.quire-version', 'should return the marker file name')
114
+ })
115
+
116
+ test('detect should return falsy for non-Quire directory', async (t) => {
117
+ const { fs, vol } = t.context
118
+
119
+ // Setup a non-Quire directory (no marker files)
120
+ vol.fromJSON({
121
+ '/non-quire-directory/some-file.txt': 'not a quire project',
122
+ '/non-quire-directory/package.json': JSON.stringify({ name: 'some-other-project' })
123
+ })
124
+
125
+ // Mock node:fs to use memfs
126
+ const detect = await esmock('./detect.js', {
127
+ 'node:fs': fs
128
+ })
129
+
130
+ const result = detect('/non-quire-directory')
131
+
132
+ t.falsy(result, 'should return falsy value for directory without marker files')
133
+ })
134
+
135
+ test('detect should return first marker file found when multiple exist', async (t) => {
136
+ const { fs, vol } = t.context
137
+
138
+ // Setup a Quire project directory with multiple markers
139
+ vol.fromJSON({
140
+ '/quire-project/.eleventy.js': 'module.exports = function() {}',
141
+ '/quire-project/.quire': '',
142
+ '/quire-project/.quire-version': '1.0.0'
143
+ })
144
+
145
+ // Mock node:fs to use memfs
146
+ const detect = await esmock('./detect.js', {
147
+ 'node:fs': fs
148
+ })
149
+
150
+ const result = detect('/quire-project')
151
+
152
+ t.truthy(result, 'should return truthy value for directory with multiple markers')
153
+ t.true(
154
+ ['.eleventy.js', '.quire', '.quire-11ty', '.quire-version', 'eleventy.config.js'].includes(result),
155
+ 'should return one of the valid marker file names'
156
+ )
157
+ })
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Project module
3
+ *
4
+ * Provides project-level concerns including paths, detection, configuration,
5
+ * build status, and version management.
6
+ */
7
+ import paths, { Paths } from './paths.js'
8
+ import detect from './detect.js'
9
+ import { loadProjectConfig } from './config.js'
10
+ import {
11
+ getBuildInfo,
12
+ hasEpubOutput,
13
+ hasPdfOutput,
14
+ hasSiteOutput,
15
+ requireBuildOutput,
16
+ } from './build.js'
17
+ import {
18
+ getVersion,
19
+ setVersion,
20
+ getVersionsFromStarter,
21
+ readVersionFile,
22
+ writeVersionFile,
23
+ } from './version.js'
24
+
25
+ export {
26
+ detect,
27
+ getBuildInfo,
28
+ hasEpubOutput,
29
+ hasPdfOutput,
30
+ hasSiteOutput,
31
+ loadProjectConfig,
32
+ paths as default,
33
+ Paths,
34
+ requireBuildOutput,
35
+ getVersion,
36
+ setVersion,
37
+ getVersionsFromStarter,
38
+ readVersionFile,
39
+ writeVersionFile,
40
+ }
@@ -0,0 +1,224 @@
1
+ import { fileURLToPath } from 'node:url'
2
+ import fs from 'fs-extra'
3
+ import path from 'node:path'
4
+
5
+ const __filename = fileURLToPath(import.meta.url)
6
+ const __dirname = path.dirname(__filename)
7
+
8
+ /**
9
+ * Project path configuration
10
+ *
11
+ * Provides accessor methods for project paths, computing values on access
12
+ * to ensure they reflect the current working directory state.
13
+ *
14
+ * Naming convention:
15
+ * - methods ending in `Path` or `Root` return absolute paths
16
+ * - methods ending in `Dir` return relative directory names
17
+ *
18
+ * @see https://www.11ty.dev/docs/config/#configuration-options
19
+ */
20
+ class Paths {
21
+ /**
22
+ * @param {Object} options
23
+ * @param {string} [options.version='latest'] - quire-11ty version to use
24
+ */
25
+ constructor(options = {}) {
26
+ this.cliRoot = path.resolve(__dirname, '..')
27
+ this.eleventyConfig = '.eleventy.js'
28
+ this.version = options.version || 'latest'
29
+ }
30
+
31
+ // ─────────────────────────────────────────────────────────────────────────
32
+ // Implicit getters for domain-specific grouping of paths
33
+ // ─────────────────────────────────────────────────────────────────────────
34
+
35
+ /**
36
+ * Eleventy configuration paths
37
+ */
38
+ get eleventy() {
39
+ return {
40
+ config: this.getConfigPath(),
41
+ data: this.getDataDir(),
42
+ includes: this.getIncludesDir(),
43
+ input: this.getInputDir(),
44
+ layouts: this.getLayoutsDir(),
45
+ output: this.getOutputDir(),
46
+ root: this.getEleventyRoot(),
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Output format-specific paths
52
+ */
53
+ get output() {
54
+ return {
55
+ site: this.getOutputDir(),
56
+ epub: this.getEpubDir(),
57
+ public: this.getPublicDir(),
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Project root paths (general)
63
+ */
64
+ get project() {
65
+ return {
66
+ content: this.getInputPath(),
67
+ root: this.getProjectRoot(),
68
+ }
69
+ }
70
+
71
+ // ─────────────────────────────────────────────────────────────────────────
72
+ // Absolute paths (method names ends with Path or Root)
73
+ // ─────────────────────────────────────────────────────────────────────────
74
+
75
+ /**
76
+ * Get the absolute path to eleventy config module
77
+ * @returns {string} Absolute path to .eleventy.js
78
+ */
79
+ getConfigPath() {
80
+ return path.join(this.getEleventyRoot(), this.eleventyConfig)
81
+ }
82
+
83
+ /**
84
+ * Get the Eleventy root directory
85
+ *
86
+ * @todo refactor how and *when* the eleventyRoot is determined:
87
+ * - we only need eleventyRoot for cli commands that run 11ty
88
+ * - paths module is a concern of the `quire` module
89
+ * - global quire-cli installation
90
+ * - local quire-cli installation
91
+ *
92
+ * @returns {string} Absolute path to Eleventy root
93
+ */
94
+ getEleventyRoot() {
95
+ return this.getProjectRoot()
96
+ }
97
+
98
+ /**
99
+ * Get the absolute path to content input directory
100
+ * @returns {string} Absolute path to content directory
101
+ */
102
+ getInputPath() {
103
+ return path.join(this.getProjectRoot(), 'content')
104
+ }
105
+
106
+ /**
107
+ * Get the path to installed quire-11ty version
108
+ * @returns {string} Absolute path to quire-11ty installation
109
+ */
110
+ getLibQuirePath() {
111
+ return path.resolve(
112
+ __dirname,
113
+ path.join(this.cliRoot, 'lib', 'quire', 'versions', this.version)
114
+ )
115
+ }
116
+
117
+ /**
118
+ * Get the current project root directory
119
+ * @returns {string} Absolute path to project root
120
+ */
121
+ getProjectRoot() {
122
+ return process.cwd()
123
+ }
124
+
125
+ // ─────────────────────────────────────────────────────────────────────────
126
+ // Relative directory names (method names ends with Dir)
127
+ // ─────────────────────────────────────────────────────────────────────────
128
+
129
+ /**
130
+ * Get data directory relative to input
131
+ * @returns {string} Relative directory name for computed data
132
+ */
133
+ getDataDir() {
134
+ return '_computed'
135
+ }
136
+
137
+ /**
138
+ * Get epub output directory relative to eleventy config
139
+ * @returns {string} Relative directory name for epub output (_epub)
140
+ */
141
+ getEpubDir() {
142
+ return path.relative(
143
+ this.getEleventyRoot(),
144
+ path.join(this.getProjectRoot(), '_epub')
145
+ )
146
+ }
147
+
148
+ /**
149
+ * Get includes directory relative to input
150
+ * @returns {string} Relative directory name for _includes
151
+ */
152
+ getIncludesDir() {
153
+ return path.relative(
154
+ this.getInputPath(),
155
+ path.join(this.getEleventyRoot(), '_includes')
156
+ )
157
+ }
158
+
159
+ /**
160
+ * Get input directory relative to eleventy config
161
+ * @returns {string} Relative directory name for 11ty input
162
+ */
163
+ getInputDir() {
164
+ return path.relative(this.getEleventyRoot(), this.getInputPath())
165
+ }
166
+
167
+ /**
168
+ * Get layouts directory relative to input
169
+ * @returns {string} Relative directory name for _layouts
170
+ */
171
+ getLayoutsDir() {
172
+ return path.relative(
173
+ this.getInputPath(),
174
+ path.join(this.getEleventyRoot(), '_layouts')
175
+ )
176
+ }
177
+
178
+ /**
179
+ * Get output directory relative to eleventy config
180
+ * @returns {string} Relative directory name for 11ty output (_site)
181
+ */
182
+ getOutputDir() {
183
+ return path.relative(
184
+ this.getEleventyRoot(),
185
+ path.join(this.getProjectRoot(), '_site')
186
+ )
187
+ }
188
+
189
+ /**
190
+ * Get public assets directory
191
+ * @returns {string} Relative directory name for public assets
192
+ */
193
+ getPublicDir() {
194
+ return './public'
195
+ }
196
+
197
+ // ─────────────────────────────────────────────────────────────────────────
198
+ // Utility methods
199
+ // ─────────────────────────────────────────────────────────────────────────
200
+
201
+ /**
202
+ * Get all paths as an object (for backward compatibility and logging)
203
+ * @returns {Object} Object containing all path values
204
+ */
205
+ toObject() {
206
+ return {
207
+ config: this.getConfigPath(),
208
+ data: this.getDataDir(),
209
+ epub: this.getEpubDir(),
210
+ includes: this.getIncludesDir(),
211
+ input: this.getInputDir(),
212
+ layouts: this.getLayoutsDir(),
213
+ output: this.getOutputDir(),
214
+ public: this.getPublicDir(),
215
+ }
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Default Paths instance
221
+ */
222
+ const paths = new Paths()
223
+
224
+ export { Paths, paths as default }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Project version module
3
+ *
4
+ * Manages quire-11ty version files in Quire projects.
5
+ *
6
+ * @module lib/project/version
7
+ */
8
+ import fs from 'fs-extra'
9
+ import path from 'node:path'
10
+ import config from '#lib/conf/config.js'
11
+ import paths from './paths.js'
12
+
13
+ const PACKAGE_NAME = '@thegetty/quire-11ty'
14
+ const VERSION_FILE = config.get('versionFile')
15
+
16
+ /**
17
+ * Read the required quire-11ty version from the project version file
18
+ *
19
+ * @param {string} [projectPath] - Absolute system path to the project root
20
+ * @returns {string} Quire-11ty semantic version
21
+ */
22
+ export function getVersion(projectPath) {
23
+ projectPath = projectPath || paths.getProjectRoot()
24
+ const version = fs.readFileSync(path.join(projectPath, VERSION_FILE), { encoding: 'utf8' })
25
+ const projectName = path.basename(projectPath)
26
+ console.debug(`${projectName} set to use quire-11ty@${version}`)
27
+ return version
28
+ }
29
+
30
+ /**
31
+ * Sets the quire-11ty version for a project
32
+ *
33
+ * Updates the version file with the new quire-11ty version while
34
+ * preserving other metadata (cli version, starter info).
35
+ *
36
+ * @param {string} version - A version identifier or distribution tag
37
+ * @param {string} [projectPath] - Absolute path to project root (defaults to cwd)
38
+ */
39
+ export function setVersion(version, projectPath) {
40
+ projectPath = projectPath || paths.getProjectRoot()
41
+ const projectName = path.basename(projectPath)
42
+ const versionFilePath = path.join(projectPath, VERSION_FILE)
43
+
44
+ // Read existing version info or create new
45
+ let versionInfo = {}
46
+ if (fs.existsSync(versionFilePath)) {
47
+ const content = fs.readFileSync(versionFilePath, { encoding: 'utf8' })
48
+ try {
49
+ versionInfo = JSON.parse(content)
50
+ } catch {
51
+ // Legacy format or invalid JSON, start fresh
52
+ versionInfo = {}
53
+ }
54
+ }
55
+
56
+ // Update the quire-11ty version
57
+ versionInfo.quire11ty = version
58
+
59
+ // Write updated version info
60
+ fs.writeFileSync(versionFilePath, JSON.stringify(versionInfo, null, 2))
61
+ console.info(`${projectName} set to use quire-11ty@${version}`)
62
+ }
63
+
64
+ /**
65
+ * Read required quire-11ty and starter versions from starter peerDependencies
66
+ *
67
+ * @param {string} projectPath - Absolute system path to the project root
68
+ * @returns {Promise<Object>}
69
+ * @property {string} quire11tyVersion - Latest compatible Quire-11ty semantic version
70
+ * @property {string} starterVersion - Starter project version from package.json
71
+ */
72
+ export async function getVersionsFromStarter(projectPath) {
73
+ const projectPackageConfig = fs.readFileSync(
74
+ path.join(projectPath, 'package.json'),
75
+ { encoding: 'utf8' }
76
+ )
77
+ const { peerDependencies, version: starterVersion } = JSON.parse(projectPackageConfig)
78
+ const quire11tyVersion = peerDependencies[PACKAGE_NAME]
79
+ return { quire11tyVersion, starterVersion }
80
+ }
81
+
82
+ /**
83
+ * Write version info to the project version file
84
+ *
85
+ * @param {string} projectPath - Absolute system path to the project root
86
+ * @param {Object} versionInfo - Version information to write
87
+ */
88
+ export function writeVersionFile(projectPath, versionInfo) {
89
+ fs.writeFileSync(path.join(projectPath, VERSION_FILE), JSON.stringify(versionInfo))
90
+ }
91
+
92
+ /**
93
+ * Read version info from the project version file
94
+ *
95
+ * @param {string} projectPath - Absolute system path to the project root
96
+ * @returns {Object|null} Parsed version info or null if file doesn't exist
97
+ */
98
+ export function readVersionFile(projectPath) {
99
+ const versionFilePath = path.join(projectPath, VERSION_FILE)
100
+ if (!fs.existsSync(versionFilePath)) {
101
+ return null
102
+ }
103
+ const content = fs.readFileSync(versionFilePath, { encoding: 'utf8' })
104
+ try {
105
+ return JSON.parse(content)
106
+ } catch {
107
+ // Legacy format: plain version string
108
+ return { version: content.trim() }
109
+ }
110
+ }