Path: blob/main/package/src/common/update-html-dependencies.ts
6450 views
/*1* bootstrap.ts2*3* Copyright (C) 2020-2022 Posit Software, PBC4*/5import { copySync, ensureDir, ensureDirSync, existsSync, walkSync } from "../../../src/deno_ral/fs.ts";6import { info } from "../../../src/deno_ral/log.ts";7import { dirname, basename, extname, join } from "../../../src/deno_ral/path.ts";8import { lines } from "../../../src/core/text.ts";9import * as ld from "../../../src/core/lodash.ts";1011import { runCmd } from "../util/cmd.ts";12import { applyGitPatches, Repo, withRepo } from "../util/git.ts";1314import { download } from "../util/utils.ts";15import { Configuration } from "./config.ts";16import { visitLines } from "../../../src/core/file.ts";17import { copyTo } from "../../../src/core/copy.ts";18import { kSourceMappingRegexes } from "../../../src/config/constants.ts";19import { unzip } from "../../../src/core/zip.ts";2021export async function updateHtmlDependencies(config: Configuration) {22info("Updating Bootstrap with version info:");2324// Read the version information from the environment25const workingDir = Deno.makeTempDirSync();2627const bsCommit = Deno.env.get("BOOTSTRAP");28if (!bsCommit) {29throw new Error(`BOOTSTRAP is not defined`);30}31const bsIconVersion = Deno.env.get("BOOTSTRAP_FONT");32if (!bsIconVersion) {33throw new Error(`BOOTSTRAP_FONT is not defined`);34}35const htmlToolsVersion = Deno.env.get("HTMLTOOLS");36if (!htmlToolsVersion) {37throw new Error("HTMLTOOLS is not defined");38}3940info(`Boostrap: ${bsCommit}`);41info(`Boostrap Icon: ${bsIconVersion}`);42info(`Html Tools: ${htmlToolsVersion}`);4344// the bootstrap and dist/themes dir45const formatDir = join(46config.directoryInfo.src,47"resources",48"formats",49"html"50);5152const bsDir = join(formatDir, "bootstrap");5354const bsThemesDir = join(bsDir, "themes");5556const bsDistDir = join(bsDir, "dist");5758const htmlToolsDir = join(formatDir, "htmltools");59const bslibDir = join(formatDir, "bslib");6061// For applying git patch to what we retreive62const patchesDir = join(config.directoryInfo.pkg, "src", "common", "patches");6364function resolvePatches(patches: string[]) {65return patches.map((patch) => {66return join(patchesDir, patch);67});68}6970// Anchor71const anchorJs = join(formatDir, "anchor", "anchor.min.js");72await updateUnpkgDependency(73"ANCHOR_JS",74"anchor-js",75"anchor.min.js",76anchorJs77);78cleanSourceMap(anchorJs);7980// Poppper81const popperJs = join(formatDir, "popper", "popper.min.js");82await updateUnpkgDependency(83"POPPER_JS",84"@popperjs/core",85"dist/umd/popper.min.js",86popperJs87);88cleanSourceMap(popperJs);8990// Clipboard91const clipboardJs = join(formatDir, "clipboard", "clipboard.min.js");92await updateGithubSourceCodeDependency(93"clipboardjs",94"zenorocha/clipboard.js",95"CLIPBOARD_JS",96workingDir,97(dir: string, version: string) => {98// Copy the js file99Deno.copyFileSync(100join(dir, `clipboard.js-${version}`, "dist", "clipboard.min.js"),101clipboardJs102);103return Promise.resolve();104}105);106cleanSourceMap(clipboardJs);107108// Day.js locales109// https://github.com/iamkun/dayjs/tree/dev/src/locale110const dayJsDir = join(111config.directoryInfo.src,112"resources",113"library",114"dayjs"115);116await updateGithubSourceCodeDependency(117"dayjs",118"iamkun/dayjs",119"DAY_JS",120workingDir,121async (dir: string, version: string) => {122const sourceDir = join(dir, `dayjs-${version}`, "src", "locale");123const targetDir = join(dayJsDir, "locale");124ensureDirSync(targetDir);125126const files = Deno.readDirSync(sourceDir);127for (const file of files) {128const targetFile = join(targetDir, file.name);129// Move the file130Deno.copyFileSync(join(sourceDir, file.name), targetFile);131132// Fixup the file to remove these lines133const ignore = [134"import dayjs from 'dayjs'",135"dayjs.locale(locale, null, true)",136];137info("Visiting lines of " + targetFile);138const output: string[] = [];139await visitLines(targetFile, (line: string | null, _count: number) => {140if (line !== null) {141if (!ignore.includes(line)) {142output.push(line);143}144}145return true;146});147148Deno.writeTextFileSync(targetFile, output.join("\n"));149}150}151);152153// Tippy154const tippyUmdJs = join(formatDir, "tippy", "tippy.umd.min.js");155await updateUnpkgDependency(156"TIPPY_JS",157"tippy.js",158"dist/tippy.umd.min.js",159tippyUmdJs160);161cleanSourceMap(tippyUmdJs);162163// List.js164const listJs = join(165config.directoryInfo.src,166"resources",167"projects",168"website",169"listing",170"list.min.js"171);172await updateGithubSourceCodeDependency(173"listjs",174"javve/list.js",175"LIST_JS",176workingDir,177(dir: string, version: string) => {178ensureDirSync(dirname(listJs));179// Copy the js file180Deno.copyFileSync(181join(dir, `list.js-${version}`, "dist", "list.min.js"),182listJs183);184185// Omit regular expression escaping186// (Fixes https://github.com/quarto-dev/quarto-cli/issues/8435)187const contents = Deno.readTextFileSync(listJs);188const removeContent = /(\(e=t\.utils\.toString\(e\)\.toLowerCase\(\)\))\.replace\(.*\)(,r=e)/g;189const cleaned = contents.replace(removeContent, "$1$2");190Deno.writeTextFileSync(listJs, cleaned);191192return Promise.resolve();193}194);195196// Zenscroll197const zenscrollJs = join(formatDir, "zenscroll", "zenscroll-min.js");198await updateGithubSourceCodeDependency(199"zenscroll",200"zengabor/zenscroll",201"ZENSCROLL_JS",202workingDir,203(dir: string, version: string) => {204ensureDirSync(dirname(zenscrollJs));205// Copy the js file206Deno.copyFileSync(207join(dir, `zenscroll-${version}`, "zenscroll-min.js"),208zenscrollJs209);210return Promise.resolve();211}212);213214// Tippy215const tippyCss = join(formatDir, "tippy", "tippy.css");216await updateUnpkgDependency(217"TIPPY_JS",218"tippy.js",219"dist/tippy.css",220tippyCss221);222cleanSourceMap(tippyCss);223224// Glightbox225const glightboxDir = join(formatDir, "glightbox");226const glightBoxVersion = Deno.env.get("GLIGHTBOX_JS");;227228info("Updating glightbox");229const fileName = `glightbox-master.zip`;230const distUrl = `https://github.com/biati-digital/glightbox/releases/download/${glightBoxVersion}/${fileName}`;231const zipFile = join(workingDir, fileName);232233// Download and unzip the release234const glightboxWorking = join(workingDir, "glightbox-master");235ensureDirSync(glightboxWorking);236237info(`Downloading ${distUrl}`);238await download(distUrl, zipFile);239await unzip(zipFile, glightboxWorking);240241// Remove extraneous files242[243{244from: join("dist", "js", "glightbox.min.js"),245to: "glightbox.min.js",246},247{248from: join("dist", "css", "glightbox.min.css"),249to: "glightbox.min.css",250},251].forEach((depends) => {252// Copy the js file253Deno.copyFileSync(254join(glightboxWorking, depends.from),255join(glightboxDir, depends.to)256);257});258info("");259260// Fuse261const fuseJs = join(262config.directoryInfo.src,263"resources",264"projects",265"website",266"search",267"fuse.min.js"268);269await updateGithubSourceCodeDependency(270"fusejs",271"krisk/Fuse",272"FUSE_JS",273workingDir,274(dir: string, version: string) => {275// Copy the js file276ensureDirSync(dirname(fuseJs));277Deno.copyFileSync(278join(dir, `Fuse-${version}`, "dist", "fuse.min.js"),279fuseJs280);281return Promise.resolve();282}283);284cleanSourceMap(fuseJs);285286// reveal.js287const revealJs = join(288config.directoryInfo.src,289"resources",290"formats",291"revealjs",292"reveal"293);294295await updateGithubSourceCodeDependency(296"reveal.js",297"hakimel/reveal.js",298"REVEAL_JS",299workingDir,300(dir: string, version: string) => {301// Copy the desired resource files302info("Copying reveal.js resources' directory");303if (existsSync(revealJs)) {304Deno.removeSync(revealJs, { recursive: true });305}306ensureDirSync(revealJs);307308info("Copying css/");309const cssDir = join(revealJs, "css");310copyTo(join(dir, `reveal.js-${version}`, "css"), cssDir, { overwrite: true, preserveTimestamps: true });311info("Port native scss themes to quarto theme");312const sourceThemes = join(cssDir, "theme", "source");313const portedThemes = join(dirname(revealJs), "themes");314for (const fileEntry of Deno.readDirSync(sourceThemes)) {315if (fileEntry.isFile && extname(fileEntry.name) === ".scss") {316// Ignore specific files that are aliased to custom quarto theme317if (["white.scss", "black.scss", "white-contrast.scss", "black-contrast.scss"].includes(fileEntry.name)) {318info(`-> ignore ${fileEntry.name} - do not port to quarto.`);319continue;320}321info(`-> porting ${fileEntry.name} to quarto theme.`);322copyTo(join(sourceThemes, fileEntry.name), join(portedThemes, fileEntry.name), { overwrite: true, preserveTimestamps: true });323portRevealTheme(join(portedThemes, fileEntry.name));324}325}326// copy settings.scss and patch to help check correct addition of theme327const templateDir = join(cssDir, "theme", "template");328const templateDirNew = join(portedThemes, "template");329ensureDirSync(templateDirNew);330copyTo(join(templateDir, "settings.scss"), join(templateDirNew, "settings.scss"), { overwrite: true, preserveTimestamps: true });331portRevealTheme(join(templateDirNew, "settings.scss"));332info("Copying dist/");333const dist = join(revealJs, "dist");334copyTo(join(dir, `reveal.js-${version}`, "dist"), dist, { overwrite: true, preserveTimestamps: true });335// remove unneeded CSS files336const theme = join(dist, "theme");337for (const fileEntry of Deno.readDirSync(theme)) {338if (fileEntry.isFile && extname(fileEntry.name) === ".css") {339info(`-> Removing unneeded ${fileEntry.name}.`);340Deno.removeSync(join(theme, fileEntry.name));341}342}343info("Copying plugin/");344copyTo(345join(dir, `reveal.js-${version}`, "plugin"),346join(revealJs, "plugin"),347{ overwrite: true, preserveTimestamps: true }348);349return Promise.resolve();350},351true,352false,353resolvePatches([354// patche for each themes355...["beige", "blood", "dracula", "league", "moon", "night", "serif", "simple", "sky", "solarized"].map(356(theme) => `revealjs-theme-0001-${theme}.patch`357),358// global patches359"revealjs-theme-0002-input-panel-bg.patch",360"revealjs-theme-0003-code-block-fixup.patch"361])362);363364// revealjs-chalkboard365const revealJsChalkboard = join(366config.directoryInfo.src,367"resources",368"formats",369"revealjs",370"plugins",371"chalkboard"372);373await updateGithubSourceCodeDependency(374"reveal.js-chalkboard",375"rajgoel/reveal.js-plugins",376"REVEAL_JS_CHALKBOARD",377workingDir,378(dir: string, version: string) => {379ensureDirSync(dirname(revealJsChalkboard));380copyTo(381join(dir, `reveal.js-plugins-${version}`, "chalkboard"),382revealJsChalkboard,383{ overwrite: true, preserveTimestamps: true }384);385return Promise.resolve();386},387true, // true if commit, false otherwise388false, // no v prefix,389);390391// revealjs-menu392const revealJsMenu = join(393config.directoryInfo.src,394"resources",395"formats",396"revealjs",397"plugins",398"menu"399);400await updateGithubSourceCodeDependency(401"reveal.js-menu",402"denehyg/reveal.js-menu",403"REVEAL_JS_MENU",404workingDir,405(dir: string, version: string) => {406// Copy the js file (modify to disable loadResource)407ensureDirSync(revealJsMenu);408const menuJs = Deno.readTextFileSync(409join(dir, `reveal.js-menu-${version}`, "menu.js")410).replace(411/function P\(e,t,n\).*?function M/,412"function P(e,t,n){n.call()}function M"413);414Deno.writeTextFileSync(join(revealJsMenu, "menu.js"), menuJs);415416// copy the css file417Deno.copyFileSync(418join(dir, `reveal.js-menu-${version}`, "menu.css"),419join(revealJsMenu, "menu.css")420);421422// copy font-awesome to chalkboard423copyTo(424join(dir, `reveal.js-menu-${version}`, "font-awesome"),425join(revealJsChalkboard, "font-awesome"),426{ overwrite: true, preserveTimestamps: true }427);428return Promise.resolve();429},430false, // not a commit431false // no v prefix432);433434// reveal-pdfexport435const revealJsPdfExport = join(436config.directoryInfo.src,437"resources",438"formats",439"revealjs",440"plugins",441"pdfexport"442);443444await updateGithubSourceCodeDependency(445"reveal-pdfexport",446"McShelby/reveal-pdfexport",447"REVEAL_JS_PDFEXPORT",448workingDir,449(dir: string, version: string) => {450ensureDirSync(revealJsPdfExport);451copyTo(452join(dir, `reveal-pdfexport-${version}`, "pdfexport.js"),453join(revealJsPdfExport, "pdfexport.js"),454{ overwrite: true, preserveTimestamps: true }455);456return Promise.resolve();457},458false, // not a commit459false, // no v prefix,460resolvePatches([461"revealjs-plugin-0001-pdfexport-to-export-toggle-fun.patch",462"revealjs-plugin-0001-pdfexport-view-mode.patch"463])464);465466// Github CSS (used for GFM HTML preview)467const ghCSS = join(468config.directoryInfo.src,469"resources",470"formats",471"gfm",472"github-markdown-css"473);474await updateGithubSourceCodeDependency(475"github-markdown-css",476"sindresorhus/github-markdown-css",477"GITHUB_MARKDOWN_CSS",478workingDir,479(dir: string, version: string) => {480ensureDirSync(ghCSS);481const files = [482"github-markdown-dark.css",483"github-markdown-light.css",484"github-markdown.css",485];486files.forEach((file) => {487// Copy the js file488Deno.copyFileSync(489join(dir, `github-markdown-css-${version}`, file),490join(ghCSS, file)491);492});493return Promise.resolve();494}495);496497// Autocomplete498const autocompleteJs = join(499config.directoryInfo.src,500"resources",501"projects",502"website",503"search",504"autocomplete.umd.js"505);506await updateUnpkgDependency(507"AUTOCOMPLETE_JS",508"@algolia/autocomplete-js",509"dist/umd/index.production.js",510autocompleteJs511);512cleanSourceMap(autocompleteJs);513514// Autocomplete preset515const autocompletePresetJs = join(516config.directoryInfo.src,517"resources",518"projects",519"website",520"search",521"autocomplete-preset-algolia.umd.js"522);523await updateUnpkgDependency(524"AUTOCOMPLETE_JS",525"@algolia/autocomplete-preset-algolia",526"dist/umd/index.production.js",527autocompletePresetJs528);529cleanSourceMap(autocompletePresetJs);530531// Update PDF JS532await updatePdfJs(config, workingDir);533534// Cookie-Consent535await updateCookieConsent(config, "4.0.0", workingDir);536537// Sticky table headers538await updateStickyThead(config, workingDir);539540// Datatables and PDF Make541await updateDatatables(config, workingDir);542543// Clean existing directories544[bsThemesDir, bsDistDir].forEach((dir) => {545if (existsSync(dir)) {546Deno.removeSync(dir, { recursive: true });547}548ensureDirSync(dir);549});550551const workingSubDir = (name: string) => {552const dir = join(workingDir, name);553ensureDirSync(dir);554return dir;555};556557// Update bootstrap558await updateBootstrapFromBslib(559bsCommit,560workingSubDir("bsdist"),561bsDistDir,562bsThemesDir,563bslibDir564);565566// Update Html Tools567await updateHtmlTools(568htmlToolsVersion,569workingSubDir("htmltools"),570htmlToolsDir571)572573// Update Bootstrap icons574await updateBoostrapIcons(bsIconVersion, workingSubDir("bsicons"), bsDistDir);575576// Update Pandoc themes577await updatePandocHighlighting(config);578579//580581// Clean up the temp dir582try {583Deno.removeSync(workingDir, { recursive: true });584} catch (_err) {585info(`Folder not deleted - Remove manually: ${workingDir}`);586}587info("\n** Done- please commit any files that have been updated. **\n");588}589590async function updatePdfJs(config: Configuration, working: string) {591const version = Deno.env.get("PDF_JS");592593info("Updating pdf.js...");594const fileName = `pdfjs-${version}-legacy-dist.zip`;595const distUrl = `https://github.com/mozilla/pdf.js/releases/download/v${version}/${fileName}`;596const zipFile = join(working, fileName);597598// Download and unzip the release599const pdfjsDir = join(working, "pdfjs");600ensureDirSync(pdfjsDir);601602info(`Downloading ${distUrl}`);603await download(distUrl, zipFile);604await unzip(zipFile, pdfjsDir);605606// Remove extraneous files607const removeFiles = ["web/compressed.tracemonkey-pldi-09.pdf"];608removeFiles.forEach((file) => Deno.removeSync(join(pdfjsDir, file)));609610const from = pdfjsDir;611const to = join(612config.directoryInfo.src,613"resources",614"formats",615"pdf",616"pdfjs"617);618copySync(from, to, { overwrite: true });619info("Done\n");620}621622async function updateCookieConsent(623config: Configuration,624version: string,625working: string626) {627const fileName = "cookie-consent.js";628const url = `https://www.cookieconsent.com/releases/${version}/${fileName}`;629const tempPath = join(working, fileName);630631info(`Downloading ${url}`);632await download(url, tempPath);633634const targetDir = join(635config.directoryInfo.src,636"resources",637"projects",638"website",639"cookie-consent"640);641await ensureDir(targetDir);642643await Deno.copyFile(tempPath, join(targetDir, fileName));644info("Done\n");645}646647async function updateDatatables(648config: Configuration,649working: string650) {651// css:652// script: https://cdn.datatables.net/v/bs5/jszip-3.10.1/dt-1.13.8/b-2.4.2/b-html5-2.4.2/b-print-2.4.2/kt-2.11.0/r-2.5.0/datatables.min.js653654// pdfmake655// https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/pdfmake.min.js656// https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.2.7/vfs_fonts.js657const datatablesConfig = Deno.env.get("DATATABLES_CONFIG");;658const pdfMakeVersion = Deno.env.get("PDF_MAKE");;659const dtFiles = ["datatables.min.css", "datatables.min.js"];660const targetDir = join(661config.directoryInfo.src,662"resources",663"formats",664"dashboard",665"js",666"dt"667);668await ensureDir(targetDir);669670for (const file of dtFiles) {671const url = `https://cdn.datatables.net/v/${datatablesConfig}/${file}`;672const tempPath = join(working, file);673info(`Downloading ${url}`);674await download(url, tempPath);675await Deno.copyFile(tempPath, join(targetDir, file));676}677678const pdfMakeFiles = ["pdfmake.min.js", "vfs_fonts.js"];679for (const file of pdfMakeFiles) {680const url = `https://cdnjs.cloudflare.com/ajax/libs/pdfmake/${pdfMakeVersion}/${file}`;681const tempPath = join(working, file);682info(`Downloading ${url}`);683await download(url, tempPath);684await Deno.copyFile(tempPath, join(targetDir, file));685}686687info("Done\n");688}689690async function updateStickyThead(691config: Configuration,692working: string693) {694const fileName = "stickythead.js";695const url = `https://raw.githubusercontent.com/rohanpujaris/stickythead/master/dist/${fileName}`;696const tempPath = join(working, fileName);697698info(`Downloading ${url}`);699await download(url, tempPath);700701const targetDir = join(702config.directoryInfo.src,703"resources",704"formats",705"dashboard",706"js"707);708await ensureDir(targetDir);709710await Deno.copyFile(tempPath, join(targetDir, fileName));711info("Done\n");712}713714async function updateHtmlTools(715version: string,716working: string,717distDir: string718) {719// https://github.com/rstudio/htmltools/archive/refs/tags/v0.5.6.zip720info("Updating Html Tools...");721const dirName = `htmltools-${version}`;722const fileName = `v${version}.zip`;723const distUrl = `https://github.com/rstudio/htmltools/archive/refs/tags/${fileName}`;724const zipFile = join(working, fileName);725726// Download and unzip the release727info(`Downloading ${distUrl}`);728await download(distUrl, zipFile);729await unzip(zipFile, working);730731// Copy the fill css file732ensureDirSync(distDir);733Deno.copyFileSync(734join(working, dirName, "inst", "fill", "fill.css"),735join(distDir, "fill.css")736);737738info("Done\n");739}740741async function updateBootstrapFromBslib(742commit: string,743working: string,744distDir: string,745themesDir: string,746bsLibDir: string747) {748info("Updating Bootstrap Scss Files...");749await withRepo(750working,751"https://github.com/rstudio/bslib.git",752async (repo: Repo) => {753// Checkout the appropriate version754await repo.checkout(commit);755756// Build the required JS files757info("Copying Components");758759// Get the components760const componentsFrom = join(repo.dir, "inst", "components", "dist");761const componentsTo = join(distDir, "components");762const components = ["accordion", "card", "grid", "sidebar", "valuebox"];763for (const component of components) {764info(` - ${component}`);765const componentDir = join(componentsTo, component);766ensureDirSync(componentDir);767768const files = [769`${component}.min.js`,770`${component}.css`771];772773for (const file of files) {774const fromPath = join(componentsFrom, component, file);775if (existsSync(fromPath)) {776const toPath = join(componentsTo, component, file);777ensureDirSync(dirname(toPath));778Deno.copyFileSync(fromPath, toPath);779780// Clean the source path781cleanSourceMap(toPath);782}783}784}785786// Copy the scss files787info("Copying scss files");788const from = join(repo.dir, "inst", "lib", "bs5", "scss");789const to = join(distDir, "scss");790info(`Copying ${from} to ${to}`);791copySync(from, to);792793// Fix up the Boostrap rules files794info(795"Rewriting bootstrap.scss to exclude functions, mixins, and variables."796);797const bootstrapFilter = [798'@import "functions";',799'@import "variables";',800'@import "mixins";',801];802const bootstrapScssFile = join(to, "bootstrap.scss");803const bootstrapScssContents = lines(804Deno.readTextFileSync(bootstrapScssFile)805)806.filter((line: string) => {807return !bootstrapFilter.includes(line);808})809.join("\n");810Deno.writeTextFileSync(bootstrapScssFile, bootstrapScssContents);811info("done.");812info("");813814// Rewrite the use of css `var()` style values to base SCSS values815info("Rewriting _variables.scss file.");816const bootstrapVariablesFile = join(to, "_variables.scss");817const varContents = lines(Deno.readTextFileSync(bootstrapVariablesFile));818const outLines: string[] = [];819for (let line of varContents) {820line = line.replaceAll(821"var(--#{$prefix}font-sans-serif)",822"$$font-family-sans-serif"823);824line = line.replaceAll(825"var(--#{$prefix}font-monospace)",826"$$font-family-monospace"827);828line = line.replaceAll(829"var(--#{$prefix}success-rgb)",830"$$success"831);832line = line.replaceAll(833"var(--#{$prefix}danger-rgb)",834"$$danger"835);836line = line.replaceAll(837"var(--#{$prefix}body-color-rgb)",838"$$body-color"839);840line = line.replaceAll(841"var(--#{$prefix}body-bg-rgb)",842"$$body-bg"843);844line = line.replaceAll(845"var(--#{$prefix}emphasis-color-rgb)",846"$$body-emphasis-color"847);848line = line.replaceAll(849/RGBA?\(var\(--#\{\$prefix\}emphasis-color-rgb,(.*?)\).*?\)/gm,850"$$body-emphasis-color"851);852line = line.replaceAll(853"var(--#{$prefix}secondary-color)",854"$$body-secondary-color"855);856line = line.replaceAll(857"var(--#{$prefix}secondary-bg)",858"$$body-secondary-bg"859);860line = line.replaceAll(861"var(--#{$prefix}tertiary-bg)",862"$$body-tertiary-bg"863);864line = line.replaceAll(865"var(--#{$prefix}tertiary-color)",866"$body-tertiary-color"867);868line = line.replaceAll(869"var(--#{$prefix}emphasis-bg)",870"$$body-emphasis-bg"871);872line = line.replaceAll(873"var(--#{$prefix}emphasis-color)",874"$$body-emphasis-color"875);876line = line.replaceAll(877"$emphasis-color-rgb",878"$$body-emphasis-color"879);880881line = line.replaceAll(/var\(--#\{\$prefix\}(.*?)\)/gm, "$$$1");882outLines.push(line);883}884Deno.writeTextFileSync(bootstrapVariablesFile, outLines.join("\n"));885info("done.");886info("");887888// Copy utils889info("Copying scss files");890const utilsFrom = join(repo.dir, "inst", "sass-utils");891const utilsTo = join(distDir, "sass-utils");892info(`Copying ${utilsFrom} to ${utilsTo}`);893copySync(utilsFrom, utilsTo);894895// Copy bslib896info("Copying BSLIB scss files");897const bslibScssFrom = join(repo.dir, "inst", "bslib-scss");898const bslibScssTo = join(bsLibDir, "bslib-scss");899info(`Copying ${bslibScssFrom} to ${bslibScssTo}`);900Deno.removeSync(bslibScssTo, { recursive: true});901copySync(bslibScssFrom, bslibScssTo);902903// Copy componennts904info("Copying BSLIB component scss files");905const componentFrom = join(repo.dir, "inst", "components", "scss");906const componentTo = join(bsLibDir, "components", "scss");907info(`Copying ${componentFrom} to ${componentTo}`);908copySync(componentFrom, componentTo, {overwrite: true});909910info("Copying BSLIB dist files");911const componentDistFrom = join(repo.dir, "inst", "components", "dist");912const componentDistTo = join(bsLibDir, "components", "dist");913info(`Copying ${componentDistFrom} to ${componentDistTo}`);914ensureDirSync(componentDistTo);915copySync(componentDistFrom, componentDistTo, {overwrite: true});916// Clean map references917for (const entry of walkSync(componentDistTo)) {918if (entry.isFile) {919cleanSourceMap(entry.path);920}921}922923// Grab the js file that we need924info("Copying dist files");925[926{927from: "bootstrap.bundle.min.js",928to: "bootstrap.min.js",929},930{931from: "bootstrap.bundle.min.js.map",932to: "bootstrap.min.js.map",933},934].forEach((file) => {935const from = join(936repo.dir,937"inst",938"lib",939"bs5",940"dist",941"js",942file.from943);944const to = join(distDir, file.to);945info(`Copying ${from} to ${to}`);946Deno.copyFileSync(from, to);947});948949// Merge the bootswatch themes950info("Merging themes:");951const exclude = ["4"];952const distPath = join(repo.dir, "inst", "lib", "bsw5", "dist");953for (const dirEntry of Deno.readDirSync(distPath)) {954if (dirEntry.isDirectory && !exclude.includes(dirEntry.name)) {955// this is a theme directory956const theme = dirEntry.name;957const themeDir = join(distPath, theme);958959info(`${theme}`);960const layer = mergedSassLayer(961join(themeDir, "_functions.scss"),962join(themeDir, "_variables.scss"),963join(themeDir, "_mixins.scss"),964join(themeDir, "_bootswatch.scss")965);966967const patchedScss = patchTheme(theme, layer, bootswatchThemePatches);968969const themeOut = join(themesDir, `${theme}.scss`);970Deno.writeTextFileSync(themeOut, patchedScss);971}972}973974975info("Done\n");976}977);978}979980async function updateBoostrapIcons(981version: string,982working: string,983distDir: string984) {985info("Updating Bootstrap Icons...");986const dirName = `bootstrap-icons-${version}`;987const fileName = `${dirName}.zip`;988const distUrl = `https://github.com/twbs/icons/releases/download/v${version}/${fileName}`;989const zipFile = join(working, fileName);990991// Download and unzip the release992info(`Downloading ${distUrl}`);993await download(distUrl, zipFile);994await unzip(zipFile, working);995996// Copy the woff file997Deno.copyFileSync(998join(working, dirName, "fonts", "bootstrap-icons.woff"),999join(distDir, "bootstrap-icons.woff")1000);10011002// Copy the css file, then fix it up1003const cssPath = join(distDir, "bootstrap-icons.css");1004Deno.copyFileSync(1005join(working, dirName, "bootstrap-icons.css"),1006cssPath1007);1008fixupFontCss(cssPath);10091010info("Done\n");1011}10121013async function updatePandocHighlighting(config: Configuration) {1014info("Updating Pandoc Highlighting Themes...");10151016const highlightDir = join(1017config.directoryInfo.src,1018"resources",1019"pandoc",1020"highlight-styles"1021);1022const pandoc =1023Deno.env.get("QUARTO_PANDOC") ||1024join(config.directoryInfo.bin, "tools", "pandoc");10251026// List the styles1027const result = await runCmd(pandoc, ["--list-highlight-styles"]);1028if (result.status.success) {1029const highlightStyles = result.stdout;1030if (highlightStyles) {1031// Got through the list of styles and extract each style to our resources1032const styles = lines(highlightStyles);1033info(`Updating ${styles.length} styles...`);1034for (const style of styles) {1035if (style) {1036info(`-> ${style}...`);1037const themeResult = await runCmd(pandoc, [1038"--print-highlight-style",1039style,1040]);10411042if (themeResult.status.success) {1043const themeData = themeResult.stdout;1044await Deno.writeTextFile(1045join(highlightDir, `${style}.theme`),1046themeData1047);1048}1049}1050}1051}1052}1053}10541055async function updateUnpkgDependency(1056versionEnvVar: string,1057pkg: string,1058filename: string,1059target: string1060) {1061const version = Deno.env.get(versionEnvVar);1062if (version) {1063info(`Updating ${pkg}...`);1064const url = `https://unpkg.com/${pkg}@${version}/${filename}`;10651066info(`Downloading ${url} to ${target}`);1067ensureDirSync(dirname(target));1068await download(url, target);1069info("done\n");1070} else {1071throw new Error(`${versionEnvVar} is not defined`);1072}1073}10741075/*1076async function updateJsDelivrDependency(1077versionEnvVar: string,1078pkg: string,1079filename: string,1080target: string,1081) {1082const version = Deno.env.get(versionEnvVar);1083if (version) {1084info(`Updating ${pkg}...`);1085const url = `https://cdn.jsdelivr.net/npm/${pkg}@${version}/${filename}`;10861087info(`Downloading ${url} to ${target}`);1088ensureDirSync(dirname(target));1089await download(url, target);1090info("done\n");1091} else {1092throw new Error(`${versionEnvVar} is not defined`);1093}1094}1095*/10961097async function updateGithubSourceCodeDependency(1098name: string,1099repo: string,1100versionEnvVar: string,1101working: string,1102onDownload: (dir: string, version: string) => Promise<void>,1103commit = false, // set to true when commit is used instead of a tag1104vPrefix = true, // set to false if github tags don't use a v prefix1105patches?: string[]1106) {1107info(`Updating ${name}...`);1108const version = Deno.env.get(versionEnvVar)?.trim();1109if (version) {1110const fileName = `${name}.zip`;1111const distUrl = join(1112`https://github.com/${repo}/archive`,1113commit1114? `${version}.zip`1115: `refs/tags/${vPrefix ? "v" : ""}${version}.zip`1116);1117const zipFile = join(working, fileName);11181119// Download and unzip the release1120info(`Downloading ${distUrl}`);1121await download(distUrl, zipFile);1122await unzip(zipFile, working);11231124await onDownload(working, version);1125if (patches) {1126await applyGitPatches(patches);1127}1128} else {1129throw new Error(`${versionEnvVar} is not defined`);1130}11311132info("Done\n");1133}11341135function fixupFontCss(path: string) {1136let css = Deno.readTextFileSync(path);11371138// Clear the woff2 reference1139const woff2Regex =1140/url\("\.\/fonts\/bootstrap-icons\.woff2.*format\("woff2"\),/;1141css = css.replace(woff2Regex, "");11421143// Update the font reference to point to the local font1144const woffPathRegex = /url\("\.(\/fonts)\/bootstrap-icons\.woff\?/;1145css = css.replace(woffPathRegex, (substring: string) => {1146return substring.replace("/fonts", "");1147});11481149Deno.writeTextFileSync(path, css);1150}11511152// Cleans the source map declaration at the end of a JS file1153function cleanSourceMap(path: string) {1154if (existsSync(path)) {1155const source = Deno.readTextFileSync(path);1156Deno.writeTextFileSync(1157path,1158source1159.replaceAll(kSourceMappingRegexes[0], "")1160.replaceAll(kSourceMappingRegexes[1], "")1161);1162}1163}11641165function mergedSassLayer(1166funcPath: string,1167defaultsPath: string,1168mixinsPath: string,1169rulesPath: string1170) {1171const merged: string[] = [];1172[1173{1174name: "functions",1175path: funcPath,1176},1177{1178name: "defaults",1179path: defaultsPath,1180},1181{1182name: "mixins",1183path: mixinsPath,1184},1185{1186name: "rules",1187path: rulesPath,1188},1189].forEach((part) => {1190const contents = existsSync(part.path)1191? Deno.readTextFileSync(part.path)1192: undefined;1193if (contents) {1194merged.push(`/*-- scss:${part.name} --*/`);11951196const inputLines = contents.split("\n");1197const outputLines: string[] = [];11981199// This filters out any leading comments1200// in the theme file (which we think could be confusing1201// to users who are using these files as exemplars)1202let emit = false;1203inputLines.forEach((line) => {1204if (!line.startsWith("//")) {1205emit = true;1206}12071208if (emit) {1209outputLines.push(line);1210}1211});12121213merged.push(outputLines.join("\n"));1214merged.push("\n");1215}1216});1217return merged.join("\n");1218}12191220function patchTheme(themeName: string, themeContents: string, themePatches: Record<string, ThemePatch[]> ) {1221const patches = themePatches[themeName];1222if (patches) {1223let patchedTheme = themeContents;1224patches.forEach((patch) => {1225if (patchedTheme.includes(patch.from)) {1226patchedTheme = patchedTheme.replace(patch.from, patch.to);1227} else {1228throw Error(1229`Unable to patch template ${themeName} because the target ${patch.from} cannot be found`1230);1231}1232});1233return patchedTheme;1234} else {1235return themeContents;1236}1237}12381239interface ThemePatch {1240from: string;1241to: string;1242}12431244const bootswatchThemePatches: Record<string, ThemePatch[]> = {1245litera: [1246{1247from: ".navbar {\n font-size: $font-size-sm;",1248to: ".navbar {\n font-size: $font-size-sm;\n border: 1px solid rgba(0, 0, 0, .1);",1249},1250],1251lumen: [1252{1253from: ".navbar {\n @include shadow();",1254to: ".navbar {\n @include shadow();\n border-color: shade-color($navbar-bg, 10%);",1255},1256{1257from: "$nav-link-color: var(--#{$prefix}link-color) !default;",1258to: "$nav-link-color: $primary !default;"1259}1260],1261simplex: [1262{1263from: ".navbar {\n border-style: solid;\n border-width: 1px;",1264to: ".navbar {\n border-width: 1px;\n border-style: solid;\n border-color: shade-color($navbar-bg, 13%);",1265},1266],1267solar: [1268{1269from: "$body-color: $gray-600 !default;",1270to: "$body-color: $gray-500 !default;",1271},1272],1273};12741275function portRevealTheme(themeFile: string) {1276const themeFileContent = Deno.readTextFileSync(themeFile);1277const themeName = basename(themeFile, extname(themeFile));1278const patchedScss = patchTheme(themeName, themeFileContent, revealjsThemePatches)1279Deno.writeTextFileSync(themeFile, patchedScss);1280}12811282// This maps the reveal.js theme variables to the Quarto theme variables1283// Revealjs variables can be seen in _settings.scss and this mapping needs to be used1284// to port the revealjs theme to a quarto theme so that users' layers can correctly override1285// framework defaults. Quarto layer insure the mapping of those SASS variable.1286// - Framework revealjs main theme file uses the revealjs variables1287// - each revealjs ported theme should use the quarto variables1288// - _quarto.scss maps the quarto variables to the revealjs variables,1289// using generic $presentation-* variants for the $revealjs-* specific1290const sassVarsMap = {1291// Background of the presentation1292backgroundColor: "body-bg",1293// Primary/body text1294mainFont: "font-family-sans-serif",1295mainFontSize: "presentation-font-size-root",1296mainColor: "body-color",1297// Vertical spacing between blocks of text1298blockMargin: "presentation-block-margin",1299// Headings1300// headingMargin is set directly in quarto.scss1301headingFont: "presentation-heading-font",1302headingColor: "presentation-heading-color",1303headingLineHeight: "presentation-heading-line-height",1304headingLetterSpacing: "presentation-heading-letter-spacing",1305headingTextTransform: "presentation-heading-text-transform",1306headingTextShadow: "presentation-heading-text-shadow",1307heading1TextShadow: "presentation-h1-text-shadow",1308headingFontWeight: "presentation-heading-font-weight",13091310heading1Size: "presentation-h1-font-size",1311heading2Size: "presentation-h2-font-size",1312heading3Size: "presentation-h3-font-size",1313heading4Size: "presentation-h4-font-size",13141315codeFont: "font-family-monospace",1316codeBackground: "code-bg",1317inlineCodeColor: "code-color", // from dracula.scss13181319// Links and actions1320linkColor: "link-color",1321linkColorHover: "link-color-hover",13221323// Text selection1324selectionBackgroundColor: "selection-bg",1325selectionColor: "selection-color",13261327// Lists1328listBulletColor: "presentation-list-bullet-color", // from dracula.scss1329};13301331let revealjsThemePatches: Record<string, ThemePatch[]> = {}; // Initialize the variable13321333const createRevealjsThemePatches = (keys: string[]): ThemePatch[] => {1334const filteredVarsMap: Record<string, string> = keys.reduce((acc, key) => {1335if (sassVarsMap[key]) {1336acc[key] = sassVarsMap[key];1337} else {1338throw Error(`Variable ${key} not found in the sassVarsMap`);1339}1340return acc;1341}, {});13421343return Object.entries(filteredVarsMap).map(([key, value]) => ({1344from: key,1345to: value,1346}));1347}13481349revealjsThemePatches["beige"] = createRevealjsThemePatches(["mainColor", "headingColor", "headingTextShadow", "backgroundColor", "linkColor","linkColorHover", "selectionBackgroundColor", "heading1TextShadow"])1350revealjsThemePatches["black-contrast"] = createRevealjsThemePatches(["backgroundColor", "mainColor", "headingColor", "mainFontSize", "mainFont", "headingFont", "headingTextShadow", "headingLetterSpacing", "headingTextTransform", "headingFontWeight", "linkColor", "linkColorHover", "selectionBackgroundColor", "heading1Size", "heading2Size", "heading3Size", "heading4Size"])1351revealjsThemePatches["black"] = createRevealjsThemePatches(["backgroundColor", "mainColor", "headingColor", "mainFontSize", "mainFont", "headingFont", "headingTextShadow", "headingLetterSpacing", "headingTextTransform", "headingFontWeight", "linkColor", "linkColorHover", "selectionBackgroundColor", "heading1Size", "heading2Size", "heading3Size", "heading4Size"])1352revealjsThemePatches["blood"] = createRevealjsThemePatches(["codeBackground", "backgroundColor", "mainFont", "mainColor", "headingFont", "headingTextShadow", "heading1TextShadow", "linkColor", "linkColorHover", "selectionBackgroundColor", "selectionColor"])1353revealjsThemePatches["dracula"] = createRevealjsThemePatches(["mainColor", "headingColor", "headingTextShadow", "headingTextTransform", "backgroundColor", "linkColor","linkColorHover", "selectionBackgroundColor", "inlineCodeColor", "listBulletColor", "mainFont", "codeFont"])1354revealjsThemePatches["league"] = createRevealjsThemePatches(["headingTextShadow", "heading1TextShadow"])1355revealjsThemePatches["moon"] = createRevealjsThemePatches(["mainColor", "headingColor", "headingTextShadow", "backgroundColor", "linkColor", "linkColorHover", "selectionBackgroundColor"])1356revealjsThemePatches["night"] = createRevealjsThemePatches(["backgroundColor", "mainFont", "linkColor", "linkColorHover", "headingFont", "headingTextShadow", "headingLetterSpacing", "headingTextTransform", "selectionBackgroundColor"])1357revealjsThemePatches["serif"] = createRevealjsThemePatches(["mainFont", "mainColor", "headingFont", "headingColor", "headingTextShadow", "headingTextTransform", "backgroundColor", "linkColor", "linkColorHover", "selectionBackgroundColor"])1358revealjsThemePatches["simple"] = createRevealjsThemePatches(["mainFont", "mainColor", "headingFont", "headingColor", "headingTextShadow", "headingTextTransform", "backgroundColor", "linkColor", "linkColorHover", "selectionBackgroundColor"])1359revealjsThemePatches["sky"] = createRevealjsThemePatches(["mainFont", "mainColor", "headingFont", "headingColor", "headingLetterSpacing", "headingTextShadow", "backgroundColor", "linkColor", "linkColorHover", "selectionBackgroundColor"])1360revealjsThemePatches["solarized"] = createRevealjsThemePatches(["mainColor", "headingColor", "headingTextShadow", "backgroundColor", "linkColor", "linkColorHover", "selectionBackgroundColor"])1361revealjsThemePatches["white-contrast"] = createRevealjsThemePatches(["backgroundColor", "mainColor", "headingColor", "mainFontSize", "mainFont", "headingFont", "headingTextShadow", "headingLetterSpacing", "headingTextTransform", "headingFontWeight", "linkColor", "linkColorHover", "selectionBackgroundColor", "heading1Size", "heading2Size", "heading3Size", "heading4Size"])1362revealjsThemePatches["white"] = createRevealjsThemePatches(["backgroundColor", "mainColor", "headingColor", "mainFontSize", "mainFont", "headingFont", "headingTextShadow", "headingLetterSpacing", "headingTextTransform", "headingFontWeight", "linkColor", "linkColorHover", "selectionBackgroundColor", "heading1Size", "heading2Size", "heading3Size", "heading4Size"])1363revealjsThemePatches["settings"] = createRevealjsThemePatches(["backgroundColor", "mainFont", "mainFontSize", "mainColor", "blockMargin", "headingFont", "headingColor", "headingLineHeight", "headingLetterSpacing", "headingTextTransform", "headingTextShadow", "headingFontWeight", "heading1TextShadow", "heading1Size", "heading2Size", "heading3Size", "heading4Size", "codeFont", "linkColor", "linkColorHover", "selectionBackgroundColor", "selectionColor"])136413651366