openxiangda 1.0.22 → 1.0.25
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/README.md +28 -10
- package/lib/cli.js +351 -20
- package/lib/workspace-init.js +13 -0
- package/openxiangda-skills/SKILL.md +26 -10
- package/openxiangda-skills/references/architecture-patterns.md +44 -22
- package/openxiangda-skills/references/best-practices.md +180 -0
- package/openxiangda-skills/references/component-guide.md +34 -8
- package/openxiangda-skills/references/pages/publish-flow.md +26 -0
- package/openxiangda-skills/references/pages/workspace-structure.md +5 -3
- package/openxiangda-skills/references/workspace-state.md +6 -0
- package/openxiangda-skills/skills/openxiangda-app/SKILL.md +12 -7
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +34 -4
- package/openxiangda-skills/skills/openxiangda-form/SKILL.md +13 -1
- package/openxiangda-skills/skills/openxiangda-page/SKILL.md +22 -1
- package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +3 -0
- package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +7 -0
- package/package.json +1 -1
- package/packages/sdk/src/build-source/scripts/publish-all.mjs +44 -5
- package/packages/sdk/src/build-source/scripts/utils/incremental.mjs +95 -0
- package/packages/sdk/src/build-source/scripts/utils/incremental.test.ts +62 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/README.md +32 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/catalog.json +61 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/decision-guide.md +44 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/design-style.md +36 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/module-structure.md +48 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/index.ts +2 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/permissions.test.ts +35 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/permissions.ts +24 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/role-governance/types.ts +17 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/index.ts +4 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/permissions.test.ts +42 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/permissions.ts +23 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/state-machine.test.ts +63 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/state-machine.ts +73 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/ticket-query.test.ts +34 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/ticket-query.ts +73 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/domain/service-ticket/types.ts +64 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/app-role/page.tsx +1 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/app-role/schema.ts +57 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/customer-profile/page.tsx +1 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/customer-profile/schema.ts +83 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/service-ticket/page.tsx +1 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/service-ticket/schema.ts +97 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/ticket-action-log/page.tsx +1 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/forms/ticket-action-log/schema.ts +65 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/js-code-nodes/daily_ticket_digest/index.ts +44 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/js-code-nodes/sync_roles_to_platform/index.ts +33 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/App.tsx +7 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/WorkbenchPage.tsx +36 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/components/ConfigPanel.tsx +34 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/components/PreviewPanel.tsx +17 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/index.tsx +10 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/page.config.ts +9 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/reducer.ts +29 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/interactive-workbench/styles.css +24 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/App.tsx +7 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/MobilePortalShell.tsx +31 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/index.tsx +10 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/modules/MobileHome.tsx +13 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/page.config.ts +14 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/routes.ts +13 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/mobile-portal-shell/styles.css +11 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/App.tsx +7 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/PcPortalShell.tsx +35 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/components/PortalMetric.tsx +11 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/index.tsx +10 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/modules/HomeModule.tsx +25 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/modules/TicketsModule.tsx +14 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/page.config.ts +14 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/routes.ts +19 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/pc-portal-shell/styles.css +35 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/App.tsx +7 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/TicketOpsPage.tsx +105 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/components/TicketActionTimeline.tsx +22 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/components/TicketDetailDrawer.tsx +41 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/components/TicketTableActions.tsx +55 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/index.tsx +10 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/page.config.ts +9 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/pages/service-ticket-ops/styles.css +35 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/automations/daily-ticket-digest/automation.json +25 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/automations/daily-ticket-digest/trigger.json +9 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/notifications/daily-ticket-digest.json +24 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/permissions/form-groups/service-ticket-college.json +21 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/permissions/roles.json +17 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/resources/workflows/expense-approval-workflow.json +48 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/ConfirmAction.tsx +22 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/QueryState.tsx +37 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/components/StatusTag.tsx +20 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/hooks/useTicketOps.ts +96 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/services/role-governance.ts +48 -0
- package/templates/sy-lowcode-app-workspace/examples/best-practices/src/shared/services/service-ticket.ts +113 -0
- package/templates/sy-lowcode-app-workspace/package.json +2 -0
- package/templates/sy-lowcode-app-workspace/src/dev/App.tsx +11 -1
- 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
|
-
|
|
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
|
|
23
|
-
|
|
24
|
-
|
|
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.
|
|
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,14 +63,16 @@ 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
|
-
openxiangda workspace publish --profile <name> [--prune]
|
|
75
|
+
openxiangda workspace publish --profile <name> [--changed [--since ref]|--form code|--page code|--only list] [--dry-run] [--force] [--resources|--skip-resources] [--prune]
|
|
69
76
|
openxiangda app list [--profile name] [--json]
|
|
70
77
|
openxiangda app create <name> [--profile name] [--description text]
|
|
71
78
|
openxiangda app snapshot <APP_XXX> [--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:
|
|
387
|
-
appType
|
|
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,12 +653,14 @@ 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;
|
|
424
660
|
}
|
|
425
661
|
|
|
426
662
|
if (subcommand === 'publish') {
|
|
663
|
+
const publishOptions = normalizeWorkspacePublishOptions(flags);
|
|
427
664
|
const state = loadProjectState();
|
|
428
665
|
const bound = state.profiles?.[profileName];
|
|
429
666
|
if (!bound?.appType) {
|
|
@@ -436,15 +673,30 @@ async function workspace(args) {
|
|
|
436
673
|
profileName,
|
|
437
674
|
`/openxiangda-api/v1/apps/${encodeURIComponent(bound.appType)}/snapshot`
|
|
438
675
|
);
|
|
439
|
-
runWorkspacePublish(profileName, profile, bound.appType);
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
676
|
+
runWorkspacePublish(profileName, profile, bound.appType, publishOptions.workspaceArgs);
|
|
677
|
+
if (publishOptions.includeResources) {
|
|
678
|
+
if (publishOptions.dryRun) {
|
|
679
|
+
const manifest = loadWorkspaceResources();
|
|
680
|
+
const validation = validateWorkspaceResources(manifest);
|
|
681
|
+
if (validation.errors.length > 0) {
|
|
682
|
+
fail(`资源配置校验失败: ${validation.errors[0]}`);
|
|
683
|
+
}
|
|
684
|
+
const target = getWorkspaceTarget(config, profileName, {});
|
|
685
|
+
const plan = await buildResourcePlan(config, target, manifest);
|
|
686
|
+
printResourcePlan(plan);
|
|
687
|
+
} else {
|
|
688
|
+
await publishResourcesForWorkspace(config, profileName, {
|
|
689
|
+
quiet: true,
|
|
690
|
+
prune: Boolean(flags.prune),
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
} else if (!publishOptions.quietResourceSkip) {
|
|
694
|
+
print('已跳过 src/resources 发布;如需同时发布资源,传 --resources。');
|
|
695
|
+
}
|
|
444
696
|
return;
|
|
445
697
|
}
|
|
446
698
|
|
|
447
|
-
fail('用法: openxiangda workspace init|bind|publish');
|
|
699
|
+
fail('用法: openxiangda workspace init|bind|publish [--changed [--since ref]|--form code|--page code|--only list] [--dry-run] [--force] [--resources|--skip-resources]');
|
|
448
700
|
}
|
|
449
701
|
|
|
450
702
|
async function app(args) {
|
|
@@ -463,13 +715,10 @@ async function app(args) {
|
|
|
463
715
|
if (subcommand === 'create') {
|
|
464
716
|
const name = flags.name || positional.join(' ');
|
|
465
717
|
if (!name) fail('用法: openxiangda app create <name> [--profile name]');
|
|
466
|
-
const data = await
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
description: flags.description || '',
|
|
471
|
-
iconfontCss: flags['iconfont-css'] || '',
|
|
472
|
-
},
|
|
718
|
+
const data = await createPlatformApp(config, profileName, {
|
|
719
|
+
name,
|
|
720
|
+
description: flags.description || '',
|
|
721
|
+
iconfontCss: flags['iconfont-css'] || '',
|
|
473
722
|
});
|
|
474
723
|
if (flags.json) return writeJson(data);
|
|
475
724
|
print(JSON.stringify(data, null, 2));
|
|
@@ -490,6 +739,29 @@ async function app(args) {
|
|
|
490
739
|
print(JSON.stringify(data, null, 2));
|
|
491
740
|
}
|
|
492
741
|
|
|
742
|
+
async function createPlatformApp(config, profileName, payload) {
|
|
743
|
+
return requestWithAuth(config, profileName, '/openxiangda-api/v1/apps/', {
|
|
744
|
+
method: 'POST',
|
|
745
|
+
body: {
|
|
746
|
+
name: payload.name,
|
|
747
|
+
description: payload.description || '',
|
|
748
|
+
iconfontCss: payload.iconfontCss || '',
|
|
749
|
+
},
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function extractCreatedAppType(data) {
|
|
754
|
+
const candidates = [
|
|
755
|
+
data?.appType,
|
|
756
|
+
data?.app?.appType,
|
|
757
|
+
data?.result?.appType,
|
|
758
|
+
data?.application?.appType,
|
|
759
|
+
data?.type,
|
|
760
|
+
];
|
|
761
|
+
const value = candidates.find(item => typeof item === 'string' && item.trim());
|
|
762
|
+
return value ? value.trim() : null;
|
|
763
|
+
}
|
|
764
|
+
|
|
493
765
|
async function form(args) {
|
|
494
766
|
const [subcommand, ...rest] = args;
|
|
495
767
|
const { flags, positional } = parseArgs(rest);
|
|
@@ -1800,10 +2072,11 @@ async function commands(args) {
|
|
|
1800
2072
|
name: 'openxiangda',
|
|
1801
2073
|
commands: [
|
|
1802
2074
|
'login <platform-url> [--profile name]',
|
|
2075
|
+
'update check|install',
|
|
1803
2076
|
'platform add|list|use|remove',
|
|
1804
2077
|
'auth status|refresh|logout',
|
|
1805
2078
|
'env',
|
|
1806
|
-
'workspace init|bind|publish [--prune]',
|
|
2079
|
+
'workspace init|bind|publish [--app-name] [--changed|--since|--form|--page|--only|--dry-run|--force|--resources|--skip-resources|--prune]',
|
|
1807
2080
|
'app list|create|snapshot',
|
|
1808
2081
|
'form list|create|bind|pull|publish',
|
|
1809
2082
|
'page list|publish|bind|releases|activate',
|
|
@@ -1828,6 +2101,9 @@ function printWorkspaceInitReport(result) {
|
|
|
1828
2101
|
`已创建 OpenXiangda workspace: ${result.targetDir}`,
|
|
1829
2102
|
`package: ${result.packageName}`,
|
|
1830
2103
|
];
|
|
2104
|
+
if (result.createdApp) {
|
|
2105
|
+
lines.push(`已创建平台应用: ${result.createdApp.name} (${result.createdApp.appType})`);
|
|
2106
|
+
}
|
|
1831
2107
|
if (result.bound) {
|
|
1832
2108
|
lines.push(`已绑定 ${result.bound.profile}: ${result.bound.appType}`);
|
|
1833
2109
|
}
|
|
@@ -4810,7 +5086,60 @@ async function refreshProfile(config, profileName) {
|
|
|
4810
5086
|
};
|
|
4811
5087
|
}
|
|
4812
5088
|
|
|
4813
|
-
function
|
|
5089
|
+
function readStringFlag(flags, key) {
|
|
5090
|
+
const value = flags[key];
|
|
5091
|
+
if (value === undefined || value === null || value === false) return '';
|
|
5092
|
+
if (value === true) fail(`--${key} 需要提供值`);
|
|
5093
|
+
return String(value).trim();
|
|
5094
|
+
}
|
|
5095
|
+
|
|
5096
|
+
function normalizeWorkspacePublishOptions(flags) {
|
|
5097
|
+
const targetForm = readStringFlag(flags, 'form');
|
|
5098
|
+
const targetPage = readStringFlag(flags, 'page');
|
|
5099
|
+
const only = readStringFlag(flags, 'only');
|
|
5100
|
+
const since = readStringFlag(flags, 'since');
|
|
5101
|
+
const changed = Boolean(flags.changed || flags['changed-only']);
|
|
5102
|
+
const dryRun = Boolean(flags['dry-run']);
|
|
5103
|
+
const force = Boolean(flags.force);
|
|
5104
|
+
const skipResources = Boolean(flags['skip-resources']);
|
|
5105
|
+
const includeResourcesFlag = Boolean(flags.resources || flags['include-resources']);
|
|
5106
|
+
const prune = Boolean(flags.prune);
|
|
5107
|
+
|
|
5108
|
+
if (targetForm && targetPage) {
|
|
5109
|
+
fail('workspace publish 不能同时使用 --form 和 --page,请分两次发布');
|
|
5110
|
+
}
|
|
5111
|
+
if (changed && (targetForm || targetPage || only)) {
|
|
5112
|
+
fail('workspace publish --changed 不能与 --form、--page 或 --only 同时使用');
|
|
5113
|
+
}
|
|
5114
|
+
if (since && !changed) {
|
|
5115
|
+
fail('workspace publish --since 只能与 --changed 一起使用');
|
|
5116
|
+
}
|
|
5117
|
+
if (skipResources && prune) {
|
|
5118
|
+
fail('workspace publish --skip-resources 不能与 --prune 同时使用');
|
|
5119
|
+
}
|
|
5120
|
+
|
|
5121
|
+
const workspaceArgs = [];
|
|
5122
|
+
if (targetForm) workspaceArgs.push('--form', targetForm);
|
|
5123
|
+
if (targetPage) workspaceArgs.push('--page', targetPage);
|
|
5124
|
+
if (only) workspaceArgs.push('--only', only);
|
|
5125
|
+
if (changed) workspaceArgs.push('--changed');
|
|
5126
|
+
if (since) workspaceArgs.push('--since', since);
|
|
5127
|
+
if (dryRun) workspaceArgs.push('--dry-run');
|
|
5128
|
+
if (force) workspaceArgs.push('--force');
|
|
5129
|
+
|
|
5130
|
+
const targeted = Boolean(targetForm || targetPage || only || changed);
|
|
5131
|
+
const includeResources =
|
|
5132
|
+
!skipResources && (includeResourcesFlag || prune || !targeted);
|
|
5133
|
+
|
|
5134
|
+
return {
|
|
5135
|
+
workspaceArgs,
|
|
5136
|
+
dryRun,
|
|
5137
|
+
includeResources,
|
|
5138
|
+
quietResourceSkip: !targeted || skipResources,
|
|
5139
|
+
};
|
|
5140
|
+
}
|
|
5141
|
+
|
|
5142
|
+
function runWorkspacePublish(profileName, profile, appType, publishArgs = []) {
|
|
4814
5143
|
const packageFile = path.join(process.cwd(), 'package.json');
|
|
4815
5144
|
if (!fs.existsSync(packageFile)) {
|
|
4816
5145
|
fail('当前目录没有 package.json,无法识别工作区发布脚本');
|
|
@@ -4828,7 +5157,9 @@ function runWorkspacePublish(profileName, profile, appType) {
|
|
|
4828
5157
|
|
|
4829
5158
|
const usePnpm = fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'));
|
|
4830
5159
|
const command = usePnpm ? 'pnpm' : 'npm';
|
|
4831
|
-
const args =
|
|
5160
|
+
const args = publishArgs.length
|
|
5161
|
+
? ['run', scriptName, '--', ...publishArgs]
|
|
5162
|
+
: ['run', scriptName];
|
|
4832
5163
|
const result = spawnSync(command, args, {
|
|
4833
5164
|
cwd: process.cwd(),
|
|
4834
5165
|
stdio: 'inherit',
|
package/lib/workspace-init.js
CHANGED
|
@@ -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
|
-
|
|
44
|
+
If `updateAvailable` is true, update from the official npm registry before continuing:
|
|
44
45
|
```bash
|
|
45
|
-
openxiangda
|
|
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.
|
|
57
|
+
6. To bind an existing app only when explicitly requested:
|
|
50
58
|
```bash
|
|
51
|
-
openxiangda app
|
|
52
|
-
# or
|
|
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
|
-
|
|
62
|
+
```
|
|
63
|
+
7. Publish from the local workspace:
|
|
64
|
+
```bash
|
|
56
65
|
openxiangda workspace publish --profile dev
|
|
57
66
|
```
|
|
58
|
-
|
|
67
|
+
8. For multi-platform publishing, always pass `--profile` explicitly:
|
|
59
68
|
```bash
|
|
60
69
|
openxiangda workspace publish --profile prod
|
|
61
70
|
```
|
|
@@ -63,14 +72,19 @@ 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
|
|
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.
|
|
82
|
+
- For routine AI edits, avoid reflexive full publish. First run `openxiangda workspace publish --profile <name> --changed --dry-run`, then use `--changed`, `--page <pageCode>`, `--form <formCode>`, or `--only pages/a,forms/b`. Use full publish only when broad shared/config changes intentionally affect many modules.
|
|
70
83
|
- Never store token data in the project directory. User tokens live in `~/.openxiangda/profiles.json`; project state lives in `.openxiangda/state.json` and stores only IDs and mappings.
|
|
71
84
|
- Use logical resource codes in local files. Platform-specific IDs such as `formUuid`, `pageId`, `workflowId`, and `automationId` must be isolated by profile.
|
|
72
85
|
- 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
86
|
- 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.
|
|
87
|
+
- 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
88
|
- 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
89
|
- Use `openxiangda app snapshot <APP_XXX> --profile <name> --json` for diagnosis before changing an existing app.
|
|
76
90
|
- Run write commands that update `.openxiangda/state.json` sequentially within the same workspace. Read-only commands can run in parallel.
|
|
@@ -106,6 +120,7 @@ Core CLI / state:
|
|
|
106
120
|
- `references/workspace-state.md` — `.openxiangda/state.json` shape and profile isolation rules.
|
|
107
121
|
- `references/connector-resources.md` — `src/resources` manifests, connector schema, and SDK connector calls.
|
|
108
122
|
- `references/notifications.md` — `src/resources/notifications`, notification templates/type bindings, and `sdk.notification` / `ctx.notification`.
|
|
123
|
+
- `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
124
|
|
|
110
125
|
Form authoring:
|
|
111
126
|
|
|
@@ -130,5 +145,6 @@ Platform domain knowledge (read these first when generating forms or pages so th
|
|
|
130
145
|
- `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
146
|
- `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
147
|
- `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.
|
|
148
|
+
- `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
149
|
- `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
150
|
- `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.
|