@thegetty/quire-cli 1.0.0-rc.36 → 1.0.0-rc.37
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/package.json +1 -1
- package/src/commands/build.js +6 -14
- package/src/commands/build.test.js +2 -7
- package/src/commands/preview.js +8 -2
- package/src/lib/11ty/api.js +25 -6
- package/src/lib/11ty/cli.js +17 -5
- package/src/lib/logger/debug.js +43 -1
- package/src/lib/logger/debug.test.js +128 -0
package/package.json
CHANGED
package/src/commands/build.js
CHANGED
|
@@ -49,20 +49,12 @@ Note: Run before "quire pdf" or "quire epub" commands.
|
|
|
49
49
|
// Configure reporter for this command
|
|
50
50
|
reporter.configure({ quiet: options.quiet, verbose: options.verbose })
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
} else {
|
|
59
|
-
this.debug('running eleventy using lib/11ty cli')
|
|
60
|
-
await cli.build(options)
|
|
61
|
-
}
|
|
62
|
-
reporter.succeed('Build complete')
|
|
63
|
-
} catch (error) {
|
|
64
|
-
reporter.fail('Build failed')
|
|
65
|
-
throw error
|
|
52
|
+
if (options['11ty'] === 'api') {
|
|
53
|
+
this.debug('running eleventy using lib/11ty api')
|
|
54
|
+
await api.build(options)
|
|
55
|
+
} else {
|
|
56
|
+
this.debug('running eleventy using lib/11ty cli')
|
|
57
|
+
await cli.build(options)
|
|
66
58
|
}
|
|
67
59
|
}
|
|
68
60
|
|
|
@@ -108,8 +108,6 @@ test('build command should call eleventy CLI with default options', async (t) =>
|
|
|
108
108
|
t.true(mockClean.called, 'clean should be called in preAction')
|
|
109
109
|
t.false(mockEleventyApi.build.called, 'eleventy API build should not be called')
|
|
110
110
|
t.true(mockEleventyCli.build.called, 'eleventy CLI build should be called')
|
|
111
|
-
t.true(mockReporter.start.called, 'reporter.start should be called')
|
|
112
|
-
t.true(mockReporter.succeed.called, 'reporter.succeed should be called')
|
|
113
111
|
})
|
|
114
112
|
|
|
115
113
|
test('build command should call eleventy API when 11ty option is "api"', async (t) => {
|
|
@@ -282,7 +280,7 @@ test('build command should pass options to eleventy build', async (t) => {
|
|
|
282
280
|
t.true(mockEleventyCli.build.calledWith(options), 'eleventy CLI build should be called with options')
|
|
283
281
|
})
|
|
284
282
|
|
|
285
|
-
test('build command should
|
|
283
|
+
test('build command should propagate errors from eleventy build', async (t) => {
|
|
286
284
|
const { sandbox, fs, mockLogger, mockReporter } = t.context
|
|
287
285
|
|
|
288
286
|
const buildError = new Error('Build failed')
|
|
@@ -334,11 +332,8 @@ test('build command should call reporter.fail when build fails', async (t) => {
|
|
|
334
332
|
command.name = sandbox.stub().returns('build')
|
|
335
333
|
|
|
336
334
|
// Run action and expect it to throw
|
|
335
|
+
// Nota bene: reporter.start/succeed/fail are called by the lib layer, not the command
|
|
337
336
|
await t.throwsAsync(() => command.action({ '11ty': 'api' }, command), { message: 'Build failed' })
|
|
338
|
-
|
|
339
|
-
t.true(mockReporter.start.called, 'reporter.start should be called')
|
|
340
|
-
t.true(mockReporter.fail.called, 'reporter.fail should be called on error')
|
|
341
|
-
t.false(mockReporter.succeed.called, 'reporter.succeed should not be called on error')
|
|
342
337
|
})
|
|
343
338
|
|
|
344
339
|
test('build command should configure reporter with quiet option', async (t) => {
|
package/src/commands/preview.js
CHANGED
|
@@ -2,6 +2,7 @@ import Command from '#src/Command.js'
|
|
|
2
2
|
import { Option } from 'commander'
|
|
3
3
|
import { withOutputModes } from '#lib/commander/index.js'
|
|
4
4
|
import { api, cli } from '#lib/11ty/index.js'
|
|
5
|
+
import reporter from '#lib/reporter/index.js'
|
|
5
6
|
import testcwd from '#helpers/test-cwd.js'
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -22,11 +23,13 @@ export default class PreviewCommand extends Command {
|
|
|
22
23
|
Examples:
|
|
23
24
|
quire preview Start preview server on default port
|
|
24
25
|
quire preview --port 3000 Run on custom port
|
|
26
|
+
quire preview --open Start server and open in browser
|
|
25
27
|
quire preview --verbose Start with detailed progress
|
|
26
28
|
`,
|
|
27
29
|
version: '1.1.0',
|
|
28
30
|
options: [
|
|
29
31
|
[ '-p, --port <port>', 'configure development server port', 8080 ],
|
|
32
|
+
[ '--open', 'open in default browser when server starts' ],
|
|
30
33
|
// Use Option object syntax to configure this as a hidden option
|
|
31
34
|
new Option('--11ty <module>', 'use the specified 11ty module')
|
|
32
35
|
.choices(['api', 'cli']).default('api').hideHelp(),
|
|
@@ -40,12 +43,15 @@ Examples:
|
|
|
40
43
|
async action(options, command) {
|
|
41
44
|
this.debug('called with options %O', options)
|
|
42
45
|
|
|
46
|
+
// Configure reporter for this command
|
|
47
|
+
reporter.configure({ quiet: options.quiet, verbose: options.verbose })
|
|
48
|
+
|
|
43
49
|
if (options['11ty'] === 'api') {
|
|
44
50
|
this.debug('running eleventy using lib/11ty api')
|
|
45
|
-
api.serve(options)
|
|
51
|
+
await api.serve(options)
|
|
46
52
|
} else {
|
|
47
53
|
this.debug('running eleventy using lib/11ty cli')
|
|
48
|
-
cli.serve(options)
|
|
54
|
+
await cli.serve(options)
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
|
package/src/lib/11ty/api.js
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
import { dynamicImport } from '#helpers/os-utils.js'
|
|
34
34
|
import path from 'node:path'
|
|
35
35
|
import paths from '#lib/project/index.js'
|
|
36
|
-
import
|
|
36
|
+
import reporter from '#lib/reporter/index.js'
|
|
37
37
|
import createDebug from '#debug'
|
|
38
38
|
|
|
39
39
|
const debug = createDebug('lib:11ty:api')
|
|
@@ -172,15 +172,21 @@ class Quire11ty {
|
|
|
172
172
|
const projectRoot = this.paths.getProjectRoot()
|
|
173
173
|
process.chdir(projectRoot)
|
|
174
174
|
|
|
175
|
-
logger.info('Building site...')
|
|
176
|
-
|
|
177
175
|
configureEleventyEnv({ mode: 'production', debug: options.debug })
|
|
178
176
|
|
|
177
|
+
reporter.start('Building site...', { showElapsed: true })
|
|
178
|
+
|
|
179
179
|
const eleventy = await createEleventyInstance(options)
|
|
180
180
|
|
|
181
181
|
eleventy.setDryRun(options.dryRun)
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
try {
|
|
184
|
+
await eleventy.write()
|
|
185
|
+
reporter.succeed('Build complete')
|
|
186
|
+
} catch (error) {
|
|
187
|
+
reporter.fail('Build failed')
|
|
188
|
+
throw error
|
|
189
|
+
}
|
|
184
190
|
}
|
|
185
191
|
|
|
186
192
|
/**
|
|
@@ -196,10 +202,10 @@ class Quire11ty {
|
|
|
196
202
|
const projectRoot = this.paths.getProjectRoot()
|
|
197
203
|
process.chdir(projectRoot)
|
|
198
204
|
|
|
199
|
-
logger.info('Starting development server...')
|
|
200
|
-
|
|
201
205
|
configureEleventyEnv({ mode: 'development', debug: options.debug })
|
|
202
206
|
|
|
207
|
+
reporter.start('Starting development server...')
|
|
208
|
+
|
|
203
209
|
const eleventy =
|
|
204
210
|
await createEleventyInstance({ ...options, runMode: 'serve' })
|
|
205
211
|
|
|
@@ -209,6 +215,19 @@ class Quire11ty {
|
|
|
209
215
|
// Initialize Eleventy before serving (required for eleventyServe)
|
|
210
216
|
await eleventy.init()
|
|
211
217
|
|
|
218
|
+
// Register a ready callback to resolve the spinner when the server is listening
|
|
219
|
+
// @see https://www.11ty.dev/docs/dev-server/#options
|
|
220
|
+
eleventy.eleventyServe.config.serverOptions = {
|
|
221
|
+
...eleventy.eleventyServe.config.serverOptions,
|
|
222
|
+
ready: (server) => {
|
|
223
|
+
const url = server.getServerUrl('localhost')
|
|
224
|
+
reporter.succeed(`Server running at ${url}`)
|
|
225
|
+
if (options.open) {
|
|
226
|
+
import('open').then(({ default: open }) => open(url))
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
|
|
212
231
|
await eleventy.serve(options.port)
|
|
213
232
|
}
|
|
214
233
|
}
|
package/src/lib/11ty/cli.js
CHANGED
|
@@ -3,8 +3,8 @@ import fs from 'node:fs'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import paths from '#lib/project/index.js'
|
|
5
5
|
import processManager from '#lib/process/manager.js'
|
|
6
|
+
import reporter from '#lib/reporter/index.js'
|
|
6
7
|
import { BuildFailedError } from '#src/errors/index.js'
|
|
7
|
-
import { logger } from '#lib/logger/index.js'
|
|
8
8
|
import createDebug from '#debug'
|
|
9
9
|
|
|
10
10
|
const debug = createDebug('lib:11ty')
|
|
@@ -108,14 +108,14 @@ export default {
|
|
|
108
108
|
* @param {Object} options - Build options
|
|
109
109
|
*/
|
|
110
110
|
build: async (options = {}) => {
|
|
111
|
-
logger.info('Building site...')
|
|
112
|
-
|
|
113
111
|
const { command, env, projectRoot } = factory(options)
|
|
114
112
|
|
|
115
113
|
if (options.dryRun) command.push('--dryrun')
|
|
116
114
|
|
|
117
115
|
env.ELEVENTY_ENV = 'production'
|
|
118
116
|
|
|
117
|
+
reporter.start('Building site...', { showElapsed: true })
|
|
118
|
+
|
|
119
119
|
try {
|
|
120
120
|
const build = spawn(command, { cwd: projectRoot, env })
|
|
121
121
|
await build
|
|
@@ -123,11 +123,13 @@ export default {
|
|
|
123
123
|
if (build.exitCode !== 0) {
|
|
124
124
|
throw new BuildFailedError(`Eleventy exited with code ${build.exitCode}`)
|
|
125
125
|
}
|
|
126
|
+
reporter.succeed('Build complete')
|
|
126
127
|
} catch (error) {
|
|
127
128
|
if (error.isCanceled) {
|
|
128
129
|
debug('build cancelled')
|
|
129
130
|
return
|
|
130
131
|
}
|
|
132
|
+
reporter.fail('Build failed')
|
|
131
133
|
throw error
|
|
132
134
|
}
|
|
133
135
|
},
|
|
@@ -137,16 +139,26 @@ export default {
|
|
|
137
139
|
* @param {Object} options - Serve options
|
|
138
140
|
*/
|
|
139
141
|
serve: async (options = {}) => {
|
|
140
|
-
logger.info('Starting development server...')
|
|
141
|
-
|
|
142
142
|
const { command, env, projectRoot } = factory(options)
|
|
143
143
|
|
|
144
144
|
command.push('--serve')
|
|
145
145
|
|
|
146
|
+
const port = options.port || 8080
|
|
146
147
|
if (options.port) command.push(`--port=${options.port}`)
|
|
147
148
|
|
|
148
149
|
env.ELEVENTY_ENV = 'development'
|
|
149
150
|
|
|
151
|
+
reporter.start('Starting development server...')
|
|
152
|
+
|
|
153
|
+
// Resolve the spinner before the subprocess takes over stdout
|
|
154
|
+
const url = `http://localhost:${port}`
|
|
155
|
+
reporter.succeed(`Server running at ${url}`)
|
|
156
|
+
|
|
157
|
+
if (options.open) {
|
|
158
|
+
const { default: open } = await import('open')
|
|
159
|
+
open(url)
|
|
160
|
+
}
|
|
161
|
+
|
|
150
162
|
try {
|
|
151
163
|
await spawn(command, { cwd: projectRoot, env })
|
|
152
164
|
} catch (error) {
|
package/src/lib/logger/debug.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import util from 'node:util'
|
|
1
2
|
import debug from 'debug'
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -6,6 +7,10 @@ import debug from 'debug'
|
|
|
6
7
|
* Creates debug instances with the `quire:` namespace prefix.
|
|
7
8
|
* Uses the standard `debug` package for namespace-based filtering.
|
|
8
9
|
*
|
|
10
|
+
* Object formatting:
|
|
11
|
+
* %O Pretty-printed, indented on a new line (multi-line)
|
|
12
|
+
* %o Compact single-line output (built-in default)
|
|
13
|
+
*
|
|
9
14
|
* @example
|
|
10
15
|
* // In a module (short alias)
|
|
11
16
|
* import createDebug from '#debug'
|
|
@@ -22,6 +27,42 @@ import debug from 'debug'
|
|
|
22
27
|
* @see https://www.npmjs.com/package/debug
|
|
23
28
|
*/
|
|
24
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Override %O formatter to produce indented multi-line output
|
|
32
|
+
*
|
|
33
|
+
* Pretty-prints objects with each line indented by 2 spaces,
|
|
34
|
+
* starting on a new line after the label.
|
|
35
|
+
*
|
|
36
|
+
* Use %o (lowercase) for compact single-line output.
|
|
37
|
+
*/
|
|
38
|
+
debug.formatters.O = function (value) {
|
|
39
|
+
this.inspectOpts.colors = this.useColors
|
|
40
|
+
const string = util.inspect(value, this.inspectOpts)
|
|
41
|
+
return '\n' + string.split('\n').map((line) => ' ' + line).join('\n')
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Override formatArgs to prevent per-line namespace prefixing
|
|
46
|
+
*
|
|
47
|
+
* The default debug formatArgs (in color mode) splits the formatted
|
|
48
|
+
* message on newlines and inserts the namespace prefix before every
|
|
49
|
+
* line. This makes multi-line %O output noisy. This override only
|
|
50
|
+
* prefixes the first line, letting continuation lines flow cleanly.
|
|
51
|
+
*/
|
|
52
|
+
const originalFormatArgs = debug.formatArgs
|
|
53
|
+
debug.formatArgs = function (args) {
|
|
54
|
+
if (this.useColors) {
|
|
55
|
+
const c = this.color
|
|
56
|
+
const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c)
|
|
57
|
+
const prefix = ` ${colorCode};1m${this.namespace} \u001B[0m`
|
|
58
|
+
|
|
59
|
+
args[0] = prefix + args[0]
|
|
60
|
+
args.push(colorCode + 'm+' + debug.humanize(this.diff) + '\u001B[0m')
|
|
61
|
+
} else {
|
|
62
|
+
originalFormatArgs.call(this, args)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
25
66
|
/**
|
|
26
67
|
* Root namespace for all Quire CLI debug output
|
|
27
68
|
*/
|
|
@@ -37,7 +78,8 @@ export const DEBUG_NAMESPACE = 'quire'
|
|
|
37
78
|
* const debug = createDebug('lib:pdf:paged')
|
|
38
79
|
* debug('printer options: %O', options)
|
|
39
80
|
* // Output (when DEBUG=quire:lib:pdf:paged):
|
|
40
|
-
* // quire:lib:pdf:paged printer options:
|
|
81
|
+
* // quire:lib:pdf:paged printer options:
|
|
82
|
+
* // { format: 'A4', landscape: false } +0ms
|
|
41
83
|
*/
|
|
42
84
|
export default function createDebug(namespace) {
|
|
43
85
|
return debug(`${DEBUG_NAMESPACE}:${namespace}`)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import test from 'ava'
|
|
2
|
+
import debug from 'debug'
|
|
3
|
+
// Import to register formatter and formatArgs overrides
|
|
4
|
+
import './debug.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Debug formatter and output tests
|
|
8
|
+
*
|
|
9
|
+
* Verifies the custom %O formatter produces indented multi-line output,
|
|
10
|
+
* the %o formatter remains compact single-line, and the formatArgs
|
|
11
|
+
* override prevents per-line namespace prefixing in colored output.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Helper to call a debug formatter with a value
|
|
16
|
+
*
|
|
17
|
+
* Formatters are called with `this` bound to a debugger-like context.
|
|
18
|
+
* We provide the minimal context the formatters expect.
|
|
19
|
+
*/
|
|
20
|
+
function callFormatter(specifier, value) {
|
|
21
|
+
const formatter = debug.formatters[specifier]
|
|
22
|
+
const context = {
|
|
23
|
+
useColors: false,
|
|
24
|
+
inspectOpts: { colors: false },
|
|
25
|
+
}
|
|
26
|
+
return formatter.call(context, value)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
// %O formatter (pretty-printed, indented)
|
|
31
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
test('%O output starts with a newline', (t) => {
|
|
34
|
+
const result = callFormatter('O', { a: 1 })
|
|
35
|
+
t.true(result.startsWith('\n'))
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('%O output lines are indented with 2 spaces', (t) => {
|
|
39
|
+
const result = callFormatter('O', { a: 1, b: 2 })
|
|
40
|
+
const lines = result.split('\n').slice(1) // skip leading empty line
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
t.true(line.startsWith(' '), `Line not indented: "${line}"`)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('%O formats nested objects with indentation', (t) => {
|
|
47
|
+
const result = callFormatter('O', { outer: { inner: 'value' } })
|
|
48
|
+
t.true(result.includes('outer'))
|
|
49
|
+
t.true(result.includes('inner'))
|
|
50
|
+
// Every line after the leading newline should be indented
|
|
51
|
+
const lines = result.split('\n').slice(1)
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
t.true(line.startsWith(' '), `Line not indented: "${line}"`)
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('%O handles null without error', (t) => {
|
|
58
|
+
const result = callFormatter('O', null)
|
|
59
|
+
t.true(result.includes('null'))
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('%O handles undefined without error', (t) => {
|
|
63
|
+
const result = callFormatter('O', undefined)
|
|
64
|
+
t.true(result.includes('undefined'))
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('%O handles primitive values', (t) => {
|
|
68
|
+
t.true(callFormatter('O', 42).includes('42'))
|
|
69
|
+
t.true(callFormatter('O', 'hello').includes('hello'))
|
|
70
|
+
t.true(callFormatter('O', true).includes('true'))
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('%O handles arrays', (t) => {
|
|
74
|
+
const result = callFormatter('O', [1, 2, 3])
|
|
75
|
+
t.true(result.includes('1'))
|
|
76
|
+
t.true(result.includes('2'))
|
|
77
|
+
t.true(result.includes('3'))
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
81
|
+
// %o formatter (compact single-line, built-in)
|
|
82
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
test('%o output is single-line', (t) => {
|
|
85
|
+
const result = callFormatter('o', { a: 1, b: 2, c: 3 })
|
|
86
|
+
t.false(result.includes('\n'))
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('%o does not start with a newline', (t) => {
|
|
90
|
+
const result = callFormatter('o', { a: 1 })
|
|
91
|
+
t.false(result.startsWith('\n'))
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
95
|
+
// formatArgs override (prefix only on first line)
|
|
96
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Helper to call formatArgs and capture the result
|
|
100
|
+
*
|
|
101
|
+
* Creates a minimal debugger-like context to test formatArgs behavior.
|
|
102
|
+
* In color mode, the default debug formatArgs would split on newlines
|
|
103
|
+
* and insert the prefix before every line. Our override prevents this.
|
|
104
|
+
*/
|
|
105
|
+
function callFormatArgs(message, useColors = true) {
|
|
106
|
+
const args = [message]
|
|
107
|
+
const context = {
|
|
108
|
+
namespace: 'test',
|
|
109
|
+
useColors,
|
|
110
|
+
color: 5,
|
|
111
|
+
diff: 0,
|
|
112
|
+
}
|
|
113
|
+
debug.formatArgs.call(context, args)
|
|
114
|
+
return args[0]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
test('formatArgs prefixes the first line in color mode', (t) => {
|
|
118
|
+
const result = callFormatArgs('hello')
|
|
119
|
+
t.true(result.includes('test'))
|
|
120
|
+
t.true(result.includes('hello'))
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('formatArgs does not duplicate prefix on multi-line output', (t) => {
|
|
124
|
+
const result = callFormatArgs('paths:\n { a: 1 }\n { b: 2 }')
|
|
125
|
+
// Namespace should appear exactly once
|
|
126
|
+
const matches = result.match(/test/g)
|
|
127
|
+
t.is(matches.length, 1)
|
|
128
|
+
})
|