openxiangda 1.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 (121) hide show
  1. package/README.md +58 -0
  2. package/bin/openxiangda.js +11 -0
  3. package/lib/cli.js +2423 -0
  4. package/lib/config.js +121 -0
  5. package/lib/http.js +47 -0
  6. package/lib/skills.js +371 -0
  7. package/lib/utils.js +87 -0
  8. package/lib/workspace-init.js +139 -0
  9. package/openxiangda-skills/SKILL.md +128 -0
  10. package/openxiangda-skills/references/architecture-patterns.md +242 -0
  11. package/openxiangda-skills/references/automation-v3.md +129 -0
  12. package/openxiangda-skills/references/component-guide.md +198 -0
  13. package/openxiangda-skills/references/forms/component-registry.md +53 -0
  14. package/openxiangda-skills/references/forms/form-schema.md +109 -0
  15. package/openxiangda-skills/references/forms/layout-and-rules.md +24 -0
  16. package/openxiangda-skills/references/openxiangda-api.md +466 -0
  17. package/openxiangda-skills/references/pages/page-sdk.md +13 -0
  18. package/openxiangda-skills/references/pages/publish-flow.md +36 -0
  19. package/openxiangda-skills/references/pages/workspace-structure.md +38 -0
  20. package/openxiangda-skills/references/permissions-settings.md +147 -0
  21. package/openxiangda-skills/references/platform-data-model.md +305 -0
  22. package/openxiangda-skills/references/style-system.md +492 -0
  23. package/openxiangda-skills/references/troubleshooting.md +246 -0
  24. package/openxiangda-skills/references/workflow-v3.md +105 -0
  25. package/openxiangda-skills/references/workspace-state.md +45 -0
  26. package/openxiangda-skills/skills/openxiangda-app/SKILL.md +64 -0
  27. package/openxiangda-skills/skills/openxiangda-core/SKILL.md +143 -0
  28. package/openxiangda-skills/skills/openxiangda-form/SKILL.md +76 -0
  29. package/openxiangda-skills/skills/openxiangda-inspect/SKILL.md +40 -0
  30. package/openxiangda-skills/skills/openxiangda-page/SKILL.md +62 -0
  31. package/openxiangda-skills/skills/openxiangda-permission-settings/SKILL.md +95 -0
  32. package/openxiangda-skills/skills/openxiangda-workflow-automation/SKILL.md +97 -0
  33. package/package.json +126 -0
  34. package/packages/sdk/bin/lowcode-workspace.mjs +4 -0
  35. package/packages/sdk/dist/build/index.cjs +33 -0
  36. package/packages/sdk/dist/build/index.cjs.map +1 -0
  37. package/packages/sdk/dist/build/index.d.mts +40 -0
  38. package/packages/sdk/dist/build/index.d.ts +40 -0
  39. package/packages/sdk/dist/build/index.mjs +8 -0
  40. package/packages/sdk/dist/build/index.mjs.map +1 -0
  41. package/packages/sdk/dist/components/index.cjs +18700 -0
  42. package/packages/sdk/dist/components/index.cjs.map +1 -0
  43. package/packages/sdk/dist/components/index.d.mts +2094 -0
  44. package/packages/sdk/dist/components/index.d.ts +2094 -0
  45. package/packages/sdk/dist/components/index.mjs +18649 -0
  46. package/packages/sdk/dist/components/index.mjs.map +1 -0
  47. package/packages/sdk/dist/runtime/index.cjs +1469 -0
  48. package/packages/sdk/dist/runtime/index.cjs.map +1 -0
  49. package/packages/sdk/dist/runtime/index.d.mts +831 -0
  50. package/packages/sdk/dist/runtime/index.d.ts +831 -0
  51. package/packages/sdk/dist/runtime/index.mjs +1420 -0
  52. package/packages/sdk/dist/runtime/index.mjs.map +1 -0
  53. package/packages/sdk/dist/styles/antd-theme.cjs +60 -0
  54. package/packages/sdk/dist/styles/antd-theme.cjs.map +1 -0
  55. package/packages/sdk/dist/styles/antd-theme.d.mts +5 -0
  56. package/packages/sdk/dist/styles/antd-theme.d.ts +5 -0
  57. package/packages/sdk/dist/styles/antd-theme.mjs +35 -0
  58. package/packages/sdk/dist/styles/antd-theme.mjs.map +1 -0
  59. package/packages/sdk/dist/styles/tailwind-preset.cjs +2641 -0
  60. package/packages/sdk/dist/styles/tailwind-preset.cjs.map +1 -0
  61. package/packages/sdk/dist/styles/tailwind-preset.d.mts +75 -0
  62. package/packages/sdk/dist/styles/tailwind-preset.d.ts +75 -0
  63. package/packages/sdk/dist/styles/tailwind-preset.mjs +2618 -0
  64. package/packages/sdk/dist/styles/tailwind-preset.mjs.map +1 -0
  65. package/packages/sdk/dist/styles/tokens.css +73 -0
  66. package/packages/sdk/src/build-source/README.md +9 -0
  67. package/packages/sdk/src/build-source/bin/lowcode-workspace.mjs +7 -0
  68. package/packages/sdk/src/build-source/package.json +34 -0
  69. package/packages/sdk/src/build-source/scripts/build-forms.mjs +824 -0
  70. package/packages/sdk/src/build-source/scripts/build-forms.runtime-entry.test.ts +18 -0
  71. package/packages/sdk/src/build-source/scripts/build-pages.mjs +793 -0
  72. package/packages/sdk/src/build-source/scripts/build-workspace.mjs +64 -0
  73. package/packages/sdk/src/build-source/scripts/publish-all.mjs +127 -0
  74. package/packages/sdk/src/build-source/scripts/publish-oss.mjs +149 -0
  75. package/packages/sdk/src/build-source/scripts/register-bundle.mjs +1 -0
  76. package/packages/sdk/src/build-source/scripts/register.mjs +329 -0
  77. package/packages/sdk/src/build-source/scripts/sync-schema.mjs +301 -0
  78. package/packages/sdk/src/build-source/scripts/utils/form-api.mjs +639 -0
  79. package/packages/sdk/src/build-source/scripts/utils/form-api.test.ts +244 -0
  80. package/packages/sdk/src/build-source/scripts/utils/form-runtime-assets.mjs +57 -0
  81. package/packages/sdk/src/build-source/scripts/utils/form-runtime-assets.test.ts +135 -0
  82. package/packages/sdk/src/build-source/scripts/utils/incremental.mjs +210 -0
  83. package/packages/sdk/src/build-source/scripts/utils/load-config.mjs +257 -0
  84. package/packages/sdk/src/build-source/scripts/utils/load-config.test.ts +44 -0
  85. package/packages/sdk/src/build-source/scripts/utils/mime-types.mjs +70 -0
  86. package/packages/sdk/src/build-source/scripts/utils/namespace-css.mjs +61 -0
  87. package/packages/sdk/src/build-source/scripts/utils/oss-client.mjs +128 -0
  88. package/packages/sdk/src/build-source/scripts/utils/pages.mjs +80 -0
  89. package/packages/sdk/src/build-source/scripts/utils/progress.mjs +57 -0
  90. package/packages/sdk/src/build-source/scripts/utils/register-payload.mjs +89 -0
  91. package/packages/sdk/src/build-source/scripts/utils/register-payload.test.ts +76 -0
  92. package/packages/sdk/src/build-source/scripts/utils/runtime-css-check.mjs +44 -0
  93. package/packages/sdk/src/build-source/scripts/utils/runtime-css-check.test.ts +54 -0
  94. package/packages/sdk/src/build-source/scripts/utils/schema-transform.mjs +130 -0
  95. package/packages/sdk/src/build-source/scripts/utils/schema-transform.test.ts +141 -0
  96. package/packages/sdk/src/build-source/scripts/utils/tailwind-config.mjs +227 -0
  97. package/packages/sdk/src/build-source/scripts/utils/tailwind-config.test.ts +187 -0
  98. package/packages/sdk/src/build-source/src/cli.mjs +679 -0
  99. package/templates/sy-lowcode-app-workspace/app-workspace.config.ts +34 -0
  100. package/templates/sy-lowcode-app-workspace/examples/forms/customer/page.tsx +1 -0
  101. package/templates/sy-lowcode-app-workspace/examples/forms/customer/schema.ts +35 -0
  102. package/templates/sy-lowcode-app-workspace/index.html +12 -0
  103. package/templates/sy-lowcode-app-workspace/package.json +49 -0
  104. package/templates/sy-lowcode-app-workspace/postcss.config.cjs +6 -0
  105. package/templates/sy-lowcode-app-workspace/scripts/build-js-code.mjs +100 -0
  106. package/templates/sy-lowcode-app-workspace/src/dev/App.tsx +26 -0
  107. package/templates/sy-lowcode-app-workspace/src/forms/.gitkeep +1 -0
  108. package/templates/sy-lowcode-app-workspace/src/forms/README.md +48 -0
  109. package/templates/sy-lowcode-app-workspace/src/index.css +28 -0
  110. package/templates/sy-lowcode-app-workspace/src/js-code-nodes/.gitkeep +1 -0
  111. package/templates/sy-lowcode-app-workspace/src/js-code-nodes/types.d.ts +3 -0
  112. package/templates/sy-lowcode-app-workspace/src/main.tsx +36 -0
  113. package/templates/sy-lowcode-app-workspace/src/pages/.gitkeep +1 -0
  114. package/templates/sy-lowcode-app-workspace/src/shared/form-schema.ts +128 -0
  115. package/templates/sy-lowcode-app-workspace/src/types/app-workspace.types.ts +31 -0
  116. package/templates/sy-lowcode-app-workspace/tailwind.config.cjs +30 -0
  117. package/templates/sy-lowcode-app-workspace/tsconfig.app.json +24 -0
  118. package/templates/sy-lowcode-app-workspace/tsconfig.js-code-nodes.json +15 -0
  119. package/templates/sy-lowcode-app-workspace/tsconfig.json +7 -0
  120. package/templates/sy-lowcode-app-workspace/tsconfig.node.json +10 -0
  121. package/templates/sy-lowcode-app-workspace/vite.config.ts +32 -0
@@ -0,0 +1,824 @@
1
+ /**
2
+ * build-forms.mjs - 构建单个或全部表单页面
3
+ */
4
+
5
+ import crypto from "node:crypto";
6
+ import fs from "node:fs";
7
+ import { createRequire } from "node:module";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { gzipSync } from "node:zlib";
11
+ import { build } from "vite";
12
+ import { rootDir } from "./utils/load-config.mjs";
13
+ import {
14
+ cleanBuildCache,
15
+ commitIncrementalBuild,
16
+ normalizeOnly,
17
+ planIncrementalBuild,
18
+ printPlan,
19
+ } from "./utils/incremental.mjs";
20
+ import { createNamespaceCssPlugin } from "./utils/namespace-css.mjs";
21
+ import { validateRuntimeCssFile } from "./utils/runtime-css-check.mjs";
22
+
23
+ process.env.NODE_ENV = "production";
24
+ process.env.BABEL_ENV = "production";
25
+
26
+ const require = createRequire(import.meta.url);
27
+ const formsDir = path.join(rootDir, "src/forms");
28
+ const distDir = path.join(rootDir, "dist/forms");
29
+ const runtimeDistDir = path.join(rootDir, "dist/form-runtime");
30
+ const tmpDir = path.join(rootDir, ".tmp");
31
+ const runtimeVersionPlaceholder = "__SY_FORM_RUNTIME_VERSION__";
32
+ const runtimeCacheFileName = "build-cache.json";
33
+
34
+ const runtimePackages = [
35
+ "react",
36
+ "react-dom",
37
+ "@ant-design/cssinjs",
38
+ "antd",
39
+ "antd-mobile",
40
+ "openxiangda",
41
+ "@tiptap/react",
42
+ ];
43
+
44
+ function formatBytes(bytes) {
45
+ if (!Number.isFinite(bytes)) return "-";
46
+ if (bytes < 1024) return `${bytes} B`;
47
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
48
+ return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
49
+ }
50
+
51
+ function formatDuration(startTime) {
52
+ return `${((Date.now() - startTime) / 1000).toFixed(2)}s`;
53
+ }
54
+
55
+ function fileSizeLabel(filePath) {
56
+ if (!fs.existsSync(filePath)) return "未生成";
57
+ return formatBytes(fs.statSync(filePath).size);
58
+ }
59
+
60
+ function fileGzipSizeLabel(filePath) {
61
+ if (!fs.existsSync(filePath)) return "未生成";
62
+ return formatBytes(gzipSync(fs.readFileSync(filePath)).length);
63
+ }
64
+
65
+ function readTextIfExists(filePath) {
66
+ if (!fs.existsSync(filePath)) return "";
67
+ return fs.readFileSync(filePath, "utf-8");
68
+ }
69
+
70
+ function readPackageVersion(packageName) {
71
+ try {
72
+ let current = path.dirname(require.resolve(packageName));
73
+ while (current && current !== path.dirname(current)) {
74
+ const packageJsonPath = path.join(current, "package.json");
75
+ if (fs.existsSync(packageJsonPath)) {
76
+ return (
77
+ JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")).version ||
78
+ "unknown"
79
+ );
80
+ }
81
+ current = path.dirname(current);
82
+ }
83
+ return "unknown";
84
+ } catch {
85
+ return "missing";
86
+ }
87
+ }
88
+
89
+ function createRuntimeInputHash(runtimeEntryContent) {
90
+ const hash = crypto.createHash("sha256");
91
+ const configFiles = [
92
+ path.join(rootDir, "src/index.css"),
93
+ path.join(rootDir, "tailwind.config.cjs"),
94
+ path.join(rootDir, "postcss.config.cjs"),
95
+ fileURLToPath(import.meta.url),
96
+ ];
97
+ const packageVersions = runtimePackages.reduce((result, packageName) => {
98
+ result[packageName] = readPackageVersion(packageName);
99
+ return result;
100
+ }, {});
101
+
102
+ hash.update("sy-form-runtime-v2-input");
103
+ hash.update(runtimeEntryContent);
104
+ hash.update(JSON.stringify(packageVersions));
105
+ configFiles.forEach((filePath) => {
106
+ hash.update(filePath);
107
+ hash.update(readTextIfExists(filePath));
108
+ });
109
+ return hash.digest("hex");
110
+ }
111
+
112
+ async function createCssOptions() {
113
+ const tailwindcssModule = await import("tailwindcss");
114
+ const autoprefixerModule = await import("autoprefixer");
115
+ const tailwindcss = tailwindcssModule.default ?? tailwindcssModule;
116
+ const autoprefixer = autoprefixerModule.default ?? autoprefixerModule;
117
+ return {
118
+ postcss: {
119
+ plugins: [
120
+ tailwindcss({ config: path.join(rootDir, "tailwind.config.cjs") }),
121
+ createNamespaceCssPlugin(),
122
+ autoprefixer(),
123
+ ],
124
+ },
125
+ };
126
+ }
127
+
128
+ async function createReactPlugin() {
129
+ const reactPluginModule = await import("@vitejs/plugin-react");
130
+ return reactPluginModule.default();
131
+ }
132
+
133
+ function parseArgs(argv) {
134
+ const result = {
135
+ form: "",
136
+ force: false,
137
+ only: "",
138
+ dryRun: false,
139
+ cleanCache: false,
140
+ runtimeCache: process.env.APP_RUNTIME_CACHE !== "false",
141
+ help: false,
142
+ };
143
+
144
+ for (let i = 0; i < argv.length; i += 1) {
145
+ const arg = argv[i];
146
+
147
+ if (arg === "--help" || arg === "-h") {
148
+ result.help = true;
149
+ continue;
150
+ }
151
+ if (arg === "--form" && argv[i + 1]) {
152
+ result.form = argv[i + 1];
153
+ i += 1;
154
+ continue;
155
+ }
156
+ if (arg === "--only" && argv[i + 1]) {
157
+ result.only = argv[i + 1];
158
+ i += 1;
159
+ continue;
160
+ }
161
+ if (arg.startsWith("--only=")) {
162
+ result.only = arg.slice("--only=".length);
163
+ continue;
164
+ }
165
+ if (arg === "--force") {
166
+ result.force = true;
167
+ result.runtimeCache = false;
168
+ continue;
169
+ }
170
+ if (arg === "--dry-run") {
171
+ result.dryRun = true;
172
+ continue;
173
+ }
174
+ if (arg === "--clean-cache") {
175
+ result.cleanCache = true;
176
+ continue;
177
+ }
178
+ if (arg === `--${["bundle", "mode"].join("-")}`) {
179
+ throw new Error("runtime mode flag has been removed");
180
+ }
181
+ if (arg === "--no-runtime-cache") {
182
+ result.runtimeCache = false;
183
+ }
184
+ }
185
+
186
+ return result;
187
+ }
188
+
189
+ function printHelp() {
190
+ console.log(`
191
+ build-forms - 构建表单页面
192
+
193
+ 用法:
194
+ tsx scripts/build-forms.mjs [options]
195
+
196
+ 选项:
197
+ --form <name> 只构建指定表单(src/forms/ 下的目录名)
198
+ --only <list> 只构建指定模块,如 forms/customer,pages/dashboard
199
+ --force 忽略增量缓存,强制重建
200
+ --dry-run 只打印增量计划,不构建
201
+ --clean-cache 删除 .openxiangda/build-cache.json
202
+ --no-runtime-cache 强制重建共享 runtime
203
+ --help, -h 显示帮助信息
204
+ `);
205
+ }
206
+
207
+ function discoverForms(filterName) {
208
+ if (!fs.existsSync(formsDir)) {
209
+ console.error(`错误: 找不到表单目录 ${formsDir}`);
210
+ process.exit(1);
211
+ }
212
+
213
+ const entries = fs.readdirSync(formsDir, { withFileTypes: true });
214
+ const formDirs = entries
215
+ .filter((entry) => entry.isDirectory())
216
+ .filter((entry) => (filterName ? entry.name === filterName : true))
217
+ .filter((entry) => {
218
+ const schemaPath = path.join(formsDir, entry.name, "schema.ts");
219
+ return fs.existsSync(schemaPath);
220
+ });
221
+
222
+ return formDirs.map((entry) => ({
223
+ name: entry.name,
224
+ key: `forms/${entry.name}`,
225
+ dirPath: path.join(formsDir, entry.name),
226
+ entryPath: path.join(formsDir, entry.name, "page.tsx"),
227
+ schemaPath: path.join(formsDir, entry.name, "schema.ts"),
228
+ outDir: path.join(distDir, entry.name),
229
+ }));
230
+ }
231
+
232
+ function createRuntimeEntryContent() {
233
+ return `import React from 'react';
234
+ import { createRoot } from 'react-dom/client';
235
+ import { StyleProvider } from '@ant-design/cssinjs';
236
+ import { App as AntdApp, ConfigProvider } from 'antd';
237
+ import zhCN from 'antd/locale/zh_CN';
238
+ import * as SyFormComponentsModule from 'openxiangda';
239
+ import { antdTheme } from 'openxiangda/antd-theme';
240
+ import '../src/index.css';
241
+
242
+ const runtimeVersion = '${runtimeVersionPlaceholder}';
243
+ const roots = new WeakMap();
244
+ const { StandardFormPage, defineFormSchema } = SyFormComponentsModule;
245
+
246
+ function getStyleContainer(el) {
247
+ const rootNode = el.getRootNode?.();
248
+ return typeof ShadowRoot !== 'undefined' && rootNode instanceof ShadowRoot
249
+ ? rootNode
250
+ : document.head;
251
+ }
252
+
253
+ function getNamespaceRoot(el) {
254
+ return el.closest?.('.sy-app-workspace') || el;
255
+ }
256
+
257
+ function isInShadowRoot(el) {
258
+ const rootNode = el.getRootNode?.();
259
+ return typeof ShadowRoot !== 'undefined' && rootNode instanceof ShadowRoot;
260
+ }
261
+
262
+ function getPopupContainer(el) {
263
+ const inShadowRoot = isInShadowRoot(el);
264
+ return (triggerNode) => {
265
+ if (inShadowRoot) return el;
266
+ return getNamespaceRoot(el) || triggerNode?.parentElement || document.body;
267
+ };
268
+ }
269
+
270
+ function getTargetContainer(el) {
271
+ return () => (isInShadowRoot(el) ? el : getNamespaceRoot(el) || document.body);
272
+ }
273
+
274
+ function renderStandardForm(el, schemaInput, context = {}) {
275
+ let root = roots.get(el);
276
+ if (!root) {
277
+ if (!isInShadowRoot(el)) el.classList.add('sy-app-workspace');
278
+ root = createRoot(el);
279
+ roots.set(el, root);
280
+ }
281
+
282
+ const schema = defineFormSchema(schemaInput);
283
+ root.render(
284
+ <StyleProvider hashPriority="high" container={getStyleContainer(el)}>
285
+ <ConfigProvider
286
+ locale={zhCN}
287
+ prefixCls="sy-ant"
288
+ iconPrefixCls="sy-anticon"
289
+ theme={antdTheme}
290
+ getPopupContainer={getPopupContainer(el)}
291
+ getTargetContainer={getTargetContainer(el)}
292
+ >
293
+ <AntdApp>
294
+ <StandardFormPage
295
+ schema={schema}
296
+ mode={context.mode || 'submit'}
297
+ initialValues={context.initialValues}
298
+ permissions={context.permissions}
299
+ formUuid={context.formUuid}
300
+ appType={context.appType}
301
+ formInstanceId={context.formInstanceId}
302
+ onSubmit={context.onSubmit}
303
+ inDrawer={context.inDrawer}
304
+ />
305
+ </AntdApp>
306
+ </ConfigProvider>
307
+ </StyleProvider>
308
+ );
309
+ }
310
+
311
+ function unmountStandardForm(el) {
312
+ const root = roots.get(el);
313
+ if (root) {
314
+ root.unmount();
315
+ roots.delete(el);
316
+ }
317
+ }
318
+
319
+ export function createStandardFormModule(schemaInput) {
320
+ let currentEl = null;
321
+ return {
322
+ mount(el, context = {}) {
323
+ currentEl = el;
324
+ renderStandardForm(el, schemaInput, context);
325
+ },
326
+ update(el, nextContext = {}) {
327
+ const target = currentEl || el;
328
+ if (target) {
329
+ renderStandardForm(target, schemaInput, nextContext);
330
+ }
331
+ },
332
+ unmount(el) {
333
+ const target = el || currentEl;
334
+ if (target) {
335
+ unmountStandardForm(target);
336
+ }
337
+ currentEl = null;
338
+ },
339
+ };
340
+ }
341
+
342
+ export const formRuntime = {
343
+ protocol: 'sy-form-runtime',
344
+ majorVersion: 2,
345
+ version: runtimeVersion,
346
+ modules: {
347
+ 'openxiangda': SyFormComponentsModule,
348
+ },
349
+ createStandardFormModule,
350
+ };
351
+
352
+ globalThis.SY_FORM_RUNTIME_V2 = formRuntime;
353
+
354
+ export default formRuntime;
355
+ `;
356
+ }
357
+
358
+ function createFormRuntimeProxyPlugin() {
359
+ return {
360
+ name: "sy-form-runtime-proxy",
361
+ enforce: "pre",
362
+ resolveId(source) {
363
+ if (source === "openxiangda")
364
+ return "\0sy-form-runtime-proxy:openxiangda";
365
+ return null;
366
+ },
367
+ load(id) {
368
+ if (id !== "\0sy-form-runtime-proxy:openxiangda") return null;
369
+ return `const runtime = globalThis.SY_FORM_RUNTIME_V2;
370
+ if (!runtime || runtime.protocol !== 'sy-form-runtime' || runtime.majorVersion !== 2) {
371
+ throw new Error('表单共享运行时未加载或版本不兼容');
372
+ }
373
+ const moduleValue = runtime.modules['openxiangda'];
374
+ export const defineFormSchema = moduleValue.defineFormSchema;
375
+ export default moduleValue;
376
+ `;
377
+ },
378
+ };
379
+ }
380
+
381
+ function generateRuntimeEntryFile(entryContent) {
382
+ const entryPath = path.join(tmpDir, "form-runtime-entry.jsx");
383
+ fs.writeFileSync(entryPath, entryContent, "utf-8");
384
+ return entryPath;
385
+ }
386
+
387
+ function generateSharedEntryFile(formName) {
388
+ const entryContent = `import schema from '../src/forms/${formName}/schema';
389
+
390
+ const runtime = globalThis.SY_FORM_RUNTIME_V2;
391
+
392
+ if (!runtime || runtime.protocol !== 'sy-form-runtime' || runtime.majorVersion !== 2) {
393
+ throw new Error('表单共享运行时未加载或版本不兼容');
394
+ }
395
+
396
+ const page = runtime.createStandardFormModule(schema);
397
+
398
+ export const mount = page.mount;
399
+ export const update = page.update;
400
+ export const unmount = page.unmount;
401
+
402
+ export default page;
403
+ `;
404
+
405
+ const entryPath = path.join(tmpDir, `${formName}-shared-entry.js`);
406
+ fs.writeFileSync(entryPath, entryContent, "utf-8");
407
+ return entryPath;
408
+ }
409
+
410
+ function readRuntimeManifest() {
411
+ const manifestPath = path.join(runtimeDistDir, "manifest.json");
412
+ if (!fs.existsSync(manifestPath)) return null;
413
+ try {
414
+ return JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
415
+ } catch {
416
+ return null;
417
+ }
418
+ }
419
+
420
+ function readRuntimeBuildCache() {
421
+ const cachePath = path.join(runtimeDistDir, runtimeCacheFileName);
422
+ if (!fs.existsSync(cachePath)) return null;
423
+ try {
424
+ return JSON.parse(fs.readFileSync(cachePath, "utf-8"));
425
+ } catch {
426
+ return null;
427
+ }
428
+ }
429
+
430
+ function isRuntimeCacheValid(inputHash) {
431
+ const manifest = readRuntimeManifest();
432
+ const cache = readRuntimeBuildCache();
433
+ if (
434
+ !manifest ||
435
+ manifest.protocol !== "sy-form-runtime" ||
436
+ manifest.majorVersion !== 2 ||
437
+ !manifest.version ||
438
+ !manifest.files?.entry ||
439
+ !cache ||
440
+ cache.inputHash !== inputHash ||
441
+ cache.runtimeVersion !== manifest.version
442
+ ) {
443
+ return false;
444
+ }
445
+
446
+ const runtimeJsPath = path.join(runtimeDistDir, manifest.files.entry);
447
+ if (!fs.existsSync(runtimeJsPath)) return false;
448
+ const runtimeCode = fs.readFileSync(runtimeJsPath, "utf-8");
449
+ if (/\bjsxDEV\b|react\/jsx-dev-runtime/.test(runtimeCode)) return false;
450
+ if (
451
+ manifest.files.css &&
452
+ !fs.existsSync(path.join(runtimeDistDir, manifest.files.css))
453
+ ) {
454
+ return false;
455
+ }
456
+ try {
457
+ validateRuntimeCssFile(
458
+ path.join(runtimeDistDir, manifest.files.css || "style.css"),
459
+ {
460
+ label: "表单共享 runtime",
461
+ },
462
+ );
463
+ } catch {
464
+ return false;
465
+ }
466
+ return true;
467
+ }
468
+
469
+ function logRuntimeAssetSummary(prefix = "共享 runtime") {
470
+ const manifest = readRuntimeManifest();
471
+ if (!manifest) return;
472
+ const runtimeJsPath = path.join(
473
+ runtimeDistDir,
474
+ manifest.files?.entry || "runtime.js",
475
+ );
476
+ const runtimeCssPath = path.join(
477
+ runtimeDistDir,
478
+ manifest.files?.css || "style.css",
479
+ );
480
+ console.log(`[build] ${prefix} version: ${manifest.version}`);
481
+ console.log(
482
+ `[build] runtime.js: ${fileSizeLabel(runtimeJsPath)} / gzip ${fileGzipSizeLabel(runtimeJsPath)}`,
483
+ );
484
+ console.log(
485
+ `[build] style.css: ${fileSizeLabel(runtimeCssPath)} / gzip ${fileGzipSizeLabel(runtimeCssPath)}`,
486
+ );
487
+ }
488
+
489
+ async function buildSharedRuntime(options = {}) {
490
+ const startedAt = Date.now();
491
+ const entryContent = createRuntimeEntryContent();
492
+ const inputHash = createRuntimeInputHash(entryContent);
493
+ if (options.runtimeCache !== false && isRuntimeCacheValid(inputHash)) {
494
+ console.log("[build] 表单共享 runtime 缓存命中,跳过构建");
495
+ logRuntimeAssetSummary("缓存 runtime");
496
+ return;
497
+ }
498
+
499
+ const entryPath = generateRuntimeEntryFile(entryContent);
500
+
501
+ try {
502
+ console.log("[build] 构建表单共享 runtime");
503
+ const cssOptions = await createCssOptions();
504
+ const reactPlugin = await createReactPlugin();
505
+ await build({
506
+ configFile: false,
507
+ mode: "production",
508
+ root: rootDir,
509
+ publicDir: false,
510
+ css: cssOptions,
511
+ define: {
512
+ "process.env.NODE_ENV": JSON.stringify("production"),
513
+ "process.env": JSON.stringify({ NODE_ENV: "production" }),
514
+ },
515
+ resolve: {
516
+ alias: [
517
+ {
518
+ find: "@",
519
+ replacement: path.join(rootDir, "src"),
520
+ },
521
+ ],
522
+ },
523
+ build: {
524
+ target: "es2018",
525
+ assetsInlineLimit: 0,
526
+ outDir: runtimeDistDir,
527
+ emptyOutDir: true,
528
+ cssCodeSplit: false,
529
+ minify: true,
530
+ lib: {
531
+ entry: entryPath,
532
+ formats: ["es"],
533
+ fileName: () => "runtime.js",
534
+ },
535
+ rollupOptions: {
536
+ output: {
537
+ inlineDynamicImports: true,
538
+ entryFileNames: "runtime.js",
539
+ chunkFileNames: "runtime.js",
540
+ assetFileNames: (assetInfo) => {
541
+ if (String(assetInfo.name || "").endsWith(".css")) {
542
+ return "style.css";
543
+ }
544
+ return "[name][extname]";
545
+ },
546
+ },
547
+ },
548
+ },
549
+ plugins: [reactPlugin],
550
+ logLevel: "warn",
551
+ });
552
+
553
+ const runtimeJsPath = path.join(runtimeDistDir, "runtime.js");
554
+ const runtimeCssPath = path.join(runtimeDistDir, "style.css");
555
+ validateRuntimeCssFile(runtimeCssPath, { label: "表单共享 runtime" });
556
+ const runtimeOutputCode = fs.readFileSync(runtimeJsPath, "utf-8");
557
+ if (/\bjsxDEV\b|react\/jsx-dev-runtime/.test(runtimeOutputCode)) {
558
+ throw new Error(
559
+ "表单共享 runtime 包含开发态 JSX runtime(jsxDEV/react/jsx-dev-runtime),请使用生产模式重新构建",
560
+ );
561
+ }
562
+ const runtimeJs = fs.readFileSync(runtimeJsPath);
563
+ const runtimeCss = fs.existsSync(runtimeCssPath)
564
+ ? fs.readFileSync(runtimeCssPath)
565
+ : Buffer.alloc(0);
566
+ const hash = crypto
567
+ .createHash("sha256")
568
+ .update(runtimeJs)
569
+ .update(runtimeCss)
570
+ .digest("hex")
571
+ .slice(0, 12);
572
+ const runtimeVersion = `v2-${hash}`;
573
+ fs.writeFileSync(
574
+ runtimeJsPath,
575
+ runtimeJs
576
+ .toString("utf-8")
577
+ .split(runtimeVersionPlaceholder)
578
+ .join(runtimeVersion),
579
+ "utf-8",
580
+ );
581
+ const manifest = {
582
+ protocol: "sy-form-runtime",
583
+ majorVersion: 2,
584
+ version: runtimeVersion,
585
+ inputHash,
586
+ files: {
587
+ entry: "runtime.js",
588
+ css: fs.existsSync(runtimeCssPath) ? "style.css" : null,
589
+ },
590
+ sizes: {
591
+ entry: fs.statSync(runtimeJsPath).size,
592
+ entryGzip: gzipSync(fs.readFileSync(runtimeJsPath)).length,
593
+ css: fs.existsSync(runtimeCssPath)
594
+ ? fs.statSync(runtimeCssPath).size
595
+ : 0,
596
+ cssGzip: fs.existsSync(runtimeCssPath)
597
+ ? gzipSync(fs.readFileSync(runtimeCssPath)).length
598
+ : 0,
599
+ },
600
+ builtAt: new Date().toISOString(),
601
+ };
602
+ fs.writeFileSync(
603
+ path.join(runtimeDistDir, "manifest.json"),
604
+ JSON.stringify(manifest, null, 2),
605
+ "utf-8",
606
+ );
607
+ fs.writeFileSync(
608
+ path.join(runtimeDistDir, runtimeCacheFileName),
609
+ JSON.stringify(
610
+ {
611
+ inputHash,
612
+ runtimeVersion,
613
+ updatedAt: new Date().toISOString(),
614
+ },
615
+ null,
616
+ 2,
617
+ ),
618
+ "utf-8",
619
+ );
620
+
621
+ console.log(
622
+ `[build] 表单共享 runtime 构建完成,用时 ${formatDuration(startedAt)}`,
623
+ );
624
+ logRuntimeAssetSummary();
625
+ } finally {
626
+ if (fs.existsSync(entryPath)) {
627
+ fs.unlinkSync(entryPath);
628
+ }
629
+ }
630
+ }
631
+
632
+ async function buildForm(form) {
633
+ const startedAt = Date.now();
634
+ const hasSchema = fs.existsSync(form.schemaPath);
635
+ if (!hasSchema) {
636
+ throw new Error(
637
+ `表单 ${form.name} 缺少 schema.ts,无法使用共享 runtime 发布`,
638
+ );
639
+ }
640
+ const entryPath = generateSharedEntryFile(form.name);
641
+
642
+ try {
643
+ console.log(`[build] 构建表单: ${form.name} (shared runtime)`);
644
+ const cssOptions = await createCssOptions();
645
+ const reactPlugin = await createReactPlugin();
646
+ await build({
647
+ configFile: false,
648
+ mode: "production",
649
+ root: rootDir,
650
+ publicDir: false,
651
+ css: cssOptions,
652
+ define: {
653
+ "process.env.NODE_ENV": JSON.stringify("production"),
654
+ "process.env": JSON.stringify({ NODE_ENV: "production" }),
655
+ },
656
+ resolve: {
657
+ alias: [
658
+ {
659
+ find: "@",
660
+ replacement: path.join(rootDir, "src"),
661
+ },
662
+ ],
663
+ },
664
+ build: {
665
+ target: "es2018",
666
+ assetsInlineLimit: 0,
667
+ outDir: form.outDir,
668
+ emptyOutDir: true,
669
+ lib: {
670
+ entry: entryPath,
671
+ name: form.name,
672
+ formats: ["es"],
673
+ fileName: () => "index.js",
674
+ },
675
+ cssCodeSplit: false,
676
+ minify: true,
677
+ rollupOptions: {
678
+ output: {
679
+ inlineDynamicImports: true,
680
+ entryFileNames: "index.js",
681
+ chunkFileNames: "index.js",
682
+ assetFileNames: (assetInfo) => {
683
+ if (String(assetInfo.name || "").endsWith(".css")) {
684
+ return "style.css";
685
+ }
686
+ return "assets/[name]-[hash][extname]";
687
+ },
688
+ },
689
+ },
690
+ },
691
+ plugins: [createFormRuntimeProxyPlugin(), reactPlugin].filter(Boolean),
692
+ logLevel: "warn",
693
+ });
694
+
695
+ const entryOutput = path.join(form.outDir, "index.js");
696
+ const cssOutput = path.join(form.outDir, "style.css");
697
+ if (!fs.existsSync(cssOutput)) {
698
+ fs.writeFileSync(cssOutput, "", "utf-8");
699
+ }
700
+ console.log(
701
+ `[build] 输出: index.js ${fileSizeLabel(entryOutput)} / gzip ${fileGzipSizeLabel(entryOutput)}`,
702
+ );
703
+ console.log(
704
+ `[build] 输出: style.css ${fileSizeLabel(cssOutput)} / gzip ${fileGzipSizeLabel(cssOutput)}`,
705
+ );
706
+ const outputCode = fs.readFileSync(entryOutput, "utf-8");
707
+ const bareImportMatch = outputCode.match(
708
+ /\bfrom\s*["'](?:react|react-dom|react\/jsx-runtime|antd|openxiangda|antd-mobile|@tiptap\/[^"']+)["']/,
709
+ );
710
+ if (bareImportMatch) {
711
+ throw new Error(
712
+ `构建产物仍包含浏览器无法直接加载的裸模块导入: ${bareImportMatch[0]}`,
713
+ );
714
+ }
715
+ if (/(?:react-dom\/client|antd-mobile|@tiptap\/)/.test(outputCode)) {
716
+ throw new Error(
717
+ "shared 表单产物仍包含运行时依赖代码,请检查入口生成逻辑",
718
+ );
719
+ }
720
+ if (/\bprocess\.env\b/.test(outputCode)) {
721
+ throw new Error("构建产物仍包含浏览器不存在的 process.env 引用");
722
+ }
723
+ if (/\bjsxDEV\b|react\/jsx-dev-runtime/.test(outputCode)) {
724
+ throw new Error(
725
+ "构建产物包含开发态 JSX runtime(jsxDEV/react/jsx-dev-runtime),请使用生产模式重新构建",
726
+ );
727
+ }
728
+ console.log(
729
+ `[build] 表单 ${form.name} 构建完成,用时 ${formatDuration(startedAt)}`,
730
+ );
731
+ } finally {
732
+ if (fs.existsSync(entryPath)) {
733
+ fs.unlinkSync(entryPath);
734
+ }
735
+ }
736
+ }
737
+
738
+ async function main() {
739
+ const args = parseArgs(process.argv.slice(2));
740
+
741
+ if (args.help) {
742
+ printHelp();
743
+ process.exit(0);
744
+ }
745
+ if (args.cleanCache) {
746
+ cleanBuildCache();
747
+ console.log("[build] 已删除 .openxiangda/build-cache.json");
748
+ return;
749
+ }
750
+
751
+ const startedAt = Date.now();
752
+ console.log("[build] 构建表单页面");
753
+ console.log("[build] 构建模式: shared runtime");
754
+ const only = normalizeOnly(args.only).filter((item) => item.startsWith("forms/"));
755
+ const filterForm = args.form || (only.length === 1 ? only[0].slice("forms/".length) : "");
756
+ const forms = discoverForms(filterForm);
757
+
758
+ if (forms.length === 0) {
759
+ if (args.form) {
760
+ console.error(`错误: 找不到表单 "${args.form}"`);
761
+ } else {
762
+ console.error("错误: 未发现任何表单页面文件");
763
+ }
764
+ process.exit(1);
765
+ }
766
+
767
+ const plan = planIncrementalBuild(forms, {
768
+ force: args.force,
769
+ only: only.length ? only : undefined,
770
+ });
771
+ printPlan(plan, "forms");
772
+ if (args.dryRun) return;
773
+ if (plan.changed.length === 0) return;
774
+
775
+ if (plan.fullRebuild && !args.form && only.length === 0 && fs.existsSync(distDir)) {
776
+ fs.rmSync(distDir, { recursive: true, force: true });
777
+ }
778
+
779
+ fs.mkdirSync(distDir, { recursive: true });
780
+ fs.mkdirSync(tmpDir, { recursive: true });
781
+
782
+ await buildSharedRuntime({ runtimeCache: args.runtimeCache });
783
+ console.log("");
784
+
785
+ let succeeded = 0;
786
+ let failed = 0;
787
+
788
+ const built = [];
789
+ for (const form of plan.changed) {
790
+ try {
791
+ await buildForm(form);
792
+ succeeded += 1;
793
+ built.push(form);
794
+ } catch (error) {
795
+ console.error(
796
+ `[build] ${form.name} 构建失败: ${error.stack || error.message}`,
797
+ );
798
+ failed += 1;
799
+ }
800
+ console.log("");
801
+ }
802
+
803
+ try {
804
+ const remaining = fs.readdirSync(tmpDir);
805
+ if (remaining.length === 0) {
806
+ fs.rmdirSync(tmpDir);
807
+ }
808
+ } catch {
809
+ // ignore
810
+ }
811
+
812
+ console.log(
813
+ `[build] 完成: ${succeeded} 成功, ${failed} 失败, 总耗时 ${formatDuration(
814
+ startedAt,
815
+ )}`,
816
+ );
817
+
818
+ if (failed > 0) {
819
+ process.exit(1);
820
+ }
821
+ commitIncrementalBuild(plan, built);
822
+ }
823
+
824
+ await main();