@appartmint/tsm-scripts 0.0.2 → 0.0.3
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/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import ts from 'typescript';
|
|
4
4
|
|
|
5
5
|
const rootArg = process.argv[2];
|
|
6
6
|
const ROOT = rootArg ? path.resolve(rootArg) : process.cwd();
|
|
7
|
-
const SRC = path.join(ROOT, 'src');
|
|
8
7
|
|
|
9
8
|
type ImportGroup = 'external' | 'workspace' | 'local' | 'environment';
|
|
10
9
|
|
|
10
|
+
interface TsConfigAliasEntry {
|
|
11
|
+
fileNames: Set<string>;
|
|
12
|
+
aliasPrefixes: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
11
15
|
interface ParsedImport {
|
|
12
16
|
spec: string;
|
|
13
17
|
group: ImportGroup;
|
|
@@ -24,17 +28,66 @@ function walkTsFiles(dir: string, out: string[] = []): string[] {
|
|
|
24
28
|
return out;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
const FROM_RE = /from\s+(['"])([^'"]+)\1/;
|
|
28
|
-
|
|
29
31
|
function getModuleSpecifier(text: string): string {
|
|
30
|
-
const m =
|
|
32
|
+
const m = /from\s+(['"])([^'"]+)\1/.exec(text);
|
|
31
33
|
return m ? m[2] : '';
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
function
|
|
36
|
+
function normalizePathForCompare(p: string): string {
|
|
37
|
+
return path.resolve(p).replace(/\\/g, '/').toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeAliasPrefix(key: string): string {
|
|
41
|
+
if (key === '*' || !key) return '';
|
|
42
|
+
if (key.endsWith('/*')) return key.slice(0, -1);
|
|
43
|
+
return key;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function discoverRootTsconfigs(rootDir: string): string[] {
|
|
47
|
+
return fs
|
|
48
|
+
.readdirSync(rootDir, { withFileTypes: true })
|
|
49
|
+
.filter((entry) => entry.isFile() && /^tsconfig.*\.json$/i.test(entry.name))
|
|
50
|
+
.map((entry) => path.join(rootDir, entry.name));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function loadTsConfigAliasEntries(rootDir: string): TsConfigAliasEntry[] {
|
|
54
|
+
const entries: TsConfigAliasEntry[] = [];
|
|
55
|
+
for (const configPath of discoverRootTsconfigs(rootDir)) {
|
|
56
|
+
const read = ts.readConfigFile(configPath, (fileName) => ts.sys.readFile(fileName));
|
|
57
|
+
if (read.error) continue;
|
|
58
|
+
|
|
59
|
+
const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, path.dirname(configPath), undefined, configPath);
|
|
60
|
+
const paths = parsed.options.paths ?? {};
|
|
61
|
+
const aliasPrefixes = Object.keys(paths)
|
|
62
|
+
.map((key) => normalizeAliasPrefix(key))
|
|
63
|
+
.filter((key) => key.length > 0);
|
|
64
|
+
if (aliasPrefixes.length === 0) continue;
|
|
65
|
+
|
|
66
|
+
entries.push({
|
|
67
|
+
fileNames: new Set(parsed.fileNames.map((name) => normalizePathForCompare(name))),
|
|
68
|
+
aliasPrefixes
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return entries;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function resolveLocalAliasPrefixes(filePath: string, tsconfigEntries: TsConfigAliasEntry[]): string[] {
|
|
75
|
+
const normalizedFilePath = normalizePathForCompare(filePath);
|
|
76
|
+
const prefixes = new Set<string>();
|
|
77
|
+
for (const entry of tsconfigEntries) {
|
|
78
|
+
if (!entry.fileNames.has(normalizedFilePath)) continue;
|
|
79
|
+
for (const prefix of entry.aliasPrefixes) {
|
|
80
|
+
prefixes.add(prefix);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return [...prefixes];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function classify(spec: string, localAliasPrefixes: string[]): ImportGroup {
|
|
35
87
|
if (!spec) return 'external';
|
|
36
|
-
if (spec.includes('/environments/') || spec.includes('environments/environment')) return 'environment';
|
|
88
|
+
if (spec.includes('/environments/') || spec.includes('environments/environment') || spec.includes('.env')) return 'environment';
|
|
37
89
|
if (spec.startsWith('@app-art-mint/') || spec.startsWith('@appartmint/')) return 'workspace';
|
|
90
|
+
if (localAliasPrefixes.some((prefix) => spec === prefix || spec.startsWith(prefix))) return 'local';
|
|
38
91
|
if (spec.startsWith('.') || spec.startsWith('..')) return 'local';
|
|
39
92
|
return 'external';
|
|
40
93
|
}
|
|
@@ -101,53 +154,15 @@ function compareSpec(a: string, b: string): number {
|
|
|
101
154
|
return sortKey(a).localeCompare(sortKey(b), 'en');
|
|
102
155
|
}
|
|
103
156
|
|
|
104
|
-
function stripKnownSectionComments(text: string): string {
|
|
105
|
-
let s = text;
|
|
106
|
-
s = s.replace(/\n\s*\/\*\*\s*\n\s*\*\s*Routes\s*\n\s*\*\/\s*\n/g, '\n');
|
|
107
|
-
s = s.replace(/\n\s*\/\*\*\s*\n\s*\*\s*Module\s*\n\s*\*\/\s*\n/g, '\n');
|
|
108
|
-
return s;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function removeLeadingSectionJSDocOnStatements(text: string): string {
|
|
112
|
-
const sf = ts.createSourceFile('x.ts', text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
113
|
-
const toRemove: [number, number][] = [];
|
|
114
|
-
|
|
115
|
-
for (const stmt of sf.statements) {
|
|
116
|
-
const ranges = ts.getLeadingCommentRanges(text, stmt.getFullStart());
|
|
117
|
-
if (!ranges?.length) continue;
|
|
118
|
-
for (const r of ranges) {
|
|
119
|
-
const ctext = text.slice(r.pos, r.end);
|
|
120
|
-
if (!/^\s*\/\*\*/.test(ctext)) continue;
|
|
121
|
-
const inner = ctext
|
|
122
|
-
.replace(/^\s*\/\*\*\s*/, '')
|
|
123
|
-
.replace(/\s*\*\/\s*$/, '')
|
|
124
|
-
.replace(/^\s*\*\s?/gm, '')
|
|
125
|
-
.trim();
|
|
126
|
-
const lines = inner.split(/\n/).map((l) => l.trim()).filter(Boolean);
|
|
127
|
-
if (lines.length === 1 && /^(Routes|Module|Imports)$/.test(lines[0])) {
|
|
128
|
-
toRemove.push([r.pos, r.end]);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
toRemove.sort((a, b) => b[0] - a[0]);
|
|
134
|
-
let out = text;
|
|
135
|
-
for (const [pos, end] of toRemove) {
|
|
136
|
-
out = out.slice(0, pos) + out.slice(end);
|
|
137
|
-
}
|
|
138
|
-
return out;
|
|
139
|
-
}
|
|
140
157
|
|
|
141
|
-
function organizeImports(sourceText: string, filePath: string): string {
|
|
158
|
+
function organizeImports(sourceText: string, filePath: string, tsconfigEntries: TsConfigAliasEntry[]): string {
|
|
142
159
|
const sf = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
160
|
+
const localAliasPrefixes = resolveLocalAliasPrefixes(filePath, tsconfigEntries);
|
|
143
161
|
|
|
144
162
|
let i = 0;
|
|
145
163
|
const stmts = sf.statements;
|
|
146
164
|
while (i < stmts.length && ts.isImportDeclaration(stmts[i])) i++;
|
|
147
|
-
|
|
148
|
-
if (i === 0) {
|
|
149
|
-
return stripKnownSectionComments(sourceText);
|
|
150
|
-
}
|
|
165
|
+
if (i === 0) return sourceText;
|
|
151
166
|
|
|
152
167
|
const importNodes = stmts.slice(0, i);
|
|
153
168
|
const replaceStart = importNodes[0].getFullStart();
|
|
@@ -159,7 +174,7 @@ function organizeImports(sourceText: string, filePath: string): string {
|
|
|
159
174
|
|
|
160
175
|
const parsed: ParsedImport[] = importTexts.map((trimmed) => {
|
|
161
176
|
const spec = getModuleSpecifier(trimmed);
|
|
162
|
-
const group = classify(spec);
|
|
177
|
+
const group = classify(spec, localAliasPrefixes);
|
|
163
178
|
const wrapped = wrapImportIfNeeded(trimmed, 100, semicolon);
|
|
164
179
|
return { spec, group, finalText: wrapped, multi: isMultiLineImport(wrapped) };
|
|
165
180
|
});
|
|
@@ -177,20 +192,19 @@ function organizeImports(sourceText: string, filePath: string): string {
|
|
|
177
192
|
const before = sourceText.slice(0, replaceStart);
|
|
178
193
|
const after = sourceText.slice(replaceEnd);
|
|
179
194
|
|
|
180
|
-
|
|
181
|
-
result = stripKnownSectionComments(result);
|
|
195
|
+
const result = before + newBlock + after;
|
|
182
196
|
return result;
|
|
183
197
|
}
|
|
184
198
|
|
|
185
199
|
let changed = 0;
|
|
186
|
-
const files = walkTsFiles(
|
|
200
|
+
const files = walkTsFiles(ROOT);
|
|
201
|
+
const tsconfigEntries = loadTsConfigAliasEntries(ROOT);
|
|
187
202
|
|
|
188
203
|
for (const filePath of files) {
|
|
189
204
|
let text = fs.readFileSync(filePath, 'utf8');
|
|
190
205
|
const original = text;
|
|
191
206
|
|
|
192
|
-
text = organizeImports(text, filePath);
|
|
193
|
-
text = removeLeadingSectionJSDocOnStatements(text);
|
|
207
|
+
text = organizeImports(text, filePath, tsconfigEntries);
|
|
194
208
|
text = text.replace(/\n{3,}(\/\*\*)/g, '\n\n$1');
|
|
195
209
|
|
|
196
210
|
if (text !== original) {
|