@jutge.org/toolkit 4.4.1 → 4.4.5
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/dist/index.js +420 -419
- package/package.json +2 -1
- package/toolkit/lint.ts +46 -44
- package/toolkit/make.ts +338 -26
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jutge.org/toolkit",
|
|
3
3
|
"description": "Toolkit to prepare problems for Jutge.org",
|
|
4
|
-
"version": "4.4.
|
|
4
|
+
"version": "4.4.5",
|
|
5
5
|
"homepage": "https://jutge.org",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Jutge.org",
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"boxen": "^8.0.1",
|
|
70
70
|
"bun-types": "^1.3.7",
|
|
71
71
|
"chalk": "^5.6.2",
|
|
72
|
+
"chokidar": "^5.0.0",
|
|
72
73
|
"cli-highlight": "^2.1.11",
|
|
73
74
|
"commander": "^14.0.2",
|
|
74
75
|
"dayjs": "^1.11.19",
|
package/toolkit/lint.ts
CHANGED
|
@@ -1,63 +1,65 @@
|
|
|
1
1
|
import { Command } from '@commander-js/extra-typings'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
|
-
import { lintDirectories, type LintIssue } from '../lib/lint'
|
|
3
|
+
import { lintDirectories, type LintIssue, type LintResult } from '../lib/lint'
|
|
4
|
+
import tui from '../lib/tui'
|
|
5
|
+
import { nothing } from '../lib/utils'
|
|
4
6
|
|
|
5
|
-
function
|
|
7
|
+
export function formatLintIssue(issue: LintIssue): string {
|
|
6
8
|
const prefix = issue.severity === 'error' ? chalk.red('error') : chalk.yellow('warning')
|
|
7
9
|
const path = issue.path ? chalk.gray(` (${issue.path})`) : ''
|
|
8
10
|
return ` ${prefix} ${issue.code}: ${issue.message}${path}`
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
export
|
|
12
|
-
.summary('Lint a problem directory')
|
|
13
|
-
.description(
|
|
14
|
-
'Check problem.yml/handler.yml schema, required files present, naming conventions, statement structure, sample vs public test consistency, etc.',
|
|
15
|
-
)
|
|
16
|
-
.argument('[directories...]', 'problem directories to lint (default: current directory)')
|
|
17
|
-
.option('-d, --directory <path>', 'problem directory when no arguments given', '.')
|
|
18
|
-
.action(async (directories: string[], { directory }) => {
|
|
19
|
-
const dirs = directories.length > 0 ? directories : [directory]
|
|
20
|
-
const results = await lintDirectories(dirs)
|
|
21
|
-
|
|
22
|
-
if (results.length === 0) {
|
|
23
|
-
console.log(chalk.yellow('No problem directories found (looked for handler.yml in the given path(s)).'))
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
|
|
13
|
+
export async function printLintResults(results: LintResult[], directories: string[]): Promise<{ hasError: boolean }> {
|
|
27
14
|
let hasError = false
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const errors = result.issues.filter((i) => i.severity === 'error')
|
|
32
|
-
const warnings = result.issues.filter((i) => i.severity === 'warning')
|
|
33
|
-
if (errors.length > 0) hasError = true
|
|
34
|
-
if (warnings.length > 0) hasWarning = true
|
|
15
|
+
for (const result of results) {
|
|
16
|
+
const errors = result.issues.filter((i) => i.severity === 'error')
|
|
17
|
+
if (errors.length > 0) hasError = true
|
|
35
18
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
19
|
+
const dirLabel = result.directory === directories[0] && results.length === 1 ? result.directory : result.directory
|
|
20
|
+
if (result.issues.length === 0) {
|
|
21
|
+
tui.print(chalk.green('✓') + ' ' + dirLabel + ' ' + chalk.gray('— no issues'))
|
|
22
|
+
} else {
|
|
23
|
+
tui.print()
|
|
24
|
+
await tui.section(`Linting ${dirLabel}`, async () => {
|
|
25
|
+
await nothing()
|
|
42
26
|
for (const issue of result.issues) {
|
|
43
|
-
|
|
27
|
+
tui.print(formatLintIssue(issue))
|
|
44
28
|
}
|
|
45
|
-
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (results.length > 1) {
|
|
34
|
+
tui.print()
|
|
35
|
+
const totalErrors = results.reduce((s, r) => s + r.issues.filter((i) => i.severity === 'error').length, 0)
|
|
36
|
+
const totalWarnings = results.reduce((s, r) => s + r.issues.filter((i) => i.severity === 'warning').length, 0)
|
|
37
|
+
if (totalErrors > 0 || totalWarnings > 0) {
|
|
38
|
+
tui.gray(
|
|
39
|
+
`Total: ${totalErrors} error(s), ${totalWarnings} warning(s) across ${results.length} problem(s)`,
|
|
40
|
+
)
|
|
46
41
|
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { hasError }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const lintCmd = new Command('lint')
|
|
48
|
+
.summary('Lint a problem directory')
|
|
49
|
+
|
|
50
|
+
.argument('[directories...]', 'problem directories to lint (default: current directory)')
|
|
51
|
+
.option('-d, --directory <path>', 'problem directory when no arguments given', '.')
|
|
52
|
+
|
|
53
|
+
.action(async (directories: string[], { directory }) => {
|
|
54
|
+
const dirs = directories.length > 0 ? directories : [directory]
|
|
55
|
+
const results = await lintDirectories(dirs)
|
|
47
56
|
|
|
48
|
-
if (results.length
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const totalWarnings = results.reduce((s, r) => s + r.issues.filter((i) => i.severity === 'warning').length, 0)
|
|
52
|
-
if (totalErrors > 0 || totalWarnings > 0) {
|
|
53
|
-
console.log(
|
|
54
|
-
chalk.gray(
|
|
55
|
-
`Total: ${totalErrors} error(s), ${totalWarnings} warning(s) across ${results.length} problem(s)`,
|
|
56
|
-
),
|
|
57
|
-
)
|
|
58
|
-
}
|
|
57
|
+
if (results.length === 0) {
|
|
58
|
+
tui.warning('No problem directories found (looked for handler.yml in the given path(s)).')
|
|
59
|
+
return
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
const { hasError } = await printLintResults(results, dirs)
|
|
61
63
|
if (hasError) {
|
|
62
64
|
process.exitCode = 1
|
|
63
65
|
}
|
package/toolkit/make.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { Command } from '@commander-js/extra-typings'
|
|
2
|
-
import
|
|
2
|
+
import chokidar from 'chokidar'
|
|
3
|
+
import { basename, dirname, join, resolve } from 'path'
|
|
3
4
|
import { findRealDirectories } from '../lib/helpers'
|
|
4
|
-
import {
|
|
5
|
+
import { lintDirectories } from '../lib/lint'
|
|
6
|
+
import { printLintResults } from './lint'
|
|
7
|
+
import { newMaker, type Maker } from '../lib/maker'
|
|
8
|
+
import type { Problem } from '../lib/problem'
|
|
5
9
|
import tui from '../lib/tui'
|
|
6
10
|
import { nothing, projectDir } from '../lib/utils'
|
|
11
|
+
import chalk from 'chalk'
|
|
12
|
+
|
|
13
|
+
const WATCH_DEBOUNCE_MS = 300
|
|
7
14
|
|
|
8
15
|
export const makeCmd = new Command('make')
|
|
9
16
|
.description('Make problem')
|
|
@@ -13,8 +20,12 @@ export const makeCmd = new Command('make')
|
|
|
13
20
|
.option('-i, --ignore-errors', 'ignore errors on a directory and continue processing', false)
|
|
14
21
|
.option('-e, --only-errors', 'only show errors at the final summary', false)
|
|
15
22
|
.option('-p, --problem_nm <problem_nm>', 'problem nm', 'DRAFT')
|
|
23
|
+
.option('-w, --watch', 'watch for changes and rebuild incrementally (under development)', false)
|
|
16
24
|
|
|
17
|
-
.action(async (tasks, { directories, ignoreErrors, onlyErrors, problem_nm }) => {
|
|
25
|
+
.action(async (tasks, { directories, ignoreErrors, onlyErrors, problem_nm, watch }) => {
|
|
26
|
+
if (watch) {
|
|
27
|
+
tasks = ['all']
|
|
28
|
+
}
|
|
18
29
|
if (tasks.length === 0) {
|
|
19
30
|
tasks = ['all']
|
|
20
31
|
}
|
|
@@ -32,38 +43,61 @@ export const makeCmd = new Command('make')
|
|
|
32
43
|
|
|
33
44
|
const realDirectories = await findRealDirectories(directories)
|
|
34
45
|
|
|
46
|
+
if (watch && realDirectories.length > 1) {
|
|
47
|
+
tui.warning('With --watch only the first directory is watched')
|
|
48
|
+
}
|
|
49
|
+
const watchDirectory = watch && realDirectories.length > 0 ? realDirectories[0]! : null
|
|
50
|
+
|
|
35
51
|
for (const directory of realDirectories) {
|
|
52
|
+
if (watch && directory !== watchDirectory) continue
|
|
53
|
+
|
|
36
54
|
try {
|
|
37
55
|
tui.title(`Making problem in directory ${tui.hyperlink(directory, resolve(directory))}`)
|
|
38
56
|
|
|
39
57
|
const maker = await newMaker(directory, problem_nm)
|
|
40
58
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// already done in maker initialization
|
|
48
|
-
}
|
|
49
|
-
if (tasks.includes('exe')) {
|
|
50
|
-
await maker.makeExecutables()
|
|
59
|
+
if (watch) {
|
|
60
|
+
const handler = maker.problem.handler.handler
|
|
61
|
+
if (handler === 'quiz' || handler === 'game') {
|
|
62
|
+
tui.warning('--watch has no effect for quiz or game problems')
|
|
63
|
+
await maker.makeProblem()
|
|
64
|
+
continue
|
|
51
65
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!watch) {
|
|
69
|
+
// If tasks include 'all', run makeProblem
|
|
70
|
+
if (tasks.includes('all')) {
|
|
71
|
+
await maker.makeProblem()
|
|
72
|
+
} else {
|
|
73
|
+
// Run specific tasks
|
|
74
|
+
if (tasks.includes('info')) {
|
|
75
|
+
// already done in maker initialization
|
|
76
|
+
}
|
|
77
|
+
if (tasks.includes('exe')) {
|
|
78
|
+
await maker.makeExecutables()
|
|
79
|
+
}
|
|
80
|
+
if (tasks.includes('cor')) {
|
|
81
|
+
await maker.makeCorrectOutputs()
|
|
82
|
+
}
|
|
83
|
+
if (tasks.includes('pdf')) {
|
|
84
|
+
await maker.makePdfStatements()
|
|
85
|
+
}
|
|
86
|
+
if (tasks.includes('txt') || tasks.includes('html') || tasks.includes('md')) {
|
|
87
|
+
await maker.makeFullTextualStatements(
|
|
88
|
+
tasks.filter((t) => ['txt', 'html', 'md'].includes(t)) as Array<'txt' | 'html' | 'md'>,
|
|
89
|
+
)
|
|
90
|
+
await maker.makeShortTextualStatements(
|
|
91
|
+
tasks.filter((t) => ['txt', 'html', 'md'].includes(t)) as Array<'txt' | 'html' | 'md'>,
|
|
92
|
+
)
|
|
93
|
+
}
|
|
65
94
|
}
|
|
66
95
|
}
|
|
96
|
+
|
|
97
|
+
if (watch && directory === watchDirectory) {
|
|
98
|
+
await runWatch(maker)
|
|
99
|
+
return
|
|
100
|
+
}
|
|
67
101
|
} catch (error) {
|
|
68
102
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
69
103
|
|
|
@@ -94,3 +128,281 @@ export const makeCmd = new Command('make')
|
|
|
94
128
|
}
|
|
95
129
|
})
|
|
96
130
|
})
|
|
131
|
+
|
|
132
|
+
async function runWatch(maker: Maker): Promise<void> {
|
|
133
|
+
const directory = maker.problem.directory
|
|
134
|
+
const goldenSolution = maker.problem.goldenSolution
|
|
135
|
+
|
|
136
|
+
const pending = {
|
|
137
|
+
inp: new Set<string>(),
|
|
138
|
+
golden: false,
|
|
139
|
+
alternatives: new Set<string>(),
|
|
140
|
+
statementTex: false,
|
|
141
|
+
ymlSchema: false,
|
|
142
|
+
changedPaths: new Set<string>(),
|
|
143
|
+
}
|
|
144
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
145
|
+
let running = false
|
|
146
|
+
|
|
147
|
+
function scheduleRun() {
|
|
148
|
+
if (debounceTimer) clearTimeout(debounceTimer)
|
|
149
|
+
debounceTimer = setTimeout(() => {
|
|
150
|
+
void flush()
|
|
151
|
+
}, WATCH_DEBOUNCE_MS)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function flush() {
|
|
155
|
+
debounceTimer = null
|
|
156
|
+
if (
|
|
157
|
+
running ||
|
|
158
|
+
(pending.inp.size === 0 &&
|
|
159
|
+
!pending.golden &&
|
|
160
|
+
pending.alternatives.size === 0 &&
|
|
161
|
+
!pending.statementTex &&
|
|
162
|
+
!pending.ymlSchema)
|
|
163
|
+
) {
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
const inp = new Set(pending.inp)
|
|
167
|
+
const golden = pending.golden
|
|
168
|
+
const alternatives = new Set(pending.alternatives)
|
|
169
|
+
const statementTex = pending.statementTex
|
|
170
|
+
const ymlSchema = pending.ymlSchema
|
|
171
|
+
const changedPaths = new Set(pending.changedPaths)
|
|
172
|
+
pending.inp.clear()
|
|
173
|
+
pending.golden = false
|
|
174
|
+
pending.alternatives.clear()
|
|
175
|
+
pending.statementTex = false
|
|
176
|
+
pending.ymlSchema = false
|
|
177
|
+
pending.changedPaths.clear()
|
|
178
|
+
running = true
|
|
179
|
+
try {
|
|
180
|
+
if (changedPaths.size > 0) {
|
|
181
|
+
tui.print()
|
|
182
|
+
tui.print(chalk.gray('Changed:') + ' ' + [...changedPaths].sort().join(', '))
|
|
183
|
+
tui.print()
|
|
184
|
+
}
|
|
185
|
+
if (ymlSchema) {
|
|
186
|
+
const results = await lintDirectories([directory])
|
|
187
|
+
if (results.length > 0) {
|
|
188
|
+
await printLintResults(results, [directory])
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const problem: Problem = maker.problem
|
|
192
|
+
await (problem.reloadTestcases as () => Promise<void>)()
|
|
193
|
+
await (problem.reloadSolutions as () => Promise<void>)()
|
|
194
|
+
if (golden && problem.goldenSolution) {
|
|
195
|
+
await maker.makeGoldenExecutable()
|
|
196
|
+
await maker.makeCorrectOutputs()
|
|
197
|
+
await maker.remakeStatements()
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
const currentGolden = problem.goldenSolution
|
|
201
|
+
for (const testcase of inp) {
|
|
202
|
+
try {
|
|
203
|
+
await maker.makeCorrectOutputForTestcase(testcase)
|
|
204
|
+
} catch (e) {
|
|
205
|
+
tui.error(e instanceof Error ? e.message : String(e))
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const hasSampleInp = [...inp].some((t) => t.includes('sample'))
|
|
209
|
+
if (hasSampleInp && inp.size > 0) {
|
|
210
|
+
await maker.remakeStatements()
|
|
211
|
+
}
|
|
212
|
+
for (const program of alternatives) {
|
|
213
|
+
if (program === currentGolden) continue
|
|
214
|
+
try {
|
|
215
|
+
await maker.verifyCandidate(program)
|
|
216
|
+
} catch (e) {
|
|
217
|
+
tui.error(e instanceof Error ? e.message : String(e))
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (inp.size > 0 && currentGolden) {
|
|
221
|
+
const changedTestcases = [...inp]
|
|
222
|
+
for (const solution of problem.solutions) {
|
|
223
|
+
if (solution === currentGolden) continue
|
|
224
|
+
try {
|
|
225
|
+
await maker.verifyCandidate(solution, changedTestcases, { skipCompile: true })
|
|
226
|
+
} catch (e) {
|
|
227
|
+
tui.error(e instanceof Error ? e.message : String(e))
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (statementTex) {
|
|
232
|
+
await maker.remakeStatements()
|
|
233
|
+
}
|
|
234
|
+
} finally {
|
|
235
|
+
running = false
|
|
236
|
+
printIdleMessage()
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function printIdleMessage() {
|
|
241
|
+
tui.print()
|
|
242
|
+
tui.title(`Watching ${directory}`)
|
|
243
|
+
tui.print('╭───╮ ╭───╮ ╭───╮ ╭───╮ ')
|
|
244
|
+
tui.print(
|
|
245
|
+
'│ ' +
|
|
246
|
+
chalk.bold('A') +
|
|
247
|
+
' │ │ ' +
|
|
248
|
+
chalk.bold('L') +
|
|
249
|
+
' │ │ ' +
|
|
250
|
+
chalk.bold('H') +
|
|
251
|
+
' │ │ ' +
|
|
252
|
+
chalk.bold('Q') +
|
|
253
|
+
' │ ',
|
|
254
|
+
)
|
|
255
|
+
tui.print('╰───╯ ╰───╯ ╰───╯ ╰───╯ ')
|
|
256
|
+
tui.print(' ♻️ 🔍 ❓ 🚫')
|
|
257
|
+
tui.print(
|
|
258
|
+
' ' +
|
|
259
|
+
chalk.blue('All') +
|
|
260
|
+
' ' +
|
|
261
|
+
chalk.blue('Lint') +
|
|
262
|
+
' ' +
|
|
263
|
+
chalk.blue('Help') +
|
|
264
|
+
' ' +
|
|
265
|
+
chalk.blue('Quit') +
|
|
266
|
+
' ',
|
|
267
|
+
)
|
|
268
|
+
tui.print(chalk.gray('Waiting for changes or keypress...'))
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const watchDir = resolve(directory)
|
|
272
|
+
const watcher = chokidar.watch(watchDir, {
|
|
273
|
+
ignoreInitial: true,
|
|
274
|
+
awaitWriteFinish: { stabilityThreshold: 100 },
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
function onFileEvent(path: string) {
|
|
278
|
+
const parent = resolve(dirname(path))
|
|
279
|
+
if (parent !== watchDir) return
|
|
280
|
+
const name = basename(path)
|
|
281
|
+
pending.changedPaths.add(path)
|
|
282
|
+
if (name.endsWith('.inp')) {
|
|
283
|
+
pending.inp.add(name.replace(/\.inp$/, ''))
|
|
284
|
+
} else if (goldenSolution && name === goldenSolution) {
|
|
285
|
+
pending.golden = true
|
|
286
|
+
} else if (name.startsWith('solution.')) {
|
|
287
|
+
if (name === goldenSolution) {
|
|
288
|
+
pending.golden = true
|
|
289
|
+
} else {
|
|
290
|
+
pending.alternatives.add(name)
|
|
291
|
+
}
|
|
292
|
+
} else if (/^problem\.\w+\.tex$/.test(name)) {
|
|
293
|
+
pending.statementTex = true
|
|
294
|
+
} else if (
|
|
295
|
+
name === 'handler.yml' ||
|
|
296
|
+
name === 'problem.yml' ||
|
|
297
|
+
/^problem\.\w+\.yml$/.test(name)
|
|
298
|
+
) {
|
|
299
|
+
pending.ymlSchema = true
|
|
300
|
+
}
|
|
301
|
+
scheduleRun()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
watcher.on('add', onFileEvent)
|
|
305
|
+
watcher.on('change', onFileEvent)
|
|
306
|
+
|
|
307
|
+
printIdleMessage()
|
|
308
|
+
|
|
309
|
+
await new Promise<void>((resolveExit, rejectExit) => {
|
|
310
|
+
function printHelp() {
|
|
311
|
+
tui.print()
|
|
312
|
+
tui.print('Under watch mode, the toolkit automatically rebuilds the necessary files in the problem directory when you make changes to your files.')
|
|
313
|
+
tui.print(
|
|
314
|
+
[
|
|
315
|
+
'Key bindings:',
|
|
316
|
+
'',
|
|
317
|
+
' A Make all (full rebuild from scratch)',
|
|
318
|
+
' L Run lint to check issues in the problem directory',
|
|
319
|
+
' H Show this help',
|
|
320
|
+
' Q Quit watch mode',
|
|
321
|
+
'',
|
|
322
|
+
].join('\n'),
|
|
323
|
+
|
|
324
|
+
)
|
|
325
|
+
tui.print('The watch mode is under development. Please report any issues to the developers.')
|
|
326
|
+
printIdleMessage()
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const onKey = (key: Buffer | string) => {
|
|
330
|
+
const k = typeof key === 'string' ? key : key.toString()
|
|
331
|
+
if (k === 'q' || k === 'Q' || k === '\x03') {
|
|
332
|
+
cleanup()
|
|
333
|
+
tui.print()
|
|
334
|
+
tui.success('Watch mode stopped')
|
|
335
|
+
resolveExit()
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
if (k === 'h' || k === 'H') {
|
|
339
|
+
printHelp()
|
|
340
|
+
return
|
|
341
|
+
}
|
|
342
|
+
if (k === 'a' || k === 'A') {
|
|
343
|
+
if (debounceTimer) {
|
|
344
|
+
clearTimeout(debounceTimer)
|
|
345
|
+
debounceTimer = null
|
|
346
|
+
}
|
|
347
|
+
pending.inp.clear()
|
|
348
|
+
pending.golden = false
|
|
349
|
+
pending.alternatives.clear()
|
|
350
|
+
pending.statementTex = false
|
|
351
|
+
pending.ymlSchema = false
|
|
352
|
+
pending.changedPaths.clear()
|
|
353
|
+
void (async () => {
|
|
354
|
+
running = true
|
|
355
|
+
try {
|
|
356
|
+
await maker.makeProblem()
|
|
357
|
+
} catch (e) {
|
|
358
|
+
tui.error(e instanceof Error ? e.message : String(e))
|
|
359
|
+
} finally {
|
|
360
|
+
running = false
|
|
361
|
+
printIdleMessage()
|
|
362
|
+
}
|
|
363
|
+
})()
|
|
364
|
+
}
|
|
365
|
+
if (k === 'l' || k === 'L') {
|
|
366
|
+
void (async () => {
|
|
367
|
+
running = true
|
|
368
|
+
try {
|
|
369
|
+
const results = await lintDirectories([directory])
|
|
370
|
+
if (results.length === 0) {
|
|
371
|
+
tui.warning('No problem directories found (looked for handler.yml in the given path(s)).')
|
|
372
|
+
} else {
|
|
373
|
+
await printLintResults(results, [directory])
|
|
374
|
+
}
|
|
375
|
+
} catch (e) {
|
|
376
|
+
tui.error(e instanceof Error ? e.message : String(e))
|
|
377
|
+
} finally {
|
|
378
|
+
running = false
|
|
379
|
+
printIdleMessage()
|
|
380
|
+
}
|
|
381
|
+
})()
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function cleanup() {
|
|
386
|
+
void watcher.close()
|
|
387
|
+
process.stdin.removeListener('data', onKey)
|
|
388
|
+
if (typeof process.stdin.setRawMode === 'function') {
|
|
389
|
+
process.stdin.setRawMode(false)
|
|
390
|
+
}
|
|
391
|
+
process.stdin.pause()
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
process.stdin.resume()
|
|
395
|
+
if (typeof process.stdin.setRawMode === 'function') {
|
|
396
|
+
process.stdin.setRawMode(true)
|
|
397
|
+
}
|
|
398
|
+
process.stdin.on('data', (key) => {
|
|
399
|
+
onKey(key)
|
|
400
|
+
})
|
|
401
|
+
process.on('SIGINT', () => {
|
|
402
|
+
cleanup()
|
|
403
|
+
tui.print()
|
|
404
|
+
tui.success('Watch stopped')
|
|
405
|
+
resolveExit()
|
|
406
|
+
})
|
|
407
|
+
})
|
|
408
|
+
}
|