@thegetty/quire-cli 1.0.0-rc.34 → 1.0.0-rc.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +9 -0
- package/bin/cli.js +19 -1
- package/package.json +21 -8
- package/schemas/config.schema.json +194 -0
- package/schemas/figures.schema.json +56 -0
- package/schemas/layout.schema.json +5 -0
- package/schemas/objects.schema.json +62 -0
- package/schemas/publication.schema.json +140 -0
- package/schemas/references.schema.json +29 -0
- 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,415 @@
|
|
|
1
|
+
import test from 'ava'
|
|
2
|
+
import sinon from 'sinon'
|
|
3
|
+
import esmock from 'esmock'
|
|
4
|
+
|
|
5
|
+
test.beforeEach((t) => {
|
|
6
|
+
// Create sinon sandbox for mocking
|
|
7
|
+
t.context.sandbox = sinon.createSandbox()
|
|
8
|
+
|
|
9
|
+
// Create mock logger
|
|
10
|
+
t.context.mockLogger = {
|
|
11
|
+
info: t.context.sandbox.stub(),
|
|
12
|
+
error: t.context.sandbox.stub(),
|
|
13
|
+
debug: t.context.sandbox.stub(),
|
|
14
|
+
log: t.context.sandbox.stub(),
|
|
15
|
+
warn: t.context.sandbox.stub()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Mock config
|
|
19
|
+
t.context.mockConfig = {
|
|
20
|
+
get: t.context.sandbox.stub().returns('./.quire-version')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Mock testcwd helper
|
|
24
|
+
t.context.mockTestcwd = t.context.sandbox.stub()
|
|
25
|
+
|
|
26
|
+
// Mock execaCommand for external commands
|
|
27
|
+
t.context.mockExecaCommand = t.context.sandbox.stub()
|
|
28
|
+
t.context.mockExecaCommand.withArgs('quire --version').resolves({ stdout: '1.0.0-rc.33' })
|
|
29
|
+
t.context.mockExecaCommand.withArgs('npm --version').resolves({ stdout: '10.2.4' })
|
|
30
|
+
|
|
31
|
+
// Mock os module
|
|
32
|
+
t.context.mockOs = {
|
|
33
|
+
type: t.context.sandbox.stub().returns('Darwin'),
|
|
34
|
+
release: t.context.sandbox.stub().returns('23.0.0')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Mock fs module
|
|
38
|
+
t.context.mockFs = {
|
|
39
|
+
readFileSync: t.context.sandbox.stub(),
|
|
40
|
+
writeFileSync: t.context.sandbox.stub(),
|
|
41
|
+
existsSync: t.context.sandbox.stub().returns(true)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Default version file content
|
|
45
|
+
t.context.versionFileContent = JSON.stringify({
|
|
46
|
+
cli: '1.0.0-rc.33',
|
|
47
|
+
starter: 'https://github.com/thegetty/quire-starter-default'
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Default package.json content
|
|
51
|
+
t.context.packageJsonContent = JSON.stringify({
|
|
52
|
+
name: 'test-project',
|
|
53
|
+
version: '1.0.0'
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// Setup default readFileSync behavior - stub returns different content based on file path
|
|
57
|
+
t.context.mockFs.readFileSync.callsFake((filePath, options) => {
|
|
58
|
+
if (filePath === './.quire-version') {
|
|
59
|
+
return t.context.versionFileContent
|
|
60
|
+
}
|
|
61
|
+
if (filePath === './package.json') {
|
|
62
|
+
return t.context.packageJsonContent
|
|
63
|
+
}
|
|
64
|
+
// For any other file, return empty string
|
|
65
|
+
return ''
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test.afterEach.always((t) => {
|
|
70
|
+
// Restore all mocks
|
|
71
|
+
t.context.sandbox.restore()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('info command should validate Quire project in preAction', async (t) => {
|
|
75
|
+
const { sandbox, mockLogger, mockTestcwd, mockConfig, mockExecaCommand, mockOs, mockFs } = t.context
|
|
76
|
+
|
|
77
|
+
const { default: InfoCommand } = await esmock('./info.js', {
|
|
78
|
+
'#helpers/test-cwd.js': {
|
|
79
|
+
default: mockTestcwd
|
|
80
|
+
},
|
|
81
|
+
'execa': {
|
|
82
|
+
execaCommand: mockExecaCommand
|
|
83
|
+
},
|
|
84
|
+
'node:os': mockOs,
|
|
85
|
+
'node:fs': mockFs
|
|
86
|
+
}, {
|
|
87
|
+
'#lib/logger/index.js': {
|
|
88
|
+
default: () => mockLogger
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const command = new InfoCommand()
|
|
93
|
+
command.config = mockConfig
|
|
94
|
+
command.logger = mockLogger
|
|
95
|
+
command.debug = sandbox.stub()
|
|
96
|
+
|
|
97
|
+
// Mock Commander.js command object
|
|
98
|
+
const mockCommand = {
|
|
99
|
+
name: () => 'info',
|
|
100
|
+
opts: () => ({})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Call preAction
|
|
104
|
+
command.preAction(mockCommand)
|
|
105
|
+
|
|
106
|
+
t.true(mockTestcwd.called, 'testcwd should be called in preAction')
|
|
107
|
+
t.true(mockTestcwd.calledWith(mockCommand), 'testcwd should be called with command')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('info command should display project version information', async (t) => {
|
|
111
|
+
const { sandbox, mockLogger, mockConfig, mockTestcwd, mockExecaCommand, mockOs, mockFs } = t.context
|
|
112
|
+
|
|
113
|
+
const { default: InfoCommand } = await esmock('./info.js', {
|
|
114
|
+
'#helpers/test-cwd.js': {
|
|
115
|
+
default: mockTestcwd
|
|
116
|
+
},
|
|
117
|
+
'execa': {
|
|
118
|
+
execaCommand: mockExecaCommand
|
|
119
|
+
},
|
|
120
|
+
'node:os': mockOs,
|
|
121
|
+
'node:fs': mockFs
|
|
122
|
+
}, {
|
|
123
|
+
'#lib/logger/index.js': {
|
|
124
|
+
default: () => mockLogger
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const command = new InfoCommand()
|
|
129
|
+
command.config = mockConfig
|
|
130
|
+
command.logger = mockLogger
|
|
131
|
+
command.debug = sandbox.stub()
|
|
132
|
+
|
|
133
|
+
await command.action({}, {})
|
|
134
|
+
|
|
135
|
+
// Verify logger.info was called with project information
|
|
136
|
+
t.true(mockLogger.info.called, 'logger.info should be called')
|
|
137
|
+
|
|
138
|
+
// Check for project section header (uses current directory name)
|
|
139
|
+
const calls = mockLogger.info.getCalls()
|
|
140
|
+
const hasProjectInfo = calls.some((call) =>
|
|
141
|
+
call.args[0] && call.args[0].includes('[') && call.args[0].includes(']')
|
|
142
|
+
)
|
|
143
|
+
t.true(hasProjectInfo, 'should display project section')
|
|
144
|
+
|
|
145
|
+
// Check for quire-cli version in output
|
|
146
|
+
const hasCliVersion = calls.some((call) =>
|
|
147
|
+
call.args[0] && call.args[0].includes('quire-cli') && call.args[0].includes('1.0.0-rc.33')
|
|
148
|
+
)
|
|
149
|
+
t.true(hasCliVersion, 'should display quire-cli version')
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test('info command should display system information with debug flag', async (t) => {
|
|
153
|
+
const { sandbox, mockLogger, mockConfig, mockTestcwd, mockExecaCommand, mockOs, mockFs } = t.context
|
|
154
|
+
|
|
155
|
+
const { default: InfoCommand } = await esmock('./info.js', {
|
|
156
|
+
'#helpers/test-cwd.js': {
|
|
157
|
+
default: mockTestcwd
|
|
158
|
+
},
|
|
159
|
+
'execa': {
|
|
160
|
+
execaCommand: mockExecaCommand
|
|
161
|
+
},
|
|
162
|
+
'node:os': mockOs,
|
|
163
|
+
'node:fs': mockFs
|
|
164
|
+
}, {
|
|
165
|
+
'#lib/logger/index.js': {
|
|
166
|
+
default: () => mockLogger
|
|
167
|
+
}
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
const command = new InfoCommand()
|
|
171
|
+
command.config = mockConfig
|
|
172
|
+
command.logger = mockLogger
|
|
173
|
+
command.debug = sandbox.stub()
|
|
174
|
+
|
|
175
|
+
await command.action({ debug: true }, {})
|
|
176
|
+
|
|
177
|
+
// Verify logger.info was called
|
|
178
|
+
t.true(mockLogger.info.called, 'logger.info should be called')
|
|
179
|
+
|
|
180
|
+
// Check for system section header
|
|
181
|
+
const calls = mockLogger.info.getCalls()
|
|
182
|
+
const hasSystemInfo = calls.some((call) =>
|
|
183
|
+
call.args[0] && call.args[0].includes('[System]')
|
|
184
|
+
)
|
|
185
|
+
t.true(hasSystemInfo, 'should display system section with debug flag')
|
|
186
|
+
|
|
187
|
+
// Check for node version in output (only shown with debug)
|
|
188
|
+
const hasNodeVersion = calls.some((call) =>
|
|
189
|
+
call.args[0] && call.args[0].includes('node')
|
|
190
|
+
)
|
|
191
|
+
t.true(hasNodeVersion, 'should display node version with debug flag')
|
|
192
|
+
|
|
193
|
+
// Check for npm version in output (only shown with debug)
|
|
194
|
+
const hasNpmVersion = calls.some((call) =>
|
|
195
|
+
call.args[0] && call.args[0].includes('npm')
|
|
196
|
+
)
|
|
197
|
+
t.true(hasNpmVersion, 'should display npm version with debug flag')
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test('info command should hide system details without debug flag', async (t) => {
|
|
201
|
+
const { sandbox, mockLogger, mockConfig, mockTestcwd, mockExecaCommand, mockOs, mockFs } = t.context
|
|
202
|
+
|
|
203
|
+
const { default: InfoCommand } = await esmock('./info.js', {
|
|
204
|
+
'#helpers/test-cwd.js': {
|
|
205
|
+
default: mockTestcwd
|
|
206
|
+
},
|
|
207
|
+
'execa': {
|
|
208
|
+
execaCommand: mockExecaCommand
|
|
209
|
+
},
|
|
210
|
+
'node:os': mockOs,
|
|
211
|
+
'node:fs': mockFs
|
|
212
|
+
}, {
|
|
213
|
+
'#lib/logger/index.js': {
|
|
214
|
+
default: () => mockLogger
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
const command = new InfoCommand()
|
|
219
|
+
command.config = mockConfig
|
|
220
|
+
command.logger = mockLogger
|
|
221
|
+
command.debug = sandbox.stub()
|
|
222
|
+
|
|
223
|
+
await command.action({}, {})
|
|
224
|
+
|
|
225
|
+
// Verify logger.info was called
|
|
226
|
+
t.true(mockLogger.info.called, 'logger.info should be called')
|
|
227
|
+
|
|
228
|
+
// Check that system details are NOT shown without debug flag
|
|
229
|
+
const calls = mockLogger.info.getCalls()
|
|
230
|
+
|
|
231
|
+
// Node version should not appear in non-debug output
|
|
232
|
+
const hasNodeVersion = calls.some((call) =>
|
|
233
|
+
call.args[0] && call.args[0].toLowerCase().includes('node') && !call.args[0].includes('quire')
|
|
234
|
+
)
|
|
235
|
+
t.false(hasNodeVersion, 'should not display node version without debug flag')
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test('info command should handle missing version file', async (t) => {
|
|
239
|
+
const { sandbox, mockLogger, mockConfig, mockTestcwd, mockExecaCommand, mockOs, mockFs } = t.context
|
|
240
|
+
|
|
241
|
+
// Setup mockFs to throw error for missing version file (simulating file not found)
|
|
242
|
+
mockFs.readFileSync.callsFake((filePath, options) => {
|
|
243
|
+
if (filePath === './.quire-version') {
|
|
244
|
+
throw new Error('ENOENT: no such file')
|
|
245
|
+
}
|
|
246
|
+
if (filePath === './package.json') {
|
|
247
|
+
return t.context.packageJsonContent
|
|
248
|
+
}
|
|
249
|
+
return ''
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
const { default: InfoCommand } = await esmock('./info.js', {
|
|
253
|
+
'#helpers/test-cwd.js': {
|
|
254
|
+
default: mockTestcwd
|
|
255
|
+
},
|
|
256
|
+
'execa': {
|
|
257
|
+
execaCommand: mockExecaCommand
|
|
258
|
+
},
|
|
259
|
+
'node:os': mockOs,
|
|
260
|
+
'node:fs': mockFs
|
|
261
|
+
}, {
|
|
262
|
+
'#lib/logger/index.js': {
|
|
263
|
+
default: () => mockLogger
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
const command = new InfoCommand()
|
|
268
|
+
command.config = mockConfig
|
|
269
|
+
command.logger = mockLogger
|
|
270
|
+
command.debug = sandbox.stub()
|
|
271
|
+
|
|
272
|
+
await command.action({}, {})
|
|
273
|
+
|
|
274
|
+
// Verify warning was logged about old version
|
|
275
|
+
t.true(mockLogger.warn.called, 'logger.warn should be called for missing version file')
|
|
276
|
+
t.true(
|
|
277
|
+
mockLogger.warn.calledWith(sinon.match(/prior to version 1.0.0.rc-8/)),
|
|
278
|
+
'should warn about old project version'
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
// Verify version file was created
|
|
282
|
+
t.true(mockFs.writeFileSync.called, 'writeFileSync should be called to create version file')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test('info command should handle malformed version file', async (t) => {
|
|
286
|
+
const { sandbox, mockLogger, mockConfig, mockTestcwd, mockExecaCommand, mockOs, mockFs } = t.context
|
|
287
|
+
|
|
288
|
+
// Setup mockFs to return malformed JSON (simulating corrupted file)
|
|
289
|
+
mockFs.readFileSync.callsFake((filePath, options) => {
|
|
290
|
+
if (filePath === './.quire-version') {
|
|
291
|
+
return 'invalid json{'
|
|
292
|
+
}
|
|
293
|
+
if (filePath === './package.json') {
|
|
294
|
+
return t.context.packageJsonContent
|
|
295
|
+
}
|
|
296
|
+
return ''
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
const { default: InfoCommand } = await esmock('./info.js', {
|
|
300
|
+
'#helpers/test-cwd.js': {
|
|
301
|
+
default: mockTestcwd
|
|
302
|
+
},
|
|
303
|
+
'execa': {
|
|
304
|
+
execaCommand: mockExecaCommand
|
|
305
|
+
},
|
|
306
|
+
'node:os': mockOs,
|
|
307
|
+
'node:fs': mockFs
|
|
308
|
+
}, {
|
|
309
|
+
'#lib/logger/index.js': {
|
|
310
|
+
default: () => mockLogger
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
const command = new InfoCommand()
|
|
315
|
+
command.config = mockConfig
|
|
316
|
+
command.logger = mockLogger
|
|
317
|
+
command.debug = sandbox.stub()
|
|
318
|
+
|
|
319
|
+
await command.action({}, {})
|
|
320
|
+
|
|
321
|
+
// Verify warning was logged
|
|
322
|
+
t.true(mockLogger.warn.called, 'logger.warn should be called for malformed version file')
|
|
323
|
+
|
|
324
|
+
// Verify version file was overwritten with valid JSON
|
|
325
|
+
t.true(mockFs.writeFileSync.called, 'writeFileSync should be called to fix malformed version file')
|
|
326
|
+
|
|
327
|
+
// Check that the content written is valid JSON
|
|
328
|
+
const writeCall = mockFs.writeFileSync.getCall(0)
|
|
329
|
+
if (writeCall) {
|
|
330
|
+
const writtenContent = writeCall.args[1]
|
|
331
|
+
t.notThrows(() => JSON.parse(writtenContent), 'written content should be valid JSON')
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
test('info command should read quire-11ty version from package.json', async (t) => {
|
|
336
|
+
const { sandbox, mockLogger, mockConfig, mockTestcwd, mockExecaCommand, mockOs, mockFs } = t.context
|
|
337
|
+
|
|
338
|
+
// Override package.json to have specific version
|
|
339
|
+
mockFs.readFileSync.callsFake((filePath, options) => {
|
|
340
|
+
if (filePath === './.quire-version') {
|
|
341
|
+
return t.context.versionFileContent
|
|
342
|
+
}
|
|
343
|
+
if (filePath === './package.json') {
|
|
344
|
+
return JSON.stringify({
|
|
345
|
+
name: 'test-project',
|
|
346
|
+
version: '2.5.3'
|
|
347
|
+
})
|
|
348
|
+
}
|
|
349
|
+
return ''
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
const { default: InfoCommand } = await esmock('./info.js', {
|
|
353
|
+
'#helpers/test-cwd.js': {
|
|
354
|
+
default: mockTestcwd
|
|
355
|
+
},
|
|
356
|
+
'execa': {
|
|
357
|
+
execaCommand: mockExecaCommand
|
|
358
|
+
},
|
|
359
|
+
'node:os': mockOs,
|
|
360
|
+
'node:fs': mockFs
|
|
361
|
+
}, {
|
|
362
|
+
'#lib/logger/index.js': {
|
|
363
|
+
default: () => mockLogger
|
|
364
|
+
}
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
const command = new InfoCommand()
|
|
368
|
+
command.config = mockConfig
|
|
369
|
+
command.logger = mockLogger
|
|
370
|
+
command.debug = sandbox.stub()
|
|
371
|
+
|
|
372
|
+
await command.action({}, {})
|
|
373
|
+
|
|
374
|
+
// Verify logger.info was called
|
|
375
|
+
t.true(mockLogger.info.called, 'logger.info should be called')
|
|
376
|
+
|
|
377
|
+
// Check for quire-11ty version from package.json
|
|
378
|
+
const calls = mockLogger.info.getCalls()
|
|
379
|
+
const hasEleventyVersion = calls.some((call) =>
|
|
380
|
+
call.args[0] && call.args[0].includes('quire-11ty') && call.args[0].includes('2.5.3')
|
|
381
|
+
)
|
|
382
|
+
t.true(hasEleventyVersion, 'should display quire-11ty version from package.json')
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
test('info command should log debug information when debug flag is set', async (t) => {
|
|
386
|
+
const { sandbox, mockLogger, mockConfig, mockTestcwd, mockExecaCommand, mockOs, mockFs } = t.context
|
|
387
|
+
|
|
388
|
+
const mockDebug = sandbox.stub()
|
|
389
|
+
|
|
390
|
+
const { default: InfoCommand } = await esmock('./info.js', {
|
|
391
|
+
'#helpers/test-cwd.js': {
|
|
392
|
+
default: mockTestcwd
|
|
393
|
+
},
|
|
394
|
+
'execa': {
|
|
395
|
+
execaCommand: mockExecaCommand
|
|
396
|
+
},
|
|
397
|
+
'node:os': mockOs,
|
|
398
|
+
'node:fs': mockFs
|
|
399
|
+
}, {
|
|
400
|
+
'#lib/logger/index.js': {
|
|
401
|
+
default: () => mockLogger
|
|
402
|
+
}
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
const command = new InfoCommand()
|
|
406
|
+
command.config = mockConfig
|
|
407
|
+
command.logger = mockLogger
|
|
408
|
+
command.debug = mockDebug
|
|
409
|
+
|
|
410
|
+
await command.action({ debug: true }, {})
|
|
411
|
+
|
|
412
|
+
// Verify debug was called
|
|
413
|
+
t.true(mockDebug.called, 'debug should be called with debug flag')
|
|
414
|
+
t.true(mockDebug.calledWith('called with options %O', { debug: true }))
|
|
415
|
+
})
|
package/src/commands/pdf.js
CHANGED
|
@@ -1,50 +1,13 @@
|
|
|
1
|
-
import { paths, projectRoot } from '#lib/11ty/index.js'
|
|
2
1
|
import Command from '#src/Command.js'
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import { withOutputModes } from '#lib/commander/index.js'
|
|
3
|
+
import paths, { hasSiteOutput } from '#lib/project/index.js'
|
|
4
|
+
import eleventy from '#lib/11ty/index.js'
|
|
5
|
+
import generatePdf, { ENGINES } from '#lib/pdf/index.js'
|
|
5
6
|
import open from 'open'
|
|
6
7
|
import path from 'node:path'
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @function loadConfig(path) - loads and validates quire config, returns an empty object if not found
|
|
12
|
-
*
|
|
13
|
-
* @param {path} string - file path to config file
|
|
14
|
-
*
|
|
15
|
-
* @return {Object|undefined} User configuration object or undefined
|
|
16
|
-
*
|
|
17
|
-
* @todo consider hardcoding a version check against .quire / project's package.json ver
|
|
18
|
-
*/
|
|
19
|
-
async function loadConfig(configPath) {
|
|
20
|
-
if (!fs.existsSync(configPath)) {
|
|
21
|
-
return undefined
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const data = fs.readFileSync(configPath)
|
|
25
|
-
let config = yaml.load(data)
|
|
26
|
-
|
|
27
|
-
// NB: Schemas and validators are specific to the 11ty version of the project being built
|
|
28
|
-
const schemaPath = path.join(projectRoot,'_plugins','schemas','config.json')
|
|
29
|
-
const validatorPath = path.join(projectRoot, '_plugins', 'globalData', 'validator.js')
|
|
30
|
-
|
|
31
|
-
if (fs.existsSync(schemaPath) && fs.existsSync(validatorPath)) {
|
|
32
|
-
|
|
33
|
-
const { validateUserConfig } = await import(pathToFileURL(validatorPath))
|
|
34
|
-
|
|
35
|
-
const schemaJSON = fs.readFileSync(schemaPath)
|
|
36
|
-
const schema = JSON.parse(schemaJSON)
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
config = validateUserConfig('config', config, { config: schema })
|
|
40
|
-
} catch (error) {
|
|
41
|
-
console.error(error)
|
|
42
|
-
process.exit(1)
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return config
|
|
47
|
-
}
|
|
8
|
+
import reporter from '#lib/reporter/index.js'
|
|
9
|
+
import testcwd from '#helpers/test-cwd.js'
|
|
10
|
+
import { MissingBuildOutputError } from '#src/errors/index.js'
|
|
48
11
|
|
|
49
12
|
/**
|
|
50
13
|
* Quire CLI `pdf` Command
|
|
@@ -55,66 +18,77 @@ async function loadConfig(configPath) {
|
|
|
55
18
|
* @extends {Command}
|
|
56
19
|
*/
|
|
57
20
|
export default class PDFCommand extends Command {
|
|
58
|
-
static definition = {
|
|
21
|
+
static definition = withOutputModes({
|
|
59
22
|
name: 'pdf',
|
|
60
23
|
description: 'Generate publication PDF',
|
|
61
|
-
summary: '
|
|
24
|
+
summary: 'generate print-ready PDF',
|
|
25
|
+
docsLink: 'quire-commands/#output-files',
|
|
26
|
+
helpText: `
|
|
27
|
+
Examples:
|
|
28
|
+
quire pdf Generate PDF using default engine
|
|
29
|
+
quire pdf --engine prince Generate PDF using PrinceXML
|
|
30
|
+
quire pdf --build Build site first, then generate PDF
|
|
31
|
+
quire pdf --output my-book.pdf Generate PDF with custom output path
|
|
32
|
+
quire pdf --verbose Generate with detailed progress
|
|
33
|
+
`,
|
|
62
34
|
version: '1.0.0',
|
|
63
|
-
args: [
|
|
64
|
-
],
|
|
65
35
|
options: [
|
|
36
|
+
[ '--build', 'run build first if output is missing' ],
|
|
37
|
+
[ '--open', 'open PDF in default application' ],
|
|
38
|
+
[ '-o, --output <path>', 'output file path (default: from project config)' ],
|
|
66
39
|
[
|
|
67
|
-
'--
|
|
68
|
-
{ choices:
|
|
40
|
+
'--engine <name>', 'PDF engine to use (default: from config or pagedjs)',
|
|
41
|
+
{ choices: ENGINES }
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
'--lib <name>', 'deprecated alias for --engine option',
|
|
45
|
+
{ hidden: true, choices: ENGINES, conflicts: 'engine' }
|
|
69
46
|
],
|
|
70
|
-
[ '--open', 'open PDF in default application' ],
|
|
71
|
-
[ '--debug', 'run build with debug output to console' ],
|
|
72
47
|
],
|
|
73
|
-
}
|
|
48
|
+
})
|
|
74
49
|
|
|
75
50
|
constructor() {
|
|
76
51
|
super(PDFCommand.definition)
|
|
77
52
|
}
|
|
78
53
|
|
|
79
54
|
async action(options, command) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
|
|
91
|
-
|
|
55
|
+
this.debug('called with options %O', options)
|
|
56
|
+
/**
|
|
57
|
+
* Configure reporter for this command
|
|
58
|
+
* reporter lifecycle (start/succeed/fail) is handled by the façade,
|
|
59
|
+
* not by the command.
|
|
60
|
+
*/
|
|
61
|
+
reporter.configure({ quiet: options.quiet, verbose: options.verbose })
|
|
62
|
+
|
|
63
|
+
// Resolve engine: CLI --engine > deprecated --lib > config pdfEngine > default
|
|
64
|
+
if (!options.engine) {
|
|
65
|
+
if (options.lib) {
|
|
66
|
+
// Support deprecated --lib option
|
|
67
|
+
options.engine = options.lib
|
|
68
|
+
} else {
|
|
69
|
+
// Use config setting or fallback to default
|
|
70
|
+
options.engine = this.config.get('pdfEngine') || 'pagedjs'
|
|
71
|
+
}
|
|
92
72
|
}
|
|
93
73
|
|
|
94
|
-
if
|
|
95
|
-
|
|
96
|
-
|
|
74
|
+
// Run build first if --build flag is set and output is missing
|
|
75
|
+
if (options.build && !hasSiteOutput()) {
|
|
76
|
+
this.debug('running build before pdf generation')
|
|
77
|
+
reporter.start('Building site...', { showElapsed: true })
|
|
78
|
+
await eleventy.build({ debug: options.debug })
|
|
79
|
+
reporter.succeed('Build complete')
|
|
97
80
|
}
|
|
98
81
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const pdfLib = await libPdf(options.lib, { ...options, pdfConfig: quireConfig.pdf })
|
|
104
|
-
await pdfLib(publicationInput, coversInput, output)
|
|
82
|
+
// Generate PDF - façade handles validation, progress, and errors
|
|
83
|
+
const pdfOptions = { ...options, lib: options.engine }
|
|
84
|
+
const output = await generatePdf(pdfOptions)
|
|
105
85
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
} catch (error) {
|
|
109
|
-
console.error(`[quire pdf]: ERROR`,error)
|
|
110
|
-
process.exit(1)
|
|
86
|
+
if (options.open) {
|
|
87
|
+
open(output)
|
|
111
88
|
}
|
|
112
89
|
}
|
|
113
90
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
*/
|
|
117
|
-
preAction(command) {
|
|
118
|
-
// testcwd(command)
|
|
91
|
+
preAction(thisCommand, actionCommand) {
|
|
92
|
+
testcwd(thisCommand)
|
|
119
93
|
}
|
|
120
94
|
}
|