@oorabona/release-it-preset 0.5.2 → 0.7.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 +87 -8
- package/bin/cli.js +50 -5
- 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 +5 -16
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -308,9 +308,9 @@ Retries npm/GitHub publishing for an existing tag without modifying git history;
|
|
|
308
308
|
**CLI:**
|
|
309
309
|
```bash
|
|
310
310
|
# Step 1: Run pre-flight checks (optional)
|
|
311
|
-
|
|
312
|
-
#
|
|
313
|
-
|
|
311
|
+
pnpm release-it-preset retry-publish-preflight
|
|
312
|
+
# Advanced (direct compiled call)
|
|
313
|
+
# node node_modules/@oorabona/release-it-preset/dist/scripts/retry-publish.js
|
|
314
314
|
|
|
315
315
|
# Step 2: Retry the publish
|
|
316
316
|
pnpm release-it-preset retry-publish
|
|
@@ -428,6 +428,26 @@ pnpm release-it-preset check
|
|
|
428
428
|
|
|
429
429
|
Useful for debugging release issues.
|
|
430
430
|
|
|
431
|
+
#### `check-pr` - Pull Request Hygiene
|
|
432
|
+
|
|
433
|
+
Evaluates PR readiness by analysing commits and changelog changes. Designed for CI usage but safe locally when the required environment variables are set (`PR_BASE_REF`, `PR_HEAD_REF`).
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
PR_BASE_REF=origin/main PR_HEAD_REF=HEAD pnpm release-it-preset check-pr
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Outputs JSON summaries for workflows (base64 encoded) and prints a human-readable report.
|
|
440
|
+
|
|
441
|
+
#### `retry-publish-preflight` - Retry Safety Checks
|
|
442
|
+
|
|
443
|
+
Runs the retry publish pre-flight script with the same CLI convenience wrapper as other utilities. Verifies that the latest tag exists, matches `package.json`, and that there are no unexpected workspace changes before attempting a retry.
|
|
444
|
+
|
|
445
|
+
```bash
|
|
446
|
+
pnpm release-it-preset retry-publish-preflight
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Use this before calling `pnpm release-it-preset retry-publish` when recovering from a failed publish.
|
|
450
|
+
|
|
431
451
|
### pnpm Script Shortcuts
|
|
432
452
|
|
|
433
453
|
The root `package.json` defines helper scripts that wrap the CLI so you can run the most common flows with `pnpm run`:
|
|
@@ -439,6 +459,7 @@ The root `package.json` defines helper scripts that wrap the CLI so you can run
|
|
|
439
459
|
- `pnpm run release:manual-changelog` → release with manually edited changelog (skip auto-generation)
|
|
440
460
|
- `pnpm run release:hotfix` → execute the hotfix workflow
|
|
441
461
|
- `pnpm run release:republish` → trigger the republish workflow (dangerous flow)
|
|
462
|
+
- `pnpm run release:retry-preflight` → run retry publish safety checks
|
|
442
463
|
- `pnpm run release:retry-publish` → retry npm/GitHub publishing for an existing tag
|
|
443
464
|
- `pnpm run release:update` → populate the `[Unreleased]` section
|
|
444
465
|
- `pnpm run release:validate` → run release validation checks
|
|
@@ -498,12 +519,10 @@ node node_modules/@oorabona/release-it-preset/dist/scripts/republish-changelog.j
|
|
|
498
519
|
Performs pre-flight checks before retrying a failed publish.
|
|
499
520
|
|
|
500
521
|
```bash
|
|
501
|
-
# Preferred
|
|
502
|
-
pnpm
|
|
503
|
-
# or
|
|
504
|
-
pnpm release-it-preset retry-publish
|
|
522
|
+
# Preferred (CLI)
|
|
523
|
+
pnpm release-it-preset retry-publish-preflight
|
|
505
524
|
|
|
506
|
-
# Advanced
|
|
525
|
+
# Advanced (call compiled output directly)
|
|
507
526
|
node node_modules/@oorabona/release-it-preset/dist/scripts/retry-publish.js
|
|
508
527
|
```
|
|
509
528
|
|
|
@@ -560,6 +579,40 @@ You can override any configuration in your project's `.release-it.json`:
|
|
|
560
579
|
}
|
|
561
580
|
```
|
|
562
581
|
|
|
582
|
+
### CLI Behavior with User Configuration
|
|
583
|
+
|
|
584
|
+
**Important:** The CLI now intelligently detects and respects your `.release-it.json` file:
|
|
585
|
+
|
|
586
|
+
- **With `.release-it.json` present:**
|
|
587
|
+
- The CLI runs `release-it` without `--config` flag
|
|
588
|
+
- release-it naturally merges your config with the preset via the `extends` field
|
|
589
|
+
- **Your settings have priority** over preset defaults
|
|
590
|
+
- Perfect for customizing tag names, commit messages, branch requirements, etc.
|
|
591
|
+
|
|
592
|
+
- **Without `.release-it.json`:**
|
|
593
|
+
- The CLI runs `release-it --config <preset-path>`
|
|
594
|
+
- Uses preset configuration directly
|
|
595
|
+
- Works exactly as before
|
|
596
|
+
|
|
597
|
+
**Example of customizing the default preset:**
|
|
598
|
+
|
|
599
|
+
```json
|
|
600
|
+
{
|
|
601
|
+
"extends": "@oorabona/release-it-preset/config/default",
|
|
602
|
+
"git": {
|
|
603
|
+
"tagName": "release-${version}",
|
|
604
|
+
"requireBranch": "develop"
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
Then run:
|
|
610
|
+
```bash
|
|
611
|
+
pnpm release-it-preset default
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
The CLI will use your customized settings instead of the preset defaults!
|
|
615
|
+
|
|
563
616
|
## Borrowing Scripts & Workflows
|
|
564
617
|
|
|
565
618
|
- 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).
|
|
@@ -1119,6 +1172,32 @@ graph TB
|
|
|
1119
1172
|
6. **Use CI for publishing** - Let GitHub Actions handle GitHub releases and npm publishing with provenance
|
|
1120
1173
|
7. **Local runs are for prep** - Keep local runs focused on changelog, versioning, and tagging unless you explicitly opt in to publish
|
|
1121
1174
|
|
|
1175
|
+
## Security
|
|
1176
|
+
|
|
1177
|
+
This preset implements OWASP security best practices:
|
|
1178
|
+
|
|
1179
|
+
### Input Validation
|
|
1180
|
+
|
|
1181
|
+
All CLI inputs are validated before execution:
|
|
1182
|
+
- **Whitelist validation**: Config names and commands are validated against allowed lists
|
|
1183
|
+
- **Argument sanitization**: All arguments are checked for dangerous characters (`;`, `&`, `|`, `` ` ``, `$()`, etc.)
|
|
1184
|
+
- **Path traversal protection**: File paths are validated to prevent directory traversal attacks
|
|
1185
|
+
|
|
1186
|
+
### Command Injection Prevention
|
|
1187
|
+
|
|
1188
|
+
- All `spawn()` calls use `shell: false` to prevent command injection
|
|
1189
|
+
- Arguments are passed as arrays, not concatenated strings
|
|
1190
|
+
- No user input is ever executed in a shell context
|
|
1191
|
+
|
|
1192
|
+
### Architecture
|
|
1193
|
+
|
|
1194
|
+
The preset follows SOLID principles:
|
|
1195
|
+
- **Single Responsibility**: Each module has one clear purpose
|
|
1196
|
+
- **DRY**: Shared configuration builders eliminate code duplication
|
|
1197
|
+
- **Dependency Inversion**: User configs have priority over preset defaults
|
|
1198
|
+
|
|
1199
|
+
All 213 unit tests verify functionality and security boundaries.
|
|
1200
|
+
|
|
1122
1201
|
## Troubleshooting
|
|
1123
1202
|
|
|
1124
1203
|
### Changelog not updating
|
package/bin/cli.js
CHANGED
|
@@ -19,11 +19,15 @@
|
|
|
19
19
|
* release-it-preset update
|
|
20
20
|
* release-it-preset validate [--allow-dirty]
|
|
21
21
|
* release-it-preset check
|
|
22
|
+
* release-it-preset check-pr
|
|
23
|
+
* release-it-preset retry-publish-preflight
|
|
22
24
|
*/
|
|
23
25
|
|
|
24
26
|
import { spawn } from 'node:child_process';
|
|
27
|
+
import { existsSync } from 'node:fs';
|
|
25
28
|
import { fileURLToPath } from 'node:url';
|
|
26
29
|
import { dirname, join } from 'node:path';
|
|
30
|
+
import { validateConfigName, validateUtilityCommand, sanitizeArgs } from './validators.js';
|
|
27
31
|
|
|
28
32
|
const __filename = fileURLToPath(import.meta.url);
|
|
29
33
|
const __dirname = dirname(__filename);
|
|
@@ -44,6 +48,8 @@ const UTILITY_COMMANDS = {
|
|
|
44
48
|
update: 'populate-unreleased-changelog',
|
|
45
49
|
validate: 'validate-release',
|
|
46
50
|
check: 'check-config',
|
|
51
|
+
'check-pr': 'check-pr-status',
|
|
52
|
+
'retry-publish-preflight': 'retry-publish',
|
|
47
53
|
};
|
|
48
54
|
|
|
49
55
|
function showHelp() {
|
|
@@ -64,6 +70,8 @@ Utility Commands:
|
|
|
64
70
|
update Update [Unreleased] section from commits
|
|
65
71
|
validate [--allow-dirty] Validate project is ready for release
|
|
66
72
|
check Display configuration and project status
|
|
73
|
+
check-pr Evaluate PR hygiene (branch diff, changelog status, conventions)
|
|
74
|
+
retry-publish-preflight Run retry publish safety checks without executing release
|
|
67
75
|
|
|
68
76
|
Examples:
|
|
69
77
|
# Release commands
|
|
@@ -76,6 +84,8 @@ Examples:
|
|
|
76
84
|
release-it-preset update
|
|
77
85
|
release-it-preset validate
|
|
78
86
|
release-it-preset check
|
|
87
|
+
release-it-preset check-pr
|
|
88
|
+
release-it-preset retry-publish-preflight
|
|
79
89
|
|
|
80
90
|
For release-it options, see: https://github.com/release-it/release-it
|
|
81
91
|
For environment variables, see: https://github.com/oorabona/release-it-preset#environment-variables
|
|
@@ -83,19 +93,45 @@ For environment variables, see: https://github.com/oorabona/release-it-preset#en
|
|
|
83
93
|
}
|
|
84
94
|
|
|
85
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
|
+
|
|
86
105
|
const configPath = join(__dirname, '..', RELEASE_CONFIGS[configName]);
|
|
106
|
+
const userConfigPath = join(process.cwd(), '.release-it.json');
|
|
107
|
+
const hasUserConfig = existsSync(userConfigPath);
|
|
87
108
|
|
|
88
|
-
console.log(`🚀 Running release-it with
|
|
89
|
-
console.log(`📝 Config file: ${configPath}`);
|
|
109
|
+
console.log(`🚀 Running release-it with preset: ${configName}`);
|
|
90
110
|
|
|
91
111
|
const releaseItCommand = 'release-it';
|
|
92
|
-
|
|
112
|
+
let fullArgs;
|
|
113
|
+
let strategyMessage;
|
|
114
|
+
|
|
115
|
+
if (hasUserConfig) {
|
|
116
|
+
// User has .release-it.json - let release-it handle the merge naturally
|
|
117
|
+
// The user config should have "extends": "@oorabona/release-it-preset/config/<preset>"
|
|
118
|
+
fullArgs = [...args];
|
|
119
|
+
strategyMessage = `📝 Using user config: ${userConfigPath}\n (should extend preset: ${configName})`;
|
|
120
|
+
console.log(strategyMessage);
|
|
121
|
+
console.log(`ℹ️ Ensure your .release-it.json contains: "extends": "@oorabona/release-it-preset/config/${configName}"`);
|
|
122
|
+
} else {
|
|
123
|
+
// No user config - use preset directly
|
|
124
|
+
fullArgs = ['--config', configPath, ...args];
|
|
125
|
+
strategyMessage = `📝 Using preset config directly: ${configPath}`;
|
|
126
|
+
console.log(strategyMessage);
|
|
127
|
+
console.log(`ℹ️ Tip: Create .release-it.json with "extends" to customize the preset`);
|
|
128
|
+
}
|
|
93
129
|
|
|
94
130
|
console.log(`💡 Command: ${releaseItCommand} ${fullArgs.join(' ')}\n`);
|
|
95
131
|
|
|
96
132
|
const child = spawn(releaseItCommand, fullArgs, {
|
|
97
133
|
stdio: 'inherit',
|
|
98
|
-
shell:
|
|
134
|
+
shell: false, // Security: disable shell to prevent command injection
|
|
99
135
|
});
|
|
100
136
|
|
|
101
137
|
child.on('error', (error) => {
|
|
@@ -111,6 +147,15 @@ function handleReleaseCommand(configName, args) {
|
|
|
111
147
|
}
|
|
112
148
|
|
|
113
149
|
function handleUtilityCommand(commandName, args) {
|
|
150
|
+
// Validate inputs
|
|
151
|
+
try {
|
|
152
|
+
validateUtilityCommand(commandName, new Set(Object.keys(UTILITY_COMMANDS)));
|
|
153
|
+
sanitizeArgs(args);
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error(`❌ Validation error: ${error.message}`);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
|
|
114
159
|
const base = UTILITY_COMMANDS[commandName];
|
|
115
160
|
const compiledPath = join(__dirname, '..', 'dist', 'scripts', `${base}.js`);
|
|
116
161
|
const sourcePath = join(__dirname, '..', 'scripts', `${base}.ts`);
|
|
@@ -128,7 +173,7 @@ function handleUtilityCommand(commandName, args) {
|
|
|
128
173
|
|
|
129
174
|
const child = spawn(runner, [target, ...args], {
|
|
130
175
|
stdio: 'inherit',
|
|
131
|
-
shell:
|
|
176
|
+
shell: false, // Security: disable shell to prevent command injection
|
|
132
177
|
});
|
|
133
178
|
|
|
134
179
|
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
|
@@ -10,31 +10,20 @@
|
|
|
10
10
|
* Usage:
|
|
11
11
|
* First run the retry script to checkout the tag:
|
|
12
12
|
* ```bash
|
|
13
|
-
*
|
|
13
|
+
* pnpm release-it-preset retry-publish-preflight
|
|
14
14
|
* pnpm release-it --config node_modules/@oorabona/release-it-preset/config/retry-publish.js
|
|
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;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oorabona/release-it-preset",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Shared release-it configuration and scripts for the organisation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
"release:manual-changelog": "node ./bin/cli.js manual-changelog",
|
|
56
56
|
"release:hotfix": "node ./bin/cli.js hotfix",
|
|
57
57
|
"release:republish": "node ./bin/cli.js republish",
|
|
58
|
+
"release:retry-preflight": "node ./bin/cli.js retry-publish-preflight",
|
|
58
59
|
"release:retry-publish": "node ./bin/cli.js retry-publish",
|
|
59
60
|
"release:update": "node ./bin/cli.js update",
|
|
60
61
|
"release:validate": "node ./bin/cli.js validate",
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
},
|
|
79
80
|
"devDependencies": {
|
|
80
81
|
"@biomejs/biome": "^2.2.5",
|
|
81
|
-
"@types/node": "^
|
|
82
|
+
"@types/node": "^24.6.2",
|
|
82
83
|
"@vitest/coverage-v8": "^3.2.4",
|
|
83
84
|
"nano-staged": "^0.8.0",
|
|
84
85
|
"rimraf": "^6.0.1",
|