Path: blob/main/src/project/project-resources.ts
12924 views
/*1* project-resources.ts2*3* Copyright (C) 2020-2022 Posit Software, PBC4*/56import { ensureDirSync } from "../deno_ral/fs.ts";7import { dirname, extname, join, relative } from "../deno_ral/path.ts";8import * as ld from "../core/lodash.ts";9import { asArray } from "../core/array.ts";1011import {12normalizePath,13resolvePathGlobs,14safeExistsSync,15} from "../core/path.ts";16import { kCssImportRegex, kCssUrlRegex } from "../core/css.ts";1718import {19kProject404File,20kProjectOutputDir,21kProjectResources,22ProjectConfig,23} from "./types.ts";2425import { kQuartoIgnore } from "./project-gitignore.ts";26import { copyFileIfNewer } from "../core/copy.ts";27import { existsSync1 } from "../core/file.ts";2829export function projectResourceFiles(30dir: string,31config: ProjectConfig,32): string[] {33let resourceGlobs = asArray(config.project[kProjectResources]);34const resourceFiles: string[] = [];35// This usage of reading the output directory is necessary since we haven't36// yet formed the project context (this function is used in creating the context)37const outputDir = config.project[kProjectOutputDir];38if (outputDir) {39resourceGlobs = (resourceGlobs || [])40// ignore standard quarto ignores (e.g. /.quarto/)41.concat(kQuartoIgnore.map((entry) => `!${entry}`));4243const exclude = outputDir ? [outputDir] : [];44const projectResourceFiles = resolvePathGlobs(45dir,46resourceGlobs,47exclude,48);49resourceFiles.push(50...ld.difference(51projectResourceFiles.include,52projectResourceFiles.exclude,53),54);55// literals56resourceFiles.push(57...["robots.txt", ".nojekyll", "CNAME", "_redirects", kProject404File]58.map((file) => join(dir, file))59.filter(existsSync1),60);61}62return ld.uniq(resourceFiles);63}6465export function copyResourceFile(66rootDir: string,67srcFile: string,68destFile: string,69) {70// ensure that the resource reference doesn't escape the root dir71if (!normalizePath(srcFile).startsWith(normalizePath(rootDir))) {72return;73}7475ensureDirSync(dirname(destFile));76copyFileIfNewer(srcFile, destFile);7778if (extname(srcFile).toLowerCase() === ".css") {79handleCssReferences(rootDir, srcFile, destFile);80}81}8283export function fixupCssReferences(84css: string,85offset: string,86onRef: (ref: string) => string,87) {88// fixup / copy refs from url()89let destCss = css.replaceAll(90kCssUrlRegex,91(_match, p1: string, p2: string) => {92const ref = p2.startsWith("/") ? `${offset}${p2.slice(1)}` : p2;93return `url(${p1 || ""}${onRef(ref)}${p1 || ""})`;94},95);9697// fixup / copy refs from @import98destCss = destCss.replaceAll(99kCssImportRegex,100(_match, p1: string, p2: string) => {101const ref = p2.startsWith("/") ? `${offset}${p2.slice(1)}` : p2;102return `@import ${p1 || ""}${onRef(ref)}${p1 || ""}`;103},104);105106return destCss;107}108109// fixup root ('/') css references and also copy references to other110// stylesheet or resources (e.g. images) to alongside the destFile111function handleCssReferences(112rootDir: string,113srcFile: string,114destFile: string,115) {116// read the css117const css = Deno.readTextFileSync(destFile);118119// offset for root references120const offset = relative(dirname(srcFile), rootDir);121122// function that can be used to copy a ref123const copyRef = (ref: string) => {124const refPath = join(dirname(srcFile), ref);125if (safeExistsSync(refPath)) {126const refDestPath = join(dirname(destFile), ref);127copyResourceFile(rootDir, refPath, refDestPath);128}129return ref;130};131132const destCss = fixupCssReferences(css, offset, copyRef);133134// write the css if necessary135if (destCss !== css) {136Deno.writeTextFileSync(destFile, destCss);137}138}139140141