configenvy 0.1.1 → 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 +31 -0
- package/dist/index.js +129 -39
- package/package.json +5 -2
- package/src/index.ts +189 -61
package/dist/index.d.ts
CHANGED
|
@@ -1 +1,32 @@
|
|
|
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, toSarif, Diagnostic } from '@configenvy/core';
|
|
6
|
+
|
|
7
|
+
type DoctorOptions = {
|
|
8
|
+
format?: "text" | "json" | "sarif";
|
|
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
|
+
toSarif: typeof toSarif;
|
|
22
|
+
writeFile: typeof writeFile;
|
|
23
|
+
};
|
|
24
|
+
declare function createProgram(dependencies?: CliDependencies): Command;
|
|
25
|
+
declare function runCli(argv: string[], dependencies?: CliDependencies): Promise<void>;
|
|
26
|
+
declare function runDoctor(projectPath: string, options: DoctorOptions, dependencies?: CliDependencies): Promise<void>;
|
|
27
|
+
declare function runInit(projectPath: string, dependencies?: CliDependencies): Promise<void>;
|
|
28
|
+
declare function resolveOutputPath(projectPath: string, outputPath: string, resolvePath?: typeof resolve): string;
|
|
29
|
+
declare function printHumanReport(diagnostics: Diagnostic[], log?: (...values: unknown[]) => void): void;
|
|
30
|
+
declare function printGitHubAnnotations(diagnostics: Diagnostic[], log?: (...values: unknown[]) => void): void;
|
|
31
|
+
|
|
32
|
+
export { type CliDependencies, createProgram, printGitHubAnnotations, printHumanReport, resolveOutputPath, runCli, runDoctor, runInit };
|
package/dist/index.js
CHANGED
|
@@ -2,61 +2,151 @@
|
|
|
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
|
-
import { buildMarkdownTable, explainVariable, scanProject, toJson } from "@configenvy/core";
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
import { buildMarkdownTable, explainVariable, scanProject, toJson, toSarif } from "@configenvy/core";
|
|
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
|
+
toSarif,
|
|
19
|
+
writeFile
|
|
20
|
+
};
|
|
21
|
+
function createProgram(dependencies = defaultDependencies) {
|
|
22
|
+
const program = new Command();
|
|
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) => {
|
|
25
|
+
await runDoctor(projectPath, options, dependencies);
|
|
26
|
+
});
|
|
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) => {
|
|
28
|
+
await runDoctor(projectPath, { ...options, strict: Boolean(options.ci), ci: Boolean(options.ci) }, dependencies);
|
|
29
|
+
});
|
|
30
|
+
program.command("table").argument("[path]", "project directory", ".").option("--out <file>", "write markdown table to a file").action(async (projectPath, options) => {
|
|
31
|
+
const rootDir = dependencies.resolvePath(projectPath);
|
|
32
|
+
const result = await dependencies.scanProject({ rootDir });
|
|
33
|
+
const table = dependencies.buildMarkdownTable(result);
|
|
34
|
+
if (options.out) {
|
|
35
|
+
await dependencies.writeFile(resolveOutputPath(rootDir, options.out, dependencies.resolvePath), `${table}
|
|
21
36
|
`, "utf8");
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
});
|
|
26
|
-
program.command("
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
} else {
|
|
38
|
+
dependencies.log(table);
|
|
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
|
+
});
|
|
44
|
+
program.command("explain").argument("<variable>", "environment variable name").argument("[path]", "project directory", ".").action(async (variable, projectPath) => {
|
|
45
|
+
const result = await dependencies.scanProject({ rootDir: dependencies.resolvePath(projectPath) });
|
|
46
|
+
dependencies.log(dependencies.explainVariable(result, variable));
|
|
47
|
+
});
|
|
48
|
+
return program;
|
|
49
|
+
}
|
|
50
|
+
var starterConfig = {
|
|
51
|
+
required: [],
|
|
52
|
+
optional: [],
|
|
53
|
+
ignore: ["NODE_ENV"],
|
|
54
|
+
docs: ["README.md", "docs"]
|
|
55
|
+
};
|
|
56
|
+
async function runCli(argv, dependencies = defaultDependencies) {
|
|
57
|
+
const program = createProgram(dependencies);
|
|
58
|
+
await program.parseAsync(argv);
|
|
59
|
+
}
|
|
60
|
+
async function runDoctor(projectPath, options, dependencies = defaultDependencies) {
|
|
61
|
+
const result = await dependencies.scanProject({
|
|
62
|
+
rootDir: dependencies.resolvePath(projectPath),
|
|
63
|
+
strict: Boolean(options.strict)
|
|
64
|
+
});
|
|
36
65
|
if (options.format === "json") {
|
|
37
|
-
|
|
66
|
+
dependencies.log(dependencies.toJson(result));
|
|
67
|
+
} else if (options.format === "sarif") {
|
|
68
|
+
dependencies.log(dependencies.toSarif(result));
|
|
38
69
|
} else {
|
|
39
|
-
printHumanReport(result.diagnostics);
|
|
70
|
+
printHumanReport(result.diagnostics, dependencies.log);
|
|
71
|
+
if (options.ci) {
|
|
72
|
+
printGitHubAnnotations(result.diagnostics, dependencies.log);
|
|
73
|
+
}
|
|
40
74
|
}
|
|
41
75
|
const hasError = result.diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
42
76
|
const hasWarning = result.diagnostics.some((diagnostic) => diagnostic.severity === "warning");
|
|
43
|
-
if (hasError || options.ci && hasWarning)
|
|
44
|
-
if (hasWarning)
|
|
77
|
+
if (hasError || options.ci && hasWarning) dependencies.exit(2);
|
|
78
|
+
if (hasWarning) dependencies.exit(1);
|
|
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}`);
|
|
45
96
|
}
|
|
46
|
-
function
|
|
97
|
+
function resolveOutputPath(projectPath, outputPath, resolvePath = resolve) {
|
|
98
|
+
if (isAbsolute(outputPath)) return outputPath;
|
|
99
|
+
return resolvePath(projectPath, outputPath);
|
|
100
|
+
}
|
|
101
|
+
function printHumanReport(diagnostics, log = defaultDependencies.log) {
|
|
47
102
|
if (diagnostics.length === 0) {
|
|
48
|
-
|
|
103
|
+
log("PASS configenvy found no environment variable issues.");
|
|
49
104
|
return;
|
|
50
105
|
}
|
|
51
106
|
for (const diagnostic of diagnostics) {
|
|
52
107
|
const label = diagnostic.severity === "error" ? "FAIL" : "WARN";
|
|
53
|
-
|
|
54
|
-
|
|
108
|
+
log(`${label} ${diagnostic.code} ${diagnostic.variable}`);
|
|
109
|
+
log(` ${diagnostic.message}`);
|
|
55
110
|
if (diagnostic.files.length > 0) {
|
|
56
|
-
|
|
111
|
+
log(` files: ${diagnostic.files.join(", ")}`);
|
|
57
112
|
}
|
|
58
113
|
}
|
|
59
114
|
const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
|
|
60
115
|
const warnings = diagnostics.length - errors;
|
|
61
|
-
|
|
116
|
+
log(`Summary: ${errors} error(s), ${warnings} warning(s).`);
|
|
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
|
+
}
|
|
137
|
+
var invokedPath = process.argv[1];
|
|
138
|
+
if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
|
|
139
|
+
runCli(process.argv).catch((error) => {
|
|
140
|
+
defaultDependencies.error(error instanceof Error ? error.message : String(error));
|
|
141
|
+
defaultDependencies.exit(3);
|
|
142
|
+
});
|
|
62
143
|
}
|
|
144
|
+
export {
|
|
145
|
+
createProgram,
|
|
146
|
+
printGitHubAnnotations,
|
|
147
|
+
printHumanReport,
|
|
148
|
+
resolveOutputPath,
|
|
149
|
+
runCli,
|
|
150
|
+
runDoctor,
|
|
151
|
+
runInit
|
|
152
|
+
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
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
|
+
"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.
|
|
38
|
+
"@configenvy/core": "0.1.4",
|
|
36
39
|
"commander": "^12.1.0"
|
|
37
40
|
},
|
|
38
41
|
"keywords": [
|
package/src/index.ts
CHANGED
|
@@ -1,99 +1,227 @@
|
|
|
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
|
-
import { buildMarkdownTable, explainVariable, scanProject, toJson, type Diagnostic } from "@configenvy/core";
|
|
6
|
+
import { buildMarkdownTable, explainVariable, scanProject, toJson, toSarif, type Diagnostic } from "@configenvy/core";
|
|
6
7
|
|
|
7
8
|
type DoctorOptions = {
|
|
8
|
-
format?: "text" | "json";
|
|
9
|
+
format?: "text" | "json" | "sarif";
|
|
9
10
|
strict?: boolean;
|
|
10
11
|
ci?: boolean;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
|
|
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
|
+
toSarif: typeof toSarif;
|
|
24
|
+
writeFile: typeof writeFile;
|
|
25
|
+
};
|
|
14
26
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.
|
|
18
|
-
.
|
|
27
|
+
const defaultDependencies: CliDependencies = {
|
|
28
|
+
buildMarkdownTable,
|
|
29
|
+
error: console.error,
|
|
30
|
+
exit: process.exit,
|
|
31
|
+
explainVariable,
|
|
32
|
+
log: console.log,
|
|
33
|
+
resolvePath: resolve,
|
|
34
|
+
scanProject,
|
|
35
|
+
toJson,
|
|
36
|
+
toSarif,
|
|
37
|
+
writeFile
|
|
38
|
+
};
|
|
19
39
|
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
});
|
|
40
|
+
export function createProgram(dependencies: CliDependencies = defaultDependencies): Command {
|
|
41
|
+
const program = new Command();
|
|
28
42
|
|
|
29
|
-
program
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
});
|
|
43
|
+
program
|
|
44
|
+
.name("configenvy")
|
|
45
|
+
.description("Find missing, unused, undocumented, and risky environment variables.")
|
|
46
|
+
.version("0.1.4");
|
|
37
47
|
|
|
38
|
-
program
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
await writeFile(resolve(options.out), `${table}\n`, "utf8");
|
|
47
|
-
} else {
|
|
48
|
-
console.log(table);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
48
|
+
program
|
|
49
|
+
.command("doctor")
|
|
50
|
+
.argument("[path]", "project directory", ".")
|
|
51
|
+
.option("--format <format>", "output format: text, json, or sarif", "text")
|
|
52
|
+
.option("--strict", "treat documentation warnings as errors")
|
|
53
|
+
.action(async (projectPath: string, options: DoctorOptions) => {
|
|
54
|
+
await runDoctor(projectPath, options, dependencies);
|
|
55
|
+
});
|
|
51
56
|
|
|
52
|
-
program
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
program
|
|
58
|
+
.command("check")
|
|
59
|
+
.argument("[path]", "project directory", ".")
|
|
60
|
+
.option("--ci", "fail on warnings and errors")
|
|
61
|
+
.option("--format <format>", "output format: text, json, or sarif", "text")
|
|
62
|
+
.action(async (projectPath: string, options: DoctorOptions) => {
|
|
63
|
+
await runDoctor(projectPath, { ...options, strict: Boolean(options.ci), ci: Boolean(options.ci) }, dependencies);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
program
|
|
67
|
+
.command("table")
|
|
68
|
+
.argument("[path]", "project directory", ".")
|
|
69
|
+
.option("--out <file>", "write markdown table to a file")
|
|
70
|
+
.action(async (projectPath: string, options: { out?: string }) => {
|
|
71
|
+
const rootDir = dependencies.resolvePath(projectPath);
|
|
72
|
+
const result = await dependencies.scanProject({ rootDir });
|
|
73
|
+
const table = dependencies.buildMarkdownTable(result);
|
|
74
|
+
if (options.out) {
|
|
75
|
+
await dependencies.writeFile(resolveOutputPath(rootDir, options.out, dependencies.resolvePath), `${table}\n`, "utf8");
|
|
76
|
+
} else {
|
|
77
|
+
dependencies.log(table);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
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
|
+
|
|
89
|
+
program
|
|
90
|
+
.command("explain")
|
|
91
|
+
.argument("<variable>", "environment variable name")
|
|
92
|
+
.argument("[path]", "project directory", ".")
|
|
93
|
+
.action(async (variable: string, projectPath: string) => {
|
|
94
|
+
const result = await dependencies.scanProject({ rootDir: dependencies.resolvePath(projectPath) });
|
|
95
|
+
dependencies.log(dependencies.explainVariable(result, variable));
|
|
96
|
+
});
|
|
60
97
|
|
|
61
|
-
program
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
98
|
+
return program;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const starterConfig = {
|
|
102
|
+
required: [],
|
|
103
|
+
optional: [],
|
|
104
|
+
ignore: ["NODE_ENV"],
|
|
105
|
+
docs: ["README.md", "docs"]
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export async function runCli(argv: string[], dependencies: CliDependencies = defaultDependencies): Promise<void> {
|
|
109
|
+
const program = createProgram(dependencies);
|
|
110
|
+
await program.parseAsync(argv);
|
|
111
|
+
}
|
|
65
112
|
|
|
66
|
-
async function runDoctor(
|
|
67
|
-
|
|
113
|
+
export async function runDoctor(
|
|
114
|
+
projectPath: string,
|
|
115
|
+
options: DoctorOptions,
|
|
116
|
+
dependencies: CliDependencies = defaultDependencies
|
|
117
|
+
): Promise<void> {
|
|
118
|
+
const result = await dependencies.scanProject({
|
|
119
|
+
rootDir: dependencies.resolvePath(projectPath),
|
|
120
|
+
strict: Boolean(options.strict)
|
|
121
|
+
});
|
|
68
122
|
|
|
69
123
|
if (options.format === "json") {
|
|
70
|
-
|
|
124
|
+
dependencies.log(dependencies.toJson(result));
|
|
125
|
+
} else if (options.format === "sarif") {
|
|
126
|
+
dependencies.log(dependencies.toSarif(result));
|
|
71
127
|
} else {
|
|
72
|
-
printHumanReport(result.diagnostics);
|
|
128
|
+
printHumanReport(result.diagnostics, dependencies.log);
|
|
129
|
+
if (options.ci) {
|
|
130
|
+
printGitHubAnnotations(result.diagnostics, dependencies.log);
|
|
131
|
+
}
|
|
73
132
|
}
|
|
74
133
|
|
|
75
134
|
const hasError = result.diagnostics.some((diagnostic) => diagnostic.severity === "error");
|
|
76
135
|
const hasWarning = result.diagnostics.some((diagnostic) => diagnostic.severity === "warning");
|
|
77
|
-
if (hasError || (options.ci && hasWarning))
|
|
78
|
-
if (hasWarning)
|
|
136
|
+
if (hasError || (options.ci && hasWarning)) dependencies.exit(2);
|
|
137
|
+
if (hasWarning) dependencies.exit(1);
|
|
79
138
|
}
|
|
80
139
|
|
|
81
|
-
function
|
|
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
|
+
|
|
162
|
+
export function resolveOutputPath(
|
|
163
|
+
projectPath: string,
|
|
164
|
+
outputPath: string,
|
|
165
|
+
resolvePath: typeof resolve = resolve
|
|
166
|
+
): string {
|
|
167
|
+
if (isAbsolute(outputPath)) return outputPath;
|
|
168
|
+
return resolvePath(projectPath, outputPath);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function printHumanReport(
|
|
172
|
+
diagnostics: Diagnostic[],
|
|
173
|
+
log: (...values: unknown[]) => void = defaultDependencies.log
|
|
174
|
+
): void {
|
|
82
175
|
if (diagnostics.length === 0) {
|
|
83
|
-
|
|
176
|
+
log("PASS configenvy found no environment variable issues.");
|
|
84
177
|
return;
|
|
85
178
|
}
|
|
86
179
|
|
|
87
180
|
for (const diagnostic of diagnostics) {
|
|
88
181
|
const label = diagnostic.severity === "error" ? "FAIL" : "WARN";
|
|
89
|
-
|
|
90
|
-
|
|
182
|
+
log(`${label} ${diagnostic.code} ${diagnostic.variable}`);
|
|
183
|
+
log(` ${diagnostic.message}`);
|
|
91
184
|
if (diagnostic.files.length > 0) {
|
|
92
|
-
|
|
185
|
+
log(` files: ${diagnostic.files.join(", ")}`);
|
|
93
186
|
}
|
|
94
187
|
}
|
|
95
188
|
|
|
96
189
|
const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length;
|
|
97
190
|
const warnings = diagnostics.length - errors;
|
|
98
|
-
|
|
191
|
+
log(`Summary: ${errors} error(s), ${warnings} warning(s).`);
|
|
192
|
+
}
|
|
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
|
+
|
|
221
|
+
const invokedPath = process.argv[1];
|
|
222
|
+
if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
|
|
223
|
+
runCli(process.argv).catch((error: unknown) => {
|
|
224
|
+
defaultDependencies.error(error instanceof Error ? error.message : String(error));
|
|
225
|
+
defaultDependencies.exit(3);
|
|
226
|
+
});
|
|
99
227
|
}
|