@thegetty/quire-cli 1.0.0-rc.45 → 1.0.0-rc.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -36
- package/README.md +0 -9
- package/bin/cli.js +1 -46
- package/package.json +10 -27
- package/src/Command.js +6 -26
- package/src/commands/README.md +122 -213
- package/src/commands/build.js +37 -45
- package/src/commands/clean.js +19 -33
- package/src/commands/conf.js +43 -0
- package/src/commands/create.js +14 -42
- package/src/commands/epub.js +31 -67
- package/src/commands/index.js +2 -9
- package/src/commands/info.js +86 -88
- package/src/commands/pdf.js +82 -63
- package/src/commands/preview.js +31 -32
- package/src/commands/validate.js +20 -64
- package/src/commands/version.js +43 -0
- package/src/errors/validation/validation-error.js +11 -20
- package/src/helpers/clean.js +1 -1
- package/src/helpers/is-quire.js +31 -0
- package/src/helpers/test-cwd.js +6 -5
- package/src/helpers/which.js +4 -10
- package/src/lib/11ty/README.md +19 -135
- package/src/lib/11ty/api.js +93 -235
- package/src/lib/11ty/cli.js +37 -112
- package/src/lib/11ty/index.js +5 -64
- package/src/lib/11ty/paths.js +103 -0
- package/src/lib/README.md +0 -330
- package/src/lib/conf/README.md +69 -86
- package/src/lib/conf/config.js +3 -5
- package/src/lib/conf/defaults.js +0 -65
- package/src/lib/conf/schema.js +9 -103
- package/src/lib/epub/README.md +2 -133
- package/src/lib/epub/epub.js +11 -36
- package/src/lib/epub/index.js +21 -111
- package/src/lib/epub/pandoc.js +4 -33
- package/src/lib/git/README.md +2 -151
- package/src/lib/git/index.js +13 -217
- package/src/lib/i18n/README.md +3 -0
- package/src/lib/i18n/config.js +53 -0
- package/src/lib/i18n/index.js +43 -0
- package/src/lib/i18n/localeService.js +58 -0
- package/src/lib/pdf/README.md +0 -131
- package/src/lib/pdf/index.js +21 -124
- package/src/lib/pdf/paged.js +52 -100
- package/src/lib/pdf/prince.js +37 -115
- package/src/lib/pdf/split.js +33 -61
- package/src/lib/quire/README.md +19 -0
- package/src/lib/quire/index.js +353 -0
- package/src/lib/reporter/README.md +2 -211
- package/src/lib/reporter/index.js +0 -607
- package/src/main.js +48 -221
- package/src/packageConfig.js +0 -17
- package/src/validators/utils.js +1 -2
- package/man/quire.1 +0 -341
- package/man/quire.1.md +0 -232
- package/schemas/config.schema.json +0 -194
- package/schemas/figures.schema.json +0 -56
- package/schemas/layout.schema.json +0 -5
- package/schemas/objects.schema.json +0 -62
- package/schemas/publication.schema.json +0 -140
- package/schemas/references.schema.json +0 -29
- package/src/Command.spec.js +0 -99
- package/src/commands/build.spec.js +0 -113
- package/src/commands/build.test.js +0 -397
- package/src/commands/clean.spec.js +0 -118
- package/src/commands/clean.test.js +0 -382
- package/src/commands/config.js +0 -238
- package/src/commands/config.spec.js +0 -107
- package/src/commands/config.test.js +0 -845
- package/src/commands/create.spec.js +0 -112
- package/src/commands/create.test.js +0 -415
- package/src/commands/doctor.js +0 -251
- package/src/commands/doctor.spec.js +0 -115
- package/src/commands/doctor.test.js +0 -1409
- package/src/commands/epub.spec.js +0 -114
- package/src/commands/epub.test.js +0 -503
- package/src/commands/help.js +0 -60
- package/src/commands/help.test.js +0 -132
- package/src/commands/info.spec.js +0 -72
- package/src/commands/info.test.js +0 -512
- package/src/commands/pdf.spec.js +0 -114
- package/src/commands/pdf.test.js +0 -464
- package/src/commands/preview.spec.js +0 -97
- package/src/commands/preview.test.js +0 -250
- package/src/commands/use.js +0 -56
- package/src/commands/use.spec.js +0 -61
- package/src/commands/use.test.js +0 -280
- package/src/commands/validate.spec.js +0 -90
- package/src/commands/validate.test.js +0 -378
- package/src/commands/workflows.js +0 -70
- package/src/errors/build/build-failed-error.js +0 -19
- package/src/errors/build/config-field-missing-error.js +0 -20
- package/src/errors/build/config-file-not-found-error.js +0 -20
- package/src/errors/build/index.js +0 -11
- package/src/errors/help/help-topic-not-found-error.js +0 -30
- package/src/errors/help/index.js +0 -8
- package/src/errors/index.js +0 -58
- package/src/errors/input/index.js +0 -8
- package/src/errors/input/invalid-input-error.js +0 -21
- package/src/errors/install/dependency-install-error.js +0 -19
- package/src/errors/install/directory-not-empty-error.js +0 -25
- package/src/errors/install/index.js +0 -12
- package/src/errors/install/invalid-path-error.js +0 -31
- package/src/errors/install/invalid-starter-error.js +0 -27
- package/src/errors/install/version-not-found-error.js +0 -35
- package/src/errors/output/epub-generation-error.js +0 -19
- package/src/errors/output/index.js +0 -14
- package/src/errors/output/invalid-epub-library-error.js +0 -20
- package/src/errors/output/invalid-pdf-library-error.js +0 -20
- package/src/errors/output/missing-build-output-error.js +0 -21
- package/src/errors/output/pdf-generation-error.js +0 -24
- package/src/errors/output/tool-not-found-error.js +0 -37
- package/src/errors/project/index.js +0 -10
- package/src/errors/project/not-in-project-error.js +0 -20
- package/src/errors/project/project-create-error.js +0 -21
- package/src/errors/quire-error.js +0 -27
- package/src/errors/validation/index.js +0 -3
- package/src/errors/validation/unknown-config-key-error.js +0 -27
- package/src/errors/validation/unknown-config-operation-error.js +0 -24
- package/src/helpers/docs-url.js +0 -32
- package/src/helpers/pager.js +0 -59
- package/src/helpers/pager.test.js +0 -85
- package/src/helpers/suggest-similar.js +0 -124
- package/src/helpers/suggest-similar.test.js +0 -108
- package/src/helpers/test-cwd.test.js +0 -192
- package/src/lib/11ty/index.test.js +0 -655
- package/src/lib/commander/index.js +0 -103
- package/src/lib/commander/index.test.js +0 -86
- package/src/lib/commander/options.js +0 -237
- package/src/lib/commander/options.test.js +0 -205
- package/src/lib/conf/build-status.js +0 -103
- package/src/lib/conf/build-status.test.js +0 -247
- package/src/lib/conf/config.test.js +0 -281
- package/src/lib/conf/format.js +0 -60
- package/src/lib/conf/format.test.js +0 -106
- package/src/lib/conf/helpers.js +0 -91
- package/src/lib/conf/helpers.test.js +0 -136
- package/src/lib/conf/index.js +0 -22
- package/src/lib/constants.js +0 -28
- package/src/lib/doctor/README.md +0 -667
- package/src/lib/doctor/checks/environment/cli-version.js +0 -62
- package/src/lib/doctor/checks/environment/cli-version.test.js +0 -132
- package/src/lib/doctor/checks/environment/git-available.js +0 -77
- package/src/lib/doctor/checks/environment/git-available.test.js +0 -52
- package/src/lib/doctor/checks/environment/index.js +0 -13
- package/src/lib/doctor/checks/environment/node-version.js +0 -66
- package/src/lib/doctor/checks/environment/node-version.test.js +0 -17
- package/src/lib/doctor/checks/environment/npm-available.js +0 -66
- package/src/lib/doctor/checks/environment/npm-available.test.js +0 -52
- package/src/lib/doctor/checks/environment/os-info.js +0 -48
- package/src/lib/doctor/checks/environment/os-info.test.js +0 -81
- package/src/lib/doctor/checks/environment/runtime-info.js +0 -51
- package/src/lib/doctor/checks/environment/runtime-info.test.js +0 -132
- package/src/lib/doctor/checks/outputs/epub-output.js +0 -119
- package/src/lib/doctor/checks/outputs/epub-output.test.js +0 -277
- package/src/lib/doctor/checks/outputs/index.js +0 -10
- package/src/lib/doctor/checks/outputs/pdf-output.js +0 -144
- package/src/lib/doctor/checks/outputs/pdf-output.test.js +0 -377
- package/src/lib/doctor/checks/outputs/stale-build.js +0 -122
- package/src/lib/doctor/checks/outputs/stale-build.test.js +0 -282
- package/src/lib/doctor/checks/project/data-files.js +0 -56
- package/src/lib/doctor/checks/project/data-files.test.js +0 -125
- package/src/lib/doctor/checks/project/dependencies.js +0 -53
- package/src/lib/doctor/checks/project/dependencies.test.js +0 -71
- package/src/lib/doctor/checks/project/index.js +0 -11
- package/src/lib/doctor/checks/project/quire-11ty.js +0 -98
- package/src/lib/doctor/checks/project/quire-11ty.test.js +0 -170
- package/src/lib/doctor/checks/project/quire-project.js +0 -38
- package/src/lib/doctor/checks/project/quire-project.test.js +0 -47
- package/src/lib/doctor/checks/tools/index.js +0 -10
- package/src/lib/doctor/checks/tools/pandoc-available.js +0 -82
- package/src/lib/doctor/checks/tools/pandoc-available.test.js +0 -73
- package/src/lib/doctor/checks/tools/prince-available.js +0 -81
- package/src/lib/doctor/checks/tools/prince-available.test.js +0 -73
- package/src/lib/doctor/constants.js +0 -39
- package/src/lib/doctor/formatDuration.js +0 -108
- package/src/lib/doctor/formatDuration.test.js +0 -76
- package/src/lib/doctor/formatters/human.js +0 -257
- package/src/lib/doctor/formatters/human.test.js +0 -463
- package/src/lib/doctor/formatters/index.js +0 -8
- package/src/lib/doctor/formatters/json.js +0 -78
- package/src/lib/doctor/formatters/json.test.js +0 -174
- package/src/lib/doctor/formatters/shared.js +0 -129
- package/src/lib/doctor/formatters/shared.test.js +0 -194
- package/src/lib/doctor/index.js +0 -271
- package/src/lib/doctor/index.test.js +0 -797
- package/src/lib/epub/engines.js +0 -46
- package/src/lib/epub/index.test.js +0 -518
- package/src/lib/epub/pandoc.test.js +0 -122
- package/src/lib/epub/schema.js +0 -21
- package/src/lib/error/README.md +0 -170
- package/src/lib/error/handler.js +0 -107
- package/src/lib/git/index.spec.js +0 -80
- package/src/lib/git/index.test.js +0 -453
- package/src/lib/help/frontmatter.js +0 -77
- package/src/lib/help/frontmatter.test.js +0 -139
- package/src/lib/help/index.js +0 -146
- package/src/lib/help/index.test.js +0 -188
- package/src/lib/help/topics/configuration.md +0 -77
- package/src/lib/help/topics/debugging.md +0 -69
- package/src/lib/help/topics/epub.md +0 -74
- package/src/lib/help/topics/pdf.md +0 -74
- package/src/lib/help/topics/publishing.md +0 -80
- package/src/lib/help/topics/workflows.md +0 -50
- package/src/lib/installer/index.js +0 -309
- package/src/lib/installer/index.spec.js +0 -83
- package/src/lib/installer/index.test.js +0 -545
- package/src/lib/logger/README.md +0 -424
- package/src/lib/logger/debug.js +0 -132
- package/src/lib/logger/debug.spec.js +0 -130
- package/src/lib/logger/debug.test.js +0 -128
- package/src/lib/logger/index.js +0 -228
- package/src/lib/logger/index.spec.js +0 -131
- package/src/lib/logger/index.test.js +0 -578
- package/src/lib/npm/README.md +0 -127
- package/src/lib/npm/index.js +0 -198
- package/src/lib/npm/index.spec.js +0 -60
- package/src/lib/npm/index.test.js +0 -355
- package/src/lib/pdf/engines.js +0 -46
- package/src/lib/pdf/index.test.js +0 -708
- package/src/lib/pdf/paged.test.js +0 -366
- package/src/lib/pdf/prince.test.js +0 -202
- package/src/lib/pdf/schema.js +0 -21
- package/src/lib/pdf/split.test.js +0 -445
- package/src/lib/platform.js +0 -95
- package/src/lib/process/manager.js +0 -110
- package/src/lib/process/manager.test.js +0 -55
- package/src/lib/project/build.js +0 -162
- package/src/lib/project/build.test.js +0 -275
- package/src/lib/project/config.js +0 -48
- package/src/lib/project/config.test.js +0 -134
- package/src/lib/project/detect.js +0 -47
- package/src/lib/project/detect.test.js +0 -157
- package/src/lib/project/index.js +0 -56
- package/src/lib/project/output-paths.js +0 -87
- package/src/lib/project/output-paths.test.js +0 -66
- package/src/lib/project/paths.js +0 -272
- package/src/lib/project/version.js +0 -110
- package/src/lib/project/version.test.js +0 -350
- package/src/lib/reporter/index.test.js +0 -862
- package/src/main.spec.js +0 -61
- package/src/main.test.js +0 -347
- package/src/validators/validate-data-files.js +0 -154
- package/src/validators/validate-data-files.test.js +0 -217
package/src/main.spec.js
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander'
|
|
2
|
-
import program from './main.js'
|
|
3
|
-
import test from 'ava'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Main Program Unit Tests
|
|
7
|
-
*
|
|
8
|
-
* Tests the CLI program configuration and setup.
|
|
9
|
-
* Command registration is tested indirectly through command spec files.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
test('program exports a Commander.js Command instance', (t) => {
|
|
13
|
-
t.truthy(program)
|
|
14
|
-
t.true(program instanceof Command, 'program should be a Commander.js Command')
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test('program has correct name and description', (t) => {
|
|
18
|
-
t.is(program.name(), 'quire')
|
|
19
|
-
t.truthy(program.description(), 'program should have a description')
|
|
20
|
-
t.regex(program.description(), /quire/i, 'description should mention Quire')
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
test('program has version option with correct flags', (t) => {
|
|
24
|
-
// Commander stores _version and the version option in the options array
|
|
25
|
-
t.truthy(program._version, 'program should have version set')
|
|
26
|
-
|
|
27
|
-
// Find the version option
|
|
28
|
-
const versionOption = program.options.find((opt) => opt.long === '--version')
|
|
29
|
-
t.truthy(versionOption, '--version option should exist')
|
|
30
|
-
t.is(versionOption.short, '-v', 'version option should have -v short flag')
|
|
31
|
-
t.truthy(versionOption.description, 'version option should have description')
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
test('program help is configured with correct width', (t) => {
|
|
35
|
-
// Access the help configuration
|
|
36
|
-
const helpConfig = program._helpConfiguration
|
|
37
|
-
|
|
38
|
-
t.truthy(helpConfig, 'help configuration should exist')
|
|
39
|
-
t.is(helpConfig.helpWidth, 80, 'help width should be 80')
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
test('program registers commands from commands/index.js', (t) => {
|
|
43
|
-
// Verify commands are registered
|
|
44
|
-
t.true(program.commands.length > 0, 'program should have registered commands')
|
|
45
|
-
|
|
46
|
-
// Verify expected commands exist
|
|
47
|
-
const commandNames = program.commands.map((cmd) => cmd.name())
|
|
48
|
-
t.true(commandNames.includes('build'), 'build command should be registered')
|
|
49
|
-
t.true(commandNames.includes('new'), 'new command should be registered')
|
|
50
|
-
t.true(commandNames.includes('preview'), 'preview command should be registered')
|
|
51
|
-
t.true(commandNames.includes('info'), 'info command should be registered')
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
test('program injects config into subcommands', (t) => {
|
|
55
|
-
// Pick a registered command and verify dependency injection of the config
|
|
56
|
-
const buildCommand = program.commands.find((cmd) => cmd.name() === 'build')
|
|
57
|
-
|
|
58
|
-
t.truthy(buildCommand, 'build command should exist')
|
|
59
|
-
t.truthy(buildCommand.config, 'subcommand should have config injected')
|
|
60
|
-
t.is(typeof buildCommand.config.get, 'function', 'config should have get method')
|
|
61
|
-
})
|
package/src/main.test.js
DELETED
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
import test from 'ava'
|
|
2
|
-
import sinon from 'sinon'
|
|
3
|
-
import esmock from 'esmock'
|
|
4
|
-
import QuireError from '#src/errors/quire-error.js'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Main Program Integration Tests
|
|
8
|
-
*
|
|
9
|
-
* Tests the runtime behavior of the CLI program, including
|
|
10
|
-
* error handling in command lifecycle hooks.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
test.beforeEach((t) => {
|
|
14
|
-
t.context.sandbox = sinon.createSandbox()
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
test.afterEach.always((t) => {
|
|
18
|
-
t.context.sandbox.restore()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Helper to create a mock command with configurable hooks
|
|
23
|
-
*/
|
|
24
|
-
function createMockCommand(overrides = {}) {
|
|
25
|
-
return {
|
|
26
|
-
name: 'mock-command',
|
|
27
|
-
description: 'Mock command for integration tests',
|
|
28
|
-
summary: 'mocked command',
|
|
29
|
-
action: async () => {},
|
|
30
|
-
...overrides
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
test('preAction hook errors are passed to handleError', async (t) => {
|
|
35
|
-
const { sandbox } = t.context
|
|
36
|
-
|
|
37
|
-
const testError = new QuireError('Test preAction error', {
|
|
38
|
-
code: 'TEST_ERROR',
|
|
39
|
-
exitCode: 42
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
const mockHandleError = sandbox.stub()
|
|
43
|
-
|
|
44
|
-
const mockCommand = createMockCommand({
|
|
45
|
-
preAction: () => {
|
|
46
|
-
throw testError
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
// Use esmock to inject our mock command and handleError
|
|
51
|
-
await esmock('./main.js', {
|
|
52
|
-
'#src/commands/index.js': {
|
|
53
|
-
default: [mockCommand]
|
|
54
|
-
},
|
|
55
|
-
'#lib/error/handler.js': {
|
|
56
|
-
handleError: mockHandleError
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
// The program is created at import time, so we need to trigger the hook
|
|
61
|
-
// by parsing a command. Import the mocked program.
|
|
62
|
-
const { default: program } = await esmock('./main.js', {
|
|
63
|
-
'#src/commands/index.js': {
|
|
64
|
-
default: [mockCommand]
|
|
65
|
-
},
|
|
66
|
-
'#lib/error/handler.js': {
|
|
67
|
-
handleError: mockHandleError
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
// Configure Commander to not exit on error during tests
|
|
72
|
-
program.exitOverride()
|
|
73
|
-
program.configureOutput({
|
|
74
|
-
writeErr: () => {},
|
|
75
|
-
writeOut: () => {}
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
// Parse the test command to trigger preAction
|
|
79
|
-
try {
|
|
80
|
-
await program.parseAsync(['node', 'quire', 'mock-command'])
|
|
81
|
-
} catch {
|
|
82
|
-
// Commander may throw after our error is handled
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
t.true(mockHandleError.called, 'handleError should be called for preAction errors')
|
|
86
|
-
t.is(mockHandleError.firstCall.args[0], testError, 'handleError should receive the thrown error')
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
test('postAction hook errors are passed to handleError', async (t) => {
|
|
90
|
-
const { sandbox } = t.context
|
|
91
|
-
|
|
92
|
-
const testError = new QuireError('Test postAction error', {
|
|
93
|
-
code: 'TEST_POST_ERROR',
|
|
94
|
-
exitCode: 43
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
const mockHandleError = sandbox.stub()
|
|
98
|
-
|
|
99
|
-
const mockCommand = createMockCommand({
|
|
100
|
-
action: async () => {
|
|
101
|
-
// Action succeeds
|
|
102
|
-
},
|
|
103
|
-
postAction: () => {
|
|
104
|
-
throw testError
|
|
105
|
-
}
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
const { default: program } = await esmock('./main.js', {
|
|
109
|
-
'#src/commands/index.js': {
|
|
110
|
-
default: [mockCommand]
|
|
111
|
-
},
|
|
112
|
-
'#lib/error/handler.js': {
|
|
113
|
-
handleError: mockHandleError
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
program.exitOverride()
|
|
118
|
-
program.configureOutput({
|
|
119
|
-
writeErr: () => {},
|
|
120
|
-
writeOut: () => {}
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
await program.parseAsync(['node', 'quire', 'mock-command'])
|
|
125
|
-
} catch {
|
|
126
|
-
// Commander may throw after our error is handled
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
t.true(mockHandleError.called, 'handleError should be called for postAction errors')
|
|
130
|
-
t.is(mockHandleError.firstCall.args[0], testError, 'handleError should receive the thrown error')
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
test('action errors are passed to handleError', async (t) => {
|
|
134
|
-
const { sandbox } = t.context
|
|
135
|
-
|
|
136
|
-
const testError = new QuireError('Test action error', {
|
|
137
|
-
code: 'TEST_ACTION_ERROR',
|
|
138
|
-
exitCode: 44
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
const mockHandleError = sandbox.stub()
|
|
142
|
-
|
|
143
|
-
const mockCommand = createMockCommand({
|
|
144
|
-
action: async () => {
|
|
145
|
-
throw testError
|
|
146
|
-
}
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
const { default: program } = await esmock('./main.js', {
|
|
150
|
-
'#src/commands/index.js': {
|
|
151
|
-
default: [mockCommand]
|
|
152
|
-
},
|
|
153
|
-
'#lib/error/handler.js': {
|
|
154
|
-
handleError: mockHandleError
|
|
155
|
-
}
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
program.exitOverride()
|
|
159
|
-
program.configureOutput({
|
|
160
|
-
writeErr: () => {},
|
|
161
|
-
writeOut: () => {}
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
await program.parseAsync(['node', 'quire', 'mock-command'])
|
|
166
|
-
} catch {
|
|
167
|
-
// Commander may throw after our error is handled
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
t.true(mockHandleError.called, 'handleError should be called for action errors')
|
|
171
|
-
t.is(mockHandleError.firstCall.args[0], testError, 'handleError should receive the thrown error')
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
test('preAction hook preserves this context for command methods', async (t) => {
|
|
175
|
-
const { sandbox } = t.context
|
|
176
|
-
|
|
177
|
-
const debugSpy = sandbox.spy()
|
|
178
|
-
let thisContext = null
|
|
179
|
-
|
|
180
|
-
const mockCommand = createMockCommand({
|
|
181
|
-
debug: debugSpy,
|
|
182
|
-
preAction: function () {
|
|
183
|
-
thisContext = this
|
|
184
|
-
this.debug('preAction called')
|
|
185
|
-
}
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
const { default: program } = await esmock('./main.js', {
|
|
189
|
-
'#src/commands/index.js': {
|
|
190
|
-
default: [mockCommand]
|
|
191
|
-
},
|
|
192
|
-
'#lib/error/handler.js': {
|
|
193
|
-
handleError: sandbox.stub()
|
|
194
|
-
}
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
program.exitOverride()
|
|
198
|
-
program.configureOutput({
|
|
199
|
-
writeErr: () => {},
|
|
200
|
-
writeOut: () => {}
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
await program.parseAsync(['node', 'quire', 'mock-command'])
|
|
205
|
-
} catch {
|
|
206
|
-
// Ignore Commander errors
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
t.is(thisContext, mockCommand, 'this should be bound to the command instance')
|
|
210
|
-
t.true(debugSpy.called, 'this.debug should be callable')
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
test('postAction hook preserves this context for command methods', async (t) => {
|
|
214
|
-
const { sandbox } = t.context
|
|
215
|
-
|
|
216
|
-
const debugSpy = sandbox.spy()
|
|
217
|
-
let thisContext = null
|
|
218
|
-
|
|
219
|
-
const mockCommand = createMockCommand({
|
|
220
|
-
debug: debugSpy,
|
|
221
|
-
action: async () => {},
|
|
222
|
-
postAction: function () {
|
|
223
|
-
thisContext = this
|
|
224
|
-
this.debug('postAction called')
|
|
225
|
-
}
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
const { default: program } = await esmock('./main.js', {
|
|
229
|
-
'#src/commands/index.js': {
|
|
230
|
-
default: [mockCommand]
|
|
231
|
-
},
|
|
232
|
-
'#lib/error/handler.js': {
|
|
233
|
-
handleError: sandbox.stub()
|
|
234
|
-
}
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
program.exitOverride()
|
|
238
|
-
program.configureOutput({
|
|
239
|
-
writeErr: () => {},
|
|
240
|
-
writeOut: () => {}
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
try {
|
|
244
|
-
await program.parseAsync(['node', 'quire', 'mock-command'])
|
|
245
|
-
} catch {
|
|
246
|
-
// Ignore Commander errors
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
t.is(thisContext, mockCommand, 'this should be bound to the command instance')
|
|
250
|
-
t.true(debugSpy.called, 'this.debug should be callable')
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
test('async preAction hooks are awaited before action runs', async (t) => {
|
|
254
|
-
const { sandbox } = t.context
|
|
255
|
-
|
|
256
|
-
const callOrder = []
|
|
257
|
-
|
|
258
|
-
const mockCommand = createMockCommand({
|
|
259
|
-
preAction: async () => {
|
|
260
|
-
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
261
|
-
callOrder.push('preAction')
|
|
262
|
-
},
|
|
263
|
-
action: async () => {
|
|
264
|
-
callOrder.push('action')
|
|
265
|
-
}
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
const { default: program } = await esmock('./main.js', {
|
|
269
|
-
'#src/commands/index.js': {
|
|
270
|
-
default: [mockCommand]
|
|
271
|
-
},
|
|
272
|
-
'#lib/error/handler.js': {
|
|
273
|
-
handleError: sandbox.stub()
|
|
274
|
-
}
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
program.exitOverride()
|
|
278
|
-
program.configureOutput({
|
|
279
|
-
writeErr: () => {},
|
|
280
|
-
writeOut: () => {}
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
try {
|
|
284
|
-
await program.parseAsync(['node', 'quire', 'mock-command'])
|
|
285
|
-
} catch {
|
|
286
|
-
// Ignore Commander errors
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
t.deepEqual(callOrder, ['preAction', 'action'], 'preAction should complete before action starts')
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
test('async postAction hooks are awaited after action completes', async (t) => {
|
|
293
|
-
const { sandbox } = t.context
|
|
294
|
-
|
|
295
|
-
const callOrder = []
|
|
296
|
-
|
|
297
|
-
const mockCommand = createMockCommand({
|
|
298
|
-
action: async () => {
|
|
299
|
-
callOrder.push('action')
|
|
300
|
-
},
|
|
301
|
-
postAction: async () => {
|
|
302
|
-
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
303
|
-
callOrder.push('postAction')
|
|
304
|
-
}
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
const { default: program } = await esmock('./main.js', {
|
|
308
|
-
'#src/commands/index.js': {
|
|
309
|
-
default: [mockCommand]
|
|
310
|
-
},
|
|
311
|
-
'#lib/error/handler.js': {
|
|
312
|
-
handleError: sandbox.stub()
|
|
313
|
-
}
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
program.exitOverride()
|
|
317
|
-
program.configureOutput({
|
|
318
|
-
writeErr: () => {},
|
|
319
|
-
writeOut: () => {}
|
|
320
|
-
})
|
|
321
|
-
|
|
322
|
-
try {
|
|
323
|
-
await program.parseAsync(['node', 'quire', 'mock-command'])
|
|
324
|
-
} catch {
|
|
325
|
-
// Ignore Commander errors
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
t.deepEqual(callOrder, ['action', 'postAction'], 'postAction should run after action completes')
|
|
329
|
-
})
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Nota bene: preSubcommand hook tests are skipped because:
|
|
333
|
-
* 1. `main` does not process a `subcommands` array in command definitions
|
|
334
|
-
* 2. No commands in the CLI currently use nested subcommands
|
|
335
|
-
* 3. The wrapped error handler pattern is identical to preAction/postAction
|
|
336
|
-
*
|
|
337
|
-
* @todo When nested subcommands are added, these tests should be implemented.
|
|
338
|
-
*/
|
|
339
|
-
test.skip('preSubcommand hook errors are passed to handleError', async (t) => {
|
|
340
|
-
// Requires main.js to support nested subcommands in command definitions
|
|
341
|
-
t.pass()
|
|
342
|
-
})
|
|
343
|
-
|
|
344
|
-
test.skip('preSubcommand hook preserves this context for command methods', async (t) => {
|
|
345
|
-
// Requires main.js to support nested subcommands in command definitions
|
|
346
|
-
t.pass()
|
|
347
|
-
})
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Data files validation for Quire projects
|
|
3
|
-
*
|
|
4
|
-
* Validates YAML files in content/_data/ directory:
|
|
5
|
-
* - Required files exist (publication.yaml)
|
|
6
|
-
* - YAML syntax is correct
|
|
7
|
-
* - Files conform to their JSON schemas (when schema exists)
|
|
8
|
-
* - No duplicate IDs in arrays
|
|
9
|
-
*
|
|
10
|
-
* @module validators/validate-data-files
|
|
11
|
-
*/
|
|
12
|
-
import fs from 'node:fs'
|
|
13
|
-
import path from 'node:path'
|
|
14
|
-
import yaml from 'js-yaml'
|
|
15
|
-
import Ajv from 'ajv'
|
|
16
|
-
import addFormats from 'ajv-formats'
|
|
17
|
-
import { DATA_DIR, REQUIRED_DATA_FILES } from '#lib/project/index.js'
|
|
18
|
-
import { getSchemaForDocument, checkForDuplicateIds } from './utils.js'
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Validation result for a single file
|
|
22
|
-
* @typedef {Object} FileValidationResult
|
|
23
|
-
* @property {string} file - Filename
|
|
24
|
-
* @property {boolean} valid - Whether the file is valid
|
|
25
|
-
* @property {string[]} errors - List of error messages
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Validation result for all data files
|
|
30
|
-
* @typedef {Object} DataFilesValidationResult
|
|
31
|
-
* @property {boolean} valid - Whether all files are valid
|
|
32
|
-
* @property {string[]} errors - All error messages across all files
|
|
33
|
-
* @property {number} fileCount - Number of YAML files found
|
|
34
|
-
* @property {FileValidationResult[]} files - Per-file validation results
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Validate a single YAML file
|
|
39
|
-
*
|
|
40
|
-
* @param {string} filePath - Absolute path to the YAML file
|
|
41
|
-
* @returns {FileValidationResult} Validation result
|
|
42
|
-
*/
|
|
43
|
-
export function validateYamlFile(filePath) {
|
|
44
|
-
const errors = []
|
|
45
|
-
const filename = path.basename(filePath)
|
|
46
|
-
|
|
47
|
-
// Check file exists
|
|
48
|
-
if (!fs.existsSync(filePath)) {
|
|
49
|
-
return { file: filename, valid: false, errors: [`File not found: ${filename}`] }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Read and parse YAML
|
|
53
|
-
let doc
|
|
54
|
-
try {
|
|
55
|
-
const content = fs.readFileSync(filePath, 'utf8')
|
|
56
|
-
doc = yaml.load(content)
|
|
57
|
-
} catch (error) {
|
|
58
|
-
const loc = error.mark ? ` at line ${error.mark.line + 1}` : ''
|
|
59
|
-
errors.push(`${filename}: YAML syntax error${loc} - ${error.reason || error.message}`)
|
|
60
|
-
return { file: filename, valid: false, errors }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Validate against schema if one exists
|
|
64
|
-
const schema = getSchemaForDocument(filePath)
|
|
65
|
-
if (schema) {
|
|
66
|
-
const ajv = new Ajv({ allErrors: true })
|
|
67
|
-
addFormats(ajv)
|
|
68
|
-
const validate = ajv.compile(schema)
|
|
69
|
-
const valid = validate(doc)
|
|
70
|
-
|
|
71
|
-
if (!valid) {
|
|
72
|
-
for (const err of validate.errors) {
|
|
73
|
-
const errPath = err.instancePath || '(root)'
|
|
74
|
-
errors.push(`${filename}: ${errPath} ${err.message}`)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Check for duplicate IDs using the existing utility
|
|
80
|
-
// Note: checkForDuplicateIds throws, so we need to catch
|
|
81
|
-
try {
|
|
82
|
-
checkForDuplicateIds(doc, filePath)
|
|
83
|
-
} catch (error) {
|
|
84
|
-
// Extract the duplicate ID info from the error
|
|
85
|
-
const match = error.reason?.match(/Duplicate IDs found: (.+)/)
|
|
86
|
-
if (match) {
|
|
87
|
-
errors.push(`${filename}: duplicate IDs found: ${match[1]}`)
|
|
88
|
-
} else {
|
|
89
|
-
errors.push(`${filename}: ${error.reason || error.message}`)
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return { file: filename, valid: errors.length === 0, errors }
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Validate all data files in content/_data/
|
|
98
|
-
*
|
|
99
|
-
* @returns {DataFilesValidationResult} Validation result
|
|
100
|
-
*/
|
|
101
|
-
export function validateDataFiles() {
|
|
102
|
-
const allErrors = []
|
|
103
|
-
const fileResults = []
|
|
104
|
-
|
|
105
|
-
// Check if data directory exists
|
|
106
|
-
if (!fs.existsSync(DATA_DIR)) {
|
|
107
|
-
return {
|
|
108
|
-
valid: true,
|
|
109
|
-
errors: [],
|
|
110
|
-
fileCount: 0,
|
|
111
|
-
files: [],
|
|
112
|
-
notInProject: true,
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Check required files exist
|
|
117
|
-
for (const required of REQUIRED_DATA_FILES) {
|
|
118
|
-
const filePath = path.join(DATA_DIR, required)
|
|
119
|
-
if (!fs.existsSync(filePath)) {
|
|
120
|
-
allErrors.push(`Required file missing: ${required}`)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Find all YAML files
|
|
125
|
-
let yamlFiles = []
|
|
126
|
-
try {
|
|
127
|
-
yamlFiles = fs.readdirSync(DATA_DIR)
|
|
128
|
-
.filter((file) => file.endsWith('.yaml') || file.endsWith('.yml'))
|
|
129
|
-
.map((file) => path.join(DATA_DIR, file))
|
|
130
|
-
} catch (error) {
|
|
131
|
-
return {
|
|
132
|
-
valid: false,
|
|
133
|
-
errors: [`Cannot read ${DATA_DIR}: ${error.message}`],
|
|
134
|
-
fileCount: 0,
|
|
135
|
-
files: [],
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Validate each file
|
|
140
|
-
for (const filePath of yamlFiles) {
|
|
141
|
-
const result = validateYamlFile(filePath)
|
|
142
|
-
fileResults.push(result)
|
|
143
|
-
allErrors.push(...result.errors)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return {
|
|
147
|
-
valid: allErrors.length === 0,
|
|
148
|
-
errors: allErrors,
|
|
149
|
-
fileCount: yamlFiles.length,
|
|
150
|
-
files: fileResults,
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export default validateDataFiles
|