openxiangda-devkit-core 2.0.0-alpha.78 → 2.0.0-alpha.80
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 +48 -0
- package/dist/compiler/bundle.js.map +1 -1
- package/dist/compiler/config.d.ts +15 -1
- package/dist/compiler/config.d.ts.map +1 -1
- package/dist/compiler/config.js +155 -1
- package/dist/compiler/config.js.map +1 -1
- package/dist/compiler/package-compiler.d.ts.map +1 -1
- package/dist/compiler/package-compiler.js +4 -0
- package/dist/compiler/package-compiler.js.map +1 -1
- package/package.json +2 -2
package/dist/compiler/config.js
CHANGED
|
@@ -344,7 +344,20 @@ export const openXiangdaAppConfigSchema = {
|
|
|
344
344
|
type: 'object',
|
|
345
345
|
additionalProperties: false,
|
|
346
346
|
required: ['root'],
|
|
347
|
-
properties: {
|
|
347
|
+
properties: {
|
|
348
|
+
root: { type: 'string', minLength: 1 },
|
|
349
|
+
requiredCapabilities: {
|
|
350
|
+
type: 'array',
|
|
351
|
+
maxItems: 64,
|
|
352
|
+
uniqueItems: true,
|
|
353
|
+
items: {
|
|
354
|
+
type: 'string',
|
|
355
|
+
minLength: 1,
|
|
356
|
+
maxLength: 128,
|
|
357
|
+
pattern: '^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$',
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
},
|
|
348
361
|
},
|
|
349
362
|
perspectives: {
|
|
350
363
|
type: 'array',
|
|
@@ -724,6 +737,16 @@ export function validateAppConfig(value) {
|
|
|
724
737
|
if (!string(app.name)) {
|
|
725
738
|
diagnostics.push(diagnostic('APP_CONFIG_NAME_REQUIRED', 'app.name 必须是非空字符串', 'app.name'));
|
|
726
739
|
}
|
|
740
|
+
const requiredCapabilities = object(config.platform).requiredCapabilities;
|
|
741
|
+
if (requiredCapabilities !== undefined &&
|
|
742
|
+
(!Array.isArray(requiredCapabilities) ||
|
|
743
|
+
requiredCapabilities.length > 64 ||
|
|
744
|
+
new Set(requiredCapabilities).size !== requiredCapabilities.length ||
|
|
745
|
+
requiredCapabilities.some(capability => typeof capability !== 'string' ||
|
|
746
|
+
capability.length > 128 ||
|
|
747
|
+
!/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(capability)))) {
|
|
748
|
+
diagnostics.push(diagnostic('APP_CONFIG_PLATFORM_CAPABILITIES_INVALID', 'platform.requiredCapabilities 必须是不重复的规范平台能力代码数组(最多 64 项)', 'platform.requiredCapabilities'));
|
|
749
|
+
}
|
|
727
750
|
const frontend = object(config.frontend);
|
|
728
751
|
const backend = object(config.backend);
|
|
729
752
|
const platform = object(config.platform);
|
|
@@ -768,6 +791,7 @@ export function validateAppConfig(value) {
|
|
|
768
791
|
!['light', 'standard'].includes(String(backend.resourceProfile))) {
|
|
769
792
|
diagnostics.push(diagnostic('APP_CONFIG_BACKEND_RESOURCE_PROFILE_UNSUPPORTED', 'backend.resourceProfile 必须是 light 或 standard', 'backend.resourceProfile'));
|
|
770
793
|
}
|
|
794
|
+
validateApplicationAuthentication(config, diagnostics);
|
|
771
795
|
validateFrontendRoutes(config, diagnostics);
|
|
772
796
|
validateAdminNavigation(config, diagnostics);
|
|
773
797
|
validatePerspectives(config.perspectives, object(config.authz).roles, diagnostics);
|
|
@@ -2199,6 +2223,18 @@ function validateFrontendRoutes(config, diagnostics) {
|
|
|
2199
2223
|
for (const claim of generatedPlatformRouteClaims(config)) {
|
|
2200
2224
|
pathClaims.set(canonicalFrontendRouteShape(claim.path), claim);
|
|
2201
2225
|
}
|
|
2226
|
+
const authentication = object(frontend.authentication);
|
|
2227
|
+
const authenticationSurfaces = object(authentication.surfaces);
|
|
2228
|
+
for (const device of ['desktop', 'mobile']) {
|
|
2229
|
+
const surface = object(authenticationSurfaces[device]);
|
|
2230
|
+
const surfacePath = string(surface.path);
|
|
2231
|
+
if (surfacePath) {
|
|
2232
|
+
pathClaims.set(canonicalFrontendRouteShape(surfacePath), {
|
|
2233
|
+
owner: `authentication:${device}`,
|
|
2234
|
+
path: surfacePath,
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2202
2238
|
routes.forEach((raw, index) => {
|
|
2203
2239
|
const route = object(raw);
|
|
2204
2240
|
const code = string(route.code);
|
|
@@ -2297,6 +2333,124 @@ function validateFrontendRoutes(config, diagnostics) {
|
|
|
2297
2333
|
}
|
|
2298
2334
|
}
|
|
2299
2335
|
}
|
|
2336
|
+
function validateApplicationAuthentication(config, diagnostics) {
|
|
2337
|
+
const frontend = object(config.frontend);
|
|
2338
|
+
if (frontend.authentication === undefined)
|
|
2339
|
+
return;
|
|
2340
|
+
const authentication = object(frontend.authentication);
|
|
2341
|
+
const allowedRootKeys = new Set([
|
|
2342
|
+
'accountMode',
|
|
2343
|
+
'registration',
|
|
2344
|
+
'methods',
|
|
2345
|
+
'surfaces',
|
|
2346
|
+
]);
|
|
2347
|
+
const registration = object(authentication.registration);
|
|
2348
|
+
const surfaces = object(authentication.surfaces);
|
|
2349
|
+
const methods = Array.isArray(authentication.methods)
|
|
2350
|
+
? authentication.methods
|
|
2351
|
+
: [];
|
|
2352
|
+
if (!frontend.authentication ||
|
|
2353
|
+
typeof frontend.authentication !== 'object' ||
|
|
2354
|
+
Array.isArray(frontend.authentication) ||
|
|
2355
|
+
Object.keys(authentication).some(key => !allowedRootKeys.has(key)) ||
|
|
2356
|
+
authentication.accountMode !== 'existing-platform-users-only' ||
|
|
2357
|
+
Object.keys(registration).some(key => key !== 'mode') ||
|
|
2358
|
+
registration.mode !== 'reject' ||
|
|
2359
|
+
!Array.isArray(authentication.methods) ||
|
|
2360
|
+
methods.length < 1 ||
|
|
2361
|
+
methods.length > 8 ||
|
|
2362
|
+
!authentication.surfaces ||
|
|
2363
|
+
typeof authentication.surfaces !== 'object' ||
|
|
2364
|
+
Array.isArray(authentication.surfaces) ||
|
|
2365
|
+
Object.keys(surfaces).some(key => !['desktop', 'mobile'].includes(key)) ||
|
|
2366
|
+
!surfaces.desktop ||
|
|
2367
|
+
!surfaces.mobile) {
|
|
2368
|
+
diagnostics.push(diagnostic('APP_CONFIG_AUTHENTICATION_INVALID', 'frontend.authentication 只支持现有平台用户、拒绝注册、1-8 个方法和独立 desktop/mobile surface', 'frontend.authentication'));
|
|
2369
|
+
}
|
|
2370
|
+
const methodCodes = new Set();
|
|
2371
|
+
const methodTypes = new Set();
|
|
2372
|
+
let primaryCount = 0;
|
|
2373
|
+
methods.forEach((raw, index) => {
|
|
2374
|
+
const method = object(raw);
|
|
2375
|
+
const type = string(method.type);
|
|
2376
|
+
const code = string(method.code);
|
|
2377
|
+
const commonKeys = ['code', 'type', 'label', 'presentation', 'required'];
|
|
2378
|
+
const allowedKeys = new Set(type === 'sso'
|
|
2379
|
+
? [...commonKeys, 'provider']
|
|
2380
|
+
: type === 'dingtalk'
|
|
2381
|
+
? [...commonKeys, 'flow']
|
|
2382
|
+
: commonKeys);
|
|
2383
|
+
if (method.presentation === 'primary')
|
|
2384
|
+
primaryCount += 1;
|
|
2385
|
+
const invalid = Object.keys(method).some(key => !allowedKeys.has(key)) ||
|
|
2386
|
+
!OPERATION_CODE_PATTERN.test(code) ||
|
|
2387
|
+
methodCodes.has(code) ||
|
|
2388
|
+
methodTypes.has(type) ||
|
|
2389
|
+
!['sso', 'password', 'dingtalk'].includes(type) ||
|
|
2390
|
+
!string(method.label) ||
|
|
2391
|
+
!['primary', 'secondary'].includes(string(method.presentation)) ||
|
|
2392
|
+
typeof method.required !== 'boolean' ||
|
|
2393
|
+
(type === 'sso' && method.provider !== 'tenant-default') ||
|
|
2394
|
+
(type === 'dingtalk' &&
|
|
2395
|
+
!['auto', 'jsapi', 'oauth'].includes(string(method.flow)));
|
|
2396
|
+
if (invalid) {
|
|
2397
|
+
diagnostics.push(diagnostic('APP_CONFIG_AUTHENTICATION_METHOD_INVALID', '登录方法必须使用唯一 code、受支持类型和不含 Secret 的有界公开描述', `frontend.authentication.methods[${index}]`));
|
|
2398
|
+
}
|
|
2399
|
+
methodCodes.add(code);
|
|
2400
|
+
methodTypes.add(type);
|
|
2401
|
+
});
|
|
2402
|
+
if (methods.length > 0 && primaryCount !== 1) {
|
|
2403
|
+
diagnostics.push(diagnostic('APP_CONFIG_AUTHENTICATION_PRIMARY_METHOD_INVALID', '登录方法必须且只能有一个 primary', 'frontend.authentication.methods'));
|
|
2404
|
+
}
|
|
2405
|
+
const routes = Array.isArray(frontend.routes) ? frontend.routes : [];
|
|
2406
|
+
const routeByCode = new Map(routes.map(raw => {
|
|
2407
|
+
const route = object(raw);
|
|
2408
|
+
return [string(route.code), route];
|
|
2409
|
+
}));
|
|
2410
|
+
const routeCodes = new Set(routeByCode.keys());
|
|
2411
|
+
const claimedShapes = new Map(generatedPlatformRouteClaims(config).map(claim => [
|
|
2412
|
+
canonicalFrontendRouteShape(claim.path),
|
|
2413
|
+
claim,
|
|
2414
|
+
]));
|
|
2415
|
+
const surfaceCodes = new Set();
|
|
2416
|
+
const surfaceShapes = new Set();
|
|
2417
|
+
for (const device of ['desktop', 'mobile']) {
|
|
2418
|
+
const surface = object(surfaces[device]);
|
|
2419
|
+
const pointer = `frontend.authentication.surfaces.${device}`;
|
|
2420
|
+
const routeCode = string(surface.routeCode);
|
|
2421
|
+
const routePath = string(surface.path);
|
|
2422
|
+
const defaultRouteCode = string(surface.defaultRouteCode);
|
|
2423
|
+
const defaultRoute = routeByCode.get(defaultRouteCode);
|
|
2424
|
+
const shape = canonicalFrontendRouteShape(routePath);
|
|
2425
|
+
const expectedPath = device === 'mobile' ? '/m/login' : '/login';
|
|
2426
|
+
const invalid = Object.keys(surface).some(key => !['routeCode', 'path', 'defaultRouteCode'].includes(key)) ||
|
|
2427
|
+
!OPERATION_CODE_PATTERN.test(routeCode) ||
|
|
2428
|
+
surfaceCodes.has(routeCode) ||
|
|
2429
|
+
routeCodes.has(routeCode) ||
|
|
2430
|
+
!validAppPath(routePath) ||
|
|
2431
|
+
/[:*?#\\\\]/.test(routePath) ||
|
|
2432
|
+
routePath !== expectedPath ||
|
|
2433
|
+
surfaceShapes.has(shape) ||
|
|
2434
|
+
/(?:^|\/)(?:auth|oauth)[^/]*callback(?:\/|$)/i.test(routePath);
|
|
2435
|
+
if (invalid) {
|
|
2436
|
+
diagnostics.push(diagnostic('APP_CONFIG_AUTHENTICATION_SURFACE_INVALID', '登录 surface 必须使用独立静态 routeCode,desktop path=/login、mobile path=/m/login', pointer));
|
|
2437
|
+
}
|
|
2438
|
+
const generatedClaim = claimedShapes.get(shape);
|
|
2439
|
+
if (routePath && generatedClaim) {
|
|
2440
|
+
diagnostics.push(diagnostic('APP_CONFIG_AUTHENTICATION_ROUTE_CONFLICT', `登录 surface path 与 ${generatedClaim.owner} 冲突: ${routePath}`, `${pointer}.path`));
|
|
2441
|
+
}
|
|
2442
|
+
if (!defaultRoute ||
|
|
2443
|
+
defaultRoute.surface !== 'user' ||
|
|
2444
|
+
(device === 'mobile'
|
|
2445
|
+
? !string(defaultRoute.path).startsWith('/m/')
|
|
2446
|
+
: string(defaultRoute.path).startsWith('/m/')) ||
|
|
2447
|
+
canonicalFrontendRouteShape(string(defaultRoute.path)) === shape) {
|
|
2448
|
+
diagnostics.push(diagnostic('APP_CONFIG_AUTHENTICATION_DEFAULT_ROUTE_INVALID', 'defaultRouteCode 必须引用同设备的受保护 user route,且不能指回登录 surface', `${pointer}.defaultRouteCode`));
|
|
2449
|
+
}
|
|
2450
|
+
surfaceCodes.add(routeCode);
|
|
2451
|
+
surfaceShapes.add(shape);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2300
2454
|
const ADMIN_NAVIGATION_ICONS = new Set([
|
|
2301
2455
|
'overview',
|
|
2302
2456
|
'database',
|