@oorabona/release-it-preset 0.8.1 → 0.9.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/README.md +129 -2
- package/bin/cli.js +122 -9
- package/bin/validators.js +64 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,6 +16,11 @@ Shared [release-it](https://github.com/release-it/release-it) configuration and
|
|
|
16
16
|
- [Quick Start](#quick-start)
|
|
17
17
|
- [Available Configurations](#available-configurations)
|
|
18
18
|
- [CLI Usage](#cli-usage)
|
|
19
|
+
- [Zero-Config Mode (Auto-Detection)](#zero-config-mode-auto-detection)
|
|
20
|
+
- [Preset Selection Mode](#preset-selection-mode)
|
|
21
|
+
- [Passthrough Mode (Custom Config Override)](#passthrough-mode-custom-config-override)
|
|
22
|
+
- [Monorepo Support](#monorepo-support)
|
|
23
|
+
- [Utility Commands](#utility-commands)
|
|
19
24
|
- [Scripts](#scripts)
|
|
20
25
|
- [Environment Variables](#environment-variables)
|
|
21
26
|
- [Configuration Override](#configuration-override)
|
|
@@ -37,6 +42,9 @@ Shared [release-it](https://github.com/release-it/release-it) configuration and
|
|
|
37
42
|
- 🔄 Republish and retry mechanisms for failed releases
|
|
38
43
|
- ⚡ Hotfix release support
|
|
39
44
|
- 🎯 Environment variable configuration
|
|
45
|
+
- 🔍 **NEW v0.9.0:** Zero-config auto-detection mode
|
|
46
|
+
- 🏢 **NEW v0.9.0:** Monorepo support with parent directory config references
|
|
47
|
+
- ⚙️ **NEW v0.9.0:** Passthrough mode for custom config files
|
|
40
48
|
|
|
41
49
|
## Installation
|
|
42
50
|
|
|
@@ -331,9 +339,42 @@ Features:
|
|
|
331
339
|
|
|
332
340
|
## CLI Usage
|
|
333
341
|
|
|
334
|
-
The package provides a `release-it-preset` CLI with
|
|
342
|
+
The package provides a `release-it-preset` CLI with four operating modes:
|
|
335
343
|
|
|
336
|
-
|
|
344
|
+
1. **Zero-Config Mode** (auto-detection) - No arguments needed
|
|
345
|
+
2. **Preset Selection Mode** - Specify which preset to use
|
|
346
|
+
3. **Passthrough Mode** - Direct config file override
|
|
347
|
+
4. **Utility Mode** - Helper commands
|
|
348
|
+
|
|
349
|
+
### Zero-Config Mode (Auto-Detection)
|
|
350
|
+
|
|
351
|
+
**NEW in v0.9.0** - The CLI can automatically detect which preset to use from your `.release-it.json`:
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
# Just run release-it-preset with no arguments
|
|
355
|
+
pnpm release-it-preset
|
|
356
|
+
|
|
357
|
+
# 🔍 Auto-detected preset: default
|
|
358
|
+
# ✅ Config validated: preset "default"
|
|
359
|
+
# 📝 Using: /path/to/.release-it.json
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**How it works:**
|
|
363
|
+
1. CLI reads your `.release-it.json`
|
|
364
|
+
2. Extracts the preset name from the `extends` field
|
|
365
|
+
3. Runs that preset automatically
|
|
366
|
+
|
|
367
|
+
**Requirements:**
|
|
368
|
+
- `.release-it.json` must exist
|
|
369
|
+
- Must have `extends` field like `"@oorabona/release-it-preset/config/default"`
|
|
370
|
+
|
|
371
|
+
**Benefits:**
|
|
372
|
+
- ✅ Shortest command possible
|
|
373
|
+
- ✅ Config file is source of truth
|
|
374
|
+
- ✅ No need to remember preset names
|
|
375
|
+
- ✅ Follows industry standards (ESLint, TypeScript, Prettier)
|
|
376
|
+
|
|
377
|
+
### Preset Selection Mode
|
|
337
378
|
|
|
338
379
|
Run release-it with specific configurations:
|
|
339
380
|
|
|
@@ -350,6 +391,92 @@ pnpm release-it-preset manual-changelog
|
|
|
350
391
|
|
|
351
392
|
All additional arguments are passed through to release-it.
|
|
352
393
|
|
|
394
|
+
### Passthrough Mode (Custom Config Override)
|
|
395
|
+
|
|
396
|
+
**NEW in v0.9.0** - Use a custom config file and bypass preset validation:
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
# Use custom config file
|
|
400
|
+
pnpm release-it-preset --config .release-it-manual.json
|
|
401
|
+
|
|
402
|
+
# 🔀 Passthrough mode: using config .release-it-manual.json
|
|
403
|
+
# Bypassing preset validation - direct release-it invocation
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**Use cases:**
|
|
407
|
+
- **Switching presets occasionally** - Have multiple config files for different scenarios
|
|
408
|
+
- **Monorepo workflows** - Reference shared configs from parent directories
|
|
409
|
+
- **Advanced customization** - Full control over release-it configuration
|
|
410
|
+
|
|
411
|
+
**Example workflow:**
|
|
412
|
+
|
|
413
|
+
```json
|
|
414
|
+
// .release-it.json (default - 95% of time)
|
|
415
|
+
{
|
|
416
|
+
"extends": "@oorabona/release-it-preset/config/default",
|
|
417
|
+
"git": { "requireBranch": "develop" }
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// .release-it-manual.json (rare - 5% of time)
|
|
421
|
+
{
|
|
422
|
+
"extends": "@oorabona/release-it-preset/config/manual-changelog",
|
|
423
|
+
"git": { "requireBranch": "develop" }
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
# Normal release
|
|
429
|
+
pnpm release-it-preset # Auto-detects default
|
|
430
|
+
|
|
431
|
+
# Manual changelog release (rare)
|
|
432
|
+
pnpm release-it-preset --config .release-it-manual.json
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Benefits:**
|
|
436
|
+
- ✅ No need to edit `.release-it.json` to switch presets
|
|
437
|
+
- ✅ Config files are explicit and version-controlled
|
|
438
|
+
- ✅ Works with monorepo parent directory references
|
|
439
|
+
|
|
440
|
+
### Monorepo Support
|
|
441
|
+
|
|
442
|
+
**NEW in v0.9.0** - Parent directory config references are now supported:
|
|
443
|
+
|
|
444
|
+
```bash
|
|
445
|
+
# Monorepo structure
|
|
446
|
+
/my-monorepo/
|
|
447
|
+
├── .release-it-base.json # Shared configuration
|
|
448
|
+
├── packages/
|
|
449
|
+
│ ├── core/
|
|
450
|
+
│ │ └── .release-it.json # extends: ../../.release-it-base.json
|
|
451
|
+
│ └── utils/
|
|
452
|
+
│ └── .release-it.json # extends: ../../.release-it-base.json
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
```json
|
|
456
|
+
// packages/core/.release-it.json
|
|
457
|
+
{
|
|
458
|
+
"extends": [
|
|
459
|
+
"../../.release-it-base.json", // ✅ Parent reference allowed!
|
|
460
|
+
"@oorabona/release-it-preset/config/default"
|
|
461
|
+
]
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
**Security validation:**
|
|
466
|
+
- ✅ Parent directory references (`../`) supported (up to 5 levels)
|
|
467
|
+
- ✅ Config file extension whitelist (`.json`, `.js`, `.cjs`, `.mjs`, `.yaml`, `.yml`, `.toml`)
|
|
468
|
+
- ✅ File existence validation
|
|
469
|
+
- ❌ Absolute paths blocked (use relative paths)
|
|
470
|
+
- ❌ Excessive traversal blocked (max `../../../../../../`)
|
|
471
|
+
|
|
472
|
+
**Why this is safe:**
|
|
473
|
+
- Config files are trusted code (developer controls the repository)
|
|
474
|
+
- Industry standard pattern (TypeScript, ESLint, Prettier all allow `../`)
|
|
475
|
+
- Multiple validation layers prevent abuse
|
|
476
|
+
- No privilege escalation in CLI tool context
|
|
477
|
+
|
|
478
|
+
See [examples/monorepo-workflow.md](examples/monorepo-workflow.md) for complete monorepo guide.
|
|
479
|
+
|
|
353
480
|
### Utility Commands
|
|
354
481
|
|
|
355
482
|
Helper commands for project setup and maintenance:
|
package/bin/cli.js
CHANGED
|
@@ -27,7 +27,7 @@ import { spawn } from 'node:child_process';
|
|
|
27
27
|
import { existsSync, readFileSync } from 'node:fs';
|
|
28
28
|
import { fileURLToPath } from 'node:url';
|
|
29
29
|
import { dirname, join } from 'node:path';
|
|
30
|
-
import { validateConfigName, validateUtilityCommand, sanitizeArgs } from './validators.js';
|
|
30
|
+
import { validateConfigName, validateUtilityCommand, sanitizeArgs, validateConfigPath } from './validators.js';
|
|
31
31
|
|
|
32
32
|
const __filename = fileURLToPath(import.meta.url);
|
|
33
33
|
const __dirname = dirname(__filename);
|
|
@@ -54,7 +54,14 @@ const UTILITY_COMMANDS = {
|
|
|
54
54
|
|
|
55
55
|
function showHelp() {
|
|
56
56
|
console.log(`
|
|
57
|
-
Usage: release-it-preset
|
|
57
|
+
Usage: release-it-preset [command] [...args]
|
|
58
|
+
release-it-preset --config <file> [...args]
|
|
59
|
+
|
|
60
|
+
CLI Modes:
|
|
61
|
+
1. Auto-detection (no command) - Reads preset from .release-it.json
|
|
62
|
+
2. Preset selection - Specify preset command
|
|
63
|
+
3. Passthrough (--config) - Direct config file, bypass validation
|
|
64
|
+
4. Utility commands - Helper scripts
|
|
58
65
|
|
|
59
66
|
Release Commands:
|
|
60
67
|
default Full release with changelog, git, GitHub, and npm
|
|
@@ -73,19 +80,28 @@ Utility Commands:
|
|
|
73
80
|
check-pr Evaluate PR hygiene (branch diff, changelog status, conventions)
|
|
74
81
|
retry-publish-preflight Run retry publish safety checks without executing release
|
|
75
82
|
|
|
83
|
+
Passthrough Mode:
|
|
84
|
+
--config <file> Use custom config file, bypass preset validation
|
|
85
|
+
|
|
76
86
|
Examples:
|
|
87
|
+
# Zero-config (auto-detect from .release-it.json)
|
|
88
|
+
release-it-preset
|
|
89
|
+
|
|
77
90
|
# Release commands
|
|
78
91
|
release-it-preset default --dry-run
|
|
79
92
|
release-it-preset hotfix --verbose
|
|
80
93
|
release-it-preset changelog-only --ci
|
|
81
94
|
|
|
95
|
+
# Passthrough mode (custom config)
|
|
96
|
+
release-it-preset --config .release-it-manual.json
|
|
97
|
+
|
|
98
|
+
# Monorepo (parent config reference)
|
|
99
|
+
release-it-preset --config ../../.release-it-base.json
|
|
100
|
+
|
|
82
101
|
# Utility commands
|
|
83
102
|
release-it-preset init
|
|
84
103
|
release-it-preset update
|
|
85
104
|
release-it-preset validate
|
|
86
|
-
release-it-preset check
|
|
87
|
-
release-it-preset check-pr
|
|
88
|
-
release-it-preset retry-publish-preflight
|
|
89
105
|
|
|
90
106
|
For release-it options, see: https://github.com/release-it/release-it
|
|
91
107
|
For environment variables, see: https://github.com/oorabona/release-it-preset#environment-variables
|
|
@@ -138,7 +154,7 @@ function handleReleaseCommand(configName, args) {
|
|
|
138
154
|
}
|
|
139
155
|
|
|
140
156
|
// Validate extends matches CLI preset
|
|
141
|
-
const extendsMatch = userConfig.extends.match(/@oorabona\/release-it-preset\/config\/(\w+)/);
|
|
157
|
+
const extendsMatch = userConfig.extends.match(/@oorabona\/release-it-preset\/config\/([\w-]+)/);
|
|
142
158
|
const extendsPreset = extendsMatch?.[1];
|
|
143
159
|
|
|
144
160
|
if (extendsPreset && extendsPreset !== configName) {
|
|
@@ -237,10 +253,53 @@ function handleUtilityCommand(commandName, args) {
|
|
|
237
253
|
});
|
|
238
254
|
}
|
|
239
255
|
|
|
256
|
+
function passthroughToReleaseIt(args) {
|
|
257
|
+
// Extract --config value
|
|
258
|
+
const configIndex = args.indexOf('--config');
|
|
259
|
+
if (configIndex === -1 || configIndex === args.length - 1) {
|
|
260
|
+
console.error('❌ --config requires a file path argument\n');
|
|
261
|
+
console.error('Usage: release-it-preset --config <file>');
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const configPath = args[configIndex + 1];
|
|
266
|
+
|
|
267
|
+
// Security validation
|
|
268
|
+
try {
|
|
269
|
+
validateConfigPath(configPath);
|
|
270
|
+
sanitizeArgs(args);
|
|
271
|
+
|
|
272
|
+
console.log(`🔀 Passthrough mode: using config ${configPath}`);
|
|
273
|
+
console.log(` Bypassing preset validation - direct release-it invocation\n`);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error(`❌ Configuration validation failed: ${error.message}`);
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Delegate to release-it
|
|
280
|
+
const releaseItCommand = 'release-it';
|
|
281
|
+
const child = spawn(releaseItCommand, args, {
|
|
282
|
+
stdio: 'inherit',
|
|
283
|
+
shell: false, // Security: prevent command injection
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
child.on('error', (error) => {
|
|
287
|
+
console.error(`❌ Failed to start release-it: ${error.message}`);
|
|
288
|
+
console.error(`\nMake sure release-it is installed:`);
|
|
289
|
+
console.error(` pnpm add -D release-it`);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
child.on('close', (code) => {
|
|
294
|
+
process.exit(code ?? 0);
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
240
298
|
function main() {
|
|
241
299
|
const args = process.argv.slice(2);
|
|
242
300
|
|
|
243
|
-
|
|
301
|
+
// Handle --help
|
|
302
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
244
303
|
showHelp();
|
|
245
304
|
process.exit(0);
|
|
246
305
|
}
|
|
@@ -248,13 +307,66 @@ function main() {
|
|
|
248
307
|
const command = args[0];
|
|
249
308
|
const commandArgs = args.slice(1);
|
|
250
309
|
|
|
251
|
-
// Check
|
|
310
|
+
// Check for conflicting arguments (preset command + --config)
|
|
311
|
+
if (args.includes('--config') && RELEASE_CONFIGS[command]) {
|
|
312
|
+
console.error('❌ Conflicting arguments detected!\n');
|
|
313
|
+
console.error(' You specified both a preset command and --config flag.');
|
|
314
|
+
console.error('');
|
|
315
|
+
console.error(' Either:');
|
|
316
|
+
console.error(` 1. Use preset: release-it-preset ${command}`);
|
|
317
|
+
console.error(` 2. Use config: release-it-preset --config <file>`);
|
|
318
|
+
console.error('');
|
|
319
|
+
console.error(' Do not mix both approaches.');
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// MODE 1: Passthrough - Direct release-it with custom config
|
|
324
|
+
if (args.includes('--config')) {
|
|
325
|
+
passthroughToReleaseIt(args);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// MODE 2: Auto-detection - No arguments, read preset from .release-it.json
|
|
330
|
+
if (args.length === 0) {
|
|
331
|
+
const userConfigPath = join(process.cwd(), '.release-it.json');
|
|
332
|
+
|
|
333
|
+
if (!existsSync(userConfigPath)) {
|
|
334
|
+
console.error('❌ No command specified and no .release-it.json found\n');
|
|
335
|
+
console.error('Either:');
|
|
336
|
+
console.error(' 1. Run: release-it-preset init');
|
|
337
|
+
console.error(' 2. Run: release-it-preset <command>\n');
|
|
338
|
+
showHelp();
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
try {
|
|
343
|
+
const config = JSON.parse(readFileSync(userConfigPath, 'utf8'));
|
|
344
|
+
const extendsMatch = config.extends?.match(/@oorabona\/release-it-preset\/config\/([\w-]+)/);
|
|
345
|
+
|
|
346
|
+
if (!extendsMatch) {
|
|
347
|
+
console.error('❌ .release-it.json does not extend a known preset\n');
|
|
348
|
+
console.error('Expected extends field like:');
|
|
349
|
+
console.error(' "@oorabona/release-it-preset/config/default"\n');
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const preset = extendsMatch[1];
|
|
354
|
+
console.log(`🔍 Auto-detected preset: ${preset}`);
|
|
355
|
+
handleReleaseCommand(preset, []);
|
|
356
|
+
return;
|
|
357
|
+
} catch (error) {
|
|
358
|
+
console.error(`❌ Error reading .release-it.json: ${error.message}`);
|
|
359
|
+
process.exit(1);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// MODE 3: Preset command (existing logic)
|
|
252
364
|
if (RELEASE_CONFIGS[command]) {
|
|
253
365
|
handleReleaseCommand(command, commandArgs);
|
|
254
366
|
return;
|
|
255
367
|
}
|
|
256
368
|
|
|
257
|
-
//
|
|
369
|
+
// MODE 4: Utility command (existing logic)
|
|
258
370
|
if (UTILITY_COMMANDS[command]) {
|
|
259
371
|
handleUtilityCommand(command, commandArgs);
|
|
260
372
|
return;
|
|
@@ -264,6 +376,7 @@ function main() {
|
|
|
264
376
|
console.error(`❌ Unknown command: ${command}`);
|
|
265
377
|
console.error(`\nAvailable release configs: ${Object.keys(RELEASE_CONFIGS).join(', ')}`);
|
|
266
378
|
console.error(`Available utility commands: ${Object.keys(UTILITY_COMMANDS).join(', ')}`);
|
|
379
|
+
console.error(`\nFor direct config file usage: release-it-preset --config <file>`);
|
|
267
380
|
console.error(`\nRun 'release-it-preset --help' for more information.`);
|
|
268
381
|
process.exit(1);
|
|
269
382
|
}
|
package/bin/validators.js
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* - Fail securely
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { existsSync, statSync } from 'node:fs';
|
|
14
|
+
import { extname, isAbsolute, resolve } from 'node:path';
|
|
15
|
+
|
|
13
16
|
/**
|
|
14
17
|
* Validates that a config name is in the allowed list
|
|
15
18
|
*
|
|
@@ -96,26 +99,75 @@ export function sanitizeArgs(args) {
|
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
/**
|
|
99
|
-
* Validates
|
|
102
|
+
* Validates config file paths with monorepo support
|
|
103
|
+
*
|
|
104
|
+
* Security approach: Defense in depth with multiple validation layers
|
|
105
|
+
* - Whitelist allowed file extensions
|
|
106
|
+
* - Limit parent directory traversal depth (monorepo support)
|
|
107
|
+
* - Reject absolute paths from CLI
|
|
108
|
+
* - Validate file existence
|
|
109
|
+
*
|
|
110
|
+
* Why we allow ".." (parent directory references):
|
|
111
|
+
* - Standard pattern in monorepos (TypeScript, ESLint, Prettier all allow it)
|
|
112
|
+
* - Developer controls the environment (config files are trusted code boundary)
|
|
113
|
+
* - No privilege escalation possible in CLI tool context
|
|
114
|
+
* - Multiple validation layers prevent abuse
|
|
100
115
|
*
|
|
101
|
-
* @param {string}
|
|
102
|
-
* @throws {Error} If
|
|
103
|
-
* @returns {string}
|
|
116
|
+
* @param {string} configPath - The config file path to validate (relative or absolute)
|
|
117
|
+
* @throws {Error} If validation fails (invalid extension, too deep, missing file, etc.)
|
|
118
|
+
* @returns {string} Absolute path to validated config file
|
|
104
119
|
*/
|
|
105
|
-
export function
|
|
106
|
-
//
|
|
107
|
-
|
|
120
|
+
export function validateConfigPath(configPath) {
|
|
121
|
+
// 1. Whitelist config file extensions (defense in depth)
|
|
122
|
+
const allowedExtensions = ['.json', '.js', '.cjs', '.mjs', '.yaml', '.yml', '.toml'];
|
|
123
|
+
const ext = extname(configPath).toLowerCase();
|
|
124
|
+
|
|
125
|
+
if (!allowedExtensions.includes(ext)) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Invalid config file extension: "${ext}"\n` +
|
|
128
|
+
`Allowed: ${allowedExtensions.join(', ')}\n` +
|
|
129
|
+
`This restriction prevents reading non-config files.`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 2. Limit parent directory traversal depth (max 5 levels for monorepo support)
|
|
134
|
+
const upwardLevels = (configPath.match(/\.\.\//g) || []).length;
|
|
135
|
+
if (upwardLevels > 5) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Too many parent directory references: ${upwardLevels}\n` +
|
|
138
|
+
`Maximum allowed: 5 levels (../../../../../../)\n` +
|
|
139
|
+
`This prevents accidental access to system directories.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 3. Reject absolute paths from CLI (could reference system files)
|
|
144
|
+
if (isAbsolute(configPath)) {
|
|
145
|
+
throw new Error(
|
|
146
|
+
`Absolute paths not allowed: "${configPath}"\n` +
|
|
147
|
+
`Use relative paths from your project directory.\n` +
|
|
148
|
+
`For monorepos, use parent references like ../../config.json`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 4. Resolve to absolute path and validate file exists
|
|
153
|
+
const resolved = resolve(process.cwd(), configPath);
|
|
154
|
+
|
|
155
|
+
if (!existsSync(resolved)) {
|
|
108
156
|
throw new Error(
|
|
109
|
-
`
|
|
157
|
+
`Config file not found: "${configPath}"\n` +
|
|
158
|
+
`Resolved to: ${resolved}\n` +
|
|
159
|
+
`Check that the file exists and the path is correct.`
|
|
110
160
|
);
|
|
111
161
|
}
|
|
112
162
|
|
|
113
|
-
//
|
|
114
|
-
|
|
163
|
+
// 5. Validate it's a file (not a directory or symlink to avoid confusion)
|
|
164
|
+
const stats = statSync(resolved);
|
|
165
|
+
if (!stats.isFile()) {
|
|
115
166
|
throw new Error(
|
|
116
|
-
`
|
|
167
|
+
`Config path must be a file, not a directory: "${configPath}"\n` +
|
|
168
|
+
`Resolved to: ${resolved}`
|
|
117
169
|
);
|
|
118
170
|
}
|
|
119
171
|
|
|
120
|
-
return
|
|
172
|
+
return resolved;
|
|
121
173
|
}
|