Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/quarto.ts
6432 views
1
/*
2
* quarto.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
7
// Must be FIRST to save original Deno.realPathSync before monkey-patching
8
import "./deno_ral/original-real-path.ts";
9
import "./core/deno/monkey-patch.ts";
10
11
import {
12
Command,
13
CompletionsCommand,
14
HelpCommand,
15
} from "cliffy/command/mod.ts";
16
17
import { commands } from "./command/command.ts";
18
import { appendLogOptions } from "./core/log.ts";
19
import { debug, error } from "./deno_ral/log.ts";
20
21
import { cleanupSessionTempDir, initSessionTempDir } from "./core/temp.ts";
22
import { removeFlags } from "./core/flags.ts";
23
import { quartoConfig } from "./core/quarto.ts";
24
import { execProcess } from "./core/process.ts";
25
import { pandocBinaryPath } from "./core/resources.ts";
26
import { appendProfileArg, setProfileFromArg } from "./quarto-core/profile.ts";
27
import { logError } from "./core/log.ts";
28
import { CommandError } from "cliffy/command/_errors.ts";
29
import { satisfies } from "semver/mod.ts";
30
31
import {
32
devConfigsEqual,
33
readInstalledDevConfig,
34
readSourceDevConfig,
35
reconfigureQuarto,
36
} from "./core/devconfig.ts";
37
import { typstBinaryPath } from "./core/typst.ts";
38
import { exitWithCleanup, onCleanup } from "./core/cleanup.ts";
39
40
import { runScript } from "./command/run/run.ts";
41
import { commandFailed } from "./command/utils.ts";
42
43
// ensures run handlers are registered
44
import "./core/run/register.ts";
45
46
// ensures language handlers are registered
47
import "./core/handlers/handlers.ts";
48
49
// ensures project types are registered
50
import "./project/types/register.ts";
51
52
// ensures writer formats are registered
53
import "./format/imports.ts";
54
55
// ensures API namespaces are registered
56
import "./core/api/register.ts";
57
58
import { kCliffyImplicitCwd } from "./config/constants.ts";
59
import { mainRunner } from "./core/main.ts";
60
61
const checkVersionRequirement = () => {
62
const versionReq = Deno.env.get("QUARTO_VERSION_REQUIREMENT");
63
if (versionReq) {
64
if (!satisfies(quartoConfig.version(), versionReq)) {
65
error(
66
`Quarto version ${quartoConfig.version()} does not meet semver requirement ${versionReq}`,
67
);
68
Deno.exit(1);
69
}
70
}
71
};
72
73
const checkReconfiguration = async () => {
74
// check for need to reconfigure
75
if (quartoConfig.isDebug()) {
76
const installed = readInstalledDevConfig();
77
const source = readSourceDevConfig();
78
if (installed == null || !devConfigsEqual(installed, source)) {
79
await reconfigureQuarto(installed, source);
80
Deno.exit(1);
81
}
82
}
83
};
84
85
const passThroughPandoc = async (
86
args: string[],
87
env?: Record<string, string>,
88
) => {
89
const result = await execProcess(
90
{
91
cmd: pandocBinaryPath(),
92
args: args.slice(1),
93
env,
94
},
95
undefined,
96
undefined,
97
undefined,
98
true,
99
);
100
Deno.exit(result.code);
101
};
102
103
const passThroughTypst = async (
104
args: string[],
105
env?: Record<string, string>,
106
) => {
107
if (args[1] === "update") {
108
error(
109
"The 'typst update' command is not supported.\n" +
110
"Please install the latest version of Quarto from http://quarto.org to get the latest supported typst features.",
111
);
112
Deno.exit(1);
113
}
114
const result = await execProcess({
115
cmd: typstBinaryPath(),
116
args: args.slice(1),
117
env,
118
});
119
Deno.exit(result.code);
120
};
121
122
export async function quarto(
123
args: string[],
124
cmdHandler?: (command: Command) => Command,
125
env?: Record<string, string>,
126
) {
127
await checkReconfiguration();
128
checkVersionRequirement();
129
if (args[0] === "pandoc" && args[1] !== "help") {
130
await passThroughPandoc(args, env);
131
}
132
if (args[0] === "typst") {
133
await passThroughTypst(args, env);
134
}
135
136
// passthrough to run handlers
137
if (args[0] === "run" && args[1] !== "help" && args[1] !== "--help") {
138
const result = await runScript(args.slice(1), env);
139
Deno.exit(result.code);
140
}
141
142
// inject implicit cwd arg for quarto preview/render whose
143
// first argument is a command line parmaeter. this allows
144
// us to evade a cliffy cli parsing issue where it requires
145
// at least one defined argument to be parsed before it can
146
// access undefined arguments.
147
//
148
// we do this via a UUID so that we can detect this happened
149
// and issue a warning in the case where the user might
150
// be calling render with parameters in incorrect order.
151
//
152
// see https://github.com/quarto-dev/quarto-cli/issues/3581
153
if (
154
args.length > 1 &&
155
(args[0] === "render" || args[0] === "preview") &&
156
args[1].startsWith("-")
157
) {
158
args = [args[0], kCliffyImplicitCwd, ...args.slice(1)];
159
}
160
161
debug("Quarto version: " + quartoConfig.version());
162
163
const oldEnv: Record<string, string | undefined> = {};
164
for (const [key, value] of Object.entries(env || {})) {
165
const oldV = Deno.env.get(key);
166
oldEnv[key] = oldV;
167
Deno.env.set(key, value);
168
}
169
170
const quartoCommand = new Command()
171
.name("quarto")
172
.help({ colors: false })
173
.version(quartoConfig.version() + "\n")
174
.description("Quarto CLI")
175
.throwErrors();
176
177
commands().forEach((command) => {
178
// turn off colors
179
command.help({ colors: false });
180
quartoCommand.command(
181
command.getName(),
182
cmdHandler !== undefined ? cmdHandler(command) : command,
183
);
184
});
185
186
// From here on, we have a temp dir that we need to clean up.
187
// The calls to Deno.exit() above are fine, but no further
188
// ones should be made
189
//
190
// init temp dir
191
initSessionTempDir();
192
onCleanup(cleanupSessionTempDir);
193
194
const promise = quartoCommand.command("help", new HelpCommand().global())
195
.command("completions", new CompletionsCommand()).hidden().parse(args);
196
197
try {
198
await promise;
199
for (const [key, value] of Object.entries(oldEnv)) {
200
if (value === undefined) {
201
Deno.env.delete(key);
202
} else {
203
Deno.env.set(key, value);
204
}
205
}
206
if (commandFailed()) {
207
exitWithCleanup(1);
208
}
209
} catch (e) {
210
if (e instanceof CommandError) {
211
logError(e, false);
212
exitWithCleanup(1);
213
} else {
214
throw e;
215
}
216
}
217
}
218
219
if (import.meta.main) {
220
await mainRunner(async (args) => {
221
// initialize profile (remove from args)
222
let quartoArgs = [...Deno.args];
223
if (setProfileFromArg(args)) {
224
const removeArgs = new Map<string, boolean>();
225
removeArgs.set("--profile", true);
226
quartoArgs = removeFlags(quartoArgs, removeArgs);
227
}
228
229
// run quarto
230
await quarto(quartoArgs, (cmd) => {
231
cmd = appendLogOptions(cmd);
232
return appendProfileArg(cmd);
233
});
234
235
if (commandFailed()) {
236
exitWithCleanup(1);
237
}
238
});
239
}
240
241