Skip to content

Commit 16097de

Browse files
refactor: clean up runRequest command and centralize temp file utilities - fixes #43 (#82)
1 parent 57c880c commit 16097de

File tree

5 files changed

+292
-198
lines changed

5 files changed

+292
-198
lines changed

src/commands/executeStepZenRequest.ts

Lines changed: 2 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,16 @@
11
import * as vscode from "vscode";
2-
import * as fs from "fs";
3-
import * as path from "path";
4-
import * as os from "os";
52
import { resolveStepZenProjectRoot } from "../utils/stepzenProject";
63
import { clearResultsPanel, openResultsPanel } from "../panels/resultsPanel";
74
import { summariseDiagnostics, publishDiagnostics } from "../utils/runtimeDiagnostics";
85
import { runtimeDiag } from "../extension";
9-
import { UI, TIMEOUTS, TEMP_FILE_PATTERNS } from "../utils/constants";
6+
import { UI } from "../utils/constants";
7+
import { createTempGraphQLFile, cleanupLater } from "../utils/tempFiles";
108
import { services } from "../services";
119
import { StepZenResponse, StepZenDiagnostic } from "../types";
1210
import { ValidationError, handleError } from "../errors";
1311

1412

15-
/**
16-
* Creates a temporary GraphQL file with the provided query content
17-
* @param content The GraphQL query content to write to the file
18-
* @returns Path to the created temporary file
19-
*/
20-
function createTempGraphQLFile(content: string): string {
21-
if (!content || typeof content !== 'string') {
22-
throw new ValidationError(
23-
"Invalid query content: expected a non-empty string",
24-
"INVALID_QUERY_CONTENT"
25-
);
26-
}
27-
28-
const tempDir = os.tmpdir();
29-
const timestamp = new Date().getTime();
30-
const randomPart = Math.random().toString(36).substring(2, 10);
31-
const filename = `${TEMP_FILE_PATTERNS.QUERY_PREFIX}${timestamp}-${randomPart}${TEMP_FILE_PATTERNS.GRAPHQL_EXTENSION}`;
32-
const filePath = path.join(tempDir, filename);
33-
34-
try {
35-
fs.writeFileSync(filePath, content, 'utf8');
36-
services.logger.debug(`Created temporary query file: ${filePath}`);
37-
return filePath;
38-
} catch (err) {
39-
throw new ValidationError(
40-
`Failed to create temporary file: ${err instanceof Error ? err.message : String(err)}`,
41-
"FILE_CREATE_FAILED",
42-
err
43-
);
44-
}
45-
}
4613

47-
/**
48-
* Schedules cleanup of a temporary file
49-
* @param filePath Path to the temporary file to clean up
50-
*/
51-
function cleanupLater(filePath: string) {
52-
if (!filePath || typeof filePath !== 'string') {
53-
services.logger.warn("Invalid path provided to cleanupLater");
54-
return;
55-
}
56-
57-
setTimeout(() => {
58-
try {
59-
if (fs.existsSync(filePath)) {
60-
fs.unlinkSync(filePath);
61-
services.logger.debug(`Cleaned up temporary file: ${filePath}`);
62-
}
63-
} catch (e) {
64-
handleError(new ValidationError(
65-
`Failed to clean up temporary file: ${filePath}`,
66-
"FILE_CLEANUP_FAILED",
67-
e
68-
));
69-
}
70-
}, TIMEOUTS.FILE_CLEANUP_DELAY_MS);
71-
}
7214

7315
/**
7416
* Executes a StepZen request using the CLI service

src/commands/runRequest.ts

Lines changed: 16 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -32,31 +32,7 @@ function extractOperationNames(query: string): string[] {
3232
return [...query.matchAll(GRAPHQL.OPERATION_TYPE_PATTERN)].map(([, , name]) => name);
3333
}
3434

35-
/**
36-
* Creates a temporary file containing the GraphQL query
37-
* @param query The GraphQL query string to write to the file
38-
* @returns Path to the created temporary file
39-
* @throws Error if query is invalid
40-
*/
41-
// function createTempGraphQLFile(query: string): string {
42-
// // Add validation
43-
// if (!query || typeof query !== 'string') {
44-
// throw new ValidationError(
45-
// "Invalid query: expected a non-empty string",
46-
// "INVALID_QUERY"
47-
// );
48-
// }
4935

50-
// const tmpDir = os.tmpdir();
51-
// const timestamp = new Date().getTime();
52-
// const tmp = path.join(
53-
// tmpDir,
54-
// `stepzen-request-${timestamp}.graphql`
55-
// );
56-
// fs.writeFileSync(tmp, query);
57-
// services.logger.debug(`Created temporary query file: ${tmp}`);
58-
// return tmp;
59-
// }
6036

6137
const SCALARS = new Set<string>(GRAPHQL.SCALAR_TYPES);
6238

@@ -181,7 +157,7 @@ export async function runGraphQLRequest() {
181157

182158
// Verify document exists
183159
if (!editor.document) {
184-
vscode.window.showErrorMessage("No document available in active editor.");
160+
vscode.window.showErrorMessage(MESSAGES.NO_DOCUMENT_AVAILABLE);
185161
services.logger.warn("Run GraphQL Request failed: No document available");
186162
return;
187163
}
@@ -190,7 +166,7 @@ export async function runGraphQLRequest() {
190166
editor.selection.isEmpty ? undefined : editor.selection
191167
);
192168
if (!query || !query.trim()) {
193-
vscode.window.showWarningMessage("No GraphQL query selected or found.");
169+
vscode.window.showWarningMessage(MESSAGES.NO_GRAPHQL_QUERY_FOUND);
194170
services.logger.warn("Run GraphQL Request failed: No query found");
195171
return;
196172
}
@@ -239,15 +215,14 @@ export async function runGraphQLRequest() {
239215
export async function runOperation(operation: OperationEntry) {
240216
// Check workspace trust first
241217
if (!vscode.workspace.isTrusted) {
242-
const message = "Running GraphQL operations is not available in untrusted workspaces. Open this folder in a trusted workspace to enable this feature.";
243-
vscode.window.showWarningMessage(message);
218+
vscode.window.showWarningMessage(MESSAGES.GRAPHQL_OPERATIONS_NOT_AVAILABLE_UNTRUSTED);
244219
return;
245220
}
246221

247222
// Validate operation parameter
248223
if (!operation || !operation.fileUri) {
249224
handleError(new ValidationError(
250-
"Invalid operation provided",
225+
MESSAGES.INVALID_OPERATION_PROVIDED,
251226
"INVALID_OPERATION"
252227
));
253228
return;
@@ -257,7 +232,7 @@ export async function runOperation(operation: OperationEntry) {
257232
const document = await vscode.workspace.openTextDocument(operation.fileUri);
258233
if (!document) {
259234
handleError(new ValidationError(
260-
`Could not open document: ${operation.fileUri.toString()}`,
235+
`${MESSAGES.DOCUMENT_NOT_FOUND}: ${operation.fileUri.toString()}`,
261236
"DOCUMENT_NOT_FOUND"
262237
));
263238
return;
@@ -266,7 +241,7 @@ export async function runOperation(operation: OperationEntry) {
266241

267242
// Validate operation range
268243
if (!operation.range || typeof operation.range.start !== 'number' || typeof operation.range.end !== 'number') {
269-
vscode.window.showErrorMessage("Invalid operation range");
244+
vscode.window.showErrorMessage(MESSAGES.INVALID_OPERATION_RANGE);
270245
return;
271246
}
272247

@@ -300,38 +275,37 @@ export async function runOperation(operation: OperationEntry) {
300275
export async function runPersisted(documentId: string, operationName: string) {
301276
// Check workspace trust first
302277
if (!vscode.workspace.isTrusted) {
303-
const message = "Running persisted GraphQL operations is not available in untrusted workspaces. Open this folder in a trusted workspace to enable this feature.";
304-
vscode.window.showWarningMessage(message);
278+
vscode.window.showWarningMessage(MESSAGES.PERSISTED_OPERATIONS_NOT_AVAILABLE_UNTRUSTED);
305279
return;
306280
}
307281

308282
// Validate parameters
309283
if (!documentId || typeof documentId !== 'string') {
310-
vscode.window.showErrorMessage("Invalid document ID provided");
284+
vscode.window.showErrorMessage(MESSAGES.INVALID_DOCUMENT_ID);
311285
return;
312286
}
313287

314288
if (!operationName || typeof operationName !== 'string') {
315-
vscode.window.showErrorMessage("Invalid operation name provided");
289+
vscode.window.showErrorMessage(MESSAGES.INVALID_OPERATION_NAME);
316290
return;
317291
}
318292

319293
const persistedDocMap = services.schemaIndex.getPersistedDocMap();
320294
if (!persistedDocMap) {
321-
vscode.window.showErrorMessage("Persisted document map is not available");
295+
vscode.window.showErrorMessage(MESSAGES.PERSISTED_DOC_MAP_NOT_AVAILABLE);
322296
return;
323297
}
324298

325299
const entry = Object.values(persistedDocMap).find(e => e && e.documentId === documentId);
326300
if (!entry) {
327-
vscode.window.showErrorMessage("Could not find persisted document.");
301+
vscode.window.showErrorMessage(MESSAGES.PERSISTED_DOC_NOT_FOUND);
328302
return;
329303
}
330304

331305
try {
332306
// Open the file to extract the operation for variable analysis
333307
if (!entry.fileUri) {
334-
vscode.window.showErrorMessage("Invalid file URI in persisted document entry");
308+
vscode.window.showErrorMessage(MESSAGES.INVALID_FILE_URI);
335309
return;
336310
}
337311

@@ -345,20 +319,20 @@ export async function runPersisted(documentId: string, operationName: string) {
345319

346320
// Validate operations array
347321
if (!entry.operations || !Array.isArray(entry.operations)) {
348-
vscode.window.showErrorMessage("Invalid operations list in persisted document entry");
322+
vscode.window.showErrorMessage(MESSAGES.INVALID_OPERATIONS_LIST);
349323
return;
350324
}
351325

352326
// Find the specific operation
353327
const op = entry.operations.find(o => o && o.name === operationName);
354328
if (!op) {
355-
vscode.window.showErrorMessage(`Operation "${operationName}" not found in document.`);
329+
vscode.window.showErrorMessage(MESSAGES.OPERATION_NOT_FOUND_IN_DOC.replace("{0}", operationName));
356330
return;
357331
}
358332

359333
// Validate operation range
360334
if (!op.range || typeof op.range.start !== 'number' || typeof op.range.end !== 'number') {
361-
vscode.window.showErrorMessage("Invalid operation range");
335+
vscode.window.showErrorMessage(MESSAGES.INVALID_OPERATION_RANGE);
362336
return;
363337
}
364338

@@ -389,102 +363,6 @@ export async function runPersisted(documentId: string, operationName: string) {
389363
export function clearResults(): void {
390364
clearResultsPanel();
391365
runtimeDiag.clear(); // Also clear any diagnostics
392-
services.logger.info("Results cleared");
366+
services.logger.info(MESSAGES.RESULTS_CLEARED);
393367
}
394368

395-
/* -------------------------------------------------------------
396-
* Helper utilities
397-
* ------------------------------------------------------------*/
398-
/**
399-
* Executes a shell command asynchronously and returns the output
400-
*
401-
* @param command The command string to execute
402-
* @param options Options for the child process
403-
* @returns Promise resolving to an object containing stdout
404-
*/
405-
/**
406-
* Executes a shell command asynchronously and returns the output
407-
*
408-
* @param command The command string to execute
409-
* @param options Options for the child process
410-
* @returns Promise resolving to an object containing stdout
411-
* @throws Error if command is invalid or execution fails
412-
*/
413-
/**
414-
* Executes a shell command asynchronously
415-
* @param command The command to execute
416-
* @param options Options for the child process
417-
* @returns Promise resolving to the command's stdout
418-
* @throws StepZenError if the command fails
419-
*
420-
* Note: For StepZen CLI operations, prefer using the StepzenCliService instead.
421-
*/
422-
423-
// TODO: cleanup old comments
424-
// This function is kept for backward compatibility with other parts of the code
425-
// that might still be using it, but for StepZen CLI operations we now prefer the StepzenCliService
426-
// function execAsync(command: string, options: cp.ExecOptions = {}): Promise<{ stdout: string }> {
427-
// // Validate inputs
428-
// if (!command || typeof command !== 'string') {
429-
// return Promise.reject(new ValidationError(
430-
// "Invalid command: expected a non-empty string",
431-
// "INVALID_COMMAND"
432-
// ));
433-
// }
434-
435-
// return new Promise<{ stdout: string }>((resolve, reject) => {
436-
// cp.exec(command, { ...options, maxBuffer: 10_000_000 }, (err, stdout, stderr) => {
437-
// if (err) {
438-
// reject(new ValidationError(
439-
// `Command failed: ${command}`,
440-
// "COMMAND_FAILED",
441-
// err
442-
// ));
443-
// } else if (!stdout) {
444-
// resolve({ stdout: "" });
445-
// } else {
446-
// resolve({ stdout });
447-
// }
448-
// });
449-
// });
450-
// }
451-
452-
// TODO: cleanup dead code
453-
// /**
454-
// * Schedules a temporary file for cleanup after a delay
455-
// *
456-
// * @param file Path to the temporary file to delete
457-
// */
458-
// /**
459-
// * Safely deletes a temporary file after a delay
460-
// * @param file Path to the temporary file to delete
461-
// * @param delayMs Delay in milliseconds before deletion attempt
462-
// */
463-
// function cleanupLater(file: string, delayMs: number = TIMEOUTS.FILE_CLEANUP_DELAY_MS): void {
464-
// // Add validation
465-
// if (!file || typeof file !== "string") {
466-
// services.logger.warn("Invalid file path provided for cleanup");
467-
// return;
468-
// }
469-
470-
// // Only attempt to clean up files in the temp directory
471-
// if (!file.startsWith(os.tmpdir())) {
472-
// services.logger.warn(`Refusing to clean up non-temporary file: ${file}`);
473-
// return;
474-
// }
475-
476-
// setTimeout(() => {
477-
// try {
478-
// if (fs.existsSync(file)) {
479-
// fs.unlinkSync(file);
480-
// services.logger.debug(`Temporary file cleaned up: ${file}`);
481-
// }
482-
// } catch (err) {
483-
// handleError(new ValidationError(
484-
// `Failed to clean up temporary file: ${file}`,
485-
// "FILE_CLEANUP_FAILED",
486-
// err
487-
// ));
488-
// }
489-
// }, delayMs);
490-
// }

0 commit comments

Comments
 (0)