@nocobase/app-template-default 1.0.0-beta.73 → 1.0.0-beta.75

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 (38) hide show
  1. package/AGENTS.md +8 -2
  2. package/README.MD +3 -1
  3. package/cli/plugins.ts +2 -1
  4. package/client/components/default-access-editor.tsx +183 -0
  5. package/client/components/permission-editor.tsx +216 -150
  6. package/client/components/role-list.tsx +85 -0
  7. package/client/lib/navigation-transform.ts +29 -1
  8. package/client/locales/system/settings-en-US.ts +74 -2
  9. package/client/locales/system/settings-zh-CN.ts +66 -2
  10. package/client/pages/settings/default-access-form.tsx +499 -0
  11. package/client/pages/settings/default-access-model.ts +154 -0
  12. package/client/pages/settings/default-access.tsx +407 -0
  13. package/client/pages/settings/permission-filters.tsx +43 -37
  14. package/client/pages/settings/permission-management-page.tsx +105 -0
  15. package/client/pages/settings/permissions-model.ts +37 -1
  16. package/client/pages/settings/role-form.tsx +46 -18
  17. package/client/pages/settings/roles.tsx +26 -43
  18. package/client/pages/settings/routes.ts +78 -19
  19. package/client/pages/settings/shared.tsx +3 -0
  20. package/client/pages/settings/users.tsx +1 -1
  21. package/client/plugins.ts +2 -0
  22. package/client/routing/route-navigation.ts +21 -1
  23. package/package.json +25 -23
  24. package/server/plugins.ts +2 -0
  25. package/tests/components/default-access.test.tsx +554 -0
  26. package/tests/components/page-harness.test.tsx +132 -112
  27. package/tests/components/permission-editor.test.tsx +233 -73
  28. package/tests/components/permission-management-tabs.test.tsx +128 -0
  29. package/tests/components/role-list.test.tsx +136 -0
  30. package/tests/components/roles.test.tsx +85 -0
  31. package/tests/components/user-permissions.test.tsx +12 -0
  32. package/tests/logic/authorization-assignment-safety.test.ts +82 -0
  33. package/tests/logic/client-routes.test.ts +8 -0
  34. package/tests/logic/default-access-model.test.ts +167 -0
  35. package/tests/logic/foundation-integration.test.ts +62 -0
  36. package/tests/logic/permission-management-routes.test.ts +64 -0
  37. package/tests/logic/settings-permissions.test.ts +89 -0
  38. package/vitest.config.ts +2 -2
package/AGENTS.md CHANGED
@@ -53,7 +53,7 @@ A feature with a page and an API touches five places: a migration for the table,
53
53
 
54
54
  ## System settings are part of feature delivery
55
55
 
56
- A bare create-app stays Home-only with no settings routes or menu. As the application develops business capabilities, inspect its current state on every iteration and automatically register the prepared Users and Roles and permissions contribution from `client/pages/settings/routes.ts` once in `client/routes.ts`; preserve it and reconcile missing management surfaces on every iteration, without a first-build flag or one-time setup stage. Whenever an application adds lifecycles, workflows, approvals, notifications, files or other configurable capabilities, automatically include their real administration surfaces without requiring a second user request. Follow [system settings delivery](.agents/skills/nocobase-app-development/references/system-settings.md); in particular, every used process includes a definitions directory, standard diagram, supported parameters and authorized execution history. Do not add demo plugins or empty settings for unused capabilities. The template preinstalls `#components/lifecycle-diagram` (React Flow + ELK) and `client/extensions/nocobase-workflow-management/`: use them by default, preserve their visuals unless the user explicitly requests another style, and connect authorized parameter bindings through `LifecycleDiagram.configuration` to the real editor. These are UI Registry sources; an npm package lookup is not a component availability check.
56
+ A bare create-app shows Home to signed-in users and System settings → App foundation only to unrestricted administrators. The Foundation plugin owns this initial settings group; the navigation transform merges its entry into the application settings group when business administration is registered. As the application develops business capabilities, inspect its current state on every iteration and automatically register the prepared Users and Roles and permissions contribution from `client/pages/settings/routes.ts` once in `client/routes.ts`; preserve it and reconcile missing management surfaces on every iteration, without a first-build flag or one-time setup stage. Whenever an application adds lifecycles, workflows, approvals, notifications, files or other configurable capabilities, automatically include their real administration surfaces without requiring a second user request. Follow [system settings delivery](.agents/skills/nocobase-app-development/references/system-settings.md); in particular, every used process includes a definitions directory, standard diagram, supported parameters and authorized execution history. Do not add demo plugins or empty settings for unused capabilities. The template preinstalls `#components/lifecycle-diagram` (React Flow + ELK) and `client/extensions/nocobase-workflow-management/`: use them by default, preserve their visuals unless the user explicitly requests another style, and connect authorized parameter bindings through `LifecycleDiagram.configuration` to the real editor. These are UI Registry sources; an npm package lookup is not a component availability check.
57
57
 
58
58
  ## Rules that apply everywhere
59
59
 
@@ -101,7 +101,7 @@ When reporting or opening this application's address, use the `Local:` URL print
101
101
 
102
102
  Scope all verification — formatting, lint, type checking, tests, builds and runtime checks — to the affected files, projects, or packages and their affected consumers. Do not run full-application or workspace-wide checks, or an aggregate `pnpm check`, as a routine step after each edit. Documentation-only changes need formatting and link checks for the changed documents, not type checking, runtime tests, or builds. Report which checks ran, their scope, and any unverified behavior.
103
103
 
104
- Add tests for what you changed — a route's authenticated, unauthenticated, and unauthorized responses; a migration's `up` and `down` against a real database; a page's actual behavior — in `tests/`, never beside the source; browser tests go in `tests/playwright/`. See [testing and verification](.agents/skills/nocobase-app-development/references/testing.md).
104
+ Add tests for what you changed — a route's authenticated, unauthenticated, and unauthorized responses; a migration's `up` and `down` against a real database; a page's actual behavior — in `tests/`, never beside the source; browser tests go in `tests/playwright/`. Start page tests from `tests/components/page-harness.test.tsx`, which keeps application hooks real and supplies external responses through the test fixture. See [testing and verification](.agents/skills/nocobase-app-development/references/testing.md).
105
105
 
106
106
  For any UI work, start with [the frontend workflow](.agents/skills/nocobase-app-development/references/frontend/ui-workflow.md), which decides the workflow and routes to the styling rules, the theme token contract and the rest.
107
107
 
@@ -123,3 +123,9 @@ When the built-in mechanism genuinely cannot express what is being asked, changi
123
123
  | [.agents/rules/components.md](.agents/rules/components.md) | Adding or changing a component, a shadcn primitive, a UI Library item, or the visual design |
124
124
  | [.agents/rules/shell.md](.agents/rules/shell.md) | Changing layouts, the header, sidebar, breadcrumbs, the account menu or loading indicators |
125
125
  | [.agents/rules/build-and-deploy.md](.agents/rules/build-and-deploy.md) | Changing the build, packing a deployment, the Docker image, `CDN_BASE_URL`, or building for another platform |
126
+
127
+ ## Visible and manageable role assignments
128
+
129
+ Assign application business roles to explicitly identified users through the prepared User management role-assignment UI. Never seed a business permission set onto `authenticated:*`, broaden the inherited platform `member` role, or write startup reconciliation that grants access to everyone merely to make a feature usable. Unchecked actions in one role do not revoke grants inherited from another. If the user explicitly requests a global default role, first deliver a permission-protected administration control that shows its recipients and allows changing or removing the assignment; show inherited roles and their source in user access details. Without that UI, use explicit user assignments and explain the limitation. Keep page access independent from business operations.
130
+
131
+ Run `pnpm exec vitest run tests/logic/authorization-assignment-safety.test.ts` after permission initialization changes and before delivery. It boots the real application on an isolated database and inspects initialized wildcard assignments and the inherited member role. Keep this test in generated applications. An intentional global default requires explicit user authorization, a working administration surface and corresponding acceptance tests before deliberately updating the expected baseline; do not delete, skip or loosen the guard to make tests pass. Test an ordinary user with only the configured role: inspect all effective grant sources and verify both hidden buttons and rejected direct API calls.
package/README.MD CHANGED
@@ -6,7 +6,7 @@ You own everything in this directory. Add pages, API routes, database tables, an
6
6
 
7
7
  ## Default and Examples
8
8
 
9
- Default provides the application shell and product capabilities without example plugins or business demo pages. Application-owned business API routes start empty, and new databases receive no article sample data. A bare create-app shows Home only, with no System settings routes or menu. As business capabilities are developed, the app development skill reconciles missing core administration on every iteration and registers the prepared Users and Roles and permissions contribution (`client/pages/settings/routes.ts`); each actually used configurable capability includes its administration as part of feature delivery. Its built-in Users integration lists direct Authorization Permission Sets as application roles, while access assigned to all authenticated users remains separate. Use `app-template-examples` for article management and runnable plugin demonstrations.
9
+ Default provides the application shell and product capabilities without example plugins or business demo pages. Application-owned business API routes start empty, and new databases receive no article sample data. A bare create-app shows Home and, for unrestricted administrators only, System settings → App foundation. Foundation is installed and registered on both Client and Server; its read-only overview is available at `/foundation` under the application base path. As business capabilities are developed, the app development skill reconciles missing core administration on every iteration and registers the prepared Users and Roles and permissions contribution (`client/pages/settings/routes.ts`); each actually used configurable capability includes its administration as part of feature delivery. Its built-in Users integration lists direct Authorization Permission Sets as application roles, while access assigned to all authenticated users remains separate. Use `app-template-examples` for article management and runnable plugin demonstrations.
10
10
 
11
11
  Existing Default installations retain their data when example plugins are unregistered. If an installation already ran the article migration, retain its original migration sources as described in the [upgrade migration rules](.agents/skills/nocobase-app-upgrade/references/edge-cases.md#migrations) before upgrading. Do not erase migration history or delete existing data to make an upgrade start.
12
12
 
@@ -401,6 +401,8 @@ An environment variable a section declares takes precedence over the file, and t
401
401
 
402
402
  Tests live in `tests/`, never beside the source they cover. Put logic and integration tests under `tests/logic/` and component tests under `tests/components/`. Browser tests belong in `tests/playwright/`, which Vitest skips. Playwright does not start the application: start it first (`pnpm dev`, or `pnpm build` and then the built application), then run `pnpm test:e2e`; each test reads the application URL, including its base path, from `APP_URL`. Tests that only need a real server, real authentication, or a real database run under Vitest with `createAppTest()` from `@nocobase/app-testing/server`.
403
403
 
404
+ Application tests use the `/server`, `/client` and `/cli` entries of `@nocobase/app-testing`. Start page tests from `tests/components/page-harness.test.tsx`: it uses `renderWithApp()` with real application hooks, strict translations, permission checks and a test toaster. Answer requests through `answerApi()` and replace external services through `services`, without also registering their owning plugins. The template's Vitest configuration includes client plugins in custom package scopes so their contexts and service tokens remain shared. See `.agents/skills/nocobase-app-development/references/testing.md` for fixture ownership and complete server, migration and CLI examples.
405
+
404
406
  ### Notification testing
405
407
 
406
408
  With the `notification:test` `send` permission, send a real message through the durable Notification Manager with `POST <APP_BASE_PATH>/api/notifications/testSends`. `GET <APP_BASE_PATH>/api/notifications/testTargets` lists each configured channel and the fields its test takes: Email tests require an address and IM tests use the configured logical target. The server enforces the permission when the message is submitted.
package/cli/plugins.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { defineCliPlugins, type AppCliPlugins } from '@nocobase/app-cli';
2
+ import foundation from '@nocobase/app-plugin-foundation/cli';
2
3
  import scheduler from '@nocobase/app-plugin-scheduler/cli';
3
4
 
4
5
  // Array order is command registration order. A plugin contributes its commands
5
6
  // by appearing in this list; removing its entry and its import removes them.
6
- const cliPlugins: AppCliPlugins = defineCliPlugins([scheduler]);
7
+ const cliPlugins: AppCliPlugins = defineCliPlugins([scheduler, foundation]);
7
8
 
8
9
  export default cliPlugins;
@@ -0,0 +1,183 @@
1
+ import { useId, type ReactElement, type ReactNode } from 'react';
2
+ import { Input } from '#components/ui/input';
3
+ import { Label } from '#components/ui/label';
4
+ import {
5
+ Select,
6
+ SelectContent,
7
+ SelectGroup,
8
+ SelectItem,
9
+ SelectTrigger,
10
+ SelectValue,
11
+ } from '#components/ui/select';
12
+
13
+ export interface DefaultAccessChoice {
14
+ readonly value: string;
15
+ readonly label: string;
16
+ }
17
+ export interface DefaultAccessScopeControl {
18
+ readonly id: string;
19
+ readonly action: string;
20
+ readonly scope: string;
21
+ readonly value: string;
22
+ readonly options: readonly DefaultAccessChoice[];
23
+ readonly configuration?: ReactNode;
24
+ readonly error?: string | undefined;
25
+ }
26
+ export interface DefaultAccessEditorProps {
27
+ readonly ruleKey: string;
28
+ readonly resource: string;
29
+ readonly resources: readonly DefaultAccessChoice[];
30
+ readonly scopes: readonly DefaultAccessScopeControl[];
31
+ readonly existing?: boolean;
32
+ readonly readOnly?: boolean;
33
+ readonly keyError?: string | undefined;
34
+ readonly resourceError?: string | undefined;
35
+ readonly labels: {
36
+ readonly key: string;
37
+ readonly resource: string;
38
+ readonly chooseResource: string;
39
+ readonly action: string;
40
+ readonly scope: string;
41
+ readonly noScopes: string;
42
+ };
43
+ readonly onKeyChange: (value: string) => void;
44
+ readonly onResourceChange: (value: string) => void;
45
+ readonly onScopeChange: (id: string, value: string) => void;
46
+ }
47
+
48
+ /** Controlled fields only: the host owns API authorization, drafts, validation and writes. */
49
+ export function DefaultAccessEditor({
50
+ ruleKey,
51
+ resource,
52
+ resources,
53
+ scopes,
54
+ existing = false,
55
+ readOnly = false,
56
+ keyError,
57
+ resourceError,
58
+ labels,
59
+ onKeyChange,
60
+ onResourceChange,
61
+ onScopeChange,
62
+ }: DefaultAccessEditorProps): ReactElement {
63
+ const id = useId();
64
+ return (
65
+ <div className='flex min-w-0 flex-col gap-6'>
66
+ <div className='grid gap-4 sm:grid-cols-2'>
67
+ <div className='flex min-w-0 flex-col gap-2'>
68
+ <Label htmlFor={`${id}-key`}>{labels.key} *</Label>
69
+ <Input
70
+ id={`${id}-key`}
71
+ name='ruleKey'
72
+ value={ruleKey}
73
+ required
74
+ disabled={existing || readOnly}
75
+ aria-invalid={Boolean(keyError)}
76
+ aria-describedby={keyError ? `${id}-key-error` : undefined}
77
+ onChange={(event) => onKeyChange(event.target.value)}
78
+ />
79
+ {keyError && (
80
+ <p id={`${id}-key-error`} className='text-sm text-destructive'>
81
+ {keyError}
82
+ </p>
83
+ )}
84
+ </div>
85
+ <div className='flex min-w-0 flex-col gap-2'>
86
+ <Label htmlFor={`${id}-resource`}>{labels.resource} *</Label>
87
+ <Select
88
+ value={resource}
89
+ items={resources}
90
+ disabled={existing || readOnly}
91
+ onValueChange={(value) => {
92
+ if (value !== null) onResourceChange(value);
93
+ }}
94
+ >
95
+ <SelectTrigger
96
+ id={`${id}-resource`}
97
+ className='w-full'
98
+ aria-invalid={Boolean(resourceError)}
99
+ aria-describedby={
100
+ resourceError ? `${id}-resource-error` : undefined
101
+ }
102
+ >
103
+ <SelectValue placeholder={labels.chooseResource} />
104
+ </SelectTrigger>
105
+ <SelectContent className='w-auto max-w-[min(var(--container-sm),var(--available-width))] min-w-(--anchor-width) [&_[data-slot=select-item]>:first-child]:whitespace-normal'>
106
+ <SelectGroup>
107
+ {resources.map((option) => (
108
+ <SelectItem key={option.value} value={option.value}>
109
+ {option.label}
110
+ </SelectItem>
111
+ ))}
112
+ </SelectGroup>
113
+ </SelectContent>
114
+ </Select>
115
+ {resourceError && (
116
+ <p id={`${id}-resource-error`} className='text-sm text-destructive'>
117
+ {resourceError}
118
+ </p>
119
+ )}
120
+ </div>
121
+ </div>
122
+ <div className='overflow-hidden rounded-lg border'>
123
+ {scopes.length ? (
124
+ scopes.map((scope) => (
125
+ <div
126
+ key={scope.id}
127
+ className='grid min-w-0 gap-3 border-b p-4 last:border-b-0 sm:grid-cols-3'
128
+ >
129
+ <div className='min-w-0 break-words text-sm font-medium'>
130
+ {scope.action}
131
+ </div>
132
+ <div className='flex min-w-0 flex-col gap-2 sm:col-span-2'>
133
+ <Label htmlFor={`${id}-${scope.id}`}>
134
+ {scope.scope || labels.scope}
135
+ </Label>
136
+ <Select
137
+ value={scope.value}
138
+ items={scope.options}
139
+ disabled={readOnly}
140
+ onValueChange={(value) => {
141
+ if (value !== null) onScopeChange(scope.id, value);
142
+ }}
143
+ >
144
+ <SelectTrigger
145
+ id={`${id}-${scope.id}`}
146
+ className='w-full'
147
+ aria-label={`${scope.action} · ${scope.scope || labels.scope}`}
148
+ aria-invalid={Boolean(scope.error)}
149
+ aria-describedby={
150
+ scope.error ? `${id}-${scope.id}-error` : undefined
151
+ }
152
+ >
153
+ <SelectValue />
154
+ </SelectTrigger>
155
+ <SelectContent className='w-auto max-w-[min(var(--container-sm),var(--available-width))] min-w-(--anchor-width) [&_[data-slot=select-item]>:first-child]:whitespace-normal'>
156
+ <SelectGroup>
157
+ {scope.options.map((option) => (
158
+ <SelectItem key={option.value} value={option.value}>
159
+ {option.label}
160
+ </SelectItem>
161
+ ))}
162
+ </SelectGroup>
163
+ </SelectContent>
164
+ </Select>
165
+ {scope.error && (
166
+ <p
167
+ id={`${id}-${scope.id}-error`}
168
+ className='text-sm text-destructive'
169
+ >
170
+ {scope.error}
171
+ </p>
172
+ )}
173
+ {scope.configuration}
174
+ </div>
175
+ </div>
176
+ ))
177
+ ) : (
178
+ <p className='p-4 text-sm text-muted-foreground'>{labels.noScopes}</p>
179
+ )}
180
+ </div>
181
+ </div>
182
+ );
183
+ }