@openyida/yidacli 0.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.
@@ -0,0 +1,39 @@
1
+ # 生日祝福小游戏 需求文档
2
+
3
+ ## 功能需求
4
+
5
+ ### 核心功能
6
+
7
+ 1. **欢迎界面**
8
+ - 输入寿星姓名
9
+ - 输入送祝福人姓名
10
+ - 开始游戏按钮
11
+
12
+ 2. **点蜡烛游戏**
13
+ - 显示生日蛋糕和蜡烛(10根)
14
+ - 点击蜡烛可以吹灭(使用麦克风)
15
+ - 计时功能 - 记录吹灭所有蜡烛的时间
16
+ - 显示已吹灭的蜡烛数量
17
+
18
+ 3. **祝福界面**
19
+ - 显示精美的生日贺卡
20
+ - 显示寿星姓名和送祝福人姓名
21
+ - 显示完成时间
22
+ - 生日祝福语
23
+
24
+ 4. **庆祝动画**
25
+ - 彩带飘落效果
26
+ - 烟花动画
27
+ - 生日祝福语滚动
28
+
29
+ ### 页面布局
30
+
31
+ - **全屏沉浸式设计**
32
+ - **温馨的粉色主题**
33
+ - **流畅的动画效果**
34
+
35
+ ## 页面配置
36
+
37
+ ### 首页(自定义页面)
38
+
39
+ 展示完整的生日祝福小游戏功能。
@@ -0,0 +1,78 @@
1
+ # 为"未来视野2026"线上发布会创建高转化率报名页。
2
+
3
+ ## 转化策略
4
+ - 紧迫感:倒计时+席位稀缺+早鸟限时
5
+ - 社交证明:实时报名动态+企业Logo墙+往届92%满意度
6
+ - 低摩擦:4字段表单+智能填充+LinkedIn/微信一键登录
7
+ - 价值前置:白皮书预承诺+顶级嘉宾+5000+创新者社群
8
+
9
+ ## 页面结构
10
+
11
+ ### 1. 固定顶部栏
12
+ - 背景:动态渐变(深蓝#0F172A→紫#7C3AED→粉#EC4899)
13
+ - Logo"未来视野2026"+实时倒计时+CTA"立即锁定席位"
14
+
15
+ ### 2. Hero区
16
+ - 视频背景:科技粒子/光线流动(移动端降级为渐变)
17
+ - 标签"2026年度科技趋势发布会·线上直播"
18
+ - 主标题:渐变文字+轻微故障艺术
19
+ - 副标题"与全球5000+创新者共同预见下一个十年"
20
+ - 信息卡:2026.3.15 | 14:00-17:00 GMT+8 | 线上直播+互动问答
21
+ - 双CTA:"免费报名"(粉→橙渐变,悬停发光)+"查看议程"(透明边框)
22
+ - 底部:实时报名动态"刚刚:张经理来自字节跳动完成报名"(10秒轮换)
23
+
24
+ ### 3. 价值主张(3列卡片)
25
+ - 趋势首发:2026白皮书独家首发(价值¥1999)
26
+ - 顶级嘉宾:3位领袖+2位神秘大咖
27
+ - 资源网络:加入5000+创新者社群
28
+ - 数据背书:往届92%满意、平均观看2.5小时
29
+
30
+ ### 4. 议程时间轴
31
+ 14:00-14:10 开场 | 14:10-14:50 AI重构商业逻辑 | 14:50-15:30 碳中和圆桌 | 15:30-15:45 中场+VR抽奖 | 15:45-16:30 神秘新品发布 | 16:30-17:00 互动问答+资料包
32
+ - 悬停显示详情,垂直线条发光效果
33
+
34
+ ### 5. 嘉宾阵容
35
+ - 头像+姓名职位+领域标签+一句话简介
36
+ - 第5位剪影问号"3月10日揭晓"
37
+
38
+ ### 6. 社交证明
39
+ - Logo墙:华为/腾讯/阿里/蔚来/理想(灰度悬停彩色)
40
+ - 评价轮播:往届参会者视频/文字
41
+ - 实时数据:"已有3,847人报名"(数字跳动)
42
+
43
+ ### 7. 报名表单
44
+ - 头部:"锁定席位"+"免费报名,仅限5000人"+进度条"剩余1,153"
45
+ - 字段:姓名*、公司*、职位*(下拉)、工作邮箱*(企业邮箱验证)
46
+ - 增强:智能填充、社交登录、默认勾选接收提醒(GDPR合规)
47
+ - 提交:"立即报名,获取专属席位"+加载动画
48
+ - 安全提示:"信息严格保密"
49
+
50
+ ### 8. 成功状态
51
+ - Confetti动画+对勾绘制
52
+ - 显示确认信息+日历.ics下载+社交分享链接+预览资料下载+社群入口
53
+
54
+ ### 9. 邮件营销
55
+ - 即时:凭证+日历+指南
56
+ - D+3:嘉宾预告 | D+7:资料包 | D+1:最后提醒 | D+0:1小时提醒 | D+1:回放+调查
57
+ - 工具:Mailchimp/SendGrid API
58
+
59
+ ## 设计规范
60
+
61
+ **色彩**:深空黑#0B0F19(背景)、霓虹蓝#3B82F6、电光紫#8B5CF6、赛博粉#EC4899(CTA)、能量橙#F59E0B、纯白#FFFFFF、玻璃态rgba(255,255,255,0.1)
62
+
63
+ **字体**:标题Inter Bold大字号紧凑、正文Inter Regular 16-18px、数据DIN Alternate等宽
64
+
65
+ **动效**:渐变网格背景、鼠标跟随粒子、按钮磁性悬停、倒计时翻转动画、卡片3D倾斜
66
+
67
+ ## 技术实现
68
+ - 框架:Next.js/React + Tailwind CSS + Framer Motion
69
+ - 表单:React Hook Form + Zod验证
70
+ - 后端:Node.js/Serverless + MongoDB/PostgreSQL
71
+ - 邮件:SendGrid/Mailchimp API
72
+ - 分析:Google Analytics 4 + Hotjar热力图
73
+
74
+ ## A/B测试
75
+ - 标题:"未来视野2026" vs "预见2026:科技趋势发布会"
76
+ - CTA颜色:赛博粉 vs 电光紫
77
+ - 表单:4字段 vs 3字段(去掉职位)
78
+ - 社交证明位置:Hero区 vs 表单上方
@@ -0,0 +1,101 @@
1
+ # 构建专业级"个人薪资计算器"工具页,帮助职场人精准估算收入结构。
2
+
3
+ ## 核心定位
4
+ 薪资结构分析师——让用户看清每一分钱的去向。
5
+
6
+ ## 功能模块
7
+
8
+ ### 1. 智能输入区
9
+ **基础字段**:
10
+ - 月薪(元):数字输入,税前/税后切换,placeholder"例如:25000"
11
+ - 工作城市:下拉选择(一线+新一线+自定义),自动匹配社保公积金比例
12
+ - 年终奖月数:滑块0-6个月,快捷按钮(13/14/15薪)
13
+ - 缴纳基数:默认等于月薪,可展开自定义(城市自动限制上下限)
14
+
15
+ **高级设置(可折叠)**:
16
+ - 公积金比例:5%-12%(默认12%)
17
+ - 社保方案:按实际工资/最低基数/上限基数
18
+ - 专项扣除:子女教育、房贷利息、赡养老人等
19
+ - 补充公积金/企业年金:可选
20
+
21
+ **输入辅助**:
22
+ - 实时验证:非法字符红色提示,有效绿色对勾
23
+ - 智能记忆:localStorage保存,下次自动填充
24
+ - 城市联动:切换城市时社保比例平滑过渡
25
+
26
+ ### 2. 计算引擎
27
+ **核心公式**:
28
+ 1. 税前年薪 = 月薪×12 + 月薪×年终奖月数
29
+ 2. 平均时薪 = 税前年薪 ÷ (21.75天×8小时×12月)
30
+ 3. 五险一金 = 各险种基数×比例之和(养老8%+医疗2%+失业0.5%+公积金12%)
31
+ 4. 应纳税所得额 = 税前年收入 - 6万免征额 - 五险一金年缴 - 专项扣除年总额
32
+ 5. 个税:累进税率(3%-45%),年终奖单独/合并计税可选
33
+ 6. 税后月收入 = 月薪 - 五险一金个人部分 - 月度个税
34
+ 7. 税后年薪 = 税后月收入×12 + 年终奖税后金额
35
+
36
+ **精度**:分位四舍五入,保留2位小数。
37
+
38
+ ### 3. 可视化结果区
39
+ **核心指标(4宫格)**:
40
+ - 税前年薪:大字号绿色,标注"¥"
41
+ - 平均时薪:"¥/小时",附注"按176小时/月"
42
+ - 税后月收入:最大卡片,绿色渐变背景
43
+ - 税后年薪:汇总值,附注"实际到手"
44
+
45
+ **拆解图表**:
46
+ - 环形图:年薪构成(基本+年终奖占比)
47
+ - 堆叠柱状图:每月资金流向(到手、五险一金、个税)
48
+ - 瀑布图:税前到税后的层层扣除过程
49
+
50
+ **对比功能**:
51
+ - 城市对比:并排显示两地税后差异(如北京vs杭州)
52
+ - 方案对比:保存当前方案,调整参数后对比(如涨薪30%)
53
+
54
+ ### 4. 结果操作
55
+ - 一键复制:结构化文本(适合微信/备忘录)
56
+ - 生成图片:HTML2Canvas导出,带品牌水印
57
+ - 保存方案:本地存储,支持命名(如"当前工作"、"offer A")
58
+ - 分享链接:URL参数编码,打开自动填充
59
+
60
+ ## 设计风格
61
+
62
+ **色彩**:
63
+ - 主绿#10B981(信任、收益)、深绿#059669(强调)、浅绿#D1FAE5(高亮)
64
+ - 中性灰#F3F4F6(背景)、#6B7280(次要)、#1F2937(主文字)
65
+ - 警示橙#F59E0B(错误提示)
66
+ - 图表:绿#10B981(到手)、蓝#3B82F6(扣除)、灰#9CA3AF(个税)
67
+
68
+ **布局**:
69
+ - 桌面:左右分栏(输入40%+结果60%),sticky输入区
70
+ - 移动:上下堆叠,输入折叠为步骤条
71
+ - 留白充足,卡片圆角8px,阴影柔和
72
+
73
+ **字体**:
74
+ - 数字:SF Mono/Roboto Mono(等宽)
75
+ - 中文:Inter/思源黑体
76
+ - 金额:24-32px粗体绿色
77
+
78
+ ## 交互细节
79
+ - 实时计算:防抖300ms,结果区即时反馈
80
+ - 输入框:focus边框变绿,label上浮
81
+ - 图表:hover显示精确值,图例可点击隐藏
82
+ - 复制成功:toast提示,按钮变勾号2秒恢复
83
+ - 响应式:640/768/1024/1280px断点,移动端数字键盘优化
84
+
85
+ ## 数据可信度
86
+ - 社保比例:内置2026年各城市数据,可手动更新
87
+ - 个税税率:最新累进表,年终奖政策至2027年
88
+ - 版本号显示,过期提示"数据可能不是最新"
89
+ - 底部声明:"计算结果仅供参考,以实际劳动合同和当地政策为准"
90
+
91
+ ## 技术实现
92
+ - 框架:React/Vue + TypeScript
93
+ - 图表:Chart.js/ECharts
94
+ - 导出:html2canvas + Clipboard API
95
+ - 优化:计算缓存、图表懒加载、城市数据JSON按需加载
96
+
97
+ ## 扩展功能(V2.0)
98
+ - 反向计算:输入期望税后,反推税前薪资
99
+ - 跳槽模拟器:对比offer差异(含股票、公积金)
100
+ - 年度汇算:模拟个税汇算,预估退补税
101
+ - 家庭模式:夫妻合并计算,优化专项扣除
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@openyida/yidacli",
3
+ "version": "0.1.0",
4
+ "description": "宜搭 CLI 工具 - 宜搭平台应用、页面、表单的命令行管理工具",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "yidacli": "./bin/yidacli.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "dist",
12
+ "scripts",
13
+ "openyida"
14
+ ],
15
+ "scripts": {
16
+ "build": "node scripts/build.js",
17
+ "prepublishOnly": "node scripts/build.js && node scripts/prepublish.js",
18
+ "postpublish": "node scripts/prepublish.js --cleanup",
19
+ "postinstall": "playwright install chromium && node scripts/postinstall.js",
20
+ "release": "npm version patch && npm publish",
21
+ "test": "jest --config tests/jest.config.js",
22
+ "test:unit": "jest --config tests/jest.config.js tests/unit",
23
+ "test:watch": "jest --config tests/jest.config.js --watch",
24
+ "test:coverage": "jest --config tests/jest.config.js --coverage"
25
+ },
26
+ "keywords": [
27
+ "yida",
28
+ "aliwork",
29
+ "cli"
30
+ ],
31
+ "author": "",
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "@babel/standalone": "^7.15.1",
35
+ "playwright": "^1.40.0",
36
+ "uglify-js": "^3.19.3"
37
+ },
38
+ "devDependencies": {
39
+ "jest": "^29.7.0"
40
+ }
41
+ }
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * build.js - 构建脚本
4
+ *
5
+ * 将 src/ 目录下所有 JS 文件用 UglifyJS 压缩后输出到 dist/,
6
+ * 保持原有目录结构。
7
+ */
8
+
9
+ "use strict";
10
+
11
+ const fs = require("fs");
12
+ const path = require("path");
13
+ const UglifyJS = require("uglify-js");
14
+
15
+ const YIDA_CLI_DIR = path.resolve(__dirname, "..");
16
+ const SRC_DIR = path.resolve(YIDA_CLI_DIR, "src");
17
+ const DIST_DIR = path.resolve(YIDA_CLI_DIR, "dist");
18
+
19
+ /**
20
+ * 递归收集目录下所有 .js 文件
21
+ */
22
+ function collectJsFiles(dir, baseDir) {
23
+ const results = [];
24
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
25
+ const fullPath = path.join(dir, entry.name);
26
+ if (entry.isDirectory()) {
27
+ results.push(...collectJsFiles(fullPath, baseDir));
28
+ } else if (entry.name.endsWith(".js")) {
29
+ results.push(fullPath);
30
+ }
31
+ }
32
+ return results;
33
+ }
34
+
35
+ function main() {
36
+ console.log("=".repeat(50));
37
+ console.log(" build - 构建 yida-cli(uglify src/ → dist/)");
38
+ console.log("=".repeat(50));
39
+
40
+ // 清空并重建 dist 目录
41
+ if (fs.existsSync(DIST_DIR)) {
42
+ fs.rmSync(DIST_DIR, { recursive: true, force: true });
43
+ console.log("\n 🗑️ 已清空旧的 dist/ 目录");
44
+ }
45
+ fs.mkdirSync(DIST_DIR, { recursive: true });
46
+
47
+ const jsFiles = collectJsFiles(SRC_DIR, SRC_DIR);
48
+ console.log(`\n 📦 共发现 ${jsFiles.length} 个源文件,开始压缩...\n`);
49
+
50
+ let successCount = 0;
51
+ let errorCount = 0;
52
+
53
+ for (const srcFile of jsFiles) {
54
+ const relativePath = path.relative(SRC_DIR, srcFile);
55
+ const distFile = path.join(DIST_DIR, relativePath);
56
+
57
+ // 确保目标目录存在
58
+ fs.mkdirSync(path.dirname(distFile), { recursive: true });
59
+
60
+ const sourceCode = fs.readFileSync(srcFile, "utf-8");
61
+ const result = UglifyJS.minify(sourceCode, {
62
+ // 保留 require 调用,不做模块打包
63
+ compress: { passes: 1 },
64
+ mangle: true,
65
+ });
66
+
67
+ if (result.error) {
68
+ console.error(` ❌ 压缩失败: ${relativePath}`);
69
+ console.error(` ${result.error.message}`);
70
+ errorCount++;
71
+ // 压缩失败时原样复制,保证可用性
72
+ fs.copyFileSync(srcFile, distFile);
73
+ } else {
74
+ fs.writeFileSync(distFile, result.code, "utf-8");
75
+ console.log(` ✅ ${relativePath}`);
76
+ successCount++;
77
+ }
78
+ }
79
+
80
+ console.log("\n" + "=".repeat(50));
81
+ if (errorCount === 0) {
82
+ console.log(`✅ 构建完成,共压缩 ${successCount} 个文件`);
83
+ } else {
84
+ console.log(`⚠️ 构建完成,${successCount} 个成功,${errorCount} 个失败(已原样复制)`);
85
+ }
86
+ console.log("=".repeat(50));
87
+
88
+ if (errorCount > 0) {
89
+ process.exit(1);
90
+ }
91
+ }
92
+
93
+ main();
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * postinstall.js - npm 安装后钩子
4
+ *
5
+ * 检测当前运行的 AI 工具环境,将 npm 包内的 openyida/ 目录
6
+ * 合并复制到对应 AI 工具的 workspace/openyida/ 目录下。
7
+ *
8
+ * 复制策略:
9
+ * - 已存在的文件:强制覆盖(更新到最新版本)
10
+ * - 目标目录中已有但源目录没有的文件:保留(不删除用户数据)
11
+ *
12
+ * AI 工具检测逻辑复用 dist/env.js 的 detectEnvironment,
13
+ * 避免与 src/env.js 中的 TOOL_DETECTORS 产生重复维护的风险。
14
+ */
15
+
16
+ "use strict";
17
+
18
+ const fs = require("fs");
19
+ const path = require("path");
20
+ const os = require("os");
21
+
22
+ // npm 包内的 openyida 目录(由 prepublish 脚本拷贝进来)
23
+ const PACKAGE_OPENYIDA_DIR = path.resolve(__dirname, "..", "openyida");
24
+
25
+ /**
26
+ * 检测所有活跃的 AI 工具,返回其 workspace 路径列表。
27
+ * 复用 dist/env.js 的 detectEnvironment,避免重复维护 TOOL_DETECTORS。
28
+ */
29
+ function detectActiveWorkspaces() {
30
+ // dist/env.js 由 prepublishOnly 构建产生,随 npm 包一起发布
31
+ const { detectEnvironment } = require("../dist/env");
32
+ const { results } = detectEnvironment();
33
+
34
+ return results
35
+ .filter((entry) => entry.isActive)
36
+ .map(({ displayName, workspaceRoot }) => ({
37
+ displayName,
38
+ // workspaceRoot 是 ~/.xxx/workspace/openyida,取其父目录得到 workspace
39
+ workspaceDir: path.dirname(workspaceRoot),
40
+ }));
41
+ }
42
+
43
+ /**
44
+ * 合并复制目录:
45
+ * - 源目录中的文件 → 强制覆盖到目标目录
46
+ * - 目标目录中已有但源目录没有的文件 → 保留不动
47
+ */
48
+ function mergeCopyDir(sourceDir, destDir) {
49
+ if (!fs.existsSync(sourceDir)) return 0;
50
+
51
+ fs.mkdirSync(destDir, { recursive: true });
52
+
53
+ const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
54
+ let copiedCount = 0;
55
+
56
+ for (const entry of entries) {
57
+ const sourcePath = path.join(sourceDir, entry.name);
58
+ const destPath = path.join(destDir, entry.name);
59
+
60
+ if (entry.isDirectory()) {
61
+ copiedCount += mergeCopyDir(sourcePath, destPath);
62
+ } else {
63
+ fs.copyFileSync(sourcePath, destPath);
64
+ console.log(` 覆盖: ${path.relative(os.homedir(), destPath)}`);
65
+ copiedCount++;
66
+ }
67
+ }
68
+
69
+ return copiedCount;
70
+ }
71
+
72
+ function main() {
73
+ console.log("=".repeat(55));
74
+ console.log(" yidacli postinstall - 初始化 openyida 工作目录");
75
+ console.log("=".repeat(55));
76
+
77
+ // 检查 npm 包内是否有 openyida 目录
78
+ if (!fs.existsSync(PACKAGE_OPENYIDA_DIR)) {
79
+ console.log("\n⚠️ npm 包内未找到 openyida/ 目录,跳过初始化");
80
+ console.log(" (这是正常情况,如需初始化请手动运行 yidacli env)");
81
+ return;
82
+ }
83
+
84
+ // 检测活跃的 AI 工具
85
+ const activeWorkspaces = detectActiveWorkspaces();
86
+
87
+ if (activeWorkspaces.length === 0) {
88
+ console.log("\n⚠️ 未检测到活跃的 AI 工具环境,跳过初始化");
89
+ console.log(" 支持的工具:悟空、OpenCode、Claude Code、Aone Copilot、Cursor、Qoder、iFlow");
90
+ return;
91
+ }
92
+
93
+ console.log(`\n🤖 检测到 ${activeWorkspaces.length} 个活跃 AI 工具环境\n`);
94
+
95
+ let totalCopied = 0;
96
+
97
+ for (const { displayName, workspaceDir } of activeWorkspaces) {
98
+ const destOpenyidaDir = path.join(workspaceDir, "openyida");
99
+ console.log(` 📁 ${displayName}`);
100
+ console.log(` 目标: ${destOpenyidaDir}`);
101
+
102
+ const copiedCount = mergeCopyDir(PACKAGE_OPENYIDA_DIR, destOpenyidaDir);
103
+ console.log(` ✅ 完成,共复制/更新 ${copiedCount} 个文件\n`);
104
+ totalCopied += copiedCount;
105
+ }
106
+
107
+ console.log(`✅ 初始化完成,共处理 ${totalCopied} 个文件`);
108
+ console.log("=".repeat(55));
109
+ }
110
+
111
+ main();
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * prepublish.js - npm 发布前钩子
4
+ *
5
+ * 将项目根目录的 openyida/ 目录完整拷贝到 yida-cli/openyida/,
6
+ * 使其随 npm 包一起发布,供 postinstall 脚本使用。
7
+ */
8
+
9
+ "use strict";
10
+
11
+ const fs = require("fs");
12
+ const path = require("path");
13
+
14
+ const YIDA_CLI_DIR = path.resolve(__dirname, "..");
15
+ const SOURCE_DIR = path.resolve(YIDA_CLI_DIR, "..", "openyida");
16
+ const DEST_DIR = path.resolve(YIDA_CLI_DIR, "openyida");
17
+
18
+ /**
19
+ * 递归复制目录(强制覆盖已有文件)
20
+ */
21
+ function copyDirRecursive(sourceDir, destDir) {
22
+ if (!fs.existsSync(sourceDir)) {
23
+ throw new Error(`源目录不存在: ${sourceDir}`);
24
+ }
25
+
26
+ fs.mkdirSync(destDir, { recursive: true });
27
+
28
+ const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
29
+ let copiedCount = 0;
30
+
31
+ for (const entry of entries) {
32
+ const sourcePath = path.join(sourceDir, entry.name);
33
+ const destPath = path.join(destDir, entry.name);
34
+
35
+ if (entry.isDirectory()) {
36
+ copiedCount += copyDirRecursive(sourcePath, destPath);
37
+ } else {
38
+ fs.copyFileSync(sourcePath, destPath);
39
+ console.log(` 复制: ${path.relative(YIDA_CLI_DIR, destPath)}`);
40
+ copiedCount++;
41
+ }
42
+ }
43
+
44
+ return copiedCount;
45
+ }
46
+
47
+ /**
48
+ * 递归收集目录下所有文件的相对路径(相对于 baseDir)。
49
+ * @returns {string[]} 排序后的相对路径列表
50
+ */
51
+ function collectAllRelativePaths(baseDir, currentDir) {
52
+ const dir = currentDir || baseDir;
53
+ const results = [];
54
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
55
+
56
+ for (const entry of entries) {
57
+ const fullPath = path.join(dir, entry.name);
58
+ if (entry.isDirectory()) {
59
+ results.push(...collectAllRelativePaths(baseDir, fullPath));
60
+ } else {
61
+ results.push(path.relative(baseDir, fullPath));
62
+ }
63
+ }
64
+
65
+ return results.sort();
66
+ }
67
+
68
+ /**
69
+ * 校验拷贝结果:逐文件对比源目录和目标目录的内容是否完全一致。
70
+ * 有差异则打印详情并抛出错误。
71
+ */
72
+ function verifyDiff(sourceDir, destDir) {
73
+ const sourcePaths = collectAllRelativePaths(sourceDir);
74
+ const destPaths = collectAllRelativePaths(destDir);
75
+
76
+ const missingInDest = sourcePaths.filter((p) => !destPaths.includes(p));
77
+ const extraInDest = destPaths.filter((p) => !sourcePaths.includes(p));
78
+ const contentMismatches = [];
79
+
80
+ for (const relativePath of sourcePaths) {
81
+ if (missingInDest.includes(relativePath)) continue;
82
+ const sourceContent = fs.readFileSync(path.join(sourceDir, relativePath));
83
+ const destContent = fs.readFileSync(path.join(destDir, relativePath));
84
+ if (!sourceContent.equals(destContent)) {
85
+ contentMismatches.push(relativePath);
86
+ }
87
+ }
88
+
89
+ const hasErrors = missingInDest.length > 0 || extraInDest.length > 0 || contentMismatches.length > 0;
90
+
91
+ if (!hasErrors) {
92
+ console.log(`\n✅ diff 校验通过,共 ${sourcePaths.length} 个文件内容完全一致`);
93
+ return;
94
+ }
95
+
96
+ console.error("\n❌ diff 校验失败,拷贝结果与源目录不一致:");
97
+
98
+ if (missingInDest.length > 0) {
99
+ console.error(`\n 缺失文件(源有,目标无):`);
100
+ missingInDest.forEach((p) => console.error(` - ${p}`));
101
+ }
102
+ if (extraInDest.length > 0) {
103
+ console.error(`\n 多余文件(目标有,源无):`);
104
+ extraInDest.forEach((p) => console.error(` + ${p}`));
105
+ }
106
+ if (contentMismatches.length > 0) {
107
+ console.error(`\n 内容不一致的文件:`);
108
+ contentMismatches.forEach((p) => console.error(` ≠ ${p}`));
109
+ }
110
+
111
+ throw new Error("openyida 拷贝校验失败,请检查上方 diff 详情");
112
+ }
113
+
114
+ function cleanup() {
115
+ console.log("=".repeat(50));
116
+ console.log(" postpublish - 清理临时 openyida 目录");
117
+ console.log("=".repeat(50));
118
+ console.log(`\n 目标目录: ${DEST_DIR}\n`);
119
+
120
+ if (fs.existsSync(DEST_DIR)) {
121
+ fs.rmSync(DEST_DIR, { recursive: true, force: true });
122
+ console.log("✅ 已删除临时 openyida/ 目录");
123
+ } else {
124
+ console.log("⚠️ 目录不存在,无需清理");
125
+ }
126
+ console.log("=".repeat(50));
127
+ }
128
+
129
+ function main() {
130
+ const isCleanup = process.argv.includes("--cleanup");
131
+
132
+ if (isCleanup) {
133
+ cleanup();
134
+ return;
135
+ }
136
+
137
+ console.log("=".repeat(50));
138
+ console.log(" prepublish - 拷贝 openyida 到 yida-cli");
139
+ console.log("=".repeat(50));
140
+ console.log(`\n 源目录: ${SOURCE_DIR}`);
141
+ console.log(` 目标目录: ${DEST_DIR}\n`);
142
+
143
+ if (!fs.existsSync(SOURCE_DIR)) {
144
+ console.error(`❌ 源目录不存在: ${SOURCE_DIR}`);
145
+ console.error(" 请确保在项目根目录下存在 openyida/ 目录");
146
+ process.exit(1);
147
+ }
148
+
149
+ // 清空目标目录(确保不残留旧文件)
150
+ if (fs.existsSync(DEST_DIR)) {
151
+ fs.rmSync(DEST_DIR, { recursive: true, force: true });
152
+ console.log(" 🗑️ 已清空旧的 openyida/ 目录");
153
+ }
154
+
155
+ const copiedCount = copyDirRecursive(SOURCE_DIR, DEST_DIR);
156
+
157
+ console.log(`\n 共复制 ${copiedCount} 个文件,开始 diff 校验...`);
158
+ verifyDiff(SOURCE_DIR, DEST_DIR);
159
+ console.log("=".repeat(50));
160
+ }
161
+
162
+ main();