Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/package/src/common/prepare-dist.ts
6450 views
1
/*
2
* prepare-dist.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*
6
*/
7
8
import { dirname, join } from "../../../src/deno_ral/path.ts";
9
import { copySync, ensureDirSync, existsSync } from "../../../src/deno_ral/fs.ts";
10
11
import { Configuration } from "../common/config.ts";
12
import { buildFilter } from "./package-filters.ts";
13
import { bundle } from "../util/deno.ts";
14
import { info } from "../../../src/deno_ral/log.ts";
15
import { buildAssets } from "../../../src/command/dev-call/build-artifacts/cmd.ts";
16
import { initTreeSitter } from "../../../src/core/schema/deno-init-tree-sitter.ts";
17
import {
18
Dependency,
19
configureDependency,
20
kDependencies,
21
} from "./dependencies/dependencies.ts";
22
import { copyQuartoScript } from "./configure.ts";
23
import { deno } from "./dependencies/deno.ts";
24
import { buildQuartoPreviewJs } from "./previewjs.ts";
25
26
export async function prepareDist(
27
config: Configuration,
28
) {
29
// run esbuild
30
// copy from resources dir to the 'share' dir (which is resources)
31
// config.directoryInfo.share
32
33
// Moving appropriate binaries into place
34
35
// Get each dependency extracted into the 'bin' folder
36
// Download dependencies
37
38
// Ensure that the pkgWorkingDir is clean
39
if (existsSync(config.directoryInfo.pkgWorking.root)) {
40
Deno.removeSync(config.directoryInfo.pkgWorking.root, { recursive: true });
41
}
42
43
// Ensure that the working directory exists
44
ensureDirSync(config.directoryInfo.pkgWorking.bin);
45
ensureDirSync(config.directoryInfo.pkgWorking.share);
46
const toolsDir = join(
47
config.directoryInfo.pkgWorking.bin,
48
"tools",
49
);
50
ensureDirSync(toolsDir);
51
52
// binary tools dir
53
const targetDir = join(
54
config.directoryInfo.pkgWorking.bin,
55
"tools",
56
);
57
58
// Function to wrap architecture specific configuration
59
const configArchDependency = async (dep: Dependency,
60
dir: string,
61
config: Configuration) => {
62
if (config.os === "darwin") {
63
// add a secondary config specifically for Mac
64
await configureDependency(dep, dir, {
65
os: config.os,
66
arch: "aarch64",
67
});
68
69
await configureDependency(dep, dir, {
70
os: config.os,
71
arch: "x86_64",
72
});
73
} else {
74
await configureDependency(dep, targetDir, config);
75
}
76
}
77
78
// Download Deno
79
const denoVersion = Deno.env.get("DENO");
80
if (denoVersion) {
81
const denoDependency = deno(denoVersion);
82
await configArchDependency(denoDependency, targetDir, config)
83
}
84
85
// Download the dependencies
86
for (const dependency of kDependencies) {
87
try {
88
await configArchDependency(dependency, targetDir, config)
89
} catch (e) {
90
if (!(e instanceof Error)) { throw e; }
91
if (
92
e.message ===
93
"The architecture aarch64 is missing the dependency deno_dom"
94
) {
95
info("\nIgnoring deno_dom dependency on Apple Silicon");
96
continue;
97
} else {
98
throw e;
99
}
100
}
101
}
102
103
// Stage typst-gather binary (built by configure.sh)
104
// Only stage if the build machine architecture matches the target architecture
105
// (cross-compilation is not currently supported)
106
const buildArch = Deno.build.arch === "aarch64" ? "aarch64" : "x86_64";
107
if (buildArch === config.arch) {
108
const typstGatherBinaryName = config.os === "windows" ? "typst-gather.exe" : "typst-gather";
109
const typstGatherSrc = join(
110
config.directoryInfo.root,
111
"package/typst-gather/target/release",
112
typstGatherBinaryName,
113
);
114
if (!existsSync(typstGatherSrc)) {
115
throw new Error(
116
`typst-gather binary not found at ${typstGatherSrc}\n` +
117
"Run ./configure.sh to build it.",
118
);
119
}
120
info("\nStaging typst-gather binary");
121
const typstGatherDest = join(targetDir, config.arch, typstGatherBinaryName);
122
ensureDirSync(join(targetDir, config.arch));
123
copySync(typstGatherSrc, typstGatherDest, { overwrite: true });
124
info(`Copied ${typstGatherSrc} to ${typstGatherDest}`);
125
} else {
126
info(`\nNote: Skipping typst-gather staging (build arch ${buildArch} != target arch ${config.arch})`);
127
}
128
129
// build quarto-preview.js
130
info("Building Quarto Web UI");
131
const result = buildQuartoPreviewJs(config.directoryInfo.src, undefined, true);
132
if (!result.success) {
133
throw new Error();
134
}
135
136
137
// Place the quarto sciprt
138
// Move the quarto script into place
139
info("Moving Quarto script");
140
copyQuartoScript(config, config.directoryInfo.pkgWorking.bin);
141
142
// Move the supporting files into place
143
info("\nMoving supporting files");
144
supportingFiles(config);
145
info("");
146
147
// Update extension-build import map for distribution
148
info("Updating extension-build import map");
149
updateImportMap(config);
150
info("");
151
152
// Create the deno bundle
153
// const input = join(config.directoryInfo.src, "quarto.ts");
154
const output = join(config.directoryInfo.pkgWorking.bin, "quarto.js");
155
info("\nCreating Deno Bundle");
156
info(output);
157
await bundle(
158
config,
159
);
160
info("");
161
162
// Inline the LUA Filters and move them into place
163
info("\nCreating Inlined LUA Filters");
164
inlineFilters(config);
165
info("");
166
167
// Write a version file to share
168
info(`Writing version: ${config.version}`);
169
Deno.writeTextFileSync(
170
join(config.directoryInfo.pkgWorking.share, "version"),
171
config.version,
172
);
173
info("");
174
175
info("\nBuilding JS assets");
176
await initTreeSitter();
177
await buildAssets();
178
const buildAssetFiles = [
179
"formats/html/ojs/quarto-ojs-runtime.js",
180
"editor/tools/yaml/yaml-intelligence-resources.json",
181
"editor/tools/yaml/web-worker.js",
182
"editor/tools/yaml/yaml.js",
183
];
184
for (const file of buildAssetFiles) {
185
copySync(
186
join(config.directoryInfo.src, "resources", file),
187
join(config.directoryInfo.pkgWorking.share, file),
188
{ overwrite: true },
189
);
190
}
191
192
// Copy quarto-types to extension-build directory
193
// Note: deno.json and import-map.json are copied by supportingFiles() and
194
// import-map.json is then updated by updateImportMap() for distribution
195
info("Copying quarto-types.d.ts to extension-build directory");
196
const extensionBuildDir = join(
197
config.directoryInfo.pkgWorking.share,
198
"extension-build",
199
);
200
copySync(
201
join(config.directoryInfo.root, "packages/quarto-types/dist/index.d.ts"),
202
join(extensionBuildDir, "quarto-types.d.ts"),
203
{ overwrite: true },
204
);
205
206
// Remove the config directory, if present
207
info(`Cleaning config`);
208
const configDir = join(config.directoryInfo.dist, "config");
209
info(configDir);
210
if (existsSync(configDir)) {
211
Deno.removeSync(configDir, { recursive: true });
212
}
213
214
info("");
215
}
216
217
function supportingFiles(config: Configuration) {
218
// Move information and share resources into place
219
const filesToCopy = [
220
{
221
from: join(config.directoryInfo.root, "COPYING.md"),
222
to: join(config.directoryInfo.pkgWorking.share, "COPYING.md"),
223
},
224
{
225
from: join(config.directoryInfo.root, "COPYRIGHT"),
226
to: join(config.directoryInfo.pkgWorking.share, "COPYRIGHT"),
227
},
228
{
229
from: join(config.directoryInfo.src, "resources"),
230
to: config.directoryInfo.pkgWorking.share,
231
},
232
];
233
234
// Gather supporting files
235
filesToCopy.forEach((fileToCopy) => {
236
const dir = dirname(fileToCopy.to);
237
info(`Ensuring dir ${dir} exists`);
238
ensureDirSync(dir);
239
240
info(`Copying ${fileToCopy.from} to ${fileToCopy.to}`);
241
copySync(fileToCopy.from, fileToCopy.to, { overwrite: true });
242
});
243
244
// Cleanup the filters directory, which contains filter source that will be
245
// compiled later
246
const pathsToClean = [
247
join(config.directoryInfo.pkgWorking.share, "filters"),
248
];
249
pathsToClean.forEach((path) => Deno.removeSync(path, { recursive: true }));
250
}
251
252
function updateImportMap(config: Configuration) {
253
// Read the import map that was copied from src/resources/extension-build/
254
const importMapPath = join(
255
config.directoryInfo.pkgWorking.share,
256
"extension-build",
257
"import-map.json",
258
);
259
const importMapContent = JSON.parse(Deno.readTextFileSync(importMapPath));
260
261
// Read the source import map to get current Deno std versions
262
const sourceImportMapPath = join(config.directoryInfo.src, "import_map.json");
263
const sourceImportMap = JSON.parse(Deno.readTextFileSync(sourceImportMapPath));
264
const sourceImports = sourceImportMap.imports as Record<string, string>;
265
266
// Update the import map for distribution:
267
// 1. Change @quarto/types path from dev (../../../packages/...) to dist (./quarto-types.d.ts)
268
// 2. Update all other imports (Deno std versions) from source import map
269
const updatedImports: Record<string, string> = {
270
"@quarto/types": "./quarto-types.d.ts",
271
};
272
273
// Copy all other imports from source, updating versions
274
for (const key in importMapContent.imports) {
275
if (key !== "@quarto/types") {
276
const sourceValue = sourceImports[key];
277
if (!sourceValue) {
278
throw new Error(
279
`Import map key "${key}" not found in source import_map.json`,
280
);
281
}
282
updatedImports[key] = sourceValue;
283
}
284
}
285
286
importMapContent.imports = updatedImports;
287
288
// Write back the updated import map
289
Deno.writeTextFileSync(
290
importMapPath,
291
JSON.stringify(importMapContent, null, 2) + "\n",
292
);
293
}
294
295
interface Filter {
296
// The name of the filter (the LUA file and perhaps the directory)
297
name: string;
298
299
// An optional name of the directory, if it is not the name of the LUA filter
300
dir?: string;
301
}
302
303
function inlineFilters(config: Configuration) {
304
info("Building inlined filters");
305
const outDir = join(config.directoryInfo.pkgWorking.share, "filters");
306
const filtersToInline: Filter[] = [
307
{ name: "main", dir: "." },
308
{ name: "pagebreak", dir: "rmarkdown" },
309
{ name: "quarto-init" },
310
{ name: "crossref" },
311
{ name: "customwriter" },
312
{ name: "qmd-reader", dir: "." },
313
{ name: "leveloneanalysis", dir: "quarto-internals"}
314
];
315
316
filtersToInline.forEach((filter) => {
317
info(filter);
318
buildFilter(
319
join(
320
config.directoryInfo.src,
321
"resources",
322
"filters",
323
filter.dir || filter.name,
324
`${filter.name}.lua`,
325
),
326
join(outDir, filter.dir || filter.name, `${filter.name}.lua`),
327
);
328
});
329
330
const modules = "modules";
331
const modulesIn = join(
332
config.directoryInfo.src,
333
"resources",
334
"filters", modules);
335
const modulesOut = join(outDir, modules);
336
337
// move the modules directory
338
copySync(modulesIn, modulesOut)
339
340
341
}
342
343