diff --git a/package.json b/package.json index 11ab54c..8b22e5d 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,7 @@ "version": "0.0.0", "private": true, "description": "Streamline API development in VSCode. Run Hurl requests, manage variables, and view responses directly in your editor.", - "categories": [ - "Other" - ], + "categories": ["Other"], "homepage": "https://github.com/jellydn/vscode-hurl-runner#readme", "bugs": { "url": "https://github.com/jellydn/vscode-hurl-runner/issues" @@ -20,11 +18,7 @@ "author": "Dung Huynh Duc ", "publisher": "jellydn", "main": "./dist/index.js", - "files": [ - "LICENSE.md", - "dist/*", - "res/*" - ], + "files": ["LICENSE.md", "dist/*", "res/*"], "scripts": { "build": "tsup src/index.ts --external vscode", "dev": "nr build --watch", @@ -93,9 +87,7 @@ } ] }, - "activationEvents": [ - "onStartupFinished" - ], + "activationEvents": ["onStartupFinished"], "devDependencies": { "@biomejs/biome": "1.9.3", "@types/node": "^22.7.5", @@ -121,4 +113,4 @@ "dependencies": { "nano-spawn": "^0.1.0" } -} \ No newline at end of file +} diff --git a/src/hurl-parser.ts b/src/hurl-parser.ts index 312f1c6..66b57d4 100644 --- a/src/hurl-parser.ts +++ b/src/hurl-parser.ts @@ -15,12 +15,14 @@ interface ParsedHurlOutput { entries: ParsedEntry[]; } -function formatTimings(timings: Record): Record { +function formatTimings( + timings: Record, +): Record { const formattedTimings: Record = {}; for (const [key, value] of Object.entries(timings)) { - if (key !== 'begin' && key !== 'end') { - if (value.endsWith('µs')) { + if (key !== "begin" && key !== "end") { + if (value.endsWith("µs")) { // Convert microseconds to a more readable format const microseconds = Number.parseInt(value.slice(0, -3)); let formattedValue: string; @@ -45,78 +47,86 @@ function formatTimings(timings: Record): Record // A line starting with ‘>’ means data sent by Hurl. // A line staring with ‘<’ means data received by Hurl. // A line starting with ‘*’ means additional info provided by Hurl. -export function parseHurlOutput(stderr: string, stdout: string): ParsedHurlOutput { - const lines = stderr.split('\n'); +export function parseHurlOutput( + stderr: string, + stdout: string, +): ParsedHurlOutput { + const lines = stderr.split("\n"); const entries: ParsedEntry[] = []; let currentEntry: ParsedEntry | null = null; let isResponseHeader = false; let isTimings = false; for (const line of lines) { - if (line.startsWith('* Executing entry')) { + if (line.startsWith("* Executing entry")) { if (currentEntry) { entries.push(currentEntry); } currentEntry = { - requestMethod: '', - requestUrl: '', + requestMethod: "", + requestUrl: "", requestHeaders: {}, response: { - status: '', + status: "", headers: {}, - body: '' + body: "", }, - timings: {} + timings: {}, }; isResponseHeader = false; isTimings = false; - } else if (line.startsWith('* Request:')) { + } else if (line.startsWith("* Request:")) { const match = line.match(/\* Request:\s*\* (\w+) (.*)/); if (match && currentEntry) { currentEntry.requestMethod = match[1]; currentEntry.requestUrl = match[2]; } - } else if (line.startsWith('* curl')) { + } else if (line.startsWith("* curl")) { if (currentEntry) { currentEntry.curlCommand = line.slice(2).trim(); } - } else if (line.startsWith('> ')) { - if (line.startsWith('> GET ') || line.startsWith('> POST ') || line.startsWith('> PUT ') || line.startsWith('> DELETE ')) { - const [method, path] = line.slice(2).split(' '); + } else if (line.startsWith("> ")) { + if ( + line.startsWith("> GET ") || + line.startsWith("> POST ") || + line.startsWith("> PUT ") || + line.startsWith("> DELETE ") + ) { + const [method, path] = line.slice(2).split(" "); if (currentEntry) { currentEntry.requestMethod = method; currentEntry.requestUrl = path; } } else { - const [key, ...values] = line.slice(2).split(':'); + const [key, ...values] = line.slice(2).split(":"); if (key && values.length && currentEntry) { - currentEntry.requestHeaders[key.trim()] = values.join(':').trim(); + currentEntry.requestHeaders[key.trim()] = values.join(":").trim(); } } - } else if (line.startsWith('< ')) { + } else if (line.startsWith("< ")) { isResponseHeader = true; - if (line.startsWith('< HTTP/')) { + if (line.startsWith("< HTTP/")) { if (currentEntry) { currentEntry.response.status = line.slice(2); } } else { - const [key, ...values] = line.slice(2).split(':'); + const [key, ...values] = line.slice(2).split(":"); if (key && values.length && currentEntry) { - currentEntry.response.headers[key.trim()] = values.join(':').trim(); + currentEntry.response.headers[key.trim()] = values.join(":").trim(); } } - } else if (line.startsWith('* Timings:')) { + } else if (line.startsWith("* Timings:")) { isTimings = true; - } else if (isTimings && line.trim() !== '') { + } else if (isTimings && line.trim() !== "") { // Remove the '* ' prefix if it exists - const cleanedLine = line.startsWith('* ') ? line.slice(2) : line; - const [key, value] = cleanedLine.split(':').map(s => s.trim()); - if (currentEntry && key && value && key !== 'begin' && key !== 'end') { + const cleanedLine = line.startsWith("* ") ? line.slice(2) : line; + const [key, value] = cleanedLine.split(":").map((s) => s.trim()); + if (currentEntry && key && value && key !== "begin" && key !== "end") { if (currentEntry.timings) { currentEntry.timings[key] = value; } } - } else if (isTimings && line.trim() === '') { + } else if (isTimings && line.trim() === "") { isTimings = false; if (currentEntry?.timings) { currentEntry.timings = formatTimings(currentEntry.timings); @@ -134,4 +144,4 @@ export function parseHurlOutput(stderr: string, stdout: string): ParsedHurlOutpu } return { entries }; -} \ No newline at end of file +} diff --git a/src/index.ts b/src/index.ts index 95baefa..275853e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -47,7 +47,10 @@ const { activate, deactivate } = defineExtension(() => { resultPanel.reveal(vscode.ViewColumn.Two); }; - const showResultInWebView = (result: { stdout: string; stderr: string }, isError = false) => { + const showResultInWebView = ( + result: { stdout: string; stderr: string }, + isError = false, + ) => { responseLogger.clear(); responseLogger.info(`Stdout: ${result.stdout}`); responseLogger.info(`Stderr: ${result.stderr}`); @@ -64,41 +67,50 @@ const { activate, deactivate } = defineExtension(() => { }); } - const title = isError ? 'Hurl Runner: Error' : 'Hurl Runner: Result'; + const title = isError ? "Hurl Runner: Error" : "Hurl Runner: Result"; // Parse the output const parsedOutput = parseHurlOutput(result.stderr, result.stdout); // Create a formatted HTML output for each entry - const htmlOutput = parsedOutput.entries.map((entry, index) => { - let bodyType = 'text'; - let formattedBody = entry.response.body || 'No response body'; - if (formattedBody.trim().startsWith('{')) { - bodyType = 'json'; - try { - formattedBody = JSON.stringify(JSON.parse(formattedBody), null, 2); - } catch { - // If parsing fails, leave it as is + const htmlOutput = parsedOutput.entries + .map((entry, index) => { + let bodyType = "text"; + let formattedBody = entry.response.body || "No response body"; + if (formattedBody.trim().startsWith("{")) { + bodyType = "json"; + try { + formattedBody = JSON.stringify(JSON.parse(formattedBody), null, 2); + } catch { + // If parsing fails, leave it as is + } + } else if (formattedBody.trim().startsWith("<")) { + bodyType = formattedBody.trim().startsWith("

Request

${entry.requestMethod} ${entry.requestUrl}
Headers -
${Object.entries(entry.requestHeaders).map(([key, value]) => `${key}: ${value}`).join('\n')}
+
${Object.entries(
+							entry.requestHeaders,
+						)
+							.map(([key, value]) => `${key}: ${value}`)
+							.join("\n")}
- ${entry.curlCommand ? ` + ${ + entry.curlCommand + ? `
cURL Command
${entry.curlCommand}
- ` : ''} + ` + : "" + }

Response Body

${formattedBody}
@@ -107,18 +119,29 @@ const { activate, deactivate } = defineExtension(() => { Response Details

Status: ${entry.response.status}

Headers

-
${Object.entries(entry.response.headers).map(([key, value]) => `${key}: ${value}`).join('\n')}
+
${Object.entries(
+							entry.response.headers,
+						)
+							.map(([key, value]) => `${key}: ${value}`)
+							.join("\n")}
- ${entry.timings ? ` + ${ + entry.timings + ? `
Timings -
${Object.entries(entry.timings).map(([key, value]) => `${key}: ${value}`).join('\n')}
+
${Object.entries(entry.timings)
+							.map(([key, value]) => `${key}: ${value}`)
+							.join("\n")}
- ` : ''} + ` + : "" + } `; - }).join('
'); + }) + .join("
"); resultPanel.webview.html = ` @@ -154,7 +177,7 @@ const { activate, deactivate } = defineExtension(() => { async () => { const editor = vscode.window.activeTextEditor; if (!editor) { - showResultInWebView({ stdout: '', stderr: "No active editor" }, true); + showResultInWebView({ stdout: "", stderr: "No active editor" }, true); return; } @@ -167,7 +190,10 @@ const { activate, deactivate } = defineExtension(() => { try { const entry = findEntryAtLine(fileContent, currentLine); if (!entry) { - showResultInWebView({ stdout: '', stderr: "No Hurl entry found at the current line" }, true); + showResultInWebView( + { stdout: "", stderr: "No Hurl entry found at the current line" }, + true, + ); return; } @@ -184,8 +210,9 @@ const { activate, deactivate } = defineExtension(() => { showResultInWebView(result); } catch (error) { - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - showResultInWebView({ stdout: '', stderr: errorMessage }, true); + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + showResultInWebView({ stdout: "", stderr: errorMessage }, true); } }, ); @@ -195,7 +222,7 @@ const { activate, deactivate } = defineExtension(() => { async () => { const editor = vscode.window.activeTextEditor; if (!editor) { - showResultInWebView({ stdout: '', stderr: "No active editor" }, true); + showResultInWebView({ stdout: "", stderr: "No active editor" }, true); return; } @@ -210,8 +237,9 @@ const { activate, deactivate } = defineExtension(() => { showResultInWebView(result); } catch (error) { - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - showResultInWebView({ stdout: '', stderr: errorMessage }, true); + const errorMessage = + error instanceof Error ? error.message : "Unknown error"; + showResultInWebView({ stdout: "", stderr: errorMessage }, true); } }, ); @@ -257,4 +285,4 @@ const { activate, deactivate } = defineExtension(() => { }; }); -export { activate, deactivate }; \ No newline at end of file +export { activate, deactivate }; diff --git a/src/utils.ts b/src/utils.ts index 5c20dbd..91f9eae 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -24,7 +24,7 @@ export async function executeHurl( statusBarMessage.show(); const { filePath, envFile, variables, fromEntry, toEntry } = options; // Refer https://hurl.dev/docs/manual.html#verbose - const args = [filePath, '--very-verbose']; + const args = [filePath, "--very-verbose"]; for (const [key, value] of Object.entries(variables)) { args.push("--variable", `${key}=${value}`); @@ -45,23 +45,23 @@ export async function executeHurl( logger.info(`Executing command: hurl ${args.join(" ")}`); return new Promise((resolve, reject) => { - const hurlProcess = spawn('hurl', args); - let stdout = ''; - let stderr = ''; + const hurlProcess = spawn("hurl", args); + let stdout = ""; + let stderr = ""; - hurlProcess.stdout.on('data', (data) => { + hurlProcess.stdout.on("data", (data) => { const str = data.toString(); stdout += str; logger.info(`Hurl stdout: ${str}`); }); - hurlProcess.stderr.on('data', (data) => { + hurlProcess.stderr.on("data", (data) => { const str = data.toString(); stderr += str; logger.info(`Hurl stderr: ${str}`); }); - hurlProcess.on('close', (code) => { + hurlProcess.on("close", (code) => { statusBarMessage.dispose(); if (code === 0 || (code === 1 && !stderr.includes("error:"))) { resolve({ stdout, stderr }); @@ -70,7 +70,7 @@ export async function executeHurl( } }); - hurlProcess.on('error', (error) => { + hurlProcess.on("error", (error) => { statusBarMessage.dispose(); reject(new Error(`Failed to start Hurl process: ${error.message}`)); });