lfify 1.1.1 → 1.2.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.
package/index.test.js CHANGED
@@ -1,89 +1,92 @@
1
- const { readConfig, parseArgs, processFile, resolveConfig, shouldProcessFile, SENSIBLE_DEFAULTS } = require('./index.cjs');
2
-
3
- jest.mock('fs');
4
- jest.mock('path');
5
- jest.mock('micromatch');
6
-
7
- describe('CRLF to LF Converter', () => {
8
- const MOCK_FILE_INFO = {
9
- './src/file1.txt': 'hello\r\nworld\r\n',
10
- './src/file2.js': 'console.log("test");\r\n',
11
- './src/subdir/file3.txt': 'test\r\n',
12
- './test/file1.txt': 'hello\r\nworld\r\n',
13
- './test/file2.js': 'console.log("test");\r\n',
14
- './test/subdir/file3.txt': 'test\r\n',
15
- './node_modules/file.js': 'console.log("test");\r\n',
16
- './node_modules/subdir/file4.txt': 'test\r\n',
17
- 'index.js': 'console.log("test");\r\n'
1
+ const mock = require('mock-fs');
2
+ const {
3
+ readConfig,
4
+ parseArgs,
5
+ processFile,
6
+ resolveConfig,
7
+ shouldProcessFile,
8
+ SENSIBLE_DEFAULTS,
9
+ } = require('./index.cjs');
10
+ const fs = require('fs');
11
+
12
+ function baseMock(overrides = {}) {
13
+ return {
14
+ 'src/file1.txt': 'hello\r\nworld\r\n',
15
+ 'src/file2.js': 'console.log("test");\r\n',
16
+ 'src/subdir/file3.txt': 'test\r\n',
17
+ 'test/file1.txt': 'hello\r\nworld\r\n',
18
+ 'test/file2.js': 'console.log("test");\r\n',
19
+ 'test/subdir/file3.txt': 'test\r\n',
20
+ 'node_modules/file.js': 'console.log("test");\r\n',
21
+ 'node_modules/subdir/file4.txt': 'test\r\n',
22
+ 'index.js': 'console.log("test");\r\n',
23
+ ...overrides,
18
24
  };
25
+ }
19
26
 
27
+ describe('CRLF to LF Converter', () => {
20
28
  let originalArgv;
21
29
 
22
30
  beforeEach(() => {
23
- jest.clearAllMocks();
24
- require('fs').__setMockFiles(MOCK_FILE_INFO);
31
+ mock(baseMock());
25
32
  originalArgv = process.argv;
26
33
  });
27
34
 
28
35
  afterEach(() => {
36
+ mock.restore();
29
37
  process.argv = originalArgv;
30
38
  });
31
39
 
32
40
  describe('readConfig', () => {
33
41
  it('should return config when valid config file is provided', async () => {
34
- // arrange
35
42
  const validConfig = {
36
43
  entry: './',
37
44
  include: ['*.js'],
38
- exclude: ['node_modules/**']
45
+ exclude: ['node_modules/**'],
39
46
  };
40
- require('fs').__setConfig(JSON.stringify(validConfig));
47
+ mock(baseMock({ '.lfifyrc.json': JSON.stringify(validConfig) }));
41
48
 
42
- // act
43
49
  const config = await readConfig('.lfifyrc.json');
44
50
 
45
- // assert
46
- expect(config).toEqual(expect.objectContaining({
47
- entry: expect.any(String),
48
- include: expect.any(Array),
49
- exclude: expect.any(Array)
50
- }));
51
+ expect(config).toEqual(
52
+ expect.objectContaining({
53
+ entry: expect.any(String),
54
+ include: expect.any(Array),
55
+ exclude: expect.any(Array),
56
+ }),
57
+ );
51
58
  });
52
59
 
53
60
  it('should throw error when config file is not found', async () => {
54
- // act & assert
55
61
  await expect(readConfig('.lfifyrc.json')).rejects.toThrow();
56
62
  });
57
63
 
58
64
  it('should throw error when config file is invalid json', async () => {
59
- // arrange
60
- require('fs').__setConfig('invalid json');
65
+ mock(baseMock({ '.lfifyrc.json': 'invalid json' }));
61
66
 
62
- // act & assert
63
67
  await expect(readConfig('.lfifyrc.json')).rejects.toThrow();
64
68
  });
65
69
  });
66
70
 
67
71
  describe('parseArgs', () => {
68
72
  it('should return config path when --config option is provided', () => {
69
- // arrange
70
- process.argv = ['node', 'lfify', '--config', './path/for/test/.lfifyrc.json'];
73
+ process.argv = [
74
+ 'node',
75
+ 'lfify',
76
+ '--config',
77
+ './path/for/test/.lfifyrc.json',
78
+ ];
71
79
 
72
- // act
73
80
  const options = parseArgs();
74
81
 
75
- // assert
76
82
  expect(options.configPath).toBe('./path/for/test/.lfifyrc.json');
77
83
  });
78
84
 
79
85
  it('should return default config path when --config option is not provided', () => {
80
- // arrange
81
86
  process.argv = ['node', 'lfify'];
82
87
 
83
- // act
84
88
  const options = parseArgs();
85
89
 
86
- // assert
87
90
  expect(options.configPath).toBe('.lfifyrc.json');
88
91
  });
89
92
 
@@ -94,7 +97,14 @@ describe('CRLF to LF Converter', () => {
94
97
  });
95
98
 
96
99
  it('should return multiple include patterns when multiple --include options are provided', () => {
97
- process.argv = ['node', 'lfify', '--include', '**/*.js', '--include', '**/*.ts'];
100
+ process.argv = [
101
+ 'node',
102
+ 'lfify',
103
+ '--include',
104
+ '**/*.js',
105
+ '--include',
106
+ '**/*.ts',
107
+ ];
98
108
  const options = parseArgs();
99
109
  expect(options.include).toEqual(['**/*.js', '**/*.ts']);
100
110
  });
@@ -106,33 +116,45 @@ describe('CRLF to LF Converter', () => {
106
116
  });
107
117
 
108
118
  it('should return multiple exclude patterns when multiple --exclude options are provided', () => {
109
- process.argv = ['node', 'lfify', '--exclude', 'node_modules/**', '--exclude', '.git/**'];
119
+ process.argv = [
120
+ 'node',
121
+ 'lfify',
122
+ '--exclude',
123
+ 'dist/**',
124
+ '--exclude',
125
+ 'coverage/**',
126
+ ];
110
127
  const options = parseArgs();
111
- expect(options.exclude).toEqual(['node_modules/**', '.git/**']);
128
+ expect(options.exclude).toEqual(['dist/**', 'coverage/**']);
112
129
  });
113
130
 
114
131
  it('should return entry path when --entry option is provided', () => {
115
132
  process.argv = ['node', 'lfify', '--entry', './src'];
116
133
  const options = parseArgs();
117
- expect(options.entry).toBe('./src');
134
+ expect(options.entry).toContain('src');
118
135
  });
119
136
 
120
137
  it('should handle all options together', () => {
121
138
  process.argv = [
122
- 'node', 'lfify',
123
- '--entry', './src',
124
- '--include', '**/*.js',
125
- '--include', '**/*.ts',
126
- '--exclude', 'node_modules/**',
127
- '--config', 'custom.json'
139
+ 'node',
140
+ 'lfify',
141
+ '--config',
142
+ 'custom.json',
143
+ '--include',
144
+ '*.js',
145
+ '--exclude',
146
+ 'dist/**',
147
+ '--entry',
148
+ './lib',
149
+ '--log-level',
150
+ 'info',
128
151
  ];
129
152
  const options = parseArgs();
130
- expect(options).toEqual({
131
- configPath: 'custom.json',
132
- entry: './src',
133
- include: ['**/*.js', '**/*.ts'],
134
- exclude: ['node_modules/**']
135
- });
153
+ expect(options.configPath).toBe('custom.json');
154
+ expect(options.include).toEqual(['*.js']);
155
+ expect(options.exclude).toEqual(['dist/**']);
156
+ expect(options.entry).toContain('lib');
157
+ expect(options.logLevel).toBe('info');
136
158
  });
137
159
 
138
160
  it('should return undefined for include/exclude/entry when not provided', () => {
@@ -142,77 +164,76 @@ describe('CRLF to LF Converter', () => {
142
164
  expect(options.exclude).toBeUndefined();
143
165
  expect(options.entry).toBeUndefined();
144
166
  });
167
+
168
+ it('should return logLevel when --log-level option is provided', () => {
169
+ process.argv = ['node', 'lfify', '--log-level', 'warn'];
170
+ const options = parseArgs();
171
+ expect(options.logLevel).toBe('warn');
172
+ });
173
+
174
+ it('should accept error, warn, info for --log-level', () => {
175
+ for (const level of ['error', 'warn', 'info']) {
176
+ process.argv = ['node', 'lfify', '--log-level', level];
177
+ expect(parseArgs().logLevel).toBe(level);
178
+ }
179
+ });
145
180
  });
146
181
 
147
182
  describe('shouldProcessFile', () => {
148
183
  it('should return true when file matches include pattern and does not match exclude pattern', () => {
149
- const config = {
150
- include: ['**/*.js'],
151
- exclude: ['node_modules/**']
152
- };
153
- expect(shouldProcessFile('src/file.js', config)).toBe(true);
184
+ const config = { include: ['**/*.js'], exclude: ['node_modules/**'] };
185
+ expect(shouldProcessFile('src/app.js', config)).toBe(true);
154
186
  });
155
187
 
156
188
  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);
189
+ const config = { include: ['**/*.js'], exclude: ['node_modules/**'] };
190
+ expect(shouldProcessFile('node_modules/pkg/index.js', config)).toBe(
191
+ false,
192
+ );
162
193
  });
163
194
 
164
195
  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);
196
+ const config = { include: ['**/*.js'], exclude: ['node_modules/**'] };
197
+ expect(shouldProcessFile('src/readme.txt', config)).toBe(false);
170
198
  });
171
199
 
172
200
  it('should handle multiple include patterns', () => {
173
201
  const config = {
174
202
  include: ['**/*.js', '**/*.ts'],
175
- exclude: []
203
+ exclude: ['node_modules/**'],
176
204
  };
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);
205
+ expect(shouldProcessFile('src/app.js', config)).toBe(true);
206
+ expect(shouldProcessFile('src/app.ts', config)).toBe(true);
180
207
  });
181
208
 
182
209
  it('should handle multiple exclude patterns', () => {
183
210
  const config = {
184
211
  include: ['**/*.js'],
185
- exclude: ['node_modules/**', 'dist/**', 'test/**']
212
+ exclude: ['node_modules/**', 'dist/**', 'build/**', 'coverage/**'],
186
213
  };
187
214
  expect(shouldProcessFile('src/file.js', config)).toBe(true);
188
- expect(shouldProcessFile('node_modules/pkg/index.js', config)).toBe(false);
215
+ expect(shouldProcessFile('node_modules/pkg/file.js', config)).toBe(false);
189
216
  expect(shouldProcessFile('dist/bundle.js', config)).toBe(false);
190
- expect(shouldProcessFile('test/unit.js', config)).toBe(false);
217
+ expect(shouldProcessFile('test/unit.js', config)).toBe(true);
191
218
  });
192
219
  });
193
220
 
194
221
  describe('processFile', () => {
195
222
  it('should convert CRLF to LF when file is processed', async () => {
196
- // arrange
197
223
  const shouldbe = 'hello\nworld\n';
198
224
 
199
- // act
200
225
  await processFile('./src/file1.txt');
201
- const content = await require('fs').promises.readFile('./src/file1.txt', 'utf8');
226
+ const content = await fs.promises.readFile('./src/file1.txt', 'utf8');
202
227
 
203
- // assert
204
228
  expect(content).toBe(shouldbe);
205
229
  });
206
230
 
207
231
  it('should not modify file when no CRLF exists', async () => {
208
- // arrange
209
- require('fs').__setMockFiles({ './src/clean.txt': 'hello\nworld\n' });
232
+ mock(baseMock({ 'src/clean.txt': 'hello\nworld\n' }));
210
233
 
211
- // act
212
234
  await processFile('./src/clean.txt');
213
- const content = await require('fs').promises.readFile('./src/clean.txt', 'utf8');
235
+ const content = await fs.promises.readFile('./src/clean.txt', 'utf8');
214
236
 
215
- // assert
216
237
  expect(content).toBe('hello\nworld\n');
217
238
  });
218
239
  });
@@ -222,7 +243,7 @@ describe('CRLF to LF Converter', () => {
222
243
  const options = {
223
244
  include: ['**/*.js'],
224
245
  exclude: ['node_modules/**'],
225
- entry: './src'
246
+ entry: './src',
226
247
  };
227
248
  const config = await resolveConfig(options);
228
249
 
@@ -239,28 +260,36 @@ describe('CRLF to LF Converter', () => {
239
260
  });
240
261
 
241
262
  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
- }));
263
+ mock(
264
+ baseMock({
265
+ '.lfifyrc.json': JSON.stringify({
266
+ entry: './',
267
+ include: ['**/*.md'],
268
+ exclude: ['dist/**'],
269
+ }),
270
+ }),
271
+ );
247
272
 
248
273
  const options = {
249
274
  configPath: '.lfifyrc.json',
250
- include: ['**/*.js'] // CLI should override config
275
+ include: ['**/*.js'],
251
276
  };
252
277
  const config = await resolveConfig(options);
253
278
 
254
- expect(config.include).toEqual(['**/*.js']); // CLI override
255
- expect(config.exclude).toEqual(['dist/**']); // from config file
279
+ expect(config.include).toEqual(['**/*.js']);
280
+ expect(config.exclude).toEqual(['dist/**']);
256
281
  });
257
282
 
258
283
  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
- }));
284
+ mock(
285
+ baseMock({
286
+ '.lfifyrc.json': JSON.stringify({
287
+ entry: './lib',
288
+ include: ['**/*.ts'],
289
+ exclude: ['test/**'],
290
+ }),
291
+ }),
292
+ );
264
293
 
265
294
  const options = { configPath: '.lfifyrc.json' };
266
295
  const config = await resolveConfig(options);
@@ -270,7 +299,6 @@ describe('CRLF to LF Converter', () => {
270
299
  });
271
300
 
272
301
  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
302
  const options = { configPath: 'nonexistent.json' };
275
303
  const config = await resolveConfig(options);
276
304
 
@@ -293,5 +321,43 @@ describe('CRLF to LF Converter', () => {
293
321
  expect(config.include).toEqual(SENSIBLE_DEFAULTS.include);
294
322
  expect(config.exclude).toEqual(['custom/**']);
295
323
  });
324
+
325
+ it('should include logLevel in config, defaulting to error', async () => {
326
+ const config = await resolveConfig({});
327
+ expect(config.logLevel).toBe('error');
328
+ });
329
+
330
+ it('should use logLevel from config file when provided', async () => {
331
+ mock(
332
+ baseMock({
333
+ '.lfifyrc.json': JSON.stringify({
334
+ entry: './',
335
+ include: ['**/*.js'],
336
+ exclude: ['node_modules/**'],
337
+ logLevel: 'info',
338
+ }),
339
+ }),
340
+ );
341
+ const config = await resolveConfig({ configPath: '.lfifyrc.json' });
342
+ expect(config.logLevel).toBe('info');
343
+ });
344
+
345
+ it('should override config file logLevel with CLI --log-level', async () => {
346
+ mock(
347
+ baseMock({
348
+ '.lfifyrc.json': JSON.stringify({
349
+ entry: './',
350
+ include: ['**/*.js'],
351
+ exclude: ['node_modules/**'],
352
+ logLevel: 'warn',
353
+ }),
354
+ }),
355
+ );
356
+ const config = await resolveConfig({
357
+ configPath: '.lfifyrc.json',
358
+ logLevel: 'info',
359
+ });
360
+ expect(config.logLevel).toBe('info');
361
+ });
296
362
  });
297
- });
363
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lfify",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "private": false,
5
5
  "description": "make your crlf to lf",
6
6
  "main": "index.cjs",
@@ -9,15 +9,20 @@
9
9
  "lfify": "./index.cjs"
10
10
  },
11
11
  "scripts": {
12
- "test": "jest",
13
- "lint": "eslint ."
12
+ "test": "npm run test:unit && npm run test:e2e",
13
+ "test:unit": "jest --testPathPattern=\"index\\.test\\.js\"",
14
+ "test:e2e": "jest --testPathPattern=\"index\\.e2e\\.test\\.js\"",
15
+ "lint": "eslint .",
16
+ "format": "prettier --write .",
17
+ "format:check": "prettier --check ."
14
18
  },
15
19
  "repository": {
16
20
  "type": "git",
17
21
  "url": "https://github.com/GyeongHoKim/lfify.git"
18
22
  },
19
23
  "publishConfig": {
20
- "registry": "https://registry.npmjs.org",
24
+ "registry": "https://registry.npmjs.org/",
25
+ "provenance": true,
21
26
  "tag": "latest"
22
27
  },
23
28
  "keywords": [
@@ -42,8 +47,12 @@
42
47
  "@semantic-release/npm": "^13.1.3",
43
48
  "@semantic-release/release-notes-generator": "^14.1.0",
44
49
  "eslint": "^9.15.0",
50
+ "eslint-config-prettier": "^10.1.8",
51
+ "eslint-plugin-prettier": "^5.5.5",
45
52
  "globals": "^15.12.0",
46
53
  "jest": "^29.7.0",
54
+ "mock-fs": "^5.5.0",
55
+ "prettier": "^3.8.1",
47
56
  "semantic-release": "^25.0.2"
48
57
  }
49
58
  }
package/__mocks__/fs.js DELETED
@@ -1,56 +0,0 @@
1
- const fs = jest.createMockFromModule('fs');
2
-
3
- const mockFiles = new Map();
4
-
5
- function __setMockFiles(newMockFiles) {
6
- mockFiles.clear();
7
- for (const [path, content] of Object.entries(newMockFiles)) {
8
- mockFiles.set(path, content);
9
- }
10
- }
11
-
12
- function __setConfig(stringifiedConfig, path = '.lfifyrc.json') {
13
- mockFiles.set(path, stringifiedConfig);
14
- }
15
-
16
- const promises = {
17
- /* eslint-disable-next-line no-unused-vars */
18
- readFile: jest.fn().mockImplementation((path, ...rest) => {
19
- if (mockFiles.has(path)) {
20
- return Promise.resolve(mockFiles.get(path));
21
- }
22
- const error = new Error(`ENOENT: no such file or directory, open '${path}'`);
23
- error.code = 'ENOENT';
24
- return Promise.reject(error);
25
- }),
26
-
27
- writeFile: jest.fn().mockImplementation((path, content) => {
28
- mockFiles.set(path, content);
29
- return Promise.resolve();
30
- }),
31
-
32
- /* eslint-disable-next-line no-unused-vars */
33
- readdir: jest.fn().mockImplementation((path, ...rest) => {
34
- const entries = [];
35
- for (const filePath of mockFiles.keys()) {
36
- if (filePath.startsWith(path)) {
37
- const relativePath = filePath.slice(path.length + 1);
38
- const name = relativePath.split('/')[0];
39
- if (name && !entries.some(e => e.name === name)) {
40
- entries.push({
41
- name,
42
- isFile: () => !name.includes('/'),
43
- isDirectory: () => name.includes('/')
44
- });
45
- }
46
- }
47
- }
48
- return Promise.resolve(entries);
49
- })
50
- };
51
-
52
- fs.promises = promises;
53
- fs.__setMockFiles = __setMockFiles;
54
- fs.__setConfig = __setConfig;
55
-
56
- module.exports = fs;
@@ -1,39 +0,0 @@
1
- const micromatch = jest.createMockFromModule('micromatch');
2
-
3
- micromatch.isMatch = jest.fn().mockImplementation((filePath, patterns) => {
4
- if (!Array.isArray(patterns)) {
5
- patterns = [patterns];
6
- }
7
-
8
- // 간단한 glob 패턴 매칭 구현
9
- return patterns.some(pattern => {
10
- // 정확한 매칭
11
- if (pattern === filePath) return true;
12
-
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
-
35
- return new RegExp(`^${regexPattern}$`).test(filePath);
36
- });
37
- });
38
-
39
- module.exports = micromatch;