configenvy 0.1.1 → 0.1.2

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 CHANGED
@@ -1 +1,29 @@
1
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, Diagnostic } from '@configenvy/core';
6
+
7
+ type DoctorOptions = {
8
+ format?: "text" | "json";
9
+ strict?: boolean;
10
+ ci?: boolean;
11
+ };
12
+ type CliDependencies = {
13
+ buildMarkdownTable: typeof buildMarkdownTable;
14
+ error: (...values: unknown[]) => void;
15
+ exit: (code: number) => never | void;
16
+ explainVariable: typeof explainVariable;
17
+ log: (...values: unknown[]) => void;
18
+ resolvePath: typeof resolve;
19
+ scanProject: typeof scanProject;
20
+ toJson: typeof toJson;
21
+ writeFile: typeof writeFile;
22
+ };
23
+ declare function createProgram(dependencies?: CliDependencies): Command;
24
+ declare function runCli(argv: string[], dependencies?: CliDependencies): Promise<void>;
25
+ declare function runDoctor(projectPath: string, options: DoctorOptions, dependencies?: CliDependencies): Promise<void>;
26
+ declare function resolveOutputPath(projectPath: string, outputPath: string, resolvePath?: typeof resolve): string;
27
+ declare function printHumanReport(diagnostics: Diagnostic[], log?: (...values: unknown[]) => void): void;
28
+
29
+ export { type CliDependencies, createProgram, printHumanReport, resolveOutputPath, runCli, runDoctor };
package/dist/index.js CHANGED
@@ -2,61 +2,98 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { writeFile } from "fs/promises";
5
- import { resolve } from "path";
5
+ import { isAbsolute, resolve } from "path";
6
+ import { pathToFileURL } from "url";
6
7
  import { Command } from "commander";
7
8
  import { buildMarkdownTable, explainVariable, scanProject, toJson } from "@configenvy/core";
8
- var program = new Command();
9
- program.name("configenvy").description("Find missing, unused, undocumented, and risky environment variables.").version("0.1.1");
10
- program.command("doctor").argument("[path]", "project directory", ".").option("--format <format>", "output format: text or json", "text").option("--strict", "treat documentation warnings as errors").action(async (projectPath, options) => {
11
- await runDoctor(projectPath, options);
12
- });
13
- program.command("check").argument("[path]", "project directory", ".").option("--ci", "fail on warnings and errors").option("--format <format>", "output format: text or json", "text").action(async (projectPath, options) => {
14
- await runDoctor(projectPath, { ...options, strict: Boolean(options.ci), ci: Boolean(options.ci) });
15
- });
16
- program.command("table").argument("[path]", "project directory", ".").option("--out <file>", "write markdown table to a file").action(async (projectPath, options) => {
17
- const result = await scanProject({ rootDir: resolve(projectPath) });
18
- const table = buildMarkdownTable(result);
19
- if (options.out) {
20
- await writeFile(resolve(options.out), `${table}
9
+ var defaultDependencies = {
10
+ buildMarkdownTable,
11
+ error: console.error,
12
+ exit: process.exit,
13
+ explainVariable,
14
+ log: console.log,
15
+ resolvePath: resolve,
16
+ scanProject,
17
+ toJson,
18
+ writeFile
19
+ };
20
+ function createProgram(dependencies = defaultDependencies) {
21
+ const program = new Command();
22
+ program.name("configenvy").description("Find missing, unused, undocumented, and risky environment variables.").version("0.1.2");
23
+ program.command("doctor").argument("[path]", "project directory", ".").option("--format <format>", "output format: text or json", "text").option("--strict", "treat documentation warnings as errors").action(async (projectPath, options) => {
24
+ await runDoctor(projectPath, options, dependencies);
25
+ });
26
+ program.command("check").argument("[path]", "project directory", ".").option("--ci", "fail on warnings and errors").option("--format <format>", "output format: text or json", "text").action(async (projectPath, options) => {
27
+ await runDoctor(projectPath, { ...options, strict: Boolean(options.ci), ci: Boolean(options.ci) }, dependencies);
28
+ });
29
+ program.command("table").argument("[path]", "project directory", ".").option("--out <file>", "write markdown table to a file").action(async (projectPath, options) => {
30
+ const rootDir = dependencies.resolvePath(projectPath);
31
+ const result = await dependencies.scanProject({ rootDir });
32
+ const table = dependencies.buildMarkdownTable(result);
33
+ if (options.out) {
34
+ await dependencies.writeFile(resolveOutputPath(rootDir, options.out, dependencies.resolvePath), `${table}
21
35
  `, "utf8");
22
- } else {
23
- console.log(table);
24
- }
25
- });
26
- program.command("explain").argument("<variable>", "environment variable name").argument("[path]", "project directory", ".").action(async (variable, projectPath) => {
27
- const result = await scanProject({ rootDir: resolve(projectPath) });
28
- console.log(explainVariable(result, variable));
29
- });
30
- program.parseAsync(process.argv).catch((error) => {
31
- console.error(error instanceof Error ? error.message : String(error));
32
- process.exit(3);
33
- });
34
- async function runDoctor(projectPath, options) {
35
- const result = await scanProject({ rootDir: resolve(projectPath), strict: Boolean(options.strict) });
36
+ } else {
37
+ dependencies.log(table);
38
+ }
39
+ });
40
+ program.command("explain").argument("<variable>", "environment variable name").argument("[path]", "project directory", ".").action(async (variable, projectPath) => {
41
+ const result = await dependencies.scanProject({ rootDir: dependencies.resolvePath(projectPath) });
42
+ dependencies.log(dependencies.explainVariable(result, variable));
43
+ });
44
+ return program;
45
+ }
46
+ async function runCli(argv, dependencies = defaultDependencies) {
47
+ const program = createProgram(dependencies);
48
+ await program.parseAsync(argv);
49
+ }
50
+ async function runDoctor(projectPath, options, dependencies = defaultDependencies) {
51
+ const result = await dependencies.scanProject({
52
+ rootDir: dependencies.resolvePath(projectPath),
53
+ strict: Boolean(options.strict)
54
+ });
36
55
  if (options.format === "json") {
37
- console.log(toJson(result));
56
+ dependencies.log(dependencies.toJson(result));
38
57
  } else {
39
- printHumanReport(result.diagnostics);
58
+ printHumanReport(result.diagnostics, dependencies.log);
40
59
  }
41
60
  const hasError = result.diagnostics.some((diagnostic) => diagnostic.severity === "error");
42
61
  const hasWarning = result.diagnostics.some((diagnostic) => diagnostic.severity === "warning");
43
- if (hasError || options.ci && hasWarning) process.exit(2);
44
- if (hasWarning) process.exit(1);
62
+ if (hasError || options.ci && hasWarning) dependencies.exit(2);
63
+ if (hasWarning) dependencies.exit(1);
64
+ }
65
+ function resolveOutputPath(projectPath, outputPath, resolvePath = resolve) {
66
+ if (isAbsolute(outputPath)) return outputPath;
67
+ return resolvePath(projectPath, outputPath);
45
68
  }
46
- function printHumanReport(diagnostics) {
69
+ function printHumanReport(diagnostics, log = defaultDependencies.log) {
47
70
  if (diagnostics.length === 0) {
48
- console.log("PASS configenvy found no environment variable issues.");
71
+ log("PASS configenvy found no environment variable issues.");
49
72
  return;
50
73
  }
51
74
  for (const diagnostic of diagnostics) {
52
75
  const label = diagnostic.severity === "error" ? "FAIL" : "WARN";
53
- console.log(`${label} ${diagnostic.code} ${diagnostic.variable}`);
54
- console.log(` ${diagnostic.message}`);
76
+ log(`${label} ${diagnostic.code} ${diagnostic.variable}`);
77
+ log(` ${diagnostic.message}`);
55
78
  if (diagnostic.files.length > 0) {
56
- console.log(` files: ${diagnostic.files.join(", ")}`);
79
+ log(` files: ${diagnostic.files.join(", ")}`);
57
80
  }
58
81
  }
59
82
  const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
60
83
  const warnings = diagnostics.length - errors;
61
- console.log(`Summary: ${errors} error(s), ${warnings} warning(s).`);
84
+ log(`Summary: ${errors} error(s), ${warnings} warning(s).`);
85
+ }
86
+ var invokedPath = process.argv[1];
87
+ if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
88
+ runCli(process.argv).catch((error) => {
89
+ defaultDependencies.error(error instanceof Error ? error.message : String(error));
90
+ defaultDependencies.exit(3);
91
+ });
62
92
  }
93
+ export {
94
+ createProgram,
95
+ printHumanReport,
96
+ resolveOutputPath,
97
+ runCli,
98
+ runDoctor
99
+ };
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "configenvy",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Find missing, unused, undocumented, and risky environment variables before setup breaks.",
5
5
  "type": "module",
6
+ "engines": {
7
+ "node": ">=18"
8
+ },
6
9
  "repository": {
7
10
  "type": "git",
8
11
  "url": "git+https://github.com/sonsriver4815/configenvy.git",
@@ -32,7 +35,7 @@
32
35
  "build": "tsup src/index.ts --format esm --dts --clean"
33
36
  },
34
37
  "dependencies": {
35
- "@configenvy/core": "0.1.1",
38
+ "@configenvy/core": "0.1.2",
36
39
  "commander": "^12.1.0"
37
40
  },
38
41
  "keywords": [
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { writeFile } from "node:fs/promises";
3
- import { resolve } from "node:path";
3
+ import { isAbsolute, resolve } from "node:path";
4
+ import { pathToFileURL } from "node:url";
4
5
  import { Command } from "commander";
5
6
  import { buildMarkdownTable, explainVariable, scanProject, toJson, type Diagnostic } from "@configenvy/core";
6
7
 
@@ -10,90 +11,146 @@ type DoctorOptions = {
10
11
  ci?: boolean;
11
12
  };
12
13
 
13
- const program = new Command();
14
+ export type CliDependencies = {
15
+ buildMarkdownTable: typeof buildMarkdownTable;
16
+ error: (...values: unknown[]) => void;
17
+ exit: (code: number) => never | void;
18
+ explainVariable: typeof explainVariable;
19
+ log: (...values: unknown[]) => void;
20
+ resolvePath: typeof resolve;
21
+ scanProject: typeof scanProject;
22
+ toJson: typeof toJson;
23
+ writeFile: typeof writeFile;
24
+ };
14
25
 
15
- program
16
- .name("configenvy")
17
- .description("Find missing, unused, undocumented, and risky environment variables.")
18
- .version("0.1.1");
26
+ const defaultDependencies: CliDependencies = {
27
+ buildMarkdownTable,
28
+ error: console.error,
29
+ exit: process.exit,
30
+ explainVariable,
31
+ log: console.log,
32
+ resolvePath: resolve,
33
+ scanProject,
34
+ toJson,
35
+ writeFile
36
+ };
19
37
 
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
- });
38
+ export function createProgram(dependencies: CliDependencies = defaultDependencies): Command {
39
+ const program = new Command();
28
40
 
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
- });
41
+ program
42
+ .name("configenvy")
43
+ .description("Find missing, unused, undocumented, and risky environment variables.")
44
+ .version("0.1.2");
37
45
 
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
- });
46
+ program
47
+ .command("doctor")
48
+ .argument("[path]", "project directory", ".")
49
+ .option("--format <format>", "output format: text or json", "text")
50
+ .option("--strict", "treat documentation warnings as errors")
51
+ .action(async (projectPath: string, options: DoctorOptions) => {
52
+ await runDoctor(projectPath, options, dependencies);
53
+ });
51
54
 
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
- });
55
+ program
56
+ .command("check")
57
+ .argument("[path]", "project directory", ".")
58
+ .option("--ci", "fail on warnings and errors")
59
+ .option("--format <format>", "output format: text or json", "text")
60
+ .action(async (projectPath: string, options: DoctorOptions) => {
61
+ await runDoctor(projectPath, { ...options, strict: Boolean(options.ci), ci: Boolean(options.ci) }, dependencies);
62
+ });
63
+
64
+ program
65
+ .command("table")
66
+ .argument("[path]", "project directory", ".")
67
+ .option("--out <file>", "write markdown table to a file")
68
+ .action(async (projectPath: string, options: { out?: string }) => {
69
+ const rootDir = dependencies.resolvePath(projectPath);
70
+ const result = await dependencies.scanProject({ rootDir });
71
+ const table = dependencies.buildMarkdownTable(result);
72
+ if (options.out) {
73
+ await dependencies.writeFile(resolveOutputPath(rootDir, options.out, dependencies.resolvePath), `${table}\n`, "utf8");
74
+ } else {
75
+ dependencies.log(table);
76
+ }
77
+ });
78
+
79
+ program
80
+ .command("explain")
81
+ .argument("<variable>", "environment variable name")
82
+ .argument("[path]", "project directory", ".")
83
+ .action(async (variable: string, projectPath: string) => {
84
+ const result = await dependencies.scanProject({ rootDir: dependencies.resolvePath(projectPath) });
85
+ dependencies.log(dependencies.explainVariable(result, variable));
86
+ });
60
87
 
61
- program.parseAsync(process.argv).catch((error: unknown) => {
62
- console.error(error instanceof Error ? error.message : String(error));
63
- process.exit(3);
64
- });
88
+ return program;
89
+ }
65
90
 
66
- async function runDoctor(projectPath: string, options: DoctorOptions): Promise<void> {
67
- const result = await scanProject({ rootDir: resolve(projectPath), strict: Boolean(options.strict) });
91
+ export async function runCli(argv: string[], dependencies: CliDependencies = defaultDependencies): Promise<void> {
92
+ const program = createProgram(dependencies);
93
+ await program.parseAsync(argv);
94
+ }
95
+
96
+ export async function runDoctor(
97
+ projectPath: string,
98
+ options: DoctorOptions,
99
+ dependencies: CliDependencies = defaultDependencies
100
+ ): Promise<void> {
101
+ const result = await dependencies.scanProject({
102
+ rootDir: dependencies.resolvePath(projectPath),
103
+ strict: Boolean(options.strict)
104
+ });
68
105
 
69
106
  if (options.format === "json") {
70
- console.log(toJson(result));
107
+ dependencies.log(dependencies.toJson(result));
71
108
  } else {
72
- printHumanReport(result.diagnostics);
109
+ printHumanReport(result.diagnostics, dependencies.log);
73
110
  }
74
111
 
75
112
  const hasError = result.diagnostics.some((diagnostic) => diagnostic.severity === "error");
76
113
  const hasWarning = result.diagnostics.some((diagnostic) => diagnostic.severity === "warning");
77
- if (hasError || (options.ci && hasWarning)) process.exit(2);
78
- if (hasWarning) process.exit(1);
114
+ if (hasError || (options.ci && hasWarning)) dependencies.exit(2);
115
+ if (hasWarning) dependencies.exit(1);
79
116
  }
80
117
 
81
- function printHumanReport(diagnostics: Diagnostic[]): void {
118
+ export function resolveOutputPath(
119
+ projectPath: string,
120
+ outputPath: string,
121
+ resolvePath: typeof resolve = resolve
122
+ ): string {
123
+ if (isAbsolute(outputPath)) return outputPath;
124
+ return resolvePath(projectPath, outputPath);
125
+ }
126
+
127
+ export function printHumanReport(
128
+ diagnostics: Diagnostic[],
129
+ log: (...values: unknown[]) => void = defaultDependencies.log
130
+ ): void {
82
131
  if (diagnostics.length === 0) {
83
- console.log("PASS configenvy found no environment variable issues.");
132
+ log("PASS configenvy found no environment variable issues.");
84
133
  return;
85
134
  }
86
135
 
87
136
  for (const diagnostic of diagnostics) {
88
137
  const label = diagnostic.severity === "error" ? "FAIL" : "WARN";
89
- console.log(`${label} ${diagnostic.code} ${diagnostic.variable}`);
90
- console.log(` ${diagnostic.message}`);
138
+ log(`${label} ${diagnostic.code} ${diagnostic.variable}`);
139
+ log(` ${diagnostic.message}`);
91
140
  if (diagnostic.files.length > 0) {
92
- console.log(` files: ${diagnostic.files.join(", ")}`);
141
+ log(` files: ${diagnostic.files.join(", ")}`);
93
142
  }
94
143
  }
95
144
 
96
145
  const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
97
146
  const warnings = diagnostics.length - errors;
98
- console.log(`Summary: ${errors} error(s), ${warnings} warning(s).`);
147
+ log(`Summary: ${errors} error(s), ${warnings} warning(s).`);
148
+ }
149
+
150
+ const invokedPath = process.argv[1];
151
+ if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
152
+ runCli(process.argv).catch((error: unknown) => {
153
+ defaultDependencies.error(error instanceof Error ? error.message : String(error));
154
+ defaultDependencies.exit(3);
155
+ });
99
156
  }