diff --git a/news/changelog-1.7.md b/news/changelog-1.7.md index b79033ace4c..8f1d25f1985 100644 --- a/news/changelog-1.7.md +++ b/news/changelog-1.7.md @@ -167,6 +167,7 @@ All changes included in 1.7: - A new folder `quarto-session-temp` can be created in `.quarto` to store temporary files created by Quarto during a rendering. Reminder: `.quarto` is for internal use of Quarto and should not be versioned (thus added to `.gitignore`). - ([fb38eb5](https://github.com/quarto-dev/quarto-cli/commit/fb38eb56c11e09f44cef58fd3b697ff24bb5a3f3)) Use the `latest` parser for Acorn when analyzing JS code imported from OJS blocks. - ([#7260](https://github.com/quarto-dev/quarto-cli/issues/7260)): Add support for `active` class in tabsets so the `.active` tab shows up by default. +- ([#7757](https://github.com/quarto-dev/quarto-cli/issues/7757)): Session temporary files are now cleaned up when the session ends abnormally (e.g. `Ctrl+C`) also on Windows. - ([#8613](https://github.com/quarto-dev/quarto-cli/issues/8613)): Fix `giscus` color on load to support dark mode (by @kv9898). - ([#9867](https://github.com/quarto-dev/quarto-cli/issues/9867)): Blank lines are now trimmed in Raw HTML Table blocks. - ([#10532](https://github.com/quarto-dev/quarto-cli/issues/10532)): Changed default of `--headless=old` to `--headless` as [Chrome 132 has removed old headless mode](https://developer.chrome.com/blog/removing-headless-old-from-chrome) and only support new mode. To use old mode, set `QUARTO_CHROMIUM` to a [new `chrome-headless-shell` binary](https://developer.chrome.com/blog/chrome-headless-shell) or to an older chrome version (between 128 and 132). Set `QUARTO_CHROMIUM_HEADLESS_MODE` to `old` to use old headless mode with that compatible version. diff --git a/src/core/main.ts b/src/core/main.ts index 25b45f5b653..abbdbdcf86b 100644 --- a/src/core/main.ts +++ b/src/core/main.ts @@ -27,8 +27,12 @@ export async function mainRunner(runner: Runner) { await initializeLogger(logOptions(args)); // install termination signal handlers + + // Even though windows doesn't technically have signals, Deno + // does the "expected" thing here and calls abend + // on interruption. + Deno.addSignalListener("SIGINT", abend); if (!isWindows) { - Deno.addSignalListener("SIGINT", abend); Deno.addSignalListener("SIGTERM", abend); } diff --git a/src/core/once.ts b/src/core/once.ts new file mode 100644 index 00000000000..deab336da8d --- /dev/null +++ b/src/core/once.ts @@ -0,0 +1,15 @@ +/* + * once.ts + * + * Copyright (C) 2025 Posit Software, PBC + */ + +export const once = (fn: () => void) => { + let called = false; + return () => { + if (!called) { + called = true; + fn(); + } + }; +}; diff --git a/src/project/project-context.ts b/src/project/project-context.ts index 1208a7f0d14..7d3ec560344 100644 --- a/src/project/project-context.ts +++ b/src/project/project-context.ts @@ -104,6 +104,9 @@ import { makeTimedFunctionAsync } from "../core/performance/function-times.ts"; import { createProjectCache } from "../core/cache/cache.ts"; import { createTempContext } from "../core/temp.ts"; +import { onCleanup } from "../core/cleanup.ts"; +import { once } from "../core/once.ts"; + export async function projectContext( path: string, notebookContext: NotebookContext, @@ -314,11 +317,11 @@ export async function projectContext( isSingleFile: false, diskCache: await createProjectCache(join(dir, ".quarto")), temp, - cleanup: () => { + cleanup: once(() => { cleanupFileInformationCache(result); result.diskCache.close(); temp.cleanup(); - }, + }), }; // see if the project [kProjectType] wants to filter the project config @@ -357,7 +360,7 @@ export async function projectContext( config: configFiles, configResources: projectConfigResources(dir, projectConfig, type), }; - + onCleanup(result.cleanup); return result; } else { debug(`projectContext: Found Quarto project in ${dir}`); @@ -408,11 +411,11 @@ export async function projectContext( isSingleFile: false, diskCache: await createProjectCache(join(dir, ".quarto")), temp, - cleanup: () => { + cleanup: once(() => { cleanupFileInformationCache(result); result.diskCache.close(); temp.cleanup(); - }, + }), }; const { files, engines } = await projectInputFiles( result, @@ -425,6 +428,7 @@ export async function projectContext( config: configFiles, configResources: projectConfigResources(dir, projectConfig), }; + onCleanup(result.cleanup); return result; } } else { @@ -486,11 +490,11 @@ export async function projectContext( isSingleFile: false, diskCache: await createProjectCache(join(temp.baseDir, ".quarto")), temp, - cleanup: () => { + cleanup: once(() => { cleanupFileInformationCache(context); context.diskCache.close(); temp.cleanup(); - }, + }), }; if (Deno.statSync(path).isDirectory) { const { files, engines } = await projectInputFiles(context); @@ -503,6 +507,7 @@ export async function projectContext( context.files.input = [input]; } debug(`projectContext: Found Quarto project in ${originalDir}`); + onCleanup(context.cleanup); return context; } else { return undefined; diff --git a/src/project/types/single-file/single-file.ts b/src/project/types/single-file/single-file.ts index 6cfea695a9c..a38b5ec227d 100644 --- a/src/project/types/single-file/single-file.ts +++ b/src/project/types/single-file/single-file.ts @@ -20,6 +20,7 @@ import { RenderFlags } from "../../../command/render/types.ts"; import { MappedString } from "../../../core/mapped-text.ts"; import { fileExecutionEngineAndTarget } from "../../../execute/engine.ts"; import { + cleanupFileInformationCache, projectFileMetadata, projectResolveBrand, projectResolveFullMarkdownForFile, @@ -27,6 +28,7 @@ import { import { ExecutionEngine } from "../../../execute/types.ts"; import { createProjectCache } from "../../../core/cache/cache.ts"; import { globalTempContext } from "../../../core/temp.ts"; +import { once } from "../../../core/once.ts"; export async function singleFileProjectContext( source: string, @@ -77,10 +79,15 @@ export async function singleFileProjectContext( isSingleFile: true, diskCache: await createProjectCache(projectCacheBaseDir), temp, - cleanup: () => { + cleanup: once(() => { + cleanupFileInformationCache(result); result.diskCache.close(); - }, + }), }; + // because the single-file project is cleaned up with + // the global text context, we don't need to register it + // in the same way that we need to register the multi-file + // projects. temp.onCleanup(result.cleanup); return result; }