ts-codemod-lib 1.0.1 → 1.1.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 (49) hide show
  1. package/README.md +428 -6
  2. package/dist/cmd/append-as-const.d.mts +3 -0
  3. package/dist/cmd/append-as-const.d.mts.map +1 -0
  4. package/dist/cmd/append-as-const.mjs +138 -0
  5. package/dist/cmd/append-as-const.mjs.map +1 -0
  6. package/dist/cmd/convert-interface-to-type.d.mts +3 -0
  7. package/dist/cmd/convert-interface-to-type.d.mts.map +1 -0
  8. package/dist/cmd/convert-interface-to-type.mjs +138 -0
  9. package/dist/cmd/convert-interface-to-type.mjs.map +1 -0
  10. package/dist/cmd/convert-to-readonly.mjs +3 -2
  11. package/dist/cmd/convert-to-readonly.mjs.map +1 -1
  12. package/dist/cmd/replace-any-with-unknown.d.mts +3 -0
  13. package/dist/cmd/replace-any-with-unknown.d.mts.map +1 -0
  14. package/dist/cmd/replace-any-with-unknown.mjs +138 -0
  15. package/dist/cmd/replace-any-with-unknown.mjs.map +1 -0
  16. package/dist/cmd/replace-record-with-unknown-record.d.mts +3 -0
  17. package/dist/cmd/replace-record-with-unknown-record.d.mts.map +1 -0
  18. package/dist/cmd/replace-record-with-unknown-record.mjs +138 -0
  19. package/dist/cmd/replace-record-with-unknown-record.mjs.map +1 -0
  20. package/dist/entry-point.mjs +2 -0
  21. package/dist/entry-point.mjs.map +1 -1
  22. package/dist/functions/ast-transformers/append-as-const.d.mts +13 -0
  23. package/dist/functions/ast-transformers/append-as-const.d.mts.map +1 -0
  24. package/dist/functions/ast-transformers/append-as-const.mjs +98 -0
  25. package/dist/functions/ast-transformers/append-as-const.mjs.map +1 -0
  26. package/dist/functions/ast-transformers/index.d.mts +2 -0
  27. package/dist/functions/ast-transformers/index.d.mts.map +1 -1
  28. package/dist/functions/ast-transformers/index.mjs +2 -0
  29. package/dist/functions/ast-transformers/index.mjs.map +1 -1
  30. package/dist/functions/ast-transformers/replace-any-with-unknown.d.mts +3 -0
  31. package/dist/functions/ast-transformers/replace-any-with-unknown.d.mts.map +1 -0
  32. package/dist/functions/ast-transformers/replace-any-with-unknown.mjs +38 -0
  33. package/dist/functions/ast-transformers/replace-any-with-unknown.mjs.map +1 -0
  34. package/dist/functions/index.mjs +2 -0
  35. package/dist/functions/index.mjs.map +1 -1
  36. package/dist/index.mjs +2 -0
  37. package/dist/index.mjs.map +1 -1
  38. package/package.json +60 -56
  39. package/src/cmd/append-as-const.mts +197 -0
  40. package/src/cmd/convert-interface-to-type.mts +197 -0
  41. package/src/cmd/convert-to-readonly.mts +3 -1
  42. package/src/cmd/replace-any-with-unknown.mts +197 -0
  43. package/src/cmd/replace-record-with-unknown-record.mts +197 -0
  44. package/src/functions/ast-transformers/append-as-const.mts +154 -0
  45. package/src/functions/ast-transformers/append-as-const.test.mts +339 -0
  46. package/src/functions/ast-transformers/convert-to-readonly-type.test.mts +3 -3
  47. package/src/functions/ast-transformers/index.mts +2 -0
  48. package/src/functions/ast-transformers/replace-any-with-unknown.mts +49 -0
  49. package/src/functions/ast-transformers/replace-any-with-unknown.test.mts +140 -0
package/README.md CHANGED
@@ -1,19 +1,434 @@
1
1
  # ts-codemod-lib
2
2
 
3
- <!--
4
3
  [![npm version](https://img.shields.io/npm/v/ts-codemod-lib.svg)](https://www.npmjs.com/package/ts-codemod-lib)
5
4
  [![npm downloads](https://img.shields.io/npm/dm/ts-codemod-lib.svg)](https://www.npmjs.com/package/ts-codemod-lib)
6
5
  [![License](https://img.shields.io/npm/l/ts-codemod-lib.svg)](./LICENSE)
7
- [![codecov](https://codecov.io/gh/noshiro-pf/ts-codemod-lib/branch/main/graph/badge.svg?token=********)](https://codecov.io/gh/noshiro-pf/ts-codemod-lib)
8
- -->
6
+ [![codecov](https://codecov.io/gh/noshiro-pf/ts-codemod-lib/graph/badge.svg?token=BVx5UgsiVr)](https://codecov.io/gh/noshiro-pf/ts-codemod-lib)
9
7
 
10
- A TypeScript library for building codemods and AST transformations
8
+ A TypeScript library for code transformations using AST (Abstract Syntax Tree) transformers, powered by the TypeScript Compiler API.
9
+
10
+ ## Overview
11
+
12
+ `ts-codemod-lib` provides utilities and ready-to-use transformers for automated TypeScript code transformations. It enables you to programmatically modify TypeScript source code through AST manipulation, making it ideal for large-scale refactoring tasks, enforcing type safety, and promoting immutability.
13
+
14
+ ## Features
15
+
16
+ - **AST-based Transformations**: Leverage TypeScript Compiler API for reliable type-aware code transformations
17
+ - **Ready-to-use Transformers**: Append `as const`, convert to readonly types, replace `any` with `unknown`, and more
18
+ - **Ready-to-use CLI Tools**: Convert interfaces to types, add readonly modifiers, and more
19
+ - **Extensible API**: Build custom transformers using the provided utilities
20
+ - **Type-safe**: Written in TypeScript with strict type checking
21
+ - **Selective Transformation**: Support for ignoring specific lines or entire files via comments
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ # Using npm
27
+ npm install -D ts-codemod-lib
28
+
29
+ # Using pnpm
30
+ pnpm add -D ts-codemod-lib
31
+
32
+ # Using yarn
33
+ yarn add -D ts-codemod-lib
34
+ ```
35
+
36
+ ## Available Transformers
37
+
38
+ This library provides TypeScript AST transformers that can be used to automatically modify your TypeScript code. The transformers can be used individually or combined for more complex transformations.
39
+
40
+ ### 1. `appendAsConstTransformer`
41
+
42
+ Appends `as const` to array literals and object literals to make them readonly constants. This transformer helps in creating immutable data structures by automatically adding the TypeScript `as const` assertion.
43
+
44
+ Example:
45
+
46
+ ```ts
47
+ // Before
48
+ const arr = [1, 2, 3];
49
+
50
+ const obj = { a: 1, b: 2 };
51
+
52
+ // After
53
+ const arr2 = [1, 2, 3] as const;
54
+
55
+ const obj2 = { a: 1, b: 2 } as const;
56
+ ```
57
+
58
+ ### 2. `convertToReadonlyTypeTransformer`
59
+
60
+ Converts TypeScript type definitions to readonly types. This transformer helps in creating more type-safe code by making types readonly where appropriate. It also normalizes nested readonly types (e.g., `Readonly<Readonly<T>>` becomes `Readonly<T>`).
61
+
62
+ Options:
63
+
64
+ - `ignorePrefixes`: Array of string prefixes for identifiers that should not be made readonly
65
+ - `DeepReadonlyTypeName`: Custom name for the DeepReadonly type utility (defaults to "DeepReadonly")
66
+
67
+ Example:
68
+
69
+ ```ts
70
+ // Before
71
+ type User = {
72
+ id: number;
73
+ description: string;
74
+ preferences: Map<string, string>;
75
+ friendIds: number[];
76
+ mut_items: string[]; // With ignorePrefixes: ['mut_']
77
+ };
78
+
79
+ // After
80
+ type User2 = Readonly<{
81
+ id: number;
82
+ description: string;
83
+ preferences: ReadonlyMap<string, string>;
84
+ friendIds: readonly number[];
85
+ mut_items: string[]; // Not made readonly due to 'mut_' prefix
86
+ }>;
87
+ ```
88
+
89
+ For more detailed transformation examples, see the [test file](./src/functions/ast-transformers/convert-to-readonly-type.test.mts) which covers various scenarios including complex types, nested structures, and DeepReadonly transformations.
90
+
91
+ ### 3. `convertInterfaceToTypeTransformer`
92
+
93
+ Converts TypeScript interface declarations to type aliases. This transformer helps in maintaining consistency by using type aliases throughout the codebase.
94
+
95
+ Example:
96
+
97
+ ```ts
98
+ // Before
99
+ interface User {
100
+ id: number;
101
+ name: string;
102
+ }
103
+
104
+ // After
105
+ type User2 = {
106
+ id: number;
107
+ name: string;
108
+ };
109
+ ```
110
+
111
+ ### 4. `replaceAnyWithUnknownTransformer`
112
+
113
+ Replaces `any` type annotations with `unknown` for improved type safety. The `unknown` type requires type checking before operations, making your code more robust. For function parameters with rest arguments, `(...args: any) => R` is converted to `(...args: readonly unknown[]) => R`.
114
+
115
+ Example:
116
+
117
+ ```ts
118
+ // Before
119
+ const getValue = (data: any): any => data.value;
120
+
121
+ const sortValues = (...args: any): any =>
122
+ args.toSorted((a: any, b: any) => a - b);
123
+
124
+ // After
125
+ const getValue2 = (data: unknown): unknown => (data as any).value;
126
+
127
+ const sortValues2 = (...args: readonly unknown[]): unknown =>
128
+ (args as any).toSorted((a: any, b: any) => a - b);
129
+ ```
130
+
131
+ For more detailed transformation examples, see the [test file](./src/functions/ast-transformers/replace-any-with-unknown.test.mts) which covers various scenarios including function parameters, return types, and variable declarations.
132
+
133
+ ### 5. `replaceRecordWithUnknownRecordTransformer`
134
+
135
+ Replaces `Record<string, unknown>` and `Readonly<Record<string, unknown>>` with `UnknownRecord` for better type safety and consistency. This transformer also handles index signatures `[k: string]: unknown` in interfaces and type literals.
136
+
137
+ Example:
138
+
139
+ ```ts
140
+ // Before
141
+ type Config = Record<string, unknown>;
142
+
143
+ type ReadonlyConfig = Readonly<Record<string, unknown>>;
144
+
145
+ type Data = Record<string, unknown>;
146
+
147
+ // After
148
+ type Config2 = UnknownRecord;
149
+
150
+ type ReadonlyConfig2 = UnknownRecord;
151
+
152
+ type Data2 = UnknownRecord;
153
+ ```
154
+
155
+ ### Disabling Transformers
156
+
157
+ - Nodes on the line immediately following a `// transformer-ignore-next-line` comment will be skipped.
158
+ - Files containing a `/* transformer-ignore */` comment will be skipped entirely.
159
+
160
+ Examples:
161
+
162
+ **Example using `// transformer-ignore-next-line`:**
163
+
164
+ ```ts
165
+ // Before
166
+ type Config = {
167
+ apiKey: string;
168
+ // transformer-ignore-next-line
169
+ mutableOptions: string[]; // This line will not be made Readonly
170
+ settings: { timeout: number };
171
+ };
172
+
173
+ // After applying convertToReadonlyTypeTransformer
174
+ type Config2 = Readonly<{
175
+ apiKey: string;
176
+ mutableOptions: string[]; // Not made Readonly because it was skipped
177
+ settings: Readonly<{ timeout: number }>;
178
+ }>;
179
+ ```
180
+
181
+ **Example using `/* transformer-ignore */`:**
182
+
183
+ ```ts
184
+ // Before
185
+ type Data = { value: any };
186
+
187
+ const items = [1, 2, 3];
188
+
189
+ // After applying any transformer (e.g., replaceAnyWithUnknownTransformer, appendAsConstTransformer)
190
+ // No changes will be made to this file.
191
+ type Data2 = { value: any };
192
+
193
+ const items2 = [1, 2, 3];
194
+ ```
195
+
196
+ ## CLI Tools
197
+
198
+ This package provides command-line tools for common TypeScript transformations:
199
+
200
+ ### append-as-const
201
+
202
+ Appends `as const` to array literals and object literals to make them readonly constants.
203
+
204
+ ```bash
205
+ npx append-as-const <baseDir> [--exclude <pattern>] [--silent]
206
+ ```
207
+
208
+ ### convert-interface-to-type
209
+
210
+ Converts TypeScript interface declarations to type aliases.
211
+
212
+ ```bash
213
+ npx convert-interface-to-type <baseDir> [--exclude <pattern>] [--silent]
214
+ ```
215
+
216
+ ### convert-to-readonly
217
+
218
+ Adds `readonly` modifiers to type definitions throughout your codebase.
219
+
220
+ ```bash
221
+ npx convert-to-readonly <baseDir> [--exclude <pattern>] [--silent]
222
+ ```
223
+
224
+ ### replace-any-with-unknown
225
+
226
+ Replaces `any` type annotations with `unknown` for improved type safety.
227
+
228
+ ```bash
229
+ npx replace-any-with-unknown <baseDir> [--exclude <pattern>] [--silent]
230
+ ```
231
+
232
+ ### replace-record-with-unknown-record
233
+
234
+ Replaces `Record<string, unknown>` with `UnknownRecord` for better type safety.
235
+
236
+ ```bash
237
+ npx replace-record-with-unknown-record <baseDir> [--exclude <pattern>] [--silent]
238
+ ```
239
+
240
+ **Common Options:**
241
+
242
+ - `baseDir`: Base directory to scan for TypeScript files
243
+ - `--exclude`: Glob patterns to exclude (e.g., `"src/generated/**/*.mts"`)
244
+ - `--silent`: Suppress output messages
245
+
246
+ ## Programmatic Usage
247
+
248
+ ### Using Transformers with String Input/Output
249
+
250
+ You can use the `astTransformerToStringTransformer` utility to apply these transformers to source code strings:
251
+
252
+ ```tsx
253
+ import dedent from 'dedent';
254
+ import {
255
+ appendAsConstTransformer,
256
+ convertInterfaceToTypeTransformer,
257
+ convertToReadonlyTypeTransformer,
258
+ replaceAnyWithUnknownTransformer,
259
+ replaceRecordWithUnknownRecordTransformer,
260
+ transformSourceCode,
261
+ } from 'ts-codemod-lib';
262
+
263
+ const originalCode = dedent`
264
+ export interface A {
265
+ name?: string;
266
+ point: [x: number, y: number, z?: number];
267
+ meta: {
268
+ description?: string;
269
+ tags: string[];
270
+ attributes: Record<string, unknown>;
271
+ data?: any;
272
+ };
273
+ }
274
+
275
+ export const obj = {
276
+ point: [1, 2],
277
+ meta: {
278
+ tags: ['example', 'test'],
279
+ attributes: {
280
+ key1: 'value1',
281
+ key2: 42,
282
+ },
283
+ },
284
+ } satisfies A;
285
+
286
+ export const arr = ['a', {}, 0];
287
+ `;
288
+
289
+ const isTsx = false;
290
+
291
+ // Apply transformations to source code
292
+ const transformedCode = transformSourceCode(originalCode, isTsx, [
293
+ convertInterfaceToTypeTransformer(),
294
+ replaceRecordWithUnknownRecordTransformer(),
295
+ convertToReadonlyTypeTransformer(),
296
+ appendAsConstTransformer(),
297
+ replaceAnyWithUnknownTransformer(),
298
+ ]);
299
+
300
+ const expected = dedent`
301
+ export type A = Readonly< {
302
+ name?: string;
303
+ point: (readonly [x: number, y: number, z?: number]);
304
+ meta: Readonly< {
305
+ description?: string;
306
+ tags: (readonly string[]);
307
+ attributes: UnknownRecord;
308
+ data?: unknown;
309
+ }>;
310
+ }>;
311
+
312
+ export const obj = {
313
+ point: [1, 2],
314
+ meta: {
315
+ tags: ['example', 'test'],
316
+ attributes: {
317
+ key1: 'value1',
318
+ key2: 42,
319
+ },
320
+ },
321
+ } as const satisfies A;
322
+
323
+ export const arr = ['a', {}, 0] as const;
324
+ `;
325
+
326
+ if (import.meta.vitest !== undefined) {
327
+ test('transformSourceCode', () => {
328
+ assert.isTrue(transformedCode === expected);
329
+ });
330
+ }
331
+ ```
332
+
333
+ Note: It is recommended to apply all transformers at once using `transformSourceCode` rather than applying each transformer individually.
334
+ This is more efficient as it avoids the overhead of parsing and printing before and after applying each AST transformation.
335
+
336
+ ### Apply Transformers to `src` Directory
337
+
338
+ ```sh
339
+ npm install -D glob prettier
340
+ ```
341
+
342
+ ```tsx
343
+ import * as fs from 'node:fs/promises';
344
+ import {
345
+ appendAsConstTransformer,
346
+ convertInterfaceToTypeTransformer,
347
+ convertToReadonlyTypeTransformer,
348
+ replaceAnyWithUnknownTransformer,
349
+ replaceRecordWithUnknownRecordTransformer,
350
+ transformSourceCode,
351
+ } from 'ts-codemod-lib';
352
+
353
+ for await (const filePath of fs.glob('path/to/src/**/*.{mts,tsx}')) {
354
+ if (filePath.endsWith('.d.mts')) {
355
+ console.log(`Skipping declaration file: ${filePath}`);
356
+
357
+ continue;
358
+ }
359
+
360
+ console.log(`Processing file: ${filePath}`);
361
+
362
+ const originalCode = await fs.readFile(filePath, 'utf8');
363
+
364
+ const isTsx = filePath.endsWith('.tsx') || filePath.endsWith('.jsx');
365
+
366
+ // Apply transformations to source code
367
+ const transformedCode = transformSourceCode(originalCode, isTsx, [
368
+ convertInterfaceToTypeTransformer(),
369
+ replaceRecordWithUnknownRecordTransformer(),
370
+ convertToReadonlyTypeTransformer(),
371
+ appendAsConstTransformer(),
372
+ replaceAnyWithUnknownTransformer(),
373
+ ]);
374
+
375
+ await fs.writeFile(filePath, transformedCode, 'utf8');
376
+ }
377
+ ```
378
+
379
+ Run:
380
+
381
+ ```sh
382
+ node codemod.mjs
383
+ ```
384
+
385
+ ## Notes
386
+
387
+ - Types within JSDoc comments are not transformed.
388
+
389
+ ```ts
390
+ // Before
391
+ /**
392
+ * Processes user data.
393
+ * @param {object} user - The user object.
394
+ * @param {string[]} user.roles - User roles.
395
+ * @returns {object} Processed data.
396
+ */
397
+ function processUser(user: { name: string; roles: string[] }): {
398
+ success: boolean;
399
+ } {
400
+ // ... implementation ...
401
+ return { success: true };
402
+ }
403
+
404
+ // After applying convertToReadonlyTypeTransformer
405
+ /**
406
+ * Processes user data.
407
+ * @param {object} user - The user object. // JSDoc type is not changed
408
+ * @param {string[]} user.roles - User roles. // JSDoc type is not changed
409
+ * @returns {object} Processed data. // JSDoc type is not changed
410
+ */
411
+ function processUser(
412
+ user: Readonly<{ name: string; roles: readonly string[] }>,
413
+ ): Readonly<{ success: boolean }> {
414
+ // ... implementation ...
415
+ return { success: true };
416
+ }
417
+ ```
418
+
419
+ - Comment positions might change due to the heuristics used for restoring comments in the code.
420
+ - When parsing source code into an AST using the TypeScript Compiler API, comments are often attached to the preceding or succeeding node. However, sometimes comments become detached (orphaned). These detached comments might be omitted when the source code string is generated by TypeScript's printer (though some might be restored). `ts-codemod-lib` includes preprocessing to identify all detached comments that the printer cannot restore and reattaches them to the immediately preceding or succeeding node, making them printable. However, the determination of whether to attach the comment before or after the node is heuristic, so the comment might move to a different position than in the original code.
421
+ - Possible workarounds include experimenting to find comment positions less likely to become orphaned (comments clearly preceding a node are less likely to be orphaned) or excluding the relevant section from transformation using the `// transformer-ignore-next-line` comment.
422
+ - I intend to resolve practical issues as much as possible, so please submit an issue if you find any problems.
423
+ - Related link: <https://github.com/microsoft/TypeScript/issues/20506#issuecomment-349740820>
11
424
 
12
425
  ## Documentation
13
426
 
14
427
  - API reference: <https://noshiro-pf.github.io/ts-codemod-lib/>
15
428
 
16
- ## Local Setup
429
+ ## For Developers
430
+
431
+ ### Local Setup
17
432
 
18
433
  ```sh
19
434
  git clone https://github.com/noshiro-pf/ts-codemod-lib.git
@@ -21,7 +436,7 @@ git submodule update --init --recursive
21
436
  pnpm i
22
437
  ```
23
438
 
24
- ## Syncing AGENTS.md Updates
439
+ ### Syncing AGENTS.md Updates
25
440
 
26
441
  1. Update `AGENTS.md` in the common repository (`common-agent-config`)
27
442
  2. Update the submodule in each project
@@ -31,3 +446,10 @@ git submodule update --remote --merge
31
446
  git add agents/common
32
447
  git commit -m "Update AGENTS.md"
33
448
  ```
449
+
450
+ ### Resources
451
+
452
+ - <https://ts-ast-viewer.com/#>
453
+ - <https://github.com/itsdouges/typescript-transformer-handbook>
454
+ - <https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API>
455
+ - <https://blog.nnn.dev/entry/2022/03/10/110000>
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import 'ts-repo-utils';
3
+ //# sourceMappingURL=append-as-const.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"append-as-const.d.mts","sourceRoot":"","sources":["../../src/cmd/append-as-const.mts"],"names":[],"mappings":";AAMA,OAAO,eAAe,CAAC"}
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+ import * as cmd from 'cmd-ts';
3
+ import dedent from 'dedent';
4
+ import { castMutable, Result, unknownToString } from 'ts-data-forge';
5
+ import 'ts-repo-utils';
6
+ import { appendAsConstTransformer } from '../functions/ast-transformers/append-as-const.mjs';
7
+ import 'ts-morph';
8
+ import '../functions/functions/is-primitive-type-node.mjs';
9
+ import '../functions/ast-transformers/readonly-transformer-helpers/constants.mjs';
10
+ import { transformSourceCode } from '../functions/ast-transformers/transform-source-code.mjs';
11
+
12
+ /* eslint-disable no-await-in-loop */
13
+ const cmdDef = cmd.command({
14
+ name: 'append-as-const-cli',
15
+ version: '1.0.0',
16
+ args: {
17
+ baseDir: cmd.positional({
18
+ type: cmd.string,
19
+ displayName: 'baseDir',
20
+ description: 'The base directory in which to perform the conversion',
21
+ }),
22
+ exclude: cmd.multioption({
23
+ long: 'exclude',
24
+ type: cmd.optional(cmd.array(cmd.string)),
25
+ description: 'Glob patterns of files to exclude from the base directory (e.g., "src/generated/**/*.mts")',
26
+ }),
27
+ silent: cmd.flag({
28
+ long: 'silent',
29
+ type: cmd.optional(cmd.boolean),
30
+ description: 'If true, suppresses output messages (default: false)',
31
+ }),
32
+ },
33
+ handler: (args) => {
34
+ appendAsConstCLI({
35
+ baseDir: args.baseDir,
36
+ exclude: args.exclude ?? [],
37
+ silent: args.silent ?? false,
38
+ }).catch((error) => {
39
+ console.error('An error occurred:', error);
40
+ process.exit(1);
41
+ });
42
+ },
43
+ });
44
+ const appendAsConstCLI = async (args) => {
45
+ const echoIfNotSilent = args.silent ? () => { } : echo;
46
+ const errorIfNotSilent = args.silent ? () => { } : console.error;
47
+ // Find all files matching the glob
48
+ const globResult = await glob(args.baseDir, {
49
+ ignore: castMutable(args.exclude),
50
+ });
51
+ if (Result.isErr(globResult)) {
52
+ errorIfNotSilent('Error finding files matching pattern:', globResult.value);
53
+ return Result.err(undefined);
54
+ }
55
+ const files = globResult.value;
56
+ if (files.length === 0) {
57
+ echoIfNotSilent('No files found matching pattern:', args.baseDir);
58
+ return Result.ok(undefined);
59
+ }
60
+ const { errorFiles, transformedCount, unchangedCount } = await transformFiles(files, args.silent);
61
+ const hr = '='.repeat(50);
62
+ echoIfNotSilent(dedent `
63
+ ${hr}
64
+ Summary:
65
+ ✅ Transformed: ${transformedCount}
66
+ ⏭️ Unchanged: ${unchangedCount}
67
+ ❌ Errors: ${errorFiles.length}
68
+ 📊 Total: ${files.length}
69
+ `);
70
+ if (errorFiles.length > 0) {
71
+ echoIfNotSilent('\nFiles with errors:');
72
+ for (const fileName of errorFiles) {
73
+ echoIfNotSilent(` - ${fileName}`);
74
+ }
75
+ }
76
+ echoIfNotSilent(hr);
77
+ if (errorFiles.length > 0) {
78
+ return Result.err(undefined);
79
+ }
80
+ return Result.ok(undefined);
81
+ };
82
+ const transformFiles = async (filePaths, silent) => {
83
+ let mut_transformedCount = 0;
84
+ let mut_unchangedCount = 0;
85
+ const mut_errorFiles = [];
86
+ for (const filePath of filePaths) {
87
+ const result = await transformOneFile(filePath, silent);
88
+ if (Result.isOk(result)) {
89
+ switch (result.value) {
90
+ case 'transformed':
91
+ mut_transformedCount += 1;
92
+ break;
93
+ case 'unchanged':
94
+ mut_unchangedCount += 1;
95
+ break;
96
+ }
97
+ }
98
+ else {
99
+ mut_errorFiles.push(path.basename(filePath));
100
+ }
101
+ }
102
+ return {
103
+ transformedCount: mut_transformedCount,
104
+ unchangedCount: mut_unchangedCount,
105
+ errorFiles: mut_errorFiles,
106
+ };
107
+ };
108
+ const transformOneFile = async (filePath, silent) => {
109
+ const echoIfNotSilent = silent ? () => { } : echo;
110
+ const errorIfNotSilent = silent ? () => { } : console.error;
111
+ const fileName = path.basename(filePath);
112
+ const isTsx = fileName.endsWith('.tsx') || fileName.endsWith('.jsx');
113
+ try {
114
+ const originalCode = await fs.readFile(filePath, 'utf8');
115
+ // Transform the code with all transformers
116
+ const transformedCode = transformSourceCode(originalCode, isTsx, [
117
+ appendAsConstTransformer(),
118
+ ]);
119
+ // Check if the code was actually changed
120
+ if (transformedCode === originalCode) {
121
+ echoIfNotSilent(`⏭️ ${fileName} - no changes needed`);
122
+ return Result.ok('unchanged');
123
+ }
124
+ else {
125
+ // Write back the transformed code
126
+ await fs.writeFile(filePath, transformedCode, 'utf8');
127
+ echoIfNotSilent(`✅ ${fileName} - transformed`);
128
+ return Result.ok('transformed');
129
+ }
130
+ }
131
+ catch (error) {
132
+ const errStr = unknownToString(error);
133
+ errorIfNotSilent(`❌ ${fileName} - error: ${errStr}`);
134
+ return Result.err(errStr);
135
+ }
136
+ };
137
+ await cmd.run(cmdDef, process.argv.slice(2));
138
+ //# sourceMappingURL=append-as-const.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"append-as-const.mjs","sources":["../../src/cmd/append-as-const.mts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import 'ts-repo-utils';
3
+ //# sourceMappingURL=convert-interface-to-type.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert-interface-to-type.d.mts","sourceRoot":"","sources":["../../src/cmd/convert-interface-to-type.mts"],"names":[],"mappings":";AAMA,OAAO,eAAe,CAAC"}