@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,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
- ```