@thegetty/quire-cli 1.0.0-rc.44 → 1.0.0-rc.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +0 -36
- package/README.md +0 -9
- package/bin/cli.js +1 -46
- package/package.json +9 -25
- 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/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
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import sinon from 'sinon'
|
|
2
|
-
import test from 'ava'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Pager Helper Tests
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
test.beforeEach((t) => {
|
|
9
|
-
t.context.sandbox = sinon.createSandbox()
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
test.afterEach.always((t) => {
|
|
13
|
-
t.context.sandbox.restore()
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
test.serial('outputWithPaging() writes directly when not a TTY', async (t) => {
|
|
17
|
-
const { sandbox } = t.context
|
|
18
|
-
const originalIsTTY = process.stdout.isTTY
|
|
19
|
-
const stdoutWrite = sandbox.stub(process.stdout, 'write')
|
|
20
|
-
|
|
21
|
-
try {
|
|
22
|
-
// Simulate non-TTY (piped output)
|
|
23
|
-
Object.defineProperty(process.stdout, 'isTTY', { value: false, configurable: true })
|
|
24
|
-
|
|
25
|
-
const { outputWithPaging } = await import('./pager.js')
|
|
26
|
-
await outputWithPaging('Short content')
|
|
27
|
-
|
|
28
|
-
t.true(stdoutWrite.calledWith('Short content\n'))
|
|
29
|
-
} finally {
|
|
30
|
-
Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, configurable: true })
|
|
31
|
-
}
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
test.serial('outputWithPaging() writes directly when content is short', async (t) => {
|
|
35
|
-
const { sandbox } = t.context
|
|
36
|
-
const originalIsTTY = process.stdout.isTTY
|
|
37
|
-
const originalRows = process.stdout.rows
|
|
38
|
-
const stdoutWrite = sandbox.stub(process.stdout, 'write')
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
// Simulate TTY with 24 rows
|
|
42
|
-
Object.defineProperty(process.stdout, 'isTTY', { value: true, configurable: true })
|
|
43
|
-
Object.defineProperty(process.stdout, 'rows', { value: 24, configurable: true })
|
|
44
|
-
|
|
45
|
-
const { outputWithPaging } = await import('./pager.js')
|
|
46
|
-
await outputWithPaging('Line 1\nLine 2\nLine 3')
|
|
47
|
-
|
|
48
|
-
t.true(stdoutWrite.calledWith('Line 1\nLine 2\nLine 3\n'))
|
|
49
|
-
} finally {
|
|
50
|
-
Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, configurable: true })
|
|
51
|
-
Object.defineProperty(process.stdout, 'rows', { value: originalRows, configurable: true })
|
|
52
|
-
}
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
test.serial('outputWithPaging() writes directly when NO_PAGER is set', async (t) => {
|
|
56
|
-
const { sandbox } = t.context
|
|
57
|
-
const originalIsTTY = process.stdout.isTTY
|
|
58
|
-
const originalRows = process.stdout.rows
|
|
59
|
-
const originalNoPager = process.env.NO_PAGER
|
|
60
|
-
const stdoutWrite = sandbox.stub(process.stdout, 'write')
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
// Simulate TTY with small terminal (would normally trigger pager)
|
|
64
|
-
Object.defineProperty(process.stdout, 'isTTY', { value: true, configurable: true })
|
|
65
|
-
Object.defineProperty(process.stdout, 'rows', { value: 5, configurable: true })
|
|
66
|
-
process.env.NO_PAGER = '1'
|
|
67
|
-
|
|
68
|
-
const { outputWithPaging } = await import('./pager.js')
|
|
69
|
-
|
|
70
|
-
// Content longer than terminal height
|
|
71
|
-
const longContent = Array.from({ length: 20 }, (_, i) => `Line ${i + 1}`).join('\n')
|
|
72
|
-
await outputWithPaging(longContent)
|
|
73
|
-
|
|
74
|
-
// Should write directly despite long content, because pager is disabled
|
|
75
|
-
t.true(stdoutWrite.calledWith(longContent + '\n'))
|
|
76
|
-
} finally {
|
|
77
|
-
Object.defineProperty(process.stdout, 'isTTY', { value: originalIsTTY, configurable: true })
|
|
78
|
-
Object.defineProperty(process.stdout, 'rows', { value: originalRows, configurable: true })
|
|
79
|
-
if (originalNoPager === undefined) {
|
|
80
|
-
delete process.env.NO_PAGER
|
|
81
|
-
} else {
|
|
82
|
-
process.env.NO_PAGER = originalNoPager
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
})
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fuzzy string matching for CLI suggestions
|
|
3
|
-
*
|
|
4
|
-
* Uses Damerau-Levenshtein distance (optimal string alignment variant)
|
|
5
|
-
* to find close matches for misspelled user input. The algorithm and
|
|
6
|
-
* parameters match Commander.js's built-in suggestion implementation
|
|
7
|
-
* for consistency.
|
|
8
|
-
*
|
|
9
|
-
* @module helpers/suggest-similar
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const MAX_DISTANCE = 3
|
|
13
|
-
const MIN_SIMILARITY = 0.4
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Calculate Damerau-Levenshtein distance between two strings
|
|
17
|
-
*
|
|
18
|
-
* Supports insertions, deletions, substitutions, and transpositions
|
|
19
|
-
* of adjacent characters. Each substring is edited at most once
|
|
20
|
-
* (optimal string alignment variant).
|
|
21
|
-
*
|
|
22
|
-
* @param {string} a - First string
|
|
23
|
-
* @param {string} b - Second string
|
|
24
|
-
* @returns {number} Edit distance
|
|
25
|
-
* @private
|
|
26
|
-
*/
|
|
27
|
-
function editDistance(a, b) {
|
|
28
|
-
if (Math.abs(a.length - b.length) > MAX_DISTANCE) {
|
|
29
|
-
return Math.max(a.length, b.length)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const d = []
|
|
33
|
-
|
|
34
|
-
for (let i = 0; i <= a.length; ++i) d[i] = [i]
|
|
35
|
-
for (let j = 0; j <= b.length; ++j) d[0][j] = j
|
|
36
|
-
|
|
37
|
-
for (let j = 1; j <= b.length; ++j) {
|
|
38
|
-
for (let i = 1; i <= a.length; ++i) {
|
|
39
|
-
const cost = a[i - 1] === b[j - 1] ? 0 : 1
|
|
40
|
-
d[i][j] = Math.min(
|
|
41
|
-
d[i - 1][j] + 1, // deletion
|
|
42
|
-
d[i][j - 1] + 1, // insertion
|
|
43
|
-
d[i - 1][j - 1] + cost, // substitution
|
|
44
|
-
)
|
|
45
|
-
// transposition
|
|
46
|
-
if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
|
|
47
|
-
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1)
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return d[a.length][b.length]
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Find similar strings from a list of candidates
|
|
57
|
-
*
|
|
58
|
-
* Returns the closest matches by edit distance, filtered by a minimum
|
|
59
|
-
* similarity threshold (40%). Only returns matches at the best distance
|
|
60
|
-
* found (does not mix distances).
|
|
61
|
-
*
|
|
62
|
-
* @param {string} word - The misspelled input
|
|
63
|
-
* @param {string[]} candidates - Valid values to match against
|
|
64
|
-
* @returns {string[]} Sorted array of best matches (empty if none close enough)
|
|
65
|
-
*
|
|
66
|
-
* @example
|
|
67
|
-
* suggestSimilar('gt', ['get', 'set', 'delete', 'reset', 'path'])
|
|
68
|
-
* // → ['get']
|
|
69
|
-
*
|
|
70
|
-
* @example
|
|
71
|
-
* suggestSimilar('logLeve', ['logLevel', 'logPrefix', 'verbose'])
|
|
72
|
-
* // → ['logLevel']
|
|
73
|
-
*/
|
|
74
|
-
export function suggestSimilar(word, candidates) {
|
|
75
|
-
if (!word || !candidates?.length) return []
|
|
76
|
-
|
|
77
|
-
const unique = [...new Set(candidates)]
|
|
78
|
-
let similar = []
|
|
79
|
-
let bestDistance = MAX_DISTANCE
|
|
80
|
-
|
|
81
|
-
for (const candidate of unique) {
|
|
82
|
-
if (candidate.length <= 1) continue
|
|
83
|
-
|
|
84
|
-
const distance = editDistance(word, candidate)
|
|
85
|
-
const length = Math.max(word.length, candidate.length)
|
|
86
|
-
const similarity = (length - distance) / length
|
|
87
|
-
|
|
88
|
-
if (similarity > MIN_SIMILARITY) {
|
|
89
|
-
if (distance < bestDistance) {
|
|
90
|
-
bestDistance = distance
|
|
91
|
-
similar = [candidate]
|
|
92
|
-
} else if (distance === bestDistance) {
|
|
93
|
-
similar.push(candidate)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return similar.sort((a, b) => a.localeCompare(b))
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Format suggestion matches into a user-facing string
|
|
103
|
-
*
|
|
104
|
-
* @param {string[]} matches - Array of suggested matches
|
|
105
|
-
* @returns {string|undefined} Formatted suggestion or undefined if no matches
|
|
106
|
-
*
|
|
107
|
-
* @example
|
|
108
|
-
* formatSuggestion(['logLevel'])
|
|
109
|
-
* // → 'Did you mean: logLevel?'
|
|
110
|
-
*
|
|
111
|
-
* @example
|
|
112
|
-
* formatSuggestion(['get', 'set'])
|
|
113
|
-
* // → 'Did you mean one of: get, set?'
|
|
114
|
-
*
|
|
115
|
-
* @example
|
|
116
|
-
* formatSuggestion([])
|
|
117
|
-
* // → undefined
|
|
118
|
-
*/
|
|
119
|
-
export function formatSuggestion(matches) {
|
|
120
|
-
if (!matches?.length) return undefined
|
|
121
|
-
return (matches.length === 1)
|
|
122
|
-
? `Did you mean: ${matches[0]}?`
|
|
123
|
-
: `Did you mean one of: ${matches.join(', ')}?`
|
|
124
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import test from 'ava'
|
|
2
|
-
import { suggestSimilar, formatSuggestion } from './suggest-similar.js'
|
|
3
|
-
|
|
4
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
-
// suggestSimilar
|
|
6
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
-
|
|
8
|
-
test('suggestSimilar returns match for single-character deletion', (t) => {
|
|
9
|
-
const result = suggestSimilar('gt', ['get', 'set', 'delete', 'reset', 'path'])
|
|
10
|
-
t.deepEqual(result, ['get'])
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
test('suggestSimilar returns match for single-character insertion', (t) => {
|
|
14
|
-
const result = suggestSimilar('geet', ['get', 'set', 'delete'])
|
|
15
|
-
t.deepEqual(result, ['get'])
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
test('suggestSimilar returns match for single-character substitution', (t) => {
|
|
19
|
-
const result = suggestSimilar('gat', ['get', 'set', 'delete'])
|
|
20
|
-
t.deepEqual(result, ['get'])
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
test('suggestSimilar returns match for transposition', (t) => {
|
|
24
|
-
const result = suggestSimilar('gte', ['get', 'set', 'delete'])
|
|
25
|
-
t.deepEqual(result, ['get'])
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
test('suggestSimilar returns match for camelCase near-miss', (t) => {
|
|
29
|
-
const result = suggestSimilar('logLeve', ['logLevel', 'logPrefix', 'verbose'])
|
|
30
|
-
t.deepEqual(result, ['logLevel'])
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
test('suggestSimilar returns match for case difference', (t) => {
|
|
34
|
-
// 'loglevel' vs 'logLevel' differs by one character (L vs l)
|
|
35
|
-
const result = suggestSimilar('loglevel', ['logLevel', 'logPrefix', 'verbose'])
|
|
36
|
-
t.deepEqual(result, ['logLevel'])
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
test('suggestSimilar returns empty array when no candidates are similar', (t) => {
|
|
40
|
-
const result = suggestSimilar('xyzabc', ['get', 'set', 'delete', 'reset', 'path'])
|
|
41
|
-
t.deepEqual(result, [])
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
test('suggestSimilar returns empty array for empty input', (t) => {
|
|
45
|
-
t.deepEqual(suggestSimilar('', ['get', 'set']), [])
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
test('suggestSimilar returns empty array for null input', (t) => {
|
|
49
|
-
t.deepEqual(suggestSimilar(null, ['get', 'set']), [])
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test('suggestSimilar returns empty array for empty candidates', (t) => {
|
|
53
|
-
t.deepEqual(suggestSimilar('get', []), [])
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
test('suggestSimilar returns empty array for null candidates', (t) => {
|
|
57
|
-
t.deepEqual(suggestSimilar('get', null), [])
|
|
58
|
-
})
|
|
59
|
-
|
|
60
|
-
test('suggestSimilar excludes single-character candidates', (t) => {
|
|
61
|
-
// 'g' is distance 0 from 'g', but should be excluded
|
|
62
|
-
const result = suggestSimilar('g', ['g', 'get', 'set'])
|
|
63
|
-
t.deepEqual(result, [])
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
test('suggestSimilar deduplicates candidates', (t) => {
|
|
67
|
-
const result = suggestSimilar('gt', ['get', 'get', 'get'])
|
|
68
|
-
t.deepEqual(result, ['get'])
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
test('suggestSimilar returns multiple matches sorted alphabetically', (t) => {
|
|
72
|
-
// 'bild' is distance 1 from 'bind' and 'build' — both should match
|
|
73
|
-
// but 'build' is distance 1 (deletion), 'bind' is distance 2 (substitution + deletion)
|
|
74
|
-
// Let's use a case where two candidates have equal distance
|
|
75
|
-
const result = suggestSimilar('delet', ['delete', 'reset'])
|
|
76
|
-
// 'delet' → 'delete' distance 1, 'delet' → 'reset' distance 2
|
|
77
|
-
t.deepEqual(result, ['delete'])
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
test('suggestSimilar returns multiple equally-close matches', (t) => {
|
|
81
|
-
// Both 'bar' and 'baz' are distance 1 from 'bax'
|
|
82
|
-
const result = suggestSimilar('bax', ['bar', 'baz', 'foo'])
|
|
83
|
-
t.deepEqual(result, ['bar', 'baz'])
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
87
|
-
// formatSuggestion
|
|
88
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
test('formatSuggestion returns undefined for empty array', (t) => {
|
|
91
|
-
t.is(formatSuggestion([]), undefined)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
test('formatSuggestion returns undefined for null', (t) => {
|
|
95
|
-
t.is(formatSuggestion(null), undefined)
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
test('formatSuggestion returns undefined for undefined', (t) => {
|
|
99
|
-
t.is(formatSuggestion(undefined), undefined)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
test('formatSuggestion formats single match', (t) => {
|
|
103
|
-
t.is(formatSuggestion(['logLevel']), 'Did you mean: logLevel?')
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
test('formatSuggestion formats multiple matches', (t) => {
|
|
107
|
-
t.is(formatSuggestion(['bar', 'baz']), 'Did you mean one of: bar, baz?')
|
|
108
|
-
})
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
import test from 'ava'
|
|
2
|
-
import { Volume, createFsFromVolume } from 'memfs'
|
|
3
|
-
import sinon from 'sinon'
|
|
4
|
-
import esmock from 'esmock'
|
|
5
|
-
import { NotInProjectError } from '#src/errors/index.js'
|
|
6
|
-
|
|
7
|
-
test.beforeEach((t) => {
|
|
8
|
-
// Create in-memory file system
|
|
9
|
-
t.context.vol = new Volume()
|
|
10
|
-
t.context.fs = createFsFromVolume(t.context.vol)
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
test.afterEach.always((t) => {
|
|
14
|
-
// Restore all mocks if sandbox exists
|
|
15
|
-
if (t.context.sandbox) {
|
|
16
|
-
t.context.sandbox.restore()
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Clear in-memory file system
|
|
20
|
-
t.context.vol.reset()
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
test.serial('testcwd should not error when called in a Quire project directory', async (t) => {
|
|
24
|
-
const { fs, vol } = t.context
|
|
25
|
-
|
|
26
|
-
// Create sandbox for this test
|
|
27
|
-
const sandbox = sinon.createSandbox()
|
|
28
|
-
t.context.sandbox = sandbox
|
|
29
|
-
|
|
30
|
-
// Setup a Quire project directory with .eleventy.js
|
|
31
|
-
vol.fromJSON({
|
|
32
|
-
'/quire-project/.eleventy.js': 'module.exports = function() {}',
|
|
33
|
-
'/quire-project/content/_data/config.yaml': 'title: Test Project',
|
|
34
|
-
'/quire-project/package.json': JSON.stringify({ name: 'test-project' })
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
// Create stubs
|
|
38
|
-
const cwdStub = sandbox.stub(process, 'cwd').returns('/quire-project')
|
|
39
|
-
|
|
40
|
-
// Create mock process object with stubbed methods
|
|
41
|
-
const mockProcess = {
|
|
42
|
-
cwd: cwdStub
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Mock detect module with memfs
|
|
46
|
-
const mockDetect = await esmock('../lib/project/detect.js', {
|
|
47
|
-
'node:fs': fs
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
// Mock node:fs and node:process to use memfs and stubbed process
|
|
51
|
-
const testcwd = await esmock('./test-cwd.js', {
|
|
52
|
-
'node:process': mockProcess,
|
|
53
|
-
'#lib/project/index.js': {
|
|
54
|
-
detect: mockDetect
|
|
55
|
-
}
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
// Create mock command
|
|
59
|
-
const mockCommand = {
|
|
60
|
-
name: sandbox.stub().returns('build')
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Call testcwd - should not throw
|
|
64
|
-
t.notThrows(() => testcwd(mockCommand))
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test.serial('testcwd should throw NotInProjectError when called outside a Quire project directory', async (t) => {
|
|
68
|
-
const { fs, vol } = t.context
|
|
69
|
-
|
|
70
|
-
// Create sandbox for this test
|
|
71
|
-
const sandbox = sinon.createSandbox()
|
|
72
|
-
t.context.sandbox = sandbox
|
|
73
|
-
|
|
74
|
-
// Setup a non-Quire directory (no .eleventy.js or other Quire marker files)
|
|
75
|
-
vol.fromJSON({
|
|
76
|
-
'/not-quire/some-file.txt': 'not a quire project',
|
|
77
|
-
'/not-quire/package.json': JSON.stringify({ name: 'not-a-quire-project' })
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
// Create stubs
|
|
81
|
-
const cwdStub = sandbox.stub(process, 'cwd').returns('/not-quire')
|
|
82
|
-
|
|
83
|
-
// Create mock process object with stubbed methods
|
|
84
|
-
const mockProcess = {
|
|
85
|
-
cwd: cwdStub
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Mock detect module with memfs
|
|
89
|
-
const mockDetect = await esmock('../lib/project/detect.js', {
|
|
90
|
-
'node:fs': fs
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
// Mock node:fs and node:process to use memfs and stubbed process
|
|
94
|
-
const testcwd = await esmock('./test-cwd.js', {
|
|
95
|
-
'node:process': mockProcess,
|
|
96
|
-
'#lib/project/index.js': {
|
|
97
|
-
detect: mockDetect
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
// Create mock command
|
|
102
|
-
const mockCommand = {
|
|
103
|
-
name: sandbox.stub().returns('build')
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Call testcwd - should throw NotInProjectError
|
|
107
|
-
const error = t.throws(() => testcwd(mockCommand), { instanceOf: NotInProjectError })
|
|
108
|
-
t.is(error.code, 'NOT_IN_PROJECT')
|
|
109
|
-
t.is(error.exitCode, 2)
|
|
110
|
-
t.true(error.message.includes('build'))
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
test.serial('testcwd should include command name in error message', async (t) => {
|
|
114
|
-
const { fs, vol } = t.context
|
|
115
|
-
|
|
116
|
-
// Create sandbox for this test
|
|
117
|
-
const sandbox = sinon.createSandbox()
|
|
118
|
-
t.context.sandbox = sandbox
|
|
119
|
-
|
|
120
|
-
// Setup a non-Quire directory
|
|
121
|
-
vol.fromJSON({
|
|
122
|
-
'/non-quire-directory/file.txt': 'test'
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
// Create stubs
|
|
126
|
-
const cwdStub = sandbox.stub(process, 'cwd').returns('/non-quire-directory')
|
|
127
|
-
|
|
128
|
-
// Create mock process object with stubbed methods
|
|
129
|
-
const mockProcess = {
|
|
130
|
-
cwd: cwdStub
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Mock detect module with memfs
|
|
134
|
-
const mockDetect = await esmock('../lib/project/detect.js', {
|
|
135
|
-
'node:fs': fs
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
// Mock node:fs and node:process to use memfs and stubbed process
|
|
139
|
-
const testcwd = await esmock('./test-cwd.js', {
|
|
140
|
-
'node:process': mockProcess,
|
|
141
|
-
'#lib/project/index.js': {
|
|
142
|
-
detect: mockDetect
|
|
143
|
-
}
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
// Create mock command with specific name
|
|
147
|
-
const mockCommand = {
|
|
148
|
-
name: sandbox.stub().returns('clean')
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Call testcwd - should throw with command name in message
|
|
152
|
-
const error = t.throws(() => testcwd(mockCommand), { instanceOf: NotInProjectError })
|
|
153
|
-
t.true(error.message.includes('clean'), 'error message should include command name')
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
test.serial('testcwd should work when command is null', async (t) => {
|
|
157
|
-
const { fs, vol } = t.context
|
|
158
|
-
|
|
159
|
-
// Create sandbox for this test
|
|
160
|
-
const sandbox = sinon.createSandbox()
|
|
161
|
-
t.context.sandbox = sandbox
|
|
162
|
-
|
|
163
|
-
// Setup a non-Quire directory
|
|
164
|
-
vol.fromJSON({
|
|
165
|
-
'/not-a-quire-directory/file.txt': 'test'
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
// Create stubs
|
|
169
|
-
const cwdStub = sandbox.stub(process, 'cwd').returns('/not-a-quire-directory')
|
|
170
|
-
|
|
171
|
-
// Create mock process object with stubbed methods
|
|
172
|
-
const mockProcess = {
|
|
173
|
-
cwd: cwdStub
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Mock detect module with memfs
|
|
177
|
-
const mockDetect = await esmock('../lib/project/detect.js', {
|
|
178
|
-
'node:fs': fs
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
// Mock node:fs and node:process to use memfs and stubbed process
|
|
182
|
-
const testcwd = await esmock('./test-cwd.js', {
|
|
183
|
-
'node:process': mockProcess,
|
|
184
|
-
'#lib/project/index.js': {
|
|
185
|
-
detect: mockDetect
|
|
186
|
-
}
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
// Call testcwd with null command - should throw but not crash
|
|
190
|
-
const error = t.throws(() => testcwd(null), { instanceOf: NotInProjectError })
|
|
191
|
-
t.is(error.exitCode, 2)
|
|
192
|
-
})
|