openxiangda 1.0.54 → 1.0.56
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/openxiangda-skills/skills/openxiangda-app/SKILL.md +3 -0
- package/openxiangda-skills/skills/openxiangda-core/SKILL.md +16 -0
- package/openxiangda-skills/skills/openxiangda-form/SKILL.md +9 -1
- package/package.json +1 -1
- package/packages/sdk/dist/build/index.cjs.map +1 -1
- package/packages/sdk/dist/build/index.d.mts +4 -1
- package/packages/sdk/dist/build/index.d.ts +4 -1
- package/packages/sdk/dist/build/index.mjs.map +1 -1
- package/packages/sdk/src/build-source/scripts/build-forms.mjs +7 -2
- package/packages/sdk/src/build-source/scripts/build-pages.mjs +7 -2
- package/packages/sdk/src/build-source/scripts/publish-all.mjs +19 -4
- package/packages/sdk/src/build-source/scripts/utils/incremental.mjs +3 -2
- package/packages/sdk/src/build-source/scripts/utils/incremental.test.ts +12 -1
- package/packages/sdk/src/build-source/scripts/utils/load-config.mjs +10 -0
- package/packages/sdk/src/build-source/scripts/utils/workspace-style-entry.mjs +29 -0
- package/templates/openxiangda-react-spa/app-workspace.config.ts +1 -0
- package/templates/sy-lowcode-app-workspace/src/types/app-workspace.types.ts +5 -0
|
@@ -65,6 +65,8 @@ openxiangda runtime deploy --profile <name>
|
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
`runtime deploy` builds and activates the app-level SPA release for `/view/:appType/*`.
|
|
68
|
+
Publish React SPA forms separately with `openxiangda workspace publish --form <formCode>`;
|
|
69
|
+
with `runtimeMode: "react-spa"` this is schema-only and does not require OSS.
|
|
68
70
|
|
|
69
71
|
Bind to an existing app only when the user explicitly provides `appType` or asks to reuse an existing platform app:
|
|
70
72
|
|
|
@@ -99,6 +101,7 @@ openxiangda workspace bind --profile <name> --app-type APP_XXX
|
|
|
99
101
|
- Use logical local codes for forms, pages, workflows, automations, and menus.
|
|
100
102
|
- Before scaffolding a new business app, read `../../references/best-practices.md` and pick an architecture from the initialized `examples/best-practices/` catalog. Do not generate a large single-file app page when a template pattern already covers the scenario.
|
|
101
103
|
- For Phase 6 React SPA apps, use `--runtime react-spa`. The generated app owns routing/layouts/menus with React Router and Tailwind CSS; platform permissions still come from backend runtime bootstrap and route checks.
|
|
104
|
+
- React SPA form publish is schema-only by default. Do not add OSS credentials or old embedded form bundles unless explicitly maintaining legacy compatibility with `--legacy-form-bundle`.
|
|
102
105
|
- When publishing after app edits, prefer targeted commands (`workspace publish --changed --dry-run`, then `--changed`, `--page`, `--form`, or `--only`). Do not publish all forms/pages just because one page changed.
|
|
103
106
|
- Use `openxiangda app snapshot <APP_XXX> --profile <name> --json` before changing an existing app.
|
|
104
107
|
|
|
@@ -45,6 +45,20 @@ openxiangda runtime deploy --profile <name>
|
|
|
45
45
|
|
|
46
46
|
`runtime deploy` builds the Vite app with a release-specific asset base, uploads `dist/` to the platform runtime release store, and activates the release unless `--no-activate` is passed.
|
|
47
47
|
|
|
48
|
+
For React SPA forms, still use targeted form publish to create/bind the form and
|
|
49
|
+
sync schema:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
openxiangda workspace publish --profile <name> --form <formCode> --dry-run
|
|
53
|
+
openxiangda workspace publish --profile <name> --form <formCode>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
When `app-workspace.config.ts` has `runtimeMode: "react-spa"`, form publish is
|
|
57
|
+
schema-only by default: it does not build or upload the legacy form page bundle
|
|
58
|
+
to OSS. The React SPA renders form submit/detail/list routes through
|
|
59
|
+
`openxiangda/runtime` default pages. Use `--legacy-form-bundle` only when a
|
|
60
|
+
React SPA app intentionally needs the old embedded form page bundle.
|
|
61
|
+
|
|
48
62
|
## DO NOT
|
|
49
63
|
|
|
50
64
|
- ❌ `pnpm publish:all` / `pnpm publish:oss` / `pnpm register` / `lowcode-workspace publish-*` directly. They are workspace internals and miss `OPENXIANGDA_PROFILE / BASE_URL / ACCESS_TOKEN / APP_TYPE` injection.
|
|
@@ -147,6 +161,8 @@ menus, and route permission checks. Do not publish React SPA pages through the
|
|
|
147
161
|
old custom-page app-shell path unless explicitly maintaining a legacy app.
|
|
148
162
|
Use `openxiangda resource publish` for roles/menus/page permission groups and
|
|
149
163
|
`openxiangda runtime deploy` for the SPA frontend release.
|
|
164
|
+
Use `openxiangda workspace publish --form <formCode>` only for schema/form shell
|
|
165
|
+
sync; it is schema-only by default and does not require OSS credentials.
|
|
150
166
|
|
|
151
167
|
Local workspace state is authoritative. If `.openxiangda/state.json` already contains an app binding for the target profile, use it. If there is no local binding, create a new app with `workspace init --app-name`; do not search platform apps for similar names.
|
|
152
168
|
|
|
@@ -85,6 +85,14 @@ openxiangda workspace publish --profile <name> --form <formCode>
|
|
|
85
85
|
|
|
86
86
|
The workspace publish script receives `OPENXIANGDA_BASE_URL`, `OPENXIANGDA_ACCESS_TOKEN`, and `OPENXIANGDA_APP_TYPE` from the CLI. It creates/binds the platform form shell only when needed, syncs form metadata, builds the React form page bundle, uploads assets, and registers the bundle.
|
|
87
87
|
|
|
88
|
+
For Phase 6 React SPA workspaces, `app-workspace.config.ts` should declare
|
|
89
|
+
`runtimeMode: "react-spa"`. In that mode, `workspace publish --form <code>` is
|
|
90
|
+
schema-only by default: it creates/binds the form and syncs schema, but skips
|
|
91
|
+
the legacy form page bundle, OSS upload, and bundle register. The app-level
|
|
92
|
+
React runtime renders submit/detail/list pages. Pass `--legacy-form-bundle`
|
|
93
|
+
only when intentionally maintaining compatibility with an old embedded form
|
|
94
|
+
page bundle.
|
|
95
|
+
|
|
88
96
|
## Low-level Commands
|
|
89
97
|
|
|
90
98
|
Use these only for diagnosis, binding existing resources, or repairing a broken publish state:
|
|
@@ -131,7 +139,7 @@ Read these references only when writing or reviewing schema:
|
|
|
131
139
|
- Put validation on field-level `rules`; never generate old top-level validation arrays under `schema.rules`. Top-level `rules` is only for `FormEffect[]` with `when` and `then`.
|
|
132
140
|
- Always provide `options` for option components. `SelectField`, `MultiSelectField`, `RadioField`, `CheckboxField`, and `CascadeSelectField` must not be emitted with `options` undefined. See `../../references/component-guide.md` for the full list and `../../references/platform-data-model.md` for the `{label, value}` storage contract.
|
|
133
141
|
- Do not emit raw native form controls in app/workspace source. For basic entry use `TextField`, `TextAreaField`, `NumberField`, `DateField`, `SelectField`, `RadioField`, etc.; for platform-specific entry use `UserSelectField`, `DepartmentSelectField`, `AttachmentField`, `ImageField`, `EditorField`, `DigitalSignatureField`, and `LocationField`.
|
|
134
|
-
- Workflow form pages use the same workspace form structure plus workflow v3 configuration
|
|
142
|
+
- Workflow form pages use the same workspace form structure plus workflow v3 configuration. In legacy workspaces, publish the form page bundle through workspace publish before treating it as complete. In React SPA workspaces, `workspace publish --form` syncs schema only, and the SPA default process pages render through `runtime deploy`.
|
|
135
143
|
- After editing one form, use `workspace publish --form <formCode>` or preview `--changed --dry-run`; do not run full workspace publish by default.
|
|
136
144
|
- Use `openxiangda form pull` or app snapshot before overwriting an existing form.
|
|
137
145
|
- Before assuming a value shape (e.g. dates, attachments, member fields, option fields), verify against `../../references/platform-data-model.md` instead of guessing.
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/build/index.ts"],"sourcesContent":["export type CssIsolation = 'namespace' | 'shadow' | 'none';\n\nexport interface AppWorkspaceConfig {\n appType?: string;\n appName?: string;\n platformUrl?: string;\n servicePrefix?: string;\n appKey?: string;\n appSecret?: string;\n userId?: string;\n version?: string;\n buildId?: string;\n oss?: {\n region?: string;\n bucket?: string;\n accessKeyId?: string;\n accessKeySecret?: string;\n pathPrefix?: string;\n corsOrigins?: string[];\n skipCors?: boolean;\n };\n defaults?: {\n protocolVersion?: string;\n frameworkVersion?: string;\n cssIsolation?: CssIsolation;\n formMenuParentId?: string;\n formMenuIcon?: string;\n pageMenuParentId?: string;\n pageMenuIcon?: string;\n formBuilderVersion?: string;\n };\n forms?: { dir?: string };\n pages?: { dir?: string };\n}\n\nexport function defineAppWorkspaceConfig<T extends AppWorkspaceConfig>(config: T): T {\n return config;\n}\n\nconst DEFAULT_NAMESPACE_PREFIX = '.sy-app-workspace';\n\nfunction splitCssSelectors(selector: string) {\n const selectors: string[] = [];\n let depth = 0;\n let quote = '';\n let current = '';\n for (const char of selector) {\n if (quote) {\n current += char;\n if (char === quote) quote = '';\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n current += char;\n continue;\n }\n if (char === '(' || char === '[') depth += 1;\n if (char === ')' || char === ']') depth -= 1;\n if (char === ',' && depth === 0) {\n selectors.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n if (current.trim()) selectors.push(current.trim());\n return selectors;\n}\n\nfunction shouldSkipNamespace(selector: string, prefix: string) {\n if (!selector || selector.includes(prefix)) return true;\n if (/^(html|body|:root)(\\b|:|\\[|$)/.test(selector)) return true;\n if (/^(@|from\\b|to\\b|\\d+%)/.test(selector)) return true;\n if (/^\\.(ant|sy-ant|anticon|adm)-/.test(selector)) return true;\n return false;\n}\n\nfunction createAntdSelectorAlias(selector: string) {\n const aliased = selector\n .replace(/(^|[^A-Za-z0-9_-])\\.ant-/g, '$1.sy-ant-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon-/g, '$1.sy-anticon-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon(?=$|[^A-Za-z0-9_-])/g, '$1.sy-anticon');\n return aliased === selector ? null : aliased;\n}\n\nexport function createOpenXiangdaNamespaceCssPlugin(\n prefix = DEFAULT_NAMESPACE_PREFIX,\n) {\n return {\n postcssPlugin: 'openxiangda-namespace-css',\n Rule(rule: { selector?: string; parent?: any }) {\n if (!rule.selector) return;\n let parent = rule.parent;\n while (parent) {\n if (parent.type === 'atrule' && /keyframes$/i.test(parent.name)) {\n return;\n }\n parent = parent.parent;\n }\n rule.selector = Array.from(\n new Set(\n splitCssSelectors(rule.selector).flatMap((selector) => {\n const scopedSelector = shouldSkipNamespace(selector, prefix)\n ? selector\n : `${prefix} ${selector}`;\n const antdAlias = createAntdSelectorAlias(scopedSelector);\n return antdAlias ? [scopedSelector, antdAlias] : [scopedSelector];\n }),\n ),\n ).join(', ');\n },\n };\n}\n\n(createOpenXiangdaNamespaceCssPlugin as any).postcss = true;\n\nexport const createNamespaceCssPlugin = createOpenXiangdaNamespaceCssPlugin;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
1
|
+
{"version":3,"sources":["../../src/build/index.ts"],"sourcesContent":["export type CssIsolation = 'namespace' | 'shadow' | 'none';\nexport type WorkspaceRuntimeMode = 'legacy' | 'react-spa';\n\nexport interface AppWorkspaceConfig {\n appType?: string;\n appName?: string;\n runtimeMode?: WorkspaceRuntimeMode;\n platformUrl?: string;\n servicePrefix?: string;\n appKey?: string;\n appSecret?: string;\n userId?: string;\n version?: string;\n buildId?: string;\n oss?: {\n region?: string;\n bucket?: string;\n accessKeyId?: string;\n accessKeySecret?: string;\n pathPrefix?: string;\n corsOrigins?: string[];\n skipCors?: boolean;\n };\n defaults?: {\n protocolVersion?: string;\n frameworkVersion?: string;\n cssIsolation?: CssIsolation;\n formMenuParentId?: string;\n formMenuIcon?: string;\n pageMenuParentId?: string;\n pageMenuIcon?: string;\n formBuilderVersion?: string;\n };\n forms?: { dir?: string; publishLegacyBundle?: boolean };\n pages?: { dir?: string };\n}\n\nexport function defineAppWorkspaceConfig<T extends AppWorkspaceConfig>(config: T): T {\n return config;\n}\n\nconst DEFAULT_NAMESPACE_PREFIX = '.sy-app-workspace';\n\nfunction splitCssSelectors(selector: string) {\n const selectors: string[] = [];\n let depth = 0;\n let quote = '';\n let current = '';\n for (const char of selector) {\n if (quote) {\n current += char;\n if (char === quote) quote = '';\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n current += char;\n continue;\n }\n if (char === '(' || char === '[') depth += 1;\n if (char === ')' || char === ']') depth -= 1;\n if (char === ',' && depth === 0) {\n selectors.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n if (current.trim()) selectors.push(current.trim());\n return selectors;\n}\n\nfunction shouldSkipNamespace(selector: string, prefix: string) {\n if (!selector || selector.includes(prefix)) return true;\n if (/^(html|body|:root)(\\b|:|\\[|$)/.test(selector)) return true;\n if (/^(@|from\\b|to\\b|\\d+%)/.test(selector)) return true;\n if (/^\\.(ant|sy-ant|anticon|adm)-/.test(selector)) return true;\n return false;\n}\n\nfunction createAntdSelectorAlias(selector: string) {\n const aliased = selector\n .replace(/(^|[^A-Za-z0-9_-])\\.ant-/g, '$1.sy-ant-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon-/g, '$1.sy-anticon-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon(?=$|[^A-Za-z0-9_-])/g, '$1.sy-anticon');\n return aliased === selector ? null : aliased;\n}\n\nexport function createOpenXiangdaNamespaceCssPlugin(\n prefix = DEFAULT_NAMESPACE_PREFIX,\n) {\n return {\n postcssPlugin: 'openxiangda-namespace-css',\n Rule(rule: { selector?: string; parent?: any }) {\n if (!rule.selector) return;\n let parent = rule.parent;\n while (parent) {\n if (parent.type === 'atrule' && /keyframes$/i.test(parent.name)) {\n return;\n }\n parent = parent.parent;\n }\n rule.selector = Array.from(\n new Set(\n splitCssSelectors(rule.selector).flatMap((selector) => {\n const scopedSelector = shouldSkipNamespace(selector, prefix)\n ? selector\n : `${prefix} ${selector}`;\n const antdAlias = createAntdSelectorAlias(scopedSelector);\n return antdAlias ? [scopedSelector, antdAlias] : [scopedSelector];\n }),\n ),\n ).join(', ');\n },\n };\n}\n\n(createOpenXiangdaNamespaceCssPlugin as any).postcss = true;\n\nexport const createNamespaceCssPlugin = createOpenXiangdaNamespaceCssPlugin;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqCO,SAAS,yBAAuD,QAAc;AACnF,SAAO;AACT;AAEA,IAAM,2BAA2B;AAEjC,SAAS,kBAAkB,UAAkB;AAC3C,QAAM,YAAsB,CAAC;AAC7B,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,aAAW,QAAQ,UAAU;AAC3B,QAAI,OAAO;AACT,iBAAW;AACX,UAAI,SAAS,MAAO,SAAQ;AAC5B;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,cAAQ;AACR,iBAAW;AACX;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,UAAU,GAAG;AAC/B,gBAAU,KAAK,QAAQ,KAAK,CAAC;AAC7B,gBAAU;AACV;AAAA,IACF;AACA,eAAW;AAAA,EACb;AACA,MAAI,QAAQ,KAAK,EAAG,WAAU,KAAK,QAAQ,KAAK,CAAC;AACjD,SAAO;AACT;AAEA,SAAS,oBAAoB,UAAkB,QAAgB;AAC7D,MAAI,CAAC,YAAY,SAAS,SAAS,MAAM,EAAG,QAAO;AACnD,MAAI,gCAAgC,KAAK,QAAQ,EAAG,QAAO;AAC3D,MAAI,wBAAwB,KAAK,QAAQ,EAAG,QAAO;AACnD,MAAI,+BAA+B,KAAK,QAAQ,EAAG,QAAO;AAC1D,SAAO;AACT;AAEA,SAAS,wBAAwB,UAAkB;AACjD,QAAM,UAAU,SACb,QAAQ,6BAA6B,YAAY,EACjD,QAAQ,iCAAiC,gBAAgB,EACzD,QAAQ,oDAAoD,eAAe;AAC9E,SAAO,YAAY,WAAW,OAAO;AACvC;AAEO,SAAS,oCACd,SAAS,0BACT;AACA,SAAO;AAAA,IACL,eAAe;AAAA,IACf,KAAK,MAA2C;AAC9C,UAAI,CAAC,KAAK,SAAU;AACpB,UAAI,SAAS,KAAK;AAClB,aAAO,QAAQ;AACb,YAAI,OAAO,SAAS,YAAY,cAAc,KAAK,OAAO,IAAI,GAAG;AAC/D;AAAA,QACF;AACA,iBAAS,OAAO;AAAA,MAClB;AACA,WAAK,WAAW,MAAM;AAAA,QACpB,IAAI;AAAA,UACF,kBAAkB,KAAK,QAAQ,EAAE,QAAQ,CAAC,aAAa;AACrD,kBAAM,iBAAiB,oBAAoB,UAAU,MAAM,IACvD,WACA,GAAG,MAAM,IAAI,QAAQ;AACzB,kBAAM,YAAY,wBAAwB,cAAc;AACxD,mBAAO,YAAY,CAAC,gBAAgB,SAAS,IAAI,CAAC,cAAc;AAAA,UAClE,CAAC;AAAA,QACH;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AACF;AAEC,oCAA4C,UAAU;AAEhD,IAAM,2BAA2B;","names":[]}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
type CssIsolation = 'namespace' | 'shadow' | 'none';
|
|
2
|
+
type WorkspaceRuntimeMode = 'legacy' | 'react-spa';
|
|
2
3
|
interface AppWorkspaceConfig {
|
|
3
4
|
appType?: string;
|
|
4
5
|
appName?: string;
|
|
6
|
+
runtimeMode?: WorkspaceRuntimeMode;
|
|
5
7
|
platformUrl?: string;
|
|
6
8
|
servicePrefix?: string;
|
|
7
9
|
appKey?: string;
|
|
@@ -30,6 +32,7 @@ interface AppWorkspaceConfig {
|
|
|
30
32
|
};
|
|
31
33
|
forms?: {
|
|
32
34
|
dir?: string;
|
|
35
|
+
publishLegacyBundle?: boolean;
|
|
33
36
|
};
|
|
34
37
|
pages?: {
|
|
35
38
|
dir?: string;
|
|
@@ -45,4 +48,4 @@ declare function createOpenXiangdaNamespaceCssPlugin(prefix?: string): {
|
|
|
45
48
|
};
|
|
46
49
|
declare const createNamespaceCssPlugin: typeof createOpenXiangdaNamespaceCssPlugin;
|
|
47
50
|
|
|
48
|
-
export { type AppWorkspaceConfig, type CssIsolation, createNamespaceCssPlugin, createOpenXiangdaNamespaceCssPlugin, defineAppWorkspaceConfig };
|
|
51
|
+
export { type AppWorkspaceConfig, type CssIsolation, type WorkspaceRuntimeMode, createNamespaceCssPlugin, createOpenXiangdaNamespaceCssPlugin, defineAppWorkspaceConfig };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
type CssIsolation = 'namespace' | 'shadow' | 'none';
|
|
2
|
+
type WorkspaceRuntimeMode = 'legacy' | 'react-spa';
|
|
2
3
|
interface AppWorkspaceConfig {
|
|
3
4
|
appType?: string;
|
|
4
5
|
appName?: string;
|
|
6
|
+
runtimeMode?: WorkspaceRuntimeMode;
|
|
5
7
|
platformUrl?: string;
|
|
6
8
|
servicePrefix?: string;
|
|
7
9
|
appKey?: string;
|
|
@@ -30,6 +32,7 @@ interface AppWorkspaceConfig {
|
|
|
30
32
|
};
|
|
31
33
|
forms?: {
|
|
32
34
|
dir?: string;
|
|
35
|
+
publishLegacyBundle?: boolean;
|
|
33
36
|
};
|
|
34
37
|
pages?: {
|
|
35
38
|
dir?: string;
|
|
@@ -45,4 +48,4 @@ declare function createOpenXiangdaNamespaceCssPlugin(prefix?: string): {
|
|
|
45
48
|
};
|
|
46
49
|
declare const createNamespaceCssPlugin: typeof createOpenXiangdaNamespaceCssPlugin;
|
|
47
50
|
|
|
48
|
-
export { type AppWorkspaceConfig, type CssIsolation, createNamespaceCssPlugin, createOpenXiangdaNamespaceCssPlugin, defineAppWorkspaceConfig };
|
|
51
|
+
export { type AppWorkspaceConfig, type CssIsolation, type WorkspaceRuntimeMode, createNamespaceCssPlugin, createOpenXiangdaNamespaceCssPlugin, defineAppWorkspaceConfig };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/build/index.ts"],"sourcesContent":["export type CssIsolation = 'namespace' | 'shadow' | 'none';\n\nexport interface AppWorkspaceConfig {\n appType?: string;\n appName?: string;\n platformUrl?: string;\n servicePrefix?: string;\n appKey?: string;\n appSecret?: string;\n userId?: string;\n version?: string;\n buildId?: string;\n oss?: {\n region?: string;\n bucket?: string;\n accessKeyId?: string;\n accessKeySecret?: string;\n pathPrefix?: string;\n corsOrigins?: string[];\n skipCors?: boolean;\n };\n defaults?: {\n protocolVersion?: string;\n frameworkVersion?: string;\n cssIsolation?: CssIsolation;\n formMenuParentId?: string;\n formMenuIcon?: string;\n pageMenuParentId?: string;\n pageMenuIcon?: string;\n formBuilderVersion?: string;\n };\n forms?: { dir?: string };\n pages?: { dir?: string };\n}\n\nexport function defineAppWorkspaceConfig<T extends AppWorkspaceConfig>(config: T): T {\n return config;\n}\n\nconst DEFAULT_NAMESPACE_PREFIX = '.sy-app-workspace';\n\nfunction splitCssSelectors(selector: string) {\n const selectors: string[] = [];\n let depth = 0;\n let quote = '';\n let current = '';\n for (const char of selector) {\n if (quote) {\n current += char;\n if (char === quote) quote = '';\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n current += char;\n continue;\n }\n if (char === '(' || char === '[') depth += 1;\n if (char === ')' || char === ']') depth -= 1;\n if (char === ',' && depth === 0) {\n selectors.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n if (current.trim()) selectors.push(current.trim());\n return selectors;\n}\n\nfunction shouldSkipNamespace(selector: string, prefix: string) {\n if (!selector || selector.includes(prefix)) return true;\n if (/^(html|body|:root)(\\b|:|\\[|$)/.test(selector)) return true;\n if (/^(@|from\\b|to\\b|\\d+%)/.test(selector)) return true;\n if (/^\\.(ant|sy-ant|anticon|adm)-/.test(selector)) return true;\n return false;\n}\n\nfunction createAntdSelectorAlias(selector: string) {\n const aliased = selector\n .replace(/(^|[^A-Za-z0-9_-])\\.ant-/g, '$1.sy-ant-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon-/g, '$1.sy-anticon-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon(?=$|[^A-Za-z0-9_-])/g, '$1.sy-anticon');\n return aliased === selector ? null : aliased;\n}\n\nexport function createOpenXiangdaNamespaceCssPlugin(\n prefix = DEFAULT_NAMESPACE_PREFIX,\n) {\n return {\n postcssPlugin: 'openxiangda-namespace-css',\n Rule(rule: { selector?: string; parent?: any }) {\n if (!rule.selector) return;\n let parent = rule.parent;\n while (parent) {\n if (parent.type === 'atrule' && /keyframes$/i.test(parent.name)) {\n return;\n }\n parent = parent.parent;\n }\n rule.selector = Array.from(\n new Set(\n splitCssSelectors(rule.selector).flatMap((selector) => {\n const scopedSelector = shouldSkipNamespace(selector, prefix)\n ? selector\n : `${prefix} ${selector}`;\n const antdAlias = createAntdSelectorAlias(scopedSelector);\n return antdAlias ? [scopedSelector, antdAlias] : [scopedSelector];\n }),\n ),\n ).join(', ');\n },\n };\n}\n\n(createOpenXiangdaNamespaceCssPlugin as any).postcss = true;\n\nexport const createNamespaceCssPlugin = createOpenXiangdaNamespaceCssPlugin;\n"],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/build/index.ts"],"sourcesContent":["export type CssIsolation = 'namespace' | 'shadow' | 'none';\nexport type WorkspaceRuntimeMode = 'legacy' | 'react-spa';\n\nexport interface AppWorkspaceConfig {\n appType?: string;\n appName?: string;\n runtimeMode?: WorkspaceRuntimeMode;\n platformUrl?: string;\n servicePrefix?: string;\n appKey?: string;\n appSecret?: string;\n userId?: string;\n version?: string;\n buildId?: string;\n oss?: {\n region?: string;\n bucket?: string;\n accessKeyId?: string;\n accessKeySecret?: string;\n pathPrefix?: string;\n corsOrigins?: string[];\n skipCors?: boolean;\n };\n defaults?: {\n protocolVersion?: string;\n frameworkVersion?: string;\n cssIsolation?: CssIsolation;\n formMenuParentId?: string;\n formMenuIcon?: string;\n pageMenuParentId?: string;\n pageMenuIcon?: string;\n formBuilderVersion?: string;\n };\n forms?: { dir?: string; publishLegacyBundle?: boolean };\n pages?: { dir?: string };\n}\n\nexport function defineAppWorkspaceConfig<T extends AppWorkspaceConfig>(config: T): T {\n return config;\n}\n\nconst DEFAULT_NAMESPACE_PREFIX = '.sy-app-workspace';\n\nfunction splitCssSelectors(selector: string) {\n const selectors: string[] = [];\n let depth = 0;\n let quote = '';\n let current = '';\n for (const char of selector) {\n if (quote) {\n current += char;\n if (char === quote) quote = '';\n continue;\n }\n if (char === '\"' || char === \"'\") {\n quote = char;\n current += char;\n continue;\n }\n if (char === '(' || char === '[') depth += 1;\n if (char === ')' || char === ']') depth -= 1;\n if (char === ',' && depth === 0) {\n selectors.push(current.trim());\n current = '';\n continue;\n }\n current += char;\n }\n if (current.trim()) selectors.push(current.trim());\n return selectors;\n}\n\nfunction shouldSkipNamespace(selector: string, prefix: string) {\n if (!selector || selector.includes(prefix)) return true;\n if (/^(html|body|:root)(\\b|:|\\[|$)/.test(selector)) return true;\n if (/^(@|from\\b|to\\b|\\d+%)/.test(selector)) return true;\n if (/^\\.(ant|sy-ant|anticon|adm)-/.test(selector)) return true;\n return false;\n}\n\nfunction createAntdSelectorAlias(selector: string) {\n const aliased = selector\n .replace(/(^|[^A-Za-z0-9_-])\\.ant-/g, '$1.sy-ant-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon-/g, '$1.sy-anticon-')\n .replace(/(^|[^A-Za-z0-9_-])\\.anticon(?=$|[^A-Za-z0-9_-])/g, '$1.sy-anticon');\n return aliased === selector ? null : aliased;\n}\n\nexport function createOpenXiangdaNamespaceCssPlugin(\n prefix = DEFAULT_NAMESPACE_PREFIX,\n) {\n return {\n postcssPlugin: 'openxiangda-namespace-css',\n Rule(rule: { selector?: string; parent?: any }) {\n if (!rule.selector) return;\n let parent = rule.parent;\n while (parent) {\n if (parent.type === 'atrule' && /keyframes$/i.test(parent.name)) {\n return;\n }\n parent = parent.parent;\n }\n rule.selector = Array.from(\n new Set(\n splitCssSelectors(rule.selector).flatMap((selector) => {\n const scopedSelector = shouldSkipNamespace(selector, prefix)\n ? selector\n : `${prefix} ${selector}`;\n const antdAlias = createAntdSelectorAlias(scopedSelector);\n return antdAlias ? [scopedSelector, antdAlias] : [scopedSelector];\n }),\n ),\n ).join(', ');\n },\n };\n}\n\n(createOpenXiangdaNamespaceCssPlugin as any).postcss = true;\n\nexport const createNamespaceCssPlugin = createOpenXiangdaNamespaceCssPlugin;\n"],"mappings":";AAqCO,SAAS,yBAAuD,QAAc;AACnF,SAAO;AACT;AAEA,IAAM,2BAA2B;AAEjC,SAAS,kBAAkB,UAAkB;AAC3C,QAAM,YAAsB,CAAC;AAC7B,MAAI,QAAQ;AACZ,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,aAAW,QAAQ,UAAU;AAC3B,QAAI,OAAO;AACT,iBAAW;AACX,UAAI,SAAS,MAAO,SAAQ;AAC5B;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,cAAQ;AACR,iBAAW;AACX;AAAA,IACF;AACA,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,SAAS,IAAK,UAAS;AAC3C,QAAI,SAAS,OAAO,UAAU,GAAG;AAC/B,gBAAU,KAAK,QAAQ,KAAK,CAAC;AAC7B,gBAAU;AACV;AAAA,IACF;AACA,eAAW;AAAA,EACb;AACA,MAAI,QAAQ,KAAK,EAAG,WAAU,KAAK,QAAQ,KAAK,CAAC;AACjD,SAAO;AACT;AAEA,SAAS,oBAAoB,UAAkB,QAAgB;AAC7D,MAAI,CAAC,YAAY,SAAS,SAAS,MAAM,EAAG,QAAO;AACnD,MAAI,gCAAgC,KAAK,QAAQ,EAAG,QAAO;AAC3D,MAAI,wBAAwB,KAAK,QAAQ,EAAG,QAAO;AACnD,MAAI,+BAA+B,KAAK,QAAQ,EAAG,QAAO;AAC1D,SAAO;AACT;AAEA,SAAS,wBAAwB,UAAkB;AACjD,QAAM,UAAU,SACb,QAAQ,6BAA6B,YAAY,EACjD,QAAQ,iCAAiC,gBAAgB,EACzD,QAAQ,oDAAoD,eAAe;AAC9E,SAAO,YAAY,WAAW,OAAO;AACvC;AAEO,SAAS,oCACd,SAAS,0BACT;AACA,SAAO;AAAA,IACL,eAAe;AAAA,IACf,KAAK,MAA2C;AAC9C,UAAI,CAAC,KAAK,SAAU;AACpB,UAAI,SAAS,KAAK;AAClB,aAAO,QAAQ;AACb,YAAI,OAAO,SAAS,YAAY,cAAc,KAAK,OAAO,IAAI,GAAG;AAC/D;AAAA,QACF;AACA,iBAAS,OAAO;AAAA,MAClB;AACA,WAAK,WAAW,MAAM;AAAA,QACpB,IAAI;AAAA,UACF,kBAAkB,KAAK,QAAQ,EAAE,QAAQ,CAAC,aAAa;AACrD,kBAAM,iBAAiB,oBAAoB,UAAU,MAAM,IACvD,WACA,GAAG,MAAM,IAAI,QAAQ;AACzB,kBAAM,YAAY,wBAAwB,cAAc;AACxD,mBAAO,YAAY,CAAC,gBAAgB,SAAS,IAAI,CAAC,cAAc;AAAA,UAClE,CAAC;AAAA,QACH;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AACF;AAEC,oCAA4C,UAAU;AAEhD,IAAM,2BAA2B;","names":[]}
|
|
@@ -24,6 +24,10 @@ import {
|
|
|
24
24
|
formatExtractedAssetSummary,
|
|
25
25
|
} from "./utils/static-assets.mjs";
|
|
26
26
|
import { warnShadcnTailwindTokens } from "./utils/tailwind-token-warnings.mjs";
|
|
27
|
+
import {
|
|
28
|
+
createWorkspaceStyleImport,
|
|
29
|
+
listWorkspaceStyleEntryPaths,
|
|
30
|
+
} from "./utils/workspace-style-entry.mjs";
|
|
27
31
|
|
|
28
32
|
process.env.NODE_ENV = "production";
|
|
29
33
|
process.env.BABEL_ENV = "production";
|
|
@@ -110,7 +114,7 @@ function readPackageVersion(packageName) {
|
|
|
110
114
|
function createRuntimeInputHash(runtimeEntryContent, cssIsolation) {
|
|
111
115
|
const hash = crypto.createHash("sha256");
|
|
112
116
|
const configFiles = [
|
|
113
|
-
|
|
117
|
+
...listWorkspaceStyleEntryPaths(rootDir),
|
|
114
118
|
path.join(rootDir, "app-workspace.config.ts"),
|
|
115
119
|
path.join(rootDir, "tailwind.config.cjs"),
|
|
116
120
|
path.join(rootDir, "postcss.config.cjs"),
|
|
@@ -255,6 +259,7 @@ function discoverForms(filterName) {
|
|
|
255
259
|
}
|
|
256
260
|
|
|
257
261
|
function createRuntimeEntryContent() {
|
|
262
|
+
const workspaceStyleImport = createWorkspaceStyleImport(rootDir, tmpDir);
|
|
258
263
|
return `import React from 'react';
|
|
259
264
|
import { createRoot } from 'react-dom/client';
|
|
260
265
|
import { StyleProvider } from '@ant-design/cssinjs';
|
|
@@ -265,7 +270,7 @@ import 'dayjs/locale/zh-cn';
|
|
|
265
270
|
import * as SyFormComponentsModule from 'openxiangda';
|
|
266
271
|
import { antdTheme, legacyAntdTheme } from 'openxiangda/antd-theme';
|
|
267
272
|
import 'antd-mobile/es/global';
|
|
268
|
-
import '
|
|
273
|
+
import '${workspaceStyleImport}';
|
|
269
274
|
|
|
270
275
|
const runtimeVersion = '${runtimeVersionPlaceholder}';
|
|
271
276
|
dayjs.locale('zh-cn');
|
|
@@ -24,6 +24,10 @@ import {
|
|
|
24
24
|
} from "./utils/static-assets.mjs";
|
|
25
25
|
import { warnShadcnTailwindTokens } from "./utils/tailwind-token-warnings.mjs";
|
|
26
26
|
import { loadConfig } from "./utils/load-config.mjs";
|
|
27
|
+
import {
|
|
28
|
+
createWorkspaceStyleImport,
|
|
29
|
+
listWorkspaceStyleEntryPaths,
|
|
30
|
+
} from "./utils/workspace-style-entry.mjs";
|
|
27
31
|
|
|
28
32
|
process.env.NODE_ENV = "production";
|
|
29
33
|
process.env.BABEL_ENV = "production";
|
|
@@ -231,7 +235,7 @@ function createRuntimeInputHash(runtimeEntryContent, cssIsolation) {
|
|
|
231
235
|
return result;
|
|
232
236
|
}, {});
|
|
233
237
|
const configFiles = [
|
|
234
|
-
|
|
238
|
+
...listWorkspaceStyleEntryPaths(rootDir),
|
|
235
239
|
path.join(rootDir, "app-workspace.config.ts"),
|
|
236
240
|
path.join(rootDir, "tailwind.config.cjs"),
|
|
237
241
|
path.join(rootDir, "postcss.config.cjs"),
|
|
@@ -250,6 +254,7 @@ function createRuntimeInputHash(runtimeEntryContent, cssIsolation) {
|
|
|
250
254
|
}
|
|
251
255
|
|
|
252
256
|
function createRuntimeEntryContent() {
|
|
257
|
+
const workspaceStyleImport = createWorkspaceStyleImport(rootDir, tmpDir);
|
|
253
258
|
return `import * as ReactModule from 'react';
|
|
254
259
|
import * as ReactJsxRuntimeModule from 'react/jsx-runtime';
|
|
255
260
|
import * as ReactDomClientModule from 'react-dom/client';
|
|
@@ -259,7 +264,7 @@ import zhCN from 'antd/locale/zh_CN';
|
|
|
259
264
|
import * as IconsModule from '@ant-design/icons';
|
|
260
265
|
import * as OpenXiangdaModule from 'openxiangda';
|
|
261
266
|
import * as OpenXiangdaRuntimeModule from 'openxiangda/runtime';
|
|
262
|
-
import '
|
|
267
|
+
import '${workspaceStyleImport}';
|
|
263
268
|
|
|
264
269
|
const runtimeVersion = '${runtimeVersionPlaceholder}';
|
|
265
270
|
|
|
@@ -10,7 +10,7 @@ import { createRequire } from "node:module";
|
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
import minimist from "minimist";
|
|
13
|
-
import { rootDir } from "./utils/load-config.mjs";
|
|
13
|
+
import { loadConfig, rootDir } from "./utils/load-config.mjs";
|
|
14
14
|
import {
|
|
15
15
|
discoverWorkspaceModules,
|
|
16
16
|
commitIncrementalPublish,
|
|
@@ -55,6 +55,7 @@ publish-all - 同步、构建、上传并注册应用工作区产物
|
|
|
55
55
|
--changed 只发布 git 变更触达的 src/forms/* 和 src/pages/*
|
|
56
56
|
--since <ref> 配合 --changed 使用,默认 HEAD
|
|
57
57
|
--force 忽略增量缓存,强制发布
|
|
58
|
+
--legacy-form-bundle React SPA 下仍发布旧表单 bundle 到 OSS
|
|
58
59
|
--help, -h 显示帮助信息
|
|
59
60
|
`);
|
|
60
61
|
process.exit(0);
|
|
@@ -137,6 +138,11 @@ function run(script, scriptArgs = []) {
|
|
|
137
138
|
|
|
138
139
|
const maybeDryRun = dryRun ? ["--dry-run"] : [];
|
|
139
140
|
const maybeForce = force ? ["--force"] : [];
|
|
141
|
+
const config = await loadConfig();
|
|
142
|
+
const publishLegacyFormBundle =
|
|
143
|
+
config.runtimeMode !== "react-spa" ||
|
|
144
|
+
Boolean(args["legacy-form-bundle"]) ||
|
|
145
|
+
Boolean(config.forms?.publishLegacyBundle);
|
|
140
146
|
|
|
141
147
|
const allModules = discoverWorkspaceModules();
|
|
142
148
|
const plan = planIncrementalPublish(allModules, { force, only });
|
|
@@ -155,9 +161,18 @@ const publishedModules = [];
|
|
|
155
161
|
for (const moduleItem of plan.changed) {
|
|
156
162
|
if (moduleItem.kind === "forms") {
|
|
157
163
|
await run("sync-schema.mjs", ["--form", moduleItem.name, ...maybeDryRun]);
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
164
|
+
if (publishLegacyFormBundle) {
|
|
165
|
+
await run("build-forms.mjs", ["--form", moduleItem.name, ...maybeForce]);
|
|
166
|
+
await run("publish-oss.mjs", ["--form", moduleItem.name, ...maybeDryRun]);
|
|
167
|
+
await run("register.mjs", ["--form", moduleItem.name, ...maybeDryRun]);
|
|
168
|
+
} else {
|
|
169
|
+
console.log(
|
|
170
|
+
`[publish] React SPA 表单 ${moduleItem.name}: 已同步 schema,跳过旧表单 bundle/OSS/register。`,
|
|
171
|
+
);
|
|
172
|
+
console.log(
|
|
173
|
+
"[publish] 如需兼容旧表单页面 bundle,请传 --legacy-form-bundle 或配置 forms.publishLegacyBundle=true。",
|
|
174
|
+
);
|
|
175
|
+
}
|
|
161
176
|
publishedModules.push(moduleItem);
|
|
162
177
|
} else if (moduleItem.kind === "pages") {
|
|
163
178
|
await run("build-pages.mjs", ["--page", moduleItem.name, ...maybeForce]);
|
|
@@ -3,6 +3,7 @@ import { spawnSync } from "node:child_process";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { rootDir } from "./load-config.mjs";
|
|
6
|
+
import { WORKSPACE_STYLE_ENTRY_CANDIDATES } from "./workspace-style-entry.mjs";
|
|
6
7
|
|
|
7
8
|
const CACHE_VERSION = 1;
|
|
8
9
|
export const CACHE_FILE = path.join(rootDir, ".openxiangda", "build-cache.json");
|
|
@@ -85,7 +86,7 @@ export function computeConfigHash() {
|
|
|
85
86
|
"postcss.config.cjs",
|
|
86
87
|
"vite.config.ts",
|
|
87
88
|
"tsconfig.json",
|
|
88
|
-
|
|
89
|
+
...WORKSPACE_STYLE_ENTRY_CANDIDATES,
|
|
89
90
|
]) {
|
|
90
91
|
hash.update(name);
|
|
91
92
|
hash.update("\0");
|
|
@@ -172,7 +173,7 @@ function isGlobalWorkspaceFile(filePath) {
|
|
|
172
173
|
filePath === "postcss.config.cjs" ||
|
|
173
174
|
filePath === "vite.config.ts" ||
|
|
174
175
|
filePath === "tsconfig.json" ||
|
|
175
|
-
filePath
|
|
176
|
+
WORKSPACE_STYLE_ENTRY_CANDIDATES.includes(filePath) ||
|
|
176
177
|
filePath.startsWith("src/shared/")
|
|
177
178
|
);
|
|
178
179
|
}
|
|
@@ -128,6 +128,14 @@ describe("incremental publish cache", () => {
|
|
|
128
128
|
"export const format = String;\n",
|
|
129
129
|
"utf-8",
|
|
130
130
|
);
|
|
131
|
+
fs.mkdirSync(path.join(workspaceRoot, "src", "styles"), {
|
|
132
|
+
recursive: true,
|
|
133
|
+
});
|
|
134
|
+
fs.writeFileSync(
|
|
135
|
+
path.join(workspaceRoot, "src", "styles", "index.css"),
|
|
136
|
+
"@tailwind utilities;\n",
|
|
137
|
+
"utf-8",
|
|
138
|
+
);
|
|
131
139
|
|
|
132
140
|
const incremental = await loadIncremental(workspaceRoot);
|
|
133
141
|
const changed = incremental.resolveGitChangedWorkspaceTargets();
|
|
@@ -135,6 +143,9 @@ describe("incremental publish cache", () => {
|
|
|
135
143
|
expect(changed.available).toBe(true);
|
|
136
144
|
expect(changed.only).toEqual(["forms/customer", "pages/dashboard"]);
|
|
137
145
|
expect(changed.resourceFiles).toEqual(["src/resources/menus.json"]);
|
|
138
|
-
expect(changed.globalFiles).toEqual([
|
|
146
|
+
expect(changed.globalFiles).toEqual([
|
|
147
|
+
"src/shared/format.ts",
|
|
148
|
+
"src/styles/index.css",
|
|
149
|
+
]);
|
|
139
150
|
});
|
|
140
151
|
});
|
|
@@ -121,6 +121,11 @@ function normalizeCssIsolation(value) {
|
|
|
121
121
|
return "none";
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
function normalizeRuntimeMode(value) {
|
|
125
|
+
if (value === "react-spa" || value === "spa") return "react-spa";
|
|
126
|
+
return value === "legacy" ? "legacy" : "";
|
|
127
|
+
}
|
|
128
|
+
|
|
124
129
|
export function resolveOpenXiangdaEndpointConfig(baseUrl) {
|
|
125
130
|
const raw = normalizeBaseUrl(baseUrl);
|
|
126
131
|
if (!raw) {
|
|
@@ -174,6 +179,11 @@ export async function loadConfig() {
|
|
|
174
179
|
...source,
|
|
175
180
|
appType: process.env.OPENXIANGDA_APP_TYPE || source.appType || process.env.APP_TYPE,
|
|
176
181
|
appName: source.appName || process.env.APP_NAME || "低代码应用",
|
|
182
|
+
runtimeMode: normalizeRuntimeMode(
|
|
183
|
+
process.env.OPENXIANGDA_RUNTIME_MODE ||
|
|
184
|
+
process.env.APP_RUNTIME_MODE ||
|
|
185
|
+
source.runtimeMode,
|
|
186
|
+
),
|
|
177
187
|
platformUrl,
|
|
178
188
|
servicePrefix:
|
|
179
189
|
openXiangdaEndpoint?.servicePrefix ??
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export const WORKSPACE_STYLE_ENTRY_CANDIDATES = [
|
|
5
|
+
"src/index.css",
|
|
6
|
+
"src/styles/index.css",
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
function toPosix(filePath) {
|
|
10
|
+
return filePath.split(path.sep).join("/");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function listWorkspaceStyleEntryPaths(rootDir) {
|
|
14
|
+
return WORKSPACE_STYLE_ENTRY_CANDIDATES.map((candidate) =>
|
|
15
|
+
path.join(rootDir, candidate),
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function resolveWorkspaceStyleEntry(rootDir) {
|
|
20
|
+
const candidates = listWorkspaceStyleEntryPaths(rootDir);
|
|
21
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) || candidates[0];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function createWorkspaceStyleImport(rootDir, fromDir) {
|
|
25
|
+
const stylePath = resolveWorkspaceStyleEntry(rootDir);
|
|
26
|
+
let relativePath = toPosix(path.relative(fromDir, stylePath));
|
|
27
|
+
if (!relativePath.startsWith(".")) relativePath = `./${relativePath}`;
|
|
28
|
+
return relativePath;
|
|
29
|
+
}
|
|
@@ -3,6 +3,7 @@ import { defineAppWorkspaceConfig } from "openxiangda/build";
|
|
|
3
3
|
export default defineAppWorkspaceConfig({
|
|
4
4
|
appType: process.env.APP_TYPE || process.env.OPENXIANGDA_APP_TYPE || "APP_XXXXXXXXXXXXXXXX",
|
|
5
5
|
appName: process.env.APP_NAME || "OpenXiangda React SPA",
|
|
6
|
+
runtimeMode: "react-spa",
|
|
6
7
|
platformUrl:
|
|
7
8
|
process.env.APP_PLATFORM_URL ||
|
|
8
9
|
process.env.OPENXIANGDA_BASE_URL ||
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface AppWorkspaceConfig {
|
|
2
2
|
appType: string;
|
|
3
3
|
appName: string;
|
|
4
|
+
runtimeMode?: "legacy" | "react-spa";
|
|
4
5
|
platformUrl: string;
|
|
5
6
|
servicePrefix: string;
|
|
6
7
|
appKey: string;
|
|
@@ -24,6 +25,10 @@ export interface AppWorkspaceConfig {
|
|
|
24
25
|
pageMenuParentId: string;
|
|
25
26
|
pageMenuIcon: string;
|
|
26
27
|
};
|
|
28
|
+
forms?: {
|
|
29
|
+
dir?: string;
|
|
30
|
+
publishLegacyBundle?: boolean;
|
|
31
|
+
};
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
export type CustomPageEntryMode = "app-shell" | "plain-page";
|