configenvy 0.1.2 → 0.1.4
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 +6 -3
- package/dist/index.js +58 -5
- package/package.json +2 -2
- package/src/index.ts +76 -5
package/dist/index.d.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
import { writeFile } from 'node:fs/promises';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
|
-
import { buildMarkdownTable, explainVariable, scanProject, toJson, Diagnostic } from '@configenvy/core';
|
|
5
|
+
import { buildMarkdownTable, explainVariable, scanProject, toJson, toSarif, Diagnostic } from '@configenvy/core';
|
|
6
6
|
|
|
7
7
|
type DoctorOptions = {
|
|
8
|
-
format?: "text" | "json";
|
|
8
|
+
format?: "text" | "json" | "sarif";
|
|
9
9
|
strict?: boolean;
|
|
10
10
|
ci?: boolean;
|
|
11
11
|
};
|
|
@@ -18,12 +18,15 @@ type CliDependencies = {
|
|
|
18
18
|
resolvePath: typeof resolve;
|
|
19
19
|
scanProject: typeof scanProject;
|
|
20
20
|
toJson: typeof toJson;
|
|
21
|
+
toSarif: typeof toSarif;
|
|
21
22
|
writeFile: typeof writeFile;
|
|
22
23
|
};
|
|
23
24
|
declare function createProgram(dependencies?: CliDependencies): Command;
|
|
24
25
|
declare function runCli(argv: string[], dependencies?: CliDependencies): Promise<void>;
|
|
25
26
|
declare function runDoctor(projectPath: string, options: DoctorOptions, dependencies?: CliDependencies): Promise<void>;
|
|
27
|
+
declare function runInit(projectPath: string, dependencies?: CliDependencies): Promise<void>;
|
|
26
28
|
declare function resolveOutputPath(projectPath: string, outputPath: string, resolvePath?: typeof resolve): string;
|
|
27
29
|
declare function printHumanReport(diagnostics: Diagnostic[], log?: (...values: unknown[]) => void): void;
|
|
30
|
+
declare function printGitHubAnnotations(diagnostics: Diagnostic[], log?: (...values: unknown[]) => void): void;
|
|
28
31
|
|
|
29
|
-
export { type CliDependencies, createProgram, printHumanReport, resolveOutputPath, runCli, runDoctor };
|
|
32
|
+
export { type CliDependencies, createProgram, printGitHubAnnotations, printHumanReport, resolveOutputPath, runCli, runDoctor, runInit };
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { writeFile } from "fs/promises";
|
|
|
5
5
|
import { isAbsolute, resolve } from "path";
|
|
6
6
|
import { pathToFileURL } from "url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
-
import { buildMarkdownTable, explainVariable, scanProject, toJson } from "@configenvy/core";
|
|
8
|
+
import { buildMarkdownTable, explainVariable, scanProject, toJson, toSarif } from "@configenvy/core";
|
|
9
9
|
var defaultDependencies = {
|
|
10
10
|
buildMarkdownTable,
|
|
11
11
|
error: console.error,
|
|
@@ -15,15 +15,16 @@ var defaultDependencies = {
|
|
|
15
15
|
resolvePath: resolve,
|
|
16
16
|
scanProject,
|
|
17
17
|
toJson,
|
|
18
|
+
toSarif,
|
|
18
19
|
writeFile
|
|
19
20
|
};
|
|
20
21
|
function createProgram(dependencies = defaultDependencies) {
|
|
21
22
|
const program = new Command();
|
|
22
|
-
program.name("configenvy").description("Find missing, unused, undocumented, and risky environment variables.").version("0.1.
|
|
23
|
-
program.command("doctor").argument("[path]", "project directory", ".").option("--format <format>", "output format: text or
|
|
23
|
+
program.name("configenvy").description("Find missing, unused, undocumented, and risky environment variables.").version("0.1.4");
|
|
24
|
+
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) => {
|
|
24
25
|
await runDoctor(projectPath, options, dependencies);
|
|
25
26
|
});
|
|
26
|
-
program.command("check").argument("[path]", "project directory", ".").option("--ci", "fail on warnings and errors").option("--format <format>", "output format: text or
|
|
27
|
+
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) => {
|
|
27
28
|
await runDoctor(projectPath, { ...options, strict: Boolean(options.ci), ci: Boolean(options.ci) }, dependencies);
|
|
28
29
|
});
|
|
29
30
|
program.command("table").argument("[path]", "project directory", ".").option("--out <file>", "write markdown table to a file").action(async (projectPath, options) => {
|
|
@@ -37,12 +38,21 @@ function createProgram(dependencies = defaultDependencies) {
|
|
|
37
38
|
dependencies.log(table);
|
|
38
39
|
}
|
|
39
40
|
});
|
|
41
|
+
program.command("init").argument("[path]", "project directory", ".").description("create a starter configenvy.config.json file").action(async (projectPath) => {
|
|
42
|
+
await runInit(projectPath, dependencies);
|
|
43
|
+
});
|
|
40
44
|
program.command("explain").argument("<variable>", "environment variable name").argument("[path]", "project directory", ".").action(async (variable, projectPath) => {
|
|
41
45
|
const result = await dependencies.scanProject({ rootDir: dependencies.resolvePath(projectPath) });
|
|
42
46
|
dependencies.log(dependencies.explainVariable(result, variable));
|
|
43
47
|
});
|
|
44
48
|
return program;
|
|
45
49
|
}
|
|
50
|
+
var starterConfig = {
|
|
51
|
+
required: [],
|
|
52
|
+
optional: [],
|
|
53
|
+
ignore: ["NODE_ENV"],
|
|
54
|
+
docs: ["README.md", "docs"]
|
|
55
|
+
};
|
|
46
56
|
async function runCli(argv, dependencies = defaultDependencies) {
|
|
47
57
|
const program = createProgram(dependencies);
|
|
48
58
|
await program.parseAsync(argv);
|
|
@@ -54,14 +64,36 @@ async function runDoctor(projectPath, options, dependencies = defaultDependencie
|
|
|
54
64
|
});
|
|
55
65
|
if (options.format === "json") {
|
|
56
66
|
dependencies.log(dependencies.toJson(result));
|
|
67
|
+
} else if (options.format === "sarif") {
|
|
68
|
+
dependencies.log(dependencies.toSarif(result));
|
|
57
69
|
} else {
|
|
58
70
|
printHumanReport(result.diagnostics, dependencies.log);
|
|
71
|
+
if (options.ci) {
|
|
72
|
+
printGitHubAnnotations(result.diagnostics, dependencies.log);
|
|
73
|
+
}
|
|
59
74
|
}
|
|
60
75
|
const hasError = result.diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
61
76
|
const hasWarning = result.diagnostics.some((diagnostic) => diagnostic.severity === "warning");
|
|
62
77
|
if (hasError || options.ci && hasWarning) dependencies.exit(2);
|
|
63
78
|
if (hasWarning) dependencies.exit(1);
|
|
64
79
|
}
|
|
80
|
+
async function runInit(projectPath, dependencies = defaultDependencies) {
|
|
81
|
+
const rootDir = dependencies.resolvePath(projectPath);
|
|
82
|
+
const configPath = dependencies.resolvePath(rootDir, "configenvy.config.json");
|
|
83
|
+
const content = `${JSON.stringify(starterConfig, null, 2)}
|
|
84
|
+
`;
|
|
85
|
+
try {
|
|
86
|
+
await dependencies.writeFile(configPath, content, { encoding: "utf8", flag: "wx" });
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (isNodeError(error) && error.code === "EEXIST") {
|
|
89
|
+
dependencies.error("configenvy.config.json already exists.");
|
|
90
|
+
dependencies.exit(1);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
dependencies.log(`Created ${configPath}`);
|
|
96
|
+
}
|
|
65
97
|
function resolveOutputPath(projectPath, outputPath, resolvePath = resolve) {
|
|
66
98
|
if (isAbsolute(outputPath)) return outputPath;
|
|
67
99
|
return resolvePath(projectPath, outputPath);
|
|
@@ -83,6 +115,25 @@ function printHumanReport(diagnostics, log = defaultDependencies.log) {
|
|
|
83
115
|
const warnings = diagnostics.length - errors;
|
|
84
116
|
log(`Summary: ${errors} error(s), ${warnings} warning(s).`);
|
|
85
117
|
}
|
|
118
|
+
function printGitHubAnnotations(diagnostics, log = defaultDependencies.log) {
|
|
119
|
+
for (const diagnostic of diagnostics) {
|
|
120
|
+
const command = diagnostic.severity === "error" ? "error" : "warning";
|
|
121
|
+
const properties = [
|
|
122
|
+
diagnostic.files[0] ? `file=${escapeAnnotationProperty(diagnostic.files[0])}` : void 0,
|
|
123
|
+
`title=${escapeAnnotationProperty(`${diagnostic.code} ${diagnostic.variable}`)}`
|
|
124
|
+
].filter(Boolean);
|
|
125
|
+
log(`::${command} ${properties.join(",")}::${escapeAnnotationMessage(diagnostic.message)}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function escapeAnnotationProperty(value) {
|
|
129
|
+
return escapeAnnotationMessage(value).replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
130
|
+
}
|
|
131
|
+
function escapeAnnotationMessage(value) {
|
|
132
|
+
return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
133
|
+
}
|
|
134
|
+
function isNodeError(error) {
|
|
135
|
+
return error instanceof Error && "code" in error;
|
|
136
|
+
}
|
|
86
137
|
var invokedPath = process.argv[1];
|
|
87
138
|
if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
|
|
88
139
|
runCli(process.argv).catch((error) => {
|
|
@@ -92,8 +143,10 @@ if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
|
|
|
92
143
|
}
|
|
93
144
|
export {
|
|
94
145
|
createProgram,
|
|
146
|
+
printGitHubAnnotations,
|
|
95
147
|
printHumanReport,
|
|
96
148
|
resolveOutputPath,
|
|
97
149
|
runCli,
|
|
98
|
-
runDoctor
|
|
150
|
+
runDoctor,
|
|
151
|
+
runInit
|
|
99
152
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "configenvy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
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.4",
|
|
39
39
|
"commander": "^12.1.0"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
package/src/index.ts
CHANGED
|
@@ -3,10 +3,10 @@ import { writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { isAbsolute, resolve } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { Command } from "commander";
|
|
6
|
-
import { buildMarkdownTable, explainVariable, scanProject, toJson, type Diagnostic } from "@configenvy/core";
|
|
6
|
+
import { buildMarkdownTable, explainVariable, scanProject, toJson, toSarif, type Diagnostic } from "@configenvy/core";
|
|
7
7
|
|
|
8
8
|
type DoctorOptions = {
|
|
9
|
-
format?: "text" | "json";
|
|
9
|
+
format?: "text" | "json" | "sarif";
|
|
10
10
|
strict?: boolean;
|
|
11
11
|
ci?: boolean;
|
|
12
12
|
};
|
|
@@ -20,6 +20,7 @@ export type CliDependencies = {
|
|
|
20
20
|
resolvePath: typeof resolve;
|
|
21
21
|
scanProject: typeof scanProject;
|
|
22
22
|
toJson: typeof toJson;
|
|
23
|
+
toSarif: typeof toSarif;
|
|
23
24
|
writeFile: typeof writeFile;
|
|
24
25
|
};
|
|
25
26
|
|
|
@@ -32,6 +33,7 @@ const defaultDependencies: CliDependencies = {
|
|
|
32
33
|
resolvePath: resolve,
|
|
33
34
|
scanProject,
|
|
34
35
|
toJson,
|
|
36
|
+
toSarif,
|
|
35
37
|
writeFile
|
|
36
38
|
};
|
|
37
39
|
|
|
@@ -41,12 +43,12 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
|
|
|
41
43
|
program
|
|
42
44
|
.name("configenvy")
|
|
43
45
|
.description("Find missing, unused, undocumented, and risky environment variables.")
|
|
44
|
-
.version("0.1.
|
|
46
|
+
.version("0.1.4");
|
|
45
47
|
|
|
46
48
|
program
|
|
47
49
|
.command("doctor")
|
|
48
50
|
.argument("[path]", "project directory", ".")
|
|
49
|
-
.option("--format <format>", "output format: text or
|
|
51
|
+
.option("--format <format>", "output format: text, json, or sarif", "text")
|
|
50
52
|
.option("--strict", "treat documentation warnings as errors")
|
|
51
53
|
.action(async (projectPath: string, options: DoctorOptions) => {
|
|
52
54
|
await runDoctor(projectPath, options, dependencies);
|
|
@@ -56,7 +58,7 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
|
|
|
56
58
|
.command("check")
|
|
57
59
|
.argument("[path]", "project directory", ".")
|
|
58
60
|
.option("--ci", "fail on warnings and errors")
|
|
59
|
-
.option("--format <format>", "output format: text or
|
|
61
|
+
.option("--format <format>", "output format: text, json, or sarif", "text")
|
|
60
62
|
.action(async (projectPath: string, options: DoctorOptions) => {
|
|
61
63
|
await runDoctor(projectPath, { ...options, strict: Boolean(options.ci), ci: Boolean(options.ci) }, dependencies);
|
|
62
64
|
});
|
|
@@ -76,6 +78,14 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
|
|
|
76
78
|
}
|
|
77
79
|
});
|
|
78
80
|
|
|
81
|
+
program
|
|
82
|
+
.command("init")
|
|
83
|
+
.argument("[path]", "project directory", ".")
|
|
84
|
+
.description("create a starter configenvy.config.json file")
|
|
85
|
+
.action(async (projectPath: string) => {
|
|
86
|
+
await runInit(projectPath, dependencies);
|
|
87
|
+
});
|
|
88
|
+
|
|
79
89
|
program
|
|
80
90
|
.command("explain")
|
|
81
91
|
.argument("<variable>", "environment variable name")
|
|
@@ -88,6 +98,13 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
|
|
|
88
98
|
return program;
|
|
89
99
|
}
|
|
90
100
|
|
|
101
|
+
const starterConfig = {
|
|
102
|
+
required: [],
|
|
103
|
+
optional: [],
|
|
104
|
+
ignore: ["NODE_ENV"],
|
|
105
|
+
docs: ["README.md", "docs"]
|
|
106
|
+
};
|
|
107
|
+
|
|
91
108
|
export async function runCli(argv: string[], dependencies: CliDependencies = defaultDependencies): Promise<void> {
|
|
92
109
|
const program = createProgram(dependencies);
|
|
93
110
|
await program.parseAsync(argv);
|
|
@@ -105,8 +122,13 @@ export async function runDoctor(
|
|
|
105
122
|
|
|
106
123
|
if (options.format === "json") {
|
|
107
124
|
dependencies.log(dependencies.toJson(result));
|
|
125
|
+
} else if (options.format === "sarif") {
|
|
126
|
+
dependencies.log(dependencies.toSarif(result));
|
|
108
127
|
} else {
|
|
109
128
|
printHumanReport(result.diagnostics, dependencies.log);
|
|
129
|
+
if (options.ci) {
|
|
130
|
+
printGitHubAnnotations(result.diagnostics, dependencies.log);
|
|
131
|
+
}
|
|
110
132
|
}
|
|
111
133
|
|
|
112
134
|
const hasError = result.diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
@@ -115,6 +137,28 @@ export async function runDoctor(
|
|
|
115
137
|
if (hasWarning) dependencies.exit(1);
|
|
116
138
|
}
|
|
117
139
|
|
|
140
|
+
export async function runInit(
|
|
141
|
+
projectPath: string,
|
|
142
|
+
dependencies: CliDependencies = defaultDependencies
|
|
143
|
+
): Promise<void> {
|
|
144
|
+
const rootDir = dependencies.resolvePath(projectPath);
|
|
145
|
+
const configPath = dependencies.resolvePath(rootDir, "configenvy.config.json");
|
|
146
|
+
const content = `${JSON.stringify(starterConfig, null, 2)}\n`;
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
await dependencies.writeFile(configPath, content, { encoding: "utf8", flag: "wx" });
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (isNodeError(error) && error.code === "EEXIST") {
|
|
152
|
+
dependencies.error("configenvy.config.json already exists.");
|
|
153
|
+
dependencies.exit(1);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
dependencies.log(`Created ${configPath}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
118
162
|
export function resolveOutputPath(
|
|
119
163
|
projectPath: string,
|
|
120
164
|
outputPath: string,
|
|
@@ -147,6 +191,33 @@ export function printHumanReport(
|
|
|
147
191
|
log(`Summary: ${errors} error(s), ${warnings} warning(s).`);
|
|
148
192
|
}
|
|
149
193
|
|
|
194
|
+
export function printGitHubAnnotations(
|
|
195
|
+
diagnostics: Diagnostic[],
|
|
196
|
+
log: (...values: unknown[]) => void = defaultDependencies.log
|
|
197
|
+
): void {
|
|
198
|
+
for (const diagnostic of diagnostics) {
|
|
199
|
+
const command = diagnostic.severity === "error" ? "error" : "warning";
|
|
200
|
+
const properties = [
|
|
201
|
+
diagnostic.files[0] ? `file=${escapeAnnotationProperty(diagnostic.files[0])}` : undefined,
|
|
202
|
+
`title=${escapeAnnotationProperty(`${diagnostic.code} ${diagnostic.variable}`)}`
|
|
203
|
+
].filter(Boolean);
|
|
204
|
+
|
|
205
|
+
log(`::${command} ${properties.join(",")}::${escapeAnnotationMessage(diagnostic.message)}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function escapeAnnotationProperty(value: string): string {
|
|
210
|
+
return escapeAnnotationMessage(value).replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function escapeAnnotationMessage(value: string): string {
|
|
214
|
+
return value.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function isNodeError(error: unknown): error is NodeJS.ErrnoException {
|
|
218
|
+
return error instanceof Error && "code" in error;
|
|
219
|
+
}
|
|
220
|
+
|
|
150
221
|
const invokedPath = process.argv[1];
|
|
151
222
|
if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
|
|
152
223
|
runCli(process.argv).catch((error: unknown) => {
|