openxiangda 1.0.22 → 1.0.24

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 (89) hide show
  1. package/README.md +28 -10
  2. package/lib/cli.js +271 -11
  3. package/lib/workspace-init.js +13 -0
  4. package/openxiangda-skills/SKILL.md +25 -10
  5. package/openxiangda-skills/references/architecture-patterns.md +44 -22
  6. package/openxiangda-skills/references/best-practices.md +163 -0
  7. package/openxiangda-skills/references/pages/workspace-structure.md +5 -3
  8. package/openxiangda-skills/references/workspace-state.md +6 -0
  9. package/openxiangda-skills/skills/openxiangda-app/SKILL.md +11 -7
  10. package/openxiangda-skills/skills/openxiangda-core/SKILL.md +22 -4
  11. package/openxiangda-skills/skills/openxiangda-form/SKILL.md +6 -1
  12. package/openxiangda-skills/skills/openxiangda-page/SKILL.md +7 -1
  13. package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +3 -0
  14. package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +7 -0
  15. package/package.json +1 -1
  16. package/templates/sy-lowcode-app-workspace/examples/best-practices/README.md +32 -0
  17. package/templates/sy-lowcode-app-workspace/examples/best-practices/catalog.json +61 -0
  18. package/templates/sy-lowcode-app-workspace/examples/best-practices/decision-guide.md +44 -0
  19. package/templates/sy-lowcode-app-workspace/examples/best-practices/design-style.md +30 -0
  20. package/templates/sy-lowcode-app-workspace/examples/best-practices/module-structure.md +48 -0
  21. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/index.ts +2 -0
  22. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/permissions.test.ts +35 -0
  23. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/permissions.ts +24 -0
  24. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/types.ts +17 -0
  25. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/index.ts +4 -0
  26. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/permissions.test.ts +42 -0
  27. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/permissions.ts +23 -0
  28. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/state-machine.test.ts +63 -0
  29. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/state-machine.ts +73 -0
  30. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/ticket-query.test.ts +34 -0
  31. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/ticket-query.ts +73 -0
  32. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/types.ts +64 -0
  33. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/app-role/page.tsx +1 -0
  34. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/app-role/schema.ts +57 -0
  35. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/customer-profile/page.tsx +1 -0
  36. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/customer-profile/schema.ts +83 -0
  37. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/service-ticket/page.tsx +1 -0
  38. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/service-ticket/schema.ts +97 -0
  39. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/ticket-action-log/page.tsx +1 -0
  40. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/ticket-action-log/schema.ts +65 -0
  41. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/js-code-nodes/daily_ticket_digest/index.ts +44 -0
  42. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/js-code-nodes/sync_roles_to_platform/index.ts +33 -0
  43. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/App.tsx +7 -0
  44. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/WorkbenchPage.tsx +36 -0
  45. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/components/ConfigPanel.tsx +34 -0
  46. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/components/PreviewPanel.tsx +17 -0
  47. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/index.tsx +10 -0
  48. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/page.config.ts +9 -0
  49. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/reducer.ts +29 -0
  50. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/styles.css +24 -0
  51. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/App.tsx +7 -0
  52. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/MobilePortalShell.tsx +31 -0
  53. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/index.tsx +10 -0
  54. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/modules/MobileHome.tsx +13 -0
  55. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/page.config.ts +14 -0
  56. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/routes.ts +13 -0
  57. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/styles.css +11 -0
  58. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/App.tsx +7 -0
  59. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/PcPortalShell.tsx +35 -0
  60. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/components/PortalMetric.tsx +11 -0
  61. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/index.tsx +10 -0
  62. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/modules/HomeModule.tsx +25 -0
  63. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/modules/TicketsModule.tsx +14 -0
  64. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/page.config.ts +14 -0
  65. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/routes.ts +19 -0
  66. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/styles.css +35 -0
  67. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/App.tsx +7 -0
  68. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/TicketOpsPage.tsx +105 -0
  69. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/components/TicketActionTimeline.tsx +22 -0
  70. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/components/TicketDetailDrawer.tsx +41 -0
  71. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/components/TicketTableActions.tsx +55 -0
  72. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/index.tsx +10 -0
  73. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/page.config.ts +9 -0
  74. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/styles.css +35 -0
  75. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/automations/daily-ticket-digest/automation.json +25 -0
  76. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/automations/daily-ticket-digest/trigger.json +9 -0
  77. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/notifications/daily-ticket-digest.json +24 -0
  78. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/permissions/form-groups/service-ticket-college.json +21 -0
  79. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/permissions/roles.json +17 -0
  80. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/workflows/expense-approval-workflow.json +48 -0
  81. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/ConfirmAction.tsx +22 -0
  82. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/QueryState.tsx +37 -0
  83. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/StatusTag.tsx +20 -0
  84. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/hooks/useTicketOps.ts +96 -0
  85. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/services/role-governance.ts +48 -0
  86. package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/services/service-ticket.ts +113 -0
  87. package/templates/sy-lowcode-app-workspace/package.json +1 -0
  88. package/templates/sy-lowcode-app-workspace/src/dev/App.tsx +11 -1
  89. package/templates/sy-lowcode-app-workspace/tsconfig.examples.json +24 -0
package/README.md CHANGED
@@ -7,21 +7,20 @@ It uses normal platform user login tokens through `/openxiangda-api/v1`; it does
7
7
  Private platform routing is fixed: backend APIs are under `/service`, platform management is under `/platform`, and app runtime access is under `/view`. Passing a root domain such as `https://yida.wisejob.cn/` to the CLI is supported; OpenXiangda stores the API base as `https://yida.wisejob.cn/service`.
8
8
 
9
9
  ```bash
10
- npm install -g .
11
- # or during local development:
10
+ npm install -g openxiangda@latest --registry=https://registry.npmjs.org
11
+ openxiangda update check
12
+ openxiangda skill install
13
+
14
+ # during local development:
12
15
  npm link
13
16
 
14
- openxiangda skill install
15
17
  openxiangda skill status
16
- openxiangda workspace init ./my-app-workspace
17
- cd ./my-app-workspace
18
- pnpm install
19
18
  openxiangda platform add dev https://dev-lowcode.example.com
20
19
  openxiangda login --profile dev
21
20
  openxiangda env
22
- openxiangda app list --profile dev
23
- openxiangda workspace bind --profile dev --app-type APP_XXXX
24
- cd /path/to/sy-lowcode-app-workspace
21
+ openxiangda workspace init ./my-app-workspace --profile dev --app-name "示例应用"
22
+ cd ./my-app-workspace
23
+ pnpm install
25
24
  openxiangda workspace publish --profile dev
26
25
  openxiangda menu list --profile dev
27
26
  openxiangda workflow list --profile dev
@@ -37,6 +36,16 @@ openxiangda app snapshot APP_XXXX --profile dev --json
37
36
 
38
37
  User tokens are stored in `~/.openxiangda/profiles.json` with `0600` permissions. Project state is stored in `.openxiangda/state.json` and contains only profile-specific resource IDs.
39
38
 
39
+ Use the official npm registry for OpenXiangda updates:
40
+
41
+ ```bash
42
+ npm install -g openxiangda@latest --registry=https://registry.npmjs.org
43
+ openxiangda update check --json
44
+ openxiangda update install
45
+ ```
46
+
47
+ Domestic npm mirrors may lag and return an older OpenXiangda version. `openxiangda update check --json` is designed for AI agents: it returns the installed version, latest official npm version, whether an update is available, and the compatibility commands to run after updating. `openxiangda update install` installs from the official registry and refreshes OpenXiangda skills by default.
48
+
40
49
  Codex skills are installed separately from login/profile state. Run `openxiangda skill install` after installing or linking the CLI. It installs the `openxiangda` root skill plus the 7 top-level subskills into `${CODEX_HOME:-~/.codex}/skills` by default:
41
50
 
42
51
  - `openxiangda`
@@ -50,10 +59,19 @@ Codex skills are installed separately from login/profile state. Run `openxiangda
50
59
 
51
60
  Use `openxiangda skill install --dest <skills-dir>` to target a different Codex skills directory. If a same-name skill exists but was not installed by OpenXiangda, the command refuses to overwrite it unless `--force` is provided. Restart Codex after installing skills.
52
61
 
53
- Create a new publishable app workspace with `openxiangda workspace init <dir>`. The command writes a minimal `sy-lowcode-app-workspace` template with React, Ant Design, the single `openxiangda` package, and the required `publish:all` / `openxiangda:publish` scripts. Use `--install` to run `pnpm install` immediately, or run it manually after creation. You can also bind during initialization:
62
+ Create a new publishable app workspace with `openxiangda workspace init <dir>`. The command writes a minimal `sy-lowcode-app-workspace` template with React, Ant Design, the single `openxiangda` package, and the required `publish:all` / `openxiangda:publish` scripts. Use `--install` to run `pnpm install` immediately, or run it manually after creation.
63
+
64
+ Local workspace state is authoritative. If the current folder has no `.openxiangda/state.json` app binding, create a new app instead of searching the platform for a similar app name:
65
+
66
+ ```bash
67
+ openxiangda workspace init ./my-app-workspace --profile dev --app-name "示例应用"
68
+ ```
69
+
70
+ Bind to an existing app only when the user explicitly provides an `appType` or asks to reuse an existing platform app:
54
71
 
55
72
  ```bash
56
73
  openxiangda workspace init ./my-app-workspace --profile dev --app-type APP_XXXX
74
+ openxiangda workspace bind --profile dev --app-type APP_XXXX
57
75
  ```
58
76
 
59
77
  表单页、流程表单页和自定义代码页都应在 `sy-lowcode-app-workspace` 中实现,由 `openxiangda workspace publish --profile <name>` 统一构建、上传 OSS 并注册到平台。`openxiangda form create`、`form publish`、`page publish` 只作为底层修复/诊断命令,不作为 AI 生成页面的主入口。
package/lib/cli.js CHANGED
@@ -14,7 +14,7 @@ const {
14
14
  } = require('./config');
15
15
  const { requestJson } = require('./http');
16
16
  const { getSkillStatusReport, installSkills } = require('./skills');
17
- const { initWorkspace } = require('./workspace-init');
17
+ const { assertCanInitializeWorkspace, initWorkspace } = require('./workspace-init');
18
18
  const {
19
19
  fail,
20
20
  openBrowser,
@@ -24,6 +24,10 @@ const {
24
24
  warn,
25
25
  writeJson,
26
26
  } = require('./utils');
27
+ const { version: CURRENT_VERSION } = require('../package.json');
28
+
29
+ const NPM_PACKAGE_NAME = 'openxiangda';
30
+ const OFFICIAL_NPM_REGISTRY = 'https://registry.npmjs.org';
27
31
 
28
32
  async function main(argv) {
29
33
  const [command, ...rest] = argv;
@@ -33,6 +37,7 @@ async function main(argv) {
33
37
  }
34
38
 
35
39
  if (command === 'login') return login(rest);
40
+ if (command === 'update') return update(rest);
36
41
  if (command === 'env') return env(rest);
37
42
  if (command === 'auth') return auth(rest);
38
43
  if (command === 'platform') return platform(rest);
@@ -58,12 +63,14 @@ function printHelp() {
58
63
 
59
64
  Usage:
60
65
  openxiangda login <platform-url> [--profile name]
66
+ openxiangda update check|install [--json] [--registry https://registry.npmjs.org]
61
67
  openxiangda platform add <name> <platform-url>
62
68
  openxiangda platform list
63
69
  openxiangda platform use <name>
64
70
  openxiangda auth status|refresh|logout [--profile name]
65
71
  openxiangda env [--profile name]
66
72
  openxiangda workspace init [dir] [--name package-name] [--install] [--profile name --app-type APP_XXX]
73
+ openxiangda workspace init [dir] --profile <name> --app-name <app-name> [--install]
67
74
  openxiangda workspace bind --profile <name> --app-type <APP_XXX>
68
75
  openxiangda workspace publish --profile <name> [--prune]
69
76
  openxiangda app list [--profile name] [--json]
@@ -102,6 +109,199 @@ OpenXiangda 使用普通用户登录 token,不需要 AK/SK。
102
109
  JS_CODE V2 使用 trusted_node;AI 源码必须写在 src/js-code-nodes/<scriptCode>/index.ts,definition-json 中的 sourceFile.localPath 会在 validate/create 时先 TS 校验、再构建并上传为快照。`);
103
110
  }
104
111
 
112
+ async function update(args) {
113
+ const requestedSubcommand = args[0] && !args[0].startsWith('--') ? args[0] : 'check';
114
+ const parsedArgs = requestedSubcommand === args[0] ? args.slice(1) : args;
115
+ const { flags } = parseArgs(parsedArgs);
116
+ const registry = normalizeNpmRegistry(flags.registry || OFFICIAL_NPM_REGISTRY);
117
+
118
+ if (requestedSubcommand === 'check') {
119
+ const result = checkOpenXiangdaUpdate(registry);
120
+ if (flags.json) return writeJson(result);
121
+ printUpdateCheck(result);
122
+ return;
123
+ }
124
+
125
+ if (requestedSubcommand === 'install') {
126
+ const result = installOpenXiangdaUpdate(registry, {
127
+ json: Boolean(flags.json),
128
+ skipSkills: Boolean(flags['no-skills']),
129
+ });
130
+ if (flags.json) return writeJson(result);
131
+ print('OpenXiangda 已更新。');
132
+ if (result.skillInstall?.attempted && result.skillInstall.status !== 0) {
133
+ warn(`skill install 未完成: ${result.skillInstall.error}`);
134
+ }
135
+ return;
136
+ }
137
+
138
+ fail('用法: openxiangda update check|install [--json] [--registry https://registry.npmjs.org] [--no-skills]');
139
+ }
140
+
141
+ function normalizeNpmRegistry(value) {
142
+ const registry = String(value || OFFICIAL_NPM_REGISTRY).trim();
143
+ return registry.replace(/\/+$/, '') || OFFICIAL_NPM_REGISTRY;
144
+ }
145
+
146
+ function checkOpenXiangdaUpdate(registry) {
147
+ const latestVersion = fetchLatestOpenXiangdaVersion(registry);
148
+ const versionComparison = compareVersions(latestVersion, CURRENT_VERSION);
149
+ const updateAvailable = versionComparison > 0;
150
+ return {
151
+ packageName: NPM_PACKAGE_NAME,
152
+ currentVersion: CURRENT_VERSION,
153
+ latestVersion,
154
+ registry,
155
+ updateAvailable,
156
+ status: updateAvailable
157
+ ? 'update_available'
158
+ : versionComparison < 0
159
+ ? 'local_newer_than_registry'
160
+ : 'latest',
161
+ installCommand: `npm install -g ${NPM_PACKAGE_NAME}@latest --registry=${registry}`,
162
+ skillInstallCommand: 'openxiangda skill install --force',
163
+ compatibilityCheckCommand: 'openxiangda commands --json',
164
+ checkedAt: new Date().toISOString(),
165
+ notes: [
166
+ 'Use the official npm registry for OpenXiangda updates; domestic mirrors may lag.',
167
+ 'After updating the package, refresh OpenXiangda skills so AI guidance matches the CLI version.',
168
+ ],
169
+ };
170
+ }
171
+
172
+ function fetchLatestOpenXiangdaVersion(registry) {
173
+ const result = spawnSync(
174
+ 'npm',
175
+ ['view', `${NPM_PACKAGE_NAME}@latest`, 'version', `--registry=${registry}`, '--json'],
176
+ {
177
+ encoding: 'utf8',
178
+ env: { ...process.env, npm_config_registry: registry },
179
+ }
180
+ );
181
+ if (result.error) {
182
+ fail(`无法执行 npm: ${result.error.message}`);
183
+ }
184
+ if (result.status !== 0) {
185
+ fail(`检查 OpenXiangda 更新失败: ${formatSpawnOutput(result)}`);
186
+ }
187
+ const raw = String(result.stdout || '').trim();
188
+ if (!raw) fail('检查 OpenXiangda 更新失败: npm 未返回版本号');
189
+ try {
190
+ const parsed = JSON.parse(raw);
191
+ return String(parsed).trim();
192
+ } catch (_error) {
193
+ return raw.replace(/^"|"$/g, '').trim();
194
+ }
195
+ }
196
+
197
+ function installOpenXiangdaUpdate(registry, options = {}) {
198
+ const npmArgs = [
199
+ 'install',
200
+ '-g',
201
+ `${NPM_PACKAGE_NAME}@latest`,
202
+ `--registry=${registry}`,
203
+ ];
204
+ const quiet = Boolean(options.json);
205
+ if (!quiet) {
206
+ print(`执行: npm ${npmArgs.join(' ')}`);
207
+ }
208
+ const installResult = spawnSync('npm', npmArgs, {
209
+ encoding: 'utf8',
210
+ stdio: quiet ? 'pipe' : 'inherit',
211
+ env: { ...process.env, npm_config_registry: registry },
212
+ });
213
+ if (installResult.error) {
214
+ fail(`无法执行 npm: ${installResult.error.message}`);
215
+ }
216
+ if (installResult.status !== 0) {
217
+ fail(`OpenXiangda 更新失败: ${formatSpawnOutput(installResult)}`);
218
+ }
219
+
220
+ const result = {
221
+ packageName: NPM_PACKAGE_NAME,
222
+ registry,
223
+ installCommand: `npm ${npmArgs.join(' ')}`,
224
+ installed: true,
225
+ skillInstall: {
226
+ attempted: false,
227
+ status: null,
228
+ error: null,
229
+ },
230
+ };
231
+
232
+ if (!options.skipSkills) {
233
+ if (!quiet) {
234
+ print('刷新 OpenXiangda skills: openxiangda skill install --force');
235
+ }
236
+ const skillResult = spawnSync('openxiangda', ['skill', 'install', '--force'], {
237
+ encoding: 'utf8',
238
+ stdio: quiet ? 'pipe' : 'inherit',
239
+ env: process.env,
240
+ });
241
+ result.skillInstall.attempted = true;
242
+ result.skillInstall.status = skillResult.status;
243
+ if (skillResult.error || skillResult.status !== 0) {
244
+ result.skillInstall.error = skillResult.error?.message || formatSpawnOutput(skillResult);
245
+ }
246
+ }
247
+
248
+ return result;
249
+ }
250
+
251
+ function printUpdateCheck(result) {
252
+ const lines = [
253
+ 'OpenXiangda update check',
254
+ `current: ${result.currentVersion}`,
255
+ `latest: ${result.latestVersion}`,
256
+ `registry: ${result.registry}`,
257
+ ];
258
+ if (result.updateAvailable) {
259
+ lines.push('status: update available');
260
+ lines.push('recommended:');
261
+ lines.push(` ${result.installCommand}`);
262
+ lines.push(` ${result.skillInstallCommand}`);
263
+ } else if (result.status === 'local_newer_than_registry') {
264
+ lines.push('status: local version is newer than registry');
265
+ } else {
266
+ lines.push('status: already latest');
267
+ }
268
+ lines.push(`compatibility: ${result.compatibilityCheckCommand}`);
269
+ print(lines.join('\n'));
270
+ }
271
+
272
+ function formatSpawnOutput(result) {
273
+ return (
274
+ String(result.stderr || '').trim() ||
275
+ String(result.stdout || '').trim() ||
276
+ `exit status ${result.status}`
277
+ );
278
+ }
279
+
280
+ function compareVersions(a, b) {
281
+ const left = parseSemver(a);
282
+ const right = parseSemver(b);
283
+ for (let index = 0; index < 3; index += 1) {
284
+ if (left.numbers[index] !== right.numbers[index]) {
285
+ return left.numbers[index] > right.numbers[index] ? 1 : -1;
286
+ }
287
+ }
288
+ if (left.prerelease === right.prerelease) return 0;
289
+ if (!left.prerelease) return 1;
290
+ if (!right.prerelease) return -1;
291
+ return left.prerelease > right.prerelease ? 1 : -1;
292
+ }
293
+
294
+ function parseSemver(value) {
295
+ const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(String(value || ''));
296
+ if (!match) {
297
+ return { numbers: [0, 0, 0], prerelease: String(value || '') };
298
+ }
299
+ return {
300
+ numbers: [Number(match[1]), Number(match[2]), Number(match[3])],
301
+ prerelease: match[4] || '',
302
+ };
303
+ }
304
+
105
305
  async function platform(args) {
106
306
  const [subcommand, ...rest] = args;
107
307
  const { flags, positional } = parseArgs(rest);
@@ -378,14 +578,49 @@ async function workspace(args) {
378
578
  const config = loadConfig();
379
579
 
380
580
  if (subcommand === 'init') {
581
+ let appType = flags['app-type'];
582
+ let profileName = flags.profile || null;
583
+ let createdApp = null;
584
+
585
+ if (flags['app-name']) {
586
+ if (appType) {
587
+ fail('workspace init 不能同时使用 --app-name 和 --app-type');
588
+ }
589
+ profileName = profileName || config.currentProfile;
590
+ if (!profileName) {
591
+ fail('workspace init --app-name 需要先选择 profile,或显式传 --profile <name>');
592
+ }
593
+ assertCanInitializeWorkspace({
594
+ dir: positional[0],
595
+ force: flags.force,
596
+ });
597
+ const data = await createPlatformApp(config, profileName, {
598
+ name: flags['app-name'],
599
+ description: flags.description || '',
600
+ iconfontCss: flags['iconfont-css'] || '',
601
+ });
602
+ appType = extractCreatedAppType(data);
603
+ if (!appType) {
604
+ fail('应用已创建,但平台响应缺少 appType,无法写入 workspace 绑定');
605
+ }
606
+ createdApp = {
607
+ name: flags['app-name'],
608
+ appType,
609
+ data,
610
+ };
611
+ }
612
+
381
613
  const result = initWorkspace({
382
614
  dir: positional[0],
383
615
  name: flags.name,
384
616
  install: flags.install,
385
617
  force: flags.force,
386
- profile: flags.profile,
387
- appType: flags['app-type'],
618
+ profile: profileName,
619
+ appType,
388
620
  });
621
+ if (createdApp) {
622
+ result.createdApp = createdApp;
623
+ }
389
624
  if (flags.json) return writeJson(result);
390
625
  printWorkspaceInitReport(result);
391
626
  return;
@@ -418,6 +653,7 @@ async function workspace(args) {
418
653
  },
419
654
  updatedAt: new Date().toISOString(),
420
655
  };
656
+ ensureResourceBuckets(state.profiles[profileName]);
421
657
  saveProjectState(state);
422
658
  print(`当前工作区已绑定 ${profileName}: ${appType}`);
423
659
  return;
@@ -463,13 +699,10 @@ async function app(args) {
463
699
  if (subcommand === 'create') {
464
700
  const name = flags.name || positional.join(' ');
465
701
  if (!name) fail('用法: openxiangda app create <name> [--profile name]');
466
- const data = await requestWithAuth(config, profileName, '/openxiangda-api/v1/apps/', {
467
- method: 'POST',
468
- body: {
469
- name,
470
- description: flags.description || '',
471
- iconfontCss: flags['iconfont-css'] || '',
472
- },
702
+ const data = await createPlatformApp(config, profileName, {
703
+ name,
704
+ description: flags.description || '',
705
+ iconfontCss: flags['iconfont-css'] || '',
473
706
  });
474
707
  if (flags.json) return writeJson(data);
475
708
  print(JSON.stringify(data, null, 2));
@@ -490,6 +723,29 @@ async function app(args) {
490
723
  print(JSON.stringify(data, null, 2));
491
724
  }
492
725
 
726
+ async function createPlatformApp(config, profileName, payload) {
727
+ return requestWithAuth(config, profileName, '/openxiangda-api/v1/apps/', {
728
+ method: 'POST',
729
+ body: {
730
+ name: payload.name,
731
+ description: payload.description || '',
732
+ iconfontCss: payload.iconfontCss || '',
733
+ },
734
+ });
735
+ }
736
+
737
+ function extractCreatedAppType(data) {
738
+ const candidates = [
739
+ data?.appType,
740
+ data?.app?.appType,
741
+ data?.result?.appType,
742
+ data?.application?.appType,
743
+ data?.type,
744
+ ];
745
+ const value = candidates.find(item => typeof item === 'string' && item.trim());
746
+ return value ? value.trim() : null;
747
+ }
748
+
493
749
  async function form(args) {
494
750
  const [subcommand, ...rest] = args;
495
751
  const { flags, positional } = parseArgs(rest);
@@ -1800,10 +2056,11 @@ async function commands(args) {
1800
2056
  name: 'openxiangda',
1801
2057
  commands: [
1802
2058
  'login <platform-url> [--profile name]',
2059
+ 'update check|install',
1803
2060
  'platform add|list|use|remove',
1804
2061
  'auth status|refresh|logout',
1805
2062
  'env',
1806
- 'workspace init|bind|publish [--prune]',
2063
+ 'workspace init|bind|publish [--app-name] [--prune]',
1807
2064
  'app list|create|snapshot',
1808
2065
  'form list|create|bind|pull|publish',
1809
2066
  'page list|publish|bind|releases|activate',
@@ -1828,6 +2085,9 @@ function printWorkspaceInitReport(result) {
1828
2085
  `已创建 OpenXiangda workspace: ${result.targetDir}`,
1829
2086
  `package: ${result.packageName}`,
1830
2087
  ];
2088
+ if (result.createdApp) {
2089
+ lines.push(`已创建平台应用: ${result.createdApp.name} (${result.createdApp.appType})`);
2090
+ }
1831
2091
  if (result.bound) {
1832
2092
  lines.push(`已绑定 ${result.bound.profile}: ${result.bound.appType}`);
1833
2093
  }
@@ -41,8 +41,14 @@ function initWorkspace(options = {}) {
41
41
  automations: {},
42
42
  menus: {},
43
43
  roles: {},
44
+ connectors: {},
45
+ notifications: {
46
+ templates: {},
47
+ typeConfigs: {},
48
+ },
44
49
  pagePermissionGroups: {},
45
50
  formPermissionGroups: {},
51
+ formSettings: {},
46
52
  },
47
53
  updatedAt: new Date().toISOString(),
48
54
  },
@@ -69,6 +75,12 @@ function initWorkspace(options = {}) {
69
75
  };
70
76
  }
71
77
 
78
+ function assertCanInitializeWorkspace(options = {}) {
79
+ const targetDir = path.resolve(options.dir || process.cwd());
80
+ ensureCanInitialize(targetDir, Boolean(options.force));
81
+ return targetDir;
82
+ }
83
+
72
84
  function ensureCanInitialize(targetDir, force) {
73
85
  if (!fs.existsSync(TEMPLATE_DIR)) {
74
86
  throw new Error(`workspace 模板不存在: ${TEMPLATE_DIR}`);
@@ -135,5 +147,6 @@ function buildNextSteps(targetDir, installedDependencies, bound) {
135
147
  }
136
148
 
137
149
  module.exports = {
150
+ assertCanInitializeWorkspace,
138
151
  initWorkspace,
139
152
  };
@@ -35,27 +35,36 @@ When the user provides a root domain such as `https://yida.wisejob.cn/`, use it
35
35
  ```bash
36
36
  openxiangda login https://dev-lowcode.example.com --profile dev
37
37
  ```
38
- 4. Check current context before any write:
38
+ 4. Check the CLI version and current context before any write:
39
39
  ```bash
40
+ openxiangda update check --json
40
41
  openxiangda env --profile dev
41
42
  openxiangda auth status --profile dev
42
43
  ```
43
- 5. Ensure there is a local app workspace. If the current directory is not a `sy-lowcode-app-workspace` and no app workspace exists yet, initialize one:
44
+ If `updateAvailable` is true, update from the official npm registry before continuing:
44
45
  ```bash
45
- openxiangda workspace init ./my-app-workspace --profile dev --app-type APP_XXXX
46
+ openxiangda update install
47
+ ```
48
+ 5. Ensure there is a local app workspace. Local workspace state is authoritative:
49
+ - If the current workspace already has `.openxiangda/state.json` with an app binding for the target profile, use that app.
50
+ - If the user explicitly provides an `appType` or asks to reuse an existing app, bind to that exact app.
51
+ - If the current folder is empty or has no local app binding, create a new app and workspace; do not search the platform for similar app names.
52
+ ```bash
53
+ openxiangda workspace init ./my-app-workspace --profile dev --app-name "示例应用"
46
54
  cd ./my-app-workspace
47
55
  pnpm install
48
56
  ```
49
- 6. Bind the local workspace to the app for the selected platform:
57
+ 6. To bind an existing app only when explicitly requested:
50
58
  ```bash
51
- openxiangda app list --profile dev
52
- # or create one when needed:
53
- openxiangda app create "示例应用" --profile dev
59
+ openxiangda workspace init ./my-app-workspace --profile dev --app-type APP_XXXX
60
+ # or, inside an existing workspace:
54
61
  openxiangda workspace bind --profile dev --app-type APP_XXXX
55
- cd /path/to/sy-lowcode-app-workspace
62
+ ```
63
+ 7. Publish from the local workspace:
64
+ ```bash
56
65
  openxiangda workspace publish --profile dev
57
66
  ```
58
- 7. For multi-platform publishing, always pass `--profile` explicitly:
67
+ 8. For multi-platform publishing, always pass `--profile` explicitly:
59
68
  ```bash
60
69
  openxiangda workspace publish --profile prod
61
70
  ```
@@ -63,7 +72,10 @@ When the user provides a root domain such as `https://yida.wisejob.cn/`, use it
63
72
  ## Rules
64
73
 
65
74
  - Never ask the user for `appKey`, `appSecret`, `AK`, or `SK`.
66
- - If `openxiangda` is not available in PATH, the CLI package is not installed or linked. In this repo, use `npm link`; in a packaged install, use `npm install -g .` or the published package.
75
+ - If `openxiangda` is not available in PATH, the CLI package is not installed or linked. In this repo, use `npm link`; in a packaged install, use `npm install -g openxiangda@latest --registry=https://registry.npmjs.org`.
76
+ - Always prefer the official npm registry for OpenXiangda updates. Domestic mirrors may lag and return an older package.
77
+ - Run `openxiangda update check --json` when starting substantial work or diagnosing version/skill mismatches. If it reports an update, run `openxiangda update install` so the CLI and installed skills match. If the installed CLI does not support `update`, run `npm install -g openxiangda@latest --registry=https://registry.npmjs.org` and then `openxiangda skill install --force`.
78
+ - Local `.openxiangda/state.json` is the source of truth for app binding. In an empty folder or unbound workspace, create a new app with `workspace init --app-name`; do not infer reuse from similar platform app names.
67
79
  - If there is no `sy-lowcode-app-workspace`, create one with `openxiangda workspace init <dir>` before writing forms, pages, or JS_CODE nodes.
68
80
  - Never treat `openxiangda form create` as page generation. It only creates a low-level platform form shell for diagnostics or for workspace publish internals. The source of user-facing pages is `sy-lowcode-app-workspace`.
69
81
  - Publish normal form pages, workflow form pages, and custom code pages through `openxiangda workspace publish --profile <name>` from the app workspace.
@@ -71,6 +83,7 @@ When the user provides a root domain such as `https://yida.wisejob.cn/`, use it
71
83
  - Use logical resource codes in local files. Platform-specific IDs such as `formUuid`, `pageId`, `workflowId`, and `automationId` must be isolated by profile.
72
84
  - Put engineering-managed resources in `src/resources/` and use `openxiangda resource validate|plan|publish|pull`. `workspace publish` publishes workspace forms/pages first, then runs non-destructive resource upsert. Only pass `--prune` when the user explicitly wants local manifests to delete platform-side extras.
73
85
  - For external APIs, create a connector manifest in `src/resources/connectors/` and call it from pages through `sdk.connector`; never put third-party API keys in page source.
86
+ - Before scaffolding a real app, complex page, data management page, portal shell, status lifecycle, role governance, or automation, read `references/best-practices.md` and pick the architecture first. Prefer copying the relevant pattern from `examples/best-practices/` into `src/` instead of generating a single large page file.
74
87
  - Before publishing to another platform, verify the workspace is bound for that profile. Resource IDs from one profile must not be reused for another profile.
75
88
  - Use `openxiangda app snapshot <APP_XXX> --profile <name> --json` for diagnosis before changing an existing app.
76
89
  - Run write commands that update `.openxiangda/state.json` sequentially within the same workspace. Read-only commands can run in parallel.
@@ -106,6 +119,7 @@ Core CLI / state:
106
119
  - `references/workspace-state.md` — `.openxiangda/state.json` shape and profile isolation rules.
107
120
  - `references/connector-resources.md` — `src/resources` manifests, connector schema, and SDK connector calls.
108
121
  - `references/notifications.md` — `src/resources/notifications`, notification templates/type bindings, and `sdk.notification` / `ctx.notification`.
122
+ - `references/best-practices.md` — initialized examples for modular pages, state lifecycles, role governance, permission isolation, high-performance queries, portal shells, workflow boundaries, and automation patterns.
109
123
 
110
124
  Form authoring:
111
125
 
@@ -130,5 +144,6 @@ Platform domain knowledge (read these first when generating forms or pages so th
130
144
  - `references/platform-data-model.md` — how the platform persists field values (JSONB, `{label, value}` for option fields, attachment shape, etc.). Use this whenever you write, render, or diagnose form data.
131
145
  - `references/style-system.md` — three-layer style architecture, CSS namespace, and the no-hardcoded-color rule. Use this before writing any page or form CSS.
132
146
  - `references/architecture-patterns.md` — CRUD data flow, `DataManagementList` pattern, recommended directory layout for `src/pages` and `src/forms`. Use this before scaffolding a new page or list view.
147
+ - `references/best-practices.md` — read this before implementing app-level business patterns; it points to `examples/best-practices/` and explains when to use status fields instead of workflow.
133
148
  - `references/component-guide.md` — when to pick platform components vs. raw Ant Design vs. custom components, with the rules for option fields. Use this before introducing a new component.
134
149
  - `references/troubleshooting.md` — known failure modes (missing styles, `options` runtime errors, broken option rendering, etc.) and how to fix them. Use this when something does not behave as expected.
@@ -29,9 +29,10 @@
29
29
  | 编写 REST API | 平台自动生成数据 API |
30
30
  | 维护权限中间件 | 表单字段级权限 + 部门/角色 |
31
31
  | 自建文件存储 | `AttachmentField` 直接接入平台存储 |
32
- | 自研流程引擎 | 表单 + workflow + js-code-nodes |
33
-
34
- AI Agent 在该平台上写代码时,**不应该考虑数据库、表结构、后端接口**——这些由表单 schema 自动派生。
32
+ | 业务状态流转 | 表单 + `status` 字段 + 操作日志 + domain/service |
33
+ | 真审批流程 | 表单 + workflow + js-code-nodes |
34
+
35
+ AI Agent 在该平台上写代码时,**不应该考虑数据库、表结构、后端接口**——这些由表单 schema 自动派生。
35
36
 
36
37
  ---
37
38
 
@@ -144,12 +145,20 @@ export default function InstrumentListPage() {
144
145
  | 页面类型 | 实现方式 | 适用场景 |
145
146
  | --- | --- | --- |
146
147
  | 表单录入 / 编辑页 | `src/forms/xxx/` (`schema.ts` + `page.tsx`) | 新增数据、修改数据 |
147
- | 数据管理页 | `src/pages/xxx/` 使用 `DataManagementList` | 列表、查询、批量操作、导出 |
148
- | 流程表单页 | `src/forms/xxx/` 关联 workflow | 审批流程发起、节点处理 |
149
- | 自定义业务页 | `src/pages/xxx/` 自由编码 | 仪表盘、看板、复杂交互、对外门户 |
150
- | 详情页 | `src/pages/xxx/[id].tsx` 使用 `FormSummaryCard` 等 | 查看单条数据、关联记录 |
151
-
152
- > AI Agent 在创建新页面前,先按上表判断「应该走表单还是代码页」,避免把列表场景误塞进表单页。
148
+ | 数据管理页 | `src/pages/xxx/` 使用 `DataManagementList` | 列表、查询、批量操作、导出 |
149
+ | 状态流转页 | 普通表单 + 状态字段 + 操作日志 + 代码页 service | 工单、订单、任务等业务状态变化 |
150
+ | 流程表单页 | `src/forms/xxx/` 关联 workflow | 真实审批流程发起、节点处理、审批意见 |
151
+ | 自定义业务页 | `src/pages/xxx/` 自由编码 | 仪表盘、看板、复杂交互、对外门户 |
152
+ | 详情页 | `src/pages/xxx/[id].tsx` 使用 `FormSummaryCard` 等 | 查看单条数据、关联记录 |
153
+
154
+ > AI Agent 在创建新页面前,先按上表判断「应该走表单还是代码页」,避免把列表场景误塞进表单页。
155
+
156
+ ### 4.1 状态流转不是审批流
157
+
158
+ - 普通业务流转默认使用普通表单:`status` 字段、责任人/归属冗余字段、操作日志表、domain 状态机、service 统一提交。
159
+ - 每次状态变更都由 service 完成,统一写操作日志并更新责任人、归属、更新时间等冗余字段。
160
+ - workflow 只用于真实审批:审批人、审批任务、同意/驳回、审批意见、节点权限、流程记录明确存在的场景。
161
+ - 不要把「待处理 / 处理中 / 已完成 / 已关闭」这类状态流转建成流程表单。
153
162
 
154
163
  ---
155
164
 
@@ -175,7 +184,7 @@ export default function InstrumentListPage() {
175
184
  - **UI 库分端**:
176
185
  - PC → `antd`
177
186
  - Mobile → `antd-mobile`
178
- - **逻辑层共享**:`src/domain/` 与 `src/shared/services/` 在两端复用,确保业务规则一处实现。
187
+ - **逻辑层共享**:`src/domain/` 与 `src/shared/services/` 在两端复用,确保业务规则一处实现。
179
188
 
180
189
  ### 5.2 共享 / 分离原则
181
190
 
@@ -228,15 +237,28 @@ forms ──▶ shared ──▶ domain
228
237
  ```
229
238
 
230
239
  - `domain/` 不依赖 `shared/` 或 UI。
231
- - `shared/` 不依赖 `pages/` 或 `forms/`。
232
- - 反向依赖(如 domain 导入 pages)= 立刻拒绝。
233
-
234
- ---
235
-
236
- ## 速查 Checklist(AI Agent 自检)
237
-
238
- - [ ] 新增数据场景:是否已经定义表单 schema?
239
- - [ ] 列表/查询场景:是否使用 `DataManagementList` 而非平台 view?
240
- - [ ] 菜单绑定:是否指向代码页 `--type=page`?
241
- - [ ] PC/Mobile:是否分离页面、共享 domain?
242
- - [ ] 目录:业务逻辑是否落在 `domain/`,未污染 UI 层?
240
+ - `shared/` 不依赖 `pages/` 或 `forms/`。
241
+ - 反向依赖(如 domain 导入 pages)= 立刻拒绝。
242
+
243
+ ---
244
+
245
+ ## 7. 查询与交互性能
246
+
247
+ - 列表必须走分页接口,传 `currentPage`、`pageSize`、排序和结构化条件。
248
+ - 禁止一次取大 `pageSize` 再在页面内筛选。
249
+ - 默认不要使用 `searchKeyWord`;多字段模糊查询用 `filterGroup` + `OR`,并显式指定字段。
250
+ - 查询条件构造放在 `domain/` 或 `shared/services/`,不要散落在 TSX 事件处理里。
251
+ - 列表刷新时保留当前数据,使用局部刷新态,避免整页闪烁。
252
+ - 操作需要统一的确认、pending、成功反馈、失败刷新或回滚。
253
+
254
+ ---
255
+
256
+ ## 速查 Checklist(AI Agent 自检)
257
+
258
+ - [ ] 新增数据场景:是否已经定义表单 schema?
259
+ - [ ] 列表/查询场景:是否使用 `DataManagementList` 而非平台 view?
260
+ - [ ] 查询:是否使用分页和结构化条件,而不是大 pageSize、页面内过滤或 `searchKeyWord`?
261
+ - [ ] 流程判断:是否确认这是「真审批」,而不是普通状态流转?
262
+ - [ ] 菜单绑定:是否指向代码页 `--type=page`?
263
+ - [ ] PC/Mobile:是否分离页面、共享 domain?
264
+ - [ ] 目录:业务逻辑是否落在 `domain/`,未污染 UI 层?