@oorabona/release-it-preset 0.8.0 → 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 +184 -67
- package/bin/cli.js +160 -39
- 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:
|
|
@@ -564,16 +691,16 @@ pnpm release
|
|
|
564
691
|
|
|
565
692
|
## Configuration Modes
|
|
566
693
|
|
|
567
|
-
The preset supports
|
|
694
|
+
The preset supports two configuration modes:
|
|
568
695
|
|
|
569
|
-
### Mode 1:
|
|
696
|
+
### Mode 1: Direct Preset Usage (No Config File)
|
|
570
697
|
|
|
571
|
-
**When to use:** Simple projects
|
|
698
|
+
**When to use:** Simple projects, trust preset defaults, or customize only via environment variables
|
|
572
699
|
|
|
573
700
|
Don't create `.release-it.json`. Just run the CLI:
|
|
574
701
|
|
|
575
702
|
```bash
|
|
576
|
-
pnpm release-it-preset
|
|
703
|
+
pnpm release-it-preset default
|
|
577
704
|
```
|
|
578
705
|
|
|
579
706
|
All configuration comes from the preset and environment variables.
|
|
@@ -582,23 +709,22 @@ All configuration comes from the preset and environment variables.
|
|
|
582
709
|
- ✅ Zero config files
|
|
583
710
|
- ✅ Consistent behavior across projects
|
|
584
711
|
- ✅ Easy to understand
|
|
712
|
+
- ✅ Perfect for getting started
|
|
585
713
|
|
|
586
714
|
---
|
|
587
715
|
|
|
588
|
-
### Mode 2:
|
|
716
|
+
### Mode 2: Preset + User Overrides (Recommended)
|
|
589
717
|
|
|
590
|
-
**When to use:** Customize specific options while
|
|
718
|
+
**When to use:** Customize specific options while keeping preset defaults
|
|
591
719
|
|
|
592
|
-
Create `.release-it.json` **
|
|
720
|
+
Create `.release-it.json` **WITH the `extends` field**:
|
|
593
721
|
|
|
594
722
|
```json
|
|
595
723
|
{
|
|
724
|
+
"extends": "@oorabona/release-it-preset/config/default",
|
|
596
725
|
"git": {
|
|
597
726
|
"requireBranch": "master",
|
|
598
727
|
"commitMessage": "chore: release v${version}"
|
|
599
|
-
},
|
|
600
|
-
"npm": {
|
|
601
|
-
"publish": true
|
|
602
728
|
}
|
|
603
729
|
}
|
|
604
730
|
```
|
|
@@ -606,24 +732,26 @@ Create `.release-it.json` **WITHOUT the `extends` field**:
|
|
|
606
732
|
Run with CLI preset:
|
|
607
733
|
|
|
608
734
|
```bash
|
|
609
|
-
pnpm release-it-preset
|
|
735
|
+
pnpm release-it-preset default
|
|
610
736
|
```
|
|
611
737
|
|
|
612
738
|
**How it works:**
|
|
613
|
-
-
|
|
614
|
-
- release-it merges your overrides on top
|
|
739
|
+
- The `extends` field loads the preset
|
|
740
|
+
- release-it merges your overrides on top via c12
|
|
615
741
|
- **Your values take precedence** over preset defaults
|
|
742
|
+
- CLI validates that `extends` matches the command
|
|
616
743
|
|
|
617
744
|
**Pros:**
|
|
618
|
-
- ✅ **Recommended
|
|
619
|
-
- ✅
|
|
620
|
-
- ✅
|
|
621
|
-
- ✅
|
|
745
|
+
- ✅ **Recommended for customization**
|
|
746
|
+
- ✅ Declarative config with explicit preset
|
|
747
|
+
- ✅ Industry standard pattern (like ESLint, TypeScript)
|
|
748
|
+
- ✅ Guaranteed config merging via release-it's c12
|
|
622
749
|
|
|
623
|
-
**Example
|
|
750
|
+
**Example:** Using `hotfix` preset but release from `master` instead of `main`:
|
|
624
751
|
|
|
625
752
|
```json
|
|
626
753
|
{
|
|
754
|
+
"extends": "@oorabona/release-it-preset/config/hotfix",
|
|
627
755
|
"git": {
|
|
628
756
|
"requireBranch": "master"
|
|
629
757
|
}
|
|
@@ -631,84 +759,73 @@ pnpm release-it-preset hotfix
|
|
|
631
759
|
```
|
|
632
760
|
|
|
633
761
|
```bash
|
|
634
|
-
pnpm release-it-preset hotfix
|
|
762
|
+
pnpm release-it-preset hotfix
|
|
635
763
|
```
|
|
636
764
|
|
|
637
765
|
---
|
|
638
766
|
|
|
639
|
-
###
|
|
767
|
+
### Configuration Validation
|
|
640
768
|
|
|
641
|
-
|
|
769
|
+
The CLI validates your `.release-it.json` to prevent misconfigurations:
|
|
642
770
|
|
|
643
|
-
|
|
771
|
+
#### Error 1: Missing `extends` field
|
|
644
772
|
|
|
645
|
-
```
|
|
773
|
+
```bash
|
|
774
|
+
# .release-it.json without extends:
|
|
646
775
|
{
|
|
647
|
-
"
|
|
648
|
-
"git": {
|
|
649
|
-
"commitMessage": "custom: ${version}"
|
|
650
|
-
}
|
|
776
|
+
"git": { "requireBranch": "master" }
|
|
651
777
|
}
|
|
652
|
-
```
|
|
653
778
|
|
|
654
|
-
|
|
779
|
+
# Running:
|
|
780
|
+
pnpm release-it-preset default
|
|
655
781
|
|
|
656
|
-
|
|
657
|
-
|
|
782
|
+
# ❌ Configuration error!
|
|
783
|
+
# .release-it.json is missing the required "extends" field.
|
|
784
|
+
#
|
|
785
|
+
# Without "extends", your config won't merge with the preset.
|
|
786
|
+
# This means you'll get release-it defaults instead of preset defaults.
|
|
787
|
+
#
|
|
788
|
+
# Fix by adding this to .release-it.json:
|
|
789
|
+
# {
|
|
790
|
+
# "extends": "@oorabona/release-it-preset/config/default",
|
|
791
|
+
# ...your overrides
|
|
792
|
+
# }
|
|
658
793
|
```
|
|
659
794
|
|
|
660
|
-
**
|
|
661
|
-
- The `extends` field locks the preset
|
|
662
|
-
- CLI command **must match** the preset in `extends`
|
|
663
|
-
- Mismatch triggers an error
|
|
664
|
-
|
|
665
|
-
**Pros:**
|
|
666
|
-
- ✅ Prevents accidental use of wrong presets
|
|
667
|
-
- ✅ Explicit preset declaration in config
|
|
795
|
+
**Why `extends` is required:** Without it, release-it only loads your config file and uses release-it's own defaults. The preset is never loaded, so you lose important defaults like `npm.publish: false`.
|
|
668
796
|
|
|
669
|
-
|
|
670
|
-
- ⚠️ Less flexible (preset locked in file)
|
|
671
|
-
- ⚠️ Requires updating `extends` to switch presets
|
|
672
|
-
|
|
673
|
-
---
|
|
674
|
-
|
|
675
|
-
### Configuration Error Handling
|
|
676
|
-
|
|
677
|
-
If your `.release-it.json` has an `extends` field that doesn't match the CLI command, you'll get a clear error:
|
|
797
|
+
#### Error 2: Preset mismatch
|
|
678
798
|
|
|
679
799
|
```bash
|
|
680
|
-
#
|
|
800
|
+
# .release-it.json extends "default":
|
|
801
|
+
{
|
|
802
|
+
"extends": "@oorabona/release-it-preset/config/default"
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
# But you run:
|
|
681
806
|
pnpm release-it-preset hotfix
|
|
682
807
|
|
|
683
808
|
# ❌ Configuration mismatch error!
|
|
684
809
|
# CLI preset: hotfix
|
|
685
810
|
# .release-it.json extends: default
|
|
686
811
|
#
|
|
687
|
-
#
|
|
688
|
-
#
|
|
689
|
-
#
|
|
690
|
-
#
|
|
691
|
-
# 2. Run: release-it-preset default
|
|
692
|
-
# → Use the preset specified in your config file
|
|
693
|
-
#
|
|
694
|
-
# 3. Update .release-it.json extends to: "@oorabona/release-it-preset/config/hotfix"
|
|
695
|
-
# → Match your config file to the CLI command
|
|
812
|
+
# Either:
|
|
813
|
+
# 1. Run: release-it-preset default
|
|
814
|
+
# 2. Update .release-it.json extends to: "@oorabona/release-it-preset/config/hotfix"
|
|
696
815
|
```
|
|
697
816
|
|
|
698
|
-
This prevents silent misconfigurations where the wrong preset runs unexpectedly.
|
|
699
|
-
|
|
700
817
|
---
|
|
701
818
|
|
|
702
819
|
### Which Mode Should I Use?
|
|
703
820
|
|
|
704
821
|
| Scenario | Recommended Mode |
|
|
705
822
|
|----------|------------------|
|
|
706
|
-
|
|
|
707
|
-
| Customize branch/commit
|
|
708
|
-
|
|
|
709
|
-
| Monorepo with
|
|
823
|
+
| Quick start, minimal config | **Mode 1** (No config file) |
|
|
824
|
+
| Customize branch/commit/hooks | **Mode 2** (Config with extends) |
|
|
825
|
+
| Environment-only customization | **Mode 1** (Use env vars) |
|
|
826
|
+
| Monorepo with per-package config | **Mode 2** (Each package has own config) |
|
|
710
827
|
|
|
711
|
-
**
|
|
828
|
+
**Use Mode 1 to get started, switch to Mode 2 when you need customization.**
|
|
712
829
|
|
|
713
830
|
## Borrowing Scripts & Workflows
|
|
714
831
|
|
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
|
|
@@ -119,36 +135,44 @@ function handleReleaseCommand(configName, args) {
|
|
|
119
135
|
|
|
120
136
|
const expectedExtends = `@oorabona/release-it-preset/config/${configName}`;
|
|
121
137
|
|
|
122
|
-
if (userConfig.extends) {
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
console.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
console.
|
|
149
|
-
console.
|
|
150
|
-
console.
|
|
138
|
+
if (!userConfig.extends) {
|
|
139
|
+
// ERROR: extends is required for config merging
|
|
140
|
+
console.error(`\n❌ Configuration error!`);
|
|
141
|
+
console.error(` .release-it.json is missing the required "extends" field.`);
|
|
142
|
+
console.error(``);
|
|
143
|
+
console.error(`Without "extends", your config won't merge with the preset.`);
|
|
144
|
+
console.error(`This means you'll get release-it defaults instead of preset defaults.`);
|
|
145
|
+
console.error(``);
|
|
146
|
+
console.error(`Fix by adding this to .release-it.json:`);
|
|
147
|
+
console.error(` {`);
|
|
148
|
+
console.error(` "extends": "${expectedExtends}",`);
|
|
149
|
+
console.error(` ...your overrides`);
|
|
150
|
+
console.error(` }`);
|
|
151
|
+
console.error(``);
|
|
152
|
+
console.error(`Or remove .release-it.json to use the preset directly.\n`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Validate extends matches CLI preset
|
|
157
|
+
const extendsMatch = userConfig.extends.match(/@oorabona\/release-it-preset\/config\/([\w-]+)/);
|
|
158
|
+
const extendsPreset = extendsMatch?.[1];
|
|
159
|
+
|
|
160
|
+
if (extendsPreset && extendsPreset !== configName) {
|
|
161
|
+
console.error(`\n❌ Configuration mismatch error!`);
|
|
162
|
+
console.error(` CLI preset: ${configName}`);
|
|
163
|
+
console.error(` .release-it.json extends: ${extendsPreset}`);
|
|
164
|
+
console.error(``);
|
|
165
|
+
console.error(`Either:`);
|
|
166
|
+
console.error(` 1. Run: release-it-preset ${extendsPreset}`);
|
|
167
|
+
console.error(` → Use the preset specified in your config file`);
|
|
168
|
+
console.error(``);
|
|
169
|
+
console.error(` 2. Update .release-it.json extends to: "${expectedExtends}"`);
|
|
170
|
+
console.error(` → Match your config file to the CLI command\n`);
|
|
171
|
+
process.exit(1);
|
|
151
172
|
}
|
|
173
|
+
|
|
174
|
+
console.log(`✅ Config validated: preset "${configName}"`);
|
|
175
|
+
console.log(`📝 Using: ${userConfigPath}\n`);
|
|
152
176
|
} catch (error) {
|
|
153
177
|
if (error instanceof SyntaxError) {
|
|
154
178
|
console.error(`❌ Failed to parse .release-it.json: ${error.message}`);
|
|
@@ -158,12 +182,12 @@ function handleReleaseCommand(configName, args) {
|
|
|
158
182
|
process.exit(1);
|
|
159
183
|
}
|
|
160
184
|
|
|
161
|
-
// Let release-it
|
|
185
|
+
// Let release-it discover .release-it.json and merge via extends
|
|
162
186
|
fullArgs = [...args];
|
|
163
187
|
} else {
|
|
164
188
|
// No user config - use preset directly
|
|
165
189
|
console.log(`📝 Using preset config directly: ${configPath}`);
|
|
166
|
-
console.log(` Tip: Create .release-it.json
|
|
190
|
+
console.log(` Tip: Create .release-it.json with "extends" to customize\n`);
|
|
167
191
|
fullArgs = ['--config', configPath, ...args];
|
|
168
192
|
}
|
|
169
193
|
|
|
@@ -229,10 +253,53 @@ function handleUtilityCommand(commandName, args) {
|
|
|
229
253
|
});
|
|
230
254
|
}
|
|
231
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
|
+
|
|
232
298
|
function main() {
|
|
233
299
|
const args = process.argv.slice(2);
|
|
234
300
|
|
|
235
|
-
|
|
301
|
+
// Handle --help
|
|
302
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
236
303
|
showHelp();
|
|
237
304
|
process.exit(0);
|
|
238
305
|
}
|
|
@@ -240,13 +307,66 @@ function main() {
|
|
|
240
307
|
const command = args[0];
|
|
241
308
|
const commandArgs = args.slice(1);
|
|
242
309
|
|
|
243
|
-
// 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)
|
|
244
364
|
if (RELEASE_CONFIGS[command]) {
|
|
245
365
|
handleReleaseCommand(command, commandArgs);
|
|
246
366
|
return;
|
|
247
367
|
}
|
|
248
368
|
|
|
249
|
-
//
|
|
369
|
+
// MODE 4: Utility command (existing logic)
|
|
250
370
|
if (UTILITY_COMMANDS[command]) {
|
|
251
371
|
handleUtilityCommand(command, commandArgs);
|
|
252
372
|
return;
|
|
@@ -256,6 +376,7 @@ function main() {
|
|
|
256
376
|
console.error(`❌ Unknown command: ${command}`);
|
|
257
377
|
console.error(`\nAvailable release configs: ${Object.keys(RELEASE_CONFIGS).join(', ')}`);
|
|
258
378
|
console.error(`Available utility commands: ${Object.keys(UTILITY_COMMANDS).join(', ')}`);
|
|
379
|
+
console.error(`\nFor direct config file usage: release-it-preset --config <file>`);
|
|
259
380
|
console.error(`\nRun 'release-it-preset --help' for more information.`);
|
|
260
381
|
process.exit(1);
|
|
261
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
|
}
|