ts-repo-utils 6.0.5 → 6.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/README.md +11 -7
- package/dist/cmd/assert-repo-is-clean.mjs +1 -1
- package/dist/cmd/check-should-run-type-checks.mjs +1 -1
- package/dist/cmd/format-diff-from.mjs +29 -8
- package/dist/cmd/format-diff-from.mjs.map +1 -1
- package/dist/cmd/format-uncommitted.d.mts +3 -0
- package/dist/cmd/format-uncommitted.d.mts.map +1 -0
- package/dist/cmd/format-uncommitted.mjs +59 -0
- package/dist/cmd/format-uncommitted.mjs.map +1 -0
- package/dist/cmd/gen-index-ts.mjs +1 -1
- package/dist/functions/diff.d.mts +32 -2
- package/dist/functions/diff.d.mts.map +1 -1
- package/dist/functions/diff.mjs +47 -29
- package/dist/functions/diff.mjs.map +1 -1
- package/dist/functions/exec-async.d.mts +2 -2
- package/dist/functions/exec-async.d.mts.map +1 -1
- package/dist/functions/format.d.mts +20 -11
- package/dist/functions/format.d.mts.map +1 -1
- package/dist/functions/format.mjs +134 -95
- package/dist/functions/format.mjs.map +1 -1
- package/dist/functions/index.mjs +2 -2
- package/dist/index.d.mts +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/cmd/assert-repo-is-clean.mts +1 -1
- package/src/cmd/check-should-run-type-checks.mts +1 -1
- package/src/cmd/format-diff-from.mts +35 -9
- package/src/cmd/format-uncommitted.mts +67 -0
- package/src/cmd/gen-index-ts.mts +1 -1
- package/src/functions/diff.mts +85 -32
- package/src/functions/diff.test.mts +569 -102
- package/src/functions/exec-async.mts +2 -2
- package/src/functions/exec-async.test.mts +77 -47
- package/src/functions/format.mts +224 -141
- package/src/functions/format.test.mts +625 -20
- package/src/functions/workspace-utils/run-cmd-in-stages.test.mts +266 -0
- package/src/index.mts +2 -0
- package/dist/cmd/format-untracked.d.mts +0 -3
- package/dist/cmd/format-untracked.d.mts.map +0 -1
- package/dist/cmd/format-untracked.mjs +0 -34
- package/dist/cmd/format-untracked.mjs.map +0 -1
- package/src/cmd/format-untracked.mts +0 -31
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/* eslint-disable vitest/no-restricted-vi-methods */
|
|
2
|
+
import '../../node-global.mjs';
|
|
3
|
+
import { executeStages } from './execute-parallel.mjs';
|
|
4
|
+
import { getWorkspacePackages } from './get-workspace-packages.mjs';
|
|
5
|
+
import { runCmdInStagesAcrossWorkspaces } from './run-cmd-in-stages.mjs';
|
|
6
|
+
import { type Package } from './types.mjs';
|
|
7
|
+
|
|
8
|
+
// Mock the dependencies
|
|
9
|
+
vi.mock('./execute-parallel.mjs', () => ({
|
|
10
|
+
executeStages: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
vi.mock('./get-workspace-packages.mjs', () => ({
|
|
14
|
+
getWorkspacePackages: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe('runCmdInStagesAcrossWorkspaces', () => {
|
|
18
|
+
type MockedSpies = Readonly<{
|
|
19
|
+
consoleLogSpy: ReturnType<typeof vi.spyOn>;
|
|
20
|
+
consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
|
21
|
+
processExitSpy: ReturnType<typeof vi.spyOn>;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
const setupSpies = (): MockedSpies => {
|
|
25
|
+
vi.clearAllMocks();
|
|
26
|
+
const consoleLogSpy = vi
|
|
27
|
+
.spyOn(console, 'log')
|
|
28
|
+
.mockImplementation((): void => {});
|
|
29
|
+
const consoleErrorSpy = vi
|
|
30
|
+
.spyOn(console, 'error')
|
|
31
|
+
.mockImplementation((): void => {});
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
33
|
+
const processExitSpy = vi
|
|
34
|
+
.spyOn(process, 'exit')
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
36
|
+
.mockImplementation((): never => undefined as never) as ReturnType<
|
|
37
|
+
typeof vi.spyOn
|
|
38
|
+
>;
|
|
39
|
+
|
|
40
|
+
return { consoleLogSpy, consoleErrorSpy, processExitSpy };
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
44
|
+
const cleanupSpies = (spies: MockedSpies): void => {
|
|
45
|
+
spies.consoleLogSpy.mockRestore();
|
|
46
|
+
spies.consoleErrorSpy.mockRestore();
|
|
47
|
+
spies.processExitSpy.mockRestore();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
test('should fail fast and exit immediately when executeStages throws an error', async () => {
|
|
51
|
+
const spies = setupSpies();
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Mock workspace packages
|
|
55
|
+
const mockPackages: Package[] = [
|
|
56
|
+
{
|
|
57
|
+
name: 'package-a',
|
|
58
|
+
path: '/test/package-a',
|
|
59
|
+
packageJson: { name: 'package-a', scripts: { test: 'exit 1' } },
|
|
60
|
+
dependencies: {},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'package-b',
|
|
64
|
+
path: '/test/package-b',
|
|
65
|
+
packageJson: { name: 'package-b', scripts: { test: 'echo success' } },
|
|
66
|
+
dependencies: {},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'package-c',
|
|
70
|
+
path: '/test/package-c',
|
|
71
|
+
packageJson: { name: 'package-c', scripts: { test: 'echo success' } },
|
|
72
|
+
dependencies: {},
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
vi.mocked(getWorkspacePackages).mockResolvedValue(mockPackages);
|
|
77
|
+
|
|
78
|
+
// Mock executeStages to throw an error (simulating a failed command)
|
|
79
|
+
const mockError = new Error('package-a exited with code 1');
|
|
80
|
+
vi.mocked(executeStages).mockRejectedValue(mockError);
|
|
81
|
+
|
|
82
|
+
// Record start time
|
|
83
|
+
const startTime = Date.now();
|
|
84
|
+
|
|
85
|
+
// Execute the function
|
|
86
|
+
await runCmdInStagesAcrossWorkspaces({
|
|
87
|
+
rootPackageJsonDir: '/test',
|
|
88
|
+
cmd: 'test',
|
|
89
|
+
concurrency: 2,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Record end time
|
|
93
|
+
const endTime = Date.now();
|
|
94
|
+
const executionTime = endTime - startTime;
|
|
95
|
+
|
|
96
|
+
// Verify that execution was fast (fail-fast behavior)
|
|
97
|
+
// Should complete within 100ms since it should fail immediately
|
|
98
|
+
expect(executionTime).toBeLessThan(100);
|
|
99
|
+
|
|
100
|
+
// Verify executeStages was called
|
|
101
|
+
expect(executeStages).toHaveBeenCalledWith(mockPackages, 'test', 2);
|
|
102
|
+
|
|
103
|
+
// Verify console.error was called with fail-fast message
|
|
104
|
+
expect(spies.consoleErrorSpy).toHaveBeenCalledWith(
|
|
105
|
+
'\n❌ test failed (fail-fast mode stopped execution):',
|
|
106
|
+
);
|
|
107
|
+
expect(spies.consoleErrorSpy).toHaveBeenCalledWith(
|
|
108
|
+
'package-a exited with code 1',
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// Verify process.exit was called with code 1
|
|
112
|
+
expect(spies.processExitSpy).toHaveBeenCalledWith(1);
|
|
113
|
+
|
|
114
|
+
// Verify success message was NOT called
|
|
115
|
+
expect(spies.consoleLogSpy).not.toHaveBeenCalledWith(
|
|
116
|
+
expect.stringContaining('✅ test completed successfully'),
|
|
117
|
+
);
|
|
118
|
+
} finally {
|
|
119
|
+
cleanupSpies(spies);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('should complete successfully when no errors occur', async () => {
|
|
124
|
+
const spies = setupSpies();
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// Mock workspace packages
|
|
128
|
+
const mockPackages: Package[] = [
|
|
129
|
+
{
|
|
130
|
+
name: 'package-a',
|
|
131
|
+
path: '/test/package-a',
|
|
132
|
+
packageJson: { name: 'package-a', scripts: { test: 'echo test' } },
|
|
133
|
+
dependencies: {},
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'package-b',
|
|
137
|
+
path: '/test/package-b',
|
|
138
|
+
packageJson: { name: 'package-b', scripts: { test: 'echo test' } },
|
|
139
|
+
dependencies: {},
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
vi.mocked(getWorkspacePackages).mockResolvedValue(mockPackages);
|
|
144
|
+
vi.mocked(executeStages).mockResolvedValue(undefined);
|
|
145
|
+
|
|
146
|
+
// Execute the function
|
|
147
|
+
await runCmdInStagesAcrossWorkspaces({
|
|
148
|
+
rootPackageJsonDir: '/test',
|
|
149
|
+
cmd: 'test',
|
|
150
|
+
concurrency: 2,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Verify executeStages was called
|
|
154
|
+
expect(executeStages).toHaveBeenCalledWith(mockPackages, 'test', 2);
|
|
155
|
+
|
|
156
|
+
// Verify success messages were called
|
|
157
|
+
expect(spies.consoleLogSpy).toHaveBeenCalledWith(
|
|
158
|
+
'\nStarting test across 2 packages (fail-fast mode)...',
|
|
159
|
+
);
|
|
160
|
+
expect(spies.consoleLogSpy).toHaveBeenCalledWith(
|
|
161
|
+
'\n✅ test completed successfully (all stages)',
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Verify process.exit was NOT called
|
|
165
|
+
expect(spies.processExitSpy).not.toHaveBeenCalled();
|
|
166
|
+
|
|
167
|
+
// Verify error messages were NOT called
|
|
168
|
+
expect(spies.consoleErrorSpy).not.toHaveBeenCalled();
|
|
169
|
+
} finally {
|
|
170
|
+
cleanupSpies(spies);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('should apply package filtering correctly', async () => {
|
|
175
|
+
const spies = setupSpies();
|
|
176
|
+
|
|
177
|
+
try {
|
|
178
|
+
// Mock workspace packages
|
|
179
|
+
const mockPackages: Package[] = [
|
|
180
|
+
{
|
|
181
|
+
name: 'package-a',
|
|
182
|
+
path: '/test/package-a',
|
|
183
|
+
packageJson: { name: 'package-a', scripts: { test: 'echo test' } },
|
|
184
|
+
dependencies: {},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'package-b',
|
|
188
|
+
path: '/test/package-b',
|
|
189
|
+
packageJson: { name: 'package-b', scripts: { test: 'echo test' } },
|
|
190
|
+
dependencies: {},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: 'other-package',
|
|
194
|
+
path: '/test/other-package',
|
|
195
|
+
packageJson: {
|
|
196
|
+
name: 'other-package',
|
|
197
|
+
scripts: { test: 'echo test' },
|
|
198
|
+
},
|
|
199
|
+
dependencies: {},
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
vi.mocked(getWorkspacePackages).mockResolvedValue(mockPackages);
|
|
204
|
+
vi.mocked(executeStages).mockResolvedValue(undefined);
|
|
205
|
+
|
|
206
|
+
// Filter to only packages starting with 'package-'
|
|
207
|
+
const filterFn = (name: string): boolean => name.startsWith('package-');
|
|
208
|
+
|
|
209
|
+
// Execute the function with filter
|
|
210
|
+
await runCmdInStagesAcrossWorkspaces({
|
|
211
|
+
rootPackageJsonDir: '/test',
|
|
212
|
+
cmd: 'test',
|
|
213
|
+
concurrency: 2,
|
|
214
|
+
filterWorkspacePattern: filterFn,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Verify executeStages was called with filtered packages
|
|
218
|
+
const expectedFilteredPackages = mockPackages.filter((pkg) =>
|
|
219
|
+
pkg.name.startsWith('package-'),
|
|
220
|
+
);
|
|
221
|
+
expect(executeStages).toHaveBeenCalledWith(
|
|
222
|
+
expectedFilteredPackages,
|
|
223
|
+
'test',
|
|
224
|
+
2,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// Verify log shows correct package count
|
|
228
|
+
expect(spies.consoleLogSpy).toHaveBeenCalledWith(
|
|
229
|
+
'\nStarting test across 2 packages (fail-fast mode)...',
|
|
230
|
+
);
|
|
231
|
+
} finally {
|
|
232
|
+
cleanupSpies(spies);
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('should handle workspace package loading errors', async () => {
|
|
237
|
+
const spies = setupSpies();
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
// Mock getWorkspacePackages to throw an error
|
|
241
|
+
const mockError = new Error('Failed to load workspace packages');
|
|
242
|
+
vi.mocked(getWorkspacePackages).mockRejectedValue(mockError);
|
|
243
|
+
|
|
244
|
+
// Execute the function
|
|
245
|
+
await runCmdInStagesAcrossWorkspaces({
|
|
246
|
+
rootPackageJsonDir: '/test',
|
|
247
|
+
cmd: 'test',
|
|
248
|
+
concurrency: 2,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Verify executeStages was NOT called
|
|
252
|
+
expect(executeStages).not.toHaveBeenCalled();
|
|
253
|
+
|
|
254
|
+
// Verify error handling
|
|
255
|
+
expect(spies.consoleErrorSpy).toHaveBeenCalledWith(
|
|
256
|
+
'\n❌ test failed (fail-fast mode stopped execution):',
|
|
257
|
+
);
|
|
258
|
+
expect(spies.consoleErrorSpy).toHaveBeenCalledWith(
|
|
259
|
+
'Failed to load workspace packages',
|
|
260
|
+
);
|
|
261
|
+
expect(spies.processExitSpy).toHaveBeenCalledWith(1);
|
|
262
|
+
} finally {
|
|
263
|
+
cleanupSpies(spies);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
});
|
package/src/index.mts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"format-untracked.d.mts","sourceRoot":"","sources":["../../src/cmd/format-untracked.mts"],"names":[],"mappings":""}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S npx tsx
|
|
2
|
-
import * as cmd from 'cmd-ts';
|
|
3
|
-
import 'ts-data-forge';
|
|
4
|
-
import '../node-global.mjs';
|
|
5
|
-
import 'node:child_process';
|
|
6
|
-
import { formatUntracked } from '../functions/format.mjs';
|
|
7
|
-
import 'micromatch';
|
|
8
|
-
import 'child_process';
|
|
9
|
-
|
|
10
|
-
const cmdDef = cmd.command({
|
|
11
|
-
name: 'format-untracked-cli',
|
|
12
|
-
version: '6.0.5',
|
|
13
|
-
args: {
|
|
14
|
-
silent: cmd.flag({
|
|
15
|
-
long: 'silent',
|
|
16
|
-
type: cmd.optional(cmd.boolean),
|
|
17
|
-
description: 'If true, suppresses output messages (default: false)',
|
|
18
|
-
}),
|
|
19
|
-
},
|
|
20
|
-
handler: (args) => {
|
|
21
|
-
main(args).catch((error) => {
|
|
22
|
-
console.error('An error occurred:', error);
|
|
23
|
-
process.exit(1);
|
|
24
|
-
});
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
const main = async (args) => {
|
|
28
|
-
const result = await formatUntracked({ silent: args.silent });
|
|
29
|
-
if (result === 'err') {
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
await cmd.run(cmdDef, process.argv.slice(2));
|
|
34
|
-
//# sourceMappingURL=format-untracked.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"format-untracked.mjs","sources":["../../src/cmd/format-untracked.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;;;;AAKA,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC;AACzB,IAAA,IAAI,EAAE,sBAAsB;AAC5B,IAAA,OAAO,EAAE,OAAO;AAChB,IAAA,IAAI,EAAE;AACJ,QAAA,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC;AACf,YAAA,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC;AAC/B,YAAA,WAAW,EAAE,sDAAsD;SACpE,CAAC;AACH,KAAA;AACD,IAAA,OAAO,EAAE,CAAC,IAAI,KAAI;QAChB,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,KAAI;AACzB,YAAA,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,KAAK,CAAC;AAC1C,YAAA,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;AACjB,QAAA,CAAC,CAAC;IACJ,CAAC;AACF,CAAA,CAAC;AAEF,MAAM,IAAI,GAAG,OAAO,IAAoC,KAAmB;AACzE,IAAA,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC;AAC7D,IAAA,IAAI,MAAM,KAAK,KAAK,EAAE;AACpB,QAAA,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACjB;AACF,CAAC;AAED,MAAM,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env -S npx tsx
|
|
2
|
-
|
|
3
|
-
import * as cmd from 'cmd-ts';
|
|
4
|
-
import { formatUntracked } from '../functions/index.mjs';
|
|
5
|
-
|
|
6
|
-
const cmdDef = cmd.command({
|
|
7
|
-
name: 'format-untracked-cli',
|
|
8
|
-
version: '6.0.5',
|
|
9
|
-
args: {
|
|
10
|
-
silent: cmd.flag({
|
|
11
|
-
long: 'silent',
|
|
12
|
-
type: cmd.optional(cmd.boolean),
|
|
13
|
-
description: 'If true, suppresses output messages (default: false)',
|
|
14
|
-
}),
|
|
15
|
-
},
|
|
16
|
-
handler: (args) => {
|
|
17
|
-
main(args).catch((error) => {
|
|
18
|
-
console.error('An error occurred:', error);
|
|
19
|
-
process.exit(1);
|
|
20
|
-
});
|
|
21
|
-
},
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const main = async (args: Readonly<{ silent?: boolean }>): Promise<void> => {
|
|
25
|
-
const result = await formatUntracked({ silent: args.silent });
|
|
26
|
-
if (result === 'err') {
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
await cmd.run(cmdDef, process.argv.slice(2));
|