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
@@ -1,6 +1,8 @@
1
+ export * from './append-as-const.mjs';
1
2
  export * from './convert-interface-to-type.mjs';
2
3
  export * from './convert-to-readonly-type.mjs';
3
4
  export * from './readonly-transformer-helpers/index.mjs';
5
+ export * from './replace-any-with-unknown.mjs';
4
6
  export * from './replace-record-with-unknown-record.mjs';
5
7
  export * from './transform-source-code.mjs';
6
8
  export * from './types.mjs';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/functions/ast-transformers/index.mts"],"names":[],"mappings":"AAAA,cAAc,iCAAiC,CAAC;AAChD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0CAA0C,CAAC;AACzD,cAAc,0CAA0C,CAAC;AACzD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/functions/ast-transformers/index.mts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,iCAAiC,CAAC;AAChD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0CAA0C,CAAC;AACzD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0CAA0C,CAAC;AACzD,cAAc,6BAA6B,CAAC;AAC5C,cAAc,aAAa,CAAC"}
@@ -1,9 +1,11 @@
1
+ export { appendAsConstTransformer } from './append-as-const.mjs';
1
2
  export { convertInterfaceToTypeTransformer } from './convert-interface-to-type.mjs';
2
3
  export { convertToReadonlyTypeTransformer } from './convert-to-readonly-type.mjs';
3
4
  export { compareUnionIntersectionTypes } from './readonly-transformer-helpers/compare-union-types.mjs';
4
5
  export { invalidDeepReadonlyTypeName } from './readonly-transformer-helpers/constants.mjs';
5
6
  export { groupUnionIntersectionTypes } from './readonly-transformer-helpers/group-union-types.mjs';
6
7
  export { nextReadonlyContext } from './readonly-transformer-helpers/readonly-context.mjs';
8
+ export { replaceAnyWithUnknownTransformer } from './replace-any-with-unknown.mjs';
7
9
  export { replaceRecordWithUnknownRecordTransformer } from './replace-record-with-unknown-record.mjs';
8
10
  export { transformSourceCode } from './transform-source-code.mjs';
9
11
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
@@ -0,0 +1,3 @@
1
+ import { type TsMorphTransformer } from './types.mjs';
2
+ export declare const replaceAnyWithUnknownTransformer: () => TsMorphTransformer;
3
+ //# sourceMappingURL=replace-any-with-unknown.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replace-any-with-unknown.d.mts","sourceRoot":"","sources":["../../../src/functions/ast-transformers/replace-any-with-unknown.mts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtD,eAAO,MAAM,gCAAgC,QAAO,kBAA0B,CAAC"}
@@ -0,0 +1,38 @@
1
+ import * as tsm from 'ts-morph';
2
+ import { hasDisableNextLineComment } from '../functions/has-disable-next-line-comment.mjs';
3
+ import '../functions/is-primitive-type-node.mjs';
4
+ import 'ts-data-forge';
5
+ import { isSpreadParameterNode, isSpreadNamedTupleMemberNode } from '../functions/is-spread-parameter-node.mjs';
6
+
7
+ const replaceAnyWithUnknownTransformer = () => body;
8
+ const body = (sourceAst) => {
9
+ for (const node of sourceAst.getChildren()) {
10
+ transformNode(node);
11
+ }
12
+ };
13
+ const transformNode = (node) => {
14
+ if (hasDisableNextLineComment(node)) {
15
+ console.debug('skipped by disable-next-line comment');
16
+ return;
17
+ }
18
+ if (node.isKind(tsm.SyntaxKind.AnyKeyword)) {
19
+ const anyKeywordNode = node;
20
+ const parent = anyKeywordNode.getParent();
21
+ if (parent !== undefined &&
22
+ // `(...args: any) => any` -> `(...args: unknown[]) => any`
23
+ (isSpreadParameterNode(parent) ||
24
+ // `[name: E0, ...args: any)]` -> `[name: E0, ...args: unknown[]]`
25
+ isSpreadNamedTupleMemberNode(parent))) {
26
+ anyKeywordNode.replaceWithText('readonly unknown[]');
27
+ return;
28
+ }
29
+ anyKeywordNode.replaceWithText('unknown');
30
+ return;
31
+ }
32
+ for (const child of node.getChildren()) {
33
+ transformNode(child);
34
+ }
35
+ };
36
+
37
+ export { replaceAnyWithUnknownTransformer };
38
+ //# sourceMappingURL=replace-any-with-unknown.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replace-any-with-unknown.mjs","sources":["../../../src/functions/ast-transformers/replace-any-with-unknown.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;MAQa,gCAAgC,GAAG,MAA0B;AAE1E,MAAM,IAAI,GAAuB,CAAC,SAAS,KAAI;IAC7C,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,WAAW,EAAE,EAAE;QAC1C,aAAa,CAAC,IAAI,CAAC;IACrB;AACF,CAAC;AAED,MAAM,aAAa,GAAG,CAAC,IAAc,KAAU;AAC7C,IAAA,IAAI,yBAAyB,CAAC,IAAI,CAAC,EAAE;AACnC,QAAA,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC;QAErD;IACF;IAEA,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE;QAC1C,MAAM,cAAc,GAAG,IAAI;AAE3B,QAAA,MAAM,MAAM,GAAG,cAAc,CAAC,SAAS,EAAE;QAEzC,IACE,MAAM,KAAK,SAAS;;aAEnB,qBAAqB,CAAC,MAAM,CAAC;;AAE5B,gBAAA,4BAA4B,CAAC,MAAM,CAAC,CAAC,EACvC;AACA,YAAA,cAAc,CAAC,eAAe,CAAC,oBAAoB,CAAC;YAEpD;QACF;AAEA,QAAA,cAAc,CAAC,eAAe,CAAC,SAAS,CAAC;QAEzC;IACF;IAEA,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE;QACtC,aAAa,CAAC,KAAK,CAAC;IACtB;AACF,CAAC;;;;"}
@@ -1,9 +1,11 @@
1
+ export { appendAsConstTransformer } from './ast-transformers/append-as-const.mjs';
1
2
  export { convertInterfaceToTypeTransformer } from './ast-transformers/convert-interface-to-type.mjs';
2
3
  export { convertToReadonlyTypeTransformer } from './ast-transformers/convert-to-readonly-type.mjs';
3
4
  export { compareUnionIntersectionTypes } from './ast-transformers/readonly-transformer-helpers/compare-union-types.mjs';
4
5
  export { invalidDeepReadonlyTypeName } from './ast-transformers/readonly-transformer-helpers/constants.mjs';
5
6
  export { groupUnionIntersectionTypes } from './ast-transformers/readonly-transformer-helpers/group-union-types.mjs';
6
7
  export { nextReadonlyContext } from './ast-transformers/readonly-transformer-helpers/readonly-context.mjs';
8
+ export { replaceAnyWithUnknownTransformer } from './ast-transformers/replace-any-with-unknown.mjs';
7
9
  export { replaceRecordWithUnknownRecordTransformer } from './ast-transformers/replace-record-with-unknown-record.mjs';
8
10
  export { transformSourceCode } from './ast-transformers/transform-source-code.mjs';
9
11
  export { IGNORE_FILE_COMMENT_TEXT, IGNORE_LINE_COMMENT_TEXT } from './constants/ignore-comment-text.mjs';
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;"}
package/dist/index.mjs CHANGED
@@ -1,9 +1,11 @@
1
+ export { appendAsConstTransformer } from './functions/ast-transformers/append-as-const.mjs';
1
2
  export { convertInterfaceToTypeTransformer } from './functions/ast-transformers/convert-interface-to-type.mjs';
2
3
  export { convertToReadonlyTypeTransformer } from './functions/ast-transformers/convert-to-readonly-type.mjs';
3
4
  export { compareUnionIntersectionTypes } from './functions/ast-transformers/readonly-transformer-helpers/compare-union-types.mjs';
4
5
  export { invalidDeepReadonlyTypeName } from './functions/ast-transformers/readonly-transformer-helpers/constants.mjs';
5
6
  export { groupUnionIntersectionTypes } from './functions/ast-transformers/readonly-transformer-helpers/group-union-types.mjs';
6
7
  export { nextReadonlyContext } from './functions/ast-transformers/readonly-transformer-helpers/readonly-context.mjs';
8
+ export { replaceAnyWithUnknownTransformer } from './functions/ast-transformers/replace-any-with-unknown.mjs';
7
9
  export { replaceRecordWithUnknownRecordTransformer } from './functions/ast-transformers/replace-record-with-unknown-record.mjs';
8
10
  export { transformSourceCode } from './functions/ast-transformers/transform-source-code.mjs';
9
11
  export { IGNORE_FILE_COMMENT_TEXT, IGNORE_LINE_COMMENT_TEXT } from './functions/constants/ignore-comment-text.mjs';
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-codemod-lib",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript",
@@ -27,8 +27,10 @@
27
27
  "module": "./dist/entry-point.mjs",
28
28
  "types": "./dist/types.d.mts",
29
29
  "bin": {
30
+ "append-as-const": "./dist/cmd/append-as-const.mjs",
30
31
  "convert-interface-to-type": "./dist/cmd/convert-interface-to-type.mjs",
31
32
  "convert-to-readonly": "./dist/cmd/convert-to-readonly.mjs",
33
+ "replace-any-with-unknown": "./dist/cmd/replace-any-with-unknown.mjs",
32
34
  "replace-record-with-unknown-record": "./dist/cmd/replace-record-with-unknown-record.mjs"
33
35
  },
34
36
  "files": [
@@ -37,6 +39,48 @@
37
39
  "README.md",
38
40
  "LICENSE"
39
41
  ],
42
+ "scripts": {
43
+ "build": "tsx ./scripts/cmd/build.mts",
44
+ "build:min": "tsx ./scripts/cmd/build.mts --skip-check",
45
+ "check-all": "tsx ./scripts/cmd/check-all.mts",
46
+ "check:ext": "tsx ./scripts/cmd/check-ext.mts",
47
+ "clean": "pnpm run /clean:.*/",
48
+ "clean:logs": "npx rimraf ./*.log",
49
+ "cspell": "cspell \"**\" --gitignore --gitignore-root ./ --no-progress",
50
+ "doc": "tsx ./scripts/cmd/gen-docs.mts",
51
+ "doc:embed": "tsx ./scripts/cmd/embed-samples.mts",
52
+ "doc:preview": "vite preview --config ./configs/vite.doc.config.mts",
53
+ "doc:watch": "typedoc --options ./configs/typedoc.config.mjs --watch",
54
+ "fmt": "format-uncommitted",
55
+ "fmt:diff": "format-diff-from origin/main",
56
+ "fmt:full": "prettier --write .",
57
+ "gh:apply-all": "gh-apply-all",
58
+ "gh:apply-repository-settings": "gh-apply-repository-settings",
59
+ "gh:apply-rulesets": "gh-apply-rulesets",
60
+ "gh:apply-variables": "gh-apply-variables",
61
+ "gh:backup-all": "gh-backup-all",
62
+ "gh:backup-repository-settings": "gh-backup-repository-settings",
63
+ "gh:backup-rulesets": "gh-backup-rulesets",
64
+ "gi": "run-s gi:src fmt",
65
+ "gi:src": "gen-index-ts ./src --index-ext .mts --export-ext .mjs --target-ext .mts --target-ext .tsx --exclude entry-point.mts --exclude cmd",
66
+ "lint": "eslint .",
67
+ "lint:fix": "eslint . --fix",
68
+ "md": "markdownlint-cli2",
69
+ "run:cmd": "tsx ./src/cmd/convert-to-readonly.mts ./test/sample1.mts",
70
+ "run:test": "tsx ./scripts/test-code/transform-test-code.mts",
71
+ "test": "pnpm run z:vitest:node run",
72
+ "test:browser": "pnpm run z:vitest --project='Browser' run",
73
+ "test:cov": "pnpm run z:vitest:node run --coverage",
74
+ "test:cov:ui": "vite preview --outDir ./coverage",
75
+ "test:ui": "pnpm run z:vitest:node --ui",
76
+ "testw": "pnpm run z:vitest:node watch",
77
+ "tsc": "tsc --noEmit",
78
+ "tscw": "tsc --noEmit --watch -p ./tsconfig.json",
79
+ "type-check": "tsc --noEmit",
80
+ "update-packages": "pnpm update --latest",
81
+ "z:vitest": "vitest --config ./configs/vitest.config.mts",
82
+ "z:vitest:node": "pnpm run z:vitest --project='Node.js'"
83
+ },
40
84
  "dependencies": {
41
85
  "cmd-ts": "0.14.3",
42
86
  "dedent": "1.7.1",
@@ -54,81 +98,41 @@
54
98
  "@semantic-release/github": "12.0.2",
55
99
  "@semantic-release/npm": "13.1.3",
56
100
  "@semantic-release/release-notes-generator": "14.1.0",
57
- "@types/node": "25.0.6",
58
- "@vitest/browser-playwright": "4.0.16",
59
- "@vitest/coverage-v8": "4.0.16",
60
- "@vitest/ui": "4.0.16",
101
+ "@types/node": "25.0.9",
102
+ "@vitest/browser-playwright": "4.0.17",
103
+ "@vitest/coverage-v8": "4.0.17",
104
+ "@vitest/ui": "4.0.17",
61
105
  "conventional-changelog-conventionalcommits": "9.1.0",
62
- "cspell": "9.4.0",
106
+ "cspell": "9.6.0",
63
107
  "eslint": "9.39.2",
64
- "eslint-config-typed": "4.4.1",
65
- "github-settings-as-code": "1.0.7",
108
+ "eslint-config-typed": "4.5.0",
109
+ "github-settings-as-code": "1.0.11",
66
110
  "jiti": "2.6.1",
67
111
  "markdownlint": "0.40.0",
68
112
  "markdownlint-cli2": "0.20.0",
69
113
  "npm-run-all2": "8.0.4",
70
114
  "playwright": "1.57.0",
71
- "prettier": "3.7.4",
115
+ "prettier": "3.8.0",
72
116
  "prettier-plugin-organize-imports": "4.3.0",
73
- "prettier-plugin-packagejson": "2.5.20",
74
- "rollup": "4.54.0",
117
+ "prettier-plugin-packagejson": "2.5.21",
118
+ "rollup": "4.55.1",
75
119
  "semantic-release": "25.0.2",
76
120
  "ts-data-forge": "6.2.1",
77
121
  "ts-type-forge": "2.3.0",
78
122
  "tslib": "2.8.1",
79
123
  "tsx": "4.21.0",
80
- "typedoc": "0.28.15",
124
+ "typedoc": "0.28.16",
81
125
  "typedoc-github-theme": "0.3.1",
82
126
  "typescript": "5.9.3",
83
- "vite": "7.3.0",
84
- "vitest": "4.0.16"
127
+ "vite": "7.3.1",
128
+ "vitest": "4.0.17"
85
129
  },
130
+ "packageManager": "pnpm@10.27.0",
86
131
  "engines": {
87
132
  "node": ">=22",
88
133
  "pnpm": ">=8.0.0"
89
134
  },
90
135
  "volta": {
91
136
  "node": "25.2.1"
92
- },
93
- "scripts": {
94
- "build": "tsx ./scripts/cmd/build.mts",
95
- "build:min": "tsx ./scripts/cmd/build.mts --skip-check",
96
- "check-all": "tsx ./scripts/cmd/check-all.mts",
97
- "check:ext": "tsx ./scripts/cmd/check-ext.mts",
98
- "clean": "pnpm run /clean:.*/",
99
- "clean:logs": "npx rimraf ./*.log",
100
- "cspell": "cspell \"**\" --gitignore --gitignore-root ./ --no-progress",
101
- "doc": "tsx ./scripts/cmd/gen-docs.mts",
102
- "doc:embed": "tsx ./scripts/cmd/embed-samples.mts",
103
- "doc:preview": "vite preview --config ./configs/vite.doc.config.mts",
104
- "doc:watch": "typedoc --options ./configs/typedoc.config.mjs --watch",
105
- "fmt": "format-uncommitted",
106
- "fmt:diff": "format-diff-from origin/main",
107
- "fmt:full": "prettier --write .",
108
- "gh:apply-all": "gh-apply-all",
109
- "gh:apply-repository-settings": "gh-apply-repository-settings",
110
- "gh:apply-rulesets": "gh-apply-rulesets",
111
- "gh:apply-variables": "gh-apply-variables",
112
- "gh:backup-all": "gh-backup-all",
113
- "gh:backup-repository-settings": "gh-backup-repository-settings",
114
- "gh:backup-rulesets": "gh-backup-rulesets",
115
- "gi": "run-s gi:src fmt",
116
- "gi:src": "gen-index-ts ./src --index-ext .mts --export-ext .mjs --target-ext .mts --target-ext .tsx --exclude entry-point.mts --exclude cmd",
117
- "lint": "eslint .",
118
- "lint:fix": "eslint . --fix",
119
- "md": "markdownlint-cli2",
120
- "run:cmd": "tsx ./src/cmd/convert-to-readonly.mts ./test/convert-to-readonly.mts",
121
- "test": "pnpm run z:vitest:node run",
122
- "test:browser": "pnpm run z:vitest --project='Browser' run",
123
- "test:cov": "pnpm run z:vitest:node run --coverage",
124
- "test:cov:ui": "vite preview --outDir ./coverage",
125
- "test:ui": "pnpm run z:vitest:node --ui",
126
- "testw": "pnpm run z:vitest:node watch",
127
- "tsc": "tsc --noEmit",
128
- "tscw": "tsc --noEmit --watch -p ./tsconfig.json",
129
- "type-check": "tsc --noEmit",
130
- "update-packages": "pnpm update --latest",
131
- "z:vitest": "vitest --config ./configs/vitest.config.mts",
132
- "z:vitest:node": "vitest --config ./configs/vitest.config.mts --project='Node.js'"
133
137
  }
134
- }
138
+ }
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-await-in-loop */
3
+
4
+ import * as cmd from 'cmd-ts';
5
+ import dedent from 'dedent';
6
+ import { castMutable, Result, unknownToString } from 'ts-data-forge';
7
+ import 'ts-repo-utils';
8
+ import {
9
+ appendAsConstTransformer,
10
+ transformSourceCode,
11
+ } from '../functions/index.mjs';
12
+
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:
26
+ 'Glob patterns of files to exclude from the base directory (e.g., "src/generated/**/*.mts")',
27
+ }),
28
+ silent: cmd.flag({
29
+ long: 'silent',
30
+ type: cmd.optional(cmd.boolean),
31
+ description: 'If true, suppresses output messages (default: false)',
32
+ }),
33
+ },
34
+ handler: (args) => {
35
+ appendAsConstCLI({
36
+ baseDir: args.baseDir,
37
+ exclude: args.exclude ?? [],
38
+ silent: args.silent ?? false,
39
+ }).catch((error: unknown) => {
40
+ console.error('An error occurred:', error);
41
+
42
+ process.exit(1);
43
+ });
44
+ },
45
+ });
46
+
47
+ type Args = Readonly<{
48
+ baseDir: string;
49
+ exclude: readonly string[];
50
+ silent: boolean;
51
+ }>;
52
+
53
+ const appendAsConstCLI = async (
54
+ args: Args,
55
+ ): Promise<Result<undefined, undefined>> => {
56
+ const echoIfNotSilent = args.silent ? () => {} : echo;
57
+
58
+ const errorIfNotSilent = args.silent ? () => {} : console.error;
59
+
60
+ // Find all files matching the glob
61
+ const globResult = await glob(args.baseDir, {
62
+ ignore: castMutable(args.exclude),
63
+ });
64
+
65
+ if (Result.isErr(globResult)) {
66
+ errorIfNotSilent('Error finding files matching pattern:', globResult.value);
67
+
68
+ return Result.err(undefined);
69
+ }
70
+
71
+ const files = globResult.value;
72
+
73
+ if (files.length === 0) {
74
+ echoIfNotSilent('No files found matching pattern:', args.baseDir);
75
+
76
+ return Result.ok(undefined);
77
+ }
78
+
79
+ const { errorFiles, transformedCount, unchangedCount } = await transformFiles(
80
+ files,
81
+ args.silent,
82
+ );
83
+
84
+ const hr = '='.repeat(50);
85
+
86
+ echoIfNotSilent(dedent`
87
+ ${hr}
88
+ Summary:
89
+ ✅ Transformed: ${transformedCount}
90
+ ⏭️ Unchanged: ${unchangedCount}
91
+ ❌ Errors: ${errorFiles.length}
92
+ 📊 Total: ${files.length}
93
+ `);
94
+
95
+ if (errorFiles.length > 0) {
96
+ echoIfNotSilent('\nFiles with errors:');
97
+
98
+ for (const fileName of errorFiles) {
99
+ echoIfNotSilent(` - ${fileName}`);
100
+ }
101
+ }
102
+
103
+ echoIfNotSilent(hr);
104
+
105
+ if (errorFiles.length > 0) {
106
+ return Result.err(undefined);
107
+ }
108
+
109
+ return Result.ok(undefined);
110
+ };
111
+
112
+ const transformFiles = async (
113
+ filePaths: readonly string[],
114
+ silent: boolean,
115
+ ): Promise<
116
+ Readonly<{
117
+ transformedCount: number;
118
+ unchangedCount: number;
119
+ errorFiles: readonly string[];
120
+ }>
121
+ > => {
122
+ let mut_transformedCount: number = 0;
123
+
124
+ let mut_unchangedCount: number = 0;
125
+
126
+ const mut_errorFiles: string[] = [];
127
+
128
+ for (const filePath of filePaths) {
129
+ const result = await transformOneFile(filePath, silent);
130
+
131
+ if (Result.isOk(result)) {
132
+ switch (result.value) {
133
+ case 'transformed':
134
+ mut_transformedCount += 1;
135
+
136
+ break;
137
+
138
+ case 'unchanged':
139
+ mut_unchangedCount += 1;
140
+
141
+ break;
142
+ }
143
+ } else {
144
+ mut_errorFiles.push(path.basename(filePath));
145
+ }
146
+ }
147
+
148
+ return {
149
+ transformedCount: mut_transformedCount,
150
+ unchangedCount: mut_unchangedCount,
151
+ errorFiles: mut_errorFiles,
152
+ };
153
+ };
154
+
155
+ const transformOneFile = async (
156
+ filePath: string,
157
+ silent: boolean,
158
+ ): Promise<Result<'unchanged' | 'transformed', string>> => {
159
+ const echoIfNotSilent = silent ? () => {} : echo;
160
+
161
+ const errorIfNotSilent = silent ? () => {} : console.error;
162
+
163
+ const fileName = path.basename(filePath);
164
+
165
+ const isTsx = fileName.endsWith('.tsx') || fileName.endsWith('.jsx');
166
+
167
+ try {
168
+ const originalCode = await fs.readFile(filePath, 'utf8');
169
+
170
+ // Transform the code with all transformers
171
+ const transformedCode = transformSourceCode(originalCode, isTsx, [
172
+ appendAsConstTransformer(),
173
+ ]);
174
+
175
+ // Check if the code was actually changed
176
+ if (transformedCode === originalCode) {
177
+ echoIfNotSilent(`⏭️ ${fileName} - no changes needed`);
178
+
179
+ return Result.ok('unchanged');
180
+ } else {
181
+ // Write back the transformed code
182
+ await fs.writeFile(filePath, transformedCode, 'utf8');
183
+
184
+ echoIfNotSilent(`✅ ${fileName} - transformed`);
185
+
186
+ return Result.ok('transformed');
187
+ }
188
+ } catch (error) {
189
+ const errStr = unknownToString(error);
190
+
191
+ errorIfNotSilent(`❌ ${fileName} - error: ${errStr}`);
192
+
193
+ return Result.err(errStr);
194
+ }
195
+ };
196
+
197
+ await cmd.run(cmdDef, process.argv.slice(2));
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable no-await-in-loop */
3
+
4
+ import * as cmd from 'cmd-ts';
5
+ import dedent from 'dedent';
6
+ import { castMutable, Result, unknownToString } from 'ts-data-forge';
7
+ import 'ts-repo-utils';
8
+ import {
9
+ convertInterfaceToTypeTransformer,
10
+ transformSourceCode,
11
+ } from '../functions/index.mjs';
12
+
13
+ const cmdDef = cmd.command({
14
+ name: 'convert-interface-to-type-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:
26
+ 'Glob patterns of files to exclude from the base directory (e.g., "src/generated/**/*.mts")',
27
+ }),
28
+ silent: cmd.flag({
29
+ long: 'silent',
30
+ type: cmd.optional(cmd.boolean),
31
+ description: 'If true, suppresses output messages (default: false)',
32
+ }),
33
+ },
34
+ handler: (args) => {
35
+ convertInterfaceToTypeCLI({
36
+ baseDir: args.baseDir,
37
+ exclude: args.exclude ?? [],
38
+ silent: args.silent ?? false,
39
+ }).catch((error: unknown) => {
40
+ console.error('An error occurred:', error);
41
+
42
+ process.exit(1);
43
+ });
44
+ },
45
+ });
46
+
47
+ type Args = Readonly<{
48
+ baseDir: string;
49
+ exclude: readonly string[];
50
+ silent: boolean;
51
+ }>;
52
+
53
+ const convertInterfaceToTypeCLI = async (
54
+ args: Args,
55
+ ): Promise<Result<undefined, undefined>> => {
56
+ const echoIfNotSilent = args.silent ? () => {} : echo;
57
+
58
+ const errorIfNotSilent = args.silent ? () => {} : console.error;
59
+
60
+ // Find all files matching the glob
61
+ const globResult = await glob(args.baseDir, {
62
+ ignore: castMutable(args.exclude),
63
+ });
64
+
65
+ if (Result.isErr(globResult)) {
66
+ errorIfNotSilent('Error finding files matching pattern:', globResult.value);
67
+
68
+ return Result.err(undefined);
69
+ }
70
+
71
+ const files = globResult.value;
72
+
73
+ if (files.length === 0) {
74
+ echoIfNotSilent('No files found matching pattern:', args.baseDir);
75
+
76
+ return Result.ok(undefined);
77
+ }
78
+
79
+ const { errorFiles, transformedCount, unchangedCount } = await transformFiles(
80
+ files,
81
+ args.silent,
82
+ );
83
+
84
+ const hr = '='.repeat(50);
85
+
86
+ echoIfNotSilent(dedent`
87
+ ${hr}
88
+ Summary:
89
+ ✅ Transformed: ${transformedCount}
90
+ ⏭️ Unchanged: ${unchangedCount}
91
+ ❌ Errors: ${errorFiles.length}
92
+ 📊 Total: ${files.length}
93
+ `);
94
+
95
+ if (errorFiles.length > 0) {
96
+ echoIfNotSilent('\nFiles with errors:');
97
+
98
+ for (const fileName of errorFiles) {
99
+ echoIfNotSilent(` - ${fileName}`);
100
+ }
101
+ }
102
+
103
+ echoIfNotSilent(hr);
104
+
105
+ if (errorFiles.length > 0) {
106
+ return Result.err(undefined);
107
+ }
108
+
109
+ return Result.ok(undefined);
110
+ };
111
+
112
+ const transformFiles = async (
113
+ filePaths: readonly string[],
114
+ silent: boolean,
115
+ ): Promise<
116
+ Readonly<{
117
+ transformedCount: number;
118
+ unchangedCount: number;
119
+ errorFiles: readonly string[];
120
+ }>
121
+ > => {
122
+ let mut_transformedCount: number = 0;
123
+
124
+ let mut_unchangedCount: number = 0;
125
+
126
+ const mut_errorFiles: string[] = [];
127
+
128
+ for (const filePath of filePaths) {
129
+ const result = await transformOneFile(filePath, silent);
130
+
131
+ if (Result.isOk(result)) {
132
+ switch (result.value) {
133
+ case 'transformed':
134
+ mut_transformedCount += 1;
135
+
136
+ break;
137
+
138
+ case 'unchanged':
139
+ mut_unchangedCount += 1;
140
+
141
+ break;
142
+ }
143
+ } else {
144
+ mut_errorFiles.push(path.basename(filePath));
145
+ }
146
+ }
147
+
148
+ return {
149
+ transformedCount: mut_transformedCount,
150
+ unchangedCount: mut_unchangedCount,
151
+ errorFiles: mut_errorFiles,
152
+ };
153
+ };
154
+
155
+ const transformOneFile = async (
156
+ filePath: string,
157
+ silent: boolean,
158
+ ): Promise<Result<'unchanged' | 'transformed', string>> => {
159
+ const echoIfNotSilent = silent ? () => {} : echo;
160
+
161
+ const errorIfNotSilent = silent ? () => {} : console.error;
162
+
163
+ const fileName = path.basename(filePath);
164
+
165
+ const isTsx = fileName.endsWith('.tsx') || fileName.endsWith('.jsx');
166
+
167
+ try {
168
+ const originalCode = await fs.readFile(filePath, 'utf8');
169
+
170
+ // Transform the code with all transformers
171
+ const transformedCode = transformSourceCode(originalCode, isTsx, [
172
+ convertInterfaceToTypeTransformer(),
173
+ ]);
174
+
175
+ // Check if the code was actually changed
176
+ if (transformedCode === originalCode) {
177
+ echoIfNotSilent(`⏭️ ${fileName} - no changes needed`);
178
+
179
+ return Result.ok('unchanged');
180
+ } else {
181
+ // Write back the transformed code
182
+ await fs.writeFile(filePath, transformedCode, 'utf8');
183
+
184
+ echoIfNotSilent(`✅ ${fileName} - transformed`);
185
+
186
+ return Result.ok('transformed');
187
+ }
188
+ } catch (error) {
189
+ const errStr = unknownToString(error);
190
+
191
+ errorIfNotSilent(`❌ ${fileName} - error: ${errStr}`);
192
+
193
+ return Result.err(errStr);
194
+ }
195
+ };
196
+
197
+ await cmd.run(cmdDef, process.argv.slice(2));