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