lfify 1.1.0 → 1.1.1

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.
@@ -4,13 +4,11 @@ on:
4
4
  branches:
5
5
  - main
6
6
 
7
- permissions:
8
- contents: read # for checkout
9
-
10
7
  jobs:
11
8
  release:
12
9
  name: Release
13
10
  runs-on: ubuntu-latest
11
+ environment: npm
14
12
  permissions:
15
13
  contents: write # to be able to publish a GitHub release
16
14
  issues: write # to be able to comment on released issues
@@ -22,7 +20,7 @@ jobs:
22
20
  with:
23
21
  fetch-depth: 0
24
22
  - name: Setup Node.js
25
- uses: actions/setup-node@v4
23
+ uses: actions/setup-node@v6
26
24
  with:
27
25
  node-version: "lts/*"
28
26
  - name: Install dependencies
@@ -32,5 +30,4 @@ jobs:
32
30
  - name: Release
33
31
  env:
34
32
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
36
- run: npx semantic-release
33
+ run: NODE_AUTH_TOKEN="" npx semantic-release
@@ -0,0 +1,21 @@
1
+ {
2
+ "branches": ["main"],
3
+ "plugins": [
4
+ "@semantic-release/commit-analyzer",
5
+ "@semantic-release/release-notes-generator",
6
+ [
7
+ "@semantic-release/changelog",
8
+ {
9
+ "changelogFile": "CHANGELOG.md"
10
+ }
11
+ ],
12
+ "@semantic-release/npm",
13
+ [
14
+ "@semantic-release/git",
15
+ {
16
+ "assets": ["package.json", "package-lock.json", "CHANGELOG.md"],
17
+ "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
18
+ }
19
+ ]
20
+ ]
21
+ }
package/README.md CHANGED
@@ -1,13 +1,19 @@
1
1
  # LFify
2
2
 
3
- > ⚠️ **Warning**: All files must be encoded in UTF-8. The library is being developed to automatically convert UTF-8 with BOM to UTF-8 without BOM. Using different encodings may cause unexpected issues.
4
-
5
- A lightweight Node.js library to convert CRLF to LF line endings.
6
- It is useful when your development environment is Windows.
3
+ A lightweight Node.js program to convert CRLF to LF line endings.
4
+ It is useful when your development environment is Windows.
7
5
 
8
6
  ## Getting started
9
7
 
10
- create .lfifyrc.json
8
+ ### Using CLI options (no config file needed)
9
+
10
+ ```bash
11
+ npx lfify --include "**/*.js" --exclude "node_modules/**"
12
+ ```
13
+
14
+ ### Using config file
15
+
16
+ Create `.lfifyrc.json`:
11
17
 
12
18
  ```json
13
19
  {
@@ -25,22 +31,52 @@ create .lfifyrc.json
25
31
  "build/**",
26
32
  "coverage/**"
27
33
  ]
28
- }
34
+ }
29
35
  ```
30
36
 
31
- and then
37
+ Then run:
32
38
 
33
39
  ```bash
34
- npx lifify
40
+ npx lfify
35
41
  ```
36
42
 
37
- you can add options below.
38
-
39
43
  ## Options
40
44
 
41
- | Option | Description |
42
- |--------|-------------|
43
- | `--config <path>` | Specify a custom path for the configuration file. Default is `.lfifyrc.json` in the current directory. |
45
+ | Option | Description |
46
+ | -------------------- | --------------------------------------------------------------------------- |
47
+ | `--config <path>` | Specify a custom path for the configuration file. Default is `.lfifyrc.json`. |
48
+ | `--entry <path>` | Specify the entry directory to process. Default is `./`. |
49
+ | `--include <pattern>`| Glob pattern(s) to include. Can be used multiple times. |
50
+ | `--exclude <pattern>`| Glob pattern(s) to exclude. Can be used multiple times. |
51
+
52
+ ## Examples
53
+
54
+ ```bash
55
+ # Process all JavaScript files, exclude node_modules
56
+ npx lfify --include "**/*.js" --exclude "node_modules/**"
57
+
58
+ # Process multiple file types
59
+ npx lfify --include "**/*.js" --include "**/*.ts" --exclude "node_modules/**" --exclude ".git/**"
60
+
61
+ # Process files in a specific directory
62
+ npx lfify --entry ./src --include "**/*.js"
63
+
64
+ # Use a custom config file
65
+ npx lfify --config ./custom-config.json
66
+ ```
67
+
68
+ ## Default behavior
69
+
70
+ When no config file is found and no CLI options are provided, lfify uses sensible defaults:
71
+ - **include**: `**/*` (all files)
72
+ - **exclude**: `node_modules/**`, `.git/**`, `dist/**`, `build/**`, `coverage/**`
73
+
74
+ ## Priority
75
+
76
+ CLI options take precedence over config file values:
77
+ 1. CLI arguments (highest)
78
+ 2. Config file
79
+ 3. Default values (lowest)
44
80
 
45
81
  # Development
46
82
 
package/__mocks__/fs.js CHANGED
@@ -19,7 +19,9 @@ const promises = {
19
19
  if (mockFiles.has(path)) {
20
20
  return Promise.resolve(mockFiles.get(path));
21
21
  }
22
- return Promise.reject(new Error(`ENOENT: no such file or directory, open '${path}'`));
22
+ const error = new Error(`ENOENT: no such file or directory, open '${path}'`);
23
+ error.code = 'ENOENT';
24
+ return Promise.reject(error);
23
25
  }),
24
26
 
25
27
  writeFile: jest.fn().mockImplementation((path, content) => {
@@ -10,12 +10,28 @@ micromatch.isMatch = jest.fn().mockImplementation((filePath, patterns) => {
10
10
  // 정확한 매칭
11
11
  if (pattern === filePath) return true;
12
12
 
13
- // 와일드카드 매칭
14
- const regexPattern = pattern
15
- .replace(/\./g, '\\.')
16
- .replace(/\*/g, '.*')
17
- .replace(/\?/g, '.');
18
-
13
+ // glob 패턴을 정규식으로 변환
14
+ // 플레이스홀더를 사용해서 순서 문제 해결
15
+ const GLOBSTAR_SLASH = '___GLOBSTARSLASH___';
16
+ const GLOBSTAR = '___GLOBSTAR___';
17
+
18
+ let regexPattern = pattern
19
+ // **/ 패턴을 플레이스홀더로 임시 변환
20
+ .replace(/\*\*\//g, GLOBSTAR_SLASH)
21
+ // ** 패턴을 플레이스홀더로 임시 변환
22
+ .replace(/\*\*/g, GLOBSTAR)
23
+ // {a,b} 패턴
24
+ .replace(/\{([^}]+)\}/g, (_, group) => `(${group.split(',').join('|')})`)
25
+ // 특수문자 이스케이프 (*, ?, / 제외)
26
+ .replace(/[.+^$|()[\]\\]/g, '\\$&')
27
+ // * 패턴: 슬래시를 제외한 모든 것 매칭
28
+ .replace(/\*/g, '[^/]*')
29
+ // ? 패턴: 슬래시를 제외한 한 문자 매칭
30
+ .replace(/\?/g, '[^/]')
31
+ // 플레이스홀더를 실제 정규식으로 변환
32
+ .replace(new RegExp(GLOBSTAR_SLASH, 'g'), '(?:.*/)?')
33
+ .replace(new RegExp(GLOBSTAR, 'g'), '.*');
34
+
19
35
  return new RegExp(`^${regexPattern}$`).test(filePath);
20
36
  });
21
37
  });
package/index.cjs CHANGED
@@ -20,7 +20,10 @@ const micromatch = require("micromatch");
20
20
 
21
21
  /**
22
22
  * @typedef {Object} CommandOptions
23
- * @property {string} configPath - 설정 파일 경로
23
+ * @property {string} [configPath] - 설정 파일 경로
24
+ * @property {string} [entry] - CLI로 지정한 entry 경로
25
+ * @property {string[]} [include] - CLI로 지정한 include 패턴
26
+ * @property {string[]} [exclude] - CLI로 지정한 exclude 패턴
24
27
  */
25
28
 
26
29
  /**
@@ -33,6 +36,22 @@ const DEFAULT_CONFIG = {
33
36
  exclude: []
34
37
  };
35
38
 
39
+ /**
40
+ * Sensible defaults when no config file is provided
41
+ * @type {Config}
42
+ */
43
+ const SENSIBLE_DEFAULTS = {
44
+ entry: './',
45
+ include: ['**/*'],
46
+ exclude: [
47
+ 'node_modules/**',
48
+ '.git/**',
49
+ 'dist/**',
50
+ 'build/**',
51
+ 'coverage/**'
52
+ ]
53
+ };
54
+
36
55
  /**
37
56
  * Configuration validation schema
38
57
  * @type {Object.<string, function(*): boolean>}
@@ -90,6 +109,83 @@ async function readConfig(configPath) {
90
109
  }
91
110
  }
92
111
 
112
+ /**
113
+ * Resolve final configuration from CLI options, config file, and defaults
114
+ * @param {CommandOptions} cliOptions - parsed CLI options
115
+ * @returns {Promise<Config>} - resolved configuration
116
+ */
117
+ async function resolveConfig(cliOptions) {
118
+ let fileConfig = null;
119
+
120
+ // Try to load config file if it exists
121
+ if (cliOptions.configPath) {
122
+ try {
123
+ const configContent = await fs.readFile(cliOptions.configPath, 'utf8');
124
+ fileConfig = JSON.parse(configContent);
125
+
126
+ // Validate config file fields
127
+ for (const [key, validator] of Object.entries(CONFIG_SCHEMA)) {
128
+ if (fileConfig[key] && !validator(fileConfig[key])) {
129
+ throw new Error(`Invalid "${key}" in configuration file`);
130
+ }
131
+ }
132
+ } catch (err) {
133
+ if (err.code !== 'ENOENT') {
134
+ // Re-throw parsing/validation errors
135
+ logger.error(`Error reading configuration file: ${err.message}`, cliOptions.configPath);
136
+ throw err;
137
+ }
138
+ // ENOENT is okay - config file is optional now
139
+ }
140
+ }
141
+
142
+ // Determine final values with precedence: CLI > config file > defaults
143
+ const hasCLIInclude = Array.isArray(cliOptions.include) && cliOptions.include.length > 0;
144
+ const hasCLIExclude = Array.isArray(cliOptions.exclude) && cliOptions.exclude.length > 0;
145
+ const hasCLIEntry = typeof cliOptions.entry === 'string';
146
+
147
+ const hasFileConfig = fileConfig !== null;
148
+ const hasFileInclude = hasFileConfig && Array.isArray(fileConfig.include) && fileConfig.include.length > 0;
149
+ const hasFileExclude = hasFileConfig && Array.isArray(fileConfig.exclude) && fileConfig.exclude.length > 0;
150
+ const hasFileEntry = hasFileConfig && typeof fileConfig.entry === 'string';
151
+
152
+ // Resolve each config property
153
+ let include, exclude, entry;
154
+
155
+ // Include: CLI > file > default
156
+ if (hasCLIInclude) {
157
+ include = cliOptions.include;
158
+ } else if (hasFileInclude) {
159
+ include = fileConfig.include;
160
+ } else {
161
+ include = SENSIBLE_DEFAULTS.include;
162
+ }
163
+
164
+ // Exclude: CLI > file > default
165
+ if (hasCLIExclude) {
166
+ exclude = cliOptions.exclude;
167
+ } else if (hasFileExclude) {
168
+ exclude = fileConfig.exclude;
169
+ } else {
170
+ exclude = SENSIBLE_DEFAULTS.exclude;
171
+ }
172
+
173
+ // Entry: CLI > file > default
174
+ if (hasCLIEntry) {
175
+ entry = cliOptions.entry;
176
+ } else if (hasFileEntry) {
177
+ entry = fileConfig.entry;
178
+ } else {
179
+ entry = SENSIBLE_DEFAULTS.entry;
180
+ }
181
+
182
+ return {
183
+ entry: path.resolve(process.cwd(), entry),
184
+ include,
185
+ exclude
186
+ };
187
+ }
188
+
93
189
  /**
94
190
  * Parse command line arguments
95
191
  * @returns {CommandOptions} - parsed arguments
@@ -101,9 +197,39 @@ function parseArgs() {
101
197
  };
102
198
 
103
199
  for (let i = 0; i < args.length; i++) {
104
- if (args[i] === '--config' && args[i + 1]) {
105
- options.configPath = args[i + 1];
106
- i++;
200
+ const arg = args[i];
201
+ const nextArg = args[i + 1];
202
+
203
+ switch (arg) {
204
+ case '--config':
205
+ if (nextArg) {
206
+ options.configPath = nextArg;
207
+ i++;
208
+ }
209
+ break;
210
+
211
+ case '--entry':
212
+ if (nextArg) {
213
+ options.entry = nextArg;
214
+ i++;
215
+ }
216
+ break;
217
+
218
+ case '--include':
219
+ if (nextArg) {
220
+ options.include = options.include || [];
221
+ options.include.push(nextArg);
222
+ i++;
223
+ }
224
+ break;
225
+
226
+ case '--exclude':
227
+ if (nextArg) {
228
+ options.exclude = options.exclude || [];
229
+ options.exclude.push(nextArg);
230
+ i++;
231
+ }
232
+ break;
107
233
  }
108
234
  }
109
235
 
@@ -184,7 +310,7 @@ async function processFile(filePath) {
184
310
 
185
311
  async function main() {
186
312
  const options = parseArgs();
187
- const config = await readConfig(options.configPath);
313
+ const config = await resolveConfig(options);
188
314
 
189
315
  logger.info(`converting CRLF to LF in: ${config.entry}`, config.entry);
190
316
 
@@ -202,4 +328,7 @@ module.exports = {
202
328
  processFile,
203
329
  readConfig,
204
330
  parseArgs,
331
+ resolveConfig,
332
+ shouldProcessFile,
333
+ SENSIBLE_DEFAULTS,
205
334
  };
package/index.test.js CHANGED
@@ -1,4 +1,4 @@
1
- const { readConfig, parseArgs, processFile } = require('./index.cjs');
1
+ const { readConfig, parseArgs, processFile, resolveConfig, shouldProcessFile, SENSIBLE_DEFAULTS } = require('./index.cjs');
2
2
 
3
3
  jest.mock('fs');
4
4
  jest.mock('path');
@@ -16,10 +16,17 @@ describe('CRLF to LF Converter', () => {
16
16
  './node_modules/subdir/file4.txt': 'test\r\n',
17
17
  'index.js': 'console.log("test");\r\n'
18
18
  };
19
-
19
+
20
+ let originalArgv;
21
+
20
22
  beforeEach(() => {
21
23
  jest.clearAllMocks();
22
24
  require('fs').__setMockFiles(MOCK_FILE_INFO);
25
+ originalArgv = process.argv;
26
+ });
27
+
28
+ afterEach(() => {
29
+ process.argv = originalArgv;
23
30
  });
24
31
 
25
32
  describe('readConfig', () => {
@@ -79,15 +86,108 @@ describe('CRLF to LF Converter', () => {
79
86
  // assert
80
87
  expect(options.configPath).toBe('.lfifyrc.json');
81
88
  });
89
+
90
+ it('should return include patterns when single --include option is provided', () => {
91
+ process.argv = ['node', 'lfify', '--include', '**/*.js'];
92
+ const options = parseArgs();
93
+ expect(options.include).toEqual(['**/*.js']);
94
+ });
95
+
96
+ it('should return multiple include patterns when multiple --include options are provided', () => {
97
+ process.argv = ['node', 'lfify', '--include', '**/*.js', '--include', '**/*.ts'];
98
+ const options = parseArgs();
99
+ expect(options.include).toEqual(['**/*.js', '**/*.ts']);
100
+ });
101
+
102
+ it('should return exclude patterns when --exclude option is provided', () => {
103
+ process.argv = ['node', 'lfify', '--exclude', 'node_modules/**'];
104
+ const options = parseArgs();
105
+ expect(options.exclude).toEqual(['node_modules/**']);
106
+ });
107
+
108
+ it('should return multiple exclude patterns when multiple --exclude options are provided', () => {
109
+ process.argv = ['node', 'lfify', '--exclude', 'node_modules/**', '--exclude', '.git/**'];
110
+ const options = parseArgs();
111
+ expect(options.exclude).toEqual(['node_modules/**', '.git/**']);
112
+ });
113
+
114
+ it('should return entry path when --entry option is provided', () => {
115
+ process.argv = ['node', 'lfify', '--entry', './src'];
116
+ const options = parseArgs();
117
+ expect(options.entry).toBe('./src');
118
+ });
119
+
120
+ it('should handle all options together', () => {
121
+ process.argv = [
122
+ 'node', 'lfify',
123
+ '--entry', './src',
124
+ '--include', '**/*.js',
125
+ '--include', '**/*.ts',
126
+ '--exclude', 'node_modules/**',
127
+ '--config', 'custom.json'
128
+ ];
129
+ const options = parseArgs();
130
+ expect(options).toEqual({
131
+ configPath: 'custom.json',
132
+ entry: './src',
133
+ include: ['**/*.js', '**/*.ts'],
134
+ exclude: ['node_modules/**']
135
+ });
136
+ });
137
+
138
+ it('should return undefined for include/exclude/entry when not provided', () => {
139
+ process.argv = ['node', 'lfify'];
140
+ const options = parseArgs();
141
+ expect(options.include).toBeUndefined();
142
+ expect(options.exclude).toBeUndefined();
143
+ expect(options.entry).toBeUndefined();
144
+ });
82
145
  });
83
146
 
84
147
  describe('shouldProcessFile', () => {
85
148
  it('should return true when file matches include pattern and does not match exclude pattern', () => {
86
- /**
87
- * This function uses micromatch to check config.include and config.exclude
88
- * so this test case is already tested in micromatch's test file
89
- * so I'm not going to test this function
90
- */
149
+ const config = {
150
+ include: ['**/*.js'],
151
+ exclude: ['node_modules/**']
152
+ };
153
+ expect(shouldProcessFile('src/file.js', config)).toBe(true);
154
+ });
155
+
156
+ it('should return false when file matches exclude pattern', () => {
157
+ const config = {
158
+ include: ['**/*.js'],
159
+ exclude: ['node_modules/**']
160
+ };
161
+ expect(shouldProcessFile('node_modules/package/index.js', config)).toBe(false);
162
+ });
163
+
164
+ it('should return false when file does not match include pattern', () => {
165
+ const config = {
166
+ include: ['**/*.js'],
167
+ exclude: ['node_modules/**']
168
+ };
169
+ expect(shouldProcessFile('src/file.txt', config)).toBe(false);
170
+ });
171
+
172
+ it('should handle multiple include patterns', () => {
173
+ const config = {
174
+ include: ['**/*.js', '**/*.ts'],
175
+ exclude: []
176
+ };
177
+ expect(shouldProcessFile('src/file.js', config)).toBe(true);
178
+ expect(shouldProcessFile('src/file.ts', config)).toBe(true);
179
+ expect(shouldProcessFile('src/file.txt', config)).toBe(false);
180
+ });
181
+
182
+ it('should handle multiple exclude patterns', () => {
183
+ const config = {
184
+ include: ['**/*.js'],
185
+ exclude: ['node_modules/**', 'dist/**', 'test/**']
186
+ };
187
+ expect(shouldProcessFile('src/file.js', config)).toBe(true);
188
+ expect(shouldProcessFile('node_modules/pkg/index.js', config)).toBe(false);
189
+ expect(shouldProcessFile('dist/bundle.js', config)).toBe(false);
190
+ expect(shouldProcessFile('test/unit.js', config)).toBe(false);
91
191
  });
92
192
  });
93
193
 
@@ -102,6 +202,96 @@ describe('CRLF to LF Converter', () => {
102
202
 
103
203
  // assert
104
204
  expect(content).toBe(shouldbe);
105
- })
205
+ });
206
+
207
+ it('should not modify file when no CRLF exists', async () => {
208
+ // arrange
209
+ require('fs').__setMockFiles({ './src/clean.txt': 'hello\nworld\n' });
210
+
211
+ // act
212
+ await processFile('./src/clean.txt');
213
+ const content = await require('fs').promises.readFile('./src/clean.txt', 'utf8');
214
+
215
+ // assert
216
+ expect(content).toBe('hello\nworld\n');
217
+ });
218
+ });
219
+
220
+ describe('resolveConfig', () => {
221
+ it('should use CLI options when provided without config file', async () => {
222
+ const options = {
223
+ include: ['**/*.js'],
224
+ exclude: ['node_modules/**'],
225
+ entry: './src'
226
+ };
227
+ const config = await resolveConfig(options);
228
+
229
+ expect(config.include).toEqual(['**/*.js']);
230
+ expect(config.exclude).toEqual(['node_modules/**']);
231
+ expect(config.entry).toContain('src');
232
+ });
233
+
234
+ it('should use sensible defaults when no config file and no CLI options', async () => {
235
+ const config = await resolveConfig({});
236
+
237
+ expect(config.include).toEqual(SENSIBLE_DEFAULTS.include);
238
+ expect(config.exclude).toEqual(SENSIBLE_DEFAULTS.exclude);
239
+ });
240
+
241
+ it('should override config file values with CLI options', async () => {
242
+ require('fs').__setConfig(JSON.stringify({
243
+ entry: './',
244
+ include: ['**/*.md'],
245
+ exclude: ['dist/**']
246
+ }));
247
+
248
+ const options = {
249
+ configPath: '.lfifyrc.json',
250
+ include: ['**/*.js'] // CLI should override config
251
+ };
252
+ const config = await resolveConfig(options);
253
+
254
+ expect(config.include).toEqual(['**/*.js']); // CLI override
255
+ expect(config.exclude).toEqual(['dist/**']); // from config file
256
+ });
257
+
258
+ it('should load config file when configPath is provided and file exists', async () => {
259
+ require('fs').__setConfig(JSON.stringify({
260
+ entry: './lib',
261
+ include: ['**/*.ts'],
262
+ exclude: ['test/**']
263
+ }));
264
+
265
+ const options = { configPath: '.lfifyrc.json' };
266
+ const config = await resolveConfig(options);
267
+
268
+ expect(config.include).toEqual(['**/*.ts']);
269
+ expect(config.exclude).toEqual(['test/**']);
270
+ });
271
+
272
+ it('should use defaults when config file not found and no CLI options', async () => {
273
+ // No config file set up - mock will throw ENOENT
274
+ const options = { configPath: 'nonexistent.json' };
275
+ const config = await resolveConfig(options);
276
+
277
+ expect(config.include).toEqual(SENSIBLE_DEFAULTS.include);
278
+ expect(config.exclude).toEqual(SENSIBLE_DEFAULTS.exclude);
279
+ });
280
+
281
+ it('should use CLI include with default exclude when only include provided', async () => {
282
+ const options = { include: ['**/*.js'] };
283
+ const config = await resolveConfig(options);
284
+
285
+ expect(config.include).toEqual(['**/*.js']);
286
+ expect(config.exclude).toEqual(SENSIBLE_DEFAULTS.exclude);
287
+ });
288
+
289
+ it('should use CLI exclude with default include when only exclude provided', async () => {
290
+ const options = { exclude: ['custom/**'] };
291
+ const config = await resolveConfig(options);
292
+
293
+ expect(config.include).toEqual(SENSIBLE_DEFAULTS.include);
294
+ expect(config.exclude).toEqual(['custom/**']);
295
+ });
106
296
  });
107
297
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lfify",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "private": false,
5
5
  "description": "make your crlf to lf",
6
6
  "main": "index.cjs",
@@ -14,10 +14,11 @@
14
14
  },
15
15
  "repository": {
16
16
  "type": "git",
17
- "url": "https://github.com/GyeongHoKim/lfify"
17
+ "url": "https://github.com/GyeongHoKim/lfify.git"
18
18
  },
19
19
  "publishConfig": {
20
- "registry": "https://registry.npmjs.org"
20
+ "registry": "https://registry.npmjs.org",
21
+ "tag": "latest"
21
22
  },
22
23
  "keywords": [
23
24
  "eol",
@@ -35,8 +36,14 @@
35
36
  },
36
37
  "devDependencies": {
37
38
  "@eslint/js": "^9.15.0",
39
+ "@semantic-release/changelog": "^6.0.3",
40
+ "@semantic-release/commit-analyzer": "^13.0.1",
41
+ "@semantic-release/git": "^10.0.1",
42
+ "@semantic-release/npm": "^13.1.3",
43
+ "@semantic-release/release-notes-generator": "^14.1.0",
38
44
  "eslint": "^9.15.0",
39
45
  "globals": "^15.12.0",
40
- "jest": "^29.7.0"
46
+ "jest": "^29.7.0",
47
+ "semantic-release": "^25.0.2"
41
48
  }
42
49
  }