openxiangda-devkit-core 2.0.0-alpha.106 → 2.0.0-alpha.107

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.
@@ -835,6 +835,7 @@ export function validateAppConfig(value) {
835
835
  validateApplicationAuthentication(config, diagnostics);
836
836
  validateFrontendDevicePolicy(config, diagnostics);
837
837
  validateFrontendRoutes(config, diagnostics);
838
+ validateResourceDetailRoutes(config, diagnostics);
838
839
  validateFrontendUser(config, diagnostics);
839
840
  validateAnonymousPublicAccess(config, diagnostics);
840
841
  validateAdminAccess(config, diagnostics);
@@ -2717,6 +2718,68 @@ function validateFrontendDevicePolicy(config, diagnostics) {
2717
2718
  diagnostics.push(diagnostic('APP_CONFIG_FRONTEND_DEVICE_POLICY_INVALID', 'frontend.devicePolicy 必须是 viewport-family,移动最大宽度和桌面最小宽度为 320-1600 的相邻整数', 'frontend.devicePolicy'));
2718
2719
  }
2719
2720
  }
2721
+ function validateResourceDetailRoutes(config, diagnostics) {
2722
+ const frontend = object(config.frontend);
2723
+ const routesByCode = new Map((Array.isArray(frontend.routes) ? frontend.routes : []).map(rawRoute => {
2724
+ const route = object(rawRoute);
2725
+ return [string(route.code), route];
2726
+ }));
2727
+ const data = object(config.data);
2728
+ const resources = Array.isArray(data.resources) ? data.resources : [];
2729
+ resources.forEach((rawResource, resourceIndex) => {
2730
+ const resource = object(rawResource);
2731
+ if (resource.detailRouteCode === undefined)
2732
+ return;
2733
+ const path = `data.resources[${resourceIndex}].detailRouteCode`;
2734
+ const detailRouteCode = object(resource.detailRouteCode);
2735
+ const desktopCode = string(detailRouteCode.desktop);
2736
+ const mobileCode = string(detailRouteCode.mobile);
2737
+ if (Object.keys(detailRouteCode).some(key => !['desktop', 'mobile'].includes(key)) ||
2738
+ !OPERATION_CODE_PATTERN.test(desktopCode) ||
2739
+ !OPERATION_CODE_PATTERN.test(mobileCode) ||
2740
+ desktopCode === mobileCode) {
2741
+ diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_DETAIL_ROUTE_INVALID', 'detailRouteCode 必须声明两个不同且合法的 desktop/mobile route code', path));
2742
+ return;
2743
+ }
2744
+ const readCapability = string(object(resource.capabilities).read);
2745
+ for (const [device, code] of [
2746
+ ['desktop', desktopCode],
2747
+ ['mobile', mobileCode],
2748
+ ]) {
2749
+ const route = routesByCode.get(code);
2750
+ const routePointer = `${path}.${device}`;
2751
+ if (!route) {
2752
+ diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_DETAIL_ROUTE_REFERENCE_MISSING', `detailRouteCode.${device} 必须引用已声明 frontend route`, routePointer));
2753
+ continue;
2754
+ }
2755
+ if (string(route.surface) !== 'user') {
2756
+ diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_DETAIL_ROUTE_SURFACE_INVALID', `detailRouteCode.${device} 必须引用 user surface`, routePointer));
2757
+ }
2758
+ const routePath = string(route.path);
2759
+ const parameters = [
2760
+ ...routePath.matchAll(/:([A-Za-z][A-Za-z0-9_]*)/g),
2761
+ ].map(match => match[1]);
2762
+ const wrongDeviceFamily = device === 'mobile'
2763
+ ? !routePath.startsWith('/m/')
2764
+ : routePath === '/m' || routePath.startsWith('/m/');
2765
+ if (routePath.includes('*') ||
2766
+ routePath.includes('?') ||
2767
+ routePath.includes('#') ||
2768
+ routePath.split('/').includes('..') ||
2769
+ parameters.length !== 1 ||
2770
+ wrongDeviceFamily) {
2771
+ diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_DETAIL_ROUTE_PATH_INVALID', `detailRouteCode.${device} 路由必须属于对应端且只能声明一个有界动态记录参数`, routePointer));
2772
+ }
2773
+ const access = object(route.access);
2774
+ const allOf = Array.isArray(access.allOf) ? access.allOf.map(string) : [];
2775
+ if (!readCapability ||
2776
+ (string(route.capability) !== readCapability &&
2777
+ !allOf.includes(readCapability))) {
2778
+ diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_DETAIL_ROUTE_CAPABILITY_INVALID', `detailRouteCode.${device} 路由必须要求当前资源的 read capability`, routePointer));
2779
+ }
2780
+ }
2781
+ });
2782
+ }
2720
2783
  function validateFrontendRoutes(config, diagnostics) {
2721
2784
  const frontend = object(config.frontend);
2722
2785
  const rawRoutes = frontend.routes;
@@ -3646,6 +3709,9 @@ export function materializeDataResource(appCode, declaration) {
3646
3709
  ...(declaration.dataPolicyCode
3647
3710
  ? { dataPolicyCode: declaration.dataPolicyCode }
3648
3711
  : {}),
3712
+ ...(declaration.detailRouteCode
3713
+ ? { detailRouteCode: { ...declaration.detailRouteCode } }
3714
+ : {}),
3649
3715
  fieldPolicies: Object.fromEntries(declaration.fields.map(field => [
3650
3716
  field.code,
3651
3717
  {
@@ -3675,6 +3741,7 @@ export function validateAppDeclaration(value) {
3675
3741
  'detail',
3676
3742
  'mobile',
3677
3743
  'dataPolicyCode',
3744
+ 'detailRouteCode',
3678
3745
  ]);
3679
3746
  const fieldKeys = new Set([
3680
3747
  'code',
@@ -3708,7 +3775,7 @@ export function validateAppDeclaration(value) {
3708
3775
  const resourcePath = `data.resources[${resourceIndex}]`;
3709
3776
  for (const key of Object.keys(resource)) {
3710
3777
  if (!resourceKeys.has(key)) {
3711
- diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_PROPERTY_UNSUPPORTED', `${resourcePath}.${key} 不属于单次资源声明合同`, `${resourcePath}.${key}`, '只声明 code、name、fields 及可选列表/布局/数据策略'));
3778
+ diagnostics.push(diagnostic('APP_CONFIG_DATA_RESOURCE_PROPERTY_UNSUPPORTED', `${resourcePath}.${key} 不属于单次资源声明合同`, `${resourcePath}.${key}`, '只声明 code、name、fields 及可选列表/布局/数据策略/详情路由'));
3712
3779
  }
3713
3780
  }
3714
3781
  const fields = Array.isArray(resource.fields) ? resource.fields : [];