openxiangda-devkit-core 2.0.0-alpha.73 → 2.0.0-alpha.76
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/dist/compiler/bundle.d.ts.map +1 -1
- package/dist/compiler/bundle.js +34 -4
- package/dist/compiler/bundle.js.map +1 -1
- package/dist/compiler/config.d.ts +3 -1
- package/dist/compiler/config.d.ts.map +1 -1
- package/dist/compiler/config.js +175 -6
- package/dist/compiler/config.js.map +1 -1
- package/dist/control-plane-client.d.ts +2 -2
- package/dist/control-plane-client.d.ts.map +1 -1
- package/dist/workspace-loader.js +1 -1
- package/dist/workspace-loader.js.map +1 -1
- package/package.json +2 -2
package/dist/compiler/config.js
CHANGED
|
@@ -38,6 +38,9 @@ export function adminResourcePage(resourceCode, options) {
|
|
|
38
38
|
export function adminOperationPage(routeCode, options) {
|
|
39
39
|
return adminNavigationItem({ kind: 'operation', routeCode }, options);
|
|
40
40
|
}
|
|
41
|
+
export function adminApplicationTodoCenterPage(options) {
|
|
42
|
+
return adminNavigationItem({ kind: 'application-todo-center' }, options);
|
|
43
|
+
}
|
|
41
44
|
export function adminWorkflowWorkCenterPage(options) {
|
|
42
45
|
return adminNavigationItem({ kind: 'workflow-work-center' }, options);
|
|
43
46
|
}
|
|
@@ -121,6 +124,10 @@ export function renderAdminNavigationSuggestion(config) {
|
|
|
121
124
|
imports.add('adminOperationPage');
|
|
122
125
|
expression = `adminOperationPage(${JSON.stringify(item.page.routeCode)}${options ? `, ${options}` : ''})`;
|
|
123
126
|
break;
|
|
127
|
+
case 'application-todo-center':
|
|
128
|
+
imports.add('adminApplicationTodoCenterPage');
|
|
129
|
+
expression = `adminApplicationTodoCenterPage(${options || ''})`;
|
|
130
|
+
break;
|
|
124
131
|
case 'workflow-work-center':
|
|
125
132
|
imports.add('adminWorkflowWorkCenterPage');
|
|
126
133
|
expression = `adminWorkflowWorkCenterPage(${options || ''})`;
|
|
@@ -761,7 +768,7 @@ export function validateAppConfig(value) {
|
|
|
761
768
|
!['light', 'standard'].includes(String(backend.resourceProfile))) {
|
|
762
769
|
diagnostics.push(diagnostic('APP_CONFIG_BACKEND_RESOURCE_PROFILE_UNSUPPORTED', 'backend.resourceProfile 必须是 light 或 standard', 'backend.resourceProfile'));
|
|
763
770
|
}
|
|
764
|
-
validateFrontendRoutes(
|
|
771
|
+
validateFrontendRoutes(config, diagnostics);
|
|
765
772
|
validateAdminNavigation(config, diagnostics);
|
|
766
773
|
validatePerspectives(config.perspectives, object(config.authz).roles, diagnostics);
|
|
767
774
|
const declaredResourceCodes = new Set((Array.isArray(object(config.data).resources)
|
|
@@ -1740,6 +1747,10 @@ export function validateAppConfig(value) {
|
|
|
1740
1747
|
const editableParameters = Array.isArray(workflows.editableParameters)
|
|
1741
1748
|
? workflows.editableParameters
|
|
1742
1749
|
: [];
|
|
1750
|
+
const frontendRoutesByCode = new Map((Array.isArray(frontend.routes) ? frontend.routes : []).map(routeValue => {
|
|
1751
|
+
const route = object(routeValue);
|
|
1752
|
+
return [string(route.code), route];
|
|
1753
|
+
}));
|
|
1743
1754
|
const definitionByKey = new Map();
|
|
1744
1755
|
const bindingByKey = new Map();
|
|
1745
1756
|
const providerKeys = new Set();
|
|
@@ -1791,6 +1802,9 @@ export function validateAppConfig(value) {
|
|
|
1791
1802
|
const version = Number(declaration.version);
|
|
1792
1803
|
const path = `workflows.definitions[${index}]`;
|
|
1793
1804
|
const launch = object(declaration.launch);
|
|
1805
|
+
if (Object.keys(declaration).some(key => !['version', 'definition', 'launch', 'detailRouteCode'].includes(key))) {
|
|
1806
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_DEFINITION_DECLARATION_INVALID', 'Workflow definition declaration 只能声明 version、definition、launch 和 detailRouteCode', path));
|
|
1807
|
+
}
|
|
1794
1808
|
if (declaration.launch !== undefined &&
|
|
1795
1809
|
(Object.keys(launch).some(key => key !== 'mode') ||
|
|
1796
1810
|
![
|
|
@@ -1801,6 +1815,41 @@ export function validateAppConfig(value) {
|
|
|
1801
1815
|
].includes(string(launch.mode)))) {
|
|
1802
1816
|
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_LAUNCH_MODE_INVALID', 'Workflow launch.mode 必须是 standalone、custom-page、hidden-handoff 或 work-center-only', `${path}.launch.mode`));
|
|
1803
1817
|
}
|
|
1818
|
+
if (declaration.detailRouteCode !== undefined) {
|
|
1819
|
+
const detailRouteCode = object(declaration.detailRouteCode);
|
|
1820
|
+
const desktopCode = string(detailRouteCode.desktop);
|
|
1821
|
+
const mobileCode = string(detailRouteCode.mobile);
|
|
1822
|
+
if (Object.keys(detailRouteCode).some(key => !['desktop', 'mobile'].includes(key)) ||
|
|
1823
|
+
!OPERATION_CODE_PATTERN.test(desktopCode) ||
|
|
1824
|
+
!OPERATION_CODE_PATTERN.test(mobileCode) ||
|
|
1825
|
+
desktopCode === mobileCode) {
|
|
1826
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_DETAIL_ROUTE_INVALID', 'detailRouteCode 必须声明两个不同且合法的 desktop/mobile route code', `${path}.detailRouteCode`));
|
|
1827
|
+
}
|
|
1828
|
+
else {
|
|
1829
|
+
for (const [device, code, surface] of [
|
|
1830
|
+
['desktop', desktopCode, 'admin'],
|
|
1831
|
+
['mobile', mobileCode, 'user'],
|
|
1832
|
+
]) {
|
|
1833
|
+
const route = frontendRoutesByCode.get(code);
|
|
1834
|
+
const routePointer = `${path}.detailRouteCode.${device}`;
|
|
1835
|
+
if (!route) {
|
|
1836
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_DETAIL_ROUTE_REFERENCE_MISSING', `detailRouteCode.${device} 必须引用已声明 frontend route`, routePointer));
|
|
1837
|
+
continue;
|
|
1838
|
+
}
|
|
1839
|
+
if (string(route.surface) !== surface) {
|
|
1840
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_DETAIL_ROUTE_SURFACE_INVALID', `detailRouteCode.${device} 必须引用 ${surface} surface`, routePointer));
|
|
1841
|
+
}
|
|
1842
|
+
const parameters = [
|
|
1843
|
+
...string(route.path).matchAll(/:([A-Za-z][A-Za-z0-9_]*)/g),
|
|
1844
|
+
].map(match => match[1]);
|
|
1845
|
+
if (string(route.path).includes('*') ||
|
|
1846
|
+
parameters.length !== 1 ||
|
|
1847
|
+
parameters[0] !== 'instanceId') {
|
|
1848
|
+
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_DETAIL_ROUTE_PATH_INVALID', `detailRouteCode.${device} 路由必须且只能声明 :instanceId 动态参数`, routePointer));
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1804
1853
|
if (!Number.isInteger(version) || version < 1) {
|
|
1805
1854
|
diagnostics.push(diagnostic('APP_CONFIG_WORKFLOW_VERSION_INVALID', 'Workflow definition version 必须是正整数', `${path}.version`));
|
|
1806
1855
|
}
|
|
@@ -2048,14 +2097,108 @@ function validateBackendOperations(rawOperations, declaredResourceCodes, diagnos
|
|
|
2048
2097
|
routes.add(routeKey);
|
|
2049
2098
|
});
|
|
2050
2099
|
}
|
|
2051
|
-
function
|
|
2100
|
+
function canonicalFrontendRouteShape(path) {
|
|
2101
|
+
const normalized = path.replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/';
|
|
2102
|
+
return normalized
|
|
2103
|
+
.split('/')
|
|
2104
|
+
.map(segment => segment.startsWith(':') ? ':' : segment.includes('*') ? '*' : segment)
|
|
2105
|
+
.join('/');
|
|
2106
|
+
}
|
|
2107
|
+
function generatedPlatformRouteClaims(config) {
|
|
2108
|
+
const claims = [
|
|
2109
|
+
{ owner: 'platform:admin-entry', path: '/' },
|
|
2110
|
+
{ owner: 'platform:admin-entry', path: '/admin' },
|
|
2111
|
+
{
|
|
2112
|
+
owner: 'platform:file-preview',
|
|
2113
|
+
path: '/files/:resourceCode/:fileId/preview',
|
|
2114
|
+
},
|
|
2115
|
+
];
|
|
2116
|
+
const data = object(config.data);
|
|
2117
|
+
const resources = Array.isArray(data.resources) ? data.resources : [];
|
|
2118
|
+
for (const rawResource of resources) {
|
|
2119
|
+
const resource = object(rawResource);
|
|
2120
|
+
const code = string(resource.code);
|
|
2121
|
+
if (!code)
|
|
2122
|
+
continue;
|
|
2123
|
+
const surface = object(resource.surface);
|
|
2124
|
+
const generated = object(surface.generated);
|
|
2125
|
+
const native = !surface.mutationOwner || surface.mutationOwner === 'native';
|
|
2126
|
+
const operations = {
|
|
2127
|
+
list: generated.list === undefined ? true : generated.list === true,
|
|
2128
|
+
detail: generated.detail === undefined ? true : generated.detail === true,
|
|
2129
|
+
create: generated.create === undefined ? native : generated.create === true,
|
|
2130
|
+
update: generated.update === undefined ? native : generated.update === true,
|
|
2131
|
+
};
|
|
2132
|
+
const base = `/admin/resources/${code}`;
|
|
2133
|
+
const candidates = [
|
|
2134
|
+
['list', base],
|
|
2135
|
+
['detail', `${base}/:id`],
|
|
2136
|
+
['create', `${base}/new`],
|
|
2137
|
+
['update', `${base}/:id/edit`],
|
|
2138
|
+
];
|
|
2139
|
+
for (const [operation, path] of candidates) {
|
|
2140
|
+
if (!operations[operation])
|
|
2141
|
+
continue;
|
|
2142
|
+
claims.push({ owner: `resource:${code}:${operation}`, path }, { owner: `resource:${code}:${operation}:mobile`, path: `/m${path}` });
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
const frontend = object(config.frontend);
|
|
2146
|
+
const admin = object(frontend.admin);
|
|
2147
|
+
const navigation = Array.isArray(admin.navigation) ? admin.navigation : [];
|
|
2148
|
+
const hasTodoCenter = navigation.some(rawGroup => {
|
|
2149
|
+
const group = object(rawGroup);
|
|
2150
|
+
const items = Array.isArray(group.items) ? group.items : [];
|
|
2151
|
+
return items.some(rawItem => string(object(object(rawItem).page).kind) === 'application-todo-center');
|
|
2152
|
+
});
|
|
2153
|
+
if (hasTodoCenter) {
|
|
2154
|
+
claims.push({ owner: 'application:todo-center', path: '/admin/todos' }, { owner: 'application:todo-center:mobile', path: '/m/todos' });
|
|
2155
|
+
}
|
|
2156
|
+
const workflows = object(config.workflows);
|
|
2157
|
+
const definitions = Array.isArray(workflows.definitions)
|
|
2158
|
+
? workflows.definitions
|
|
2159
|
+
: [];
|
|
2160
|
+
if (definitions.length > 0) {
|
|
2161
|
+
claims.push({ owner: 'workflow:work-center', path: '/admin/work-center' }, { owner: 'workflow:work-center:mobile', path: '/m/work-center' }, { owner: 'workflow:task', path: '/admin/tasks/:taskId' }, { owner: 'workflow:task:mobile', path: '/m/tasks/:taskId' }, { owner: 'workflow:instance', path: '/admin/workflows/:instanceId' }, {
|
|
2162
|
+
owner: 'workflow:instance:mobile',
|
|
2163
|
+
path: '/m/workflows/:instanceId',
|
|
2164
|
+
});
|
|
2165
|
+
const standaloneCodes = new Set();
|
|
2166
|
+
for (const rawDefinition of definitions) {
|
|
2167
|
+
const declaration = object(rawDefinition);
|
|
2168
|
+
if (string(object(declaration.launch).mode) !== 'standalone')
|
|
2169
|
+
continue;
|
|
2170
|
+
const workflowCode = string(object(declaration.definition).code);
|
|
2171
|
+
if (workflowCode)
|
|
2172
|
+
standaloneCodes.add(workflowCode);
|
|
2173
|
+
}
|
|
2174
|
+
for (const workflowCode of standaloneCodes) {
|
|
2175
|
+
claims.push({
|
|
2176
|
+
owner: `workflow:${workflowCode}:launch`,
|
|
2177
|
+
path: `/admin/workflows/${workflowCode}/new`,
|
|
2178
|
+
});
|
|
2179
|
+
}
|
|
2180
|
+
if (standaloneCodes.size > 0) {
|
|
2181
|
+
claims.push({
|
|
2182
|
+
owner: 'workflow:launch:mobile',
|
|
2183
|
+
path: '/m/workflows/:workflowCode/start',
|
|
2184
|
+
});
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
return claims;
|
|
2188
|
+
}
|
|
2189
|
+
function validateFrontendRoutes(config, diagnostics) {
|
|
2190
|
+
const frontend = object(config.frontend);
|
|
2191
|
+
const rawRoutes = frontend.routes;
|
|
2052
2192
|
if (rawRoutes !== undefined && !Array.isArray(rawRoutes)) {
|
|
2053
2193
|
diagnostics.push(diagnostic('APP_CONFIG_FRONTEND_ROUTES_INVALID', 'frontend.routes 必须是数组', 'frontend.routes'));
|
|
2054
2194
|
return;
|
|
2055
2195
|
}
|
|
2056
2196
|
const routes = Array.isArray(rawRoutes) ? rawRoutes : [];
|
|
2057
2197
|
const codes = new Set();
|
|
2058
|
-
const
|
|
2198
|
+
const pathClaims = new Map();
|
|
2199
|
+
for (const claim of generatedPlatformRouteClaims(config)) {
|
|
2200
|
+
pathClaims.set(canonicalFrontendRouteShape(claim.path), claim);
|
|
2201
|
+
}
|
|
2059
2202
|
routes.forEach((raw, index) => {
|
|
2060
2203
|
const route = object(raw);
|
|
2061
2204
|
const code = string(route.code);
|
|
@@ -2103,17 +2246,31 @@ function validateFrontendRoutes(rawRoutes, diagnostics) {
|
|
|
2103
2246
|
if (!OPERATION_CODE_PATTERN.test(code) ||
|
|
2104
2247
|
codes.has(code) ||
|
|
2105
2248
|
!validAppPath(routePath) ||
|
|
2106
|
-
paths.has(routePath) ||
|
|
2107
2249
|
!string(route.label) ||
|
|
2108
2250
|
!['admin', 'user'].includes(string(route.surface)) ||
|
|
2251
|
+
(route.surface === 'admin' &&
|
|
2252
|
+
routePath !== '/admin' &&
|
|
2253
|
+
!routePath.startsWith('/admin/')) ||
|
|
2254
|
+
(route.surface === 'user' &&
|
|
2255
|
+
(routePath === '/admin' || routePath.startsWith('/admin/'))) ||
|
|
2109
2256
|
(route.capability !== undefined &&
|
|
2110
2257
|
!CAPABILITY_PATTERN.test(string(route.capability))) ||
|
|
2111
2258
|
accessInvalid ||
|
|
2112
2259
|
routePolicyInvalid) {
|
|
2113
2260
|
diagnostics.push(diagnostic('APP_CONFIG_FRONTEND_ROUTE_INVALID', '前端 route 必须声明唯一 code/path、label、surface、有效访问表达式和有界标签策略;动态 route 不能固定、恢复或保活', path));
|
|
2114
2261
|
}
|
|
2262
|
+
const shape = canonicalFrontendRouteShape(routePath);
|
|
2263
|
+
const existing = pathClaims.get(shape);
|
|
2264
|
+
if (routePath && existing) {
|
|
2265
|
+
diagnostics.push(diagnostic('APP_CONFIG_FRONTEND_ROUTE_PATH_CONFLICT', `前端 route path 与 ${existing.owner} 冲突: ${routePath}`, `${path}.path`));
|
|
2266
|
+
}
|
|
2267
|
+
else if (routePath) {
|
|
2268
|
+
pathClaims.set(shape, {
|
|
2269
|
+
owner: `frontend:${code || index}`,
|
|
2270
|
+
path: routePath,
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2115
2273
|
codes.add(code);
|
|
2116
|
-
paths.add(routePath);
|
|
2117
2274
|
});
|
|
2118
2275
|
const parentByCode = new Map();
|
|
2119
2276
|
routes.forEach((raw, index) => {
|
|
@@ -2239,6 +2396,12 @@ function validateAdminNavigation(config, diagnostics) {
|
|
|
2239
2396
|
internalCode = routeCode;
|
|
2240
2397
|
exists = Boolean(route && route.surface === 'admin' && !/[:*]/.test(route.path));
|
|
2241
2398
|
}
|
|
2399
|
+
else if (kind === 'application-todo-center') {
|
|
2400
|
+
pageKey = 'application:todo-center';
|
|
2401
|
+
defaultLabel = '待办中心';
|
|
2402
|
+
internalCode = pageKey;
|
|
2403
|
+
exists = true;
|
|
2404
|
+
}
|
|
2242
2405
|
else if (kind === 'workflow-work-center') {
|
|
2243
2406
|
pageKey = 'workflow:work-center';
|
|
2244
2407
|
defaultLabel = '我的审批';
|
|
@@ -2255,7 +2418,13 @@ function validateAdminNavigation(config, diagnostics) {
|
|
|
2255
2418
|
exists = Boolean(workflow && workflow.mode === 'standalone');
|
|
2256
2419
|
}
|
|
2257
2420
|
if (Object.keys(item).some(key => !['page', 'label', 'icon', 'order'].includes(key)) ||
|
|
2258
|
-
![
|
|
2421
|
+
![
|
|
2422
|
+
'resource',
|
|
2423
|
+
'operation',
|
|
2424
|
+
'application-todo-center',
|
|
2425
|
+
'workflow-work-center',
|
|
2426
|
+
'workflow-launch',
|
|
2427
|
+
].includes(kind) ||
|
|
2259
2428
|
(item.icon !== undefined &&
|
|
2260
2429
|
!ADMIN_NAVIGATION_ICONS.has(string(item.icon))) ||
|
|
2261
2430
|
!validAdminOrder(item.order)) {
|