npmapps 1.1.2 → 1.1.4

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.
@@ -0,0 +1,344 @@
1
+ // scripts/check-import-case.js
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ // ==================== 配置区 ====================
6
+ const ROOT_DIR = path.resolve(__dirname, '..');
7
+ const SRC_DIR = path.resolve(ROOT_DIR, 'src');
8
+
9
+ // 别名映射 —— 与 vite.config.js / tsconfig.json 保持一致
10
+ const ALIAS = {
11
+ '@': SRC_DIR,
12
+ '@components': path.join(SRC_DIR, 'components'),
13
+ '@views': path.join(SRC_DIR, 'views'),
14
+ '@utils': path.join(SRC_DIR, 'utils'),
15
+ '@api': path.join(SRC_DIR, 'api'),
16
+ '@store': path.join(SRC_DIR, 'store'),
17
+ '@assets': path.join(SRC_DIR, 'assets'),
18
+ '@styles': path.join(SRC_DIR, 'styles'),
19
+ };
20
+
21
+ // 尝试补全的扩展名(按 Vite 默认解析顺序)
22
+ const RESOLVE_EXTENSIONS = [
23
+ '.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs',
24
+ '.vue',
25
+ '.json',
26
+ '.css', '.scss', '.sass', '.less', '.styl', '.stylus',
27
+ '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico',
28
+ '.woff', '.woff2', '.ttf', '.eot',
29
+ ];
30
+
31
+ // index 文件候选
32
+ const INDEX_FILES = [
33
+ 'index.js', 'index.ts', 'index.jsx', 'index.tsx',
34
+ 'index.vue', 'index.json',
35
+ 'index.css', 'index.scss', 'index.less',
36
+ ];
37
+
38
+ // 要扫描的源文件类型
39
+ const SCAN_EXTS = ['.js', '.ts', '.jsx', '.tsx', '.vue', '.mjs', '.cjs'];
40
+
41
+ // 需要跳过的 import 前缀(node_modules 包 / 虚拟模块 / 协议)
42
+ const SKIP_PREFIXES = [
43
+ 'virtual:', 'vite/', 'node:', 'http:', 'https:', 'data:',
44
+ ];
45
+
46
+ // 已知的 node_modules 包名(可按需扩充,或用更通用的判断)
47
+ const KNOWN_PACKAGES = new Set([
48
+ 'vue', 'vue-router', 'pinia', 'vuex', 'axios', 'lodash',
49
+ 'dayjs', 'moment', 'element-plus', 'ant-design-vue',
50
+ 'echarts', 'nprogress', 'qs', 'crypto-js',
51
+ ]);
52
+ // ==================== 配置区结束 ====================
53
+
54
+
55
+ let errorCount = 0;
56
+ const errors = [];
57
+
58
+
59
+ // ---------- 工具函数 ----------
60
+
61
+ /**
62
+ * 判断是否为已知的扩展名
63
+ */
64
+ function hasKnownExtension(filePath) {
65
+ const ext = path.extname(filePath).toLowerCase();
66
+ return RESOLVE_EXTENSIONS.includes(ext);
67
+ }
68
+
69
+ /**
70
+ * 判断是否应该跳过(node_modules 包 / 虚拟模块等)
71
+ */
72
+ function shouldSkip(importPath) {
73
+ // 虚拟模块 / 协议
74
+ for (const prefix of SKIP_PREFIXES) {
75
+ if (importPath.startsWith(prefix)) return true;
76
+ }
77
+ // 绝对路径(非项目内)
78
+ if (path.isAbsolute(importPath) && !importPath.startsWith(SRC_DIR)) return true;
79
+ // 不以 . 或 @ 开头 → 大概率是 node_modules 包
80
+ if (!importPath.startsWith('.') && !importPath.startsWith('@')) return true;
81
+ // @ 开头但不是我们配置的别名 → 可能是 npm 包(如 @vueuse/core)
82
+ if (importPath.startsWith('@')) {
83
+ const isAlias = Object.keys(ALIAS).some(
84
+ a => importPath === a || importPath.startsWith(a + '/')
85
+ );
86
+ if (!isAlias) return true;
87
+ }
88
+ return false;
89
+ }
90
+
91
+ /**
92
+ * 将别名替换为真实路径
93
+ */
94
+ function resolveAlias(importPath) {
95
+ // 精确匹配或前缀匹配(长的别名优先)
96
+ const sortedAliases = Object.keys(ALIAS).sort((a, b) => b.length - a.length);
97
+ for (const alias of sortedAliases) {
98
+ if (importPath === alias) {
99
+ return ALIAS[alias];
100
+ }
101
+ if (importPath.startsWith(alias + '/')) {
102
+ return importPath.replace(alias, ALIAS[alias]);
103
+ }
104
+ }
105
+ return null;
106
+ }
107
+
108
+ /**
109
+ * 核心:逐段校验路径大小写
110
+ * 返回 { ok, mismatch? }
111
+ */
112
+ function verifyPathCase(targetPath) {
113
+ const normalized = path.resolve(targetPath); // 处理 .. 等
114
+ const segments = normalized.split(path.sep).filter(Boolean);
115
+
116
+ // Windows 盘符
117
+ let current = '';
118
+ let startIdx = 0;
119
+ if (/^[a-zA-Z]:$/.test(segments[0])) {
120
+ current = segments[0] + path.sep;
121
+ startIdx = 1;
122
+ } else {
123
+ current = path.sep;
124
+ }
125
+
126
+ for (let i = startIdx; i < segments.length; i++) {
127
+ const seg = segments[i];
128
+ let entries;
129
+ try {
130
+ entries = fs.readdirSync(current);
131
+ } catch {
132
+ return { ok: false, notFound: true };
133
+ }
134
+
135
+ const exactMatch = entries.find(e => e === seg);
136
+ if (exactMatch) {
137
+ current = path.join(current, seg);
138
+ continue;
139
+ }
140
+
141
+ const caseMatch = entries.find(e => e.toLowerCase() === seg.toLowerCase());
142
+ if (caseMatch) {
143
+ return {
144
+ ok: false,
145
+ mismatch: {
146
+ expected: seg,
147
+ actual: caseMatch,
148
+ dir: current,
149
+ },
150
+ };
151
+ }
152
+
153
+ return { ok: false, notFound: true };
154
+ }
155
+
156
+ return { ok: true, realPath: current };
157
+ }
158
+
159
+ /**
160
+ * 解析 import 路径,尝试各种补全
161
+ * 返回 { found, caseOk, mismatch? }
162
+ */
163
+ function resolveAndCheck(importPath, fromFile) {
164
+ // 1. 别名替换
165
+ let basePath;
166
+ if (importPath.startsWith('.')) {
167
+ basePath = path.resolve(path.dirname(fromFile), importPath);
168
+ } else {
169
+ basePath = resolveAlias(importPath);
170
+ if (!basePath) return { found: false, skipped: true };
171
+ }
172
+
173
+ // normalize(处理 ../ 等)
174
+ basePath = path.resolve(basePath);
175
+
176
+ // 2. 构建候选路径列表
177
+ const candidates = [];
178
+
179
+ if (hasKnownExtension(basePath)) {
180
+ // 已有扩展名:./index.scss → 直接验证
181
+ candidates.push(basePath);
182
+ } else {
183
+ // 无扩展名:./router → 可能是文件或目录
184
+ // 2a. 当作文件,补扩展名
185
+ for (const ext of RESOLVE_EXTENSIONS) {
186
+ candidates.push(basePath + ext);
187
+ }
188
+ // 2b. 当作目录,找 index
189
+ for (const idx of INDEX_FILES) {
190
+ candidates.push(path.join(basePath, idx));
191
+ }
192
+ // 2c. 原路径本身(可能是无扩展名的特殊文件)
193
+ candidates.push(basePath);
194
+ }
195
+
196
+ // 3. 逐个候选验证
197
+ for (const candidate of candidates) {
198
+ const result = verifyPathCase(candidate);
199
+
200
+ if (result.ok) {
201
+ // 路径存在且大小写正确
202
+ try {
203
+ const stat = fs.statSync(candidate);
204
+ if (stat.isFile()) {
205
+ return { found: true, caseOk: true };
206
+ }
207
+ } catch { /* continue */ }
208
+ }
209
+
210
+ if (result.mismatch) {
211
+ // 路径存在但大小写不对 → 这就是我们要找的 bug
212
+ return {
213
+ found: true,
214
+ caseOk: false,
215
+ mismatch: result.mismatch,
216
+ candidate,
217
+ };
218
+ }
219
+ }
220
+
221
+ return { found: false };
222
+ }
223
+
224
+ /**
225
+ * 从文件内容提取所有 import 路径
226
+ */
227
+ function extractImports(content, filePath) {
228
+ const imports = [];
229
+ const ext = path.extname(filePath);
230
+
231
+ // JS/TS/Vue 中的 import
232
+ const jsPatterns = [
233
+ /import\s+[\s\S]*?from\s+['"]([^'"]+)['"]/g,
234
+ /import\s+['"]([^'"]+)['"]/g,
235
+ /export\s+[\s\S]*?from\s+['"]([^'"]+)['"]/g,
236
+ /export\s*\{[^}]*\}\s*from\s+['"]([^'"]+)['"]/g,
237
+ /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
238
+ /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
239
+ ];
240
+
241
+ for (const regex of jsPatterns) {
242
+ let m;
243
+ while ((m = regex.exec(content)) !== null) {
244
+ imports.push(m[1]);
245
+ }
246
+ }
247
+
248
+ // Vue SFC 中 <style> 里的 @import 和 url()
249
+ if (ext === '.vue' || ext === '.css' || ext === '.scss' || ext === '.less') {
250
+ const cssPatterns = [
251
+ /@import\s+(?:url\(\s*)?['"]?([^'");\s]+)['"]?\s*\)?/g,
252
+ /url\(\s*['"]?([^'");\s]+)['"]?\s*\)/g,
253
+ ];
254
+ for (const regex of cssPatterns) {
255
+ let m;
256
+ while ((m = regex.exec(content)) !== null) {
257
+ const p = m[1];
258
+ // 跳过 http / data-uri / # 等
259
+ if (/^(https?:|data:|#|\/\/)/.test(p)) continue;
260
+ imports.push(p);
261
+ }
262
+ }
263
+ }
264
+
265
+ // 去重
266
+ return [...new Set(imports)];
267
+ }
268
+
269
+ /**
270
+ * 递归扫描目录
271
+ */
272
+ function walkDir(dir, result = []) {
273
+ let entries;
274
+ try {
275
+ entries = fs.readdirSync(dir, { withFileTypes: true });
276
+ } catch {
277
+ return result;
278
+ }
279
+ for (const entry of entries) {
280
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist') continue;
281
+ const fullPath = path.join(dir, entry.name);
282
+ if (entry.isDirectory()) {
283
+ walkDir(fullPath, result);
284
+ } else if (SCAN_EXTS.includes(path.extname(entry.name))) {
285
+ result.push(fullPath);
286
+ }
287
+ }
288
+ return result;
289
+ }
290
+
291
+
292
+ // ==================== 主流程 ====================
293
+ console.log('');
294
+ console.log('🔍 检查 import 路径大小写');
295
+ console.log('─'.repeat(50));
296
+ console.log(` 项目根目录: ${ROOT_DIR}`);
297
+ console.log(` 扫描目录: ${SRC_DIR}`);
298
+ console.log('');
299
+
300
+ const files = walkDir(SRC_DIR);
301
+ console.log(` 共 ${files.length} 个源文件`);
302
+ console.log('');
303
+
304
+ for (const file of files) {
305
+ const content = fs.readFileSync(file, 'utf-8');
306
+ const imports = extractImports(content, file);
307
+ const relFile = path.relative(ROOT_DIR, file);
308
+
309
+ for (const imp of imports) {
310
+ if (shouldSkip(imp)) continue;
311
+
312
+ const result = resolveAndCheck(imp, file);
313
+
314
+ if (result.found && !result.caseOk) {
315
+ errorCount++;
316
+ const { expected, actual, dir } = result.mismatch;
317
+ const relDir = path.relative(ROOT_DIR, dir);
318
+
319
+ errors.push({
320
+ file: relFile,
321
+ importPath: imp,
322
+ expected,
323
+ actual,
324
+ dir: relDir,
325
+ });
326
+
327
+ console.error(` ❌ ${relFile}`);
328
+ console.error(` import: "${imp}"`);
329
+ console.error(` 路径段: "${expected}" → 实际应为 "${actual}"`);
330
+ console.error(` 目录: ${relDir}`);
331
+ console.error('');
332
+ }
333
+ }
334
+ }
335
+
336
+ // ==================== 结果 ====================
337
+ console.log('─'.repeat(50));
338
+ if (errorCount > 0) {
339
+ console.error(`\n💥 共 ${errorCount} 处大小写不匹配!\n`);
340
+ process.exit(1);
341
+ } else {
342
+ console.log(`\n✅ 全部通过,未发现大小写问题。\n`);
343
+ process.exit(0);
344
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "npmapps",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
Binary file