@wise/wds-codemods 0.0.1-experimental-2eb5228 → 0.0.1-experimental-0d8d466

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.
@@ -1,59 +1,18 @@
1
1
  import type { Options, Transform } from 'jscodeshift';
2
2
  import { applyTransform, type TestOptions } from 'jscodeshift/src/testUtils';
3
3
 
4
- interface PackageRequirement {
5
- name: string;
6
- version: string;
7
- found?: boolean; // Optional - defaults to true
8
- }
9
-
10
4
  /**
11
5
  * This function creates a test transform function that applies a given transformer to the input code.
12
6
  * It uses the 'tsx' parser to support both TypeScript and JSX syntax.
13
7
  *
14
8
  * The transform function is used to modify the input code based on the provided transformer.
15
- *
16
- * @param transformer - The jscodeshift transformer function
17
- * @param packageRequirements - Array of package requirements to mock (defaults to empty array)
18
9
  */
19
- function createTestTransform(
20
- transformer: Transform,
21
- packageRequirements: PackageRequirement[] = [],
22
- ) {
23
- // Create mock package results from requirements
24
- const packageResults: Record<string, boolean> = {};
25
- packageRequirements.forEach((pkg) => {
26
- const key = `${pkg.name}@${pkg.version}`;
27
- packageResults[key] = pkg.found ?? true; // Default to found unless explicitly set to false
28
- });
29
-
30
- const options: Options = {
31
- // Only add packageResults if we have package requirements
32
- ...(packageRequirements.length > 0 ? { packageResults: JSON.stringify(packageResults) } : {}),
33
- };
34
-
10
+ function createTestTransform(transformer: Transform) {
11
+ const options: Options = {};
35
12
  // Use 'tsx' parser to support both TypeScript and JSX syntax
36
13
  const testOptions: TestOptions = { parser: 'tsx' };
37
-
38
14
  return (input: { path?: string; source: string }) =>
39
15
  applyTransform(transformer, options, input, testOptions);
40
16
  }
41
17
 
42
- // Convenience functions for common use cases
43
- export const createTestTransformWithPackage = (
44
- transformer: Transform,
45
- packageName: string,
46
- version: string,
47
- ) => {
48
- return createTestTransform(transformer, [{ name: packageName, version }]);
49
- };
50
-
51
- export const createTestTransformWithoutPackage = (
52
- transformer: Transform,
53
- packageName: string,
54
- version: string,
55
- ) => {
56
- return createTestTransform(transformer, [{ name: packageName, version, found: false }]);
57
- };
58
-
59
18
  export default createTestTransform;
@@ -1,5 +1,5 @@
1
+ export { default as createTestTransform } from './createTestTransform';
1
2
  export { default as hasImport } from './hasImport';
2
3
  export { default as processIconChildren } from './iconUtils';
3
4
  export * from './jsxElementUtils';
4
5
  export * from './jsxReportingUtils';
5
- export * from './packageValidation';
@@ -30,7 +30,6 @@ describe('getOptions', () => {
30
30
  print: true,
31
31
  gitignore: true,
32
32
  ignorePattern: undefined,
33
- isMonorepo: false,
34
33
  });
35
34
  });
36
35
 
@@ -45,13 +44,6 @@ describe('getOptions', () => {
45
44
  ];
46
45
  const options = await getOptions(transformFiles);
47
46
  expect(options.ignorePattern).toBe('node_modules/**');
48
- expect(options.isMonorepo).toBe(false);
49
- });
50
-
51
- it('should parse --monorepo argument from CLI', async () => {
52
- process.argv = ['node', 'script.js', 'fileA.js', './packages', '--monorepo'];
53
- const options = await getOptions(transformFiles);
54
- expect(options.isMonorepo).toBe(true);
55
47
  });
56
48
 
57
49
  it('should parse --gitignore and --no-gitignore arguments from CLI and prioritize --gitignore', async () => {
@@ -68,12 +60,12 @@ describe('getOptions', () => {
68
60
  (list as jest.Mock).mockResolvedValue('fileB.ts');
69
61
  (input as jest.Mock).mockResolvedValueOnce('./output').mockResolvedValueOnce('node_modules/**');
70
62
  (confirm as jest.Mock)
71
- .mockResolvedValueOnce(true)
72
63
  .mockResolvedValueOnce(true)
73
64
  .mockResolvedValueOnce(false)
74
65
  .mockResolvedValueOnce(true);
75
66
 
76
- const options = await getOptions(transformFiles);
67
+ const optionsPromise = getOptions(transformFiles);
68
+ const options = await optionsPromise;
77
69
 
78
70
  expect(input).toHaveBeenCalledWith({
79
71
  message: 'Enter ignore pattern(s) (comma separated) or leave empty:',
@@ -85,35 +77,22 @@ describe('getOptions', () => {
85
77
  });
86
78
  expect(options.ignorePattern).toBe('node_modules/**');
87
79
  expect(options.gitignore).toBe(true);
88
- expect(options.isMonorepo).toBe(true);
89
80
  });
90
81
 
91
- it('should prompt for monorepo when target path looks like monorepo directory', async () => {
92
- (list as jest.Mock).mockResolvedValue('fileB.ts');
93
- (input as jest.Mock).mockResolvedValueOnce('./packages').mockResolvedValueOnce('');
94
- (confirm as jest.Mock)
95
- .mockResolvedValueOnce(true)
96
- .mockResolvedValueOnce(true)
97
- .mockResolvedValueOnce(false)
98
- .mockResolvedValueOnce(true);
99
-
100
- const options = await getOptions(transformFiles);
82
+ it('should throw an error if transform file is invalid via command line', async () => {
83
+ process.argv = ['node', 'script.js', 'invalid.js', './dist'];
84
+ await expect(getOptions(transformFiles)).rejects.toThrow('Invalid transform file specified.');
85
+ });
101
86
 
102
- expect(confirm).toHaveBeenCalledWith({
103
- message: 'Are you targeting a monorepo packages/apps directory?',
104
- default: true,
105
- });
106
- expect(options.isMonorepo).toBe(true);
87
+ it('should throw an error if target path is empty via command line', async () => {
88
+ process.argv = ['node', 'script.js', 'fileA.js', ''];
89
+ await expect(getOptions(transformFiles)).rejects.toThrow('Target path cannot be empty.');
107
90
  });
108
91
 
109
92
  it('should prompt for transform file, target path, dry mode, and print when no arguments are provided', async () => {
110
93
  (list as jest.Mock).mockResolvedValue('fileB.ts');
111
- (input as jest.Mock).mockResolvedValueOnce('./output').mockResolvedValueOnce('');
112
- (confirm as jest.Mock)
113
- .mockResolvedValueOnce(false)
114
- .mockResolvedValueOnce(true)
115
- .mockResolvedValueOnce(false)
116
- .mockResolvedValueOnce(true);
94
+ (input as jest.Mock).mockResolvedValue('./output');
95
+ (confirm as jest.Mock).mockResolvedValueOnce(true).mockResolvedValueOnce(false);
117
96
 
118
97
  const options = await getOptions(transformFiles);
119
98
 
@@ -138,20 +117,15 @@ describe('getOptions', () => {
138
117
  targetPath: './output',
139
118
  dry: true,
140
119
  print: false,
141
- gitignore: true,
142
- ignorePattern: '',
143
- isMonorepo: false,
120
+ gitignore: undefined,
121
+ ignorePattern: './output',
144
122
  });
145
123
  });
146
124
 
147
125
  it('should handle target path validation error', async () => {
148
126
  (list as jest.Mock).mockResolvedValue('fileA.js');
149
- (input as jest.Mock).mockResolvedValueOnce('./valid-path').mockResolvedValueOnce('');
150
- (confirm as jest.Mock)
151
- .mockResolvedValueOnce(false)
152
- .mockResolvedValueOnce(true)
153
- .mockResolvedValueOnce(false)
154
- .mockResolvedValueOnce(true);
127
+ (input as jest.Mock).mockResolvedValueOnce(' ');
128
+ (confirm as jest.Mock).mockResolvedValue(true);
155
129
 
156
130
  await getOptions(transformFiles);
157
131
 
@@ -159,7 +133,7 @@ describe('getOptions', () => {
159
133
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
160
134
  const validateFn = (input as jest.Mock).mock.calls[0][0].validate!;
161
135
  expect(validateFn(' ')).toBe('Target path cannot be empty');
162
- expect(validateFn('./valid-path')).toBe(true);
136
+ expect(validateFn('./valid-path')).toBe(true); // Ensure valid input passes validation
163
137
  expect(input).toHaveBeenCalledWith({
164
138
  message: 'Enter the target directory or file path to run codemod on:',
165
139
  validate: expect.any(Function),
@@ -168,12 +142,8 @@ describe('getOptions', () => {
168
142
 
169
143
  it('should allow changing the default dry mode', async () => {
170
144
  (list as jest.Mock).mockResolvedValue('fileC.tsx');
171
- (input as jest.Mock).mockResolvedValueOnce('./another-path').mockResolvedValueOnce('');
172
- (confirm as jest.Mock)
173
- .mockResolvedValueOnce(false)
174
- .mockResolvedValueOnce(false)
175
- .mockResolvedValueOnce(false)
176
- .mockResolvedValueOnce(true);
145
+ (input as jest.Mock).mockResolvedValue('./another-path');
146
+ (confirm as jest.Mock).mockResolvedValueOnce(false).mockResolvedValueOnce(false);
177
147
 
178
148
  const options = await getOptions(transformFiles);
179
149
 
@@ -186,12 +156,8 @@ describe('getOptions', () => {
186
156
 
187
157
  it('should allow changing the default print mode', async () => {
188
158
  (list as jest.Mock).mockResolvedValue('fileA.js');
189
- (input as jest.Mock).mockResolvedValueOnce('./yet-another-path').mockResolvedValueOnce('');
190
- (confirm as jest.Mock)
191
- .mockResolvedValueOnce(false)
192
- .mockResolvedValueOnce(true)
193
- .mockResolvedValueOnce(true)
194
- .mockResolvedValueOnce(true);
159
+ (input as jest.Mock).mockResolvedValue('./yet-another-path');
160
+ (confirm as jest.Mock).mockResolvedValueOnce(true).mockResolvedValueOnce(true);
195
161
 
196
162
  const options = await getOptions(transformFiles);
197
163
 
@@ -201,19 +167,4 @@ describe('getOptions', () => {
201
167
  });
202
168
  expect(options.print).toBe(true);
203
169
  });
204
-
205
- it('should not prompt for monorepo when target path does not look like monorepo directory', async () => {
206
- (list as jest.Mock).mockResolvedValue('fileA.js');
207
- (input as jest.Mock).mockResolvedValueOnce('./src').mockResolvedValueOnce('');
208
- (confirm as jest.Mock)
209
- .mockResolvedValueOnce(false)
210
- .mockResolvedValueOnce(true)
211
- .mockResolvedValueOnce(false)
212
- .mockResolvedValueOnce(true);
213
-
214
- const options = await getOptions(transformFiles);
215
-
216
- expect(confirm).toHaveBeenCalledTimes(4);
217
- expect(options.isMonorepo).toBe(false);
218
- });
219
170
  });
@@ -1,5 +1,4 @@
1
1
  import { confirm, input, select as list } from '@inquirer/prompts';
2
- import path from 'path';
3
2
 
4
3
  async function getOptions(transformFiles: string[]) {
5
4
  const args = process.argv.slice(2);
@@ -14,7 +13,6 @@ async function getOptions(transformFiles: string[]) {
14
13
  }
15
14
  const gitignore = args.includes('--gitignore');
16
15
  const noGitignore = args.includes('--no-gitignore');
17
- const isMonorepo = args.includes('--monorepo');
18
16
 
19
17
  if (!transformFile || !transformFiles.includes(transformFile)) {
20
18
  throw new Error('Invalid transform file specified.');
@@ -26,15 +24,7 @@ async function getOptions(transformFiles: string[]) {
26
24
  // If both --gitignore and --no-gitignore are specified, prioritize --gitignore
27
25
  const useGitignore = !!(gitignore || (!gitignore && !noGitignore));
28
26
 
29
- return {
30
- transformFile,
31
- targetPath,
32
- dry,
33
- print,
34
- ignorePattern,
35
- gitignore: useGitignore,
36
- isMonorepo,
37
- };
27
+ return { transformFile, targetPath, dry, print, ignorePattern, gitignore: useGitignore };
38
28
  }
39
29
 
40
30
  const transformFile = await list({
@@ -47,11 +37,6 @@ async function getOptions(transformFiles: string[]) {
47
37
  validate: (value) => value.trim() !== '' || 'Target path cannot be empty',
48
38
  });
49
39
 
50
- const isMonorepo = await confirm({
51
- message: 'Are you targeting a monorepo packages/apps directory?',
52
- default: true,
53
- });
54
-
55
40
  const dry = await confirm({
56
41
  message: 'Run in dry mode (no changes written to files)?',
57
42
  default: true,
@@ -72,7 +57,7 @@ async function getOptions(transformFiles: string[]) {
72
57
  default: true,
73
58
  });
74
59
 
75
- return { transformFile, targetPath, dry, print, ignorePattern, gitignore, isMonorepo };
60
+ return { transformFile, targetPath, dry, print, ignorePattern, gitignore };
76
61
  }
77
62
 
78
63
  export default getOptions;
@@ -1,45 +0,0 @@
1
- import type { Options } from 'jscodeshift';
2
-
3
- import { validatePackageRequirements } from '../packageValidation';
4
-
5
- describe('validatePackageRequirements', () => {
6
- const requirements = [{ name: '@transferwise/components', version: '>=46.5.0' }];
7
-
8
- beforeEach(() => {
9
- jest.spyOn(console, 'debug').mockImplementation(() => {});
10
- });
11
-
12
- afterEach(() => {
13
- jest.restoreAllMocks();
14
- });
15
-
16
- it('returns true when package is found (object format)', () => {
17
- const options: Options = {
18
- packageResults: { '@transferwise/components@>=46.5.0': true },
19
- };
20
-
21
- expect(validatePackageRequirements(options, requirements)).toBe(true);
22
- });
23
-
24
- it('returns true when package is found (JSON string format)', () => {
25
- const options: Options = {
26
- packageResults: '{"@transferwise/components@>=46.5.0":true}',
27
- };
28
-
29
- expect(validatePackageRequirements(options, requirements)).toBe(true);
30
- });
31
-
32
- it('returns false when package is missing', () => {
33
- const options: Options = {
34
- packageResults: { '@transferwise/components@>=46.5.0': false },
35
- };
36
-
37
- expect(validatePackageRequirements(options, requirements)).toBe(false);
38
- });
39
-
40
- it('returns false when no packageResults provided', () => {
41
- const options: Options = {};
42
-
43
- expect(validatePackageRequirements(options, requirements)).toBe(false);
44
- });
45
- });
@@ -1,53 +0,0 @@
1
- import type { Options } from 'jscodeshift';
2
-
3
- interface PackageRequirement {
4
- name: string;
5
- version: string;
6
- }
7
-
8
- /**
9
- * Check if package requirements are met
10
- * Returns true if all packages are found, false otherwise
11
- */
12
- export function validatePackageRequirements(
13
- options: Options,
14
- requirements: PackageRequirement[],
15
- ): boolean {
16
- let packageResults: Record<string, boolean> = {};
17
-
18
- // Handle both string and object cases
19
- if (options.packageResults) {
20
- if (typeof options.packageResults === 'string') {
21
- // Parse JSON string
22
- try {
23
- if (options.packageResults.trim() && options.packageResults.trim().startsWith('{')) {
24
- packageResults = JSON.parse(options.packageResults) as Record<string, boolean>;
25
- }
26
- } catch (error) {
27
- console.debug('Failed to parse packageResults:', error);
28
- return false;
29
- }
30
- } else if (typeof options.packageResults === 'object') {
31
- // Already an object, use directly
32
- packageResults = options.packageResults as Record<string, boolean>;
33
- }
34
- }
35
-
36
- // Check if all requirements are met
37
- const allRequirementsMet = requirements.every((req) => {
38
- const key = `${req.name}@${req.version}`;
39
- return packageResults[key];
40
- });
41
-
42
- if (!allRequirementsMet) {
43
- const missing = requirements.filter((req) => {
44
- const key = `${req.name}@${req.version}`;
45
- return !packageResults[key];
46
- });
47
-
48
- const missingPackages = missing.map((pkg) => `${pkg.name}@${pkg.version}`).join(', ');
49
- console.debug(`Skipping transform - missing required packages: ${missingPackages}`);
50
- }
51
-
52
- return allRequirementsMet;
53
- }
@@ -1,190 +0,0 @@
1
- import { existsSync, readdirSync, readFileSync, type Stats, statSync } from 'node:fs';
2
-
3
- import hasPackageVersion, { packageVersionCache } from '../hasPackageVersion';
4
-
5
- jest.mock('node:fs');
6
-
7
- const mockExistsSync = existsSync as jest.MockedFunction<typeof existsSync>;
8
- const mockReadFileSync = readFileSync as jest.MockedFunction<typeof readFileSync>;
9
- const mockReaddirSync = readdirSync as jest.MockedFunction<typeof readdirSync>;
10
- const mockStatSync = statSync as jest.MockedFunction<typeof statSync>;
11
- const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
12
-
13
- describe('hasPackageVersion', () => {
14
- beforeEach(() => {
15
- jest.clearAllMocks();
16
- mockExistsSync.mockReturnValue(false);
17
-
18
- const mockStats: Partial<Stats> = {
19
- isFile: jest.fn(() => false),
20
- isDirectory: jest.fn(() => true),
21
- isBlockDevice: jest.fn(() => false),
22
- isCharacterDevice: jest.fn(() => false),
23
- isFIFO: jest.fn(() => false),
24
- isSocket: jest.fn(() => false),
25
- isSymbolicLink: jest.fn(() => false),
26
- };
27
-
28
- mockStatSync.mockReturnValue(mockStats as Stats);
29
- packageVersionCache.clear();
30
- consoleSpy.mockClear();
31
- });
32
-
33
- afterAll(() => {
34
- consoleSpy.mockRestore();
35
- });
36
-
37
- it('finds package in package.json dependencies', () => {
38
- mockExistsSync.mockImplementation((path) => {
39
- const pathStr = String(path);
40
- return pathStr.endsWith('package.json') && !pathStr.includes('node_modules');
41
- });
42
- mockReadFileSync.mockReturnValue(
43
- JSON.stringify({
44
- dependencies: {
45
- '@transferwise/components': '46.5.0',
46
- },
47
- }),
48
- );
49
-
50
- expect(hasPackageVersion('@transferwise/components', '>=46.5.0')).toBe(true);
51
- });
52
-
53
- it('finds package in standard node_modules directory', () => {
54
- mockExistsSync.mockImplementation((path) => {
55
- const pathStr = String(path);
56
- return (
57
- pathStr.includes('node_modules/@transferwise/components/package.json') &&
58
- !pathStr.includes('.pnpm')
59
- );
60
- });
61
- mockReadFileSync.mockReturnValue(
62
- JSON.stringify({
63
- version: '46.5.0',
64
- }),
65
- );
66
-
67
- expect(hasPackageVersion('@transferwise/components', '>=46.5.0')).toBe(true);
68
- });
69
-
70
- it('finds package in pnpm node_modules structure', () => {
71
- mockExistsSync.mockImplementation((path) => {
72
- const pathStr = String(path);
73
- return (
74
- pathStr.includes('node_modules/.pnpm') ||
75
- pathStr.includes(
76
- '@transferwise+components@46.5.0/node_modules/@transferwise/components/package.json',
77
- )
78
- );
79
- });
80
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
81
- mockReaddirSync.mockReturnValue([
82
- '@transferwise+components@46.5.0',
83
- 'other-package@1.0.0',
84
- ] as any);
85
- mockReadFileSync.mockReturnValue(
86
- JSON.stringify({
87
- version: '46.5.0',
88
- }),
89
- );
90
-
91
- expect(hasPackageVersion('@transferwise/components', '>=46.5.0')).toBe(true);
92
- });
93
-
94
- it('returns false when package is not found', () => {
95
- mockExistsSync.mockReturnValue(false);
96
-
97
- expect(hasPackageVersion('@nonexistent/package', '>=1.0.0')).toBe(false);
98
- });
99
-
100
- it('returns false when version does not satisfy requirement', () => {
101
- mockExistsSync.mockImplementation((path) => {
102
- const pathStr = String(path);
103
- return pathStr.endsWith('package.json') && !pathStr.includes('node_modules');
104
- });
105
- mockReadFileSync.mockReturnValue(
106
- JSON.stringify({
107
- dependencies: {
108
- '@transferwise/components': '40.0.0',
109
- },
110
- }),
111
- );
112
-
113
- expect(hasPackageVersion('@transferwise/components', '>=46.5.0')).toBe(false);
114
- });
115
-
116
- it('handles malformed JSON gracefully', () => {
117
- mockExistsSync.mockImplementation((path) => {
118
- const pathStr = String(path);
119
- return pathStr.endsWith('package.json') && !pathStr.includes('node_modules');
120
- });
121
- mockReadFileSync.mockReturnValue('invalid json');
122
-
123
- expect(hasPackageVersion('@transferwise/components', '>=46.5.0')).toBe(false);
124
- });
125
-
126
- it('finds package in devDependencies', () => {
127
- mockExistsSync.mockImplementation((path) => {
128
- const pathStr = String(path);
129
- return pathStr.endsWith('package.json') && !pathStr.includes('node_modules');
130
- });
131
- mockReadFileSync.mockReturnValue(
132
- JSON.stringify({
133
- devDependencies: {
134
- '@transferwise/components': '46.5.0',
135
- },
136
- }),
137
- );
138
-
139
- expect(hasPackageVersion('@transferwise/components', '>=46.5.0')).toBe(true);
140
- });
141
-
142
- it('finds package in peerDependencies', () => {
143
- mockExistsSync.mockImplementation((path) => {
144
- const pathStr = String(path);
145
- return pathStr.endsWith('package.json') && !pathStr.includes('node_modules');
146
- });
147
- mockReadFileSync.mockReturnValue(
148
- JSON.stringify({
149
- peerDependencies: {
150
- '@transferwise/components': '>=46.5.0',
151
- },
152
- }),
153
- );
154
-
155
- expect(hasPackageVersion('@transferwise/components', '>=46.5.0')).toBe(true);
156
- });
157
-
158
- it('handles complex version ranges', () => {
159
- mockExistsSync.mockImplementation((path) => {
160
- const pathStr = String(path);
161
- return pathStr.endsWith('package.json') && !pathStr.includes('node_modules');
162
- });
163
- mockReadFileSync.mockReturnValue(
164
- JSON.stringify({
165
- dependencies: {
166
- '@transferwise/components': '^46.5.0',
167
- },
168
- }),
169
- );
170
-
171
- expect(hasPackageVersion('@transferwise/components', '>=46.5.0')).toBe(true);
172
- });
173
-
174
- it('uses cache for repeated calls', () => {
175
- mockExistsSync.mockImplementation((path) => {
176
- const pathStr = String(path);
177
- return pathStr.endsWith('package.json') && !pathStr.includes('node_modules');
178
- });
179
- mockReadFileSync.mockReturnValue(
180
- JSON.stringify({
181
- dependencies: {
182
- '@transferwise/components': '46.5.0',
183
- },
184
- }),
185
- );
186
-
187
- expect(hasPackageVersion('@transferwise/components', '>=46.5.0')).toBe(true);
188
- expect(mockReadFileSync).toHaveBeenCalledTimes(1);
189
- });
190
- });