lynkr 7.2.5 → 8.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 (124) hide show
  1. package/README.md +3 -3
  2. package/config/model-tiers.json +89 -0
  3. package/install.sh +6 -1
  4. package/package.json +4 -2
  5. package/scripts/setup.js +0 -1
  6. package/src/agents/executor.js +14 -6
  7. package/src/api/middleware/session.js +15 -2
  8. package/src/api/openai-router.js +162 -37
  9. package/src/api/providers-handler.js +15 -1
  10. package/src/api/router.js +107 -2
  11. package/src/budget/index.js +4 -3
  12. package/src/clients/databricks.js +431 -234
  13. package/src/clients/gpt-utils.js +181 -0
  14. package/src/clients/ollama-utils.js +66 -140
  15. package/src/clients/routing.js +0 -1
  16. package/src/clients/standard-tools.js +99 -3
  17. package/src/config/index.js +133 -35
  18. package/src/context/toon.js +173 -0
  19. package/src/logger/index.js +23 -0
  20. package/src/orchestrator/index.js +688 -213
  21. package/src/routing/agentic-detector.js +320 -0
  22. package/src/routing/complexity-analyzer.js +202 -2
  23. package/src/routing/cost-optimizer.js +305 -0
  24. package/src/routing/index.js +168 -159
  25. package/src/routing/model-tiers.js +365 -0
  26. package/src/server.js +4 -14
  27. package/src/sessions/cleanup.js +3 -3
  28. package/src/sessions/record.js +10 -1
  29. package/src/sessions/store.js +7 -2
  30. package/src/tools/agent-task.js +48 -1
  31. package/src/tools/index.js +19 -2
  32. package/src/tools/lazy-loader.js +7 -0
  33. package/src/tools/tinyfish.js +358 -0
  34. package/src/tools/truncate.js +1 -0
  35. package/.github/FUNDING.yml +0 -15
  36. package/.github/workflows/README.md +0 -215
  37. package/.github/workflows/ci.yml +0 -69
  38. package/.github/workflows/index.yml +0 -62
  39. package/.github/workflows/web-tools-tests.yml +0 -56
  40. package/CITATIONS.bib +0 -6
  41. package/CLAWROUTER_ROUTING_PLAN.md +0 -910
  42. package/DEPLOYMENT.md +0 -1001
  43. package/LYNKR-TUI-PLAN.md +0 -984
  44. package/PERFORMANCE-REPORT.md +0 -866
  45. package/PLAN-per-client-model-routing.md +0 -252
  46. package/ROUTER_COMPARISON.md +0 -173
  47. package/TIER_ROUTING_PLAN.md +0 -771
  48. package/docs/42642f749da6234f41b6b425c3bb07c9.txt +0 -1
  49. package/docs/BingSiteAuth.xml +0 -4
  50. package/docs/docs-style.css +0 -478
  51. package/docs/docs.html +0 -197
  52. package/docs/google5be250e608e6da39.html +0 -1
  53. package/docs/index.html +0 -577
  54. package/docs/index.md +0 -577
  55. package/docs/robots.txt +0 -4
  56. package/docs/sitemap.xml +0 -44
  57. package/docs/style.css +0 -1223
  58. package/documentation/README.md +0 -100
  59. package/documentation/api.md +0 -806
  60. package/documentation/claude-code-cli.md +0 -672
  61. package/documentation/codex-cli.md +0 -397
  62. package/documentation/contributing.md +0 -571
  63. package/documentation/cursor-integration.md +0 -731
  64. package/documentation/docker.md +0 -867
  65. package/documentation/embeddings.md +0 -760
  66. package/documentation/faq.md +0 -659
  67. package/documentation/features.md +0 -396
  68. package/documentation/headroom.md +0 -519
  69. package/documentation/installation.md +0 -706
  70. package/documentation/memory-system.md +0 -476
  71. package/documentation/production.md +0 -601
  72. package/documentation/providers.md +0 -906
  73. package/documentation/testing.md +0 -629
  74. package/documentation/token-optimization.md +0 -323
  75. package/documentation/tools.md +0 -697
  76. package/documentation/troubleshooting.md +0 -893
  77. package/final-test.js +0 -33
  78. package/headroom-sidecar/config.py +0 -93
  79. package/headroom-sidecar/requirements.txt +0 -14
  80. package/headroom-sidecar/server.py +0 -451
  81. package/monitor-agents.sh +0 -31
  82. package/scripts/audit-log-reader.js +0 -399
  83. package/scripts/compact-dictionary.js +0 -204
  84. package/scripts/test-deduplication.js +0 -448
  85. package/src/db/database.sqlite +0 -0
  86. package/test/README.md +0 -212
  87. package/test/azure-openai-config.test.js +0 -204
  88. package/test/azure-openai-error-resilience.test.js +0 -238
  89. package/test/azure-openai-format-conversion.test.js +0 -354
  90. package/test/azure-openai-integration.test.js +0 -281
  91. package/test/azure-openai-routing.test.js +0 -177
  92. package/test/azure-openai-streaming.test.js +0 -171
  93. package/test/bedrock-integration.test.js +0 -471
  94. package/test/comprehensive-test-suite.js +0 -928
  95. package/test/config-validation.test.js +0 -207
  96. package/test/cursor-integration.test.js +0 -484
  97. package/test/format-conversion.test.js +0 -578
  98. package/test/hybrid-routing-integration.test.js +0 -254
  99. package/test/hybrid-routing-performance.test.js +0 -418
  100. package/test/llamacpp-integration.test.js +0 -863
  101. package/test/lmstudio-integration.test.js +0 -335
  102. package/test/memory/extractor.test.js +0 -398
  103. package/test/memory/retriever.test.js +0 -613
  104. package/test/memory/retriever.test.js.bak +0 -585
  105. package/test/memory/search.test.js +0 -537
  106. package/test/memory/search.test.js.bak +0 -389
  107. package/test/memory/store.test.js +0 -344
  108. package/test/memory/store.test.js.bak +0 -312
  109. package/test/memory/surprise.test.js +0 -300
  110. package/test/memory-performance.test.js +0 -472
  111. package/test/openai-integration.test.js +0 -686
  112. package/test/openrouter-error-resilience.test.js +0 -418
  113. package/test/passthrough-mode.test.js +0 -385
  114. package/test/performance-benchmark.js +0 -351
  115. package/test/performance-tests.js +0 -528
  116. package/test/routing.test.js +0 -219
  117. package/test/web-tools.test.js +0 -329
  118. package/test-agents-simple.js +0 -43
  119. package/test-cli-connection.sh +0 -33
  120. package/test-learning-unit.js +0 -126
  121. package/test-learning.js +0 -112
  122. package/test-parallel-agents.sh +0 -124
  123. package/test-parallel-direct.js +0 -155
  124. package/test-subagents.sh +0 -117
@@ -0,0 +1,358 @@
1
+ const { URL } = require("url");
2
+ const { Agent } = require("undici");
3
+ const config = require("../config");
4
+ const logger = require("../logger");
5
+ const { registerTool } = require(".");
6
+
7
+ /**
8
+ * Dedicated HTTP agent for TinyFish SSE streams.
9
+ * The default webAgent in web-client.js has a 30s bodyTimeout which is too
10
+ * short for browser-automation tasks that can take up to 120s.
11
+ */
12
+ const sseAgent = new Agent({
13
+ connections: 10,
14
+ pipelining: 1,
15
+ keepAliveTimeout: 60000,
16
+ connectTimeout: 15000,
17
+ bodyTimeout: 0, // no body timeout — we manage timeout via AbortController
18
+ headersTimeout: 15000,
19
+ maxRedirections: 3,
20
+ strictContentLength: false,
21
+ });
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Argument normalisers
25
+ // ---------------------------------------------------------------------------
26
+
27
+ function normalizeUrl(args) {
28
+ const raw = args.url ?? args.uri ?? args.href ?? args.target_url;
29
+ if (typeof raw !== "string" || raw.trim().length === 0) {
30
+ throw new Error("web_agent requires a non-empty url string.");
31
+ }
32
+ // Validate URL
33
+ try {
34
+ new URL(raw.trim());
35
+ } catch {
36
+ throw new Error(`web_agent received an invalid URL: ${raw}`);
37
+ }
38
+ return raw.trim();
39
+ }
40
+
41
+ function normalizeGoal(args) {
42
+ const goal = args.goal ?? args.task ?? args.prompt ?? args.instruction;
43
+ if (typeof goal !== "string" || goal.trim().length === 0) {
44
+ throw new Error("web_agent requires a non-empty goal string.");
45
+ }
46
+ return goal.trim();
47
+ }
48
+
49
+ function resolveBrowserProfile(args) {
50
+ const profile = args.browser_profile ?? args.browserProfile ?? config.tinyfish.browserProfile;
51
+ if (profile === "stealth") return "stealth";
52
+ return "lite";
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // SSE stream consumer
57
+ // ---------------------------------------------------------------------------
58
+
59
+ async function consumeSSEStream(response, timeoutMs) {
60
+ const reader = response.body.getReader();
61
+ const decoder = new TextDecoder();
62
+ let buffer = "";
63
+ const startTime = Date.now();
64
+
65
+ try {
66
+ while (true) {
67
+ if (Date.now() - startTime > timeoutMs) {
68
+ const err = new Error(`TinyFish SSE stream timed out after ${timeoutMs}ms`);
69
+ err.code = "ETIMEDOUT";
70
+ err.status = 504;
71
+ throw err;
72
+ }
73
+
74
+ const { done, value } = await reader.read();
75
+ if (done) break;
76
+
77
+ buffer += decoder.decode(value, { stream: true });
78
+
79
+ // SSE events are separated by double newlines
80
+ const parts = buffer.split("\n\n");
81
+ // Keep the last (possibly incomplete) chunk in the buffer
82
+ buffer = parts.pop() || "";
83
+
84
+ for (const part of parts) {
85
+ // Extract the data: line(s)
86
+ const lines = part.split("\n");
87
+ let dataStr = "";
88
+ for (const line of lines) {
89
+ if (line.startsWith("data: ")) {
90
+ dataStr += line.slice(6);
91
+ } else if (line.startsWith("data:")) {
92
+ dataStr += line.slice(5);
93
+ }
94
+ }
95
+
96
+ if (!dataStr) continue;
97
+
98
+ let event;
99
+ try {
100
+ event = JSON.parse(dataStr);
101
+ } catch {
102
+ // Not valid JSON — skip this SSE frame
103
+ logger.debug({ raw: dataStr.slice(0, 200) }, "TinyFish: non-JSON SSE frame, skipping");
104
+ continue;
105
+ }
106
+
107
+ logger.debug(
108
+ { type: event.type, status: event.status },
109
+ "TinyFish SSE event"
110
+ );
111
+
112
+ if (event.type === "COMPLETE" || event.type === "complete") {
113
+ const status = (event.status ?? "").toUpperCase();
114
+ if (status === "COMPLETED" || status === "SUCCESS") {
115
+ return event.resultJson ?? event.result ?? event.data ?? event;
116
+ }
117
+ // Task failed
118
+ const errMsg = event.error ?? event.message ?? "TinyFish task failed";
119
+ const err = new Error(typeof errMsg === "string" ? errMsg : JSON.stringify(errMsg));
120
+ err.code = "TINYFISH_TASK_FAILED";
121
+ err.status = 502;
122
+ throw err;
123
+ }
124
+ }
125
+ }
126
+
127
+ // Stream ended without a COMPLETE event
128
+ const err = new Error("TinyFish SSE stream ended without a COMPLETE event");
129
+ err.code = "TINYFISH_INCOMPLETE";
130
+ err.status = 502;
131
+ throw err;
132
+ } finally {
133
+ reader.releaseLock();
134
+ }
135
+ }
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // Core API call
139
+ // ---------------------------------------------------------------------------
140
+
141
+ async function callTinyFishAPI({ url, goal, browserProfile, proxyConfig, timeoutMs }) {
142
+ const endpoint = config.tinyfish.endpoint;
143
+ const apiKey = config.tinyfish.apiKey;
144
+
145
+ if (!apiKey) {
146
+ return {
147
+ ok: false,
148
+ status: 503,
149
+ content: JSON.stringify({
150
+ error: "tinyfish_not_configured",
151
+ message:
152
+ "TinyFish API key is not configured. Set TINYFISH_API_KEY in your .env file. Get a key from https://tinyfish.ai",
153
+ }, null, 2),
154
+ };
155
+ }
156
+
157
+ const body = {
158
+ url,
159
+ goal,
160
+ browserProfile,
161
+ };
162
+
163
+ if (proxyConfig) {
164
+ body.proxy = proxyConfig;
165
+ }
166
+
167
+ const controller = new AbortController();
168
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
169
+
170
+ try {
171
+ const response = await fetch(endpoint, {
172
+ method: "POST",
173
+ headers: {
174
+ "Content-Type": "application/json",
175
+ "X-API-Key": apiKey,
176
+ Accept: "text/event-stream",
177
+ },
178
+ body: JSON.stringify(body),
179
+ signal: controller.signal,
180
+ dispatcher: sseAgent,
181
+ });
182
+
183
+ // Handle non-2xx responses before attempting SSE parse
184
+ if (!response.ok) {
185
+ const text = await response.text().catch(() => "");
186
+ const shouldRetry = response.status === 429 || response.status >= 500;
187
+
188
+ if (shouldRetry) {
189
+ // Retry once with 2s backoff
190
+ logger.warn(
191
+ { status: response.status, body: text.slice(0, 200) },
192
+ "TinyFish API error, retrying once"
193
+ );
194
+ await new Promise((r) => setTimeout(r, 2000));
195
+
196
+ const retryController = new AbortController();
197
+ const retryTimeout = setTimeout(() => retryController.abort(), timeoutMs);
198
+ try {
199
+ const retryResponse = await fetch(endpoint, {
200
+ method: "POST",
201
+ headers: {
202
+ "Content-Type": "application/json",
203
+ "X-API-Key": apiKey,
204
+ Accept: "text/event-stream",
205
+ },
206
+ body: JSON.stringify(body),
207
+ signal: retryController.signal,
208
+ dispatcher: sseAgent,
209
+ });
210
+
211
+ if (!retryResponse.ok) {
212
+ const retryText = await retryResponse.text().catch(() => "");
213
+ const err = new Error(
214
+ `TinyFish API error (${retryResponse.status}): ${retryResponse.statusText}`
215
+ );
216
+ err.status = retryResponse.status;
217
+ err.body = retryText;
218
+ throw err;
219
+ }
220
+
221
+ const result = await consumeSSEStream(retryResponse, timeoutMs);
222
+ return {
223
+ ok: true,
224
+ status: 200,
225
+ result,
226
+ };
227
+ } finally {
228
+ clearTimeout(retryTimeout);
229
+ }
230
+ }
231
+
232
+ const err = new Error(
233
+ `TinyFish API error (${response.status}): ${response.statusText}`
234
+ );
235
+ err.status = response.status;
236
+ err.body = text;
237
+ throw err;
238
+ }
239
+
240
+ const result = await consumeSSEStream(response, timeoutMs);
241
+ return {
242
+ ok: true,
243
+ status: 200,
244
+ result,
245
+ };
246
+ } catch (error) {
247
+ if (error.name === "AbortError") {
248
+ const err = new Error(`TinyFish request timed out after ${timeoutMs}ms`);
249
+ err.code = "ETIMEDOUT";
250
+ err.status = 504;
251
+ throw err;
252
+ }
253
+ throw error;
254
+ } finally {
255
+ clearTimeout(timeout);
256
+ }
257
+ }
258
+
259
+ // ---------------------------------------------------------------------------
260
+ // Tool registration
261
+ // ---------------------------------------------------------------------------
262
+
263
+ function registerTinyFishTool() {
264
+ registerTool(
265
+ "web_agent",
266
+ async ({ args = {} }) => {
267
+ const url = normalizeUrl(args);
268
+ const goal = normalizeGoal(args);
269
+ const browserProfile = resolveBrowserProfile(args);
270
+ const timeoutMs = config.tinyfish.timeoutMs;
271
+
272
+ // Build proxy config if enabled
273
+ let proxyConfig = null;
274
+ if (config.tinyfish.proxyEnabled) {
275
+ proxyConfig = {
276
+ enabled: true,
277
+ country: config.tinyfish.proxyCountry,
278
+ };
279
+ }
280
+
281
+ try {
282
+ const response = await callTinyFishAPI({
283
+ url,
284
+ goal,
285
+ browserProfile,
286
+ proxyConfig,
287
+ timeoutMs,
288
+ });
289
+
290
+ // Guard clause: not configured
291
+ if (!response.ok && response.status === 503) {
292
+ return response;
293
+ }
294
+
295
+ const resultStr =
296
+ typeof response.result === "string"
297
+ ? response.result
298
+ : JSON.stringify(response.result, null, 2);
299
+
300
+ logger.debug(
301
+ {
302
+ url,
303
+ goal: goal.slice(0, 100),
304
+ browserProfile,
305
+ resultLength: resultStr.length,
306
+ },
307
+ "TinyFish web_agent completed"
308
+ );
309
+
310
+ return {
311
+ ok: true,
312
+ status: 200,
313
+ content: resultStr,
314
+ metadata: {
315
+ url,
316
+ goal,
317
+ browserProfile,
318
+ resultLength: resultStr.length,
319
+ },
320
+ };
321
+ } catch (err) {
322
+ logger.error(
323
+ { err, url, goal: goal.slice(0, 100) },
324
+ "web_agent request failed"
325
+ );
326
+ return {
327
+ ok: false,
328
+ status: err.status ?? 500,
329
+ content: JSON.stringify(
330
+ {
331
+ error: err.code ?? "web_agent_failed",
332
+ message: err.message,
333
+ url,
334
+ ...(err.status ? { http_status: err.status } : {}),
335
+ },
336
+ null,
337
+ 2
338
+ ),
339
+ metadata: {
340
+ url,
341
+ goal,
342
+ error_code: err.code,
343
+ ...(err.status ? { http_status: err.status } : {}),
344
+ },
345
+ };
346
+ }
347
+ },
348
+ { category: "tinyfish" }
349
+ );
350
+ }
351
+
352
+ function registerTinyFishTools() {
353
+ registerTinyFishTool();
354
+ }
355
+
356
+ module.exports = {
357
+ registerTinyFishTools,
358
+ };
@@ -8,6 +8,7 @@ const TRUNCATION_LIMITS = {
8
8
  Glob: { maxChars: 8000, strategy: 'head' },
9
9
  WebFetch: { maxChars: 16000, strategy: 'head' },
10
10
  WebSearch: { maxChars: 12000, strategy: 'head' },
11
+ WebAgent: { maxChars: 16000, strategy: 'head' },
11
12
  LSP: { maxChars: 8000, strategy: 'head' },
12
13
  Edit: { maxChars: 8000, strategy: 'middle' },
13
14
  Write: { maxChars: 8000, strategy: 'middle' },
@@ -1,15 +0,0 @@
1
- # These are supported funding model platforms
2
-
3
- github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4
- patreon: # Replace with a single Patreon username
5
- open_collective: # Replace with a single Open Collective username
6
- ko_fi: # Replace with a single Ko-fi username
7
- tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8
- community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9
- liberapay: # Replace with a single Liberapay username
10
- issuehunt: # Replace with a single IssueHunt username
11
- lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12
- polar: # Replace with a single Polar username
13
- buy_me_a_coffee: srinivasveera
14
- thanks_dev: # Replace with a single thanks.dev username
15
- custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
@@ -1,215 +0,0 @@
1
- # GitHub Actions Workflows
2
-
3
- This directory contains GitHub Actions workflows for automated testing and CI/CD.
4
-
5
- ## Available Workflows
6
-
7
- ### 1. CI Tests (`ci.yml`)
8
-
9
- **Purpose:** Run comprehensive test suite on every push and pull request
10
-
11
- **Triggers:**
12
- - Push to `main` or `develop` branches
13
- - Pull requests to `main` or `develop` branches
14
-
15
- **What it does:**
16
- - Tests on Node.js 20.x and 22.x
17
- - Runs linter (`npm run lint`)
18
- - Runs unit tests (`npm run test:unit`)
19
- - Runs performance tests (`npm run test:performance`)
20
- - Uses npm cache for faster builds
21
-
22
- **Environment Variables:**
23
- - `DATABRICKS_API_KEY=test-key` (mock value for tests)
24
- - `DATABRICKS_API_BASE=http://test.com` (mock value for tests)
25
-
26
- **Status:** Runs on every push/PR, fails if unit tests fail
27
-
28
- ---
29
-
30
- ### 2. Web Tools Tests (`web-tools-tests.yml`)
31
-
32
- **Purpose:** Run web search tool tests when related files change
33
-
34
- **Triggers:**
35
- - Changes to web tools source files:
36
- - `src/tools/web.js`
37
- - `src/tools/web-client.js`
38
- - `src/clients/retry.js`
39
- - `src/config/index.js`
40
- - `test/web-tools.test.js`
41
-
42
- **What it does:**
43
- - Runs only the web tools test suite
44
- - Generates test summary in GitHub Actions UI
45
- - Faster feedback for web tools changes
46
-
47
- **Test Coverage:**
48
- - HTML extraction (9 tests)
49
- - HTTP keep-alive agent (2 tests)
50
- - Retry logic with exponential backoff (2 tests)
51
- - Configuration management (3 tests)
52
- - Error handling (1 test)
53
- - Performance validation (1 test)
54
- - Body preview configuration (1 test)
55
-
56
- **Total:** 19 tests
57
-
58
- ---
59
-
60
- ### 3. NPM Publish (`npm-publish.yml`)
61
-
62
- **Purpose:** Automatically publish package to npm registry
63
-
64
- **Triggers:**
65
- - Git tags starting with `v` (e.g., `v0.1.5`)
66
- - GitHub Releases created
67
-
68
- **What it does:**
69
- - Runs full test suite before publishing
70
- - Checks if version already exists on npm
71
- - Publishes package to npm registry (if tests pass)
72
- - Prevents duplicate publishes
73
- - Creates publish summary
74
-
75
- **Requirements:**
76
- - `NPM_TOKEN` secret must be configured
77
- - Tests must pass
78
- - Version must be new
79
-
80
- **Status:** Only publishes on successful builds
81
-
82
- ---
83
-
84
- ### 4. Version Bump (`version-bump.yml`)
85
-
86
- **Purpose:** Manual workflow to bump version and create releases
87
-
88
- **Triggers:**
89
- - Manual workflow dispatch (button in Actions tab)
90
-
91
- **What it does:**
92
- - Prompts for version type (patch/minor/major)
93
- - Runs tests before version bump
94
- - Updates package.json version
95
- - Creates git commit and tag
96
- - Pushes changes to repository
97
- - Creates GitHub Release with changelog
98
- - Triggers npm-publish workflow automatically
99
-
100
- **Options:**
101
- - `patch` - Bug fixes (0.1.4 → 0.1.5)
102
- - `minor` - New features (0.1.4 → 0.2.0)
103
- - `major` - Breaking changes (0.1.4 → 1.0.0)
104
-
105
- ---
106
-
107
- ### 5. IndexNow Notification (`index.yml`)
108
-
109
- **Purpose:** Notify search engines when documentation is updated
110
-
111
- **Triggers:**
112
- - Push to `main` branch
113
- - Changes in `docs/**` directory
114
-
115
- **What it does:**
116
- - Notifies Bing IndexNow about updated documentation
117
- - Helps with SEO and documentation discoverability
118
-
119
- ---
120
-
121
- ## Adding Status Badges
122
-
123
- Add these badges to your README.md:
124
-
125
- ```markdown
126
- ![CI Tests](https://github.com/vishalveerareddy123/Lynkr/actions/workflows/ci.yml/badge.svg)
127
- ![Web Tools Tests](https://github.com/vishalveerareddy123/Lynkr/actions/workflows/web-tools-tests.yml/badge.svg)
128
- ![npm version](https://img.shields.io/npm/v/lynkr.svg)
129
- ![npm downloads](https://img.shields.io/npm/dt/lynkr.svg)
130
- ```
131
-
132
- ## Running Tests Locally
133
-
134
- Before pushing, run tests locally:
135
-
136
- ```bash
137
- # Run all unit tests
138
- npm run test:unit
139
-
140
- # Run only web tools tests
141
- DATABRICKS_API_KEY=test-key DATABRICKS_API_BASE=http://test.com \
142
- node --test test/web-tools.test.js
143
-
144
- # Run quick tests (routing only)
145
- npm run test:quick
146
-
147
- # Run all tests including performance
148
- npm test
149
- ```
150
-
151
- ## Workflow Configuration
152
-
153
- ### Required Secrets
154
-
155
- **For npm publishing workflows:**
156
- - `NPM_TOKEN` - Your npm automation token (required to publish)
157
- - Get from: https://www.npmjs.com/settings/YOUR_USERNAME/tokens
158
- - Type: "Automation" token
159
- - Add to: Settings → Secrets → Actions → New repository secret
160
-
161
- **For test workflows:**
162
- - No secrets required (uses mock credentials)
163
-
164
- **For IndexNow workflow:**
165
- - `INDEX_NOW` - Your IndexNow API key (optional, only for docs)
166
-
167
- ### Matrix Strategy
168
-
169
- The CI workflow uses a matrix strategy to test on multiple Node.js versions:
170
- - Node.js 20.x (LTS)
171
- - Node.js 22.x (Current)
172
-
173
- This ensures compatibility across different Node versions.
174
-
175
- ## Troubleshooting
176
-
177
- ### Tests fail locally but pass in CI
178
- - Check Node.js version (`node --version`)
179
- - Ensure `npm ci` is used (not `npm install`)
180
- - Check for platform-specific issues (macOS vs Linux)
181
-
182
- ### Tests pass locally but fail in CI
183
- - Environment variables might be missing
184
- - Dependencies might need updating
185
- - Check GitHub Actions logs for details
186
-
187
- ### Workflow doesn't trigger
188
- - Verify file paths in `on.push.paths`
189
- - Check branch names match
190
- - Ensure workflow file is in `.github/workflows/`
191
-
192
- ## Modifying Workflows
193
-
194
- When making changes:
195
-
196
- 1. Test YAML syntax (use a YAML validator)
197
- 2. Test locally first with same commands
198
- 3. Create a PR to test in CI before merging
199
- 4. Check GitHub Actions tab for results
200
-
201
- ## Performance Considerations
202
-
203
- - **npm cache:** Workflows cache `node_modules` for faster builds
204
- - **Parallel jobs:** Tests run on multiple Node versions in parallel
205
- - **Path filtering:** Web tools workflow only runs when relevant files change
206
- - **continue-on-error:** Performance tests won't fail the build
207
-
208
- ## Future Improvements
209
-
210
- Potential additions:
211
- - Code coverage reporting
212
- - Docker container testing
213
- - E2E integration tests
214
- - Deploy previews for PRs
215
- - Automated dependency updates (Dependabot)
@@ -1,69 +0,0 @@
1
- name: CI Tests
2
-
3
- on:
4
- push:
5
- branches:
6
- - main
7
- - develop
8
- pull_request:
9
- branches:
10
- - main
11
- - develop
12
-
13
- jobs:
14
- test:
15
- name: Run Tests
16
- runs-on: ubuntu-latest
17
-
18
- strategy:
19
- matrix:
20
- node-version: [20.x, 22.x, 23.x, 24.x]
21
-
22
- steps:
23
- - name: Checkout code
24
- uses: actions/checkout@v4
25
-
26
- - name: Setup Node.js ${{ matrix.node-version }}
27
- uses: actions/setup-node@v4
28
- with:
29
- node-version: ${{ matrix.node-version }}
30
- cache: 'npm'
31
-
32
- - name: Install dependencies (C++20 for native addons)
33
- env:
34
- CXXFLAGS: "-std=gnu++20"
35
- npm_config_build_from_source: true
36
- run: npm ci
37
-
38
- - name: Run linter
39
- run: npm run lint
40
- continue-on-error: true
41
-
42
- - name: Run unit tests
43
- run: npm run test:unit
44
- env:
45
- DATABRICKS_API_KEY: test-key
46
- DATABRICKS_API_BASE: http://test.com
47
-
48
- - name: Run performance tests
49
- run: npm run test:performance
50
- env:
51
- DATABRICKS_API_KEY: test-key
52
- DATABRICKS_API_BASE: http://test.com
53
- continue-on-error: true
54
-
55
- test-summary:
56
- name: Test Summary
57
- runs-on: ubuntu-latest
58
- needs: test
59
- if: always()
60
-
61
- steps:
62
- - name: Check test results
63
- run: |
64
- echo "Tests completed"
65
- if [ "${{ needs.test.result }}" == "failure" ]; then
66
- echo "Tests failed!"
67
- exit 1
68
- fi
69
- echo "All tests passed!"