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 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
- runWorkspacePublish(profileName, profile, bound.appType, publishOptions.workspaceArgs);
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 = [];
@@ -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 workspaces through `openxiangda resource validate` → `openxiangda resource publish --dry-run` → `openxiangda resource publish` → `openxiangda runtime deploy`.
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. 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.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openxiangda",
3
- "version": "1.0.109",
3
+ "version": "1.0.110",
4
4
  "description": "OpenXiangda CLI, workspace build tools, runtime SDK, and form components.",
5
5
  "private": false,
6
6
  "bin": {
@@ -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>`。不要直接运行 `pnpm publish:all`、`pnpm openxiangda:publish` 或 `lowcode-workspace publish-all`;这些是 legacy classic workspace 的内部发布入口,会被 `scripts/guard-publish.mjs` 拦截。
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 的发布入口分两步,且必须显式指定 profile:',
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
  '',