import * as ld from "../core/lodash.ts";
import { existsSync, walkSync } from "../deno_ral/fs.ts";
import {
basename,
dirname,
extname,
isAbsolute,
join,
relative,
} from "../deno_ral/path.ts";
import {
InputMetadata,
PublishFiles,
PublishProvider,
} from "./provider-types.ts";
import { AccountToken } from "./provider-types.ts";
import { PublishOptions } from "./types.ts";
import { render } from "../command/render/render-shared.ts";
import { renderServices } from "../command/render/render-services.ts";
import { projectOutputDir } from "../project/project-shared.ts";
import { PublishRecord } from "../publish/types.ts";
import { ProjectContext } from "../project/types.ts";
import { renderProgress } from "../command/render/render-info.ts";
import { inspectConfig, isDocumentConfig } from "../inspect/inspect.ts";
import { kOutputFile, kTitle } from "../config/constants.ts";
import { inputFilesDir } from "../core/render.ts";
import {
writeProjectPublishDeployment,
writePublishDeployment,
} from "./config.ts";
import { websiteTitle } from "../project/types/website/website-config.ts";
import { gfmAutoIdentifier } from "../core/pandoc/pandoc-id.ts";
import { RenderResultFile } from "../command/render/types.ts";
import { isHtmlContent, isPdfContent } from "../core/mime.ts";
import { RenderFlags } from "../command/render/types.ts";
import { normalizePath } from "../core/path.ts";
import { notebookContext } from "../render/notebook/notebook-context.ts";
export const kSiteContent = "site";
export const kDocumentContent = "document";
export async function publishSite(
project: ProjectContext,
provider: PublishProvider,
account: AccountToken,
options: PublishOptions,
target?: PublishRecord,
) {
const renderForPublish = async (
flags?: RenderFlags,
): Promise<PublishFiles> => {
let metadataByInput: Record<string, InputMetadata> = {};
if (options.render) {
renderProgress("Rendering for publish:\n");
const services = renderServices(notebookContext());
try {
const result = await render(project.dir, {
services,
flags,
setProjectDir: true,
});
metadataByInput = result.files.reduce(
(accumulatedResult: any, currentInput) => {
const key: string = currentInput.input as string;
accumulatedResult[key] = {
title: currentInput.format.metadata.title,
author: currentInput.format.metadata.author,
date: currentInput.format.metadata.date,
};
return accumulatedResult;
},
{},
);
result.context.cleanup();
if (result.error) {
throw result.error;
}
} finally {
services.cleanup();
}
}
const outputDir = projectOutputDir(project);
const files: string[] = [];
for (const walk of walkSync(outputDir)) {
if (walk.isFile) {
files.push(relative(outputDir, walk.path));
}
}
return normalizePublishFiles({
baseDir: outputDir,
rootFile: "index.html",
files,
metadataByInput,
});
};
const siteTitle = websiteTitle(project.config) || basename(project.dir);
const siteSlug = gfmAutoIdentifier(siteTitle, false);
const [publishRecord, siteUrl] = await provider.publish(
account,
kSiteContent,
project.dir,
siteTitle,
siteSlug,
renderForPublish,
options,
target,
);
if (publishRecord) {
if (options.id === undefined) {
writeProjectPublishDeployment(
project,
provider.name,
account,
publishRecord,
);
}
}
return siteUrl;
}
export async function publishDocument(
document: string,
provider: PublishProvider,
account: AccountToken,
options: PublishOptions,
target?: PublishRecord,
) {
let title = basename(document, extname(document));
const fileConfig = await inspectConfig(document);
if (isDocumentConfig(fileConfig)) {
title = (Object.values(fileConfig.formats)[0].metadata[kTitle] ||
title) as string;
}
const renderForPublish = async (
flags?: RenderFlags,
): Promise<PublishFiles> => {
const files: string[] = [];
if (options.render) {
renderProgress("Rendering for publish:\n");
const services = renderServices(notebookContext());
try {
const result = await render(document, {
services,
flags,
});
if (result.error) {
result.context.cleanup();
throw result.error;
}
if (result.baseDir) {
result.baseDir = normalizePath(result.baseDir);
const docDir = normalizePath(dirname(document));
if (result.baseDir !== docDir) {
const docRelative = (file: string) => {
if (!isAbsolute(file)) {
file = join(result.baseDir!, file);
}
return relative(docDir, file);
};
result.files = result.files.map((resultFile) => {
return {
...resultFile,
file: docRelative(resultFile.file),
supporting: resultFile.supporting
? resultFile.supporting.map(docRelative)
: undefined,
resourceFiles: resultFile.resourceFiles.map(docRelative),
};
});
result.baseDir = docDir;
}
}
const baseDir = result.baseDir || dirname(document);
const asRelative = (file: string) => {
if (isAbsolute(file)) {
return relative(baseDir, file);
} else {
return file;
}
};
const findRootFile = (files: RenderResultFile[]) => {
const rootFile = files.find((renderResult) => {
return isHtmlContent(renderResult.file);
}) || files.find((renderResult) => {
return isPdfContent(renderResult.file);
}) || files[0];
if (rootFile) {
return asRelative(rootFile.file);
} else {
return undefined;
}
};
const rootFile: string | undefined = findRootFile(result.files);
for (const resultFile of result.files) {
const file = asRelative(resultFile.file);
files.push(file);
if (resultFile.supporting) {
files.push(
...resultFile.supporting
.map((sf) => {
if (isAbsolute(sf)) {
return relative(baseDir, sf);
} else {
return sf;
}
})
.map(asRelative),
);
}
files.push(...resultFile.resourceFiles.map(asRelative));
}
let finalBaseDir = baseDir;
if (result.outputDir && result.context) {
const relBasePath = relative(result.context.dir, baseDir);
finalBaseDir = join(
result.context.dir,
result.outputDir,
relBasePath,
);
}
result.context.cleanup();
return normalizePublishFiles({
baseDir: finalBaseDir,
rootFile: rootFile!,
files,
});
} finally {
services.cleanup();
}
} else {
const baseDir = dirname(document);
if (isDocumentConfig(fileConfig)) {
let rootFile: string | undefined;
for (const format of Object.values(fileConfig.formats)) {
title = (format.metadata[kTitle] || title) as string;
const outputFile = format.pandoc[kOutputFile];
if (outputFile && existsSync(join(baseDir, outputFile))) {
files.push(outputFile);
if (!rootFile) {
rootFile = outputFile;
}
} else {
throw new Error(`Output file ${outputFile} does not exist.`);
}
}
const filesDir = inputFilesDir(document);
if (existsSync(filesDir)) {
files.push(filesDir);
}
files.push(...fileConfig.resources);
return normalizePublishFiles({
baseDir,
rootFile: rootFile!,
files,
});
} else {
throw new Error(
`The specifed document (${document}) is not a valid quarto input file`,
);
}
}
};
const [publishRecord, siteUrl] = await provider.publish(
account,
kDocumentContent,
document,
title,
gfmAutoIdentifier(title, false),
renderForPublish,
options,
target,
);
if (publishRecord) {
if (options.id === undefined) {
writePublishDeployment(document, provider.name, account, publishRecord);
}
}
return siteUrl;
}
function normalizePublishFiles(publishFiles: PublishFiles) {
publishFiles.files = publishFiles.files.reduce((files, file) => {
const filePath = join(publishFiles.baseDir, file);
if (Deno.statSync(filePath).isDirectory) {
for (const walk of walkSync(filePath)) {
if (walk.isFile) {
files.push(relative(publishFiles.baseDir, walk.path));
}
}
} else {
files.push(file);
}
return files;
}, new Array<string>());
publishFiles.files = ld.uniq(publishFiles.files) as string[];
return publishFiles;
}