openxiangda-cli 2.0.0-alpha.124 → 2.0.0-alpha.126
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/package.json +5 -5
- package/template/AGENTS.md +1 -0
- package/template/README.md +8 -0
- package/template/apps/server/package.json +1 -1
- package/template/apps/web/e2e/workflow.spec.ts +1 -1
- package/template/apps/web/package.json +1 -1
- package/template/apps/web/src/main.tsx +26 -3
- package/template/apps/web/test/contracts.test.ts +6 -1
- package/template/openxiangda.config.ts +39 -1
- package/template/package.json +2 -1
- package/template/packages/contracts/src/generated.ts +99 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openxiangda-cli",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.126",
|
|
4
4
|
"description": "Thin application-level CLI for OpenXiangda 2.0.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@oclif/core": "4.13.3",
|
|
24
|
-
"openxiangda-contracts": "2.0.0-alpha.
|
|
25
|
-
"openxiangda-devkit-core": "2.0.0-alpha.
|
|
26
|
-
"openxiangda-mcp": "2.0.0-alpha.
|
|
27
|
-
"openxiangda-skill-kit": "2.0.0-alpha.
|
|
24
|
+
"openxiangda-contracts": "2.0.0-alpha.62",
|
|
25
|
+
"openxiangda-devkit-core": "2.0.0-alpha.80",
|
|
26
|
+
"openxiangda-mcp": "2.0.0-alpha.80",
|
|
27
|
+
"openxiangda-skill-kit": "2.0.0-alpha.101"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"tsx": "4.23.12",
|
package/template/AGENTS.md
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
- `appspec/` is the optional OpenXiangda 2.0 business-intent layer. Before changing observable behavior, read the bounded index with `pnpm openxiangda spec context --json`, then select only the relevant stable ID; use no ChangeSpec for L0 work, one short ChangeSpec for L1, and add permission/rollback/concurrency detail only for L2/L3. Before close, the user—not AI—confirms `currentSpec=merged` or `not-applicable`. AppSpec is advisory and never a release gate. Never read, import or migrate 1.x `openspec/` or SDD.
|
|
5
5
|
- Use Vite, React Router, Refine Core and Ant Design. Do not add Umi, ProComponents or another admin shell.
|
|
6
6
|
- Bind every generated `appRoutes` entry to its local page with `defineApplicationContributions`; desktop `admin` routes stay inside the platform Shell, while `user` routes render without an admin Shell for independent mobile/user experiences. Generated resource CRUD routes are compiler-owned under `/admin/resources/<resourceCode>...` and `/m/admin/resources/<resourceCode>...`; never recreate root resource paths, aliases or redirects. Explicit routes that have the same canonical shape as another explicit or generated route fail compilation, even when dynamic parameter names differ. Use only the typed `toolbar`, `row` and `detail` resource slots for generated resource actions. Declare the complete editable admin menu with `defineAdminNavigation` and its page/group helpers; the Shell renders only generated `adminNavigation` references and permissions only filter them. Do not create another router, menu store, layout, identity provider, permission store or copied CRUD page; route/action access uses capability or `allOf`/`anyOf`, while Data/App API and Workflow authorization remain server-owned.
|
|
7
|
+
- Application login is optional `frontend.authentication`: existing platform users only, registration rejected, exact desktop `/login` and mobile `/m/login`. Bind generated `authenticationSurfaces` to separate PC/mobile renderers through `defineApplicationContributions`; renderers own only brand visuals and call `ApplicationLoginSurfaceProps`. The platform alone owns passwords, providers, OAuth state/callbacks, secure cookies, current identity and authorization. Never put login in protected `appRoutes`, call a v1 auth route, store tokens, create users/roles, or add another Router/identity provider. Keep QA in generated `platformAuthManifest`, outside the protected route denominator.
|
|
7
8
|
- Declare each resource once in `openxiangda.config.ts` with only `code`, `name`, `fields` and optional `mutationOwner`, generated/list/layout/data-policy settings. Each field owns type, label, required state, Surface flags, reference/file metadata and access. Resource codes are lower kebab-case. Use `native` for direct Data API mutations, `action`, `readonly` or `workflow` for non-Native ownership; never grant or generate Native mutation for a non-Native owner.
|
|
8
9
|
- Never write resource-level `schemaVersion`, `appCode`, `schema`, `surface`, `capabilities`, `fieldPolicies` or `platform/data` modules. The compiler derives the strict DataResource, CRUD capabilities, Surface and AI Schema. The application manifest still starts with its one top-level `schemaVersion: 3`.
|
|
9
10
|
- Ordinary list/get/create/update/delete, filters, export and batch operations use the platform Native Data API. Do not create Function CRUD or NestJS wrappers.
|
package/template/README.md
CHANGED
|
@@ -26,6 +26,14 @@ BrowserRouter、RuntimeBoundary、Refine、generated routes 和后台 Shell
|
|
|
26
26
|
在显式后台导航组中加入 `adminApplicationTodoCenterPage()`;平台生成
|
|
27
27
|
`/admin/todos` 与 `/m/todos`,只读取当前用户的消息/待办投影并统一解析目标页面。
|
|
28
28
|
|
|
29
|
+
登录声明位于 `frontend.authentication`,只允许现有平台用户并拒绝注册。桌面 `/login`
|
|
30
|
+
与移动 `/m/login` 是独立生成的 `authenticationSurfaces`,不计入受保护的
|
|
31
|
+
`appRoutes`。用 `defineApplicationContributions({ routes: appRoutes,
|
|
32
|
+
authenticationSurfaces }, { pages, authentication })` 分别绑定 PC/移动品牌 renderer;
|
|
33
|
+
renderer 只使用 `ApplicationLoginSurfaceProps` 回调,不能验证密码、读取 Token、处理
|
|
34
|
+
OAuth callback 或创建第二个 Router/身份状态。`platformAuthManifest` 单独记录登录面 QA,
|
|
35
|
+
不会改变用户路由覆盖数。
|
|
36
|
+
|
|
29
37
|
生成式资源后台路径固定为 `/admin/resources/<resourceCode>...`,移动后台面为
|
|
30
38
|
`/m/admin/resources/<resourceCode>...`;普通根路径和 `/m/...` 产品路径可由显式
|
|
31
39
|
`user` route 使用。生成页内部跳转来自编译后的 `adminPages`,不要手写旧根路径、别名或
|
|
@@ -579,7 +579,7 @@ test('shows an explicit authorization error instead of an empty work center', as
|
|
|
579
579
|
await page.goto('/workflow-experience.e2e.html?initial=/admin/work-center', {
|
|
580
580
|
waitUntil: 'domcontentloaded',
|
|
581
581
|
});
|
|
582
|
-
await expect(page.getByRole('alert')).toContainText('
|
|
582
|
+
await expect(page.getByRole('alert')).toContainText('403: forbidden', {
|
|
583
583
|
timeout: 30_000,
|
|
584
584
|
});
|
|
585
585
|
await expect(page.getByRole('link', { name: '采购申请审批' })).toHaveCount(0);
|
|
@@ -3,13 +3,17 @@ import ReactDOM from 'react-dom/client';
|
|
|
3
3
|
import {
|
|
4
4
|
defineApplicationContributions,
|
|
5
5
|
defineWorkflowLaunchContributions,
|
|
6
|
+
DefaultDesktopApplicationLoginSurface,
|
|
7
|
+
DefaultMobileApplicationLoginSurface,
|
|
6
8
|
OpenXiangdaApplication,
|
|
9
|
+
type ApplicationLoginSurfaceProps,
|
|
7
10
|
} from 'openxiangda/react';
|
|
8
11
|
import {
|
|
9
12
|
appCode,
|
|
10
13
|
appName,
|
|
11
14
|
appPerspectives,
|
|
12
15
|
appRoutes,
|
|
16
|
+
authenticationSurfaces,
|
|
13
17
|
adminNavigation,
|
|
14
18
|
adminPages,
|
|
15
19
|
resourceDefinitions,
|
|
@@ -17,9 +21,28 @@ import {
|
|
|
17
21
|
} from '../../../packages/contracts/src/generated.js';
|
|
18
22
|
import 'openxiangda/react/styles.css';
|
|
19
23
|
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
const ApplicationHome = () => <main>OpenXiangda 应用</main>;
|
|
25
|
+
const ApplicationHomeMobile = () => <main>OpenXiangda 移动应用</main>;
|
|
26
|
+
const BrandedDesktopLogin = (props: ApplicationLoginSurfaceProps) => (
|
|
27
|
+
<DefaultDesktopApplicationLoginSurface {...props} />
|
|
28
|
+
);
|
|
29
|
+
const BrandedMobileLogin = (props: ApplicationLoginSurfaceProps) => (
|
|
30
|
+
<DefaultMobileApplicationLoginSurface {...props} />
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const applicationContributions = defineApplicationContributions(
|
|
34
|
+
{ routes: appRoutes, authenticationSurfaces },
|
|
35
|
+
{
|
|
36
|
+
pages: {
|
|
37
|
+
applicationHome: ApplicationHome,
|
|
38
|
+
applicationHomeMobile: ApplicationHomeMobile,
|
|
39
|
+
},
|
|
40
|
+
authentication: {
|
|
41
|
+
applicationLogin: BrandedDesktopLogin,
|
|
42
|
+
applicationLoginMobile: BrandedMobileLogin,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
);
|
|
23
46
|
const workflowContributions = defineWorkflowLaunchContributions(
|
|
24
47
|
workflowDefinitions,
|
|
25
48
|
{},
|
|
@@ -4,6 +4,7 @@ import test from 'node:test';
|
|
|
4
4
|
import { readRuntimeMount, resolveApplicationBasename } from 'openxiangda/core';
|
|
5
5
|
import {
|
|
6
6
|
notificationTemplateCodes,
|
|
7
|
+
platformAuthManifest,
|
|
7
8
|
resourceCodes,
|
|
8
9
|
resourceDefinitions,
|
|
9
10
|
} from '../../../packages/contracts/src/generated.js';
|
|
@@ -26,9 +27,13 @@ test('clean template contains only application declarations and one React entry'
|
|
|
26
27
|
notificationTemplateCodes.applicationInformational,
|
|
27
28
|
'application.informational.standard'
|
|
28
29
|
);
|
|
30
|
+
assert.equal(platformAuthManifest?.protectedUserRouteCount, 2);
|
|
31
|
+
assert.equal(platformAuthManifest?.surfaces.length, 2);
|
|
29
32
|
assert.match(config, /data:\s*\{\s*resources:/);
|
|
30
33
|
assert.match(source, /OpenXiangdaApplication/);
|
|
31
|
-
assert.match(source, /
|
|
34
|
+
assert.match(source, /routes: appRoutes, authenticationSurfaces/);
|
|
35
|
+
assert.match(source, /DefaultDesktopApplicationLoginSurface/);
|
|
36
|
+
assert.match(source, /DefaultMobileApplicationLoginSurface/);
|
|
32
37
|
assert.match(source, /contributions=\{applicationContributions\}/);
|
|
33
38
|
assert.match(source, /from 'openxiangda\/react'/);
|
|
34
39
|
assert.match(source, /openxiangda\/react\/styles\.css/);
|
|
@@ -6,7 +6,45 @@ export default defineOpenXiangdaApp({
|
|
|
6
6
|
app: { code: 'openxiangda-application', name: 'OpenXiangda 应用' },
|
|
7
7
|
frontend: {
|
|
8
8
|
root: 'apps/web',
|
|
9
|
-
routes: [
|
|
9
|
+
routes: [
|
|
10
|
+
{
|
|
11
|
+
code: 'application-home',
|
|
12
|
+
path: '/home',
|
|
13
|
+
label: '应用首页',
|
|
14
|
+
surface: 'user',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
code: 'application-home-mobile',
|
|
18
|
+
path: '/m/home',
|
|
19
|
+
label: '移动首页',
|
|
20
|
+
surface: 'user',
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
authentication: {
|
|
24
|
+
accountMode: 'existing-platform-users-only',
|
|
25
|
+
registration: { mode: 'reject' },
|
|
26
|
+
methods: [
|
|
27
|
+
{
|
|
28
|
+
code: 'password',
|
|
29
|
+
type: 'password',
|
|
30
|
+
label: '账号密码登录',
|
|
31
|
+
presentation: 'primary',
|
|
32
|
+
required: true,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
surfaces: {
|
|
36
|
+
desktop: {
|
|
37
|
+
routeCode: 'application-login',
|
|
38
|
+
path: '/login',
|
|
39
|
+
defaultRouteCode: 'application-home',
|
|
40
|
+
},
|
|
41
|
+
mobile: {
|
|
42
|
+
routeCode: 'application-login-mobile',
|
|
43
|
+
path: '/m/login',
|
|
44
|
+
defaultRouteCode: 'application-home-mobile',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
},
|
|
10
48
|
admin: { navigation: defineAdminNavigation([]) },
|
|
11
49
|
},
|
|
12
50
|
backend: {
|
package/template/package.json
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
"name": "openxiangda-application",
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
|
+
"type": "module",
|
|
5
6
|
"packageManager": "pnpm@10.15.1",
|
|
6
7
|
"engines": {
|
|
7
8
|
"node": ">=22.12"
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
"build": "pnpm --recursive build"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
|
-
"openxiangda": "2.0.0-alpha.
|
|
26
|
+
"openxiangda": "2.0.0-alpha.40",
|
|
26
27
|
"typescript": "5.9.3"
|
|
27
28
|
}
|
|
28
29
|
}
|
|
@@ -16,7 +16,102 @@ export const capabilities = [
|
|
|
16
16
|
"app:super-admin:manage"
|
|
17
17
|
] as const;
|
|
18
18
|
export const appOperations = {} as const;
|
|
19
|
-
export const appRoutes = {
|
|
19
|
+
export const appRoutes = {
|
|
20
|
+
applicationHome: {
|
|
21
|
+
"code": "application-home",
|
|
22
|
+
"path": "/home",
|
|
23
|
+
"label": "应用首页",
|
|
24
|
+
"surface": "user",
|
|
25
|
+
"tabPersistence": "session",
|
|
26
|
+
"keepAlive": "none"
|
|
27
|
+
},
|
|
28
|
+
applicationHomeMobile: {
|
|
29
|
+
"code": "application-home-mobile",
|
|
30
|
+
"path": "/m/home",
|
|
31
|
+
"label": "移动首页",
|
|
32
|
+
"surface": "user",
|
|
33
|
+
"tabPersistence": "session",
|
|
34
|
+
"keepAlive": "none"
|
|
35
|
+
}
|
|
36
|
+
} as const;
|
|
37
|
+
export const authenticationMethods = [
|
|
38
|
+
{
|
|
39
|
+
"code": "password",
|
|
40
|
+
"type": "password",
|
|
41
|
+
"label": "账号密码登录",
|
|
42
|
+
"presentation": "primary",
|
|
43
|
+
"required": true
|
|
44
|
+
}
|
|
45
|
+
] as const;
|
|
46
|
+
export const authenticationSurfaces = {
|
|
47
|
+
applicationLogin: {
|
|
48
|
+
"device": "desktop",
|
|
49
|
+
"routeCode": "application-login",
|
|
50
|
+
"path": "/login",
|
|
51
|
+
"defaultRouteCode": "application-home"
|
|
52
|
+
},
|
|
53
|
+
applicationLoginMobile: {
|
|
54
|
+
"device": "mobile",
|
|
55
|
+
"routeCode": "application-login-mobile",
|
|
56
|
+
"path": "/m/login",
|
|
57
|
+
"defaultRouteCode": "application-home-mobile"
|
|
58
|
+
}
|
|
59
|
+
} as const;
|
|
60
|
+
export const applicationAuthentication = {
|
|
61
|
+
"accountMode": "existing-platform-users-only",
|
|
62
|
+
"registration": {
|
|
63
|
+
"mode": "reject"
|
|
64
|
+
},
|
|
65
|
+
"methods": [
|
|
66
|
+
{
|
|
67
|
+
"code": "password",
|
|
68
|
+
"type": "password",
|
|
69
|
+
"label": "账号密码登录",
|
|
70
|
+
"presentation": "primary",
|
|
71
|
+
"required": true
|
|
72
|
+
}
|
|
73
|
+
],
|
|
74
|
+
"surfaces": {
|
|
75
|
+
"desktop": {
|
|
76
|
+
"routeCode": "application-login",
|
|
77
|
+
"path": "/login",
|
|
78
|
+
"defaultRouteCode": "application-home"
|
|
79
|
+
},
|
|
80
|
+
"mobile": {
|
|
81
|
+
"routeCode": "application-login-mobile",
|
|
82
|
+
"path": "/m/login",
|
|
83
|
+
"defaultRouteCode": "application-home-mobile"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} as const;
|
|
87
|
+
export const platformAuthManifest = {
|
|
88
|
+
"schemaVersion": "openxiangda.platform-auth-manifest/v2",
|
|
89
|
+
"appCode": "openxiangda-application",
|
|
90
|
+
"protectedUserRouteCount": 2,
|
|
91
|
+
"methods": [
|
|
92
|
+
{
|
|
93
|
+
"code": "password",
|
|
94
|
+
"type": "password",
|
|
95
|
+
"label": "账号密码登录",
|
|
96
|
+
"presentation": "primary",
|
|
97
|
+
"required": true
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"surfaces": [
|
|
101
|
+
{
|
|
102
|
+
"device": "desktop",
|
|
103
|
+
"routeCode": "application-login",
|
|
104
|
+
"path": "/login",
|
|
105
|
+
"defaultRouteCode": "application-home"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"device": "mobile",
|
|
109
|
+
"routeCode": "application-login-mobile",
|
|
110
|
+
"path": "/m/login",
|
|
111
|
+
"defaultRouteCode": "application-home-mobile"
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
} as const;
|
|
20
115
|
export const adminPages = [] as const;
|
|
21
116
|
export const adminNavigation = [] as const;
|
|
22
117
|
export const eventTypes = [] as const;
|
|
@@ -39,6 +134,9 @@ export type Capability = (typeof capabilities)[number];
|
|
|
39
134
|
export type AppOperation = (typeof appOperations)[keyof typeof appOperations];
|
|
40
135
|
export type AppRoute = (typeof appRoutes)[keyof typeof appRoutes];
|
|
41
136
|
export type AppRouteCode = AppRoute["code"];
|
|
137
|
+
export type ApplicationAuthenticationMethod = (typeof authenticationMethods)[number];
|
|
138
|
+
export type ApplicationAuthenticationSurface = (typeof authenticationSurfaces)[keyof typeof authenticationSurfaces];
|
|
139
|
+
export type ApplicationAuthenticationSurfaceCode = ApplicationAuthenticationSurface["routeCode"];
|
|
42
140
|
export type AdminPage = (typeof adminPages)[number];
|
|
43
141
|
export type AdminNavigationGroup = (typeof adminNavigation)[number];
|
|
44
142
|
export type AppPerspective = (typeof appPerspectives)[number];
|