configenvy 0.1.5 → 0.1.7
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/dist/index.d.ts +10 -2
- package/dist/index.js +57 -6
- package/package.json +2 -2
- package/src/index.ts +73 -5
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ type InitOptions = {
|
|
|
13
13
|
dryRun?: boolean;
|
|
14
14
|
envExample?: boolean;
|
|
15
15
|
force?: boolean;
|
|
16
|
+
preset?: string;
|
|
16
17
|
};
|
|
17
18
|
type CliDependencies = {
|
|
18
19
|
buildMarkdownTable: typeof buildMarkdownTable;
|
|
@@ -28,16 +29,23 @@ type CliDependencies = {
|
|
|
28
29
|
writeFile: typeof writeFile;
|
|
29
30
|
};
|
|
30
31
|
declare function createProgram(dependencies?: CliDependencies): Command;
|
|
32
|
+
type StarterConfig = {
|
|
33
|
+
docs: string[];
|
|
34
|
+
ignore: string[];
|
|
35
|
+
optional: string[];
|
|
36
|
+
required: string[];
|
|
37
|
+
};
|
|
31
38
|
type InitFile = {
|
|
32
39
|
content: string;
|
|
33
40
|
path: string;
|
|
34
41
|
};
|
|
42
|
+
declare const cliVersion: string;
|
|
35
43
|
declare const tableBlockStart = "<!-- configenvy:start -->";
|
|
36
44
|
declare const tableBlockEnd = "<!-- configenvy:end -->";
|
|
37
45
|
declare function runCli(argv: string[], dependencies?: CliDependencies): Promise<void>;
|
|
38
46
|
declare function runDoctor(projectPath: string, options: DoctorOptions, dependencies?: CliDependencies): Promise<void>;
|
|
39
47
|
declare function runInit(projectPath: string, options?: InitOptions, dependencies?: CliDependencies): Promise<void>;
|
|
40
|
-
declare function buildInitFiles(rootDir: string, result: ScanResult, includeEnvExample: boolean, resolvePath?: typeof resolve, existingEnvExample?: string): InitFile[];
|
|
48
|
+
declare function buildInitFiles(rootDir: string, result: ScanResult, includeEnvExample: boolean, resolvePath?: typeof resolve, existingEnvExample?: string, preset?: Partial<StarterConfig>): InitFile[];
|
|
41
49
|
declare function runTableUpdate(rootDir: string, table: string, options: {
|
|
42
50
|
dryRun?: boolean;
|
|
43
51
|
force?: boolean;
|
|
@@ -48,4 +56,4 @@ declare function resolveOutputPath(projectPath: string, outputPath: string, reso
|
|
|
48
56
|
declare function printHumanReport(diagnostics: Diagnostic[], log?: (...values: unknown[]) => void): void;
|
|
49
57
|
declare function printGitHubAnnotations(diagnostics: Diagnostic[], log?: (...values: unknown[]) => void): void;
|
|
50
58
|
|
|
51
|
-
export { type CliDependencies, buildInitFiles, createProgram, printGitHubAnnotations, printHumanReport, resolveOutputPath, runCli, runDoctor, runInit, runTableUpdate, tableBlockEnd, tableBlockStart, updateMarkdownTableBlock };
|
|
59
|
+
export { type CliDependencies, buildInitFiles, cliVersion, createProgram, printGitHubAnnotations, printHumanReport, resolveOutputPath, runCli, runDoctor, runInit, runTableUpdate, tableBlockEnd, tableBlockStart, updateMarkdownTableBlock };
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { readFile, writeFile } from "fs/promises";
|
|
5
|
+
import { createRequire } from "module";
|
|
5
6
|
import { isAbsolute, resolve } from "path";
|
|
6
7
|
import { pathToFileURL } from "url";
|
|
7
8
|
import { Command } from "commander";
|
|
@@ -27,12 +28,12 @@ var defaultDependencies = {
|
|
|
27
28
|
};
|
|
28
29
|
function createProgram(dependencies = defaultDependencies) {
|
|
29
30
|
const program = new Command();
|
|
30
|
-
program.name("configenvy").description("Find missing, unused, undocumented, and risky environment variables.").version(
|
|
31
|
+
program.name("configenvy").description("Find missing, unused, undocumented, and risky environment variables.").version(cliVersion);
|
|
31
32
|
program.command("doctor").argument("[path]", "project directory", ".").option("--format <format>", "output format: text, json, or sarif", "text").option("--strict", "treat documentation warnings as errors").action(async (projectPath, options) => {
|
|
32
33
|
await runDoctor(projectPath, options, dependencies);
|
|
33
34
|
});
|
|
34
|
-
program.command("check").argument("[path]", "project directory", ".").option("--ci", "fail on warnings and errors").option("--format <format>", "output format: text, json, or sarif", "text").action(async (projectPath, options) => {
|
|
35
|
-
await runDoctor(projectPath, { ...options, strict: Boolean(options.ci), ci: Boolean(options.ci) }, dependencies);
|
|
35
|
+
program.command("check").argument("[path]", "project directory", ".").option("--ci", "fail on warnings and errors").option("--format <format>", "output format: text, json, or sarif", "text").option("--strict", "treat documentation warnings as errors").action(async (projectPath, options) => {
|
|
36
|
+
await runDoctor(projectPath, { ...options, strict: Boolean(options.strict || options.ci), ci: Boolean(options.ci) }, dependencies);
|
|
36
37
|
});
|
|
37
38
|
program.command("table").argument("[path]", "project directory", ".").option("--out <file>", "write markdown table to a file").option("--update <file>", "replace a marked configenvy table block in a markdown file").option("--force", "append a configenvy table block when --update target has no marked block").option("--dry-run", "print the updated markdown instead of writing it").action(async (projectPath, options) => {
|
|
38
39
|
const rootDir = dependencies.resolvePath(projectPath);
|
|
@@ -47,7 +48,7 @@ function createProgram(dependencies = defaultDependencies) {
|
|
|
47
48
|
dependencies.log(table);
|
|
48
49
|
}
|
|
49
50
|
});
|
|
50
|
-
program.command("init").argument("[path]", "project directory", ".").description("create starter configenvy files").option("--dry-run", "print planned files instead of writing them").option("--env-example", "also create a .env.example draft from detected variables").option("--force", "overwrite generated files if they already exist").action(async (projectPath, options) => {
|
|
51
|
+
program.command("init").argument("[path]", "project directory", ".").description("create starter configenvy files").option("--dry-run", "print planned files instead of writing them").option("--env-example", "also create a .env.example draft from detected variables").option("--force", "overwrite generated files if they already exist").option("--preset <name>", `apply a preset: ${availablePresetList}`).action(async (projectPath, options) => {
|
|
51
52
|
await runInit(projectPath, options, dependencies);
|
|
52
53
|
});
|
|
53
54
|
program.command("explain").argument("<variable>", "environment variable name").argument("[path]", "project directory", ".").action(async (variable, projectPath) => {
|
|
@@ -62,6 +63,38 @@ var starterConfig = {
|
|
|
62
63
|
ignore: ["NODE_ENV"],
|
|
63
64
|
docs: ["README.md", "docs"]
|
|
64
65
|
};
|
|
66
|
+
var presetConfigs = {
|
|
67
|
+
astro: {
|
|
68
|
+
optional: ["PUBLIC_SITE_URL"],
|
|
69
|
+
ignore: ["BASE_URL", "DEV", "MODE", "PROD", "SSR"]
|
|
70
|
+
},
|
|
71
|
+
docker: {
|
|
72
|
+
optional: ["COMPOSE_PROJECT_NAME"],
|
|
73
|
+
ignore: ["HOSTNAME"]
|
|
74
|
+
},
|
|
75
|
+
nextjs: {
|
|
76
|
+
optional: ["NEXT_PUBLIC_APP_URL"],
|
|
77
|
+
ignore: ["NEXT_RUNTIME"]
|
|
78
|
+
},
|
|
79
|
+
nuxt: {
|
|
80
|
+
optional: ["NUXT_PUBLIC_API_BASE"]
|
|
81
|
+
},
|
|
82
|
+
sveltekit: {
|
|
83
|
+
optional: ["PUBLIC_BASE_URL"]
|
|
84
|
+
},
|
|
85
|
+
vercel: {
|
|
86
|
+
ignore: ["VERCEL", "VERCEL_ENV", "VERCEL_URL", "VERCEL_BRANCH_URL", "VERCEL_PROJECT_PRODUCTION_URL", "VERCEL_REGION"]
|
|
87
|
+
},
|
|
88
|
+
vite: {
|
|
89
|
+
optional: ["VITE_PUBLIC_URL"],
|
|
90
|
+
ignore: ["BASE_URL", "DEV", "MODE", "PROD", "SSR"]
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var availablePresetNames = Object.keys(presetConfigs).sort();
|
|
94
|
+
var availablePresetList = availablePresetNames.join(", ");
|
|
95
|
+
var require2 = createRequire(import.meta.url);
|
|
96
|
+
var cliPackage = require2("../package.json");
|
|
97
|
+
var cliVersion = cliPackage.version;
|
|
65
98
|
var tableBlockStart = "<!-- configenvy:start -->";
|
|
66
99
|
var tableBlockEnd = "<!-- configenvy:end -->";
|
|
67
100
|
async function runCli(argv, dependencies = defaultDependencies) {
|
|
@@ -91,8 +124,10 @@ async function runDoctor(projectPath, options, dependencies = defaultDependencie
|
|
|
91
124
|
async function runInit(projectPath, options = {}, dependencies = defaultDependencies) {
|
|
92
125
|
const rootDir = dependencies.resolvePath(projectPath);
|
|
93
126
|
const result = await dependencies.scanProject({ rootDir });
|
|
127
|
+
const preset = resolvePreset(options.preset, dependencies);
|
|
128
|
+
if (options.preset && !preset) return;
|
|
94
129
|
const existingEnvExample = options.envExample && options.force ? await readOptionalText(dependencies.resolvePath(rootDir, ".env.example"), dependencies) : void 0;
|
|
95
|
-
const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample);
|
|
130
|
+
const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample, preset);
|
|
96
131
|
if (options.dryRun) {
|
|
97
132
|
for (const file of files) {
|
|
98
133
|
dependencies.log(`Would write ${file.path}`);
|
|
@@ -118,10 +153,13 @@ async function runInit(projectPath, options = {}, dependencies = defaultDependen
|
|
|
118
153
|
dependencies.log(`Created ${file.path}`);
|
|
119
154
|
}
|
|
120
155
|
}
|
|
121
|
-
function buildInitFiles(rootDir, result, includeEnvExample, resolvePath = resolve, existingEnvExample) {
|
|
156
|
+
function buildInitFiles(rootDir, result, includeEnvExample, resolvePath = resolve, existingEnvExample, preset) {
|
|
122
157
|
const required = detectedRuntimeVariables(result);
|
|
123
158
|
const config = {
|
|
124
159
|
...starterConfig,
|
|
160
|
+
docs: mergeUnique(starterConfig.docs, preset?.docs ?? []),
|
|
161
|
+
ignore: mergeUnique(starterConfig.ignore, preset?.ignore ?? []),
|
|
162
|
+
optional: mergeUnique(starterConfig.optional, preset?.optional ?? []),
|
|
125
163
|
required
|
|
126
164
|
};
|
|
127
165
|
const files = [
|
|
@@ -139,6 +177,18 @@ function buildInitFiles(rootDir, result, includeEnvExample, resolvePath = resolv
|
|
|
139
177
|
}
|
|
140
178
|
return files;
|
|
141
179
|
}
|
|
180
|
+
function resolvePreset(name, dependencies) {
|
|
181
|
+
if (!name) return void 0;
|
|
182
|
+
if (name in presetConfigs) {
|
|
183
|
+
return presetConfigs[name];
|
|
184
|
+
}
|
|
185
|
+
dependencies.error(`Unknown preset "${name}". Available presets: ${availablePresetList}.`);
|
|
186
|
+
dependencies.exit(1);
|
|
187
|
+
return void 0;
|
|
188
|
+
}
|
|
189
|
+
function mergeUnique(base, extra) {
|
|
190
|
+
return [.../* @__PURE__ */ new Set([...base, ...extra])].sort();
|
|
191
|
+
}
|
|
142
192
|
async function ensureInitTargetsDoNotExist(files, dependencies) {
|
|
143
193
|
for (const file of files) {
|
|
144
194
|
const existing = await readOptionalText(file.path, dependencies);
|
|
@@ -273,6 +323,7 @@ if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
|
|
|
273
323
|
}
|
|
274
324
|
export {
|
|
275
325
|
buildInitFiles,
|
|
326
|
+
cliVersion,
|
|
276
327
|
createProgram,
|
|
277
328
|
printGitHubAnnotations,
|
|
278
329
|
printHumanReport,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "configenvy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Find missing, unused, undocumented, and risky environment variables before setup breaks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"build": "tsup src/index.ts --format esm --dts --clean"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@configenvy/core": "0.1.
|
|
38
|
+
"@configenvy/core": "0.1.7",
|
|
39
39
|
"commander": "^12.1.0"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
3
4
|
import { isAbsolute, resolve } from "node:path";
|
|
4
5
|
import { pathToFileURL } from "node:url";
|
|
5
6
|
import { Command } from "commander";
|
|
@@ -23,6 +24,7 @@ type InitOptions = {
|
|
|
23
24
|
dryRun?: boolean;
|
|
24
25
|
envExample?: boolean;
|
|
25
26
|
force?: boolean;
|
|
27
|
+
preset?: string;
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
export type CliDependencies = {
|
|
@@ -59,7 +61,7 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
|
|
|
59
61
|
program
|
|
60
62
|
.name("configenvy")
|
|
61
63
|
.description("Find missing, unused, undocumented, and risky environment variables.")
|
|
62
|
-
.version(
|
|
64
|
+
.version(cliVersion);
|
|
63
65
|
|
|
64
66
|
program
|
|
65
67
|
.command("doctor")
|
|
@@ -75,8 +77,9 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
|
|
|
75
77
|
.argument("[path]", "project directory", ".")
|
|
76
78
|
.option("--ci", "fail on warnings and errors")
|
|
77
79
|
.option("--format <format>", "output format: text, json, or sarif", "text")
|
|
80
|
+
.option("--strict", "treat documentation warnings as errors")
|
|
78
81
|
.action(async (projectPath: string, options: DoctorOptions) => {
|
|
79
|
-
await runDoctor(projectPath, { ...options, strict: Boolean(options.ci), ci: Boolean(options.ci) }, dependencies);
|
|
82
|
+
await runDoctor(projectPath, { ...options, strict: Boolean(options.strict || options.ci), ci: Boolean(options.ci) }, dependencies);
|
|
80
83
|
});
|
|
81
84
|
|
|
82
85
|
program
|
|
@@ -106,6 +109,7 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
|
|
|
106
109
|
.option("--dry-run", "print planned files instead of writing them")
|
|
107
110
|
.option("--env-example", "also create a .env.example draft from detected variables")
|
|
108
111
|
.option("--force", "overwrite generated files if they already exist")
|
|
112
|
+
.option("--preset <name>", `apply a preset: ${availablePresetList}`)
|
|
109
113
|
.action(async (projectPath: string, options: InitOptions) => {
|
|
110
114
|
await runInit(projectPath, options, dependencies);
|
|
111
115
|
});
|
|
@@ -122,18 +126,62 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
|
|
|
122
126
|
return program;
|
|
123
127
|
}
|
|
124
128
|
|
|
125
|
-
|
|
129
|
+
type StarterConfig = {
|
|
130
|
+
docs: string[];
|
|
131
|
+
ignore: string[];
|
|
132
|
+
optional: string[];
|
|
133
|
+
required: string[];
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const starterConfig: StarterConfig = {
|
|
126
137
|
required: [],
|
|
127
138
|
optional: [],
|
|
128
139
|
ignore: ["NODE_ENV"],
|
|
129
140
|
docs: ["README.md", "docs"]
|
|
130
141
|
};
|
|
131
142
|
|
|
143
|
+
type PresetName = keyof typeof presetConfigs;
|
|
144
|
+
|
|
145
|
+
const presetConfigs = {
|
|
146
|
+
astro: {
|
|
147
|
+
optional: ["PUBLIC_SITE_URL"],
|
|
148
|
+
ignore: ["BASE_URL", "DEV", "MODE", "PROD", "SSR"]
|
|
149
|
+
},
|
|
150
|
+
docker: {
|
|
151
|
+
optional: ["COMPOSE_PROJECT_NAME"],
|
|
152
|
+
ignore: ["HOSTNAME"]
|
|
153
|
+
},
|
|
154
|
+
nextjs: {
|
|
155
|
+
optional: ["NEXT_PUBLIC_APP_URL"],
|
|
156
|
+
ignore: ["NEXT_RUNTIME"]
|
|
157
|
+
},
|
|
158
|
+
nuxt: {
|
|
159
|
+
optional: ["NUXT_PUBLIC_API_BASE"]
|
|
160
|
+
},
|
|
161
|
+
sveltekit: {
|
|
162
|
+
optional: ["PUBLIC_BASE_URL"]
|
|
163
|
+
},
|
|
164
|
+
vercel: {
|
|
165
|
+
ignore: ["VERCEL", "VERCEL_ENV", "VERCEL_URL", "VERCEL_BRANCH_URL", "VERCEL_PROJECT_PRODUCTION_URL", "VERCEL_REGION"]
|
|
166
|
+
},
|
|
167
|
+
vite: {
|
|
168
|
+
optional: ["VITE_PUBLIC_URL"],
|
|
169
|
+
ignore: ["BASE_URL", "DEV", "MODE", "PROD", "SSR"]
|
|
170
|
+
}
|
|
171
|
+
} satisfies Record<string, Partial<StarterConfig>>;
|
|
172
|
+
|
|
173
|
+
const availablePresetNames = Object.keys(presetConfigs).sort();
|
|
174
|
+
const availablePresetList = availablePresetNames.join(", ");
|
|
175
|
+
|
|
132
176
|
type InitFile = {
|
|
133
177
|
content: string;
|
|
134
178
|
path: string;
|
|
135
179
|
};
|
|
136
180
|
|
|
181
|
+
const require = createRequire(import.meta.url);
|
|
182
|
+
const cliPackage = require("../package.json") as { version: string };
|
|
183
|
+
export const cliVersion = cliPackage.version;
|
|
184
|
+
|
|
137
185
|
export const tableBlockStart = "<!-- configenvy:start -->";
|
|
138
186
|
export const tableBlockEnd = "<!-- configenvy:end -->";
|
|
139
187
|
|
|
@@ -176,10 +224,12 @@ export async function runInit(
|
|
|
176
224
|
): Promise<void> {
|
|
177
225
|
const rootDir = dependencies.resolvePath(projectPath);
|
|
178
226
|
const result = await dependencies.scanProject({ rootDir });
|
|
227
|
+
const preset = resolvePreset(options.preset, dependencies);
|
|
228
|
+
if (options.preset && !preset) return;
|
|
179
229
|
const existingEnvExample = options.envExample && options.force
|
|
180
230
|
? await readOptionalText(dependencies.resolvePath(rootDir, ".env.example"), dependencies)
|
|
181
231
|
: undefined;
|
|
182
|
-
const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample);
|
|
232
|
+
const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample, preset);
|
|
183
233
|
|
|
184
234
|
if (options.dryRun) {
|
|
185
235
|
for (const file of files) {
|
|
@@ -215,11 +265,15 @@ export function buildInitFiles(
|
|
|
215
265
|
result: ScanResult,
|
|
216
266
|
includeEnvExample: boolean,
|
|
217
267
|
resolvePath: typeof resolve = resolve,
|
|
218
|
-
existingEnvExample?: string
|
|
268
|
+
existingEnvExample?: string,
|
|
269
|
+
preset?: Partial<StarterConfig>
|
|
219
270
|
): InitFile[] {
|
|
220
271
|
const required = detectedRuntimeVariables(result);
|
|
221
272
|
const config = {
|
|
222
273
|
...starterConfig,
|
|
274
|
+
docs: mergeUnique(starterConfig.docs, preset?.docs ?? []),
|
|
275
|
+
ignore: mergeUnique(starterConfig.ignore, preset?.ignore ?? []),
|
|
276
|
+
optional: mergeUnique(starterConfig.optional, preset?.optional ?? []),
|
|
223
277
|
required
|
|
224
278
|
};
|
|
225
279
|
const files: InitFile[] = [
|
|
@@ -239,6 +293,20 @@ export function buildInitFiles(
|
|
|
239
293
|
return files;
|
|
240
294
|
}
|
|
241
295
|
|
|
296
|
+
function resolvePreset(name: string | undefined, dependencies: CliDependencies): Partial<StarterConfig> | undefined {
|
|
297
|
+
if (!name) return undefined;
|
|
298
|
+
if (name in presetConfigs) {
|
|
299
|
+
return presetConfigs[name as PresetName];
|
|
300
|
+
}
|
|
301
|
+
dependencies.error(`Unknown preset "${name}". Available presets: ${availablePresetList}.`);
|
|
302
|
+
dependencies.exit(1);
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function mergeUnique(base: string[], extra: readonly string[]): string[] {
|
|
307
|
+
return [...new Set([...base, ...extra])].sort();
|
|
308
|
+
}
|
|
309
|
+
|
|
242
310
|
async function ensureInitTargetsDoNotExist(files: InitFile[], dependencies: CliDependencies): Promise<boolean> {
|
|
243
311
|
for (const file of files) {
|
|
244
312
|
const existing = await readOptionalText(file.path, dependencies);
|