@karmaniverous/get-dotenv 4.6.0-0 → 5.0.0-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -23
- package/dist/cliHost.cjs +1089 -0
- package/dist/cliHost.d.cts +191 -0
- package/dist/cliHost.d.mts +191 -0
- package/dist/cliHost.d.ts +191 -0
- package/dist/cliHost.mjs +1085 -0
- package/dist/config.cjs +247 -0
- package/dist/config.d.cts +53 -0
- package/dist/config.d.mts +53 -0
- package/dist/config.d.ts +53 -0
- package/dist/config.mjs +242 -0
- package/dist/env-overlay.cjs +163 -0
- package/dist/env-overlay.d.cts +48 -0
- package/dist/env-overlay.d.mts +48 -0
- package/dist/env-overlay.d.ts +48 -0
- package/dist/env-overlay.mjs +161 -0
- package/dist/getdotenv.cli.mjs +2788 -734
- package/dist/index.cjs +902 -280
- package/dist/index.d.cts +122 -64
- package/dist/index.d.mts +122 -64
- package/dist/index.d.ts +122 -64
- package/dist/index.mjs +904 -283
- package/dist/plugins-aws.cjs +618 -0
- package/dist/plugins-aws.d.cts +176 -0
- package/dist/plugins-aws.d.mts +176 -0
- package/dist/plugins-aws.d.ts +176 -0
- package/dist/plugins-aws.mjs +616 -0
- package/dist/plugins-batch.cjs +569 -0
- package/dist/plugins-batch.d.cts +198 -0
- package/dist/plugins-batch.d.mts +198 -0
- package/dist/plugins-batch.d.ts +198 -0
- package/dist/plugins-batch.mjs +567 -0
- package/dist/plugins-init.cjs +282 -0
- package/dist/plugins-init.d.cts +180 -0
- package/dist/plugins-init.d.mts +180 -0
- package/dist/plugins-init.d.ts +180 -0
- package/dist/plugins-init.mjs +280 -0
- package/getdotenv.config.json +19 -0
- package/package.json +88 -17
- package/templates/cli/ts/index.ts +9 -0
- package/templates/cli/ts/plugins/hello.ts +17 -0
- package/templates/config/js/getdotenv.config.js +15 -0
- package/templates/config/json/local/getdotenv.config.local.json +7 -0
- package/templates/config/json/public/getdotenv.config.json +12 -0
- package/templates/config/public/getdotenv.config.json +13 -0
- package/templates/config/ts/getdotenv.config.ts +16 -0
- package/templates/config/yaml/local/getdotenv.config.local.yaml +7 -0
- package/templates/config/yaml/public/getdotenv.config.yaml +10 -0
package/dist/config.cjs
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('fs-extra');
|
|
4
|
+
var packageDirectory = require('package-directory');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var url = require('url');
|
|
7
|
+
var YAML = require('yaml');
|
|
8
|
+
var zod = require('zod');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Zod schemas for configuration files discovered by the new loader. *
|
|
12
|
+
* Notes:
|
|
13
|
+
* - RAW: all fields optional; shapes are stringly-friendly (paths may be string[] or string).
|
|
14
|
+
* - RESOLVED: normalized shapes (paths always string[]).
|
|
15
|
+
* - For this step (JSON/YAML only), any defined `dynamic` will be rejected by the loader.
|
|
16
|
+
*/
|
|
17
|
+
// String-only env value map
|
|
18
|
+
const stringMap = zod.z.record(zod.z.string(), zod.z.string());
|
|
19
|
+
const envStringMap = zod.z.record(zod.z.string(), stringMap);
|
|
20
|
+
// Allow string[] or single string for "paths" in RAW; normalize later.
|
|
21
|
+
const rawPathsSchema = zod.z.union([zod.z.array(zod.z.string()), zod.z.string()]).optional();
|
|
22
|
+
const getDotenvConfigSchemaRaw = zod.z.object({
|
|
23
|
+
dotenvToken: zod.z.string().optional(),
|
|
24
|
+
privateToken: zod.z.string().optional(),
|
|
25
|
+
paths: rawPathsSchema,
|
|
26
|
+
loadProcess: zod.z.boolean().optional(),
|
|
27
|
+
log: zod.z.boolean().optional(),
|
|
28
|
+
shell: zod.z.union([zod.z.string(), zod.z.boolean()]).optional(),
|
|
29
|
+
scripts: zod.z.record(zod.z.string(), zod.z.unknown()).optional(), // Scripts validation left wide; generator validates elsewhere
|
|
30
|
+
vars: stringMap.optional(), // public, global
|
|
31
|
+
envVars: envStringMap.optional(), // public, per-env
|
|
32
|
+
// Dynamic in config (JS/TS only). JSON/YAML loader will reject if set.
|
|
33
|
+
dynamic: zod.z.unknown().optional(),
|
|
34
|
+
// Per-plugin config bag; validated by plugins/host when used.
|
|
35
|
+
plugins: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
|
|
36
|
+
});
|
|
37
|
+
// Normalize paths to string[]
|
|
38
|
+
const normalizePaths = (p) => p === undefined ? undefined : Array.isArray(p) ? p : [p];
|
|
39
|
+
const getDotenvConfigSchemaResolved = getDotenvConfigSchemaRaw.transform((raw) => ({
|
|
40
|
+
...raw,
|
|
41
|
+
paths: normalizePaths(raw.paths),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
// Discovery candidates (first match wins per scope/privacy).
|
|
45
|
+
// Order preserves historical JSON/YAML precedence; JS/TS added afterwards.
|
|
46
|
+
const PUBLIC_FILENAMES = [
|
|
47
|
+
'getdotenv.config.json',
|
|
48
|
+
'getdotenv.config.yaml',
|
|
49
|
+
'getdotenv.config.yml',
|
|
50
|
+
'getdotenv.config.js',
|
|
51
|
+
'getdotenv.config.mjs',
|
|
52
|
+
'getdotenv.config.cjs',
|
|
53
|
+
'getdotenv.config.ts',
|
|
54
|
+
'getdotenv.config.mts',
|
|
55
|
+
'getdotenv.config.cts',
|
|
56
|
+
];
|
|
57
|
+
const LOCAL_FILENAMES = [
|
|
58
|
+
'getdotenv.config.local.json',
|
|
59
|
+
'getdotenv.config.local.yaml',
|
|
60
|
+
'getdotenv.config.local.yml',
|
|
61
|
+
'getdotenv.config.local.js',
|
|
62
|
+
'getdotenv.config.local.mjs',
|
|
63
|
+
'getdotenv.config.local.cjs',
|
|
64
|
+
'getdotenv.config.local.ts',
|
|
65
|
+
'getdotenv.config.local.mts',
|
|
66
|
+
'getdotenv.config.local.cts',
|
|
67
|
+
];
|
|
68
|
+
const isYaml = (p) => ['.yaml', '.yml'].includes(path.extname(p).toLowerCase());
|
|
69
|
+
const isJson = (p) => path.extname(p).toLowerCase() === '.json';
|
|
70
|
+
const isJsOrTs = (p) => ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'].includes(path.extname(p).toLowerCase());
|
|
71
|
+
// --- Internal JS/TS module loader helpers (default export) ---
|
|
72
|
+
const importDefault = async (fileUrl) => {
|
|
73
|
+
const mod = (await import(fileUrl));
|
|
74
|
+
return mod.default;
|
|
75
|
+
};
|
|
76
|
+
const cacheName = (absPath, suffix) => {
|
|
77
|
+
// sanitized filename with suffix; recompile on mtime changes not tracked here (simplified)
|
|
78
|
+
const base = path.basename(absPath).replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
79
|
+
return `${base}.${suffix}.mjs`;
|
|
80
|
+
};
|
|
81
|
+
const ensureDir = async (dir) => {
|
|
82
|
+
await fs.ensureDir(dir);
|
|
83
|
+
return dir;
|
|
84
|
+
};
|
|
85
|
+
const loadJsTsDefault = async (absPath) => {
|
|
86
|
+
const fileUrl = url.pathToFileURL(absPath).toString();
|
|
87
|
+
const ext = path.extname(absPath).toLowerCase();
|
|
88
|
+
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
|
|
89
|
+
return importDefault(fileUrl);
|
|
90
|
+
}
|
|
91
|
+
// Try direct import first in case a TS loader is active.
|
|
92
|
+
try {
|
|
93
|
+
const val = await importDefault(fileUrl);
|
|
94
|
+
if (val)
|
|
95
|
+
return val;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
/* fallthrough */
|
|
99
|
+
}
|
|
100
|
+
// esbuild bundle to a temp ESM file
|
|
101
|
+
try {
|
|
102
|
+
const esbuild = (await import('esbuild'));
|
|
103
|
+
const outDir = await ensureDir(path.resolve('.tsbuild', 'getdotenv-config'));
|
|
104
|
+
const outfile = path.join(outDir, cacheName(absPath, 'bundle'));
|
|
105
|
+
await esbuild.build({
|
|
106
|
+
entryPoints: [absPath],
|
|
107
|
+
bundle: true,
|
|
108
|
+
platform: 'node',
|
|
109
|
+
format: 'esm',
|
|
110
|
+
target: 'node20',
|
|
111
|
+
outfile,
|
|
112
|
+
sourcemap: false,
|
|
113
|
+
logLevel: 'silent',
|
|
114
|
+
});
|
|
115
|
+
return await importDefault(url.pathToFileURL(outfile).toString());
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
/* fallthrough to TS transpile */
|
|
119
|
+
}
|
|
120
|
+
// typescript.transpileModule simple transpile (single-file)
|
|
121
|
+
try {
|
|
122
|
+
const ts = (await import('typescript'));
|
|
123
|
+
const src = await fs.readFile(absPath, 'utf-8');
|
|
124
|
+
const out = ts.transpileModule(src, {
|
|
125
|
+
compilerOptions: {
|
|
126
|
+
module: 'ESNext',
|
|
127
|
+
target: 'ES2022',
|
|
128
|
+
moduleResolution: 'NodeNext',
|
|
129
|
+
},
|
|
130
|
+
}).outputText;
|
|
131
|
+
const outDir = await ensureDir(path.resolve('.tsbuild', 'getdotenv-config'));
|
|
132
|
+
const outfile = path.join(outDir, cacheName(absPath, 'ts'));
|
|
133
|
+
await fs.writeFile(outfile, out, 'utf-8');
|
|
134
|
+
return await importDefault(url.pathToFileURL(outfile).toString());
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
throw new Error(`Unable to load JS/TS config: ${absPath}. Install 'esbuild' for robust bundling or ensure a TS loader.`);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Discover JSON/YAML config files in the packaged root and project root.
|
|
142
|
+
* Order: packaged public → project public → project local. */
|
|
143
|
+
const discoverConfigFiles = async (importMetaUrl) => {
|
|
144
|
+
const files = [];
|
|
145
|
+
// Packaged root via importMetaUrl (optional)
|
|
146
|
+
if (importMetaUrl) {
|
|
147
|
+
const fromUrl = url.fileURLToPath(importMetaUrl);
|
|
148
|
+
const packagedRoot = await packageDirectory.packageDirectory({ cwd: fromUrl });
|
|
149
|
+
if (packagedRoot) {
|
|
150
|
+
for (const name of PUBLIC_FILENAMES) {
|
|
151
|
+
const p = path.join(packagedRoot, name);
|
|
152
|
+
if (await fs.pathExists(p)) {
|
|
153
|
+
files.push({ path: p, privacy: 'public', scope: 'packaged' });
|
|
154
|
+
break; // only one public file expected per scope
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// By policy, packaged .local is not expected; skip even if present.
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Project root (from current working directory)
|
|
161
|
+
const projectRoot = await packageDirectory.packageDirectory();
|
|
162
|
+
if (projectRoot) {
|
|
163
|
+
for (const name of PUBLIC_FILENAMES) {
|
|
164
|
+
const p = path.join(projectRoot, name);
|
|
165
|
+
if (await fs.pathExists(p)) {
|
|
166
|
+
files.push({ path: p, privacy: 'public', scope: 'project' });
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const name of LOCAL_FILENAMES) {
|
|
171
|
+
const p = path.join(projectRoot, name);
|
|
172
|
+
if (await fs.pathExists(p)) {
|
|
173
|
+
files.push({ path: p, privacy: 'local', scope: 'project' });
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return files;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Load a single config file (JSON/YAML). JS/TS is not supported in this step.
|
|
182
|
+
* Validates with Zod RAW schema, then normalizes to RESOLVED.
|
|
183
|
+
*
|
|
184
|
+
* For JSON/YAML: if a "dynamic" property is present, throws with guidance.
|
|
185
|
+
* For JS/TS: default export is loaded; "dynamic" is allowed.
|
|
186
|
+
*/
|
|
187
|
+
const loadConfigFile = async (filePath) => {
|
|
188
|
+
let raw = {};
|
|
189
|
+
try {
|
|
190
|
+
const abs = path.resolve(filePath);
|
|
191
|
+
if (isJsOrTs(abs)) {
|
|
192
|
+
// JS/TS support: load default export via robust pipeline.
|
|
193
|
+
const mod = await loadJsTsDefault(abs);
|
|
194
|
+
raw = mod ?? {};
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
const txt = await fs.readFile(abs, 'utf-8');
|
|
198
|
+
raw = isJson(abs) ? JSON.parse(txt) : isYaml(abs) ? YAML.parse(txt) : {};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
throw new Error(`Failed to read/parse config: ${filePath}. ${String(err)}`);
|
|
203
|
+
}
|
|
204
|
+
// Validate RAW
|
|
205
|
+
const parsed = getDotenvConfigSchemaRaw.safeParse(raw);
|
|
206
|
+
if (!parsed.success) {
|
|
207
|
+
const msgs = parsed.error.issues
|
|
208
|
+
.map((i) => `${i.path.join('.')}: ${i.message}`)
|
|
209
|
+
.join('\n');
|
|
210
|
+
throw new Error(`Invalid config ${filePath}:\n${msgs}`);
|
|
211
|
+
}
|
|
212
|
+
// Disallow dynamic in JSON/YAML; allow in JS/TS
|
|
213
|
+
if (!isJsOrTs(filePath) && parsed.data.dynamic !== undefined) {
|
|
214
|
+
throw new Error(`Config ${filePath} specifies "dynamic"; JSON/YAML configs cannot include dynamic in this step. Use JS/TS config.`);
|
|
215
|
+
}
|
|
216
|
+
return getDotenvConfigSchemaResolved.parse(parsed.data);
|
|
217
|
+
};
|
|
218
|
+
/**
|
|
219
|
+
* Discover and load configs into resolved shapes, ordered by scope/privacy.
|
|
220
|
+
* JSON/YAML/JS/TS supported; first match per scope/privacy applies.
|
|
221
|
+
*/
|
|
222
|
+
const resolveGetDotenvConfigSources = async (importMetaUrl) => {
|
|
223
|
+
const discovered = await discoverConfigFiles(importMetaUrl);
|
|
224
|
+
const result = {};
|
|
225
|
+
for (const f of discovered) {
|
|
226
|
+
const cfg = await loadConfigFile(f.path);
|
|
227
|
+
if (f.scope === 'packaged') {
|
|
228
|
+
// packaged public only
|
|
229
|
+
result.packaged = cfg;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
result.project ??= {};
|
|
233
|
+
if (f.privacy === 'public')
|
|
234
|
+
result.project.public = cfg;
|
|
235
|
+
else
|
|
236
|
+
result.project.local = cfg;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
};
|
|
241
|
+
// Utility primarily for tests: create a file: URL string from a path
|
|
242
|
+
const toFileUrl = (p) => url.pathToFileURL(path.resolve(p)).toString();
|
|
243
|
+
|
|
244
|
+
exports.discoverConfigFiles = discoverConfigFiles;
|
|
245
|
+
exports.loadConfigFile = loadConfigFile;
|
|
246
|
+
exports.resolveGetDotenvConfigSources = resolveGetDotenvConfigSources;
|
|
247
|
+
exports.toFileUrl = toFileUrl;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
type Scripts = Record<string, string | {
|
|
2
|
+
cmd: string;
|
|
3
|
+
shell?: string | boolean;
|
|
4
|
+
}>;
|
|
5
|
+
|
|
6
|
+
type GetDotenvConfigResolved = {
|
|
7
|
+
dotenvToken?: string;
|
|
8
|
+
privateToken?: string;
|
|
9
|
+
paths?: string[];
|
|
10
|
+
loadProcess?: boolean;
|
|
11
|
+
log?: boolean;
|
|
12
|
+
shell?: string | boolean;
|
|
13
|
+
scripts?: Scripts;
|
|
14
|
+
vars?: Record<string, string>;
|
|
15
|
+
envVars?: Record<string, Record<string, string>>;
|
|
16
|
+
dynamic?: unknown;
|
|
17
|
+
plugins?: Record<string, unknown>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type ConfigPrivacy = 'public' | 'local';
|
|
21
|
+
type ConfigScope = 'packaged' | 'project';
|
|
22
|
+
type ConfigFile = {
|
|
23
|
+
path: string;
|
|
24
|
+
privacy: ConfigPrivacy;
|
|
25
|
+
scope: ConfigScope;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Discover JSON/YAML config files in the packaged root and project root.
|
|
29
|
+
* Order: packaged public → project public → project local. */
|
|
30
|
+
declare const discoverConfigFiles: (importMetaUrl?: string) => Promise<ConfigFile[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Load a single config file (JSON/YAML). JS/TS is not supported in this step.
|
|
33
|
+
* Validates with Zod RAW schema, then normalizes to RESOLVED.
|
|
34
|
+
*
|
|
35
|
+
* For JSON/YAML: if a "dynamic" property is present, throws with guidance.
|
|
36
|
+
* For JS/TS: default export is loaded; "dynamic" is allowed.
|
|
37
|
+
*/
|
|
38
|
+
declare const loadConfigFile: (filePath: string) => Promise<GetDotenvConfigResolved>;
|
|
39
|
+
type ResolvedConfigSources = {
|
|
40
|
+
packaged?: GetDotenvConfigResolved;
|
|
41
|
+
project?: {
|
|
42
|
+
public?: GetDotenvConfigResolved;
|
|
43
|
+
local?: GetDotenvConfigResolved;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Discover and load configs into resolved shapes, ordered by scope/privacy.
|
|
48
|
+
* JSON/YAML/JS/TS supported; first match per scope/privacy applies.
|
|
49
|
+
*/
|
|
50
|
+
declare const resolveGetDotenvConfigSources: (importMetaUrl?: string) => Promise<ResolvedConfigSources>;
|
|
51
|
+
declare const toFileUrl: (p: string) => string;
|
|
52
|
+
|
|
53
|
+
export { discoverConfigFiles, loadConfigFile, resolveGetDotenvConfigSources, toFileUrl };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
type Scripts = Record<string, string | {
|
|
2
|
+
cmd: string;
|
|
3
|
+
shell?: string | boolean;
|
|
4
|
+
}>;
|
|
5
|
+
|
|
6
|
+
type GetDotenvConfigResolved = {
|
|
7
|
+
dotenvToken?: string;
|
|
8
|
+
privateToken?: string;
|
|
9
|
+
paths?: string[];
|
|
10
|
+
loadProcess?: boolean;
|
|
11
|
+
log?: boolean;
|
|
12
|
+
shell?: string | boolean;
|
|
13
|
+
scripts?: Scripts;
|
|
14
|
+
vars?: Record<string, string>;
|
|
15
|
+
envVars?: Record<string, Record<string, string>>;
|
|
16
|
+
dynamic?: unknown;
|
|
17
|
+
plugins?: Record<string, unknown>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type ConfigPrivacy = 'public' | 'local';
|
|
21
|
+
type ConfigScope = 'packaged' | 'project';
|
|
22
|
+
type ConfigFile = {
|
|
23
|
+
path: string;
|
|
24
|
+
privacy: ConfigPrivacy;
|
|
25
|
+
scope: ConfigScope;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Discover JSON/YAML config files in the packaged root and project root.
|
|
29
|
+
* Order: packaged public → project public → project local. */
|
|
30
|
+
declare const discoverConfigFiles: (importMetaUrl?: string) => Promise<ConfigFile[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Load a single config file (JSON/YAML). JS/TS is not supported in this step.
|
|
33
|
+
* Validates with Zod RAW schema, then normalizes to RESOLVED.
|
|
34
|
+
*
|
|
35
|
+
* For JSON/YAML: if a "dynamic" property is present, throws with guidance.
|
|
36
|
+
* For JS/TS: default export is loaded; "dynamic" is allowed.
|
|
37
|
+
*/
|
|
38
|
+
declare const loadConfigFile: (filePath: string) => Promise<GetDotenvConfigResolved>;
|
|
39
|
+
type ResolvedConfigSources = {
|
|
40
|
+
packaged?: GetDotenvConfigResolved;
|
|
41
|
+
project?: {
|
|
42
|
+
public?: GetDotenvConfigResolved;
|
|
43
|
+
local?: GetDotenvConfigResolved;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Discover and load configs into resolved shapes, ordered by scope/privacy.
|
|
48
|
+
* JSON/YAML/JS/TS supported; first match per scope/privacy applies.
|
|
49
|
+
*/
|
|
50
|
+
declare const resolveGetDotenvConfigSources: (importMetaUrl?: string) => Promise<ResolvedConfigSources>;
|
|
51
|
+
declare const toFileUrl: (p: string) => string;
|
|
52
|
+
|
|
53
|
+
export { discoverConfigFiles, loadConfigFile, resolveGetDotenvConfigSources, toFileUrl };
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
type Scripts = Record<string, string | {
|
|
2
|
+
cmd: string;
|
|
3
|
+
shell?: string | boolean;
|
|
4
|
+
}>;
|
|
5
|
+
|
|
6
|
+
type GetDotenvConfigResolved = {
|
|
7
|
+
dotenvToken?: string;
|
|
8
|
+
privateToken?: string;
|
|
9
|
+
paths?: string[];
|
|
10
|
+
loadProcess?: boolean;
|
|
11
|
+
log?: boolean;
|
|
12
|
+
shell?: string | boolean;
|
|
13
|
+
scripts?: Scripts;
|
|
14
|
+
vars?: Record<string, string>;
|
|
15
|
+
envVars?: Record<string, Record<string, string>>;
|
|
16
|
+
dynamic?: unknown;
|
|
17
|
+
plugins?: Record<string, unknown>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type ConfigPrivacy = 'public' | 'local';
|
|
21
|
+
type ConfigScope = 'packaged' | 'project';
|
|
22
|
+
type ConfigFile = {
|
|
23
|
+
path: string;
|
|
24
|
+
privacy: ConfigPrivacy;
|
|
25
|
+
scope: ConfigScope;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Discover JSON/YAML config files in the packaged root and project root.
|
|
29
|
+
* Order: packaged public → project public → project local. */
|
|
30
|
+
declare const discoverConfigFiles: (importMetaUrl?: string) => Promise<ConfigFile[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Load a single config file (JSON/YAML). JS/TS is not supported in this step.
|
|
33
|
+
* Validates with Zod RAW schema, then normalizes to RESOLVED.
|
|
34
|
+
*
|
|
35
|
+
* For JSON/YAML: if a "dynamic" property is present, throws with guidance.
|
|
36
|
+
* For JS/TS: default export is loaded; "dynamic" is allowed.
|
|
37
|
+
*/
|
|
38
|
+
declare const loadConfigFile: (filePath: string) => Promise<GetDotenvConfigResolved>;
|
|
39
|
+
type ResolvedConfigSources = {
|
|
40
|
+
packaged?: GetDotenvConfigResolved;
|
|
41
|
+
project?: {
|
|
42
|
+
public?: GetDotenvConfigResolved;
|
|
43
|
+
local?: GetDotenvConfigResolved;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Discover and load configs into resolved shapes, ordered by scope/privacy.
|
|
48
|
+
* JSON/YAML/JS/TS supported; first match per scope/privacy applies.
|
|
49
|
+
*/
|
|
50
|
+
declare const resolveGetDotenvConfigSources: (importMetaUrl?: string) => Promise<ResolvedConfigSources>;
|
|
51
|
+
declare const toFileUrl: (p: string) => string;
|
|
52
|
+
|
|
53
|
+
export { discoverConfigFiles, loadConfigFile, resolveGetDotenvConfigSources, toFileUrl };
|
package/dist/config.mjs
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import { packageDirectory } from 'package-directory';
|
|
3
|
+
import path, { join, extname } from 'path';
|
|
4
|
+
import { pathToFileURL, fileURLToPath } from 'url';
|
|
5
|
+
import YAML from 'yaml';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Zod schemas for configuration files discovered by the new loader. *
|
|
10
|
+
* Notes:
|
|
11
|
+
* - RAW: all fields optional; shapes are stringly-friendly (paths may be string[] or string).
|
|
12
|
+
* - RESOLVED: normalized shapes (paths always string[]).
|
|
13
|
+
* - For this step (JSON/YAML only), any defined `dynamic` will be rejected by the loader.
|
|
14
|
+
*/
|
|
15
|
+
// String-only env value map
|
|
16
|
+
const stringMap = z.record(z.string(), z.string());
|
|
17
|
+
const envStringMap = z.record(z.string(), stringMap);
|
|
18
|
+
// Allow string[] or single string for "paths" in RAW; normalize later.
|
|
19
|
+
const rawPathsSchema = z.union([z.array(z.string()), z.string()]).optional();
|
|
20
|
+
const getDotenvConfigSchemaRaw = z.object({
|
|
21
|
+
dotenvToken: z.string().optional(),
|
|
22
|
+
privateToken: z.string().optional(),
|
|
23
|
+
paths: rawPathsSchema,
|
|
24
|
+
loadProcess: z.boolean().optional(),
|
|
25
|
+
log: z.boolean().optional(),
|
|
26
|
+
shell: z.union([z.string(), z.boolean()]).optional(),
|
|
27
|
+
scripts: z.record(z.string(), z.unknown()).optional(), // Scripts validation left wide; generator validates elsewhere
|
|
28
|
+
vars: stringMap.optional(), // public, global
|
|
29
|
+
envVars: envStringMap.optional(), // public, per-env
|
|
30
|
+
// Dynamic in config (JS/TS only). JSON/YAML loader will reject if set.
|
|
31
|
+
dynamic: z.unknown().optional(),
|
|
32
|
+
// Per-plugin config bag; validated by plugins/host when used.
|
|
33
|
+
plugins: z.record(z.string(), z.unknown()).optional(),
|
|
34
|
+
});
|
|
35
|
+
// Normalize paths to string[]
|
|
36
|
+
const normalizePaths = (p) => p === undefined ? undefined : Array.isArray(p) ? p : [p];
|
|
37
|
+
const getDotenvConfigSchemaResolved = getDotenvConfigSchemaRaw.transform((raw) => ({
|
|
38
|
+
...raw,
|
|
39
|
+
paths: normalizePaths(raw.paths),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
// Discovery candidates (first match wins per scope/privacy).
|
|
43
|
+
// Order preserves historical JSON/YAML precedence; JS/TS added afterwards.
|
|
44
|
+
const PUBLIC_FILENAMES = [
|
|
45
|
+
'getdotenv.config.json',
|
|
46
|
+
'getdotenv.config.yaml',
|
|
47
|
+
'getdotenv.config.yml',
|
|
48
|
+
'getdotenv.config.js',
|
|
49
|
+
'getdotenv.config.mjs',
|
|
50
|
+
'getdotenv.config.cjs',
|
|
51
|
+
'getdotenv.config.ts',
|
|
52
|
+
'getdotenv.config.mts',
|
|
53
|
+
'getdotenv.config.cts',
|
|
54
|
+
];
|
|
55
|
+
const LOCAL_FILENAMES = [
|
|
56
|
+
'getdotenv.config.local.json',
|
|
57
|
+
'getdotenv.config.local.yaml',
|
|
58
|
+
'getdotenv.config.local.yml',
|
|
59
|
+
'getdotenv.config.local.js',
|
|
60
|
+
'getdotenv.config.local.mjs',
|
|
61
|
+
'getdotenv.config.local.cjs',
|
|
62
|
+
'getdotenv.config.local.ts',
|
|
63
|
+
'getdotenv.config.local.mts',
|
|
64
|
+
'getdotenv.config.local.cts',
|
|
65
|
+
];
|
|
66
|
+
const isYaml = (p) => ['.yaml', '.yml'].includes(extname(p).toLowerCase());
|
|
67
|
+
const isJson = (p) => extname(p).toLowerCase() === '.json';
|
|
68
|
+
const isJsOrTs = (p) => ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'].includes(extname(p).toLowerCase());
|
|
69
|
+
// --- Internal JS/TS module loader helpers (default export) ---
|
|
70
|
+
const importDefault = async (fileUrl) => {
|
|
71
|
+
const mod = (await import(fileUrl));
|
|
72
|
+
return mod.default;
|
|
73
|
+
};
|
|
74
|
+
const cacheName = (absPath, suffix) => {
|
|
75
|
+
// sanitized filename with suffix; recompile on mtime changes not tracked here (simplified)
|
|
76
|
+
const base = path.basename(absPath).replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
77
|
+
return `${base}.${suffix}.mjs`;
|
|
78
|
+
};
|
|
79
|
+
const ensureDir = async (dir) => {
|
|
80
|
+
await fs.ensureDir(dir);
|
|
81
|
+
return dir;
|
|
82
|
+
};
|
|
83
|
+
const loadJsTsDefault = async (absPath) => {
|
|
84
|
+
const fileUrl = pathToFileURL(absPath).toString();
|
|
85
|
+
const ext = extname(absPath).toLowerCase();
|
|
86
|
+
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
|
|
87
|
+
return importDefault(fileUrl);
|
|
88
|
+
}
|
|
89
|
+
// Try direct import first in case a TS loader is active.
|
|
90
|
+
try {
|
|
91
|
+
const val = await importDefault(fileUrl);
|
|
92
|
+
if (val)
|
|
93
|
+
return val;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
/* fallthrough */
|
|
97
|
+
}
|
|
98
|
+
// esbuild bundle to a temp ESM file
|
|
99
|
+
try {
|
|
100
|
+
const esbuild = (await import('esbuild'));
|
|
101
|
+
const outDir = await ensureDir(path.resolve('.tsbuild', 'getdotenv-config'));
|
|
102
|
+
const outfile = path.join(outDir, cacheName(absPath, 'bundle'));
|
|
103
|
+
await esbuild.build({
|
|
104
|
+
entryPoints: [absPath],
|
|
105
|
+
bundle: true,
|
|
106
|
+
platform: 'node',
|
|
107
|
+
format: 'esm',
|
|
108
|
+
target: 'node20',
|
|
109
|
+
outfile,
|
|
110
|
+
sourcemap: false,
|
|
111
|
+
logLevel: 'silent',
|
|
112
|
+
});
|
|
113
|
+
return await importDefault(pathToFileURL(outfile).toString());
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
/* fallthrough to TS transpile */
|
|
117
|
+
}
|
|
118
|
+
// typescript.transpileModule simple transpile (single-file)
|
|
119
|
+
try {
|
|
120
|
+
const ts = (await import('typescript'));
|
|
121
|
+
const src = await fs.readFile(absPath, 'utf-8');
|
|
122
|
+
const out = ts.transpileModule(src, {
|
|
123
|
+
compilerOptions: {
|
|
124
|
+
module: 'ESNext',
|
|
125
|
+
target: 'ES2022',
|
|
126
|
+
moduleResolution: 'NodeNext',
|
|
127
|
+
},
|
|
128
|
+
}).outputText;
|
|
129
|
+
const outDir = await ensureDir(path.resolve('.tsbuild', 'getdotenv-config'));
|
|
130
|
+
const outfile = path.join(outDir, cacheName(absPath, 'ts'));
|
|
131
|
+
await fs.writeFile(outfile, out, 'utf-8');
|
|
132
|
+
return await importDefault(pathToFileURL(outfile).toString());
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
throw new Error(`Unable to load JS/TS config: ${absPath}. Install 'esbuild' for robust bundling or ensure a TS loader.`);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Discover JSON/YAML config files in the packaged root and project root.
|
|
140
|
+
* Order: packaged public → project public → project local. */
|
|
141
|
+
const discoverConfigFiles = async (importMetaUrl) => {
|
|
142
|
+
const files = [];
|
|
143
|
+
// Packaged root via importMetaUrl (optional)
|
|
144
|
+
if (importMetaUrl) {
|
|
145
|
+
const fromUrl = fileURLToPath(importMetaUrl);
|
|
146
|
+
const packagedRoot = await packageDirectory({ cwd: fromUrl });
|
|
147
|
+
if (packagedRoot) {
|
|
148
|
+
for (const name of PUBLIC_FILENAMES) {
|
|
149
|
+
const p = join(packagedRoot, name);
|
|
150
|
+
if (await fs.pathExists(p)) {
|
|
151
|
+
files.push({ path: p, privacy: 'public', scope: 'packaged' });
|
|
152
|
+
break; // only one public file expected per scope
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// By policy, packaged .local is not expected; skip even if present.
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Project root (from current working directory)
|
|
159
|
+
const projectRoot = await packageDirectory();
|
|
160
|
+
if (projectRoot) {
|
|
161
|
+
for (const name of PUBLIC_FILENAMES) {
|
|
162
|
+
const p = join(projectRoot, name);
|
|
163
|
+
if (await fs.pathExists(p)) {
|
|
164
|
+
files.push({ path: p, privacy: 'public', scope: 'project' });
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for (const name of LOCAL_FILENAMES) {
|
|
169
|
+
const p = join(projectRoot, name);
|
|
170
|
+
if (await fs.pathExists(p)) {
|
|
171
|
+
files.push({ path: p, privacy: 'local', scope: 'project' });
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return files;
|
|
177
|
+
};
|
|
178
|
+
/**
|
|
179
|
+
* Load a single config file (JSON/YAML). JS/TS is not supported in this step.
|
|
180
|
+
* Validates with Zod RAW schema, then normalizes to RESOLVED.
|
|
181
|
+
*
|
|
182
|
+
* For JSON/YAML: if a "dynamic" property is present, throws with guidance.
|
|
183
|
+
* For JS/TS: default export is loaded; "dynamic" is allowed.
|
|
184
|
+
*/
|
|
185
|
+
const loadConfigFile = async (filePath) => {
|
|
186
|
+
let raw = {};
|
|
187
|
+
try {
|
|
188
|
+
const abs = path.resolve(filePath);
|
|
189
|
+
if (isJsOrTs(abs)) {
|
|
190
|
+
// JS/TS support: load default export via robust pipeline.
|
|
191
|
+
const mod = await loadJsTsDefault(abs);
|
|
192
|
+
raw = mod ?? {};
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const txt = await fs.readFile(abs, 'utf-8');
|
|
196
|
+
raw = isJson(abs) ? JSON.parse(txt) : isYaml(abs) ? YAML.parse(txt) : {};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
throw new Error(`Failed to read/parse config: ${filePath}. ${String(err)}`);
|
|
201
|
+
}
|
|
202
|
+
// Validate RAW
|
|
203
|
+
const parsed = getDotenvConfigSchemaRaw.safeParse(raw);
|
|
204
|
+
if (!parsed.success) {
|
|
205
|
+
const msgs = parsed.error.issues
|
|
206
|
+
.map((i) => `${i.path.join('.')}: ${i.message}`)
|
|
207
|
+
.join('\n');
|
|
208
|
+
throw new Error(`Invalid config ${filePath}:\n${msgs}`);
|
|
209
|
+
}
|
|
210
|
+
// Disallow dynamic in JSON/YAML; allow in JS/TS
|
|
211
|
+
if (!isJsOrTs(filePath) && parsed.data.dynamic !== undefined) {
|
|
212
|
+
throw new Error(`Config ${filePath} specifies "dynamic"; JSON/YAML configs cannot include dynamic in this step. Use JS/TS config.`);
|
|
213
|
+
}
|
|
214
|
+
return getDotenvConfigSchemaResolved.parse(parsed.data);
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* Discover and load configs into resolved shapes, ordered by scope/privacy.
|
|
218
|
+
* JSON/YAML/JS/TS supported; first match per scope/privacy applies.
|
|
219
|
+
*/
|
|
220
|
+
const resolveGetDotenvConfigSources = async (importMetaUrl) => {
|
|
221
|
+
const discovered = await discoverConfigFiles(importMetaUrl);
|
|
222
|
+
const result = {};
|
|
223
|
+
for (const f of discovered) {
|
|
224
|
+
const cfg = await loadConfigFile(f.path);
|
|
225
|
+
if (f.scope === 'packaged') {
|
|
226
|
+
// packaged public only
|
|
227
|
+
result.packaged = cfg;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
result.project ??= {};
|
|
231
|
+
if (f.privacy === 'public')
|
|
232
|
+
result.project.public = cfg;
|
|
233
|
+
else
|
|
234
|
+
result.project.local = cfg;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
};
|
|
239
|
+
// Utility primarily for tests: create a file: URL string from a path
|
|
240
|
+
const toFileUrl = (p) => pathToFileURL(path.resolve(p)).toString();
|
|
241
|
+
|
|
242
|
+
export { discoverConfigFiles, loadConfigFile, resolveGetDotenvConfigSources, toFileUrl };
|