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 +74 -10
- package/package.json +2 -2
- package/src/index.ts +90 -9
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
|
|
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
|
|
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.
|
|
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.
|
|
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):
|
|
286
|
-
if (!name) return
|
|
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
|
|
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[] {
|