openxiangda 2.0.0-alpha.98 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +23 -20
  3. package/bin/distribution/commands.js +55 -0
  4. package/bin/distribution/launcher.js +49 -0
  5. package/bin/distribution/migrate.js +60 -0
  6. package/bin/distribution/releases.js +52 -0
  7. package/bin/distribution/skills.js +80 -0
  8. package/bin/distribution/update.js +68 -0
  9. package/bin/distribution/workspace.js +85 -0
  10. package/bin/run.js +9 -11
  11. package/dist/browser/AuthoritativeSelector.d.ts +3 -2
  12. package/dist/browser/AuthoritativeSelector.d.ts.map +1 -1
  13. package/dist/browser/AuthoritativeSelector.js +39 -24
  14. package/dist/browser/AuthoritativeSelector.js.map +1 -1
  15. package/dist/browser/Shell.d.ts.map +1 -1
  16. package/dist/browser/Shell.js +24 -18
  17. package/dist/browser/Shell.js.map +1 -1
  18. package/dist/browser/admin-information-architecture.d.ts +4 -2
  19. package/dist/browser/admin-information-architecture.d.ts.map +1 -1
  20. package/dist/browser/admin-information-architecture.js +2 -2
  21. package/dist/browser/admin-information-architecture.js.map +1 -1
  22. package/dist/browser/application.d.ts.map +1 -1
  23. package/dist/browser/application.js +3 -3
  24. package/dist/browser/application.js.map +1 -1
  25. package/dist/browser/components/platform-fields/AttachmentFileList.d.ts.map +1 -1
  26. package/dist/browser/components/platform-fields/AttachmentFileList.js +5 -1
  27. package/dist/browser/components/platform-fields/AttachmentFileList.js.map +1 -1
  28. package/dist/browser/components/platform-fields/MobileFieldControls.d.ts.map +1 -1
  29. package/dist/browser/components/platform-fields/MobileFieldControls.js +2 -2
  30. package/dist/browser/components/platform-fields/MobileFieldControls.js.map +1 -1
  31. package/dist/browser/components/platform-fields/ResourceReferenceField.d.ts +4 -2
  32. package/dist/browser/components/platform-fields/ResourceReferenceField.d.ts.map +1 -1
  33. package/dist/browser/components/platform-fields/ResourceReferenceField.js +2 -2
  34. package/dist/browser/components/platform-fields/ResourceReferenceField.js.map +1 -1
  35. package/dist/browser/components/platform-fields/SubtableField.d.ts.map +1 -1
  36. package/dist/browser/components/platform-fields/SubtableField.js +60 -80
  37. package/dist/browser/components/platform-fields/SubtableField.js.map +1 -1
  38. package/dist/browser/components/platform-fields/rich-text-value.d.ts.map +1 -1
  39. package/dist/browser/components/platform-fields/rich-text-value.js +11 -1
  40. package/dist/browser/components/platform-fields/rich-text-value.js.map +1 -1
  41. package/dist/browser/components/resource/GeneratedResourceCrud.d.ts.map +1 -1
  42. package/dist/browser/components/resource/GeneratedResourceCrud.js +33 -114
  43. package/dist/browser/components/resource/GeneratedResourceCrud.js.map +1 -1
  44. package/dist/browser/components/resource/GeneratedResourceForm.d.ts +3 -1
  45. package/dist/browser/components/resource/GeneratedResourceForm.d.ts.map +1 -1
  46. package/dist/browser/components/resource/GeneratedResourceForm.js +11 -4
  47. package/dist/browser/components/resource/GeneratedResourceForm.js.map +1 -1
  48. package/dist/browser/components/resource/RecordChangeHistory.d.ts +12 -0
  49. package/dist/browser/components/resource/RecordChangeHistory.d.ts.map +1 -0
  50. package/dist/browser/components/resource/RecordChangeHistory.js +61 -0
  51. package/dist/browser/components/resource/RecordChangeHistory.js.map +1 -0
  52. package/dist/browser/components/resource/RecordDetailFrame.d.ts +30 -0
  53. package/dist/browser/components/resource/RecordDetailFrame.d.ts.map +1 -0
  54. package/dist/browser/components/resource/RecordDetailFrame.js +25 -0
  55. package/dist/browser/components/resource/RecordDetailFrame.js.map +1 -0
  56. package/dist/browser/components/resource/ResourceFormFrame.d.ts +2 -1
  57. package/dist/browser/components/resource/ResourceFormFrame.d.ts.map +1 -1
  58. package/dist/browser/components/resource/ResourceFormFrame.js +4 -4
  59. package/dist/browser/components/resource/ResourceFormFrame.js.map +1 -1
  60. package/dist/browser/components/resource/StandardResourcePages.d.ts +2 -4
  61. package/dist/browser/components/resource/StandardResourcePages.d.ts.map +1 -1
  62. package/dist/browser/components/resource/StandardResourcePages.js +8 -26
  63. package/dist/browser/components/resource/StandardResourcePages.js.map +1 -1
  64. package/dist/browser/components/resource/SurfaceFields.d.ts +5 -3
  65. package/dist/browser/components/resource/SurfaceFields.d.ts.map +1 -1
  66. package/dist/browser/components/resource/SurfaceFields.js +37 -12
  67. package/dist/browser/components/resource/SurfaceFields.js.map +1 -1
  68. package/dist/browser/components/resource/resource-import.d.ts +16 -1
  69. package/dist/browser/components/resource/resource-import.d.ts.map +1 -1
  70. package/dist/browser/components/resource/resource-import.js +58 -34
  71. package/dist/browser/components/resource/resource-import.js.map +1 -1
  72. package/dist/browser/components/resource/useResourceFormDrafts.d.ts.map +1 -1
  73. package/dist/browser/components/todo/ApplicationTodoCenterPage.d.ts +5 -2
  74. package/dist/browser/components/todo/ApplicationTodoCenterPage.d.ts.map +1 -1
  75. package/dist/browser/components/todo/ApplicationTodoCenterPage.js +19 -14
  76. package/dist/browser/components/todo/ApplicationTodoCenterPage.js.map +1 -1
  77. package/dist/browser/components/workflow/StandardWorkflowPages.d.ts +21 -5
  78. package/dist/browser/components/workflow/StandardWorkflowPages.d.ts.map +1 -1
  79. package/dist/browser/components/workflow/StandardWorkflowPages.js +175 -192
  80. package/dist/browser/components/workflow/StandardWorkflowPages.js.map +1 -1
  81. package/dist/browser/components/workflow/WorkflowRecordEditor.d.ts +2 -1
  82. package/dist/browser/components/workflow/WorkflowRecordEditor.d.ts.map +1 -1
  83. package/dist/browser/components/workflow/WorkflowRecordEditor.js +7 -4
  84. package/dist/browser/components/workflow/WorkflowRecordEditor.js.map +1 -1
  85. package/dist/browser/platform-client.d.ts +10 -4
  86. package/dist/browser/platform-client.d.ts.map +1 -1
  87. package/dist/browser/platform-client.js +78 -17
  88. package/dist/browser/platform-client.js.map +1 -1
  89. package/dist/browser/record-detail.css +115 -0
  90. package/dist/browser/runtime.d.ts.map +1 -1
  91. package/dist/browser/runtime.js +26 -2
  92. package/dist/browser/runtime.js.map +1 -1
  93. package/dist/browser/styles.css +1 -0
  94. package/dist/browser/workflow-launch.d.ts +4 -1
  95. package/dist/browser/workflow-launch.d.ts.map +1 -1
  96. package/dist/browser/workflow-launch.js +32 -0
  97. package/dist/browser/workflow-launch.js.map +1 -1
  98. package/dist/core.d.ts +1 -1
  99. package/dist/core.d.ts.map +1 -1
  100. package/dist/core.js.map +1 -1
  101. package/documentation/AGENTS.md +26 -0
  102. package/documentation/administration.md +27 -0
  103. package/documentation/application-foundation.md +162 -0
  104. package/documentation/appspec.md +152 -0
  105. package/documentation/backend.md +132 -0
  106. package/documentation/concepts.md +61 -0
  107. package/documentation/data-authz.md +62 -0
  108. package/documentation/delivery.md +110 -0
  109. package/documentation/development.md +32 -0
  110. package/documentation/field-components.md +236 -0
  111. package/documentation/frontend.md +269 -0
  112. package/documentation/getting-started.md +66 -0
  113. package/documentation/interaction-patterns.md +56 -0
  114. package/documentation/manifest.json +120 -0
  115. package/documentation/product-design.md +142 -0
  116. package/documentation/public-access.md +167 -0
  117. package/documentation/reference/cli.md +27 -0
  118. package/documentation/reference/mcp.md +649 -0
  119. package/documentation/testing.md +63 -0
  120. package/documentation/upgrading.md +39 -0
  121. package/documentation/workflow-events.md +181 -0
  122. package/launcher-skill/openxiangda/SKILL.md +24 -0
  123. package/package.json +72 -9
  124. package/releases/2.0.0.json +50 -0
  125. package/skills/manifest.json +2 -2
  126. package/skills/openxiangda-v2/SKILL.md +64 -51
  127. package/skills/openxiangda-v2/agents/openai.yaml +2 -2
  128. package/skills/openxiangda-v2/references/administration.md +27 -0
  129. package/skills/openxiangda-v2/references/application-foundation.md +162 -0
  130. package/skills/openxiangda-v2/references/appspec.md +132 -47
  131. package/skills/openxiangda-v2/references/backend.md +101 -248
  132. package/skills/openxiangda-v2/references/cli.md +27 -0
  133. package/skills/openxiangda-v2/references/concepts.md +61 -0
  134. package/skills/openxiangda-v2/references/data-authz.md +36 -388
  135. package/skills/openxiangda-v2/references/delivery.md +110 -49
  136. package/skills/openxiangda-v2/references/development.md +32 -0
  137. package/skills/openxiangda-v2/references/field-components.md +236 -0
  138. package/skills/openxiangda-v2/references/frontend.md +254 -280
  139. package/skills/openxiangda-v2/references/getting-started.md +66 -0
  140. package/skills/openxiangda-v2/references/interaction-patterns.md +56 -0
  141. package/skills/openxiangda-v2/references/mcp.md +649 -0
  142. package/skills/openxiangda-v2/references/product-design.md +142 -0
  143. package/skills/openxiangda-v2/references/public-access.md +92 -84
  144. package/skills/openxiangda-v2/references/testing.md +45 -56
  145. package/skills/openxiangda-v2/references/upgrading.md +39 -0
  146. package/skills/openxiangda-v2/references/workflow-events.md +143 -266
  147. package/skills/openxiangda-v2/references/architecture.md +0 -9
  148. package/skills/openxiangda-v2/references/commands.md +0 -21
  149. package/skills/openxiangda-v2/references/discovery.md +0 -15
  150. package/skills/openxiangda-v2/references/workspace.md +0 -62
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenXiangda contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,26 +1,29 @@
1
- # openxiangda
1
+ # OpenXiangda 2.0
2
2
 
3
- `openxiangda` is the only application-facing OpenXiangda 2.0 package. Use its
4
- stable subpaths instead of depending on physical implementation packages:
3
+ `openxiangda` 是应用开发统一安装的根包,提供 CLI、MCP、中文使用资料及同版本的 `openxiangda-v2` Skill。已有项目使用锁定版本;新项目使用明确的 2.0 版本。不要用未指定版本的全局命令判断项目行为。
5
4
 
6
- - `openxiangda/core`
7
- - `openxiangda/field-kit`
8
- - `openxiangda/react`
9
- - `openxiangda/nest`
10
- - `openxiangda/testing`
5
+ ```bash
6
+ pnpm openxiangda context --json
7
+ pnpm openxiangda docs
8
+ pnpm openxiangda docs getting-started
9
+ pnpm openxiangda skill install --workspace . --force
10
+ pnpm openxiangda dev
11
+ ```
11
12
 
12
- The package also owns the `openxiangda` CLI and the version-matched
13
- `openxiangda-v2` AI Skill distribution. OpenXiangda 1.x remains on the 1.x
14
- version line and is not a compatibility target for this package.
13
+ 应用导入统一子路径:
15
14
 
16
- ## Standard component UI
15
+ | 子路径 | 用途 |
16
+ | --- | --- |
17
+ | `openxiangda/config` | 模型、任务页、权限与应用声明 |
18
+ | `openxiangda/core` | 数据客户端与契约 |
19
+ | `openxiangda/react` | PC 页面、管理入口与标准组件 |
20
+ | `openxiangda/mobile` | 手机端组件 |
21
+ | `openxiangda/field-kit` | 字段、表单与详情 |
22
+ | `openxiangda/nest` | 按需启用的业务后端 |
23
+ | `openxiangda/testing` | 权限与业务测试辅助 |
17
24
 
18
- `OpenXiangdaApplication` includes the default Ant Design component styles, Chinese
19
- locale and contextual message/notification support. Standalone component consumers
20
- can wrap their content in `OpenXiangdaUiProvider`. Custom admin pages use
21
- `OpenXiangdaAdminPage`; business fields use `openxiangda/field-kit`.
25
+ 默认在本地运行 React,连接远端测试平台;标准 CRUD、审批和通知无需自建后端。`check` 是统一校验入口;`deploy` 已包含校验。生产通过 `deploy --environment production --from <test-deployment-id>` 复用成功测试版本。
22
26
 
23
- The platform has no appearance preferences or color configuration. Keep custom
24
- styles scoped to their components and use the library defaults. The application
25
- owns document resets. Mobile pages use `openxiangda/mobile` and its scoped
26
- stylesheet; do not import the upstream mobile root entry's document reset.
27
+ MCP 使用 `pnpm exec openxiangda --mcp-stdio --cwd <工作区绝对路径>`。先读取 workspace_context,再使用 docs_read 查询同版本中文正文。完整参数可运行 `pnpm openxiangda docs mcp`。
28
+
29
+ `OpenXiangdaApplication` 包含默认 Ant Design 样式、中文语言和消息上下文;独立组件可使用 `OpenXiangdaUiProvider`。管理页面使用 `OpenXiangdaAdminPage`,字段使用 Field Kit。自定义样式限制在组件内,应用负责全局重置;手机端只导入本包的局部样式,不引入上游移动库的全局重置。
@@ -0,0 +1,55 @@
1
+ import { bundledRelease, registryMetadata, VERSION } from './releases.js';
2
+ import { flagValue, fail } from './workspace.js';
3
+ import { update } from './update.js';
4
+ import { assessMigration } from './migrate.js';
5
+ import { installDistributionSkills } from './skills.js';
6
+
7
+ export async function distributionCommand(context, args) {
8
+ const command = args[0];
9
+ const skillInstall = command === 'skill' && args[1] === 'install';
10
+ if (!['version', 'update', 'changelog', 'migrate'].includes(command) && !skillInstall) return false;
11
+ validateArguments(command, args.slice(1));
12
+ if (args.includes('--help') || args.includes('-h')) {
13
+ process.stdout.write('openxiangda version [--json]\nopenxiangda update check|install [--target workspace|launcher] [--dry-run] [--json]\nopenxiangda changelog [version] [--json]\nopenxiangda migrate assess --to v2 [--json]\nopenxiangda skill install [--workspace <directory> | --destination <directory>] [--agent codex|claude|qoder|dual] [--force] [--dry-run]\n以上命令支持 --cwd <directory>。新应用默认 V2,项目升级保持原代际。\n');
14
+ return true;
15
+ }
16
+ let data;
17
+ if (skillInstall) data = await installDistributionSkills(context, args);
18
+ if (command === 'version') data = {
19
+ productVersion: context.manifest.version,
20
+ workspace: context.workspace,
21
+ engine: { version: context.engine.version, generation: context.engine.generation, source: context.engine.source, packageRoot: context.engine.packageRoot, declaredVersion: context.engine.declaredVersion },
22
+ nodeVersion: process.versions.node,
23
+ };
24
+ if (command === 'update') data = update(context, args);
25
+ if (command === 'migrate') data = assessMigration(context, args);
26
+ if (command === 'changelog') {
27
+ const requested = args[1] && !args[1].startsWith('-') ? args[1] : context.engine.version;
28
+ if (!VERSION.test(requested)) fail('DISTRIBUTION_VERSION_INVALID', requested);
29
+ data = bundledRelease(context.engine.packageRoot, requested);
30
+ if (data.available === false && requested !== context.engine.version) {
31
+ const manifest = registryMetadata(requested, flagValue(args, '--registry'));
32
+ data = manifest.openxiangdaRelease || data;
33
+ }
34
+ }
35
+ const result = { schemaVersion: skillInstall ? 'openxiangda.cli-result/v2' : 'openxiangda.distribution/v1', ok: true, operation: skillInstall ? 'skill install' : command, data };
36
+ process.stdout.write(`${args.includes('--json') ? JSON.stringify(result) : JSON.stringify(data, null, 2)}\n`);
37
+ return true;
38
+ }
39
+
40
+ function validateArguments(command, args) {
41
+ const booleans = new Set(['--json', '--help', '-h', ...(command === 'skill' ? ['--force', '--dry-run'] : command === 'update' ? ['--dry-run'] : [])]);
42
+ const values = new Set(['--cwd', ...({ update: ['--target', '--registry'], changelog: ['--registry'], migrate: ['--to'], skill: ['--workspace', '--destination', '--dest', '--agent'] }[command] || [])]);
43
+ const seen = new Set(); let positionals = 0;
44
+ for (let i = 0; i < args.length; i++) {
45
+ const argument = args[i], key = argument.split('=')[0];
46
+ if (!argument.startsWith('-')) { positionals++; continue; }
47
+ if (seen.has(key)) fail('DISTRIBUTION_ARGUMENT_INVALID', `重复参数 ${key}`);
48
+ seen.add(key);
49
+ if (booleans.has(argument)) continue;
50
+ if (!values.has(key)) fail('DISTRIBUTION_ARGUMENT_INVALID', argument);
51
+ if (argument.includes('=')) { if (!argument.slice(key.length + 1)) fail('DISTRIBUTION_ARGUMENT_REQUIRED', key); }
52
+ else if (!args[++i] || args[i].startsWith('-')) fail('DISTRIBUTION_ARGUMENT_REQUIRED', key);
53
+ }
54
+ if (positionals > (command === 'version' ? 0 : 1)) fail('DISTRIBUTION_ARGUMENT_INVALID', '多余的位置参数');
55
+ }
@@ -0,0 +1,49 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { spawn } from 'node:child_process';
4
+ import { discoverWorkspace, flagValue, readJson, resolveEngine } from './workspace.js';
5
+ import { distributionCommand } from './commands.js';
6
+
7
+ export async function launch(packageRoot, args = process.argv.slice(2)) {
8
+ const manifest = readJson(join(packageRoot, 'package.json'));
9
+ const workspace = discoverWorkspace(flagValue(args, '--cwd') || (args[0] === 'skill' ? flagValue(args, '--workspace') : null) || process.cwd());
10
+ // Launcher-only updates must also work when a project's dependencies are not yet installed.
11
+ const launcherUpdate = args[0] === 'update' && flagValue(args, '--target') === 'launcher';
12
+ const engine = resolveEngine(launcherUpdate ? null : workspace, packageRoot);
13
+ const context = { manifest, packageRoot, workspace, engine };
14
+ if (await distributionCommand(context, args)) return;
15
+ if (!args.length || args[0] === '--help' || args[0] === '-h') {
16
+ process.stdout.write('统一分发命令:version、update check|install、changelog、migrate assess。\nSkill 安装支持 --workspace、--destination 和 --agent;正文跟随项目版本。\n\n');
17
+ }
18
+ const env = { ...process.env };
19
+ for (const key of ['OPENXIANGDA_DISTRIBUTION_NAME', 'OPENXIANGDA_DISTRIBUTION_VERSION', 'OPENXIANGDA_DOCUMENTATION_ROOT', 'OPENXIANGDA_SKILLS_ROOT']) delete env[key];
20
+ if (engine.generation === 'v2') {
21
+ env.OPENXIANGDA_DISTRIBUTION_NAME = 'openxiangda';
22
+ env.OPENXIANGDA_DISTRIBUTION_VERSION = engine.version;
23
+ env.OPENXIANGDA_DOCUMENTATION_ROOT = join(engine.packageRoot, 'documentation');
24
+ if (existsSync(join(engine.packageRoot, 'skills'))) env.OPENXIANGDA_SKILLS_ROOT = join(engine.packageRoot, 'skills');
25
+ }
26
+ const forwarded = engine.generation === 'v1' ? removeCwd(args) : args;
27
+ const child = spawn(process.execPath, [engine.entry, ...forwarded], {
28
+ cwd: engine.generation === 'v1' ? workspace?.root || process.cwd() : process.cwd(), env, stdio: 'inherit',
29
+ });
30
+ const signals = ['SIGINT', 'SIGTERM'];
31
+ const handlers = signals.map(signal => () => child.kill(signal));
32
+ signals.forEach((signal, i) => process.on(signal, handlers[i]));
33
+ try {
34
+ const { code, signal } = await new Promise((resolve, reject) => {
35
+ child.once('error', reject);
36
+ child.once('exit', (code, signal) => resolve({ code, signal }));
37
+ });
38
+ process.exitCode = code ?? (signal === 'SIGINT' ? 130 : 143);
39
+ } finally { signals.forEach((signal, i) => process.removeListener(signal, handlers[i])); }
40
+ }
41
+
42
+ function removeCwd(args) {
43
+ const output = [];
44
+ for (let i = 0; i < args.length; i++) {
45
+ if (args[i] === '--cwd') i++;
46
+ else if (!args[i].startsWith('--cwd=')) output.push(args[i]);
47
+ }
48
+ return output;
49
+ }
@@ -0,0 +1,60 @@
1
+ import { readdirSync, lstatSync, readFileSync } from 'node:fs';
2
+ import { join, relative } from 'node:path';
3
+ import { fail, flagValue } from './workspace.js';
4
+
5
+ export function assessMigration(context, args) {
6
+ if (args[1] !== 'assess' || flagValue(args, '--to') !== 'v2') fail('DISTRIBUTION_MIGRATION_ARGUMENT_INVALID', '用法:openxiangda migrate assess --to v2');
7
+ if (context.workspace?.generation !== 'v1') fail('DISTRIBUTION_MIGRATION_V1_REQUIRED', '请在 V1 工作区内进行迁移评估');
8
+ const root = context.workspace.root;
9
+ const inventory = { resources: [], workflows: [], permissions: [], pages: [], runtime: [], data: [] };
10
+ let files = 0, bytes = 0, entries = 0;
11
+ const skipped = [];
12
+ const excluded = new Set(['node_modules', '.git', '.codegraph', 'dist', 'build', 'coverage', '.openxiangda', '.agents', '.codex', '.claude', '.qoder', '.cursor', '.env', 'secrets', 'logs']);
13
+ function visit(directory, depth = 0) {
14
+ if (depth > 12) { skipped.push(relative(root, directory)); return; }
15
+ for (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
16
+ if (++entries > 20000) fail('DISTRIBUTION_MIGRATION_SCAN_LIMIT', '目录超过 20000 个条目;请按应用拆分评估范围');
17
+ if (excluded.has(entry.name) || entry.name.startsWith('.env')) continue;
18
+ const file = join(directory, entry.name), path = relative(root, file);
19
+ if (entry.isSymbolicLink()) { skipped.push(path); continue; }
20
+ if (entry.isDirectory()) { visit(file, depth + 1); continue; }
21
+ if (!/\.(?:[cm]?[jt]sx?|json|ya?ml|sql)$/.test(entry.name) || /lock\.json$/.test(entry.name)) continue;
22
+ const size = lstatSync(file).size;
23
+ if (++files > 5000 || bytes + size > 20 * 1024 * 1024) fail('DISTRIBUTION_MIGRATION_SCAN_LIMIT', '工作区超过 5000 个文件或 20 MiB;请按应用拆分评估范围');
24
+ if (size > 1024 * 1024) { skipped.push(path); continue; }
25
+ bytes += size;
26
+ const text = readFileSync(file, 'utf8');
27
+ // Return source pointers, never credentials, customer records or executable config values.
28
+ if (/resource|form/i.test(path) || /defineResource|formUuid/.test(text)) inventory.resources.push(path);
29
+ if (/workflow|process|automation/i.test(path) || /JS_CODE|approver/.test(text)) inventory.workflows.push(path);
30
+ if (/permission|authz|role/i.test(path) || /permissionGroup|permissionCode/.test(text)) inventory.permissions.push(path);
31
+ if (/page|route|\.tsx$|\.jsx$/i.test(path)) inventory.pages.push(path);
32
+ if (/function|server|runtime|backend/i.test(path)) inventory.runtime.push(path);
33
+ if (/migration|schema|\.sql$/i.test(path)) inventory.data.push(path);
34
+ }
35
+ }
36
+ visit(root);
37
+ return {
38
+ schemaVersion: 'openxiangda.migration-assessment/v1', from: 'v1', to: 'v2', root, readOnly: true,
39
+ engineVersion: context.engine.version, scanned: { files, bytes }, complete: skipped.length === 0, skipped, inventory,
40
+ recommendation: {
41
+ preferredGeneration: 'v2',
42
+ policy: 'V2 能力满足、项目仍在测试阶段且迁移成本可控时,优先建议采用 V2。',
43
+ decision: '条件待确认;本地源码指针不能证明能力覆盖、上线阶段或迁移成本。',
44
+ criteria: [
45
+ { code: 'capability-fit', status: 'needs-confirmation', question: 'V2 是否覆盖项目需要的模型、页面、权限、流程与外部集成?' },
46
+ { code: 'project-stage', status: 'needs-confirmation', question: '项目是否仍在测试阶段,已有多少真实数据和在途流程?' },
47
+ { code: 'migration-cost', status: 'needs-confirmation', question: '重建、数据映射、测试和切换的成本是否可控?' },
48
+ ],
49
+ },
50
+ assessment: [
51
+ { area: 'resources', pointers: inventory.resources.length, decision: '逐项确认业务模型、字段、关联和服务端约束,再映射到 V2 Data API;V1 表单标识不直接作为 V2 模型。' },
52
+ { area: 'workflows', pointers: inventory.workflows.length, decision: '核对节点、审批人规则、JS_CODE 副作用、通知和在途实例;在途流程须制定独立切换方案。' },
53
+ { area: 'permissions', pointers: inventory.permissions.length, decision: '由项目确认角色并集、数据范围和字段权限,按真实角色回归;不可直接复制旧权限组。' },
54
+ { area: 'data', pointers: inventory.data.length, decision: '此次只检查本地源码。远端数据量、附件、主键映射和历史流程尚未读取,需另行授权盘点、校验和回滚演练。' },
55
+ { area: 'experience', pointers: inventory.pages.length, decision: '先确认 AppSpec 产品、页面和权限设计基线,再逐页重建并验收 PC/移动端。' },
56
+ ],
57
+ nextSteps: ['确认范围与源环境', '完成详细设计和迁移映射', '在测试环境演练并核对数量、摘要和权限', '冻结切换窗口与回滚条件', '逐项目审批实施'],
58
+ migrated: false,
59
+ };
60
+ }
@@ -0,0 +1,52 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { fail, readJson } from './workspace.js';
5
+
6
+ export const VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
7
+ export const RELEASE_REPOSITORY = 'https://github.com/1377385356/openxiangda';
8
+ export const GENERATION_CHANNEL = { v1: 'legacy-v1', v2: 'stable-v2' };
9
+
10
+ export function bundledRelease(packageRoot, version) {
11
+ const file = join(packageRoot, 'releases', `${version}.json`);
12
+ if (existsSync(file)) return readJson(file);
13
+ const manifest = readJson(join(packageRoot, 'package.json'));
14
+ return (manifest.openxiangdaRelease?.version === version ? manifest.openxiangdaRelease : null) || {
15
+ version, available: false, url: `${RELEASE_REPOSITORY}/releases/tag/v${version}`,
16
+ summary: '此历史版本未随包提供结构化更新说明。',
17
+ };
18
+ }
19
+
20
+ export function registryMetadata(selector, registry = 'https://registry.npmjs.org') {
21
+ selector = GENERATION_CHANNEL[selector] || selector;
22
+ if (!VERSION.test(selector) && !['legacy-v1', 'stable-v2', 'latest', 'alpha'].includes(selector)) fail('DISTRIBUTION_VERSION_INVALID', selector);
23
+ const url = new URL(registry);
24
+ if (url.username || url.password || url.protocol !== 'https:' && !['127.0.0.1', 'localhost'].includes(url.hostname)) fail('DISTRIBUTION_REGISTRY_INVALID', 'registry 必须为 HTTPS 地址');
25
+ const result = spawnSync('npm', ['view', `openxiangda@${selector}`, '--json', `--registry=${url.href}`], {
26
+ encoding: 'utf8', timeout: 30000, maxBuffer: 4 * 1024 * 1024,
27
+ env: { ...process.env, npm_config_fetch_retries: '0', npm_config_fetch_timeout: '20000' },
28
+ });
29
+ if (result.error || result.status !== 0) fail('DISTRIBUTION_REGISTRY_UNAVAILABLE', '无法读取版本资料;本地命令与随包说明仍可使用');
30
+ let manifest;
31
+ try { manifest = JSON.parse(result.stdout); } catch { fail('DISTRIBUTION_REGISTRY_RESPONSE_INVALID', 'npm 未返回有效 JSON'); }
32
+ if (!VERSION.test(manifest.version || '') || manifest.name !== 'openxiangda') fail('DISTRIBUTION_REGISTRY_RESPONSE_INVALID', 'npm 返回了不匹配的包');
33
+ return manifest;
34
+ }
35
+
36
+ export function compareVersions(left, right) {
37
+ const parse = version => { const [base, ...pre] = version.split('-'); return { base: base.split('.').map(Number), pre: pre.join('-') }; };
38
+ const a = parse(left), b = parse(right);
39
+ for (let i = 0; i < 3; i++) if (a.base[i] !== b.base[i]) return Math.sign(a.base[i] - b.base[i]);
40
+ if (a.pre === b.pre) return 0;
41
+ if (!a.pre || !b.pre) return a.pre ? -1 : 1;
42
+ const ap = a.pre.split('.'), bp = b.pre.split('.');
43
+ for (let i = 0; i < Math.max(ap.length, bp.length); i++) {
44
+ if (ap[i] === bp[i]) continue;
45
+ if (ap[i] === undefined || bp[i] === undefined) return ap[i] === undefined ? -1 : 1;
46
+ const an = /^\d+$/.test(ap[i]), bn = /^\d+$/.test(bp[i]);
47
+ if (an && bn) return Math.sign(Number(ap[i]) - Number(bp[i]));
48
+ if (an !== bn) return an ? -1 : 1;
49
+ return ap[i] < bp[i] ? -1 : 1;
50
+ }
51
+ return 0;
52
+ }
@@ -0,0 +1,80 @@
1
+ import { cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { homedir, tmpdir } from 'node:os';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ import { createRequire } from 'node:module';
5
+ import { pathToFileURL } from 'node:url';
6
+ import { fail, flagValue, readJson } from './workspace.js';
7
+
8
+ export async function installDistributionSkills(context, args) {
9
+ const workspace = flagValue(args, '--workspace');
10
+ const destination = flagValue(args, '--destination') || flagValue(args, '--dest');
11
+ const agent = flagValue(args, '--agent') || 'codex';
12
+ if (!['codex', 'claude', 'qoder', 'dual'].includes(agent)) fail('DISTRIBUTION_SKILL_AGENT_INVALID', agent);
13
+ const homes = { codex: process.env.CODEX_HOME || join(homedir(), '.codex'), claude: process.env.CLAUDE_HOME || join(homedir(), '.claude'), qoder: process.env.QODER_HOME || join(homedir(), '.qoder') };
14
+ const destinations = destination ? [resolve(destination)] : workspace ? [resolve(workspace, '.agents/skills')] : (agent === 'dual' ? Object.values(homes) : [homes[agent]]).map(path => join(path, 'skills'));
15
+ const staging = mkdtempSync(join(tmpdir(), 'openxiangda-skills-'));
16
+ const source = join(staging, 'skills');
17
+ try {
18
+ const guidanceTemplate = workspace && context.engine.generation === 'v2' && !args.includes('--dry-run')
19
+ ? readFileSync(join(context.engine.packageRoot, 'documentation/AGENTS.md'), 'utf8') : undefined;
20
+ if (context.engine.generation === 'v1') {
21
+ const require = createRequire(join(context.engine.packageRoot, 'package.json'));
22
+ const { installSkills } = require(join(context.engine.packageRoot, 'lib/skills.js'));
23
+ installSkills({ agent: 'codex', dest: source, env: { ...process.env, CODEX_HOME: staging } });
24
+ // Historical V1 packages named their root skill openxiangda. Preserve its content under V1 ownership.
25
+ if (existsSync(join(source, 'openxiangda'))) {
26
+ renameSync(join(source, 'openxiangda'), join(source, 'openxiangda-v1'));
27
+ const file = join(source, 'openxiangda-v1/SKILL.md');
28
+ writeFileSync(file, readFileSync(file, 'utf8').replace(/^name: openxiangda$/m, 'name: openxiangda-v1'));
29
+ }
30
+ } else {
31
+ const { installSkills } = await importEngineModule(context.engine.packageRoot, 'openxiangda-skill-kit');
32
+ await installSkills({ skillsRoot: join(context.engine.packageRoot, 'skills'), destination: source });
33
+ }
34
+ const router = join(source, 'openxiangda');
35
+ cpSync(join(context.packageRoot, 'launcher-skill/openxiangda'), router, { recursive: true });
36
+ const file = join(router, 'SKILL.md');
37
+ writeFileSync(file, readFileSync(file, 'utf8').replaceAll('__OPENXIANGDA_VERSION__', context.manifest.version));
38
+ const names = readdirSync(source, { withFileTypes: true }).filter(entry => entry.isDirectory()).map(entry => entry.name).sort();
39
+ for (const dest of destinations) for (const name of names) {
40
+ if (existsSync(join(dest, name)) && !args.includes('--force')) fail('SKILL_ALREADY_EXISTS', `${join(dest, name)};使用 --force 显式替换`);
41
+ }
42
+ if (!args.includes('--dry-run')) for (const dest of destinations) installSet(source, dest, names);
43
+ let guidance;
44
+ if (workspace && context.engine.generation === 'v2' && !args.includes('--dry-run')) {
45
+ const { refreshWorkspaceGuidance } = await importEngineModule(context.engine.packageRoot, 'openxiangda-skill-kit');
46
+ guidance = refreshWorkspaceGuidance(resolve(workspace), guidanceTemplate);
47
+ }
48
+ return { destinations, installed: names, engineVersion: context.engine.version, generation: context.engine.generation, dryRun: args.includes('--dry-run'), ...(guidance ? { guidance } : {}) };
49
+ } finally { rmSync(staging, { recursive: true, force: true }); }
50
+ }
51
+
52
+ function importEngineModule(packageRoot, name) {
53
+ const require = createRequire(join(packageRoot, 'package.json'));
54
+ // These ESM packages do not expose package.json to require.resolve.
55
+ // Resolve from this engine's dependency paths, never the launcher's imports.
56
+ const file = require.resolve.paths(name).map(base => join(base, name, 'package.json')).find(existsSync);
57
+ if (!file) fail('DISTRIBUTION_ENGINE_MODULE_MISSING', name);
58
+ const manifest = readJson(file);
59
+ const entry = manifest.exports?.['.']?.import || manifest.main;
60
+ if (manifest.name !== name || typeof entry !== 'string' || !entry.startsWith('./') || !resolve(dirname(file), entry).startsWith(`${dirname(file)}/`)) fail('DISTRIBUTION_ENGINE_MODULE_INVALID', name);
61
+ return import(pathToFileURL(resolve(dirname(file), entry)).href);
62
+ }
63
+
64
+ function installSet(source, destination, names) {
65
+ mkdirSync(destination, { recursive: true });
66
+ const transaction = mkdtempSync(join(destination, '.openxiangda-distribution-'));
67
+ const installed = [], backups = [];
68
+ try {
69
+ for (const name of names) cpSync(join(source, name), join(transaction, 'next', name), { recursive: true });
70
+ for (const name of names) {
71
+ const target = join(destination, name), backup = join(transaction, 'previous', name);
72
+ if (existsSync(target)) { mkdirSync(dirname(backup), { recursive: true }); renameSync(target, backup); backups.push({ target, backup }); }
73
+ renameSync(join(transaction, 'next', name), target); installed.push(target);
74
+ }
75
+ } catch (error) {
76
+ for (const target of installed.reverse()) rmSync(target, { recursive: true, force: true });
77
+ for (const { target, backup } of backups.reverse()) renameSync(backup, target);
78
+ throw error;
79
+ } finally { rmSync(transaction, { recursive: true, force: true }); }
80
+ }
@@ -0,0 +1,68 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { bundledRelease, compareVersions, registryMetadata, VERSION, GENERATION_CHANNEL } from './releases.js';
5
+ import { fail, flagValue, readJson } from './workspace.js';
6
+
7
+ export function updatePlan(context, args, metadata) {
8
+ const target = flagValue(args, '--target') || (context.workspace ? 'workspace' : 'launcher');
9
+ if (!['workspace', 'launcher'].includes(target)) fail('DISTRIBUTION_UPDATE_TARGET_INVALID', target);
10
+ if (target === 'workspace' && !context.workspace) fail('DISTRIBUTION_WORKSPACE_REQUIRED', '请在项目中执行 workspace 升级');
11
+ const generation = target === 'launcher' ? 'v2' : context.workspace.generation;
12
+ if (!VERSION.test(metadata.version) || !metadata.version.startsWith(generation === 'v1' ? '1.' : '2.') || metadata.version.includes('-')) {
13
+ fail('DISTRIBUTION_UPDATE_GENERATION_MISMATCH', `${generation} 稳定渠道返回了 ${metadata.version}`);
14
+ }
15
+ const currentVersion = target === 'launcher' ? context.manifest.version : context.engine.version;
16
+ const plan = {
17
+ target, generation, channel: GENERATION_CHANNEL[generation], currentVersion, version: metadata.version,
18
+ updateAvailable: compareVersions(metadata.version, currentVersion) > 0,
19
+ compatibility: metadata.engines || {}, release: metadata.openxiangdaRelease || { version: metadata.version, available: false },
20
+ cwd: target === 'workspace' ? context.workspace.root : process.cwd(),
21
+ command: 'npm', args: ['install', '--global', `openxiangda@${metadata.version}`],
22
+ };
23
+ if (target === 'workspace') {
24
+ const manifestFile = join(context.workspace.root, 'package.json');
25
+ if (!existsSync(manifestFile)) fail('DISTRIBUTION_PACKAGE_MANIFEST_REQUIRED', '项目缺少 package.json,不能自动决定依赖归属');
26
+ const manifest = readJson(manifestFile);
27
+ const locks = ['pnpm-lock.yaml', 'package-lock.json', 'yarn.lock'].filter(file => existsSync(join(context.workspace.root, file)));
28
+ if (locks.length > 1) fail('DISTRIBUTION_PACKAGE_MANAGER_CONFLICT', locks.join(', '));
29
+ const manager = manifest.packageManager?.split('@')[0] || ({ 'pnpm-lock.yaml': 'pnpm', 'package-lock.json': 'npm', 'yarn.lock': 'yarn' }[locks[0]]) || 'npm';
30
+ if (!['npm', 'pnpm', 'yarn'].includes(manager)) fail('DISTRIBUTION_PACKAGE_MANAGER_UNSUPPORTED', manager);
31
+ const dependency = manifest.dependencies?.openxiangda;
32
+ const spec = `openxiangda@${metadata.version}`;
33
+ plan.command = manager;
34
+ plan.args = manager === 'npm' ? ['install', dependency ? '--save-prod' : '--save-dev', '--save-exact', spec]
35
+ : ['add', ...(dependency ? [] : ['--dev']), '--exact', spec, ...(manager === 'pnpm' && existsSync(join(context.workspace.root, 'pnpm-workspace.yaml')) ? ['--workspace-root'] : [])];
36
+ if (manager === 'pnpm' && existsSync(join(context.workspace.root, 'pnpm-workspace.yaml'))) {
37
+ plan.args = ['--recursive', '--include-workspace-root', 'update', '--save-exact', spec];
38
+ }
39
+ }
40
+ const registry = flagValue(args, '--registry') || 'https://registry.npmjs.org';
41
+ plan.args.push(`--registry=${registry}`);
42
+ return plan;
43
+ }
44
+
45
+ export function update(context, args) {
46
+ const action = args[1] && !args[1].startsWith('-') ? args[1] : 'check';
47
+ if (!['check', 'install'].includes(action)) fail('DISTRIBUTION_UPDATE_ACTION_INVALID', action);
48
+ const target = flagValue(args, '--target') || (context.workspace ? 'workspace' : 'launcher');
49
+ if (!['workspace', 'launcher'].includes(target)) fail('DISTRIBUTION_UPDATE_TARGET_INVALID', target);
50
+ if (target === 'workspace' && !context.workspace) fail('DISTRIBUTION_WORKSPACE_REQUIRED', '请在项目中执行 workspace 升级');
51
+ const channel = GENERATION_CHANNEL[target === 'launcher' ? 'v2' : context.workspace?.generation || 'v2'];
52
+ let metadata;
53
+ try { metadata = registryMetadata(channel, flagValue(args, '--registry')); }
54
+ catch (error) {
55
+ if (action === 'install') throw error;
56
+ return { status: 'offline', target, channel, error: { code: error.code, message: error.message }, release: bundledRelease(context.engine.packageRoot, context.engine.version) };
57
+ }
58
+ const plan = updatePlan(context, args, metadata);
59
+ if (action === 'check' || args.includes('--dry-run')) return { status: 'checked', ...plan, executed: false };
60
+ if (!plan.updateAvailable) return { status: 'current', ...plan, executed: false };
61
+ const requirement = metadata.engines?.node;
62
+ // Published stable distributions use a lower-bound Node contract; reject unknown expressions instead of guessing.
63
+ if (requirement && !/^>=\d+(?:\.\d+){0,2}$/.test(requirement)) fail('DISTRIBUTION_NODE_REQUIREMENT_UNSUPPORTED', requirement);
64
+ if (requirement && compareVersions(process.versions.node, requirement.slice(2).split('.').concat(['0', '0']).slice(0, 3).join('.')) < 0) fail('DISTRIBUTION_NODE_VERSION_REQUIRED', `目标版本需要 Node.js ${requirement}`);
65
+ const result = spawnSync(plan.command, plan.args, { cwd: plan.cwd, stdio: args.includes('--json') ? ['inherit', 'pipe', 'pipe'] : 'inherit', timeout: 600000, maxBuffer: 8 * 1024 * 1024 });
66
+ if (result.error || result.status !== 0) fail('DISTRIBUTION_UPDATE_INSTALL_FAILED', '包管理器未完成升级;保留项目与锁文件现场,请检查后使用原计划恢复');
67
+ return { status: 'installed', ...plan, executed: true, next: plan.target === 'workspace' ? '按项目说明执行 check 和业务验收;需要刷新 Skill 时显式运行 skill install。' : '重新运行 openxiangda version --json 核对入口和项目引擎。' };
68
+ }
@@ -0,0 +1,85 @@
1
+ import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import { createRequire } from 'node:module';
4
+
5
+ export function fail(code, message) {
6
+ throw Object.assign(new Error(`${code}: ${message}`), { code });
7
+ }
8
+
9
+ export function readJson(file) {
10
+ if (statSync(file).size > 2 * 1024 * 1024) fail('DISTRIBUTION_FILE_TOO_LARGE', file);
11
+ try { return JSON.parse(readFileSync(file, 'utf8')); }
12
+ catch { fail('DISTRIBUTION_JSON_INVALID', file); }
13
+ }
14
+
15
+ export function flagValue(args, name) {
16
+ const values = [];
17
+ for (let i = 0; i < args.length && args[i] !== '--'; i++) {
18
+ if (args[i] === name) {
19
+ if (!args[i + 1] || args[i + 1].startsWith('--')) fail('DISTRIBUTION_ARGUMENT_REQUIRED', name);
20
+ values.push(args[++i]);
21
+ } else if (args[i].startsWith(`${name}=`)) values.push(args[i].slice(name.length + 1));
22
+ }
23
+ if (values.length > 1 || values.some(value => !value)) fail('DISTRIBUTION_ARGUMENT_INVALID', name);
24
+ return values[0];
25
+ }
26
+
27
+ // Discovery reads markers only. Loading either generation's executable config belongs to its engine.
28
+ export function discoverWorkspace(cwd) {
29
+ let root = realpathSync(resolve(cwd));
30
+ if (!statSync(root).isDirectory()) fail('WORKSPACE_DIRECTORY_REQUIRED', root);
31
+ while (true) {
32
+ const v2 = ['openxiangda.config.ts', 'openxiangda-app.config.ts'].filter(file => existsSync(join(root, file)));
33
+ const v1 = ['app-workspace.config.ts'].filter(file => existsSync(join(root, file)));
34
+ const binding = join(root, '.openxiangda/state.json');
35
+ if (existsSync(binding)) {
36
+ const state = readJson(binding);
37
+ if (state.version === 1 && state.profiles && typeof state.profiles === 'object') v1.push('.openxiangda/state.json');
38
+ }
39
+ if (v1.length && v2.length || v2.length > 1) fail('WORKSPACE_GENERATION_CONFLICT', `${root} 同时存在冲突的工作区标记:${[...v1, ...v2].join(', ')}`);
40
+ if (v1.length || v2.length) return { root, generation: v1.length ? 'v1' : 'v2', markers: [...v1, ...v2] };
41
+ const parent = dirname(root);
42
+ if (parent === root) return null;
43
+ root = parent;
44
+ }
45
+ }
46
+
47
+ export function packageEngine(packageRoot, generation, source, declaredVersion = null) {
48
+ const manifest = readJson(join(packageRoot, 'package.json'));
49
+ const major = generation === 'v1' ? '1' : '2';
50
+ if (manifest.name !== 'openxiangda' || !manifest.version?.startsWith(`${major}.`)) {
51
+ fail('WORKSPACE_ENGINE_GENERATION_MISMATCH', `${packageRoot} 需要 ${generation} openxiangda`);
52
+ }
53
+ if (/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(declaredVersion || '') && manifest.version !== declaredVersion) {
54
+ fail('WORKSPACE_ENGINE_PIN_MISMATCH', `项目声明 ${declaredVersion},已安装 ${manifest.version};请先按锁文件安装依赖`);
55
+ }
56
+ let entry;
57
+ if (generation === 'v1') {
58
+ const bin = typeof manifest.bin === 'string' ? manifest.bin : manifest.bin?.openxiangda;
59
+ if (!bin) fail('WORKSPACE_ENGINE_BIN_MISSING', packageRoot);
60
+ entry = resolve(packageRoot, bin);
61
+ if (!entry.startsWith(`${realpathSync(packageRoot)}/`) && !entry.startsWith(`${resolve(packageRoot)}/`)) fail('WORKSPACE_ENGINE_BIN_INVALID', entry);
62
+ } else {
63
+ try { entry = createRequire(join(packageRoot, 'package.json')).resolve('openxiangda-cli/run'); }
64
+ catch { fail('WORKSPACE_ENGINE_DEPENDENCY_MISSING', `${packageRoot}: openxiangda-cli`); }
65
+ }
66
+ if (!existsSync(entry)) fail('WORKSPACE_ENGINE_BIN_MISSING', entry);
67
+ return { generation, version: manifest.version, source, packageRoot: realpathSync(packageRoot), entry, declaredVersion };
68
+ }
69
+
70
+ export function resolveEngine(workspace, launcherRoot) {
71
+ const generation = workspace?.generation || 'v2';
72
+ if (workspace) {
73
+ const manifestFile = join(workspace.root, 'package.json');
74
+ const manifest = existsSync(manifestFile) ? readJson(manifestFile) : {};
75
+ const declaredVersion = manifest.dependencies?.openxiangda || manifest.devDependencies?.openxiangda;
76
+ const installed = join(workspace.root, 'node_modules/openxiangda');
77
+ if (existsSync(join(installed, 'package.json'))) return packageEngine(installed, generation, 'workspace', declaredVersion);
78
+ if (declaredVersion) fail('WORKSPACE_ENGINE_NOT_INSTALLED', `${workspace.root} 已声明 openxiangda;请先按项目锁文件安装依赖`);
79
+ }
80
+ if (generation === 'v2') return packageEngine(launcherRoot, 'v2', 'launcher');
81
+ let legacyManifest;
82
+ try { legacyManifest = createRequire(join(launcherRoot, 'package.json')).resolve('openxiangda-legacy/package.json'); }
83
+ catch { fail('DISTRIBUTION_LEGACY_ENGINE_MISSING', '请重新安装完整的统一入口'); }
84
+ return packageEngine(dirname(legacyManifest), 'v1', 'bundled-v1');
85
+ }
package/bin/run.js CHANGED
@@ -1,16 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readFileSync } from 'node:fs';
3
- import { dirname, join } from 'node:path';
2
+ import { dirname } from 'node:path';
4
3
  import { fileURLToPath } from 'node:url';
4
+ import { launch } from './distribution/launcher.js';
5
5
 
6
6
  const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
7
- const packageManifest = JSON.parse(
8
- readFileSync(join(packageRoot, 'package.json'), 'utf8')
9
- );
10
- process.env.OPENXIANGDA_DISTRIBUTION_NAME = packageManifest.name;
11
- process.env.OPENXIANGDA_DISTRIBUTION_VERSION = packageManifest.version;
12
- const packagedSkills = join(packageRoot, 'skills');
13
- if (existsSync(packagedSkills)) {
14
- process.env.OPENXIANGDA_SKILLS_ROOT ||= packagedSkills;
7
+ try {
8
+ if (Number(process.versions.node.split('.')[0]) < 24) throw Object.assign(new Error('统一入口需要 Node.js 24 或更高版本;已有 V1 项目仍可使用其原工具链。'), { code: 'DISTRIBUTION_NODE_VERSION_REQUIRED' });
9
+ await launch(packageRoot);
10
+ } catch (error) {
11
+ const result = { schemaVersion: 'openxiangda.distribution/v1', ok: false, error: { code: error.code || 'DISTRIBUTION_COMMAND_FAILED', message: error.message } };
12
+ (process.argv.includes('--json') ? process.stdout : process.stderr).write(`${process.argv.includes('--json') ? JSON.stringify(result) : error.message}\n`);
13
+ process.exitCode = 1;
15
14
  }
16
- await import('openxiangda-cli/run');
@@ -1,4 +1,4 @@
1
- import type { DepartmentReferenceValue, ResourceReferenceValue, UserReferenceValue } from 'openxiangda-contracts/browser';
1
+ import type { DataFieldSourceLaunchBinding, DepartmentReferenceValue, ResourceReferenceValue, UserReferenceValue } from 'openxiangda-contracts/browser';
2
2
  import { type DirectoryKind } from './platform-client';
3
3
  type Source = DirectoryKind | 'resource';
4
4
  type StoredValue = UserReferenceValue | DepartmentReferenceValue | ResourceReferenceValue;
@@ -15,8 +15,9 @@ export type AuthoritativeSelectorProps = {
15
15
  resourceCode?: string;
16
16
  fieldCode?: string;
17
17
  bindings?: Record<string, unknown>;
18
+ launch?: DataFieldSourceLaunchBinding;
18
19
  };
19
- export declare function AuthoritativeSelector({ value, onChange, source, operation, multiple, disabled, id, placeholder, resourceCode, fieldCode, bindings, }: AuthoritativeSelectorProps): import("react/jsx-runtime").JSX.Element;
20
+ export declare function AuthoritativeSelector({ value, onChange, source, operation, multiple, disabled, id, placeholder, resourceCode, fieldCode, bindings, launch: launchInput, }: AuthoritativeSelectorProps): import("react/jsx-runtime").JSX.Element;
20
21
  /** Mobile fields consume the same authoritative query and stored snapshots. */
21
22
  export declare function MobileAuthoritativeSelector(props: AuthoritativeSelectorProps): import("react/jsx-runtime").JSX.Element;
22
23
  export declare function ResolvedValueText({ source, value, }: {
@@ -1 +1 @@
1
- {"version":3,"file":"AuthoritativeSelector.d.ts","sourceRoot":"","sources":["../../src/browser/AuthoritativeSelector.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,wBAAwB,EAExB,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,+BAA+B,CAAC;AAKvC,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAC;AAG3B,KAAK,MAAM,GAAG,aAAa,GAAG,UAAU,CAAC;AAWzC,KAAK,WAAW,GACZ,kBAAkB,GAClB,wBAAwB,GACxB,sBAAsB,CAAC;AAyH3B,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,CAAC,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;IACpC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,EAAE,GAAG,SAAS,KAAK,IAAI,CAAC;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,EACpC,KAAK,EACL,QAAQ,EACR,MAAM,EACN,SAAS,EACT,QAAgB,EAChB,QAAgB,EAChB,EAAE,EACF,WAAW,EACX,YAAY,EACZ,SAAS,EACT,QAAQ,GACT,EAAE,0BAA0B,2CA8M5B;AAED,+EAA+E;AAC/E,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,0BAA0B,2CAM5E;AAgCD,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,KAAK,GACN,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;CACrC,2CA2BA;AAED,gGAAgG;AAChG,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,EAAE,GACH,EAAE;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC;CACZ,2CAoBA"}
1
+ {"version":3,"file":"AuthoritativeSelector.d.ts","sourceRoot":"","sources":["../../src/browser/AuthoritativeSelector.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,4BAA4B,EAC5B,wBAAwB,EAExB,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,+BAA+B,CAAC;AAKvC,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,mBAAmB,CAAC;AAG3B,KAAK,MAAM,GAAG,aAAa,GAAG,UAAU,CAAC;AAWzC,KAAK,WAAW,GACZ,kBAAkB,GAClB,wBAAwB,GACxB,sBAAsB,CAAC;AAwI3B,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,CAAC,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;IACpC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,GAAG,WAAW,EAAE,GAAG,SAAS,KAAK,IAAI,CAAC;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,MAAM,CAAC,EAAE,4BAA4B,CAAC;CACvC,CAAC;AAEF,wBAAgB,qBAAqB,CAAC,EACpC,KAAK,EACL,QAAQ,EACR,MAAM,EACN,SAAS,EACT,QAAgB,EAChB,QAAgB,EAChB,EAAE,EACF,WAAW,EACX,YAAY,EACZ,SAAS,EACT,QAAQ,EACR,MAAM,EAAE,WAAW,GACpB,EAAE,0BAA0B,2CA+M5B;AAED,+EAA+E;AAC/E,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,0BAA0B,2CAM5E;AAiCD,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,KAAK,GACN,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,WAAW,GAAG,WAAW,EAAE,CAAC;CACrC,2CA2BA;AAED,gGAAgG;AAChG,wBAAgB,qBAAqB,CAAC,EACpC,MAAM,EACN,EAAE,GACH,EAAE;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC;CACZ,2CAoBA"}