diff --git a/src/command/render/render-contexts.ts b/src/command/render/render-contexts.ts index 03620fe8c10..a7c227f28ee 100644 --- a/src/command/render/render-contexts.ts +++ b/src/command/render/render-contexts.ts @@ -678,15 +678,20 @@ const readExtensionFormat = async ( ) => { // Determine effective extension - use default for certain project/format combinations let effectiveExtension = formatDesc.extension; + let preferLocal = false; - // For book projects with typst format and no explicit extension, - // use orange-book as the default typst book template if ( - !effectiveExtension && formatDesc.baseFormat === "typst" && project?.config?.project?.[kProjectType] === "book" ) { - effectiveExtension = "orange-book"; + if (effectiveExtension) { + // User explicitly named a typst book extension (e.g. format: orange-book-typst), + // prefer a locally installed copy over the built-in so customizations take effect + preferLocal = true; + } else { + // No explicit extension - use orange-book as the default typst book template + effectiveExtension = "orange-book"; + } } // Read the format file and populate this @@ -697,6 +702,7 @@ const readExtensionFormat = async ( file, project?.config, project?.dir, + preferLocal, ); // Read the yaml file and resolve / bucketize diff --git a/src/extension/extension.ts b/src/extension/extension.ts index 5827e9d8601..4dfdbddd1d0 100644 --- a/src/extension/extension.ts +++ b/src/extension/extension.ts @@ -119,9 +119,15 @@ export function createExtensionContext(): ExtensionContext { input: string, config?: ProjectConfig, projectDir?: string, + preferLocal = false, ): Promise => { // Load the extension and resolve any paths - const unresolved = await loadExtension(name, input, projectDir); + const unresolved = await loadExtension( + name, + input, + projectDir, + preferLocal, + ); return resolveExtensionPaths(unresolved, input, config); }; @@ -342,9 +348,15 @@ const loadExtension = async ( extension: string, input: string, projectDir?: string, + preferLocal = false, ): Promise => { const extensionId = toExtensionId(extension); - const extensionPath = discoverExtensionPath(input, extensionId, projectDir); + const extensionPath = discoverExtensionPath( + input, + extensionId, + projectDir, + preferLocal, + ); if (extensionPath) { // Find the metadata file, if any @@ -585,8 +597,9 @@ export function discoverExtensionPath( input: string, extensionId: ExtensionId, projectDir?: string, + preferLocal = false, ) { - const extensionDirGlobs = []; + const extensionDirGlobs: string[] = []; if (extensionId.organization) { // If there is an organization, always match that exactly extensionDirGlobs.push( @@ -619,6 +632,41 @@ export function discoverExtensionPath( } }; + const findLocalExtensionDir = () => { + const sourceDir = Deno.statSync(input).isDirectory ? input : dirname(input); + const sourceDirAbs = normalizePath(sourceDir); + + if (projectDir && isSubdir(projectDir, sourceDirAbs)) { + let extensionDir; + let currentDir = normalize(sourceDirAbs); + const projDir = normalize(projectDir); + while (!extensionDir) { + extensionDir = findExtensionDir( + join(currentDir, kExtensionDir), + extensionDirGlobs, + ); + if (currentDir == projDir) { + break; + } + currentDir = dirname(currentDir); + } + return extensionDir; + } else { + return findExtensionDir( + join(sourceDirAbs, kExtensionDir), + extensionDirGlobs, + ); + } + }; + + // When preferLocal is set, check local project extensions first + if (preferLocal) { + const localDir = findLocalExtensionDir(); + if (localDir) { + return localDir; + } + } + // check for built-in const builtinExtensionDir = findExtensionDir( builtinExtensions(), @@ -646,30 +694,9 @@ export function discoverExtensionPath( } } - // Start in the source directory - const sourceDir = Deno.statSync(input).isDirectory ? input : dirname(input); - const sourceDirAbs = normalizePath(sourceDir); - - if (projectDir && isSubdir(projectDir, sourceDirAbs)) { - let extensionDir; - let currentDir = normalize(sourceDirAbs); - const projDir = normalize(projectDir); - while (!extensionDir) { - extensionDir = findExtensionDir( - join(currentDir, kExtensionDir), - extensionDirGlobs, - ); - if (currentDir == projDir) { - break; - } - currentDir = dirname(currentDir); - } - return extensionDir; - } else { - return findExtensionDir( - join(sourceDirAbs, kExtensionDir), - extensionDirGlobs, - ); + // Check local project extensions (when not already checked via preferLocal) + if (!preferLocal) { + return findLocalExtensionDir(); } } diff --git a/src/extension/types.ts b/src/extension/types.ts index 6de34af6dfd..60287944c2c 100644 --- a/src/extension/types.ts +++ b/src/extension/types.ts @@ -71,6 +71,7 @@ export interface ExtensionContext { input: string, config?: ProjectConfig, projectDir?: string, + preferLocal?: boolean, ): Promise; find( name: string, diff --git a/tests/docs/smoke-all/typst/override-orange-book/.gitignore b/tests/docs/smoke-all/typst/override-orange-book/.gitignore new file mode 100644 index 00000000000..ad293093b07 --- /dev/null +++ b/tests/docs/smoke-all/typst/override-orange-book/.gitignore @@ -0,0 +1,2 @@ +/.quarto/ +**/*.quarto_ipynb diff --git a/tests/docs/smoke-all/typst/override-orange-book/_extensions/orange-book/_extension.yml b/tests/docs/smoke-all/typst/override-orange-book/_extensions/orange-book/_extension.yml new file mode 100644 index 00000000000..63de6871448 --- /dev/null +++ b/tests/docs/smoke-all/typst/override-orange-book/_extensions/orange-book/_extension.yml @@ -0,0 +1,8 @@ +title: Override Test +author: Test +version: 0.1.0 +contributes: + formats: + typst: + template-partials: + - typst-show.typ diff --git a/tests/docs/smoke-all/typst/override-orange-book/_extensions/orange-book/typst-show.typ b/tests/docs/smoke-all/typst/override-orange-book/_extensions/orange-book/typst-show.typ new file mode 100644 index 00000000000..9642ebb3f73 --- /dev/null +++ b/tests/docs/smoke-all/typst/override-orange-book/_extensions/orange-book/typst-show.typ @@ -0,0 +1 @@ +// LOCAL-OVERRIDE-MARKER diff --git a/tests/docs/smoke-all/typst/override-orange-book/_quarto.yml b/tests/docs/smoke-all/typst/override-orange-book/_quarto.yml new file mode 100644 index 00000000000..68408b6ba54 --- /dev/null +++ b/tests/docs/smoke-all/typst/override-orange-book/_quarto.yml @@ -0,0 +1,13 @@ +project: + type: book + +book: + title: "Override Test" + author: "Test Author" + date: "2024-01-01" + chapters: + - index.qmd + +format: + orange-book-typst: + keep-typ: true diff --git a/tests/docs/smoke-all/typst/override-orange-book/index.qmd b/tests/docs/smoke-all/typst/override-orange-book/index.qmd new file mode 100644 index 00000000000..c12f6ea9c6c --- /dev/null +++ b/tests/docs/smoke-all/typst/override-orange-book/index.qmd @@ -0,0 +1,14 @@ +--- +keep-typ: true +_quarto: + render-project: true + tests: + orange-book-typst: + ensureTypstFileRegexMatches: + - # must match + - "LOCAL-OVERRIDE-MARKER" +--- + +# Introduction + +Hello world.