openyida 0.1.2 → 1.0.0-beta.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.
Files changed (61) hide show
  1. package/README.md +68 -38
  2. package/bin/yida.js +164 -761
  3. package/lib/babel-transform/index.js +244 -0
  4. package/lib/babel-transform/jsx-utils.js +89 -0
  5. package/lib/check-update.js +72 -0
  6. package/lib/copy.js +258 -0
  7. package/lib/create-app.js +174 -0
  8. package/lib/create-form.js +2244 -0
  9. package/lib/create-page.js +89 -0
  10. package/lib/env.js +164 -0
  11. package/lib/get-page-config.js +102 -0
  12. package/lib/get-schema.js +76 -0
  13. package/lib/login.js +323 -0
  14. package/lib/publish.js +610 -0
  15. package/lib/save-share-config.js +268 -0
  16. package/lib/update-form-config.js +237 -0
  17. package/lib/utils.js +443 -0
  18. package/lib/verify-short-url.js +279 -0
  19. package/package.json +20 -7
  20. package/project/.cache/demo-schema.json +2353 -0
  21. package/project/pages/src/demo-birthday-game.js +833 -0
  22. package/project/pages/src/demo-future-vision-2026.js +1102 -0
  23. package/project/pages/src/demo-salary-calculator.js +904 -0
  24. package/project/prd/demo-birthday-game.md +39 -0
  25. package/project/prd/demo-future-vision-2026.md +78 -0
  26. package/project/prd/demo-salary-calculator.md +101 -0
  27. package/scripts/postinstall.js +114 -0
  28. package/yida-skills/SKILL.md +273 -0
  29. package/yida-skills/reference/association-form-field.md +469 -0
  30. package/yida-skills/reference/employee-field.md +17 -0
  31. package/yida-skills/reference/model-api.md +73 -0
  32. package/yida-skills/reference/serial-number-field.md +132 -0
  33. package/yida-skills/reference/yida-api.md +1208 -0
  34. package/yida-skills/skills/yida-app/SKILL.md +394 -0
  35. package/yida-skills/skills/yida-create-app/SKILL.md +158 -0
  36. package/yida-skills/skills/yida-create-form-page/SKILL.md +598 -0
  37. package/yida-skills/skills/yida-create-page/SKILL.md +103 -0
  38. package/yida-skills/skills/yida-custom-page/SKILL.md +533 -0
  39. package/yida-skills/skills/yida-get-schema/SKILL.md +90 -0
  40. package/yida-skills/skills/yida-login/SKILL.md +200 -0
  41. package/yida-skills/skills/yida-logout/SKILL.md +58 -0
  42. package/yida-skills/skills/yida-page-config/SKILL.md +261 -0
  43. package/yida-skills/skills/yida-publish-page/SKILL.md +113 -0
  44. package/.eslintrc.json +0 -25
  45. package/.github/workflows/ci.yml +0 -123
  46. package/.github/workflows/publish.yml +0 -105
  47. package/.github/workflows/update-contributors.yml +0 -151
  48. package/.openclaw/skills/yida-issue/SKILL.md +0 -27
  49. package/.openclaw/skills/yida-issue/scripts/create-issue.js +0 -317
  50. package/CLAUDE.md +0 -168
  51. package/CONTRIBUTING.md +0 -59
  52. package/install-skills.ps1 +0 -162
  53. package/install-skills.sh +0 -175
  54. package/pages/dist/.gitkeep +0 -0
  55. package/pages/src/.gitkeep +0 -0
  56. package/prd/salary-calculator.md +0 -15
  57. package/tests/cli.test.js +0 -930
  58. package/tests/install.test.js +0 -277
  59. package/tests/yida-issue.test.js +0 -314
  60. /package/{config.json → project/config.json} +0 -0
  61. /package/{.cache → project/pages/dist}/.gitkeep +0 -0
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "JSXUtil", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _jsxUtils.default;
10
+ }
11
+ });
12
+ exports.default = compile;
13
+ exports.getCode = getCode;
14
+ var _jsxUtils = _interopRequireDefault(require("./jsx-utils"));
15
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
16
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
17
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
18
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
19
+ function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
20
+ function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
21
+ function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
22
+ // should not use import Babel from 'babel-standalone';
23
+ var Babel = require('@babel/standalone');
24
+ var RE_VERSION = '4.0.0';
25
+ // 区分浏览器还是node环境
26
+ if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object') {
27
+ var VisualEngine = window.VisualEngine;
28
+ RE_VERSION = window.pageConfig && window.pageConfig.RE_VERSION || VisualEngine && VisualEngine.Env && VisualEngine.Env.get('RE_VERSION') || window.RenderEngine && window.RenderEngine.version || '4.0.0';
29
+ }
30
+ function isString(string) {
31
+ return {}.toString.call(string) === '[object String]';
32
+ }
33
+
34
+ // 查找组件
35
+ function findComps(ast) {
36
+ var id = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'normal';
37
+ var _ref = (typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object' ? window : {},
38
+ MyBabel = _ref.MyBabel;
39
+ if (!ast || !MyBabel) {
40
+ return;
41
+ }
42
+ var comps = [];
43
+ MyBabel.traverse(ast, {
44
+ CallExpression: function CallExpression(path) {
45
+ if (!path.node.callee || path.node.callee.type !== 'MemberExpression' || !path.node.callee.property || !['createElement', 'getComponentView'].includes(path.node.callee.property.name)) {
46
+ return;
47
+ }
48
+ var node = Array.isArray(path.node.arguments) ? path.node.arguments[0] : {};
49
+ // 形如 Deep.Button,分析出Deep.Button
50
+ var componentName = '';
51
+ while (true) {
52
+ if (node && node.type === 'MemberExpression' && node.object) {
53
+ var _node, _node$property;
54
+ componentName = "".concat((_node = node) === null || _node === void 0 ? void 0 : (_node$property = _node.property) === null || _node$property === void 0 ? void 0 : _node$property.name).concat(componentName == '' ? '' : '.') + componentName;
55
+ node = node.object;
56
+ } else if (node && node.type === 'Identifier') {
57
+ componentName = "".concat(node.name).concat(componentName == '' ? '' : '.') + componentName;
58
+ break;
59
+ } else if (node && node.type === 'StringLiteral') {
60
+ componentName += "".concat(node.value) + componentName;
61
+ break;
62
+ } else {
63
+ break;
64
+ }
65
+ }
66
+ comps.push(componentName);
67
+ }
68
+ });
69
+ _jsxUtils.default.setComps(id, comps);
70
+ }
71
+ var EMPTY_FUNC_STRING = 'function emptyCall() {}';
72
+ function getCode(obj) {
73
+ var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
74
+ compiled: false,
75
+ allowEmpty: false
76
+ };
77
+ if (typeof obj === 'string') {
78
+ return obj;
79
+ }
80
+ if (_typeof(obj) === 'object' && obj !== null) {
81
+ var result = opts.compiled ? 'value' in obj ? obj.value : obj.compiled : obj.source;
82
+ return opts.allowEmpty ? result : result || EMPTY_FUNC_STRING;
83
+ }
84
+ if (opts.allowEmpty) {
85
+ return obj;
86
+ }
87
+ return EMPTY_FUNC_STRING;
88
+ }
89
+ function compile() {
90
+ var source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
91
+ var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
92
+ var transformFunction = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
93
+ var env = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
94
+ var compileOptions = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : {};
95
+ var compiled = '';
96
+ var error = {};
97
+ env = _objectSpread({
98
+ RE_VERSION: RE_VERSION
99
+ }, env);
100
+ var isOldRenderEngine = parseFloat(env.RE_VERSION, 10) < 4.2;
101
+ if (isString(source) && source !== '') {
102
+ if (isOldRenderEngine && !env.shouldCompile) {
103
+ return source;
104
+ }
105
+ var decoSource = source;
106
+ if (transformFunction) {
107
+ decoSource = "var __compiledFunc__ = ".concat(source, ";");
108
+ }
109
+ try {
110
+ var presets = parseInt(Babel.version, 10) > 6 ? ['react', ['env', {
111
+ "loose": true,
112
+ "spec": true,
113
+ targets: {
114
+ browsers: ['ie >= 11', 'chrome >= 62']
115
+ }
116
+ }]] : ['react', 'es2015-loose', 'stage-0'];
117
+ if (parseFloat(env.RE_VERSION, 10) < 7.0) {
118
+ var result = Babel.transform(decoSource, _objectSpread({
119
+ presets: presets,
120
+ ast: true
121
+ }, options));
122
+ compiled = result.code;
123
+ findComps(result.ast, env.id);
124
+ if (transformFunction) {
125
+ compiled = "function main(){\n ".concat(compiled, "\n return __compiledFunc__.apply(this, arguments);\n }");
126
+ } else {
127
+ compiled = "".concat(compiled, "\n");
128
+ }
129
+ } else {
130
+ var addonBindings = [];
131
+ function addBindingToScope(scope, bind) {
132
+ if (!scope.addonBindings) {
133
+ scope.addonBindings = [];
134
+ }
135
+ scope.addonBindings.push(bind);
136
+ }
137
+ function hasAddonBinding(scope, bind) {
138
+ return scope.addonBindings && scope.addonBindings.indexOf(bind) > -1;
139
+ }
140
+ var plugins = [function (_ref2) {
141
+ var types = _ref2.types,
142
+ template = _ref2.template;
143
+ function getJSXTagName(ast) {
144
+ if (ast.type === 'JSXMemberExpression') {
145
+ return getJSXTagName(ast.object);
146
+ }
147
+ if (ast.type === 'JSXIdentifier') {
148
+ return ast.name;
149
+ }
150
+ return null;
151
+ }
152
+ function isCustomTag(name) {
153
+ return /^[A-Z]/.test(name);
154
+ }
155
+ function getTopStatement(path) {
156
+ // todo:
157
+ var st = path.getStatementParent();
158
+ }
159
+ var DC = template('const TAGNAME = this._getComponentView(TAGNAME_S)');
160
+ return {
161
+ visitor: {
162
+ JSXElement: function JSXElement(path) {
163
+ var node = path.node,
164
+ scope = path.scope;
165
+ // todo: get top function declaration for nesting function statement
166
+ var tagName = getJSXTagName(node.openingElement.name);
167
+ if (!tagName || !isCustomTag(tagName) || scope.hasBinding(tagName)) {
168
+ return;
169
+ }
170
+ if (transformFunction) {
171
+ if (addonBindings.indexOf(tagName) > -1) {
172
+ return;
173
+ }
174
+ addonBindings.push(tagName);
175
+ } else {
176
+ if (hasAddonBinding(scope, tagName)) {
177
+ return;
178
+ }
179
+ var st = path.getStatementParent();
180
+ // simple arrow function, body is not statement
181
+ if (st.parentPath.isProgram()) {
182
+ // todo: not go here now
183
+ /*
184
+ path.replaceWith(ast);
185
+ const body = path.scope.path.get('body');
186
+ body.replaceWith(types.blockStatement([
187
+ ...declares,
188
+ types.returnStatement(body.node)
189
+ ]));
190
+ */
191
+ } else {
192
+ // st mybe return statement
193
+ st.insertBefore(DC({
194
+ TAGNAME: types.identifier(tagName),
195
+ TAGNAME_S: types.stringLiteral(tagName)
196
+ }));
197
+ }
198
+ addBindingToScope(scope, tagName);
199
+ }
200
+ }
201
+ }
202
+ };
203
+ }];
204
+ if (options.plugins) {
205
+ plugins = plugins.concat(options.plugins);
206
+ }
207
+ var _result = Babel.transform(decoSource, _objectSpread(_objectSpread({
208
+ presets: presets,
209
+ ast: true
210
+ }, options), {}, {
211
+ plugins: plugins
212
+ }));
213
+ compiled = _result.code;
214
+ findComps(_result.ast, env.id);
215
+ if (transformFunction) {
216
+ var addonCodes = addonBindings.map(function (name) {
217
+ return "const ".concat(name, " = this._getComponentView('").concat(name, "');");
218
+ }).join('\n');
219
+ compiled = "function main(){\n ".concat(addonCodes, "\n ").concat(compiled, "\n return __compiledFunc__.apply(this, arguments);\n }");
220
+ } else {
221
+ compiled = "".concat(compiled, "\n");
222
+ }
223
+ }
224
+ } catch (e) {
225
+ console.error(e);
226
+ error = e;
227
+ }
228
+ }
229
+ if (compileOptions !== null && compileOptions !== void 0 && compileOptions.preferJSExpression) {
230
+ return {
231
+ type: 'JSExpression',
232
+ source: source,
233
+ value: compiled,
234
+ extType: 'function',
235
+ error: error
236
+ };
237
+ }
238
+ return {
239
+ type: 'js',
240
+ source: source,
241
+ compiled: compiled,
242
+ error: error
243
+ };
244
+ }
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
8
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
9
+ function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
10
+ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
11
+ function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
12
+ function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
13
+ function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
14
+ var JSXUtil = /*#__PURE__*/function () {
15
+ function JSXUtil() {
16
+ _classCallCheck(this, JSXUtil);
17
+ _defineProperty(this, "tags", {});
18
+ }
19
+ _createClass(JSXUtil, [{
20
+ key: "setComps",
21
+ value: function setComps(id, tags) {
22
+ if (!Array.isArray(tags)) {
23
+ return;
24
+ }
25
+
26
+ // 去重
27
+ var s = new Set(tags);
28
+ tags = Array.from(s);
29
+ if (!id) {
30
+ id = 'normal';
31
+ }
32
+
33
+ // 初始化
34
+ if (!Array.isArray(this.tags[id])) {
35
+ this.tags[id] = [];
36
+ }
37
+
38
+ // 收集未传id的情况下的组件,保证组件只多不少
39
+ if (id === 'normal') {
40
+ // 去重
41
+ var arr = this.tags[id];
42
+ tags.forEach(function (i) {
43
+ return !arr.includes(i) && arr.push(i);
44
+ });
45
+ } else {
46
+ // 具体属性更改后重新覆盖
47
+ this.tags[id] = tags;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * 返回自定义渲染的组件列表
53
+ */
54
+ }, {
55
+ key: "getComponentList",
56
+ value: function getComponentList() {
57
+ var _this = this;
58
+ var s = new Set();
59
+ Object.keys(this.tags).forEach(function (id) {
60
+ var comps = _this.tags[id] || [];
61
+ if (Array.isArray(comps)) {
62
+ comps.forEach(function (c) {
63
+ return s.add(c);
64
+ });
65
+ }
66
+ });
67
+ return Array.from(s);
68
+ }
69
+ }, {
70
+ key: "reset",
71
+ value: function reset() {
72
+ this.tags = {};
73
+ }
74
+ }]);
75
+ return JSXUtil;
76
+ }();
77
+ var __vu_jsx_util__;
78
+ if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object') {
79
+ var _window;
80
+ window.__vu_jsx_util__ = ((_window = window) === null || _window === void 0 ? void 0 : _window.__vu_jsx_util__) || new JSXUtil();
81
+ __vu_jsx_util__ = window.__vu_jsx_util__;
82
+ } else {
83
+ var _global;
84
+ global.__vu_jsx_util__ = ((_global = global) === null || _global === void 0 ? void 0 : _global.__vu_jsx_util__) || new JSXUtil();
85
+ __vu_jsx_util__ = global.__vu_jsx_util__;
86
+ }
87
+ var _default = __vu_jsx_util__;
88
+ exports.default = _default;
89
+ module.exports = exports.default;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * check-update.js - openyida 版本更新检查
3
+ *
4
+ * 向 npm registry 查询最新版本,有新版本时打印提示。
5
+ * 全程异步,不阻塞主命令流程。
6
+ */
7
+
8
+ "use strict";
9
+
10
+ const https = require("https");
11
+
12
+ const REGISTRY_URL = "https://registry.npmjs.org/openyida/latest";
13
+
14
+ /**
15
+ * 从 npm registry 获取最新版本号。
16
+ * @returns {Promise<string|null>}
17
+ */
18
+ function fetchLatestVersion() {
19
+ return new Promise((resolve) => {
20
+ const req = https.get(REGISTRY_URL, { timeout: 5000 }, (res) => {
21
+ let data = "";
22
+ res.on("data", (chunk) => { data += chunk; });
23
+ res.on("end", () => {
24
+ try {
25
+ const parsed = JSON.parse(data);
26
+ resolve(parsed.version || null);
27
+ } catch {
28
+ resolve(null);
29
+ }
30
+ });
31
+ });
32
+ req.on("error", () => resolve(null));
33
+ req.on("timeout", () => { req.destroy(); resolve(null); });
34
+ });
35
+ }
36
+
37
+ /**
38
+ * 比较版本号,返回 latestVersion 是否比 currentVersion 更新。
39
+ * 仅支持 semver 格式(major.minor.patch)。
40
+ */
41
+ function isNewer(currentVersion, latestVersion) {
42
+ const parseParts = (v) => (v || "").split(".").map((n) => parseInt(n, 10) || 0);
43
+ const [cMajor, cMinor, cPatch] = parseParts(currentVersion);
44
+ const [lMajor, lMinor, lPatch] = parseParts(latestVersion);
45
+
46
+ if (lMajor !== cMajor) return lMajor > cMajor;
47
+ if (lMinor !== cMinor) return lMinor > cMinor;
48
+ return lPatch > cPatch;
49
+ }
50
+
51
+ /**
52
+ * 检查是否有新版本,有则打印提示。
53
+ * @param {string} currentVersion - 当前版本号(来自 package.json)
54
+ */
55
+ async function checkUpdate(currentVersion) {
56
+ try {
57
+ const latestVersion = await fetchLatestVersion();
58
+
59
+ if (latestVersion && isNewer(currentVersion, latestVersion)) {
60
+ process.nextTick(() => {
61
+ console.error(
62
+ `\n💡 发现新版本 ${latestVersion}(当前 ${currentVersion})` +
63
+ `\n 运行以下命令更新:\n npm install -g openyida@latest\n`
64
+ );
65
+ });
66
+ }
67
+ } catch {
68
+ // 版本检查失败静默忽略,不影响主流程
69
+ }
70
+ }
71
+
72
+ module.exports = { checkUpdate, isNewer, fetchLatestVersion };
package/lib/copy.js ADDED
@@ -0,0 +1,258 @@
1
+ /**
2
+ * copy.js - 复制 project 工作目录模板 / 创建 yida-skills 软链接到当前 AI 工具环境
3
+ *
4
+ * 用法:
5
+ * openyida copy → 复制 project/ 目录模板(默认,合并模式)
6
+ * openyida copy --force → 复制 project/ 目录模板(强制覆盖,先清空目标目录)
7
+ * openyida copy -skills → 创建 yida-skills/ 软链接(如果存在实际目录则先删除)
8
+ * openyida copy -project → 复制 project/ 目录模板(与默认行为相同,显式指定)
9
+ * openyida copy -project --force → 复制 project/ 目录模板(强制覆盖)
10
+ *
11
+ * 目标策略:
12
+ * - 悟空(Wukong):复制/链接到 ~/.real/workspace/(专属 workspace,路径固定)
13
+ * - 其他 AI 工具:复制/链接到当前工程目录(process.cwd())下
14
+ *
15
+ * 源路径:npm 全局安装包根目录(通过 require.resolve 定位)
16
+ *
17
+ * project/ 合并模式(默认):已存在的文件强制覆盖,目标目录中多余的文件保留不动
18
+ * project/ 强制模式(--force):先清空目标目录,再完整复制
19
+ * yida-skills/:始终创建软链接,如目标存在实际目录则先删除
20
+ */
21
+
22
+ "use strict";
23
+
24
+ const fs = require("fs");
25
+ const path = require("path");
26
+ const os = require("os");
27
+ const { detectEnvironment } = require("./env");
28
+
29
+ /**
30
+ * 查找 npm 全局安装包根目录。
31
+ * 优先通过 require.resolve 定位(适用于正式全局安装),
32
+ * 失败时 fallback 到 __dirname 向上查找(适用于 npm link 本地开发)。
33
+ * @returns {string|null} 包根目录的绝对路径,找不到则返回 null
34
+ */
35
+ function findPackageRoot() {
36
+ try {
37
+ const packageJsonPath = require.resolve("openyida/package.json");
38
+ return path.dirname(packageJsonPath);
39
+ } catch {
40
+ // fallback:从当前文件向上查找包含 package.json 的目录
41
+ let dir = path.resolve(__dirname);
42
+ while (dir !== path.dirname(dir)) {
43
+ if (fs.existsSync(path.join(dir, "package.json"))) {
44
+ return dir;
45
+ }
46
+ dir = path.dirname(dir);
47
+ }
48
+ return null;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * 合并复制目录:源文件强制覆盖,目标目录多余文件保留。
54
+ * @returns {number} 复制的文件数量
55
+ */
56
+ function mergeCopyDir(sourceDir, destDir) {
57
+ if (!fs.existsSync(sourceDir)) return 0;
58
+
59
+ fs.mkdirSync(destDir, { recursive: true });
60
+
61
+ const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
62
+ let copiedCount = 0;
63
+
64
+ for (const entry of entries) {
65
+ const sourcePath = path.join(sourceDir, entry.name);
66
+ const destPath = path.join(destDir, entry.name);
67
+
68
+ if (entry.isDirectory()) {
69
+ copiedCount += mergeCopyDir(sourcePath, destPath);
70
+ } else {
71
+ fs.copyFileSync(sourcePath, destPath);
72
+ console.log(` 复制: ${destPath}`);
73
+ copiedCount++;
74
+ }
75
+ }
76
+
77
+ return copiedCount;
78
+ }
79
+
80
+ /**
81
+ * 强制复制目录:先清空目标目录,再完整复制。
82
+ * @returns {number} 复制的文件数量
83
+ */
84
+ function forceCopyDir(sourceDir, destDir) {
85
+ if (!fs.existsSync(sourceDir)) return 0;
86
+
87
+ if (fs.existsSync(destDir)) {
88
+ fs.rmSync(destDir, { recursive: true, force: true });
89
+ console.log(` 🗑️ 已清空: ${destDir}`);
90
+ }
91
+
92
+ return mergeCopyDir(sourceDir, destDir);
93
+ }
94
+
95
+ /**
96
+ * 创建软链接:如果目标存在实际目录则先删除,再创建软链接。
97
+ * @returns {boolean} 是否成功创建
98
+ */
99
+ function createSymlink(sourceDir, destLink) {
100
+ if (!fs.existsSync(sourceDir)) return false;
101
+
102
+ // 如果目标已存在,判断是目录还是软链接
103
+ if (fs.existsSync(destLink)) {
104
+ const stats = fs.lstatSync(destLink);
105
+ if (stats.isSymbolicLink()) {
106
+ // 已是软链接,删除后重新创建(确保指向正确)
107
+ fs.unlinkSync(destLink);
108
+ console.log(` 🗑️ 已移除旧软链接: ${destLink}`);
109
+ } else if (stats.isDirectory()) {
110
+ // 是实际目录,删除后创建软链接
111
+ fs.rmSync(destLink, { recursive: true, force: true });
112
+ console.log(` 🗑️ 已删除实际目录: ${destLink}`);
113
+ } else {
114
+ // 其他类型(文件等),直接删除
115
+ fs.unlinkSync(destLink);
116
+ console.log(` 🗑️ 已移除: ${destLink}`);
117
+ }
118
+ }
119
+
120
+ // 创建软链接(使用相对路径或绝对路径,这里用绝对路径确保稳定)
121
+ fs.symlinkSync(sourceDir, destLink, "junction");
122
+ console.log(` 🔗 软链接: ${destLink} -> ${sourceDir}`);
123
+ return true;
124
+ }
125
+
126
+ /**
127
+ * 检测 AI 工具环境,返回目标根目录。
128
+ * @returns {string} 目标根目录路径
129
+ */
130
+ function resolveDestBase() {
131
+ const { activeToolName, activeProjectRoot, results } = detectEnvironment();
132
+ const activeResult = results.find((r) => r.displayName === activeToolName);
133
+ const isWukong = activeResult && activeResult.dirName === ".real";
134
+
135
+ if (isWukong) {
136
+ return activeProjectRoot
137
+ ? path.dirname(activeProjectRoot)
138
+ : path.join(os.homedir(), ".real", "workspace");
139
+ }
140
+
141
+ if (activeToolName) {
142
+ return process.cwd();
143
+ }
144
+
145
+ // 未检测到活跃工具
146
+ console.error("\n❌ 未检测到活跃的 AI 工具环境");
147
+ console.error(" 支持的工具:悟空、OpenCode、Claude Code、Aone Copilot、Cursor、Qoder、iFlow");
148
+ console.error("\n 当前检测结果:");
149
+ results.forEach((r) => {
150
+ console.error(` ${r.isActive ? "✅" : "⬜"} ${r.displayName}`);
151
+ });
152
+ console.error("\n 如需强制复制到当前目录,请运行:");
153
+ console.error(" openyida copy --force");
154
+ process.exit(1);
155
+ }
156
+
157
+ /**
158
+ * 执行单项复制任务,打印结果。
159
+ */
160
+ function copyItem(label, sourceDir, destDir, isForce) {
161
+ console.log(`\n📂 复制 ${label}...`);
162
+ const count = isForce
163
+ ? forceCopyDir(sourceDir, destDir)
164
+ : mergeCopyDir(sourceDir, destDir);
165
+ return count;
166
+ }
167
+
168
+ /**
169
+ * 执行 copy 命令主逻辑。
170
+ */
171
+ function run() {
172
+ const SEP = "=".repeat(55);
173
+ console.log(SEP);
174
+ console.log(" openyida copy - 初始化宜搭工作目录");
175
+ console.log(SEP);
176
+
177
+ const args = process.argv.slice(3);
178
+ const isForce = args.includes("--force");
179
+ const wantsSkills = args.includes("-skills");
180
+ const wantsProject = args.includes("-project");
181
+
182
+ // 1. 查找 npm 包根目录
183
+ const packageRoot = findPackageRoot();
184
+ if (!packageRoot) {
185
+ console.error("\n❌ 未找到 openyida 安装包目录");
186
+ console.error(" 请确认 openyida 已正确全局安装:");
187
+ console.error(" npm install -g openyida");
188
+ process.exit(1);
189
+ }
190
+
191
+ const packageProjectDir = path.join(packageRoot, "project");
192
+ const packageYidaSkillsDir = path.join(packageRoot, "yida-skills");
193
+
194
+ console.log(`\n📦 包根目录: ${packageRoot}`);
195
+
196
+ // 2. 确定目标根目录(检测 AI 工具环境)
197
+ const destBase = resolveDestBase();
198
+ console.log(`🤖 目标根目录: ${destBase}`);
199
+ if (isForce) {
200
+ console.log("⚠️ --force 模式:目标目录将被清空后重新复制");
201
+ }
202
+
203
+ // 3. 确定要复制/链接的内容
204
+ // - 指定了 -skills:只创建 yida-skills/ 软链接
205
+ // - 指定了 -project:只复制 project/
206
+ // - 两者都没指定(默认):只复制 project/
207
+ // - 两者都指定:同时处理两项
208
+ const shouldCopyProject = wantsProject || (!wantsSkills);
209
+ const shouldLinkSkills = wantsSkills;
210
+
211
+ const results = [];
212
+
213
+ if (shouldCopyProject) {
214
+ const count = copyItem(
215
+ "project/ 工作目录模板",
216
+ packageProjectDir,
217
+ path.join(destBase, "project"),
218
+ isForce
219
+ );
220
+ results.push({ label: "project/", dest: path.join(destBase, "project"), count, type: "copy" });
221
+ }
222
+
223
+ if (shouldLinkSkills) {
224
+ console.log(`\n📂 创建 yida-skills/ 软链接...`);
225
+ const success = createSymlink(
226
+ packageYidaSkillsDir,
227
+ path.join(destBase, "yida-skills")
228
+ );
229
+ results.push({
230
+ label: "yida-skills/",
231
+ dest: path.join(destBase, "yida-skills"),
232
+ count: success ? 1 : 0,
233
+ type: "symlink"
234
+ });
235
+ }
236
+
237
+ // 4. 打印汇总
238
+ const copyCount = results.filter(r => r.type === "copy").reduce((sum, r) => sum + r.count, 0);
239
+ const linkCount = results.filter(r => r.type === "symlink").length;
240
+ console.log(`\n${SEP}`);
241
+ console.log(`✅ 完成!`);
242
+ if (copyCount > 0) {
243
+ console.log(` 复制文件: ${copyCount} 个`);
244
+ }
245
+ if (linkCount > 0) {
246
+ console.log(` 创建软链接: ${linkCount} 个`);
247
+ }
248
+ results.forEach((r) => {
249
+ if (r.type === "symlink") {
250
+ console.log(` ${r.label.padEnd(14)} → ${r.dest} (软链接)`);
251
+ } else {
252
+ console.log(` ${r.label.padEnd(14)} → ${r.dest} (${r.count} 个文件)`);
253
+ }
254
+ });
255
+ console.log(SEP);
256
+ }
257
+
258
+ module.exports = { run };