import { error } from "../deno_ral/log.ts";
import { basename, join } from "../deno_ral/path.ts";
import { ProjectConfig } from "../project/types.ts";
import * as ld from "../core/lodash.ts";
import { readAndValidateYamlFromFile } from "../core/schema/validated-yaml.ts";
import { mergeProjectMetadata } from "../config/metadata.ts";
import { safeExistsSync } from "../core/path.ts";
import { Schema } from "../core/lib/yaml-schema/types.ts";
import {
activeProfiles,
kQuartoProfile,
readProfile,
} from "../quarto-core/profile.ts";
import { dotenvQuartoProfile } from "../quarto-core/dotenv.ts";
import { Metadata } from "../config/types.ts";
const kQuartoProfileConfig = "profile";
type QuartoProfileConfig = {
default?: string | string[] | undefined;
group?: string[] | Array<string[]> | undefined;
};
let baseQuartoProfile: string | undefined;
export async function initializeProfileConfig(
dir: string,
config: ProjectConfig,
schema: Schema,
) {
const firstRun = baseQuartoProfile === undefined;
if (firstRun) {
baseQuartoProfile = Deno.env.get(kQuartoProfile) || "";
}
const profileConfig = ld.isObject(config[kQuartoProfileConfig])
? config[kQuartoProfileConfig] as QuartoProfileConfig
: undefined;
delete config[kQuartoProfileConfig];
let quartoProfile = baseQuartoProfile || await dotenvQuartoProfile(dir) ||
await localConfigQuartoProfile(dir, schema) || "";
if (!quartoProfile) {
if (Array.isArray(profileConfig?.default)) {
quartoProfile = profileConfig!.default
.map((value) => String(value)).join(",");
} else if (typeof (profileConfig?.default) === "string") {
quartoProfile = profileConfig.default;
}
}
const active = readProfile(quartoProfile);
if (active.length === 0) {
if (Deno.env.get("RSTUDIO_PRODUCT") === "CONNECT") {
active.push("connect");
}
}
const groups = readProfileGroups(profileConfig);
for (const group of groups) {
if (!group.some((name) => active!.includes(name))) {
active.push(group[0]);
}
}
const updatedQuartoProfile = active.join(",");
if (!firstRun) {
if (Deno.env.get(kQuartoProfile) !== updatedQuartoProfile) {
fireActiveProfileChanged(updatedQuartoProfile);
}
}
Deno.env.set(kQuartoProfile, active.join(","));
return await mergeProfiles(
dir,
config,
schema,
);
}
const listeners = new Array<(profile: string) => void>();
function fireActiveProfileChanged(profile: string) {
listeners.forEach((listener) => listener(profile));
}
export function onActiveProfileChanged(
listener: (profile: string) => void,
) {
listeners.push(listener);
}
async function localConfigQuartoProfile(dir: string, schema: Schema) {
const localConfigPath = localProjectConfigFile(dir);
if (localConfigPath) {
const yaml = await readAndValidateYamlFromFile(
localConfigPath,
schema,
`Validation of configuration profile file ${
basename(localConfigPath)
} failed.`,
"{}",
) as Metadata;
const profile = yaml[kQuartoProfileConfig] as
| QuartoProfileConfig
| undefined;
if (Array.isArray(profile?.default)) {
return profile?.default.join(",");
} else if (typeof (profile?.default) === "string") {
return profile?.default;
} else {
return undefined;
}
} else {
return undefined;
}
}
async function mergeProfiles(
dir: string,
config: ProjectConfig,
schema: Schema,
) {
const files: string[] = [];
const mergeProfile = async (profilePath: string) => {
try {
const yaml = await readAndValidateYamlFromFile(
profilePath,
schema,
`Validation of configuration profile file ${
basename(profilePath)
} failed.`,
"{}",
);
config = mergeProjectMetadata(config, yaml);
files.push(profilePath);
} catch (e) {
error(
"\nError reading configuration profile file from " +
basename(profilePath) +
"\n",
);
throw e;
}
};
for (const profileName of activeProfiles().reverse()) {
const profilePath = [".yml", ".yaml"].map((
ext,
) => join(dir, `_quarto-${profileName}${ext}`)).find(safeExistsSync);
if (profilePath) {
await mergeProfile(profilePath);
}
}
const localConfigPath = localProjectConfigFile(dir);
if (localConfigPath) {
await mergeProfile(localConfigPath);
}
return { config, files };
}
function localProjectConfigFile(dir: string) {
return [".yml", ".yaml"].map((ext) => join(dir, `_quarto${ext}.local`))
.find(safeExistsSync);
}
function readProfileGroups(
profileConfig?: QuartoProfileConfig,
): Array<string[]> {
const groups: Array<string[]> = [];
const configGroup = profileConfig?.group as unknown;
if (Array.isArray(configGroup)) {
if (configGroup.every((value) => typeof value === "string")) {
groups.push(configGroup);
} else if (configGroup.every(Array.isArray)) {
groups.push(...configGroup);
}
}
return groups;
}