ts-repo-utils 2.1.0 → 2.2.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/dist/functions/assert-ext.d.mts +2 -2
- package/dist/functions/assert-ext.d.mts.map +1 -1
- package/dist/functions/assert-ext.mjs +10 -7
- package/dist/functions/assert-ext.mjs.map +1 -1
- package/dist/functions/diff.d.mts +8 -2
- package/dist/functions/diff.d.mts.map +1 -1
- package/dist/functions/diff.mjs +7 -4
- package/dist/functions/diff.mjs.map +1 -1
- package/dist/functions/format.d.mts +11 -1
- package/dist/functions/format.d.mts.map +1 -1
- package/dist/functions/format.mjs +84 -110
- package/dist/functions/format.mjs.map +1 -1
- package/dist/functions/index.d.mts +1 -0
- package/dist/functions/index.d.mts.map +1 -1
- package/dist/functions/index.mjs +2 -1
- package/dist/functions/index.mjs.map +1 -1
- package/dist/functions/should-run.d.mts +3 -0
- package/dist/functions/should-run.d.mts.map +1 -0
- package/dist/functions/should-run.mjs +26 -0
- package/dist/functions/should-run.mjs.map +1 -0
- package/dist/globals.d.mjs +2 -0
- package/dist/globals.d.mjs.map +1 -0
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/functions/assert-ext.mts +16 -9
- package/src/functions/diff.mts +17 -3
- package/src/functions/format.mts +112 -130
- package/src/functions/format.test.mts +225 -16
- package/src/functions/index.mts +1 -0
- package/src/functions/should-run.mts +33 -0
package/src/functions/diff.mts
CHANGED
|
@@ -5,7 +5,12 @@ import '../node-global.mjs';
|
|
|
5
5
|
/**
|
|
6
6
|
* Get files that have been changed (git status).
|
|
7
7
|
*/
|
|
8
|
-
export const getUntrackedFiles = async (
|
|
8
|
+
export const getUntrackedFiles = async (
|
|
9
|
+
options?: Readonly<{
|
|
10
|
+
/** @default true */
|
|
11
|
+
excludeDeleted?: boolean;
|
|
12
|
+
}>,
|
|
13
|
+
): Promise<
|
|
9
14
|
Result<readonly string[], ExecException | Readonly<{ message: string }>>
|
|
10
15
|
> => {
|
|
11
16
|
// Get changed files from git status
|
|
@@ -29,7 +34,10 @@ export const getUntrackedFiles = async (): Promise<
|
|
|
29
34
|
.filter(
|
|
30
35
|
(file): file is string =>
|
|
31
36
|
// Filter out deleted files (status starts with 'D')
|
|
32
|
-
file !== undefined &&
|
|
37
|
+
file !== undefined &&
|
|
38
|
+
((options?.excludeDeleted ?? true)
|
|
39
|
+
? !stdout.includes(`D ${file}`)
|
|
40
|
+
: true),
|
|
33
41
|
);
|
|
34
42
|
|
|
35
43
|
return Result.ok(files);
|
|
@@ -40,11 +48,17 @@ export const getUntrackedFiles = async (): Promise<
|
|
|
40
48
|
*/
|
|
41
49
|
export const getDiffFrom = async (
|
|
42
50
|
base: string,
|
|
51
|
+
options?: Readonly<{
|
|
52
|
+
/** @default true */
|
|
53
|
+
excludeDeleted?: boolean;
|
|
54
|
+
}>,
|
|
43
55
|
): Promise<
|
|
44
56
|
Result<readonly string[], ExecException | Readonly<{ message: string }>>
|
|
45
57
|
> => {
|
|
46
58
|
// Get files that differ from base branch/commit (excluding deleted files)
|
|
47
|
-
const result = await $(
|
|
59
|
+
const result = await $(
|
|
60
|
+
`git diff --name-only ${base} ${(options?.excludeDeleted ?? true) ? '--diff-filter=d' : ''}`,
|
|
61
|
+
);
|
|
48
62
|
|
|
49
63
|
if (Result.isErr(result)) {
|
|
50
64
|
return result;
|
package/src/functions/format.mts
CHANGED
|
@@ -5,6 +5,64 @@ import { Result } from 'ts-data-forge';
|
|
|
5
5
|
import '../node-global.mjs';
|
|
6
6
|
import { getDiffFrom, getUntrackedFiles } from './diff.mjs';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Format a list of files using Prettier
|
|
10
|
+
* @param files - Array of file paths to format
|
|
11
|
+
* @returns 'ok' if successful, 'err' if any errors occurred
|
|
12
|
+
*/
|
|
13
|
+
export const formatFilesList = async (
|
|
14
|
+
files: readonly string[],
|
|
15
|
+
): Promise<'ok' | 'err'> => {
|
|
16
|
+
if (files.length === 0) {
|
|
17
|
+
echo('No files to format');
|
|
18
|
+
return 'ok';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
echo(`Formatting ${files.length} files...`);
|
|
22
|
+
|
|
23
|
+
// Format each file
|
|
24
|
+
const results = await Promise.allSettled(
|
|
25
|
+
files.map(async (filePath) => {
|
|
26
|
+
try {
|
|
27
|
+
// Read file content
|
|
28
|
+
const content = await readFile(filePath, 'utf8');
|
|
29
|
+
|
|
30
|
+
// Resolve prettier config for this file
|
|
31
|
+
const options = await prettier.resolveConfig(filePath);
|
|
32
|
+
|
|
33
|
+
// Check if file is ignored by prettier
|
|
34
|
+
const fileInfo = await prettier.getFileInfo(filePath, {
|
|
35
|
+
ignorePath: '.prettierignore',
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (fileInfo.ignored) {
|
|
39
|
+
echo(`Skipping ignored file: ${filePath}`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Format the content
|
|
44
|
+
const formatted = await prettier.format(content, {
|
|
45
|
+
...options,
|
|
46
|
+
filepath: filePath,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Only write if content changed
|
|
50
|
+
if (formatted !== content) {
|
|
51
|
+
await writeFile(filePath, formatted, 'utf8');
|
|
52
|
+
echo(`Formatted: ${filePath}`);
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error(`Error formatting ${filePath}:`, error);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// Check if any formatting failed
|
|
62
|
+
const hasErrors = results.some((result) => result.status === 'rejected');
|
|
63
|
+
return hasErrors ? 'err' : 'ok';
|
|
64
|
+
};
|
|
65
|
+
|
|
8
66
|
/**
|
|
9
67
|
* Format files matching the given glob pattern using Prettier
|
|
10
68
|
* @param pathGlob - Glob pattern to match files
|
|
@@ -24,49 +82,7 @@ export const formatFiles = async (pathGlob: string): Promise<'ok' | 'err'> => {
|
|
|
24
82
|
return 'ok';
|
|
25
83
|
}
|
|
26
84
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// Format each file
|
|
30
|
-
const results = await Promise.allSettled(
|
|
31
|
-
files.map(async (filePath) => {
|
|
32
|
-
try {
|
|
33
|
-
// Read file content
|
|
34
|
-
const content = await readFile(filePath, 'utf8');
|
|
35
|
-
|
|
36
|
-
// Resolve prettier config for this file
|
|
37
|
-
const options = await prettier.resolveConfig(filePath);
|
|
38
|
-
|
|
39
|
-
// Check if file is ignored by prettier
|
|
40
|
-
const fileInfo = await prettier.getFileInfo(filePath, {
|
|
41
|
-
ignorePath: '.prettierignore',
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
if (fileInfo.ignored) {
|
|
45
|
-
echo(`Skipping ignored file: ${filePath}`);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Format the content
|
|
50
|
-
const formatted = await prettier.format(content, {
|
|
51
|
-
...options,
|
|
52
|
-
filepath: filePath,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Only write if content changed
|
|
56
|
-
if (formatted !== content) {
|
|
57
|
-
await writeFile(filePath, formatted, 'utf8');
|
|
58
|
-
echo(`Formatted: ${filePath}`);
|
|
59
|
-
}
|
|
60
|
-
} catch (error) {
|
|
61
|
-
console.error(`Error formatting ${filePath}:`, error);
|
|
62
|
-
throw error;
|
|
63
|
-
}
|
|
64
|
-
}),
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
// Check if any formatting failed
|
|
68
|
-
const hasErrors = results.some((result) => result.status === 'rejected');
|
|
69
|
-
return hasErrors ? 'err' : 'ok';
|
|
85
|
+
return await formatFilesList(files);
|
|
70
86
|
} catch (error) {
|
|
71
87
|
console.error('Error in formatFiles:', error);
|
|
72
88
|
return 'err';
|
|
@@ -95,53 +111,29 @@ export const formatUntracked = async (): Promise<'ok' | 'err'> => {
|
|
|
95
111
|
|
|
96
112
|
echo('Formatting changed files:', files);
|
|
97
113
|
|
|
98
|
-
//
|
|
99
|
-
const
|
|
114
|
+
// Filter out non-existent files before formatting
|
|
115
|
+
const fileExistenceChecks = await Promise.allSettled(
|
|
100
116
|
files.map(async (filePath) => {
|
|
101
117
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Resolve prettier config for this file
|
|
110
|
-
const options = await prettier.resolveConfig(filePath);
|
|
111
|
-
|
|
112
|
-
// Check if file is ignored by prettier
|
|
113
|
-
const fileInfo = await prettier.getFileInfo(filePath, {
|
|
114
|
-
ignorePath: '.prettierignore',
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
if (fileInfo.ignored) {
|
|
118
|
-
echo(`Skipping ignored file: ${filePath}`);
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Format the content
|
|
123
|
-
const formatted = await prettier.format(content, {
|
|
124
|
-
...options,
|
|
125
|
-
filepath: filePath,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// Only write if content changed
|
|
129
|
-
if (formatted !== content) {
|
|
130
|
-
await writeFile(filePath, formatted, 'utf8');
|
|
131
|
-
echo(`Formatted: ${filePath}`);
|
|
132
|
-
}
|
|
133
|
-
} catch (error) {
|
|
134
|
-
console.error(`Error formatting ${filePath}:`, error);
|
|
135
|
-
throw error;
|
|
118
|
+
await readFile(filePath, 'utf8');
|
|
119
|
+
return filePath;
|
|
120
|
+
} catch {
|
|
121
|
+
echo(`Skipping non-existent file: ${filePath}`);
|
|
122
|
+
return undefined;
|
|
136
123
|
}
|
|
137
124
|
}),
|
|
138
125
|
);
|
|
139
126
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
127
|
+
const existingFiles = fileExistenceChecks
|
|
128
|
+
.filter(
|
|
129
|
+
(result): result is PromiseFulfilledResult<string> =>
|
|
130
|
+
result.status === 'fulfilled' && result.value !== undefined,
|
|
131
|
+
)
|
|
132
|
+
.map((result) => result.value);
|
|
133
|
+
|
|
134
|
+
return await formatFilesList(existingFiles);
|
|
143
135
|
} catch (error) {
|
|
144
|
-
console.error('Error in
|
|
136
|
+
console.error('Error in formatUntracked:', error);
|
|
145
137
|
return 'err';
|
|
146
138
|
}
|
|
147
139
|
};
|
|
@@ -149,9 +141,14 @@ export const formatUntracked = async (): Promise<'ok' | 'err'> => {
|
|
|
149
141
|
/**
|
|
150
142
|
* Format only files that differ from the specified base branch or commit
|
|
151
143
|
* @param base - Base branch name or commit hash to compare against (defaults to 'main')
|
|
144
|
+
* @param options - Options for formatting
|
|
145
|
+
* @param options.includeUntracked - Include untracked files in addition to diff files (default is true)
|
|
152
146
|
* @returns 'ok' if successful, 'err' if any errors occurred
|
|
153
147
|
*/
|
|
154
|
-
export const formatDiffFrom = async (
|
|
148
|
+
export const formatDiffFrom = async (
|
|
149
|
+
base: string,
|
|
150
|
+
options?: Readonly<{ includeUntracked?: boolean }>,
|
|
151
|
+
): Promise<'ok' | 'err'> => {
|
|
155
152
|
try {
|
|
156
153
|
// Get files that differ from base branch/commit (excluding deleted files)
|
|
157
154
|
const diffFromBaseResult = await getDiffFrom(base);
|
|
@@ -161,56 +158,41 @@ export const formatDiffFrom = async (base: string): Promise<'ok' | 'err'> => {
|
|
|
161
158
|
return 'err';
|
|
162
159
|
}
|
|
163
160
|
|
|
164
|
-
const
|
|
161
|
+
const diffFiles = diffFromBaseResult.value;
|
|
162
|
+
let allFiles = diffFiles;
|
|
163
|
+
|
|
164
|
+
// If includeUntracked is true, also get untracked files
|
|
165
|
+
if (options?.includeUntracked ?? true) {
|
|
166
|
+
const untrackedFilesResult = await getUntrackedFiles();
|
|
167
|
+
|
|
168
|
+
if (Result.isErr(untrackedFilesResult)) {
|
|
169
|
+
console.error(
|
|
170
|
+
'Error getting untracked files:',
|
|
171
|
+
untrackedFilesResult.value,
|
|
172
|
+
);
|
|
173
|
+
return 'err';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const untrackedFiles = untrackedFilesResult.value;
|
|
177
|
+
|
|
178
|
+
// Combine and deduplicate files
|
|
179
|
+
const uniqueFiles = new Set([...diffFiles, ...untrackedFiles]);
|
|
180
|
+
allFiles = Array.from(uniqueFiles);
|
|
181
|
+
|
|
182
|
+
echo(
|
|
183
|
+
`Formatting files that differ from ${base} and untracked files:`,
|
|
184
|
+
allFiles,
|
|
185
|
+
);
|
|
186
|
+
} else {
|
|
187
|
+
echo(`Formatting files that differ from ${base}:`, allFiles);
|
|
188
|
+
}
|
|
165
189
|
|
|
166
|
-
if (
|
|
167
|
-
echo(`No files
|
|
190
|
+
if (allFiles.length === 0) {
|
|
191
|
+
echo(`No files to format`);
|
|
168
192
|
return 'ok';
|
|
169
193
|
}
|
|
170
194
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
// Format each file
|
|
174
|
-
const results = await Promise.allSettled(
|
|
175
|
-
files.map(async (filePath) => {
|
|
176
|
-
try {
|
|
177
|
-
// Read file content
|
|
178
|
-
const content = await readFile(filePath, 'utf8');
|
|
179
|
-
|
|
180
|
-
// Resolve prettier config for this file
|
|
181
|
-
const options = await prettier.resolveConfig(filePath);
|
|
182
|
-
|
|
183
|
-
// Check if file is ignored by prettier
|
|
184
|
-
const fileInfo = await prettier.getFileInfo(filePath, {
|
|
185
|
-
ignorePath: '.prettierignore',
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
if (fileInfo.ignored) {
|
|
189
|
-
echo(`Skipping ignored file: ${filePath}`);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Format the content
|
|
194
|
-
const formatted = await prettier.format(content, {
|
|
195
|
-
...options,
|
|
196
|
-
filepath: filePath,
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
// Only write if content changed
|
|
200
|
-
if (formatted !== content) {
|
|
201
|
-
await writeFile(filePath, formatted, 'utf8');
|
|
202
|
-
echo(`Formatted: ${filePath}`);
|
|
203
|
-
}
|
|
204
|
-
} catch (error) {
|
|
205
|
-
console.error(`Error formatting ${filePath}:`, error);
|
|
206
|
-
throw error;
|
|
207
|
-
}
|
|
208
|
-
}),
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
// Check if any formatting failed
|
|
212
|
-
const hasErrors = results.some((result) => result.status === 'rejected');
|
|
213
|
-
return hasErrors ? 'err' : 'ok';
|
|
195
|
+
return await formatFilesList(allFiles);
|
|
214
196
|
} catch (error) {
|
|
215
197
|
console.error('Error in formatDiffFrom:', error);
|
|
216
198
|
return 'err';
|
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { Result } from 'ts-data-forge';
|
|
5
|
+
import { getDiffFrom, getUntrackedFiles } from './diff.mjs';
|
|
6
|
+
import { formatDiffFrom, formatFiles, formatFilesList } from './format.mjs';
|
|
7
|
+
|
|
8
|
+
vi.mock('./diff.mjs', () => ({
|
|
9
|
+
getDiffFrom: vi.fn(),
|
|
10
|
+
getUntrackedFiles: vi.fn(),
|
|
11
|
+
}));
|
|
5
12
|
|
|
6
13
|
describe('formatFiles', () => {
|
|
7
|
-
const testDir = join(process.cwd(), 'test-format-files');
|
|
14
|
+
const testDir = path.join(process.cwd(), 'test-format-files');
|
|
8
15
|
|
|
9
16
|
// Helper to create a test file with unformatted content
|
|
10
17
|
const createTestFile = async (
|
|
11
18
|
filename: string,
|
|
12
19
|
content: string,
|
|
13
20
|
): Promise<string> => {
|
|
14
|
-
const filePath = join(testDir, filename);
|
|
15
|
-
await writeFile(filePath, content, 'utf8');
|
|
21
|
+
const filePath = path.join(testDir, filename);
|
|
22
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
16
23
|
return filePath;
|
|
17
24
|
};
|
|
18
25
|
|
|
19
26
|
// Helper to read file content
|
|
20
|
-
const readTestFile = async (filePath: string): Promise<string> =>
|
|
21
|
-
|
|
22
|
-
return readFile(filePath, 'utf8');
|
|
23
|
-
};
|
|
27
|
+
const readTestFile = async (filePath: string): Promise<string> =>
|
|
28
|
+
fs.readFile(filePath, 'utf8');
|
|
24
29
|
|
|
25
30
|
test('should format files matching glob pattern', async () => {
|
|
26
31
|
// Setup test directory
|
|
27
|
-
await mkdir(testDir, { recursive: true });
|
|
32
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
28
33
|
|
|
29
34
|
try {
|
|
30
35
|
// Create test files with unformatted code
|
|
@@ -71,11 +76,11 @@ describe('formatFiles', () => {
|
|
|
71
76
|
);
|
|
72
77
|
|
|
73
78
|
// Check that non-matching file was not touched
|
|
74
|
-
const mdContent = await readTestFile(join(testDir, 'test.md'));
|
|
79
|
+
const mdContent = await readTestFile(path.join(testDir, 'test.md'));
|
|
75
80
|
expect(mdContent).toBe('# Test\n\nSome spaces');
|
|
76
81
|
} finally {
|
|
77
82
|
// Cleanup
|
|
78
|
-
await rm(testDir, { recursive: true, force: true });
|
|
83
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
79
84
|
}
|
|
80
85
|
});
|
|
81
86
|
|
|
@@ -86,12 +91,12 @@ describe('formatFiles', () => {
|
|
|
86
91
|
|
|
87
92
|
test('should handle nested directories with glob pattern', async () => {
|
|
88
93
|
// Setup test directory with nested structure
|
|
89
|
-
await mkdir(join(testDir, 'src', 'utils'), { recursive: true });
|
|
94
|
+
await fs.mkdir(path.join(testDir, 'src', 'utils'), { recursive: true });
|
|
90
95
|
|
|
91
96
|
try {
|
|
92
97
|
// Create nested test file
|
|
93
98
|
const nestedFile = await createTestFile(
|
|
94
|
-
join('src', 'utils', 'helper.ts'),
|
|
99
|
+
path.join('src', 'utils', 'helper.ts'),
|
|
95
100
|
dedent`
|
|
96
101
|
export const helper=(x:number)=>{return x*2}
|
|
97
102
|
`,
|
|
@@ -112,7 +117,211 @@ describe('formatFiles', () => {
|
|
|
112
117
|
);
|
|
113
118
|
} finally {
|
|
114
119
|
// Cleanup
|
|
115
|
-
await rm(testDir, { recursive: true, force: true });
|
|
120
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('formatFilesList', () => {
|
|
126
|
+
const testDir = path.join(process.cwd(), 'test-format-files-list');
|
|
127
|
+
|
|
128
|
+
// Helper to create a test file with unformatted content
|
|
129
|
+
const createTestFile = async (
|
|
130
|
+
filename: string,
|
|
131
|
+
content: string,
|
|
132
|
+
): Promise<string> => {
|
|
133
|
+
const filePath = path.join(testDir, filename);
|
|
134
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
135
|
+
return filePath;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Helper to read file content
|
|
139
|
+
const readTestFile = async (filePath: string): Promise<string> =>
|
|
140
|
+
fs.readFile(filePath, 'utf8');
|
|
141
|
+
|
|
142
|
+
test('should format a list of files', async () => {
|
|
143
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
// Create test files
|
|
147
|
+
const file1 = await createTestFile(
|
|
148
|
+
'file1.ts',
|
|
149
|
+
dedent`
|
|
150
|
+
const x={a:1,b:2}
|
|
151
|
+
`,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const file2 = await createTestFile(
|
|
155
|
+
'file2.ts',
|
|
156
|
+
dedent`
|
|
157
|
+
function test(){return"hello"}
|
|
158
|
+
`,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
// Format the files
|
|
162
|
+
const result = await formatFilesList([file1, file2]);
|
|
163
|
+
expect(result).toBe('ok');
|
|
164
|
+
|
|
165
|
+
// Check formatted content
|
|
166
|
+
const content1 = await readTestFile(file1);
|
|
167
|
+
expect(content1).toBe(
|
|
168
|
+
`${dedent`
|
|
169
|
+
const x = { a: 1, b: 2 };
|
|
170
|
+
`}\n`,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const content2 = await readTestFile(file2);
|
|
174
|
+
expect(content2).toBe(
|
|
175
|
+
`${dedent`
|
|
176
|
+
function test() {
|
|
177
|
+
return 'hello';
|
|
178
|
+
}
|
|
179
|
+
`}\n`,
|
|
180
|
+
);
|
|
181
|
+
} finally {
|
|
182
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test('should return ok for empty file list', async () => {
|
|
187
|
+
const result = await formatFilesList([]);
|
|
188
|
+
expect(result).toBe('ok');
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('formatDiffFrom', () => {
|
|
193
|
+
const testDir = path.join(process.cwd(), 'test-format-diff');
|
|
194
|
+
|
|
195
|
+
const createTestFile = async (
|
|
196
|
+
filename: string,
|
|
197
|
+
content: string,
|
|
198
|
+
): Promise<string> => {
|
|
199
|
+
const filePath = path.join(testDir, filename);
|
|
200
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
201
|
+
return filePath;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const readTestFile = async (filePath: string): Promise<string> =>
|
|
205
|
+
fs.readFile(filePath, 'utf8');
|
|
206
|
+
|
|
207
|
+
beforeEach(() => {
|
|
208
|
+
vi.clearAllMocks();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test('should format files from diff', async () => {
|
|
212
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const file1 = await createTestFile(
|
|
216
|
+
'diff1.ts',
|
|
217
|
+
dedent`
|
|
218
|
+
const a=1;const b=2
|
|
219
|
+
`,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// Mock getDiffFrom to return our test file
|
|
223
|
+
vi.mocked(getDiffFrom).mockResolvedValue(Result.ok([file1]));
|
|
224
|
+
|
|
225
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(Result.ok([]));
|
|
226
|
+
|
|
227
|
+
const result = await formatDiffFrom('main');
|
|
228
|
+
expect(result).toBe('ok');
|
|
229
|
+
|
|
230
|
+
// Check file was formatted
|
|
231
|
+
const content = await readTestFile(file1);
|
|
232
|
+
expect(content).toBe(
|
|
233
|
+
`${dedent`
|
|
234
|
+
const a = 1;
|
|
235
|
+
const b = 2;
|
|
236
|
+
`}\n`,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
expect(getDiffFrom).toHaveBeenCalledWith('main');
|
|
240
|
+
} finally {
|
|
241
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('should include untracked files when option is set', async () => {
|
|
246
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const diffFile = await createTestFile(
|
|
250
|
+
'diff.ts',
|
|
251
|
+
dedent`
|
|
252
|
+
const diff=true
|
|
253
|
+
`,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const untrackedFile = await createTestFile(
|
|
257
|
+
'untracked.ts',
|
|
258
|
+
dedent`
|
|
259
|
+
const untracked=true
|
|
260
|
+
`,
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
// Mock both functions
|
|
264
|
+
vi.mocked(getDiffFrom).mockResolvedValue(Result.ok([diffFile]));
|
|
265
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(
|
|
266
|
+
Result.ok([untrackedFile]),
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const result = await formatDiffFrom('main', { includeUntracked: true });
|
|
270
|
+
expect(result).toBe('ok');
|
|
271
|
+
|
|
272
|
+
// Check both files were formatted
|
|
273
|
+
const diffContent = await readTestFile(diffFile);
|
|
274
|
+
expect(diffContent).toBe(
|
|
275
|
+
`${dedent`
|
|
276
|
+
const diff = true;
|
|
277
|
+
`}\n`,
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
const untrackedContent = await readTestFile(untrackedFile);
|
|
281
|
+
expect(untrackedContent).toBe(
|
|
282
|
+
`${dedent`
|
|
283
|
+
const untracked = true;
|
|
284
|
+
`}\n`,
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
expect(getDiffFrom).toHaveBeenCalledWith('main');
|
|
288
|
+
expect(getUntrackedFiles).toHaveBeenCalled();
|
|
289
|
+
} finally {
|
|
290
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
test('should deduplicate files when including untracked', async () => {
|
|
295
|
+
await fs.mkdir(testDir, { recursive: true });
|
|
296
|
+
|
|
297
|
+
try {
|
|
298
|
+
const sharedFile = await createTestFile(
|
|
299
|
+
'shared.ts',
|
|
300
|
+
dedent`
|
|
301
|
+
const shared={value:1}
|
|
302
|
+
`,
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Mock both functions to return the same file
|
|
306
|
+
vi.mocked(getDiffFrom).mockResolvedValue(Result.ok([sharedFile]));
|
|
307
|
+
vi.mocked(getUntrackedFiles).mockResolvedValue(Result.ok([sharedFile]));
|
|
308
|
+
|
|
309
|
+
const result = await formatDiffFrom('main', { includeUntracked: true });
|
|
310
|
+
expect(result).toBe('ok');
|
|
311
|
+
|
|
312
|
+
// Verify both functions were called
|
|
313
|
+
expect(getDiffFrom).toHaveBeenCalledWith('main');
|
|
314
|
+
expect(getUntrackedFiles).toHaveBeenCalled();
|
|
315
|
+
|
|
316
|
+
// Check that the file was formatted (content should change)
|
|
317
|
+
const finalContent = await readTestFile(sharedFile);
|
|
318
|
+
expect(finalContent).toBe(
|
|
319
|
+
`${dedent`
|
|
320
|
+
const shared = { value: 1 };
|
|
321
|
+
`}\n`,
|
|
322
|
+
);
|
|
323
|
+
} finally {
|
|
324
|
+
await fs.rm(testDir, { recursive: true, force: true });
|
|
116
325
|
}
|
|
117
326
|
});
|
|
118
327
|
});
|
package/src/functions/index.mts
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { pipe, Result } from 'ts-data-forge';
|
|
2
|
+
import '../node-global.mjs';
|
|
3
|
+
import { getDiffFrom } from './diff.mjs';
|
|
4
|
+
|
|
5
|
+
export const checkShouldRunTypeChecks = async (): Promise<void> => {
|
|
6
|
+
// paths-ignore:
|
|
7
|
+
// - '.cspell.json'
|
|
8
|
+
// - '**.md'
|
|
9
|
+
// - '**.txt'
|
|
10
|
+
// - 'docs/**'
|
|
11
|
+
|
|
12
|
+
const GITHUB_OUTPUT = process.env['GITHUB_OUTPUT'];
|
|
13
|
+
|
|
14
|
+
const files = await getDiffFrom('origin/main');
|
|
15
|
+
|
|
16
|
+
if (Result.isErr(files)) {
|
|
17
|
+
console.error('Error getting diff:', files.value);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const shouldRunTsChecks: boolean = !files.value.every(
|
|
22
|
+
(file) =>
|
|
23
|
+
file === '.cspell.json' ||
|
|
24
|
+
file.startsWith('docs/') ||
|
|
25
|
+
pipe(path.basename(file)).map(
|
|
26
|
+
(filename) => filename.endsWith('.md') || filename.endsWith('.txt'),
|
|
27
|
+
).value,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (GITHUB_OUTPUT !== undefined) {
|
|
31
|
+
await fs.appendFile(GITHUB_OUTPUT, `should_run=${shouldRunTsChecks}\n`);
|
|
32
|
+
}
|
|
33
|
+
};
|