configenvy 0.1.5 → 0.1.6

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
@@ -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,7 +28,7 @@ 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("0.1.4");
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
  });
@@ -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,28 @@ var starterConfig = {
62
63
  ignore: ["NODE_ENV"],
63
64
  docs: ["README.md", "docs"]
64
65
  };
66
+ var presetConfigs = {
67
+ docker: {
68
+ optional: ["COMPOSE_PROJECT_NAME"],
69
+ ignore: ["HOSTNAME"]
70
+ },
71
+ nextjs: {
72
+ optional: ["NEXT_PUBLIC_APP_URL"],
73
+ ignore: ["NEXT_RUNTIME"]
74
+ },
75
+ vercel: {
76
+ ignore: ["VERCEL", "VERCEL_ENV", "VERCEL_URL", "VERCEL_BRANCH_URL", "VERCEL_PROJECT_PRODUCTION_URL", "VERCEL_REGION"]
77
+ },
78
+ vite: {
79
+ optional: ["VITE_PUBLIC_URL"],
80
+ ignore: ["BASE_URL", "DEV", "MODE", "PROD", "SSR"]
81
+ }
82
+ };
83
+ var availablePresetNames = Object.keys(presetConfigs).sort();
84
+ var availablePresetList = availablePresetNames.join(", ");
85
+ var require2 = createRequire(import.meta.url);
86
+ var cliPackage = require2("../package.json");
87
+ var cliVersion = cliPackage.version;
65
88
  var tableBlockStart = "<!-- configenvy:start -->";
66
89
  var tableBlockEnd = "<!-- configenvy:end -->";
67
90
  async function runCli(argv, dependencies = defaultDependencies) {
@@ -91,8 +114,10 @@ async function runDoctor(projectPath, options, dependencies = defaultDependencie
91
114
  async function runInit(projectPath, options = {}, dependencies = defaultDependencies) {
92
115
  const rootDir = dependencies.resolvePath(projectPath);
93
116
  const result = await dependencies.scanProject({ rootDir });
117
+ const preset = resolvePreset(options.preset, dependencies);
118
+ if (options.preset && !preset) return;
94
119
  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);
120
+ const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample, preset);
96
121
  if (options.dryRun) {
97
122
  for (const file of files) {
98
123
  dependencies.log(`Would write ${file.path}`);
@@ -118,10 +143,13 @@ async function runInit(projectPath, options = {}, dependencies = defaultDependen
118
143
  dependencies.log(`Created ${file.path}`);
119
144
  }
120
145
  }
121
- function buildInitFiles(rootDir, result, includeEnvExample, resolvePath = resolve, existingEnvExample) {
146
+ function buildInitFiles(rootDir, result, includeEnvExample, resolvePath = resolve, existingEnvExample, preset) {
122
147
  const required = detectedRuntimeVariables(result);
123
148
  const config = {
124
149
  ...starterConfig,
150
+ docs: mergeUnique(starterConfig.docs, preset?.docs ?? []),
151
+ ignore: mergeUnique(starterConfig.ignore, preset?.ignore ?? []),
152
+ optional: mergeUnique(starterConfig.optional, preset?.optional ?? []),
125
153
  required
126
154
  };
127
155
  const files = [
@@ -139,6 +167,18 @@ function buildInitFiles(rootDir, result, includeEnvExample, resolvePath = resolv
139
167
  }
140
168
  return files;
141
169
  }
170
+ function resolvePreset(name, dependencies) {
171
+ if (!name) return void 0;
172
+ if (name in presetConfigs) {
173
+ return presetConfigs[name];
174
+ }
175
+ dependencies.error(`Unknown preset "${name}". Available presets: ${availablePresetList}.`);
176
+ dependencies.exit(1);
177
+ return void 0;
178
+ }
179
+ function mergeUnique(base, extra) {
180
+ return [.../* @__PURE__ */ new Set([...base, ...extra])].sort();
181
+ }
142
182
  async function ensureInitTargetsDoNotExist(files, dependencies) {
143
183
  for (const file of files) {
144
184
  const existing = await readOptionalText(file.path, dependencies);
@@ -273,6 +313,7 @@ if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
273
313
  }
274
314
  export {
275
315
  buildInitFiles,
316
+ cliVersion,
276
317
  createProgram,
277
318
  printGitHubAnnotations,
278
319
  printHumanReport,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "configenvy",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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.5",
38
+ "@configenvy/core": "0.1.6",
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("0.1.4");
64
+ .version(cliVersion);
63
65
 
64
66
  program
65
67
  .command("doctor")
@@ -106,6 +108,7 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
106
108
  .option("--dry-run", "print planned files instead of writing them")
107
109
  .option("--env-example", "also create a .env.example draft from detected variables")
108
110
  .option("--force", "overwrite generated files if they already exist")
111
+ .option("--preset <name>", `apply a preset: ${availablePresetList}`)
109
112
  .action(async (projectPath: string, options: InitOptions) => {
110
113
  await runInit(projectPath, options, dependencies);
111
114
  });
@@ -122,18 +125,52 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
122
125
  return program;
123
126
  }
124
127
 
125
- const starterConfig = {
128
+ type StarterConfig = {
129
+ docs: string[];
130
+ ignore: string[];
131
+ optional: string[];
132
+ required: string[];
133
+ };
134
+
135
+ const starterConfig: StarterConfig = {
126
136
  required: [],
127
137
  optional: [],
128
138
  ignore: ["NODE_ENV"],
129
139
  docs: ["README.md", "docs"]
130
140
  };
131
141
 
142
+ type PresetName = keyof typeof presetConfigs;
143
+
144
+ const presetConfigs = {
145
+ docker: {
146
+ optional: ["COMPOSE_PROJECT_NAME"],
147
+ ignore: ["HOSTNAME"]
148
+ },
149
+ nextjs: {
150
+ optional: ["NEXT_PUBLIC_APP_URL"],
151
+ ignore: ["NEXT_RUNTIME"]
152
+ },
153
+ vercel: {
154
+ ignore: ["VERCEL", "VERCEL_ENV", "VERCEL_URL", "VERCEL_BRANCH_URL", "VERCEL_PROJECT_PRODUCTION_URL", "VERCEL_REGION"]
155
+ },
156
+ vite: {
157
+ optional: ["VITE_PUBLIC_URL"],
158
+ ignore: ["BASE_URL", "DEV", "MODE", "PROD", "SSR"]
159
+ }
160
+ } satisfies Record<string, Partial<StarterConfig>>;
161
+
162
+ const availablePresetNames = Object.keys(presetConfigs).sort();
163
+ const availablePresetList = availablePresetNames.join(", ");
164
+
132
165
  type InitFile = {
133
166
  content: string;
134
167
  path: string;
135
168
  };
136
169
 
170
+ const require = createRequire(import.meta.url);
171
+ const cliPackage = require("../package.json") as { version: string };
172
+ export const cliVersion = cliPackage.version;
173
+
137
174
  export const tableBlockStart = "<!-- configenvy:start -->";
138
175
  export const tableBlockEnd = "<!-- configenvy:end -->";
139
176
 
@@ -176,10 +213,12 @@ export async function runInit(
176
213
  ): Promise<void> {
177
214
  const rootDir = dependencies.resolvePath(projectPath);
178
215
  const result = await dependencies.scanProject({ rootDir });
216
+ const preset = resolvePreset(options.preset, dependencies);
217
+ if (options.preset && !preset) return;
179
218
  const existingEnvExample = options.envExample && options.force
180
219
  ? await readOptionalText(dependencies.resolvePath(rootDir, ".env.example"), dependencies)
181
220
  : undefined;
182
- const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample);
221
+ const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample, preset);
183
222
 
184
223
  if (options.dryRun) {
185
224
  for (const file of files) {
@@ -215,11 +254,15 @@ export function buildInitFiles(
215
254
  result: ScanResult,
216
255
  includeEnvExample: boolean,
217
256
  resolvePath: typeof resolve = resolve,
218
- existingEnvExample?: string
257
+ existingEnvExample?: string,
258
+ preset?: Partial<StarterConfig>
219
259
  ): InitFile[] {
220
260
  const required = detectedRuntimeVariables(result);
221
261
  const config = {
222
262
  ...starterConfig,
263
+ docs: mergeUnique(starterConfig.docs, preset?.docs ?? []),
264
+ ignore: mergeUnique(starterConfig.ignore, preset?.ignore ?? []),
265
+ optional: mergeUnique(starterConfig.optional, preset?.optional ?? []),
223
266
  required
224
267
  };
225
268
  const files: InitFile[] = [
@@ -239,6 +282,20 @@ export function buildInitFiles(
239
282
  return files;
240
283
  }
241
284
 
285
+ function resolvePreset(name: string | undefined, dependencies: CliDependencies): Partial<StarterConfig> | undefined {
286
+ if (!name) return undefined;
287
+ if (name in presetConfigs) {
288
+ return presetConfigs[name as PresetName];
289
+ }
290
+ dependencies.error(`Unknown preset "${name}". Available presets: ${availablePresetList}.`);
291
+ dependencies.exit(1);
292
+ return undefined;
293
+ }
294
+
295
+ function mergeUnique(base: string[], extra: readonly string[]): string[] {
296
+ return [...new Set([...base, ...extra])].sort();
297
+ }
298
+
242
299
  async function ensureInitTargetsDoNotExist(files: InitFile[], dependencies: CliDependencies): Promise<boolean> {
243
300
  for (const file of files) {
244
301
  const existing = await readOptionalText(file.path, dependencies);