@oorabona/release-it-preset 0.6.0 → 0.8.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 +163 -6
- package/bin/cli.js +81 -6
- package/bin/validators.js +121 -0
- package/config/base-config.js +77 -0
- package/config/constants.js +76 -0
- package/config/default.js +5 -23
- package/config/helpers.js +1 -14
- package/config/hotfix.js +9 -24
- package/config/manual-changelog.js +5 -23
- package/config/no-changelog.js +8 -20
- package/config/republish.js +7 -22
- package/config/retry-publish.js +4 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -562,23 +562,154 @@ GIT_REQUIRE_CLEAN="true" \
|
|
|
562
562
|
pnpm release
|
|
563
563
|
```
|
|
564
564
|
|
|
565
|
-
## Configuration
|
|
565
|
+
## Configuration Modes
|
|
566
566
|
|
|
567
|
-
|
|
567
|
+
The preset supports three configuration modes to suit different workflows:
|
|
568
|
+
|
|
569
|
+
### Mode 1: CLI Only (No Config File)
|
|
570
|
+
|
|
571
|
+
**When to use:** Simple projects with minimal customization needs
|
|
572
|
+
|
|
573
|
+
Don't create `.release-it.json`. Just run the CLI:
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
pnpm release-it-preset hotfix
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
All configuration comes from the preset and environment variables.
|
|
580
|
+
|
|
581
|
+
**Pros:**
|
|
582
|
+
- ✅ Zero config files
|
|
583
|
+
- ✅ Consistent behavior across projects
|
|
584
|
+
- ✅ Easy to understand
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
### Mode 2: CLI + User Overrides (Recommended)
|
|
589
|
+
|
|
590
|
+
**When to use:** Customize specific options while using CLI presets
|
|
591
|
+
|
|
592
|
+
Create `.release-it.json` **WITHOUT the `extends` field**:
|
|
568
593
|
|
|
569
594
|
```json
|
|
570
595
|
{
|
|
571
|
-
"extends": "@oorabona/release-it-preset/config/default",
|
|
572
596
|
"git": {
|
|
573
|
-
"requireBranch": "
|
|
597
|
+
"requireBranch": "master",
|
|
574
598
|
"commitMessage": "chore: release v${version}"
|
|
575
599
|
},
|
|
576
|
-
"
|
|
577
|
-
"
|
|
600
|
+
"npm": {
|
|
601
|
+
"publish": true
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
Run with CLI preset:
|
|
607
|
+
|
|
608
|
+
```bash
|
|
609
|
+
pnpm release-it-preset hotfix
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
**How it works:**
|
|
613
|
+
- CLI selects the preset (`hotfix` in this example)
|
|
614
|
+
- release-it merges your overrides on top of the preset
|
|
615
|
+
- **Your values take precedence** over preset defaults
|
|
616
|
+
|
|
617
|
+
**Pros:**
|
|
618
|
+
- ✅ **Recommended approach** for most use cases
|
|
619
|
+
- ✅ CLI controls preset selection
|
|
620
|
+
- ✅ Declarative overrides in config file
|
|
621
|
+
- ✅ No `extends` maintenance needed
|
|
622
|
+
|
|
623
|
+
**Example use case:** You want to use the `hotfix` preset but require releases from `master` instead of `main`:
|
|
624
|
+
|
|
625
|
+
```json
|
|
626
|
+
{
|
|
627
|
+
"git": {
|
|
628
|
+
"requireBranch": "master"
|
|
578
629
|
}
|
|
579
630
|
}
|
|
580
631
|
```
|
|
581
632
|
|
|
633
|
+
```bash
|
|
634
|
+
pnpm release-it-preset hotfix # Uses hotfix preset + your branch override
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
---
|
|
638
|
+
|
|
639
|
+
### Mode 3: File with Extends (Advanced)
|
|
640
|
+
|
|
641
|
+
**When to use:** Lock a specific preset regardless of CLI command
|
|
642
|
+
|
|
643
|
+
Create `.release-it.json` **WITH the `extends` field**:
|
|
644
|
+
|
|
645
|
+
```json
|
|
646
|
+
{
|
|
647
|
+
"extends": "@oorabona/release-it-preset/config/hotfix",
|
|
648
|
+
"git": {
|
|
649
|
+
"commitMessage": "custom: ${version}"
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
Run matching CLI command:
|
|
655
|
+
|
|
656
|
+
```bash
|
|
657
|
+
pnpm release-it-preset hotfix # Must match the extends!
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
**How it works:**
|
|
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
|
|
668
|
+
|
|
669
|
+
**Cons:**
|
|
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:
|
|
678
|
+
|
|
679
|
+
```bash
|
|
680
|
+
# Your config extends "default", but you run:
|
|
681
|
+
pnpm release-it-preset hotfix
|
|
682
|
+
|
|
683
|
+
# ❌ Configuration mismatch error!
|
|
684
|
+
# CLI preset: hotfix
|
|
685
|
+
# .release-it.json extends: default
|
|
686
|
+
#
|
|
687
|
+
# Either:
|
|
688
|
+
# 1. Remove the "extends" field from .release-it.json (recommended)
|
|
689
|
+
# → Your overrides will merge with the CLI preset automatically
|
|
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
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
This prevents silent misconfigurations where the wrong preset runs unexpectedly.
|
|
699
|
+
|
|
700
|
+
---
|
|
701
|
+
|
|
702
|
+
### Which Mode Should I Use?
|
|
703
|
+
|
|
704
|
+
| Scenario | Recommended Mode |
|
|
705
|
+
|----------|------------------|
|
|
706
|
+
| Minimal config, trust defaults | **Mode 1** (CLI only) |
|
|
707
|
+
| Customize branch/commit messages | **Mode 2** (CLI + overrides) |
|
|
708
|
+
| Lock preset for safety | **Mode 3** (File with extends) |
|
|
709
|
+
| Monorepo with different presets per package | **Mode 2** (CLI + overrides) |
|
|
710
|
+
|
|
711
|
+
**Most users should use Mode 2** for the best balance of flexibility and clarity.
|
|
712
|
+
|
|
582
713
|
## Borrowing Scripts & Workflows
|
|
583
714
|
|
|
584
715
|
- The root `package.json` of this repository shows how to expose convenient `pnpm run release:*` shortcuts. Feel free to copy that block into your own project (adjust the commands if you only need a subset).
|
|
@@ -1138,6 +1269,32 @@ graph TB
|
|
|
1138
1269
|
6. **Use CI for publishing** - Let GitHub Actions handle GitHub releases and npm publishing with provenance
|
|
1139
1270
|
7. **Local runs are for prep** - Keep local runs focused on changelog, versioning, and tagging unless you explicitly opt in to publish
|
|
1140
1271
|
|
|
1272
|
+
## Security
|
|
1273
|
+
|
|
1274
|
+
This preset implements OWASP security best practices:
|
|
1275
|
+
|
|
1276
|
+
### Input Validation
|
|
1277
|
+
|
|
1278
|
+
All CLI inputs are validated before execution:
|
|
1279
|
+
- **Whitelist validation**: Config names and commands are validated against allowed lists
|
|
1280
|
+
- **Argument sanitization**: All arguments are checked for dangerous characters (`;`, `&`, `|`, `` ` ``, `$()`, etc.)
|
|
1281
|
+
- **Path traversal protection**: File paths are validated to prevent directory traversal attacks
|
|
1282
|
+
|
|
1283
|
+
### Command Injection Prevention
|
|
1284
|
+
|
|
1285
|
+
- All `spawn()` calls use `shell: false` to prevent command injection
|
|
1286
|
+
- Arguments are passed as arrays, not concatenated strings
|
|
1287
|
+
- No user input is ever executed in a shell context
|
|
1288
|
+
|
|
1289
|
+
### Architecture
|
|
1290
|
+
|
|
1291
|
+
The preset follows SOLID principles:
|
|
1292
|
+
- **Single Responsibility**: Each module has one clear purpose
|
|
1293
|
+
- **DRY**: Shared configuration builders eliminate code duplication
|
|
1294
|
+
- **Dependency Inversion**: User configs have priority over preset defaults
|
|
1295
|
+
|
|
1296
|
+
All 213 unit tests verify functionality and security boundaries.
|
|
1297
|
+
|
|
1141
1298
|
## Troubleshooting
|
|
1142
1299
|
|
|
1143
1300
|
### Changelog not updating
|
package/bin/cli.js
CHANGED
|
@@ -24,8 +24,10 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import { spawn } from 'node:child_process';
|
|
27
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
27
28
|
import { fileURLToPath } from 'node:url';
|
|
28
29
|
import { dirname, join } from 'node:path';
|
|
30
|
+
import { validateConfigName, validateUtilityCommand, sanitizeArgs } from './validators.js';
|
|
29
31
|
|
|
30
32
|
const __filename = fileURLToPath(import.meta.url);
|
|
31
33
|
const __dirname = dirname(__filename);
|
|
@@ -91,19 +93,83 @@ For environment variables, see: https://github.com/oorabona/release-it-preset#en
|
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
function handleReleaseCommand(configName, args) {
|
|
96
|
+
// Validate inputs
|
|
97
|
+
try {
|
|
98
|
+
validateConfigName(configName, new Set(Object.keys(RELEASE_CONFIGS)));
|
|
99
|
+
sanitizeArgs(args);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
console.error(`❌ Validation error: ${error.message}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
94
105
|
const configPath = join(__dirname, '..', RELEASE_CONFIGS[configName]);
|
|
106
|
+
const userConfigPath = join(process.cwd(), '.release-it.json');
|
|
107
|
+
const hasUserConfig = existsSync(userConfigPath);
|
|
95
108
|
|
|
96
|
-
console.log(`🚀 Running release-it with
|
|
97
|
-
console.log(`📝 Config file: ${configPath}`);
|
|
109
|
+
console.log(`🚀 Running release-it with preset: ${configName}`);
|
|
98
110
|
|
|
99
111
|
const releaseItCommand = 'release-it';
|
|
100
|
-
|
|
112
|
+
let fullArgs;
|
|
113
|
+
|
|
114
|
+
if (hasUserConfig) {
|
|
115
|
+
// Read and validate user config
|
|
116
|
+
try {
|
|
117
|
+
const userConfigContent = readFileSync(userConfigPath, 'utf8');
|
|
118
|
+
const userConfig = JSON.parse(userConfigContent);
|
|
119
|
+
|
|
120
|
+
const expectedExtends = `@oorabona/release-it-preset/config/${configName}`;
|
|
121
|
+
|
|
122
|
+
if (userConfig.extends) {
|
|
123
|
+
// If extends exists, it MUST match the CLI preset
|
|
124
|
+
const extendsMatch = userConfig.extends.match(/@oorabona\/release-it-preset\/config\/(\w+)/);
|
|
125
|
+
const extendsPreset = extendsMatch?.[1];
|
|
126
|
+
|
|
127
|
+
if (extendsPreset && extendsPreset !== configName) {
|
|
128
|
+
console.error(`\n❌ Configuration mismatch error!`);
|
|
129
|
+
console.error(` CLI preset: ${configName}`);
|
|
130
|
+
console.error(` .release-it.json extends: ${extendsPreset}`);
|
|
131
|
+
console.error(``);
|
|
132
|
+
console.error(`Either:`);
|
|
133
|
+
console.error(` 1. Remove the "extends" field from .release-it.json (recommended)`);
|
|
134
|
+
console.error(` → Your overrides will merge with the CLI preset automatically`);
|
|
135
|
+
console.error(``);
|
|
136
|
+
console.error(` 2. Run: release-it-preset ${extendsPreset}`);
|
|
137
|
+
console.error(` → Use the preset specified in your config file`);
|
|
138
|
+
console.error(``);
|
|
139
|
+
console.error(` 3. Update .release-it.json extends to: "${expectedExtends}"`);
|
|
140
|
+
console.error(` → Match your config file to the CLI command\n`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(`✅ Config file extends matches CLI preset: ${configName}`);
|
|
145
|
+
console.log(`📝 Using: ${userConfigPath}\n`);
|
|
146
|
+
} else {
|
|
147
|
+
// No extends = natural merge (Mode 3 - Hybrid)
|
|
148
|
+
console.log(`📝 Merging CLI preset "${configName}" with user config overrides`);
|
|
149
|
+
console.log(` Config file: ${userConfigPath}`);
|
|
150
|
+
console.log(` User overrides will take precedence\n`);
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error instanceof SyntaxError) {
|
|
154
|
+
console.error(`❌ Failed to parse .release-it.json: ${error.message}`);
|
|
155
|
+
} else {
|
|
156
|
+
console.error(`❌ Error reading .release-it.json: ${error.message}`);
|
|
157
|
+
}
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
101
160
|
|
|
102
|
-
|
|
161
|
+
// Let release-it handle the merge naturally
|
|
162
|
+
fullArgs = [...args];
|
|
163
|
+
} else {
|
|
164
|
+
// No user config - use preset directly
|
|
165
|
+
console.log(`📝 Using preset config directly: ${configPath}`);
|
|
166
|
+
console.log(` Tip: Create .release-it.json (without "extends") to add overrides\n`);
|
|
167
|
+
fullArgs = ['--config', configPath, ...args];
|
|
168
|
+
}
|
|
103
169
|
|
|
104
170
|
const child = spawn(releaseItCommand, fullArgs, {
|
|
105
171
|
stdio: 'inherit',
|
|
106
|
-
shell:
|
|
172
|
+
shell: false, // Security: disable shell to prevent command injection
|
|
107
173
|
});
|
|
108
174
|
|
|
109
175
|
child.on('error', (error) => {
|
|
@@ -119,6 +185,15 @@ function handleReleaseCommand(configName, args) {
|
|
|
119
185
|
}
|
|
120
186
|
|
|
121
187
|
function handleUtilityCommand(commandName, args) {
|
|
188
|
+
// Validate inputs
|
|
189
|
+
try {
|
|
190
|
+
validateUtilityCommand(commandName, new Set(Object.keys(UTILITY_COMMANDS)));
|
|
191
|
+
sanitizeArgs(args);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error(`❌ Validation error: ${error.message}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
122
197
|
const base = UTILITY_COMMANDS[commandName];
|
|
123
198
|
const compiledPath = join(__dirname, '..', 'dist', 'scripts', `${base}.js`);
|
|
124
199
|
const sourcePath = join(__dirname, '..', 'scripts', `${base}.ts`);
|
|
@@ -136,7 +211,7 @@ function handleUtilityCommand(commandName, args) {
|
|
|
136
211
|
|
|
137
212
|
const child = spawn(runner, [target, ...args], {
|
|
138
213
|
stdio: 'inherit',
|
|
139
|
-
shell:
|
|
214
|
+
shell: false, // Security: disable shell to prevent command injection
|
|
140
215
|
});
|
|
141
216
|
|
|
142
217
|
child.on('error', (error) => {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input validation and security utilities for CLI
|
|
3
|
+
*
|
|
4
|
+
* This module provides validation functions to prevent security issues
|
|
5
|
+
* like command injection, path traversal, and invalid input.
|
|
6
|
+
*
|
|
7
|
+
* OWASP Security Principles Applied:
|
|
8
|
+
* - Input Validation
|
|
9
|
+
* - Whitelist validation
|
|
10
|
+
* - Fail securely
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validates that a config name is in the allowed list
|
|
15
|
+
*
|
|
16
|
+
* @param {string} configName - The configuration name to validate
|
|
17
|
+
* @param {Set<string>} allowedConfigs - Set of allowed configuration names
|
|
18
|
+
* @throws {Error} If config name is not in the allowed list
|
|
19
|
+
* @returns {string} The validated config name
|
|
20
|
+
*/
|
|
21
|
+
export function validateConfigName(configName, allowedConfigs) {
|
|
22
|
+
if (!allowedConfigs.has(configName)) {
|
|
23
|
+
const allowed = Array.from(allowedConfigs).join(', ');
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Invalid configuration name: "${configName}"\n` +
|
|
26
|
+
`Allowed configurations: ${allowed}`
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return configName;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Validates that a utility command name is in the allowed list
|
|
34
|
+
*
|
|
35
|
+
* @param {string} commandName - The command name to validate
|
|
36
|
+
* @param {Set<string>} allowedCommands - Set of allowed command names
|
|
37
|
+
* @throws {Error} If command name is not in the allowed list
|
|
38
|
+
* @returns {string} The validated command name
|
|
39
|
+
*/
|
|
40
|
+
export function validateUtilityCommand(commandName, allowedCommands) {
|
|
41
|
+
if (!allowedCommands.has(commandName)) {
|
|
42
|
+
const allowed = Array.from(allowedCommands).join(', ');
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Invalid utility command: "${commandName}"\n` +
|
|
45
|
+
`Allowed commands: ${allowed}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return commandName;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Dangerous patterns that could indicate command injection attempts
|
|
53
|
+
* Includes shell metacharacters and control operators
|
|
54
|
+
*/
|
|
55
|
+
const DANGEROUS_PATTERNS = [
|
|
56
|
+
/[;&|`$()]/, // Shell control operators
|
|
57
|
+
/\$\{[^}]*\}/, // Variable substitution
|
|
58
|
+
/\$\([^)]*\)/, // Command substitution
|
|
59
|
+
/[<>]/, // Redirection operators
|
|
60
|
+
/\n|\r/, // Line breaks (can chain commands)
|
|
61
|
+
/\\\\/, // Backslash escaping
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Sanitizes command arguments to prevent injection attacks
|
|
66
|
+
*
|
|
67
|
+
* This function validates each argument against dangerous patterns.
|
|
68
|
+
* It uses a whitelist approach: only safe characters are allowed.
|
|
69
|
+
*
|
|
70
|
+
* @param {string[]} args - Array of command arguments
|
|
71
|
+
* @throws {Error} If any argument contains dangerous patterns
|
|
72
|
+
* @returns {string[]} The validated arguments
|
|
73
|
+
*/
|
|
74
|
+
export function sanitizeArgs(args) {
|
|
75
|
+
return args.map((arg, index) => {
|
|
76
|
+
// Check each dangerous pattern
|
|
77
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
78
|
+
if (pattern.test(arg)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Argument ${index + 1} contains potentially dangerous characters: "${arg}"\n` +
|
|
81
|
+
`Matched pattern: ${pattern.toString()}\n` +
|
|
82
|
+
`This could be a security risk and has been blocked.`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Additional check for null bytes (common in exploits)
|
|
88
|
+
if (arg.includes('\0')) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Argument ${index + 1} contains null bytes, which is not allowed for security reasons.`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return arg;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Validates that a path does not contain directory traversal attempts
|
|
100
|
+
*
|
|
101
|
+
* @param {string} path - The path to validate
|
|
102
|
+
* @throws {Error} If path contains traversal patterns
|
|
103
|
+
* @returns {string} The validated path
|
|
104
|
+
*/
|
|
105
|
+
export function validatePath(path) {
|
|
106
|
+
// Check for directory traversal patterns
|
|
107
|
+
if (path.includes('..')) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Path contains directory traversal pattern (..) which is not allowed: "${path}"`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check for absolute paths (we expect relative paths)
|
|
114
|
+
if (path.startsWith('/') || /^[a-zA-Z]:/.test(path)) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Absolute paths are not allowed: "${path}"`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return path;
|
|
121
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base configuration builders for release-it presets
|
|
3
|
+
*
|
|
4
|
+
* This module provides reusable configuration builders to eliminate code duplication
|
|
5
|
+
* across all preset configuration files. Each builder function creates a configuration
|
|
6
|
+
* object with environment variable support and sensible defaults.
|
|
7
|
+
*
|
|
8
|
+
* All builders support overrides to allow preset-specific customization while
|
|
9
|
+
* maintaining DRY principles.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createReleaseNotesGenerator, getGitChangelogCommand } from './helpers.js';
|
|
13
|
+
import { GIT_DEFAULTS, NPM_DEFAULTS } from './constants.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates base git configuration
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} overrides - Properties to override in the base config
|
|
19
|
+
* @returns {Object} Git configuration object
|
|
20
|
+
*/
|
|
21
|
+
export function createBaseGitConfig(overrides = {}) {
|
|
22
|
+
const defaults = {
|
|
23
|
+
changelog: getGitChangelogCommand(),
|
|
24
|
+
commitMessage: process.env.GIT_COMMIT_MESSAGE || GIT_DEFAULTS.COMMIT_MESSAGE,
|
|
25
|
+
tagName: process.env.GIT_TAG_NAME || GIT_DEFAULTS.TAG_NAME,
|
|
26
|
+
requireBranch: process.env.GIT_REQUIRE_BRANCH || GIT_DEFAULTS.REQUIRE_BRANCH,
|
|
27
|
+
requireUpstream: process.env.GIT_REQUIRE_UPSTREAM === 'true',
|
|
28
|
+
requireCleanWorkingDir: process.env.GIT_REQUIRE_CLEAN === 'true',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
...defaults,
|
|
33
|
+
...overrides,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Creates base npm configuration
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} overrides - Properties to override in the base config
|
|
41
|
+
* @returns {Object} Npm configuration object
|
|
42
|
+
*/
|
|
43
|
+
export function createBaseNpmConfig(overrides = {}) {
|
|
44
|
+
const defaults = {
|
|
45
|
+
skipChecks: process.env.NPM_SKIP_CHECKS === 'true',
|
|
46
|
+
publish: process.env.NPM_PUBLISH === 'true',
|
|
47
|
+
versionArgs: NPM_DEFAULTS.VERSION_ARGS,
|
|
48
|
+
publishArgs: [
|
|
49
|
+
...NPM_DEFAULTS.PUBLISH_ARGS_BASE,
|
|
50
|
+
'--access',
|
|
51
|
+
process.env.NPM_ACCESS || NPM_DEFAULTS.ACCESS,
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
...defaults,
|
|
57
|
+
...overrides,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Creates base GitHub configuration
|
|
63
|
+
*
|
|
64
|
+
* @param {Object} overrides - Properties to override in the base config
|
|
65
|
+
* @returns {Object} GitHub configuration object
|
|
66
|
+
*/
|
|
67
|
+
export function createBaseGitHubConfig(overrides = {}) {
|
|
68
|
+
const defaults = {
|
|
69
|
+
release: process.env.GITHUB_RELEASE === 'true',
|
|
70
|
+
releaseNotes: createReleaseNotesGenerator(),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
...defaults,
|
|
75
|
+
...overrides,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration constants and defaults
|
|
3
|
+
*
|
|
4
|
+
* This module centralizes all default values used across the preset configurations.
|
|
5
|
+
* By centralizing these values, we ensure consistency and make it easier to update
|
|
6
|
+
* defaults in the future.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: These are ONLY fallback values when environment variables are not set.
|
|
9
|
+
* All configuration should be driven by environment variables in production.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Git configuration defaults
|
|
14
|
+
*/
|
|
15
|
+
export const GIT_DEFAULTS = {
|
|
16
|
+
COMMIT_MESSAGE: 'release: bump v${version}',
|
|
17
|
+
HOTFIX_COMMIT_MESSAGE: 'hotfix: bump v${version}',
|
|
18
|
+
TAG_NAME: 'v${version}',
|
|
19
|
+
REQUIRE_BRANCH: 'main',
|
|
20
|
+
REMOTE: 'origin',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default git changelog command
|
|
25
|
+
* Filters out commits matching release/hotfix/ci patterns
|
|
26
|
+
*/
|
|
27
|
+
export const DEFAULT_CHANGELOG_COMMAND = [
|
|
28
|
+
'git log',
|
|
29
|
+
'--pretty=format:"* %s (%h)"',
|
|
30
|
+
'${from}..${to}',
|
|
31
|
+
'--grep="^release"',
|
|
32
|
+
'--grep="^Release"',
|
|
33
|
+
'--grep="^release-"',
|
|
34
|
+
'--grep="^Release-"',
|
|
35
|
+
'--grep="^hotfix"',
|
|
36
|
+
'--grep="^Hotfix"',
|
|
37
|
+
'--grep="^ci"',
|
|
38
|
+
'--grep="^CI"',
|
|
39
|
+
'--invert-grep',
|
|
40
|
+
].join(' ');
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* npm configuration defaults
|
|
44
|
+
*/
|
|
45
|
+
export const NPM_DEFAULTS = {
|
|
46
|
+
ACCESS: 'public',
|
|
47
|
+
VERSION_ARGS: ['--allow-same-version'],
|
|
48
|
+
PUBLISH_ARGS_BASE: ['--provenance'],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Changelog configuration defaults
|
|
53
|
+
*/
|
|
54
|
+
export const CHANGELOG_DEFAULTS = {
|
|
55
|
+
FILE: 'CHANGELOG.md',
|
|
56
|
+
UNRELEASED_SECTION: '## [Unreleased]',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Keep a Changelog section headers
|
|
61
|
+
*/
|
|
62
|
+
export const CHANGELOG_SECTIONS = {
|
|
63
|
+
ADDED: '### Added',
|
|
64
|
+
FIXED: '### Fixed',
|
|
65
|
+
CHANGED: '### Changed',
|
|
66
|
+
REMOVED: '### Removed',
|
|
67
|
+
SECURITY: '### Security',
|
|
68
|
+
BREAKING: '### ⚠️ BREAKING CHANGES',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Hotfix configuration defaults
|
|
73
|
+
*/
|
|
74
|
+
export const HOTFIX_DEFAULTS = {
|
|
75
|
+
INCREMENT: 'patch',
|
|
76
|
+
};
|
package/config/default.js
CHANGED
|
@@ -17,17 +17,11 @@
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import { runScriptCommand } from './helpers.js';
|
|
21
|
+
import { createBaseGitConfig, createBaseGitHubConfig, createBaseNpmConfig } from './base-config.js';
|
|
21
22
|
|
|
22
23
|
const config = {
|
|
23
|
-
git:
|
|
24
|
-
changelog: getGitChangelogCommand(),
|
|
25
|
-
commitMessage: process.env.GIT_COMMIT_MESSAGE || 'release: bump v${version}',
|
|
26
|
-
tagName: process.env.GIT_TAG_NAME || 'v${version}',
|
|
27
|
-
requireBranch: process.env.GIT_REQUIRE_BRANCH || 'main',
|
|
28
|
-
requireUpstream: process.env.GIT_REQUIRE_UPSTREAM === 'true',
|
|
29
|
-
requireCleanWorkingDir: process.env.GIT_REQUIRE_CLEAN === 'true',
|
|
30
|
-
},
|
|
24
|
+
git: createBaseGitConfig(),
|
|
31
25
|
hooks: {
|
|
32
26
|
'before:bump': [
|
|
33
27
|
runScriptCommand('populate-unreleased-changelog'),
|
|
@@ -36,20 +30,8 @@ const config = {
|
|
|
36
30
|
runScriptCommand('republish-changelog'),
|
|
37
31
|
],
|
|
38
32
|
},
|
|
39
|
-
github:
|
|
40
|
-
|
|
41
|
-
releaseNotes: createReleaseNotesGenerator(),
|
|
42
|
-
},
|
|
43
|
-
npm: {
|
|
44
|
-
skipChecks: process.env.NPM_SKIP_CHECKS === 'true',
|
|
45
|
-
publish: process.env.NPM_PUBLISH === 'true',
|
|
46
|
-
versionArgs: ['--allow-same-version'],
|
|
47
|
-
publishArgs: [
|
|
48
|
-
'--provenance',
|
|
49
|
-
'--access',
|
|
50
|
-
process.env.NPM_ACCESS || 'public',
|
|
51
|
-
],
|
|
52
|
-
},
|
|
33
|
+
github: createBaseGitHubConfig(),
|
|
34
|
+
npm: createBaseNpmConfig(),
|
|
53
35
|
};
|
|
54
36
|
|
|
55
37
|
export default config;
|
package/config/helpers.js
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { DEFAULT_CHANGELOG_COMMAND } from './constants.js';
|
|
4
5
|
|
|
5
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
7
|
const __dirname = dirname(__filename);
|
|
7
8
|
const RUN_SCRIPT_PATH = join(__dirname, '..', 'bin', 'run-script.js');
|
|
8
|
-
const DEFAULT_CHANGELOG_COMMAND = [
|
|
9
|
-
'git log',
|
|
10
|
-
'--pretty=format:"* %s (%h)"',
|
|
11
|
-
'${from}..${to}',
|
|
12
|
-
'--grep="^release"',
|
|
13
|
-
'--grep="^Release"',
|
|
14
|
-
'--grep="^release-"',
|
|
15
|
-
'--grep="^Release-"',
|
|
16
|
-
'--grep="^hotfix"',
|
|
17
|
-
'--grep="^Hotfix"',
|
|
18
|
-
'--grep="^ci"',
|
|
19
|
-
'--grep="^CI"',
|
|
20
|
-
'--invert-grep',
|
|
21
|
-
].join(' ');
|
|
22
9
|
|
|
23
10
|
const DOUBLE_QUOTE = /["\\]/g;
|
|
24
11
|
|
package/config/hotfix.js
CHANGED
|
@@ -14,38 +14,23 @@
|
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
17
|
+
import { runScriptCommand } from './helpers.js';
|
|
18
|
+
import { createBaseGitConfig, createBaseGitHubConfig, createBaseNpmConfig } from './base-config.js';
|
|
19
|
+
import { GIT_DEFAULTS, HOTFIX_DEFAULTS } from './constants.js';
|
|
18
20
|
|
|
19
21
|
const config = {
|
|
20
|
-
increment: process.env.HOTFIX_INCREMENT ||
|
|
21
|
-
git: {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
tagName: process.env.GIT_TAG_NAME || 'v${version}',
|
|
25
|
-
requireBranch: process.env.GIT_REQUIRE_BRANCH || 'main',
|
|
26
|
-
requireUpstream: process.env.GIT_REQUIRE_UPSTREAM === 'true',
|
|
27
|
-
requireCleanWorkingDir: process.env.GIT_REQUIRE_CLEAN === 'true',
|
|
28
|
-
},
|
|
22
|
+
increment: process.env.HOTFIX_INCREMENT || HOTFIX_DEFAULTS.INCREMENT,
|
|
23
|
+
git: createBaseGitConfig({
|
|
24
|
+
commitMessage: process.env.GIT_COMMIT_MESSAGE || GIT_DEFAULTS.HOTFIX_COMMIT_MESSAGE,
|
|
25
|
+
}),
|
|
29
26
|
hooks: {
|
|
30
27
|
'before:bump': [
|
|
31
28
|
'echo "Creating hotfix release..."',
|
|
32
29
|
runScriptCommand('populate-unreleased-changelog'),
|
|
33
30
|
],
|
|
34
31
|
},
|
|
35
|
-
github:
|
|
36
|
-
|
|
37
|
-
releaseNotes: createReleaseNotesGenerator(),
|
|
38
|
-
},
|
|
39
|
-
npm: {
|
|
40
|
-
skipChecks: process.env.NPM_SKIP_CHECKS === 'true',
|
|
41
|
-
publish: process.env.NPM_PUBLISH === 'true',
|
|
42
|
-
versionArgs: ['--allow-same-version'],
|
|
43
|
-
publishArgs: [
|
|
44
|
-
'--provenance',
|
|
45
|
-
'--access',
|
|
46
|
-
process.env.NPM_ACCESS || 'public',
|
|
47
|
-
],
|
|
48
|
-
},
|
|
32
|
+
github: createBaseGitHubConfig(),
|
|
33
|
+
npm: createBaseNpmConfig(),
|
|
49
34
|
};
|
|
50
35
|
|
|
51
36
|
export default config;
|
|
@@ -29,37 +29,19 @@
|
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
|
-
import {
|
|
32
|
+
import { runScriptCommand } from './helpers.js';
|
|
33
|
+
import { createBaseGitConfig, createBaseGitHubConfig, createBaseNpmConfig } from './base-config.js';
|
|
33
34
|
|
|
34
35
|
const config = {
|
|
35
|
-
git:
|
|
36
|
-
changelog: getGitChangelogCommand(),
|
|
37
|
-
commitMessage: process.env.GIT_COMMIT_MESSAGE || 'release: bump v${version}',
|
|
38
|
-
tagName: process.env.GIT_TAG_NAME || 'v${version}',
|
|
39
|
-
requireBranch: process.env.GIT_REQUIRE_BRANCH || 'main',
|
|
40
|
-
requireUpstream: process.env.GIT_REQUIRE_UPSTREAM === 'true',
|
|
41
|
-
requireCleanWorkingDir: process.env.GIT_REQUIRE_CLEAN === 'true',
|
|
42
|
-
},
|
|
36
|
+
git: createBaseGitConfig(),
|
|
43
37
|
hooks: {
|
|
44
38
|
// No before:bump - preserve manual changelog edits
|
|
45
39
|
'after:bump': [
|
|
46
40
|
runScriptCommand('republish-changelog'),
|
|
47
41
|
],
|
|
48
42
|
},
|
|
49
|
-
github:
|
|
50
|
-
|
|
51
|
-
releaseNotes: createReleaseNotesGenerator(),
|
|
52
|
-
},
|
|
53
|
-
npm: {
|
|
54
|
-
skipChecks: process.env.NPM_SKIP_CHECKS === 'true',
|
|
55
|
-
publish: process.env.NPM_PUBLISH === 'true',
|
|
56
|
-
versionArgs: ['--allow-same-version'],
|
|
57
|
-
publishArgs: [
|
|
58
|
-
'--provenance',
|
|
59
|
-
'--access',
|
|
60
|
-
process.env.NPM_ACCESS || 'public',
|
|
61
|
-
],
|
|
62
|
-
},
|
|
43
|
+
github: createBaseGitHubConfig(),
|
|
44
|
+
npm: createBaseNpmConfig(),
|
|
63
45
|
};
|
|
64
46
|
|
|
65
47
|
export default config;
|
package/config/no-changelog.js
CHANGED
|
@@ -13,28 +13,16 @@
|
|
|
13
13
|
* ```
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { createBaseGitConfig, createBaseGitHubConfig, createBaseNpmConfig } from './base-config.js';
|
|
17
|
+
|
|
16
18
|
const config = {
|
|
17
|
-
git: {
|
|
19
|
+
git: createBaseGitConfig({
|
|
18
20
|
changelog: false,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
},
|
|
25
|
-
github: {
|
|
26
|
-
release: process.env.GITHUB_RELEASE === 'true',
|
|
27
|
-
},
|
|
28
|
-
npm: {
|
|
29
|
-
skipChecks: process.env.NPM_SKIP_CHECKS === 'true',
|
|
30
|
-
publish: process.env.NPM_PUBLISH === 'true',
|
|
31
|
-
versionArgs: ['--allow-same-version'],
|
|
32
|
-
publishArgs: [
|
|
33
|
-
'--provenance',
|
|
34
|
-
'--access',
|
|
35
|
-
process.env.NPM_ACCESS || 'public',
|
|
36
|
-
],
|
|
37
|
-
},
|
|
21
|
+
}),
|
|
22
|
+
github: createBaseGitHubConfig({
|
|
23
|
+
releaseNotes: undefined, // No release notes without changelog
|
|
24
|
+
}),
|
|
25
|
+
npm: createBaseNpmConfig(),
|
|
38
26
|
};
|
|
39
27
|
|
|
40
28
|
export default config;
|
package/config/republish.js
CHANGED
|
@@ -18,19 +18,15 @@
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import { runScriptCommand } from './helpers.js';
|
|
22
|
+
import { createBaseGitConfig, createBaseGitHubConfig, createBaseNpmConfig } from './base-config.js';
|
|
22
23
|
|
|
23
24
|
const config = {
|
|
24
25
|
increment: false,
|
|
25
|
-
git: {
|
|
26
|
-
changelog: getGitChangelogCommand(),
|
|
26
|
+
git: createBaseGitConfig({
|
|
27
27
|
commitMessage: process.env.GIT_COMMIT_MESSAGE || 'chore: republish v${version}',
|
|
28
|
-
tagName: process.env.GIT_TAG_NAME || 'v${version}',
|
|
29
28
|
tagAnnotation: 'Release ${version} (republished)',
|
|
30
|
-
|
|
31
|
-
requireUpstream: process.env.GIT_REQUIRE_UPSTREAM === 'true',
|
|
32
|
-
requireCleanWorkingDir: process.env.GIT_REQUIRE_CLEAN === 'true',
|
|
33
|
-
},
|
|
29
|
+
}),
|
|
34
30
|
hooks: {
|
|
35
31
|
'before:init': [
|
|
36
32
|
'echo "⚠️ WARNING: You are about to MOVE an existing tag!"',
|
|
@@ -41,21 +37,10 @@ const config = {
|
|
|
41
37
|
runScriptCommand('republish-changelog'),
|
|
42
38
|
],
|
|
43
39
|
},
|
|
44
|
-
npm:
|
|
45
|
-
|
|
46
|
-
publish: process.env.NPM_PUBLISH === 'true',
|
|
47
|
-
versionArgs: ['--allow-same-version'],
|
|
48
|
-
publishArgs: [
|
|
49
|
-
'--provenance',
|
|
50
|
-
'--access',
|
|
51
|
-
process.env.NPM_ACCESS || 'public',
|
|
52
|
-
],
|
|
53
|
-
},
|
|
54
|
-
github: {
|
|
55
|
-
release: process.env.GITHUB_RELEASE === 'true',
|
|
40
|
+
npm: createBaseNpmConfig(),
|
|
41
|
+
github: createBaseGitHubConfig({
|
|
56
42
|
update: true,
|
|
57
|
-
|
|
58
|
-
},
|
|
43
|
+
}),
|
|
59
44
|
};
|
|
60
45
|
|
|
61
46
|
export default config;
|
package/config/retry-publish.js
CHANGED
|
@@ -15,26 +15,15 @@
|
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import {
|
|
18
|
+
import { createBaseGitHubConfig, createBaseNpmConfig } from './base-config.js';
|
|
19
19
|
|
|
20
20
|
const config = {
|
|
21
21
|
increment: false,
|
|
22
22
|
git: false,
|
|
23
|
-
npm:
|
|
24
|
-
|
|
25
|
-
publish: process.env.NPM_PUBLISH === 'true',
|
|
26
|
-
versionArgs: ['--allow-same-version'],
|
|
27
|
-
publishArgs: [
|
|
28
|
-
'--provenance',
|
|
29
|
-
'--access',
|
|
30
|
-
process.env.NPM_ACCESS || 'public',
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
github: {
|
|
34
|
-
release: process.env.GITHUB_RELEASE === 'true',
|
|
23
|
+
npm: createBaseNpmConfig(),
|
|
24
|
+
github: createBaseGitHubConfig({
|
|
35
25
|
update: true,
|
|
36
|
-
|
|
37
|
-
},
|
|
26
|
+
}),
|
|
38
27
|
};
|
|
39
28
|
|
|
40
29
|
export default config;
|