Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/core/cache/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ import {
type ImmediateBufferCacheEntry,
type ImmediateStringCacheEntry,
} from "./cache-types.ts";
import { Cloneable } from "../safe-clone-deep.ts";
export { type ProjectCache } from "./cache-types.ts";

const currentCacheVersion = "1";
const requiredQuartoVersions: Record<string, string> = {
"1": ">1.7.0",
};

class ProjectCacheImpl {
class ProjectCacheImpl implements Cloneable<ProjectCacheImpl> {
projectScratchDir: string;
index: Deno.Kv | null;

Expand All @@ -35,6 +36,10 @@ class ProjectCacheImpl {
this.index = null;
}

clone() {
return this;
Comment thread
cscheid marked this conversation as resolved.
}

close() {
if (this.index) {
this.index.close();
Expand Down
36 changes: 36 additions & 0 deletions src/core/safe-clone-deep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* safe-clone-deep.ts
*
* CloneDeep that uses object's own cloning methods when available
*
* Copyright (C) 2025 Posit Software, PBC
*/

export interface Cloneable<T> {
clone(): T;
}

export function safeCloneDeep<T>(obj: T): T {
if (obj === null || typeof obj !== "object") {
return obj;
}

// Handle arrays
if (Array.isArray(obj)) {
return obj.map((item) => safeCloneDeep(item)) as T;
}

if (obj && ("clone" in obj) && typeof obj.clone === "function") {
return obj.clone();
}

// Handle regular objects
const result = {} as T;
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
result[key] = safeCloneDeep(obj[key]);
}
}

return result;
}
3 changes: 3 additions & 0 deletions src/core/sass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ export async function compileWithCache(
const result = await memoizedGetVarsBlock(project, input);
return input + "\n" + result;
} catch (e) {
if (e.name !== "SCSSParsingError") {
throw e;
}
console.warn("Error adding css vars block", e);
console.warn(
"The resulting CSS file will not have SCSS color variables exported as CSS.",
Expand Down
16 changes: 15 additions & 1 deletion src/core/sass/add-css-vars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,22 @@ import { propagateDeclarationTypes } from "./analyzer/declaration-types.ts";
import { getVariableDependencies } from "./analyzer/get-dependencies.ts";

const { getSassAst } = makeParserModule(parse);

export class SCSSParsingError extends Error {
constructor(message: string) {
super(`SCSS Parsing Error: ${message}`);
this.name = "SCSSParsingError";
}
}

export const cssVarsBlock = (scssSource: string) => {
const ast = propagateDeclarationTypes(cleanSassAst(getSassAst(scssSource)));
let astOriginal;
try {
astOriginal = getSassAst(scssSource);
} catch (e) {
throw new SCSSParsingError(e.message);
}
const ast = propagateDeclarationTypes(cleanSassAst(astOriginal));
const deps = getVariableDependencies(ast);

const output: string[] = [":root {"];
Expand Down
7 changes: 6 additions & 1 deletion src/core/sass/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ import { TempContext } from "../temp.ts";
import { safeRemoveIfExists } from "../path.ts";
import * as log from "../../deno_ral/log.ts";
import { onCleanup } from "../cleanup.ts";
import { Cloneable } from "../safe-clone-deep.ts";

class SassCache {
class SassCache implements Cloneable<SassCache> {
kv: Deno.Kv;
path: string;

clone() {
return this;
}

constructor(kv: Deno.Kv, path: string) {
this.kv = kv;
this.path = path;
Expand Down
3 changes: 2 additions & 1 deletion src/render/notebook/notebook-contributor-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import { isQmdFile } from "../../execute/qmd.ts";
import { dirAndStem } from "../../core/path.ts";
import { projectOutputDir } from "../../project/project-shared.ts";
import { existsSync } from "../../deno_ral/fs.ts";
import { safeCloneDeep } from "../../core/safe-clone-deep.ts";

export const htmlNotebookContributor: NotebookContributor = {
resolve: resolveHtmlNotebook,
Expand Down Expand Up @@ -85,7 +86,7 @@ function resolveHtmlNotebook(
executedFile: ExecutedFile,
notebookMetadata?: NotebookMetadata,
) {
const resolved = ld.cloneDeep(executedFile) as ExecutedFile;
const resolved = safeCloneDeep(executedFile) as ExecutedFile;

// Set the output file
resolved.recipe.format.pandoc[kOutputFile] = `${outputFile(nbAbsPath)}`;
Expand Down
3 changes: 2 additions & 1 deletion src/render/notebook/notebook-contributor-ipynb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { ipynbTitleTemplatePath } from "../../format/ipynb/format-ipynb.ts";
import { projectOutputDir } from "../../project/project-shared.ts";
import { existsSync } from "../../deno_ral/fs.ts";
import { dirname, join, relative } from "../../deno_ral/path.ts";
import { safeCloneDeep } from "../../core/safe-clone-deep.ts";

export const outputNotebookContributor: NotebookContributor = {
resolve: resolveOutputNotebook,
Expand Down Expand Up @@ -67,7 +68,7 @@ function resolveOutputNotebook(
executedFile: ExecutedFile,
_notebookMetadata?: NotebookMetadata,
) {
const resolved = ld.cloneDeep(executedFile);
const resolved = safeCloneDeep(executedFile);
resolved.recipe.format.pandoc[kOutputFile] = outputFile(nbAbsPath);
resolved.recipe.output = resolved.recipe.format.pandoc[kOutputFile];

Expand Down
3 changes: 2 additions & 1 deletion src/render/notebook/notebook-contributor-jats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import * as ld from "../../core/lodash.ts";

import { error } from "../../deno_ral/log.ts";
import { Format } from "../../config/types.ts";
import { safeCloneDeep } from "../../core/safe-clone-deep.ts";

export const jatsContributor: NotebookContributor = {
resolve: resolveJats,
Expand All @@ -56,7 +57,7 @@ function resolveJats(
executedFile: ExecutedFile,
_notebookMetadata?: NotebookMetadata,
) {
const resolved = ld.cloneDeep(executedFile);
const resolved = safeCloneDeep(executedFile);
const to =
resolved.recipe.format.render[kVariant]?.includes("+element_citations")
? "jats+element_citations"
Expand Down
3 changes: 2 additions & 1 deletion src/render/notebook/notebook-contributor-qmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { ipynbTitleTemplatePath } from "../../format/ipynb/format-ipynb.ts";
import { projectScratchPath } from "../../project/project-scratch.ts";
import { ensureDirSync, existsSync } from "../../deno_ral/fs.ts";
import { dirname, join, relative } from "../../deno_ral/path.ts";
import { safeCloneDeep } from "../../core/safe-clone-deep.ts";

export const qmdNotebookContributor: NotebookContributor = {
resolve: resolveOutputNotebook,
Expand Down Expand Up @@ -86,7 +87,7 @@ function resolveOutputNotebook(
executedFile: ExecutedFile,
_notebookMetadata?: NotebookMetadata,
) {
const resolved = ld.cloneDeep(executedFile);
const resolved = safeCloneDeep(executedFile);
resolved.recipe.format.pandoc[kOutputFile] = ipynbOutputFile(nbAbsPath);
resolved.recipe.output = resolved.recipe.format.pandoc[kOutputFile];

Expand Down
Loading