@northflare/runner 0.0.1

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 (154) hide show
  1. package/DEBUG_LOGGING.md +60 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION_PLAN.md +52 -0
  4. package/README.md +220 -0
  5. package/SDK_IMPLEMENTATION_GUIDE.md +1036 -0
  6. package/bin/northflare-runner +367 -0
  7. package/coverage/base.css +224 -0
  8. package/coverage/block-navigation.js +87 -0
  9. package/coverage/coverage-final.json +12 -0
  10. package/coverage/favicon.png +0 -0
  11. package/coverage/index.html +176 -0
  12. package/coverage/lib/index.html +116 -0
  13. package/coverage/lib/preload-script.js.html +964 -0
  14. package/coverage/prettify.css +1 -0
  15. package/coverage/prettify.js +2 -0
  16. package/coverage/sort-arrow-sprite.png +0 -0
  17. package/coverage/sorter.js +196 -0
  18. package/coverage/src/collections/index.html +116 -0
  19. package/coverage/src/collections/runner-messages.ts.html +312 -0
  20. package/coverage/src/components/claude-manager.ts.html +1290 -0
  21. package/coverage/src/components/index.html +146 -0
  22. package/coverage/src/components/message-handler.ts.html +730 -0
  23. package/coverage/src/components/repository-manager.ts.html +841 -0
  24. package/coverage/src/index.html +131 -0
  25. package/coverage/src/index.ts.html +448 -0
  26. package/coverage/src/runner.ts.html +1239 -0
  27. package/coverage/src/utils/config.ts.html +780 -0
  28. package/coverage/src/utils/console.ts.html +121 -0
  29. package/coverage/src/utils/index.html +161 -0
  30. package/coverage/src/utils/logger.ts.html +475 -0
  31. package/coverage/src/utils/status-line.ts.html +445 -0
  32. package/dist/collections/runner-messages.d.ts +52 -0
  33. package/dist/collections/runner-messages.d.ts.map +1 -0
  34. package/dist/collections/runner-messages.js +161 -0
  35. package/dist/collections/runner-messages.js.map +1 -0
  36. package/dist/components/claude-manager.d.ts +39 -0
  37. package/dist/components/claude-manager.d.ts.map +1 -0
  38. package/dist/components/claude-manager.js +783 -0
  39. package/dist/components/claude-manager.js.map +1 -0
  40. package/dist/components/claude-sdk-manager.d.ts +47 -0
  41. package/dist/components/claude-sdk-manager.d.ts.map +1 -0
  42. package/dist/components/claude-sdk-manager.js +1088 -0
  43. package/dist/components/claude-sdk-manager.js.map +1 -0
  44. package/dist/components/enhanced-repository-manager.d.ts +134 -0
  45. package/dist/components/enhanced-repository-manager.d.ts.map +1 -0
  46. package/dist/components/enhanced-repository-manager.js +602 -0
  47. package/dist/components/enhanced-repository-manager.js.map +1 -0
  48. package/dist/components/message-handler-sse.d.ts +46 -0
  49. package/dist/components/message-handler-sse.d.ts.map +1 -0
  50. package/dist/components/message-handler-sse.js +734 -0
  51. package/dist/components/message-handler-sse.js.map +1 -0
  52. package/dist/components/message-handler.d.ts +35 -0
  53. package/dist/components/message-handler.d.ts.map +1 -0
  54. package/dist/components/message-handler.js +689 -0
  55. package/dist/components/message-handler.js.map +1 -0
  56. package/dist/components/repository-manager.d.ts +51 -0
  57. package/dist/components/repository-manager.d.ts.map +1 -0
  58. package/dist/components/repository-manager.js +295 -0
  59. package/dist/components/repository-manager.js.map +1 -0
  60. package/dist/index.d.ts +9 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +166 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/runner-sse.d.ts +57 -0
  65. package/dist/runner-sse.d.ts.map +1 -0
  66. package/dist/runner-sse.js +698 -0
  67. package/dist/runner-sse.js.map +1 -0
  68. package/dist/runner.d.ts +51 -0
  69. package/dist/runner.d.ts.map +1 -0
  70. package/dist/runner.js +530 -0
  71. package/dist/runner.js.map +1 -0
  72. package/dist/services/RunnerAPIClient.d.ts +30 -0
  73. package/dist/services/RunnerAPIClient.d.ts.map +1 -0
  74. package/dist/services/RunnerAPIClient.js +112 -0
  75. package/dist/services/RunnerAPIClient.js.map +1 -0
  76. package/dist/services/SSEClient.d.ts +60 -0
  77. package/dist/services/SSEClient.d.ts.map +1 -0
  78. package/dist/services/SSEClient.js +204 -0
  79. package/dist/services/SSEClient.js.map +1 -0
  80. package/dist/types/claude.d.ts +45 -0
  81. package/dist/types/claude.d.ts.map +1 -0
  82. package/dist/types/claude.js +6 -0
  83. package/dist/types/claude.js.map +1 -0
  84. package/dist/types/index.d.ts +47 -0
  85. package/dist/types/index.d.ts.map +1 -0
  86. package/dist/types/index.js +23 -0
  87. package/dist/types/index.js.map +1 -0
  88. package/dist/types/messages.d.ts +31 -0
  89. package/dist/types/messages.d.ts.map +1 -0
  90. package/dist/types/messages.js +6 -0
  91. package/dist/types/messages.js.map +1 -0
  92. package/dist/types/runner-interface.d.ts +24 -0
  93. package/dist/types/runner-interface.d.ts.map +1 -0
  94. package/dist/types/runner-interface.js +6 -0
  95. package/dist/types/runner-interface.js.map +1 -0
  96. package/dist/utils/StateManager.d.ts +52 -0
  97. package/dist/utils/StateManager.d.ts.map +1 -0
  98. package/dist/utils/StateManager.js +162 -0
  99. package/dist/utils/StateManager.js.map +1 -0
  100. package/dist/utils/config.d.ts +41 -0
  101. package/dist/utils/config.d.ts.map +1 -0
  102. package/dist/utils/config.js +250 -0
  103. package/dist/utils/config.js.map +1 -0
  104. package/dist/utils/console.d.ts +11 -0
  105. package/dist/utils/console.d.ts.map +1 -0
  106. package/dist/utils/console.js +15 -0
  107. package/dist/utils/console.js.map +1 -0
  108. package/dist/utils/expand-env.d.ts +2 -0
  109. package/dist/utils/expand-env.d.ts.map +1 -0
  110. package/dist/utils/expand-env.js +20 -0
  111. package/dist/utils/expand-env.js.map +1 -0
  112. package/dist/utils/logger.d.ts +9 -0
  113. package/dist/utils/logger.d.ts.map +1 -0
  114. package/dist/utils/logger.js +108 -0
  115. package/dist/utils/logger.js.map +1 -0
  116. package/dist/utils/status-line.d.ts +37 -0
  117. package/dist/utils/status-line.d.ts.map +1 -0
  118. package/dist/utils/status-line.js +113 -0
  119. package/dist/utils/status-line.js.map +1 -0
  120. package/docs/claude-manager.md +91 -0
  121. package/exceptions.log +22 -0
  122. package/lib/preload-script.js +293 -0
  123. package/package.json +55 -0
  124. package/rejections.log +63 -0
  125. package/runner.log +488 -0
  126. package/src/components/claude-sdk-manager.ts +1354 -0
  127. package/src/components/enhanced-repository-manager.ts +823 -0
  128. package/src/components/message-handler-sse.ts +1011 -0
  129. package/src/components/repository-manager.ts +337 -0
  130. package/src/index.ts +166 -0
  131. package/src/runner-sse.ts +847 -0
  132. package/src/services/RunnerAPIClient.ts +135 -0
  133. package/src/services/SSEClient.ts +258 -0
  134. package/src/types/claude.ts +55 -0
  135. package/src/types/computer-name.d.ts +4 -0
  136. package/src/types/index.ts +63 -0
  137. package/src/types/messages.ts +39 -0
  138. package/src/types/runner-interface.ts +34 -0
  139. package/src/utils/StateManager.ts +187 -0
  140. package/src/utils/codex-sdk.js +448 -0
  141. package/src/utils/config.ts +315 -0
  142. package/src/utils/console.ts +13 -0
  143. package/src/utils/expand-env.ts +22 -0
  144. package/src/utils/logger.ts +131 -0
  145. package/src/utils/sdk-demo.js +34 -0
  146. package/src/utils/status-line.ts +121 -0
  147. package/test-debug.sh +26 -0
  148. package/tests/retry-strategies.test.ts +410 -0
  149. package/tests/sdk-integration.test.ts +329 -0
  150. package/tests/sdk-streaming.test.ts +1180 -0
  151. package/tests/setup.ts +5 -0
  152. package/tests/test-claude-manager.ts +120 -0
  153. package/tsconfig.json +36 -0
  154. package/vitest.config.ts +27 -0
@@ -0,0 +1,367 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI executable for Northflare Runner
5
+ */
6
+
7
+ const { program, Command } = require("commander");
8
+ const pkg = require("../package.json");
9
+
10
+ /**
11
+ * Helper function to fetch runner ID from orchestrator
12
+ */
13
+ async function fetchRunnerId(orchestratorUrl, token) {
14
+ try {
15
+ const response = await fetch(`${orchestratorUrl}/api/runner/id`, {
16
+ method: "GET",
17
+ headers: {
18
+ Authorization: `Bearer ${token}`,
19
+ },
20
+ });
21
+
22
+ if (!response.ok) {
23
+ const error = await response.json().catch(() => ({ error: response.statusText }));
24
+ throw new Error(`Failed to fetch runner ID: ${error.error || response.statusText}`);
25
+ }
26
+
27
+ const data = await response.json();
28
+ if (!data.runnerId) {
29
+ throw new Error("Server did not return runnerId");
30
+ }
31
+
32
+ return data.runnerId;
33
+ } catch (error) {
34
+ throw new Error(`Failed to fetch runner ID: ${error.message}`);
35
+ }
36
+ }
37
+
38
+ // Create the start command
39
+ const startCommand = new Command("start")
40
+ .description("start the runner")
41
+ .option("-c, --config <path>", "path to configuration file")
42
+ .option("-d, --debug", "enable debug logging")
43
+ .option("--data-dir <path>", "override data directory")
44
+ .option(
45
+ "--workspace-dir <path>",
46
+ "workspace directory for Git checkouts (default: uses env-paths data directory)"
47
+ )
48
+ .option("--token <token>", "authentication token")
49
+ .action(async (options, command) => {
50
+ // Set environment variables from CLI options
51
+ if (options.debug) {
52
+ process.env.DEBUG = "true";
53
+ }
54
+ if (options.dataDir) {
55
+ process.env.NORTHFLARE_DATA_DIR = options.dataDir;
56
+ }
57
+ if (options.workspaceDir) {
58
+ process.env.NORTHFLARE_WORKSPACE_DIR = options.workspaceDir;
59
+ }
60
+ const token = options.token || command.parent?.opts().token;
61
+ if (token) {
62
+ process.env.NORTHFLARE_RUNNER_TOKEN = token;
63
+ }
64
+
65
+ // Load the main module with config path if provided
66
+ // Clear argv[2] since it contains 'start' command
67
+ process.argv.splice(2);
68
+ const configPath = options.config || command.parent?.opts().config;
69
+ if (configPath) {
70
+ process.argv[2] = configPath;
71
+ }
72
+
73
+ // Manually call the main function since require.main won't match
74
+ const { main } = require("../dist");
75
+ await main().catch((error) => {
76
+ console.error("Fatal error:", error.message);
77
+ process.exit(1);
78
+ });
79
+ });
80
+
81
+ // Create the validate command
82
+ const validateCommand = new Command("validate")
83
+ .description("validate configuration without starting")
84
+ .option("-c, --config <path>", "path to configuration file")
85
+ .option("--token <token>", "authentication token")
86
+ .option("--data-dir <path>", "override data directory")
87
+ .option(
88
+ "--workspace-dir <path>",
89
+ "workspace directory for Git checkouts (default: uses env-paths data directory)"
90
+ )
91
+ .action(async (options, command) => {
92
+ try {
93
+ // Set environment variables from CLI options before validation
94
+ const token = options.token || command.parent?.opts().token;
95
+ if (token) {
96
+ process.env.NORTHFLARE_RUNNER_TOKEN = token;
97
+ }
98
+ if (options.dataDir) {
99
+ process.env.NORTHFLARE_DATA_DIR = options.dataDir;
100
+ }
101
+ if (options.workspaceDir) {
102
+ process.env.NORTHFLARE_WORKSPACE_DIR = options.workspaceDir;
103
+ }
104
+
105
+ const { ConfigManager } = require("../dist/utils/config.js");
106
+ const configPath = options.config || command.parent?.opts().config;
107
+ const config = await ConfigManager.loadConfig(configPath);
108
+ console.log("Configuration is valid");
109
+ console.log("Runner ID:", config.runnerId);
110
+ console.log("ElectricSQL URL:", config.electricUrl);
111
+ console.log("Orchestrator URL:", config.orchestratorUrl);
112
+ console.log("Workspace Directory:", process.env.NORTHFLARE_WORKSPACE_DIR);
113
+ process.exit(0);
114
+ } catch (error) {
115
+ console.error("Configuration validation failed:", error.message);
116
+ process.exit(1);
117
+ }
118
+ });
119
+
120
+ // Create the list-repos command
121
+ const listReposCommand = new Command("list-repos")
122
+ .description("list current runner repositories")
123
+ .option(
124
+ "-c, --config <path>",
125
+ "path to configuration file (defaults to ~/.config/northflare-runner/config.json)"
126
+ )
127
+ .option("--token <token>", "authentication token")
128
+ .action(async (options, command) => {
129
+ try {
130
+ const fs = require("fs").promises;
131
+ const path = require("path");
132
+ const envPaths = require("env-paths").default || require("env-paths");
133
+
134
+ // Get token from command options, parent options, or environment
135
+ const token = options.token || command.parent?.opts().token || process.env.NORTHFLARE_RUNNER_TOKEN;
136
+ if (!token) {
137
+ console.error("Error: NORTHFLARE_RUNNER_TOKEN is required. Provide it via --token or environment variable.");
138
+ process.exit(1);
139
+ }
140
+
141
+ // Get default config path
142
+ const paths = envPaths("northflare-runner", { suffix: "" });
143
+
144
+ // Determine config path from command options, parent options, or default
145
+ const configOption = options.config || command.parent?.opts().config;
146
+ let configPath;
147
+ if (configOption) {
148
+ configPath = path.resolve(configOption);
149
+ } else {
150
+ configPath = path.join(paths.config, "config.json");
151
+ }
152
+
153
+ // Load config file
154
+ let config = {};
155
+ try {
156
+ const content = await fs.readFile(configPath, "utf-8");
157
+ config = JSON.parse(content);
158
+ } catch (error) {
159
+ if (error.code === "ENOENT") {
160
+ if (options.config) {
161
+ console.error(`Configuration file not found: ${configPath}`);
162
+ process.exit(1);
163
+ } else {
164
+ console.log(
165
+ `No configuration file found at default location: ${configPath}`
166
+ );
167
+ console.log(
168
+ "Use -c/--config to specify a config file or add-repo to create one."
169
+ );
170
+ process.exit(0);
171
+ }
172
+ }
173
+ throw error;
174
+ }
175
+
176
+ // Log config file location in debug mode
177
+ if (process.env.DEBUG) {
178
+ console.log(`Config file location: ${configPath}`);
179
+ }
180
+
181
+ // Get orchestrator URL from config or environment
182
+ const orchestratorUrl = config.orchestratorUrl || process.env.NORTHFLARE_ORCHESTRATOR_URL || "https://api.northflare.ai";
183
+
184
+ // Fetch runner ID
185
+ const runnerId = await fetchRunnerId(orchestratorUrl, token);
186
+ console.log(`Runner ID: ${runnerId}\n`);
187
+
188
+ // Ensure runnerRepos is properly structured
189
+ if (!config.runnerRepos || typeof config.runnerRepos !== "object") {
190
+ config.runnerRepos = {};
191
+ }
192
+
193
+ // Get repos for this runner
194
+ const repos = config.runnerRepos[runnerId] || [];
195
+
196
+ // List runner repos
197
+ if (repos.length > 0) {
198
+ console.log(`Found ${repos.length} runner repositories:`);
199
+ repos.forEach((repo, index) => {
200
+ console.log(
201
+ `${index + 1}. ${repo.name} (${repo.path})${
202
+ repo.uuid ? ` [UUID: ${repo.uuid}]` : ""
203
+ }`
204
+ );
205
+ });
206
+ } else {
207
+ console.log("No runner repositories configured for this runner.");
208
+ }
209
+
210
+ process.exit(0);
211
+ } catch (error) {
212
+ console.error("Error listing repositories:", error.message);
213
+ process.exit(1);
214
+ }
215
+ });
216
+
217
+ // Create the add-repo command
218
+ const addRepoCommand = new Command("add-repo")
219
+ .description("add a repository to the configuration")
220
+ .argument("<path>", "path to the repository")
221
+ .option(
222
+ "-c, --config <path>",
223
+ "path to configuration file (defaults to ~/.config/northflare-runner/config.json)"
224
+ )
225
+ .option("-n, --name <name>", "repository name (defaults to folder name)")
226
+ .option("--token <token>", "authentication token")
227
+ .action(async (repoPath, options, command) => {
228
+ try {
229
+ const fs = require("fs").promises;
230
+ const path = require("path");
231
+ const envPaths = require("env-paths").default || require("env-paths");
232
+ const crypto = require("crypto");
233
+
234
+ // Get token from command options, parent options, or environment
235
+ const token = options.token || command.parent?.opts().token || process.env.NORTHFLARE_RUNNER_TOKEN;
236
+ if (!token) {
237
+ console.error("Error: NORTHFLARE_RUNNER_TOKEN is required. Provide it via --token or environment variable.");
238
+ process.exit(1);
239
+ }
240
+
241
+ // Get default config path
242
+ const paths = envPaths("northflare-runner", { suffix: "" });
243
+
244
+ // Determine config path from command options, parent options, or default
245
+ const configOption = options.config || command.parent?.opts().config;
246
+ let configPath;
247
+ if (configOption) {
248
+ configPath = path.resolve(configOption);
249
+ } else {
250
+ configPath = path.join(paths.config, "config.json");
251
+ // Ensure config directory exists
252
+ await fs.mkdir(paths.config, { recursive: true });
253
+ }
254
+
255
+ // Load existing config
256
+ let config = {};
257
+ try {
258
+ const content = await fs.readFile(configPath, "utf-8");
259
+ config = JSON.parse(content);
260
+ } catch (error) {
261
+ if (error.code === "ENOENT") {
262
+ console.log(
263
+ `Configuration file not found: ${configPath}. Creating new config file.`
264
+ );
265
+ config = {};
266
+ } else {
267
+ throw error;
268
+ }
269
+ }
270
+
271
+ // Log config file location in debug mode
272
+ if (process.env.DEBUG) {
273
+ console.log(`Config file location: ${configPath}`);
274
+ }
275
+
276
+ // Get orchestrator URL from config or environment
277
+ const orchestratorUrl = config.orchestratorUrl || process.env.NORTHFLARE_ORCHESTRATOR_URL || "https://api.northflare.ai";
278
+
279
+ // Fetch runner ID
280
+ const runnerId = await fetchRunnerId(orchestratorUrl, token);
281
+ console.log(`Runner ID: ${runnerId}`);
282
+
283
+ // Ensure runnerRepos is properly structured
284
+ if (!config.runnerRepos || typeof config.runnerRepos !== "object" || Array.isArray(config.runnerRepos)) {
285
+ config.runnerRepos = {};
286
+ }
287
+
288
+ // Initialize repos array for this runner if not present
289
+ if (!config.runnerRepos[runnerId] || !Array.isArray(config.runnerRepos[runnerId])) {
290
+ config.runnerRepos[runnerId] = [];
291
+ }
292
+
293
+ // Resolve the repository path
294
+ const absoluteRepoPath = path.resolve(repoPath);
295
+
296
+ // Check if path exists
297
+ try {
298
+ const stats = await fs.stat(absoluteRepoPath);
299
+ if (!stats.isDirectory()) {
300
+ console.error(`Error: ${absoluteRepoPath} is not a directory`);
301
+ process.exit(1);
302
+ }
303
+ } catch (error) {
304
+ console.error(
305
+ `Error: Repository path does not exist: ${absoluteRepoPath}`
306
+ );
307
+ process.exit(1);
308
+ }
309
+
310
+ // Check for duplicates
311
+ const duplicate = config.runnerRepos[runnerId].find(
312
+ (repo) => repo.path === absoluteRepoPath
313
+ );
314
+ if (duplicate) {
315
+ console.error(
316
+ `Error: Repository already exists in configuration: ${duplicate.name} (${duplicate.path})`
317
+ );
318
+ process.exit(1);
319
+ }
320
+
321
+ // Determine repository name
322
+ const repoName = options.name || path.basename(absoluteRepoPath);
323
+
324
+ // Generate UUID for the repository
325
+ const repoUuid = crypto.randomUUID();
326
+
327
+ // Add new repository
328
+ const newRepo = {
329
+ uuid: repoUuid,
330
+ name: repoName,
331
+ path: absoluteRepoPath,
332
+ };
333
+
334
+ config.runnerRepos[runnerId].push(newRepo);
335
+
336
+ // Save updated config
337
+ const { ConfigManager } = require("../dist/utils/config.js");
338
+ await ConfigManager.saveConfigFile(configPath, config);
339
+
340
+ console.log(
341
+ `Successfully added repository: ${repoName} (${absoluteRepoPath})`
342
+ );
343
+ console.log(`UUID: ${repoUuid}`);
344
+ console.log(`Total repositories for this runner: ${config.runnerRepos[runnerId].length}`);
345
+
346
+ process.exit(0);
347
+ } catch (error) {
348
+ console.error("Error adding repository:", error.message);
349
+ process.exit(1);
350
+ }
351
+ });
352
+
353
+ // Set up the main program
354
+ program
355
+ .name("northflare-runner")
356
+ .description("Distributed conversation runner for Northflare")
357
+ .version(pkg.version)
358
+ .option("--token <token>", "authentication token (can be used with any command)")
359
+ .option("-c, --config <path>", "path to configuration file (can be used with any command)")
360
+ .enablePositionalOptions() // Allow options to be placed before or after positional arguments
361
+ .passThroughOptions() // Pass options through to subcommands
362
+ .addCommand(startCommand, { isDefault: true })
363
+ .addCommand(validateCommand)
364
+ .addCommand(listReposCommand)
365
+ .addCommand(addRepoCommand);
366
+
367
+ program.parse(process.argv);
@@ -0,0 +1,224 @@
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
+ }
@@ -0,0 +1,87 @@
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
+ // Selecter 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);