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/apiRequest.js b/lib/apiRequest.js index 70280e7ef..5c296853c 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/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 971c369e0..2635c2da9 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'; @@ -37,6 +38,7 @@ class TestLogStream extends EventEmitter { } start() { + this.abortController = new AbortController(); this.intervalId = setInterval(() => this.fetchLogs(), POLL_INTERVAL_MS); this.timeoutId = setTimeout(() => { this.emit('timeout'); @@ -47,6 +49,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 +64,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 +81,7 @@ class TestLogStream extends EventEmitter { this.processLogMessage(row); } } catch (error) { + if (error.name === 'AbortError') return; logger.Debug(`Error fetching logs: ${error.message}`); } } @@ -83,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) { @@ -136,7 +145,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 1bede03c2..89321fe23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2393,7 +2393,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" } @@ -3108,7 +3107,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" }, @@ -3170,7 +3168,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5061,7 +5058,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", @@ -10950,7 +10946,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -11438,7 +11433,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -11532,7 +11526,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" },