configenvy 0.1.0

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 ADDED
@@ -0,0 +1,9 @@
1
+ # configenvy
2
+
3
+ Catch missing, stale, undocumented, and risky environment variables before setup breaks.
4
+
5
+ ```bash
6
+ npx configenvy doctor
7
+ ```
8
+
9
+ See the full documentation at [github.com/sonsriver4815/configenvy](https://github.com/sonsriver4815/configenvy).
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ import { writeFile } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ import { Command } from "commander";
5
+ import { buildMarkdownTable, explainVariable, scanProject, toJson } from "configenvy-core";
6
+ const program = new Command();
7
+ program
8
+ .name("configenvy")
9
+ .description("Find missing, unused, undocumented, and risky environment variables.")
10
+ .version("0.1.0");
11
+ program
12
+ .command("doctor")
13
+ .argument("[path]", "project directory", ".")
14
+ .option("--format <format>", "output format: text or json", "text")
15
+ .option("--strict", "treat documentation warnings as errors")
16
+ .action(async (projectPath, options) => {
17
+ await runDoctor(projectPath, options);
18
+ });
19
+ program
20
+ .command("check")
21
+ .argument("[path]", "project directory", ".")
22
+ .option("--ci", "fail on warnings and errors")
23
+ .option("--format <format>", "output format: text or json", "text")
24
+ .action(async (projectPath, options) => {
25
+ await runDoctor(projectPath, { ...options, strict: Boolean(options.ci), ci: Boolean(options.ci) });
26
+ });
27
+ program
28
+ .command("table")
29
+ .argument("[path]", "project directory", ".")
30
+ .option("--out <file>", "write markdown table to a file")
31
+ .action(async (projectPath, options) => {
32
+ const result = await scanProject({ rootDir: resolve(projectPath) });
33
+ const table = buildMarkdownTable(result);
34
+ if (options.out) {
35
+ await writeFile(resolve(options.out), `${table}\n`, "utf8");
36
+ }
37
+ else {
38
+ console.log(table);
39
+ }
40
+ });
41
+ program
42
+ .command("explain")
43
+ .argument("<variable>", "environment variable name")
44
+ .argument("[path]", "project directory", ".")
45
+ .action(async (variable, projectPath) => {
46
+ const result = await scanProject({ rootDir: resolve(projectPath) });
47
+ console.log(explainVariable(result, variable));
48
+ });
49
+ program.parseAsync(process.argv).catch((error) => {
50
+ console.error(error instanceof Error ? error.message : String(error));
51
+ process.exit(3);
52
+ });
53
+ async function runDoctor(projectPath, options) {
54
+ const result = await scanProject({ rootDir: resolve(projectPath), strict: Boolean(options.strict) });
55
+ if (options.format === "json") {
56
+ console.log(toJson(result));
57
+ }
58
+ else {
59
+ printHumanReport(result.diagnostics);
60
+ }
61
+ const hasError = result.diagnostics.some((diagnostic) => diagnostic.severity === "error");
62
+ const hasWarning = result.diagnostics.some((diagnostic) => diagnostic.severity === "warning");
63
+ if (hasError || (options.ci && hasWarning))
64
+ process.exit(2);
65
+ if (hasWarning)
66
+ process.exit(1);
67
+ }
68
+ function printHumanReport(diagnostics) {
69
+ if (diagnostics.length === 0) {
70
+ console.log("PASS configenvy found no environment variable issues.");
71
+ return;
72
+ }
73
+ for (const diagnostic of diagnostics) {
74
+ const label = diagnostic.severity === "error" ? "FAIL" : "WARN";
75
+ console.log(`${label} ${diagnostic.code} ${diagnostic.variable}`);
76
+ console.log(` ${diagnostic.message}`);
77
+ if (diagnostic.files.length > 0) {
78
+ console.log(` files: ${diagnostic.files.join(", ")}`);
79
+ }
80
+ }
81
+ const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
82
+ const warnings = diagnostics.length - errors;
83
+ console.log(`Summary: ${errors} error(s), ${warnings} warning(s).`);
84
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "configenvy",
3
+ "version": "0.1.0",
4
+ "description": "Find missing, unused, undocumented, and risky environment variables before setup breaks.",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/sonsriver4815/configenvy.git",
9
+ "directory": "packages/cli"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/sonsriver4815/configenvy/issues"
13
+ },
14
+ "homepage": "https://github.com/sonsriver4815/configenvy#readme",
15
+ "bin": {
16
+ "configenvy": "dist/index.js"
17
+ },
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "src",
29
+ "README.md"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsup src/index.ts --format esm --dts --clean"
33
+ },
34
+ "dependencies": {
35
+ "configenvy-core": "0.1.0",
36
+ "commander": "^12.1.0"
37
+ },
38
+ "keywords": [
39
+ "env",
40
+ "dotenv",
41
+ "cli",
42
+ "developer-tools",
43
+ "documentation"
44
+ ],
45
+ "license": "MIT"
46
+ }
package/src/index.ts ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ import { writeFile } from "node:fs/promises";
3
+ import { resolve } from "node:path";
4
+ import { Command } from "commander";
5
+ import { buildMarkdownTable, explainVariable, scanProject, toJson, type Diagnostic } from "configenvy-core";
6
+
7
+ type DoctorOptions = {
8
+ format?: "text" | "json";
9
+ strict?: boolean;
10
+ ci?: boolean;
11
+ };
12
+
13
+ const program = new Command();
14
+
15
+ program
16
+ .name("configenvy")
17
+ .description("Find missing, unused, undocumented, and risky environment variables.")
18
+ .version("0.1.0");
19
+
20
+ program
21
+ .command("doctor")
22
+ .argument("[path]", "project directory", ".")
23
+ .option("--format <format>", "output format: text or json", "text")
24
+ .option("--strict", "treat documentation warnings as errors")
25
+ .action(async (projectPath: string, options: DoctorOptions) => {
26
+ await runDoctor(projectPath, options);
27
+ });
28
+
29
+ program
30
+ .command("check")
31
+ .argument("[path]", "project directory", ".")
32
+ .option("--ci", "fail on warnings and errors")
33
+ .option("--format <format>", "output format: text or json", "text")
34
+ .action(async (projectPath: string, options: DoctorOptions) => {
35
+ await runDoctor(projectPath, { ...options, strict: Boolean(options.ci), ci: Boolean(options.ci) });
36
+ });
37
+
38
+ program
39
+ .command("table")
40
+ .argument("[path]", "project directory", ".")
41
+ .option("--out <file>", "write markdown table to a file")
42
+ .action(async (projectPath: string, options: { out?: string }) => {
43
+ const result = await scanProject({ rootDir: resolve(projectPath) });
44
+ const table = buildMarkdownTable(result);
45
+ if (options.out) {
46
+ await writeFile(resolve(options.out), `${table}\n`, "utf8");
47
+ } else {
48
+ console.log(table);
49
+ }
50
+ });
51
+
52
+ program
53
+ .command("explain")
54
+ .argument("<variable>", "environment variable name")
55
+ .argument("[path]", "project directory", ".")
56
+ .action(async (variable: string, projectPath: string) => {
57
+ const result = await scanProject({ rootDir: resolve(projectPath) });
58
+ console.log(explainVariable(result, variable));
59
+ });
60
+
61
+ program.parseAsync(process.argv).catch((error: unknown) => {
62
+ console.error(error instanceof Error ? error.message : String(error));
63
+ process.exit(3);
64
+ });
65
+
66
+ async function runDoctor(projectPath: string, options: DoctorOptions): Promise<void> {
67
+ const result = await scanProject({ rootDir: resolve(projectPath), strict: Boolean(options.strict) });
68
+
69
+ if (options.format === "json") {
70
+ console.log(toJson(result));
71
+ } else {
72
+ printHumanReport(result.diagnostics);
73
+ }
74
+
75
+ const hasError = result.diagnostics.some((diagnostic) => diagnostic.severity === "error");
76
+ const hasWarning = result.diagnostics.some((diagnostic) => diagnostic.severity === "warning");
77
+ if (hasError || (options.ci && hasWarning)) process.exit(2);
78
+ if (hasWarning) process.exit(1);
79
+ }
80
+
81
+ function printHumanReport(diagnostics: Diagnostic[]): void {
82
+ if (diagnostics.length === 0) {
83
+ console.log("PASS configenvy found no environment variable issues.");
84
+ return;
85
+ }
86
+
87
+ for (const diagnostic of diagnostics) {
88
+ const label = diagnostic.severity === "error" ? "FAIL" : "WARN";
89
+ console.log(`${label} ${diagnostic.code} ${diagnostic.variable}`);
90
+ console.log(` ${diagnostic.message}`);
91
+ if (diagnostic.files.length > 0) {
92
+ console.log(` files: ${diagnostic.files.join(", ")}`);
93
+ }
94
+ }
95
+
96
+ const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
97
+ const warnings = diagnostics.length - errors;
98
+ console.log(`Summary: ${errors} error(s), ${warnings} warning(s).`);
99
+ }