configenvy 0.1.5 → 0.1.7

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,12 +28,12 @@ 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
  });
34
- 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) => {
35
- await runDoctor(projectPath, { ...options, strict: Boolean(options.ci), ci: Boolean(options.ci) }, dependencies);
35
+ program.command("check").argument("[path]", "project directory", ".").option("--ci", "fail on warnings and errors").option("--format <format>", "output format: text, json, or sarif", "text").option("--strict", "treat documentation warnings as errors").action(async (projectPath, options) => {
36
+ await runDoctor(projectPath, { ...options, strict: Boolean(options.strict || options.ci), ci: Boolean(options.ci) }, dependencies);
36
37
  });
37
38
  program.command("table").argument("[path]", "project directory", ".").option("--out <file>", "write markdown table to a file").option("--update <file>", "replace a marked configenvy table block in a markdown file").option("--force", "append a configenvy table block when --update target has no marked block").option("--dry-run", "print the updated markdown instead of writing it").action(async (projectPath, options) => {
38
39
  const rootDir = dependencies.resolvePath(projectPath);
@@ -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,38 @@ var starterConfig = {
62
63
  ignore: ["NODE_ENV"],
63
64
  docs: ["README.md", "docs"]
64
65
  };
66
+ var presetConfigs = {
67
+ astro: {
68
+ optional: ["PUBLIC_SITE_URL"],
69
+ ignore: ["BASE_URL", "DEV", "MODE", "PROD", "SSR"]
70
+ },
71
+ docker: {
72
+ optional: ["COMPOSE_PROJECT_NAME"],
73
+ ignore: ["HOSTNAME"]
74
+ },
75
+ nextjs: {
76
+ optional: ["NEXT_PUBLIC_APP_URL"],
77
+ ignore: ["NEXT_RUNTIME"]
78
+ },
79
+ nuxt: {
80
+ optional: ["NUXT_PUBLIC_API_BASE"]
81
+ },
82
+ sveltekit: {
83
+ optional: ["PUBLIC_BASE_URL"]
84
+ },
85
+ vercel: {
86
+ ignore: ["VERCEL", "VERCEL_ENV", "VERCEL_URL", "VERCEL_BRANCH_URL", "VERCEL_PROJECT_PRODUCTION_URL", "VERCEL_REGION"]
87
+ },
88
+ vite: {
89
+ optional: ["VITE_PUBLIC_URL"],
90
+ ignore: ["BASE_URL", "DEV", "MODE", "PROD", "SSR"]
91
+ }
92
+ };
93
+ var availablePresetNames = Object.keys(presetConfigs).sort();
94
+ var availablePresetList = availablePresetNames.join(", ");
95
+ var require2 = createRequire(import.meta.url);
96
+ var cliPackage = require2("../package.json");
97
+ var cliVersion = cliPackage.version;
65
98
  var tableBlockStart = "<!-- configenvy:start -->";
66
99
  var tableBlockEnd = "<!-- configenvy:end -->";
67
100
  async function runCli(argv, dependencies = defaultDependencies) {
@@ -91,8 +124,10 @@ async function runDoctor(projectPath, options, dependencies = defaultDependencie
91
124
  async function runInit(projectPath, options = {}, dependencies = defaultDependencies) {
92
125
  const rootDir = dependencies.resolvePath(projectPath);
93
126
  const result = await dependencies.scanProject({ rootDir });
127
+ const preset = resolvePreset(options.preset, dependencies);
128
+ if (options.preset && !preset) return;
94
129
  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);
130
+ const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample, preset);
96
131
  if (options.dryRun) {
97
132
  for (const file of files) {
98
133
  dependencies.log(`Would write ${file.path}`);
@@ -118,10 +153,13 @@ async function runInit(projectPath, options = {}, dependencies = defaultDependen
118
153
  dependencies.log(`Created ${file.path}`);
119
154
  }
120
155
  }
121
- function buildInitFiles(rootDir, result, includeEnvExample, resolvePath = resolve, existingEnvExample) {
156
+ function buildInitFiles(rootDir, result, includeEnvExample, resolvePath = resolve, existingEnvExample, preset) {
122
157
  const required = detectedRuntimeVariables(result);
123
158
  const config = {
124
159
  ...starterConfig,
160
+ docs: mergeUnique(starterConfig.docs, preset?.docs ?? []),
161
+ ignore: mergeUnique(starterConfig.ignore, preset?.ignore ?? []),
162
+ optional: mergeUnique(starterConfig.optional, preset?.optional ?? []),
125
163
  required
126
164
  };
127
165
  const files = [
@@ -139,6 +177,18 @@ function buildInitFiles(rootDir, result, includeEnvExample, resolvePath = resolv
139
177
  }
140
178
  return files;
141
179
  }
180
+ function resolvePreset(name, dependencies) {
181
+ if (!name) return void 0;
182
+ if (name in presetConfigs) {
183
+ return presetConfigs[name];
184
+ }
185
+ dependencies.error(`Unknown preset "${name}". Available presets: ${availablePresetList}.`);
186
+ dependencies.exit(1);
187
+ return void 0;
188
+ }
189
+ function mergeUnique(base, extra) {
190
+ return [.../* @__PURE__ */ new Set([...base, ...extra])].sort();
191
+ }
142
192
  async function ensureInitTargetsDoNotExist(files, dependencies) {
143
193
  for (const file of files) {
144
194
  const existing = await readOptionalText(file.path, dependencies);
@@ -273,6 +323,7 @@ if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
273
323
  }
274
324
  export {
275
325
  buildInitFiles,
326
+ cliVersion,
276
327
  createProgram,
277
328
  printGitHubAnnotations,
278
329
  printHumanReport,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "configenvy",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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.7",
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")
@@ -75,8 +77,9 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
75
77
  .argument("[path]", "project directory", ".")
76
78
  .option("--ci", "fail on warnings and errors")
77
79
  .option("--format <format>", "output format: text, json, or sarif", "text")
80
+ .option("--strict", "treat documentation warnings as errors")
78
81
  .action(async (projectPath: string, options: DoctorOptions) => {
79
- await runDoctor(projectPath, { ...options, strict: Boolean(options.ci), ci: Boolean(options.ci) }, dependencies);
82
+ await runDoctor(projectPath, { ...options, strict: Boolean(options.strict || options.ci), ci: Boolean(options.ci) }, dependencies);
80
83
  });
81
84
 
82
85
  program
@@ -106,6 +109,7 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
106
109
  .option("--dry-run", "print planned files instead of writing them")
107
110
  .option("--env-example", "also create a .env.example draft from detected variables")
108
111
  .option("--force", "overwrite generated files if they already exist")
112
+ .option("--preset <name>", `apply a preset: ${availablePresetList}`)
109
113
  .action(async (projectPath: string, options: InitOptions) => {
110
114
  await runInit(projectPath, options, dependencies);
111
115
  });
@@ -122,18 +126,62 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
122
126
  return program;
123
127
  }
124
128
 
125
- const starterConfig = {
129
+ type StarterConfig = {
130
+ docs: string[];
131
+ ignore: string[];
132
+ optional: string[];
133
+ required: string[];
134
+ };
135
+
136
+ const starterConfig: StarterConfig = {
126
137
  required: [],
127
138
  optional: [],
128
139
  ignore: ["NODE_ENV"],
129
140
  docs: ["README.md", "docs"]
130
141
  };
131
142
 
143
+ type PresetName = keyof typeof presetConfigs;
144
+
145
+ const presetConfigs = {
146
+ astro: {
147
+ optional: ["PUBLIC_SITE_URL"],
148
+ ignore: ["BASE_URL", "DEV", "MODE", "PROD", "SSR"]
149
+ },
150
+ docker: {
151
+ optional: ["COMPOSE_PROJECT_NAME"],
152
+ ignore: ["HOSTNAME"]
153
+ },
154
+ nextjs: {
155
+ optional: ["NEXT_PUBLIC_APP_URL"],
156
+ ignore: ["NEXT_RUNTIME"]
157
+ },
158
+ nuxt: {
159
+ optional: ["NUXT_PUBLIC_API_BASE"]
160
+ },
161
+ sveltekit: {
162
+ optional: ["PUBLIC_BASE_URL"]
163
+ },
164
+ vercel: {
165
+ ignore: ["VERCEL", "VERCEL_ENV", "VERCEL_URL", "VERCEL_BRANCH_URL", "VERCEL_PROJECT_PRODUCTION_URL", "VERCEL_REGION"]
166
+ },
167
+ vite: {
168
+ optional: ["VITE_PUBLIC_URL"],
169
+ ignore: ["BASE_URL", "DEV", "MODE", "PROD", "SSR"]
170
+ }
171
+ } satisfies Record<string, Partial<StarterConfig>>;
172
+
173
+ const availablePresetNames = Object.keys(presetConfigs).sort();
174
+ const availablePresetList = availablePresetNames.join(", ");
175
+
132
176
  type InitFile = {
133
177
  content: string;
134
178
  path: string;
135
179
  };
136
180
 
181
+ const require = createRequire(import.meta.url);
182
+ const cliPackage = require("../package.json") as { version: string };
183
+ export const cliVersion = cliPackage.version;
184
+
137
185
  export const tableBlockStart = "<!-- configenvy:start -->";
138
186
  export const tableBlockEnd = "<!-- configenvy:end -->";
139
187
 
@@ -176,10 +224,12 @@ export async function runInit(
176
224
  ): Promise<void> {
177
225
  const rootDir = dependencies.resolvePath(projectPath);
178
226
  const result = await dependencies.scanProject({ rootDir });
227
+ const preset = resolvePreset(options.preset, dependencies);
228
+ if (options.preset && !preset) return;
179
229
  const existingEnvExample = options.envExample && options.force
180
230
  ? await readOptionalText(dependencies.resolvePath(rootDir, ".env.example"), dependencies)
181
231
  : undefined;
182
- const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample);
232
+ const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample, preset);
183
233
 
184
234
  if (options.dryRun) {
185
235
  for (const file of files) {
@@ -215,11 +265,15 @@ export function buildInitFiles(
215
265
  result: ScanResult,
216
266
  includeEnvExample: boolean,
217
267
  resolvePath: typeof resolve = resolve,
218
- existingEnvExample?: string
268
+ existingEnvExample?: string,
269
+ preset?: Partial<StarterConfig>
219
270
  ): InitFile[] {
220
271
  const required = detectedRuntimeVariables(result);
221
272
  const config = {
222
273
  ...starterConfig,
274
+ docs: mergeUnique(starterConfig.docs, preset?.docs ?? []),
275
+ ignore: mergeUnique(starterConfig.ignore, preset?.ignore ?? []),
276
+ optional: mergeUnique(starterConfig.optional, preset?.optional ?? []),
223
277
  required
224
278
  };
225
279
  const files: InitFile[] = [
@@ -239,6 +293,20 @@ export function buildInitFiles(
239
293
  return files;
240
294
  }
241
295
 
296
+ function resolvePreset(name: string | undefined, dependencies: CliDependencies): Partial<StarterConfig> | undefined {
297
+ if (!name) return undefined;
298
+ if (name in presetConfigs) {
299
+ return presetConfigs[name as PresetName];
300
+ }
301
+ dependencies.error(`Unknown preset "${name}". Available presets: ${availablePresetList}.`);
302
+ dependencies.exit(1);
303
+ return undefined;
304
+ }
305
+
306
+ function mergeUnique(base: string[], extra: readonly string[]): string[] {
307
+ return [...new Set([...base, ...extra])].sort();
308
+ }
309
+
242
310
  async function ensureInitTargetsDoNotExist(files: InitFile[], dependencies: CliDependencies): Promise<boolean> {
243
311
  for (const file of files) {
244
312
  const existing = await readOptionalText(file.path, dependencies);