Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/quarto-core/dotenv.ts
3557 views
1
/*
2
* dotenv.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
7
import { load as config } from "dotenv";
8
import { stringify } from "dotenv/stringify";
9
import { join } from "../deno_ral/path.ts";
10
import { safeExistsSync } from "../core/path.ts";
11
import { isEqual } from "../core/lodash.ts";
12
import { globalTempContext } from "../core/temp.ts";
13
import { existsSync } from "../deno_ral/fs.ts";
14
import { activeProfiles, kQuartoProfile } from "./profile.ts";
15
16
const kQuartoEnv = "_environment";
17
const kQuartoEnvLocal = `${kQuartoEnv}.local`;
18
const kQuartoEnvRequired = `${kQuartoEnv}.required`;
19
20
// read the QUARTO_PROFILE from dotenv if it's there
21
export async function dotenvQuartoProfile(projectDir: string) {
22
// read config
23
const conf1 = await config({
24
envPath: join(projectDir, kQuartoEnv),
25
});
26
const conf2 = await config({
27
envPath: join(projectDir, kQuartoEnvLocal),
28
});
29
30
// return profile if we have it
31
return conf2[kQuartoProfile] || conf1[kQuartoProfile] || "";
32
}
33
34
// process dotenv files -- note that we track the processing we have done
35
// previously and we back it out when we are called to re-process (as might
36
// occur on a re-render in quarto)
37
const dotenvVariablesSet: string[] = [];
38
39
// track previous variables defined (used to trigger event indicating a change)
40
let prevDotenvVariablesDefined: Record<string, string> | undefined;
41
42
export async function dotenvSetVariables(projectDir: string) {
43
// back out any previous variables set (and note firstRun)
44
dotenvVariablesSet.forEach(Deno.env.delete);
45
dotenvVariablesSet.splice(0, dotenvVariablesSet.length);
46
47
// form a list of dotenv files we might read, filter by existence, then
48
// reverse it (so we read and apply them in priority order)
49
const dotenvFiles = [
50
join(projectDir, kQuartoEnv),
51
...activeProfiles().reverse().map((profile) =>
52
join(projectDir, `_environment-${profile}`)
53
),
54
join(projectDir, kQuartoEnvLocal),
55
].filter(safeExistsSync).reverse();
56
57
// read the dot env files in turn, track variables defined for validation
58
const dotenvVariablesDefined: Record<string, string> = {};
59
for (const dotenvFile of dotenvFiles) {
60
const conf = await config({ envPath: dotenvFile });
61
for (const key in conf) {
62
// set into environment (and track that we did so for reversing out later)
63
if (Deno.env.get(key) === undefined) {
64
Deno.env.set(key, conf[key]);
65
dotenvVariablesSet.push(key);
66
}
67
// track all defined variables (for validation against example)
68
if (dotenvVariablesDefined[key] === undefined) {
69
dotenvVariablesDefined[key] = conf[key];
70
}
71
}
72
}
73
74
// validate against example if it exists
75
const dotenvRequired = join(projectDir, kQuartoEnvRequired);
76
if (existsSync(dotenvRequired)) {
77
const definedEnvTempPath = globalTempContext().createFile({
78
suffix: ".yml",
79
});
80
Deno.writeTextFileSync(
81
definedEnvTempPath,
82
stringify(dotenvVariablesDefined),
83
);
84
// FIXME the removal of the safe option
85
// in https://github.com/denoland/deno_std/pull/2616
86
// seems to indicate that we shouldn't be using examplePath here...
87
await config({
88
envPath: definedEnvTempPath,
89
});
90
}
91
92
// check to see if the environment changed and emit an event if it did
93
if (
94
prevDotenvVariablesDefined &&
95
!isEqual(dotenvVariablesDefined, prevDotenvVariablesDefined)
96
) {
97
fireDotenvChanged();
98
}
99
100
// set last defined
101
prevDotenvVariablesDefined = dotenvVariablesDefined;
102
103
// return the files we processed
104
return dotenvFiles;
105
}
106
107
// broadcast changes
108
const listeners = new Array<() => void>();
109
function fireDotenvChanged() {
110
listeners.forEach((listener) => listener());
111
}
112
export function onDotenvChanged(
113
listener: () => void,
114
) {
115
listeners.push(listener);
116
}
117
118