@funkai/cli 0.1.0 → 0.1.2

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.
Files changed (42) hide show
  1. package/.turbo/turbo-build.log +15 -16
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +1 -1
  4. package/dist/index.mjs +5018 -298
  5. package/dist/index.mjs.map +1 -1
  6. package/package.json +5 -5
  7. package/src/commands/generate.ts +4 -62
  8. package/src/commands/prompts/generate.ts +75 -48
  9. package/src/commands/prompts/lint.ts +62 -44
  10. package/src/commands/prompts/setup.ts +2 -7
  11. package/src/commands/validate.ts +4 -27
  12. package/src/index.ts +6 -1
  13. package/src/lib/prompts/codegen.ts +12 -16
  14. package/src/lib/prompts/flatten.ts +2 -7
  15. package/src/lib/prompts/frontmatter.ts +28 -33
  16. package/src/lib/prompts/lint.ts +8 -8
  17. package/src/lib/prompts/paths.ts +5 -5
  18. package/src/lib/prompts/pipeline.ts +6 -7
  19. package/.turbo/turbo-test$colon$coverage.log +0 -36
  20. package/.turbo/turbo-test.log +0 -26
  21. package/.turbo/turbo-typecheck.log +0 -4
  22. package/coverage/lcov-report/base.css +0 -224
  23. package/coverage/lcov-report/block-navigation.js +0 -87
  24. package/coverage/lcov-report/commands/create.ts.html +0 -208
  25. package/coverage/lcov-report/commands/generate.ts.html +0 -388
  26. package/coverage/lcov-report/commands/index.html +0 -161
  27. package/coverage/lcov-report/commands/lint.ts.html +0 -331
  28. package/coverage/lcov-report/commands/setup.ts.html +0 -493
  29. package/coverage/lcov-report/favicon.png +0 -0
  30. package/coverage/lcov-report/index.html +0 -131
  31. package/coverage/lcov-report/lib/codegen.ts.html +0 -805
  32. package/coverage/lcov-report/lib/extract-variables.ts.html +0 -181
  33. package/coverage/lcov-report/lib/flatten.ts.html +0 -385
  34. package/coverage/lcov-report/lib/frontmatter.ts.html +0 -487
  35. package/coverage/lcov-report/lib/index.html +0 -191
  36. package/coverage/lcov-report/lib/lint.ts.html +0 -307
  37. package/coverage/lcov-report/lib/paths.ts.html +0 -487
  38. package/coverage/lcov-report/prettify.css +0 -1
  39. package/coverage/lcov-report/prettify.js +0 -2
  40. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  41. package/coverage/lcov-report/sorter.js +0 -210
  42. package/coverage/lcov.info +0 -749
@@ -1,8 +1,7 @@
1
- import { match } from "ts-pattern";
2
1
  import { parse as parseYaml } from "yaml";
3
2
 
4
- const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
5
- const NAME_RE = /^[a-z0-9-]+$/;
3
+ export const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
4
+ export const NAME_RE = /^[a-z0-9-]+$/;
6
5
 
7
6
  /**
8
7
  * Parse raw YAML content into a record, wrapping parse errors
@@ -26,20 +25,20 @@ function parseYamlContent(yaml: string, filePath: string): Record<string, unknow
26
25
  * A variable declared in the frontmatter `schema` block.
27
26
  */
28
27
  export interface SchemaVariable {
29
- name: string;
30
- type: string;
31
- required: boolean;
32
- description?: string;
28
+ readonly name: string;
29
+ readonly type: string;
30
+ readonly required: boolean;
31
+ readonly description?: string;
33
32
  }
34
33
 
35
34
  /**
36
35
  * Parsed frontmatter from a `.prompt` file.
37
36
  */
38
37
  export interface ParsedFrontmatter {
39
- name: string;
40
- group?: string;
41
- version?: string;
42
- schema: SchemaVariable[];
38
+ readonly name: string;
39
+ readonly group?: string;
40
+ readonly version?: string;
41
+ readonly schema: readonly SchemaVariable[];
43
42
  }
44
43
 
45
44
  /**
@@ -77,22 +76,21 @@ export function parseFrontmatter(content: string, filePath: string): ParsedFront
77
76
  );
78
77
  }
79
78
 
80
- const group = match(typeof parsed.group === "string")
81
- .with(true, () => {
82
- const g = parsed.group as string;
83
- const invalidSegment = g.split("/").find((segment) => !NAME_RE.test(segment));
84
- if (invalidSegment !== undefined) {
85
- throw new Error(
86
- `Invalid group segment "${invalidSegment}" in ${filePath}. ` +
87
- "Group segments must be lowercase alphanumeric with hyphens only.",
88
- );
89
- }
90
- return g;
91
- })
92
- .otherwise(() => undefined);
93
- const version = match(parsed.version != null)
94
- .with(true, () => String(parsed.version))
95
- .otherwise(() => undefined);
79
+ const group =
80
+ typeof parsed.group === "string"
81
+ ? (() => {
82
+ const g = parsed.group as string;
83
+ const invalidSegment = g.split("/").find((segment) => !NAME_RE.test(segment));
84
+ if (invalidSegment !== undefined) {
85
+ throw new Error(
86
+ `Invalid group segment "${invalidSegment}" in ${filePath}. ` +
87
+ "Group segments must be lowercase alphanumeric with hyphens only.",
88
+ );
89
+ }
90
+ return g;
91
+ })()
92
+ : undefined;
93
+ const version = parsed.version != null ? String(parsed.version) : undefined;
96
94
 
97
95
  const schema = parseSchemaBlock(parsed.schema, filePath);
98
96
 
@@ -124,13 +122,10 @@ function parseSchemaBlock(raw: unknown, filePath: string): SchemaVariable[] {
124
122
 
125
123
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
126
124
  const def = value as Record<string, unknown>;
127
- const type = match(typeof def.type === "string")
128
- .with(true, () => def.type as string)
129
- .otherwise(() => "string");
125
+ const type = typeof def.type === "string" ? (def.type as string) : "string";
130
126
  const required = def.required !== false;
131
- const description = match(typeof def.description === "string")
132
- .with(true, () => def.description as string)
133
- .otherwise(() => undefined);
127
+ const description =
128
+ typeof def.description === "string" ? (def.description as string) : undefined;
134
129
 
135
130
  return { name: varName, type, required, description };
136
131
  }
@@ -4,17 +4,17 @@ import type { SchemaVariable } from "./frontmatter.js";
4
4
  * A single lint diagnostic.
5
5
  */
6
6
  export interface LintDiagnostic {
7
- level: "error" | "warn";
8
- message: string;
7
+ readonly level: "error" | "warn";
8
+ readonly message: string;
9
9
  }
10
10
 
11
11
  /**
12
12
  * Result of linting a single prompt file.
13
13
  */
14
14
  export interface LintResult {
15
- name: string;
16
- filePath: string;
17
- diagnostics: LintDiagnostic[];
15
+ readonly name: string;
16
+ readonly filePath: string;
17
+ readonly diagnostics: readonly LintDiagnostic[];
18
18
  }
19
19
 
20
20
  /**
@@ -33,8 +33,8 @@ export interface LintResult {
33
33
  export function lintPrompt(
34
34
  name: string,
35
35
  filePath: string,
36
- schemaVars: SchemaVariable[],
37
- templateVars: string[],
36
+ schemaVars: readonly SchemaVariable[],
37
+ templateVars: readonly string[],
38
38
  ): LintResult {
39
39
  const diagnostics: LintDiagnostic[] = [];
40
40
  const declared = new Set(schemaVars.map((v) => v.name));
@@ -69,6 +69,6 @@ export function lintPrompt(
69
69
  /**
70
70
  * Check whether any lint results contain errors.
71
71
  */
72
- export function hasLintErrors(results: LintResult[]): boolean {
72
+ export function hasLintErrors(results: readonly LintResult[]): boolean {
73
73
  return results.some((r) => r.diagnostics.some((d) => d.level === "error"));
74
74
  }
@@ -1,14 +1,14 @@
1
1
  import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { basename, extname, join, resolve } from "node:path";
3
3
 
4
+ import { FRONTMATTER_RE, NAME_RE } from "./frontmatter.js";
5
+
4
6
  const MAX_DEPTH = 5;
5
7
  const PROMPT_EXT = ".prompt";
6
- const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
7
- const NAME_RE = /^[a-z0-9-]+$/;
8
8
 
9
9
  export interface DiscoveredPrompt {
10
- name: string;
11
- filePath: string;
10
+ readonly name: string;
11
+ readonly filePath: string;
12
12
  }
13
13
 
14
14
  /**
@@ -102,7 +102,7 @@ function scanDirectory(dir: string, depth: number): DiscoveredPrompt[] {
102
102
  * @returns Sorted, deduplicated list of discovered prompts.
103
103
  * @throws If duplicate prompt names are found across roots.
104
104
  */
105
- export function discoverPrompts(roots: string[]): DiscoveredPrompt[] {
105
+ export function discoverPrompts(roots: readonly string[]): DiscoveredPrompt[] {
106
106
  const all = roots.flatMap((root) => scanDirectory(resolve(root), 0));
107
107
 
108
108
  const byName = Map.groupBy(all, (prompt) => prompt.name);
@@ -2,7 +2,6 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
 
4
4
  import { clean, PARTIALS_DIR } from "@funkai/prompts";
5
- import { match } from "ts-pattern";
6
5
 
7
6
  import { type ParsedPrompt } from "./codegen.js";
8
7
  import { extractVariables } from "./extract-variables.js";
@@ -37,9 +36,9 @@ export function runLintPipeline(options: LintPipelineOptions): LintPipelineResul
37
36
  const discovered = discoverPrompts([...options.roots]);
38
37
  const customPartialsDir = resolve(options.partials ?? ".prompts/partials");
39
38
  // oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: checking custom partials directory from CLI config
40
- const partialsDirs = match(existsSync(customPartialsDir))
41
- .with(true, () => [customPartialsDir, PARTIALS_DIR])
42
- .otherwise(() => [PARTIALS_DIR]);
39
+ const partialsDirs = existsSync(customPartialsDir)
40
+ ? [customPartialsDir, PARTIALS_DIR]
41
+ : [PARTIALS_DIR];
43
42
 
44
43
  const results = discovered.map((d) => {
45
44
  // oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: reading discovered prompt file
@@ -83,9 +82,9 @@ export function runGeneratePipeline(options: GeneratePipelineOptions): GenerateP
83
82
  const discovered = discoverPrompts([...options.roots]);
84
83
  const customPartialsDir = resolve(options.partials ?? resolve(options.out, "../partials"));
85
84
  // oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: checking custom partials directory from CLI config
86
- const partialsDirs = match(existsSync(customPartialsDir))
87
- .with(true, () => [customPartialsDir, PARTIALS_DIR])
88
- .otherwise(() => [PARTIALS_DIR]);
85
+ const partialsDirs = existsSync(customPartialsDir)
86
+ ? [customPartialsDir, PARTIALS_DIR]
87
+ : [PARTIALS_DIR];
89
88
 
90
89
  const processed = discovered.map((d) => {
91
90
  // oxlint-disable-next-line security/detect-non-literal-fs-filename -- safe: reading discovered prompt file
@@ -1,36 +0,0 @@
1
-
2
- > @funkai/cli@0.4.0 test:coverage /Users/zacrosenbauer/Code/joggr/funkai/packages/cli
3
- > vitest run --coverage
4
-
5
-
6
-  RUN  v4.1.0 /Users/zacrosenbauer/Code/joggr/funkai/packages/cli
7
- Coverage enabled with v8
8
-
9
- ✓ src/lib/__tests__/lint.test.ts (8 tests) 3ms
10
- ✓ src/lib/__tests__/frontmatter.test.ts (11 tests) 7ms
11
- ✓ src/lib/__tests__/extract-variables.test.ts (9 tests) 15ms
12
- ✓ src/lib/__tests__/flatten.test.ts (27 tests) 23ms
13
-
14
-  Test Files  4 passed (4)
15
-  Tests  55 passed (55)
16
-  Start at  20:28:56
17
-  Duration  173ms (transform 89ms, setup 0ms, import 139ms, tests 47ms, environment 0ms)
18
-
19
-  % Coverage report from v8
20
- -------------------|---------|----------|---------|---------|-------------------
21
- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
22
- -------------------|---------|----------|---------|---------|-------------------
23
- All files | 28.3 | 26.31 | 34.11 | 27.8 |
24
- commands | 0 | 0 | 0 | 0 |
25
- create.ts | 0 | 0 | 0 | 0 | 8-39
26
- generate.ts | 0 | 0 | 0 | 0 | 25-98
27
- lint.ts | 0 | 0 | 0 | 0 | 23-79
28
- setup.ts | 0 | 0 | 0 | 0 | 7-134
29
- lib | 48.83 | 42.16 | 50 | 48.29 |
30
- codegen.ts | 0 | 0 | 0 | 0 | 25-239
31
- ...-variables.ts | 100 | 75 | 100 | 100 | 21
32
- flatten.ts | 100 | 100 | 100 | 100 |
33
- frontmatter.ts | 90.38 | 88.88 | 91.66 | 90.19 | 48,53,99,117,127
34
- lint.ts | 100 | 100 | 100 | 100 |
35
- paths.ts | 0 | 0 | 0 | 0 | 4-133
36
- -------------------|---------|----------|---------|---------|-------------------
@@ -1,26 +0,0 @@
1
-
2
- 
3
- > @funkai/cli@0.4.0 test /Users/zacrosenbauer/Code/joggr/funkai/packages/cli
4
- > vitest run
5
-
6
- [?25l
7
-  RUN  v4.1.0 /Users/zacrosenbauer/Code/joggr/funkai/packages/cli
8
-
9
- [?2026h
10
-  ❯ src/lib/prompts/__tests__/extract-variables.test.ts [queued]
11
-
12
-  Test Files 0 passed (4)
13
-  Tests 0 passed (0)
14
-  Start at 21:15:41
15
-  Duration 102ms
16
- [?2026l ✓ src/lib/prompts/__tests__/lint.test.ts (8 tests) 3ms
17
- ✓ src/lib/prompts/__tests__/extract-variables.test.ts (9 tests) 18ms
18
- ✓ src/lib/prompts/__tests__/frontmatter.test.ts (11 tests) 11ms
19
- ✓ src/lib/prompts/__tests__/flatten.test.ts (27 tests) 34ms
20
-
21
-  Test Files  4 passed (4)
22
-  Tests  55 passed (55)
23
-  Start at  21:15:41
24
-  Duration  233ms (transform 140ms, setup 0ms, import 222ms, tests 66ms, environment 0ms)
25
-
26
- [?25h
@@ -1,4 +0,0 @@
1
-
2
- > @funkai/cli@0.1.0 typecheck /Users/zacrosenbauer/Code/joggr/funkai/packages/cli
3
- > tsc --noEmit
4
-
@@ -1,224 +0,0 @@
1
- body, html {
2
- margin:0; padding: 0;
3
- height: 100%;
4
- }
5
- body {
6
- font-family: Helvetica Neue, Helvetica, Arial;
7
- font-size: 14px;
8
- color:#333;
9
- }
10
- .small { font-size: 12px; }
11
- *, *:after, *:before {
12
- -webkit-box-sizing:border-box;
13
- -moz-box-sizing:border-box;
14
- box-sizing:border-box;
15
- }
16
- h1 { font-size: 20px; margin: 0;}
17
- h2 { font-size: 14px; }
18
- pre {
19
- font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
20
- margin: 0;
21
- padding: 0;
22
- -moz-tab-size: 2;
23
- -o-tab-size: 2;
24
- tab-size: 2;
25
- }
26
- a { color:#0074D9; text-decoration:none; }
27
- a:hover { text-decoration:underline; }
28
- .strong { font-weight: bold; }
29
- .space-top1 { padding: 10px 0 0 0; }
30
- .pad2y { padding: 20px 0; }
31
- .pad1y { padding: 10px 0; }
32
- .pad2x { padding: 0 20px; }
33
- .pad2 { padding: 20px; }
34
- .pad1 { padding: 10px; }
35
- .space-left2 { padding-left:55px; }
36
- .space-right2 { padding-right:20px; }
37
- .center { text-align:center; }
38
- .clearfix { display:block; }
39
- .clearfix:after {
40
- content:'';
41
- display:block;
42
- height:0;
43
- clear:both;
44
- visibility:hidden;
45
- }
46
- .fl { float: left; }
47
- @media only screen and (max-width:640px) {
48
- .col3 { width:100%; max-width:100%; }
49
- .hide-mobile { display:none!important; }
50
- }
51
-
52
- .quiet {
53
- color: #7f7f7f;
54
- color: rgba(0,0,0,0.5);
55
- }
56
- .quiet a { opacity: 0.7; }
57
-
58
- .fraction {
59
- font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
60
- font-size: 10px;
61
- color: #555;
62
- background: #E8E8E8;
63
- padding: 4px 5px;
64
- border-radius: 3px;
65
- vertical-align: middle;
66
- }
67
-
68
- div.path a:link, div.path a:visited { color: #333; }
69
- table.coverage {
70
- border-collapse: collapse;
71
- margin: 10px 0 0 0;
72
- padding: 0;
73
- }
74
-
75
- table.coverage td {
76
- margin: 0;
77
- padding: 0;
78
- vertical-align: top;
79
- }
80
- table.coverage td.line-count {
81
- text-align: right;
82
- padding: 0 5px 0 20px;
83
- }
84
- table.coverage td.line-coverage {
85
- text-align: right;
86
- padding-right: 10px;
87
- min-width:20px;
88
- }
89
-
90
- table.coverage td span.cline-any {
91
- display: inline-block;
92
- padding: 0 5px;
93
- width: 100%;
94
- }
95
- .missing-if-branch {
96
- display: inline-block;
97
- margin-right: 5px;
98
- border-radius: 3px;
99
- position: relative;
100
- padding: 0 4px;
101
- background: #333;
102
- color: yellow;
103
- }
104
-
105
- .skip-if-branch {
106
- display: none;
107
- margin-right: 10px;
108
- position: relative;
109
- padding: 0 4px;
110
- background: #ccc;
111
- color: white;
112
- }
113
- .missing-if-branch .typ, .skip-if-branch .typ {
114
- color: inherit !important;
115
- }
116
- .coverage-summary {
117
- border-collapse: collapse;
118
- width: 100%;
119
- }
120
- .coverage-summary tr { border-bottom: 1px solid #bbb; }
121
- .keyline-all { border: 1px solid #ddd; }
122
- .coverage-summary td, .coverage-summary th { padding: 10px; }
123
- .coverage-summary tbody { border: 1px solid #bbb; }
124
- .coverage-summary td { border-right: 1px solid #bbb; }
125
- .coverage-summary td:last-child { border-right: none; }
126
- .coverage-summary th {
127
- text-align: left;
128
- font-weight: normal;
129
- white-space: nowrap;
130
- }
131
- .coverage-summary th.file { border-right: none !important; }
132
- .coverage-summary th.pct { }
133
- .coverage-summary th.pic,
134
- .coverage-summary th.abs,
135
- .coverage-summary td.pct,
136
- .coverage-summary td.abs { text-align: right; }
137
- .coverage-summary td.file { white-space: nowrap; }
138
- .coverage-summary td.pic { min-width: 120px !important; }
139
- .coverage-summary tfoot td { }
140
-
141
- .coverage-summary .sorter {
142
- height: 10px;
143
- width: 7px;
144
- display: inline-block;
145
- margin-left: 0.5em;
146
- background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
147
- }
148
- .coverage-summary .sorted .sorter {
149
- background-position: 0 -20px;
150
- }
151
- .coverage-summary .sorted-desc .sorter {
152
- background-position: 0 -10px;
153
- }
154
- .status-line { height: 10px; }
155
- /* yellow */
156
- .cbranch-no { background: yellow !important; color: #111; }
157
- /* dark red */
158
- .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
159
- .low .chart { border:1px solid #C21F39 }
160
- .highlighted,
161
- .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
162
- background: #C21F39 !important;
163
- }
164
- /* medium red */
165
- .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
166
- /* light red */
167
- .low, .cline-no { background:#FCE1E5 }
168
- /* light green */
169
- .high, .cline-yes { background:rgb(230,245,208) }
170
- /* medium green */
171
- .cstat-yes { background:rgb(161,215,106) }
172
- /* dark green */
173
- .status-line.high, .high .cover-fill { background:rgb(77,146,33) }
174
- .high .chart { border:1px solid rgb(77,146,33) }
175
- /* dark yellow (gold) */
176
- .status-line.medium, .medium .cover-fill { background: #f9cd0b; }
177
- .medium .chart { border:1px solid #f9cd0b; }
178
- /* light yellow */
179
- .medium { background: #fff4c2; }
180
-
181
- .cstat-skip { background: #ddd; color: #111; }
182
- .fstat-skip { background: #ddd; color: #111 !important; }
183
- .cbranch-skip { background: #ddd !important; color: #111; }
184
-
185
- span.cline-neutral { background: #eaeaea; }
186
-
187
- .coverage-summary td.empty {
188
- opacity: .5;
189
- padding-top: 4px;
190
- padding-bottom: 4px;
191
- line-height: 1;
192
- color: #888;
193
- }
194
-
195
- .cover-fill, .cover-empty {
196
- display:inline-block;
197
- height: 12px;
198
- }
199
- .chart {
200
- line-height: 0;
201
- }
202
- .cover-empty {
203
- background: white;
204
- }
205
- .cover-full {
206
- border-right: none !important;
207
- }
208
- pre.prettyprint {
209
- border: none !important;
210
- padding: 0 !important;
211
- margin: 0 !important;
212
- }
213
- .com { color: #999 !important; }
214
- .ignore-none { color: #999; font-weight: normal; }
215
-
216
- .wrapper {
217
- min-height: 100%;
218
- height: auto !important;
219
- height: 100%;
220
- margin: 0 auto -48px;
221
- }
222
- .footer, .push {
223
- height: 48px;
224
- }
@@ -1,87 +0,0 @@
1
- /* eslint-disable */
2
- var jumpToCode = (function init() {
3
- // Classes of code we would like to highlight in the file view
4
- var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
5
-
6
- // Elements to highlight in the file listing view
7
- var fileListingElements = ['td.pct.low'];
8
-
9
- // We don't want to select elements that are direct descendants of another match
10
- var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
11
-
12
- // Selector that finds elements on the page to which we can jump
13
- var selector =
14
- fileListingElements.join(', ') +
15
- ', ' +
16
- notSelector +
17
- missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
18
-
19
- // The NodeList of matching elements
20
- var missingCoverageElements = document.querySelectorAll(selector);
21
-
22
- var currentIndex;
23
-
24
- function toggleClass(index) {
25
- missingCoverageElements
26
- .item(currentIndex)
27
- .classList.remove('highlighted');
28
- missingCoverageElements.item(index).classList.add('highlighted');
29
- }
30
-
31
- function makeCurrent(index) {
32
- toggleClass(index);
33
- currentIndex = index;
34
- missingCoverageElements.item(index).scrollIntoView({
35
- behavior: 'smooth',
36
- block: 'center',
37
- inline: 'center'
38
- });
39
- }
40
-
41
- function goToPrevious() {
42
- var nextIndex = 0;
43
- if (typeof currentIndex !== 'number' || currentIndex === 0) {
44
- nextIndex = missingCoverageElements.length - 1;
45
- } else if (missingCoverageElements.length > 1) {
46
- nextIndex = currentIndex - 1;
47
- }
48
-
49
- makeCurrent(nextIndex);
50
- }
51
-
52
- function goToNext() {
53
- var nextIndex = 0;
54
-
55
- if (
56
- typeof currentIndex === 'number' &&
57
- currentIndex < missingCoverageElements.length - 1
58
- ) {
59
- nextIndex = currentIndex + 1;
60
- }
61
-
62
- makeCurrent(nextIndex);
63
- }
64
-
65
- return function jump(event) {
66
- if (
67
- document.getElementById('fileSearch') === document.activeElement &&
68
- document.activeElement != null
69
- ) {
70
- // if we're currently focused on the search input, we don't want to navigate
71
- return;
72
- }
73
-
74
- switch (event.which) {
75
- case 78: // n
76
- case 74: // j
77
- goToNext();
78
- break;
79
- case 66: // b
80
- case 75: // k
81
- case 80: // p
82
- goToPrevious();
83
- break;
84
- }
85
- };
86
- })();
87
- window.addEventListener('keydown', jumpToCode);