@thegetty/quire-cli 1.0.0-rc.35 → 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.
- package/CHANGELOG.md +36 -0
- package/README.md +9 -0
- package/bin/cli.js +19 -1
- package/package.json +21 -8
- package/patches/README.md +19 -0
- package/patches/install-npm-version+1.0.9.patch +12119 -0
- package/schemas/objects.schema.json +12 -38
- package/schemas/publication.schema.json +0 -22
- package/schemas/references.schema.json +0 -1
- package/src/Command.js +26 -6
- package/src/Command.spec.js +99 -0
- package/src/commands/README.md +213 -122
- package/src/commands/build.js +46 -37
- package/src/commands/build.spec.js +113 -0
- package/src/commands/build.test.js +402 -0
- package/src/commands/clean.js +25 -19
- package/src/commands/clean.spec.js +108 -0
- package/src/commands/clean.test.js +260 -0
- package/src/commands/config.js +251 -0
- package/src/commands/config.spec.js +107 -0
- package/src/commands/config.test.js +715 -0
- package/src/commands/create.js +42 -14
- package/src/commands/create.spec.js +112 -0
- package/src/commands/create.test.js +415 -0
- package/src/commands/epub.js +59 -30
- package/src/commands/epub.spec.js +114 -0
- package/src/commands/epub.test.js +503 -0
- package/src/commands/index.js +9 -2
- package/src/commands/info.js +18 -24
- package/src/commands/info.spec.js +64 -0
- package/src/commands/info.test.js +415 -0
- package/src/commands/pdf.js +58 -84
- package/src/commands/pdf.spec.js +114 -0
- package/src/commands/pdf.test.js +464 -0
- package/src/commands/preview.js +26 -31
- package/src/commands/preview.spec.js +97 -0
- package/src/commands/preview.test.js +250 -0
- package/src/commands/use.js +56 -0
- package/src/commands/use.spec.js +61 -0
- package/src/commands/use.test.js +280 -0
- package/src/commands/validate.js +31 -19
- package/src/commands/validate.spec.js +82 -0
- package/src/commands/validate.test.js +234 -0
- package/src/commands/workflows.js +70 -0
- package/src/errors/build/build-failed-error.js +19 -0
- package/src/errors/build/config-field-missing-error.js +20 -0
- package/src/errors/build/config-file-not-found-error.js +20 -0
- package/src/errors/build/index.js +11 -0
- package/src/errors/index.js +48 -0
- package/src/errors/install/dependency-install-error.js +19 -0
- package/src/errors/install/directory-not-empty-error.js +25 -0
- package/src/errors/install/index.js +12 -0
- package/src/errors/install/invalid-path-error.js +31 -0
- package/src/errors/install/invalid-starter-error.js +27 -0
- package/src/errors/install/version-not-found-error.js +35 -0
- package/src/errors/output/epub-generation-error.js +19 -0
- package/src/errors/output/index.js +14 -0
- package/src/errors/output/invalid-epub-library-error.js +20 -0
- package/src/errors/output/invalid-pdf-library-error.js +20 -0
- package/src/errors/output/missing-build-output-error.js +21 -0
- package/src/errors/output/pdf-generation-error.js +24 -0
- package/src/errors/output/tool-not-found-error.js +37 -0
- package/src/errors/project/index.js +10 -0
- package/src/errors/project/not-in-project-error.js +20 -0
- package/src/errors/project/project-create-error.js +21 -0
- package/src/errors/quire-error.js +27 -0
- package/src/errors/validation/validation-error.js +20 -11
- package/src/helpers/clean.js +1 -1
- package/src/helpers/docs-url.js +32 -0
- package/src/helpers/test-cwd.js +5 -6
- package/src/helpers/test-cwd.test.js +192 -0
- package/src/helpers/which.js +10 -4
- package/src/lib/11ty/README.md +135 -19
- package/src/lib/11ty/api.js +176 -93
- package/src/lib/11ty/cli.js +77 -35
- package/src/lib/11ty/index.js +64 -5
- package/src/lib/11ty/index.test.js +655 -0
- package/src/lib/README.md +275 -0
- package/src/lib/commander/index.js +100 -0
- package/src/lib/commander/index.test.js +86 -0
- package/src/lib/commander/options.js +195 -0
- package/src/lib/commander/options.test.js +109 -0
- package/src/lib/conf/README.md +84 -73
- package/src/lib/conf/config.js +5 -3
- package/src/lib/conf/config.test.js +281 -0
- package/src/lib/conf/defaults.js +44 -0
- package/src/lib/conf/format.js +60 -0
- package/src/lib/conf/format.test.js +106 -0
- package/src/lib/conf/helpers.js +91 -0
- package/src/lib/conf/helpers.test.js +136 -0
- package/src/lib/conf/index.js +22 -0
- package/src/lib/conf/schema.js +53 -8
- package/src/lib/epub/README.md +133 -2
- package/src/lib/epub/engines.js +46 -0
- package/src/lib/epub/epub.js +36 -11
- package/src/lib/epub/index.js +111 -21
- package/src/lib/epub/index.test.js +518 -0
- package/src/lib/epub/pandoc.js +33 -4
- package/src/lib/epub/pandoc.test.js +122 -0
- package/src/lib/epub/schema.js +21 -0
- package/src/lib/error/README.md +170 -0
- package/src/lib/error/handler.js +107 -0
- package/src/lib/git/README.md +151 -2
- package/src/lib/git/index.js +217 -13
- package/src/lib/git/index.spec.js +80 -0
- package/src/lib/git/index.test.js +453 -0
- package/src/lib/installer/index.js +309 -0
- package/src/lib/installer/index.spec.js +83 -0
- package/src/lib/installer/index.test.js +545 -0
- package/src/lib/logger/README.md +424 -0
- package/src/lib/logger/debug.js +90 -0
- package/src/lib/logger/debug.spec.js +130 -0
- package/src/lib/logger/index.js +228 -0
- package/src/lib/logger/index.spec.js +131 -0
- package/src/lib/logger/index.test.js +477 -0
- package/src/lib/npm/README.md +127 -0
- package/src/lib/npm/index.js +198 -0
- package/src/lib/npm/index.spec.js +60 -0
- package/src/lib/npm/index.test.js +355 -0
- package/src/lib/pdf/README.md +131 -0
- package/src/lib/pdf/engines.js +46 -0
- package/src/lib/pdf/index.js +124 -21
- package/src/lib/pdf/index.test.js +708 -0
- package/src/lib/pdf/paged.js +100 -52
- package/src/lib/pdf/paged.test.js +366 -0
- package/src/lib/pdf/prince.js +115 -37
- package/src/lib/pdf/prince.test.js +202 -0
- package/src/lib/pdf/schema.js +21 -0
- package/src/lib/pdf/split.js +61 -33
- package/src/lib/pdf/split.test.js +445 -0
- package/src/lib/process/manager.js +110 -0
- package/src/lib/process/manager.test.js +55 -0
- package/src/lib/project/build.js +143 -0
- package/src/lib/project/build.test.js +253 -0
- package/src/lib/project/config.js +48 -0
- package/src/lib/project/config.test.js +134 -0
- package/src/{helpers/is-quire.js → lib/project/detect.js} +5 -3
- package/src/lib/project/detect.test.js +157 -0
- package/src/lib/project/index.js +40 -0
- package/src/lib/project/paths.js +224 -0
- package/src/lib/project/version.js +110 -0
- package/src/lib/project/version.test.js +350 -0
- package/src/lib/reporter/README.md +211 -2
- package/src/lib/reporter/index.js +512 -0
- package/src/lib/reporter/index.test.js +593 -0
- package/src/main.js +134 -47
- package/src/main.spec.js +61 -0
- package/src/main.test.js +347 -0
- package/src/validators/utils.js +2 -1
- package/src/commands/conf.js +0 -43
- package/src/commands/version.js +0 -43
- package/src/lib/11ty/paths.js +0 -103
- package/src/lib/i18n/README.md +0 -3
- package/src/lib/i18n/config.js +0 -53
- package/src/lib/i18n/index.js +0 -43
- package/src/lib/i18n/localeService.js +0 -58
- package/src/lib/quire/README.md +0 -19
- package/src/lib/quire/index.js +0 -350
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import test from 'ava'
|
|
2
|
+
import sinon from 'sinon'
|
|
3
|
+
import esmock from 'esmock'
|
|
4
|
+
|
|
5
|
+
test.beforeEach((t) => {
|
|
6
|
+
t.context.sandbox = sinon.createSandbox()
|
|
7
|
+
|
|
8
|
+
// Create mock reporter
|
|
9
|
+
t.context.mockReporter = {
|
|
10
|
+
configure: t.context.sandbox.stub().returnsThis(),
|
|
11
|
+
start: t.context.sandbox.stub().returnsThis(),
|
|
12
|
+
update: t.context.sandbox.stub().returnsThis(),
|
|
13
|
+
succeed: t.context.sandbox.stub().returnsThis(),
|
|
14
|
+
fail: t.context.sandbox.stub().returnsThis(),
|
|
15
|
+
stop: t.context.sandbox.stub().returnsThis(),
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test.afterEach.always((t) => {
|
|
20
|
+
t.context.sandbox.restore()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('epub command should call generateEpub with epubjs engine', async (t) => {
|
|
24
|
+
const { sandbox, mockReporter } = t.context
|
|
25
|
+
|
|
26
|
+
const mockGenerateEpub = sandbox.stub().resolves('/project/epubjs.epub')
|
|
27
|
+
|
|
28
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
29
|
+
'#lib/epub/index.js': {
|
|
30
|
+
default: mockGenerateEpub
|
|
31
|
+
},
|
|
32
|
+
'#lib/project/index.js': {
|
|
33
|
+
hasEpubOutput: () => true
|
|
34
|
+
},
|
|
35
|
+
'#lib/reporter/index.js': {
|
|
36
|
+
default: mockReporter
|
|
37
|
+
},
|
|
38
|
+
'fs-extra': {
|
|
39
|
+
existsSync: () => true
|
|
40
|
+
},
|
|
41
|
+
open: {
|
|
42
|
+
default: sandbox.stub()
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const command = new EPUBCommand()
|
|
47
|
+
command.name = sandbox.stub().returns('epub')
|
|
48
|
+
|
|
49
|
+
await command.action({ engine: 'epubjs' }, command)
|
|
50
|
+
|
|
51
|
+
t.true(mockGenerateEpub.called, 'generateEpub should be called')
|
|
52
|
+
t.is(mockGenerateEpub.firstCall.args[0].lib, 'epubjs', 'should pass lib to generateEpub')
|
|
53
|
+
// Nota bene: reporter.start/succeed are called by façade (lib/epub/index.js), not the command
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('epub command should call generateEpub with pandoc engine', async (t) => {
|
|
57
|
+
const { sandbox, mockReporter } = t.context
|
|
58
|
+
|
|
59
|
+
const mockGenerateEpub = sandbox.stub().resolves('/project/pandoc.epub')
|
|
60
|
+
|
|
61
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
62
|
+
'#lib/epub/index.js': {
|
|
63
|
+
default: mockGenerateEpub
|
|
64
|
+
},
|
|
65
|
+
'#lib/project/index.js': {
|
|
66
|
+
hasEpubOutput: () => true
|
|
67
|
+
},
|
|
68
|
+
'#lib/reporter/index.js': {
|
|
69
|
+
default: mockReporter
|
|
70
|
+
},
|
|
71
|
+
'fs-extra': {
|
|
72
|
+
existsSync: () => true
|
|
73
|
+
},
|
|
74
|
+
open: {
|
|
75
|
+
default: sandbox.stub()
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const command = new EPUBCommand()
|
|
80
|
+
command.name = sandbox.stub().returns('epub')
|
|
81
|
+
|
|
82
|
+
await command.action({ engine: 'pandoc' }, command)
|
|
83
|
+
|
|
84
|
+
t.true(mockGenerateEpub.called, 'generateEpub should be called')
|
|
85
|
+
t.is(mockGenerateEpub.firstCall.args[0].lib, 'pandoc', 'should pass lib to generateEpub')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('epub command should open EPUB when --open flag is provided', async (t) => {
|
|
89
|
+
const { sandbox, mockReporter } = t.context
|
|
90
|
+
|
|
91
|
+
const outputPath = '/project/epubjs.epub'
|
|
92
|
+
const mockGenerateEpub = sandbox.stub().resolves(outputPath)
|
|
93
|
+
const mockOpen = sandbox.stub()
|
|
94
|
+
|
|
95
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
96
|
+
'#lib/epub/index.js': {
|
|
97
|
+
default: mockGenerateEpub
|
|
98
|
+
},
|
|
99
|
+
'#lib/project/index.js': {
|
|
100
|
+
hasEpubOutput: () => true
|
|
101
|
+
},
|
|
102
|
+
'#lib/reporter/index.js': {
|
|
103
|
+
default: mockReporter
|
|
104
|
+
},
|
|
105
|
+
'fs-extra': {
|
|
106
|
+
existsSync: () => true
|
|
107
|
+
},
|
|
108
|
+
open: {
|
|
109
|
+
default: mockOpen
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const command = new EPUBCommand()
|
|
114
|
+
command.name = sandbox.stub().returns('epub')
|
|
115
|
+
|
|
116
|
+
await command.action({ engine: 'epubjs', open: true }, command)
|
|
117
|
+
|
|
118
|
+
t.true(mockGenerateEpub.called, 'generateEpub should be called')
|
|
119
|
+
t.true(mockOpen.called, 'open should be called when --open flag is provided')
|
|
120
|
+
t.is(mockOpen.firstCall.args[0], outputPath, 'should open the generated EPUB path')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('epub command should not open EPUB when --open flag is not provided', async (t) => {
|
|
124
|
+
const { sandbox, mockReporter } = t.context
|
|
125
|
+
|
|
126
|
+
const mockGenerateEpub = sandbox.stub().resolves('/project/epubjs.epub')
|
|
127
|
+
const mockOpen = sandbox.stub()
|
|
128
|
+
|
|
129
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
130
|
+
'#lib/epub/index.js': {
|
|
131
|
+
default: mockGenerateEpub
|
|
132
|
+
},
|
|
133
|
+
'#lib/project/index.js': {
|
|
134
|
+
hasEpubOutput: () => true
|
|
135
|
+
},
|
|
136
|
+
'#lib/reporter/index.js': {
|
|
137
|
+
default: mockReporter
|
|
138
|
+
},
|
|
139
|
+
'fs-extra': {
|
|
140
|
+
existsSync: () => true
|
|
141
|
+
},
|
|
142
|
+
open: {
|
|
143
|
+
default: mockOpen
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const command = new EPUBCommand()
|
|
148
|
+
command.name = sandbox.stub().returns('epub')
|
|
149
|
+
|
|
150
|
+
await command.action({ engine: 'epubjs' }, command)
|
|
151
|
+
|
|
152
|
+
t.true(mockGenerateEpub.called, 'generateEpub should be called')
|
|
153
|
+
t.false(mockOpen.called, 'open should not be called without --open flag')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('epub command should pass debug option to generateEpub', async (t) => {
|
|
157
|
+
const { sandbox, mockReporter } = t.context
|
|
158
|
+
|
|
159
|
+
const mockGenerateEpub = sandbox.stub().resolves('/project/epubjs.epub')
|
|
160
|
+
|
|
161
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
162
|
+
'#lib/epub/index.js': {
|
|
163
|
+
default: mockGenerateEpub
|
|
164
|
+
},
|
|
165
|
+
'#lib/project/index.js': {
|
|
166
|
+
hasEpubOutput: () => true
|
|
167
|
+
},
|
|
168
|
+
'#lib/reporter/index.js': {
|
|
169
|
+
default: mockReporter
|
|
170
|
+
},
|
|
171
|
+
'fs-extra': {
|
|
172
|
+
existsSync: () => true
|
|
173
|
+
},
|
|
174
|
+
open: {
|
|
175
|
+
default: sandbox.stub()
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
const command = new EPUBCommand()
|
|
180
|
+
command.name = sandbox.stub().returns('epub')
|
|
181
|
+
|
|
182
|
+
await command.action({ engine: 'epubjs', debug: true }, command)
|
|
183
|
+
|
|
184
|
+
t.true(mockGenerateEpub.called, 'generateEpub should be called')
|
|
185
|
+
t.is(mockGenerateEpub.firstCall.args[0].lib, 'epubjs', 'should pass lib to generateEpub')
|
|
186
|
+
t.true(mockGenerateEpub.firstCall.args[0].debug, 'should pass debug option')
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('epub command should throw MissingBuildOutputError when build output is missing', async (t) => {
|
|
190
|
+
const { sandbox, mockReporter } = t.context
|
|
191
|
+
|
|
192
|
+
const mockGenerateEpub = sandbox.stub().resolves('/project/epubjs.epub')
|
|
193
|
+
|
|
194
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
195
|
+
'#lib/epub/index.js': {
|
|
196
|
+
default: mockGenerateEpub
|
|
197
|
+
},
|
|
198
|
+
'#lib/project/index.js': {
|
|
199
|
+
default: { getEpubDir: () => '_epub' },
|
|
200
|
+
hasEpubOutput: () => false
|
|
201
|
+
},
|
|
202
|
+
'#lib/reporter/index.js': {
|
|
203
|
+
default: mockReporter
|
|
204
|
+
},
|
|
205
|
+
'fs-extra': {
|
|
206
|
+
existsSync: () => true
|
|
207
|
+
},
|
|
208
|
+
open: {
|
|
209
|
+
default: sandbox.stub()
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
const command = new EPUBCommand()
|
|
214
|
+
command.name = sandbox.stub().returns('epub')
|
|
215
|
+
|
|
216
|
+
const error = await t.throwsAsync(() => command.action({ engine: 'epubjs' }, command))
|
|
217
|
+
|
|
218
|
+
t.is(error.code, 'BUILD_OUTPUT_MISSING', 'should throw BUILD_OUTPUT_MISSING error')
|
|
219
|
+
t.regex(error.message, /EPUB/, 'error should mention EPUB')
|
|
220
|
+
t.false(mockGenerateEpub.called, 'generateEpub should not be called when build output is missing')
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
test('epub command should run build first when --build flag is set and output missing', async (t) => {
|
|
224
|
+
const { sandbox, mockReporter } = t.context
|
|
225
|
+
|
|
226
|
+
const mockGenerateEpub = sandbox.stub().resolves('/project/epubjs.epub')
|
|
227
|
+
const mockBuild = sandbox.stub().resolves()
|
|
228
|
+
let buildCalled = false
|
|
229
|
+
|
|
230
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
231
|
+
'#lib/epub/index.js': {
|
|
232
|
+
default: mockGenerateEpub
|
|
233
|
+
},
|
|
234
|
+
'#lib/project/index.js': {
|
|
235
|
+
hasEpubOutput: () => {
|
|
236
|
+
// Return false first (before build), true after build
|
|
237
|
+
if (!buildCalled) return false
|
|
238
|
+
return true
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
'#lib/11ty/index.js': {
|
|
242
|
+
default: {
|
|
243
|
+
build: async (opts) => {
|
|
244
|
+
buildCalled = true
|
|
245
|
+
return mockBuild(opts)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
'#lib/reporter/index.js': {
|
|
250
|
+
default: mockReporter
|
|
251
|
+
},
|
|
252
|
+
'fs-extra': {
|
|
253
|
+
existsSync: () => true
|
|
254
|
+
},
|
|
255
|
+
open: {
|
|
256
|
+
default: sandbox.stub()
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
const command = new EPUBCommand()
|
|
261
|
+
command.name = sandbox.stub().returns('epub')
|
|
262
|
+
|
|
263
|
+
await command.action({ engine: 'epubjs', build: true }, command)
|
|
264
|
+
|
|
265
|
+
t.true(mockBuild.called, 'build should be called when --build flag is set')
|
|
266
|
+
t.true(mockGenerateEpub.called, 'generateEpub should be called after build')
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
test('epub command should support deprecated --lib option', async (t) => {
|
|
270
|
+
const { sandbox, mockReporter } = t.context
|
|
271
|
+
|
|
272
|
+
const mockGenerateEpub = sandbox.stub().resolves('/project/pandoc.epub')
|
|
273
|
+
|
|
274
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
275
|
+
'#lib/epub/index.js': {
|
|
276
|
+
default: mockGenerateEpub
|
|
277
|
+
},
|
|
278
|
+
'#lib/project/index.js': {
|
|
279
|
+
hasEpubOutput: () => true
|
|
280
|
+
},
|
|
281
|
+
'#lib/reporter/index.js': {
|
|
282
|
+
default: mockReporter
|
|
283
|
+
},
|
|
284
|
+
'fs-extra': {
|
|
285
|
+
existsSync: () => true
|
|
286
|
+
},
|
|
287
|
+
open: {
|
|
288
|
+
default: sandbox.stub()
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
const command = new EPUBCommand()
|
|
293
|
+
command.name = sandbox.stub().returns('epub')
|
|
294
|
+
|
|
295
|
+
// Use deprecated --lib option (should still work)
|
|
296
|
+
await command.action({ lib: 'pandoc' }, command)
|
|
297
|
+
|
|
298
|
+
t.true(mockGenerateEpub.called, 'generateEpub should be called with deprecated --lib')
|
|
299
|
+
t.is(mockGenerateEpub.firstCall.args[0].lib, 'pandoc', 'should pass lib to generateEpub from deprecated option')
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
test('epub command should propagate errors from generateEpub', async (t) => {
|
|
303
|
+
const { sandbox, mockReporter } = t.context
|
|
304
|
+
|
|
305
|
+
const epubError = new Error('EPUB generation failed')
|
|
306
|
+
const mockGenerateEpub = sandbox.stub().rejects(epubError)
|
|
307
|
+
|
|
308
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
309
|
+
'#lib/epub/index.js': {
|
|
310
|
+
default: mockGenerateEpub
|
|
311
|
+
},
|
|
312
|
+
'#lib/project/index.js': {
|
|
313
|
+
hasEpubOutput: () => true
|
|
314
|
+
},
|
|
315
|
+
'#lib/reporter/index.js': {
|
|
316
|
+
default: mockReporter
|
|
317
|
+
},
|
|
318
|
+
'fs-extra': {
|
|
319
|
+
existsSync: () => true
|
|
320
|
+
},
|
|
321
|
+
open: {
|
|
322
|
+
default: sandbox.stub()
|
|
323
|
+
}
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
const command = new EPUBCommand()
|
|
327
|
+
command.name = sandbox.stub().returns('epub')
|
|
328
|
+
|
|
329
|
+
// Error is propagated; reporter lifecycle is handled by façade (lib/epub/index.js)
|
|
330
|
+
await t.throwsAsync(() => command.action({ engine: 'epubjs' }, command), { message: 'EPUB generation failed' })
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
test('epub command should configure reporter with quiet option', async (t) => {
|
|
334
|
+
const { sandbox, mockReporter } = t.context
|
|
335
|
+
|
|
336
|
+
const mockGenerateEpub = sandbox.stub().resolves('/project/epubjs.epub')
|
|
337
|
+
|
|
338
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
339
|
+
'#lib/epub/index.js': {
|
|
340
|
+
default: mockGenerateEpub
|
|
341
|
+
},
|
|
342
|
+
'#lib/project/index.js': {
|
|
343
|
+
hasEpubOutput: () => true
|
|
344
|
+
},
|
|
345
|
+
'#lib/reporter/index.js': {
|
|
346
|
+
default: mockReporter
|
|
347
|
+
},
|
|
348
|
+
'fs-extra': {
|
|
349
|
+
existsSync: () => true
|
|
350
|
+
},
|
|
351
|
+
open: {
|
|
352
|
+
default: sandbox.stub()
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
const command = new EPUBCommand()
|
|
357
|
+
command.name = sandbox.stub().returns('epub')
|
|
358
|
+
|
|
359
|
+
await command.action({ engine: 'epubjs', quiet: true }, command)
|
|
360
|
+
|
|
361
|
+
t.true(
|
|
362
|
+
mockReporter.configure.calledWith(sinon.match({ quiet: true })),
|
|
363
|
+
'reporter.configure should be called with quiet option'
|
|
364
|
+
)
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
test('epub command should pass output option to generateEpub', async (t) => {
|
|
368
|
+
const { sandbox, mockReporter } = t.context
|
|
369
|
+
|
|
370
|
+
const customOutput = '/custom/path/my-book.epub'
|
|
371
|
+
const mockGenerateEpub = sandbox.stub().resolves(customOutput)
|
|
372
|
+
|
|
373
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
374
|
+
'#lib/epub/index.js': {
|
|
375
|
+
default: mockGenerateEpub
|
|
376
|
+
},
|
|
377
|
+
'#lib/project/index.js': {
|
|
378
|
+
hasEpubOutput: () => true
|
|
379
|
+
},
|
|
380
|
+
'#lib/reporter/index.js': {
|
|
381
|
+
default: mockReporter
|
|
382
|
+
},
|
|
383
|
+
'fs-extra': {
|
|
384
|
+
existsSync: () => true
|
|
385
|
+
},
|
|
386
|
+
open: {
|
|
387
|
+
default: sandbox.stub()
|
|
388
|
+
}
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
const command = new EPUBCommand()
|
|
392
|
+
command.name = sandbox.stub().returns('epub')
|
|
393
|
+
|
|
394
|
+
await command.action({ engine: 'epubjs', output: customOutput }, command)
|
|
395
|
+
|
|
396
|
+
t.true(mockGenerateEpub.called, 'generateEpub should be called')
|
|
397
|
+
t.is(mockGenerateEpub.firstCall.args[0].output, customOutput, 'should pass output option to generateEpub')
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
test('epub command should use epubEngine from config when --engine not specified', async (t) => {
|
|
401
|
+
const { sandbox, mockReporter } = t.context
|
|
402
|
+
|
|
403
|
+
const mockGenerateEpub = sandbox.stub().resolves('/project/pandoc.epub')
|
|
404
|
+
|
|
405
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
406
|
+
'#lib/epub/index.js': {
|
|
407
|
+
default: mockGenerateEpub
|
|
408
|
+
},
|
|
409
|
+
'#lib/project/index.js': {
|
|
410
|
+
hasEpubOutput: () => true
|
|
411
|
+
},
|
|
412
|
+
'#lib/reporter/index.js': {
|
|
413
|
+
default: mockReporter
|
|
414
|
+
},
|
|
415
|
+
'fs-extra': {
|
|
416
|
+
existsSync: () => true
|
|
417
|
+
},
|
|
418
|
+
open: {
|
|
419
|
+
default: sandbox.stub()
|
|
420
|
+
}
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
const command = new EPUBCommand()
|
|
424
|
+
command.name = sandbox.stub().returns('epub')
|
|
425
|
+
// Mock config to return 'pandoc' for epubEngine
|
|
426
|
+
command.config = { get: (key) => key === 'epubEngine' ? 'pandoc' : undefined }
|
|
427
|
+
|
|
428
|
+
// No --engine specified, should use config value
|
|
429
|
+
await command.action({}, command)
|
|
430
|
+
|
|
431
|
+
t.true(mockGenerateEpub.called, 'generateEpub should be called')
|
|
432
|
+
t.is(mockGenerateEpub.firstCall.args[0].lib, 'pandoc', 'should use epubEngine from config')
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
test('epub command should use default engine when --engine not specified and config not set', async (t) => {
|
|
436
|
+
const { sandbox, mockReporter } = t.context
|
|
437
|
+
|
|
438
|
+
const mockGenerateEpub = sandbox.stub().resolves('/project/epubjs.epub')
|
|
439
|
+
|
|
440
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
441
|
+
'#lib/epub/index.js': {
|
|
442
|
+
default: mockGenerateEpub
|
|
443
|
+
},
|
|
444
|
+
'#lib/project/index.js': {
|
|
445
|
+
hasEpubOutput: () => true
|
|
446
|
+
},
|
|
447
|
+
'#lib/reporter/index.js': {
|
|
448
|
+
default: mockReporter
|
|
449
|
+
},
|
|
450
|
+
'fs-extra': {
|
|
451
|
+
existsSync: () => true
|
|
452
|
+
},
|
|
453
|
+
open: {
|
|
454
|
+
default: sandbox.stub()
|
|
455
|
+
}
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
const command = new EPUBCommand()
|
|
459
|
+
command.name = sandbox.stub().returns('epub')
|
|
460
|
+
// Mock config to return undefined (no epubEngine set)
|
|
461
|
+
command.config = { get: () => undefined }
|
|
462
|
+
|
|
463
|
+
// No --engine specified, no config, should use default 'epubjs'
|
|
464
|
+
await command.action({}, command)
|
|
465
|
+
|
|
466
|
+
t.true(mockGenerateEpub.called, 'generateEpub should be called')
|
|
467
|
+
t.is(mockGenerateEpub.firstCall.args[0].lib, 'epubjs', 'should use default epubjs when no config')
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
test('epub command --engine flag should override config epubEngine', async (t) => {
|
|
471
|
+
const { sandbox, mockReporter } = t.context
|
|
472
|
+
|
|
473
|
+
const mockGenerateEpub = sandbox.stub().resolves('/project/epubjs.epub')
|
|
474
|
+
|
|
475
|
+
const EPUBCommand = await esmock('./epub.js', {
|
|
476
|
+
'#lib/epub/index.js': {
|
|
477
|
+
default: mockGenerateEpub
|
|
478
|
+
},
|
|
479
|
+
'#lib/project/index.js': {
|
|
480
|
+
hasEpubOutput: () => true
|
|
481
|
+
},
|
|
482
|
+
'#lib/reporter/index.js': {
|
|
483
|
+
default: mockReporter
|
|
484
|
+
},
|
|
485
|
+
'fs-extra': {
|
|
486
|
+
existsSync: () => true
|
|
487
|
+
},
|
|
488
|
+
open: {
|
|
489
|
+
default: sandbox.stub()
|
|
490
|
+
}
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
const command = new EPUBCommand()
|
|
494
|
+
command.name = sandbox.stub().returns('epub')
|
|
495
|
+
// Mock config to return 'pandoc' for epubEngine
|
|
496
|
+
command.config = { get: (key) => key === 'epubEngine' ? 'pandoc' : undefined }
|
|
497
|
+
|
|
498
|
+
// --engine epubjs should override config's 'pandoc'
|
|
499
|
+
await command.action({ engine: 'epubjs' }, command)
|
|
500
|
+
|
|
501
|
+
t.true(mockGenerateEpub.called, 'generateEpub should be called')
|
|
502
|
+
t.is(mockGenerateEpub.firstCall.args[0].lib, 'epubjs', 'CLI --engine should override config')
|
|
503
|
+
})
|
package/src/commands/index.js
CHANGED
|
@@ -8,14 +8,21 @@ const __dirname = path.dirname(__filename)
|
|
|
8
8
|
|
|
9
9
|
const thisfile = path.basename(__filename)
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Filter out files that are not command modules
|
|
13
|
+
* @param directory entry
|
|
14
|
+
* @returns Boolean
|
|
15
|
+
*/
|
|
16
|
+
const filterEntries = (entry) => entry !== thisfile && entry.match(/^.*(?<!spec|test)\.js$/)
|
|
17
|
+
|
|
11
18
|
/**
|
|
12
19
|
* Dynamically import command modules in parallel
|
|
13
20
|
* and call the class constuctor for each command
|
|
14
21
|
*/
|
|
15
22
|
const commands = await Promise.all(
|
|
16
23
|
fs.readdirSync(__dirname)
|
|
17
|
-
.filter((
|
|
18
|
-
.map(async (
|
|
24
|
+
.filter((entry) => filterEntries(entry))
|
|
25
|
+
.map(async (entry) => await dynamicImport(path.resolve(__dirname, entry)))
|
|
19
26
|
).then((modules) => {
|
|
20
27
|
return modules.map(({ default: CommandClass }) => new CommandClass())
|
|
21
28
|
})
|
package/src/commands/info.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Command from '#src/Command.js'
|
|
2
|
-
import
|
|
2
|
+
import npm from '#lib/npm/index.js'
|
|
3
|
+
import { execa } from 'execa'
|
|
3
4
|
import fs from 'node:fs'
|
|
4
5
|
import os from 'node:os'
|
|
5
6
|
import path from 'node:path'
|
|
@@ -17,9 +18,13 @@ export default class InfoCommand extends Command {
|
|
|
17
18
|
static definition = {
|
|
18
19
|
name: 'info',
|
|
19
20
|
description: 'List Quire cli, quire-11ty, and node versions',
|
|
20
|
-
summary: '
|
|
21
|
+
summary: 'show version information',
|
|
22
|
+
docsLink: 'quire-commands/#get-help',
|
|
23
|
+
helpText: `
|
|
24
|
+
Example:
|
|
25
|
+
quire info --debug Include node, npm, and OS versions
|
|
26
|
+
`,
|
|
21
27
|
version: '1.0.0',
|
|
22
|
-
args: [],
|
|
23
28
|
options: [
|
|
24
29
|
['--debug', 'include os versions in output']
|
|
25
30
|
],
|
|
@@ -30,15 +35,7 @@ export default class InfoCommand extends Command {
|
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
async action(options, command) {
|
|
33
|
-
|
|
34
|
-
console.debug(
|
|
35
|
-
'[CLI] Command \'%s\' called with options %o',
|
|
36
|
-
this.name(),
|
|
37
|
-
options
|
|
38
|
-
)
|
|
39
|
-
} else {
|
|
40
|
-
console.debug('[CLI] Command \'%s\' called', this.name())
|
|
41
|
-
}
|
|
38
|
+
this.debug('called with options %O', options)
|
|
42
39
|
|
|
43
40
|
// Load filename from config with a default constraint if it doesn't exist
|
|
44
41
|
const versionFileName = this.config.get('versionFile')
|
|
@@ -49,7 +46,7 @@ export default class InfoCommand extends Command {
|
|
|
49
46
|
|
|
50
47
|
versionInfo = JSON.parse(versionFileData)
|
|
51
48
|
} catch (error) {
|
|
52
|
-
|
|
49
|
+
this.logger.warn(
|
|
53
50
|
`This project was generated with the quire-cli prior to version 1.0.0.rc-8. Updating the version file to the new format, though this project's version file will not contain specific starter version information.`
|
|
54
51
|
)
|
|
55
52
|
|
|
@@ -85,7 +82,7 @@ export default class InfoCommand extends Command {
|
|
|
85
82
|
{
|
|
86
83
|
name: 'quire-cli',
|
|
87
84
|
get: async () => {
|
|
88
|
-
const { stdout } = await
|
|
85
|
+
const { stdout } = await execa('quire', ['--version'])
|
|
89
86
|
return stdout
|
|
90
87
|
},
|
|
91
88
|
},
|
|
@@ -97,10 +94,7 @@ export default class InfoCommand extends Command {
|
|
|
97
94
|
{
|
|
98
95
|
debug: true,
|
|
99
96
|
name: 'npm',
|
|
100
|
-
get: async () =>
|
|
101
|
-
const { stdout } = await execaCommand('npm --version')
|
|
102
|
-
return stdout
|
|
103
|
-
},
|
|
97
|
+
get: async () => await npm.version(),
|
|
104
98
|
},
|
|
105
99
|
{
|
|
106
100
|
debug: true,
|
|
@@ -114,17 +108,17 @@ export default class InfoCommand extends Command {
|
|
|
114
108
|
/**
|
|
115
109
|
* Filter the command output based on `debug` settings
|
|
116
110
|
*/
|
|
117
|
-
|
|
118
|
-
const
|
|
111
|
+
for (const { items, title } of versions) {
|
|
112
|
+
const versionList = await Promise.all(
|
|
119
113
|
items
|
|
120
114
|
.filter(({ debug }) => !debug || (options.debug && debug))
|
|
121
115
|
.map(async ({ name, get }) => `${name} ${await get()}`)
|
|
122
116
|
)
|
|
123
|
-
|
|
124
|
-
}
|
|
117
|
+
this.logger.info(`${title}\n ${versionList.join('\n ')}`)
|
|
118
|
+
}
|
|
125
119
|
}
|
|
126
120
|
|
|
127
|
-
preAction(
|
|
128
|
-
testcwd(
|
|
121
|
+
preAction(thisCommand, actionCommand) {
|
|
122
|
+
testcwd(thisCommand)
|
|
129
123
|
}
|
|
130
124
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Command, Option } from 'commander'
|
|
2
|
+
import program from '#src/main.js'
|
|
3
|
+
import test from 'ava'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Command Contract/Interface Tests
|
|
7
|
+
*
|
|
8
|
+
* Verifies the command's public API and Commander.js integration.
|
|
9
|
+
* @see docs/testing-commands.md
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
test.before((t) => {
|
|
13
|
+
// Get the registered command (from program.commands) once and share across all tests
|
|
14
|
+
t.context.command = program.commands.find((cmd) => cmd.name() === 'info')
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('command is registered in CLI program', (t) => {
|
|
18
|
+
const { command } = t.context
|
|
19
|
+
|
|
20
|
+
t.truthy(command, 'command "info" should be registered in program')
|
|
21
|
+
t.true(command instanceof Command, 'registered command should be Commander.js Command instance')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('registered command has correct metadata', (t) => {
|
|
25
|
+
const { command } = t.context
|
|
26
|
+
|
|
27
|
+
t.is(command.name(), 'info')
|
|
28
|
+
t.truthy(command.description())
|
|
29
|
+
t.is(typeof command._actionHandler, 'function', 'command should have action handler')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('registered command has no arguments', (t) => {
|
|
33
|
+
const { command } = t.context
|
|
34
|
+
const registeredArguments = command.registeredArguments
|
|
35
|
+
|
|
36
|
+
t.is(registeredArguments.length, 0, 'info command should have no arguments')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('registered command has correct options', (t) => {
|
|
40
|
+
const { command } = t.context
|
|
41
|
+
|
|
42
|
+
// Get all options
|
|
43
|
+
const debugOption = command.options.find((opt) => opt.long === '--debug')
|
|
44
|
+
|
|
45
|
+
// Verify all options exist
|
|
46
|
+
t.truthy(debugOption, '--debug option should exist')
|
|
47
|
+
|
|
48
|
+
// Verify they are Option instances
|
|
49
|
+
t.true(debugOption instanceof Option, '--debug should be Option instance')
|
|
50
|
+
|
|
51
|
+
// Verify option properties
|
|
52
|
+
t.is(debugOption.long, '--debug')
|
|
53
|
+
t.truthy(debugOption.description)
|
|
54
|
+
t.false(debugOption.required, '--debug should not require a value')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('command options are accessible via public API', (t) => {
|
|
58
|
+
const { command } = t.context
|
|
59
|
+
|
|
60
|
+
// Test that options can be accessed the way Commander.js does
|
|
61
|
+
const optionNames = command.options.map((opt) => opt.long)
|
|
62
|
+
|
|
63
|
+
t.true(optionNames.includes('--debug'))
|
|
64
|
+
})
|