@appartmint/tsm-scripts 0.0.1 → 0.0.2
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 +3 -11
- package/src/format/organize-imports-comments.ts +202 -0
- package/src/tinify/tinyify-inject-cert.ts +41 -0
- package/dist/__vite-browser-external-BYRIRx8p.js +0 -8
- package/dist/eslint.config.d.ts +0 -2
- package/dist/format/organize-imports-comments.cjs.js +0 -442
- package/dist/format/organize-imports-comments.es.js +0 -160250
- package/dist/src/format/organize-imports-comments.d.ts +0 -1
- package/dist/src/tinify/tinyify-inject-cert.d.ts +0 -1
- package/dist/tinify/tinyify-inject-cert.cjs.js +0 -1
- package/dist/tinify/tinyify-inject-cert.es.js +0 -12
- package/dist/vite.config.d.ts +0 -2
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appartmint/tsm-scripts",
|
|
3
3
|
"author": "App Art Mint LLC",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"description": "TypeScript
|
|
7
|
+
"description": "TypeScript Node Scripts from App Art Mint LLC",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"typescript",
|
|
10
10
|
"modules",
|
|
@@ -25,16 +25,8 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"files": [
|
|
28
|
-
"
|
|
28
|
+
"src/**/*.{ts,d.ts}"
|
|
29
29
|
],
|
|
30
|
-
"exports": {
|
|
31
|
-
"./*": {
|
|
32
|
-
"types": "./dist/*.d.ts",
|
|
33
|
-
"import": "./dist/*.es.js",
|
|
34
|
-
"require": "./dist/*.cjs.js",
|
|
35
|
-
"default": "./dist/*.es.js"
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
30
|
"scripts": {
|
|
39
31
|
"version": "npm i && git add -A",
|
|
40
32
|
"postversion": "git push && git push --tags && cross-replace gh release create \"v$npm_package_version\" --generate-notes",
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as ts from 'typescript';
|
|
4
|
+
|
|
5
|
+
const rootArg = process.argv[2];
|
|
6
|
+
const ROOT = rootArg ? path.resolve(rootArg) : process.cwd();
|
|
7
|
+
const SRC = path.join(ROOT, 'src');
|
|
8
|
+
|
|
9
|
+
type ImportGroup = 'external' | 'workspace' | 'local' | 'environment';
|
|
10
|
+
|
|
11
|
+
interface ParsedImport {
|
|
12
|
+
spec: string;
|
|
13
|
+
group: ImportGroup;
|
|
14
|
+
finalText: string;
|
|
15
|
+
multi: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function walkTsFiles(dir: string, out: string[] = []): string[] {
|
|
19
|
+
for (const name of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
20
|
+
const p = path.join(dir, name.name);
|
|
21
|
+
if (name.isDirectory()) walkTsFiles(p, out);
|
|
22
|
+
else if (name.isFile() && name.name.endsWith('.ts')) out.push(p);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const FROM_RE = /from\s+(['"])([^'"]+)\1/;
|
|
28
|
+
|
|
29
|
+
function getModuleSpecifier(text: string): string {
|
|
30
|
+
const m = FROM_RE.exec(text);
|
|
31
|
+
return m ? m[2] : '';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function classify(spec: string): ImportGroup {
|
|
35
|
+
if (!spec) return 'external';
|
|
36
|
+
if (spec.includes('/environments/') || spec.includes('environments/environment')) return 'environment';
|
|
37
|
+
if (spec.startsWith('@app-art-mint/') || spec.startsWith('@appartmint/')) return 'workspace';
|
|
38
|
+
if (spec.startsWith('.') || spec.startsWith('..')) return 'local';
|
|
39
|
+
return 'external';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function groupOrder(g: ImportGroup): number {
|
|
43
|
+
const order: Record<ImportGroup, number> = { external: 0, workspace: 1, local: 2, environment: 3 };
|
|
44
|
+
return order[g];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isMultiLineImport(text: string): boolean {
|
|
48
|
+
return text.includes('\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function fileUsesSemicolon(sourceText: string, importTexts: string[]): boolean {
|
|
52
|
+
if (importTexts.some((t) => /;\s*$/.test(t))) return true;
|
|
53
|
+
const sample = sourceText.slice(0, 2000);
|
|
54
|
+
return /import[^;]+;\s*\n/.test(sample);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function normalizeImportLine(text: string): string {
|
|
58
|
+
let s = text.replace(/\s+/g, ' ').replace(/;+\s*$/, '').trim();
|
|
59
|
+
s = s.replace(/,\s*\}(?=\s+from\s+)/g, ' }');
|
|
60
|
+
s = s.replace(/([\w$])}(?=\s+from\s+)/g, '$1 }');
|
|
61
|
+
return s;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function ensureSemicolon(text: string, semicolon: boolean): string {
|
|
65
|
+
const t = text.replace(/;+\s*$/, '').trimEnd();
|
|
66
|
+
return semicolon ? `${t};` : t;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function wrapImportIfNeeded(text: string, maxLen: number, semicolon: boolean): string {
|
|
70
|
+
const normalized = normalizeImportLine(text);
|
|
71
|
+
if (normalized.length <= maxLen) return ensureSemicolon(normalized, semicolon);
|
|
72
|
+
|
|
73
|
+
const fromMatch = /^(.+?)\s+from\s+(['"][^'"]+['"])$/.exec(normalized);
|
|
74
|
+
if (!fromMatch) return ensureSemicolon(normalized, semicolon);
|
|
75
|
+
|
|
76
|
+
const head = fromMatch[1].trim();
|
|
77
|
+
const fromPart = fromMatch[2];
|
|
78
|
+
const m = /^(import\s+(?:type\s+)?)(\{)(.*)(\})\s*$/.exec(head);
|
|
79
|
+
if (!m) return ensureSemicolon(normalized, semicolon);
|
|
80
|
+
|
|
81
|
+
const prefix = m[1];
|
|
82
|
+
const inner = m[3];
|
|
83
|
+
const names = inner
|
|
84
|
+
.split(',')
|
|
85
|
+
.map((s) => s.trim())
|
|
86
|
+
.filter(Boolean);
|
|
87
|
+
if (names.length <= 1) return ensureSemicolon(normalized, semicolon);
|
|
88
|
+
|
|
89
|
+
const innerIndent = '\t';
|
|
90
|
+
const lines = names.map((n) => `${innerIndent}${n},`);
|
|
91
|
+
const body = `${prefix}{\n${lines.join('\n')}\n} from ${fromPart}`;
|
|
92
|
+
return ensureSemicolon(body, semicolon);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sortKey(spec: string): string {
|
|
96
|
+
if (spec.startsWith('@')) return spec.slice(1);
|
|
97
|
+
return spec;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function compareSpec(a: string, b: string): number {
|
|
101
|
+
return sortKey(a).localeCompare(sortKey(b), 'en');
|
|
102
|
+
}
|
|
103
|
+
|
|
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
|
+
|
|
141
|
+
function organizeImports(sourceText: string, filePath: string): string {
|
|
142
|
+
const sf = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
143
|
+
|
|
144
|
+
let i = 0;
|
|
145
|
+
const stmts = sf.statements;
|
|
146
|
+
while (i < stmts.length && ts.isImportDeclaration(stmts[i])) i++;
|
|
147
|
+
|
|
148
|
+
if (i === 0) {
|
|
149
|
+
return stripKnownSectionComments(sourceText);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const importNodes = stmts.slice(0, i);
|
|
153
|
+
const replaceStart = importNodes[0].getFullStart();
|
|
154
|
+
const replaceEnd = importNodes[importNodes.length - 1].end;
|
|
155
|
+
|
|
156
|
+
const importTexts = importNodes.map((node) => sourceText.slice(node.getStart(sf), node.end).trim());
|
|
157
|
+
|
|
158
|
+
const semicolon = fileUsesSemicolon(sourceText, importTexts);
|
|
159
|
+
|
|
160
|
+
const parsed: ParsedImport[] = importTexts.map((trimmed) => {
|
|
161
|
+
const spec = getModuleSpecifier(trimmed);
|
|
162
|
+
const group = classify(spec);
|
|
163
|
+
const wrapped = wrapImportIfNeeded(trimmed, 100, semicolon);
|
|
164
|
+
return { spec, group, finalText: wrapped, multi: isMultiLineImport(wrapped) };
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
parsed.sort((a, b) => {
|
|
168
|
+
const go = groupOrder(a.group) - groupOrder(b.group);
|
|
169
|
+
if (go !== 0) return go;
|
|
170
|
+
const cmp = compareSpec(a.spec, b.spec);
|
|
171
|
+
if (cmp !== 0) return cmp;
|
|
172
|
+
if (a.multi !== b.multi) return a.multi ? 1 : -1;
|
|
173
|
+
return a.finalText.localeCompare(b.finalText);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const newBlock = parsed.map((p) => p.finalText).join('\n');
|
|
177
|
+
const before = sourceText.slice(0, replaceStart);
|
|
178
|
+
const after = sourceText.slice(replaceEnd);
|
|
179
|
+
|
|
180
|
+
let result = before + newBlock + after;
|
|
181
|
+
result = stripKnownSectionComments(result);
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let changed = 0;
|
|
186
|
+
const files = walkTsFiles(SRC);
|
|
187
|
+
|
|
188
|
+
for (const filePath of files) {
|
|
189
|
+
let text = fs.readFileSync(filePath, 'utf8');
|
|
190
|
+
const original = text;
|
|
191
|
+
|
|
192
|
+
text = organizeImports(text, filePath);
|
|
193
|
+
text = removeLeadingSectionJSDocOnStatements(text);
|
|
194
|
+
text = text.replace(/\n{3,}(\/\*\*)/g, '\n\n$1');
|
|
195
|
+
|
|
196
|
+
if (text !== original) {
|
|
197
|
+
fs.writeFileSync(filePath, text, 'utf8');
|
|
198
|
+
changed++;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log('Updated ' + changed.toString() + ' / ' + files.length.toString() + ' files.');
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
|
|
3
|
+
// Check if node_modules exists
|
|
4
|
+
if (!fs.existsSync('node_modules')) {
|
|
5
|
+
console.error('node_modules does not exist');
|
|
6
|
+
process.exit(1);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Check if tinify dependency exists
|
|
10
|
+
if (!fs.existsSync('node_modules/tinify')) {
|
|
11
|
+
console.error('tinify does not exist');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Check if tinify/lib/data/cacert.pem exists
|
|
16
|
+
const certPath = 'node_modules/tinify/lib/data/cacert.pem';
|
|
17
|
+
if (!fs.existsSync(certPath)) {
|
|
18
|
+
console.error('cacert.pem does not exist');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Check if tinify/lib/tinify/Client.js exists
|
|
23
|
+
const clientPath = 'node_modules/tinify/lib/tinify/Client.js';
|
|
24
|
+
if (!fs.existsSync(clientPath)) {
|
|
25
|
+
console.error('Client.js does not exist');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Load cacert.pem into a string value
|
|
30
|
+
const certData = fs.readFileSync(certPath, 'utf8');
|
|
31
|
+
|
|
32
|
+
// Replace the line: const data = fs.readFileSync(`${__dirname}/../data/cacert.pem`).toString();
|
|
33
|
+
// with the cacert.pem string value
|
|
34
|
+
let clientData = fs.readFileSync(clientPath, 'utf8');
|
|
35
|
+
clientData = clientData.replace(/const data = fs\.readFileSync\(`\$\{__dirname\}\/\.\.\/data\/cacert\.pem`\)\.toString\(\);/g, `const data = \`${certData}\`;`);
|
|
36
|
+
|
|
37
|
+
// Save the file
|
|
38
|
+
fs.writeFileSync(clientPath, clientData);
|
|
39
|
+
|
|
40
|
+
// Log success
|
|
41
|
+
console.log('Tinify transformed successfully! cacert.pem injected into Client.js');
|
package/dist/eslint.config.d.ts
DELETED