@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 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
- node node_modules/@oorabona/release-it-preset/dist/scripts/retry-publish.js
312
- # or during local development (TypeScript sources):
313
- pnpm tsx scripts/retry-publish.ts
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 run release:retry-publish
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 config: ${configName}`);
89
- console.log(`📝 Config file: ${configPath}`);
109
+ console.log(`🚀 Running release-it with preset: ${configName}`);
90
110
 
91
111
  const releaseItCommand = 'release-it';
92
- const fullArgs = ['--config', configPath, ...args];
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: true,
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: true,
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 { createReleaseNotesGenerator, getGitChangelogCommand, runScriptCommand } from './helpers.js';
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
- release: process.env.GITHUB_RELEASE === 'true',
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 { createReleaseNotesGenerator, getGitChangelogCommand, runScriptCommand } from './helpers.js';
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 || 'patch',
21
- git: {
22
- changelog: getGitChangelogCommand(),
23
- commitMessage: process.env.GIT_COMMIT_MESSAGE || 'hotfix: bump v${version}',
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
- release: process.env.GITHUB_RELEASE === 'true',
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 { createReleaseNotesGenerator, getGitChangelogCommand, runScriptCommand } from './helpers.js';
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
- release: process.env.GITHUB_RELEASE === 'true',
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;
@@ -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
- commitMessage: process.env.GIT_COMMIT_MESSAGE || 'release: bump v${version}',
20
- tagName: process.env.GIT_TAG_NAME || 'v${version}',
21
- requireBranch: process.env.GIT_REQUIRE_BRANCH || 'main',
22
- requireUpstream: process.env.GIT_REQUIRE_UPSTREAM === 'true',
23
- requireCleanWorkingDir: process.env.GIT_REQUIRE_CLEAN === 'true',
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;
@@ -18,19 +18,15 @@
18
18
  * ```
19
19
  */
20
20
 
21
- import { createReleaseNotesGenerator, getGitChangelogCommand, runScriptCommand } from './helpers.js';
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
- requireBranch: process.env.GIT_REQUIRE_BRANCH || 'main',
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
- skipChecks: process.env.NPM_SKIP_CHECKS === 'true',
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
- releaseNotes: createReleaseNotesGenerator(),
58
- },
43
+ }),
59
44
  };
60
45
 
61
46
  export default config;
@@ -10,31 +10,20 @@
10
10
  * Usage:
11
11
  * First run the retry script to checkout the tag:
12
12
  * ```bash
13
- * node node_modules/@oorabona/release-it-preset/dist/scripts/retry-publish.js
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 { createReleaseNotesGenerator } from './helpers.js';
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
- skipChecks: process.env.NPM_SKIP_CHECKS === 'true',
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
- releaseNotes: createReleaseNotesGenerator(),
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.5.2",
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": "^22.10.5",
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",