Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/core/dart-sass.ts
12924 views
1
/*
2
* dart-sass.ts
3
*
4
* Copyright (C) 2020-2025 Posit Software, PBC
5
*/
6
import { join } from "../deno_ral/path.ts";
7
8
import { architectureToolsPath } from "./resources.ts";
9
import { execProcess } from "./process.ts";
10
import { TempContext } from "./temp.ts";
11
import { lines } from "./text.ts";
12
import { debug, info } from "../deno_ral/log.ts";
13
import { existsSync } from "../deno_ral/fs.ts";
14
import { warnOnce } from "./log.ts";
15
import { isWindows } from "../deno_ral/platform.ts";
16
17
export function dartSassInstallDir() {
18
return architectureToolsPath("dart-sass");
19
}
20
21
export async function dartSassVersion() {
22
return await dartCommand(["--version"]);
23
}
24
25
export async function dartCompile(
26
input: string,
27
outputFilePath: string,
28
temp: TempContext,
29
loadPaths?: string[],
30
compressed?: boolean,
31
): Promise<string | undefined> {
32
// Write the scss to a file
33
// We were previously passing it via stdin, but that can be overflowed
34
const inputFilePath = temp.createFile({ suffix: ".scss" });
35
36
// Write the css itself to a file
37
Deno.writeTextFileSync(inputFilePath, input);
38
const args = [
39
inputFilePath,
40
outputFilePath,
41
"--style",
42
compressed ? "compressed" : "expanded",
43
"--quiet", // Remove this flag to get depedency warnings from SASS
44
];
45
46
if (loadPaths) {
47
loadPaths.forEach((loadPath) => {
48
args.push(`--load-path=${loadPath}`);
49
});
50
}
51
52
await dartCommand(args);
53
return outputFilePath;
54
}
55
56
/**
57
* Options for dartCommand
58
*/
59
export interface DartCommandOptions {
60
/**
61
* Override the dart-sass install directory.
62
* Used for testing with non-standard paths (spaces, accented characters).
63
*/
64
installDir?: string;
65
}
66
67
/**
68
* Resolve the dart-sass command and its base arguments.
69
*
70
* On Windows, calls dart.exe + sass.snapshot directly instead of going
71
* through sass.bat. The bundled sass.bat is a thin wrapper generated by
72
* dart_cli_pkg that just runs:
73
* "%SCRIPTPATH%\src\dart.exe" "%SCRIPTPATH%\src\sass.snapshot" %arguments%
74
*
75
* Template source:
76
* https://github.com/google/dart_cli_pkg/blob/main/lib/src/templates/standalone/executable.bat.mustache
77
* Upstream issue to ship standalone .exe instead of .bat + dart.exe:
78
* https://github.com/google/dart_cli_pkg/issues/67
79
*
80
* Bypassing sass.bat avoids multiple .bat file issues on Windows:
81
* - Deno quoting bugs with spaced paths (#13997)
82
* - cmd.exe OEM code page misreading UTF-8 accented paths (#14267)
83
* - Enterprise group policy blocking .bat execution (#6651)
84
*/
85
function resolveSassCommand(options?: DartCommandOptions): {
86
cmd: string;
87
baseArgs: string[];
88
} {
89
const installDir = options?.installDir;
90
if (installDir == null) {
91
// Only check env var override when no explicit installDir is provided.
92
// If QUARTO_DART_SASS doesn't exist on disk, fall through to use the
93
// bundled dart-sass at the default architectureToolsPath.
94
const dartOverrideCmd = Deno.env.get("QUARTO_DART_SASS");
95
if (dartOverrideCmd) {
96
if (!existsSync(dartOverrideCmd)) {
97
warnOnce(
98
`Specified QUARTO_DART_SASS does not exist, using built in dart sass.`,
99
);
100
} else {
101
return { cmd: dartOverrideCmd, baseArgs: [] };
102
}
103
}
104
}
105
106
const sassDir = installDir ?? architectureToolsPath("dart-sass");
107
108
if (isWindows) {
109
return {
110
cmd: join(sassDir, "src", "dart.exe"),
111
baseArgs: [join(sassDir, "src", "sass.snapshot")],
112
};
113
}
114
115
return { cmd: join(sassDir, "sass"), baseArgs: [] };
116
}
117
118
export async function dartCommand(
119
args: string[],
120
options?: DartCommandOptions,
121
) {
122
const { cmd, baseArgs } = resolveSassCommand(options);
123
124
const result = await execProcess({
125
cmd,
126
args: [...baseArgs, ...args],
127
stdout: "piped",
128
stderr: "piped",
129
});
130
131
if (result.success) {
132
if (result.stderr) {
133
info(result.stderr);
134
}
135
return result.stdout;
136
} else {
137
debug(`[DART cmd] : ${cmd}`);
138
debug(`[DART args] : ${[...baseArgs, ...args].join(" ")}`);
139
debug(`[DART stdout] : ${result.stdout}`);
140
debug(`[DART stderr] : ${result.stderr}`);
141
142
const errLines = lines(result.stderr || "");
143
// truncate the last 2 lines (they include a pointer to the temp file containing
144
// all of the concatenated sass, which is more or less incomprehensible for users.
145
const errMsg = errLines.slice(0, errLines.length - 2).join("\n");
146
throw new Error("Theme file compilation failed:\n\n" + errMsg);
147
}
148
}
149
150