Path: blob/main/src/command/render/render-shared.ts
6428 views
/*1* render-shared.ts2*3* Copyright (C) 2020-2022 Posit Software, PBC4*/56import { dirname } from "../../deno_ral/path.ts";78import { info } from "../../deno_ral/log.ts";9import * as colors from "fmt/colors";1011import {12projectContext,13projectContextForDirectory,14} from "../../project/project-context.ts";1516import { renderProject } from "./project.ts";17import { renderFiles } from "./render-files.ts";18import { resourceFilesFromRenderedFile } from "./resources.ts";19import { RenderFlags, RenderOptions, RenderResult } from "./types.ts";2021import {22isProjectInputFile,23projectExcludeDirs,24} from "../../project/project-shared.ts";2526import {27initState,28setInitializer,29} from "../../core/lib/yaml-validation/state.ts";30import { initYamlIntelligenceResourcesFromFilesystem } from "../../core/schema/utils.ts";31import { kTextPlain } from "../../core/mime.ts";32import { normalizePath } from "../../core/path.ts";33import { notebookContext } from "../../render/notebook/notebook-context.ts";34import { singleFileProjectContext } from "../../project/types/single-file/single-file.ts";35import { ProjectContext } from "../../project/types.ts";3637export async function render(38path: string,39options: RenderOptions,40pContext?: ProjectContext,41): Promise<RenderResult> {42// one time initialization of yaml validators43setInitializer(initYamlIntelligenceResourcesFromFilesystem);44await initState();4546const nbContext = pContext?.notebookContext || notebookContext();4748// determine target context/files49let context = pContext || (await projectContext(path, nbContext, options));5051// Create a synthetic project when --output-dir is used without a project file52// This creates a temporary .quarto directory to manage the render, which must53// be fully cleaned up afterward to avoid leaving debris (see #9745)54if (!context && options.flags?.outputDir) {55context = await projectContextForDirectory(path, nbContext, options);5657// forceClean signals this is a synthetic project that needs full cleanup58// including removing the .quarto scratch directory after rendering (#13625)59options.forceClean = options.flags.clean !== false;60}6162// Fall back to single file context if we still don't have a context63if (!context) {64context = await singleFileProjectContext(path, nbContext, options);65}6667// set env var if requested68if (context && options.setProjectDir) {69// FIXME we can't set environment variables like this with asyncs flying around70Deno.env.set("QUARTO_PROJECT_DIR", context.dir);71}7273if (Deno.statSync(path).isDirectory) {74// if the path is a sub-directory of the project, then create75// a files list that is only those files in the subdirectory76let files: string[] | undefined;77if (context) {78const renderDir = normalizePath(path);79const projectDir = normalizePath(context.dir);80if (renderDir !== projectDir) {81files = context.files.input.filter((file) =>82file.startsWith(renderDir)83);84}85return renderProject(86context,87options,88files,89);90} else {91throw new Error(92"The specified directory ('" + path +93"') is not a Quarto project.\n(If you have not specified a path, quarto will attempt to render the entire current directory as a project.)",94);95}96} else if (context?.config) {97// if there is a project file then treat this as a project render98// if the passed file is in the render list99if (isProjectInputFile(path, context)) {100return renderProject(context, options, [path]);101}102}103104// validate that we didn't get any project-only options105validateDocumentRenderFlags(options.flags);106107// otherwise it's just a file render108const result = await renderFiles(109[{ path }],110options,111nbContext,112undefined,113undefined,114context,115);116117// get partitioned markdown if we had result files118const { engine } = await context.fileExecutionEngineAndTarget(119path,120);121const partitioned = (engine && result.files.length > 0)122? await engine.partitionedMarkdown(path)123: undefined;124125const excludeDirs = context ? projectExcludeDirs(context) : [];126127// compute render result128const renderResult = {129context,130files: await Promise.all(result.files.map(async (file) => {131const resourceFiles = await resourceFilesFromRenderedFile(132dirname(path),133excludeDirs,134file,135partitioned,136);137return {138input: file.input,139markdown: file.markdown,140format: file.format,141file: file.file,142supporting: file.supporting,143resourceFiles,144};145})),146error: result.error,147baseDir: normalizePath(dirname(path)),148};149150if (!renderResult.error && engine?.postRender) {151for (const file of renderResult.files) {152await engine.postRender(file);153}154}155156// return157return renderResult;158}159160export function printWatchingForChangesMessage() {161info("Watching files for changes", { format: colors.green });162}163164export function previewUnableToRenderResponse() {165return new Response("not found", {166status: 404,167headers: {168"Content-Type": kTextPlain,169},170});171}172173// QUARTO_RENDER_TOKEN174let quartoRenderToken: string | null | undefined;175export function renderToken(): string | null {176const kQuartoRenderToken = "QUARTO_RENDER_TOKEN";177if (quartoRenderToken === undefined) {178quartoRenderToken = Deno.env.get(kQuartoRenderToken) || null;179Deno.env.delete(kQuartoRenderToken);180}181return quartoRenderToken;182}183184function validateDocumentRenderFlags(flags?: RenderFlags) {185if (flags) {186const projectOnly: { [key: string]: string | undefined } = {187["--output-dir"]: flags.outputDir,188["--site-url"]: flags.siteUrl,189};190for (const arg of Object.keys(projectOnly)) {191if (projectOnly[arg]) {192throw new Error(193`The ${arg} flag can only be used when rendering projects.`,194);195}196}197}198}199200201