@thegetty/quire-cli 1.0.0-rc.45 → 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 (245) 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 +10 -27
  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/man/quire.1 +0 -341
  56. package/man/quire.1.md +0 -232
  57. package/schemas/config.schema.json +0 -194
  58. package/schemas/figures.schema.json +0 -56
  59. package/schemas/layout.schema.json +0 -5
  60. package/schemas/objects.schema.json +0 -62
  61. package/schemas/publication.schema.json +0 -140
  62. package/schemas/references.schema.json +0 -29
  63. package/src/Command.spec.js +0 -99
  64. package/src/commands/build.spec.js +0 -113
  65. package/src/commands/build.test.js +0 -397
  66. package/src/commands/clean.spec.js +0 -118
  67. package/src/commands/clean.test.js +0 -382
  68. package/src/commands/config.js +0 -238
  69. package/src/commands/config.spec.js +0 -107
  70. package/src/commands/config.test.js +0 -845
  71. package/src/commands/create.spec.js +0 -112
  72. package/src/commands/create.test.js +0 -415
  73. package/src/commands/doctor.js +0 -251
  74. package/src/commands/doctor.spec.js +0 -115
  75. package/src/commands/doctor.test.js +0 -1409
  76. package/src/commands/epub.spec.js +0 -114
  77. package/src/commands/epub.test.js +0 -503
  78. package/src/commands/help.js +0 -60
  79. package/src/commands/help.test.js +0 -132
  80. package/src/commands/info.spec.js +0 -72
  81. package/src/commands/info.test.js +0 -512
  82. package/src/commands/pdf.spec.js +0 -114
  83. package/src/commands/pdf.test.js +0 -464
  84. package/src/commands/preview.spec.js +0 -97
  85. package/src/commands/preview.test.js +0 -250
  86. package/src/commands/use.js +0 -56
  87. package/src/commands/use.spec.js +0 -61
  88. package/src/commands/use.test.js +0 -280
  89. package/src/commands/validate.spec.js +0 -90
  90. package/src/commands/validate.test.js +0 -378
  91. package/src/commands/workflows.js +0 -70
  92. package/src/errors/build/build-failed-error.js +0 -19
  93. package/src/errors/build/config-field-missing-error.js +0 -20
  94. package/src/errors/build/config-file-not-found-error.js +0 -20
  95. package/src/errors/build/index.js +0 -11
  96. package/src/errors/help/help-topic-not-found-error.js +0 -30
  97. package/src/errors/help/index.js +0 -8
  98. package/src/errors/index.js +0 -58
  99. package/src/errors/input/index.js +0 -8
  100. package/src/errors/input/invalid-input-error.js +0 -21
  101. package/src/errors/install/dependency-install-error.js +0 -19
  102. package/src/errors/install/directory-not-empty-error.js +0 -25
  103. package/src/errors/install/index.js +0 -12
  104. package/src/errors/install/invalid-path-error.js +0 -31
  105. package/src/errors/install/invalid-starter-error.js +0 -27
  106. package/src/errors/install/version-not-found-error.js +0 -35
  107. package/src/errors/output/epub-generation-error.js +0 -19
  108. package/src/errors/output/index.js +0 -14
  109. package/src/errors/output/invalid-epub-library-error.js +0 -20
  110. package/src/errors/output/invalid-pdf-library-error.js +0 -20
  111. package/src/errors/output/missing-build-output-error.js +0 -21
  112. package/src/errors/output/pdf-generation-error.js +0 -24
  113. package/src/errors/output/tool-not-found-error.js +0 -37
  114. package/src/errors/project/index.js +0 -10
  115. package/src/errors/project/not-in-project-error.js +0 -20
  116. package/src/errors/project/project-create-error.js +0 -21
  117. package/src/errors/quire-error.js +0 -27
  118. package/src/errors/validation/index.js +0 -3
  119. package/src/errors/validation/unknown-config-key-error.js +0 -27
  120. package/src/errors/validation/unknown-config-operation-error.js +0 -24
  121. package/src/helpers/docs-url.js +0 -32
  122. package/src/helpers/pager.js +0 -59
  123. package/src/helpers/pager.test.js +0 -85
  124. package/src/helpers/suggest-similar.js +0 -124
  125. package/src/helpers/suggest-similar.test.js +0 -108
  126. package/src/helpers/test-cwd.test.js +0 -192
  127. package/src/lib/11ty/index.test.js +0 -655
  128. package/src/lib/commander/index.js +0 -103
  129. package/src/lib/commander/index.test.js +0 -86
  130. package/src/lib/commander/options.js +0 -237
  131. package/src/lib/commander/options.test.js +0 -205
  132. package/src/lib/conf/build-status.js +0 -103
  133. package/src/lib/conf/build-status.test.js +0 -247
  134. package/src/lib/conf/config.test.js +0 -281
  135. package/src/lib/conf/format.js +0 -60
  136. package/src/lib/conf/format.test.js +0 -106
  137. package/src/lib/conf/helpers.js +0 -91
  138. package/src/lib/conf/helpers.test.js +0 -136
  139. package/src/lib/conf/index.js +0 -22
  140. package/src/lib/constants.js +0 -28
  141. package/src/lib/doctor/README.md +0 -667
  142. package/src/lib/doctor/checks/environment/cli-version.js +0 -62
  143. package/src/lib/doctor/checks/environment/cli-version.test.js +0 -132
  144. package/src/lib/doctor/checks/environment/git-available.js +0 -77
  145. package/src/lib/doctor/checks/environment/git-available.test.js +0 -52
  146. package/src/lib/doctor/checks/environment/index.js +0 -13
  147. package/src/lib/doctor/checks/environment/node-version.js +0 -66
  148. package/src/lib/doctor/checks/environment/node-version.test.js +0 -17
  149. package/src/lib/doctor/checks/environment/npm-available.js +0 -66
  150. package/src/lib/doctor/checks/environment/npm-available.test.js +0 -52
  151. package/src/lib/doctor/checks/environment/os-info.js +0 -48
  152. package/src/lib/doctor/checks/environment/os-info.test.js +0 -81
  153. package/src/lib/doctor/checks/environment/runtime-info.js +0 -51
  154. package/src/lib/doctor/checks/environment/runtime-info.test.js +0 -132
  155. package/src/lib/doctor/checks/outputs/epub-output.js +0 -119
  156. package/src/lib/doctor/checks/outputs/epub-output.test.js +0 -277
  157. package/src/lib/doctor/checks/outputs/index.js +0 -10
  158. package/src/lib/doctor/checks/outputs/pdf-output.js +0 -144
  159. package/src/lib/doctor/checks/outputs/pdf-output.test.js +0 -377
  160. package/src/lib/doctor/checks/outputs/stale-build.js +0 -122
  161. package/src/lib/doctor/checks/outputs/stale-build.test.js +0 -282
  162. package/src/lib/doctor/checks/project/data-files.js +0 -56
  163. package/src/lib/doctor/checks/project/data-files.test.js +0 -125
  164. package/src/lib/doctor/checks/project/dependencies.js +0 -53
  165. package/src/lib/doctor/checks/project/dependencies.test.js +0 -71
  166. package/src/lib/doctor/checks/project/index.js +0 -11
  167. package/src/lib/doctor/checks/project/quire-11ty.js +0 -98
  168. package/src/lib/doctor/checks/project/quire-11ty.test.js +0 -170
  169. package/src/lib/doctor/checks/project/quire-project.js +0 -38
  170. package/src/lib/doctor/checks/project/quire-project.test.js +0 -47
  171. package/src/lib/doctor/checks/tools/index.js +0 -10
  172. package/src/lib/doctor/checks/tools/pandoc-available.js +0 -82
  173. package/src/lib/doctor/checks/tools/pandoc-available.test.js +0 -73
  174. package/src/lib/doctor/checks/tools/prince-available.js +0 -81
  175. package/src/lib/doctor/checks/tools/prince-available.test.js +0 -73
  176. package/src/lib/doctor/constants.js +0 -39
  177. package/src/lib/doctor/formatDuration.js +0 -108
  178. package/src/lib/doctor/formatDuration.test.js +0 -76
  179. package/src/lib/doctor/formatters/human.js +0 -257
  180. package/src/lib/doctor/formatters/human.test.js +0 -463
  181. package/src/lib/doctor/formatters/index.js +0 -8
  182. package/src/lib/doctor/formatters/json.js +0 -78
  183. package/src/lib/doctor/formatters/json.test.js +0 -174
  184. package/src/lib/doctor/formatters/shared.js +0 -129
  185. package/src/lib/doctor/formatters/shared.test.js +0 -194
  186. package/src/lib/doctor/index.js +0 -271
  187. package/src/lib/doctor/index.test.js +0 -797
  188. package/src/lib/epub/engines.js +0 -46
  189. package/src/lib/epub/index.test.js +0 -518
  190. package/src/lib/epub/pandoc.test.js +0 -122
  191. package/src/lib/epub/schema.js +0 -21
  192. package/src/lib/error/README.md +0 -170
  193. package/src/lib/error/handler.js +0 -107
  194. package/src/lib/git/index.spec.js +0 -80
  195. package/src/lib/git/index.test.js +0 -453
  196. package/src/lib/help/frontmatter.js +0 -77
  197. package/src/lib/help/frontmatter.test.js +0 -139
  198. package/src/lib/help/index.js +0 -146
  199. package/src/lib/help/index.test.js +0 -188
  200. package/src/lib/help/topics/configuration.md +0 -77
  201. package/src/lib/help/topics/debugging.md +0 -69
  202. package/src/lib/help/topics/epub.md +0 -74
  203. package/src/lib/help/topics/pdf.md +0 -74
  204. package/src/lib/help/topics/publishing.md +0 -80
  205. package/src/lib/help/topics/workflows.md +0 -50
  206. package/src/lib/installer/index.js +0 -309
  207. package/src/lib/installer/index.spec.js +0 -83
  208. package/src/lib/installer/index.test.js +0 -545
  209. package/src/lib/logger/README.md +0 -424
  210. package/src/lib/logger/debug.js +0 -132
  211. package/src/lib/logger/debug.spec.js +0 -130
  212. package/src/lib/logger/debug.test.js +0 -128
  213. package/src/lib/logger/index.js +0 -228
  214. package/src/lib/logger/index.spec.js +0 -131
  215. package/src/lib/logger/index.test.js +0 -578
  216. package/src/lib/npm/README.md +0 -127
  217. package/src/lib/npm/index.js +0 -198
  218. package/src/lib/npm/index.spec.js +0 -60
  219. package/src/lib/npm/index.test.js +0 -355
  220. package/src/lib/pdf/engines.js +0 -46
  221. package/src/lib/pdf/index.test.js +0 -708
  222. package/src/lib/pdf/paged.test.js +0 -366
  223. package/src/lib/pdf/prince.test.js +0 -202
  224. package/src/lib/pdf/schema.js +0 -21
  225. package/src/lib/pdf/split.test.js +0 -445
  226. package/src/lib/platform.js +0 -95
  227. package/src/lib/process/manager.js +0 -110
  228. package/src/lib/process/manager.test.js +0 -55
  229. package/src/lib/project/build.js +0 -162
  230. package/src/lib/project/build.test.js +0 -275
  231. package/src/lib/project/config.js +0 -48
  232. package/src/lib/project/config.test.js +0 -134
  233. package/src/lib/project/detect.js +0 -47
  234. package/src/lib/project/detect.test.js +0 -157
  235. package/src/lib/project/index.js +0 -56
  236. package/src/lib/project/output-paths.js +0 -87
  237. package/src/lib/project/output-paths.test.js +0 -66
  238. package/src/lib/project/paths.js +0 -272
  239. package/src/lib/project/version.js +0 -110
  240. package/src/lib/project/version.test.js +0 -350
  241. package/src/lib/reporter/index.test.js +0 -862
  242. package/src/main.spec.js +0 -61
  243. package/src/main.test.js +0 -347
  244. package/src/validators/validate-data-files.js +0 -154
  245. package/src/validators/validate-data-files.test.js +0 -217
@@ -1,578 +0,0 @@
1
- import esmock from 'esmock'
2
- import sinon from 'sinon'
3
- import test from 'ava'
4
- import createLogger, { logger, LOG_LEVELS, LOG_LEVEL_ENV_VAR } from './index.js'
5
-
6
- /**
7
- * Logger Module Integration Tests
8
- *
9
- * Tests the actual logging behavior including output formatting and level filtering.
10
- *
11
- * Note: loglevel captures console methods at module load time, so we can't stub
12
- * console.log directly. Instead, we test the logger's behavior through its API
13
- * and verify level filtering works correctly.
14
- */
15
-
16
- test.beforeEach((t) => {
17
- t.context.sandbox = sinon.createSandbox()
18
- })
19
-
20
- test.afterEach.always((t) => {
21
- t.context.sandbox.restore()
22
- })
23
-
24
- // ─────────────────────────────────────────────────────────────────────────────
25
- // Log method invocation tests
26
- // ─────────────────────────────────────────────────────────────────────────────
27
-
28
- test('logger methods are callable without error', (t) => {
29
- const log = createLogger('test:callable', 'silent') // Silent to avoid output
30
-
31
- t.notThrows(() => log.trace('trace message'))
32
- t.notThrows(() => log.debug('debug message'))
33
- t.notThrows(() => log.info('info message'))
34
- t.notThrows(() => log.warn('warn message'))
35
- t.notThrows(() => log.error('error message'))
36
- })
37
-
38
- test('logger methods accept multiple arguments', (t) => {
39
- const log = createLogger('test:multiarg', 'silent')
40
-
41
- t.notThrows(() => log.info('message', 'arg1', 'arg2'))
42
- t.notThrows(() => log.info('message', { key: 'value' }))
43
- t.notThrows(() => log.info('message', 123, true, null))
44
- })
45
-
46
- test('logger methods accept no arguments', (t) => {
47
- const log = createLogger('test:noarg', 'silent')
48
-
49
- t.notThrows(() => log.info())
50
- t.notThrows(() => log.error())
51
- })
52
-
53
- // ─────────────────────────────────────────────────────────────────────────────
54
- // Level filtering behavior tests
55
- // ─────────────────────────────────────────────────────────────────────────────
56
-
57
- test('logger at trace level has getLevel() return 0', (t) => {
58
- const log = createLogger('test:trace', 'trace')
59
- t.is(log.getLevel(), LOG_LEVELS.trace)
60
- })
61
-
62
- test('logger at debug level has getLevel() return 1', (t) => {
63
- const log = createLogger('test:debug', 'debug')
64
- t.is(log.getLevel(), LOG_LEVELS.debug)
65
- })
66
-
67
- test('logger at info level has getLevel() return 2', (t) => {
68
- const log = createLogger('test:info', 'info')
69
- t.is(log.getLevel(), LOG_LEVELS.info)
70
- })
71
-
72
- test('logger at warn level has getLevel() return 3', (t) => {
73
- const log = createLogger('test:warn', 'warn')
74
- t.is(log.getLevel(), LOG_LEVELS.warn)
75
- })
76
-
77
- test('logger at error level has getLevel() return 4', (t) => {
78
- const log = createLogger('test:error', 'error')
79
- t.is(log.getLevel(), LOG_LEVELS.error)
80
- })
81
-
82
- test('logger at silent level has getLevel() return 5', (t) => {
83
- const log = createLogger('test:silent', 'silent')
84
- t.is(log.getLevel(), LOG_LEVELS.silent)
85
- })
86
-
87
- // ─────────────────────────────────────────────────────────────────────────────
88
- // Runtime level change tests
89
- // ─────────────────────────────────────────────────────────────────────────────
90
-
91
- test('setLevel changes getLevel() return value', (t) => {
92
- const log = createLogger('test:setlevel', 'info')
93
- t.is(log.getLevel(), LOG_LEVELS.info)
94
-
95
- log.setLevel('debug')
96
- t.is(log.getLevel(), LOG_LEVELS.debug)
97
-
98
- log.setLevel('error')
99
- t.is(log.getLevel(), LOG_LEVELS.error)
100
-
101
- log.setLevel('silent')
102
- t.is(log.getLevel(), LOG_LEVELS.silent)
103
- })
104
-
105
- test('setLevel accepts numeric values', (t) => {
106
- const log = createLogger('test:numeric', 'info')
107
-
108
- log.setLevel(0)
109
- t.is(log.getLevel(), 0)
110
-
111
- log.setLevel(4)
112
- t.is(log.getLevel(), 4)
113
- })
114
-
115
- test('setLevel with invalid string defaults to info', (t) => {
116
- const log = createLogger('test:invalid', 'debug')
117
- t.is(log.getLevel(), LOG_LEVELS.debug)
118
-
119
- log.setLevel('not-a-level')
120
- t.is(log.getLevel(), LOG_LEVELS.info)
121
- })
122
-
123
- // ─────────────────────────────────────────────────────────────────────────────
124
- // Independent instance tests
125
- // ─────────────────────────────────────────────────────────────────────────────
126
-
127
- test('multiple loggers maintain independent levels', (t) => {
128
- const log1 = createLogger('independent:one', 'debug')
129
- const log2 = createLogger('independent:two', 'error')
130
-
131
- t.is(log1.getLevel(), LOG_LEVELS.debug)
132
- t.is(log2.getLevel(), LOG_LEVELS.error)
133
-
134
- log1.setLevel('warn')
135
-
136
- t.is(log1.getLevel(), LOG_LEVELS.warn)
137
- t.is(log2.getLevel(), LOG_LEVELS.error) // Unchanged
138
- })
139
-
140
- // ─────────────────────────────────────────────────────────────────────────────
141
- // Singleton behavior tests
142
- // ─────────────────────────────────────────────────────────────────────────────
143
-
144
- test('default singleton is functional', (t) => {
145
- t.notThrows(() => logger.info('singleton test'))
146
- t.is(typeof logger.getLevel(), 'number')
147
- })
148
-
149
- // ─────────────────────────────────────────────────────────────────────────────
150
- // Console output tests with level labels (logShowLevel: true)
151
- // ─────────────────────────────────────────────────────────────────────────────
152
-
153
- test.serial('error level outputs to console.error with level label when configured', async (t) => {
154
- const { sandbox } = t.context
155
- const consoleErrorStub = sandbox.stub(console, 'error')
156
-
157
- // Mock config to show level labels
158
- const mockConfig = {
159
- get: sandbox.stub().callsFake((key) => ({
160
- logPrefix: 'quire',
161
- logPrefixStyle: 'bracket',
162
- logShowLevel: true,
163
- logUseColor: false
164
- })[key])
165
- }
166
-
167
- const { default: createLoggerMocked } = await esmock('./index.js', {
168
- '#lib/conf/config.js': { default: mockConfig }
169
- })
170
-
171
- const log = createLoggerMocked('test:error:output', 'error')
172
- log.error('Error message')
173
-
174
- t.true(consoleErrorStub.calledOnce)
175
- const output = consoleErrorStub.firstCall.args.join(' ')
176
- t.true(output.includes('[quire]'))
177
- t.true(output.includes('ERROR'))
178
- t.true(output.includes('Error message'))
179
- })
180
-
181
- test.serial('warn level outputs to console.warn with level label when configured', async (t) => {
182
- const { sandbox } = t.context
183
- const consoleWarnStub = sandbox.stub(console, 'warn')
184
-
185
- // Mock config to show level labels
186
- const mockConfig = {
187
- get: sandbox.stub().callsFake((key) => ({
188
- logPrefix: 'quire',
189
- logPrefixStyle: 'bracket',
190
- logShowLevel: true,
191
- logUseColor: false
192
- })[key])
193
- }
194
-
195
- const { default: createLoggerMocked } = await esmock('./index.js', {
196
- '#lib/conf/config.js': { default: mockConfig }
197
- })
198
-
199
- const log = createLoggerMocked('test:warn:output', 'warn')
200
- log.warn('Warning message')
201
-
202
- t.true(consoleWarnStub.calledOnce)
203
- const output = consoleWarnStub.firstCall.args.join(' ')
204
- t.true(output.includes('[quire]'))
205
- t.true(output.includes('WARN'))
206
- t.true(output.includes('Warning message'))
207
- })
208
-
209
- test.serial('trace level outputs to console.trace with level label when configured', async (t) => {
210
- const { sandbox } = t.context
211
- const consoleTraceStub = sandbox.stub(console, 'trace')
212
-
213
- // Mock config to show level labels
214
- const mockConfig = {
215
- get: sandbox.stub().callsFake((key) => ({
216
- logPrefix: 'quire',
217
- logPrefixStyle: 'bracket',
218
- logShowLevel: true,
219
- logUseColor: false
220
- })[key])
221
- }
222
-
223
- const { default: createLoggerMocked } = await esmock('./index.js', {
224
- '#lib/conf/config.js': { default: mockConfig }
225
- })
226
-
227
- const log = createLoggerMocked('test:trace:output', 'trace')
228
- log.trace('Trace message')
229
-
230
- t.true(consoleTraceStub.calledOnce)
231
- const output = consoleTraceStub.firstCall.args.join(' ')
232
- t.true(output.includes('[quire]'))
233
- t.true(output.includes('TRACE'))
234
- t.true(output.includes('Trace message'))
235
- })
236
-
237
- // ─────────────────────────────────────────────────────────────────────────────
238
- // Prefix configuration tests
239
- // ─────────────────────────────────────────────────────────────────────────────
240
-
241
- test('different prefixes create distinct loggers', (t) => {
242
- const log1 = createLogger('prefix:alpha', 'debug')
243
- const log2 = createLogger('prefix:beta', 'warn')
244
-
245
- // Different levels confirm different instances
246
- t.not(log1.getLevel(), log2.getLevel())
247
- })
248
-
249
- test('same prefix returns same underlying loglevel instance', (t) => {
250
- const log1 = createLogger('same:prefix', 'debug')
251
- const log2 = createLogger('same:prefix', 'warn')
252
-
253
- // Setting level on one affects the other (same loglevel instance)
254
- log1.setLevel('error')
255
- t.is(log1.getLevel(), LOG_LEVELS.error)
256
- t.is(log2.getLevel(), LOG_LEVELS.error)
257
- })
258
-
259
- // ─────────────────────────────────────────────────────────────────────────────
260
- // Edge case tests
261
- // ─────────────────────────────────────────────────────────────────────────────
262
-
263
- test('empty prefix throws error (loglevel requirement)', (t) => {
264
- t.throws(() => createLogger('', 'info'), {
265
- instanceOf: TypeError,
266
- message: /supply a name/
267
- })
268
- })
269
-
270
- test('special characters in prefix are allowed', (t) => {
271
- const log = createLogger('lib:pdf/paged.js', 'info')
272
- t.notThrows(() => log.info('message'))
273
- })
274
-
275
- test('undefined level defaults to info', (t) => {
276
- const log = createLogger('test:undefined')
277
- t.is(log.getLevel(), LOG_LEVELS.info)
278
- })
279
-
280
- // ─────────────────────────────────────────────────────────────────────────────
281
- // Environment variable tests
282
- // ─────────────────────────────────────────────────────────────────────────────
283
-
284
- test('LOG_LEVEL_ENV_VAR constant is exported', (t) => {
285
- t.is(LOG_LEVEL_ENV_VAR, 'QUIRE_LOG_LEVEL')
286
- })
287
-
288
- test.serial('logger reads level from QUIRE_LOG_LEVEL env var', async (t) => {
289
- const originalEnv = process.env[LOG_LEVEL_ENV_VAR]
290
-
291
- try {
292
- // Set env var to debug
293
- process.env[LOG_LEVEL_ENV_VAR] = 'debug'
294
-
295
- // Re-import to get fresh module that reads env var
296
- // Note: We need to use a unique prefix so loglevel creates a new instance
297
- const { default: freshCreateLogger } = await import('./index.js?env-debug')
298
- const log = freshCreateLogger('env:debug:test')
299
-
300
- t.is(log.getLevel(), LOG_LEVELS.debug)
301
- } finally {
302
- // Restore original env
303
- if (originalEnv === undefined) {
304
- delete process.env[LOG_LEVEL_ENV_VAR]
305
- } else {
306
- process.env[LOG_LEVEL_ENV_VAR] = originalEnv
307
- }
308
- }
309
- })
310
-
311
- test.serial('logger reads error level from QUIRE_LOG_LEVEL env var', async (t) => {
312
- const originalEnv = process.env[LOG_LEVEL_ENV_VAR]
313
-
314
- try {
315
- process.env[LOG_LEVEL_ENV_VAR] = 'error'
316
-
317
- const { default: freshCreateLogger } = await import('./index.js?env-error')
318
- const log = freshCreateLogger('env:error:test')
319
-
320
- t.is(log.getLevel(), LOG_LEVELS.error)
321
- } finally {
322
- if (originalEnv === undefined) {
323
- delete process.env[LOG_LEVEL_ENV_VAR]
324
- } else {
325
- process.env[LOG_LEVEL_ENV_VAR] = originalEnv
326
- }
327
- }
328
- })
329
-
330
- test.serial('explicit level parameter overrides env var', async (t) => {
331
- const originalEnv = process.env[LOG_LEVEL_ENV_VAR]
332
-
333
- try {
334
- process.env[LOG_LEVEL_ENV_VAR] = 'error'
335
-
336
- const { default: freshCreateLogger } = await import('./index.js?env-override')
337
- // Explicitly pass 'debug' which should override env var 'error'
338
- const log = freshCreateLogger('env:override:test', 'debug')
339
-
340
- t.is(log.getLevel(), LOG_LEVELS.debug)
341
- } finally {
342
- if (originalEnv === undefined) {
343
- delete process.env[LOG_LEVEL_ENV_VAR]
344
- } else {
345
- process.env[LOG_LEVEL_ENV_VAR] = originalEnv
346
- }
347
- }
348
- })
349
-
350
- // ─────────────────────────────────────────────────────────────────────────────
351
- // Printf-style string substitution tests
352
- // ─────────────────────────────────────────────────────────────────────────────
353
-
354
- test.serial('logger supports %s string substitution', async (t) => {
355
- const { sandbox } = t.context
356
- const consoleInfoStub = sandbox.stub(console, 'info')
357
-
358
- const mockConfig = {
359
- get: sandbox.stub().callsFake((key) => ({
360
- logPrefix: 'quire',
361
- logPrefixStyle: 'none',
362
- logShowLevel: false,
363
- logUseColor: false
364
- })[key])
365
- }
366
-
367
- const { default: createLoggerMocked } = await esmock('./index.js', {
368
- '#lib/conf/config.js': { default: mockConfig }
369
- })
370
-
371
- const log = createLoggerMocked('test:printf:string', 'info')
372
- log.info('Hello %s', 'world')
373
-
374
- t.true(consoleInfoStub.calledOnce)
375
- const output = consoleInfoStub.firstCall.args.join(' ')
376
- t.true(output.includes('Hello world'))
377
- t.false(output.includes('%s'))
378
- })
379
-
380
- test.serial('logger supports %d number substitution', async (t) => {
381
- const { sandbox } = t.context
382
- const consoleInfoStub = sandbox.stub(console, 'info')
383
-
384
- const mockConfig = {
385
- get: sandbox.stub().callsFake((key) => ({
386
- logPrefix: 'quire',
387
- logPrefixStyle: 'none',
388
- logShowLevel: false,
389
- logUseColor: false
390
- })[key])
391
- }
392
-
393
- const { default: createLoggerMocked } = await esmock('./index.js', {
394
- '#lib/conf/config.js': { default: mockConfig }
395
- })
396
-
397
- const log = createLoggerMocked('test:printf:number', 'info')
398
- log.info('Count: %d', 42)
399
-
400
- t.true(consoleInfoStub.calledOnce)
401
- const output = consoleInfoStub.firstCall.args.join(' ')
402
- t.true(output.includes('Count: 42'))
403
- t.false(output.includes('%d'))
404
- })
405
-
406
- test.serial('logger supports %O object substitution', async (t) => {
407
- const { sandbox } = t.context
408
- const consoleInfoStub = sandbox.stub(console, 'info')
409
-
410
- const mockConfig = {
411
- get: sandbox.stub().callsFake((key) => ({
412
- logPrefix: 'quire',
413
- logPrefixStyle: 'none',
414
- logShowLevel: false,
415
- logUseColor: false
416
- })[key])
417
- }
418
-
419
- const { default: createLoggerMocked } = await esmock('./index.js', {
420
- '#lib/conf/config.js': { default: mockConfig }
421
- })
422
-
423
- const log = createLoggerMocked('test:printf:object', 'info')
424
- log.info('Data: %O', { key: 'value' })
425
-
426
- t.true(consoleInfoStub.calledOnce)
427
- const output = consoleInfoStub.firstCall.args.join(' ')
428
- t.true(output.includes('key'))
429
- t.true(output.includes('value'))
430
- t.false(output.includes('%O'))
431
- })
432
-
433
- test.serial('logger supports multiple substitutions', async (t) => {
434
- const { sandbox } = t.context
435
- const consoleInfoStub = sandbox.stub(console, 'info')
436
-
437
- const mockConfig = {
438
- get: sandbox.stub().callsFake((key) => ({
439
- logPrefix: 'quire',
440
- logPrefixStyle: 'none',
441
- logShowLevel: false,
442
- logUseColor: false
443
- })[key])
444
- }
445
-
446
- const { default: createLoggerMocked } = await esmock('./index.js', {
447
- '#lib/conf/config.js': { default: mockConfig }
448
- })
449
-
450
- const log = createLoggerMocked('test:printf:multi', 'info')
451
- log.info('%s is %d years old', 'Alice', 30)
452
-
453
- t.true(consoleInfoStub.calledOnce)
454
- const output = consoleInfoStub.firstCall.args.join(' ')
455
- t.true(output.includes('Alice is 30 years old'))
456
- t.false(output.includes('%s'))
457
- t.false(output.includes('%d'))
458
- })
459
-
460
- test.serial('invalid env var value falls back to info', async (t) => {
461
- const originalEnv = process.env[LOG_LEVEL_ENV_VAR]
462
-
463
- try {
464
- process.env[LOG_LEVEL_ENV_VAR] = 'not-a-valid-level'
465
-
466
- const { default: freshCreateLogger } = await import('./index.js?env-invalid')
467
- const log = freshCreateLogger('env:invalid:test')
468
-
469
- t.is(log.getLevel(), LOG_LEVELS.info)
470
- } finally {
471
- if (originalEnv === undefined) {
472
- delete process.env[LOG_LEVEL_ENV_VAR]
473
- } else {
474
- process.env[LOG_LEVEL_ENV_VAR] = originalEnv
475
- }
476
- }
477
- })
478
-
479
- // ─────────────────────────────────────────────────────────────────────────────
480
- // NO_COLOR environment variable tests
481
- // ─────────────────────────────────────────────────────────────────────────────
482
-
483
- test.serial('logger disables color when NO_COLOR env var is set', async (t) => {
484
- const { sandbox } = t.context
485
- const consoleErrorStub = sandbox.stub(console, 'error')
486
- const originalNoColor = process.env.NO_COLOR
487
-
488
- try {
489
- process.env.NO_COLOR = '1'
490
-
491
- const mockConfig = {
492
- get: sandbox.stub().callsFake((key) => ({
493
- logPrefix: 'quire',
494
- logPrefixStyle: 'bracket',
495
- logShowLevel: true,
496
- logUseColor: true, // Config says use color, but NO_COLOR overrides
497
- logColorMessages: true,
498
- })[key])
499
- }
500
-
501
- const { default: createLoggerMocked } = await esmock('./index.js', {
502
- '#lib/conf/config.js': { default: mockConfig }
503
- })
504
-
505
- const log = createLoggerMocked('test:nocolor', 'error')
506
- log.error('Error message')
507
-
508
- t.true(consoleErrorStub.calledOnce)
509
- const output = consoleErrorStub.firstCall.args.join(' ')
510
- // Should not contain ANSI escape sequences when NO_COLOR is set
511
- t.false(/\u001b\[/.test(output), 'output should not contain ANSI escape codes')
512
- // Should still contain the actual text
513
- t.true(output.includes('[quire]'))
514
- t.true(output.includes('ERROR'))
515
- t.true(output.includes('Error message'))
516
- } finally {
517
- if (originalNoColor === undefined) {
518
- delete process.env.NO_COLOR
519
- } else {
520
- process.env.NO_COLOR = originalNoColor
521
- }
522
- }
523
- })
524
-
525
- test.serial('logger uses color when NO_COLOR env var is not set and config enables color', async (t) => {
526
- const { sandbox } = t.context
527
- const consoleErrorStub = sandbox.stub(console, 'error')
528
- const originalNoColor = process.env.NO_COLOR
529
-
530
- try {
531
- delete process.env.NO_COLOR
532
-
533
- const mockConfig = {
534
- get: sandbox.stub().callsFake((key) => ({
535
- logPrefix: 'quire',
536
- logPrefixStyle: 'bracket',
537
- logShowLevel: true,
538
- logUseColor: true,
539
- logColorMessages: true,
540
- })[key])
541
- }
542
-
543
- // Create a mock chalk that always applies ANSI styling regardless of TTY/env
544
- const ansiRed = (s) => `\u001b[91m${s}\u001b[39m`
545
- const ansiRedInverse = (s) => `\u001b[91m\u001b[7m${s}\u001b[27m\u001b[39m`
546
- const ansiBold = (s) => `\u001b[1m${s}\u001b[22m`
547
- const mockChalk = {
548
- bold: ansiBold,
549
- gray: (s) => s,
550
- yellow: (s) => s,
551
- magenta: (s) => s,
552
- red: ansiRed,
553
- redBright: { inverse: ansiRedInverse },
554
- 'yellow.inverse': (s) => s,
555
- }
556
- // Support chained properties
557
- mockChalk.yellow.inverse = (s) => s
558
-
559
- const { default: createLoggerMocked } = await esmock('./index.js', {
560
- '#lib/conf/config.js': { default: mockConfig },
561
- 'chalk': { default: mockChalk }
562
- })
563
-
564
- const log = createLoggerMocked('test:withcolor', 'error')
565
- log.error('Error message')
566
-
567
- t.true(consoleErrorStub.calledOnce)
568
- const output = consoleErrorStub.firstCall.args.join(' ')
569
- // Should contain ANSI escape sequences when color is enabled and NO_COLOR not set
570
- t.true(/\u001b\[/.test(output), 'output should contain ANSI escape codes')
571
- } finally {
572
- if (originalNoColor === undefined) {
573
- delete process.env.NO_COLOR
574
- } else {
575
- process.env.NO_COLOR = originalNoColor
576
- }
577
- }
578
- })
@@ -1,127 +0,0 @@
1
- # NPM Façade
2
-
3
- A façade module that abstracts npm command-line operations for the Quire CLI.
4
-
5
- ## Purpose
6
-
7
- This module provides a unified interface for npm operations with:
8
-
9
- - Encapsulation of npm implementation details
10
- - Consistent logging prefixed with `[CLI:lib/npm]`
11
- - Unified error handling
12
- - Easy mockability for testing
13
-
14
- ## Usage
15
-
16
- ```javascript
17
- import npm from '#lib/npm/index.js'
18
-
19
- // Check npm availability
20
- if (!npm.isAvailable()) {
21
- console.error('npm is not installed')
22
- }
23
-
24
- // Get npm version
25
- // @see https://docs.npmjs.com/cli/commands/npm-version
26
- const version = await npm.version()
27
-
28
- // Initialize package.json
29
- // @see https://docs.npmjs.com/cli/commands/npm-init
30
- await npm.init('/path/to/project', { yes: true })
31
-
32
- // Install dependencies
33
- // @see https://docs.npmjs.com/cli/commands/npm-install
34
- await npm.install('/path/to/project', {
35
- saveDev: true,
36
- preferOffline: true
37
- })
38
-
39
- // Pack a package
40
- // @see https://docs.npmjs.com/cli/commands/npm-pack
41
- await npm.pack('@thegetty/quire-11ty@1.0.0', '/path/to/destination', {
42
- quiet: true
43
- })
44
-
45
- // Clean cache
46
- // @see https://docs.npmjs.com/cli/commands/npm-cache
47
- await npm.cacheClean()
48
-
49
- // View package version from registry
50
- // @see https://docs.npmjs.com/cli/commands/npm-view
51
- const latestVersion = await npm.view('@thegetty/quire-11ty', 'version')
52
-
53
- // Get compatible version for a range
54
- // @see https://docs.npmjs.com/about-semantic-versioning
55
- const compatible = await npm.getCompatibleVersion('@thegetty/quire-11ty', '^1.0.0')
56
- ```
57
-
58
- ## API
59
-
60
- ### `isAvailable()`
61
-
62
- Check if npm is available in PATH.
63
-
64
- ### `version()`
65
-
66
- Get the installed npm version.
67
-
68
- ### `init(cwd, options)`
69
-
70
- Initialize a new package.json in the specified directory.
71
-
72
- **Options:**
73
- - `yes` (boolean, default: `true`) - Auto-accept defaults
74
-
75
- ### `install(cwd, options)`
76
-
77
- Install dependencies in the specified directory.
78
-
79
- **Options:**
80
- - `preferOffline` (boolean, default: `false`) - Prefer cached packages
81
- - `saveDev` (boolean, default: `false`) - Install as devDependencies
82
-
83
- ### `pack(packageSpec, destination, options)`
84
-
85
- Download and pack a package to a tarball.
86
-
87
- **Options:**
88
- - `debug` (boolean, default: `false`) - Enable debug output
89
- - `quiet` (boolean, default: `true`) - Suppress output
90
-
91
- ### `cacheClean(cwd)`
92
-
93
- Force clean the npm cache.
94
-
95
- ### `view(packageName, field)`
96
-
97
- Query package information from the npm registry using `npm view`.
98
-
99
- ### `show(packageName, field)`
100
-
101
- Query package information from the npm registry using `npm show`.
102
-
103
- ### `fetchFromRegistry(packageName)`
104
-
105
- Fetch package metadata directly from the npm registry API.
106
-
107
- ### `getCompatibleVersion(packageName, range)`
108
-
109
- Find the latest version compatible with a semver range.
110
-
111
- ## Testing
112
-
113
- The module exports a singleton instance which can be mocked in tests:
114
-
115
- ```javascript
116
- import esmock from 'esmock'
117
-
118
- const mockNpm = {
119
- isAvailable: sandbox.stub().returns(true),
120
- install: sandbox.stub().resolves(),
121
- version: sandbox.stub().resolves('10.2.4')
122
- }
123
-
124
- const MyCommand = await esmock('./mycommand.js', {
125
- '#lib/npm/index.js': { default: mockNpm }
126
- })
127
- ```