From 4ffdc8cf3485e81dea9c9a874a65c74d7fd7b192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Krysiewicz?= Date: Mon, 26 Jan 2026 15:43:49 +0100 Subject: [PATCH 1/3] Claude fixes exiting test program --- lib/apiRequest.js | 6 ++- lib/proxy.js | 4 +- lib/test-runner/logStream.js | 60 ++++++++++++++++++++++++-- package-lock.json | 7 --- test/fixtures/yeoman/package-lock.json | 14 +++--- 5 files changed, 73 insertions(+), 18 deletions(-) diff --git a/lib/apiRequest.js b/lib/apiRequest.js index e0a6bd3e4..52f5b0f4a 100644 --- a/lib/apiRequest.js +++ b/lib/apiRequest.js @@ -18,7 +18,7 @@ const buildFormData = (formData) => { return form; }; -const apiRequest = async ({ method = 'GET', uri, body, headers = {}, formData, json = true, forever }) => { +const apiRequest = async ({ method = 'GET', uri, body, headers = {}, formData, json = true, forever, signal }) => { logger.Debug(`[${method}] ${uri}`); const fetchOptions = { @@ -26,6 +26,10 @@ const apiRequest = async ({ method = 'GET', uri, body, headers = {}, formData, j headers: { ...headers } }; + if (signal) { + fetchOptions.signal = signal; + } + if (formData) { const form = buildFormData(formData); fetchOptions.body = form; diff --git a/lib/proxy.js b/lib/proxy.js index 8cd143639..b145b2d84 100644 --- a/lib/proxy.js +++ b/lib/proxy.js @@ -92,8 +92,8 @@ class Gateway { return apiRequest({ uri: `${this.api_url}/logs`, headers: this.defaultHeaders }); } - logs(json) { - return apiRequest({ uri: `${this.api_url}/logs?last_id=${json.lastId}`, json: true, forever: true, headers: this.defaultHeaders }); + logs(json, { signal } = {}) { + return apiRequest({ uri: `${this.api_url}/logs?last_id=${json.lastId}`, json: true, forever: true, headers: this.defaultHeaders, signal }); } logsv2(params) { diff --git a/lib/test-runner/logStream.js b/lib/test-runner/logStream.js index 971c369e0..5a6b67e4c 100644 --- a/lib/test-runner/logStream.js +++ b/lib/test-runner/logStream.js @@ -19,11 +19,12 @@ class TestLogStream extends EventEmitter { this.lastId = 0; this.intervalId = null; this.timeoutId = null; + this.abortController = null; } isValidTestSummaryJson(message) { try { - const obj = JSON.parse(message); + const obj = JSON.parse(message.trim()); const hasTestsArray = Array.isArray(obj.tests); const hasSuccessField = typeof obj.success === 'boolean'; @@ -36,7 +37,31 @@ class TestLogStream extends EventEmitter { } } + isPlainTextSummary(message) { + // Detect plain text summary format: "Assertions: N. Failed: N. Time: Nms" + return /Assertions:\s*\d+\.\s*Failed:\s*\d+\.\s*Time:\s*\d+ms/i.test(message); + } + + parsePlainTextSummary(message) { + const match = message.match(/Assertions:\s*(\d+)\.\s*Failed:\s*(\d+)\.\s*Time:\s*(\d+)ms/i); + if (!match) return null; + + const assertions = parseInt(match[1], 10); + const failed = parseInt(match[2], 10); + const duration = parseInt(match[3], 10); + + return { + total: assertions, + passed: assertions - failed, + failed: failed, + assertions: assertions, + tests: [], + duration: duration + }; + } + start() { + this.abortController = new AbortController(); this.intervalId = setInterval(() => this.fetchLogs(), POLL_INTERVAL_MS); this.timeoutId = setTimeout(() => { this.emit('timeout'); @@ -47,6 +72,10 @@ class TestLogStream extends EventEmitter { } stop() { + if (this.abortController) { + this.abortController.abort(); + this.abortController = null; + } if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; @@ -58,8 +87,10 @@ class TestLogStream extends EventEmitter { } async fetchLogs() { + if (!this.abortController) return; + try { - const response = await this.gateway.logs({ lastId: this.lastId || 0 }); + const response = await this.gateway.logs({ lastId: this.lastId || 0 }, { signal: this.abortController.signal }); const logs = response && response.logs; if (!logs) return; @@ -73,6 +104,7 @@ class TestLogStream extends EventEmitter { this.processLogMessage(row); } } catch (error) { + if (error.name === 'AbortError') return; logger.Debug(`Error fetching logs: ${error.message}`); } } @@ -103,6 +135,17 @@ class TestLogStream extends EventEmitter { } } + // Check for plain text summary format + if (!this.completed && this.isPlainTextSummary(fullMessage)) { + const testResults = this.parsePlainTextSummary(fullMessage); + if (testResults) { + this.completed = true; + this.emit('testCompleted', testResults); + this.stop(); + return; + } + } + if (this.testStarted && !this.completed) { this.emit('testLog', row, logType === this.testName); } @@ -128,6 +171,17 @@ class TestLogStream extends EventEmitter { } } + // Check for plain text summary format + if (!this.completed && this.isPlainTextSummary(fullMessage)) { + const testResults = this.parsePlainTextSummary(fullMessage); + if (testResults) { + this.completed = true; + this.emit('testCompleted', testResults); + this.stop(); + return; + } + } + if (this.testStarted && !this.completed) { this.emit('testLog', row, true); } @@ -136,7 +190,7 @@ class TestLogStream extends EventEmitter { parseJsonSummary(message) { try { - const summary = JSON.parse(message); + const summary = JSON.parse(message.trim()); return transformTestResponse(summary); } catch (error) { logger.Debug(`[DEBUG] Failed to parse JSON summary: ${error.message}`); diff --git a/package-lock.json b/package-lock.json index 5d059516e..3f7cd7260 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2311,7 +2311,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -2995,7 +2994,6 @@ "resolved": "https://registry.npmjs.org/@yeoman/types/-/types-1.9.1.tgz", "integrity": "sha512-5BMdA/zMzLv/ahnL1ktaV46nSXorb4sU4kQPQKDhIcK8ERbx9TAbGAE+XAlCXKioNIiOrihYj6gW1d/GEfU9Zw==", "license": "MIT", - "peer": true, "engines": { "node": "^16.13.0 || >=18.12.0" }, @@ -3057,7 +3055,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4936,7 +4933,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -10744,7 +10740,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11232,7 +11227,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -11326,7 +11320,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, diff --git a/test/fixtures/yeoman/package-lock.json b/test/fixtures/yeoman/package-lock.json index fcf90d3df..64a7f74ea 100644 --- a/test/fixtures/yeoman/package-lock.json +++ b/test/fixtures/yeoman/package-lock.json @@ -98,7 +98,6 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.6.tgz", "integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==", "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", @@ -326,7 +325,8 @@ "version": "1.20.4", "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/lodash": { "version": "4.17.23", @@ -348,7 +348,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -364,6 +363,7 @@ "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", "integrity": "sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==", "license": "MIT", + "peer": true, "dependencies": { "@types/expect": "^1.20.4", "@types/node": "*" @@ -748,6 +748,7 @@ "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-5.0.0.tgz", "integrity": "sha512-WdHo4ejd2cG2Dl+sLkW79SctU7mUQDfr4s1i26ffOZRs5mgv+BRttIM9gwcq0rDbemo0KlpVPaa3LBVLqPXzcQ==", "license": "MIT", + "peer": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -926,7 +927,8 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/isbinaryfile": { "version": "5.0.3", @@ -1041,7 +1043,6 @@ "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-11.1.4.tgz", "integrity": "sha512-Z4QX14Ev6eOVTuVSayS5rdiOua6C3gHcFw+n9Qc7WiaVTbC+H8b99c32MYGmbQN9UFHJeI/p3lf3LAxiIzwEmA==", "license": "MIT", - "peer": true, "dependencies": { "@types/ejs": "^3.1.4", "@types/node": ">=18", @@ -1601,6 +1602,7 @@ "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-3.0.1.tgz", "integrity": "sha512-iJaWw2WroigLHzQysdc5WWeUc99p7ea7AEgB6JkY8CMyiO1yTVAA1gIlJJgORElUIR+lcZJkNl1OGChMhvc2Cw==", "license": "MIT", + "peer": true, "dependencies": { "is-utf8": "^0.2.1" }, @@ -1616,6 +1618,7 @@ "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-5.0.0.tgz", "integrity": "sha512-Yo472mU+3smhzqeKlIxClre4s4pwtYZEvDNQvY/sJpnChdaxmKuwU28UVx/v1ORKNMxkmj1GBuvxJQyBk6wYMQ==", "license": "MIT", + "peer": true, "dependencies": { "first-chunk-stream": "^5.0.0", "strip-bom-buf": "^3.0.0" @@ -1777,6 +1780,7 @@ "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-5.0.0.tgz", "integrity": "sha512-MvkPF/yA1EX7c6p+juVIvp9+Lxp70YUfNKzEWeHMKpUNVSnTZh2coaOqLxI0pmOe2V9nB+OkgFaMDkodaJUyGw==", "license": "MIT", + "peer": true, "dependencies": { "@types/vinyl": "^2.0.7", "strip-bom-buf": "^3.0.1", From 88782c910aa8a3d5366d28d1849940a332c6a4bc Mon Sep 17 00:00:00 2001 From: Maciej Krajowski-Kukiel Date: Tue, 27 Jan 2026 12:57:03 +0100 Subject: [PATCH 2/3] remove duplicated logic --- lib/test-runner/logStream.js | 45 -------------------------- test/fixtures/yeoman/package-lock.json | 14 +++----- 2 files changed, 5 insertions(+), 54 deletions(-) diff --git a/lib/test-runner/logStream.js b/lib/test-runner/logStream.js index 5a6b67e4c..9b0b215d4 100644 --- a/lib/test-runner/logStream.js +++ b/lib/test-runner/logStream.js @@ -37,29 +37,6 @@ class TestLogStream extends EventEmitter { } } - isPlainTextSummary(message) { - // Detect plain text summary format: "Assertions: N. Failed: N. Time: Nms" - return /Assertions:\s*\d+\.\s*Failed:\s*\d+\.\s*Time:\s*\d+ms/i.test(message); - } - - parsePlainTextSummary(message) { - const match = message.match(/Assertions:\s*(\d+)\.\s*Failed:\s*(\d+)\.\s*Time:\s*(\d+)ms/i); - if (!match) return null; - - const assertions = parseInt(match[1], 10); - const failed = parseInt(match[2], 10); - const duration = parseInt(match[3], 10); - - return { - total: assertions, - passed: assertions - failed, - failed: failed, - assertions: assertions, - tests: [], - duration: duration - }; - } - start() { this.abortController = new AbortController(); this.intervalId = setInterval(() => this.fetchLogs(), POLL_INTERVAL_MS); @@ -135,17 +112,6 @@ class TestLogStream extends EventEmitter { } } - // Check for plain text summary format - if (!this.completed && this.isPlainTextSummary(fullMessage)) { - const testResults = this.parsePlainTextSummary(fullMessage); - if (testResults) { - this.completed = true; - this.emit('testCompleted', testResults); - this.stop(); - return; - } - } - if (this.testStarted && !this.completed) { this.emit('testLog', row, logType === this.testName); } @@ -171,17 +137,6 @@ class TestLogStream extends EventEmitter { } } - // Check for plain text summary format - if (!this.completed && this.isPlainTextSummary(fullMessage)) { - const testResults = this.parsePlainTextSummary(fullMessage); - if (testResults) { - this.completed = true; - this.emit('testCompleted', testResults); - this.stop(); - return; - } - } - if (this.testStarted && !this.completed) { this.emit('testLog', row, true); } diff --git a/test/fixtures/yeoman/package-lock.json b/test/fixtures/yeoman/package-lock.json index 64a7f74ea..fcf90d3df 100644 --- a/test/fixtures/yeoman/package-lock.json +++ b/test/fixtures/yeoman/package-lock.json @@ -98,6 +98,7 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.6.tgz", "integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.2.2", @@ -325,8 +326,7 @@ "version": "1.20.4", "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/lodash": { "version": "4.17.23", @@ -348,6 +348,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -363,7 +364,6 @@ "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", "integrity": "sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==", "license": "MIT", - "peer": true, "dependencies": { "@types/expect": "^1.20.4", "@types/node": "*" @@ -748,7 +748,6 @@ "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-5.0.0.tgz", "integrity": "sha512-WdHo4ejd2cG2Dl+sLkW79SctU7mUQDfr4s1i26ffOZRs5mgv+BRttIM9gwcq0rDbemo0KlpVPaa3LBVLqPXzcQ==", "license": "MIT", - "peer": true, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, @@ -927,8 +926,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/isbinaryfile": { "version": "5.0.3", @@ -1043,6 +1041,7 @@ "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-11.1.4.tgz", "integrity": "sha512-Z4QX14Ev6eOVTuVSayS5rdiOua6C3gHcFw+n9Qc7WiaVTbC+H8b99c32MYGmbQN9UFHJeI/p3lf3LAxiIzwEmA==", "license": "MIT", + "peer": true, "dependencies": { "@types/ejs": "^3.1.4", "@types/node": ">=18", @@ -1602,7 +1601,6 @@ "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-3.0.1.tgz", "integrity": "sha512-iJaWw2WroigLHzQysdc5WWeUc99p7ea7AEgB6JkY8CMyiO1yTVAA1gIlJJgORElUIR+lcZJkNl1OGChMhvc2Cw==", "license": "MIT", - "peer": true, "dependencies": { "is-utf8": "^0.2.1" }, @@ -1618,7 +1616,6 @@ "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-5.0.0.tgz", "integrity": "sha512-Yo472mU+3smhzqeKlIxClre4s4pwtYZEvDNQvY/sJpnChdaxmKuwU28UVx/v1ORKNMxkmj1GBuvxJQyBk6wYMQ==", "license": "MIT", - "peer": true, "dependencies": { "first-chunk-stream": "^5.0.0", "strip-bom-buf": "^3.0.0" @@ -1780,7 +1777,6 @@ "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-5.0.0.tgz", "integrity": "sha512-MvkPF/yA1EX7c6p+juVIvp9+Lxp70YUfNKzEWeHMKpUNVSnTZh2coaOqLxI0pmOe2V9nB+OkgFaMDkodaJUyGw==", "license": "MIT", - "peer": true, "dependencies": { "@types/vinyl": "^2.0.7", "strip-bom-buf": "^3.0.1", From 7088fce19f212bb0ac4d7613204b24a92546de3e Mon Sep 17 00:00:00 2001 From: Maciej Krajowski-Kukiel Date: Tue, 27 Jan 2026 13:33:51 +0100 Subject: [PATCH 3/3] account for \r --- bin/pos-cli-logs.js | 2 +- lib/test-runner/formatters.js | 2 +- lib/test-runner/logStream.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/pos-cli-logs.js b/bin/pos-cli-logs.js index 808b9f742..35651ec1d 100755 --- a/bin/pos-cli-logs.js +++ b/bin/pos-cli-logs.js @@ -88,7 +88,7 @@ program if (message == null) message = ''; if (typeof(message) != 'string') message = JSON.stringify(message); - const text = `[${created_at.replace('T', ' ')}] - ${error_type}: ${message.replace(/\n$/, '')}`; + const text = `[${created_at.replace('T', ' ')}] - ${error_type}: ${message.replace(/\r?\n$/, '')}`; const options = { exit: false, hideTimestamp: true }; if (isError(message)) { diff --git a/lib/test-runner/formatters.js b/lib/test-runner/formatters.js index 4cd123b78..caf75ef19 100644 --- a/lib/test-runner/formatters.js +++ b/lib/test-runner/formatters.js @@ -13,7 +13,7 @@ const formatTestLog = (logRow, isTestLog) => { const message = logRow.message || ''; const logType = logRow.error_type || ''; const fullMessage = typeof message === 'string' ? message : JSON.stringify(message); - const cleanMessage = fullMessage.replace(/\n$/, ''); + const cleanMessage = fullMessage.replace(/\r?\n$/, ''); const hasTestPath = /app\/lib\/test\/|modules\/.*\/test\/|\.liquid/.test(cleanMessage); diff --git a/lib/test-runner/logStream.js b/lib/test-runner/logStream.js index 9b0b215d4..2635c2da9 100644 --- a/lib/test-runner/logStream.js +++ b/lib/test-runner/logStream.js @@ -92,7 +92,7 @@ class TestLogStream extends EventEmitter { const fullMessage = typeof message === 'string' ? message : JSON.stringify(message); const summaryType = this.testName ? `${this.testName} SUMMARY` : null; - const cleanMessage = fullMessage.replace(/\n$/, ''); + const cleanMessage = fullMessage.replace(/\r?\n$/, ''); const hasTestPath = /app\/lib\/test\/|modules\/.*\/test\/|\.liquid/.test(cleanMessage); if (this.testName) {