@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 CHANGED
@@ -562,23 +562,154 @@ GIT_REQUIRE_CLEAN="true" \
562
562
  pnpm release
563
563
  ```
564
564
 
565
- ## Configuration Override
565
+ ## Configuration Modes
566
566
 
567
- You can override any configuration in your project's `.release-it.json`:
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": "develop",
597
+ "requireBranch": "master",
574
598
  "commitMessage": "chore: release v${version}"
575
599
  },
576
- "github": {
577
- "releaseName": "Release ${version}"
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 config: ${configName}`);
97
- console.log(`📝 Config file: ${configPath}`);
109
+ console.log(`🚀 Running release-it with preset: ${configName}`);
98
110
 
99
111
  const releaseItCommand = 'release-it';
100
- const fullArgs = ['--config', configPath, ...args];
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
- console.log(`💡 Command: ${releaseItCommand} ${fullArgs.join(' ')}\n`);
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: true,
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: true,
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 { 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;
@@ -15,26 +15,15 @@
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.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Shared release-it configuration and scripts for the organisation",
5
5
  "type": "module",
6
6
  "keywords": [