Skip to content

Commit 83065f5

Browse files
Merge pull request #13718 from quarto-dev/feature/engine-extension-3
feature: engine extension
2 parents 13e395c + 6b040a1 commit 83065f5

File tree

146 files changed

+13638
-1650
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

146 files changed

+13638
-1650
lines changed

dev-docs/subtree-extensions.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Subtree extensions
2+
3+
Subtree extensions live in `src/resources/subtree-extensions`.
4+
5+
Each is the complete git repository of a Quarto extension, e.g. the directory tree for the Julia engine looks like
6+
7+
```
8+
src/resources/extension-subtrees/
9+
julia-engine/
10+
_extensions/
11+
julia-engine/
12+
_extension.yml
13+
julia-engine.ts
14+
...
15+
```
16+
17+
The command to add or update a subtree is
18+
19+
```
20+
quarto dev-call pull-git-subtree subtree-name
21+
```
22+
23+
Omit _subtree-name_ to add/update all.
24+
25+
The code in `src/command/dev-call/pull-git-subtree/cmd.ts` contains a table of subtree
26+
27+
- `name`
28+
- `prefix` (subdirectory)
29+
- `remoteUrl`
30+
- `remoteBranch`
31+
32+
If the command is successful, it will add two commits, one the squashed changes from the remote repo and one a merge commit.
33+
34+
The commits have subtree status information in the message and metadata, so don't change them.
35+
36+
The commits can't be rebased -- you'll get weird errors indicating it tried to merge changes at the root of the repo.
37+
38+
So you must either
39+
40+
- run the command when ready to merge to main, or
41+
- remove the commits when rebasing, and run the `dev-call` command again

llm-docs/error-messages.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Writing Error Messages in Quarto
2+
3+
## The Rule
4+
5+
Each `error()`, `warning()`, or `info()` call should be **exactly one line**.
6+
7+
- ✅ End messages with `\n` to add blank lines after
8+
- ❌ Never start messages with `\n`
9+
- ❌ Never use empty `error("")` calls
10+
11+
## Why This Matters
12+
13+
Quarto's logging prefixes each call with `ERROR:` / `WARNING:` / `INFO:`. Starting a message with `\n` or using empty calls creates confusing output:
14+
15+
```
16+
ERROR: Multiple files found
17+
ERROR:
18+
Or specify entry point: ← Empty "ERROR:" line from \n at start
19+
```
20+
21+
## Adding Blank Lines
22+
23+
To add a blank line between sections, end the **previous** message with `\n`:
24+
25+
### ✅ Good
26+
27+
```typescript
28+
error("Multiple .ts files found in src/\n"); // \n at END
29+
error("Specify entry point as argument:");
30+
error(" quarto call build-ts-extension src/my-engine.ts");
31+
```
32+
33+
Output:
34+
```
35+
ERROR: Multiple .ts files found in src/
36+
ERROR:
37+
ERROR: Specify entry point as argument:
38+
ERROR: quarto call build-ts-extension src/my-engine.ts
39+
```
40+
41+
### ❌ Bad
42+
43+
```typescript
44+
error("Multiple .ts files found in src/");
45+
error("\nSpecify entry point as argument:"); // \n at START
46+
error(" quarto call build-ts-extension src/my-engine.ts");
47+
```
48+
49+
Output:
50+
```
51+
ERROR: Multiple .ts files found in src/
52+
ERROR:
53+
ERROR: Specify entry point as argument: ← Blank "ERROR:" line before
54+
ERROR: quarto call build-ts-extension src/my-engine.ts
55+
```
56+
57+
### ❌ Also Bad
58+
59+
```typescript
60+
error("Multiple .ts files found in src/");
61+
error(""); // Empty call to add spacing
62+
error("Specify entry point as argument:");
63+
```
64+
65+
Output:
66+
```
67+
ERROR: Multiple .ts files found in src/
68+
ERROR: ← Empty "ERROR:" line
69+
ERROR: Specify entry point as argument:
70+
```
71+
72+
## Complete Example
73+
74+
Here's a real example from `build-ts-extension` showing proper formatting:
75+
76+
### ✅ Good
77+
78+
```typescript
79+
error("No src/ directory found.\n");
80+
error("Create a TypeScript file in src/:");
81+
error(" mkdir -p src");
82+
error(" touch src/my-engine.ts\n");
83+
error("Or specify entry point as argument:");
84+
error(" quarto call build-ts-extension src/my-engine.ts");
85+
```
86+
87+
Output:
88+
```
89+
ERROR: No src/ directory found.
90+
ERROR:
91+
ERROR: Create a TypeScript file in src/:
92+
ERROR: mkdir -p src
93+
ERROR: touch src/my-engine.ts
94+
ERROR:
95+
ERROR: Or specify entry point as argument:
96+
ERROR: quarto call build-ts-extension src/my-engine.ts
97+
```
98+
99+
Notice:
100+
- Each `error()` call is one complete line
101+
- Blank lines are created by ending the previous message with `\n`
102+
- Indentation (with spaces) is preserved within each message
103+
- Message flow is clear and readable

llm-docs/quarto-api.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Quarto API and @quarto/types
2+
3+
## Building @quarto/types
4+
5+
To build the @quarto/types package:
6+
7+
```
8+
cd packages/quarto-types
9+
npm run build
10+
```
11+
12+
This runs typecheck and then bundles all type definitions into `dist/index.d.ts`.
13+
14+
---
15+
16+
## Updating the Quarto API
17+
18+
The Quarto API is how external execution engines access Quarto's core functionality. The API exists in two places:
19+
20+
1. **Type definitions** in `packages/quarto-types/` - consumed by external engines (TypeScript)
21+
2. **Implementation** in `src/core/quarto-api.ts` - used within quarto-cli
22+
23+
### Step-by-step: Adding to the Quarto API
24+
25+
Follow these steps in order when adding new functionality to the API:
26+
27+
#### 1. Update quarto-types type definitions
28+
29+
**Add auxiliary types** (if needed):
30+
31+
- Types belong in `packages/quarto-types/src/`
32+
- Follow the existing file organization:
33+
- `system.ts` - System/process types (ProcessResult, TempContext, etc.)
34+
- `console.ts` - Console/UI types (SpinnerOptions, etc.)
35+
- `jupyter.ts` - Jupyter-specific types
36+
- `check.ts` - Check command types
37+
- `execution.ts` - Execution engine types
38+
- etc.
39+
- Create new files if needed for logical grouping
40+
41+
**Export types from index.ts:**
42+
43+
```typescript
44+
// In packages/quarto-types/src/index.ts
45+
export type * from "./your-new-file.ts";
46+
```
47+
48+
**Add to QuartoAPI interface:**
49+
50+
```typescript
51+
// In packages/quarto-types/src/quarto-api.ts
52+
53+
// 1. Import any new types at the top
54+
import type { YourNewType } from "./your-file.ts";
55+
56+
// 2. Add to the QuartoAPI interface
57+
export interface QuartoAPI {
58+
// ... existing namespaces
59+
60+
yourNamespace: {
61+
yourMethod: (param: YourNewType) => ReturnType;
62+
};
63+
}
64+
```
65+
66+
#### 2. Test the type definitions
67+
68+
```bash
69+
cd packages/quarto-types
70+
npm run build
71+
```
72+
73+
This will:
74+
75+
- Run `tsc --noEmit` to typecheck
76+
- Bundle types into `dist/index.d.ts`
77+
- Show any type errors
78+
79+
Fix any errors before proceeding.
80+
81+
#### 3. Update the internal QuartoAPI interface
82+
83+
The file `src/core/quarto-api.ts` contains a **duplicate** QuartoAPI interface definition used for the internal implementation. Update it to match:
84+
85+
```typescript
86+
// In src/core/quarto-api.ts (near top of file)
87+
export interface QuartoAPI {
88+
// ... existing namespaces
89+
90+
yourNamespace: {
91+
yourMethod: (param: YourNewType) => ReturnType;
92+
};
93+
}
94+
```
95+
96+
**Note:** This interface must match the one in quarto-types, but uses internal types.
97+
98+
#### 4. Wire up the implementation
99+
100+
Still in `src/core/quarto-api.ts`:
101+
102+
**Add imports** (near top):
103+
104+
```typescript
105+
import { yourMethod } from "./your-module.ts";
106+
```
107+
108+
**Add to quartoAPI object** (at bottom):
109+
110+
```typescript
111+
export const quartoAPI: QuartoAPI = {
112+
// ... existing namespaces
113+
114+
yourNamespace: {
115+
yourMethod,
116+
},
117+
};
118+
```
119+
120+
#### 5. Verify with typecheck
121+
122+
Run the quarto typecheck:
123+
124+
```bash
125+
package/dist/bin/quarto
126+
```
127+
128+
No output means success! Fix any type errors.
129+
130+
#### 6. Commit with built artifact
131+
132+
**Always commit the built `dist/index.d.ts` file** along with source changes:
133+
134+
```bash
135+
git add packages/quarto-types/src/your-file.ts \
136+
packages/quarto-types/src/index.ts \
137+
packages/quarto-types/src/quarto-api.ts \
138+
packages/quarto-types/dist/index.d.ts \
139+
src/core/quarto-api.ts
140+
141+
git commit -m "Add yourNamespace to Quarto API"
142+
```
143+
144+
### Using the Quarto API in source files
145+
146+
#### Inside quarto-cli (internal modules)
147+
148+
```typescript
149+
// Import the quartoAPI instance
150+
import { quartoAPI as quarto } from "../../core/quarto-api.ts";
151+
152+
// Use it
153+
const caps = await quarto.jupyter.capabilities();
154+
await quarto.console.withSpinner({ message: "Working..." }, async () => {
155+
// do work
156+
});
157+
```
158+
159+
#### External engines
160+
161+
External engines receive the API via their `init()` method:
162+
163+
```typescript
164+
let quarto: QuartoAPI;
165+
166+
export const myEngineDiscovery: ExecutionEngineDiscovery = {
167+
init: (quartoAPI: QuartoAPI) => {
168+
quarto = quartoAPI; // Store for later use
169+
},
170+
171+
// ... other methods can now use quarto
172+
};
173+
```
174+
175+
#### Removing old imports
176+
177+
When moving functionality to the API, **remove direct imports** from internal modules:
178+
179+
```typescript
180+
// ❌ OLD - direct import
181+
import { withSpinner } from "../../core/console.ts";
182+
183+
// ✅ NEW - use API
184+
import { quartoAPI as quarto } from "../../core/quarto-api.ts";
185+
const result = await quarto.console.withSpinner(...);
186+
```
187+
188+
This ensures external engines and internal code use the same interface.

package/src/common/import-report/deno-info.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
*
88
*/
99

10+
import { architectureToolsPath } from "../../../../src/core/resources.ts";
11+
1012
////////////////////////////////////////////////////////////////////////////////
1113

1214
export interface DenoInfoDependency {
@@ -61,8 +63,9 @@ export interface Edge {
6163
////////////////////////////////////////////////////////////////////////////////
6264

6365
export async function getDenoInfo(_root: string): Promise<DenoInfoJSON> {
66+
const denoBinary = Deno.env.get("QUARTO_DENO") || architectureToolsPath("deno");
6467
const process = Deno.run({
65-
cmd: ["deno", "info", Deno.args[0], "--json"],
68+
cmd: [denoBinary, "info", Deno.args[0], "--json"],
6669
stdout: "piped",
6770
});
6871
const rawOutput = await process.output();

package/src/common/import-report/explain-all-cycles.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,15 +156,19 @@ if (import.meta.main) {
156156
between all files reachable from some source file.
157157
158158
Usage:
159-
$ quarto run explain-all-cycles.ts <entry-point.ts>
159+
$ quarto run --dev explain-all-cycles.ts <entry-point.ts> [--simplify <prefixes...>] [--graph|--toon [filename]]
160160
161-
Examples:
162-
163-
From ./src:
161+
Options:
162+
--simplify <prefixes...> Collapse paths with given prefixes (must be first if used)
163+
--graph [filename] Output .dot specification (default: graph.dot)
164+
--toon [filename] Output edges in TOON format (default: cycles.toon)
164165
165-
$ quarto run quarto.ts
166+
Examples:
167+
$ quarto run --dev package/src/common/import-report/explain-all-cycles.ts src/quarto.ts
168+
$ quarto run --dev package/src/common/import-report/explain-all-cycles.ts src/quarto.ts --simplify core/ command/ --toon
169+
$ quarto run --dev package/src/common/import-report/explain-all-cycles.ts src/quarto.ts --graph cycles.dot
166170
167-
If the second parameter is "--graph", then this program outputs the .dot specification to the file given by the third parameter, rather opening a full report.
171+
If no output option is given, opens an interactive preview.
168172
`,
169173
);
170174
Deno.exit(1);
@@ -187,7 +191,17 @@ If the second parameter is "--graph", then this program outputs the .dot specifi
187191

188192
result = dropTypesFiles(result);
189193

190-
if (args[1] === "--graph") {
194+
if (args[1] === "--toon") {
195+
// Output in TOON format
196+
const lines = [`edges[${result.length}]{from,to}:`];
197+
for (const { from, to } of result) {
198+
lines.push(` ${from},${to}`);
199+
}
200+
Deno.writeTextFileSync(
201+
args[2] ?? "cycles.toon",
202+
lines.join("\n") + "\n",
203+
);
204+
} else if (args[1] === "--graph") {
191205
Deno.writeTextFileSync(
192206
args[2] ?? "graph.dot",
193207
generateGraph(result),

0 commit comments

Comments
 (0)