Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 10 additions & 4 deletions src/command/render/render-contexts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -697,6 +702,7 @@ const readExtensionFormat = async (
file,
project?.config,
project?.dir,
preferLocal,
);

// Read the yaml file and resolve / bucketize
Expand Down
81 changes: 54 additions & 27 deletions src/extension/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,15 @@ export function createExtensionContext(): ExtensionContext {
input: string,
config?: ProjectConfig,
projectDir?: string,
preferLocal = false,
): Promise<Extension | undefined> => {
// 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);
};

Expand Down Expand Up @@ -342,9 +348,15 @@ const loadExtension = async (
extension: string,
input: string,
projectDir?: string,
preferLocal = false,
): Promise<Extension> => {
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
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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();
}
}

Expand Down
1 change: 1 addition & 0 deletions src/extension/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export interface ExtensionContext {
input: string,
config?: ProjectConfig,
projectDir?: string,
preferLocal?: boolean,
): Promise<Extension | undefined>;
find(
name: string,
Expand Down
2 changes: 2 additions & 0 deletions tests/docs/smoke-all/typst/override-orange-book/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/.quarto/
**/*.quarto_ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
title: Override Test
author: Test
version: 0.1.0
contributes:
formats:
typst:
template-partials:
- typst-show.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// LOCAL-OVERRIDE-MARKER
13 changes: 13 additions & 0 deletions tests/docs/smoke-all/typst/override-orange-book/_quarto.yml
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions tests/docs/smoke-all/typst/override-orange-book/index.qmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
keep-typ: true
_quarto:
render-project: true
tests:
orange-book-typst:
ensureTypstFileRegexMatches:
- # must match
- "LOCAL-OVERRIDE-MARKER"
---

# Introduction

Hello world.