configenvy 0.1.6 → 0.2.0

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.js CHANGED
@@ -32,8 +32,8 @@ function createProgram(dependencies = defaultDependencies) {
32
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) => {
33
33
  await runDoctor(projectPath, options, dependencies);
34
34
  });
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").action(async (projectPath, options) => {
36
- 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);
37
37
  });
38
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) => {
39
39
  const rootDir = dependencies.resolvePath(projectPath);
@@ -64,6 +64,10 @@ var starterConfig = {
64
64
  docs: ["README.md", "docs"]
65
65
  };
66
66
  var presetConfigs = {
67
+ astro: {
68
+ optional: ["PUBLIC_SITE_URL"],
69
+ ignore: ["BASE_URL", "DEV", "MODE", "PROD", "SSR"]
70
+ },
67
71
  docker: {
68
72
  optional: ["COMPOSE_PROJECT_NAME"],
69
73
  ignore: ["HOSTNAME"]
@@ -72,6 +76,12 @@ var presetConfigs = {
72
76
  optional: ["NEXT_PUBLIC_APP_URL"],
73
77
  ignore: ["NEXT_RUNTIME"]
74
78
  },
79
+ nuxt: {
80
+ optional: ["NUXT_PUBLIC_API_BASE"]
81
+ },
82
+ sveltekit: {
83
+ optional: ["PUBLIC_BASE_URL"]
84
+ },
75
85
  vercel: {
76
86
  ignore: ["VERCEL", "VERCEL_ENV", "VERCEL_URL", "VERCEL_BRANCH_URL", "VERCEL_PROJECT_PRODUCTION_URL", "VERCEL_REGION"]
77
87
  },
@@ -81,7 +91,7 @@ var presetConfigs = {
81
91
  }
82
92
  };
83
93
  var availablePresetNames = Object.keys(presetConfigs).sort();
84
- var availablePresetList = availablePresetNames.join(", ");
94
+ var availablePresetList = ["auto", ...availablePresetNames].join(", ");
85
95
  var require2 = createRequire(import.meta.url);
86
96
  var cliPackage = require2("../package.json");
87
97
  var cliVersion = cliPackage.version;
@@ -114,10 +124,11 @@ async function runDoctor(projectPath, options, dependencies = defaultDependencie
114
124
  async function runInit(projectPath, options = {}, dependencies = defaultDependencies) {
115
125
  const rootDir = dependencies.resolvePath(projectPath);
116
126
  const result = await dependencies.scanProject({ rootDir });
117
- const preset = resolvePreset(options.preset, dependencies);
118
- if (options.preset && !preset) return;
127
+ const preset = await resolvePreset(rootDir, options.preset, dependencies);
128
+ if (options.preset && !preset.config && options.preset !== "auto") return;
129
+ if (preset.message) dependencies.log(preset.message);
119
130
  const existingEnvExample = options.envExample && options.force ? await readOptionalText(dependencies.resolvePath(rootDir, ".env.example"), dependencies) : void 0;
120
- const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample, preset);
131
+ const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample, preset.config);
121
132
  if (options.dryRun) {
122
133
  for (const file of files) {
123
134
  dependencies.log(`Would write ${file.path}`);
@@ -167,14 +178,67 @@ function buildInitFiles(rootDir, result, includeEnvExample, resolvePath = resolv
167
178
  }
168
179
  return files;
169
180
  }
170
- function resolvePreset(name, dependencies) {
171
- if (!name) return void 0;
181
+ async function resolvePreset(rootDir, name, dependencies) {
182
+ if (!name) return {};
183
+ if (name === "auto") {
184
+ return detectPreset(rootDir, dependencies);
185
+ }
172
186
  if (name in presetConfigs) {
173
- return presetConfigs[name];
187
+ return { config: presetConfigs[name] };
174
188
  }
175
189
  dependencies.error(`Unknown preset "${name}". Available presets: ${availablePresetList}.`);
176
190
  dependencies.exit(1);
177
- return void 0;
191
+ return {};
192
+ }
193
+ async function detectPreset(rootDir, dependencies) {
194
+ const packageJson = await readPackageJson(rootDir, dependencies);
195
+ const packages = new Set(Object.keys({
196
+ ...packageJson?.dependencies,
197
+ ...packageJson?.devDependencies
198
+ }));
199
+ const packageDetections = [
200
+ ["nextjs", "next", 'dependency "next"'],
201
+ ["astro", "astro", 'dependency "astro"'],
202
+ ["nuxt", "nuxt", 'dependency "nuxt"'],
203
+ ["sveltekit", "@sveltejs/kit", 'dependency "@sveltejs/kit"'],
204
+ ["vite", "vite", 'dependency "vite"']
205
+ ];
206
+ for (const [presetName, packageName, reason] of packageDetections) {
207
+ if (packages.has(packageName)) {
208
+ return detectedPreset(presetName, reason);
209
+ }
210
+ }
211
+ const configDetections = [
212
+ ["nextjs", ["next.config.js", "next.config.mjs", "next.config.ts"]],
213
+ ["astro", ["astro.config.js", "astro.config.mjs", "astro.config.ts"]],
214
+ ["nuxt", ["nuxt.config.js", "nuxt.config.mjs", "nuxt.config.ts"]],
215
+ ["sveltekit", ["svelte.config.js", "svelte.config.ts"]],
216
+ ["vite", ["vite.config.js", "vite.config.mjs", "vite.config.ts"]]
217
+ ];
218
+ for (const [presetName, files] of configDetections) {
219
+ for (const file of files) {
220
+ if (await readOptionalText(dependencies.resolvePath(rootDir, file), dependencies) !== void 0) {
221
+ return detectedPreset(presetName, `found ${file}`);
222
+ }
223
+ }
224
+ }
225
+ return { message: "No framework preset detected. Using the base config." };
226
+ }
227
+ function detectedPreset(name, reason) {
228
+ return {
229
+ config: presetConfigs[name],
230
+ message: `Detected preset: ${name}
231
+ Reason: ${reason}`
232
+ };
233
+ }
234
+ async function readPackageJson(rootDir, dependencies) {
235
+ const content = await readOptionalText(dependencies.resolvePath(rootDir, "package.json"), dependencies);
236
+ if (content === void 0) return void 0;
237
+ try {
238
+ return JSON.parse(content);
239
+ } catch {
240
+ return void 0;
241
+ }
178
242
  }
179
243
  function mergeUnique(base, extra) {
180
244
  return [.../* @__PURE__ */ new Set([...base, ...extra])].sort();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "configenvy",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
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.6",
38
+ "@configenvy/core": "0.2.0",
39
39
  "commander": "^12.1.0"
40
40
  },
41
41
  "keywords": [
package/src/index.ts CHANGED
@@ -77,8 +77,9 @@ export function createProgram(dependencies: CliDependencies = defaultDependencie
77
77
  .argument("[path]", "project directory", ".")
78
78
  .option("--ci", "fail on warnings and errors")
79
79
  .option("--format <format>", "output format: text, json, or sarif", "text")
80
+ .option("--strict", "treat documentation warnings as errors")
80
81
  .action(async (projectPath: string, options: DoctorOptions) => {
81
- 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);
82
83
  });
83
84
 
84
85
  program
@@ -142,6 +143,10 @@ const starterConfig: StarterConfig = {
142
143
  type PresetName = keyof typeof presetConfigs;
143
144
 
144
145
  const presetConfigs = {
146
+ astro: {
147
+ optional: ["PUBLIC_SITE_URL"],
148
+ ignore: ["BASE_URL", "DEV", "MODE", "PROD", "SSR"]
149
+ },
145
150
  docker: {
146
151
  optional: ["COMPOSE_PROJECT_NAME"],
147
152
  ignore: ["HOSTNAME"]
@@ -150,6 +155,12 @@ const presetConfigs = {
150
155
  optional: ["NEXT_PUBLIC_APP_URL"],
151
156
  ignore: ["NEXT_RUNTIME"]
152
157
  },
158
+ nuxt: {
159
+ optional: ["NUXT_PUBLIC_API_BASE"]
160
+ },
161
+ sveltekit: {
162
+ optional: ["PUBLIC_BASE_URL"]
163
+ },
153
164
  vercel: {
154
165
  ignore: ["VERCEL", "VERCEL_ENV", "VERCEL_URL", "VERCEL_BRANCH_URL", "VERCEL_PROJECT_PRODUCTION_URL", "VERCEL_REGION"]
155
166
  },
@@ -160,13 +171,18 @@ const presetConfigs = {
160
171
  } satisfies Record<string, Partial<StarterConfig>>;
161
172
 
162
173
  const availablePresetNames = Object.keys(presetConfigs).sort();
163
- const availablePresetList = availablePresetNames.join(", ");
174
+ const availablePresetList = ["auto", ...availablePresetNames].join(", ");
164
175
 
165
176
  type InitFile = {
166
177
  content: string;
167
178
  path: string;
168
179
  };
169
180
 
181
+ type PresetResolution = {
182
+ config?: Partial<StarterConfig>;
183
+ message?: string;
184
+ };
185
+
170
186
  const require = createRequire(import.meta.url);
171
187
  const cliPackage = require("../package.json") as { version: string };
172
188
  export const cliVersion = cliPackage.version;
@@ -213,12 +229,13 @@ export async function runInit(
213
229
  ): Promise<void> {
214
230
  const rootDir = dependencies.resolvePath(projectPath);
215
231
  const result = await dependencies.scanProject({ rootDir });
216
- const preset = resolvePreset(options.preset, dependencies);
217
- if (options.preset && !preset) return;
232
+ const preset = await resolvePreset(rootDir, options.preset, dependencies);
233
+ if (options.preset && !preset.config && options.preset !== "auto") return;
234
+ if (preset.message) dependencies.log(preset.message);
218
235
  const existingEnvExample = options.envExample && options.force
219
236
  ? await readOptionalText(dependencies.resolvePath(rootDir, ".env.example"), dependencies)
220
237
  : undefined;
221
- const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample, preset);
238
+ const files = buildInitFiles(rootDir, result, Boolean(options.envExample), dependencies.resolvePath, existingEnvExample, preset.config);
222
239
 
223
240
  if (options.dryRun) {
224
241
  for (const file of files) {
@@ -282,14 +299,78 @@ export function buildInitFiles(
282
299
  return files;
283
300
  }
284
301
 
285
- function resolvePreset(name: string | undefined, dependencies: CliDependencies): Partial<StarterConfig> | undefined {
286
- if (!name) return undefined;
302
+ async function resolvePreset(rootDir: string, name: string | undefined, dependencies: CliDependencies): Promise<PresetResolution> {
303
+ if (!name) return {};
304
+ if (name === "auto") {
305
+ return detectPreset(rootDir, dependencies);
306
+ }
287
307
  if (name in presetConfigs) {
288
- return presetConfigs[name as PresetName];
308
+ return { config: presetConfigs[name as PresetName] };
289
309
  }
290
310
  dependencies.error(`Unknown preset "${name}". Available presets: ${availablePresetList}.`);
291
311
  dependencies.exit(1);
292
- return undefined;
312
+ return {};
313
+ }
314
+
315
+ async function detectPreset(rootDir: string, dependencies: CliDependencies): Promise<PresetResolution> {
316
+ const packageJson = await readPackageJson(rootDir, dependencies);
317
+ const packages = new Set(Object.keys({
318
+ ...packageJson?.dependencies,
319
+ ...packageJson?.devDependencies
320
+ }));
321
+
322
+ const packageDetections: Array<[PresetName, string, string]> = [
323
+ ["nextjs", "next", "dependency \"next\""],
324
+ ["astro", "astro", "dependency \"astro\""],
325
+ ["nuxt", "nuxt", "dependency \"nuxt\""],
326
+ ["sveltekit", "@sveltejs/kit", "dependency \"@sveltejs/kit\""],
327
+ ["vite", "vite", "dependency \"vite\""]
328
+ ];
329
+ for (const [presetName, packageName, reason] of packageDetections) {
330
+ if (packages.has(packageName)) {
331
+ return detectedPreset(presetName, reason);
332
+ }
333
+ }
334
+
335
+ const configDetections: Array<[PresetName, string[]]> = [
336
+ ["nextjs", ["next.config.js", "next.config.mjs", "next.config.ts"]],
337
+ ["astro", ["astro.config.js", "astro.config.mjs", "astro.config.ts"]],
338
+ ["nuxt", ["nuxt.config.js", "nuxt.config.mjs", "nuxt.config.ts"]],
339
+ ["sveltekit", ["svelte.config.js", "svelte.config.ts"]],
340
+ ["vite", ["vite.config.js", "vite.config.mjs", "vite.config.ts"]]
341
+ ];
342
+ for (const [presetName, files] of configDetections) {
343
+ for (const file of files) {
344
+ if (await readOptionalText(dependencies.resolvePath(rootDir, file), dependencies) !== undefined) {
345
+ return detectedPreset(presetName, `found ${file}`);
346
+ }
347
+ }
348
+ }
349
+
350
+ return { message: "No framework preset detected. Using the base config." };
351
+ }
352
+
353
+ function detectedPreset(name: PresetName, reason: string): PresetResolution {
354
+ return {
355
+ config: presetConfigs[name],
356
+ message: `Detected preset: ${name}\nReason: ${reason}`
357
+ };
358
+ }
359
+
360
+ async function readPackageJson(rootDir: string, dependencies: CliDependencies): Promise<{
361
+ dependencies?: Record<string, string>;
362
+ devDependencies?: Record<string, string>;
363
+ } | undefined> {
364
+ const content = await readOptionalText(dependencies.resolvePath(rootDir, "package.json"), dependencies);
365
+ if (content === undefined) return undefined;
366
+ try {
367
+ return JSON.parse(content) as {
368
+ dependencies?: Record<string, string>;
369
+ devDependencies?: Record<string, string>;
370
+ };
371
+ } catch {
372
+ return undefined;
373
+ }
293
374
  }
294
375
 
295
376
  function mergeUnique(base: string[], extra: readonly string[]): string[] {