openxiangda 1.0.109 → 1.0.110
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/lib/cli.js +92 -2
- package/lib/design-gates.js +1 -0
- package/openxiangda-skills/SKILL.md +2 -2
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +1 -0
- package/package.json +1 -1
- package/packages/sdk/src/build-source/scripts/sync-schema.mjs +7 -0
- package/packages/sdk/src/build-source/scripts/utils/form-api.mjs +7 -0
- package/templates/openxiangda-react-spa/AGENTS.md +1 -1
- package/templates/openxiangda-react-spa/scripts/guard-publish.mjs +2 -1
package/lib/cli.js
CHANGED
|
@@ -1483,7 +1483,25 @@ async function workspace(args) {
|
|
|
1483
1483
|
profileName,
|
|
1484
1484
|
`/openxiangda-api/v1/apps/${encodeURIComponent(bound.appType)}/snapshot`
|
|
1485
1485
|
);
|
|
1486
|
-
|
|
1486
|
+
const reactSpaWorkspace = isReactSpaWorkspace();
|
|
1487
|
+
if (reactSpaWorkspace && !publishOptions.legacyFormBundle) {
|
|
1488
|
+
if (publishOptions.targetForm && !publishOptions.targetPage && !publishOptions.only && !publishOptions.changed) {
|
|
1489
|
+
runReactSpaFormSchemaPublish(profileName, profile, bound.appType, publishOptions);
|
|
1490
|
+
} else {
|
|
1491
|
+
fail(
|
|
1492
|
+
[
|
|
1493
|
+
'React SPA 工作区不使用 workspace publish 发布前端页面。',
|
|
1494
|
+
'如需发布表单 schema,请运行:',
|
|
1495
|
+
` openxiangda workspace publish --profile ${profileName} --form <formCode>`,
|
|
1496
|
+
'如需发布资源和前端运行时,请运行:',
|
|
1497
|
+
` openxiangda resource publish --profile ${profileName}`,
|
|
1498
|
+
` openxiangda runtime deploy --profile ${profileName}`,
|
|
1499
|
+
].join('\n')
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
} else {
|
|
1503
|
+
runWorkspacePublish(profileName, profile, bound.appType, publishOptions.workspaceArgs);
|
|
1504
|
+
}
|
|
1487
1505
|
if (publishOptions.includeResources) {
|
|
1488
1506
|
if (publishOptions.dryRun) {
|
|
1489
1507
|
const manifest = loadWorkspaceResources();
|
|
@@ -3649,7 +3667,9 @@ async function resource(args) {
|
|
|
3649
3667
|
return;
|
|
3650
3668
|
}
|
|
3651
3669
|
|
|
3670
|
+
const target = getWorkspaceTarget(config, profileName, flags);
|
|
3652
3671
|
const validation = validateWorkspaceResources(manifest);
|
|
3672
|
+
validateWorkspaceResourceBindings(manifest, target.bound, validation);
|
|
3653
3673
|
await validateCompiledWorkflowResources(manifest, validation);
|
|
3654
3674
|
if (subcommand === 'validate') {
|
|
3655
3675
|
if (flags.json) return writeJson(validation);
|
|
@@ -3663,7 +3683,6 @@ async function resource(args) {
|
|
|
3663
3683
|
fail(`资源配置校验失败: ${validation.errors[0]}`);
|
|
3664
3684
|
}
|
|
3665
3685
|
|
|
3666
|
-
const target = getWorkspaceTarget(config, profileName, flags);
|
|
3667
3686
|
const dryRun = subcommand === 'publish' && Boolean(flags['dry-run']);
|
|
3668
3687
|
if (subcommand === 'plan' || dryRun) {
|
|
3669
3688
|
const plan = await buildResourcePlan(config, target, manifest);
|
|
@@ -5358,6 +5377,34 @@ function validateWorkspaceResources(manifest) {
|
|
|
5358
5377
|
};
|
|
5359
5378
|
}
|
|
5360
5379
|
|
|
5380
|
+
function validateWorkspaceResourceBindings(manifest, bound, validation) {
|
|
5381
|
+
for (const item of manifest.workflows || []) {
|
|
5382
|
+
if (!item.formCode && !item.form) continue;
|
|
5383
|
+
const formCode = item.formCode || item.form;
|
|
5384
|
+
const formBinding = bound?.resources?.forms?.[formCode];
|
|
5385
|
+
const formUuid = formBinding?.formUuid;
|
|
5386
|
+
if (!formUuid) {
|
|
5387
|
+
validation.errors.push(
|
|
5388
|
+
`${resourceLabel('workflow', item)}: formCode ${formCode} 未绑定。请先运行 openxiangda workspace publish --profile <name> --form ${formCode}`
|
|
5389
|
+
);
|
|
5390
|
+
continue;
|
|
5391
|
+
}
|
|
5392
|
+
const localSchemaPath = path.join(process.cwd(), 'src', 'forms', formCode, 'schema.ts');
|
|
5393
|
+
if (fs.existsSync(localSchemaPath) && !formBinding.schemaSyncedAt) {
|
|
5394
|
+
validation.errors.push(
|
|
5395
|
+
`${resourceLabel('workflow', item)}: formCode ${formCode} 已绑定但本地 schema 尚未同步。请先运行 openxiangda workspace publish --profile <name> --form ${formCode}`
|
|
5396
|
+
);
|
|
5397
|
+
}
|
|
5398
|
+
if (formBinding.formType && formBinding.formType !== 'process') {
|
|
5399
|
+
validation.errors.push(
|
|
5400
|
+
`${resourceLabel('workflow', item)}: formCode ${formCode} 当前 formType=${formBinding.formType},流程表单需要 process`
|
|
5401
|
+
);
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5404
|
+
validation.valid = validation.errors.length === 0;
|
|
5405
|
+
return validation;
|
|
5406
|
+
}
|
|
5407
|
+
|
|
5361
5408
|
async function validateCompiledWorkflowResources(manifest, validation) {
|
|
5362
5409
|
for (const item of manifest.workflows || []) {
|
|
5363
5410
|
if (!item.workflowFile) continue;
|
|
@@ -9663,6 +9710,7 @@ function normalizeWorkspacePublishOptions(flags) {
|
|
|
9663
9710
|
if (since) workspaceArgs.push('--since', since);
|
|
9664
9711
|
if (dryRun) workspaceArgs.push('--dry-run');
|
|
9665
9712
|
if (force) workspaceArgs.push('--force');
|
|
9713
|
+
if (flags['legacy-form-bundle']) workspaceArgs.push('--legacy-form-bundle');
|
|
9666
9714
|
|
|
9667
9715
|
const targeted = Boolean(targetForm || targetPage || only || changed);
|
|
9668
9716
|
const includeResources =
|
|
@@ -9671,6 +9719,11 @@ function normalizeWorkspacePublishOptions(flags) {
|
|
|
9671
9719
|
return {
|
|
9672
9720
|
workspaceArgs,
|
|
9673
9721
|
dryRun,
|
|
9722
|
+
targetForm,
|
|
9723
|
+
targetPage,
|
|
9724
|
+
only,
|
|
9725
|
+
changed,
|
|
9726
|
+
legacyFormBundle: Boolean(flags['legacy-form-bundle']),
|
|
9674
9727
|
includeResources,
|
|
9675
9728
|
quietResourceSkip: !targeted || skipResources,
|
|
9676
9729
|
};
|
|
@@ -9708,6 +9761,43 @@ function runWorkspacePublish(profileName, profile, appType, publishArgs = []) {
|
|
|
9708
9761
|
}
|
|
9709
9762
|
}
|
|
9710
9763
|
|
|
9764
|
+
function isReactSpaWorkspace() {
|
|
9765
|
+
const configFile = path.join(process.cwd(), 'app-workspace.config.ts');
|
|
9766
|
+
if (!fs.existsSync(configFile)) return false;
|
|
9767
|
+
const content = fs.readFileSync(configFile, 'utf8');
|
|
9768
|
+
return /runtimeMode\s*:\s*['"]react-spa['"]/.test(content);
|
|
9769
|
+
}
|
|
9770
|
+
|
|
9771
|
+
function runReactSpaFormSchemaPublish(profileName, profile, appType, publishOptions) {
|
|
9772
|
+
const packageFile = path.join(process.cwd(), 'package.json');
|
|
9773
|
+
if (!fs.existsSync(packageFile)) {
|
|
9774
|
+
fail('当前目录没有 package.json,无法识别工作区发布脚本');
|
|
9775
|
+
}
|
|
9776
|
+
const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
|
|
9777
|
+
const scripts = pkg.scripts || {};
|
|
9778
|
+
const usePnpm = fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'));
|
|
9779
|
+
const command = usePnpm ? 'pnpm' : 'npm';
|
|
9780
|
+
const syncArgs = ['--form', publishOptions.targetForm];
|
|
9781
|
+
if (publishOptions.dryRun) syncArgs.push('--dry-run');
|
|
9782
|
+
const args = scripts['sync-schema']
|
|
9783
|
+
? ['run', 'sync-schema', '--', ...syncArgs]
|
|
9784
|
+
: usePnpm
|
|
9785
|
+
? ['exec', 'lowcode-workspace', 'sync-schema', ...syncArgs]
|
|
9786
|
+
: ['exec', '--', 'lowcode-workspace', 'sync-schema', ...syncArgs];
|
|
9787
|
+
print(
|
|
9788
|
+
`React SPA 表单 schema 发布${publishOptions.dryRun ? ' (dry-run)' : ''}: ${publishOptions.targetForm}`
|
|
9789
|
+
);
|
|
9790
|
+
const globalEnv = loadGlobalEnv();
|
|
9791
|
+
const result = spawnSync(command, args, {
|
|
9792
|
+
cwd: process.cwd(),
|
|
9793
|
+
stdio: 'inherit',
|
|
9794
|
+
env: buildWorkspacePublishEnv(profileName, profile, appType, globalEnv),
|
|
9795
|
+
});
|
|
9796
|
+
if (result.status !== 0) {
|
|
9797
|
+
process.exit(result.status || 1);
|
|
9798
|
+
}
|
|
9799
|
+
}
|
|
9800
|
+
|
|
9711
9801
|
function buildWorkspacePublishEnv(profileName, profile, appType, globalEnv) {
|
|
9712
9802
|
const env = { ...process.env };
|
|
9713
9803
|
const injectedGlobalKeys = [];
|
package/lib/design-gates.js
CHANGED
|
@@ -498,6 +498,7 @@ const RESOURCE_EXPLAINS = {
|
|
|
498
498
|
enable: true,
|
|
499
499
|
},
|
|
500
500
|
commands: [
|
|
501
|
+
'openxiangda workspace publish --profile <name> --form expense_request',
|
|
501
502
|
'openxiangda workflow compile src/workflows/expense_approval/workflow.ts --check',
|
|
502
503
|
'openxiangda workflow compile src/workflows/expense_approval/workflow.ts --out-definition src/generated/workflows/expense_approval/definition.v3.json --out-preview src/generated/workflows/expense_approval/preview.json',
|
|
503
504
|
'openxiangda resource validate workflow',
|
|
@@ -38,7 +38,7 @@ OpenXiangda supports two workspace modes. Classic `sy-lowcode-app-workspace` pub
|
|
|
38
38
|
|
|
39
39
|
### Hard rules — always
|
|
40
40
|
|
|
41
|
-
- ✅ Publish classic workspaces through `openxiangda workspace publish --profile <name>`; publish React SPA
|
|
41
|
+
- ✅ Publish classic workspaces through `openxiangda workspace publish --profile <name>`; publish React SPA forms first with `openxiangda workspace publish --form <formCode>`, then resources/runtime through `openxiangda resource validate` → `openxiangda resource publish --dry-run` → `openxiangda resource publish` → `openxiangda runtime deploy`.
|
|
42
42
|
- ✅ Architecture-class requests are plan-gated by default. For 新应用、复杂页面、登录注册、公开访问、权限数据范围、流程自动化、连接器/通知、外部集成, first run `openxiangda doctor --json` when a workspace exists and `openxiangda design gates --topic <code> --json`; then ask the user to confirm the design. Before confirmation, only read files, inspect/snapshot, dry-run, ask questions, and write/output design docs. Do not edit source files, write platform resources, send notifications, publish, or deploy.
|
|
43
43
|
- ✅ `runtime deploy` defaults to staged multipart `dist/` uploads plus a final manifest. Use `--upload-mode legacy-json` only for old platform servers.
|
|
44
44
|
- ✅ User token lives in `~/.openxiangda/profiles.json`; project state in `.openxiangda/state.json` (IDs only).
|
|
@@ -162,7 +162,7 @@ When the user provides a root domain such as `https://yida.wisejob.cn/`, use it
|
|
|
162
162
|
- Use JS_CODE for node-local cross-form data queries, create/update/batch update operations, process termination, platform API calls, external HTTP calls, and backend trigger orchestration. For logic that pages, automations, and workflows should reuse through a stable backend entry, prefer App Function. Do not use JS_CODE for simple UI interactions, ordinary form validation, or display-only page behavior.
|
|
163
163
|
- For workflow/automation JS_CODE nodes, prefer V2 `runtimeMode: "trusted_node"`. AI-authored source must be TypeScript under `src/js-code-nodes/<scriptCode>/index.ts`, `src/automations/<resourceCode>/index.ts`, or `src/functions/<functionCode>/index.ts`. `pnpm build-js-code --script <scriptCode>` runs TypeScript validation before bundling, and `sourceFile.localPath` should point to the TS source; the CLI builds, uploads, and replaces it with snapshot metadata during validate/create.
|
|
164
164
|
- Form permission group resources must have stable local codes. Use unique `code` values such as `ticket_reporter_view` and `ticket_repairer_view`; do not rely on the form code when one form has multiple groups.
|
|
165
|
-
- Workflow resources should prefer `workflowFile` + `defineWorkflow` semantic DSL over hand-written raw JSON.
|
|
165
|
+
- Workflow resources should prefer `workflowFile` + `defineWorkflow` semantic DSL over hand-written raw JSON. For workflows bound by `formCode`, run `openxiangda workspace publish --form <formCode>` first so the process form schema/table is initialized, then run `openxiangda workflow compile <workflow.ts> --check` and `openxiangda resource publish --dry-run --profile <name>` before publish; use `sdk.process.resolveCapabilities(...)` or `ProcessActionBar` / `ProcessTimeline` from `openxiangda/runtime/react` for runtime buttons and timelines instead of hard-coded operation lists.
|
|
166
166
|
- Workflow resources can be created as drafts. After resource publish, verify `openxiangda workflow list --profile <name> --json` and run `openxiangda workflow publish <workflowCode> --profile <name>` until `isPublished: true` is visible.
|
|
167
167
|
|
|
168
168
|
## Subskills
|
|
@@ -37,6 +37,7 @@ openxiangda workspace publish --profile <name>
|
|
|
37
37
|
For Phase 6 React SPA workspaces (`workspace init --runtime react-spa`), do not use `workspace publish` for frontend routes. Publish in two explicit steps:
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
+
openxiangda workspace publish --profile <name> --form <formCode> # required for forms/workflows
|
|
40
41
|
openxiangda resource validate --profile <name>
|
|
41
42
|
openxiangda resource plan --profile <name>
|
|
42
43
|
openxiangda resource publish --profile <name> --dry-run
|
package/package.json
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
ensureSchemaFormUuid,
|
|
23
23
|
getOpenApiAccessToken,
|
|
24
24
|
isOpenXiangdaMode,
|
|
25
|
+
markWorkspaceFormSchemaSynced,
|
|
25
26
|
} from "./utils/form-api.mjs";
|
|
26
27
|
import {
|
|
27
28
|
assertSchemaSyncResult,
|
|
@@ -305,6 +306,12 @@ async function main() {
|
|
|
305
306
|
} else {
|
|
306
307
|
try {
|
|
307
308
|
const response = await sendToApi(apiPayload, config, accessToken);
|
|
309
|
+
markWorkspaceFormSchemaSynced(config, form.name, apiPayload.formUuid, {
|
|
310
|
+
title: schema.formMeta?.title || form.name,
|
|
311
|
+
formType: apiPayload.formType || schema.formMeta?.formType || "receipt",
|
|
312
|
+
fieldCount: apiPayload.fieldCount,
|
|
313
|
+
tableName: response?.data?.tableName,
|
|
314
|
+
});
|
|
308
315
|
console.log(` ✅ 同步成功,tableName=${response.data.tableName}`);
|
|
309
316
|
results.push({ name: form.name, success: true, response });
|
|
310
317
|
} catch (error) {
|
|
@@ -116,6 +116,13 @@ function saveWorkspaceFormBinding(config, formName, formUuid, extra = {}) {
|
|
|
116
116
|
return true;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
export function markWorkspaceFormSchemaSynced(config, formName, formUuid, extra = {}) {
|
|
120
|
+
return saveWorkspaceFormBinding(config, formName, formUuid, {
|
|
121
|
+
...extra,
|
|
122
|
+
schemaSyncedAt: new Date().toISOString(),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
119
126
|
function readExportedStringConstant(filePath, exportName) {
|
|
120
127
|
if (!filePath || !fs.existsSync(filePath)) return "";
|
|
121
128
|
const content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -33,7 +33,7 @@ openxiangda runtime deploy --profile <name>
|
|
|
33
33
|
openxiangda commands --json
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
`pnpm deploy -- --profile <name>` 会按顺序执行 `openxiangda resource publish --profile <name>` 与 `openxiangda runtime deploy --profile <name
|
|
36
|
+
`pnpm deploy -- --profile <name>` 会按顺序执行 `openxiangda resource publish --profile <name>` 与 `openxiangda runtime deploy --profile <name>`。如果应用包含 `src/forms/<formCode>/schema.ts`,先执行 `openxiangda workspace publish --profile <name> --form <formCode>` 初始化表单 schema/table。不要直接运行 `pnpm publish:all`、`pnpm openxiangda:publish` 或 `lowcode-workspace publish-all`;这些是 legacy classic workspace 的内部发布入口,会被 `scripts/guard-publish.mjs` 拦截。
|
|
37
37
|
|
|
38
38
|
完整发布顺序:
|
|
39
39
|
|
|
@@ -4,7 +4,8 @@ const lines = [
|
|
|
4
4
|
'',
|
|
5
5
|
'错误:React SPA 工作区不要直接调用 lowcode-workspace publish-all。',
|
|
6
6
|
'',
|
|
7
|
-
'React SPA
|
|
7
|
+
'React SPA 的发布入口必须显式指定 profile:',
|
|
8
|
+
' openxiangda workspace publish --profile <name> --form <formCode> # 仅同步表单 schema',
|
|
8
9
|
' openxiangda resource publish --profile <name>',
|
|
9
10
|
' openxiangda runtime deploy --profile <name>',
|
|
10
11
|
'',
|