@locode2/harness 0.1.0-preview.1

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 (162) hide show
  1. package/README.md +27 -0
  2. package/THIRD_PARTY_LICENSES.txt +32769 -0
  3. package/THIRD_PARTY_NOTICES.md +13 -0
  4. package/apps/host/dist/document-parser-worker.js +215 -0
  5. package/apps/host/dist/frontend-runtime-worker.js +1755 -0
  6. package/apps/host/dist/index.js +23380 -0
  7. package/apps/web/dist/assets/LocalMonacoEditor-CXkKwxiP.css +1 -0
  8. package/apps/web/dist/assets/LocalMonacoEditor-WKAHQmzG.js +940 -0
  9. package/apps/web/dist/assets/LogsView-BFFFSFB-.js +1 -0
  10. package/apps/web/dist/assets/PluginsView-DM_ZnENS.js +1 -0
  11. package/apps/web/dist/assets/SettingsView-Cb5Q4ile.js +1 -0
  12. package/apps/web/dist/assets/abap-DLDM7-KI.js +1 -0
  13. package/apps/web/dist/assets/apex-DNDY2TF8.js +1 -0
  14. package/apps/web/dist/assets/azcli-Y6nb8tq_.js +1 -0
  15. package/apps/web/dist/assets/bat-BwHxbl9M.js +1 -0
  16. package/apps/web/dist/assets/bicep-CFznDFnq.js +2 -0
  17. package/apps/web/dist/assets/cameligo-Bf6VGUru.js +1 -0
  18. package/apps/web/dist/assets/clojure-Dnu-v4kV.js +1 -0
  19. package/apps/web/dist/assets/codicon-Brq4_Ui5.ttf +0 -0
  20. package/apps/web/dist/assets/coffee-Bd8akH9Z.js +1 -0
  21. package/apps/web/dist/assets/cpp-BbWJElDN.js +1 -0
  22. package/apps/web/dist/assets/csharp-Co3qMtFm.js +1 -0
  23. package/apps/web/dist/assets/csp-D-4FJmMZ.js +1 -0
  24. package/apps/web/dist/assets/css-DdJfP1eB.js +3 -0
  25. package/apps/web/dist/assets/css.worker-11mhRRLC.js +97 -0
  26. package/apps/web/dist/assets/cssMode-L5f5LAEn.js +1 -0
  27. package/apps/web/dist/assets/cypher-cTPe9QuQ.js +1 -0
  28. package/apps/web/dist/assets/dart-BOtBlQCF.js +1 -0
  29. package/apps/web/dist/assets/dockerfile-BG73LgW2.js +1 -0
  30. package/apps/web/dist/assets/ecl-BEgZUVRK.js +1 -0
  31. package/apps/web/dist/assets/editor.worker-DysRFSaV.js +30 -0
  32. package/apps/web/dist/assets/elixir-BkW5O-1t.js +1 -0
  33. package/apps/web/dist/assets/flow9-BeJ5waoc.js +1 -0
  34. package/apps/web/dist/assets/freemarker2-UOwftS75.js +3 -0
  35. package/apps/web/dist/assets/fsharp-PahG7c26.js +1 -0
  36. package/apps/web/dist/assets/go-acbASCJo.js +1 -0
  37. package/apps/web/dist/assets/graphql-BxJiqAUM.js +1 -0
  38. package/apps/web/dist/assets/handlebars-C_E41ffr.js +1 -0
  39. package/apps/web/dist/assets/hcl-DtV1sZF8.js +1 -0
  40. package/apps/web/dist/assets/html-HsrixH2x.js +1 -0
  41. package/apps/web/dist/assets/html.worker-C8Tw8ncf.js +474 -0
  42. package/apps/web/dist/assets/htmlMode-DipGmEOi.js +1 -0
  43. package/apps/web/dist/assets/index-COUF63cT.js +213 -0
  44. package/apps/web/dist/assets/index-SNSmF2-c.css +1 -0
  45. package/apps/web/dist/assets/ini-Kd9XrMLS.js +1 -0
  46. package/apps/web/dist/assets/java-CXBNlu9o.js +1 -0
  47. package/apps/web/dist/assets/javascript-D36WEqF9.js +1 -0
  48. package/apps/web/dist/assets/json.worker-BzkYZgJ7.js +62 -0
  49. package/apps/web/dist/assets/jsonMode-D9BrIats.js +7 -0
  50. package/apps/web/dist/assets/julia-cl7-CwDS.js +1 -0
  51. package/apps/web/dist/assets/kotlin-s7OhZKlX.js +1 -0
  52. package/apps/web/dist/assets/less-9HpZscsL.js +2 -0
  53. package/apps/web/dist/assets/lexon-OrD6JF1K.js +1 -0
  54. package/apps/web/dist/assets/liquid-DES89Tjc.js +1 -0
  55. package/apps/web/dist/assets/lock-keyhole-jo-FJcGq.js +1 -0
  56. package/apps/web/dist/assets/lspLanguageFeatures-YKkgcn4q.js +4 -0
  57. package/apps/web/dist/assets/lua-Cyyb5UIc.js +1 -0
  58. package/apps/web/dist/assets/m3-B8OfTtLu.js +1 -0
  59. package/apps/web/dist/assets/markdown-BFxVWTOG.js +1 -0
  60. package/apps/web/dist/assets/mdx-D1Bfax8e.js +1 -0
  61. package/apps/web/dist/assets/mips-CiqrrVzr.js +1 -0
  62. package/apps/web/dist/assets/msdax-DmeGPVcC.js +1 -0
  63. package/apps/web/dist/assets/mysql-C_tMU-Nz.js +1 -0
  64. package/apps/web/dist/assets/objective-c-BDtDVThU.js +1 -0
  65. package/apps/web/dist/assets/pascal-vHIfCaH5.js +1 -0
  66. package/apps/web/dist/assets/pascaligo-DtZ0uQbO.js +1 -0
  67. package/apps/web/dist/assets/perl-Ub6l9XKa.js +1 -0
  68. package/apps/web/dist/assets/pgsql-BlNEE0v7.js +1 -0
  69. package/apps/web/dist/assets/php-BBUBE1dy.js +1 -0
  70. package/apps/web/dist/assets/pla-DSh2-awV.js +1 -0
  71. package/apps/web/dist/assets/postiats-CocnycG-.js +1 -0
  72. package/apps/web/dist/assets/powerquery-tScXyioY.js +1 -0
  73. package/apps/web/dist/assets/powershell-COWaemsV.js +1 -0
  74. package/apps/web/dist/assets/protobuf-Brw8urJB.js +2 -0
  75. package/apps/web/dist/assets/pug-8SOpv6rk.js +1 -0
  76. package/apps/web/dist/assets/python-DKhPhY0N.js +1 -0
  77. package/apps/web/dist/assets/qsharp-Bw9ernYp.js +1 -0
  78. package/apps/web/dist/assets/r-j7ic8hl3.js +1 -0
  79. package/apps/web/dist/assets/razor-Cha8qrI6.js +1 -0
  80. package/apps/web/dist/assets/redis-Bu5POkcn.js +1 -0
  81. package/apps/web/dist/assets/redshift-Bs9aos_-.js +1 -0
  82. package/apps/web/dist/assets/restructuredtext-CqXO7rUv.js +1 -0
  83. package/apps/web/dist/assets/ruby-zBfavPgS.js +1 -0
  84. package/apps/web/dist/assets/rust-BzKRNQWT.js +1 -0
  85. package/apps/web/dist/assets/sb-BBc9UKZt.js +1 -0
  86. package/apps/web/dist/assets/scala-D9hQfWCl.js +1 -0
  87. package/apps/web/dist/assets/scheme-BPhDTwHR.js +1 -0
  88. package/apps/web/dist/assets/scss-CBJaRo0y.js +3 -0
  89. package/apps/web/dist/assets/shell-DiJ1NA_G.js +1 -0
  90. package/apps/web/dist/assets/solidity-Db0IVjzk.js +1 -0
  91. package/apps/web/dist/assets/sophia-CnS9iZB_.js +1 -0
  92. package/apps/web/dist/assets/sparql-CJmd_6j2.js +1 -0
  93. package/apps/web/dist/assets/sql-ClhHkBeG.js +1 -0
  94. package/apps/web/dist/assets/st-CHwy0fLd.js +1 -0
  95. package/apps/web/dist/assets/swift-CnmFD0ga.js +1 -0
  96. package/apps/web/dist/assets/systemverilog-Bs9z6M-B.js +1 -0
  97. package/apps/web/dist/assets/tcl-Dm6ycUr_.js +1 -0
  98. package/apps/web/dist/assets/ts.worker-Bupg6llt.js +67722 -0
  99. package/apps/web/dist/assets/tsMode-CEhssXv8.js +11 -0
  100. package/apps/web/dist/assets/twig-Csy3S7wG.js +1 -0
  101. package/apps/web/dist/assets/typescript-DWqmw58R.js +1 -0
  102. package/apps/web/dist/assets/typespec-Btyra-wh.js +1 -0
  103. package/apps/web/dist/assets/vb-Db0cS2oM.js +1 -0
  104. package/apps/web/dist/assets/wgsl-DumH7NcR.js +298 -0
  105. package/apps/web/dist/assets/workers-BmZvNFRq.js +1 -0
  106. package/apps/web/dist/assets/xml-WQTAFV9b.js +1 -0
  107. package/apps/web/dist/assets/yaml-Ke2Lf79d.js +1 -0
  108. package/apps/web/dist/index.html +15 -0
  109. package/bin/launch.mjs +71 -0
  110. package/bin/locode-mini.mjs +12 -0
  111. package/bin/options.mjs +49 -0
  112. package/bin/restore-assets.mjs +40 -0
  113. package/bundled-plugins/fast-frontend/.gitignore +2 -0
  114. package/bundled-plugins/fast-frontend/README.md +29 -0
  115. package/bundled-plugins/fast-frontend/harness.plugin.json +123 -0
  116. package/bundled-plugins/fast-frontend/runtimes/antd-admin/PUBLIC_CONTRACT.md +139 -0
  117. package/bundled-plugins/fast-frontend/runtimes/antd-admin/contracts/app.schema.json +24 -0
  118. package/bundled-plugins/fast-frontend/runtimes/antd-admin/contracts/route.schema.json +52 -0
  119. package/bundled-plugins/fast-frontend/runtimes/antd-admin/examples/app.json +9 -0
  120. package/bundled-plugins/fast-frontend/runtimes/antd-admin/examples/route.json +8 -0
  121. package/bundled-plugins/fast-frontend/runtimes/antd-admin/index.html +17 -0
  122. package/bundled-plugins/fast-frontend/runtimes/antd-admin/package.json +31 -0
  123. package/bundled-plugins/fast-frontend/runtimes/antd-admin/scripts/smoke-build.mjs +40 -0
  124. package/bundled-plugins/fast-frontend/runtimes/antd-admin/src/inspector/index.ts +612 -0
  125. package/bundled-plugins/fast-frontend/runtimes/antd-admin/src/main.tsx +17 -0
  126. package/bundled-plugins/fast-frontend/runtimes/antd-admin/src/sdk/index.ts +1028 -0
  127. package/bundled-plugins/fast-frontend/runtimes/antd-admin/src/shell/RuntimeApp.tsx +380 -0
  128. package/bundled-plugins/fast-frontend/runtimes/antd-admin/src/shell/runtime.css +246 -0
  129. package/bundled-plugins/fast-frontend/runtimes/antd-admin/tsconfig.json +20 -0
  130. package/bundled-plugins/fast-frontend/runtimes/antd-admin/types/runtime.consumer-smoke.ts +70 -0
  131. package/bundled-plugins/fast-frontend/runtimes/antd-admin/types/runtime.d.ts +227 -0
  132. package/bundled-plugins/fast-frontend/templates/antd-admin/.gitignore +9 -0
  133. package/bundled-plugins/fast-frontend/templates/antd-admin/.harness/template.json +10 -0
  134. package/bundled-plugins/fast-frontend/templates/antd-admin/index.html +13 -0
  135. package/bundled-plugins/fast-frontend/templates/antd-admin/package.json +34 -0
  136. package/bundled-plugins/fast-frontend/templates/antd-admin/pnpm-lock.yaml +1451 -0
  137. package/bundled-plugins/fast-frontend/templates/antd-admin/pnpm-workspace.yaml +4 -0
  138. package/bundled-plugins/fast-frontend/templates/antd-admin/public/generated/.gitkeep +1 -0
  139. package/bundled-plugins/fast-frontend/templates/antd-admin/scripts/validate-routes.mjs +153 -0
  140. package/bundled-plugins/fast-frontend/templates/antd-admin/src/app/App.tsx +27 -0
  141. package/bundled-plugins/fast-frontend/templates/antd-admin/src/app/NotFoundPage.tsx +15 -0
  142. package/bundled-plugins/fast-frontend/templates/antd-admin/src/features/.gitkeep +1 -0
  143. package/bundled-plugins/fast-frontend/templates/antd-admin/src/layouts/MainLayout.tsx +77 -0
  144. package/bundled-plugins/fast-frontend/templates/antd-admin/src/main.tsx +14 -0
  145. package/bundled-plugins/fast-frontend/templates/antd-admin/src/mocks/dashboard.ts +48 -0
  146. package/bundled-plugins/fast-frontend/templates/antd-admin/src/pages/dashboard/page.tsx +151 -0
  147. package/bundled-plugins/fast-frontend/templates/antd-admin/src/pages/dashboard/route.ts +9 -0
  148. package/bundled-plugins/fast-frontend/templates/antd-admin/src/routes/definition.ts +21 -0
  149. package/bundled-plugins/fast-frontend/templates/antd-admin/src/routes/menu.tsx +44 -0
  150. package/bundled-plugins/fast-frontend/templates/antd-admin/src/routes/registry.ts +48 -0
  151. package/bundled-plugins/fast-frontend/templates/antd-admin/src/routes/router.tsx +34 -0
  152. package/bundled-plugins/fast-frontend/templates/antd-admin/src/shared/components/business/.gitkeep +1 -0
  153. package/bundled-plugins/fast-frontend/templates/antd-admin/src/shared/components/business/BusinessProviders.tsx +9 -0
  154. package/bundled-plugins/fast-frontend/templates/antd-admin/src/styles/global.css +180 -0
  155. package/bundled-plugins/fast-frontend/templates/antd-admin/src/vite-env.d.ts +1 -0
  156. package/bundled-plugins/fast-frontend/templates/antd-admin/tsconfig.app.json +24 -0
  157. package/bundled-plugins/fast-frontend/templates/antd-admin/tsconfig.json +7 -0
  158. package/bundled-plugins/fast-frontend/templates/antd-admin/tsconfig.node.json +17 -0
  159. package/bundled-plugins/fast-frontend/templates/antd-admin/vite.config.ts +39 -0
  160. package/npm-shrinkwrap.json +9737 -0
  161. package/package.json +80 -0
  162. package/release-integrity.json +168 -0
@@ -0,0 +1,380 @@
1
+ import {
2
+ App as AntApp,
3
+ Avatar,
4
+ Breadcrumb,
5
+ ConfigProvider,
6
+ Empty,
7
+ Layout,
8
+ Menu,
9
+ Result,
10
+ Skeleton,
11
+ Space,
12
+ theme,
13
+ Typography,
14
+ type MenuProps,
15
+ } from "antd";
16
+ import {
17
+ AppstoreOutlined,
18
+ BarChartOutlined,
19
+ DashboardOutlined,
20
+ FileTextOutlined,
21
+ FormOutlined,
22
+ HomeOutlined,
23
+ SettingOutlined,
24
+ TeamOutlined,
25
+ UnorderedListOutlined,
26
+ } from "@ant-design/icons";
27
+ import {
28
+ Component,
29
+ Suspense,
30
+ createElement,
31
+ useCallback,
32
+ useEffect,
33
+ useMemo,
34
+ useRef,
35
+ useState,
36
+ type ErrorInfo,
37
+ type PropsWithChildren,
38
+ type ReactNode,
39
+ } from "react";
40
+ import {
41
+ HashRouter,
42
+ Navigate,
43
+ Route,
44
+ Routes,
45
+ matchPath,
46
+ useLocation,
47
+ useNavigate,
48
+ } from "react-router-dom";
49
+ import {
50
+ appManifest,
51
+ providers,
52
+ routes,
53
+ stylesUrl,
54
+ } from "virtual:harness-business-registry";
55
+
56
+ import {
57
+ FrontendChromeContextProvider,
58
+ FrontendRuntimeContextProvider,
59
+ type FrontendChromeRegistrationApi,
60
+ type FrontendPageOptions,
61
+ type FrontendRouteMetadata,
62
+ type FrontendRuntimeProvider,
63
+ type FrontendShellOptions,
64
+ type FrontendShellUser,
65
+ } from "../sdk/index.js";
66
+
67
+ const { Header, Sider, Content } = Layout;
68
+
69
+ const icons: Record<string, ReactNode> = {
70
+ AppstoreOutlined: <AppstoreOutlined />,
71
+ BarChartOutlined: <BarChartOutlined />,
72
+ DashboardOutlined: <DashboardOutlined />,
73
+ FileTextOutlined: <FileTextOutlined />,
74
+ FormOutlined: <FormOutlined />,
75
+ HomeOutlined: <HomeOutlined />,
76
+ SettingOutlined: <SettingOutlined />,
77
+ TeamOutlined: <TeamOutlined />,
78
+ UnorderedListOutlined: <UnorderedListOutlined />,
79
+ };
80
+
81
+ function orderRoutes(left: FrontendRouteMetadata, right: FrontendRouteMetadata): number {
82
+ return (left.order ?? 0) - (right.order ?? 0) || left.title.localeCompare(right.title, "zh-CN");
83
+ }
84
+
85
+ function buildMenuItems(metadata: readonly FrontendRouteMetadata[]): MenuProps["items"] {
86
+ const visible = metadata.filter((route) => route.hidden !== true);
87
+ const byParent = new Map<string | undefined, FrontendRouteMetadata[]>();
88
+ for (const route of visible) {
89
+ const siblings = byParent.get(route.parentId) ?? [];
90
+ siblings.push(route);
91
+ byParent.set(route.parentId, siblings);
92
+ }
93
+
94
+ const visit = (parentId: string | undefined): NonNullable<MenuProps["items"]> =>
95
+ (byParent.get(parentId) ?? []).sort(orderRoutes).map((route) => {
96
+ const children = visit(route.id);
97
+ return {
98
+ key: route.path,
99
+ label: route.title,
100
+ icon: route.icon === undefined ? undefined : icons[route.icon],
101
+ ...(children.length > 0 ? { children } : {}),
102
+ };
103
+ });
104
+
105
+ return visit(undefined);
106
+ }
107
+
108
+ function buildBreadcrumbs(
109
+ metadata: readonly FrontendRouteMetadata[],
110
+ active: FrontendRouteMetadata | undefined,
111
+ ): FrontendRouteMetadata[] {
112
+ if (active === undefined) return [];
113
+ const byId = new Map(metadata.map((route) => [route.id, route]));
114
+ const breadcrumbs: FrontendRouteMetadata[] = [];
115
+ const visited = new Set<string>();
116
+ let current: FrontendRouteMetadata | undefined = active;
117
+ while (current !== undefined && !visited.has(current.id)) {
118
+ visited.add(current.id);
119
+ breadcrumbs.unshift(current);
120
+ current = current.parentId === undefined ? undefined : byId.get(current.parentId);
121
+ }
122
+ return breadcrumbs;
123
+ }
124
+
125
+ function BusinessProviders({ children }: PropsWithChildren): ReactNode {
126
+ return providers.reduceRight<ReactNode>(
127
+ (content, Provider: FrontendRuntimeProvider) => createElement(Provider, null, content),
128
+ children,
129
+ );
130
+ }
131
+
132
+ interface RuntimeErrorBoundaryState {
133
+ readonly error?: Error;
134
+ }
135
+
136
+ class RuntimeErrorBoundary extends Component<PropsWithChildren, RuntimeErrorBoundaryState> {
137
+ state: RuntimeErrorBoundaryState = {};
138
+
139
+ static getDerivedStateFromError(error: Error): RuntimeErrorBoundaryState {
140
+ return { error };
141
+ }
142
+
143
+ componentDidCatch(error: Error, info: ErrorInfo): void {
144
+ console.error("Business runtime render failed", error, info.componentStack);
145
+ }
146
+
147
+ render(): ReactNode {
148
+ if (this.state.error !== undefined) {
149
+ return (
150
+ <Result
151
+ status="error"
152
+ title="页面渲染失败"
153
+ subTitle={this.state.error.message}
154
+ />
155
+ );
156
+ }
157
+ return this.props.children;
158
+ }
159
+ }
160
+
161
+ function RuntimeRoutes(): ReactNode {
162
+ const defaultPath = routes
163
+ .map((route) => route.metadata)
164
+ .filter((route) => route.hidden !== true)
165
+ .sort(orderRoutes)[0]?.path;
166
+ const hasRootRoute = routes.some((route) => route.metadata.path === "/");
167
+
168
+ if (routes.length === 0) {
169
+ return <Empty description="还没有业务页面,请在 src/pages 中创建页面" />;
170
+ }
171
+
172
+ return (
173
+ <Routes>
174
+ {routes.map(({ metadata, Component: Page }) => (
175
+ <Route
176
+ key={metadata.id}
177
+ path={metadata.path}
178
+ element={
179
+ <RuntimeErrorBoundary>
180
+ <Suspense fallback={<Skeleton active paragraph={{ rows: 8 }} />}>
181
+ <Page />
182
+ </Suspense>
183
+ </RuntimeErrorBoundary>
184
+ }
185
+ />
186
+ ))}
187
+ {!hasRootRoute && defaultPath !== undefined && (
188
+ <Route path="/" element={<Navigate replace to={defaultPath} />} />
189
+ )}
190
+ <Route path="*" element={<Result status="404" title="404" subTitle="页面不存在" />} />
191
+ </Routes>
192
+ );
193
+ }
194
+
195
+ function RuntimeCurrentUser({ user }: { readonly user: FrontendShellUser }): ReactNode {
196
+ const fallback = typeof user.name === "string" && user.name.trim().length > 0
197
+ ? user.name.trim().slice(0, 1)
198
+ : "用";
199
+ return (
200
+ <div className="harness-runtime-current-user">
201
+ {user.avatar ?? <Avatar size="small">{fallback}</Avatar>}
202
+ <div className="harness-runtime-current-user-copy">
203
+ <Typography.Text ellipsis>{user.name}</Typography.Text>
204
+ {user.description === undefined
205
+ ? null
206
+ : <Typography.Text ellipsis type="secondary" className="harness-runtime-current-user-description">{user.description}</Typography.Text>}
207
+ </div>
208
+ </div>
209
+ );
210
+ }
211
+
212
+ function RuntimeShell(): ReactNode {
213
+ const location = useLocation();
214
+ const navigate = useNavigate();
215
+ const shellToken = useRef<symbol | undefined>(undefined);
216
+ const pageToken = useRef<symbol | undefined>(undefined);
217
+ const [shellOptions, setShellOptions] = useState<FrontendShellOptions>({});
218
+ const [pageRegistration, setPageRegistration] = useState<{
219
+ readonly routeKey: string;
220
+ readonly options: FrontendPageOptions;
221
+ }>();
222
+ const metadata = useMemo(() => routes.map((route) => route.metadata), []);
223
+ const menuItems = useMemo(() => buildMenuItems(metadata), [metadata]);
224
+ const activeRoute = metadata.find((route) =>
225
+ matchPath({ path: route.path, end: true }, location.pathname),
226
+ );
227
+ const breadcrumbs = useMemo(
228
+ () => buildBreadcrumbs(metadata, activeRoute),
229
+ [activeRoute, metadata],
230
+ );
231
+ const runtimeApi = useMemo(() => ({
232
+ app: appManifest,
233
+ routes: metadata,
234
+ currentPath: location.pathname,
235
+ navigate,
236
+ }), [location.pathname, metadata, navigate]);
237
+ const registerShell = useCallback((options: FrontendShellOptions) => {
238
+ const token = Symbol("frontend-shell");
239
+ shellToken.current = token;
240
+ setShellOptions(options);
241
+ return () => {
242
+ if (shellToken.current !== token) return;
243
+ shellToken.current = undefined;
244
+ setShellOptions({});
245
+ };
246
+ }, []);
247
+ const registerPage = useCallback((routeKey: string, options: FrontendPageOptions) => {
248
+ const token = Symbol("frontend-page");
249
+ pageToken.current = token;
250
+ setPageRegistration({ routeKey, options });
251
+ return () => {
252
+ if (pageToken.current !== token) return;
253
+ pageToken.current = undefined;
254
+ setPageRegistration(undefined);
255
+ };
256
+ }, []);
257
+ const chromeApi = useMemo<FrontendChromeRegistrationApi>(() => ({
258
+ routeKey: location.pathname,
259
+ registerShell,
260
+ registerPage,
261
+ }), [location.pathname, registerPage, registerShell]);
262
+ const pageOptions = pageRegistration?.routeKey === location.pathname
263
+ ? pageRegistration.options
264
+ : undefined;
265
+ const pageWidth = pageOptions?.width ?? "full";
266
+ const pageTitle = pageOptions === undefined ? undefined : pageOptions.title ?? activeRoute?.title;
267
+ const hasHeaderExtensions = shellOptions.headerActions !== undefined ||
268
+ shellOptions.currentUser !== undefined || shellOptions.userSwitcher !== undefined;
269
+ const hasPageHeading = pageOptions !== undefined && (
270
+ pageTitle !== undefined || pageOptions.description !== undefined || pageOptions.titleActions !== undefined
271
+ );
272
+
273
+ useEffect(() => {
274
+ if (stylesUrl === undefined) return undefined;
275
+ const link = document.createElement("link");
276
+ link.rel = "stylesheet";
277
+ link.href = stylesUrl;
278
+ link.dataset.harnessBusinessStyles = "true";
279
+ document.head.append(link);
280
+ return () => link.remove();
281
+ }, []);
282
+
283
+ return (
284
+ <FrontendRuntimeContextProvider value={runtimeApi}>
285
+ <FrontendChromeContextProvider value={chromeApi}>
286
+ <BusinessProviders>
287
+ <Layout className="harness-runtime-layout">
288
+ <Sider className="harness-runtime-sider" breakpoint="lg" collapsedWidth="0" width={232}>
289
+ <div className="harness-runtime-brand">
290
+ {appManifest.logo !== undefined && (
291
+ <img alt="" className="harness-runtime-logo" src={appManifest.logo} />
292
+ )}
293
+ <Typography.Title ellipsis level={4}>{appManifest.name ?? "业务管理平台"}</Typography.Title>
294
+ </div>
295
+ <Menu
296
+ items={menuItems}
297
+ mode="inline"
298
+ selectedKeys={activeRoute === undefined ? [] : [activeRoute.path]}
299
+ onClick={({ key }) => navigate(key)}
300
+ />
301
+ </Sider>
302
+ <Layout className="harness-runtime-main">
303
+ <Header className="harness-runtime-header">
304
+ <div className="harness-runtime-header-inner">
305
+ <Typography.Text strong ellipsis className="harness-runtime-header-brand">
306
+ {appManifest.name ?? "业务管理平台"}
307
+ </Typography.Text>
308
+ {hasHeaderExtensions && (
309
+ <Space className="harness-runtime-header-controls" size="middle">
310
+ {shellOptions.headerActions}
311
+ {shellOptions.userSwitcher}
312
+ {shellOptions.currentUser === undefined
313
+ ? null
314
+ : <RuntimeCurrentUser user={shellOptions.currentUser} />}
315
+ </Space>
316
+ )}
317
+ </div>
318
+ </Header>
319
+ <Content className="harness-runtime-content">
320
+ <div className={`harness-runtime-content-inner harness-runtime-content-${pageWidth}`}>
321
+ {breadcrumbs.length > 0 && (
322
+ <Breadcrumb
323
+ className="harness-runtime-breadcrumb"
324
+ items={breadcrumbs.map((route, index) => ({
325
+ title: index === breadcrumbs.length - 1
326
+ ? route.title
327
+ : <a href={`#${route.path}`}>{route.title}</a>,
328
+ }))}
329
+ />
330
+ )}
331
+ {hasPageHeading && (
332
+ <div className="harness-runtime-page-heading">
333
+ <div className="harness-runtime-page-heading-copy">
334
+ {pageTitle === undefined
335
+ ? null
336
+ : <Typography.Title level={3} ellipsis className="harness-runtime-page-title">{pageTitle}</Typography.Title>}
337
+ {pageOptions?.description === undefined
338
+ ? null
339
+ : <Typography.Paragraph type="secondary" className="harness-runtime-page-description">{pageOptions.description}</Typography.Paragraph>}
340
+ </div>
341
+ {pageOptions?.titleActions === undefined
342
+ ? null
343
+ : <div className="harness-runtime-page-title-actions">{pageOptions.titleActions}</div>}
344
+ </div>
345
+ )}
346
+ <RuntimeRoutes />
347
+ </div>
348
+ </Content>
349
+ </Layout>
350
+ </Layout>
351
+ </BusinessProviders>
352
+ </FrontendChromeContextProvider>
353
+ </FrontendRuntimeContextProvider>
354
+ );
355
+ }
356
+
357
+ export function RuntimeApp(): ReactNode {
358
+ const compact = appManifest.theme?.compact === true;
359
+ return (
360
+ <ConfigProvider
361
+ theme={{
362
+ algorithm: compact ? theme.compactAlgorithm : theme.defaultAlgorithm,
363
+ token: {
364
+ colorPrimary: appManifest.theme?.colorPrimary ?? "#1677ff",
365
+ borderRadius: appManifest.theme?.borderRadius ?? 8,
366
+ },
367
+ }}
368
+ >
369
+ <AntApp>
370
+ <HashRouter>
371
+ <RuntimeErrorBoundary>
372
+ <Suspense fallback={<Skeleton active paragraph={{ rows: 8 }} />}>
373
+ <RuntimeShell />
374
+ </Suspense>
375
+ </RuntimeErrorBoundary>
376
+ </HashRouter>
377
+ </AntApp>
378
+ </ConfigProvider>
379
+ );
380
+ }
@@ -0,0 +1,246 @@
1
+ :root {
2
+ color: #172033;
3
+ background: #f4f7fb;
4
+ font-family: Inter, "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
5
+ font-synthesis: none;
6
+ text-rendering: optimizeLegibility;
7
+ }
8
+
9
+ * {
10
+ box-sizing: border-box;
11
+ }
12
+
13
+ html,
14
+ body,
15
+ #root {
16
+ min-width: 320px;
17
+ height: 100% !important;
18
+ min-height: 0 !important;
19
+ margin: 0;
20
+ overflow: hidden !important;
21
+ }
22
+
23
+ .harness-runtime-layout {
24
+ height: 100vh !important;
25
+ height: 100dvh !important;
26
+ min-height: 0 !important;
27
+ overflow: hidden !important;
28
+ }
29
+
30
+ .harness-runtime-main {
31
+ min-width: 0 !important;
32
+ min-height: 0 !important;
33
+ overflow: hidden !important;
34
+ }
35
+
36
+ .harness-runtime-sider {
37
+ height: 100%;
38
+ border-right: 1px solid #e7ebf2;
39
+ background: #fff !important;
40
+ }
41
+
42
+ .harness-runtime-sider .ant-layout-sider-children {
43
+ display: flex;
44
+ height: 100%;
45
+ min-height: 0;
46
+ flex-direction: column;
47
+ overflow: hidden;
48
+ }
49
+
50
+ .harness-runtime-sider .ant-menu {
51
+ min-height: 0;
52
+ flex: 1;
53
+ overflow-x: hidden;
54
+ overflow-y: auto;
55
+ }
56
+
57
+ .harness-runtime-brand {
58
+ display: flex;
59
+ min-height: 64px;
60
+ align-items: center;
61
+ gap: 10px;
62
+ padding: 12px 20px;
63
+ border-bottom: 1px solid #eef1f6;
64
+ }
65
+
66
+ .harness-runtime-brand .ant-typography {
67
+ margin: 0;
68
+ }
69
+
70
+ .harness-runtime-logo {
71
+ width: 32px;
72
+ height: 32px;
73
+ border-radius: 8px;
74
+ object-fit: contain;
75
+ }
76
+
77
+ .harness-runtime-header {
78
+ display: flex;
79
+ flex: 0 0 64px;
80
+ align-items: center;
81
+ height: 64px;
82
+ padding: 0 24px;
83
+ border-bottom: 1px solid #e7ebf2;
84
+ background: rgba(255, 255, 255, 0.92);
85
+ backdrop-filter: blur(10px);
86
+ }
87
+
88
+ .harness-runtime-header-inner {
89
+ display: flex;
90
+ width: 100%;
91
+ min-width: 0;
92
+ align-items: center;
93
+ justify-content: space-between;
94
+ gap: 20px;
95
+ }
96
+
97
+ .harness-runtime-header-brand {
98
+ min-width: 0;
99
+ }
100
+
101
+ .harness-runtime-header-controls {
102
+ flex: none;
103
+ }
104
+
105
+ .harness-runtime-current-user {
106
+ display: flex;
107
+ min-width: 0;
108
+ align-items: center;
109
+ gap: 8px;
110
+ }
111
+
112
+ .harness-runtime-current-user-copy {
113
+ display: grid;
114
+ min-width: 0;
115
+ max-width: 168px;
116
+ line-height: 1.25;
117
+ }
118
+
119
+ .harness-runtime-current-user-description {
120
+ font-size: 12px;
121
+ }
122
+
123
+ .harness-runtime-content {
124
+ min-width: 0 !important;
125
+ min-height: 0 !important;
126
+ padding: 24px;
127
+ overflow: auto !important;
128
+ overscroll-behavior: contain;
129
+ }
130
+
131
+ .harness-runtime-content-inner {
132
+ width: 100%;
133
+ margin: 0 auto;
134
+ }
135
+
136
+ .harness-runtime-content-standard {
137
+ max-width: 960px;
138
+ }
139
+
140
+ .harness-runtime-content-wide {
141
+ max-width: 1280px;
142
+ }
143
+
144
+ .harness-runtime-content-full {
145
+ max-width: none;
146
+ }
147
+
148
+ .harness-runtime-breadcrumb {
149
+ margin-bottom: 16px;
150
+ overflow: hidden;
151
+ }
152
+
153
+ .harness-runtime-page-heading {
154
+ display: flex;
155
+ min-width: 0;
156
+ align-items: flex-start;
157
+ justify-content: space-between;
158
+ gap: 20px;
159
+ margin-bottom: 20px;
160
+ }
161
+
162
+ .harness-runtime-page-heading-copy {
163
+ min-width: 0;
164
+ }
165
+
166
+ .harness-runtime-page-title.ant-typography,
167
+ .harness-runtime-page-description.ant-typography {
168
+ margin: 0;
169
+ }
170
+
171
+ .harness-runtime-page-description.ant-typography {
172
+ max-width: 720px;
173
+ margin-top: 4px;
174
+ }
175
+
176
+ .harness-runtime-page-title-actions {
177
+ flex: none;
178
+ }
179
+
180
+ .harness-page-container {
181
+ display: grid;
182
+ min-width: 0;
183
+ gap: 16px;
184
+ }
185
+
186
+ .harness-runtime-metric-grid {
187
+ width: 100%;
188
+ }
189
+
190
+ .harness-runtime-metric-card {
191
+ height: 100%;
192
+ }
193
+
194
+ .harness-runtime-metric-description {
195
+ display: block;
196
+ margin-top: 8px;
197
+ }
198
+
199
+ .harness-runtime-data-table-card {
200
+ min-width: 0;
201
+ }
202
+
203
+ .harness-runtime-data-table-heading {
204
+ display: grid;
205
+ min-width: 0;
206
+ gap: 2px;
207
+ }
208
+
209
+ .harness-runtime-data-table-heading .ant-typography {
210
+ margin: 0;
211
+ }
212
+
213
+ .harness-runtime-crud-drawer-footer {
214
+ display: flex;
215
+ justify-content: flex-end;
216
+ }
217
+
218
+ @media (max-width: 768px) {
219
+ .harness-runtime-content {
220
+ padding: 16px;
221
+ }
222
+
223
+ .harness-runtime-breadcrumb {
224
+ margin-bottom: 12px;
225
+ font-size: 12px;
226
+ }
227
+
228
+ .harness-runtime-header-controls {
229
+ max-width: 72%;
230
+ overflow-x: auto;
231
+ }
232
+
233
+ .harness-runtime-current-user-copy {
234
+ display: none;
235
+ }
236
+
237
+ .harness-runtime-page-heading {
238
+ flex-direction: column;
239
+ gap: 12px;
240
+ }
241
+
242
+ .harness-runtime-page-title-actions {
243
+ width: 100%;
244
+ overflow-x: auto;
245
+ }
246
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "allowJs": false,
7
+ "skipLibCheck": true,
8
+ "esModuleInterop": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "strict": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "module": "ESNext",
13
+ "moduleResolution": "Bundler",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true,
17
+ "jsx": "react-jsx"
18
+ },
19
+ "include": ["src", "types"]
20
+ }
@@ -0,0 +1,70 @@
1
+ import {
2
+ CrudDrawer,
3
+ DataTable,
4
+ FrontendShellProvider,
5
+ MetricGrid,
6
+ PageContainer,
7
+ StatusTag,
8
+ createFrontendStore,
9
+ defineFrontendProviders,
10
+ getFrontendStorage,
11
+ usePersistentFrontendState,
12
+ useFrontendRuntime,
13
+ type FrontendStorage,
14
+ type FrontendRouteMetadata,
15
+ type FrontendRuntimeProvider,
16
+ } from "@harness/frontend-runtime";
17
+ import { createElement, type ReactNode } from "react";
18
+
19
+ const provider: FrontendRuntimeProvider = ({ children }) => createElement(
20
+ FrontendShellProvider,
21
+ {
22
+ currentUser: { name: "Runtime user", description: "管理员" },
23
+ headerActions: createElement("span", null, "全局操作"),
24
+ },
25
+ children,
26
+ );
27
+ export const smokeProviders = defineFrontendProviders([provider]);
28
+ export const smokeStorage: FrontendStorage = getFrontendStorage("smoke");
29
+ export const smokeStore = createFrontendStore({ rows: [{ id: "1", title: "Smoke" }] });
30
+
31
+ export const smokeRoute = {
32
+ id: "smoke",
33
+ path: "/smoke",
34
+ title: "Smoke",
35
+ } satisfies FrontendRouteMetadata;
36
+
37
+ export function useRuntimeContractSmoke(): string {
38
+ return useFrontendRuntime().currentPath;
39
+ }
40
+
41
+ export function RuntimePrimitiveSmoke(): ReactNode {
42
+ const [filter] = usePersistentFrontendState("filter", "", { namespace: "smoke" });
43
+ const rows = smokeStore.useStore((state) => state.rows);
44
+ return createElement(
45
+ PageContainer,
46
+ {
47
+ title: "Runtime primitives",
48
+ description: filter,
49
+ titleActions: createElement(StatusTag, { status: "进行中" }),
50
+ width: "standard",
51
+ },
52
+ createElement(MetricGrid, {
53
+ columns: 2,
54
+ items: [{ key: "rows", title: "Rows", value: rows.length }],
55
+ }),
56
+ createElement(DataTable, {
57
+ rowKey: "id",
58
+ dataSource: rows,
59
+ columns: [{ title: "Title", dataIndex: "title" }],
60
+ toolbar: createElement("span", null, "Toolbar"),
61
+ }),
62
+ createElement(CrudDrawer, {
63
+ open: false,
64
+ title: "Edit",
65
+ initialValues: rows[0],
66
+ onClose: () => undefined,
67
+ onSubmit: () => undefined,
68
+ }),
69
+ );
70
+ }