adspecs 0.1.19 → 0.1.21
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/.adspecs/feature.json +16 -16
- package/.adspecs/feature.yml +28 -28
- package/.adspecs/paths.json +17 -17
- package/.adspecs/templates/04-/345/211/215/347/253/257/345/212/237/350/203/275/350/256/276/350/256/241/346/250/241/346/235/277.md +1 -1
- package/.adspecs/templates/05-/345/220/216/347/253/257/344/273/273/345/212/241/346/270/205/345/215/225/346/250/241/346/235/277.md +613 -724
- package/.adspecs/templates/05b-/345/211/215/347/253/257/344/273/273/345/212/241/346/270/205/345/215/225/346/250/241/346/235/277.md +51 -51
- package/.claude-plugin/marketplace.json +23 -23
- package/.claude-plugin/plugin.json +18 -18
- package/.qoder-plugin/plugin.json +31 -31
- package/CLAUDE.md +1 -5
- package/INSTALL.md +3 -3
- package/README.md +395 -395
- package/bin/adspecs.js +129 -129
- package/hooks/commit-queue.js +245 -245
- package/hooks/hooks.json +63 -63
- package/hooks/session-start.js +44 -44
- package/hooks/wiki-queue.js +127 -127
- package/package.json +61 -61
- package/references/ant6-front-standard/index.md +99 -99
- package/references/antd-front-demo/public/mockServiceWorker.js +361 -361
- package/references/ecp-end-standard/index.md +63 -63
- package/references/python-end-standard/01-Python/345/220/216/347/253/257/347/274/226/347/240/201/350/247/204/350/214/203.md +372 -372
- package/references/python-end-standard/02-/346/225/260/346/215/256/345/272/223/350/256/276/350/256/241/344/270/216/344/275/277/347/224/250/350/247/204/350/214/203.md +226 -226
- package/references/python-end-standard/03-Celery/345/274/202/346/255/245/344/273/273/345/212/241/350/247/204/350/214/203.md +237 -237
- package/references/python-end-standard/04-Redis/344/275/277/347/224/250/350/247/204/350/214/203.md +231 -231
- package/scripts/postinstall.js +107 -107
- package/scripts/sync-version.js +105 -105
- package/skills/.claude/.wiki-update-queue +26 -26
- package/skills/adspecs-constitution/SKILL.md +157 -0
- package/skills/adspecs-export-word/SKILL.md +498 -498
- package/skills/adspecs-export-word/references/md-to-docx.js +862 -862
- package/skills/adspecs-export-word/references/package-lock.json +220 -220
- package/skills/adspecs-export-word/references/package.json +10 -10
- package/skills/adspecs-front-prototype/SKILL.md +405 -405
- package/skills/adspecs-front-spec/SKILL.md +4 -4
- package/skills/adspecs-front-tasks/SKILL.md +213 -173
- package/skills/adspecs-plan/SKILL.md +59 -69
- package/skills/adspecs-prd/SKILL.md +13 -5
- package/skills/adspecs-prd-to-demo/SKILL.md +532 -0
- package/skills/adspecs-tasks/SKILL.md +175 -204
- package/skills/adspecs-update-status/SKILL.md +382 -382
- package/skills/adspecs-utest/SKILL.md +107 -116
- package/skills/grill-me/SKILL.md +7 -0
- package/skills/grill-me/agents/openai.yaml +5 -0
- package/skills/playwright-cli/SKILL.md +420 -0
- package/skills/playwright-cli/references/element-attributes.md +23 -0
- package/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/skills/playwright-cli/references/request-mocking.md +87 -0
- package/skills/playwright-cli/references/running-code.md +241 -0
- package/skills/playwright-cli/references/session-management.md +225 -0
- package/skills/playwright-cli/references/storage-state.md +275 -0
- package/skills/playwright-cli/references/test-generation.md +433 -0
- package/skills/playwright-cli/references/tracing.md +139 -0
- package/skills/playwright-cli/references/video-recording.md +143 -0
- package/skills/playwright-trace/SKILL.md +171 -0
- package/skills/project-init/SKILL.md +93 -22
- package/skills/project-init/references/front-demo/.claude/settings.local.json +9 -0
- package/skills/wiki-update/SKILL.md +232 -232
- package/src/commands/doctor.js +197 -197
- package/src/commands/init.js +83 -83
- package/src/commands/plugin.js +165 -165
- package/src/commands/update.js +87 -87
- package/src/lib/area-scanner.js +129 -129
- package/src/lib/copier.js +104 -104
- package/src/lib/dir-utils.js +161 -133
- package/src/lib/json-merge.js +114 -114
- package/src/lib/paths-defaults.js +37 -37
- package/src/lib/prompts.js +428 -347
- package/src/lib/readme-gen.js +143 -143
- package/src/lib/report.js +338 -327
- package/src/lib/scaffolder.js +551 -518
- package/src/lib/short-name.js +36 -36
- package/src/utils.js +80 -80
- package/references/antd-front-demo/.env +0 -15
package/src/lib/area-scanner.js
CHANGED
|
@@ -1,129 +1,129 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const { hasAnyFile } = require('./dir-utils');
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* 区域状态扫描
|
|
9
|
-
*
|
|
10
|
-
* 按模式扫描目标目录,返回各区域的存在状态,
|
|
11
|
-
* 为冲突决策和计划摘要提供数据基础。
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 各区域的目录名映射
|
|
16
|
-
*/
|
|
17
|
-
const AREA_DIRS = {
|
|
18
|
-
adspecs: '.adspecs',
|
|
19
|
-
docs: 'docs',
|
|
20
|
-
wiki: 'wiki',
|
|
21
|
-
webUi: 'web-ui',
|
|
22
|
-
service: 'service',
|
|
23
|
-
frontDemo: 'front-demo',
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* 按模式返回需要处理的区域列表
|
|
28
|
-
*
|
|
29
|
-
* @param {string} mode - 初始化模式
|
|
30
|
-
* @returns {string[]} 区域 key 列表
|
|
31
|
-
*/
|
|
32
|
-
function getAreaListForMode(mode) {
|
|
33
|
-
switch (mode) {
|
|
34
|
-
case 'simple':
|
|
35
|
-
return ['adspecs', 'docs', 'wiki'];
|
|
36
|
-
case 'classic':
|
|
37
|
-
return ['adspecs', 'docs', 'wiki', 'frontDemo'];
|
|
38
|
-
case 'full':
|
|
39
|
-
case 'demo':
|
|
40
|
-
return ['adspecs', 'docs', 'wiki', 'webUi', 'service', 'frontDemo'];
|
|
41
|
-
default:
|
|
42
|
-
return ['adspecs', 'docs', 'wiki'];
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* 按模式扫描目标目录,返回各区域存在状态
|
|
48
|
-
*
|
|
49
|
-
* @param {string} targetDir - 目标项目根目录
|
|
50
|
-
* @param {string} mode - 初始化模式
|
|
51
|
-
* @returns {object} 区域状态映射 { areaKey: { exists, hasFiles, dirName } }
|
|
52
|
-
*/
|
|
53
|
-
function scanAreaStates(targetDir, mode) {
|
|
54
|
-
const areas = getAreaListForMode(mode);
|
|
55
|
-
const states = {};
|
|
56
|
-
|
|
57
|
-
for (const areaKey of areas) {
|
|
58
|
-
const dirName = AREA_DIRS[areaKey];
|
|
59
|
-
const fullPath = path.join(targetDir, dirName);
|
|
60
|
-
const exists = fs.existsSync(fullPath);
|
|
61
|
-
|
|
62
|
-
states[areaKey] = {
|
|
63
|
-
exists,
|
|
64
|
-
hasFiles: exists ? hasAnyFile(fullPath) : false,
|
|
65
|
-
dirName,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return states;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* 将区域 key 转换为中文显示名
|
|
74
|
-
*
|
|
75
|
-
* @param {string} areaKey
|
|
76
|
-
* @returns {string}
|
|
77
|
-
*/
|
|
78
|
-
function areaDisplayName(areaKey) {
|
|
79
|
-
const names = {
|
|
80
|
-
adspecs: '.adspecs',
|
|
81
|
-
docs: 'docs',
|
|
82
|
-
wiki: 'wiki',
|
|
83
|
-
webUi: 'web-ui',
|
|
84
|
-
service: 'service',
|
|
85
|
-
frontDemo: 'front-demo',
|
|
86
|
-
};
|
|
87
|
-
return names[areaKey] || areaKey;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* 将决策转换为中文描述
|
|
92
|
-
*
|
|
93
|
-
* @param {string} decision - create | recreate | update | skip
|
|
94
|
-
* @returns {string}
|
|
95
|
-
*/
|
|
96
|
-
function decisionLabel(decision) {
|
|
97
|
-
const labels = {
|
|
98
|
-
create: '创建',
|
|
99
|
-
recreate: '重新创建',
|
|
100
|
-
update: '更新',
|
|
101
|
-
skip: '跳过',
|
|
102
|
-
};
|
|
103
|
-
return labels[decision] || decision;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 将决策转换为状态图标
|
|
108
|
-
*
|
|
109
|
-
* @param {string} decision
|
|
110
|
-
* @returns {string}
|
|
111
|
-
*/
|
|
112
|
-
function decisionIcon(decision) {
|
|
113
|
-
const icons = {
|
|
114
|
-
create: '✅',
|
|
115
|
-
recreate: '🔄',
|
|
116
|
-
update: '🔄',
|
|
117
|
-
skip: '⏭',
|
|
118
|
-
};
|
|
119
|
-
return icons[decision] || '❓';
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
module.exports = {
|
|
123
|
-
AREA_DIRS,
|
|
124
|
-
getAreaListForMode,
|
|
125
|
-
scanAreaStates,
|
|
126
|
-
areaDisplayName,
|
|
127
|
-
decisionLabel,
|
|
128
|
-
decisionIcon,
|
|
129
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { hasAnyFile } = require('./dir-utils');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 区域状态扫描
|
|
9
|
+
*
|
|
10
|
+
* 按模式扫描目标目录,返回各区域的存在状态,
|
|
11
|
+
* 为冲突决策和计划摘要提供数据基础。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 各区域的目录名映射
|
|
16
|
+
*/
|
|
17
|
+
const AREA_DIRS = {
|
|
18
|
+
adspecs: '.adspecs',
|
|
19
|
+
docs: 'docs',
|
|
20
|
+
wiki: 'wiki',
|
|
21
|
+
webUi: 'web-ui',
|
|
22
|
+
service: 'service',
|
|
23
|
+
frontDemo: 'front-demo',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 按模式返回需要处理的区域列表
|
|
28
|
+
*
|
|
29
|
+
* @param {string} mode - 初始化模式
|
|
30
|
+
* @returns {string[]} 区域 key 列表
|
|
31
|
+
*/
|
|
32
|
+
function getAreaListForMode(mode) {
|
|
33
|
+
switch (mode) {
|
|
34
|
+
case 'simple':
|
|
35
|
+
return ['adspecs', 'docs', 'wiki'];
|
|
36
|
+
case 'classic':
|
|
37
|
+
return ['adspecs', 'docs', 'wiki', 'frontDemo'];
|
|
38
|
+
case 'full':
|
|
39
|
+
case 'demo':
|
|
40
|
+
return ['adspecs', 'docs', 'wiki', 'webUi', 'service', 'frontDemo'];
|
|
41
|
+
default:
|
|
42
|
+
return ['adspecs', 'docs', 'wiki'];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 按模式扫描目标目录,返回各区域存在状态
|
|
48
|
+
*
|
|
49
|
+
* @param {string} targetDir - 目标项目根目录
|
|
50
|
+
* @param {string} mode - 初始化模式
|
|
51
|
+
* @returns {object} 区域状态映射 { areaKey: { exists, hasFiles, dirName } }
|
|
52
|
+
*/
|
|
53
|
+
function scanAreaStates(targetDir, mode) {
|
|
54
|
+
const areas = getAreaListForMode(mode);
|
|
55
|
+
const states = {};
|
|
56
|
+
|
|
57
|
+
for (const areaKey of areas) {
|
|
58
|
+
const dirName = AREA_DIRS[areaKey];
|
|
59
|
+
const fullPath = path.join(targetDir, dirName);
|
|
60
|
+
const exists = fs.existsSync(fullPath);
|
|
61
|
+
|
|
62
|
+
states[areaKey] = {
|
|
63
|
+
exists,
|
|
64
|
+
hasFiles: exists ? hasAnyFile(fullPath) : false,
|
|
65
|
+
dirName,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return states;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 将区域 key 转换为中文显示名
|
|
74
|
+
*
|
|
75
|
+
* @param {string} areaKey
|
|
76
|
+
* @returns {string}
|
|
77
|
+
*/
|
|
78
|
+
function areaDisplayName(areaKey) {
|
|
79
|
+
const names = {
|
|
80
|
+
adspecs: '.adspecs',
|
|
81
|
+
docs: 'docs',
|
|
82
|
+
wiki: 'wiki',
|
|
83
|
+
webUi: 'web-ui',
|
|
84
|
+
service: 'service',
|
|
85
|
+
frontDemo: 'front-demo',
|
|
86
|
+
};
|
|
87
|
+
return names[areaKey] || areaKey;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 将决策转换为中文描述
|
|
92
|
+
*
|
|
93
|
+
* @param {string} decision - create | recreate | update | skip
|
|
94
|
+
* @returns {string}
|
|
95
|
+
*/
|
|
96
|
+
function decisionLabel(decision) {
|
|
97
|
+
const labels = {
|
|
98
|
+
create: '创建',
|
|
99
|
+
recreate: '重新创建',
|
|
100
|
+
update: '更新',
|
|
101
|
+
skip: '跳过',
|
|
102
|
+
};
|
|
103
|
+
return labels[decision] || decision;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 将决策转换为状态图标
|
|
108
|
+
*
|
|
109
|
+
* @param {string} decision
|
|
110
|
+
* @returns {string}
|
|
111
|
+
*/
|
|
112
|
+
function decisionIcon(decision) {
|
|
113
|
+
const icons = {
|
|
114
|
+
create: '✅',
|
|
115
|
+
recreate: '🔄',
|
|
116
|
+
update: '🔄',
|
|
117
|
+
skip: '⏭',
|
|
118
|
+
};
|
|
119
|
+
return icons[decision] || '❓';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
AREA_DIRS,
|
|
124
|
+
getAreaListForMode,
|
|
125
|
+
scanAreaStates,
|
|
126
|
+
areaDisplayName,
|
|
127
|
+
decisionLabel,
|
|
128
|
+
decisionIcon,
|
|
129
|
+
};
|
package/src/lib/copier.js
CHANGED
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 复制过程中需要跳过的目录名
|
|
8
|
-
* 这些目录通常不应出现在目标项目中(体积大 / 与版本控制冲突 / 运行时缓存)
|
|
9
|
-
*/
|
|
10
|
-
const EXCLUDED_DIRS = new Set([
|
|
11
|
-
'node_modules',
|
|
12
|
-
'.git',
|
|
13
|
-
'.vite',
|
|
14
|
-
'.venv',
|
|
15
|
-
'__pycache__',
|
|
16
|
-
'.next',
|
|
17
|
-
'dist',
|
|
18
|
-
'build',
|
|
19
|
-
]);
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 递归复制目录,过滤掉不需要的大目录
|
|
23
|
-
*
|
|
24
|
-
* @param {string} src - 源目录绝对路径
|
|
25
|
-
* @param {string} dest - 目标目录绝对路径(不存在会自动创建)
|
|
26
|
-
* @param {object} [opts] - 选项
|
|
27
|
-
* @param {Set<string>} [opts.exclude] - 额外要排除的目录名
|
|
28
|
-
* @param {function} [opts.onProgress] - 进度回调 (filePath: string) => void
|
|
29
|
-
* @returns {{ fileCount: number, skippedDirs: string[] }} 复制的文件数和被跳过的目录
|
|
30
|
-
*/
|
|
31
|
-
function copyDirFiltered(src, dest, opts = {}) {
|
|
32
|
-
const exclude = opts.exclude || new Set();
|
|
33
|
-
const onProgress = opts.onProgress || (() => {});
|
|
34
|
-
const skippedDirs = [];
|
|
35
|
-
|
|
36
|
-
if (!fs.existsSync(src)) {
|
|
37
|
-
return { fileCount: 0, skippedDirs };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
41
|
-
|
|
42
|
-
let fileCount = 0;
|
|
43
|
-
|
|
44
|
-
function _copy(currentSrc, currentDest) {
|
|
45
|
-
const entries = fs.readdirSync(currentSrc, { withFileTypes: true });
|
|
46
|
-
for (const entry of entries) {
|
|
47
|
-
const srcPath = path.join(currentSrc, entry.name);
|
|
48
|
-
const destPath = path.join(currentDest, entry.name);
|
|
49
|
-
|
|
50
|
-
if (entry.isDirectory()) {
|
|
51
|
-
if (EXCLUDED_DIRS.has(entry.name) || exclude.has(entry.name)) {
|
|
52
|
-
skippedDirs.push(entry.name);
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
fs.mkdirSync(destPath, { recursive: true });
|
|
56
|
-
_copy(srcPath, destPath);
|
|
57
|
-
} else if (entry.isFile()) {
|
|
58
|
-
fs.copyFileSync(srcPath, destPath);
|
|
59
|
-
onProgress(destPath);
|
|
60
|
-
fileCount++;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
_copy(src, dest);
|
|
66
|
-
return { fileCount, skippedDirs };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* 删除目录(如果存在)
|
|
71
|
-
* @param {string} dirPath - 目录路径
|
|
72
|
-
*/
|
|
73
|
-
function removeDir(dirPath) {
|
|
74
|
-
if (fs.existsSync(dirPath)) {
|
|
75
|
-
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* 统计目录下的文件数量
|
|
81
|
-
* @param {string} dirPath - 目录路径
|
|
82
|
-
* @returns {number}
|
|
83
|
-
*/
|
|
84
|
-
function countFiles(dirPath) {
|
|
85
|
-
if (!fs.existsSync(dirPath)) return 0;
|
|
86
|
-
let count = 0;
|
|
87
|
-
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
88
|
-
for (const entry of entries) {
|
|
89
|
-
const fullPath = path.join(dirPath, entry.name);
|
|
90
|
-
if (entry.isDirectory()) {
|
|
91
|
-
count += countFiles(fullPath);
|
|
92
|
-
} else if (entry.isFile()) {
|
|
93
|
-
count++;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return count;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
module.exports = {
|
|
100
|
-
copyDirFiltered,
|
|
101
|
-
removeDir,
|
|
102
|
-
countFiles,
|
|
103
|
-
EXCLUDED_DIRS,
|
|
104
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 复制过程中需要跳过的目录名
|
|
8
|
+
* 这些目录通常不应出现在目标项目中(体积大 / 与版本控制冲突 / 运行时缓存)
|
|
9
|
+
*/
|
|
10
|
+
const EXCLUDED_DIRS = new Set([
|
|
11
|
+
'node_modules',
|
|
12
|
+
'.git',
|
|
13
|
+
'.vite',
|
|
14
|
+
'.venv',
|
|
15
|
+
'__pycache__',
|
|
16
|
+
'.next',
|
|
17
|
+
'dist',
|
|
18
|
+
'build',
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 递归复制目录,过滤掉不需要的大目录
|
|
23
|
+
*
|
|
24
|
+
* @param {string} src - 源目录绝对路径
|
|
25
|
+
* @param {string} dest - 目标目录绝对路径(不存在会自动创建)
|
|
26
|
+
* @param {object} [opts] - 选项
|
|
27
|
+
* @param {Set<string>} [opts.exclude] - 额外要排除的目录名
|
|
28
|
+
* @param {function} [opts.onProgress] - 进度回调 (filePath: string) => void
|
|
29
|
+
* @returns {{ fileCount: number, skippedDirs: string[] }} 复制的文件数和被跳过的目录
|
|
30
|
+
*/
|
|
31
|
+
function copyDirFiltered(src, dest, opts = {}) {
|
|
32
|
+
const exclude = opts.exclude || new Set();
|
|
33
|
+
const onProgress = opts.onProgress || (() => {});
|
|
34
|
+
const skippedDirs = [];
|
|
35
|
+
|
|
36
|
+
if (!fs.existsSync(src)) {
|
|
37
|
+
return { fileCount: 0, skippedDirs };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
41
|
+
|
|
42
|
+
let fileCount = 0;
|
|
43
|
+
|
|
44
|
+
function _copy(currentSrc, currentDest) {
|
|
45
|
+
const entries = fs.readdirSync(currentSrc, { withFileTypes: true });
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
const srcPath = path.join(currentSrc, entry.name);
|
|
48
|
+
const destPath = path.join(currentDest, entry.name);
|
|
49
|
+
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
if (EXCLUDED_DIRS.has(entry.name) || exclude.has(entry.name)) {
|
|
52
|
+
skippedDirs.push(entry.name);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
56
|
+
_copy(srcPath, destPath);
|
|
57
|
+
} else if (entry.isFile()) {
|
|
58
|
+
fs.copyFileSync(srcPath, destPath);
|
|
59
|
+
onProgress(destPath);
|
|
60
|
+
fileCount++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
_copy(src, dest);
|
|
66
|
+
return { fileCount, skippedDirs };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 删除目录(如果存在)
|
|
71
|
+
* @param {string} dirPath - 目录路径
|
|
72
|
+
*/
|
|
73
|
+
function removeDir(dirPath) {
|
|
74
|
+
if (fs.existsSync(dirPath)) {
|
|
75
|
+
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 统计目录下的文件数量
|
|
81
|
+
* @param {string} dirPath - 目录路径
|
|
82
|
+
* @returns {number}
|
|
83
|
+
*/
|
|
84
|
+
function countFiles(dirPath) {
|
|
85
|
+
if (!fs.existsSync(dirPath)) return 0;
|
|
86
|
+
let count = 0;
|
|
87
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
90
|
+
if (entry.isDirectory()) {
|
|
91
|
+
count += countFiles(fullPath);
|
|
92
|
+
} else if (entry.isFile()) {
|
|
93
|
+
count++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return count;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = {
|
|
100
|
+
copyDirFiltered,
|
|
101
|
+
removeDir,
|
|
102
|
+
countFiles,
|
|
103
|
+
EXCLUDED_DIRS,
|
|
104
|
+
};
|