ts-repo-utils 6.1.0 → 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.
Files changed (40) hide show
  1. package/README.md +11 -7
  2. package/dist/cmd/assert-repo-is-clean.mjs +1 -1
  3. package/dist/cmd/check-should-run-type-checks.mjs +1 -1
  4. package/dist/cmd/format-diff-from.mjs +29 -8
  5. package/dist/cmd/format-diff-from.mjs.map +1 -1
  6. package/dist/cmd/format-uncommitted.d.mts +3 -0
  7. package/dist/cmd/format-uncommitted.d.mts.map +1 -0
  8. package/dist/cmd/format-uncommitted.mjs +59 -0
  9. package/dist/cmd/format-uncommitted.mjs.map +1 -0
  10. package/dist/cmd/gen-index-ts.mjs +1 -1
  11. package/dist/functions/diff.d.mts +32 -2
  12. package/dist/functions/diff.d.mts.map +1 -1
  13. package/dist/functions/diff.mjs +47 -29
  14. package/dist/functions/diff.mjs.map +1 -1
  15. package/dist/functions/exec-async.d.mts +2 -2
  16. package/dist/functions/exec-async.d.mts.map +1 -1
  17. package/dist/functions/format.d.mts +20 -11
  18. package/dist/functions/format.d.mts.map +1 -1
  19. package/dist/functions/format.mjs +134 -95
  20. package/dist/functions/format.mjs.map +1 -1
  21. package/dist/functions/index.mjs +2 -2
  22. package/dist/index.mjs +2 -2
  23. package/package.json +2 -2
  24. package/src/cmd/assert-repo-is-clean.mts +1 -1
  25. package/src/cmd/check-should-run-type-checks.mts +1 -1
  26. package/src/cmd/format-diff-from.mts +35 -9
  27. package/src/cmd/format-uncommitted.mts +67 -0
  28. package/src/cmd/gen-index-ts.mts +1 -1
  29. package/src/functions/diff.mts +85 -32
  30. package/src/functions/diff.test.mts +569 -102
  31. package/src/functions/exec-async.mts +2 -2
  32. package/src/functions/exec-async.test.mts +77 -47
  33. package/src/functions/format.mts +224 -141
  34. package/src/functions/format.test.mts +625 -20
  35. package/src/functions/workspace-utils/run-cmd-in-stages.test.mts +266 -0
  36. package/dist/cmd/format-untracked.d.mts +0 -3
  37. package/dist/cmd/format-untracked.d.mts.map +0 -1
  38. package/dist/cmd/format-untracked.mjs +0 -34
  39. package/dist/cmd/format-untracked.mjs.map +0 -1
  40. 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
+ });
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env -S npx tsx
2
- export {};
3
- //# sourceMappingURL=format-untracked.d.mts.map
@@ -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.1.0',
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.1.0',
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));